Added Rosalina, see details

- see release notes
- ( ͡° ͜ʖ ͡°)( ͡° ͜ʖ ͡°)( ͡° ͜ʖ ͡°)
- (∩ ͡° ͜ʖ ͡°)⊃━☆゚
- ( ͡ᵔ ͜ʖ ͡ᵔ) ♫┌( ͡° ͜ʖ ͡°)┘♪ ♫└( ͡° ͜ʖ ͡°)┐♪
This commit is contained in:
TuxSH
2017-06-05 02:02:04 +02:00
parent 4429cb2095
commit 21db0d45bd
226 changed files with 20505 additions and 1320 deletions

View File

@@ -0,0 +1,284 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
/* This file was entirely written by fincs */
#include <3ds.h>
#include "3dsx.h"
#include "memory.h"
#define Log_PrintP(...) ((void)0)
#define MAXRELOCS 512
static _3DSX_Reloc s_relocBuf[MAXRELOCS];
u32 ldrArgvBuf[ARGVBUF_SIZE/4];
#define SEC_ASSERT(x) do { if (!(x)) { Log_PrintP("Assertion failed: %s", #x); return false; } } while (0)
typedef struct
{
void* segPtrs[3]; // code, rodata & data
u32 segAddrs[3];
u32 segSizes[3];
} _3DSX_LoadInfo;
static inline u32 TranslateAddr(u32 off, _3DSX_LoadInfo* d, u32* offsets)
{
if (off < offsets[0])
return d->segAddrs[0] + off;
if (off < offsets[1])
return d->segAddrs[1] + off - offsets[0];
return d->segAddrs[2] + off - offsets[1];
}
u32 IFile_Read2(IFile *file, void* buffer, u32 size, u32 offset)
{
Result res;
u64 total = 0;
file->pos = offset;
res = IFile_Read(file, &total, buffer, size);
return R_SUCCEEDED(res) ? total : 0;
}
bool Ldr_Get3dsxSize(u32* pSize, IFile *file)
{
_3DSX_Header hdr;
if (IFile_Read2(file, &hdr, sizeof(hdr), 0) != sizeof(hdr))
{
Log_PrintP("Cannot read 3DSX header");
return false;
}
if (hdr.magic != _3DSX_MAGIC)
{
Log_PrintP("Not a valid 3DSX file");
return false;
}
u32 segSizes[3];
segSizes[0] = (hdr.codeSegSize+0xFFF) &~ 0xFFF;
segSizes[1] = (hdr.rodataSegSize+0xFFF) &~ 0xFFF;
segSizes[2] = (hdr.dataSegSize+0xFFF) &~ 0xFFF;
SEC_ASSERT(segSizes[0] >= hdr.codeSegSize);
SEC_ASSERT(segSizes[1] >= hdr.rodataSegSize);
SEC_ASSERT(segSizes[2] >= hdr.dataSegSize);
// TODO: Check int overflow
*pSize = segSizes[0] + segSizes[1] + segSizes[2] + 0x1000; // One extra page reserved for settings/etc
return true;
}
static inline u32 min(u32 a, u32 b)
{
return a < b ? a : b;
}
Handle Ldr_CodesetFrom3dsx(const char* name, u32* codePages, u32 baseAddr, IFile *file, u64 tid)
{
u32 i,j,k,m;
Result res;
_3DSX_Header hdr;
IFile_Read2(file, &hdr, sizeof(hdr), 0);
_3DSX_LoadInfo d;
d.segSizes[0] = (hdr.codeSegSize+0xFFF) &~ 0xFFF;
d.segSizes[1] = (hdr.rodataSegSize+0xFFF) &~ 0xFFF;
d.segSizes[2] = (hdr.dataSegSize+0xFFF) &~ 0xFFF;
d.segPtrs[0] = codePages;
d.segPtrs[1] = (char*)d.segPtrs[0] + d.segSizes[0];
d.segPtrs[2] = (char*)d.segPtrs[1] + d.segSizes[1];
d.segAddrs[0] = baseAddr;
d.segAddrs[1] = d.segAddrs[0] + d.segSizes[0];
d.segAddrs[2] = d.segAddrs[1] + d.segSizes[1];
u32 offsets[2] = { d.segSizes[0], d.segSizes[0] + d.segSizes[1] };
u32* segLimit = d.segPtrs[2] + d.segSizes[2];
u32 readOffset = hdr.headerSize;
u32 nRelocTables = hdr.relocHdrSize/4;
SEC_ASSERT((3*4*nRelocTables) <= 0x1000);
u32* extraPage = (u32*)((char*)d.segPtrs[2] + d.segSizes[2]);
u32 extraPageAddr = d.segAddrs[2] + d.segSizes[2];
// Read the relocation headers
for (i = 0; i < 3; i ++)
{
if (IFile_Read2(file, &extraPage[i*nRelocTables], hdr.relocHdrSize, readOffset) != hdr.relocHdrSize)
{
Log_PrintP("Cannot read relheader %d", i);
return 0;
}
readOffset += hdr.relocHdrSize;
}
// Read the code segment
if (IFile_Read2(file, d.segPtrs[0], hdr.codeSegSize, readOffset) != hdr.codeSegSize)
{
Log_PrintP("Cannot read code segment");
return 0;
}
readOffset += hdr.codeSegSize;
// Read the rodata segment
if (IFile_Read2(file, d.segPtrs[1], hdr.rodataSegSize, readOffset) != hdr.rodataSegSize)
{
Log_PrintP("Cannot read rodata segment");
return 0;
}
readOffset += hdr.rodataSegSize;
// Read the data segment
u32 dataLoadSegSize = hdr.dataSegSize - hdr.bssSize;
if (IFile_Read2(file, d.segPtrs[2], dataLoadSegSize, readOffset) != dataLoadSegSize)
{
Log_PrintP("Cannot read data segment");
return 0;
}
readOffset += dataLoadSegSize;
// Relocate the segments
for (i = 0; i < 3; i ++)
{
for (j = 0; j < nRelocTables; j ++)
{
int nRelocs = extraPage[i*nRelocTables + j];
if (j >= (sizeof(_3DSX_RelocHdr)/4))
{
// Not using this header
readOffset += nRelocs;
continue;
}
u32* pos = (u32*)d.segPtrs[i];
u32* endPos = pos + (d.segSizes[i]/4);
SEC_ASSERT(endPos <= segLimit);
while (nRelocs)
{
u32 toDo = nRelocs > MAXRELOCS ? MAXRELOCS : nRelocs;
nRelocs -= toDo;
u32 readSize = toDo*sizeof(_3DSX_Reloc);
if (IFile_Read2(file, s_relocBuf, readSize, readOffset) != readSize)
{
Log_PrintP("Cannot read reloc table (%d,%d)", i, j);
return 0;
}
readOffset += readSize;
for (k = 0; k < toDo && pos < endPos; k ++)
{
pos += s_relocBuf[k].skip;
u32 nPatches = s_relocBuf[k].patch;
for (m = 0; m < nPatches && pos < endPos; m ++)
{
u32 inAddr = baseAddr + 4*(pos - codePages);
u32 origData = *pos;
u32 subType = origData >> (32-4);
u32 addr = TranslateAddr(origData &~ 0xF0000000, &d, offsets);
//Log_PrintP("%08lX<-%08lX", inAddr, addr);
switch (j)
{
case 0:
{
if (subType != 0)
{
Log_PrintP("Unsupported absolute reloc subtype (%lu)", subType);
return 0;
}
*pos = addr;
break;
}
case 1:
{
u32 data = addr - inAddr;
switch (subType)
{
case 0: *pos = data; break; // 32-bit signed offset
case 1: *pos = data &~ BIT(31); break; // 31-bit signed offset
default:
Log_PrintP("Unsupported relative reloc subtype (%lu)", subType);
return 0;
}
break;
}
}
pos++;
}
}
}
}
}
// Detect and fill _prm structure
PrmStruct* pst = (PrmStruct*) &codePages[1];
if (pst->magic == _PRM_MAGIC)
{
memset(extraPage, 0, 0x1000);
memcpy(extraPage, ldrArgvBuf, sizeof(ldrArgvBuf));
pst->pSrvOverride = extraPageAddr + 0xFFC;
pst->pArgList = extraPageAddr;
pst->runFlags |= RUNFLAG_APTCHAINLOAD;
s64 dummy;
bool isN3DS = svcGetSystemInfo(&dummy, 0x10001, 0) == 0;
if (isN3DS)
{
pst->heapSize = 48*1024*1024;
pst->linearHeapSize = 64*1024*1024;
} else
{
pst->heapSize = 24*1024*1024;
pst->linearHeapSize = 32*1024*1024;
}
}
// Create the codeset
CodeSetInfo csinfo;
memset(&csinfo, 0, sizeof(csinfo));
strncpy((char*)csinfo.name, name, 8);
csinfo.program_id = tid;
csinfo.text_addr = d.segAddrs[0];
csinfo.text_size = d.segSizes[0] >> 12;
csinfo.ro_addr = d.segAddrs[1];
csinfo.ro_size = d.segSizes[1] >> 12;
csinfo.rw_addr = d.segAddrs[2];
csinfo.rw_size = (d.segSizes[2] >> 12) + 1; // One extra page reserved for settings/etc
csinfo.text_size_total = csinfo.text_size;
csinfo.ro_size_total = csinfo.ro_size;
csinfo.rw_size_total = csinfo.rw_size;
Handle hCodeset = 0;
res = svcCreateCodeSet(&hCodeset, &csinfo, d.segPtrs[0], d.segPtrs[1], d.segPtrs[2]);
if (res)
{
Log_PrintP("svcCreateCodeSet: %08lX", res);
return 0;
}
return hCodeset;
}

View File

@@ -0,0 +1,60 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include "MyThread.h"
#include "memory.h"
static void _thread_begin(void* arg)
{
MyThread *t = (MyThread *)arg;
t->ep();
MyThread_Exit();
}
Result MyThread_Create(MyThread *t, void (*entrypoint)(void), void *stack, u32 stackSize, int prio, int affinity)
{
t->ep = entrypoint;
t->stacktop = (u8 *)stack + stackSize;
return svcCreateThread(&t->handle, _thread_begin, (u32)t, (u32*)t->stacktop, prio, affinity);
}
Result MyThread_Join(MyThread *thread, s64 timeout_ns)
{
if (thread == NULL) return 0;
Result res = svcWaitSynchronization(thread->handle, timeout_ns);
if(R_FAILED(res)) return res;
svcCloseHandle(thread->handle);
thread->handle = (Handle)0;
return res;
}
void MyThread_Exit(void)
{
svcExitThread();
}

View File

