Merge branch 'master' into toggle-power-button

This commit is contained in:
TuxSH 2020-04-17 00:36:07 +02:00 committed by GitHub
commit 68b670f94f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 686 additions and 100 deletions

View File

@ -1,41 +1,36 @@
# Luma3DS # Luma3DS
*Noob-proof (N)3DS "Custom Firmware"* *Noob-proof (N)3DS "Custom Firmware"*
## What it is ### What it is
**Luma3DS** is a program to patch the system software of (New) Nintendo (2)3DS handheld consoles "on the fly", adding features such as per-game language settings, debugging capabilities for developers, and removing restrictions enforced by Nintendo such as the region lock.
**Luma3DS** is a program to patch the system software of (New) Nintendo 3DS handheld consoles "on the fly", adding features (such as per-game language settings and debugging capabilities for developers) and removing restrictions enforced by Nintendo (such as the region lock).
It also allows you to run unauthorized ("homebrew") content by removing signature checks. It also allows you to run unauthorized ("homebrew") content by removing signature checks.
To use it, you will need a console capable of running homebrew software on the ARM9 processor. We recommend [Plailect's guide](https://3ds.hacks.guide/) for details on how to get your system ready. To use it, you will need a console capable of running homebrew software on the ARM9 processor. We recommend [Plailect's guide](https://3ds.hacks.guide/) for details on how to get your system ready.
Since Luma3DS v8.0, Luma3DS has its own in-game menu, triggerable by `L+Down+Select` (see the [release notes](https://github.com/AuroraWright/Luma3DS/releases/tag/v8.0)). Since v8.0, Luma3DS has its own in-game menu, triggerable by <kbd>L+Down+Select</kbd> (see the [release notes](https://github.com/AuroraWright/Luma3DS/releases/tag/v8.0)).
--- #
### Compiling
* Prerequisites
1. git
2. [makerom](https://github.com/jakcron/Project_CTR) in PATH
3. [firmtool](https://github.com/TuxSH/firmtool)
4. Up-to-date devkitARM+libctru
1. Clone the repository with `git clone https://github.com/AuroraWright/Luma3DS.git`
2. Run `make`.
## Compiling The produced `boot.firm` is meant to be copied to the root of your SD card for usage with Boot9Strap.
First you need to clone the repository with: `git clone https://github.com/AuroraWright/Luma3DS.git`
To compile, you'll need a recent commit of [makerom](https://github.com/profi200/Project_CTR) added to your PATH. You'll also need to install [firmtool](https://github.com/TuxSH/firmtool), its README contains installation instructions.
You'll also need to update your libctru and devkitARM installation to their latest releases.
Then, run `make`.
The produced file is called `boot.firm` and is meant to be copied to the root of your SD card, for usage with boot9strap.
---
## Setup / Usage / Features
#
### Setup / Usage / Features
See https://github.com/AuroraWright/Luma3DS/wiki See https://github.com/AuroraWright/Luma3DS/wiki
--- #
### Credits
## Credits
See https://github.com/AuroraWright/Luma3DS/wiki/Credits See https://github.com/AuroraWright/Luma3DS/wiki/Credits
--- #
### Licensing
This software is licensed under the terms of the GPLv3. You can find a copy of the license in the LICENSE.txt file.
## Licensing Files in the GDB stub are instead double-licensed as MIT or "GPLv2 or any later version", in which case it's specified in the file header.
This software is licensed under the terms of the GPLv3.
You can find a copy of the license in the LICENSE.txt file.
Files in the GDB stub are instead double-licensed as MIT or "GPLv2 or any later version", in which case it is specified in the file header.

View File

@ -28,13 +28,13 @@ INCLUDES := include
ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft
DEFINES := -DARM11 -D_3DS DEFINES := -DARM11 -D_3DS
CFLAGS := -g -std=gnu11 -Wall -Wextra -Werror -O2 -mword-relocations \ COMMON_FLAGS = -g -Wall -Wextra -Werror -O2 -mword-relocations \
-fomit-frame-pointer -ffunction-sections -fdata-sections \ -fomit-frame-pointer -ffunction-sections -fdata-sections \
$(ARCH) $(DEFINES) $(ARCH) $(DEFINES) $(INCLUDE)
CFLAGS += $(INCLUDE) CFLAGS := -std=gnu11 $(COMMON_FLAGS)
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11 CXXFLAGS := -fno-rtti -fno-exceptions -std=gnu++17 $(COMMON_FLAGS)
ASFLAGS := -g $(ARCH) ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map),--section-start,.text=0x14000000 LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map),--section-start,.text=0x14000000

View File

@ -0,0 +1,283 @@
#include "bps_patcher.h"
#include <array>
#include <cstring>
#include <optional>
#include <string_view>
#include <type_traits>
extern "C"
{
#include <3ds/os.h>
#include <3ds/result.h>
#include <3ds/services/fs.h>
#include <3ds/svc.h>
#include "patcher.h"
#include "strings.h"
}
#include "file_util.h"
namespace patcher
{
namespace Bps
{
// The BPS format uses variable length encoding for all integers.
// Realistically uint32s are more than enough for code patching.
using Number = u32;
constexpr std::size_t FooterSize = 12;
// The BPS format uses CRC32 checksums.
[[gnu::optimize("Os")]] static u32 crc32(const u8 *data, std::size_t size)
{
u32 crc = 0xFFFFFFFF;
for(std::size_t i = 0; i < size; ++i)
{
crc ^= data[i];
for(std::size_t j = 0; j < 8; ++j)
{
u32 mask = -(crc & 1);
crc = (crc >> 1) ^ (0xEDB88320 & mask);
}
}
return ~crc;
}
// Utility class to make keeping track of offsets and bound checks less error prone.
template <typename T>
class Stream
{
public:
Stream(T *ptr, std::size_t size) : m_ptr{ptr}, m_size{size} {}
bool Read(void *buffer, std::size_t length)
{
if(m_offset + length > m_size)
return false;
std::memcpy(buffer, m_ptr + m_offset, length);
m_offset += length;
return true;
}
template <typename OtherType>
[[gnu::optimize("Os")]] bool CopyFrom(Stream<OtherType> &other, std::size_t length)
{
if(m_offset + length > m_size)
return false;
if(!other.Read(m_ptr + m_offset, length))
return false;
m_offset += length;
return true;
}
template <typename ValueType>
std::optional<ValueType> Read()
{
static_assert(std::is_pod_v<ValueType>);
ValueType val{};
if(!Read(&val, sizeof(val)))
return std::nullopt;
return val;
}
[[gnu::optimize("Os")]] Number ReadNumber()
{
Number data = 0, shift = 1;
std::optional<u8> x;
while((x = Read<u8>()))
{
data += (*x & 0x7f) * shift;
if(*x & 0x80)
break;
shift <<= 7;
data += shift;
}
return data;
}
auto data() const { return m_ptr; }
std::size_t size() const { return m_size; }
std::size_t Tell() const { return m_offset; }
bool Seek(size_t offset)
{
m_offset = offset;
return true;
}
private:
T *m_ptr = nullptr;
std::size_t m_size = 0;
std::size_t m_offset = 0;
};
class PatchApplier
{
public:
PatchApplier(Stream<const u8> source, Stream<u8> target, Stream<const u8> patch)
: m_source{source}, m_target{target}, m_patch{patch}
{
}
[[gnu::always_inline]] bool Apply()
{
const auto magic = *m_patch.Read<std::array<char, 4>>();
if(std::string_view(magic.data(), magic.size()) != "BPS1")
return false;
const Bps::Number source_size = m_patch.ReadNumber();
const Bps::Number target_size = m_patch.ReadNumber();
const Bps::Number metadata_size = m_patch.ReadNumber();
if(source_size > m_source.size() || target_size > m_target.size() || metadata_size != 0)
return false;
const std::size_t command_start_offset = m_patch.Tell();
const std::size_t command_end_offset = m_patch.size() - FooterSize;
m_patch.Seek(command_end_offset);
const u32 source_crc32 = *m_patch.Read<u32>();
const u32 target_crc32 = *m_patch.Read<u32>();
m_patch.Seek(command_start_offset);
if(crc32(m_source.data(), source_size) != source_crc32)
return false;
// Process all patch commands.
std::memset(m_target.data(), 0, m_target.size());
while(m_patch.Tell() < command_end_offset)
{
const bool ok = HandleCommand();
if(!ok)
return false;
}
return crc32(m_target.data(), target_size) == target_crc32;
}
private:
bool HandleCommand()
{
const Number data = m_patch.ReadNumber();
const Number command = data & 3;
const Number length = (data >> 2) + 1;
switch(command)
{
case 0:
return SourceRead(length);
case 1:
return TargetRead(length);
case 2:
return SourceCopy(length);
case 3:
return TargetCopy(length);
default:
return false;
}
}
bool SourceRead(Number length)
{
return m_source.Seek(m_target.Tell()) && m_target.CopyFrom(m_source, length);
}
bool TargetRead(Number length) { return m_target.CopyFrom(m_patch, length); }
bool SourceCopy(Number length)
{
const Number data = m_patch.ReadNumber();
m_source_relative_offset += (data & 1 ? -1 : +1) * int(data >> 1);
if(!m_source.Seek(m_source_relative_offset) || !m_target.CopyFrom(m_source, length))
return false;
m_source_relative_offset += length;
return true;
}
bool TargetCopy(Number length)
{
const Number data = m_patch.ReadNumber();
m_target_relative_offset += (data & 1 ? -1 : +1) * int(data >> 1);
if(m_target.Tell() + length > m_target.size())
return false;
if(m_target_relative_offset + length > m_target.size())
return false;
// Byte by byte copy.
for(size_t i = 0; i < length; ++i)
m_target.data()[m_target.Tell() + i] = m_target.data()[m_target_relative_offset++];
m_target.Seek(m_target.Tell() + length);
return true;
}
std::size_t m_source_relative_offset = 0;
std::size_t m_target_relative_offset = 0;
Stream<const u8> m_source;
Stream<u8> m_target;
Stream<const u8> m_patch;
};
} // namespace Bps
class ScopedAppHeap
{
public:
ScopedAppHeap()
{
u32 tmp;
m_size = osGetMemRegionFree(MEMREGION_APPLICATION);
if(!R_SUCCEEDED(svcControlMemory(&tmp, BaseAddress, 0, m_size,
MemOp(MEMOP_ALLOC | MEMOP_REGION_APP),
MemPerm(MEMPERM_READ | MEMPERM_WRITE))))
{
svcBreak(USERBREAK_PANIC);
}
}
~ScopedAppHeap()
{
u32 tmp;
svcControlMemory(&tmp, BaseAddress, 0, m_size, MEMOP_FREE, MemPerm(0));
}
static constexpr u32 BaseAddress = 0x08000000;
private:
u32 m_size;
};
static inline bool ApplyCodeBpsPatch(u64 prog_id, u8 *code, u32 size)
{
char bps_path[] = "/luma/titles/0000000000000000/code.bps";
progIdToStr(bps_path + 28, prog_id);
util::File patch_file;
if(!patch_file.Open(bps_path, FS_OPEN_READ))
return true;
const u32 patch_size = u32(patch_file.GetSize().value_or(0));
// Temporarily use APPLICATION memory to store the source and patch data.
ScopedAppHeap memory;
u8 *source_data = reinterpret_cast<u8 *>(memory.BaseAddress);
u8 *patch_data = source_data + size;
std::memcpy(source_data, code, size);
if(!patch_file.Read(patch_data, patch_size, 0))
return false;
Bps::Stream<const u8> source_stream{source_data, size};
Bps::Stream target_stream{code, size};
Bps::Stream<const u8> patch_stream{patch_data, patch_size};
Bps::PatchApplier applier{source_stream, target_stream, patch_stream};
if(!applier.Apply())
svcBreak(USERBREAK_PANIC);
return true;
}
} // namespace patcher
extern "C"
{
bool patcherApplyCodeBpsPatch(u64 progId, u8 *code, u32 size)
{
return patcher::ApplyCodeBpsPatch(progId, code, size);
}
}

View File

@ -0,0 +1,12 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <3ds/types.h>
bool patcherApplyCodeBpsPatch(u64 progId, u8* code, u32 size);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,76 @@
#pragma once
#include <optional>
#include <string.h>
extern "C"
{
#include <3ds/result.h>
#include <3ds/services/fs.h>
#include <3ds/svc.h>
#include <3ds/types.h>
}
namespace util
{
inline FS_Path MakePath(const char *path)
{
return {PATH_ASCII, strnlen(path, 255) + 1, path};
}
// A small wrapper to make forgetting to close a file and
// to check read lengths impossible.
class File
{
public:
File() = default;
File(const File &other) = delete;
File &operator=(const File &) = delete;
File(File &&other) { *this = std::move(other); }
File &operator=(File &&other)
{
std::swap(m_handle, other.m_handle);
return *this;
}
~File() { Close(); }
bool Close()
{
const bool ok = !m_handle || R_SUCCEEDED(FSFILE_Close(*m_handle));
if(ok)
m_handle = std::nullopt;
return ok;
}
bool Open(const char *path, int open_flags)
{
const FS_Path archive_path = {PATH_EMPTY, 1, ""};
Handle handle;
const bool ok = R_SUCCEEDED(FSUSER_OpenFileDirectly(&handle, ARCHIVE_SDMC, archive_path,
MakePath(path), open_flags, 0));
if(ok)
m_handle = handle;
return ok;
}
bool Read(void *buffer, u32 size, u64 offset)
{
u32 bytes_read = 0;
const Result res = FSFILE_Read(*m_handle, &bytes_read, offset, buffer, size);
return R_SUCCEEDED(res) && bytes_read == size;
}
std::optional<u64> GetSize() const
{
u64 size;
if(!R_SUCCEEDED(FSFILE_GetSize(*m_handle, &size)))
return std::nullopt;
return size;
}
private:
std::optional<Handle> m_handle;
};
} // namespace util

View File

@ -1,5 +1,6 @@
#include <3ds.h> #include <3ds.h>
#include "patcher.h" #include "patcher.h"
#include "bps_patcher.h"
#include "memory.h" #include "memory.h"
#include "strings.h" #include "strings.h"
#include "romfsredir.h" #include "romfsredir.h"
@ -369,7 +370,7 @@ bool loadTitleExheaderInfo(u64 progId, ExHeader_Info *exheaderInfo)
u64 fileSize; u64 fileSize;
if(R_FAILED(IFile_GetSize(&file, &fileSize)) || fileSize != sizeof(ExHeader_Info) || fileSize != sizeof(ExHeader)) goto error; if(R_FAILED(IFile_GetSize(&file, &fileSize)) || (fileSize != sizeof(ExHeader_Info) && fileSize != sizeof(ExHeader))) goto error;
else else
{ {
u64 total; u64 total;
@ -468,13 +469,13 @@ static inline bool loadTitleLocaleConfig(u64 progId, u8 *mask, u8 *regionId, u8
((buf[10] >= '0' && buf[10] <= '9') || (buf[10] >= 'a' && buf[10] <= 'f') || (buf[10] >= 'A' && buf[10] <= 'F')) && ((buf[10] >= '0' && buf[10] <= '9') || (buf[10] >= 'a' && buf[10] <= 'f') || (buf[10] >= 'A' && buf[10] <= 'F')) &&
((buf[11] >= '0' && buf[11] <= '9') || (buf[11] >= 'a' && buf[11] <= 'f') || (buf[11] >= 'A' && buf[11] <= 'F'))) ((buf[11] >= '0' && buf[11] <= '9') || (buf[11] >= 'a' && buf[11] <= 'f') || (buf[11] >= 'A' && buf[11] <= 'F')))
{ {
if (buf[10] >= '0' && buf[10] <= '9') *stateId = 16 * (buf[10] - '0'); if (buf[10] >= '0' && buf[10] <= '9') *stateId = 16 * (buf[10] - '0' + 10);
else if(buf[10] >= 'a' && buf[10] <= 'f') *stateId = 16 * (buf[10] - 'a'); else if(buf[10] >= 'a' && buf[10] <= 'f') *stateId = 16 * (buf[10] - 'a' + 10);
else if(buf[10] >= 'A' && buf[10] <= 'F') *stateId = 16 * (buf[10] - 'A'); else if(buf[10] >= 'A' && buf[10] <= 'F') *stateId = 16 * (buf[10] - 'A' + 10);
if (buf[11] >= '0' && buf[11] <= '9') *stateId += buf[11] - '0'; if (buf[11] >= '0' && buf[11] <= '9') *stateId += buf[11] - '0' + 10;
else if(buf[11] >= 'a' && buf[11] <= 'f') *stateId += buf[11] - 'a'; else if(buf[11] >= 'a' && buf[11] <= 'f') *stateId += buf[11] - 'a' + 10;
else if(buf[11] >= 'A' && buf[11] <= 'F') *stateId += buf[11] - 'A'; else if(buf[11] >= 'A' && buf[11] <= 'F') *stateId += buf[11] - 'A' + 10;
*mask |= 8; *mask |= 8;
} }
@ -845,6 +846,7 @@ void patchCode(u64 progId, u16 progVer, u8 *code, u32 size, u32 textSize, u32 ro
if(CONFIG(PATCHGAMES)) if(CONFIG(PATCHGAMES))
{ {
if(!patcherApplyCodeBpsPatch(progId, code, size)) goto error;
if(!applyCodeIpsPatch(progId, code, size)) goto error; if(!applyCodeIpsPatch(progId, code, size)) goto error;
if((u32)((progId >> 0x20) & 0xFFFFFFEDULL) == 0x00040000) if((u32)((progId >> 0x20) & 0xFFFFFFEDULL) == 0x00040000)

View File

@ -28,7 +28,7 @@ INCLUDES := include
ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft
DEFINES := -DARM11 -D_3DS DEFINES := -DARM11 -D_3DS
CFLAGS := -g -std=gnu11 -Wall -Wextra -Werror -O2 -mword-relocations \ CFLAGS := -g -std=gnu11 -Wall -Wextra -Werror -Os -mword-relocations \
-fomit-frame-pointer -ffunction-sections -fdata-sections \ -fomit-frame-pointer -ffunction-sections -fdata-sections \
$(ARCH) $(DEFINES) $(ARCH) $(DEFINES)

View File

@ -28,8 +28,8 @@ INCLUDES := include include/gdb include/menus include/redshift
ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft
DEFINES := -DARM11 -D_3DS DEFINES := -DARM11 -D_3DS
CFLAGS := -g -std=gnu11 -Wall -Wextra -Werror -Wno-unused-value -O2 -mword-relocations \ CFLAGS := -g -std=gnu11 -Wall -Wextra -Werror -Wno-unused-value -Os -mword-relocations \
-fomit-frame-pointer -ffunction-sections -fdata-sections \ -fomit-frame-pointer -ffunction-sections -fdata-sections -fno-math-errno \
$(ARCH) $(DEFINES) $(ARCH) $(DEFINES)
CFLAGS += $(INCLUDE) CFLAGS += $(INCLUDE)
@ -39,7 +39,7 @@ CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11
ASFLAGS := -g $(ARCH) ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map),--section-start,.text=0x14000000 LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map),--section-start,.text=0x14000000
LIBS := -lctru -lm LIBS := -lctru
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing # list of directories containing libraries, this must be the top level containing

View File

@ -27,5 +27,8 @@
#pragma once #pragma once
#include <3ds/types.h> #include <3ds/types.h>
#include "MyThread.h"
void ERRF_HandleCommands(void *ctx); MyThread *errDispCreateThread(void);
void ERRF_HandleCommands(void);
void errDispThreadMain(void);

View File

@ -18,14 +18,14 @@
#define MAX_DEBUG 3 #define MAX_DEBUG 3
#define MAX_DEBUG_THREAD 127 #define MAX_DEBUG_THREAD 127
#define MAX_BREAKPOINT 256 #define MAX_BREAKPOINT 64
#define MAX_TIO_OPEN_FILE 32 #define MAX_TIO_OPEN_FILE 32
// 512+24 is the ideal size as IDA will try to read exactly 0x100 bytes at a time. Add 4 to this, for $#<checksum>, see below. // 512+24 is the ideal size as IDA will try to read exactly 0x100 bytes at a time. Add 4 to this, for $#<checksum>, see below.
// IDA seems to want additional bytes as well. // IDA seems to want additional bytes as well.
// 1024 is fine enough to put all regs in the 'T' stop reply packets // 1024 is fine enough to put all regs in the 'T' stop reply packets
#define GDB_BUF_LEN 2048 #define GDB_BUF_LEN 1024
#define GDB_HANDLER(name) GDB_Handle##name #define GDB_HANDLER(name) GDB_Handle##name
#define GDB_QUERY_HANDLER(name) GDB_HANDLER(Query##name) #define GDB_QUERY_HANDLER(name) GDB_HANDLER(Query##name)

View File

@ -34,3 +34,4 @@ extern Menu sysconfigMenu;
void SysConfigMenu_ToggleLEDs(void); void SysConfigMenu_ToggleLEDs(void);
void SysConfigMenu_ToggleWireless(void); void SysConfigMenu_ToggleWireless(void);
void SysConfigMenu_TogglePowerButton(void); void SysConfigMenu_TogglePowerButton(void);
void SysConfigMenu_ControlWifi(void);

View File

@ -32,13 +32,26 @@
#include "fmt.h" #include "fmt.h"
#include "ifile.h" #include "ifile.h"
extern Handle terminationRequestEvent;
static inline void assertSuccess(Result res) static inline void assertSuccess(Result res)
{ {
if(R_FAILED(res)) if(R_FAILED(res))
svcBreak(USERBREAK_PANIC); svcBreak(USERBREAK_PANIC);
} }
static MyThread errDispThread;
static u8 ALIGN(8) errDispThreadStack[0xD00];
static char userString[0x100 + 1] = {0}; static char userString[0x100 + 1] = {0};
static char staticBuf[0x100 + 1] = {0};
MyThread *errDispCreateThread(void)
{
if(R_FAILED(MyThread_Create(&errDispThread, errDispThreadMain, errDispThreadStack, 0xD00, 0x18, CORE_SYSTEM)))
svcBreak(USERBREAK_PANIC);
return &errDispThread;
}
static inline u32 ERRF_DisplayRegisterValue(u32 posX, u32 posY, const char *name, u32 value) static inline u32 ERRF_DisplayRegisterValue(u32 posX, u32 posY, const char *name, u32 value)
{ {
@ -50,6 +63,11 @@ static inline int ERRF_FormatRegisterValue(char *out, const char *name, u32 valu
return sprintf(out, "%-9s %08lx", name, value); return sprintf(out, "%-9s %08lx", name, value);
} }
static inline void ERRF_GetErrInfo(ERRF_FatalErrInfo* info, u32* in, u32 size)
{
memcpy(info, in, size);
}
static int ERRF_FormatError(char *out, ERRF_FatalErrInfo *info) static int ERRF_FormatError(char *out, ERRF_FatalErrInfo *info)
{ {
char *outStart = out; char *outStart = out;
@ -154,7 +172,7 @@ static int ERRF_FormatError(char *out, ERRF_FatalErrInfo *info)
desc = "The System Memory has been damaged."; desc = "The System Memory has been damaged.";
break; break;
case ERRF_ERRTYPE_FAILURE: case ERRF_ERRTYPE_FAILURE:
info->data.failure_mesg[0x60] = 0; // make sure the last byte in the IPC buffer is NULL info->data.failure_mesg[0x5F] = 0; // make sure the last byte in the IPC buffer is NULL
desc = info->data.failure_mesg; desc = info->data.failure_mesg;
break; break;
default: default:
@ -225,18 +243,18 @@ static Result ERRF_SaveErrorToFile(ERRF_FatalErrInfo *info)
return res; return res;
} }
void ERRF_HandleCommands(void *ctx) void ERRF_HandleCommands(void)
{ {
(void)ctx;
u32 *cmdbuf = getThreadCommandBuffer(); u32 *cmdbuf = getThreadCommandBuffer();
ERRF_FatalErrInfo info;
switch(cmdbuf[0] >> 16) switch(cmdbuf[0] >> 16)
{ {
case 1: // Throw case 1: // Throw
{ {
ERRF_FatalErrInfo *info = (ERRF_FatalErrInfo *)(cmdbuf + 1); ERRF_GetErrInfo(&info, (cmdbuf + 1), sizeof(ERRF_FatalErrInfo));
ERRF_SaveErrorToFile(info); ERRF_SaveErrorToFile(&info);
if(info->type != ERRF_ERRTYPE_LOGGED || info->procId == 0) if(info.type != ERRF_ERRTYPE_LOGGED || info.procId == 0)
{ {
menuEnter(); menuEnter();
@ -244,7 +262,7 @@ void ERRF_HandleCommands(void *ctx)
Draw_ClearFramebuffer(); Draw_ClearFramebuffer();
Draw_FlushFramebuffer(); Draw_FlushFramebuffer();
ERRF_DisplayError(info); ERRF_DisplayError(&info);
/* /*
If we ever wanted to return: If we ever wanted to return:
@ -258,26 +276,98 @@ void ERRF_HandleCommands(void *ctx)
__builtin_unreachable(); __builtin_unreachable();
} }
cmdbuf[0] = 0x10040; cmdbuf[0] = IPC_MakeHeader(1, 1, 0);
cmdbuf[1] = 0; cmdbuf[1] = 0;
break; break;
} }
case 2: // SetUserString case 2: // SetUserString
{ {
if(cmdbuf[0] != 0x20042 || (cmdbuf[2] & 0x3C0F) != 2) if(cmdbuf[0] != IPC_MakeHeader(2, 1, 2) || (cmdbuf[2] & 0x3C0F) != 2)
{ {
cmdbuf[0] = 0x40; cmdbuf[0] = IPC_MakeHeader(0, 1, 0);
cmdbuf[1] = 0xD9001830; cmdbuf[1] = 0xD9001830;
} }
else else
{ {
cmdbuf[0] = 0x20040; u32 sz = cmdbuf[1] <= 0x100 ? cmdbuf[1] : 0x100;
u32 sz = cmdbuf[1] <= 0x100 ? sz : 0x100;
memcpy(userString, cmdbuf + 3, sz); memcpy(userString, cmdbuf + 3, sz);
userString[sz] = 0; userString[sz] = 0;
cmdbuf[0] = IPC_MakeHeader(2, 1, 0);
cmdbuf[1] = 0;
} }
break; break;
} }
} }
} }
void errDispThreadMain(void)
{
Handle handles[3];
Handle serverHandle, clientHandle, sessionHandle = 0;
u32 replyTarget = 0;
s32 index;
Result res;
u32 *cmdbuf = getThreadCommandBuffer();
u32 *sbuf = getThreadStaticBuffers();
sbuf[0] = IPC_Desc_StaticBuffer(0x100, 0);
sbuf[1] = (u32)staticBuf;
assertSuccess(svcCreatePort(&serverHandle, &clientHandle, "err:f", 1));
do
{
handles[0] = terminationRequestEvent;
handles[1] = serverHandle;
handles[2] = sessionHandle;
if(replyTarget == 0) // k11
cmdbuf[0] = 0xFFFF0000;
res = svcReplyAndReceive(&index, handles, 1 + (sessionHandle == 0 ? 1 : 2), replyTarget);
if(R_FAILED(res))
{
if((u32)res == 0xC920181A) // session closed by remote
{
svcCloseHandle(sessionHandle);
sessionHandle = 0;
replyTarget = 0;
}
else
svcBreak(USERBREAK_PANIC);
}
else
{
if (index == 0)
{
break;
}
else if(index == 1)
{
Handle session;
assertSuccess(svcAcceptSession(&session, serverHandle));
if(sessionHandle == 0)
sessionHandle = session;
else
svcCloseHandle(session);
}
else
{
ERRF_HandleCommands();
replyTarget = sessionHandle;
}
}
}
while(!terminationRequest);
svcCloseHandle(sessionHandle);
svcCloseHandle(clientHandle);
svcCloseHandle(serverHandle);
}

View File

@ -136,9 +136,9 @@ GDBContext *GDB_SelectAvailableContext(GDBServer *server, u16 minPort, u16 maxPo
{ {
ctx->flags |= GDB_FLAG_SELECTED; ctx->flags |= GDB_FLAG_SELECTED;
ctx->localPort = port; ctx->localPort = port;
ctx->parent = server;
} }
ctx->parent = server;
GDB_UnlockAllContexts(server); GDB_UnlockAllContexts(server);
return ctx; return ctx;
} }

View File

@ -90,11 +90,15 @@ void __appInit()
if (R_FAILED(pmDbgInit())) if (R_FAILED(pmDbgInit()))
svcBreak(USERBREAK_PANIC); svcBreak(USERBREAK_PANIC);
if (R_FAILED(acInit()))
svcBreak(USERBREAK_PANIC);
} }
// this is called after main exits // this is called after main exits
void __appExit() void __appExit()
{ {
acExit();
pmDbgExit(); pmDbgExit();
fsExit(); fsExit();
svcCloseHandle(*fsRegGetSessionHandle()); svcCloseHandle(*fsRegGetSessionHandle());
@ -166,7 +170,6 @@ static void handleNextApplicationDebuggedByForce(u32 notificationId)
} }
static const ServiceManagerServiceEntry services[] = { static const ServiceManagerServiceEntry services[] = {
{ "err:f", 1, ERRF_HandleCommands, true },
{ "hb:ldr", 2, HBLDR_HandleCommands, true }, { "hb:ldr", 2, HBLDR_HandleCommands, true },
{ NULL }, { NULL },
}; };
@ -194,12 +197,14 @@ int main(void)
MyThread *menuThread = menuCreateThread(); MyThread *menuThread = menuCreateThread();
MyThread *taskRunnerThread = taskRunnerCreateThread(); MyThread *taskRunnerThread = taskRunnerCreateThread();
MyThread *errDispThread = errDispCreateThread();
if (R_FAILED(ServiceManager_Run(services, notifications, NULL))) if (R_FAILED(ServiceManager_Run(services, notifications, NULL)))
svcBreak(USERBREAK_PANIC); svcBreak(USERBREAK_PANIC);
MyThread_Join(menuThread, -1LL); MyThread_Join(menuThread, -1LL);
MyThread_Join(taskRunnerThread, -1LL); MyThread_Join(taskRunnerThread, -1LL);
MyThread_Join(errDispThread, -1LL);
return 0; return 0;
} }

View File

@ -125,12 +125,12 @@ u32 waitCombo(void)
} }
static MyThread menuThread; static MyThread menuThread;
static u8 ALIGN(8) menuThreadStack[0x3000]; static u8 ALIGN(8) menuThreadStack[0x1000];
static u8 batteryLevel = 255; static u8 batteryLevel = 255;
MyThread *menuCreateThread(void) MyThread *menuCreateThread(void)
{ {
if(R_FAILED(MyThread_Create(&menuThread, menuThreadMain, menuThreadStack, 0x3000, 52, CORE_SYSTEM))) if(R_FAILED(MyThread_Create(&menuThread, menuThreadMain, menuThreadStack, 0x1000, 52, CORE_SYSTEM)))
svcBreak(USERBREAK_PANIC); svcBreak(USERBREAK_PANIC);
return &menuThread; return &menuThread;
} }

View File

@ -168,7 +168,7 @@ static u8 ReadWriteBuffer8 = 0;
static bool Cheat_Write8(const Handle processHandle, u32 offset, u8 value) static bool Cheat_Write8(const Handle processHandle, u32 offset, u8 value)
{ {
u32 addr = *activeOffset() + offset; u32 addr = *activeOffset() + offset;
if (addr >= 0x01E81000 && addr + 1 < 0x01E82000) if (addr >= 0x01E81000 && addr < 0x01E82000)
{ {
cheatPage[addr - 0x01E81000] = value; cheatPage[addr - 0x01E81000] = value;
return true; return true;
@ -184,7 +184,7 @@ static bool Cheat_Write8(const Handle processHandle, u32 offset, u8 value)
static bool Cheat_Write16(const Handle processHandle, u32 offset, u16 value) static bool Cheat_Write16(const Handle processHandle, u32 offset, u16 value)
{ {
u32 addr = *activeOffset() + offset; u32 addr = *activeOffset() + offset;
if (addr >= 0x01E81000 && addr + 2 < 0x01E82000) if (addr >= 0x01E81000 && addr + 1 < 0x01E82000)
{ {
*(u16*)(cheatPage + addr - 0x01E81000) = value; *(u16*)(cheatPage + addr - 0x01E81000) = value;
return true; return true;
@ -200,7 +200,7 @@ static bool Cheat_Write16(const Handle processHandle, u32 offset, u16 value)
static bool Cheat_Write32(const Handle processHandle, u32 offset, u32 value) static bool Cheat_Write32(const Handle processHandle, u32 offset, u32 value)
{ {
u32 addr = *activeOffset() + offset; u32 addr = *activeOffset() + offset;
if (addr >= 0x01E81000 && addr + 4 < 0x01E82000) if (addr >= 0x01E81000 && addr + 3 < 0x01E82000)
{ {
*(u32*)(cheatPage + addr - 0x01E81000) = value; *(u32*)(cheatPage + addr - 0x01E81000) = value;
return true; return true;
@ -216,7 +216,7 @@ static bool Cheat_Write32(const Handle processHandle, u32 offset, u32 value)
static bool Cheat_Read8(const Handle processHandle, u32 offset, u8* retValue) static bool Cheat_Read8(const Handle processHandle, u32 offset, u8* retValue)
{ {
u32 addr = *activeOffset() + offset; u32 addr = *activeOffset() + offset;
if (addr >= 0x01E81000 && addr + 1 < 0x01E82000) if (addr >= 0x01E81000 && addr < 0x01E82000)
{ {
*retValue = cheatPage[addr - 0x01E81000]; *retValue = cheatPage[addr - 0x01E81000];
return true; return true;
@ -233,7 +233,7 @@ static bool Cheat_Read8(const Handle processHandle, u32 offset, u8* retValue)
static bool Cheat_Read16(const Handle processHandle, u32 offset, u16* retValue) static bool Cheat_Read16(const Handle processHandle, u32 offset, u16* retValue)
{ {
u32 addr = *activeOffset() + offset; u32 addr = *activeOffset() + offset;
if (addr >= 0x01E81000 && addr + 2 < 0x01E82000) if (addr >= 0x01E81000 && addr + 1 < 0x01E82000)
{ {
*retValue = *(u16*)(cheatPage + addr - 0x01E81000); *retValue = *(u16*)(cheatPage + addr - 0x01E81000);
return true; return true;
@ -250,7 +250,7 @@ static bool Cheat_Read16(const Handle processHandle, u32 offset, u16* retValue)
static bool Cheat_Read32(const Handle processHandle, u32 offset, u32* retValue) static bool Cheat_Read32(const Handle processHandle, u32 offset, u32* retValue)
{ {
u32 addr = *activeOffset() + offset; u32 addr = *activeOffset() + offset;
if (addr >= 0x01E81000 && addr + 4 < 0x01E82000) if (addr >= 0x01E81000 && addr + 3 < 0x01E82000)
{ {
*retValue = *(u32*)(cheatPage + addr - 0x01E81000); *retValue = *(u32*)(cheatPage + addr - 0x01E81000);
return true; return true;

View File

@ -336,6 +336,7 @@ void MiscellaneousMenu_SyncTimeDate(void)
cantStart = R_FAILED(res) || !isSocURegistered; cantStart = R_FAILED(res) || !isSocURegistered;
int utcOffset = 12; int utcOffset = 12;
int utcOffsetMinute = 0;
int absOffset; int absOffset;
do do
{ {
@ -344,14 +345,15 @@ void MiscellaneousMenu_SyncTimeDate(void)
absOffset = utcOffset - 12; absOffset = utcOffset - 12;
absOffset = absOffset < 0 ? -absOffset : absOffset; absOffset = absOffset < 0 ? -absOffset : absOffset;
posY = Draw_DrawFormattedString(10, 30, COLOR_WHITE, "Current UTC offset: %c%02d", utcOffset < 12 ? '-' : '+', absOffset); posY = Draw_DrawFormattedString(10, 30, COLOR_WHITE, "Current UTC offset: %c%02d%02d", utcOffset < 12 ? '-' : '+', absOffset, utcOffsetMinute);
posY = Draw_DrawFormattedString(10, posY + SPACING_Y, COLOR_WHITE, "Use DPAD Left/Right to change offset.\nPress A when done.") + SPACING_Y; posY = Draw_DrawFormattedString(10, posY + SPACING_Y, COLOR_WHITE, "Use DPAD Left/Right to change hour offset.\nUse DPAD Up/Down to change minute offset.\nPress A when done.") + SPACING_Y;
input = waitInput(); input = waitInput();
if(input & BUTTON_LEFT) utcOffset = (24 + utcOffset - 1) % 24; // ensure utcOffset >= 0 if(input & BUTTON_LEFT) utcOffset = (24 + utcOffset - 1) % 24; // ensure utcOffset >= 0
if(input & BUTTON_RIGHT) utcOffset = (utcOffset + 1) % 24; if(input & BUTTON_RIGHT) utcOffset = (utcOffset + 1) % 24;
if(input & BUTTON_UP) utcOffsetMinute = (utcOffsetMinute + 1) % 60;
if(input & BUTTON_DOWN) utcOffsetMinute = (60 + utcOffsetMinute - 1) % 60;
Draw_FlushFramebuffer(); Draw_FlushFramebuffer();
Draw_Unlock(); Draw_Unlock();
} }
@ -371,6 +373,7 @@ void MiscellaneousMenu_SyncTimeDate(void)
if(R_SUCCEEDED(res)) if(R_SUCCEEDED(res))
{ {
t += 3600 * utcOffset; t += 3600 * utcOffset;
t += 60 * utcOffsetMinute;
gmtime_r(&t, &localt); gmtime_r(&t, &localt);
res = ntpSetTimeDate(&localt); res = ntpSetTimeDate(&localt);
} }

View File

@ -55,37 +55,38 @@ typedef struct {
u8 z; u8 z;
} Pixel; } Pixel;
static u16 g_c[0x600];
static Pixel g_px[0x400];
void applyColorSettings(color_setting_t* cs) void applyColorSettings(color_setting_t* cs)
{ {
u16 c[0x600];
Pixel px[0x400];
u8 i = 0; u8 i = 0;
memset(c, 0, sizeof(c)); memset(g_c, 0, sizeof(g_c));
memset(px, 0, sizeof(px)); memset(g_px, 0, sizeof(g_px));
do { do {
px[i].r = i; g_px[i].r = i;
px[i].g = i; g_px[i].g = i;
px[i].b = i; g_px[i].b = i;
px[i].z = 0; g_px[i].z = 0;
} while(++i); } while(++i);
do { do {
*(c + i + 0x000) = px[i].r | (px[i].r << 8); *(g_c + i + 0x000) = g_px[i].r | (g_px[i].r << 8);
*(c + i + 0x100) = px[i].g | (px[i].g << 8); *(g_c + i + 0x100) = g_px[i].g | (g_px[i].g << 8);
*(c + i + 0x200) = px[i].b | (px[i].b << 8); *(g_c + i + 0x200) = g_px[i].b | (g_px[i].b << 8);
} while(++i); } while(++i);
colorramp_fill(c + 0x000, c + 0x100, c + 0x200, 0x100, cs); colorramp_fill(g_c + 0x000, g_c + 0x100, g_c + 0x200, 0x100, cs);
do { do {
px[i].r = *(c + i + 0x000) >> 8; g_px[i].r = *(g_c + i + 0x000) >> 8;
px[i].g = *(c + i + 0x100) >> 8; g_px[i].g = *(g_c + i + 0x100) >> 8;
px[i].b = *(c + i + 0x200) >> 8; g_px[i].b = *(g_c + i + 0x200) >> 8;
} while(++i); } while(++i);
writeLut((u32*)px); writeLut((u32*)g_px);
} }
Menu screenFiltersMenu = { Menu screenFiltersMenu = {
@ -143,10 +144,10 @@ void screenFiltersSetTemperature(int temperature)
memset(&cs, 0, sizeof(cs)); memset(&cs, 0, sizeof(cs));
cs.temperature = temperature; cs.temperature = temperature;
cs.gamma[0] = 1.0F; /*cs.gamma[0] = 1.0F;
cs.gamma[1] = 1.0F; cs.gamma[1] = 1.0F;
cs.gamma[2] = 1.0F; cs.gamma[2] = 1.0F;
cs.brightness = 1.0F; cs.brightness = 1.0F;*/
applyColorSettings(&cs); applyColorSettings(&cs);
} }

View File

@ -34,11 +34,12 @@
Menu sysconfigMenu = { Menu sysconfigMenu = {
"System configuration menu", "System configuration menu",
.nbItems = 3, .nbItems = 4,
{ {
{ "Toggle LEDs", METHOD, .method = &SysConfigMenu_ToggleLEDs }, { "Toggle LEDs", METHOD, .method = &SysConfigMenu_ToggleLEDs },
{ "Toggle Wireless", METHOD, .method = &SysConfigMenu_ToggleWireless }, { "Toggle Wireless", METHOD, .method = &SysConfigMenu_ToggleWireless },
{ "Toggle Power Button", METHOD, .method=&SysConfigMenu_TogglePowerButton }, { "Toggle Power Button", METHOD, .method=&SysConfigMenu_TogglePowerButton },
{ "Control Wireless connection", METHOD, .method = &SysConfigMenu_ControlWifi },
} }
}; };
@ -148,15 +149,69 @@ void SysConfigMenu_ToggleWireless(void)
while(!terminationRequest); while(!terminationRequest);
} }
void SysConfigMenu_TogglePowerButton(void) static void SysConfigMenu_ForceWifiConnection(int slot)
{ {
u32 mcuIRQMask; char ssid[0x20 + 1] = {0};
acuConfig config = {0};
ACU_CreateDefaultConfig(&config);
ACU_SetNetworkArea(&config, 2);
ACU_SetAllowApType(&config, 1 << slot);
ACU_SetRequestEulaVersion(&config);
Handle connectEvent = 0;
svcCreateEvent(&connectEvent, RESET_ONESHOT);
bool forcedConnection = false;
if(R_SUCCEEDED(ACU_ConnectAsync(&config, connectEvent)))
{
if(R_SUCCEEDED(svcWaitSynchronization(connectEvent, -1)))
{
ACU_GetSSID(ssid);
forcedConnection = true;
}
}
svcCloseHandle(connectEvent);
char infoString[80] = {0};
u32 infoStringColor = forcedConnection ? COLOR_GREEN : COLOR_RED;
if(forcedConnection)
sprintf(infoString, "Succesfully forced a connection to: %s", ssid);
else
sprintf(infoString, "Failed to connect to slot %d", slot + 1);
Draw_Lock(); Draw_Lock();
Draw_ClearFramebuffer(); Draw_ClearFramebuffer();
Draw_FlushFramebuffer(); Draw_FlushFramebuffer();
Draw_Unlock(); Draw_Unlock();
do
{
Draw_Lock();
Draw_DrawString(10, 10, COLOR_TITLE, "System configuration menu");
Draw_DrawString(10, 30, infoStringColor, infoString);
Draw_DrawString(10, 40, COLOR_WHITE, "Press B to go back.");
Draw_FlushFramebuffer();
Draw_Unlock();
u32 pressed = waitInputWithTimeout(1000);
if(pressed & BUTTON_B)
return;
}
while(!terminationRequest);
}
void SysConfigMenu_TogglePowerButton(void)
{
u32 mcuIRQMask;
Draw_Lock();
Draw_ClearFramebuffer();
Draw_FlushFramebuffer();
Draw_Unlock();
mcuHwcInit(); mcuHwcInit();
MCUHWC_ReadRegister(0x18, (u8*)&mcuIRQMask, 4); MCUHWC_ReadRegister(0x18, (u8*)&mcuIRQMask, 4);
mcuHwcExit(); mcuHwcExit();
@ -169,10 +224,10 @@ void SysConfigMenu_TogglePowerButton(void)
Draw_DrawString(10, 50, COLOR_WHITE, "Current status:"); Draw_DrawString(10, 50, COLOR_WHITE, "Current status:");
Draw_DrawString(100, 50, (((mcuIRQMask & 0x00000001) == 0x00000001) ? COLOR_RED : COLOR_GREEN), (((mcuIRQMask & 0x00000001) == 0x00000001) ? " DISABLED" : " ENABLED ")); Draw_DrawString(100, 50, (((mcuIRQMask & 0x00000001) == 0x00000001) ? COLOR_RED : COLOR_GREEN), (((mcuIRQMask & 0x00000001) == 0x00000001) ? " DISABLED" : " ENABLED "));
Draw_FlushFramebuffer(); Draw_FlushFramebuffer();
Draw_Unlock(); Draw_Unlock();
u32 pressed = waitInputWithTimeout(1000); u32 pressed = waitInputWithTimeout(1000);
if(pressed & BUTTON_A) if(pressed & BUTTON_A)
@ -188,3 +243,61 @@ void SysConfigMenu_TogglePowerButton(void)
} }
while(!terminationRequest); while(!terminationRequest);
} }
void SysConfigMenu_ControlWifi(void)
{
Draw_Lock();
Draw_ClearFramebuffer();
Draw_FlushFramebuffer();
Draw_Unlock();
int slot = 0;
char slotString[12] = {0};
sprintf(slotString, ">1< 2 3 ");
do
{
Draw_Lock();
Draw_DrawString(10, 10, COLOR_TITLE, "System configuration menu");
Draw_DrawString(10, 30, COLOR_WHITE, "Press A to force a connection to slot:");
Draw_DrawString(10, 40, COLOR_WHITE, slotString);
Draw_DrawString(10, 60, COLOR_WHITE, "Press B to go back.");
Draw_FlushFramebuffer();
Draw_Unlock();
u32 pressed = waitInputWithTimeout(1000);
if(pressed & BUTTON_A)
{
SysConfigMenu_ForceWifiConnection(slot);
Draw_Lock();
Draw_ClearFramebuffer();
Draw_FlushFramebuffer();
Draw_Unlock();
}
else if(pressed & BUTTON_LEFT)
{
slotString[slot * 4] = ' ';
slotString[(slot * 4) + 2] = ' ';
slot--;
if(slot == -1)
slot = 2;
slotString[slot * 4] = '>';
slotString[(slot * 4) + 2] = '<';
}
else if(pressed & BUTTON_RIGHT)
{
slotString[slot * 4] = ' ';
slotString[(slot * 4) + 2] = ' ';
slot++;
if(slot == 3)
slot = 0;
slotString[slot * 4] = '>';
slotString[(slot * 4) + 2] = '<';
}
else if(pressed & BUTTON_B)
return;
}
while(!terminationRequest);
}

View File

@ -19,7 +19,7 @@
*/ */
#include <stdint.h> #include <stdint.h>
#include <math.h> //#include <math.h>
#include "redshift/redshift.h" #include "redshift/redshift.h"
@ -282,8 +282,10 @@ interpolate_color(float a, const float *c1, const float *c2, float *c)
} }
/* Helper macro used in the fill functions */ /* Helper macro used in the fill functions */
#define F(Y, C) pow((Y) * setting->brightness * \ #define F(Y, C) ((Y) * white_point[C])
white_point[C], 1.0/setting->gamma[C])
/*#define F(Y, C) pow((Y) * setting->brightness * \
white_point[C], 1.0/setting->gamma[C])*/
void void
colorramp_fill(uint16_t *gamma_r, uint16_t *gamma_g, uint16_t *gamma_b, colorramp_fill(uint16_t *gamma_r, uint16_t *gamma_g, uint16_t *gamma_b,
@ -324,4 +326,4 @@ colorramp_fill_float(float *gamma_r, float *gamma_g, float *gamma_b,
} }
} }
#undef F #undef F

View File

@ -28,7 +28,7 @@ INCLUDES := include
ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft
DEFINES := -DARM11 -D_3DS DEFINES := -DARM11 -D_3DS
CFLAGS := -g -std=gnu11 -Wall -Wextra -Werror -O2 -mword-relocations \ CFLAGS := -g -std=gnu11 -Wall -Wextra -Werror -Os -mword-relocations \
-fomit-frame-pointer -ffunction-sections -fdata-sections \ -fomit-frame-pointer -ffunction-sections -fdata-sections \
$(ARCH) $(DEFINES) $(ARCH) $(DEFINES)

View File

@ -101,7 +101,7 @@ Result ReceiveNotification(SessionData *sessionData, u32 *notificationId)
if(processData == NULL || !processData->notificationEnabled || processData->nbPendingNotifications == 0) if(processData == NULL || !processData->notificationEnabled || processData->nbPendingNotifications == 0)
{ {
if(processData->nbPendingNotifications) if(processData != NULL && processData->nbPendingNotifications)
*notificationId = 0; *notificationId = 0;
return 0xD8806404; return 0xD8806404;
} }