mirror of
https://github.com/Atmosphere-NX/Atmosphere-libs.git
synced 2025-06-21 11:02:45 +02:00
libstrat: implement functionality for loader rewrite
This commit is contained in:
parent
cf5c6cdad9
commit
e37089d167
2
Makefile
2
Makefile
@ -16,7 +16,7 @@ include $(DEVKITPRO)/libnx/switch_rules
|
||||
# INCLUDES is a list of directories containing header files
|
||||
#---------------------------------------------------------------------------------
|
||||
TARGET := $(notdir $(CURDIR))
|
||||
SOURCES := source source/spl source/spl/smc source/updater source/patcher source/map source/rnd source/util source/sm
|
||||
SOURCES := source source/spl source/spl/smc source/updater source/patcher source/map source/rnd source/util source/sm source/cfg source/pm source/hid source/ldr
|
||||
DATA := data
|
||||
INCLUDES := include
|
||||
|
||||
|
@ -45,5 +45,8 @@
|
||||
|
||||
#include "stratosphere/on_crash.hpp"
|
||||
|
||||
#include "stratosphere/cfg.hpp"
|
||||
#include "stratosphere/hid.hpp"
|
||||
#include "stratosphere/pm.hpp"
|
||||
#include "stratosphere/rnd.hpp"
|
||||
#include "stratosphere/util.hpp"
|
20
include/stratosphere/cfg.hpp
Normal file
20
include/stratosphere/cfg.hpp
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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 "cfg/cfg_api.hpp"
|
46
include/stratosphere/cfg/cfg_api.hpp
Normal file
46
include/stratosphere/cfg/cfg_api.hpp
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 "../ncm/ncm_types.hpp"
|
||||
|
||||
namespace sts::cfg {
|
||||
|
||||
/* Is the current proces privileged? */
|
||||
bool IsInitialProcess();
|
||||
|
||||
/* SD card configuration. */
|
||||
bool IsSdCardInitialized();
|
||||
void WaitSdCardInitialized();
|
||||
|
||||
/* Override key utilities. */
|
||||
bool IsTitleOverrideKeyHeld(ncm::TitleId title_id);
|
||||
bool IsHblOverrideKeyHeld(ncm::TitleId title_id);
|
||||
void GetOverrideKeyHeldStatus(bool *out_hbl, bool *out_title, ncm::TitleId title_id);
|
||||
bool IsCheatEnableKeyHeld(ncm::TitleId title_id);
|
||||
|
||||
/* Flag utilities. */
|
||||
bool HasFlag(ncm::TitleId title_id, const char *flag);
|
||||
bool HasTitleSpecificFlag(ncm::TitleId title_id, const char *flag);
|
||||
bool HasGlobalFlag(const char *flag);
|
||||
|
||||
/* HBL Configuration utilities. */
|
||||
bool IsHblTitleId(ncm::TitleId title_id);
|
||||
bool HasHblFlag(const char *flag);
|
||||
const char *GetHblPath();
|
||||
|
||||
}
|
20
include/stratosphere/hid.hpp
Normal file
20
include/stratosphere/hid.hpp
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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 "hid/hid_api.hpp"
|
24
include/stratosphere/hid/hid_api.hpp
Normal file
24
include/stratosphere/hid/hid_api.hpp
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
namespace sts::hid {
|
||||
|
||||
/* Key API. */
|
||||
Result GetKeysHeld(u64 *out);
|
||||
|
||||
}
|
@ -52,6 +52,10 @@ struct MovedHandle : public IpcHandle {
|
||||
MovedHandle(Handle h) {
|
||||
this->handle = h;
|
||||
}
|
||||
|
||||
Handle GetValue() const {
|
||||
return this->handle;
|
||||
}
|
||||
};
|
||||
|
||||
/* Represents a copied handle. */
|
||||
@ -67,6 +71,10 @@ struct CopiedHandle : public IpcHandle {
|
||||
CopiedHandle(Handle h) {
|
||||
this->handle = h;
|
||||
}
|
||||
|
||||
Handle GetValue() const {
|
||||
return this->handle;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
|
20
include/stratosphere/ldr.hpp
Normal file
20
include/stratosphere/ldr.hpp
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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 "ldr/ldr_types.hpp"
|
30
include/stratosphere/ldr/ldr_pm_api.hpp
Normal file
30
include/stratosphere/ldr/ldr_pm_api.hpp
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 "ldr_types.hpp"
|
||||
|
||||
namespace sts::ldr::pm {
|
||||
|
||||
/* Process Manager API. */
|
||||
Result CreateProcess(Handle *out, PinId pin_id, u32 flags, Handle reslimit);
|
||||
Result GetProgramInfo(ProgramInfo *out, const ncm::TitleLocation &loc);
|
||||
Result PinTitle(PinId *out, const ncm::TitleLocation &loc);
|
||||
Result UnpinTitle(PinId pin_id);
|
||||
Result HasLaunchedTitle(bool *out, ncm::TitleId title_id);
|
||||
|
||||
}
|
243
include/stratosphere/ldr/ldr_types.hpp
Normal file
243
include/stratosphere/ldr/ldr_types.hpp
Normal file
@ -0,0 +1,243 @@
|
||||
/*
|
||||
* 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 <type_traits>
|
||||
#include <switch.h>
|
||||
#include "../ncm/ncm_types.hpp"
|
||||
|
||||
namespace sts::ldr {
|
||||
|
||||
/* General types. */
|
||||
struct ProgramInfo {
|
||||
u8 main_thread_priority;
|
||||
u8 default_cpu_id;
|
||||
u16 flags;
|
||||
u32 main_thread_stack_size;
|
||||
ncm::TitleId title_id;
|
||||
u32 acid_sac_size;
|
||||
u32 aci_sac_size;
|
||||
u32 acid_fac_size;
|
||||
u32 aci_fah_size;
|
||||
u8 ac_buffer[0x3E0];
|
||||
};
|
||||
static_assert(sizeof(ProgramInfo) == 0x400, "ProgramInfo definition!");
|
||||
|
||||
enum ProgramInfoFlag {
|
||||
ProgramInfoFlag_SystemModule = (0 << 0),
|
||||
ProgramInfoFlag_Application = (1 << 0),
|
||||
ProgramInfoFlag_Applet = (2 << 0),
|
||||
ProgramInfoFlag_InvalidType = (3 << 0),
|
||||
ProgramInfoFlag_ApplicationTypeMask = (3 << 0),
|
||||
|
||||
ProgramInfoFlag_AllowDebug = (1 << 2),
|
||||
};
|
||||
|
||||
enum CreateProcessFlag {
|
||||
CreateProcessFlag_EnableDebug = (1 << 0),
|
||||
CreateProcessFlag_DisableAslr = (1 << 1),
|
||||
};
|
||||
|
||||
struct ProgramArguments {
|
||||
u32 allocated_size;
|
||||
u32 arguments_size;
|
||||
u8 reserved[0x18];
|
||||
u8 arguments[];
|
||||
};
|
||||
static_assert(sizeof(ProgramArguments) == 0x20, "ProgramArguments definition!");
|
||||
|
||||
struct PinId {
|
||||
u64 value;
|
||||
};
|
||||
|
||||
inline bool operator==(const PinId &lhs, const PinId &rhs) {
|
||||
return lhs.value == rhs.value;
|
||||
}
|
||||
|
||||
inline bool operator!=(const PinId &lhs, const PinId &rhs) {
|
||||
return lhs.value != rhs.value;
|
||||
}
|
||||
static_assert(sizeof(PinId) == sizeof(u64) && std::is_pod<PinId>::value, "PinId definition!");
|
||||
|
||||
/* Import ModuleInfo from libnx. */
|
||||
using ModuleInfo = ::LoaderModuleInfo;
|
||||
|
||||
/* NSO types. */
|
||||
struct NsoHeader {
|
||||
static constexpr u32 Magic = 0x304F534E;
|
||||
enum Segment : size_t {
|
||||
Segment_Text = 0,
|
||||
Segment_Ro = 1,
|
||||
Segment_Rw = 2,
|
||||
Segment_Count,
|
||||
};
|
||||
|
||||
enum Flag : u32 {
|
||||
Flag_CompressedText = (1 << 0),
|
||||
Flag_CompressedRo = (1 << 1),
|
||||
Flag_CompressedRw = (1 << 2),
|
||||
Flag_CheckHashText = (1 << 3),
|
||||
Flag_CheckHashRo = (1 << 4),
|
||||
Flag_CheckHashRw = (1 << 5),
|
||||
};
|
||||
|
||||
struct SegmentInfo {
|
||||
u32 file_offset;
|
||||
u32 dst_offset;
|
||||
u32 size;
|
||||
u32 reserved;
|
||||
};
|
||||
|
||||
u32 magic;
|
||||
u32 version;
|
||||
u32 reserved_08;
|
||||
u32 flags;
|
||||
union {
|
||||
struct {
|
||||
u32 text_file_offset;
|
||||
u32 text_dst_offset;
|
||||
u32 text_size;
|
||||
u32 unk_file_offset;
|
||||
u32 ro_file_offset;
|
||||
u32 ro_dst_offset;
|
||||
u32 ro_size;
|
||||
u32 unk_size;
|
||||
u32 rw_file_offset;
|
||||
u32 rw_dst_offset;
|
||||
u32 rw_size;
|
||||
u32 bss_size;
|
||||
};
|
||||
SegmentInfo segments[Segment_Count];
|
||||
};
|
||||
u8 build_id[sizeof(ModuleInfo::build_id)];
|
||||
union {
|
||||
u32 compressed_sizes[Segment_Count];
|
||||
struct {
|
||||
u32 text_compressed_size;
|
||||
u32 ro_compressed_size;
|
||||
u32 rw_compressed_size;
|
||||
};
|
||||
};
|
||||
u8 reserved_6C[0x34];
|
||||
union {
|
||||
u8 segment_hashes[Segment_Count][SHA256_HASH_SIZE];
|
||||
struct {
|
||||
u8 text_hash[SHA256_HASH_SIZE];
|
||||
u8 ro_hash[SHA256_HASH_SIZE];
|
||||
u8 rw_hash[SHA256_HASH_SIZE];
|
||||
};
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(NsoHeader) == 0x100 && std::is_pod<NsoHeader>::value, "NsoHeader definition!");
|
||||
|
||||
/* NPDM types. */
|
||||
struct Aci {
|
||||
static constexpr u32 Magic = 0x30494341;
|
||||
|
||||
u32 magic;
|
||||
u8 reserved_04[0xC];
|
||||
ncm::TitleId title_id;
|
||||
u8 reserved_18[0x8];
|
||||
u32 fah_offset;
|
||||
u32 fah_size;
|
||||
u32 sac_offset;
|
||||
u32 sac_size;
|
||||
u32 kac_offset;
|
||||
u32 kac_size;
|
||||
u8 reserved_38[0x8];
|
||||
};
|
||||
static_assert(sizeof(Aci) == 0x40 && std::is_pod<Aci>::value, "Aci definition!");
|
||||
|
||||
struct Acid {
|
||||
static constexpr u32 Magic = 0x44494341;
|
||||
|
||||
enum AcidFlag {
|
||||
AcidFlag_Production = (1 << 0),
|
||||
AcidFlag_UnqualifiedApproval = (1 << 1),
|
||||
|
||||
AcidFlag_DeprecatedUseSecureMemory = (1 << 2),
|
||||
|
||||
AcidFlag_PoolPartitionShift = 2,
|
||||
AcidFlag_PoolPartitionMask = (3 << AcidFlag_PoolPartitionShift),
|
||||
};
|
||||
|
||||
enum PoolPartition {
|
||||
PoolPartition_Application = 0,
|
||||
PoolPartition_Applet = 1,
|
||||
PoolPartition_System = 2,
|
||||
PoolPartition_SystemNonSecure = 3,
|
||||
};
|
||||
|
||||
u8 signature[0x100];
|
||||
u8 modulus[0x100];
|
||||
u32 magic;
|
||||
u32 size;
|
||||
u8 version;
|
||||
u8 reserved_209[3];
|
||||
u32 flags;
|
||||
ncm::TitleId title_id_min;
|
||||
ncm::TitleId title_id_max;
|
||||
u32 fac_offset;
|
||||
u32 fac_size;
|
||||
u32 sac_offset;
|
||||
u32 sac_size;
|
||||
u32 kac_offset;
|
||||
u32 kac_size;
|
||||
u8 reserved_238[0x8];
|
||||
};
|
||||
static_assert(sizeof(Acid) == 0x240 && std::is_pod<Acid>::value, "Acid definition!");
|
||||
|
||||
struct Npdm {
|
||||
static constexpr u32 Magic = 0x4154454D;
|
||||
|
||||
enum MetaFlag {
|
||||
MetaFlag_Is64Bit = (1 << 0),
|
||||
|
||||
MetaFlag_AddressSpaceTypeShift = 1,
|
||||
MetaFlag_AddressSpaceTypeMask = (7 << MetaFlag_AddressSpaceTypeShift),
|
||||
|
||||
MetaFlag_OptimizeMemoryAllocation = (1 << 4),
|
||||
};
|
||||
|
||||
enum AddressSpaceType {
|
||||
AddressSpaceType_32Bit = 0,
|
||||
AddressSpaceType_64BitDeprecated = 1,
|
||||
AddressSpaceType_32BitWithoutAlias = 2,
|
||||
AddressSpaceType_64Bit = 3,
|
||||
};
|
||||
|
||||
u32 magic;
|
||||
u8 reserved_04[8];
|
||||
u8 flags;
|
||||
u8 reserved_0D;
|
||||
u8 main_thread_priority;
|
||||
u8 default_cpu_id;
|
||||
u8 reserved_10[4];
|
||||
u32 system_resource_size;
|
||||
u32 version;
|
||||
u32 main_thread_stack_size;
|
||||
char title_name[0x10];
|
||||
char product_code[0x10];
|
||||
u8 reserved_40[0x30];
|
||||
u32 aci_offset;
|
||||
u32 aci_size;
|
||||
u32 acid_offset;
|
||||
u32 acid_size;
|
||||
};
|
||||
static_assert(sizeof(Npdm) == 0x80 && std::is_pod<Npdm>::value, "Npdm definition!");
|
||||
|
||||
}
|
@ -33,6 +33,13 @@ namespace sts::map {
|
||||
uintptr_t aslr_end;
|
||||
};
|
||||
|
||||
static constexpr uintptr_t AslrBase32Bit = 0x0000200000ul;
|
||||
static constexpr size_t AslrSize32Bit = 0x003FE00000ul;
|
||||
static constexpr uintptr_t AslrBase64BitDeprecated = 0x0008000000ul;
|
||||
static constexpr size_t AslrSize64BitDeprecated = 0x0078000000ul;
|
||||
static constexpr uintptr_t AslrBase64Bit = 0x0008000000ul;
|
||||
static constexpr size_t AslrSize64Bit = 0x7FF8000000ul;
|
||||
|
||||
class AutoCloseMap {
|
||||
private:
|
||||
Handle process_handle;
|
||||
|
20
include/stratosphere/ncm.hpp
Normal file
20
include/stratosphere/ncm.hpp
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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 "ncm/ncm_types.hpp"
|
82
include/stratosphere/ncm/ncm_types.hpp
Normal file
82
include/stratosphere/ncm/ncm_types.hpp
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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 <type_traits>
|
||||
|
||||
namespace sts::ncm {
|
||||
|
||||
/* Storage IDs. */
|
||||
enum class StorageId : u8 {
|
||||
None = 0,
|
||||
Host = 1,
|
||||
GameCard = 2,
|
||||
NandSystem = 3,
|
||||
NandUser = 4,
|
||||
SdCard = 5,
|
||||
};
|
||||
|
||||
/* Title IDs. */
|
||||
struct TitleId {
|
||||
u64 value;
|
||||
|
||||
inline explicit operator u64() const {
|
||||
return this->value;
|
||||
}
|
||||
};
|
||||
static constexpr TitleId InvalidTitleId = {};
|
||||
|
||||
inline bool operator==(const TitleId &lhs, const TitleId &rhs) {
|
||||
return lhs.value == rhs.value;
|
||||
}
|
||||
|
||||
inline bool operator!=(const TitleId &lhs, const TitleId &rhs) {
|
||||
return lhs.value != rhs.value;
|
||||
}
|
||||
|
||||
inline bool operator<(const TitleId &lhs, const TitleId &rhs) {
|
||||
return lhs.value < rhs.value;
|
||||
}
|
||||
|
||||
inline bool operator<=(const TitleId &lhs, const TitleId &rhs) {
|
||||
return lhs.value <= rhs.value;
|
||||
}
|
||||
|
||||
inline bool operator>(const TitleId &lhs, const TitleId &rhs) {
|
||||
return lhs.value > rhs.value;
|
||||
}
|
||||
|
||||
inline bool operator>=(const TitleId &lhs, const TitleId &rhs) {
|
||||
return lhs.value >= rhs.value;
|
||||
}
|
||||
|
||||
static_assert(sizeof(TitleId) == sizeof(u64) && std::is_pod<TitleId>::value, "TitleId definition!");
|
||||
|
||||
/* Title Location. */
|
||||
struct TitleLocation {
|
||||
TitleId title_id;
|
||||
u8 storage_id;
|
||||
};
|
||||
|
||||
constexpr TitleLocation MakeTitleLocation(TitleId title_id, StorageId storage_id) {
|
||||
TitleLocation loc = { .title_id = title_id, .storage_id = static_cast<u8>(storage_id) };
|
||||
return loc;
|
||||
}
|
||||
|
||||
static_assert(sizeof(TitleLocation) == 0x10 && std::is_pod<TitleLocation>::value, "TitleLocation definition!");
|
||||
|
||||
}
|
20
include/stratosphere/pm.hpp
Normal file
20
include/stratosphere/pm.hpp
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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 "pm/pm_info_api.hpp"
|
29
include/stratosphere/pm/pm_info_api.hpp
Normal file
29
include/stratosphere/pm/pm_info_api.hpp
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
namespace sts::pm::info {
|
||||
|
||||
/* Information API. */
|
||||
Result GetTitleId(u64 *out_title_id, u64 process_id);
|
||||
Result GetProcessId(u64 *out_process_id, u64 title_id);
|
||||
Result HasLaunchedTitle(bool *out, u64 title_id);
|
||||
|
||||
/* Information convenience API. */
|
||||
bool HasLaunchedTitle(u64 title_id);
|
||||
|
||||
}
|
@ -26,7 +26,7 @@ static constexpr Result ResultLoaderInvalidMeta = MAKERESULT(Module_Lo
|
||||
static constexpr Result ResultLoaderInvalidNso = MAKERESULT(Module_Loader, 5);
|
||||
static constexpr Result ResultLoaderInvalidPath = MAKERESULT(Module_Loader, 6);
|
||||
static constexpr Result ResultLoaderTooManyProcesses = MAKERESULT(Module_Loader, 7);
|
||||
static constexpr Result ResultLoaderProcessNotRegistered = MAKERESULT(Module_Loader, 8);
|
||||
static constexpr Result ResultLoaderNotPinned = MAKERESULT(Module_Loader, 8);
|
||||
static constexpr Result ResultLoaderInvalidProgramId = MAKERESULT(Module_Loader, 9);
|
||||
static constexpr Result ResultLoaderInvalidVersion = MAKERESULT(Module_Loader, 10);
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <switch/result.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <cstdlib>
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
@ -187,13 +187,13 @@ static inline bool TitleIdIsSystem(const u64 title_id) {
|
||||
}
|
||||
|
||||
static inline bool TitleIdIsArchive(const u64 title_id) {
|
||||
return TitleId_ArchiveStart <= title_id && title_id <= TitleId_ArchiveEnd;
|
||||
return TitleId_ArchiveStart <= title_id && title_id <= TitleId_ArchiveEnd;
|
||||
}
|
||||
|
||||
static inline bool TitleIdIsApplet(const u64 title_id) {
|
||||
return TitleId_AppletStart <= title_id && title_id <= TitleId_AppletEnd;
|
||||
return TitleId_AppletStart <= title_id && title_id <= TitleId_AppletEnd;
|
||||
}
|
||||
|
||||
static inline bool TitleIdIsApplication(const u64 title_id) {
|
||||
return TitleId_ApplicationStart <= title_id && title_id <= TitleId_ApplicationEnd;
|
||||
return TitleId_ApplicationStart <= title_id && title_id <= TitleId_ApplicationEnd;
|
||||
}
|
74
source/cfg/cfg_flags.cpp
Normal file
74
source/cfg/cfg_flags.cpp
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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/cfg.hpp>
|
||||
|
||||
namespace sts::cfg {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Helper. */
|
||||
bool HasFlagFile(const char *flag_path) {
|
||||
/* All flags are not present until the SD card is. */
|
||||
if (!IsSdCardInitialized()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Mount the SD card. */
|
||||
FsFileSystem sd_fs = {};
|
||||
if (R_FAILED(fsMountSdcard(&sd_fs))) {
|
||||
return false;
|
||||
}
|
||||
ON_SCOPE_EXIT { serviceClose(&sd_fs.s); };
|
||||
|
||||
/* Open the file. */
|
||||
FsFile flag_file;
|
||||
if (R_FAILED(fsFsOpenFile(&sd_fs, flag_path, FS_OPEN_READ, &flag_file))) {
|
||||
return false;
|
||||
}
|
||||
fsFileClose(&flag_file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Flag utilities. */
|
||||
bool HasFlag(ncm::TitleId title_id, const char *flag) {
|
||||
return HasTitleSpecificFlag(title_id, flag) || (IsHblTitleId(title_id) && HasHblFlag(flag));
|
||||
}
|
||||
|
||||
bool HasTitleSpecificFlag(ncm::TitleId title_id, const char *flag) {
|
||||
char title_flag[FS_MAX_PATH];
|
||||
std::snprintf(title_flag, sizeof(title_flag) - 1, "/atmosphere/titles/%016lx/flags/%s.flag", static_cast<u64>(title_id), flag);
|
||||
return HasFlagFile(title_flag);
|
||||
}
|
||||
|
||||
bool HasGlobalFlag(const char *flag) {
|
||||
char title_flag[FS_MAX_PATH];
|
||||
std::snprintf(title_flag, sizeof(title_flag) - 1, "/atmosphere/flags/%s.flag", flag);
|
||||
return HasFlagFile(title_flag);
|
||||
}
|
||||
|
||||
bool HasHblFlag(const char *flag) {
|
||||
char hbl_flag[0x100];
|
||||
std::snprintf(hbl_flag, sizeof(hbl_flag) - 1, "hbl_%s", flag);
|
||||
return HasGlobalFlag(hbl_flag);
|
||||
}
|
||||
|
||||
}
|
303
source/cfg/cfg_override.cpp
Normal file
303
source/cfg/cfg_override.cpp
Normal file
@ -0,0 +1,303 @@
|
||||
/*
|
||||
* 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 <cstring>
|
||||
#include <strings.h>
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include <stratosphere/cfg.hpp>
|
||||
#include <stratosphere/pm.hpp>
|
||||
#include <stratosphere/util.hpp>
|
||||
|
||||
namespace sts::cfg {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Types. */
|
||||
struct OverrideKey {
|
||||
u64 key_combination;
|
||||
bool override_by_default;
|
||||
};
|
||||
|
||||
struct HblOverrideConfig {
|
||||
OverrideKey override_key;
|
||||
ncm::TitleId title_id;
|
||||
bool override_any_app;
|
||||
};
|
||||
|
||||
struct TitleSpecificOverrideConfig {
|
||||
OverrideKey override_key;
|
||||
OverrideKey cheat_enable_key;
|
||||
};
|
||||
|
||||
/* Override globals. */
|
||||
OverrideKey g_default_override_key = {
|
||||
.key_combination = KEY_L,
|
||||
.override_by_default = true,
|
||||
};
|
||||
|
||||
OverrideKey g_default_cheat_enable_key = {
|
||||
.key_combination = KEY_L,
|
||||
.override_by_default = true,
|
||||
};
|
||||
|
||||
HblOverrideConfig g_hbl_override_config = {
|
||||
.override_key = {
|
||||
.key_combination = KEY_R,
|
||||
.override_by_default = false,
|
||||
},
|
||||
.title_id = {TitleId_AppletPhotoViewer},
|
||||
.override_any_app = true,
|
||||
};
|
||||
|
||||
char g_hbl_sd_path[0x100] = "/atmosphere/hbl.nsp";
|
||||
|
||||
/* Helpers. */
|
||||
OverrideKey ParseOverrideKey(const char *value) {
|
||||
OverrideKey cfg = {};
|
||||
|
||||
/* Parse on by default. */
|
||||
if (value[0] == '!') {
|
||||
cfg.override_by_default = true;
|
||||
value++;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
int LoaderIniHandler(void *user, const char *section, const char *name, const char *value) {
|
||||
/* Taken and modified, with love, from Rajkosto's implementation. */
|
||||
if (strcasecmp(section, "hbl_config") == 0) {
|
||||
if (strcasecmp(name, "title_id") == 0) {
|
||||
u64 override_tid = strtoul(value, NULL, 16);
|
||||
if (override_tid != 0) {
|
||||
g_hbl_override_config.title_id = {override_tid};
|
||||
}
|
||||
} else if (strcasecmp(name, "path") == 0) {
|
||||
while (*value == '/' || *value == '\\') {
|
||||
value++;
|
||||
}
|
||||
std::snprintf(g_hbl_sd_path, sizeof(g_hbl_sd_path) - 1, "/%s", value);
|
||||
g_hbl_sd_path[sizeof(g_hbl_sd_path) - 1] = '\0';
|
||||
} else if (strcasecmp(name, "override_key") == 0) {
|
||||
g_hbl_override_config.override_key = ParseOverrideKey(value);
|
||||
} else if (strcasecmp(name, "override_any_app") == 0) {
|
||||
if (strcasecmp(value, "true") == 0 || strcasecmp(value, "1") == 0) {
|
||||
g_hbl_override_config.override_any_app = true;
|
||||
} else if (strcasecmp(value, "false") == 0 || strcasecmp(value, "0") == 0) {
|
||||
g_hbl_override_config.override_any_app = false;
|
||||
} else {
|
||||
/* I guess we default to not changing the value? */
|
||||
}
|
||||
}
|
||||
} else if (strcasecmp(section, "default_config") == 0) {
|
||||
if (strcasecmp(name, "override_key") == 0) {
|
||||
g_default_override_key = ParseOverrideKey(value);
|
||||
} else if (strcasecmp(name, "cheat_enable_key") == 0) {
|
||||
g_default_cheat_enable_key = ParseOverrideKey(value);
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int TitleSpecificIniHandler(void *user, const char *section, const char *name, const char *value) {
|
||||
TitleSpecificOverrideConfig *config = reinterpret_cast<TitleSpecificOverrideConfig *>(user);
|
||||
|
||||
if (strcasecmp(section, "override_config") == 0) {
|
||||
if (strcasecmp(name, "override_key") == 0) {
|
||||
config->override_key = ParseOverrideKey(value);
|
||||
} else if (strcasecmp(name, "cheat_enable_key") == 0) {
|
||||
config->cheat_enable_key = ParseOverrideKey(value);
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool IsOverrideKeyHeld(OverrideKey *cfg) {
|
||||
u64 kHeld = 0;
|
||||
bool keys_triggered = (R_SUCCEEDED(hid::GetKeysHeld(&kHeld)) && ((kHeld & cfg->key_combination) != 0));
|
||||
return IsSdCardInitialized() && (cfg->override_by_default ^ keys_triggered);
|
||||
}
|
||||
|
||||
void ParseIniFile(util::ini::Handler handler, const char *path, void *user_ctx) {
|
||||
/* Mount the SD card. */
|
||||
FsFileSystem sd_fs = {};
|
||||
if (R_FAILED(fsMountSdcard(&sd_fs))) {
|
||||
return;
|
||||
}
|
||||
ON_SCOPE_EXIT { serviceClose(&sd_fs.s); };
|
||||
|
||||
/* Open the file. */
|
||||
FsFile config_file;
|
||||
if (R_FAILED(fsFsOpenFile(&sd_fs, path, FS_OPEN_READ, &config_file))) {
|
||||
return;
|
||||
}
|
||||
ON_SCOPE_EXIT { fsFileClose(&config_file); };
|
||||
|
||||
/* Parse the config. */
|
||||
util::ini::ParseFile(&config_file, user_ctx, handler);
|
||||
}
|
||||
|
||||
void RefreshLoaderConfiguration() {
|
||||
ParseIniFile(LoaderIniHandler, "/atmosphere/loader.ini", nullptr);
|
||||
}
|
||||
|
||||
TitleSpecificOverrideConfig GetTitleOverrideConfig(ncm::TitleId title_id) {
|
||||
char path[FS_MAX_PATH];
|
||||
std::snprintf(path, sizeof(path) - 1, "/atmosphere/titles/%016lx/config.ini", static_cast<u64>(title_id));
|
||||
|
||||
TitleSpecificOverrideConfig config = {
|
||||
.override_key = g_default_override_key,
|
||||
.cheat_enable_key = g_default_cheat_enable_key,
|
||||
};
|
||||
ParseIniFile(TitleSpecificIniHandler, path, &config);
|
||||
return config;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool IsHblOverrideKeyHeld(ncm::TitleId title_id) {
|
||||
/* If the SD card isn't initialized, we can't override. */
|
||||
if (!IsSdCardInitialized()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* For system modules and anything launched before the home menu, always override. */
|
||||
if (static_cast<u64>(title_id) < TitleId_AppletStart || !pm::info::HasLaunchedTitle(TitleId_AppletQlaunch)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Unconditionally refresh loader.ini contents. */
|
||||
RefreshLoaderConfiguration();
|
||||
|
||||
/* Check HBL config. */
|
||||
return IsHblTitleId(title_id) && IsOverrideKeyHeld(&g_hbl_override_config.override_key);
|
||||
}
|
||||
|
||||
bool IsTitleOverrideKeyHeld(ncm::TitleId title_id) {
|
||||
/* If the SD card isn't initialized, we can't override. */
|
||||
if (!IsSdCardInitialized()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* For system modules and anything launched before the home menu, always override. */
|
||||
if (static_cast<u64>(title_id) < TitleId_AppletStart || !pm::info::HasLaunchedTitle(TitleId_AppletQlaunch)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Unconditionally refresh loader.ini contents. */
|
||||
RefreshLoaderConfiguration();
|
||||
|
||||
TitleSpecificOverrideConfig title_cfg = GetTitleOverrideConfig(title_id);
|
||||
return IsOverrideKeyHeld(&title_cfg.override_key);
|
||||
}
|
||||
|
||||
void GetOverrideKeyHeldStatus(bool *out_hbl, bool *out_title, ncm::TitleId title_id) {
|
||||
|
||||
/* If the SD card isn't initialized, we can't override. */
|
||||
if (!IsSdCardInitialized()) {
|
||||
*out_hbl = false;
|
||||
*out_title = false;
|
||||
return;
|
||||
}
|
||||
|
||||
/* For system modules and anything launched before the home menu, always override. */
|
||||
if (static_cast<u64>(title_id) < TitleId_AppletStart || !pm::info::HasLaunchedTitle(TitleId_AppletQlaunch)) {
|
||||
*out_hbl = false;
|
||||
*out_title = true;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Unconditionally refresh loader.ini contents. */
|
||||
RefreshLoaderConfiguration();
|
||||
|
||||
/* Set HBL output. */
|
||||
*out_hbl = IsHblTitleId(title_id) && IsOverrideKeyHeld(&g_hbl_override_config.override_key);
|
||||
|
||||
/* Set title specific output. */
|
||||
TitleSpecificOverrideConfig title_cfg = GetTitleOverrideConfig(title_id);
|
||||
*out_title = IsOverrideKeyHeld(&title_cfg.override_key);
|
||||
}
|
||||
|
||||
bool IsCheatEnableKeyHeld(ncm::TitleId title_id) {
|
||||
/* If the SD card isn't initialized, don't apply cheats. */
|
||||
if (!IsSdCardInitialized()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Don't apply cheats to HBL. */
|
||||
if (IsHblOverrideKeyHeld(title_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TitleSpecificOverrideConfig title_cfg = GetTitleOverrideConfig(title_id);
|
||||
return IsOverrideKeyHeld(&title_cfg.cheat_enable_key);
|
||||
}
|
||||
|
||||
/* HBL Configuration utilities. */
|
||||
bool IsHblTitleId(ncm::TitleId title_id) {
|
||||
return (g_hbl_override_config.override_any_app && TitleIdIsApplication(static_cast<u64>(title_id))) || (title_id == g_hbl_override_config.title_id);
|
||||
}
|
||||
|
||||
const char *GetHblPath() {
|
||||
return g_hbl_sd_path;
|
||||
}
|
||||
|
||||
|
||||
}
|
85
source/cfg/cfg_privileged_process.cpp
Normal file
85
source/cfg/cfg_privileged_process.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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/cfg.hpp>
|
||||
|
||||
namespace sts::cfg {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Convenience definitions. */
|
||||
constexpr u64 InitialProcessIdMinDeprecated = 0x00;
|
||||
constexpr u64 InitialProcessIdMaxDeprecated = 0x50;
|
||||
|
||||
/* Privileged process globals. */
|
||||
HosMutex g_lock;
|
||||
bool g_detected_privileged_process = false;
|
||||
bool g_is_privileged_process = false;
|
||||
|
||||
/* SD card helpers. */
|
||||
void GetPrivilegedProcessIdRange(u64 *out_min, u64 *out_max) {
|
||||
u64 min = 0, max = 0;
|
||||
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_500) {
|
||||
/* On 5.0.0+, we can get precise limits from svcGetSystemInfo. */
|
||||
R_ASSERT(svcGetSystemInfo(&min, SystemInfoType_InitialProcessIdRange, INVALID_HANDLE, InitialProcessIdRangeInfo_Minimum));
|
||||
R_ASSERT(svcGetSystemInfo(&max, SystemInfoType_InitialProcessIdRange, INVALID_HANDLE, InitialProcessIdRangeInfo_Maximum));
|
||||
} else if (GetRuntimeFirmwareVersion() >= FirmwareVersion_400) {
|
||||
/* On 4.0.0-4.1.0, we can get the precise limits from normal svcGetInfo. */
|
||||
R_ASSERT(svcGetInfo(&min, InfoType_InitialProcessIdRange, INVALID_HANDLE, InitialProcessIdRangeInfo_Minimum));
|
||||
R_ASSERT(svcGetInfo(&max, InfoType_InitialProcessIdRange, INVALID_HANDLE, InitialProcessIdRangeInfo_Maximum));
|
||||
} else {
|
||||
/* On < 4.0.0, we just use hardcoded extents. */
|
||||
min = InitialProcessIdMinDeprecated;
|
||||
max = InitialProcessIdMaxDeprecated;
|
||||
}
|
||||
|
||||
*out_min = min;
|
||||
*out_max = max;
|
||||
}
|
||||
|
||||
u64 GetCurrentProcessId() {
|
||||
u64 process_id = 0;
|
||||
R_ASSERT(svcGetProcessId(&process_id, CUR_PROCESS_HANDLE));
|
||||
return process_id;
|
||||
}
|
||||
|
||||
void DetectIsPrivilegedProcess() {
|
||||
u64 min = 0, max = 0, cur = 0;
|
||||
GetPrivilegedProcessIdRange(&min, &max);
|
||||
cur = GetCurrentProcessId();
|
||||
g_is_privileged_process = min <= cur && cur <= max;
|
||||
g_detected_privileged_process = true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/* SD card utilities. */
|
||||
bool IsPrivilegedProcess() {
|
||||
std::scoped_lock<HosMutex> lk(g_lock);
|
||||
|
||||
/* If we've already detected, return cached result. */
|
||||
if (!g_detected_privileged_process) {
|
||||
DetectIsPrivilegedProcess();
|
||||
}
|
||||
|
||||
/* Determine if we're privileged, and return. */
|
||||
return g_is_privileged_process;
|
||||
}
|
||||
|
||||
}
|
84
source/cfg/cfg_sd_card.cpp
Normal file
84
source/cfg/cfg_sd_card.cpp
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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/cfg.hpp>
|
||||
#include <stratosphere/sm.hpp>
|
||||
|
||||
namespace sts::cfg {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Convenience definitions. */
|
||||
constexpr sm::ServiceName RequiredServicesForSdCardAccess[] = {
|
||||
sm::ServiceName::Encode("pcv"),
|
||||
sm::ServiceName::Encode("gpio"),
|
||||
sm::ServiceName::Encode("pinmux"),
|
||||
sm::ServiceName::Encode("psc:c")
|
||||
};
|
||||
constexpr size_t NumRequiredServicesForSdCardAccess = sizeof(RequiredServicesForSdCardAccess) / sizeof(RequiredServicesForSdCardAccess[0]);
|
||||
|
||||
/* SD card globals. */
|
||||
HosMutex g_sd_card_lock;
|
||||
bool g_sd_card_initialized = false;
|
||||
FsFileSystem g_sd_card_filesystem = {};
|
||||
|
||||
/* SD card helpers. */
|
||||
Result TryInitializeSdCard() {
|
||||
for (size_t i = 0; i < NumRequiredServicesForSdCardAccess; i++) {
|
||||
bool service_present = false;
|
||||
R_TRY(sm::HasService(&service_present, RequiredServicesForSdCardAccess[i]));
|
||||
if (!service_present) {
|
||||
return ResultFsSdCardNotPresent;
|
||||
}
|
||||
}
|
||||
R_ASSERT(fsMountSdcard(&g_sd_card_filesystem));
|
||||
g_sd_card_initialized = true;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void InitializeSdCard() {
|
||||
for (size_t i = 0; i < NumRequiredServicesForSdCardAccess; i++) {
|
||||
Service tmp;
|
||||
R_ASSERT(sm::GetService(&tmp, RequiredServicesForSdCardAccess[i]));
|
||||
serviceClose(&tmp);
|
||||
}
|
||||
R_ASSERT(fsMountSdcard(&g_sd_card_filesystem));
|
||||
g_sd_card_initialized = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* SD card utilities. */
|
||||
bool IsSdCardInitialized() {
|
||||
std::scoped_lock<HosMutex> lk(g_sd_card_lock);
|
||||
|
||||
if (!g_sd_card_initialized) {
|
||||
if (R_SUCCEEDED(TryInitializeSdCard())) {
|
||||
g_sd_card_initialized = true;
|
||||
}
|
||||
}
|
||||
return g_sd_card_initialized;
|
||||
}
|
||||
|
||||
void WaitSdCardInitialized() {
|
||||
std::scoped_lock<HosMutex> lk(g_sd_card_lock);
|
||||
|
||||
InitializeSdCard();
|
||||
}
|
||||
|
||||
}
|
70
source/hid/hid_api.cpp
Normal file
70
source/hid/hid_api.cpp
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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 <set>
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include <stratosphere/pm.hpp>
|
||||
#include <stratosphere/hid.hpp>
|
||||
|
||||
namespace sts::hid {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Global lock. */
|
||||
HosMutex g_hid_lock;
|
||||
bool g_initialized_hid = false;
|
||||
|
||||
/* Helper. */
|
||||
void InitializeHid() {
|
||||
R_ASSERT(smInitialize());
|
||||
ON_SCOPE_EXIT { smExit(); };
|
||||
{
|
||||
R_ASSERT(hidInitialize());
|
||||
}
|
||||
}
|
||||
|
||||
Result EnsureHidInitialized() {
|
||||
if (!g_initialized_hid) {
|
||||
if (!serviceIsActive(hidGetSessionService())) {
|
||||
if (!pm::info::HasLaunchedTitle(TitleId_Hid)) {
|
||||
return MAKERESULT(Module_Libnx, LibnxError_InitFail_HID);
|
||||
}
|
||||
InitializeHid();
|
||||
}
|
||||
g_initialized_hid = true;
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result GetKeysHeld(u64 *out) {
|
||||
std::scoped_lock<HosMutex> lk(g_hid_lock);
|
||||
|
||||
R_TRY(EnsureHidInitialized());
|
||||
|
||||
hidScanInput();
|
||||
*out = 0;
|
||||
|
||||
for (size_t controller = 0; controller < 10; controller++) {
|
||||
*out |= hidKeysHeld(static_cast<HidControllerID>(controller));
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
}
|
68
source/ldr/ldr_ams.c
Normal file
68
source/ldr/ldr_ams.c
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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 "ldr_ams.h"
|
||||
|
||||
static Result _ldrAtmosphereHasLaunchedTitle(Service *srv, bool *out, u64 tid) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 title_id;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(srv, &c, sizeof(*raw));
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65000;
|
||||
raw->title_id = tid;
|
||||
|
||||
Result rc = serviceIpcDispatch(srv);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
u8 has_launched_title;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(srv, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
*out = resp->has_launched_title != 0;
|
||||
} else {
|
||||
rc = 0x666;
|
||||
}
|
||||
} else {
|
||||
rc = 0x555;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result ldrDmntAtmosphereHasLaunchedTitle(bool *out, u64 tid) {
|
||||
return _ldrAtmosphereHasLaunchedTitle(ldrDmntGetServiceSession(), out, tid);
|
||||
}
|
||||
|
||||
Result ldrPmAtmosphereHasLaunchedTitle(bool *out, u64 tid) {
|
||||
return _ldrAtmosphereHasLaunchedTitle(ldrPmGetServiceSession(), out, tid);
|
||||
}
|
19
source/ldr/ldr_ams.h
Normal file
19
source/ldr/ldr_ams.h
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @file ldr_ams.h
|
||||
* @brief Loader (ldr:*) IPC wrapper for Atmosphere extensions.
|
||||
* @author SciresM
|
||||
* @copyright libnx Authors
|
||||
*/
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
Result ldrPmAtmosphereHasLaunchedTitle(bool *out, u64 tid);
|
||||
Result ldrDmntAtmosphereHasLaunchedTitle(bool *out, u64 tid);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
48
source/ldr/ldr_pm_api.cpp
Normal file
48
source/ldr/ldr_pm_api.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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 <set>
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include <stratosphere/ldr.hpp>
|
||||
#include <stratosphere/ldr/ldr_pm_api.hpp>
|
||||
|
||||
#include "ldr_ams.h"
|
||||
|
||||
namespace sts::ldr::pm {
|
||||
|
||||
/* Information API. */
|
||||
Result CreateProcess(Handle *out, PinId pin_id, u32 flags, Handle reslimit) {
|
||||
return ldrPmCreateProcess(flags, pin_id.value, reslimit, out);
|
||||
}
|
||||
|
||||
Result GetProgramInfo(ProgramInfo *out, const ncm::TitleLocation &loc) {
|
||||
return ldrPmGetProgramInfo(static_cast<u64>(loc.title_id), static_cast<FsStorageId>(loc.storage_id), reinterpret_cast<LoaderProgramInfo *>(out));
|
||||
}
|
||||
|
||||
Result PinTitle(PinId *out, const ncm::TitleLocation &loc) {
|
||||
static_assert(sizeof(*out) == sizeof(u64), "PinId definition!");
|
||||
return ldrPmRegisterTitle(static_cast<u64>(loc.title_id), static_cast<FsStorageId>(loc.storage_id), reinterpret_cast<u64 *>(out));
|
||||
}
|
||||
|
||||
Result UnpinTitle(PinId pin_id) {
|
||||
return ldrPmUnregisterTitle(pin_id.value);
|
||||
}
|
||||
|
||||
Result HasLaunchedTitle(bool *out, ncm::TitleId title_id) {
|
||||
return ldrPmAtmosphereHasLaunchedTitle(out, static_cast<u64>(title_id));
|
||||
}
|
||||
|
||||
}
|
@ -23,11 +23,6 @@ namespace sts::map {
|
||||
namespace {
|
||||
|
||||
/* Convenience defines. */
|
||||
constexpr uintptr_t Deprecated64BitAslrBase = 0x08000000ul;
|
||||
constexpr size_t Deprecated64BitAslrSize = 0x78000000ul;
|
||||
constexpr uintptr_t Deprecated32BitAslrBase = 0x00200000ul;
|
||||
constexpr size_t Deprecated32BitAslrSize = 0x3FE00000ul;
|
||||
|
||||
constexpr size_t GuardRegionSize = 0x4000;
|
||||
constexpr size_t LocateRetryCount = 0x200;
|
||||
|
||||
@ -194,12 +189,12 @@ namespace sts::map {
|
||||
R_TRY(svcGetInfo(&out->aslr_size, InfoType_AslrRegionSize, process_h, 0));
|
||||
} else {
|
||||
/* Auto-detect 32-bit vs 64-bit. */
|
||||
if (out->heap_base < Deprecated64BitAslrBase || out->alias_base < Deprecated64BitAslrBase) {
|
||||
out->aslr_base = Deprecated32BitAslrBase;
|
||||
out->aslr_size = Deprecated32BitAslrSize;
|
||||
if (out->heap_base < AslrBase64BitDeprecated || out->alias_base < AslrBase64BitDeprecated) {
|
||||
out->aslr_base = AslrBase32Bit;
|
||||
out->aslr_size = AslrSize32Bit;
|
||||
} else {
|
||||
out->aslr_base = Deprecated64BitAslrBase;
|
||||
out->aslr_size = Deprecated64BitAslrSize;
|
||||
out->aslr_base = AslrBase64BitDeprecated;
|
||||
out->aslr_size = AslrSize64BitDeprecated;
|
||||
}
|
||||
}
|
||||
|
||||
|
96
source/pm/pm_ams.c
Normal file
96
source/pm/pm_ams.c
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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_ams.h"
|
||||
|
||||
Result pminfoAtmosphereGetProcessId(u64 *out_pid, u64 tid) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
Service *srv = pminfoGetServiceSession();
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 title_id;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(srv, &c, sizeof(*raw));
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65000;
|
||||
raw->title_id = tid;
|
||||
|
||||
Result rc = serviceIpcDispatch(srv);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
u64 pid;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(srv, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
*out_pid = resp->pid;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result pminfoAtmosphereHasLaunchedTitle(bool *out, u64 tid) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
Service *srv = pminfoGetServiceSession();
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 title_id;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(srv, &c, sizeof(*raw));
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65001;
|
||||
raw->title_id = tid;
|
||||
|
||||
Result rc = serviceIpcDispatch(srv);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
u8 has_launched_title;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(srv, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
*out = resp->has_launched_title != 0;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
19
source/pm/pm_ams.h
Normal file
19
source/pm/pm_ams.h
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @file pm_ams.h
|
||||
* @brief Process Manager (pm:*) IPC wrapper for Atmosphere extensions.
|
||||
* @author SciresM
|
||||
* @copyright libnx Authors
|
||||
*/
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
Result pminfoAtmosphereGetProcessId(u64 *out_pid, u64 tid);
|
||||
Result pminfoAtmosphereHasLaunchedTitle(bool *out, u64 tid);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
70
source/pm/pm_info_api.cpp
Normal file
70
source/pm/pm_info_api.cpp
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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 <set>
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include <stratosphere/pm.hpp>
|
||||
|
||||
#include "pm_ams.h"
|
||||
|
||||
namespace sts::pm::info {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Global lock. */
|
||||
HosMutex g_info_lock;
|
||||
std::set<u64> g_cached_launched_titles;
|
||||
|
||||
}
|
||||
|
||||
/* Information API. */
|
||||
Result GetTitleId(u64 *out_title_id, u64 process_id) {
|
||||
std::scoped_lock<HosMutex> lk(g_info_lock);
|
||||
|
||||
return pminfoGetTitleId(out_title_id, process_id);
|
||||
}
|
||||
|
||||
Result GetProcessId(u64 *out_process_id, u64 title_id) {
|
||||
std::scoped_lock<HosMutex> lk(g_info_lock);
|
||||
|
||||
return pminfoAtmosphereGetProcessId(out_process_id, title_id);
|
||||
}
|
||||
|
||||
Result __attribute__((weak)) HasLaunchedTitle(bool *out, u64 title_id) {
|
||||
std::scoped_lock<HosMutex> lk(g_info_lock);
|
||||
|
||||
if (g_cached_launched_titles.find(title_id) != g_cached_launched_titles.end()) {
|
||||
*out = true;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
bool has_launched = false;
|
||||
R_TRY(pminfoAtmosphereHasLaunchedTitle(&has_launched, title_id));
|
||||
if (has_launched) {
|
||||
g_cached_launched_titles.insert(title_id);
|
||||
}
|
||||
|
||||
*out = has_launched;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
bool HasLaunchedTitle(u64 title_id) {
|
||||
bool has_launched = false;
|
||||
R_ASSERT(HasLaunchedTitle(&has_launched, title_id));
|
||||
return has_launched;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user