@@ -0,0 +1,101 @@
@ This paricular file is licensed under the following terms:
@ This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable
@ for any damages arising from the use of this software.
@
@ Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it
@ and redistribute it freely, subject to the following restrictions:
@
@ The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
@ If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
@
@ Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
@ This notice may not be removed or altered from any source distribution.
.arm
.balign 4
.macro SVC_BEGIN name
.section .text.\name, "ax", %progbits
.global \name
.type \name, %function
.align 2
.cfi_startproc
\name:
.endm
.macro SVC_END
.cfi_endproc
.endm
SVC_BEGIN svcCustomBackdoor
svc 0x80
bx lr
SVC_END
SVC_BEGIN svcConvertVAToPA
svc 0x90
bx lr
SVC_END
SVC_BEGIN svcFlushDataCacheRange
svc 0x91
bx lr
SVC_END
SVC_BEGIN svcFlushEntireDataCache
svc 0x92
bx lr
SVC_END
SVC_BEGIN svcInvalidateInstructionCacheRange
svc 0x93
bx lr
SVC_END
SVC_BEGIN svcInvalidateEntireInstructionCache
svc 0x94
bx lr
SVC_END
SVC_BEGIN svcMapProcessMemoryEx
svc 0xA0
bx lr
SVC_END
SVC_BEGIN svcUnmapProcessMemoryEx
svc 0xA1
bx lr
SVC_END
SVC_BEGIN svcControlMemoryEx
push {r0, r4, r5}
ldr r0, [sp, #0xC]
ldr r4, [sp, #0xC+0x4]
ldr r5, [sp, #0xC+0x8]
svc 0xA2
pop {r2, r4, r5}
str r1, [r2]
bx lr
SVC_END
SVC_BEGIN svcControlService
svc 0xB0
bx lr
SVC_END
SVC_BEGIN svcCopyHandle
str r0, [sp, #-4]!
svc 0xB1
ldr r2, [sp], #4
str r1, [r2]
bx lr
SVC_END
SVC_BEGIN svcTranslateHandle
str r0, [sp, #-4]!
svc 0xB2
ldr r2, [sp], #4
str r1, [r2]
bx lr
SVC_END

View File

@@ -0,0 +1,277 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include <3ds.h>
#include <stdarg.h>
#include "fmt.h"
#include "draw.h"
#include "font.h"
#include "memory.h"
#include "menu.h"
#include "utils.h"
u8 framebuffer_cache[FB_BOTTOM_SIZE];
static u32 gpuSavedFramebufferAddr1, gpuSavedFramebufferSelect, gpuSavedFramebufferFormat, gpuSavedFramebufferStride;
static RecursiveLock lock;
void Draw_Lock(void)
{
static bool lockInitialized = false;
if(!lockInitialized)
{
RecursiveLock_Init(&lock);
lockInitialized = true;
}
RecursiveLock_Lock(&lock);
}
void Draw_Unlock(void)
{
RecursiveLock_Unlock(&lock);
}
void Draw_DrawCharacter(u32 posX, u32 posY, u32 color, char character)
{
volatile u16 *const fb = (volatile u16 *const)FB_BOTTOM_VRAM_ADDR;
s32 y;
for(y = 0; y < 10; y++)
{
char charPos = font[character * 10 + y];
s32 x;
for(x = 6; x >= 1; x--)
{
u32 screenPos = (posX * SCREEN_BOT_HEIGHT * 2 + (SCREEN_BOT_HEIGHT - y - posY - 1) * 2) + (5 - x) * 2 * SCREEN_BOT_HEIGHT;
u32 pixelColor = ((charPos >> x) & 1) ? color : COLOR_BLACK;
fb[screenPos / 2] = pixelColor;
}
}
}
u32 Draw_DrawString(u32 posX, u32 posY, u32 color, const char *string)
{
for(u32 i = 0, line_i = 0; i < ((u32) strlen(string)); i++)
{
if(string[i] == '\n')
{
posY += SPACING_Y;
line_i = 0;
continue;
}
else if(line_i >= (SCREEN_BOT_WIDTH - posX) / SPACING_X)
{
// Make sure we never get out of the screen.
posY += SPACING_Y;
line_i = 0;
if(string[i] == ' ')
continue; // Spaces at the start look weird
}
Draw_DrawCharacter(posX + line_i * SPACING_X, posY, color, string[i]);
line_i++;
}
return posY;
}
u32 Draw_DrawFormattedString(u32 posX, u32 posY, u32 color, const char *fmt, ...)
{
char buf[DRAW_MAX_FORMATTED_STRING_SIZE + 1];
va_list args;
va_start(args, fmt);
vsprintf(buf, fmt, args);
va_end(args);
return Draw_DrawString(posX, posY, color, buf);
}
void Draw_FillFramebuffer(u32 value)
{
memset32(FB_BOTTOM_VRAM_ADDR, value, FB_BOTTOM_SIZE);
}
void Draw_ClearFramebuffer(void)
{
Draw_FillFramebuffer(0);
}
void Draw_SetupFramebuffer(void)
{
while((GPU_PSC0_CNT | GPU_PSC1_CNT | GPU_TRANSFER_CNT | GPU_CMDLIST_CNT) & 1);
memcpy(framebuffer_cache, FB_BOTTOM_VRAM_ADDR, FB_BOTTOM_SIZE);
gpuSavedFramebufferAddr1 = GPU_FB_BOTTOM_ADDR_1;
gpuSavedFramebufferSelect = GPU_FB_BOTTOM_SEL;
gpuSavedFramebufferFormat = GPU_FB_BOTTOM_FMT;
gpuSavedFramebufferStride = GPU_FB_BOTTOM_STRIDE;
GPU_FB_BOTTOM_SEL &= ~1;
GPU_FB_BOTTOM_ADDR_1 = FB_BOTTOM_VRAM_PA;
GPU_FB_BOTTOM_FMT = 0x80302;
GPU_FB_BOTTOM_STRIDE = 240 * 2;
Draw_FlushFramebuffer();
}
void Draw_RestoreFramebuffer(void)
{
memcpy(FB_BOTTOM_VRAM_ADDR, framebuffer_cache, FB_BOTTOM_SIZE);
GPU_FB_BOTTOM_ADDR_1 = gpuSavedFramebufferAddr1;
GPU_FB_BOTTOM_FMT = gpuSavedFramebufferFormat;
GPU_FB_BOTTOM_STRIDE = gpuSavedFramebufferStride;
GPU_FB_BOTTOM_SEL = gpuSavedFramebufferSelect;
Draw_FlushFramebuffer();
}
void Draw_FlushFramebuffer(void)
{
svcFlushProcessDataCache(CUR_PROCESS_HANDLE, FB_BOTTOM_VRAM_ADDR, FB_BOTTOM_SIZE);
}
u32 Draw_GetCurrentFramebufferAddress(bool top, bool left)
{
if(GPU_FB_BOTTOM_SEL & 1)
{
if(left)
return top ? GPU_FB_TOP_LEFT_ADDR_2 : GPU_FB_BOTTOM_ADDR_2;
else
return top ? GPU_FB_TOP_RIGHT_ADDR_2 : GPU_FB_BOTTOM_ADDR_2;
}
else
{
if(left)
return top ? GPU_FB_TOP_LEFT_ADDR_1 : GPU_FB_BOTTOM_ADDR_1;
else
return top ? GPU_FB_TOP_RIGHT_ADDR_1 : GPU_FB_BOTTOM_ADDR_1;
}
}
static inline void Draw_WriteUnaligned(u8 *dst, u32 tmp, u32 size)
{
memcpy(dst, &tmp, size);
}
void Draw_CreateBitmapHeader(u8 *dst, u32 width, u32 heigth)
{
static const u8 bmpHeaderTemplate[54] = {
0x42, 0x4D, 0xCC, 0xCC, 0xCC, 0xCC, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00,
0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00,
0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0x12, 0x0B, 0x00, 0x00, 0x12, 0x0B, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
memcpy(dst, bmpHeaderTemplate, 54);
Draw_WriteUnaligned(dst + 2, 54 + 3 * width * heigth, 4);
Draw_WriteUnaligned(dst + 0x12, width, 4);
Draw_WriteUnaligned(dst + 0x16, heigth, 4);
Draw_WriteUnaligned(dst + 0x22, 3 * width * heigth, 4);
}
static inline void Draw_ConvertPixelToBGR8(u8 *dst, const u8 *src, GSPGPU_FramebufferFormats srcFormat)
{
u8 red, green, blue;
switch(srcFormat)
{
case GSP_RGBA8_OES:
{
u32 px = *(u32 *)src;
dst[0] = (px >> 8) & 0xFF;
dst[1] = (px >> 16) & 0xFF;
dst[2] = (px >> 24) & 0xFF;
break;
}
case GSP_BGR8_OES:
{
dst[2] = src[2];
dst[1] = src[1];
dst[0] = src[0];
break;
}
case GSP_RGB565_OES:
{
// thanks neobrain
u16 px = *(u16 *)src;
blue = px & 0x1F;
green = (px >> 5) & 0x3F;
red = (px >> 11) & 0x1F;
dst[0] = (blue << 3) | (blue >> 2);
dst[1] = (green << 2) | (green >> 4);
dst[2] = (red << 3) | (red >> 2);
break;
}
case GSP_RGB5_A1_OES:
{
u16 px = *(u16 *)src;
blue = px & 0x1F;
green = (px >> 5) & 0x1F;
red = (px >> 10) & 0x1F;
dst[0] = (blue << 3) | (blue >> 2);
dst[1] = (green << 3) | (green >> 2);
dst[2] = (red << 3) | (red >> 2);
break;
}
case GSP_RGBA4_OES:
{
u16 px = *(u32 *)src;
blue = px & 0xF;
green = (px >> 4) & 0xF;
red = (px >> 8) & 0xF;
dst[0] = (blue << 4) | (blue >> 4);
dst[1] = (green << 4) | (green >> 4);
dst[2] = (red << 4) | (red >> 4);
break;
}
default: break;
}
}
static u8 line[3 * 400];
u8 *Draw_ConvertFrameBufferLine(bool top, bool left, u32 y)
{
GSPGPU_FramebufferFormats fmt = top ? (GSPGPU_FramebufferFormats)(GPU_FB_TOP_FMT & 7) : (GSPGPU_FramebufferFormats)(GPU_FB_BOTTOM_FMT & 7);
u32 width = top ? 400 : 320;
u8 formatSizes[] = { 4, 3, 2, 2, 2 };
u32 pa = Draw_GetCurrentFramebufferAddress(top, left);
u8 *addr = (u8 *)PA_PTR(pa);
for(u32 x = 0; x < width; x++)
Draw_ConvertPixelToBGR8(line + x * 3 , addr + (x * 240 + y) * formatSizes[(u8)fmt], fmt);
return line;
}

View File

@@ -0,0 +1,283 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include <3ds.h>
#include "errdisp.h"
#include "draw.h"
#include "menu.h"
#include "memory.h"
static inline void assertSuccess(Result res)
{
if(R_FAILED(res))
svcBreak(USERBREAK_PANIC);
}
static MyThread errDispThread;
static u8 ALIGN(8) errDispThreadStack[THREAD_STACK_SIZE];
static char userString[0x100 + 1] = {0};
MyThread *errDispCreateThread(void)
{
if(R_FAILED(MyThread_Create(&errDispThread, errDispThreadMain, errDispThreadStack, THREAD_STACK_SIZE, 0x18, CORE_SYSTEM)))
svcBreak(USERBREAK_PANIC);
return &errDispThread;
}
static inline u32 ERRF_DisplayRegisterValue(u32 posX, u32 posY, const char *name, u32 value)
{
return Draw_DrawFormattedString(posX, posY, COLOR_WHITE, "%-9s %08x", name, value);
}
void ERRF_DisplayError(ERRF_FatalErrInfo *info)
{
Draw_Lock();
u32 posY = Draw_DrawString(10, 10, COLOR_RED, userString[0] == 0 ? "An error occurred (ErrDisp)" : userString);
static const char *types[] = {
"generic", "corrupted", "card removed", "exception", "result failure", "logged", "invalid"
};
static const char *exceptionTypes[] = {
"prefetch abort", "data abort", "undefined instruction", "VFP", "invalid"
};
const char *type = (u32)info->type > (u32)ERRF_ERRTYPE_LOGGED ? types[6] : types[(u32)info->type];
posY = posY < 30 ? 30 : posY;
if(info->type == ERRF_ERRTYPE_EXCEPTION)
{
const char *exceptionType = (u32)info->data.exception_data.excep.type > (u32)ERRF_EXCEPTION_VFP ?
exceptionTypes[4] : exceptionTypes[(u32)info->data.exception_data.excep.type];
Draw_DrawFormattedString(10, posY, COLOR_WHITE, "Error type: exception (%s)", exceptionType);
}
else
Draw_DrawFormattedString(10, posY, COLOR_WHITE, "Error type: %s", type);
if(info->type != ERRF_ERRTYPE_CARD_REMOVED)
{
Handle processHandle;
Result res;
posY += SPACING_Y;
posY = Draw_DrawFormattedString(10, posY + SPACING_Y, COLOR_WHITE, "Process ID: %u", info->procId);
res = svcOpenProcess(&processHandle, info->procId);
if(R_SUCCEEDED(res))
{
u64 titleId;
char name[9] = { 0 };
svcGetProcessInfo((s64 *)name, processHandle, 0x10000);
svcGetProcessInfo((s64 *)&titleId, processHandle, 0x10001);
svcCloseHandle(processHandle);
posY = Draw_DrawFormattedString(10, posY + SPACING_Y, COLOR_WHITE, "Process name: %s", name);
posY = Draw_DrawFormattedString(10, posY + SPACING_Y, COLOR_WHITE, "Process title ID: 0x%016llx", titleId);
}
posY += SPACING_Y;
}
if(info->type == ERRF_ERRTYPE_EXCEPTION)
{
static const char *registerNames[] = {
"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12",
"sp", "lr", "pc", "cpsr"
};
u32 *regs = (u32 *)(&info->data.exception_data.regs);
for(u32 i = 0; i < 17; i += 2)
{
posY = ERRF_DisplayRegisterValue(10, posY + SPACING_Y, registerNames[i], regs[i]);
if(i != 16)
ERRF_DisplayRegisterValue(10 + 28 * SPACING_X, posY, registerNames[i + 1], i == 16 ? regs[20] : regs[i + 1]);
}
if(info->data.exception_data.excep.type == ERRF_EXCEPTION_PREFETCH_ABORT
|| info->data.exception_data.excep.type == ERRF_EXCEPTION_DATA_ABORT)
{
ERRF_DisplayRegisterValue(10 + 28 * SPACING_X, posY, "far", info->data.exception_data.excep.far);
posY = ERRF_DisplayRegisterValue(10, posY + SPACING_Y, "fsr", info->data.exception_data.excep.fsr);
}
else if(info->data.exception_data.excep.type == ERRF_EXCEPTION_VFP)
{
ERRF_DisplayRegisterValue(10 + 28 * SPACING_X, posY, "fpexc", info->data.exception_data.excep.fpexc);
posY = ERRF_DisplayRegisterValue(10, posY + SPACING_Y, "fpinst", info->data.exception_data.excep.fpinst);
ERRF_DisplayRegisterValue(10 + 28 * SPACING_X, posY, "fpinst2", info->data.exception_data.excep.fpinst2);
}
}
else if(info->type != ERRF_ERRTYPE_CARD_REMOVED)
{
if(info->type != ERRF_ERRTYPE_FAILURE)
posY = Draw_DrawFormattedString(10, posY + SPACING_Y, COLOR_WHITE, "Address: 0x%08x", info->pcAddr);
posY = Draw_DrawFormattedString(10, posY + SPACING_Y, COLOR_WHITE, "Error code: 0x%08x", info->resCode);
}
const char *desc;
switch(info->type)
{
case ERRF_ERRTYPE_CARD_REMOVED:
desc = "The Game Card was removed.";
break;
case ERRF_ERRTYPE_MEM_CORRUPT:
desc = "The System Memory has been damaged.";
break;
case ERRF_ERRTYPE_FAILURE:
info->data.failure_mesg[0x60] = 0; // make sure the last byte in the IPC buffer is NULL
desc = info->data.failure_mesg;
break;
default:
desc = "";
break;
}
posY += SPACING_Y;
if(desc[0] != 0)
posY = Draw_DrawString(10, posY + SPACING_Y, COLOR_WHITE, desc) + SPACING_Y;
posY = Draw_DrawString(10, posY + SPACING_Y, COLOR_WHITE, "Press any button to reboot");
Draw_FlushFramebuffer();
Draw_Unlock();
}
void ERRF_HandleCommands(void)
{
u32 *cmdbuf = getThreadCommandBuffer();
switch(cmdbuf[0] >> 16)
{
case 1: // Throw
{
ERRF_FatalErrInfo *info = (ERRF_FatalErrInfo *)(cmdbuf + 1);
menuEnter();
Draw_Lock();
Draw_ClearFramebuffer();
Draw_FlushFramebuffer();
ERRF_DisplayError(info);
/*
If we ever wanted to return:
draw_unlock();
menuLeave();
cmdbuf[0] = 0x10040;
cmdbuf[1] = 0;
but we don't
*/
waitInput();
svcKernelSetState(7);
__builtin_unreachable();
break;
}
case 2: // SetUserString
{
if(cmdbuf[0] != 0x20042 || (cmdbuf[2] & 0x3C0F) != 2)
{
cmdbuf[0] = 0x40;
cmdbuf[1] = 0xD9001830;
}
else
{
cmdbuf[0] = 0x20040;
u32 sz = cmdbuf[1] <= 0x100 ? sz : 0x100;
memcpy(userString, cmdbuf + 3, sz);
userString[sz] = 0;
}
break;
}
}
}
void errDispThreadMain(void)
{
Handle handles[2];
Handle serverHandle, clientHandle, sessionHandle = 0;
u32 replyTarget = 0;
s32 index;
Result res;
u32 *cmdbuf = getThreadCommandBuffer();
assertSuccess(svcCreatePort(&serverHandle, &clientHandle, "err:f", 1));
do
{
handles[0] = serverHandle;
handles[1] = sessionHandle;
if(replyTarget == 0) // k11
cmdbuf[0] = 0xFFFF0000;
res = svcReplyAndReceive(&index, handles, 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)
{
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

@@ -0,0 +1,315 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
/* File : barebones/ee_printf.c
This file contains an implementation of ee_printf that only requires a method to output a char to a UART without pulling in library code.
This code is based on a file that contains the following:
Copyright (C) 2002 Michael Ringgaard. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the project nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
*/
//TuxSH's changes: add support for 64-bit numbers, remove floating-point code
#include <3ds/types.h>
#include "memory.h"
#include "fmt.h"
#define ZEROPAD (1<<0) //Pad with zero
#define SIGN (1<<1) //Unsigned/signed long
#define PLUS (1<<2) //Show plus
#define SPACE (1<<3) //Spacer
#define LEFT (1<<4) //Left justified
#define HEX_PREP (1<<5) //0x
#define UPPERCASE (1<<6) //'ABCDEF'
#define IS_DIGIT(c) ((c) >= '0' && (c) <= '9')
static s32 skipAtoi(const char **s)
{
s32 i = 0;
while(IS_DIGIT(**s)) i = i * 10 + *((*s)++) - '0';
return i;
}
static char *processNumber(char *str, s64 num, bool isHex, s32 size, s32 precision, u32 type)
{
char sign = 0;
if(type & SIGN)
{
if(num < 0)
{
sign = '-';
num = -num;
size--;
}
else if(type & PLUS)
{
sign = '+';
size--;
}
else if(type & SPACE)
{
sign = ' ';
size--;
}
}
static const char *lowerDigits = "0123456789abcdef",
*upperDigits = "0123456789ABCDEF";
s32 i = 0;
char tmp[20];
const char *dig = (type & UPPERCASE) ? upperDigits : lowerDigits;
if(num == 0)
{
if(precision != 0) tmp[i++] = '0';
type &= ~HEX_PREP;
}
else
{
while(num != 0)
{
u64 base = isHex ? 16ULL : 10ULL;
tmp[i++] = dig[(u64)num % base];
num = (s64)((u64)num / base);
}
}
if(type & LEFT || precision != -1) type &= ~ZEROPAD;
if(type & HEX_PREP && isHex) size -= 2;
if(i > precision) precision = i;
size -= precision;
if(!(type & (ZEROPAD | LEFT))) while(size-- > 0) *str++ = ' ';
if(sign) *str++ = sign;
if(type & HEX_PREP && isHex)
{
*str++ = '0';
*str++ = 'x';
}
if(type & ZEROPAD) while(size-- > 0) *str++ = '0';
while(i < precision--) *str++ = '0';
while(i-- > 0) *str++ = tmp[i];
while(size-- > 0) *str++ = ' ';
return str;
}
int vsprintf(char *buf, const char *fmt, va_list args)
{
char *str;
for(str = buf; *fmt; fmt++)
{
if(*fmt != '%')
{
*str++ = *fmt;
continue;
}
//Process flags
u32 flags = 0; //Flags to number()
bool loop = true;
while(loop)
{
switch(*++fmt)
{
case '-': flags |= LEFT; break;
case '+': flags |= PLUS; break;
case ' ': flags |= SPACE; break;
case '#': flags |= HEX_PREP; break;
case '0': flags |= ZEROPAD; break;
default: loop = false; break;
}
}
//Get field width
s32 fieldWidth = -1; //Width of output field
if(IS_DIGIT(*fmt)) fieldWidth = skipAtoi(&fmt);
else if(*fmt == '*')
{
fmt++;
fieldWidth = va_arg(args, s32);
if(fieldWidth < 0)
{
fieldWidth = -fieldWidth;
flags |= LEFT;
}
}
//Get the precision
s32 precision = -1; //Min. # of digits for integers; max number of chars for from string
if(*fmt == '.')
{
fmt++;
if(IS_DIGIT(*fmt)) precision = skipAtoi(&fmt);
else if(*fmt == '*')
{
fmt++;
precision = va_arg(args, s32);
}
if(precision < 0) precision = 0;
}
//Get the conversion qualifier
u32 integerType = 0;
if(*fmt == 'l')
{
if(*++fmt == 'l')
{
fmt++;
integerType = 1;
}
}
else if(*fmt == 'h')
{
if(*++fmt == 'h')
{
fmt++;
integerType = 3;
}
else integerType = 2;
}
bool isHex;
switch(*fmt)
{
case 'c':
if(!(flags & LEFT)) while(--fieldWidth > 0) *str++ = ' ';
*str++ = (u8)va_arg(args, s32);
while(--fieldWidth > 0) *str++ = ' ';
continue;
case 's':
{
char *s = va_arg(args, char *);
if(!s) s = "<NULL>";
u32 len = (precision != -1) ? strnlen(s, precision) : strlen(s);
if(!(flags & LEFT)) while((s32)len < fieldWidth--) *str++ = ' ';
for(u32 i = 0; i < len; i++) *str++ = *s++;
while((s32)len < fieldWidth--) *str++ = ' ';
continue;
}
case 'p':
if(fieldWidth == -1)
{
fieldWidth = 8;
flags |= ZEROPAD;
}
str = processNumber(str, va_arg(args, u32), true, fieldWidth, precision, flags);
continue;
//Integer number formats - set up the flags and "break"
case 'X':
flags |= UPPERCASE;
case 'x':
isHex = true;
break;
case 'd':
case 'i':
flags |= SIGN;
case 'u':
isHex = false;
break;
default:
if(*fmt != '%') *str++ = '%';
if(*fmt) *str++ = *fmt;
else fmt--;
continue;
}
s64 num;
if(flags & SIGN)
{
if(integerType == 1) num = va_arg(args, s64);
else num = va_arg(args, s32);
if(integerType == 2) num = (s16)num;
else if(integerType == 3) num = (s8)num;
}
else
{
if(integerType == 1) num = va_arg(args, u64);
else num = va_arg(args, u32);
if(integerType == 2) num = (u16)num;
else if(integerType == 3) num = (u8)num;
}
str = processNumber(str, num, isHex, fieldWidth, precision, flags);
}
*str = 0;
return str - buf;
}
int sprintf(char *buf, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
int res = vsprintf(buf, fmt, args);
va_end(args);
return res;
}

View File

@@ -0,0 +1,179 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include <3ds.h>
#include <string.h>
#include "fsreg.h"
#include "services.h"
#include "csvc.h"
static Handle fsregHandle;
static int fsregRefCount;
Result fsregInit(void)
{
Result ret = 0;
if(AtomicPostIncrement(&fsregRefCount))
return 0;
ret = svcControlService(SERVICEOP_STEAL_CLIENT_SESSION, &fsregHandle, "fs:REG");
while(ret == 0x9401BFE)
{
svcSleepThread(500 * 1000LL);
ret = svcControlService(SERVICEOP_STEAL_CLIENT_SESSION, &fsregHandle, "fs:REG");
}
if(R_FAILED(ret))
AtomicDecrement(&fsregRefCount);
return ret;
}
void fsregExit(void)
{
if(AtomicDecrement(&fsregRefCount) || !fsregHandle)
return;
svcCloseHandle(fsregHandle);
}
Handle fsregGetHandle(void)
{
return fsregHandle;
}
Result fsregSetupPermissions(void)
{
u32 pid;
Result res;
FS_ProgramInfo info;
u32 storage[8] = {0};
storage[6] = 0x800 | 0x400 | 0x80 | 0x1; // SDMC access and NAND access flags
info.programId = 0x0004013000006902LL; // Rosalina TID
info.mediaType = MEDIATYPE_NAND;
if(R_SUCCEEDED(res = svcGetProcessId(&pid, 0xFFFF8001))) // 0xFFFF8001 is an handle to the active process
res = FSREG_Register(pid, 0xFFFF000000000000LL, &info, (u8*)storage);
return res;
}
Result FSREG_CheckHostLoadId(u64 prog_handle)
{
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = IPC_MakeHeader(0x406,2,0); // 0x4060080
cmdbuf[1] = (u32) (prog_handle);
cmdbuf[2] = (u32) (prog_handle >> 32);
Result ret = 0;
if(R_FAILED(ret = svcSendSyncRequest(fsregHandle)))
return ret;
return cmdbuf[1];
}
Result FSREG_LoadProgram(u64 *prog_handle, FS_ProgramInfo *title)
{
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = IPC_MakeHeader(0x404,4,0); // 0x4040100
memcpy(&cmdbuf[1], &title->programId, sizeof(u64));
*(u8 *)&cmdbuf[3] = title->mediaType;
memcpy(((u8 *)&cmdbuf[3])+1, &title->padding, 7);
Result ret = 0;
if(R_FAILED(ret = svcSendSyncRequest(fsregHandle)))
return ret;
*prog_handle = *(u64 *)&cmdbuf[2];
return cmdbuf[1];
}
Result FSREG_GetProgramInfo(exheader_header *exheader, u32 entry_count, u64 prog_handle)
{
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = IPC_MakeHeader(0x403,3,0); // 0x40300C0
cmdbuf[1] = entry_count;
*(u64 *)&cmdbuf[2] = prog_handle;
cmdbuf[64] = ((entry_count << 10) << 14) | 2;
cmdbuf[65] = (u32) exheader;
Result ret = 0;
if(R_FAILED(ret = svcSendSyncRequest(fsregHandle)))
return ret;
return cmdbuf[1];
}
Result FSREG_UnloadProgram(u64 prog_handle)
{
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = IPC_MakeHeader(0x405,2,0); // 0x4050080
cmdbuf[1] = (u32) (prog_handle);
cmdbuf[2] = (u32) (prog_handle >> 32);
Result ret = 0;
if(R_FAILED(ret = svcSendSyncRequest(fsregHandle)))
return ret;
return cmdbuf[1];
}
Result FSREG_Unregister(u32 pid)
{
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = IPC_MakeHeader(0x402,1,0); // 0x4020040
cmdbuf[1] = pid;
Result ret = 0;
if(R_FAILED(ret = svcSendSyncRequest(fsregHandle)))
return ret;
return cmdbuf[1];
}
Result FSREG_Register(u32 pid, u64 prog_handle, FS_ProgramInfo *info, void *storageinfo)
{
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = IPC_MakeHeader(0x401,0xf,0); // 0x40103C0
cmdbuf[1] = pid;
*(u64 *)&cmdbuf[2] = prog_handle;
memcpy(&cmdbuf[4], &info->programId, sizeof(u64));
*(u8 *)&cmdbuf[6] = info->mediaType;
memcpy(((u8 *)&cmdbuf[6])+1, &info->padding, 7);
memcpy((u8 *)&cmdbuf[8], storageinfo, 32);
Result ret = 0;
if(R_FAILED(ret = svcSendSyncRequest(fsregHandle)))
return ret;
return cmdbuf[1];
}

View File

@@ -0,0 +1,60 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include "gdb.h"
#include "gdb/net.h"
void GDB_InitializeContext(GDBContext *ctx)
{
RecursiveLock_Init(&ctx->lock);
RecursiveLock_Lock(&ctx->lock);
svcCreateEvent(&ctx->continuedEvent, RESET_ONESHOT);
svcCreateEvent(&ctx->clientAcceptedEvent, RESET_STICKY);
ctx->eventToWaitFor = ctx->clientAcceptedEvent;
ctx->continueFlags = (DebugFlags)(DBG_SIGNAL_FAULT_EXCEPTION_EVENTS | DBG_INHIBIT_USER_CPU_EXCEPTION_HANDLERS);
RecursiveLock_Unlock(&ctx->lock);
}
void GDB_FinalizeContext(GDBContext *ctx)
{
RecursiveLock_Lock(&ctx->lock);
svcClearEvent(ctx->clientAcceptedEvent);
svcCloseHandle(ctx->clientAcceptedEvent);
svcCloseHandle(ctx->continuedEvent);
RecursiveLock_Unlock(&ctx->lock);
}
GDB_DECLARE_HANDLER(Unsupported)
{
return GDB_ReplyEmpty(ctx);
}

View File

@@ -0,0 +1,130 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include "gdb/breakpoints.h"
#define _REENT_ONLY
#include <errno.h>
u32 GDB_FindClosestBreakpointSlot(GDBContext *ctx, u32 address)
{
if(ctx->nbBreakpoints == 0 || address <= ctx->breakpoints[0].address)
return 0;
else if(address > ctx->breakpoints[ctx->nbBreakpoints - 1].address)
return ctx->nbBreakpoints;
u32 a = 0, b = ctx->nbBreakpoints - 1, m;
do
{
m = (a + b) / 2;
if(ctx->breakpoints[m].address < address)
a = m;
else if(ctx->breakpoints[m].address > address)
b = m;
else
return m;
}
while(b - a > 1);
return b;
}
int GDB_GetBreakpointInstruction(u32 *instruction, GDBContext *ctx, u32 address)
{
u32 id = GDB_FindClosestBreakpointSlot(ctx, address);
if(id == ctx->nbBreakpoints || ctx->breakpoints[id].address != address)
return -EINVAL;
if(instruction != NULL)
*instruction = ctx->breakpoints[id].savedInstruction;
return 0;
}
int GDB_AddBreakpoint(GDBContext *ctx, u32 address, bool thumb, bool persist)
{
u32 id = GDB_FindClosestBreakpointSlot(ctx, address);
if(id != ctx->nbBreakpoints && ctx->breakpoints[id].instructionSize != 0 && ctx->breakpoints[id].address == address)
return 0;
else if(ctx->nbBreakpoints == MAX_BREAKPOINT)
return -EBUSY;
else if((thumb && (address & 1) != 0) || (!thumb && (address & 3) != 0))
return -EINVAL;
for(u32 i = ctx->nbBreakpoints; i > id && i != 0; i--)
ctx->breakpoints[i] = ctx->breakpoints[i - 1];
ctx->nbBreakpoints++;
Breakpoint *bkpt = &ctx->breakpoints[id];
u32 instr = thumb ? BREAKPOINT_INSTRUCTION_THUMB : BREAKPOINT_INSTRUCTION_ARM;
if(R_FAILED(svcReadProcessMemory(&bkpt->savedInstruction, ctx->debug, address, thumb ? 2 : 4)) ||
R_FAILED(svcWriteProcessMemory(ctx->debug, &instr, address, thumb ? 2 : 4)))
{
for(u32 i = id; i < ctx->nbBreakpoints - 1; i++)
ctx->breakpoints[i] = ctx->breakpoints[i + 1];
memset(&ctx->breakpoints[--ctx->nbBreakpoints], 0, sizeof(Breakpoint));
return -EFAULT;
}
bkpt->instructionSize = thumb ? 2 : 4;
bkpt->address = address;
bkpt->persistent = persist;
return 0;
}
int GDB_DisableBreakpointById(GDBContext *ctx, u32 id)
{
Breakpoint *bkpt = &ctx->breakpoints[id];
if(R_FAILED(svcWriteProcessMemory(ctx->debug, &bkpt->savedInstruction, bkpt->address, bkpt->instructionSize)))
return -EFAULT;
else return 0;
}
int GDB_RemoveBreakpoint(GDBContext *ctx, u32 address)
{
u32 id = GDB_FindClosestBreakpointSlot(ctx, address);
if(id == ctx->nbBreakpoints || ctx->breakpoints[id].address != address)
return -EINVAL;
int r = GDB_DisableBreakpointById(ctx, id);
if(r != 0)
return r;
else
{
for(u32 i = id; i < ctx->nbBreakpoints - 1; i++)
ctx->breakpoints[i] = ctx->breakpoints[i + 1];
memset(&ctx->breakpoints[--ctx->nbBreakpoints], 0, sizeof(Breakpoint));
return 0;
}
}

View File

@@ -0,0 +1,512 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include "gdb/debug.h"
#include "gdb/verbose.h"
#include "gdb/net.h"
#include "gdb/thread.h"
#include "gdb/mem.h"
#include "gdb/watchpoints.h"
#include "fmt.h"
#include <stdlib.h>
#include <signal.h>
/*
Since we can't select particular threads to continue (and that's uncompliant behavior):
- if we continue the current thread, continue all threads
- otherwise, leaves all threads stopped but make the client believe it's continuing
*/
GDB_DECLARE_HANDLER(Detach)
{
ctx->state = GDB_STATE_CLOSING;
return GDB_ReplyOk(ctx);
}
GDB_DECLARE_HANDLER(Kill)
{
ctx->state = GDB_STATE_CLOSING;
ctx->flags |= GDB_FLAG_TERMINATE_PROCESS;
return 0;
}
GDB_DECLARE_HANDLER(Break)
{
if(!(ctx->flags & GDB_FLAG_PROCESS_CONTINUING))
return GDB_SendPacket(ctx, "S02", 3);
else
{
ctx->flags &= ~GDB_FLAG_PROCESS_CONTINUING;
return 0;
}
}
static void GDB_ContinueExecution(GDBContext *ctx)
{
ctx->selectedThreadId = ctx->selectedThreadIdForContinuing = 0;
svcContinueDebugEvent(ctx->debug, ctx->continueFlags);
ctx->flags |= GDB_FLAG_PROCESS_CONTINUING;
}
GDB_DECLARE_HANDLER(Continue)
{
char *addrStart = NULL;
u32 addr = 0;
if(ctx->selectedThreadIdForContinuing != 0 && ctx->selectedThreadIdForContinuing != ctx->currentThreadId)
return 0;
if(ctx->commandData[-1] == 'C')
{
if(ctx->commandData[0] == 0 || ctx->commandData[1] == 0 || (ctx->commandData[2] != 0 && ctx->commandData[2] == ';'))
return GDB_ReplyErrno(ctx, EILSEQ);
// Signal ignored...
if(ctx->commandData[2] == ';')
addrStart = ctx->commandData + 3;
}
else
{
if(ctx->commandData[0] != 0)
addrStart = ctx->commandData;
}
if(addrStart != NULL && ctx->currentThreadId != 0)
{
ThreadContext regs;
if(GDB_ParseHexIntegerList(&addr, ctx->commandData + 3, 1, 0) == NULL)
return GDB_ReplyErrno(ctx, EILSEQ);
Result r = svcGetDebugThreadContext(&regs, ctx->debug, ctx->currentThreadId, THREADCONTEXT_CONTROL_CPU_SPRS);
if(R_SUCCEEDED(r))
{
regs.cpu_registers.pc = addr;
r = svcSetDebugThreadContext(ctx->debug, ctx->currentThreadId, &regs, THREADCONTEXT_CONTROL_CPU_SPRS);
}
}
GDB_ContinueExecution(ctx);
return 0;
}
GDB_DECLARE_VERBOSE_HANDLER(Continue)
{
char *pos = ctx->commandData;
bool currentThreadFound = false;
while(pos != NULL && *pos != 0 && !currentThreadFound)
{
if(*pos != 'c' && *pos != 'C')
return GDB_ReplyErrno(ctx, EPERM);
pos += *pos == 'C' ? 3 : 1;
if(*pos++ != ':') // default action found
{
currentThreadFound = true;
break;
}
char *nextpos = (char *)strchr(pos, ';');
if(strncmp(pos, "-1", 2) == 0)
currentThreadFound = true;
else
{
u32 threadId;
if(GDB_ParseHexIntegerList(&threadId, pos, 1, ';') == NULL)
return GDB_ReplyErrno(ctx, EILSEQ);
currentThreadFound = currentThreadFound || threadId == ctx->currentThreadId;
}
pos = nextpos;
}
if(ctx->currentThreadId == 0 || currentThreadFound)
GDB_ContinueExecution(ctx);
return 0;
}
GDB_DECLARE_HANDLER(GetStopReason)
{
return GDB_SendStopReply(ctx, &ctx->latestDebugEvent);
}
static int GDB_ParseCommonThreadInfo(char *out, GDBContext *ctx, int sig)
{
u32 threadId = ctx->currentThreadId;
ThreadContext regs;
s64 dummy;
u32 core;
Result r = svcGetDebugThreadContext(&regs, ctx->debug, threadId, THREADCONTEXT_CONTROL_ALL);
int n = sprintf(out, "T%02xthread:%x;", sig, threadId);
if(R_FAILED(r))
return n;
r = svcGetDebugThreadParam(&dummy, &core, ctx->debug, ctx->currentThreadId, DBGTHREAD_PARAMETER_CPU_CREATOR); // Creator = "first ran, and running the thread"
if(R_SUCCEEDED(r))
n += sprintf(out + n, "core:%x;", core);
if(ctx->isGDB)
{
for(u32 i = 0; i <= 12; i++)
n += sprintf(out + n, "%x:%08x;", i, __builtin_bswap32(regs.cpu_registers.r[i]));
}
n += sprintf(out + n, "d:%08x;e:%08x;f:%08x;19:%08x;",
__builtin_bswap32(regs.cpu_registers.sp), __builtin_bswap32(regs.cpu_registers.lr), __builtin_bswap32(regs.cpu_registers.pc),
__builtin_bswap32(regs.cpu_registers.cpsr));
if(ctx->isGDB)
{
for(u32 i = 0; i < 16; i++)
{
u64 val;
memcpy(&val, &regs.fpu_registers.d[i], 8);
n += sprintf(out + n, "%x:%016llx;", 26 + i, __builtin_bswap64(val));
}
n += sprintf(out + n, "2a:%08x;2b:%08x;", __builtin_bswap32(regs.fpu_registers.fpscr), __builtin_bswap32(regs.fpu_registers.fpexc));
}
return n;
}
void GDB_PreprocessDebugEvent(GDBContext *ctx, DebugEventInfo *info)
{
switch(info->type)
{
case DBGEVENT_ATTACH_THREAD:
{
if(ctx->nbThreads == MAX_DEBUG_THREAD)
svcBreak(USERBREAK_ASSERT);
else
{
ctx->threadInfos[ctx->nbThreads].id = info->thread_id;
ctx->threadInfos[ctx->nbThreads++].tls = info->attach_thread.thread_local_storage;
}
break;
}
case DBGEVENT_EXIT_THREAD:
{
u32 i;
for(i = 0; i < ctx->nbThreads && ctx->threadInfos[i].id != info->thread_id; i++);
if(i == ctx->nbThreads || ctx->threadInfos[i].id != info->thread_id)
svcBreak(USERBREAK_ASSERT);
else
{
for(u32 j = i; j < ctx->nbThreads - 1; j++)
memcpy(ctx->threadInfos + j, ctx->threadInfos + j + 1, sizeof(ThreadInfo));
memset(ctx->threadInfos + --ctx->nbThreads, 0, sizeof(ThreadInfo));
}
break;
}
case DBGEVENT_EXIT_PROCESS:
{
ctx->processEnded = true;
ctx->processExited = info->exit_process.reason == EXITPROCESS_EVENT_EXIT;
break;
}
case DBGEVENT_OUTPUT_STRING:
{
if(info->output_string.string_addr >= 0xFFFFFFFE)
{
u32 sz = info->output_string.string_size, addr = info->output_string.string_addr, threadId = info->thread_id;
memset(info, 0, sizeof(DebugEventInfo));
info->type = (addr == 0xFFFFFFFF) ? DBGEVENT_SYSCALL_OUT : DBGEVENT_SYSCALL_IN;
info->thread_id = threadId;
info->flags = 1;
info->syscall.syscall = sz;
}
break;
}
case DBGEVENT_EXCEPTION:
{
switch(info->exception.type)
{
case EXCEVENT_UNDEFINED_INSTRUCTION:
{
// kernel bugfix for thumb mode
ThreadContext regs;
Result r = svcGetDebugThreadContext(&regs, ctx->debug, info->thread_id, THREADCONTEXT_CONTROL_CPU_SPRS);
if(R_SUCCEEDED(r) && (regs.cpu_registers.cpsr & 0x20) != 0)
{
regs.cpu_registers.pc += 2;
r = svcSetDebugThreadContext(ctx->debug, info->thread_id, &regs, THREADCONTEXT_CONTROL_CPU_SPRS);
}
break;
}
default:
break;
}
}
default:
break;
}
}
int GDB_SendStopReply(GDBContext *ctx, const DebugEventInfo *info)
{
char buffer[GDB_BUF_LEN + 1];
switch(info->type)
{
case DBGEVENT_ATTACH_PROCESS:
break; // Dismissed
case DBGEVENT_ATTACH_THREAD:
{
if(info->attach_thread.creator_thread_id == 0 || !ctx->catchThreadEvents)
break; // Dismissed
else
{
ctx->currentThreadId = info->thread_id;
return GDB_SendPacket(ctx, "T05create:;", 10);
}
}
case DBGEVENT_EXIT_THREAD:
{
if(ctx->catchThreadEvents && info->exit_thread.reason < EXITTHREAD_EVENT_EXIT_PROCESS)
{
// no signal, SIGTERM, SIGQUIT (process exited), SIGTERM (process terminated)
static int threadExitRepliesSigs[] = { 0, SIGTERM, SIGQUIT, SIGTERM };
return GDB_SendFormattedPacket(ctx, "w%02x;%x", threadExitRepliesSigs[(u32)info->exit_thread.reason], info->thread_id);
}
break;
}
case DBGEVENT_EXIT_PROCESS:
{
// exited (no error / unhandled exception), SIGTERM (process terminated) * 2
static const char *processExitReplies[] = { "W00", "X0f", "X0f" };
return GDB_SendPacket(ctx, processExitReplies[(u32)info->exit_process.reason], 3);
}
case DBGEVENT_EXCEPTION:
{
ExceptionEvent exc = info->exception;
switch(exc.type)
{
case EXCEVENT_UNDEFINED_INSTRUCTION:
case EXCEVENT_PREFETCH_ABORT: // doesn't include hardware breakpoints
case EXCEVENT_DATA_ABORT: // doesn't include hardware watchpoints
case EXCEVENT_UNALIGNED_DATA_ACCESS:
case EXCEVENT_UNDEFINED_SYSCALL:
{
u32 signum = exc.type == EXCEVENT_UNDEFINED_INSTRUCTION ? SIGILL :
(exc.type == EXCEVENT_UNDEFINED_SYSCALL ? SIGSYS : SIGSEGV);
ctx->currentThreadId = info->thread_id;
GDB_ParseCommonThreadInfo(buffer, ctx, signum);
return GDB_SendFormattedPacket(ctx, "%s", buffer);
}
case EXCEVENT_ATTACH_BREAK:
return GDB_SendPacket(ctx, "S00", 3);
case EXCEVENT_STOP_POINT:
{
ctx->currentThreadId = info->thread_id;
switch(exc.stop_point.type)
{
case STOPPOINT_SVC_FF:
{
GDB_ParseCommonThreadInfo(buffer, ctx, SIGTRAP);
return GDB_SendFormattedPacket(ctx, "%sswbreak:;", buffer);
break;
}
case STOPPOINT_BREAKPOINT:
{
// /!\ Not actually implemented (and will never be)
GDB_ParseCommonThreadInfo(buffer, ctx, SIGTRAP);
return GDB_SendFormattedPacket(ctx, "%shwbreak:;", buffer);
break;
}
case STOPPOINT_WATCHPOINT:
{
const char *kinds = "arwa";
WatchpointKind kind = GDB_GetWatchpointKind(ctx, exc.stop_point.fault_information);
if(kind == WATCHPOINT_DISABLED)
GDB_SendDebugString(ctx, "Warning: unknown watchpoint encountered!\n");
GDB_ParseCommonThreadInfo(buffer, ctx, SIGTRAP);
return GDB_SendFormattedPacket(ctx, "%s%cwatch:%08x;", buffer, kinds[(u32)kind], exc.stop_point.fault_information);
break;
}
default:
break;
}
}
case EXCEVENT_USER_BREAK:
{
ctx->currentThreadId = info->thread_id;
GDB_ParseCommonThreadInfo(buffer, ctx, SIGINT);
return GDB_SendFormattedPacket(ctx, "%s", buffer);
//TODO
}
case EXCEVENT_DEBUGGER_BREAK:
{
u32 threadIds[4];
u32 nbThreads = 0;
for(u32 i = 0; i < 4; i++)
{
if(exc.debugger_break.thread_ids[i] > 0)
threadIds[nbThreads++] = (u32)exc.debugger_break.thread_ids[i];
}
u32 currentThreadId = nbThreads > 0 ? GDB_GetCurrentThreadFromList(ctx, threadIds, nbThreads) : GDB_GetCurrentThread(ctx);
s64 dummy;
u32 mask = 0;
svcGetDebugThreadParam(&dummy, &mask, ctx->debug, currentThreadId, DBGTHREAD_PARAMETER_SCHEDULING_MASK_LOW);
if(mask == 1)
{
ctx->currentThreadId = currentThreadId;
GDB_ParseCommonThreadInfo(buffer, ctx, SIGINT);
return GDB_SendFormattedPacket(ctx, "%s", buffer);
}
else
return GDB_SendPacket(ctx, "S02", 3);
}
default:
break;
}
}
case DBGEVENT_SYSCALL_IN:
{
ctx->currentThreadId = info->thread_id;
GDB_ParseCommonThreadInfo(buffer, ctx, SIGTRAP);
return GDB_SendFormattedPacket(ctx, "%ssyscall_entry:%02x;", buffer, info->syscall.syscall);
}
case DBGEVENT_SYSCALL_OUT:
{
ctx->currentThreadId = info->thread_id;
GDB_ParseCommonThreadInfo(buffer, ctx, SIGTRAP);
return GDB_SendFormattedPacket(ctx, "%ssyscall_return:%02x;", buffer, info->syscall.syscall);
}
case DBGEVENT_OUTPUT_STRING:
{
u32 addr = info->output_string.string_addr;
u32 remaining = info->output_string.string_size;
u32 sent = 0;
int total = 0;
while(remaining > 0)
{
u32 pending = (GDB_BUF_LEN - 1) / 2;
pending = pending < remaining ? pending : remaining;
int res = GDB_SendMemory(ctx, "O", 1, addr + sent, pending);
if(res < 0 || (u32) res != 5 + 2 * pending)
break;
sent += pending;
remaining -= pending;
total += res;
}
return total;
}
default:
break;
}
return 0;
}
/*
Only 1 blocking event can be enqueued at a time: they preempt all the other threads.
The only "non-blocking" event that is implemented is EXIT PROCESS (but it's a very special case)
*/
int GDB_HandleDebugEvents(GDBContext *ctx)
{
if(ctx->state == GDB_STATE_CLOSING)
return -1;
DebugEventInfo info;
Result rdbg = svcGetProcessDebugEvent(&info, ctx->debug);
if(R_FAILED(rdbg))
return -1;
GDB_PreprocessDebugEvent(ctx, &info);
int ret = 0;
bool continueAutomatically = info.type == DBGEVENT_OUTPUT_STRING || info.type == DBGEVENT_ATTACH_PROCESS ||
(info.type == DBGEVENT_ATTACH_THREAD && (info.attach_thread.creator_thread_id == 0 || !ctx->catchThreadEvents)) ||
(info.type == DBGEVENT_EXIT_THREAD && (info.exit_thread.reason >= EXITTHREAD_EVENT_EXIT_PROCESS || !ctx->catchThreadEvents)) ||
info.type == DBGEVENT_EXIT_PROCESS || !(info.flags & 1);
if(continueAutomatically)
{
Result r = 0;
ret = GDB_SendStopReply(ctx, &info);
if(info.flags & 1)
r = svcContinueDebugEvent(ctx->debug, ctx->continueFlags);
if(r == (Result)0xD8A02008) // process ended
return -2;
return -ret - 3;
}
else
{
int ret;
if(ctx->processEnded)
return -2;
ctx->latestDebugEvent = info;
ret = GDB_SendStopReply(ctx, &info);
ctx->flags &= ~GDB_FLAG_PROCESS_CONTINUING;
return ret;
}
}

View File

@@ -0,0 +1,292 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include "gdb/mem.h"
#include "gdb/net.h"
#include "utils.h"
static void *k_memcpy_no_interrupt(void *dst, const void *src, u32 len)
{
__asm__ volatile("cpsid aif");
return memcpy(dst, src, len);
}
Result GDB_ReadMemoryInPage(void *out, GDBContext *ctx, u32 addr, u32 len)
{
s64 TTBCR;
svcGetSystemInfo(&TTBCR, 0x10002, 0);
if(addr < (1u << (32 - (u32)TTBCR)))
return svcReadProcessMemory(out, ctx->debug, addr, len);
else if(addr >= 0x80000000 && addr < 0xB0000000)
{
memcpy(out, (const void *)addr, len);
return 0;
}
else
{
u32 PA = svcConvertVAToPA((const void *)addr, false);
if(PA == 0)
return -1;
else
{
svcCustomBackdoor(k_memcpy_no_interrupt, out, (const void *)addr, len);
return 0;
}
}
}
Result GDB_WriteMemoryInPage(GDBContext *ctx, const void *in, u32 addr, u32 len)
{
s64 TTBCR;
svcGetSystemInfo(&TTBCR, 0x10002, 0);
if(addr < (1u << (32 - (u32)TTBCR)))
return svcWriteProcessMemory(ctx->debug, in, addr, len); // not sure if it checks if it's IO or not. It probably does
else if(addr >= 0x80000000 && addr < 0xB0000000)
{
memcpy((void *)addr, in, len);
return 0;
}
else
{
u32 PA = svcConvertVAToPA((const void *)addr, true);
if(PA != 0)
{
svcCustomBackdoor(k_memcpy_no_interrupt, (void *)addr, in, len);
return 0;
}
else
{
// Unreliable, use at your own risk
svcFlushEntireDataCache();
svcInvalidateEntireInstructionCache();
Result ret = GDB_WriteMemoryInPage(ctx, PA_FROM_VA_PTR(in), addr, len);
svcFlushEntireDataCache();
svcInvalidateEntireInstructionCache();
return ret;
}
}
}
int GDB_SendMemory(GDBContext *ctx, const char *prefix, u32 prefixLen, u32 addr, u32 len)
{
char buf[GDB_BUF_LEN];
u8 membuf[GDB_BUF_LEN / 2];
if(prefix != NULL)
memcpy(buf, prefix, prefixLen);
else
prefixLen = 0;
if(prefixLen + 2 * len > GDB_BUF_LEN) // gdb shouldn't send requests which responses don't fit in a packet
return prefix == NULL ? GDB_ReplyErrno(ctx, ENOMEM) : -1;
Result r = 0;
u32 remaining = len, total = 0;
do
{
u32 nb = (remaining > 0x1000 - (addr & 0xFFF)) ? 0x1000 - (addr & 0xFFF) : remaining;
r = GDB_ReadMemoryInPage(membuf + total, ctx, addr, nb);
if(R_SUCCEEDED(r))
{
addr += nb;
total += nb;
remaining -= nb;
}
}
while(remaining > 0 && R_SUCCEEDED(r));
if(total == 0)
return prefix == NULL ? GDB_ReplyErrno(ctx, EFAULT) : -EFAULT;
else
{
GDB_EncodeHex(buf + prefixLen, membuf, total);
return GDB_SendPacket(ctx, buf, prefixLen + 2 * total);
}
}
int GDB_WriteMemory(GDBContext *ctx, const void *buf, u32 addr, u32 len)
{
Result r = 0;
u32 remaining = len, total = 0;
do
{
u32 nb = (remaining > 0x1000 - (addr & 0xFFF)) ? 0x1000 - (addr & 0xFFF) : remaining;
r = GDB_WriteMemoryInPage(ctx, (u8 *)buf + total, addr, nb);
if(R_SUCCEEDED(r))
{
addr += nb;
total += nb;
remaining -= nb;
}
}
while(remaining > 0 && R_SUCCEEDED(r));
if(R_FAILED(r))
return GDB_ReplyErrno(ctx, EFAULT);
else
return GDB_ReplyOk(ctx);
}
u32 GDB_SearchMemory(bool *found, GDBContext *ctx, u32 addr, u32 len, const void *pattern, u32 patternLen)
{
u8 buf[0x1000 + 0x1000 * ((GDB_BUF_LEN + 0xFFF) / 0x1000)];
u32 maxNbPages = 1 + ((GDB_BUF_LEN + 0xFFF) / 0x1000);
u32 curAddr = addr;
s64 TTBCR;
svcGetSystemInfo(&TTBCR, 0x10002, 0);
while(curAddr < addr + len)
{
u32 nbPages;
u32 addrBase = curAddr & ~0xFFF, addrDispl = curAddr & 0xFFF;
for(nbPages = 0; nbPages < maxNbPages; nbPages++)
{
if(addr >= (1u << (32 - (u32)TTBCR)))
{
u32 PA = svcConvertVAToPA((const void *)addr, false);
if(PA == 0 || (PA >= 0x10000000 && PA <= 0x18000000))
break;
}
if(R_FAILED(GDB_ReadMemoryInPage(buf + 0x1000 * nbPages, ctx, addrBase + nbPages * 0x1000, 0x1000)))
break;
}
u8 *pos = NULL;
if(addrDispl + patternLen <= 0x1000 * nbPages)
pos = memsearch(buf + addrDispl, pattern, 0x1000 * nbPages - addrDispl, patternLen);
if(pos != NULL)
{
*found = true;
return addrBase + (pos - buf);
}
curAddr = addrBase + 0x1000;
}
*found = false;
return 0;
}
GDB_DECLARE_HANDLER(ReadMemory)
{
u32 lst[2];
if(GDB_ParseHexIntegerList(lst, ctx->commandData, 2, 0) == NULL)
return GDB_ReplyErrno(ctx, EILSEQ);
u32 addr = lst[0];
u32 len = lst[1];
return GDB_SendMemory(ctx, NULL, 0, addr, len);
}
GDB_DECLARE_HANDLER(WriteMemory)
{
u32 lst[2];
const char *dataStart = GDB_ParseHexIntegerList(lst, ctx->commandData, 2, ':');
if(dataStart == NULL || *dataStart != ':')
return GDB_ReplyErrno(ctx, EILSEQ);
dataStart++;
u32 addr = lst[0];
u32 len = lst[1];
if(dataStart + 2 * len >= ctx->buffer + 4 + GDB_BUF_LEN)
return GDB_ReplyErrno(ctx, ENOMEM);
u8 data[GDB_BUF_LEN / 2];
u32 n = GDB_DecodeHex(data, dataStart, len);
if(n != len)
return GDB_ReplyErrno(ctx, EILSEQ);
return GDB_WriteMemory(ctx, data, addr, len);
}
GDB_DECLARE_HANDLER(WriteMemoryRaw)
{
u32 lst[2];
const char *dataStart = GDB_ParseHexIntegerList(lst, ctx->commandData, 2, ':');
if(dataStart == NULL || *dataStart != ':')
return GDB_ReplyErrno(ctx, EILSEQ);
dataStart++;
u32 addr = lst[0];
u32 len = lst[1];
if(dataStart + len >= ctx->buffer + 4 + GDB_BUF_LEN)
return GDB_ReplyErrno(ctx, ENOMEM);
u8 data[GDB_BUF_LEN];
u32 n = GDB_UnescapeBinaryData(data, dataStart, len);
if(n != len)
return GDB_ReplyErrno(ctx, n);
return GDB_WriteMemory(ctx, data, addr, len);
}
GDB_DECLARE_QUERY_HANDLER(SearchMemory)
{
u32 lst[2];
u32 addr, len;
u8 pattern[GDB_BUF_LEN];
const char *patternStart;
u32 patternLen;
bool found;
u32 foundAddr;
if(strncmp(ctx->commandData, "memory:", 7) != 0)
return GDB_ReplyErrno(ctx, EILSEQ);
ctx->commandData += 7;
patternStart = GDB_ParseIntegerList(lst, ctx->commandData, 2, ';', ';', 16, false);
if(patternStart == NULL || *patternStart != ';')
return GDB_ReplyErrno(ctx, EILSEQ);
addr = lst[0];
len = lst[1];
patternStart++;
patternLen = ctx->commandEnd - patternStart;
patternLen = GDB_UnescapeBinaryData(pattern, patternStart, patternLen);
foundAddr = GDB_SearchMemory(&found, ctx, addr, len, patternStart, patternLen);
if(found)
return GDB_SendFormattedPacket(ctx, "1,%x", foundAddr);
else
return GDB_SendPacket(ctx, "0", 1);
}

View File

@@ -0,0 +1,94 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include "gdb/monitor.h"
#include "gdb/net.h"
#include "gdb/debug.h"
extern Handle terminationRequestEvent;
extern bool terminationRequest;
void GDB_RunMonitor(GDBServer *server)
{
Handle handles[3 + MAX_DEBUG];
Result r = 0;
handles[0] = terminationRequestEvent;
handles[1] = server->super.shall_terminate_event;
handles[2] = server->statusUpdated;
do
{
for(int i = 0; i < MAX_DEBUG; i++)
{
GDBContext *ctx = &server->ctxs[i];
handles[3 + i] = ctx->eventToWaitFor;
}
s32 idx = -1;
r = svcWaitSynchronizationN(&idx, handles, 3 + MAX_DEBUG, false, -1LL);
if(R_FAILED(r) || idx < 2)
break;
else if(idx == 2)
continue;
else
{
GDBContext *ctx = &server->ctxs[idx - 3];
RecursiveLock_Lock(&ctx->lock);
if(ctx->state == GDB_STATE_DISCONNECTED || ctx->state == GDB_STATE_CLOSING)
{
svcClearEvent(ctx->clientAcceptedEvent);
ctx->eventToWaitFor = ctx->clientAcceptedEvent;
RecursiveLock_Unlock(&ctx->lock);
continue;
}
if(ctx->eventToWaitFor == ctx->clientAcceptedEvent)
ctx->eventToWaitFor = ctx->continuedEvent;
else if(ctx->eventToWaitFor == ctx->continuedEvent)
ctx->eventToWaitFor = ctx->debug;
else
{
int res = GDB_HandleDebugEvents(ctx);
if(res >= 0)
ctx->eventToWaitFor = ctx->continuedEvent;
else if(res == -2)
{
while(GDB_HandleDebugEvents(ctx) != -1) // until we've got all the remaining debug events
svcSleepThread(1 * 1000 * 1000LL); // sleep just in case
svcClearEvent(ctx->clientAcceptedEvent);
ctx->eventToWaitFor = ctx->clientAcceptedEvent;
}
}
RecursiveLock_Unlock(&ctx->lock);
}
}
while(!terminationRequest && server->super.running);
}

View File

@@ -0,0 +1,332 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include "gdb/net.h"
#include <stdarg.h>
#include <stdlib.h>
#include "fmt.h"
#include "minisoc.h"
u8 GDB_ComputeChecksum(const char *packetData, u32 len)
{
u8 cksum = 0;
for(u32 i = 0; i < len; i++)
cksum += (u8)packetData[i];
return cksum;
}
void GDB_EncodeHex(char *dst, const void *src, u32 len)
{
static const char *alphabet = "0123456789abcdef";
const u8 *src8 = (u8 *)src;
for(u32 i = 0; i < len; i++)
{
dst[2 * i] = alphabet[(src8[i] & 0xf0) >> 4];
dst[2 * i + 1] = alphabet[src8[i] & 0x0f];
}
}
static inline u32 GDB_DecodeHexDigit(char src, bool *ok)
{
*ok = true;
if(src >= '0' && src <= '9') return src - '0';
else if(src >= 'a' && src <= 'f') return 0xA + (src - 'a');
else if(src >= 'A' && src <= 'F') return 0xA + (src - 'A');
else
{
*ok = false;
return 0;
}
}
u32 GDB_DecodeHex(void *dst, const char *src, u32 len)
{
u32 i = 0;
bool ok = true;
u8 *dst8 = (u8 *)dst;
for(i = 0; i < len && ok && src[2 * i] != 0 && src[2 * i + 1] != 0; i++)
dst8[i] = (GDB_DecodeHexDigit(src[2 * i], &ok) << 4) | GDB_DecodeHexDigit(src[2 * i + 1], &ok);
return (!ok) ? i - 1 : i;
}
u32 GDB_UnescapeBinaryData(void *dst, const void *src, u32 len)
{
u8 *dst8 = (u8 *)dst;
const u8 *src8 = (const u8 *)src;
for(u32 i = 0; i < len; i++)
{
if(*src8 == '}')
{
src8++;
*dst8++ = *src8++ ^ 0x20;
}
else
*dst8++ = *src8++;
}
return dst8 - (u8 *)dst;
}
const char *GDB_ParseIntegerList(u32 *dst, const char *src, u32 nb, char sep, char lastSep, u32 base, bool allowPrefix)
{
const char *pos = src;
const char *endpos;
bool ok;
for(u32 i = 0; i < nb; i++)
{
u32 n = xstrtoul(pos, (char **)&endpos, (int) base, allowPrefix, &ok);
if(!ok || endpos == pos)
return NULL;
if(i != nb - 1)
{
if(*endpos != sep)
return NULL;
pos = endpos + 1;
}
else
{
if(*endpos != lastSep && *endpos != 0)
return NULL;
pos = endpos;
}
dst[i] = n;
}
return pos;
}
const char *GDB_ParseHexIntegerList(u32 *dst, const char *src, u32 nb, char lastSep)
{
return GDB_ParseIntegerList(dst, src, nb, ',', lastSep, 16, false);
}
int GDB_ReceivePacket(GDBContext *ctx)
{
char backupbuf[GDB_BUF_LEN + 4];
memcpy(backupbuf, ctx->buffer, ctx->latestSentPacketSize);
memset(ctx->buffer, 0, sizeof(ctx->buffer));
int r = soc_recv(ctx->super.sockfd, ctx->buffer, sizeof(ctx->buffer), MSG_PEEK);
if(r < 1)
return -1;
if(ctx->buffer[0] == '+') // GDB sometimes acknowleges TCP acknowledgment packets (yes...). IDA does it properly
{
if(ctx->state == GDB_STATE_NOACK)
return -1;
// Consume it
r = soc_recv(ctx->super.sockfd, ctx->buffer, 1, 0);
if(r != 1)
return -1;
ctx->buffer[0] = 0;
r = soc_recv(ctx->super.sockfd, ctx->buffer, sizeof(ctx->buffer), MSG_PEEK);
if(r == -1)
goto packet_error;
}
else if(ctx->buffer[0] == '-')
{
soc_send(ctx->super.sockfd, backupbuf, ctx->latestSentPacketSize, 0);
return 0;
}
int maxlen = r > (int)sizeof(ctx->buffer) ? (int)sizeof(ctx->buffer) : r;
if(ctx->buffer[0] == '$') // normal packet
{
char *pos;
for(pos = ctx->buffer; pos < ctx->buffer + maxlen && *pos != '#'; pos++);
if(pos == ctx->buffer + maxlen) // malformed packet
return -1;
else
{
u8 checksum;
r = soc_recv(ctx->super.sockfd, ctx->buffer, 3 + pos - ctx->buffer, 0);
if(r != 3 + pos - ctx->buffer || GDB_DecodeHex(&checksum, pos + 1, 1) != 1)
goto packet_error;
else if(GDB_ComputeChecksum(ctx->buffer + 1, pos - ctx->buffer - 1) != checksum)
goto packet_error;
ctx->commandEnd = pos;
*pos = 0; // replace trailing '#' by a NUL character
}
}
else if(ctx->buffer[0] == '\x03')
{
r = soc_recv(ctx->super.sockfd, ctx->buffer, 1, 0);
if(r != 1)
goto packet_error;
ctx->commandEnd = ctx->buffer;
}
if(ctx->state >= GDB_STATE_CONNECTED && ctx->state < GDB_STATE_NOACK)
{
int r2 = soc_send(ctx->super.sockfd, "+", 1, 0);
if(r2 != 1)
return -1;
}
if(ctx->state == GDB_STATE_NOACK_SENT)
ctx->state = GDB_STATE_NOACK;
return r;
packet_error:
if(ctx->state >= GDB_STATE_CONNECTED && ctx->state < GDB_STATE_NOACK)
{
r = soc_send(ctx->super.sockfd, "-", 1, 0);
if(r != 1)
return -1;
else
return 0;
}
else
return -1;
}
static int GDB_DoSendPacket(GDBContext *ctx, u32 len)
{
int r = soc_send(ctx->super.sockfd, ctx->buffer, len, 0);
if(r > 0)
ctx->latestSentPacketSize = r;
return r;
}
int GDB_SendPacket(GDBContext *ctx, const char *packetData, u32 len)
{
ctx->buffer[0] = '$';
memcpy(ctx->buffer + 1, packetData, len);
char *checksumLoc = ctx->buffer + len + 1;
*checksumLoc++ = '#';
hexItoa(GDB_ComputeChecksum(packetData, len), checksumLoc, 2, false);
return GDB_DoSendPacket(ctx, 4 + len);
}
int GDB_SendFormattedPacket(GDBContext *ctx, const char *packetDataFmt, ...)
{
// It goes without saying you shouldn't use that with user-controlled data...
char buf[GDB_BUF_LEN + 1];
va_list args;
va_start(args, packetDataFmt);
int n = vsprintf(buf, packetDataFmt, args);
va_end(args);
if(n < 0) return -1;
else return GDB_SendPacket(ctx, buf, (u32)n);
}
int GDB_SendHexPacket(GDBContext *ctx, const void *packetData, u32 len)
{
if(4 + 2 * len > GDB_BUF_LEN)
return -1;
ctx->buffer[0] = '$';
GDB_EncodeHex(ctx->buffer + 1, packetData, len);
char *checksumLoc = ctx->buffer + 2 * len + 1;
*checksumLoc++ = '#';
hexItoa(GDB_ComputeChecksum(ctx->buffer + 1, 2 * len), checksumLoc, 2, false);
return GDB_DoSendPacket(ctx, 4 + 2 * len);
}
int GDB_SendStreamData(GDBContext *ctx, const char *streamData, u32 offset, u32 length, u32 totalSize, bool forceEmptyLast)
{
char buf[GDB_BUF_LEN];
if(length > GDB_BUF_LEN - 1)
length = GDB_BUF_LEN - 1;
if((forceEmptyLast && offset >= totalSize) || (!forceEmptyLast && offset + length >= totalSize))
{
length = totalSize - offset;
buf[0] = 'l';
memcpy(buf + 1, streamData + offset, length);
return GDB_SendPacket(ctx, buf, 1 + length);
}
else
{
buf[0] = 'm';
memcpy(buf + 1, streamData + offset, length);
return GDB_SendPacket(ctx, buf, 1 + length);
}
}
int GDB_SendDebugString(GDBContext *ctx, const char *fmt, ...) // unsecure
{
if(ctx->state == GDB_STATE_CLOSING || !(ctx->flags & GDB_FLAG_PROCESS_CONTINUING))
return 0;
char formatted[(GDB_BUF_LEN - 1) / 2 + 1];
ctx->buffer[0] = '$';
ctx->buffer[1] = 'O';
va_list args;
va_start(args, fmt);
int n = vsprintf(formatted, fmt, args);
va_end(args);
if(n <= 0) return n;
GDB_EncodeHex(ctx->buffer + 2, formatted, 2 * n);
char *checksumLoc = ctx->buffer + 2 * n + 2;
*checksumLoc++ = '#';
hexItoa(GDB_ComputeChecksum(ctx->buffer + 1, 2 * n + 1), checksumLoc, 2, false);
return GDB_DoSendPacket(ctx, 5 + 2 * n);
}
int GDB_ReplyEmpty(GDBContext *ctx)
{
return GDB_SendPacket(ctx, "", 0);
}
int GDB_ReplyOk(GDBContext *ctx)
{
return GDB_SendPacket(ctx, "OK", 2);
}
int GDB_ReplyErrno(GDBContext *ctx, int no)
{
char buf[] = "E01";
hexItoa((u8)no, buf + 1, 2, false);
return GDB_SendPacket(ctx, buf, 3);
}

View File

@@ -0,0 +1,162 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include "gdb/query.h"
#include "gdb/xfer.h"
#include "gdb/thread.h"
#include "gdb/mem.h"
#include "gdb/net.h"
#include "gdb/remote_command.h"
typedef enum GDBQueryDirection
{
GDB_QUERY_DIRECTION_READ,
GDB_QUERY_DIRECTION_WRITE
} GDBQueryDirection;
// https://gcc.gnu.org/onlinedocs/gcc-5.3.0/cpp/Stringification.html
#define xstr(s) str(s)
#define str(s) #s
#define GDB_QUERY_HANDLER_LIST_ITEM_3(name, name2, direction) { name, GDB_QUERY_HANDLER(name2), GDB_QUERY_DIRECTION_##direction }
#define GDB_QUERY_HANDLER_LIST_ITEM(name, direction) GDB_QUERY_HANDLER_LIST_ITEM_3(xstr(name), name, direction)
static const struct
{
const char *name;
GDBCommandHandler handler;
GDBQueryDirection direction;
} gdbQueryHandlers[] =
{
GDB_QUERY_HANDLER_LIST_ITEM(Supported, READ),
GDB_QUERY_HANDLER_LIST_ITEM(Xfer, READ),
GDB_QUERY_HANDLER_LIST_ITEM(StartNoAckMode, WRITE),
GDB_QUERY_HANDLER_LIST_ITEM(Attached, READ),
GDB_QUERY_HANDLER_LIST_ITEM(fThreadInfo, READ),
GDB_QUERY_HANDLER_LIST_ITEM(sThreadInfo, READ),
GDB_QUERY_HANDLER_LIST_ITEM(ThreadEvents, WRITE),
GDB_QUERY_HANDLER_LIST_ITEM(ThreadExtraInfo, READ),
GDB_QUERY_HANDLER_LIST_ITEM_3("C", CurrentThreadId, READ),
GDB_QUERY_HANDLER_LIST_ITEM_3("Search", SearchMemory, READ),
GDB_QUERY_HANDLER_LIST_ITEM(CatchSyscalls, WRITE),
GDB_QUERY_HANDLER_LIST_ITEM(Rcmd, READ),
};
static int GDB_HandleQuery(GDBContext *ctx, GDBQueryDirection direction)
{
char *nameBegin = ctx->commandData; // w/o leading 'q'/'Q'
if(*nameBegin == 0)
return GDB_ReplyErrno(ctx, EILSEQ);
char *nameEnd;
char *queryData = NULL;
for(nameEnd = nameBegin; *nameEnd != 0 && *nameEnd != ':' && *nameEnd != ','; nameEnd++);
if(*nameEnd != 0)
{
*nameEnd = 0;
queryData = nameEnd + 1;
}
else
queryData = nameEnd;
for(u32 i = 0; i < sizeof(gdbQueryHandlers) / sizeof(gdbQueryHandlers[0]); i++)
{
if(strcmp(gdbQueryHandlers[i].name, nameBegin) == 0 && gdbQueryHandlers[i].direction == direction)
{
ctx->commandData = queryData;
return gdbQueryHandlers[i].handler(ctx);
}
}
return GDB_HandleUnsupported(ctx); // No handler found!
}
int GDB_HandleReadQuery(GDBContext *ctx)
{
return GDB_HandleQuery(ctx, GDB_QUERY_DIRECTION_READ);
}
int GDB_HandleWriteQuery(GDBContext *ctx)
{
return GDB_HandleQuery(ctx, GDB_QUERY_DIRECTION_WRITE);
}
GDB_DECLARE_QUERY_HANDLER(Supported)
{
return GDB_SendFormattedPacket(ctx,
"PacketSize=%x;"
"qXfer:features:read+;qXfer:osdata:read+;"
"QStartNoAckMode+;QThreadEvents+;QCatchSyscalls+;"
"vContSupported+;swbreak+", sizeof(ctx->buffer));
}
GDB_DECLARE_QUERY_HANDLER(StartNoAckMode)
{
ctx->isGDB = true;
ctx->state = GDB_STATE_NOACK_SENT;
return GDB_ReplyOk(ctx);
}
GDB_DECLARE_QUERY_HANDLER(Attached)
{
return GDB_SendPacket(ctx, "1", 1);
}
GDB_DECLARE_QUERY_HANDLER(CatchSyscalls)
{
if(ctx->commandData[0] == '0')
{
memset(ctx->svcMask, 0, 32);
return R_SUCCEEDED(svcKernelSetState(0x10002, ctx->pid, false)) ? GDB_ReplyOk(ctx) : GDB_ReplyErrno(ctx, EPERM);
}
else if(ctx->commandData[0] == '1')
{
if(ctx->commandData[1] == ';')
{
u32 id;
const char *pos = ctx->commandData + 1;
memset(ctx->svcMask, 0, 32);
do
{
pos = GDB_ParseHexIntegerList(&id, pos + 1, 1, ';');
if(pos == NULL)
return GDB_ReplyErrno(ctx, EILSEQ);
if(id < 0xFE)
ctx->svcMask[id / 32] |= 1 << (31 - (id % 32));
}
while(*pos != 0);
}
else
memset(ctx->svcMask, 0xFF, 32);
return R_SUCCEEDED(svcKernelSetState(0x10002, ctx->pid, true, ctx->svcMask)) ? GDB_ReplyOk(ctx) : GDB_ReplyErrno(ctx, EPERM);
}
else
return GDB_ReplyErrno(ctx, EILSEQ);
}

View File

@@ -0,0 +1,172 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include "gdb/regs.h"
#include "gdb/net.h"
GDB_DECLARE_HANDLER(ReadRegisters)
{
if(ctx->selectedThreadId == 0)
ctx->selectedThreadId = ctx->currentThreadId;
ThreadContext regs;
Result r = svcGetDebugThreadContext(&regs, ctx->debug, ctx->selectedThreadId, THREADCONTEXT_CONTROL_ALL);
if(R_FAILED(r))
return GDB_ReplyErrno(ctx, EPERM);
return GDB_SendHexPacket(ctx, &regs, sizeof(ThreadContext));
}
GDB_DECLARE_HANDLER(WriteRegisters)
{
if(ctx->selectedThreadId == 0)
ctx->selectedThreadId = ctx->currentThreadId;
ThreadContext regs;
if(GDB_DecodeHex(&regs, ctx->commandData, sizeof(ThreadContext)) != sizeof(ThreadContext))
return GDB_ReplyErrno(ctx, EPERM);
Result r = svcSetDebugThreadContext(ctx->debug, ctx->selectedThreadId, &regs, THREADCONTEXT_CONTROL_ALL);
if(R_FAILED(r))
return GDB_ReplyErrno(ctx, EPERM);
else
return GDB_ReplyOk(ctx);
}
static u32 GDB_ConvertRegisterNumber(ThreadContextControlFlags *flags, u32 gdbNum)
{
if(gdbNum <= 15)
{
*flags = (gdbNum >= 13) ? THREADCONTEXT_CONTROL_CPU_SPRS : THREADCONTEXT_CONTROL_CPU_GPRS;
return gdbNum;
}
else if(gdbNum == 25)
{
*flags = THREADCONTEXT_CONTROL_CPU_SPRS;
return 16;
}
else if(gdbNum >= 26 && gdbNum <= 41)
{
*flags = THREADCONTEXT_CONTROL_FPU_GPRS;
return gdbNum - 26;
}
else if(gdbNum == 42 || gdbNum == 43)
{
*flags = THREADCONTEXT_CONTROL_FPU_SPRS;
return gdbNum - 42;
}
else
{
*flags = (ThreadContextControlFlags)0;
return 0;
}
}
GDB_DECLARE_HANDLER(ReadRegister)
{
if(ctx->selectedThreadId == 0)
ctx->selectedThreadId = ctx->currentThreadId;
ThreadContext regs;
ThreadContextControlFlags flags;
u32 gdbRegNum;
if(GDB_ParseHexIntegerList(&gdbRegNum, ctx->commandData, 1, 0) == NULL)
return GDB_ReplyErrno(ctx, EILSEQ);
u32 n = GDB_ConvertRegisterNumber(&flags, gdbRegNum);
if(!flags)
return GDB_ReplyErrno(ctx, EINVAL);
Result r = svcGetDebugThreadContext(&regs, ctx->debug, ctx->selectedThreadId, flags);
if(R_FAILED(r))
return GDB_ReplyErrno(ctx, EPERM);
if(flags & THREADCONTEXT_CONTROL_CPU_GPRS)
return GDB_SendHexPacket(ctx, &regs.cpu_registers.r[n], 4);
else if(flags & THREADCONTEXT_CONTROL_CPU_SPRS)
return GDB_SendHexPacket(ctx, &regs.cpu_registers.sp + (n - 13), 4); // hacky
else if(flags & THREADCONTEXT_CONTROL_FPU_GPRS)
return GDB_SendHexPacket(ctx, &regs.fpu_registers.d[n], 8);
else
return GDB_SendHexPacket(ctx, &regs.fpu_registers.fpscr + n, 4); // hacky
}
GDB_DECLARE_HANDLER(WriteRegister)
{
if(ctx->selectedThreadId == 0)
ctx->selectedThreadId = ctx->currentThreadId;
ThreadContext regs;
ThreadContextControlFlags flags;
u32 gdbRegNum;
const char *valueStart = GDB_ParseHexIntegerList(&gdbRegNum, ctx->commandData, 1, '=');
if(valueStart == NULL || *valueStart != '=')
return GDB_ReplyErrno(ctx, EILSEQ);
valueStart++;
u32 n = GDB_ConvertRegisterNumber(&flags, gdbRegNum);
u32 value;
u64 value64;
if(flags & THREADCONTEXT_CONTROL_FPU_GPRS)
{
if(GDB_DecodeHex(&value64, valueStart, 8) != 8 || valueStart[16] != 0)
return GDB_ReplyErrno(ctx, EINVAL);
}
else if(flags)
{
if(GDB_DecodeHex(&value, valueStart, 4) != 4 || valueStart[8] != 0)
return GDB_ReplyErrno(ctx, EINVAL);
}
else
return GDB_ReplyErrno(ctx, EINVAL);
Result r = svcGetDebugThreadContext(&regs, ctx->debug, ctx->selectedThreadId, flags);
if(R_FAILED(r))
return GDB_ReplyErrno(ctx, EPERM);
if(flags & THREADCONTEXT_CONTROL_CPU_GPRS)
regs.cpu_registers.r[n] = value;
else if(flags & THREADCONTEXT_CONTROL_CPU_SPRS)
*(&regs.cpu_registers.sp + (n - 13)) = value; // hacky
else if(flags & THREADCONTEXT_CONTROL_FPU_GPRS)
memcpy(&regs.fpu_registers.d[n], &value64, 8);
else
*(&regs.fpu_registers.fpscr + n) = value; // hacky
r = svcSetDebugThreadContext(ctx->debug, ctx->selectedThreadId, &regs, flags);
if(R_FAILED(r))
return GDB_ReplyErrno(ctx, EPERM);
else
return GDB_ReplyOk(ctx);
}

View File

@@ -0,0 +1,265 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include "gdb/remote_command.h"
#include "gdb/net.h"
#include "csvc.h"
#include "fmt.h"
#include "gdb/breakpoints.h"
struct
{
const char *name;
GDBCommandHandler handler;
} remoteCommandHandlers[] =
{
{ "syncrequestinfo", GDB_REMOTE_COMMAND_HANDLER(SyncRequestInfo) },
{ "translatehandle", GDB_REMOTE_COMMAND_HANDLER(TranslateHandle) },
{ "getmmuconfig" , GDB_REMOTE_COMMAND_HANDLER(GetMmuConfig) },
{ "flushcaches" , GDB_REMOTE_COMMAND_HANDLER(FlushCaches) },
};
static const char *GDB_SkipSpaces(const char *pos)
{
const char *nextpos;
for(nextpos = pos; *nextpos != 0 && ((*nextpos >= 9 && *nextpos <= 13) || *nextpos == ' '); nextpos++);
return nextpos;
}
GDB_DECLARE_REMOTE_COMMAND_HANDLER(SyncRequestInfo)
{
char outbuf[GDB_BUF_LEN / 2 + 1];
Result r;
int n;
if(ctx->commandData[0] != 0)
return GDB_ReplyErrno(ctx, EILSEQ);
if(ctx->selectedThreadId == 0)
ctx->selectedThreadId = ctx->currentThreadId;
if(ctx->selectedThreadId == 0)
{
n = sprintf(outbuf, "Cannot run this command without a selected thread.\n");
goto end;
}
u32 id;
u32 cmdId;
ThreadContext regs;
u32 instr;
Handle process;
r = svcOpenProcess(&process, ctx->pid);
if(R_FAILED(r))
{
n = sprintf(outbuf, "Invalid process (wtf?)\n");
goto end;
}
for(id = 0; id < MAX_DEBUG_THREAD && ctx->threadInfos[id].id != ctx->selectedThreadId; id++);
r = svcGetDebugThreadContext(&regs, ctx->debug, ctx->selectedThreadId, THREADCONTEXT_CONTROL_CPU_REGS);
if(R_FAILED(r) || id == MAX_DEBUG_THREAD)
{
n = sprintf(outbuf, "Invalid or running thread.\n");
goto end;
}
r = svcReadProcessMemory(&cmdId, ctx->debug, ctx->threadInfos[id].tls + 0x80, 4);
if(R_FAILED(r))
{
n = sprintf(outbuf, "Invalid or running thread.\n");
goto end;
}
r = svcReadProcessMemory(&instr, ctx->debug, regs.cpu_registers.pc, (regs.cpu_registers.cpsr & 0x20) ? 2 : 4);
if(R_SUCCEEDED(r) && (((regs.cpu_registers.cpsr & 0x20) && instr == BREAKPOINT_INSTRUCTION_THUMB) || instr == BREAKPOINT_INSTRUCTION_ARM))
{
u32 savedInstruction;
if(GDB_GetBreakpointInstruction(&savedInstruction, ctx, regs.cpu_registers.pc) == 0)
instr = savedInstruction;
}
if(R_FAILED(r) || ((regs.cpu_registers.cpsr & 0x20) && !(instr == 0xDF32 || (instr == 0xDFFE && regs.cpu_registers.r[12] == 0x32)))
|| (!(regs.cpu_registers.cpsr & 0x20) && !(instr == 0xEF000032 || (instr == 0xEF0000FE && regs.cpu_registers.r[12] == 0x32))))
{
n = sprintf(outbuf, "The selected thread is not currently performing a sync request (svc 0x32).\n");
goto end;
}
char name[12];
Handle handle;
r = svcCopyHandle(&handle, CUR_PROCESS_HANDLE, (Handle)regs.cpu_registers.r[0], process);
if(R_FAILED(r))
{
n = sprintf(outbuf, "Invalid handle.\n");
goto end;
}
r = svcControlService(SERVICEOP_GET_NAME, name, handle);
if(R_FAILED(r))
name[0] = 0;
n = sprintf(outbuf, "%s 0x%x, 0x%08x\n", name, cmdId, ctx->threadInfos[id].tls + 0x80);
end:
svcCloseHandle(handle);
svcCloseHandle(process);
return GDB_SendHexPacket(ctx, outbuf, n);
}
GDB_DECLARE_REMOTE_COMMAND_HANDLER(TranslateHandle)
{
bool ok;
u32 val;
char *end;
int n;
Result r;
u32 kernelAddr;
Handle handle, process;
s64 refcountRaw;
u32 refcount;
char classBuf[32], serviceBuf[12] = { 0 };
char outbuf[GDB_BUF_LEN / 2 + 1];
if(ctx->commandData[0] == 0)
return GDB_ReplyErrno(ctx, EILSEQ);
val = xstrtoul(ctx->commandData, &end, 0, true, &ok);
if(!ok)
return GDB_ReplyErrno(ctx, EILSEQ);
end = (char *)GDB_SkipSpaces(end);
if(*end != 0)
return GDB_ReplyErrno(ctx, EILSEQ);
r = svcOpenProcess(&process, ctx->pid);
if(R_FAILED(r))
{
n = sprintf(outbuf, "Invalid process (wtf?)\n");
goto end;
}
r = svcCopyHandle(&handle, CUR_PROCESS_HANDLE, (Handle)val, process);
if(R_FAILED(r))
{
n = sprintf(outbuf, "Invalid handle.\n");
goto end;
}
svcTranslateHandle(&kernelAddr, classBuf, handle);
svcGetHandleInfo(&refcountRaw, handle, 1);
svcControlService(SERVICEOP_GET_NAME, serviceBuf, handle);
refcount = (u32)(refcountRaw - 1);
if(serviceBuf[0] != 0)
n = sprintf(outbuf, "(%s *)0x%08x /* %s handle, %u %s */\n", classBuf, kernelAddr, serviceBuf, refcount, refcount == 1 ? "reference" : "references");
else
n = sprintf(outbuf, "(%s *)0x%08x /* %u %s */\n", classBuf, kernelAddr, refcount, refcount == 1 ? "reference" : "references");
end:
svcCloseHandle(handle);
svcCloseHandle(process);
return GDB_SendHexPacket(ctx, outbuf, n);
}
extern bool isN3DS;
GDB_DECLARE_REMOTE_COMMAND_HANDLER(GetMmuConfig)
{
int n;
char outbuf[GDB_BUF_LEN / 2 + 1];
Result r;
Handle process;
if(ctx->commandData[0] != 0)
return GDB_ReplyErrno(ctx, EILSEQ);
r = svcOpenProcess(&process, ctx->pid);
if(R_FAILED(r))
n = sprintf(outbuf, "Invalid process (wtf?)\n");
else
{
s64 TTBCR, TTBR0;
svcGetSystemInfo(&TTBCR, 0x10002, 0);
svcGetProcessInfo(&TTBR0, process, 0x10008);
n = sprintf(outbuf, "TTBCR = %u\nTTBR0 = 0x%08x\nTTBR1 =", (u32)TTBCR, (u32)TTBR0);
for(u32 i = 0; i < (isN3DS ? 4 : 2); i++)
{
s64 TTBR1;
svcGetSystemInfo(&TTBR1, 0x10002, 1 + i);
if(i == (isN3DS ? 3 : 1))
n += sprintf(outbuf + n, " 0x%08x\n", (u32)TTBR1);
else
n += sprintf(outbuf + n, " 0x%08x /", (u32)TTBR1);
}
svcCloseHandle(process);
}
return GDB_SendHexPacket(ctx, outbuf, n);
}
GDB_DECLARE_REMOTE_COMMAND_HANDLER(FlushCaches)
{
if(ctx->commandData[0] != 0)
return GDB_ReplyErrno(ctx, EILSEQ);
svcFlushEntireDataCache();
svcInvalidateEntireInstructionCache();
return GDB_ReplyOk(ctx);
}
GDB_DECLARE_QUERY_HANDLER(Rcmd)
{
char commandData[GDB_BUF_LEN / 2 + 1];
char *endpos;
const char *errstr = "Unrecognized command.\n";
u32 len = strlen(ctx->commandData);
if(len == 0 || (len % 2) == 1 || GDB_DecodeHex(commandData, ctx->commandData, len / 2) != len / 2)
return GDB_ReplyErrno(ctx, EILSEQ);
commandData[len / 2] = 0;
for(endpos = commandData; !(*endpos >= 9 && *endpos <= 13) && *endpos != ' ' && *endpos != 0; endpos++);
char *nextpos = (char *)GDB_SkipSpaces(endpos);
*endpos = 0;
for(u32 i = 0; i < sizeof(remoteCommandHandlers) / sizeof(remoteCommandHandlers[0]); i++)
{
if(strcmp(commandData, remoteCommandHandlers[i].name) == 0)
{
ctx->commandData = nextpos;
return remoteCommandHandlers[i].handler(ctx);
}
}
return GDB_SendHexPacket(ctx, errstr, strlen(errstr));
}

View File

@@ -0,0 +1,297 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include "gdb/server.h"
#include "gdb/net.h"
#include "gdb/query.h"
#include "gdb/verbose.h"
#include "gdb/thread.h"
#include "gdb/debug.h"
#include "gdb/regs.h"
#include "gdb/mem.h"
#include "gdb/watchpoints.h"
#include "gdb/breakpoints.h"
#include "gdb/stop_point.h"
Result GDB_InitializeServer(GDBServer *server)
{
Result ret = server_init(&server->super);
if(ret != 0)
return ret;
server->super.host = 0;
server->super.accept_cb = (sock_accept_cb)GDB_AcceptClient;
server->super.data_cb = (sock_data_cb) GDB_DoPacket;
server->super.close_cb = (sock_close_cb) GDB_CloseClient;
server->super.alloc = (sock_alloc_func) GDB_GetClient;
server->super.free = (sock_free_func) GDB_ReleaseClient;
server->super.clients_per_server = 1;
server->referenceCount = 0;
svcCreateEvent(&server->statusUpdated, RESET_ONESHOT);
for(u32 i = 0; i < sizeof(server->ctxs) / sizeof(GDBContext); i++)
GDB_InitializeContext(server->ctxs + i);
GDB_ResetWatchpoints();
return 0;
}
void GDB_FinalizeServer(GDBServer *server)
{
server_finalize(&server->super);
svcCloseHandle(server->statusUpdated);
}
void GDB_IncrementServerReferenceCount(GDBServer *server)
{
AtomicPostIncrement(&server->referenceCount);
}
void GDB_DecrementServerReferenceCount(GDBServer *server)
{
if(AtomicDecrement(&server->referenceCount) == 0)
GDB_FinalizeServer(server);
}
void GDB_RunServer(GDBServer *server)
{
server_bind(&server->super, GDB_PORT_BASE);
server_bind(&server->super, GDB_PORT_BASE + 1);
server_bind(&server->super, GDB_PORT_BASE + 2);
server_run(&server->super);
}
int GDB_AcceptClient(GDBContext *ctx)
{
RecursiveLock_Lock(&ctx->lock);
Result r = svcDebugActiveProcess(&ctx->debug, ctx->pid);
if(R_SUCCEEDED(r))
{
DebugEventInfo *info = &ctx->latestDebugEvent;
ctx->state = GDB_STATE_CONNECTED;
ctx->processExited = ctx->processEnded = false;
ctx->latestSentPacketSize = 0;
while(R_SUCCEEDED(svcGetProcessDebugEvent(info, ctx->debug)) && info->type != DBGEVENT_EXCEPTION &&
info->exception.type != EXCEVENT_ATTACH_BREAK)
{
GDB_PreprocessDebugEvent(ctx, info);
svcContinueDebugEvent(ctx->debug, ctx->continueFlags);
}
}
else
{
RecursiveLock_Unlock(&ctx->lock);
return -1;
}
svcSignalEvent(ctx->clientAcceptedEvent);
RecursiveLock_Unlock(&ctx->lock);
return 0;
}
int GDB_CloseClient(GDBContext *ctx)
{
RecursiveLock_Lock(&ctx->lock);
for(u32 i = 0; i < ctx->nbBreakpoints; i++)
{
if(!ctx->breakpoints[i].persistent)
GDB_DisableBreakpointById(ctx, i);
}
memset(&ctx->breakpoints, 0, sizeof(ctx->breakpoints));
ctx->nbBreakpoints = 0;
for(u32 i = 0; i < ctx->nbWatchpoints; i++)
{
GDB_RemoveWatchpoint(ctx, ctx->watchpoints[i], WATCHPOINT_DISABLED);
ctx->watchpoints[i] = 0;
}
ctx->nbWatchpoints = 0;
svcKernelSetState(0x10002, ctx->pid, false);
memset(ctx->svcMask, 0, 32);
memset(ctx->memoryOsInfoXmlData, 0, sizeof(ctx->memoryOsInfoXmlData));
memset(ctx->processesOsInfoXmlData, 0, sizeof(ctx->processesOsInfoXmlData));
memset(ctx->threadListData, 0, sizeof(ctx->threadListData));
ctx->threadListDataPos = 0;
svcClearEvent(ctx->clientAcceptedEvent);
ctx->eventToWaitFor = ctx->clientAcceptedEvent;
RecursiveLock_Unlock(&ctx->lock);
return 0;
}
GDBContext *GDB_GetClient(GDBServer *server, u16 port)
{
if(port < GDB_PORT_BASE || port >= GDB_PORT_BASE + sizeof(server->ctxs) / sizeof(GDBContext))
return NULL;
GDBContext *ctx = &server->ctxs[port - GDB_PORT_BASE];
if(!(ctx->flags & GDB_FLAG_USED) && (ctx->flags & GDB_FLAG_SELECTED))
{
RecursiveLock_Lock(&ctx->lock);
ctx->flags |= GDB_FLAG_USED;
ctx->state = GDB_STATE_CONNECTED;
RecursiveLock_Unlock(&ctx->lock);
return ctx;
}
return NULL;
}
void GDB_ReleaseClient(GDBServer *server, GDBContext *ctx)
{
DebugEventInfo dummy;
svcSignalEvent(server->statusUpdated);
RecursiveLock_Lock(&ctx->lock);
/*
There's a possibility of a race condition with a possible user exception handler, but you shouldn't
use 'kill' on APPLICATION titles in the first place (reboot hanging because the debugger is still running, etc).
*/
ctx->continueFlags = (DebugFlags)0;
while(R_SUCCEEDED(svcGetProcessDebugEvent(&dummy, ctx->debug)));
while(R_SUCCEEDED(svcContinueDebugEvent(ctx->debug, ctx->continueFlags)));
if(ctx->flags & GDB_FLAG_TERMINATE_PROCESS)
{
svcTerminateDebugProcess(ctx->debug);
ctx->processEnded = true;
ctx->processExited = false;
}
while(R_SUCCEEDED(svcGetProcessDebugEvent(&dummy, ctx->debug)));
while(R_SUCCEEDED(svcContinueDebugEvent(ctx->debug, ctx->continueFlags)));
svcCloseHandle(ctx->debug);
ctx->debug = 0;
ctx->flags = (GDBFlags)0;
ctx->state = GDB_STATE_DISCONNECTED;
ctx->eventToWaitFor = ctx->clientAcceptedEvent;
ctx->continueFlags = (DebugFlags)(DBG_SIGNAL_FAULT_EXCEPTION_EVENTS | DBG_INHIBIT_USER_CPU_EXCEPTION_HANDLERS);
ctx->pid = 0;
ctx->currentThreadId = ctx->selectedThreadId = ctx->selectedThreadIdForContinuing = 0;
ctx->nbThreads = 0;
memset(ctx->threadInfos, 0, sizeof(ctx->threadInfos));
ctx->catchThreadEvents = false;
ctx->isGDB = false;
RecursiveLock_Unlock(&ctx->lock);
}
static const struct
{
char command;
GDBCommandHandler handler;
} gdbCommandHandlers[] =
{
{ '?', GDB_HANDLER(GetStopReason) },
{ 'c', GDB_HANDLER(Continue) },
{ 'C', GDB_HANDLER(Continue) },
{ 'D', GDB_HANDLER(Detach) },
{ 'g', GDB_HANDLER(ReadRegisters) },
{ 'G', GDB_HANDLER(WriteRegisters) },
{ 'H', GDB_HANDLER(SetThreadId) },
{ 'k', GDB_HANDLER(Kill) },
{ 'm', GDB_HANDLER(ReadMemory) },
{ 'M', GDB_HANDLER(WriteMemory) },
{ 'p', GDB_HANDLER(ReadRegister) },
{ 'P', GDB_HANDLER(WriteRegister) },
{ 'q', GDB_HANDLER(ReadQuery) },
{ 'Q', GDB_HANDLER(WriteQuery) },
{ 'T', GDB_HANDLER(IsThreadAlive) },
{ 'v', GDB_HANDLER(VerboseCommand) },
{ 'X', GDB_HANDLER(WriteMemoryRaw) },
{ 'z', GDB_HANDLER(ToggleStopPoint) },
{ 'Z', GDB_HANDLER(ToggleStopPoint) },
};
static inline GDBCommandHandler GDB_GetCommandHandler(char command)
{
static const u32 nbHandlers = sizeof(gdbCommandHandlers) / sizeof(gdbCommandHandlers[0]);
u32 i;
for(i = 0; i < nbHandlers && gdbCommandHandlers[i].command != command; i++);
return i < nbHandlers ? gdbCommandHandlers[i].handler : GDB_HANDLER(Unsupported);
}
int GDB_DoPacket(GDBContext *ctx)
{
int ret;
RecursiveLock_Lock(&ctx->lock);
GDBFlags oldFlags = ctx->flags;
if(ctx->state == GDB_STATE_DISCONNECTED)
return -1;
int r = GDB_ReceivePacket(ctx);
if(r == 0)
ret = 0;
else if(r == -1)
ret = -1;
else if(ctx->buffer[0] == '\x03')
{
GDB_HandleBreak(ctx);
ret = 0;
}
else if(ctx->buffer[0] == '$')
{
GDBCommandHandler handler = GDB_GetCommandHandler(ctx->buffer[1]);
ctx->commandData = ctx->buffer + 2;
ret = handler(ctx);
}
else
ret = 0;
RecursiveLock_Unlock(&ctx->lock);
if(ctx->state == GDB_STATE_CLOSING)
return -1;
if((oldFlags & GDB_FLAG_PROCESS_CONTINUING) && !(ctx->flags & GDB_FLAG_PROCESS_CONTINUING))
{
if(R_FAILED(svcBreakDebugProcess(ctx->debug)))
ctx->flags |= GDB_FLAG_PROCESS_CONTINUING;
}
else if(!(oldFlags & GDB_FLAG_PROCESS_CONTINUING) && (ctx->flags & GDB_FLAG_PROCESS_CONTINUING))
svcSignalEvent(ctx->continuedEvent);
return ret;
}

View File

@@ -0,0 +1,71 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include "gdb.h"
#include "gdb/net.h"
#include "gdb/watchpoints.h"
#include "gdb/breakpoints.h"
GDB_DECLARE_HANDLER(ToggleStopPoint)
{
bool add = ctx->commandData[-1] == 'Z';
u32 lst[3];
const char *pos = GDB_ParseHexIntegerList(lst, ctx->commandData, 3, ';');
if(pos == NULL)
return GDB_ReplyErrno(ctx, EILSEQ);
bool persist = *pos != 0 && strncmp(pos, ";cmds:1", 7) == 0;
u32 kind = lst[0];
u32 addr = lst[1];
u32 size = lst[2];
int res;
static const WatchpointKind kinds[3] = { WATCHPOINT_WRITE, WATCHPOINT_READ, WATCHPOINT_READWRITE };
switch(kind)
{
case 0: // software breakpoint
if(size != 2 && size != 4)
return GDB_ReplyEmpty(ctx);
else
{
res = add ? GDB_AddBreakpoint(ctx, addr, size == 2, persist) :
GDB_RemoveBreakpoint(ctx, addr);
return res == 0 ? GDB_ReplyOk(ctx) : GDB_ReplyErrno(ctx, -res);
}
// Watchpoints
case 2:
case 3:
case 4:
res = add ? GDB_AddWatchpoint(ctx, addr, size, kinds[kind - 2]) :
GDB_RemoveWatchpoint(ctx, addr, kinds[kind - 2]);
return res == 0 ? GDB_ReplyOk(ctx) : GDB_ReplyErrno(ctx, -res);
default:
return GDB_ReplyEmpty(ctx);
}
}

View File

@@ -0,0 +1,304 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include "gdb/thread.h"
#include "gdb/net.h"
#include "fmt.h"
#include <stdlib.h>
static s32 GDB_GetDynamicThreadPriority(GDBContext *ctx, u32 threadId)
{
Handle process, thread;
Result r;
s32 prio = 65;
r = svcOpenProcess(&process, ctx->pid);
if(R_FAILED(r))
return 65;
r = svcOpenThread(&thread, process, threadId);
if(R_FAILED(r))
goto cleanup;
r = svcGetThreadPriority(&prio, thread);
cleanup:
svcCloseHandle(thread);
svcCloseHandle(process);
return prio;
}
struct ThreadIdWithCtx
{
GDBContext *ctx;
u32 id;
};
static int thread_compare_func(const void *a_, const void *b_)
{
const struct ThreadIdWithCtx *a = (const struct ThreadIdWithCtx *)a_;
const struct ThreadIdWithCtx *b = (const struct ThreadIdWithCtx *)b_;
u32 maskA = 2, maskB = 2;
s32 prioAStatic = 65, prioBStatic = 65;
s32 prioADynamic = GDB_GetDynamicThreadPriority(a->ctx, a->id);
s32 prioBDynamic = GDB_GetDynamicThreadPriority(b->ctx, b->id);
s64 dummy;
svcGetDebugThreadParam(&dummy, &maskA, a->ctx->debug, a->id, DBGTHREAD_PARAMETER_SCHEDULING_MASK_LOW);
svcGetDebugThreadParam(&dummy, &maskB, b->ctx->debug, b->id, DBGTHREAD_PARAMETER_SCHEDULING_MASK_LOW);
svcGetDebugThreadParam(&dummy, (u32 *)&prioAStatic, a->ctx->debug, a->id, DBGTHREAD_PARAMETER_PRIORITY);
svcGetDebugThreadParam(&dummy, (u32 *)&prioBStatic, b->ctx->debug, b->id, DBGTHREAD_PARAMETER_PRIORITY);
if(maskA == 1 && maskB != 1)
return -1;
else if(maskA != 1 && maskB == 1)
return 1;
else if(prioADynamic != prioBDynamic)
return prioADynamic - prioBDynamic;
else
return prioAStatic - prioBStatic;
}
u32 GDB_GetCurrentThreadFromList(GDBContext *ctx, u32 *threadIds, u32 nbThreads)
{
struct ThreadIdWithCtx lst[MAX_DEBUG_THREAD];
for(u32 i = 0; i < nbThreads; i++)
{
lst[i].ctx = ctx;
lst[i].id = threadIds[i];
}
qsort(lst, nbThreads, sizeof(struct ThreadIdWithCtx), thread_compare_func);
return lst[0].id;
}
u32 GDB_GetCurrentThread(GDBContext *ctx)
{
u32 threadIds[MAX_DEBUG_THREAD];
if(ctx->nbThreads == 0)
return 0;
for(u32 i = 0; i < ctx->nbThreads; i++)
threadIds[i] = ctx->threadInfos[i].id;
return GDB_GetCurrentThreadFromList(ctx, threadIds, ctx->nbThreads);
}
GDB_DECLARE_HANDLER(SetThreadId)
{
if(ctx->commandData[0] == 'g')
{
if(strncmp(ctx->commandData + 1, "-1", 2) == 0)
return GDB_ReplyErrno(ctx, EILSEQ); // a thread must be specified
u32 id;
if(GDB_ParseHexIntegerList(&id, ctx->commandData + 1, 1, 0) == NULL)
return GDB_ReplyErrno(ctx, EILSEQ);
ctx->selectedThreadId = id;
return GDB_ReplyOk(ctx);
}
else if(ctx->commandData[0] == 'c')
{
// We can't stop/continue particular threads (uncompliant behavior)
if(strncmp(ctx->commandData + 1, "-1", 2) == 0)
ctx->selectedThreadIdForContinuing = 0;
else
{
u32 id;
if(GDB_ParseHexIntegerList(&id, ctx->commandData + 1, 1, 0) == NULL)
return GDB_ReplyErrno(ctx, EILSEQ);
ctx->selectedThreadIdForContinuing = id;
}
return GDB_ReplyOk(ctx);
}
else
return GDB_ReplyErrno(ctx, EPERM);
}
GDB_DECLARE_HANDLER(IsThreadAlive)
{
u32 threadId;
s64 dummy;
u32 mask;
if(GDB_ParseHexIntegerList(&threadId, ctx->commandData, 1, 0) == NULL)
return GDB_ReplyErrno(ctx, EILSEQ);
Result r = svcGetDebugThreadParam(&dummy, &mask, ctx->debug, threadId, DBGTHREAD_PARAMETER_SCHEDULING_MASK_LOW);
if(R_SUCCEEDED(r) && mask != 2)
return GDB_ReplyOk(ctx);
else
return GDB_ReplyErrno(ctx, EPERM);
}
GDB_DECLARE_QUERY_HANDLER(CurrentThreadId)
{
if(ctx->currentThreadId == 0)
ctx->currentThreadId = GDB_GetCurrentThread(ctx);
return ctx->currentThreadId != 0 ? GDB_SendFormattedPacket(ctx, "QC%x", ctx->currentThreadId) : GDB_ReplyErrno(ctx, EPERM);
}
static void GDB_GenerateThreadListData(GDBContext *ctx)
{
u32 aliveThreadIds[MAX_DEBUG_THREAD];
u32 nbAliveThreads = 0; // just in case. This is probably redundant
for(u32 i = 0; i < ctx->nbThreads; i++)
{
s64 dummy;
u32 mask;
Result r = svcGetDebugThreadParam(&dummy, &mask, ctx->debug, ctx->threadInfos[i].id, DBGTHREAD_PARAMETER_SCHEDULING_MASK_LOW);
if(R_SUCCEEDED(r) && mask != 2)
aliveThreadIds[nbAliveThreads++] = ctx->threadInfos[i].id;
}
if(nbAliveThreads == 0)
ctx->threadListData[0] = 0;
char *bufptr = ctx->threadListData;
for(u32 i = 0; i < nbAliveThreads; i++)
bufptr += sprintf(bufptr, i == (nbAliveThreads - 1) ? "%x" : "%x,", aliveThreadIds[i]);
}
static int GDB_SendThreadData(GDBContext *ctx)
{
u32 sz = strlen(ctx->threadListData);
u32 len;
if(ctx->threadListDataPos >= sz)
len = 0;
else if(sz - ctx->threadListDataPos <= GDB_BUF_LEN - 1)
len = sz - ctx->threadListDataPos;
else
{
for(len = GDB_BUF_LEN - 1; ctx->threadListData[ctx->threadListDataPos + len] != ',' && len > 0; len--);
if(len > 0)
len--;
}
int n = GDB_SendStreamData(ctx, ctx->threadListData, ctx->threadListDataPos, len, sz, true);
if(ctx->threadListDataPos >= sz)
{
ctx->threadListDataPos = 0;
ctx->threadListData[0] = 0;
}
else
ctx->threadListDataPos += len;
return n;
}
GDB_DECLARE_QUERY_HANDLER(fThreadInfo)
{
if(ctx->threadListData[0] == 0)
GDB_GenerateThreadListData(ctx);
return GDB_SendThreadData(ctx);
}
GDB_DECLARE_QUERY_HANDLER(sThreadInfo)
{
return GDB_SendThreadData(ctx);
}
GDB_DECLARE_QUERY_HANDLER(ThreadEvents)
{
switch(ctx->commandData[0])
{
case '0':
ctx->catchThreadEvents = false;
return GDB_ReplyOk(ctx);
case '1':
ctx->catchThreadEvents = true;
return GDB_ReplyOk(ctx);
default:
return GDB_ReplyErrno(ctx, EILSEQ);
}
}
GDB_DECLARE_QUERY_HANDLER(ThreadExtraInfo)
{
u32 id;
s64 dummy;
u32 val;
Result r;
int n;
const char *sStatus;
char sThreadDynamicPriority[64], sThreadStaticPriority[64];
char sCoreIdeal[64], sCoreCreator[64];
char buf[512];
u32 tls = 0;
if(GDB_ParseHexIntegerList(&id, ctx->commandData, 1, 0) == NULL)
return GDB_ReplyErrno(ctx, EILSEQ);
for(u32 i = 0; i < MAX_DEBUG_THREAD; i++)
{
if(ctx->threadInfos[i].id == id)
tls = ctx->threadInfos[i].tls;
}
r = svcGetDebugThreadParam(&dummy, &val, ctx->debug, id, DBGTHREAD_PARAMETER_SCHEDULING_MASK_LOW);
sStatus = R_SUCCEEDED(r) ? (val == 1 ? ", running, " : ", idle, ") : "";
val = (u32)GDB_GetDynamicThreadPriority(ctx, id);
if(val == 65)
sThreadDynamicPriority[0] = 0;
else
sprintf(sThreadDynamicPriority, "dynamic prio.: %d, ", (s32)val);
r = svcGetDebugThreadParam(&dummy, &val, ctx->debug, id, DBGTHREAD_PARAMETER_PRIORITY);
if(R_FAILED(r))
sThreadStaticPriority[0] = 0;
else
sprintf(sThreadStaticPriority, "static prio.: %d, ", (s32)val);
r = svcGetDebugThreadParam(&dummy, &val, ctx->debug, id, DBGTHREAD_PARAMETER_CPU_IDEAL);
if(R_FAILED(r))
sCoreIdeal[0] = 0;
else
sprintf(sCoreIdeal, "ideal core: %u, ", val);
r = svcGetDebugThreadParam(&dummy, &val, ctx->debug, id, DBGTHREAD_PARAMETER_CPU_CREATOR); // Creator = "first ran, and running the thread"
if(R_FAILED(r))
sCoreCreator[0] = 0;
else
sprintf(sCoreCreator, "running on core %u", val);
n = sprintf(buf, "TLS: 0x%08x%s%s%s%s%s", tls, sStatus, sThreadDynamicPriority, sThreadStaticPriority,
sCoreIdeal, sCoreCreator);
return GDB_SendHexPacket(ctx, buf, (u32)n);
}

View File

@@ -0,0 +1,73 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include "gdb/verbose.h"
#include "gdb/net.h"
#include "gdb/debug.h"
static const struct
{
const char *name;
GDBCommandHandler handler;
} gdbVerboseCommandHandlers[] =
{
{ "Cont?", GDB_VERBOSE_HANDLER(ContinueSupported) },
{ "Cont", GDB_VERBOSE_HANDLER(Continue) },
{ "MustReplyEmpty", GDB_HANDLER(Unsupported) },
};
GDB_DECLARE_HANDLER(VerboseCommand)
{
char *nameBegin = ctx->commandData; // w/o leading 'v'
if(*nameBegin == 0)
return GDB_ReplyErrno(ctx, EILSEQ);
char *nameEnd;
char *vData = NULL;
for(nameEnd = nameBegin; *nameEnd != 0 && *nameEnd != ';' && *nameEnd != ':'; nameEnd++);
if(*nameEnd != 0)
{
*nameEnd = 0;
vData = nameEnd + 1;
}
for(u32 i = 0; i < sizeof(gdbVerboseCommandHandlers) / sizeof(gdbVerboseCommandHandlers[0]); i++)
{
if(strcmp(gdbVerboseCommandHandlers[i].name, nameBegin) == 0)
{
ctx->commandData = vData;
return gdbVerboseCommandHandlers[i].handler(ctx);
}
}
return GDB_HandleUnsupported(ctx); // No handler found!
}
GDB_DECLARE_VERBOSE_HANDLER(ContinueSupported)
{
return GDB_SendPacket(ctx, "vCont;c;C", 9);
}

View File

@@ -0,0 +1,177 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include "gdb/watchpoints.h"
#define _REENT_ONLY
#include <errno.h>
/*
There are only 2 Watchpoint Register Pairs on MPCORE ARM11 CPUs,
and only 2 Breakpoint Register Pairs with context ID capabilities (BRP4-5) as well.
We'll reserve and use all 4 of them
*/
RecursiveLock watchpointManagerLock;
typedef struct Watchpoint
{
u32 address;
u32 size;
WatchpointKind kind;
Handle debug; // => context ID
} Watchpoint;
typedef struct WatchpointManager
{
u32 total;
Watchpoint watchpoints[2];
} WatchpointManager;
static WatchpointManager manager;
void GDB_ResetWatchpoints(void)
{
static bool lockInitialized = false;
if(!lockInitialized)
{
RecursiveLock_Init(&watchpointManagerLock);
lockInitialized = true;
}
RecursiveLock_Lock(&watchpointManagerLock);
svcSetHardwareBreakPoint(4, 0, 0);
svcSetHardwareBreakPoint(0x100, 0, 0);
svcSetHardwareBreakPoint(5, 0, 0);
svcSetHardwareBreakPoint(0x101, 0, 0);
memset(&manager, 0, sizeof(WatchpointManager));
RecursiveLock_Unlock(&watchpointManagerLock);
}
int GDB_AddWatchpoint(GDBContext *ctx, u32 address, u32 size, WatchpointKind kind)
{
RecursiveLock_Lock(&watchpointManagerLock);
u32 offset = address - (address & ~3);
if(manager.total == 2)
return -EBUSY;
if(size == 0 || (offset + size) > 4 || kind == WATCHPOINT_DISABLED)
return -EINVAL;
if(GDB_GetWatchpointKind(ctx, address) != WATCHPOINT_DISABLED)
// Disallow duplicate watchpoints: the kernel doesn't give us sufficient info to differentiate them by kind (DFSR)
return -EINVAL;
u32 id = manager.watchpoints[0].kind == WATCHPOINT_DISABLED ? 0 : 1;
u32 selectMask = ((1 << size) - 1) << offset;
u32 BCR = (1 << 21) | /* compare with context ID */
(1 << 20) | /* linked (with a WRP in our case) */
(0xf << 5) | /* byte address select, +0 to +3 as mandated when linking with a WRP */
(3 << 1) | /* either privileged modes or user mode, as mandated when linking with a WRP */
(1 << 0) ; /* enabled */
u32 WCR = (1 << 20) | /* linked */
((4 + id) << 16) | /* ID of the linked BRP */
(selectMask << 5) | /* byte address select */
((u32)kind << 3) | /* kind */
(2 << 1) | /* user mode only */
(1 << 0) ; /* enabled */
Result r = svcSetHardwareBreakPoint(0x100 | id, WCR, address & ~3);
if(R_SUCCEEDED(r))
r = svcSetHardwareBreakPoint(4 + id, BCR, (u32)ctx->debug);
if(R_SUCCEEDED(r))
{
Watchpoint *watchpoint = &manager.watchpoints[id];
manager.total++;
watchpoint->address = address;
watchpoint->size = size;
watchpoint->kind = kind;
watchpoint->debug = ctx->debug;
ctx->watchpoints[ctx->nbWatchpoints++] = address;
RecursiveLock_Unlock(&watchpointManagerLock);
return 0;
}
else
{
RecursiveLock_Unlock(&watchpointManagerLock);
return -EINVAL;
}
}
int GDB_RemoveWatchpoint(GDBContext *ctx, u32 address, WatchpointKind kind)
{
RecursiveLock_Lock(&watchpointManagerLock);
u32 id;
for(id = 0; id < 2 && manager.watchpoints[id].address != address && manager.watchpoints[id].debug != ctx->debug; id++);
if(id == 2 || (kind != WATCHPOINT_DISABLED && manager.watchpoints[id].kind != kind))
{
RecursiveLock_Unlock(&watchpointManagerLock);
return -EINVAL;
}
else
{
svcSetHardwareBreakPoint(4 + id, 0, 0);
svcSetHardwareBreakPoint(0x100 | id, 0, 0);
memset(&manager.watchpoints[id], 0, sizeof(Watchpoint));
manager.total--;
if(ctx->watchpoints[0] == address)
{
ctx->watchpoints[0] = ctx->watchpoints[1];
ctx->watchpoints[1] = 0;
ctx->nbWatchpoints--;
}
else if(ctx->watchpoints[1] == address)
{
ctx->watchpoints[1] = 0;
ctx->nbWatchpoints--;
}
RecursiveLock_Unlock(&watchpointManagerLock);
return 0;
}
}
WatchpointKind GDB_GetWatchpointKind(GDBContext *ctx, u32 address)
{
u32 id;
for(id = 0; id < 2 && manager.watchpoints[id].address != address && manager.watchpoints[id].debug != ctx->debug; id++);
return id == 2 ? WATCHPOINT_DISABLED : manager.watchpoints[id].kind;
}

View File

@@ -0,0 +1,271 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include "gdb/xfer.h"
#include "gdb/net.h"
#include "../../build/xml_data.h"
#include <3ds/os.h>
#include "fmt.h"
struct
{
const char *name;
int (*handler)(GDBContext *ctx, bool write, const char *annex, u32 offset, u32 length);
} xferCommandHandlers[] =
{
{ "features", GDB_XFER_HANDLER(Features) },
{ "osdata", GDB_XFER_HANDLER(OsData) },
};
GDB_DECLARE_XFER_HANDLER(Features)
{
if(strcmp(annex, "target.xml") != 0 || write)
return GDB_ReplyEmpty(ctx);
else
return GDB_SendStreamData(ctx, target_xml, offset, length, sizeof(target_xml) - 1, false);
}
struct
{
const char *name;
int (*handler)(GDBContext *ctx, bool write, u32 offset, u32 length);
} xferOsDataCommandHandlers[] =
{
{ "cfwversion", GDB_XFER_OSDATA_HANDLER(CfwVersion) },
{ "memory", GDB_XFER_OSDATA_HANDLER(Memory) },
{ "processes", GDB_XFER_OSDATA_HANDLER(Processes) },
};
GDB_DECLARE_XFER_OSDATA_HANDLER(CfwVersion)
{
if(write)
return GDB_HandleUnsupported(ctx);
else
{
char buf[sizeof(osdata_cfw_version_template_xml) + 64];
char versionString[16];
s64 out;
u32 version, commitHash;
bool isRelease;
u32 sz;
svcGetSystemInfo(&out, 0x10000, 0);
version = (u32)out;
svcGetSystemInfo(&out, 0x10000, 1);
commitHash = (u32)out;
svcGetSystemInfo(&out, 0x10000, 3);
isRelease = (bool)out;
if(GET_VERSION_REVISION(version) == 0)
sprintf(versionString, "v%u.%u", GET_VERSION_MAJOR(version), GET_VERSION_MINOR(version));
else
sprintf(versionString, "v%u.%u.%u", GET_VERSION_MAJOR(version), GET_VERSION_MINOR(version), GET_VERSION_REVISION(version));
sz = (u32)sprintf(buf, osdata_cfw_version_template_xml, versionString, commitHash, isRelease ? "Yes" : "No");
return GDB_SendStreamData(ctx, buf, offset, length, sz, false);
}
}
GDB_DECLARE_XFER_OSDATA_HANDLER(Memory)
{
if(write)
return GDB_HandleUnsupported(ctx);
else
{
if(ctx->memoryOsInfoXmlData[0] == 0)
{
s64 out;
u32 applicationUsed, systemUsed, baseUsed;
u32 applicationTotal = *(vu32 *)0x1FF80040, systemTotal = *(vu32 *)0x1FF80044, baseTotal = *(vu32 *)0x1FF80048;
svcGetSystemInfo(&out, 0, 1);
applicationUsed = (u32)out;
svcGetSystemInfo(&out, 0, 2);
systemUsed = (u32)out;
svcGetSystemInfo(&out, 0, 3);
baseUsed = (u32)out;
sprintf(ctx->memoryOsInfoXmlData, osdata_memory_template_xml,
applicationUsed, applicationTotal - applicationUsed, applicationTotal, (u32)((5ULL + ((1000ULL * applicationUsed) / applicationTotal)) / 10ULL),
systemUsed, systemTotal - systemUsed, systemTotal, (u32)((5ULL + ((1000ULL * systemUsed) / systemTotal)) / 10ULL),
baseUsed, baseTotal - baseUsed, baseTotal, (u32)((5ULL + ((1000ULL * baseUsed) / baseTotal)) / 10ULL)
);
}
u32 size = strlen(ctx->memoryOsInfoXmlData);
int n = GDB_SendStreamData(ctx, ctx->memoryOsInfoXmlData, offset, length, size, false);
if(offset + length >= size)
ctx->memoryOsInfoXmlData[0] = 0; // we're done, invalidate
return n;
}
}
GDB_DECLARE_XFER_OSDATA_HANDLER(Processes)
{
if(write)
return GDB_HandleUnsupported(ctx);
else
{
if(ctx->processesOsInfoXmlData[0] == 0)
{
static const char header[] =
"<?xml version=\"1.0\"?>\n"
"<!DOCTYPE target SYSTEM \"osdata.dtd\">\n"
"<osdata type=\"processes\">\n";
static const char item[] =
" <item>\n"
" <column name=\"pid\">%u</column>\n"
" <column name=\"command\">%s</column>\n"
" </item>\n";
static const char footer[] = "</osdata>\n";
int n;
u32 pos = 0;
u32 pidList[0x40];
s32 processAmount;
strcpy(ctx->processesOsInfoXmlData, header);
pos = sizeof(header) - 1;
svcGetProcessList(&processAmount, pidList, 0x40);
for(s32 i = 0; i < processAmount; i++)
{
u32 pid = pidList[i];
char name[9] = { 0 };
s64 out;
Handle processHandle;
Result res = svcOpenProcess(&processHandle, pidList[i]);
if(R_FAILED(res))
continue;
svcGetProcessInfo(&out, processHandle, 0x10000);
memcpy(name, &out, 8);
svcCloseHandle(processHandle);
n = sprintf(ctx->processesOsInfoXmlData + pos, item, pid, name);
pos += (u32)n;
}
strcpy(ctx->processesOsInfoXmlData + pos, footer);
pos = sizeof(footer) - 1;
}
u32 size = strlen(ctx->processesOsInfoXmlData);
int n = GDB_SendStreamData(ctx, ctx->processesOsInfoXmlData, offset, length, size, false);
if(offset + length >= size)
ctx->processesOsInfoXmlData[0] = 0; // we're done, invalidate
return n;
}
}
GDB_DECLARE_XFER_HANDLER(OsData)
{
if(strcmp(annex, "") == 0 && !write)
return GDB_SendStreamData(ctx, osdata_xml, offset, length, sizeof(osdata_xml) - 1, false);
else
{
for(u32 i = 0; i < sizeof(xferOsDataCommandHandlers) / sizeof(xferOsDataCommandHandlers[0]); i++)
{
if(strcmp(annex, xferOsDataCommandHandlers[i].name) == 0)
return xferOsDataCommandHandlers[i].handler(ctx, write, offset, length);
}
}
return GDB_HandleUnsupported(ctx);
}
GDB_DECLARE_QUERY_HANDLER(Xfer)
{
const char *objectStart = ctx->commandData;
char *objectEnd = (char*)strchr(objectStart, ':');
if(objectEnd == NULL) return -1;
*objectEnd = 0;
char *opStart = objectEnd + 1;
char *opEnd = (char*)strchr(opStart, ':');
if(opEnd == NULL) return -1;
*opEnd = 0;
char *annexStart = opEnd + 1;
char *annexEnd = (char*)strchr(annexStart, ':');
if(annexEnd == NULL) return -1;
*annexEnd = 0;
const char *offStart = annexEnd + 1;
u32 offset, length;
bool write;
const char *pos;
if(strcmp(opStart, "read") == 0)
{
u32 lst[2];
if(GDB_ParseHexIntegerList(lst, offStart, 2, 0) == NULL)
return GDB_ReplyErrno(ctx, EILSEQ);
offset = lst[0];
length = lst[1];
write = false;
}
else if(strcmp(opStart, "write") == 0)
{
pos = GDB_ParseHexIntegerList(&offset, offStart, 1, ':');
if(pos == NULL || *pos++ != ':')
return GDB_ReplyErrno(ctx, EILSEQ);
u32 len = strlen(pos);
if(len == 0 || (len % 2) != 0)
return GDB_ReplyErrno(ctx, EILSEQ);
length = len / 2;
write = true;
}
else
return GDB_ReplyErrno(ctx, EILSEQ);
for(u32 i = 0; i < sizeof(xferCommandHandlers) / sizeof(xferCommandHandlers[0]); i++)
{
if(strcmp(objectStart, xferCommandHandlers[i].name) == 0)
{
if(write)
ctx->commandData = (char *)pos;
return xferCommandHandlers[i].handler(ctx, write, annexStart, offset, length);
}
}
return GDB_HandleUnsupported(ctx);
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0"?>
<!DOCTYPE target SYSTEM "osdata.dtd">
<osdata type="types">
<item>
<column name="Type">cfwversion</column>
<column name="Description">Listing of the Luma3DS version, commit hash, etc.</column>
<column name="Title">CFW</column>
</item>
<item>
<column name="Type">memory</column>
<column name="Description">Listing of memory usage per region</column>
<column name="Title">Memory</column>
</item>
<item>
<column name="Type">processes</column>
<column name="Description">Listing of all processes</column>
<column name="Title">Processes</column>
</item>
</osdata>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<!DOCTYPE target SYSTEM "osdata.dtd">
<osdata type="cfwversion">
<item>
<column name="Version">%s</column>
<column name="Commit hash">%08x</column>
<column name="Release">%s</column>
</item>
</osdata>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0"?>
<!DOCTYPE target SYSTEM "osdata.dtd">
<osdata type="memory">
<item>
<column name="Region">APPLICATION</column>
<column name="Bytes used">%u</column>
<column name="Bytes free">%u</column>
<column name="Total size">%u</column>
<column name="Percentage used">%u%%</column>
</item>
<item>
<column name="Region">SYSTEM</column>
<column name="Bytes used">%u</column>
<column name="Bytes free">%u</column>
<column name="Total size">%u</column>
<column name="Percentage used">%u%%</column>
</item>
<item>
<column name="Region">BASE</column>
<column name="Bytes used">%u</column>
<column name="Bytes free">%u</column>
<column name="Total size">%u</column>
<column name="Percentage used">%u%%</column>
</item>
</osdata>

View File

@@ -0,0 +1,45 @@
<?xml version="1.0"?>
<!DOCTYPE target SYSTEM "gdb-target.dtd">
<target version="1.0">
<architecture>arm</architecture>
<osabi>3DS</osabi>
<feature name="org.gnu.gdb.arm.core">
<reg name="r0" bitsize="32"/>
<reg name="r1" bitsize="32"/>
<reg name="r2" bitsize="32"/>
<reg name="r3" bitsize="32"/>
<reg name="r4" bitsize="32"/>
<reg name="r5" bitsize="32"/>
<reg name="r6" bitsize="32"/>
<reg name="r7" bitsize="32"/>
<reg name="r8" bitsize="32"/>
<reg name="r9" bitsize="32"/>
<reg name="r10" bitsize="32"/>
<reg name="r11" bitsize="32"/>
<reg name="r12" bitsize="32"/>
<reg name="sp" bitsize="32" type="data_ptr"/>
<reg name="lr" bitsize="32"/>
<reg name="pc" bitsize="32" type="code_ptr"/>
<reg name="cpsr" bitsize="32" regnum="25"/>
</feature>
<feature name="org.gnu.gdb.arm.vfp">
<reg name="d0" bitsize="64" type="float"/>
<reg name="d1" bitsize="64" type="float"/>
<reg name="d2" bitsize="64" type="float"/>
<reg name="d3" bitsize="64" type="float"/>
<reg name="d4" bitsize="64" type="float"/>
<reg name="d5" bitsize="64" type="float"/>
<reg name="d6" bitsize="64" type="float"/>
<reg name="d7" bitsize="64" type="float"/>
<reg name="d8" bitsize="64" type="float"/>
<reg name="d9" bitsize="64" type="float"/>
<reg name="d10" bitsize="64" type="float"/>
<reg name="d11" bitsize="64" type="float"/>
<reg name="d12" bitsize="64" type="float"/>
<reg name="d13" bitsize="64" type="float"/>
<reg name="d14" bitsize="64" type="float"/>
<reg name="d15" bitsize="64" type="float"/>
<reg name="fpscr" bitsize="32" type="int" group="float"/>
<reg name="fpexc" bitsize="32" type="int" group="float"/>
</feature>
</target>

View File

@@ -0,0 +1,396 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
/* This file was entirely written by fincs */
#include <3ds.h>
#include "hbloader.h"
#include "3dsx.h"
#include "menu.h"
#include "csvc.h"
#include "memory.h"
#include "exheader.h"
#define MAP_BASE 0x10000000
static const char serviceList[32][8] =
{
"APT:U",
"ac:u",
"am:net",
"boss:P",
"cam:u",
"cfg:nor",
"cfg:u",
"csnd:SND",
"dsp::DSP",
"fs:USER",
"gsp::Lcd",
"gsp::Gpu",
"hid:USER",
"http:C",
"ir:USER",
"ir:rst",
"ir:u",
"ldr:ro",
"mic:u",
"ndm:u",
"news:s",
"nim:s",
"ns:s",
"nwm::UDS",
"nwm::EXT",
"ptm:u",
"ptm:sysm",
"pxi:dev",
"qtm:u",
"soc:U",
"ssl:C",
"y2r:u",
};
static const u64 dependencyList[] =
{
0x0004013000002402LL, //ac
0x0004013000001502LL, //am
0x0004013000003402LL, //boss
0x0004013000001602LL, //camera
0x0004013000001702LL, //cfg
0x0004013000001802LL, //codec
0x0004013000002702LL, //csnd
0x0004013000002802LL, //dlp
0x0004013000001A02LL, //dsp
0x0004013000001B02LL, //gpio
0x0004013000001C02LL, //gsp
0x0004013000001D02LL, //hid
0x0004013000002902LL, //http
0x0004013000001E02LL, //i2c
0x0004013000003302LL, //ir
0x0004013000001F02LL, //mcu
0x0004013000002002LL, //mic
0x0004013000002B02LL, //ndm
0x0004013000003502LL, //news
0x0004013000002C02LL, //nim
0x0004013000002D02LL, //nwm
0x0004013000002102LL, //pdn
0x0004013000003102LL, //ps
0x0004013000002202LL, //ptm
0x0004013000003702LL, //ro
0x0004013000002E02LL, //socket
0x0004013000002302LL, //spi
0x0004013000002F02LL, //ssl
};
static const u32 kernelCaps[] =
{
0xFC00022C, // Kernel release version: 8.0 (necessary for using the new linear mapping)
0xFF81FF50, // RW static mapping: 0x1FF50000
0xFF81FF58, // RW static mapping: 0x1FF58000
0xFF81FF70, // RW static mapping: 0x1FF70000
0xFF81FF78, // RW static mapping: 0x1FF78000
0xFF91F000, // RO static mapping: 0x1F000000
0xFF91F600, // RO static mapping: 0x1F600000
0xFF002101, // Exflags: APPLICATION memtype + "Allow debug" + "Access core2"
0xFE000200, // Handle table size: 0x200
};
static inline void assertSuccess(Result res)
{
if(R_FAILED(res))
svcBreak(USERBREAK_PANIC);
}
static MyThread hbldrThread;
static u8 ALIGN(8) hbldrThreadStack[THREAD_STACK_SIZE];
static u16 hbldrTarget[PATH_MAX+1];
MyThread *hbldrCreateThread(void)
{
assertSuccess(MyThread_Create(&hbldrThread, hbldrThreadMain, hbldrThreadStack, THREAD_STACK_SIZE, 0x18, CORE_SYSTEM));
return &hbldrThread;
}
static inline void error(u32* cmdbuf, Result rc)
{
cmdbuf[0] = IPC_MakeHeader(0, 1, 0);
cmdbuf[1] = rc;
}
static u16 *u16_strncpy(u16 *dest, const u16 *src, u32 size)
{
u32 i;
for (i = 0; i < size && src[i] != 0; i++)
dest[i] = src[i];
while (i < size)
dest[i++] = 0;
return dest;
}
static void HBLDR_HandleCommands(void)
{
Result res;
IFile file;
u32 *cmdbuf = getThreadCommandBuffer();
switch (cmdbuf[0] >> 16)
{
case 1:
{
if (cmdbuf[0] != IPC_MakeHeader(1, 6, 0))
{
error(cmdbuf, 0xD9001830);
break;
}
u32 baseAddr = cmdbuf[1];
u32 flags = cmdbuf[2] & 0xF00;
u64 tid = (u64)cmdbuf[3] | ((u64)cmdbuf[4]<<32);
char name[8];
memcpy(name, &cmdbuf[5], sizeof(name));
if (hbldrTarget[0] == 0)
{
u16_strncpy(hbldrTarget, u"/boot.3dsx", PATH_MAX);
ldrArgvBuf[0] = 1;
strncpy((char*)&ldrArgvBuf[1], "sdmc:/boot.3dsx", sizeof(ldrArgvBuf)-4);
}
res = IFile_Open(&file, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, ""), fsMakePath(PATH_UTF16, hbldrTarget), FS_OPEN_READ);
hbldrTarget[0] = 0;
if (R_FAILED(res))
{
error(cmdbuf, res);
break;
}
u32 totalSize = 0;
res = Ldr_Get3dsxSize(&totalSize, &file);
if (R_FAILED(res))
{
IFile_Close(&file);
error(cmdbuf, res);
break;
}
u32 tmp = 0;
res = svcControlMemoryEx(&tmp, MAP_BASE, 0, totalSize, MEMOP_ALLOC | flags, MEMPERM_READ | MEMPERM_WRITE, true);
if (R_FAILED(res))
{
IFile_Close(&file);
error(cmdbuf, res);
break;
}
Handle hCodeset = Ldr_CodesetFrom3dsx(name, (u32*)MAP_BASE, baseAddr, &file, tid);
IFile_Close(&file);
if (!hCodeset)
{
svcControlMemory(&tmp, MAP_BASE, 0, totalSize, MEMOP_FREE, 0);
error(cmdbuf, MAKERESULT(RL_PERMANENT, RS_INTERNAL, RM_LDR, RD_NOT_FOUND));
break;
}
cmdbuf[0] = IPC_MakeHeader(1, 1, 2);
cmdbuf[1] = 0;
cmdbuf[2] = IPC_Desc_MoveHandles(1);
cmdbuf[3] = hCodeset;
break;
}
case 2:
{
if (cmdbuf[0] != IPC_MakeHeader(2, 0, 2) || (cmdbuf[1] & 0x3FFF) != 0x0002)
{
error(cmdbuf, 0xD9001830);
break;
}
ssize_t units = utf8_to_utf16(hbldrTarget, (const uint8_t*)cmdbuf[2], PATH_MAX);
if (units < 0 || units > PATH_MAX)
{
hbldrTarget[0] = 0;
error(cmdbuf, 0xD9001830);
break;
}
hbldrTarget[units] = 0;
cmdbuf[0] = IPC_MakeHeader(2, 1, 0);
cmdbuf[1] = 0;
break;
}
case 3:
{
if (cmdbuf[0] != IPC_MakeHeader(3, 0, 2) || (cmdbuf[1] & 0x3FFF) != (0x2 | (1<<10)))
{
error(cmdbuf, 0xD9001830);
break;
}
// No need to do anything - the kernel already copied the data to our buffer
cmdbuf[0] = IPC_MakeHeader(3, 1, 0);
cmdbuf[1] = 0;
break;
}
case 4:
{
if (cmdbuf[0] != IPC_MakeHeader(4, 0, 2) || cmdbuf[1] != IPC_Desc_Buffer(sizeof(exheader_header), IPC_BUFFER_RW))
{
error(cmdbuf, 0xD9001830);
break;
}
// Perform ExHeader patches
exheader_header* exh = (exheader_header*)cmdbuf[2];
u32 stacksize = 4096; // 3dsx/libctru don't require anymore than this
memcpy(exh->codesetinfo.name, "3dsx_app", 8);
memcpy(exh->codesetinfo.stacksize, &stacksize, 4);
memset(&exh->deplist, 0, sizeof(exh->deplist));
memcpy(exh->deplist.programid, dependencyList, sizeof(dependencyList));
exheader_arm11systemlocalcaps* localcaps0 = &exh->arm11systemlocalcaps;
exheader_arm11systemlocalcaps* localcaps1 = &exh->accessdesc.arm11systemlocalcaps;
localcaps0->flags[0] = 0x00;
localcaps1->flags[0] = 0x00;
localcaps0->flags[1] = 0x01;
localcaps1->flags[1] = 0x01;
localcaps0->flags[2] = 0x04;
localcaps1->flags[2] = 0x05;
localcaps0->priority = 0x30;
localcaps1->priority = 0;
memset(localcaps0->resourcelimitdescriptor, 0, 0x10);
memset(localcaps1->resourcelimitdescriptor, 0, 0x10);
memset(localcaps0->storageinfo.accessinfo, 0xFF, 7);
memset(localcaps1->storageinfo.accessinfo, 0xFF, 7);
memcpy(localcaps0->serviceaccesscontrol, serviceList, sizeof(serviceList));
memcpy(localcaps1->serviceaccesscontrol, serviceList, sizeof(serviceList));
memset(localcaps0->serviceaccesscontrol+0x20, 0, 2);
memset(localcaps1->serviceaccesscontrol+0x20, 0, 2);
localcaps0->resourcelimitcategory = 0;
localcaps1->resourcelimitcategory = 0;
exheader_arm11kernelcapabilities* kcaps0 = &exh->arm11kernelcaps;
exheader_arm11kernelcapabilities* kcaps1 = &exh->accessdesc.arm11kernelcaps;
memset(kcaps0->descriptors, 0xFF, sizeof(kcaps0->descriptors));
memset(kcaps1->descriptors, 0xFF, sizeof(kcaps1->descriptors));
memcpy(kcaps0->descriptors, kernelCaps, sizeof(kernelCaps));
memcpy(kcaps1->descriptors, kernelCaps, sizeof(kernelCaps));
u64 lastdep = sizeof(dependencyList)/8;
if (osGetFirmVersion() >= SYSTEM_VERSION(2,50,0)) // 9.6+ FIRM
{
exh->deplist.programid[lastdep++] = 0x0004013000004002ULL; // nfc
strncpy((char*)&localcaps0->serviceaccesscontrol[0x20], "nfc:u", 8);
strncpy((char*)&localcaps1->serviceaccesscontrol[0x20], "nfc:u", 8);
s64 dummy = 0;
bool isN3DS = svcGetSystemInfo(&dummy, 0x10001, 0) == 0;
if (isN3DS)
{
exh->deplist.programid[lastdep++] = 0x0004013020004102ULL; // mvd
strncpy((char*)&localcaps0->serviceaccesscontrol[0x21], "mvd:STD", 8);
strncpy((char*)&localcaps1->serviceaccesscontrol[0x21], "mvd:STD", 8);
}
}
cmdbuf[0] = IPC_MakeHeader(4, 1, 2);
cmdbuf[1] = 0;
cmdbuf[2] = IPC_Desc_Buffer(sizeof(exheader_header), IPC_BUFFER_RW);
cmdbuf[3] = (u32)exh;
break;
}
default:
{
error(cmdbuf, 0xD900182F);
break;
}
}
}
void hbldrThreadMain(void)
{
Handle handles[2];
Handle serverHandle, clientHandle, sessionHandle = 0;
u32 replyTarget = 0;
s32 index;
char ipcBuf[PATH_MAX+1];
u32* bufPtrs = getThreadStaticBuffers();
memset(bufPtrs, 0, 16*2*4);
bufPtrs[0] = IPC_Desc_StaticBuffer(sizeof(ipcBuf), 0);
bufPtrs[1] = (u32)ipcBuf;
bufPtrs[2] = IPC_Desc_StaticBuffer(sizeof(ldrArgvBuf), 1);
bufPtrs[3] = (u32)ldrArgvBuf;
Result res;
u32 *cmdbuf = getThreadCommandBuffer();
assertSuccess(svcCreatePort(&serverHandle, &clientHandle, "hb:ldr", 1));
do
{
handles[0] = serverHandle;
handles[1] = sessionHandle;
if(replyTarget == 0) // k11
cmdbuf[0] = 0xFFFF0000;
res = svcReplyAndReceive(&index, handles, 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)
{
Handle session;
assertSuccess(svcAcceptSession(&session, serverHandle));
if(sessionHandle == 0)
sessionHandle = session;
else
svcCloseHandle(session);
}
else
{
HBLDR_HandleCommands();
replyTarget = sessionHandle;
}
}
}
while(!terminationRequest);
svcCloseHandle(sessionHandle);
svcCloseHandle(clientHandle);
svcCloseHandle(serverHandle);
}

View File

@@ -0,0 +1,130 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include <3ds.h>
#include "ifile.h"
Result IFile_Open(IFile *file, FS_ArchiveID archiveId, FS_Path archivePath, FS_Path filePath, u32 flags)
{
Result res;
res = FSUSER_OpenFileDirectly(&file->handle, archiveId, archivePath, filePath, flags, 0);
file->pos = 0;
file->size = 0;
return res;
}
Result IFile_Close(IFile *file)
{
return FSFILE_Close(file->handle);
}
Result IFile_GetSize(IFile *file, u64 *size)
{
Result res;
res = FSFILE_GetSize(file->handle, size);
file->size = *size;
return res;
}
Result IFile_Read(IFile *file, u64 *total, void *buffer, u32 len)
{
u32 read;
u32 left;
char *buf;
u64 cur;
Result res;
if (len == 0)
{
*total = 0;
return 0;
}
buf = (char *)buffer;
cur = 0;
left = len;
while (1)
{
res = FSFILE_Read(file->handle, &read, file->pos, buf, left);
if (R_FAILED(res))
{
break;
}
cur += read;
file->pos += read;
if (read == left)
{
break;
}
buf += read;
left -= read;
}
*total = cur;
return res;
}
Result IFile_Write(IFile *file, u64 *total, void *buffer, u32 len, u32 flags)
{
u32 written;
u32 left;
char *buf;
u64 cur;
Result res;
if (len == 0)
{
*total = 0;
return 0;
}
buf = (char *)buffer;
cur = 0;
left = len;
while (1)
{
res = FSFILE_Write(file->handle, &written, file->pos, buf, left, flags);
if (R_FAILED(res))
{
break;
}
cur += written;
file->pos += written;
if (written == left)
{
break;
}
buf += written;
left -= written;
}
*total = cur;
return res;
}

View File

@@ -0,0 +1,306 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include <3ds.h>
#include <arpa/inet.h>
#include "utils.h" // for makeARMBranch
#include "minisoc.h"
#include "input_redirection.h"
#include "menus.h"
#include "memory.h"
bool inputRedirectionEnabled = false;
Handle inputRedirectionThreadStartedEvent;
static MyThread inputRedirectionThread;
static u8 ALIGN(8) inputRedirectionThreadStack[0x4000];
MyThread *inputRedirectionCreateThread(void)
{
if(R_FAILED(MyThread_Create(&inputRedirectionThread, inputRedirectionThreadMain, inputRedirectionThreadStack, 0x4000, 0x20, CORE_SYSTEM)))
svcBreak(USERBREAK_PANIC);
return &inputRedirectionThread;
}
// local hid, local tsrd localcprd, localtswr, localcpwr, remote hid, remote ts, remote circle
static u32 hidData[] = { 0x00000FFF, 0x02000000, 0x007FF7FF, 0x00000000, 0x00000000, 0x00000FFF, 0x02000000, 0x007FF7FF };
// remote ir
static u32 irData[] = { 0x80800081 }; // Default: C-Stick at the center, no buttons.
int inputRedirectionStartResult;
void inputRedirectionThreadMain(void)
{
Result res = 0;
res = miniSocInit();
if(R_FAILED(res))
return;
int sock = socSocket(AF_INET, SOCK_DGRAM, 0);
while(sock == -1)
{
svcSleepThread(1000 * 0000 * 0000LL);
sock = socSocket(AF_INET, SOCK_DGRAM, 0);
}
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(4950);
saddr.sin_addr.s_addr = gethostid();
res = socBind(sock, (struct sockaddr*)&saddr, sizeof(struct sockaddr_in));
if(res != 0)
{
socClose(sock);
miniSocExit();
inputRedirectionStartResult = res;
return;
}
inputRedirectionEnabled = true;
svcSignalEvent(inputRedirectionThreadStartedEvent);
u32 *hidDataPhys = PA_FROM_VA_PTR(hidData);
hidDataPhys += 5; // skip to +20
u32 *irDataPhys = PA_FROM_VA_PTR(irData);
char buf[20];
u32 oldSpecialButtons = 0, specialButtons = 0;
while(inputRedirectionEnabled && !terminationRequest)
{
struct pollfd pfd;
pfd.fd = sock;
pfd.events = POLLIN;
pfd.revents = 0;
int pollres = socPoll(&pfd, 1, 10);
if(pollres > 0 && (pfd.revents & POLLIN))
{
int n = soc_recvfrom(sock, buf, 20, 0, NULL, 0);
if(n < 0)
break;
else if(n < 12)
continue;
memcpy(hidDataPhys, buf, 12);
if(n >= 20)
{
memcpy(irDataPhys, buf + 12, 4);
oldSpecialButtons = specialButtons;
memcpy(&specialButtons, buf + 16, 4);
if(!(oldSpecialButtons & 1) && (specialButtons & 1)) // HOME button pressed
srvPublishToSubscriber(0x204, 0);
else if((oldSpecialButtons & 1) && !(specialButtons & 1)) // HOME button released
srvPublishToSubscriber(0x205, 0);
if(!(oldSpecialButtons & 2) && (specialButtons & 2)) // POWER button pressed
srvPublishToSubscriber(0x202, 0);
if(!(oldSpecialButtons & 4) && (specialButtons & 4)) // POWER button held long
srvPublishToSubscriber(0x203, 0);
}
}
}
struct linger linger;
linger.l_onoff = 1;
linger.l_linger = 0;
socSetsockopt(sock, SOL_SOCKET, SO_LINGER, &linger, sizeof(struct linger));
socClose(sock);
miniSocExit();
}
void hidCodePatchFunc(void);
void irCodePatchFunc(void);
Result InputRedirection_DoOrUndoPatches(void)
{
s64 startAddress, textTotalRoundedSize, rodataTotalRoundedSize, dataTotalRoundedSize;
u32 totalSize;
Handle processHandle;
Result res = OpenProcessByName("hid", &processHandle);
if(R_SUCCEEDED(res))
{
svcGetProcessInfo(&textTotalRoundedSize, processHandle, 0x10002); // only patch .text + .data
svcGetProcessInfo(&rodataTotalRoundedSize, processHandle, 0x10003);
svcGetProcessInfo(&dataTotalRoundedSize, processHandle, 0x10004);
totalSize = (u32)(textTotalRoundedSize + rodataTotalRoundedSize + dataTotalRoundedSize);
svcGetProcessInfo(&startAddress, processHandle, 0x10005);
res = svcMapProcessMemoryEx(processHandle, 0x00100000, (u32) startAddress, totalSize);
if(R_SUCCEEDED(res))
{
static const u32 hidOrigRegisterAndValue[] = { 0x1EC46000, 0x4001 };
static const u32 hidOrigCode[] = {
0xE92D4070, // push {r4-r6, lr}
0xE1A05001, // mov r5, r1
0xEE1D4F70, // mrc p15, 0, r4, c13, c0, 3
0xE3A01801, // mov r1, #0x10000
0xE5A41080, // str r1, [r4,#0x80]!
};
static u32 *hidRegPatchOffsets[2];
static u32 *hidPatchJumpLoc;
if(inputRedirectionEnabled)
{
memcpy(hidRegPatchOffsets[0], &hidOrigRegisterAndValue, sizeof(hidOrigRegisterAndValue));
memcpy(hidRegPatchOffsets[1], &hidOrigRegisterAndValue, sizeof(hidOrigRegisterAndValue));
memcpy(hidPatchJumpLoc, &hidOrigCode, sizeof(hidOrigCode));
}
else
{
u32 hidDataPhys = (u32)PA_FROM_VA_PTR(hidData);
u32 hidCodePhys = (u32)PA_FROM_VA_PTR(&hidCodePatchFunc);
u32 hidHook[] = {
0xE59F3004, // ldr r3, [pc, #4]
0xE59FC004, // ldr r12, [pc, #4]
0xE12FFF1C, // bx r12
hidDataPhys,
hidCodePhys,
};
u32 *off = (u32 *)memsearch((u8 *)0x00100000, &hidOrigRegisterAndValue, totalSize, sizeof(hidOrigRegisterAndValue));
if(off == NULL)
{
svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
return -1;
}
u32 *off2 = (u32 *)memsearch((u8 *)off + sizeof(hidOrigRegisterAndValue), &hidOrigRegisterAndValue, totalSize - ((u32)off - 0x00100000), sizeof(hidOrigRegisterAndValue));
if(off2 == NULL)
{
svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
return -2;
}
u32 *off3 = (u32 *)memsearch((u8 *)0x00100000, &hidOrigCode, totalSize, sizeof(hidOrigCode));
if(off3 == NULL)
{
svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
return -3;
}
hidRegPatchOffsets[0] = off;
hidRegPatchOffsets[1] = off2;
hidPatchJumpLoc = off3;
*off = *off2 = hidDataPhys;
memcpy(off3, &hidHook, sizeof(hidHook));
}
}
res = svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
}
res = OpenProcessByName("ir", &processHandle);
if(R_SUCCEEDED(res))
{
svcGetProcessInfo(&textTotalRoundedSize, processHandle, 0x10002); // only patch .text + .data
svcGetProcessInfo(&rodataTotalRoundedSize, processHandle, 0x10003);
svcGetProcessInfo(&dataTotalRoundedSize, processHandle, 0x10004);
totalSize = (u32)(textTotalRoundedSize + rodataTotalRoundedSize + dataTotalRoundedSize);
svcGetProcessInfo(&startAddress, processHandle, 0x10005);
res = svcMapProcessMemoryEx(processHandle, 0x00100000, (u32) startAddress, totalSize);
if(R_SUCCEEDED(res))
{
static u32 irOrigReadingCode[5] = {
0xE5940000, // ldr r0, [r4]
0xE1A01005, // mov r1, r5
0xE3A03005, // mov r3, #5
0xE3A02011, // mov r2, #17
};
static const u32 irOrigWaitSyncCode[] = {
0xEF000024, // svc 0x24 (WaitSynchronization)
0xE1B01FA0, // movs r1, r0,lsr#31
0xE1A0A000, // mov r10, r0
};
static u32 *irHookLoc, *irWaitSyncLoc;
if(inputRedirectionEnabled)
{
memcpy(irHookLoc, &irOrigReadingCode, sizeof(irOrigReadingCode));
memcpy(irWaitSyncLoc, &irOrigWaitSyncCode, sizeof(irOrigWaitSyncCode));
}
else
{
u32 irDataPhys = (u32)PA_FROM_VA_PTR(irData);
u32 irCodePhys = (u32)PA_FROM_VA_PTR(&irCodePatchFunc);
u32 irHook[] = {
0xE59F0004, // ldr r0, [pc, #4]
0xE59FC004, // ldr r12, [pc, #4]
0xE12FFF1C, // bx r12
irDataPhys,
irCodePhys,
};
u32 *off = (u32 *)memsearch((u8 *)0x00100000, &irOrigReadingCode, totalSize, sizeof(irOrigReadingCode) - 4);
if(off == NULL)
{
svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
return -4;
}
u32 *off2 = (u32 *)memsearch((u8 *)0x00100000, &irOrigWaitSyncCode, totalSize, sizeof(irOrigWaitSyncCode));
if(off2 == NULL)
{
svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
return -5;
}
*(void **)(irCodePhys + 4) = decodeARMBranch(off + 4);
irHookLoc = off;
irWaitSyncLoc = off2;
irOrigReadingCode[4] = off[4];
memcpy(irHookLoc, &irHook, sizeof(irHook));
// This "NOP"s out a WaitSynchronisation1 (on the event bound to the 'IR' interrupt)
*irWaitSyncLoc = 0xE3A00000; // mov r0, #0
memcpy((u32*)irHookLoc, irHook, sizeof(irHook));
}
}
res = svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
}
return res;
}

View File

@@ -0,0 +1,140 @@
@ This file is part of Luma3DS
@ Copyright (C) 2016-2017 Aurora Wright, TuxSH
@
@ This program is free software: you can redistribute it and/or modify
@ it under the terms of the GNU General Public License as published by
@ the Free Software Foundation, either version 3 of the License, or
@ (at your option) any later version.
@
@ This program is distributed in the hope that 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/>.
@
@ Additional Terms 7.b and 7.c of GPLv3 apply to this file:
@ * Requiring preservation of specified reasonable legal notices or
@ author attributions in that material or in the Appropriate Legal
@ Notices displayed by works containing it.
@ * Prohibiting misrepresentation of the origin of that material,
@ or requiring that modified versions of such material be marked in
@ reasonable ways as different from the original version.
@ Hooks mainly written by Stary (PoC originally by Shiny Quagsire)
.section .rodata
.balign 4
.arm
@@@@@ HID hook @@@@@
.global hidCodePatchFunc
.type hidCodePatchFunc, %function
hidCodePatchFunc:
push {r4-r6, lr}
push {r3}
mov r5, r1
mrc p15, 0, r4, c13, c0, 3
mov r1, #0x10000
str r1, [r4,#0x80]!
ldr r0, [r0]
svc 0x32
mov r12, r0
pop {r3}
cmp r12, #0
bne skip_touch_cp_cpy
ldrd r0, [r4,#8] @ load the touch/cp regs into into r0/1
strd r0, [r3, #12] @ write to r3+12
ldrd r0, [r3, #4] @ read from r3+4
strd r0, [r5]
skip_touch_cp_cpy:
@ +0 +4 +8 +12 +16 +20 +24 +28
@ local hid, local tsrd localcprd, localtswr, localcpwr, remote hid, remote ts, remote circle
@u32 hidData[] = {0x00000FFF, 0x02000000, 0x007FF7FF, 0x00000000, 0x00000000, 0x00000FFF, 0x02000000, 0x007FF7FF};
mov r0, r3 @ Base address.
ldr r1, =0x1ec46000 @ HID reg address.
mov r3, #0xf00
orr r3, #0xff
@ HID reg. Copy +20 => +0 if remote is not exactly 0xfff. Else, pass local through.
ldr r1, [r1] @ Read HID reg.
ldr r2, [r0, #20] @ Load remote HID reg.
cmp r2, r3 @ Is remote 0xfff?
movne r1, r2 @ If not, load remote.
str r1, [r0]
@ Touch screen. Copy +24 => +4 if remote is not exactly 0x2000000. Else, pass local through (+12 => +4)
cmp r12, #0 @ full success check
mov r3, #0x2000000
ldreq r1, [r0, #12] @ Load the original (written) touch value.
movne r1, r3
ldr r2, [r0, #24] @ Load the remote touch value.
cmp r2, r3 @ Is remote 0x2000000?
movne r1, r2 @ If not, load remote.
str r1, [r0, #4] @ Store.
@ Circle pad. Copy +28 => +8 if remote is not exactly 0x7FF7FF. Else, pass local through. (+16 => +8)
cmp r12, #0 @ full success check
ldr r3, =0x7ff7ff
ldreq r1, [r0, #16] @ Load the original (written) circle value.
movne r1, r3
ldr r2, [r0, #28] @ Load the remote circle value.
cmp r2, r3 @ Is remote 0x7FF7FF?
movne r1, r2 @ If not, load remote.
str r1, [r0, #8] @ Store.
ldr r0, [r4,#4]
pop {r4-r6, pc}
.pool
@@@@@ IR hook @@@@@
.global irCodePatchFunc
.type irCodePatchFunc, %function
irCodePatchFunc:
b skip_vars
i2c_readdeviceraw_addr:
.word 0
skip_vars:
@ Address to read from is passed by the hook, save it.
mov r6, r0
ir_check_i2c:
@ Original code. Does an i2c read at device 17 func 5.
ldr r0, [r4]
mov r1, r5
mov r2, #17
mov r3, #5
adr r12, i2c_readdeviceraw_addr
ldr r12, [r12]
blx r12
ldr r1, =0x80800081 @ no c-stick activity / zlzr not pressed
cmp r0, #0 @ check if the i2c read succeeded completely
ldreq r2, [r5]
tsteq r2, #0x100 @ apparently this is set only if the c-stick hasn't been moved yet
movne r2, r1
cmp r1, r2
ldreq r0, [r6] @ Pull the remote input in.
streq r0, [r5] @ store it instead of the value read from i2c
@ Return!
ldmfd sp!, {r4-r6,pc}
.pool

View File

@@ -0,0 +1,61 @@
@ This file is part of Luma3DS
@ Copyright (C) 2016-2017 Aurora Wright, TuxSH
@
@ This program is free software: you can redistribute it and/or modify
@ it under the terms of the GNU General Public License as published by
@ the Free Software Foundation, either version 3 of the License, or
@ (at your option) any later version.
@
@ This program is distributed in the hope that 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/>.
@
@ Additional Terms 7.b and 7.c of GPLv3 apply to this file:
@ * Requiring preservation of specified reasonable legal notices or
@ author attributions in that material or in the Appropriate Legal
@ Notices displayed by works containing it.
@ * Prohibiting misrepresentation of the origin of that material,
@ or requiring that modified versions of such material be marked in
@ reasonable ways as different from the original version.
.text
.arm
.balign 4
.global svc0x2F
.type svc0x2F, %function
svc0x2F:
@ custom backdoor before kernel ext. is installed
svc 0x2F
bx lr
.global convertVAToPA
.type convertVAToPA, %function
convertVAToPA:
@ needs to be executed in supervisor mode
mov r1, #0x1000
sub r1, #1
and r2, r0, r1
bic r0, r1
mcr p15, 0, r0, c7, c8, 0 @ VA to PA translation with privileged read permission check
mrc p15, 0, r0, c7, c4, 0 @ read PA register
tst r0, #1 @ failure bit
bic r0, r1
addeq r0, r2
movne r0, #0
bx lr
.section .data
.p2align 12
.global kernel_extension
kernel_extension: .incbin "build/kernel_extension.bin"
.p2align 12
kernel_extension_end:
.global kernel_extension_size
kernel_extension_size: .word kernel_extension_end - kernel_extension

View File

@@ -0,0 +1,146 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include "kernel_extension.h"
#include "kernel_extension_setup.h"
#define MPCORE_REGS_BASE ((u32)PA_PTR(0x17E00000))
#define MPCORE_GID_REGS_BASE (MPCORE_REGS_BASE + 0x1000)
#define MPCORE_GID_SGI (*(vu32 *)(MPCORE_GID_REGS_BASE + 0xF00))
struct Parameters
{
void (*SGI0HandlerCallback)(struct Parameters *, u32 *);
void *interruptManager;
u32 *L2MMUTable; // bit31 mapping
void (*initFPU)(void);
void (*mcuReboot)(void);
void (*coreBarrier)(void);
u32 TTBCR;
u32 L1MMUTableAddrs[4];
u32 kernelVersion;
struct CfwInfo
{
char magic[4];
u8 versionMajor;
u8 versionMinor;
u8 versionBuild;
u8 flags;
u32 commitHash;
u32 config;
} __attribute__((packed)) info;
};
static void K_SGI0HandlerCallback(volatile struct Parameters *p)
{
u32 L1MMUTableAddr;
vu32 *L1MMUTable;
u32 coreId;
__asm__ volatile("cpsid aif"); // disable interrupts
p->coreBarrier();
__asm__ volatile("mrc p15, 0, %0, c0, c0, 5" : "=r"(coreId));
coreId &= 3;
__asm__ volatile("mrc p15, 0, %0, c2, c0, 1" : "=r"(L1MMUTableAddr));
L1MMUTableAddr &= ~0x3FFF;
p->L1MMUTableAddrs[coreId] = L1MMUTableAddr;
L1MMUTable = (vu32 *)(L1MMUTableAddr | (1 << 31));
// Actually map the kernel ext
u32 L2MMUTableAddr = (u32)(p->L2MMUTable) & ~(1 << 31);
L1MMUTable[0x40000000 >> 20] = L2MMUTableAddr | 1;
__asm__ __volatile__("mcr p15, 0, %[val], c7, c10, 4" :: [val] "r" (0) : "memory");
((void (*)(volatile struct Parameters *))0x40000000)(p);
p->coreBarrier();
}
static u32 ALIGN(0x400) L2MMUTableFor0x40000000[256] = { 0 };
u32 TTBCR;
static void K_ConfigureSGI0(void)
{
// see /patches/k11MainHook.s
u32 *off;
u32 *initFPU, *mcuReboot, *coreBarrier;
// Search for stuff in the 0xFFFF0000 page
for(initFPU = (u32 *)0xFFFF0000; initFPU < (u32 *)0xFFFF1000 && *initFPU != 0xE1A0D002; initFPU++);
initFPU += 3;
for(mcuReboot = initFPU; mcuReboot < (u32 *)0xFFFF1000 && *mcuReboot != 0xE3A0A0C2; mcuReboot++);
mcuReboot--;
coreBarrier = (u32 *)decodeARMBranch(mcuReboot - 4);
for(off = mcuReboot; off < (u32 *)0xFFFF1000 && *off != 0x726C6468; off++); // "hdlr"
volatile struct Parameters *p = (struct Parameters *)PA_FROM_VA_PTR(off); // Caches? What are caches?
p->SGI0HandlerCallback = (void (*)(struct Parameters *, u32 *))PA_FROM_VA_PTR(K_SGI0HandlerCallback);
p->L2MMUTable = (u32 *)PA_FROM_VA_PTR(L2MMUTableFor0x40000000);
p->initFPU = (void (*) (void))initFPU;
p->mcuReboot = (void (*) (void))mcuReboot;
p->coreBarrier = (void (*) (void))coreBarrier;
__asm__ volatile("mrc p15, 0, %0, c2, c0, 2" : "=r"(TTBCR));
p->TTBCR = TTBCR;
p->kernelVersion = *(vu32 *)0x1FF80000;
// Now let's configure the L2 table
//4KB extended small pages: [SYS:RW USR:-- X TYP:NORMAL SHARED OUTER NOCACHE, INNER CACHED WB WA]
for(u32 offset = 0; offset < kernel_extension_size; offset += 0x1000)
L2MMUTableFor0x40000000[offset >> 12] = (u32)convertVAToPA(kernel_extension + offset) | 0x516;
}
static void K_SendSGI0ToAllCores(void)
{
MPCORE_GID_SGI = 0xF0000; // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0360f/CACGDJJC.html
}
static inline void flushAllCaches(void)
{
svcUnmapProcessMemory(CUR_PROCESS_HANDLE, 0, 0); // this SVC flush both caches entirely (and properly) even when returing an error
}
void installKernelExtension(void)
{
svc0x2F(K_ConfigureSGI0);
flushAllCaches();
svc0x2F(K_SendSGI0ToAllCores);
flushAllCaches();
}

View File

@@ -0,0 +1,129 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include <3ds.h>
#include "memory.h"
#include "services.h"
#include "fsreg.h"
#include "menu.h"
#include "errdisp.h"
#include "hbloader.h"
#include "utils.h"
#include "MyThread.h"
#include "kernel_extension_setup.h"
#include "menus/process_patches.h"
// this is called before main
bool isN3DS;
void __appInit()
{
srvSysInit();
fsregInit();
fsSysInit();
s64 dummy;
isN3DS = svcGetSystemInfo(&dummy, 0x10001, 0) == 0;
}
// this is called after main exits
void __appExit()
{
fsExit();
fsregExit();
srvSysExit();
}
Result __sync_init(void);
Result __sync_fini(void);
void __libc_init_array(void);
void __libc_fini_array(void);
void __ctru_exit()
{
__appExit();
__sync_fini();
__libc_fini_array();
svcSleepThread(-1LL); // kernel-loaded sysmodules except PXI are not supposed to terminate anyways
svcExitProcess();
}
void initSystem()
{
__libc_init_array();
HBLDR_3DSX_TID = HBLDR_DEFAULT_3DSX_TID;
installKernelExtension();
__sync_init();
__appInit();
}
bool terminationRequest = false;
Handle terminationRequestEvent;
int main(void)
{
Result res = 0;
Handle notificationHandle;
MyThread *menuThread = menuCreateThread(), *errDispThread = errDispCreateThread(), *hbldrThread = hbldrCreateThread();
if(R_FAILED(srvEnableNotification(&notificationHandle)))
svcBreak(USERBREAK_ASSERT);
if(R_FAILED(svcCreateEvent(&terminationRequestEvent, RESET_STICKY)))
svcBreak(USERBREAK_ASSERT);
do
{
res = svcWaitSynchronization(notificationHandle, -1LL);
if(R_FAILED(res))
continue;
u32 notifId = 0;
if(R_FAILED(srvReceiveNotification(&notifId)))
svcBreak(USERBREAK_ASSERT);
if(notifId == 0x100)
{
// Termination request
terminationRequest = true;
svcSignalEvent(terminationRequestEvent);
}
}
while(!terminationRequest);
MyThread_Join(menuThread, -1LL);
MyThread_Join(errDispThread, -1LL);
MyThread_Join(hbldrThread, -1LL);
svcCloseHandle(notificationHandle);
return 0;
}

View File

@@ -0,0 +1,274 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include "memory.h"
void *memcpy(void *dest, const void *src, u32 size)
{
u8 *destc = (u8 *)dest;
const u8 *srcc = (const u8 *)src;
for(u32 i = 0; i < size; i++)
destc[i] = srcc[i];
return dest;
}
int memcmp(const void *buf1, const void *buf2, u32 size)
{
const u8 *buf1c = (const u8 *)buf1;
const u8 *buf2c = (const u8 *)buf2;
for(u32 i = 0; i < size; i++)
{
int cmp = buf1c[i] - buf2c[i];
if(cmp)
return cmp;
}
return 0;
}
void *memset(void *dest, u32 value, u32 size)
{
u8 *destc = (u8 *)dest;
for(u32 i = 0; i < size; i++) destc[i] = (u8)value;
return dest;
}
void *memset32(void *dest, u32 value, u32 size)
{
u32 *dest32 = (u32 *)dest;
for(u32 i = 0; i < size/4; i++) dest32[i] = value;
return dest;
}
//Boyer-Moore Horspool algorithm, adapted from http://www-igm.univ-mlv.fr/~lecroq/string/node18.html#SECTION00180
u8 *memsearch(u8 *startPos, const void *pattern, u32 size, u32 patternSize)
{
const u8 *patternc = (const u8 *)pattern;
u32 table[256];
//Preprocessing
for(u32 i = 0; i < 256; i++)
table[i] = patternSize;
for(u32 i = 0; i < patternSize - 1; i++)
table[patternc[i]] = patternSize - i - 1;
//Searching
u32 j = 0;
while(j <= size - patternSize)
{
u8 c = startPos[j + patternSize - 1];
if(patternc[patternSize - 1] == c && memcmp(pattern, startPos + j, patternSize - 1) == 0)
return startPos + j;
j += table[c];
}
return NULL;
}
char *strcpy(char *dest, const char *src)
{
u32 i;
for(i = 0; src[i] != 0; i++)
dest[i] = src[i];
dest[i] = 0;
return dest;
}
char *strncpy(char *dest, const char *src, u32 size)
{
u32 i;
for(i = 0; i < size && src[i] != 0; i++)
dest[i] = src[i];
for(; i < size; i++)
dest[i] = 0;
return dest;
}
s32 strnlen(const char *string, s32 maxlen)
{
s32 size;
for(size = 0; size < maxlen && *string; string++, size++);
return size;
}
s32 strlen(const char *string)
{
char *stringEnd = (char *)string;
while(*stringEnd) stringEnd++;
return stringEnd - string;
}
s32 strcmp(const char *str1, const char *str2)
{
while(*str1 && (*str1 == *str2))
{
str1++;
str2++;
}
return *str1 - *str2;
}
s32 strncmp(const char *str1, const char *str2, u32 size)
{
while(size && *str1 && (*str1 == *str2))
{
str1++;
str2++;
size--;
}
if (!size)
return 0;
else
return *(u8*)str1 - *(u8*)str2;
}
const char *strchr(const char *string, int c)
{
char *stringEnd = (char *)string;
while(*stringEnd != 0)
{
if(*stringEnd == c) return stringEnd;
stringEnd++;
}
return NULL;
}
void hexItoa(u64 number, char *out, u32 digits, bool uppercase)
{
const char hexDigits[] = "0123456789ABCDEF";
const char hexDigitsLowercase[] = "0123456789abcdef";
u32 i = 0;
while(number > 0)
{
out[digits - 1 - i++] = uppercase ? hexDigits[number & 0xF] : hexDigitsLowercase[number & 0xF];
number >>= 4;
}
while(i < digits) out[digits - 1 - i++] = '0';
}
// Copied from newlib, without the reent stuff + some other stuff
unsigned long int xstrtoul(const char *nptr, char **endptr, int base, bool allowPrefix, bool *ok)
{
register const unsigned char *s = (const unsigned char *)nptr;
register unsigned long acc;
register int c;
register unsigned long cutoff;
register int neg = 0, any, cutlim;
if(ok != NULL)
*ok = true;
/*
* See strtol for comments as to the logic used.
*/
do {
c = *s++;
} while ((c >= 9 && c <= 13) || c == ' ');
if (c == '-') {
if(!allowPrefix) {
if(ok != NULL)
*ok = false;
return 0;
}
neg = 1;
c = *s++;
} else if (c == '+'){
if(!allowPrefix) {
if(ok != NULL)
*ok = false;
return 0;
}
c = *s++;
}
if ((base == 0 || base == 16) &&
c == '0' && (*s == 'x' || *s == 'X')) {
if(!allowPrefix) {
if(ok != NULL)
*ok = false;
return 0;
}
c = s[1];
s += 2;
base = 16;
}
if (base == 0) {
if(!allowPrefix) {
if(ok != NULL)
*ok = false;
return 0;
}
base = c == '0' ? 8 : 10;
}
cutoff = (unsigned long)(-1) / (unsigned long)base;
cutlim = (unsigned long)(-1) % (unsigned long)base;
for (acc = 0, any = 0;; c = *s++) {
if (c >= '0' && c <= '9')
c -= '0';
else if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))
c -= c >= 'A' && c <= 'Z' ? 'A' - 10 : 'a' - 10;
else
break;
if (c >= base)
break;
if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim))
any = -1;
else {
any = 1;
acc *= base;
acc += c;
}
}
if (any < 0) {
acc = (unsigned long)-1;
if(ok != NULL)
*ok = false;
// rptr->_errno = ERANGE;
} else if (neg)
acc = -acc;
if (endptr != 0)
*endptr = (char *) (any ? (char *)s - 1 : nptr);
return (acc);
}

View File

@@ -0,0 +1,285 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include <3ds.h>
#include "menu.h"
#include "draw.h"
#include "fmt.h"
#include "memory.h"
#include "ifile.h"
#include "menus.h"
#include "utils.h"
#include "menus/n3ds.h"
u32 waitInputWithTimeout(u32 msec)
{
bool pressedKey = false;
u32 key = 0;
u32 n = 0;
//Wait for no keys to be pressed
while(HID_PAD && !terminationRequest && (msec == 0 || n < msec))
{
svcSleepThread(1 * 1000 * 1000LL);
n++;
}
if(terminationRequest || (msec != 0 && n >= msec))
return 0;
do
{
//Wait for a key to be pressed
while(!HID_PAD && !terminationRequest && (msec == 0 || n < msec))
{
svcSleepThread(1 * 1000 * 1000LL);
n++;
}
if(terminationRequest || (msec != 0 && n >= msec))
return 0;
key = HID_PAD;
//Make sure it's pressed
for(u32 i = 0x26000; i > 0; i --)
{
if(key != HID_PAD) break;
if(i == 1) pressedKey = true;
}
}
while(!pressedKey);
return key;
}
u32 waitInput(void)
{
return waitInputWithTimeout(0);
}
static Result _MCUHWC_GetBatteryLevel(u8 *out)
{
#define TRY(expr) if(R_FAILED(res = (expr))) { svcCloseHandle(mcuhwcHandle); return res; }
Result res;
Handle mcuhwcHandle;
TRY(srvGetServiceHandle(&mcuhwcHandle, "mcu::HWC"));
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = 0x50000;
TRY(svcSendSyncRequest(mcuhwcHandle));
*out = (u8) cmdbuf[2];
svcCloseHandle(mcuhwcHandle);
return cmdbuf[1];
#undef TRY
}
static MyThread menuThread;
static u8 ALIGN(8) menuThreadStack[THREAD_STACK_SIZE];
static u8 batteryLevel = 255;
MyThread *menuCreateThread(void)
{
if(R_FAILED(MyThread_Create(&menuThread, menuThreadMain, menuThreadStack, THREAD_STACK_SIZE, 0x18, CORE_SYSTEM)))
svcBreak(USERBREAK_PANIC);
return &menuThread;
}
extern bool isN3DS;
u32 menuCombo = DEFAULT_MENU_COMBO;
void menuThreadMain(void)
{
if(!isN3DS)
{
rosalinaMenu.nbItems--;
for(u32 i = 3; i <= rosalinaMenu.nbItems; i++)
rosalinaMenu.items[i] = rosalinaMenu.items[i+1];
}
else
N3DSMenu_UpdateStatus();
while(!terminationRequest)
{
if((HID_PAD & menuCombo) == menuCombo)
{
menuEnter();
menuShow(&rosalinaMenu);
menuLeave();
}
svcSleepThread(50 * 1000 * 1000LL);
}
}
static s32 menuRefCount = 0;
void menuEnter(void)
{
if(AtomicPostIncrement(&menuRefCount) == 0)
{
svcKernelSetState(0x10000, 1);
svcSleepThread(5 * 1000 * 100LL);
Draw_SetupFramebuffer();
Draw_ClearFramebuffer();
}
}
void menuLeave(void)
{
svcSleepThread(50 * 1000 * 1000);
if(AtomicDecrement(&menuRefCount) == 0)
{
Draw_Lock();
Draw_FlushFramebuffer();
Draw_RestoreFramebuffer();
Draw_Unlock();
svcKernelSetState(0x10000, 1);
}
}
static void menuDraw(Menu *menu, u32 selected)
{
char versionString[16];
s64 out;
u32 version, commitHash;
bool isRelease;
if(R_FAILED(_MCUHWC_GetBatteryLevel(&batteryLevel)))
batteryLevel = 255;
svcGetSystemInfo(&out, 0x10000, 0);
version = (u32)out;
svcGetSystemInfo(&out, 0x10000, 1);
commitHash = (u32)out;
svcGetSystemInfo(&out, 0x10000, 3);
isRelease = (bool)out;
if(GET_VERSION_REVISION(version) == 0)
sprintf(versionString, "v%u.%u", GET_VERSION_MAJOR(version), GET_VERSION_MINOR(version));
else
sprintf(versionString, "v%u.%u.%u", GET_VERSION_MAJOR(version), GET_VERSION_MINOR(version), GET_VERSION_REVISION(version));
Draw_DrawString(10, 10, COLOR_TITLE, menu->title);
for(u32 i = 0; i < 15; i++)
{
if(i >= menu->nbItems)
break;
Draw_DrawString(30, 30 + i * SPACING_Y, COLOR_WHITE, menu->items[i].title);
Draw_DrawCharacter(10, 30 + i * SPACING_Y, COLOR_TITLE, i == selected ? '>' : ' ');
}
if(batteryLevel != 255)
Draw_DrawFormattedString(SCREEN_BOT_WIDTH - 10 - 4 * SPACING_X, SCREEN_BOT_HEIGHT - 20, COLOR_WHITE, "%02hhu%%", batteryLevel);
else
Draw_DrawString(SCREEN_BOT_WIDTH - 10 - 4 * SPACING_X, SCREEN_BOT_HEIGHT - 20, COLOR_WHITE, " ");
if(isRelease)
Draw_DrawFormattedString(10, SCREEN_BOT_HEIGHT - 20, COLOR_TITLE, "Luma3DS %s", versionString);
else
Draw_DrawFormattedString(10, SCREEN_BOT_HEIGHT - 20, COLOR_TITLE, "Luma3DS %s-%08x", versionString, commitHash);
Draw_FlushFramebuffer();
}
void menuShow(Menu *root)
{
u32 selectedItem = 0;
Menu *currentMenu = root;
u32 nbPreviousMenus = 0;
Menu *previousMenus[0x80];
Draw_Lock();
Draw_ClearFramebuffer();
Draw_FlushFramebuffer();
menuDraw(currentMenu, selectedItem);
Draw_Unlock();
do
{
u32 pressed = waitInputWithTimeout(1000);
if(pressed & BUTTON_A)
{
Draw_Lock();
Draw_ClearFramebuffer();
Draw_FlushFramebuffer();
Draw_Unlock();
switch(currentMenu->items[selectedItem].action_type)
{
case METHOD:
if(currentMenu->items[selectedItem].method != NULL)
currentMenu->items[selectedItem].method();
break;
case MENU:
previousMenus[nbPreviousMenus++] = currentMenu;
currentMenu = currentMenu->items[selectedItem].menu;
selectedItem = 0;
break;
}
Draw_Lock();
Draw_ClearFramebuffer();
Draw_FlushFramebuffer();
Draw_Unlock();
}
else if(pressed & BUTTON_B)
{
Draw_Lock();
Draw_ClearFramebuffer();
Draw_FlushFramebuffer();
Draw_Unlock();
if(nbPreviousMenus > 0)
currentMenu = previousMenus[--nbPreviousMenus];
else
break;
}
else if(pressed & BUTTON_DOWN)
{
if(++selectedItem >= currentMenu->nbItems)
selectedItem = 0;
}
else if(pressed & BUTTON_UP)
{
if(selectedItem-- <= 0)
selectedItem = currentMenu->nbItems - 1;
}
Draw_Lock();
menuDraw(currentMenu, selectedItem);
Draw_Unlock();
}
while(!terminationRequest);
}

View File

@@ -0,0 +1,202 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include <3ds.h>
#include "menus.h"
#include "menu.h"
#include "draw.h"
#include "menus/process_list.h"
#include "menus/process_patches.h"
#include "menus/n3ds.h"
#include "menus/debugger.h"
#include "menus/miscellaneous.h"
#include "ifile.h"
#include "memory.h"
#include "fmt.h"
Menu rosalinaMenu = {
"Rosalina menu",
.nbItems = 7,
{
{ "Process list", METHOD, .method = &RosalinaMenu_ProcessList },
{ "Process patches menu...", MENU, .menu = &processPatchesMenu },
{ "Take screenshot (slow!)", METHOD, .method = &RosalinaMenu_TakeScreenshot },
{ "New 3DS menu...", MENU, .menu = &N3DSMenu },
{ "Debugger options...", MENU, .menu = &debuggerMenu },
{ "Miscellaneous options...", MENU, .menu = &miscellaneousMenu },
{ "Credits", METHOD, .method = &RosalinaMenu_ShowCredits }
}
};
void RosalinaMenu_ShowCredits(void)
{
Draw_Lock();
Draw_ClearFramebuffer();
Draw_FlushFramebuffer();
Draw_Unlock();
do
{
Draw_Lock();
Draw_DrawString(10, 10, COLOR_TITLE, "Rosalina -- Luma3DS credits");
u32 posY = Draw_DrawString(10, 30, COLOR_WHITE, "Luma3DS (c) 2016-2017 AuroraWright, TuxSH") + SPACING_Y;
posY = Draw_DrawString(10, posY + SPACING_Y, COLOR_WHITE, "3DSX loading code by fincs");
posY = Draw_DrawString(10, posY + SPACING_Y, COLOR_WHITE, "Networking code & basic GDB functionality by Stary");
posY = Draw_DrawString(10, posY + SPACING_Y, COLOR_WHITE, "InputRedirection by Stary (PoC by ShinyQuagsire)");
posY += 2 * SPACING_Y;
Draw_DrawString(10, posY, COLOR_WHITE,
(
"Special thanks to:\n"
" Bond697, WinterMute, yifanlu,\n"
" Luma3DS contributors, ctrulib contributors,\n"
" other people"
));
Draw_FlushFramebuffer();
Draw_Unlock();
}
while(!(waitInput() & BUTTON_B) && !terminationRequest);
}
void RosalinaMenu_TakeScreenshot(void)
{
#define TRY(expr) if(R_FAILED(res = (expr))) goto end;
u64 total;
IFile file;
u8 buf[54];
Result res;
u32 filenum;
char filename[64];
FS_Archive archive;
FS_ArchiveID archiveId;
s64 out;
bool isSdMode;
if(R_FAILED(svcGetSystemInfo(&out, 0x10000, 6))) svcBreak(USERBREAK_ASSERT);
isSdMode = (bool)out;
archiveId = isSdMode ? ARCHIVE_SDMC : ARCHIVE_NAND_RW;
Draw_Lock();
Draw_RestoreFramebuffer();
res = FSUSER_OpenArchive(&archive, archiveId, fsMakePath(PATH_EMPTY, ""));
if(R_SUCCEEDED(res))
{
res = FSUSER_CreateDirectory(archive, fsMakePath(PATH_ASCII, "/luma/screenshots"), 0);
if((u32)res == 0xC82044BE) // directory already exists
res = 0;
FSUSER_CloseArchive(archive);
}
for(filenum = 0; filenum <= 9999999; filenum++) // find an unused file name
{
Result res1, res2, res3;
IFile fileR;
sprintf(filename, "/luma/screenshots/top_%04u.bmp", filenum);
res1 = IFile_Open(&fileR, archiveId, fsMakePath(PATH_EMPTY, ""), fsMakePath(PATH_ASCII, filename), FS_OPEN_READ);
IFile_Close(&fileR);
sprintf(filename, "/luma/screenshots/bot_%04u.bmp", filenum);
res2 = IFile_Open(&fileR, archiveId, fsMakePath(PATH_EMPTY, ""), fsMakePath(PATH_ASCII, filename), FS_OPEN_READ);
IFile_Close(&fileR);
sprintf(filename, "/luma/screenshots/top_right_%04u.bmp", filenum);
res3 = IFile_Open(&fileR, archiveId, fsMakePath(PATH_EMPTY, ""), fsMakePath(PATH_ASCII, filename), FS_OPEN_READ);
IFile_Close(&fileR);
if(R_FAILED(res1) && R_FAILED(res2) && R_FAILED(res3))
break;
}
sprintf(filename, "/luma/screenshots/top_%04u.bmp", filenum);
TRY(IFile_Open(&file, archiveId, fsMakePath(PATH_EMPTY, ""), fsMakePath(PATH_ASCII, filename), FS_OPEN_CREATE | FS_OPEN_WRITE));
Draw_CreateBitmapHeader(buf, 400, 240);
TRY(IFile_Write(&file, &total, buf, 54, 0));
for(u32 y = 0; y < 240; y++)
{
u8 *line = Draw_ConvertFrameBufferLine(true, true, y);
TRY(IFile_Write(&file, &total, line, 3 * 400, 0));
}
TRY(IFile_Close(&file));
sprintf(filename, "/luma/screenshots/bot_%04u.bmp", filenum);
TRY(IFile_Open(&file, archiveId, fsMakePath(PATH_EMPTY, ""), fsMakePath(PATH_ASCII, filename), FS_OPEN_CREATE | FS_OPEN_WRITE));
Draw_CreateBitmapHeader(buf, 320, 240);
TRY(IFile_Write(&file, &total, buf, 54, 0));
for(u32 y = 0; y < 240; y++)
{
u8 *line = Draw_ConvertFrameBufferLine(false, true, y);
TRY(IFile_Write(&file, &total, line, 3 * 320, 0));
}
if((GPU_FB_TOP_FMT & 0x20) && (Draw_GetCurrentFramebufferAddress(true, true) != Draw_GetCurrentFramebufferAddress(true, false)))
{
sprintf(filename, "/luma/screenshots/top_right_%04u.bmp", filenum);
TRY(IFile_Open(&file, archiveId, fsMakePath(PATH_EMPTY, ""), fsMakePath(PATH_ASCII, filename), FS_OPEN_CREATE | FS_OPEN_WRITE));
Draw_CreateBitmapHeader(buf, 400, 240);
TRY(IFile_Write(&file, &total, buf, 54, 0));
for(u32 y = 0; y < 240; y++)
{
u8 *line = Draw_ConvertFrameBufferLine(true, false, y);
TRY(IFile_Write(&file, &total, line, 3 * 400, 0));
}
}
end:
IFile_Close(&file);
Draw_SetupFramebuffer();
Draw_ClearFramebuffer();
Draw_FlushFramebuffer();
Draw_Unlock();
do
{
Draw_Lock();
Draw_DrawString(10, 10, COLOR_TITLE, "Screenshot");
if(R_FAILED(res))
Draw_DrawFormattedString(10, 30, COLOR_WHITE, "Operation failed (0x%08x).", (u32)res);
else
Draw_DrawString(10, 30, COLOR_WHITE, "Operation succeeded.");
Draw_FlushFramebuffer();
Draw_Unlock();
}
while(!(waitInput() & BUTTON_B) && !terminationRequest);
#undef TRY
}

View File

@@ -0,0 +1,165 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include "menus/debugger.h"
#include "memory.h"
#include "draw.h"
#include "minisoc.h"
#include "fmt.h"
#include "gdb/server.h"
#include "gdb/debug.h"
#include "gdb/monitor.h"
#include "gdb/net.h"
Menu debuggerMenu = {
"Debugger options menu",
.nbItems = 2,
{
{ "Enable debugger", METHOD, .method = &DebuggerMenu_EnableDebugger },
{ "Disable debugger", METHOD, .method = &DebuggerMenu_DisableDebugger }
}
};
static MyThread debuggerSocketThread;
static MyThread debuggerDebugThread;
static u8 ALIGN(8) debuggerSocketThreadStack[0x4000];
static u8 ALIGN(8) debuggerDebugThreadStack[0x2000];
GDBServer gdbServer = { 0 };
void debuggerSocketThreadMain(void);
MyThread *debuggerCreateSocketThread(void)
{
MyThread_Create(&debuggerSocketThread, debuggerSocketThreadMain, debuggerSocketThreadStack, 0x4000, 0x20, CORE_SYSTEM);
return &debuggerSocketThread;
}
void debuggerDebugThreadMain(void);
MyThread *debuggerCreateDebugThread(void)
{
MyThread_Create(&debuggerDebugThread, debuggerDebugThreadMain, debuggerDebugThreadStack, 0x2000, 0x20, CORE_SYSTEM);
return &debuggerDebugThread;
}
void DebuggerMenu_EnableDebugger(void)
{
bool done = false, alreadyEnabled = gdbServer.super.running;
Result res = 0;
char buf[65];
bool cantStart;
Handle dummy;
res = OpenProcessByName("socket", &dummy);
cantStart = R_FAILED(res);
svcCloseHandle(dummy);
Draw_Lock();
Draw_ClearFramebuffer();
Draw_FlushFramebuffer();
Draw_Unlock();
do
{
Draw_Lock();
Draw_DrawString(10, 10, COLOR_TITLE, "Debugger options menu");
if(alreadyEnabled)
Draw_DrawString(10, 30, COLOR_WHITE, "Already enabled!");
else if(cantStart)
Draw_DrawString(10, 30, COLOR_WHITE, "Can't start the debugger before the system has fi-\nnished loading.");
else
{
Draw_DrawString(10, 30, COLOR_WHITE, "Starting debugger...");
if(!done)
{
res = GDB_InitializeServer(&gdbServer);
if(R_SUCCEEDED(res))
{
debuggerCreateSocketThread();
debuggerCreateDebugThread();
res = svcWaitSynchronization(gdbServer.super.started_event, 10 * 1000 * 1000 * 1000LL);
}
if(res != 0)
sprintf(buf, "Starting debugger... failed (0x%08x).", (u32)res);
done = true;
}
if(res == 0)
Draw_DrawString(10, 30, COLOR_WHITE, "Starting debugger... OK.");
else
Draw_DrawString(10, 30, COLOR_WHITE, buf);
}
Draw_FlushFramebuffer();
Draw_Unlock();
}
while(!(waitInput() & BUTTON_B) && !terminationRequest);
}
void DebuggerMenu_DisableDebugger(void)
{
bool initialized = gdbServer.referenceCount != 0;
Result res = 0;
char buf[65];
if(initialized)
{
svcSignalEvent(gdbServer.super.shall_terminate_event);
res = MyThread_Join(&debuggerDebugThread, 5 * 1000 * 1000 * 1000LL);
if(res == 0)
res = MyThread_Join(&debuggerSocketThread, 5 * 1000 * 1000 * 1000LL);
svcKernelSetState(0x10000, 2);
}
if(res != 0)
sprintf(buf, "Failed to disable debugger (0x%08x).", (u32)res);
do
{
Draw_Lock();
Draw_DrawString(10, 10, COLOR_TITLE, "Debugger options menu");
Draw_DrawString(10, 30, COLOR_WHITE, initialized ? (res == 0 ? "Debugger disabled successfully." : buf) : "Debugger not enabled.");
Draw_FlushFramebuffer();
Draw_Unlock();
}
while(!(waitInput() & BUTTON_B) && !terminationRequest);
}
void debuggerSocketThreadMain(void)
{
GDB_IncrementServerReferenceCount(&gdbServer);
GDB_RunServer(&gdbServer);
GDB_DecrementServerReferenceCount(&gdbServer);
}
void debuggerDebugThreadMain(void)
{
GDB_IncrementServerReferenceCount(&gdbServer);
GDB_RunMonitor(&gdbServer);
GDB_DecrementServerReferenceCount(&gdbServer);
}

View File

@@ -0,0 +1,261 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include <3ds.h>
#include "menus/miscellaneous.h"
#include "input_redirection.h"
#include "memory.h"
#include "draw.h"
#include "hbloader.h"
#include "fmt.h"
#include "utils.h" // for makeARMBranch
#include "minisoc.h"
Menu miscellaneousMenu = {
"Miscellaneous options menu",
.nbItems = 3,
{
{ "Switch the hb. title to the current app.", METHOD, .method = &MiscellaneousMenu_SwitchBoot3dsxTargetTitle },
{ "Change the menu combo", METHOD, .method = MiscellaneousMenu_ChangeMenuCombo },
{ "Start InputRedirection", METHOD, .method = &MiscellaneousMenu_InputRedirection },
}
};
void MiscellaneousMenu_SwitchBoot3dsxTargetTitle(void)
{
Result res;
u64 titleId = 0;
char failureReason[64];
if(HBLDR_3DSX_TID == HBLDR_DEFAULT_3DSX_TID)
{
u32 pidList[0x40];
s32 processAmount;
res = svcGetProcessList(&processAmount, pidList, 0x40);
if(R_SUCCEEDED(res))
{
for(s32 i = 0; i < processAmount && (u32)(titleId >> 32) != 0x00040010 && (u32)(titleId >> 32) != 0x00040000; i++)
{
Handle processHandle;
Result res = svcOpenProcess(&processHandle, pidList[i]);
if(R_FAILED(res))
continue;
svcGetProcessInfo((s64 *)&titleId, processHandle, 0x10001);
svcCloseHandle(processHandle);
}
}
if(R_SUCCEEDED(res) && ((u32)(titleId >> 32) == 0x00040010 || (u32)(titleId >> 32) == 0x00040000))
{
HBLDR_3DSX_TID = titleId;
miscellaneousMenu.items[0].title = "Switch the hb. title to hblauncher_loader";
}
else if(R_FAILED(res))
sprintf(failureReason, "%08x", (u32)res);
else
{
res = -1;
strcpy(failureReason, "no suitable process found");
}
}
else
{
res = 0;
HBLDR_3DSX_TID = HBLDR_DEFAULT_3DSX_TID;
miscellaneousMenu.items[0].title = "Switch the hb. title to the current app.";
}
Draw_Lock();
Draw_ClearFramebuffer();
Draw_FlushFramebuffer();
Draw_Unlock();
do
{
Draw_Lock();
Draw_DrawString(10, 10, COLOR_TITLE, "Miscellaneous options menu");
if(R_SUCCEEDED(res))
Draw_DrawString(10, 30, COLOR_WHITE, "Operation succeeded.");
else
Draw_DrawFormattedString(10, 30, COLOR_WHITE, "Operation failed (%s).", failureReason);
Draw_FlushFramebuffer();
Draw_Unlock();
}
while(!(waitInput() & BUTTON_B) && !terminationRequest);
}
static void MiscellaneousMenu_ConvertComboToString(char *out, u32 combo)
{
static const char *keys[] = { "A", "B", "Select", "Start", "Right", "Left", "Up", "Down", "R", "L", "X", "Y" };
for(s32 i = 11; i >= 0; i--)
{
if(combo & (1 << i))
{
strcpy(out, keys[i]);
out += strlen(keys[i]);
*out++ = '+';
}
}
out[-1] = 0;
}
void MiscellaneousMenu_ChangeMenuCombo(void)
{
char comboStrOrig[64], comboStr[64];
u32 posY;
Draw_Lock();
Draw_ClearFramebuffer();
Draw_FlushFramebuffer();
Draw_Unlock();
MiscellaneousMenu_ConvertComboToString(comboStrOrig, menuCombo);
Draw_Lock();
Draw_DrawString(10, 10, COLOR_TITLE, "Miscellaneous options menu");
posY = Draw_DrawFormattedString(10, 30, COLOR_WHITE, "The current menu combo is: %s", comboStrOrig);
posY = Draw_DrawString(10, posY + SPACING_Y, COLOR_WHITE, "Please enter the new combo:");
Draw_FlushFramebuffer();
Draw_Unlock();
menuCombo = waitInput();
MiscellaneousMenu_ConvertComboToString(comboStr, menuCombo);
do
{
Draw_Lock();
Draw_DrawString(10, 10, COLOR_TITLE, "Miscellaneous options menu");
posY = Draw_DrawFormattedString(10, 30, COLOR_WHITE, "The current menu combo is: %s", comboStrOrig);
posY = Draw_DrawFormattedString(10, posY + SPACING_Y, COLOR_WHITE, "Please enter the new combo: %s", comboStr) + SPACING_Y;
posY = Draw_DrawString(10, posY + SPACING_Y, COLOR_WHITE, "Successfully changed the menu combo.");
Draw_FlushFramebuffer();
Draw_Unlock();
}
while(!(waitInput() & BUTTON_B) && !terminationRequest);
}
void MiscellaneousMenu_InputRedirection(void)
{
static MyThread *inputRedirectionThread = NULL;
bool done = false;
Result res;
char buf[65];
bool wasEnabled = inputRedirectionEnabled;
bool cantStart;
if(wasEnabled)
{
res = InputRedirection_DoOrUndoPatches();
inputRedirectionEnabled = false;
res = MyThread_Join(inputRedirectionThread, 5 * 1000 * 1000 * 1000LL);
svcCloseHandle(inputRedirectionThreadStartedEvent);
if(res != 0)
sprintf(buf, "Failed to stop InputRedirection (0x%08x).", (u32)res);
else
miscellaneousMenu.items[2].title = "Start InputRedirection";
}
else
{
Handle dummy;
s64 dummyInfo;
bool isN3DS = svcGetSystemInfo(&dummyInfo, 0x10001, 0) == 0;
res = OpenProcessByName("socket", &dummy);
cantStart = R_FAILED(res);
svcCloseHandle(dummy);
if(!cantStart && isN3DS)
{
res = OpenProcessByName("ir", &dummy);
cantStart = R_FAILED(res);
svcCloseHandle(dummy);
}
}
do
{
Draw_Lock();
Draw_DrawString(10, 10, COLOR_TITLE, "Miscellaneous options menu");
if(!wasEnabled && cantStart)
Draw_DrawString(10, 30, COLOR_WHITE, "Can't start the debugger before the system has fi-\nnished loading.");
else if(!wasEnabled)
{
Draw_DrawString(10, 30, COLOR_WHITE, "Starting InputRedirection...");
if(!done)
{
res = InputRedirection_DoOrUndoPatches();
if(R_SUCCEEDED(res))
{
res = svcCreateEvent(&inputRedirectionThreadStartedEvent, RESET_STICKY);
if(R_SUCCEEDED(res))
{
inputRedirectionThread = inputRedirectionCreateThread();
res = svcWaitSynchronization(inputRedirectionThreadStartedEvent, 10 * 1000 * 1000 * 1000LL);
if(res == 0)
res = (Result)inputRedirectionStartResult;
if(res != 0)
InputRedirection_DoOrUndoPatches();
}
}
if(res != 0)
sprintf(buf, "Starting InputRedirection... failed (0x%08x).", (u32)res);
else
miscellaneousMenu.items[2].title = "Stop InputRedirection";
done = true;
}
if(res == 0)
Draw_DrawString(10, 30, COLOR_WHITE, "Starting InputRedirection... OK.");
else
Draw_DrawString(10, 30, COLOR_WHITE, buf);
}
else
{
if(res == 0)
Draw_DrawString(10, 30, COLOR_WHITE, "InputRedirection stopped successfully.");
else
Draw_DrawString(10, 30, COLOR_WHITE, buf);
}
Draw_FlushFramebuffer();
Draw_Unlock();
}
while(!(waitInput() & BUTTON_B) && !terminationRequest);
}

View File

@@ -0,0 +1,74 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include <3ds.h>
#include "fmt.h"
#include "menus/n3ds.h"
#include "memory.h"
#include "menu.h"
static char clkRateBuf[128 + 1];
Menu N3DSMenu = {
"New 3DS menu",
.nbItems = 2,
{
{ "Enable L2 cache", METHOD, .method = &N3DSMenu_EnableDisableL2Cache },
{ clkRateBuf, METHOD, .method = &N3DSMenu_ChangeClockRate }
}
};
static s64 clkRate = 0, higherClkRate = 0, L2CacheEnabled = 0;
void N3DSMenu_UpdateStatus(void)
{
svcGetSystemInfo(&clkRate, 0x10001, 0);
svcGetSystemInfo(&higherClkRate, 0x10001, 1);
svcGetSystemInfo(&L2CacheEnabled, 0x10001, 2);
N3DSMenu.items[0].title = L2CacheEnabled ? "Disable L2 cache" : "Enable L2 cache";
sprintf(clkRateBuf, "Set clock rate to %uMHz", clkRate != 268 ? 268 : (u32)higherClkRate);
}
void N3DSMenu_ChangeClockRate(void)
{
N3DSMenu_UpdateStatus();
s64 newBitMask = (L2CacheEnabled << 1) | ((clkRate != 268 ? 1 : 0) ^ 1);
svcKernelSetState(10, (u32)newBitMask);
N3DSMenu_UpdateStatus();
}
void N3DSMenu_EnableDisableL2Cache(void)
{
N3DSMenu_UpdateStatus();
s64 newBitMask = ((L2CacheEnabled ^ 1) << 1) | (clkRate != 268 ? 1 : 0);
svcKernelSetState(10, (u32)newBitMask);
N3DSMenu_UpdateStatus();
}

View File

@@ -0,0 +1,229 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include <3ds.h>
#include "menus/process_list.h"
#include "memory.h"
#include "draw.h"
#include "menu.h"
#include "utils.h"
#include "fmt.h"
#include "gdb/server.h"
#include "minisoc.h"
#include <arpa/inet.h>
#include <sys/socket.h>
typedef struct ProcessInfo
{
u32 pid;
u64 titleId;
char name[8];
bool isZombie;
} ProcessInfo;
static ProcessInfo infos[0x40] = {0}, infosPrev[0x40] = {0};
extern GDBServer gdbServer;
static inline int ProcessListMenu_FormatInfoLine(char *out, const ProcessInfo *info)
{
const char *checkbox;
u32 id;
for(id = 0; id < MAX_DEBUG && (!(gdbServer.ctxs[id].flags & GDB_FLAG_SELECTED) || gdbServer.ctxs[id].pid != info->pid); id++);
checkbox = !gdbServer.super.running ? "" : (id < MAX_DEBUG ? "(x) " : "( ) ");
char commentBuf[23 + 1] = { 0 }; // exactly the size of "Remote: 255.255.255.255"
memset(commentBuf, ' ', 23);
if(info->isZombie)
memcpy(commentBuf, "Zombie", 7);
else if(gdbServer.super.running && id < MAX_DEBUG)
{
if(gdbServer.ctxs[id].state >= GDB_STATE_CONNECTED && gdbServer.ctxs[id].state < GDB_STATE_CLOSING)
{
u8 *addr = (u8 *)&gdbServer.ctxs[id].super.addr_in.sin_addr;
checkbox = "(A) ";
sprintf(commentBuf, "Remote: %hhu.%hhu.%hhu.%hhu", addr[0], addr[1], addr[2], addr[3]);
}
else
{
checkbox = "(W) ";
sprintf(commentBuf, "Port: %d", GDB_PORT_BASE + id);
}
}
return sprintf(out, "%s%-4u %-8.8s %s", checkbox, info->pid, info->name, commentBuf); // Theoritically PIDs are 32-bit ints, but we'll only justify 4 digits
}
static inline void ProcessListMenu_HandleSelected(const ProcessInfo *info)
{
if(!gdbServer.super.running || info->isZombie)
return;
u32 id;
for(id = 0; id < MAX_DEBUG && (!(gdbServer.ctxs[id].flags & GDB_FLAG_SELECTED) || gdbServer.ctxs[id].pid != info->pid); id++);
GDBContext *ctx = &gdbServer.ctxs[id];
if(id < MAX_DEBUG)
{
if(ctx->flags & GDB_FLAG_USED)
{
RecursiveLock_Lock(&ctx->lock);
ctx->super.should_close = true;
RecursiveLock_Unlock(&ctx->lock);
while(ctx->super.should_close)
svcSleepThread(12 * 1000 * 1000LL);
}
else
{
RecursiveLock_Lock(&ctx->lock);
ctx->flags &= ~GDB_FLAG_SELECTED;
RecursiveLock_Unlock(&ctx->lock);
}
}
else
{
for(id = 0; id < MAX_DEBUG && gdbServer.ctxs[id].flags & GDB_FLAG_SELECTED; id++);
if(id < MAX_DEBUG)
{
ctx = &gdbServer.ctxs[id];
RecursiveLock_Lock(&ctx->lock);
ctx->pid = info->pid;
ctx->flags |= GDB_FLAG_SELECTED;
RecursiveLock_Unlock(&ctx->lock);
}
}
}
s32 ProcessListMenu_FetchInfo(void)
{
u32 pidList[0x40];
s32 processAmount;
svcGetProcessList(&processAmount, pidList, 0x40);
for(s32 i = 0; i < processAmount; i++)
{
Handle processHandle;
Result res = svcOpenProcess(&processHandle, pidList[i]);
if(R_FAILED(res))
continue;
infos[i].pid = pidList[i];
svcGetProcessInfo((s64 *)&infos[i].name, processHandle, 0x10000);
svcGetProcessInfo((s64 *)&infos[i].titleId, processHandle, 0x10001);
infos[i].isZombie = svcWaitSynchronization(processHandle, 0) == 0;
svcCloseHandle(processHandle);
}
return processAmount;
}
void RosalinaMenu_ProcessList(void)
{
s32 processAmount = ProcessListMenu_FetchInfo();
s32 selected = 0, page = 0, pagePrev = 0;
nfds_t nfdsPrev;
do
{
nfdsPrev = gdbServer.super.nfds;
memcpy(infosPrev, infos, sizeof(infos));
Draw_Lock();
if(page != pagePrev)
Draw_ClearFramebuffer();
Draw_DrawString(10, 10, COLOR_TITLE, "Process list");
if(gdbServer.super.running)
{
char ipBuffer[17];
u32 ip = gethostid();
u8 *addr = (u8 *)&ip;
int n = sprintf(ipBuffer, "%hhu.%hhu.%hhu.%hhu", addr[0], addr[1], addr[2], addr[3]);
Draw_DrawString(SCREEN_BOT_WIDTH - 10 - SPACING_X * n, 10, COLOR_WHITE, ipBuffer);
}
for(s32 i = 0; i < PROCESSES_PER_MENU_PAGE && page * PROCESSES_PER_MENU_PAGE + i < processAmount; i++)
{
char buf[65] = {0};
ProcessListMenu_FormatInfoLine(buf, &infos[page * PROCESSES_PER_MENU_PAGE + i]);
Draw_DrawString(30, 30 + i * SPACING_Y, COLOR_WHITE, buf);
Draw_DrawCharacter(10, 30 + i * SPACING_Y, COLOR_TITLE, page * PROCESSES_PER_MENU_PAGE + i == selected ? '>' : ' ');
}
Draw_FlushFramebuffer();
Draw_Unlock();
if(terminationRequest)
break;
u32 pressed;
do
{
pressed = waitInputWithTimeout(50);
if(pressed != 0 || nfdsPrev != gdbServer.super.nfds)
break;
processAmount = ProcessListMenu_FetchInfo();
if(memcmp(infos, infosPrev, sizeof(infos)) != 0)
break;
}
while(pressed == 0 && !terminationRequest);
if(pressed & BUTTON_B)
break;
else if(pressed & BUTTON_A)
ProcessListMenu_HandleSelected(&infos[selected]);
else if(pressed & BUTTON_DOWN)
selected++;
else if(pressed & BUTTON_UP)
selected--;
else if(pressed & BUTTON_LEFT)
selected -= PROCESSES_PER_MENU_PAGE;
else if(pressed & BUTTON_RIGHT)
{
if(selected + PROCESSES_PER_MENU_PAGE < processAmount)
selected += PROCESSES_PER_MENU_PAGE;
else if((processAmount - 1) / PROCESSES_PER_MENU_PAGE == page)
selected %= PROCESSES_PER_MENU_PAGE;
else
selected = processAmount - 1;
}
if(selected < 0)
selected = processAmount - 1;
else if(selected >= processAmount)
selected = 0;
pagePrev = page;
page = selected / PROCESSES_PER_MENU_PAGE;
}
while(!terminationRequest);
}

View File

@@ -0,0 +1,183 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include <3ds.h>
#include "csvc.h"
#include "menus/process_patches.h"
#include "memory.h"
#include "draw.h"
#include "hbloader.h"
#include "fmt.h"
#include "utils.h"
Menu processPatchesMenu = {
"Process patches menu",
.nbItems = 2,
{
{ "Patch SM for the service checks", METHOD, .method = &ProcessPatchesMenu_PatchUnpatchSM },
{ "Patch FS for the archive checks", METHOD, .method = &ProcessPatchesMenu_PatchUnpatchFS },
}
};
static Result ProcessPatchesMenu_DoPatchUnpatchSM(u32 textTotalRoundedSize)
{
static bool patched = false;
static u32 *off;
static u32 origData[7];
if(patched)
{
memcpy(off, &origData, sizeof(origData));
patched = false;
}
else
{
for(off = (u32 *)0x00100000; off < (u32 *)(0x00100000 + textTotalRoundedSize) - 6 &&
(off[0] != 0xE1A01006 || off[1] != 0xE1A00005 || off[3] != 0xE3500000 || off[6] != 0xE2850004);
off++);
if(off >= (u32 *)(0x00100000 + textTotalRoundedSize) - 6)
return -1;
memcpy(&origData, off, sizeof(origData));
off[2] = off[3] = off[4] = off[5] = 0xE320F000; // nop
patched = true;
}
processPatchesMenu.items[0].title = patched ? "Unpatch SM for the service checks" : "Patch SM for the service checks";
return 0;
}
static Result ProcessPatchesMenu_DoPatchUnpatchFS(u32 textTotalRoundedSize)
{
static bool patched = false;
static u16 *off;
static const u16 origData[2] = {
0x4618, // mov r0, r3
0x3481, // adds r4, #0x81
};
if(patched)
{
memcpy(off, &origData, sizeof(origData));
patched = false;
}
else
{
off = (u16 *)memsearch((u8 *)0x00100000, &origData, textTotalRoundedSize, sizeof(origData));
if(off == NULL)
return -1;
off[0] = 0x2001; // mov r0, #1
off[1] = 0x4770; // bx lr
patched = true;
}
processPatchesMenu.items[1].title = patched ? "Unpatch FS for the archive checks" : "Patch FS for the archive checks";
return 0;
}
Result OpenProcessByName(const char *name, Handle *h)
{
u32 pidList[0x40];
s32 processCount;
svcGetProcessList(&processCount, pidList, 0x40);
Handle dstProcessHandle = 0;
for(s32 i = 0; i < processCount; i++)
{
Handle processHandle;
Result res = svcOpenProcess(&processHandle, pidList[i]);
if(R_FAILED(res))
continue;
char procName[8] = {0};
svcGetProcessInfo((s64 *)procName, processHandle, 0x10000);
if(strncmp(procName, name, 8) == 0)
dstProcessHandle = processHandle;
else
svcCloseHandle(processHandle);
}
if(dstProcessHandle == 0)
return -1;
*h = dstProcessHandle;
return 0;
}
static u32 ProcessPatchesMenu_PatchUnpatchProcessByName(const char *name, Result (*func)(u32 size))
{
Result res;
Handle processHandle;
OpenProcessByName(name, &processHandle);
s64 textTotalRoundedSize = 0, startAddress = 0;
svcGetProcessInfo(&textTotalRoundedSize, processHandle, 0x10002); // only patch .text
svcGetProcessInfo(&startAddress, processHandle, 0x10005);
if(R_FAILED(res = svcMapProcessMemoryEx(processHandle, 0x00100000, (u32) startAddress, textTotalRoundedSize)))
return res;
res = func(textTotalRoundedSize);
svcUnmapProcessMemory(processHandle, 0x00100000, textTotalRoundedSize);
return res;
}
static void ProcessPatchesMenu_PatchUnpatchProcess(const char *processName, Result (*func)(u32 size))
{
Draw_Lock();
Draw_ClearFramebuffer();
Draw_FlushFramebuffer();
Draw_Unlock();
Result res = ProcessPatchesMenu_PatchUnpatchProcessByName(processName, func);
do
{
Draw_Lock();
Draw_DrawString(10, 10, COLOR_TITLE, "Process patches menu");
if(R_SUCCEEDED(res))
Draw_DrawString(10, 30, COLOR_WHITE, "Operation succeeded.");
else
Draw_DrawFormattedString(10, 30, COLOR_WHITE, "Operation failed (0x%08x).", res);
Draw_FlushFramebuffer();
Draw_Unlock();
}
while(!(waitInput() & BUTTON_B) && !terminationRequest);
}
void ProcessPatchesMenu_PatchUnpatchSM(void)
{
ProcessPatchesMenu_PatchUnpatchProcess("sm", &ProcessPatchesMenu_DoPatchUnpatchSM);
}
void ProcessPatchesMenu_PatchUnpatchFS(void)
{
ProcessPatchesMenu_PatchUnpatchProcess("fs", &ProcessPatchesMenu_DoPatchUnpatchFS);
}

View File

@@ -0,0 +1,434 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include "minisoc.h"
#include <sys/socket.h>
#include <3ds/ipc.h>
#include <3ds/os.h>
#include <3ds/synchronization.h>
#include "memory.h"
s32 _net_convert_error(s32 sock_retval);
static Result SOCU_Initialize(Handle memhandle, u32 memsize)
{
Result ret = 0;
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = IPC_MakeHeader(0x1,1,4); // 0x10044
cmdbuf[1] = memsize;
cmdbuf[2] = IPC_Desc_CurProcessHandle();
cmdbuf[4] = IPC_Desc_SharedHandles(1);
cmdbuf[5] = memhandle;
ret = svcSendSyncRequest(SOCU_handle);
if(ret != 0)
return ret;
return cmdbuf[1];
}
static Result SOCU_Shutdown(void)
{
Result ret = 0;
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = IPC_MakeHeader(0x19,0,0); // 0x190000
ret = svcSendSyncRequest(SOCU_handle);
if(ret != 0)
return ret;
return cmdbuf[1];
}
static s32 miniSocRefCount = 0;
static u32 socContextAddr = 0x08000000;
static u32 socContextSize = 0x60000;
// SOCU_handle from ctrulib
// socMemhandle from ctrulib
Result miniSocInit()
{
if(AtomicPostIncrement(&miniSocRefCount))
return 0;
u32 tmp = 0;
Result ret = 0;
ret = svcControlMemory(&tmp, socContextAddr, 0, socContextSize, MEMOP_ALLOC, MEMPERM_READ | MEMPERM_WRITE);
if(ret != 0) goto cleanup;
socContextAddr = tmp;
ret = svcCreateMemoryBlock(&socMemhandle, (u32)socContextAddr, socContextSize, 0, 3);
if(ret != 0) goto cleanup;
ret = srvGetServiceHandle(&SOCU_handle, "soc:U");
if(ret != 0) goto cleanup;
ret = SOCU_Initialize(socMemhandle, socContextSize);
if(ret != 0) goto cleanup;
svcKernelSetState(0x10000, 2);
return 0;
cleanup:
AtomicDecrement(&miniSocRefCount);
if(socMemhandle != 0)
{
svcCloseHandle(socMemhandle);
socMemhandle = 0;
}
if(SOCU_handle != 0)
{
SOCU_Shutdown();
svcCloseHandle(SOCU_handle);
SOCU_handle = 0;
}
if(tmp != 0)
svcControlMemory(&tmp, socContextAddr, socContextAddr, socContextSize, MEMOP_FREE, MEMPERM_DONTCARE);
return ret;
}
Result miniSocExit(void)
{
if(AtomicDecrement(&miniSocRefCount))
return 0;
Result ret = 0;
u32 tmp;
svcCloseHandle(socMemhandle);
socMemhandle = 0;
ret = SOCU_Shutdown();
svcCloseHandle(SOCU_handle);
SOCU_handle = 0;
svcControlMemory(&tmp, socContextAddr, socContextAddr, socContextSize, MEMOP_FREE, MEMPERM_DONTCARE);
if(ret == 0)
svcKernelSetState(0x10000, 2);
return ret;
}
int socSocket(int domain, int type, int protocol)
{
int ret = 0;
u32 *cmdbuf = getThreadCommandBuffer();
// The protocol on the 3DS *must* be 0 to work
// To that end, when appropriate, we will make the change for the user
if (domain == AF_INET
&& type == SOCK_STREAM
&& protocol == IPPROTO_TCP) {
protocol = 0; // TCP is the only option, so 0 will work as expected
}
if (domain == AF_INET
&& type == SOCK_DGRAM
&& protocol == IPPROTO_UDP) {
protocol = 0; // UDP is the only option, so 0 will work as expected
}
cmdbuf[0] = IPC_MakeHeader(0x2,3,2); // 0x200C2
cmdbuf[1] = domain;
cmdbuf[2] = type;
cmdbuf[3] = protocol;
cmdbuf[4] = IPC_Desc_CurProcessHandle();
ret = svcSendSyncRequest(SOCU_handle);
if(ret != 0)
{
//errno = SYNC_ERROR;
return ret;
}
ret = (int)cmdbuf[1];
if(ret == 0) ret = cmdbuf[2];
if(ret < 0)
{
//if(cmdbuf[1] == 0)errno = _net_convert_error(ret);
//if(cmdbuf[1] != 0)errno = SYNC_ERROR;
return -1;
}
else
return cmdbuf[2];
}
int socBind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
Result ret = 0;
socklen_t tmp_addrlen = 0;
u32 *cmdbuf = getThreadCommandBuffer();
u8 tmpaddr[0x1c];
memset(tmpaddr, 0, 0x1c);
if(addr->sa_family == AF_INET)
tmp_addrlen = 8;
else
tmp_addrlen = 0x1c;
if(addrlen < tmp_addrlen)
{
//errno = EINVAL;
return -1;
}
tmpaddr[0] = tmp_addrlen;
tmpaddr[1] = addr->sa_family;
memcpy(&tmpaddr[2], &addr->sa_data, tmp_addrlen-2);
cmdbuf[0] = IPC_MakeHeader(0x5,2,4); // 0x50084
cmdbuf[1] = (u32)sockfd;
cmdbuf[2] = (u32)tmp_addrlen;
cmdbuf[3] = IPC_Desc_CurProcessHandle();
cmdbuf[5] = IPC_Desc_StaticBuffer(tmp_addrlen,0);
cmdbuf[6] = (u32)tmpaddr;
ret = svcSendSyncRequest(SOCU_handle);
if(ret != 0) {
//errno = SYNC_ERROR;
return ret;
}
ret = (int)cmdbuf[1];
if(ret == 0)
ret = _net_convert_error(cmdbuf[2]);
if(ret < 0) {
//errno = -ret;
return -1;
}
return ret;
}
int socListen(int sockfd, int max_connections)
{
Result ret = 0;
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = IPC_MakeHeader(0x3,2,2); // 0x30082
cmdbuf[1] = (u32)sockfd;
cmdbuf[2] = (u32)max_connections;
cmdbuf[3] = IPC_Desc_CurProcessHandle();
ret = svcSendSyncRequest(SOCU_handle);
if(ret != 0)
{
//errno = SYNC_ERROR;
return ret;
}
ret = (int)cmdbuf[1];
if(ret == 0)
ret = _net_convert_error(cmdbuf[2]);
if(ret < 0) {
//errno = -ret;
return -1;
}
return 0;
}
int socAccept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
{
Result ret = 0;
int tmp_addrlen = 0x1c;
u32 *cmdbuf = getThreadCommandBuffer();
u8 tmpaddr[0x1c];
u32 saved_threadstorage[2];
memset(tmpaddr, 0, 0x1c);
cmdbuf[0] = IPC_MakeHeader(0x4,2,2); // 0x40082
cmdbuf[1] = (u32)sockfd;
cmdbuf[2] = (u32)tmp_addrlen;
cmdbuf[3] = IPC_Desc_CurProcessHandle();
u32 *staticbufs = getThreadStaticBuffers();
saved_threadstorage[0] = staticbufs[0];
saved_threadstorage[1] = staticbufs[1];
staticbufs[0] = IPC_Desc_StaticBuffer(tmp_addrlen,0);
staticbufs[1] = (u32)tmpaddr;
ret = svcSendSyncRequest(SOCU_handle);
staticbufs[0] = saved_threadstorage[0];
staticbufs[1] = saved_threadstorage[1];
if(ret != 0)
return ret;
ret = (int)cmdbuf[1];
if(ret == 0)
ret = _net_convert_error(cmdbuf[2]);
if(ret < 0)
//errno = -ret;
if(ret >= 0 && addr != NULL)
{
addr->sa_family = tmpaddr[1];
if(*addrlen > tmpaddr[0])
*addrlen = tmpaddr[0];
memcpy(addr->sa_data, &tmpaddr[2], *addrlen - 2);
}
if(ret < 0)
return -1;
return ret;
}
int socPoll(struct pollfd *fds, nfds_t nfds, int timeout)
{
int ret = 0;
nfds_t i;
u32 size = sizeof(struct pollfd)*nfds;
u32 *cmdbuf = getThreadCommandBuffer();
u32 saved_threadstorage[2];
if(nfds == 0) {
return -1;
}
for(i = 0; i < nfds; ++i) {
fds[i].revents = 0;
}
cmdbuf[0] = IPC_MakeHeader(0x14,2,4); // 0x140084
cmdbuf[1] = (u32)nfds;
cmdbuf[2] = (u32)timeout;
cmdbuf[3] = IPC_Desc_CurProcessHandle();
cmdbuf[5] = IPC_Desc_StaticBuffer(size,10);
cmdbuf[6] = (u32)fds;
u32 * staticbufs = getThreadStaticBuffers();
saved_threadstorage[0] = staticbufs[0];
saved_threadstorage[1] = staticbufs[1];
staticbufs[0] = IPC_Desc_StaticBuffer(size,0);
staticbufs[1] = (u32)fds;
ret = svcSendSyncRequest(SOCU_handle);
staticbufs[0] = saved_threadstorage[0];
staticbufs[1] = saved_threadstorage[1];
if(ret != 0) {
return ret;
}
ret = (int)cmdbuf[1];
if(ret == 0)
ret = _net_convert_error(cmdbuf[2]);
if(ret < 0) {
//errno = -ret;
return -1;
}
return ret;
}
int socClose(int sockfd)
{
int ret = 0;
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = IPC_MakeHeader(0xB,1,2); // 0xB0042
cmdbuf[1] = (u32)sockfd;
cmdbuf[2] = IPC_Desc_CurProcessHandle();
ret = svcSendSyncRequest(SOCU_handle);
if(ret != 0) {
//errno = SYNC_ERROR;
return ret;
}
ret = (int)cmdbuf[1];
if(ret == 0)
ret =_net_convert_error(cmdbuf[2]);
if(ret < 0) {
//errno = -ret;
return -1;
}
return 0;
}
int socSetsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen)
{
int ret = 0;
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = IPC_MakeHeader(0x12,4,4); // 0x120104
cmdbuf[1] = (u32)sockfd;
cmdbuf[2] = (u32)level;
cmdbuf[3] = (u32)optname;
cmdbuf[4] = (u32)optlen;
cmdbuf[5] = IPC_Desc_CurProcessHandle();
cmdbuf[7] = IPC_Desc_StaticBuffer(optlen,9);
cmdbuf[8] = (u32)optval;
ret = svcSendSyncRequest(SOCU_handle);
if(ret != 0) {
return ret;
}
ret = (int)cmdbuf[1];
if(ret == 0)
ret = _net_convert_error(cmdbuf[2]);
if(ret < 0) {
//errno = -ret;
return -1;
}
return ret;
}
ssize_t soc_recv(int sockfd, void *buf, size_t len, int flags)
{
return soc_recvfrom(sockfd, buf, len, flags, NULL, 0);
}
ssize_t soc_send(int sockfd, const void *buf, size_t len, int flags)
{
return soc_sendto(sockfd, buf, len, flags, NULL, 0);
}

View File

@@ -0,0 +1,108 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include <3ds.h>
#include <string.h>
#include "fsreg.h"
#include "services.h"
static Handle *srvHandlePtr;
static int srvRefCount;
static RecursiveLock initLock;
static int initLockinit = 0;
Result srvSysInit(void)
{
Result rc = 0;
if (!initLockinit)
RecursiveLock_Init(&initLock);
RecursiveLock_Lock(&initLock);
if (srvRefCount > 0)
{
RecursiveLock_Unlock(&initLock);
return MAKERESULT(RL_INFO, RS_NOP, 25, RD_ALREADY_INITIALIZED);
}
srvHandlePtr = srvGetSessionHandle();
while(1)
{
rc = svcConnectToPort(srvHandlePtr, "srv:");
if (R_LEVEL(rc) != RL_PERMANENT ||
R_SUMMARY(rc) != RS_NOTFOUND ||
R_DESCRIPTION(rc) != RD_NOT_FOUND
) break;
svcSleepThread(500 * 1000LL);
}
if(R_SUCCEEDED(rc))
{
rc = srvRegisterClient();
srvRefCount++;
}
RecursiveLock_Unlock(&initLock);
return rc;
}
Result srvSysExit(void)
{
Result rc = 0;
RecursiveLock_Lock(&initLock);
if(srvRefCount > 1)
{
srvRefCount--;
RecursiveLock_Unlock(&initLock);
return MAKERESULT(RL_INFO, RS_NOP, 25, RD_BUSY);
}
if(srvHandlePtr != 0)
svcCloseHandle(*srvHandlePtr);
else
svcBreak(USERBREAK_ASSERT);
srvHandlePtr = 0;
srvRefCount--;
RecursiveLock_Unlock(&initLock);
return rc;
}
void fsSysInit(void)
{
if(R_FAILED(fsregSetupPermissions()))
svcBreak(USERBREAK_ASSERT);
Handle *fsHandlePtr = fsGetSessionHandle();
srvGetServiceHandle(fsHandlePtr, "fs:USER");
FSUSER_InitializeWithSdkVersion(*fsHandlePtr, SDK_VERSION);
FSUSER_SetPriority(0);
}

View File

@@ -0,0 +1,297 @@
/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include <sys/socket.h>
#include <3ds/result.h>
#include <3ds/svc.h>
#include <3ds/synchronization.h>
#include <arpa/inet.h>
#include "memory.h"
#include "minisoc.h"
#include "sock_util.h"
extern Handle terminationRequestEvent;
extern bool terminationRequest;
// soc's poll function is odd, and doesn't like -1 as fd.
// so this compacts everything together
static void compact(struct sock_server *serv)
{
int new_fds[MAX_CTXS];
struct sock_ctx *new_ctxs[MAX_CTXS];
nfds_t n = 0;
for(nfds_t i = 0; i < serv->nfds; i++)
{
if(serv->poll_fds[i].fd != -1)
{
new_fds[n] = serv->poll_fds[i].fd;
new_ctxs[n] = serv->ctx_ptrs[i];
n++;
}
}
for(nfds_t i = 0; i < n; i++)
{
serv->poll_fds[i].fd = new_fds[i];
serv->ctx_ptrs[i] = new_ctxs[i];
serv->ctx_ptrs[i]->i = i;
}
serv->nfds = n;
serv->compact_needed = false;
}
static struct sock_ctx *server_alloc_server_ctx(struct sock_server *serv)
{
for(int i = 0; i < MAX_PORTS; i++)
{
if(serv->serv_ctxs[i].type == SOCK_NONE)
return &serv->serv_ctxs[i];
}
return NULL;
}
static void server_close_ctx(struct sock_server *serv, struct sock_ctx *ctx)
{
serv->compact_needed = true;
Handle sock = serv->poll_fds[ctx->i].fd;
if(ctx->type == SOCK_CLIENT)
{
serv->close_cb(ctx);
serv->free(serv, ctx);
ctx->serv->n--;
}
socClose(sock);
ctx->should_close = false;
serv->poll_fds[ctx->i].fd = -1;
serv->poll_fds[ctx->i].events = 0;
serv->poll_fds[ctx->i].revents = 0;
ctx->type = SOCK_NONE;
}
Result server_init(struct sock_server *serv)
{
Result ret = 0;
ret = miniSocInit();
if(R_FAILED(ret))
return ret;
memset(serv, 0, sizeof(struct sock_server));
for(int i = 0; i < MAX_PORTS; i++)
serv->serv_ctxs[i].type = SOCK_NONE;
for(int i = 0; i < MAX_CTXS; i++)
serv->ctx_ptrs[i] = NULL;
ret = svcCreateEvent(&serv->started_event, RESET_STICKY);
if(R_FAILED(ret))
return ret;
return svcCreateEvent(&serv->shall_terminate_event, RESET_STICKY);
}
void server_bind(struct sock_server *serv, u16 port)
{
int server_sockfd;
Handle handles[2] = { terminationRequestEvent, serv->shall_terminate_event };
s32 idx = -1;
server_sockfd = socSocket(AF_INET, SOCK_STREAM, 0);
int res;
while(server_sockfd == -1)
{
if(svcWaitSynchronizationN(&idx, handles, 2, false, 100 * 1000 * 1000LL) == 0)
return;
server_sockfd = socSocket(AF_INET, SOCK_STREAM, 0);
}
if(server_sockfd != -1)
{
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(port);
saddr.sin_addr.s_addr = gethostid();
res = socBind(server_sockfd, (struct sockaddr*)&saddr, sizeof(struct sockaddr_in));
if(res == 0)
{
res = socListen(server_sockfd, 2);
if(res == 0)
{
int idx = serv->nfds;
serv->nfds++;
serv->poll_fds[idx].fd = server_sockfd;
serv->poll_fds[idx].events = POLLIN;
struct sock_ctx *new_ctx = server_alloc_server_ctx(serv);
memcpy(&new_ctx->addr_in, &saddr, sizeof(struct sockaddr_in));
new_ctx->type = SOCK_SERVER;
new_ctx->sockfd = server_sockfd;
new_ctx->n = 0;
new_ctx->i = idx;
serv->ctx_ptrs[idx] = new_ctx;
}
}
}
}
void server_run(struct sock_server *serv)
{
struct pollfd *fds = serv->poll_fds;
Handle handles[2] = { terminationRequestEvent, serv->shall_terminate_event };
serv->running = true;
svcSignalEvent(serv->started_event);
while(serv->running && !terminationRequest)
{
s32 idx = -1;
if(svcWaitSynchronizationN(&idx, handles, 2, false, 0LL) == 0)
goto abort_connections;
if(serv->nfds == 0)
{
if(svcWaitSynchronizationN(&idx, handles, 2, false, 12 * 1000 * 1000LL) == 0)
goto abort_connections;
else
continue;
}
for(nfds_t i = 0; i < serv->nfds; i++)
fds[i].revents = 0;
int pollres = socPoll(fds, serv->nfds, 50);
for(nfds_t i = 0; pollres > 0 && i < serv->nfds; i++)
{
struct sock_ctx *curr_ctx = serv->ctx_ptrs[i];
if((fds[i].revents & POLLHUP) || curr_ctx->should_close)
server_close_ctx(serv, curr_ctx);
else if(fds[i].revents & POLLIN)
{
if(curr_ctx->type == SOCK_SERVER) // Listening socket?
{
struct sockaddr_in saddr;
socklen_t len = sizeof(struct sockaddr_in);
int client_sockfd = socAccept(fds[i].fd, (struct sockaddr *)&saddr, &len);
if(svcWaitSynchronizationN(&idx, handles, 2, false, 0LL) == 0)
goto abort_connections;
if(client_sockfd < 0 || curr_ctx->n == serv->clients_per_server || serv->nfds == MAX_CTXS)
socClose(client_sockfd);
else
{
struct sock_ctx *new_ctx = serv->alloc(serv, ntohs(curr_ctx->addr_in.sin_port));
if(new_ctx == NULL)
socClose(client_sockfd);
else
{
fds[serv->nfds].fd = client_sockfd;
fds[serv->nfds].events = POLLIN;
int new_idx = serv->nfds;
serv->nfds++;
curr_ctx->n++;
new_ctx->type = SOCK_CLIENT;
new_ctx->sockfd = client_sockfd;
new_ctx->serv = curr_ctx;
new_ctx->i = new_idx;
new_ctx->n = 0;
serv->ctx_ptrs[new_idx] = new_ctx;
if(serv->accept_cb(new_ctx) == -1)
server_close_ctx(serv, new_ctx);
memcpy(&new_ctx->addr_in, &saddr, sizeof(struct sockaddr_in));
}
}
}
else
{
if(serv->data_cb(curr_ctx) == -1)
server_close_ctx(serv, curr_ctx);
}
}
}
if(serv->compact_needed)
compact(serv);
}
// Clean up.
for(unsigned int i = 0; i < serv->nfds; i++)
{
if(fds[i].fd != -1)
socClose(fds[i].fd);
}
serv->running = false;
svcClearEvent(serv->started_event);
return;
abort_connections:
for(unsigned int i = 0; i < serv->nfds; i++)
{
struct linger linger;
linger.l_onoff = 1;
linger.l_linger = 0;
socSetsockopt(fds[i].fd, SOL_SOCKET, SO_LINGER, &linger, sizeof(struct linger));
socClose(fds[i].fd);
}
serv->running = false;
svcClearEvent(serv->started_event);
}
void server_finalize(struct sock_server *serv)
{
for(nfds_t i = 0; i < MAX_CTXS; i++)
{
if(serv->ctx_ptrs[i] != NULL)
server_close_ctx(serv, serv->ctx_ptrs[i]);
}
miniSocExit();
svcClearEvent(serv->shall_terminate_event);
svcCloseHandle(serv->shall_terminate_event);
svcClearEvent(serv->started_event);
svcCloseHandle(serv->started_event);
}