diff --git a/sysmodules/rosalina/source/menus/process_list.c b/sysmodules/rosalina/source/menus/process_list.c index 2499bc5..a0b700f 100644 --- a/sysmodules/rosalina/source/menus/process_list.c +++ b/sysmodules/rosalina/source/menus/process_list.c @@ -27,6 +27,7 @@ #include <3ds.h> #include "menus/process_list.h" #include "memory.h" +#include "csvc.h" #include "draw.h" #include "menu.h" #include "utils.h" @@ -78,10 +79,340 @@ static inline int ProcessListMenu_FormatInfoLine(char *out, const ProcessInfo *i 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 void ProcessListMenu_MemoryViewer(const ProcessInfo *info) +{ + Handle processHandle; + Result res = OpenProcessByName(info->name, &processHandle); + + if(R_SUCCEEDED(res)) + { + u32 codeStartAddress, heapStartAddress; + u32 codeDestAddress, heapDestAddress; + u32 codeTotalSize, heapTotalSize; + + s64 textStartAddress, textTotalRoundedSize, rodataTotalRoundedSize, dataTotalRoundedSize; + + svcGetProcessInfo(&textTotalRoundedSize, processHandle, 0x10002); + svcGetProcessInfo(&rodataTotalRoundedSize, processHandle, 0x10003); + svcGetProcessInfo(&dataTotalRoundedSize, processHandle, 0x10004); + + svcGetProcessInfo(&textStartAddress, processHandle, 0x10005); + + codeTotalSize = (u32)(textTotalRoundedSize + rodataTotalRoundedSize + dataTotalRoundedSize); + codeDestAddress = codeStartAddress = (u32)textStartAddress; //should be 0x00100000 + + MemInfo info; + PageInfo out; + + heapDestAddress = heapStartAddress = 0x08000000; + svcQueryProcessMemory(&info, &out, processHandle, heapStartAddress); + heapTotalSize = info.size; + + Result codeRes = svcMapProcessMemoryEx(processHandle, codeDestAddress, codeStartAddress, codeTotalSize); + Result heapRes = svcMapProcessMemoryEx(processHandle, heapDestAddress, heapStartAddress, heapTotalSize); + + if(R_SUCCEEDED(codeRes | heapRes)) + { + #define ROWS_PER_SCREEN 0x10 + #define BYTES_PER_ROW 0x10 + #define VIEWER_PAGE_SIZE (ROWS_PER_SCREEN*BYTES_PER_ROW) + + #define totalRows ((menus[MENU_MODE_NORMAL].max - (menus[MENU_MODE_NORMAL].max % BYTES_PER_ROW))/ROWS_PER_SCREEN) + + enum MenuModes { + MENU_MODE_NORMAL = 0, + MENU_MODE_GOTO, + MENU_MODE_SEARCH, + + MENU_MODE_MAX, + }; + + typedef struct { + u32 selected; + u8 * buf; + + u32 starti; + u32 max; + } MenuData; + + bool editing; + + MenuData menus[MENU_MODE_MAX] = {0}; + int menuMode = MENU_MODE_NORMAL; + + bool checkMode(int newMode) + { + if(menuMode == newMode) + { + menuMode = MENU_MODE_NORMAL; + return true; + } + else + { + menuMode = newMode; + return false; + } + } + + // Editing + void selectedByteIncrement(void) { menus[menuMode].buf[menus[menuMode].selected]++; } + void selectedByteDecrement(void) { menus[menuMode].buf[menus[menuMode].selected]--; } + + void selectedByteAdd0x10(void) { menus[menuMode].buf[menus[menuMode].selected] += 0x10; } + void selectedByteSub0x10(void) { menus[menuMode].buf[menus[menuMode].selected] -= 0x10; } + // ------------------------------------------ + + // Movement + #define SELECTED_DEC(decval) do { \ + if(menus[menuMode].selected >= decval) \ + menus[menuMode].selected -= decval; \ + } while(0) + + #define SELECTED_INC(incval) do { \ + if(menus[menuMode].selected < (menus[menuMode].max - incval)) \ + menus[menuMode].selected += incval; \ + } while(0) + + void selectedMoveLeft(void) { SELECTED_DEC(1); } + void selectedMoveRight(void) { SELECTED_INC(1); } + + void selectedMoveUp(void) { SELECTED_DEC(BYTES_PER_ROW); } + void selectedMoveDown(void) { SELECTED_INC(BYTES_PER_ROW); } + // ------------------------------------------ + + // Viewing + void viewHeap(void) + { + menus[MENU_MODE_NORMAL].buf = (u8*)heapDestAddress; + menus[MENU_MODE_NORMAL].max = heapTotalSize; + } + void viewCode(void) + { + menus[MENU_MODE_NORMAL].buf = (u8*)codeDestAddress; + menus[MENU_MODE_NORMAL].max = codeTotalSize; + } + + viewHeap(); + // ------------------------------------------ + + // Jumping + u32 gotoAddress = 0; + + void finishJumping(void) + { + menus[MENU_MODE_NORMAL].selected = __builtin_bswap32(gotoAddress); // The data is edited in reverse, so it needs to be swapped before usage + } + + menus[MENU_MODE_GOTO].buf = (u8*)&gotoAddress; + menus[MENU_MODE_GOTO].max = sizeof(gotoAddress); + // ------------------------------------------ + + // Searching + #define searchPatternSize menus[MENU_MODE_SEARCH].max + #define searchPatternMaxSize (u32)VIEWER_PAGE_SIZE + u8 searchPattern[searchPatternMaxSize] = {0}; + + void searchPatternEnlarge(void) + { + searchPatternSize++; + if(searchPatternSize > searchPatternMaxSize) + searchPatternSize = 1; + } + void searchPatternReduce(void) + { + searchPatternSize--; + if(searchPatternSize < 1) + searchPatternSize = searchPatternMaxSize; + } + + void finishSearching(void) + { + u8 * startpos = menus[MENU_MODE_NORMAL].buf + menus[MENU_MODE_NORMAL].selected; + u32 size = menus[MENU_MODE_NORMAL].max - menus[MENU_MODE_NORMAL].selected; + if (size >= searchPatternSize) + menus[MENU_MODE_NORMAL].selected = (u32)memsearch(startpos, searchPattern, size, searchPatternSize) - (u32)menus[MENU_MODE_NORMAL].buf; + } + + menus[MENU_MODE_SEARCH].buf = searchPattern; + menus[MENU_MODE_SEARCH].max = 1; + // ------------------------------------------ + + void drawMenu(void) + { + Draw_Lock(); + Draw_DrawString(10, 10, COLOR_TITLE, "Memory viewer"); + Draw_DrawString(10, 26, COLOR_WHITE, "D-PAD to move, X to jump, Y to search, A to edit."); + + #define CHARACTER_WIDTH 6 + if(editing) + Draw_DrawString(10 + CHARACTER_WIDTH* 44, 26, COLOR_RED, "edit"); + + char * modeStr[] = { + "move", + "jump", + "search", + }; + u32 modeStrPos[] = { + 9, + 20, + 31, + }; + Draw_DrawString(10 + CHARACTER_WIDTH*modeStrPos[menuMode], 26, COLOR_GREEN, modeStr[menuMode]); + + for(u32 row = menus[menuMode].starti; row < (menus[menuMode].starti + ROWS_PER_SCREEN); row++) + { + u32 offset = row - menus[menuMode].starti; + u32 y = 44 + offset*12; + + u32 address = row*BYTES_PER_ROW; + Draw_DrawFormattedString(10, y, COLOR_TITLE, "%.8lx | ", address); + + for(int cursor = 0; cursor < BYTES_PER_ROW; cursor++, address++) + { + u32 x = 10+66 + cursor*14 + (cursor >= BYTES_PER_ROW/2)*10; + + if(address < menus[menuMode].max) + { + Draw_DrawFormattedString(x, y, + address == menus[menuMode].selected ? (editing ? COLOR_RED : COLOR_GREEN) : COLOR_WHITE, + "%.2x", + menus[menuMode].buf[address]); + } + else + Draw_DrawString(x, y, COLOR_WHITE, " "); + } + } + + Draw_FlushFramebuffer(); + Draw_Unlock(); + } + + void clearMenu(void) + { + Draw_Lock(); + Draw_ClearFramebuffer(); + Draw_FlushFramebuffer(); + Draw_Unlock(); + } + + void handleScrolling(void) + { + for(u32 i = 0; i < totalRows; i++) + { + if(totalRows <= ROWS_PER_SCREEN) + break; + + u32 scroll = menus[MENU_MODE_NORMAL].starti; + u32 selectedRow = (menus[MENU_MODE_NORMAL].selected - (menus[MENU_MODE_NORMAL].selected % BYTES_PER_ROW))/BYTES_PER_ROW; + + if(scroll > selectedRow) + scroll--; + + if((i <= selectedRow) && \ + ((selectedRow - scroll) >= ROWS_PER_SCREEN) && \ + (scroll != (totalRows - ROWS_PER_SCREEN))) + scroll++; + + menus[MENU_MODE_NORMAL].starti = scroll; + } + } + + clearMenu(); + + do + { + if(menuMode == MENU_MODE_NORMAL) + handleScrolling(); + + drawMenu(); + + u32 pressed = waitInputWithTimeout(1000); + + if(pressed & BUTTON_A) + editing = !editing; + else if(pressed & BUTTON_X) + { + if(checkMode(MENU_MODE_GOTO)) + finishJumping(); + } + else if(pressed & BUTTON_Y) + { + if(checkMode(MENU_MODE_SEARCH)) + finishSearching(); + } + + if(editing) + { + // Edit the highlighted byte + if(pressed & BUTTON_LEFT) + selectedByteAdd0x10(); + else if(pressed & BUTTON_RIGHT) + selectedByteSub0x10(); + else if(pressed & BUTTON_UP) + selectedByteIncrement(); + else if(pressed & BUTTON_DOWN) + selectedByteDecrement(); + } + else + { + // Move the cursor + if(pressed & BUTTON_LEFT) + selectedMoveLeft(); + else if(pressed & BUTTON_RIGHT) + selectedMoveRight(); + else if(pressed & BUTTON_UP) + selectedMoveUp(); + else if(pressed & BUTTON_DOWN) + selectedMoveDown(); + + else if(pressed & BUTTON_L1) + { + if(menuMode == MENU_MODE_NORMAL) + viewHeap(); + else if(menuMode == MENU_MODE_SEARCH) + searchPatternReduce(); + } + else if(pressed & BUTTON_R1) + { + if(menuMode == MENU_MODE_NORMAL) + viewCode(); + else if(menuMode == MENU_MODE_SEARCH) + searchPatternEnlarge(); + } + } + + if(pressed & BUTTON_B) // go back to the list, or the simple viewer + { + if(menuMode != MENU_MODE_NORMAL) + { + menuMode = MENU_MODE_NORMAL; + editing = false; + } + else + break; + } + + if(menus[menuMode].selected >= menus[menuMode].max) + menus[menuMode].selected = menus[menuMode].max - 1; + } + while(!terminationRequest); + + clearMenu(); + } + + if(R_SUCCEEDED(codeRes)) + svcUnmapProcessMemoryEx(processHandle, codeDestAddress, codeTotalSize); + if(R_SUCCEEDED(heapRes)) + svcUnmapProcessMemoryEx(processHandle, heapDestAddress, heapTotalSize); + + svcCloseHandle(processHandle); + } +} + static inline void ProcessListMenu_HandleSelected(const ProcessInfo *info) { if(!gdbServer.super.running || info->isZombie) - return; + ProcessListMenu_MemoryViewer(info); u32 id; for(id = 0; id < MAX_DEBUG && (!(gdbServer.ctxs[id].flags & GDB_FLAG_SELECTED) || gdbServer.ctxs[id].pid != info->pid); id++);