From 0d1128c2fa20c8539b4359ddffd46b52cbb9408d Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Tue, 14 Jun 2022 18:55:08 -0700 Subject: [PATCH 1/9] Switch hex editor to full CUSTOMDRAW - This will allow us to draw with more precision. Especially for selection ranges and editing. --- r4300i Memory.c | 87 +++++++++++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 32 deletions(-) diff --git a/r4300i Memory.c b/r4300i Memory.c index a19bbeb..b0caebf 100644 --- a/r4300i Memory.c +++ b/r4300i Memory.c @@ -59,13 +59,12 @@ const COLORREF TC_DEC = RGB(180, 0, 0); // Value decreased const COLORREF TC_READ = RGB(0, 0, 255); // Address has read watchpoint const COLORREF TC_WRITE = RGB(255, 0, 255); // Address has write watchpoint const COLORREF TC_READ_WRITE = RGB(128, 0, 255); // Address has read/write watchpoint -const COLORREF TC_DEFAULT = RGB(0, 0, 0); // Default text color const COLORREF BG_EVEN = RGB(240, 240, 240); const COLORREF BG_ODD = RGB(230, 230, 230); -const COLORREF BG_DEFAULT = RGB(255, 255, 255); static HWND Memory_Win_hDlg, hAddrEdit, hVAddr, hPAddr, hRefresh, hList, hScrlBar; +static HBRUSH hBkEven, hBkOdd; static HFONT hWatchFont; static HANDLE hRefreshThread = NULL; static HANDLE hRefreshMutex = NULL; @@ -77,10 +76,21 @@ static struct MEMORY_VIEW_ROW MemoryViewRows[16]; void __cdecl Create_Memory_Window ( int Child ) { DWORD ThreadID; if ( Child ) { + hBkEven = CreateSolidBrush(BG_EVEN); + hBkOdd = CreateSolidBrush(BG_ODD); + + LOGFONT lf; + GetObject(GetStockObject(ANSI_FIXED_FONT), sizeof(lf), &lf); + lf.lfUnderline = TRUE; + hWatchFont = CreateFontIndirect(&lf); + InMemoryWindow = TRUE; DialogBox( hInst, "MEMORY", NULL,(DLGPROC) Memory_Window_Proc ); - DeleteObject(hWatchFont); InMemoryWindow = FALSE; + + DeleteObject(hWatchFont); + DeleteObject(hBkOdd); + DeleteObject(hBkEven); } else { if (!InMemoryWindow) { CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Create_Memory_Window, @@ -105,7 +115,7 @@ void Update_Data_Column(struct MEMORY_VIEW_ROW* row, MIPS_WORD word, int index, } else if (ShowDiff && word.UB[3 - i] < row->OldData[index]) { row->TextColors[index] = TC_DEC; } else { - row->TextColors[index] = TC_DEFAULT; + row->TextColors[index] = GetSysColor(COLOR_WINDOWTEXT); } row->OldData[index] = word.UB[3 - i]; @@ -135,7 +145,7 @@ void Update_Data_Column_With_WatchPoint(struct MEMORY_VIEW_ROW* row, DWORD locat } else if (ShowDiff && word.UB[3 - i] < row->OldData[index]) { row->TextColors[index] = TC_DEC; } else { - row->TextColors[index] = TC_DEFAULT; + row->TextColors[index] = GetSysColor(COLOR_WINDOWTEXT); } break; } @@ -296,6 +306,7 @@ LRESULT CALLBACK Memory_Window_Proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM } case NM_CUSTOMDRAW: { LPNMLVCUSTOMDRAW lplvcd = (LPNMLVCUSTOMDRAW)lParam; + switch (lplvcd->nmcd.dwDrawStage) { case CDDS_PREPAINT: SetWindowLong(hDlg, DWL_MSGRESULT, CDRF_NOTIFYITEMDRAW); @@ -303,38 +314,55 @@ LRESULT CALLBACK Memory_Window_Proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM case CDDS_ITEMPREPAINT: SetWindowLong(hDlg, DWL_MSGRESULT, CDRF_NOTIFYSUBITEMDRAW); return TRUE; - case CDDS_ITEMPREPAINT | CDDS_SUBITEM: - if (lplvcd->iSubItem >= 1 && lplvcd->iSubItem < 17) { + case CDDS_ITEMPREPAINT | CDDS_SUBITEM: { + struct MEMORY_VIEW_ROW* row = &MemoryViewRows[lplvcd->nmcd.dwItemSpec]; + + switch (lplvcd->iSubItem) { + case 0: + // Address column + SetWindowLong(hDlg, DWL_MSGRESULT, CDRF_DODEFAULT); + break; + case 17: + // ASCII column + SetTextColor(lplvcd->nmcd.hdc, GetSysColor(COLOR_WINDOWTEXT)); + SetBkColor(lplvcd->nmcd.hdc, GetSysColor(COLOR_WINDOW)); + + DrawText(lplvcd->nmcd.hdc, row->AsciiStr, strlen(row->AsciiStr), &lplvcd->nmcd.rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER); + + SetWindowLong(hDlg, DWL_MSGRESULT, CDRF_SKIPDEFAULT); + break; + default: { + // Hex columns int index = lplvcd->iSubItem - 1; - struct MEMORY_VIEW_ROW* row = &MemoryViewRows[lplvcd->nmcd.dwItemSpec]; SelectObject(lplvcd->nmcd.hdc, row->Fonts[index]); - lplvcd->clrText = row->TextColors[index]; + SetTextColor(lplvcd->nmcd.hdc, row->TextColors[index]); if (index & 1) { - lplvcd->clrTextBk = BG_ODD; + SetBkColor(lplvcd->nmcd.hdc, BG_ODD); + FillRect(lplvcd->nmcd.hdc, &lplvcd->nmcd.rc, hBkOdd); } else { - lplvcd->clrTextBk = BG_EVEN; + SetBkColor(lplvcd->nmcd.hdc, BG_EVEN); + FillRect(lplvcd->nmcd.hdc, &lplvcd->nmcd.rc, hBkEven); + } + + char* str = row->HexStr[index]; + DrawText(lplvcd->nmcd.hdc, str, strlen(str), &lplvcd->nmcd.rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER); + + if (index % 4 == 3 && index < 15) { + // Draw vertical separators on word boundaries + rcBox.left = lplvcd->nmcd.rc.right - 1; + rcBox.top = lplvcd->nmcd.rc.top; + rcBox.right = lplvcd->nmcd.rc.right; + rcBox.bottom = lplvcd->nmcd.rc.bottom; + FillRect(lplvcd->nmcd.hdc, &rcBox, (HBRUSH)GetStockObject(GRAY_BRUSH)); } - } else { - SelectObject(lplvcd->nmcd.hdc, GetStockObject(ANSI_FIXED_FONT)); - lplvcd->clrText = TC_DEFAULT; - lplvcd->clrTextBk = BG_DEFAULT; + SetWindowLong(hDlg, DWL_MSGRESULT, CDRF_SKIPDEFAULT); + break; } - SetWindowLong(hDlg, DWL_MSGRESULT, CDRF_NEWFONT | CDRF_NOTIFYPOSTPAINT); - return TRUE; - case CDDS_ITEMPOSTPAINT | CDDS_SUBITEM: - if (lplvcd->iSubItem >= 1 && lplvcd->iSubItem < 13 && (lplvcd->iSubItem - 1) % 4 == 3) { - int x = (lplvcd->iSubItem - 1) * 28 + 117; - int y = lplvcd->nmcd.dwItemSpec * 17 + 24; - - rcBox.left = x; - rcBox.top = y; - rcBox.right = x + 1; - rcBox.bottom = y + 17; - FillRect(lplvcd->nmcd.hdc, &rcBox, (HBRUSH)GetStockObject(GRAY_BRUSH)); } return TRUE; + } default: SetWindowLong(hDlg, DWL_MSGRESULT, CDRF_DODEFAULT); return TRUE; @@ -591,11 +619,6 @@ void Setup_Memory_Window (HWND hDlg) { SetScrollInfo(hScrlBar,SB_CTL,&si,TRUE); SetWindowSubclass(hScrlBar, Memory_ListViewScroll_Proc, 0, 0); } - - LOGFONT lf; - GetObject(GetStockObject(ANSI_FIXED_FONT), sizeof(lf), &lf); - lf.lfUnderline = TRUE; - hWatchFont = CreateFontIndirect(&lf); if ( !GetStoredWinPos( "Memory", &X, &Y ) ) { X = (GetSystemMetrics( SM_CXSCREEN ) - WindowWidth) / 2; From bd3d7b718dc29e9f962ab62ed8a7c515d645b409 Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Tue, 21 Jun 2022 21:45:56 -0700 Subject: [PATCH 2/9] Add basic hex selection --- r4300i Memory.c | 156 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 144 insertions(+), 12 deletions(-) diff --git a/r4300i Memory.c b/r4300i Memory.c index b0caebf..23f835a 100644 --- a/r4300i Memory.c +++ b/r4300i Memory.c @@ -25,6 +25,7 @@ */ #include +#include #include #include #include "main.h" @@ -42,6 +43,7 @@ void Setup_Memory_Window (HWND hDlg); void Start_Auto_Refresh_Thread(void); void Scroll_Memory_View(int lines); void Refresh_Memory_With_Diff(BOOL ShowDiff); +void Clear_Selection(void); LRESULT CALLBACK Memory_Window_Proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); @@ -49,11 +51,21 @@ struct MEMORY_VIEW_ROW { unsigned char OldData[16]; COLORREF TextColors[16]; HFONT Fonts[16]; + DWORD Location; char LocationStr[11]; char HexStr[16][3]; char AsciiStr[17]; }; +struct SELECTION { + BOOL enabled; + BOOL dragging; + BOOL column_hex; + DWORD anchor; + DWORD range[2]; + DWORD range_cmp[2]; +}; + const COLORREF TC_INC = RGB(0, 180, 0); // Value increased const COLORREF TC_DEC = RGB(180, 0, 0); // Value decreased const COLORREF TC_READ = RGB(0, 0, 255); // Address has read watchpoint @@ -72,6 +84,7 @@ static int InMemoryWindow = FALSE; static int wheel = 0; static int thumb = -1; static struct MEMORY_VIEW_ROW MemoryViewRows[16]; +static struct SELECTION selection = { 0 }; void __cdecl Create_Memory_Window ( int Child ) { DWORD ThreadID; @@ -159,6 +172,7 @@ void Insert_MemoryLineDump (unsigned int location, int InsertPos, BOOL ShowDiff) location <<= 4; + row->Location = location; sprintf(row->LocationStr, "0x%08X", location); __try { @@ -266,12 +280,18 @@ LRESULT CALLBACK Memory_Window_Proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM Refresh_Memory_With_Diff(FALSE); break; case IDCANCEL: - EndDialog( hDlg, IDCANCEL ); + Clear_Selection(); break; default: break; } return FALSE; + case WM_SYSCOMMAND: + if (wParam == SC_CLOSE) { + EndDialog(hDlg, TRUE); + return TRUE; + } + break; case WM_NOTIFY: switch (((LPNMHDR)lParam)->code) { case HDN_BEGINTRACK: @@ -324,6 +344,7 @@ LRESULT CALLBACK Memory_Window_Proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM break; case 17: // ASCII column + // TODO: Selection colors SetTextColor(lplvcd->nmcd.hdc, GetSysColor(COLOR_WINDOWTEXT)); SetBkColor(lplvcd->nmcd.hdc, GetSysColor(COLOR_WINDOW)); @@ -334,23 +355,27 @@ LRESULT CALLBACK Memory_Window_Proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM default: { // Hex columns int index = lplvcd->iSubItem - 1; + DWORD location = row->Location + index; SelectObject(lplvcd->nmcd.hdc, row->Fonts[index]); - SetTextColor(lplvcd->nmcd.hdc, row->TextColors[index]); - - if (index & 1) { - SetBkColor(lplvcd->nmcd.hdc, BG_ODD); - FillRect(lplvcd->nmcd.hdc, &lplvcd->nmcd.rc, hBkOdd); + if (selection.enabled && (location >= selection.range[0] && location <= selection.range[1])) { + SetTextColor(lplvcd->nmcd.hdc, GetSysColor(COLOR_HIGHLIGHTTEXT)); + FillRect(lplvcd->nmcd.hdc, &lplvcd->nmcd.rc, GetSysColorBrush(COLOR_HIGHLIGHT)); } else { - SetBkColor(lplvcd->nmcd.hdc, BG_EVEN); - FillRect(lplvcd->nmcd.hdc, &lplvcd->nmcd.rc, hBkEven); + SetTextColor(lplvcd->nmcd.hdc, row->TextColors[index]); + + if (index & 1) { + FillRect(lplvcd->nmcd.hdc, &lplvcd->nmcd.rc, hBkOdd); + } else { + FillRect(lplvcd->nmcd.hdc, &lplvcd->nmcd.rc, hBkEven); + } } char* str = row->HexStr[index]; DrawText(lplvcd->nmcd.hdc, str, strlen(str), &lplvcd->nmcd.rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER); - if (index % 4 == 3 && index < 15) { - // Draw vertical separators on word boundaries + if (index % 4 == 3 && index < 15 && (!selection.enabled || location < selection.range[0] || location > selection.range[1])) { + // Draw vertical separators on word boundaries only when the byte is not selected rcBox.left = lplvcd->nmcd.rc.right - 1; rcBox.top = lplvcd->nmcd.rc.top; rcBox.right = lplvcd->nmcd.rc.right; @@ -445,6 +470,7 @@ LRESULT CALLBACK Memory_ListViewKeys_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, L default: break; } + break; default: break; } @@ -452,6 +478,110 @@ LRESULT CALLBACK Memory_ListViewKeys_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, L return DefSubclassProc(hWnd, uMsg, wParam, lParam); } +LRESULT CALLBACK Memory_ListViewDrag_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) { + switch (uMsg) { + case WM_LBUTTONDOWN: { + SetFocus(hWnd); + + POINT pt = { LOWORD(lParam), HIWORD(lParam) }; + LVHITTESTINFO hit_test; + hit_test.pt = pt; + ListView_SubItemHitTest(hWnd, &hit_test); + + struct MEMORY_VIEW_ROW* row = &MemoryViewRows[hit_test.iItem]; + + if (hit_test.iSubItem == 17) { + // Clicking in ASCII column + // TODO + return FALSE; + } else if (hit_test.iSubItem > 0) { + // Clicking in hex columns + int index = hit_test.iSubItem - 1; + DWORD location = row->Location + index; + + selection.enabled = TRUE; + selection.dragging = TRUE; + selection.column_hex = TRUE; + + if (wParam & MK_SHIFT) { + // Shift-Click + selection.range[0] = min(location, selection.anchor); + selection.range[1] = max(location, selection.anchor); + memcpy(selection.range_cmp, selection.range, sizeof(selection.range_cmp)); + } else { + // Click without Shift + selection.anchor = location; + selection.range[0] = location; + selection.range[1] = location; + selection.range_cmp[0] = location; + selection.range_cmp[1] = location; + } + return FALSE; + } + break; + } + case WM_LBUTTONUP: + selection.dragging = FALSE; + break; + case WM_MOUSEMOVE: + if (selection.dragging) { + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + LVHITTESTINFO hit_test; + hit_test.pt = pt; + ListView_SubItemHitTest(hWnd, &hit_test); + struct MEMORY_VIEW_ROW* row = &MemoryViewRows[hit_test.iItem]; + + if (hit_test.iSubItem == 17 && selection.column_hex == FALSE) { + // Dragging in ASCII column + // TODO + } else if (hit_test.iSubItem > 0 && selection.column_hex == TRUE) { + // Dragging in hex columns + int index = hit_test.iSubItem - 1; + DWORD location = row->Location + index; + + selection.range[0] = min(location, selection.anchor); + selection.range[1] = max(location, selection.anchor); + + if (memcmp(selection.range, selection.range_cmp, sizeof(selection.range)) != 0) { + // TODO: Invalidate only the changed rect + InvalidateRect(hWnd, NULL, FALSE); + + memcpy(selection.range_cmp, selection.range, sizeof(selection.range_cmp)); + return FALSE; + } + } + } + break; + case WM_ACTIVATE: + if (selection.dragging && wParam == WA_INACTIVE) { + Clear_Selection(); + return FALSE; + } + break; + case WM_KILLFOCUS: + case WM_RBUTTONDOWN: + if (selection.dragging) { + Clear_Selection(); + return FALSE; + } + default: + break; + } + + return DefSubclassProc(hWnd, uMsg, wParam, lParam); +} + +void Clear_Selection(void) { + selection.enabled = FALSE; + selection.dragging = FALSE; + selection.column_hex = FALSE; + selection.anchor = 0; + selection.range[0] = 0; + selection.range[1] = 0; + selection.range_cmp[0] = 0; + selection.range_cmp[1] = 0; +} + void Scroll_Memory_View(int lines) { char value[20]; @@ -555,7 +685,7 @@ void Setup_Memory_Window (HWND hDlg) { Start_Auto_Refresh_Thread(); hList = CreateWindowEx(WS_EX_CLIENTEDGE, WC_LISTVIEW, "", WS_CHILD | WS_VISIBLE | - LVS_OWNERDATA | LVS_REPORT | LVS_NOSORTHEADER, 14,39,682,300,hDlg, + LVS_OWNERDATA | LVS_REPORT | LVS_NOSORTHEADER | LVS_SINGLESEL, 14,39,682,300,hDlg, (HMENU)IDC_LIST_VIEW,hInst,NULL ); if (hList) { ListView_SetExtendedListViewStyle(hList, LVS_EX_DOUBLEBUFFER); @@ -591,6 +721,8 @@ void Setup_Memory_Window (HWND hDlg) { } SetWindowSubclass(hList, Memory_ListViewScroll_Proc, 0, 0); SetWindowSubclass(hList, Memory_ListViewKeys_Proc, 0, 0); + SetWindowSubclass(hList, Memory_ListViewDrag_Proc, 0, 0); + Clear_Selection(); } hAddrEdit = GetDlgItem(hDlg, IDC_ADDR_EDIT); @@ -625,6 +757,6 @@ void Setup_Memory_Window (HWND hDlg) { Y = (GetSystemMetrics( SM_CYSCREEN ) - WindowHeight) / 2; } - SetWindowPos(hDlg,NULL,X,Y,WindowWidth,WindowHeight, SWP_NOZORDER | SWP_SHOWWINDOW); + SetWindowPos(hDlg,NULL,X,Y,WindowWidth,WindowHeight, SWP_NOZORDER | SWP_SHOWWINDOW); } \ No newline at end of file From d58fcffce614493244c530dedc30b18cd4687923 Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Tue, 21 Jun 2022 23:54:05 -0700 Subject: [PATCH 3/9] Add ASCII column text selection --- r4300i Memory.c | 182 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 143 insertions(+), 39 deletions(-) diff --git a/r4300i Memory.c b/r4300i Memory.c index 23f835a..47850ad 100644 --- a/r4300i Memory.c +++ b/r4300i Memory.c @@ -39,11 +39,16 @@ #define IDC_SCRL_BAR 0x103 #define IDC_REFRESH 0x104 +// TODO: Figure out if these can be queried instead of hardcoded +#define PADDING 6 +#define CHAR_WIDTH 8 + void Setup_Memory_Window (HWND hDlg); void Start_Auto_Refresh_Thread(void); void Scroll_Memory_View(int lines); void Refresh_Memory_With_Diff(BOOL ShowDiff); void Clear_Selection(void); +int Get_Ascii_Index(POINTS pt); LRESULT CALLBACK Memory_Window_Proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); @@ -342,23 +347,100 @@ LRESULT CALLBACK Memory_Window_Proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM // Address column SetWindowLong(hDlg, DWL_MSGRESULT, CDRF_DODEFAULT); break; - case 17: + case 17: { // ASCII column - // TODO: Selection colors SetTextColor(lplvcd->nmcd.hdc, GetSysColor(COLOR_WINDOWTEXT)); - SetBkColor(lplvcd->nmcd.hdc, GetSysColor(COLOR_WINDOW)); - DrawText(lplvcd->nmcd.hdc, row->AsciiStr, strlen(row->AsciiStr), &lplvcd->nmcd.rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER); + DWORD location_start = row->Location; + DWORD location_end = row->Location + 15; + if (selection.enabled && location_start <= selection.range[1] && location_end >= selection.range[0]) { + RECT rc = lplvcd->nmcd.rc; + rc.left += PADDING; + rc.right -= PADDING; + + // Text has selection + // + // There are four possible configurations: + // 1. The entire line is selected (one DrawText call) + // 2. Only the beginning of the line is selected (two DrawText calls) + // 3. Only the end of the line is selected (two DrawText calls) + // 4. Only the middle of the line is selected (three DrawText calls) + if (location_start >= selection.range[0] && location_end <= selection.range[1]) { + // Entire line selected + SetTextColor(lplvcd->nmcd.hdc, GetSysColor(COLOR_HIGHLIGHTTEXT)); + FillRect(lplvcd->nmcd.hdc, &rc, GetSysColorBrush(COLOR_HIGHLIGHT)); + DrawText(lplvcd->nmcd.hdc, row->AsciiStr, strlen(row->AsciiStr), &lplvcd->nmcd.rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER); + } else if (location_start >= selection.range[0]) { + // Only the beginning selected + int unselected_count = location_end - selection.range[1]; + int selected_count = 16 - unselected_count; + + // Draw right side (unselected) + RECT right_rc = rc; + right_rc.left += selected_count * CHAR_WIDTH; + DrawText(lplvcd->nmcd.hdc, &row->AsciiStr[selected_count], unselected_count, &right_rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER); + + // Draw left side (selected) + RECT left_rc = rc; + left_rc.right = right_rc.left; + SetTextColor(lplvcd->nmcd.hdc, GetSysColor(COLOR_HIGHLIGHTTEXT)); + FillRect(lplvcd->nmcd.hdc, &left_rc, GetSysColorBrush(COLOR_HIGHLIGHT)); + DrawText(lplvcd->nmcd.hdc, row->AsciiStr, selected_count, &left_rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER); + } else if (location_end <= selection.range[1]) { + // Only the end selected + int selected_count = location_end - selection.range[0] + 1; + int unselected_count = 16 - selected_count; + + // Draw left side (unselected) + RECT left_rc = rc; + left_rc.right = left_rc.left + unselected_count * CHAR_WIDTH; + DrawText(lplvcd->nmcd.hdc, row->AsciiStr, unselected_count, &left_rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER); + + // Draw right side (selected) + RECT right_rc = rc; + right_rc.left = left_rc.right; + SetTextColor(lplvcd->nmcd.hdc, GetSysColor(COLOR_HIGHLIGHTTEXT)); + FillRect(lplvcd->nmcd.hdc, &right_rc, GetSysColorBrush(COLOR_HIGHLIGHT)); + DrawText(lplvcd->nmcd.hdc, &row->AsciiStr[unselected_count], selected_count, &right_rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER); + } else { + // Only the middle selected + int left_count = selection.range[0] - location_start; + int right_count = location_end - selection.range[1]; + int selected_count = 16 - (left_count + right_count); + + // Draw left side (unselected) + RECT left_rc = rc; + left_rc.right = left_rc.left + left_count * CHAR_WIDTH; + DrawText(lplvcd->nmcd.hdc, row->AsciiStr, left_count, &left_rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER); + + // Draw right side (unselected) + RECT right_rc = rc; + right_rc.left += (left_count + selected_count) * CHAR_WIDTH; + DrawText(lplvcd->nmcd.hdc, &row->AsciiStr[left_count + selected_count], right_count, &right_rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER); + + // Draw middle (selected) + RECT mid_rc = rc; + mid_rc.left = left_rc.right; + mid_rc.right = right_rc.left; + SetTextColor(lplvcd->nmcd.hdc, GetSysColor(COLOR_HIGHLIGHTTEXT)); + FillRect(lplvcd->nmcd.hdc, &mid_rc, GetSysColorBrush(COLOR_HIGHLIGHT)); + DrawText(lplvcd->nmcd.hdc, &row->AsciiStr[left_count], selected_count, &mid_rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER); + } + } else { + // Unselected text + DrawText(lplvcd->nmcd.hdc, row->AsciiStr, strlen(row->AsciiStr), &lplvcd->nmcd.rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER); + } SetWindowLong(hDlg, DWL_MSGRESULT, CDRF_SKIPDEFAULT); break; + } default: { // Hex columns int index = lplvcd->iSubItem - 1; DWORD location = row->Location + index; SelectObject(lplvcd->nmcd.hdc, row->Fonts[index]); - if (selection.enabled && (location >= selection.range[0] && location <= selection.range[1])) { + if (selection.enabled && location >= selection.range[0] && location <= selection.range[1]) { SetTextColor(lplvcd->nmcd.hdc, GetSysColor(COLOR_HIGHLIGHTTEXT)); FillRect(lplvcd->nmcd.hdc, &lplvcd->nmcd.rc, GetSysColorBrush(COLOR_HIGHLIGHT)); } else { @@ -488,37 +570,42 @@ LRESULT CALLBACK Memory_ListViewDrag_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, L hit_test.pt = pt; ListView_SubItemHitTest(hWnd, &hit_test); + if (hit_test.iSubItem == 0) { + // Clicking in address column + break; + } + struct MEMORY_VIEW_ROW* row = &MemoryViewRows[hit_test.iItem]; + selection.enabled = TRUE; + selection.dragging = TRUE; + + int index; if (hit_test.iSubItem == 17) { // Clicking in ASCII column - // TODO - return FALSE; - } else if (hit_test.iSubItem > 0) { + selection.column_hex = FALSE; + index = Get_Ascii_Index(MAKEPOINTS(lParam)); + } else { // Clicking in hex columns - int index = hit_test.iSubItem - 1; - DWORD location = row->Location + index; - - selection.enabled = TRUE; - selection.dragging = TRUE; selection.column_hex = TRUE; + index = hit_test.iSubItem - 1; + } - if (wParam & MK_SHIFT) { - // Shift-Click - selection.range[0] = min(location, selection.anchor); - selection.range[1] = max(location, selection.anchor); - memcpy(selection.range_cmp, selection.range, sizeof(selection.range_cmp)); - } else { - // Click without Shift - selection.anchor = location; - selection.range[0] = location; - selection.range[1] = location; - selection.range_cmp[0] = location; - selection.range_cmp[1] = location; - } - return FALSE; + DWORD location = row->Location + index; + if (wParam & MK_SHIFT) { + // Shift-Click + selection.range[0] = min(location, selection.anchor); + selection.range[1] = max(location, selection.anchor); + memcpy(selection.range_cmp, selection.range, sizeof(selection.range_cmp)); } - break; + else { + selection.anchor = location; + selection.range[0] = location; + selection.range[1] = location; + selection.range_cmp[0] = location; + selection.range_cmp[1] = location; + } + return FALSE; } case WM_LBUTTONUP: selection.dragging = FALSE; @@ -531,24 +618,29 @@ LRESULT CALLBACK Memory_ListViewDrag_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, L ListView_SubItemHitTest(hWnd, &hit_test); struct MEMORY_VIEW_ROW* row = &MemoryViewRows[hit_test.iItem]; + int index; if (hit_test.iSubItem == 17 && selection.column_hex == FALSE) { // Dragging in ASCII column - // TODO + index = Get_Ascii_Index(MAKEPOINTS(lParam)); } else if (hit_test.iSubItem > 0 && selection.column_hex == TRUE) { // Dragging in hex columns - int index = hit_test.iSubItem - 1; - DWORD location = row->Location + index; + index = hit_test.iSubItem - 1; + } else { + // Dragging in address column or drag started in a different column + break; + } - selection.range[0] = min(location, selection.anchor); - selection.range[1] = max(location, selection.anchor); + DWORD location = row->Location + index; - if (memcmp(selection.range, selection.range_cmp, sizeof(selection.range)) != 0) { - // TODO: Invalidate only the changed rect - InvalidateRect(hWnd, NULL, FALSE); + selection.range[0] = min(location, selection.anchor); + selection.range[1] = max(location, selection.anchor); - memcpy(selection.range_cmp, selection.range, sizeof(selection.range_cmp)); - return FALSE; - } + if (memcmp(selection.range, selection.range_cmp, sizeof(selection.range)) != 0) { + // TODO: Invalidate only the changed rect + InvalidateRect(hWnd, NULL, FALSE); + + memcpy(selection.range_cmp, selection.range, sizeof(selection.range_cmp)); + return FALSE; } } break; @@ -582,6 +674,18 @@ void Clear_Selection(void) { selection.range_cmp[1] = 0; } +int Get_Ascii_Index(POINTS pt) { + RECT rc; + // The row doesn't matter because we ignore the Y coordinate + ListView_GetSubItemRect(hList, 0, 17, LVIR_BOUNDS, &rc); + + // Compute the character index + int x = pt.x - rc.left; + int index = (x - PADDING) / CHAR_WIDTH; + + return min(max(index, 0), 15); +} + void Scroll_Memory_View(int lines) { char value[20]; From 13c69ee6a2f5d35786861906de4ca996baab2895 Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Wed, 22 Jun 2022 00:50:01 -0700 Subject: [PATCH 4/9] Ctrl+C to copy selected memory --- r4300i Memory.c | 122 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 118 insertions(+), 4 deletions(-) diff --git a/r4300i Memory.c b/r4300i Memory.c index 47850ad..25ea4b1 100644 --- a/r4300i Memory.c +++ b/r4300i Memory.c @@ -48,6 +48,7 @@ void Start_Auto_Refresh_Thread(void); void Scroll_Memory_View(int lines); void Refresh_Memory_With_Diff(BOOL ShowDiff); void Clear_Selection(void); +void Copy_Selection(void); int Get_Ascii_Index(POINTS pt); LRESULT CALLBACK Memory_Window_Proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); @@ -192,8 +193,7 @@ void Insert_MemoryLineDump (unsigned int location, int InsertPos, BOOL ShowDiff) word.UB[2] < ' ' || word.UB[2] > '~' ? '.' : word.UB[2], word.UB[1] < ' ' || word.UB[1] > '~' ? '.' : word.UB[1], word.UB[0] < ' ' || word.UB[0] > '~' ? '.' : word.UB[0]); - } - else { + } else { for (int i = 0; i < 4; i++) { int index = count * 4 + i; @@ -218,8 +218,7 @@ void Insert_MemoryLineDump (unsigned int location, int InsertPos, BOOL ShowDiff) word.UB[2] < ' ' || word.UB[2] > '~' ? '.' : word.UB[2], word.UB[1] < ' ' || word.UB[1] > '~' ? '.' : word.UB[1], word.UB[0] < ' ' || word.UB[0] > '~' ? '.' : word.UB[0]); - } - else { + } else { for (int i = 0; i < 4; i++) { int index = count * 4 + i; @@ -239,6 +238,85 @@ void Insert_MemoryLineDump (unsigned int location, int InsertPos, BOOL ShowDiff) } } +void Write_MemoryLineDump(char *output, unsigned int location) { + MIPS_WORD word; + + sprintf(output, "0x%08X ", location); + output += 12; + + char bytes[49] = { 0 }; + char ascii[17] = { 0 }; + char *b = bytes; + char *a = ascii; + __try { + if (SendMessage(hVAddr, BM_GETSTATE, 0, 0) & BST_CHECKED) { + for (int count = 0; count < 4; count++) { + if (r4300i_LW_VAddr_NonCPU(location, &word.UW)) { + for (int i = 0; i < 4; i++) { + if (selection.enabled && (selection.range[0] > location + i || selection.range[1] < location + i)) { + strcpy(b, " "); + strcpy(a, " "); + } else { + sprintf(b, "%02X ", word.UB[3 - i]); + sprintf(a, "%c", (word.UB[3 - i] < ' ' || word.UB[3 - i] > '~') ? '.' : word.UB[3 - i]); + } + b += 3; + a++; + } + } else { + for (int i = 0; i < 4; i++) { + if (selection.enabled && (selection.range[0] > location + i || selection.range[1] < location + i)) { + strcpy(b, " "); + strcpy(a, " "); + } else { + strcpy(b, "** "); + strcpy(a, "*"); + } + b += 3; + a++; + } + } + location += 4; + } + } else { + for (int count = 0; count < 4; count++) { + if (location < 0x1FFFFFFC) { + r4300i_LW_PAddr(location, &word.UW); + for (int i = 0; i < 4; i++) { + if (selection.enabled && (selection.range[0] > location + i || selection.range[1] < location + i)) { + strcpy(b, " "); + strcpy(a, " "); + } else { + sprintf(b, "%02X ", word.UB[3 - i]); + sprintf(a, "%c", (word.UB[3 - i] < ' ' || word.UB[3 - i] > '~') ? '.' : word.UB[3 - i]); + } + b += 3; + a++; + } + } else { + for (int i = 0; i < 4; i++) { + if (selection.enabled && (selection.range[0] > location + i || selection.range[1] < location + i)) { + strcpy(b, " "); + strcpy(a, " "); + } else { + strcpy(b, "** "); + strcpy(a, "*"); + } + b += 3; + a++; + } + } + location += 4; + } + } + } __except( r4300i_Command_MemoryFilter( GetExceptionCode(), GetExceptionInformation()) ) { + DisplayError(GS(MSG_UNKNOWN_MEM_ACTION)); + PostQuitMessage(0); + } + + sprintf(output, "%s %s\r\n", bytes, ascii); +} + LRESULT CALLBACK Memory_Window_Proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; RECT rcBox; @@ -549,6 +627,11 @@ LRESULT CALLBACK Memory_ListViewKeys_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, L case VK_NEXT: Scroll_Memory_View(16); return FALSE; + case 'C': + if (GetKeyState(VK_CONTROL) & 0x8000) { + Copy_Selection(); + } + return FALSE; default: break; } @@ -674,6 +757,37 @@ void Clear_Selection(void) { selection.range_cmp[1] = 0; } +void Copy_Selection(void) { + if (!selection.enabled || !OpenClipboard(NULL)) { + return; + } + EmptyClipboard(); + + int lines = (selection.range[1] - selection.range[0]) / 16 + 1; + + // Each line is exactly 79 bytes, plus the null terminator. + HGLOBAL hMemory = GlobalAlloc(GMEM_MOVEABLE, lines * 79 + 1); + if (!hMemory) { + CloseClipboard(); + return; + } + + char *memory = GlobalLock(hMemory); + + char *output = memory; + DWORD location = selection.range[0] & ~15; + for (int i = 0; i < lines; i++) { + Write_MemoryLineDump(output, location); + output += 79; + location += 16; + } + + GlobalUnlock(hMemory); + // TODO: Also copy the binary data + SetClipboardData(CF_TEXT, memory); + CloseClipboard(); +} + int Get_Ascii_Index(POINTS pt) { RECT rc; // The row doesn't matter because we ignore the Y coordinate From b23b2e83674ec88f6ba288c8c20d1397dd57a74c Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Wed, 22 Jun 2022 10:07:00 -0700 Subject: [PATCH 5/9] Fix moving the window --- r4300i Memory.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/r4300i Memory.c b/r4300i Memory.c index 25ea4b1..9a5c7a6 100644 --- a/r4300i Memory.c +++ b/r4300i Memory.c @@ -374,7 +374,7 @@ LRESULT CALLBACK Memory_Window_Proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM EndDialog(hDlg, TRUE); return TRUE; } - break; + return FALSE; case WM_NOTIFY: switch (((LPNMHDR)lParam)->code) { case HDN_BEGINTRACK: From 282489c34e0d132785d487eda108947947309fe1 Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Thu, 20 Jul 2023 01:10:11 -0700 Subject: [PATCH 6/9] Fix minor bugs in memory viewer with watchpoints - The colors and underlines were only showing when a watchpoint was disabled. - If the last hex byte in a row was underlined, the entire ASCII section would also be underlined on that row. - Now colors always show when a watchpoint exists. And the byte is underlined only when the watchpoint is enabled. --- r4300i Memory.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/r4300i Memory.c b/r4300i Memory.c index 9a5c7a6..be28d2e 100644 --- a/r4300i Memory.c +++ b/r4300i Memory.c @@ -143,17 +143,21 @@ void Update_Data_Column(struct MEMORY_VIEW_ROW* row, MIPS_WORD word, int index, void Update_Data_Column_With_WatchPoint(struct MEMORY_VIEW_ROW* row, DWORD location, MIPS_WORD word, int index, int i, BOOL ShowDiff) { sprintf(row->HexStr[index], "%02X", word.UB[3 - i]); - switch (HasWatchPoint(location + i)) { - case WP_READ: + int has_watch = (int)HasWatchPoint(location + i); + if (has_watch & WP_ENABLED) { row->Fonts[index] = hWatchFont; + } else { + row->Fonts[index] = GetStockObject(ANSI_FIXED_FONT); + } + + switch (has_watch & ~WP_ENABLED) { + case WP_READ: row->TextColors[index] = TC_READ; break; case WP_WRITE: - row->Fonts[index] = hWatchFont; row->TextColors[index] = TC_WRITE; break; case WP_READ_WRITE: - row->Fonts[index] = hWatchFont; row->TextColors[index] = TC_READ_WRITE; break; default: @@ -427,6 +431,7 @@ LRESULT CALLBACK Memory_Window_Proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM break; case 17: { // ASCII column + SelectObject(lplvcd->nmcd.hdc, GetStockObject(ANSI_FIXED_FONT)); SetTextColor(lplvcd->nmcd.hdc, GetSysColor(COLOR_WINDOWTEXT)); DWORD location_start = row->Location; From ef38508bde12b831fc7c5afcadee7652ec8ab0ab Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Thu, 20 Jul 2023 12:05:40 -0700 Subject: [PATCH 7/9] Fix copying the last line of memory with some selection sizes - This bug prevented the last line of the memory view from being copied to the clipboard when the start of the selection was at a higher column number than the end of the selection. - E.g. when address range 0x8000000c-0x80000023 was selected, it would only copy 0x8000000c-0x8000001f, truncating the last line and missing four selected bytes. --- r4300i Memory.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/r4300i Memory.c b/r4300i Memory.c index be28d2e..dc9be93 100644 --- a/r4300i Memory.c +++ b/r4300i Memory.c @@ -768,7 +768,8 @@ void Copy_Selection(void) { } EmptyClipboard(); - int lines = (selection.range[1] - selection.range[0]) / 16 + 1; + DWORD location = selection.range[0] & ~15; + int lines = (selection.range[1] - location) / 16 + 1; // Each line is exactly 79 bytes, plus the null terminator. HGLOBAL hMemory = GlobalAlloc(GMEM_MOVEABLE, lines * 79 + 1); @@ -780,7 +781,6 @@ void Copy_Selection(void) { char *memory = GlobalLock(hMemory); char *output = memory; - DWORD location = selection.range[0] & ~15; for (int i = 0; i < lines; i++) { Write_MemoryLineDump(output, location); output += 79; From 978108f5c41586797780fa607ffc7a5b6cc1a08f Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Mon, 24 Jul 2023 22:58:22 -0700 Subject: [PATCH 8/9] Add memory editor bookmarks --- PJ64.rc | 161 ++++++++---- Project64.vcxproj | 7 +- Project64.vcxproj.filters | 9 + SessionMemBookmarks.h | 40 +++ Sessions.cpp | 205 +++++++++++++++ Sessions.h | 28 ++ Settings Common Defines.h | 1 - cbor-lite/COPYRIGHT.md | 52 ++++ cbor-lite/codec-fp.h | 145 +++++++++++ cbor-lite/codec.h | 498 +++++++++++++++++++++++++++++++++++ r4300i Memory.c | 527 ++++++++++++++++++++++++++++++++++---- resource.h | 9 +- 12 files changed, 1574 insertions(+), 108 deletions(-) create mode 100644 SessionMemBookmarks.h create mode 100644 Sessions.cpp create mode 100644 Sessions.h create mode 100644 cbor-lite/COPYRIGHT.md create mode 100644 cbor-lite/codec-fp.h create mode 100644 cbor-lite/codec.h diff --git a/PJ64.rc b/PJ64.rc index dafdc1d..a80359d 100644 --- a/PJ64.rc +++ b/PJ64.rc @@ -36,6 +36,59 @@ IDB_BITMAP3 BITMAP "PJ64_3.bmp" IDR_ABOUT_TXT TEXT "About.txt" + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_MEM_BOOKMARK_EDIT DIALOGEX 0, 0, 239, 105 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Edit Bookmark" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "OK",IDOK,127,84,50,14 + PUSHBUTTON "Cancel",IDCANCEL,182,84,50,14 + EDITTEXT IDC_NAME,61,7,171,12,ES_MULTILINE | ES_AUTOHSCROLL + EDITTEXT IDC_START,62,23,170,12,ES_AUTOHSCROLL + EDITTEXT IDC_END,62,39,170,12,ES_AUTOHSCROLL + CONTROL "Virtual Address",IDC_BOOKMARK_VADDR,"Button",BS_AUTORADIOBUTTON,62,54,170,10 + CONTROL "Physical Address",IDC_BOOKMARK_PADDR,"Button",BS_AUTORADIOBUTTON,62,66,170,10 + LTEXT "Name:",IDC_STATIC,7,9,49,11,0,WS_EX_RIGHT + LTEXT "Start Address:",IDC_STATIC,7,24,49,11,0,WS_EX_RIGHT + LTEXT "End Address:",IDC_STATIC,7,40,49,11,0,WS_EX_RIGHT +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_MEM_BOOKMARK_EDIT, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 232 + TOPMARGIN, 7 + BOTTOMMARGIN, 98 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_MEM_BOOKMARK_EDIT AFX_DIALOG_LAYOUT +BEGIN + 0 +END + #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// @@ -744,6 +797,57 @@ END #endif // APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_Settings_Rom AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_Settings_Options AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_Cheats_CodeEx AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_About AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +MEMORY AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +ROM_INFO_DIALOG AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_Debugger_MemDump AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +BLANK AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_TLB AFX_DIALOG_LAYOUT +BEGIN + 0 +END + + ///////////////////////////////////////////////////////////////////////////// // // Icon @@ -798,7 +902,7 @@ BEGIN MENUITEM "&Save\tF5", ID_CPU_SAVE, GRAYED MENUITEM "Save As...\tCtrl+S", ID_CPU_SAVEAS, GRAYED MENUITEM "&Restore\tF7", ID_CPU_RESTORE, GRAYED - MENUITEM "Restore From\tCtrl+L", ID_CPU_LOAD, GRAYED + MENUITEM "Restore From\tCtrl+L", ID_CPU_LOAD, GRAYED MENUITEM SEPARATOR POPUP "Current Save S&tate", GRAYED BEGIN @@ -813,8 +917,6 @@ BEGIN MENUITEM "Slot 7\t7", ID_CURRENTSAVE_7 MENUITEM "Slot 8\t8", ID_CURRENTSAVE_8 MENUITEM "Slot 9\t9", ID_CURRENTSAVE_9 - // Removed this as we now have 0 to 9 - Gent - // MENUITEM "Slot 10\t0", ID_CURRENTSAVE_0 END MENUITEM SEPARATOR MENUITEM "Cheats...\tCtrl+C", ID_OPTIONS_CHEATS, GRAYED @@ -981,8 +1083,6 @@ BEGIN VK_F1, ID_CPU_RESET, VIRTKEY, NOINVERT VK_F7, ID_CPU_RESTORE, VIRTKEY, NOINVERT VK_F5, ID_CPU_SAVE, VIRTKEY, NOINVERT - // Removed this as we now have 0 to 9 - Gent - // "0", ID_CURRENTSAVE_0, VIRTKEY, NOINVERT "1", ID_CURRENTSAVE_1, VIRTKEY, NOINVERT "2", ID_CURRENTSAVE_2, VIRTKEY, NOINVERT "3", ID_CURRENTSAVE_3, VIRTKEY, NOINVERT @@ -1002,57 +1102,6 @@ BEGIN VK_F4, ID_SYSTEM_LIMITFPS, VIRTKEY, NOINVERT END - -///////////////////////////////////////////////////////////////////////////// -// -// AFX_DIALOG_LAYOUT -// - -IDD_Settings_Rom AFX_DIALOG_LAYOUT -BEGIN - 0 -END - -IDD_Settings_Options AFX_DIALOG_LAYOUT -BEGIN - 0 -END - -IDD_Cheats_CodeEx AFX_DIALOG_LAYOUT -BEGIN - 0 -END - -IDD_About AFX_DIALOG_LAYOUT -BEGIN - 0 -END - -MEMORY AFX_DIALOG_LAYOUT -BEGIN - 0 -END - -ROM_INFO_DIALOG AFX_DIALOG_LAYOUT -BEGIN - 0 -END - -IDD_Debugger_MemDump AFX_DIALOG_LAYOUT -BEGIN - 0 -END - -BLANK AFX_DIALOG_LAYOUT -BEGIN - 0 -END - -IDD_TLB AFX_DIALOG_LAYOUT -BEGIN - 0 -END - #endif // English (Australia) resources ///////////////////////////////////////////////////////////////////////////// diff --git a/Project64.vcxproj b/Project64.vcxproj index 0797062..cb845b6 100644 --- a/Project64.vcxproj +++ b/Project64.vcxproj @@ -102,7 +102,7 @@ .\Debug\ .\Debug\ EnableFastChecks - false + Sync Default false true @@ -285,6 +285,7 @@ + @@ -321,6 +322,8 @@ + + @@ -354,6 +357,8 @@ + + diff --git a/Project64.vcxproj.filters b/Project64.vcxproj.filters index e37b26c..d1f7c70 100644 --- a/Project64.vcxproj.filters +++ b/Project64.vcxproj.filters @@ -70,6 +70,9 @@ {e8877f6e-d284-4c15-84e8-18cf809faa07} + + {c59b946f-43e7-4676-8b2b-840d70c41937} + @@ -491,6 +494,12 @@ Header Files\Debugger Headers + + Header Files\cbor-lite Headers + + + Header Files\cbor-lite Headers + diff --git a/SessionMemBookmarks.h b/SessionMemBookmarks.h new file mode 100644 index 0000000..67fe8fc --- /dev/null +++ b/SessionMemBookmarks.h @@ -0,0 +1,40 @@ +/* + * Project 64 - A Nintendo 64 emulator. + * + * (c) Copyright 2023 parasyte (jay@kodewerx.org) + * + * pj64 homepage: www.pj64.net + * + * Permission to use, copy, modify and distribute Project64 in both binary and + * source form, for non-commercial purposes, is hereby granted without fee, + * providing that this license information and copyright notice appear with + * all copies and any derived work. + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event shall the authors be held liable for any damages + * arising from the use of this software. + * + * Project64 is freeware for PERSONAL USE only. Commercial users should + * seek permission of the copyright holders first. Commercial use includes + * charging money for Project64 or software derived from Project64. + * + * The copyright holders request that bug fixes and improvements to the code + * should be forwarded to them so if they want them. + * + */ + +#include + +#pragma once + +#define MAX_MEM_BOOKMARKS 1000 + +struct MEM_BOOKMARK { + char name[100]; + unsigned int width_required; + DWORD selection_range[2]; + BOOL is_virtual; +}; + +BOOL Session_Load_MemBookmarks(unsigned int *num_bookmarks, struct MEM_BOOKMARK *bookmarks, unsigned int max_bookmarks, char *filename); +BOOL Session_Save_MemBookmarks(char *filename, struct MEM_BOOKMARK *bookmarks, unsigned int num_bookmarks); \ No newline at end of file diff --git a/Sessions.cpp b/Sessions.cpp new file mode 100644 index 0000000..52ce954 --- /dev/null +++ b/Sessions.cpp @@ -0,0 +1,205 @@ +/* + * Project 64 - A Nintendo 64 emulator. + * + * (c) Copyright 2023 parasyte (jay@kodewerx.org) + * + * pj64 homepage: www.pj64.net + * + * Permission to use, copy, modify and distribute Project64 in both binary and + * source form, for non-commercial purposes, is hereby granted without fee, + * providing that this license information and copyright notice appear with + * all copies and any derived work. + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event shall the authors be held liable for any damages + * arising from the use of this software. + * + * Project64 is freeware for PERSONAL USE only. Commercial users should + * seek permission of the copyright holders first. Commercial use includes + * charging money for Project64 or software derived from Project64. + * + * The copyright holders request that bug fixes and improvements to the code + * should be forwarded to them so if they want them. + * + */ + +#include +#include +#include +#include +#include "cbor-lite/codec.h" + +using namespace CborLite; + +extern "C" { + #include "Sessions.h" +} + +// Constants + +constexpr uint64_t SESSION_MEM_BOOKMARKS_VERSION = 1; + +// Utility functions + +std::string as_path(char *filename) { + auto filepath = std::string(filename); + + // Support long file paths. + // SEE: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#parameters + if (filepath.substr(0, 4) != std::string(R"(\\?\)")) { + filepath.insert(0, R"(\\?\)"); + } + + return filepath; +} + +bool read_cbor(std::vector &buffer, char *filename) { + auto filepath = as_path(filename); + + HANDLE file = CreateFile(filepath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (file == INVALID_HANDLE_VALUE) { + return false; + } + + DWORD len = GetFileSize(file, NULL); + DWORD bytes_read = 0; + + buffer.clear(); + buffer.resize(len); + BOOL read_success = (ReadFile(file, buffer.data(), len, &bytes_read, NULL) == TRUE); + + CloseHandle(file); + + return read_success && bytes_read == len; +} + +bool write_cbor(char *filename, std::vector &buffer) { + auto filepath = as_path(filename); + + HANDLE file = CreateFile(filepath.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (file == INVALID_HANDLE_VALUE) { + return false; + } + + DWORD bytes_written; + BOOL write_success = (WriteFile(file, buffer.data(), buffer.size(), &bytes_written, NULL) == TRUE); + + CloseHandle(file); + + return write_success && bytes_written == buffer.size(); +} + +// C++ implementations + +template +size_t decode_mem_bookmark(InputIterator &pos, InputIterator &end, struct MEM_BOOKMARK &bookmark, Flags flags = Flag::none) { + uint64_t num_items = 0; + auto len = decodeArraySize(pos, end, num_items, flags); + if (num_items != 5) { + throw Exception("expected 4 items"); + } + + auto name = std::string(); + len += decodeText(pos, end, name, flags); + // Truncate name to fit in the buffer. + strncpy(bookmark.name, name.c_str(), min(name.size(), sizeof(bookmark.name) - 1)); + bookmark.name[sizeof(bookmark.name) - 1] = 0; + + uint64_t width_required = 0; + len += decodeUnsigned(pos, end, width_required, flags); + // Truncate 64-bit to 32-bit. + bookmark.width_required = (unsigned int)width_required; + + uint64_t selection_range = 0; + len += decodeUnsigned(pos, end, selection_range, flags); + // Truncate 64-bit to 32-bit. + bookmark.selection_range[0] = (DWORD)selection_range; + + len += decodeUnsigned(pos, end, selection_range, flags); + // Truncate 64-bit to 32-bit. + bookmark.selection_range[1] = (DWORD)selection_range; + + bool is_virtual = true; + len += decodeBool(pos, end, is_virtual, flags); + // Convert bool to int. + bookmark.is_virtual = is_virtual; + + return len; +} + +template +size_t encode_mem_bookmark(Buffer &buffer, const struct MEM_BOOKMARK &bookmark) { + auto len = encodeArraySize(buffer, 5u); + + auto name = std::string(bookmark.name); + len += encodeText(buffer, name); + len += encodeUnsigned(buffer, bookmark.width_required); + len += encodeUnsigned(buffer, bookmark.selection_range[0]); + len += encodeUnsigned(buffer, bookmark.selection_range[1]); + + bool is_virtual = bookmark.is_virtual; + len += encodeBool(buffer, is_virtual); + + return len; +} + +// C interfaces + +BOOL Session_Load_MemBookmarks(unsigned int *num_bookmarks, struct MEM_BOOKMARK *bookmarks, unsigned int max_bookmarks, char *filename) { + try { + auto buffer = std::vector(); + if (!read_cbor(buffer, filename)) { + return FALSE; + } + + auto pos = buffer.begin(); + auto end = buffer.end(); + + // The version 1 schema looks like this: + // [1, ["name", width_required, selection_range_start, selection_range_end, is_virtual], ...] + // + // Example: + // [1, + // ["Off : Debug [0x800015C0]", 138, 2147489216, 2147489226, true], + // ["Play Track Options [0x80001584]", 173, 2147489156, 2147489173, true] + // ] + uint64_t num_items = 0; + decodeArraySize(pos, end, num_items); + if (num_items < 1 || num_items - 1 > max_bookmarks) { + return FALSE; + } + *num_bookmarks = (unsigned int)(num_items - 1); + + uint64_t version = 0; + decodeUnsigned(pos, end, version); + if (version != SESSION_MEM_BOOKMARKS_VERSION) { + // NOTICE: Only version 1 is supported today. + return FALSE; + } + + for (unsigned int i = 0; i < *num_bookmarks; i++) { + decode_mem_bookmark(pos, end, bookmarks[i]); + } + + return TRUE; + } catch (Exception) { + return FALSE; + } +} + +BOOL Session_Save_MemBookmarks(char *filename, struct MEM_BOOKMARK *bookmarks, unsigned int num_bookmarks) { + try { + auto buffer = std::vector(); + + encodeArraySize(buffer, num_bookmarks + 1); + encodeUnsigned(buffer, SESSION_MEM_BOOKMARKS_VERSION); + + for (unsigned int i = 0; i < num_bookmarks; i++) { + encode_mem_bookmark(buffer, bookmarks[i]); + } + + return write_cbor(filename, buffer); + } catch (Exception) { + return FALSE; + } +} \ No newline at end of file diff --git a/Sessions.h b/Sessions.h new file mode 100644 index 0000000..cc96800 --- /dev/null +++ b/Sessions.h @@ -0,0 +1,28 @@ +/* + * Project 64 - A Nintendo 64 emulator. + * + * (c) Copyright 2023 parasyte (jay@kodewerx.org) + * + * pj64 homepage: www.pj64.net + * + * Permission to use, copy, modify and distribute Project64 in both binary and + * source form, for non-commercial purposes, is hereby granted without fee, + * providing that this license information and copyright notice appear with + * all copies and any derived work. + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event shall the authors be held liable for any damages + * arising from the use of this software. + * + * Project64 is freeware for PERSONAL USE only. Commercial users should + * seek permission of the copyright holders first. Commercial use includes + * charging money for Project64 or software derived from Project64. + * + * The copyright holders request that bug fixes and improvements to the code + * should be forwarded to them so if they want them. + * + */ + +#pragma once + +#include "SessionMemBookmarks.h" \ No newline at end of file diff --git a/Settings Common Defines.h b/Settings Common Defines.h index fe2853e..5ce6410 100644 --- a/Settings Common Defines.h +++ b/Settings Common Defines.h @@ -5,7 +5,6 @@ #define RDN_NAME "Project64.rdn" #define RDI_NAME "Project64.rdi" #define CDB_NAME "Project64.cdb" -#define CHTDEV_NAME "Project64.chtdev" #define LNG_NAME "Project64.lng" #define ROC_NAME "Project64.roc" #define APPS_NAME "Project64.apps" diff --git a/cbor-lite/COPYRIGHT.md b/cbor-lite/COPYRIGHT.md new file mode 100644 index 0000000..0236663 --- /dev/null +++ b/cbor-lite/COPYRIGHT.md @@ -0,0 +1,52 @@ +Copyright +========= + +This work is copyright [Isode Limited](https://isode.com) and made available as follows: + +``` +Copyright 2018-2019 Isode Limited. +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +Contributors +------------ + +None (yet). See [Contributing Guidelines](./CONTRIBUTING.md). + +Acknowledgements +---------------- + +This work is an implementation of the [Concise Binary Object Representation](https://cbor.io) (CBOR) as specified in [RFC 7049](https://tools.ietf.org/html/rfc7049), authored by C. Bormann and P. Hoffman and is a product of the [IETF](https://ietf.org). This work may quote portions of this technical specification. It also incorporates test cases published in this technical specification. RFC 7049 is published with the following notice: + +``` +Copyright (c) 2013 IETF Trust and the persons identified as the +document authors. All rights reserved. + +This document is subject to BCP 78 and the IETF Trust's Legal +Provisions Relating to IETF Documents +(http://trustee.ietf.org/license-info) in effect on the date of +publication of this document. Please review these documents +carefully, as they describe your rights and restrictions with respect +to this document. Code Components extracted from this document must +include Simplified BSD License text as described in Section 4.e of +the Trust Legal Provisions and are provided without warranty as +described in the Simplified BSD License. +``` diff --git a/cbor-lite/codec-fp.h b/cbor-lite/codec-fp.h new file mode 100644 index 0000000..bc6494b --- /dev/null +++ b/cbor-lite/codec-fp.h @@ -0,0 +1,145 @@ +#pragma once +// This file is part of CBOR-lite which is copyright Isode Limited +// and others and released under a MIT license. For details, see the +// COPYRIGHT.md file in the top-level folder of the CBOR-lite software +// distribution. + +// This copy of CBOR-lite has been modified for 32-bit MSVC for Project 64 Legacy. + +#include "codec.h" + +#ifndef __BYTE_ORDER__ +#ifdef _MSC_VER +// PJ64: Byte order hack for MSVC +#define __ORDER_LITTLE_ENDIAN__ 0 +#define __ORDER_BIG_ENDIAN__ 1 +#define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__ +#else +#error __BYTE_ORDER__ not defined +#endif +#elif (__BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__) && (__BYTE_ORDER__ != __ORDER_BIG_ENDIAN__) +#error __BYTE_ORDER__ neither __ORDER_BIG_ENDIAN__ nor __ORDER_LITTLE_ENDIAN__ +#endif + +namespace CborLite { + +template +typename std::enable_if::value && std::is_floating_point::value, std::size_t>::type encodeSingleFloat( + Buffer& buffer, const Type& t) { + static_assert(sizeof(float) == 4, "sizeof(float) expected to be 4"); + auto len = encodeTagAndAdditional(buffer, Major::floatingPoint, Minor::singleFloat); + const char* p; + float ft; + if (sizeof(t) == sizeof(ft)) { + p = reinterpret_cast(&t); + } else { + ft = static_cast(t); + p = reinterpret_cast(&ft); + } +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + for (auto i = 0u; i < sizeof(ft); ++i) { + buffer.push_back(p[i]); + } +#else + for (auto i = 1u; i <= sizeof(ft); ++i) { + buffer.push_back(p[sizeof(ft) - i]); + } +#endif + return len + sizeof(ft); +} + +template +typename std::enable_if::value && std::is_floating_point::value, std::size_t>::type encodeDoubleFloat( + Buffer& buffer, const Type& t) { + static_assert(sizeof(double) == 8, "sizeof(double) expected to be 8"); + auto len = encodeTagAndAdditional(buffer, Major::floatingPoint, Minor::doubleFloat); + const char* p; + double ft; + if (sizeof(t) == sizeof(ft)) { + p = reinterpret_cast(&t); + } else { + ft = t; + p = reinterpret_cast(&ft); + } +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + for (auto i = 0u; i < sizeof(ft); ++i) { + buffer.push_back(p[i]); + } +#else + for (auto i = 1u; i <= sizeof(ft); ++i) { + buffer.push_back(p[sizeof(ft) - i]); + } +#endif + return len + sizeof(ft); +} + +template +typename std::enable_if::value && std::is_floating_point::value && !std::is_const::value, + std::size_t>::type +decodeSingleFloat(InputIterator& pos, InputIterator end, Type& t, Flags flags = Flag::none) { + static_assert(sizeof(float) == 4, "sizeof(float) expected to be 4"); + auto tag = undefined; + auto value = undefined; + auto len = decodeTagAndAdditional(pos, end, tag, value, flags); + if (tag != Major::floatingPoint) throw Exception("not floating-point"); + if (value != Minor::singleFloat) throw Exception("not single-precision floating-point"); + if (std::distance(pos, end) < static_cast(sizeof(float))) throw Exception("not enough input"); + + char* p; + float ft; + if (sizeof(t) == sizeof(ft)) { + p = reinterpret_cast(&t); + } else { + ft = static_cast(t); + p = reinterpret_cast(&ft); + } + +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + for (auto i = 0u; i < sizeof(ft); ++i) { + p[i] = *(pos++); + } +#else + for (auto i = 1u; i <= sizeof(ft); ++i) { + p[sizeof(ft) - i] = *(pos++); + } +#endif + if (sizeof(t) != sizeof(ft)) t = ft; + return len + sizeof(ft); +} + +template +typename std::enable_if::value && std::is_floating_point::value && !std::is_const::value, + std::size_t>::type +decodeDoubleFloat(InputIterator& pos, InputIterator end, Type& t, Flags flags = Flag::none) { + static_assert(sizeof(double) == 8, "sizeof(double) expected to be 8"); + auto tag = undefined; + auto value = undefined; + auto len = decodeTagAndAdditional(pos, end, tag, value, flags); + if (tag != Major::floatingPoint) throw Exception("not floating-point"); + if (value != Minor::doubleFloat) throw Exception("not double-precision floating-point"); + if (std::distance(pos, end) < static_cast(sizeof(double))) throw Exception("not enough input"); + + char* p; + double ft; + if (sizeof(t) == sizeof(ft)) { + p = reinterpret_cast(&t); + } else { + ft = t; + p = reinterpret_cast(&ft); + } + +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + for (auto i = 0u; i < sizeof(ft); ++i) { + p[i] = *(pos++); + } +#else + for (auto i = 1u; i <= sizeof(ft); ++i) { + p[sizeof(ft) - i] = *(pos++); + } +#endif + + if (sizeof(t) != sizeof(ft)) t = ft; + return len + sizeof(ft); +} + +} // namespace CborLite diff --git a/cbor-lite/codec.h b/cbor-lite/codec.h new file mode 100644 index 0000000..79780fc --- /dev/null +++ b/cbor-lite/codec.h @@ -0,0 +1,498 @@ +#pragma once +// This file is part of CBOR-lite which is copyright Isode Limited +// and others and released under a MIT license. For details, see the +// COPYRIGHT.md file in the top-level folder of the CBOR-lite software +// distribution. + +#include +#include +#include +#include + +namespace CborLite { + + class Exception : public std::exception { + public: + Exception() noexcept { + } + virtual ~Exception() noexcept = default; + + Exception(const char *d) noexcept { + what_ += std::string(": ") + d; + } + + Exception(const std::string &d) noexcept { + what_ += ": " + d; + } + + Exception(const Exception &e) noexcept : what_(e.what_) { + } + + Exception(Exception &&e) noexcept : what_(std::move(e.what_)) { + // Note that e.what_ is not re-initialized to "CBOR Exception" as + // the moved-from object is not expected to ever be reused. + } + + Exception &operator=(const Exception &) = delete; + Exception &operator=(Exception &&) = delete; + + virtual const char *what() const noexcept { + return what_.c_str(); + } + + private: + std::string what_ = "CBOR Exception"; + }; + + using Tag = std::uint_fast64_t; + + namespace Major { + constexpr Tag unsignedInteger = 0u; + constexpr Tag negativeInteger = 1u << 5; + constexpr Tag byteString = 2u << 5; + constexpr Tag textString = 3u << 5; + constexpr Tag array = 4u << 5; + constexpr Tag map = 5u << 5; + constexpr Tag semantic = 6u << 5; + constexpr Tag floatingPoint = 7u << 5; + constexpr Tag simple = 7u << 5; + constexpr Tag mask = 0xe0u; + } // namespace Major + + namespace Minor { + constexpr Tag length1 = 24u; + constexpr Tag length2 = 25u; + constexpr Tag length4 = 26u; + constexpr Tag length8 = 27u; + + constexpr Tag False = 20u; + constexpr Tag True = 21u; + constexpr Tag null = 22u; + constexpr Tag undefined = 23u; + constexpr Tag halfFloat = 25u; // not implemented + constexpr Tag singleFloat = 26u; + constexpr Tag doubleFloat = 27u; + + constexpr Tag dataTime = 0u; + constexpr Tag epochDataTime = 1u; + constexpr Tag positiveBignum = 2u; + constexpr Tag negativeBignum = 3u; + constexpr Tag decimalFraction = 4u; + constexpr Tag bigfloat = 5u; + constexpr Tag convertBase64Url = 21u; + constexpr Tag convertBase64 = 22u; + constexpr Tag convertBase16 = 23u; + constexpr Tag cborEncodedData = 24u; + constexpr Tag uri = 32u; + constexpr Tag base64Url = 33u; + constexpr Tag base64 = 34u; + constexpr Tag regex = 35u; + constexpr Tag mimeMessage = 36u; + constexpr Tag selfDescribeCbor = 55799u; + + constexpr Tag mask = 0x1fu; + } // namespace Minor + + constexpr Tag undefined = Major::semantic + Minor::undefined; + + using Flags = unsigned; + namespace Flag { + constexpr Flags none = 0; + constexpr Flags requireMinimalEncoding = 1 << 0; + } // namespace Flag + + template + typename std::enable_if::value, std::size_t>::type length(Type val) { + if (val < 24) return 0; + for (std::size_t i = 1; i <= ((sizeof val) >> 1); i <<= 1) { + if (!(val >> (i << 3))) return i; + } + return sizeof val; + } + + template + typename std::enable_if::value, std::size_t>::type encodeTagAndAdditional( + Buffer &buffer, Tag tag, Tag additional) { + buffer.push_back(static_cast(tag + additional)); + return 1; + } + + template + typename std::enable_if::value, std::size_t>::type decodeTagAndAdditional( + InputIterator &pos, InputIterator end, Tag &tag, Tag &additional, Flags = Flag::none) { + if (pos == end) throw Exception("not enough input"); + auto octet = *(pos++); + tag = octet & Major::mask; + additional = octet & Minor::mask; + return 1; + } + + // PJ64: Added type specialization for 64-bit values + template + typename std::enable_if::value && std::is_unsigned::value && sizeof(Type) == 8, std::size_t>::type encodeTagAndValue( + Buffer &buffer, Tag tag, const Type t) { + auto len = length(t); + buffer.reserve(buffer.size() + len + 1); + + switch (len) { + case 8: + encodeTagAndAdditional(buffer, tag, Minor::length8); + break; + case 4: + encodeTagAndAdditional(buffer, tag, Minor::length4); + break; + case 2: + encodeTagAndAdditional(buffer, tag, Minor::length2); + break; + case 1: + encodeTagAndAdditional(buffer, tag, Minor::length1); + break; + case 0: + return encodeTagAndAdditional(buffer, tag, t); + default: + throw Exception("too long"); + } + + switch (len) { + case 8: + buffer.push_back((t >> 56) & 0xffU); + buffer.push_back((t >> 48) & 0xffU); + buffer.push_back((t >> 40) & 0xffU); + buffer.push_back((t >> 32) & 0xffU); + case 4: + buffer.push_back((t >> 24) & 0xffU); + buffer.push_back((t >> 16) & 0xffU); + case 2: + buffer.push_back((t >> 8) & 0xffU); + case 1: + buffer.push_back(t & 0xffU); + } + + return 1 + len; + } + + // PJ64: Added type specialization for 32-bit values + template + typename std::enable_if::value && std::is_unsigned::value && sizeof(Type) == 4, std::size_t>::type encodeTagAndValue( + Buffer &buffer, Tag tag, const Type t) { + auto len = length(t); + buffer.reserve(buffer.size() + len + 1); + + switch (len) { + case 4: + encodeTagAndAdditional(buffer, tag, Minor::length4); + break; + case 2: + encodeTagAndAdditional(buffer, tag, Minor::length2); + break; + case 1: + encodeTagAndAdditional(buffer, tag, Minor::length1); + break; + case 0: + return encodeTagAndAdditional(buffer, tag, t); + default: + throw Exception("too long"); + } + + switch (len) { + case 4: + buffer.push_back((t >> 24) & 0xffU); + buffer.push_back((t >> 16) & 0xffU); + case 2: + buffer.push_back((t >> 8) & 0xffU); + case 1: + buffer.push_back(t & 0xffU); + } + + return 1 + len; + } + + // PJ64: Added type specialization for 64-bit values + template + typename std::enable_if::value && std::is_unsigned::value && sizeof(Type) == 8, std::size_t>::type decodeTagAndValue( + InputIterator &pos, InputIterator end, Tag &tag, Type &t, Flags flags = Flag::none) { + if (pos == end) throw Exception("not enough input"); + auto additional = Minor::undefined; + auto len = decodeTagAndAdditional(pos, end, tag, additional, flags); + if (additional < Minor::length1) { + t = additional; + return len; + } + t = 0u; + switch (additional) { + case Minor::length8: + if (std::distance(pos, end) < 8) throw Exception("not enough input"); + t |= static_cast(reinterpret_cast(*(pos++))) << 56; + t |= static_cast(reinterpret_cast(*(pos++))) << 48; + t |= static_cast(reinterpret_cast(*(pos++))) << 40; + t |= static_cast(reinterpret_cast(*(pos++))) << 32; + len += 4; + if ((flags & Flag::requireMinimalEncoding) && !t) throw Exception("encoding not minimal"); + case Minor::length4: + if (std::distance(pos, end) < 4) throw Exception("not enough input"); + t |= static_cast(reinterpret_cast(*(pos++))) << 24; + t |= static_cast(reinterpret_cast(*(pos++))) << 16; + len += 2; + if ((flags & Flag::requireMinimalEncoding) && !t) throw Exception("encoding not minimal"); + case Minor::length2: + if (std::distance(pos, end) < 2) throw Exception("not enough input"); + t |= static_cast(reinterpret_cast(*(pos++))) << 8; + len++; + if ((flags & Flag::requireMinimalEncoding) && !t) throw Exception("encoding not minimal"); + case Minor::length1: + if (std::distance(pos, end) < 1) throw Exception("not enough input"); + t |= static_cast(reinterpret_cast(*(pos++))); + len++; + if ((flags & Flag::requireMinimalEncoding) && t < 24) throw Exception("encoding not minimal"); + return len; + } + throw Exception("bad additional value"); + } + + // PJ64: Added type specialization for 32-bit values + template + typename std::enable_if::value && std::is_unsigned::value && sizeof(Type) == 4, std::size_t>::type decodeTagAndValue( + InputIterator &pos, InputIterator end, Tag &tag, Type &t, Flags flags = Flag::none) { + if (pos == end) throw Exception("not enough input"); + auto additional = Minor::undefined; + auto len = decodeTagAndAdditional(pos, end, tag, additional, flags); + if (additional < Minor::length1) { + t = additional; + return len; + } + t = 0u; + switch (additional) { + case Minor::length4: + if (std::distance(pos, end) < 4) throw Exception("not enough input"); + t |= static_cast(reinterpret_cast(*(pos++))) << 24; + t |= static_cast(reinterpret_cast(*(pos++))) << 16; + len += 2; + if ((flags & Flag::requireMinimalEncoding) && !t) throw Exception("encoding not minimal"); + case Minor::length2: + if (std::distance(pos, end) < 2) throw Exception("not enough input"); + t |= static_cast(reinterpret_cast(*(pos++))) << 8; + len++; + if ((flags & Flag::requireMinimalEncoding) && !t) throw Exception("encoding not minimal"); + case Minor::length1: + if (std::distance(pos, end) < 1) throw Exception("not enough input"); + t |= static_cast(reinterpret_cast(*(pos++))); + len++; + if ((flags & Flag::requireMinimalEncoding) && t < 24) throw Exception("encoding not minimal"); + return len; + } + throw Exception("bad additional value"); + } + + template + typename std::enable_if::value, std::size_t>::type encodeUnsigned(Buffer &buffer, const Type &t) { + return encodeTagAndValue(buffer, Major::unsignedInteger, t); + } + + template + typename std::enable_if::value &&std::is_unsigned::value && !std::is_const::value, + std::size_t>::type + decodeUnsigned(InputIterator &pos, InputIterator end, Type &t, Flags flags = Flag::none) { + auto tag = undefined; + auto len = decodeTagAndValue(pos, end, tag, t, flags); + if (tag != Major::unsignedInteger) throw Exception("not Unsigned"); + return len; + } + + template + typename std::enable_if::value, std::size_t>::type encodeNegative(Buffer &buffer, const Type &t) { + return encodeTagAndValue(buffer, Major::negativeInteger, t); + } + + template + typename std::enable_if::value &&std::is_unsigned::value && !std::is_const::value, + std::size_t>::type + decodeNegative(InputIterator &pos, InputIterator end, Type &t, Flags flags = Flag::none) { + auto tag = undefined; + auto len = decodeTagAndValue(pos, end, tag, t, flags); + if (tag != Major::negativeInteger) throw Exception("not Unsigned"); + return len; + } + + template + typename std::enable_if::value, std::size_t>::type encodeInteger(Buffer &buffer, const Type &t) { + if (t >= 0) { + unsigned long long val = t; + return encodeUnsigned(buffer, val); + } else { + unsigned long long val = -t - 1; + return encodeNegative(buffer, val); + } + } + + template + typename std::enable_if::value &&std::is_signed::value && !std::is_const::value, + std::size_t>::type + decodeInteger(InputIterator &pos, InputIterator end, Type &t, Flags flags = Flag::none) { + auto tag = undefined; + unsigned long long val; + auto len = decodeTagAndValue(pos, end, tag, val, flags); + switch (tag) { + case Major::unsignedInteger: + t = val; + break; + case Major::negativeInteger: + t = -1 - static_cast(val); + break; + default: + throw Exception("not integer"); + } + return len; + } + + template + typename std::enable_if::value &&std::is_same::value, std::size_t>::type encodeBool( + Buffer &buffer, const Type &t) { + return encodeTagAndAdditional(buffer, Major::simple, t ? Minor::True : Minor::False); + } + + template + typename std::enable_if::value &&std::is_same::value && !std::is_const::value, + std::size_t>::type + decodeBool(InputIterator &pos, InputIterator end, Type &t, Flags flags = Flag::none) { + auto tag = undefined; + auto value = undefined; + auto len = decodeTagAndValue(pos, end, tag, value, flags); + if (tag == Major::simple) { + if (value == Minor::True) { + t = true; + return len; + } else if (value == Minor::False) { + t = false; + return len; + } + throw Exception("not Boolean"); + } + throw Exception("not Simple"); + } + + template + typename std::enable_if::value, std::size_t>::type encodeBytes(Buffer &buffer, const Type &t) { + auto len = encodeTagAndValue(buffer, Major::byteString, t.size()); + buffer.insert(std::end(buffer), std::begin(t), std::end(t)); + return len + t.size(); + } + + template + typename std::enable_if::value && !std::is_const::value, std::size_t>::type decodeBytes( + InputIterator &pos, InputIterator end, Type &t, Flags flags = Flag::none) { + auto tag = undefined; + auto value = undefined; + auto len = decodeTagAndValue(pos, end, tag, value, flags); + if (tag != Major::byteString) throw Exception("not ByteString"); + + auto dist = std::distance(pos, end); + if (dist < static_cast(value)) throw Exception("not enough input"); + t.insert(std::end(t), pos, pos + value); + std::advance(pos, value); + return len + value; + } + + template + typename std::enable_if::value &&std::is_unsigned::value, std::size_t>::type encodeEncodedBytesPrefix( + Buffer &buffer, const Type &t) { + auto len = encodeTagAndValue(buffer, Major::semantic, Minor::cborEncodedData); + return len + encodeTagAndValue(buffer, Major::byteString, t); + } + + template + typename std::enable_if::value && !std::is_const::value, std::size_t>::type + decodeEncodedBytesPrefix(InputIterator &pos, InputIterator end, Type &t, Flags flags = Flag::none) { + auto tag = undefined; + auto value = undefined; + auto len = decodeTagAndValue(pos, end, tag, value, flags); + if (tag != Major::semantic || value != Minor::cborEncodedData) { + throw Exception("not CBOR Encoded Data"); + } + tag = undefined; + len += decodeTagAndValue(pos, end, tag, value, flags); + if (tag != Major::byteString) throw Exception("not ByteString"); + t = value; + return len; + } + + template + typename std::enable_if::value, std::size_t>::type encodeEncodedBytes(Buffer &buffer, const Type &t) { + auto len = encodeTagAndValue(buffer, Major::semantic, Minor::cborEncodedData); + return len + encodeBytes(buffer, t); + } + + template + typename std::enable_if::value && !std::is_const::value, std::size_t>::type decodeEncodedBytes( + InputIterator &pos, InputIterator end, Type &t, Flags flags = Flag::none) { + auto tag = undefined; + auto value = undefined; + auto len = decodeTagAndValue(pos, end, tag, value, flags); + if (tag != Major::semantic || value != Minor::cborEncodedData) { + throw Exception("not CBOR Encoded Data"); + } + return len + decodeBytes(pos, end, t, flags); + } + + template + typename std::enable_if::value, std::size_t>::type encodeText(Buffer &buffer, const Type &t) { + auto len = encodeTagAndValue(buffer, Major::textString, t.size()); + buffer.insert(std::end(buffer), std::begin(t), std::end(t)); + return len + t.size(); + } + + template + typename std::enable_if::value && !std::is_const::value, std::size_t>::type decodeText( + InputIterator &pos, InputIterator end, Type &t, Flags flags = Flag::none) { + auto tag = undefined; + auto value = undefined; + auto len = decodeTagAndValue(pos, end, tag, value, flags); + if (tag != Major::textString) throw Exception("not TextString"); + + auto dist = std::distance(pos, end); + if (dist < static_cast(value)) throw Exception("not enough input"); + // PJ64: 32-bit plaform support + if (sizeof(size_t) == 4 && value > UINT32_MAX) { + throw Exception("TextString too long for 32-bit platforms"); + } + t.insert(std::end(t), pos, pos + (size_t)value); + std::advance(pos, (size_t)value); + return (size_t)(len + value); + } + + template + typename std::enable_if::value &&std::is_unsigned::value, std::size_t>::type encodeArraySize( + Buffer &buffer, const Type &t) { + return encodeTagAndValue(buffer, Major::array, t); + } + + template + typename std::enable_if::value && !std::is_const::value &&std::is_unsigned::value, + std::size_t>::type + decodeArraySize(InputIterator &pos, InputIterator end, Type &t, Flags flags = Flag::none) { + auto tag = undefined; + auto value = undefined; + auto len = decodeTagAndValue(pos, end, tag, value, flags); + if (tag != Major::array) throw Exception("not Array"); + t = value; + return len; + } + + template + typename std::enable_if::value &&std::is_unsigned::value, std::size_t>::type encodeMapSize( + Buffer &buffer, const Type &t) { + return encodeTagAndValue(buffer, Major::map, t); + } + + template + typename std::enable_if::value && !std::is_const::value &&std::is_unsigned::value, + std::size_t>::type + decodeMapSize(InputIterator &pos, InputIterator end, Type &t, Flags flags = Flag::none) { + auto tag = undefined; + auto value = undefined; + auto len = decodeTagAndValue(pos, end, tag, value, flags); + if (tag != Major::map) throw Exception("not Map"); + t = value; + return len; + } + +} // namespace CborLite \ No newline at end of file diff --git a/r4300i Memory.c b/r4300i Memory.c index dc9be93..4b3ac07 100644 --- a/r4300i Memory.c +++ b/r4300i Memory.c @@ -26,18 +26,25 @@ #include #include +#include #include #include #include "main.h" #include "CPU.h" #include "debugger.h" #include "resource.h" +#include "rom.h" +#include "SessionMemBookmarks.h" #define IDC_VADDR 0x100 #define IDC_PADDR 0x101 #define IDC_LIST_VIEW 0x102 #define IDC_SCRL_BAR 0x103 #define IDC_REFRESH 0x104 +#define IDC_BOOKMARKS 0x105 +#define IDC_BOOKMARK_ADD 0x106 +#define IDC_BOOKMARK_UPDATE 0x107 +#define IDC_BOOKMARK_REMOVE 0x108 // TODO: Figure out if these can be queried instead of hardcoded #define PADDING 6 @@ -50,8 +57,16 @@ void Refresh_Memory_With_Diff(BOOL ShowDiff); void Clear_Selection(void); void Copy_Selection(void); int Get_Ascii_Index(POINTS pt); +LRESULT Change_Bookmark_Selection(int next); +unsigned int Get_Bookmark_Name_Width(char *name); +void Update_Bookmark_Width(void); +void Add_Bookmark(void); +void Edit_Bookmark(void); +void Update_Bookmark(unsigned int item); +void Remove_Bookmark(unsigned int item); +void Load_Bookmark(unsigned int item); -LRESULT CALLBACK Memory_Window_Proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); +LRESULT CALLBACK Memory_Window_Proc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); struct MEMORY_VIEW_ROW { unsigned char OldData[16]; @@ -83,7 +98,7 @@ const COLORREF BG_ODD = RGB(230, 230, 230); static HWND Memory_Win_hDlg, hAddrEdit, hVAddr, hPAddr, hRefresh, hList, hScrlBar; static HBRUSH hBkEven, hBkOdd; -static HFONT hWatchFont; +static HFONT hWatchFont, hDefaultFont; static HANDLE hRefreshThread = NULL; static HANDLE hRefreshMutex = NULL; static int InMemoryWindow = FALSE; @@ -91,6 +106,9 @@ static int wheel = 0; static int thumb = -1; static struct MEMORY_VIEW_ROW MemoryViewRows[16]; static struct SELECTION selection = { 0 }; +static struct MEM_BOOKMARK bookmarks[MAX_MEM_BOOKMARKS] = { 0 }; +static unsigned int num_bookmarks = 0; +static char bookmarks_cbor[MAX_PATH + 1] = { 0 }; void __cdecl Create_Memory_Window ( int Child ) { DWORD ThreadID; @@ -98,11 +116,35 @@ void __cdecl Create_Memory_Window ( int Child ) { hBkEven = CreateSolidBrush(BG_EVEN); hBkOdd = CreateSolidBrush(BG_ODD); + // Create a font for the watchpoints within the hex editor LOGFONT lf; GetObject(GetStockObject(ANSI_FIXED_FONT), sizeof(lf), &lf); lf.lfUnderline = TRUE; hWatchFont = CreateFontIndirect(&lf); + // Create a default font that matches the system theme + NONCLIENTMETRICS metrics = { 0 }; + metrics.cbSize = sizeof(NONCLIENTMETRICS); + if (!IsWindowsVistaOrGreater()) { + // NOTE: This is for compatibility with Windows XP. + metrics.cbSize -= sizeof(metrics.iPaddedBorderWidth); + } + SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &metrics, 0); + hDefaultFont = CreateFontIndirect(&metrics.lfMessageFont); + + // Load session bookmarks + char save_directory[MAX_PATH + 1] = { 0 }; + Settings_GetDirectory(AutoSaveDir, save_directory, sizeof(save_directory)); + if (strlen(save_directory) + strlen(RomFullName) + 27 <= sizeof(bookmarks_cbor)) { + sprintf(bookmarks_cbor, "%s%s.session.membookmarks.cbor", save_directory, RomFullName); + if (!Session_Load_MemBookmarks(&num_bookmarks, bookmarks, MAX_MEM_BOOKMARKS, bookmarks_cbor)) { + num_bookmarks = 0; + } + } else { + num_bookmarks = 0; + bookmarks_cbor[0] = 0; + } + InMemoryWindow = TRUE; DialogBox( hInst, "MEMORY", NULL,(DLGPROC) Memory_Window_Proc ); InMemoryWindow = FALSE; @@ -321,7 +363,7 @@ void Write_MemoryLineDump(char *output, unsigned int location) { sprintf(output, "%s %s\r\n", bytes, ascii); } -LRESULT CALLBACK Memory_Window_Proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { +LRESULT CALLBACK Memory_Window_Proc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; RECT rcBox; @@ -335,21 +377,28 @@ LRESULT CALLBACK Memory_Window_Proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM StoreCurrentWinPos("Memory",hDlg); break; case WM_PAINT: - BeginPaint( hDlg, &ps ); + BeginPaint(hDlg, &ps); + SetBkMode(ps.hdc, TRANSPARENT); + + SelectObject(ps.hdc, hDefaultFont); + TextOut(ps.hdc, 731, 15, "Bookmarks:", 10); + SelectObject(ps.hdc, GetStockObject(ANSI_FIXED_FONT)); - SetBkMode( ps.hdc, TRANSPARENT ); - TextOut(ps.hdc,25,17,"Address:",8); + TextOut(ps.hdc, 25, 17, "Address:", 8); + rcBox.left = 5; rcBox.top = 5; - rcBox.right = 721; + rcBox.right = 719; + rcBox.bottom = 348; + DrawEdge(ps.hdc, &rcBox, EDGE_ETCHED, BF_RECT); + + rcBox.left = 724; + rcBox.top = 5; + rcBox.right = 891; rcBox.bottom = 348; - DrawEdge( ps.hdc, &rcBox, EDGE_RAISED, BF_RECT ); - rcBox.left = 8; - rcBox.top = 8; - rcBox.right = 718; - rcBox.bottom = 345; - DrawEdge( ps.hdc, &rcBox, EDGE_ETCHED, BF_RECT ); - EndPaint( hDlg, &ps ); + DrawEdge(ps.hdc, &rcBox, EDGE_ETCHED, BF_RECT); + + EndPaint(hDlg, &ps); return TRUE; case WM_COMMAND: switch (LOWORD(wParam)) { @@ -359,9 +408,21 @@ LRESULT CALLBACK Memory_Window_Proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM } break; case IDC_VADDR: - case IDC_PADDR: + case IDC_PADDR: { + char Value[20]; + GetWindowText(hAddrEdit, Value, sizeof(Value)); + DWORD address = AsciiToHex(Value); + if (LOWORD(wParam) == IDC_VADDR && address < 0x80000000 || address >= 0x80800000) { + SetWindowText(hAddrEdit, "80000000"); + } else if (LOWORD(wParam) == IDC_PADDR && address >= 0x20000000) { + SetWindowText(hAddrEdit, "00000000"); + } + + Clear_Selection(); + ListBox_SetCurSel(GetDlgItem(hDlg, IDC_BOOKMARKS), -1); Refresh_Memory_With_Diff(FALSE); break; + } case IDC_REFRESH: Start_Auto_Refresh_Thread(); Refresh_Memory_With_Diff(FALSE); @@ -369,6 +430,28 @@ LRESULT CALLBACK Memory_Window_Proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM case IDCANCEL: Clear_Selection(); break; + case IDC_BOOKMARKS: + if (HIWORD(wParam) == LBN_DBLCLK) { + Edit_Bookmark(); + } + break; + case IDC_BOOKMARK_ADD: + Add_Bookmark(); + break; + case IDC_BOOKMARK_UPDATE: { + int item = ListBox_GetCurSel(GetDlgItem(hDlg, IDC_BOOKMARKS)); + if (item != LB_ERR) { + Update_Bookmark(item); + } + break; + } + case IDC_BOOKMARK_REMOVE: { + int item = ListBox_GetCurSel(GetDlgItem(hDlg, IDC_BOOKMARKS)); + if (item != LB_ERR) { + Remove_Bookmark(item); + } + break; + } default: break; } @@ -693,6 +776,9 @@ LRESULT CALLBACK Memory_ListViewDrag_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, L selection.range_cmp[0] = location; selection.range_cmp[1] = location; } + + EnableWindow(GetDlgItem(Memory_Win_hDlg, IDC_BOOKMARK_ADD), TRUE); + return FALSE; } case WM_LBUTTONUP: @@ -751,6 +837,175 @@ LRESULT CALLBACK Memory_ListViewDrag_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, L return DefSubclassProc(hWnd, uMsg, wParam, lParam); } +LRESULT CALLBACK Bookmarks_ListBox_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) { + (void)uIdSubclass; + (void)dwRefData; + + static BOOL mouse_held = FALSE; + + switch (uMsg) { + case WM_LBUTTONDOWN: { + mouse_held = TRUE; + + int height = ListBox_GetItemHeight(hWnd, 0); + POINT pt = { LOWORD(lParam), HIWORD(lParam) }; + int item = (pt.y / height) + ListBox_GetTopIndex(hWnd); + + if (item >= 0 && (unsigned int)item < num_bookmarks) { + BOOL is_virtual = (Button_GetCheck(hVAddr) == BST_CHECKED); + if (bookmarks[item].is_virtual != is_virtual) { + // Deselect the item if the virtual addressing setting doesn't match + // TODO: Color these items so they look disabled (needs OWNER DRAW?) + return FALSE; + } + + Load_Bookmark(item); + } + + break; + } + case WM_LBUTTONUP: + mouse_held = FALSE; + break; + case WM_MOUSEMOVE: + if (mouse_held) { + // Disable dragging + // TODO: Use drag to reorder bookmarks + return FALSE; + } + break; + case WM_KEYDOWN: + switch (wParam) { + case VK_UP: + if (Change_Bookmark_Selection(-1)) { + return TRUE; + } + break; + case VK_DOWN: + if (Change_Bookmark_Selection(1)) { + return TRUE; + } + break; + case VK_F2: { + int item = ListBox_GetCurSel(hWnd); + if (item != LB_ERR) { + Edit_Bookmark(); + return TRUE; + } + break; + } + case VK_DELETE: { + int item = ListBox_GetCurSel(hWnd); + if (item != LB_ERR) { + Remove_Bookmark(item); + return TRUE; + } + break; + } + default: + break; + } + break; + } + + return DefSubclassProc(hWnd, uMsg, wParam, lParam); +} + +INT_PTR CALLBACK Edit_Bookmark_Proc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { + (void)lParam; + + HWND hBookmarks = GetDlgItem(Memory_Win_hDlg, IDC_BOOKMARKS); + int item = ListBox_GetCurSel(hBookmarks); + + switch (uMsg) { + case WM_INITDIALOG: { + char address[9] = { 0 }; + + // TODO: Support language translations + HWND hName = GetDlgItem(hDlg, IDC_NAME); + Edit_SetText(hName, bookmarks[item].name); + Edit_LimitText(hName, sizeof(bookmarks->name) - 1); + + HWND hStart = GetDlgItem(hDlg, IDC_START); + sprintf(address, "%08X", bookmarks[item].selection_range[0]); + Edit_SetText(hStart, address); + Edit_LimitText(hStart, 8); + + HWND hEnd = GetDlgItem(hDlg, IDC_END); + sprintf(address, "%08X", bookmarks[item].selection_range[1]); + Edit_SetText(hEnd, address); + Edit_LimitText(hEnd, 8); + + CheckRadioButton(hDlg, IDC_BOOKMARK_VADDR, IDC_BOOKMARK_PADDR, bookmarks[item].is_virtual ? IDC_BOOKMARK_VADDR : IDC_BOOKMARK_PADDR); + + return TRUE; + } + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: { + char name[sizeof(bookmarks->name)] = { 0 }; + char temp[9] = { 0 }; + char *end = NULL; + DWORD start_address = 0; + DWORD end_address = 0; + + // Validate inputs + Edit_GetText(GetDlgItem(hDlg, IDC_NAME), name, sizeof(name)); + if (name[0] == 0) { + DisplayError("Name is required"); + return FALSE; + } + + Edit_GetText(GetDlgItem(hDlg, IDC_START), temp, sizeof(temp)); + start_address = strtoul(temp, &end, 16); + if (end == temp || *end != 0) { + DisplayError("Start address is invalid"); + return FALSE; + } + + Edit_GetText(GetDlgItem(hDlg, IDC_END), temp, sizeof(temp)); + end_address = strtoul(temp, &end, 16); + if (end == temp || *end != 0) { + DisplayError("End address is invalid"); + return FALSE; + } + if (end_address < start_address) { + DisplayError("Invalid address range: End must be greater or equal to start"); + return FALSE; + } + + // Update bookmark "atomically" + strcpy(bookmarks[item].name, name); + bookmarks[item].width_required = Get_Bookmark_Name_Width(name); + bookmarks[item].selection_range[0] = start_address; + bookmarks[item].selection_range[1] = end_address; + bookmarks[item].is_virtual = (Button_GetCheck(GetDlgItem(hDlg, IDC_BOOKMARK_VADDR)) == BST_CHECKED); + + // Update parent control + BOOL is_virtual = (Button_GetCheck(hVAddr) == BST_CHECKED); + if (bookmarks[item].is_virtual == is_virtual) { + ListBox_DeleteString(hBookmarks, item); + ListBox_InsertString(hBookmarks, item, name); + ListBox_SetCurSel(hBookmarks, item); + Load_Bookmark(item); + } else { + ListBox_SetCurSel(hBookmarks, -1); + } + + EndDialog(hDlg, 1); + return TRUE; + } + case IDCANCEL: + EndDialog(hDlg, 0); + return TRUE; + default: + return FALSE; + } + default: + return FALSE; + } +} + void Clear_Selection(void) { selection.enabled = FALSE; selection.dragging = FALSE; @@ -760,6 +1015,8 @@ void Clear_Selection(void) { selection.range[1] = 0; selection.range_cmp[0] = 0; selection.range_cmp[1] = 0; + + EnableWindow(GetDlgItem(Memory_Win_hDlg, IDC_BOOKMARK_ADD), FALSE); } void Copy_Selection(void) { @@ -824,7 +1081,7 @@ void Scroll_Memory_View(int lines) { location = UINT_MAX - 256 + 1; } } else { - if (location >= -lines * 16) { + if (location >= (unsigned int)(-lines * 16)) { location += lines * 16; } else { location = 0; @@ -837,6 +1094,24 @@ void Scroll_Memory_View(int lines) { ReleaseMutex(hRefreshMutex); } +LRESULT Change_Bookmark_Selection(int next) { + BOOL result = FALSE; + BOOL is_virtual = (Button_GetCheck(hVAddr) == BST_CHECKED); + HWND hBookmarks = GetDlgItem(Memory_Win_hDlg, IDC_BOOKMARKS); + int item = ListBox_GetCurSel(hBookmarks) + next; + while (item >= 0 && (unsigned int)item < num_bookmarks) { + result = TRUE; + if (bookmarks[item].is_virtual == is_virtual) { + ListBox_SetCurSel(hBookmarks, item); + Load_Bookmark(item); + return TRUE; + } + + item += next; + } + return result; +} + void __cdecl Refresh_Memory(void) { Refresh_Memory_With_Diff(TRUE); } @@ -886,8 +1161,9 @@ void Start_Auto_Refresh_Thread(void) { } void Setup_Memory_Window (HWND hDlg) { -#define WindowWidth 742 +#define WindowWidth 912 #define WindowHeight 392 + HWND hBookmarks, hBookmarkAdd, hBookmarkEdit, hBookmarkRemove; DWORD X, Y; if (hRefreshMutex == NULL) { @@ -898,48 +1174,52 @@ void Setup_Memory_Window (HWND hDlg) { hVAddr = CreateWindowEx(0,"BUTTON", "Virtual Addressing", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 215,13,150,21,hDlg,(HMENU)IDC_VADDR,hInst,NULL ); SendMessage(hVAddr,BM_SETCHECK, BST_CHECKED,0); - - hPAddr = CreateWindowEx(0,"BUTTON", "Physical Addressing", WS_CHILD | WS_VISIBLE | - BS_AUTORADIOBUTTON, 375,13,155,21,hDlg,(HMENU)IDC_PADDR,hInst,NULL ); + SendMessage(hVAddr, WM_SETFONT, (WPARAM)hDefaultFont, 0); + + hPAddr = CreateWindowEx(0, "BUTTON", "Physical Addressing", WS_CHILD | WS_VISIBLE | + BS_AUTORADIOBUTTON, 375, 13, 155, 21, hDlg, (HMENU)IDC_PADDR, hInst, NULL); + SendMessage(hPAddr, WM_SETFONT, (WPARAM)hDefaultFont, 0); - hRefresh = CreateWindowEx(0,"BUTTON", "Auto Refresh", WS_CHILD | WS_VISIBLE | - BS_AUTOCHECKBOX, 595,13,100,21,hDlg,(HMENU)IDC_REFRESH,hInst,NULL ); + hRefresh = CreateWindowEx(0, "BUTTON", "Auto Refresh", WS_CHILD | WS_VISIBLE | + BS_AUTOCHECKBOX, 595, 13, 100, 21, hDlg, (HMENU)IDC_REFRESH, hInst, NULL); SendMessage(hRefresh, BM_SETCHECK, BST_CHECKED, 0); Start_Auto_Refresh_Thread(); + SendMessage(hRefresh, WM_SETFONT, (WPARAM)hDefaultFont, 0); hList = CreateWindowEx(WS_EX_CLIENTEDGE, WC_LISTVIEW, "", WS_CHILD | WS_VISIBLE | - LVS_OWNERDATA | LVS_REPORT | LVS_NOSORTHEADER | LVS_SINGLESEL, 14,39,682,300,hDlg, - (HMENU)IDC_LIST_VIEW,hInst,NULL ); + LVS_OWNERDATA | LVS_REPORT | LVS_NOSORTHEADER | LVS_SINGLESEL, 12, 39, 682, 300, hDlg, + (HMENU)IDC_LIST_VIEW, hInst, NULL); if (hList) { + SendMessage(hList, WM_SETFONT, (WPARAM)hDefaultFont, 0); ListView_SetExtendedListViewStyle(hList, LVS_EX_DOUBLEBUFFER); LV_COLUMN col; int count; col.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; - col.fmt = LVCFMT_LEFT; + col.fmt = LVCFMT_LEFT; - col.pszText = "Address"; - col.cx = 90; + col.pszText = "Address"; + col.cx = 90; col.iSubItem = 0; - ListView_InsertColumn ( hList, 0, &col); + ListView_InsertColumn(hList, 0, &col); char ColumnName[3] = { 0 }; - col.pszText = ColumnName; - col.cx = 28; + col.pszText = ColumnName; + col.cx = 28; for (int i = 0; i < 16; i++) { sprintf(ColumnName, " %X", i); col.iSubItem = i + 1; ListView_InsertColumn(hList, i + 1, &col); } - col.pszText = "Memory Ascii"; - col.cx = 140; + col.pszText = "Memory Ascii"; + col.cx = 140; col.iSubItem = 17; - ListView_InsertColumn ( hList, 17, &col); - ListView_SetItemCount ( hList, 16); - SendMessage(hList,WM_SETFONT, (WPARAM)GetStockObject(ANSI_FIXED_FONT),0); - for (count = 0 ; count < 16;count ++ ){ + ListView_InsertColumn(hList, 17, &col); + ListView_SetItemCount(hList, 16); + SendMessage(hList, WM_SETFONT, (WPARAM)GetStockObject(ANSI_FIXED_FONT), 0); + for (count = 0; count < 16; count++) { Insert_MemoryLineDump(count, count, FALSE); } SetWindowSubclass(hList, Memory_ListViewScroll_Proc, 0, 0); @@ -956,24 +1236,49 @@ void Setup_Memory_Window (HWND hDlg) { } ReleaseMutex(hRefreshMutex); - SendMessage(hAddrEdit,EM_SETLIMITTEXT,(WPARAM)8,(LPARAM)0); - SetWindowPos(hAddrEdit,NULL, 100,13,100,21, SWP_NOZORDER | SWP_SHOWWINDOW); - SendMessage(hAddrEdit,WM_SETFONT, (WPARAM)GetStockObject(ANSI_FIXED_FONT),0); + SendMessage(hAddrEdit, EM_SETLIMITTEXT, (WPARAM)8, (LPARAM)0); + SetWindowPos(hAddrEdit, NULL, 100, 13, 100, 21, SWP_NOZORDER | SWP_SHOWWINDOW); + SendMessage(hAddrEdit, WM_SETFONT, (WPARAM)GetStockObject(ANSI_FIXED_FONT), 0); hScrlBar = CreateWindowEx(0, "SCROLLBAR", "", WS_CHILD | WS_VISIBLE | - WS_TABSTOP | SBS_VERT, 696,39,20,300, hDlg, (HMENU)IDC_SCRL_BAR, hInst, NULL ); + WS_TABSTOP | SBS_VERT, 694, 39, 20, 300, hDlg, (HMENU)IDC_SCRL_BAR, hInst, NULL); if (hScrlBar) { SCROLLINFO si; si.cbSize = sizeof(si); - si.fMask = SIF_RANGE | SIF_POS | SIF_PAGE; - si.nMin = 0; - si.nMax = 300; - si.nPos = 145; - si.nPage = 10; - SetScrollInfo(hScrlBar,SB_CTL,&si,TRUE); + si.fMask = SIF_RANGE | SIF_POS | SIF_PAGE; + si.nMin = 0; + si.nMax = 300; + si.nPos = 145; + si.nPage = 10; + SetScrollInfo(hScrlBar, SB_CTL, &si, TRUE); SetWindowSubclass(hScrlBar, Memory_ListViewScroll_Proc, 0, 0); - } + } + + hBookmarks = CreateWindowEx(WS_EX_CLIENTEDGE, WC_LISTBOX, "", WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | LBS_NOTIFY, + 731, 39, 152, 280, hDlg, (HMENU)IDC_BOOKMARKS, hInst, NULL); + SendMessage(hBookmarks, WM_SETFONT, (WPARAM)hDefaultFont, 0); + SetWindowSubclass(hBookmarks, Bookmarks_ListBox_Proc, 0, 0); + + for (unsigned int i = 0; i < num_bookmarks; i++) { + ListBox_AddString(hBookmarks, bookmarks[i].name); + } + Update_Bookmark_Width(); + + hBookmarkAdd = CreateWindowEx(WS_EX_WINDOWEDGE, WC_BUTTON, "Add", WS_CHILD | WS_VISIBLE, + 731, 318, 36, 22, hDlg, (HMENU)IDC_BOOKMARK_ADD, hInst, NULL); + SendMessage(hBookmarkAdd, WM_SETFONT, (WPARAM)hDefaultFont, 0); + EnableWindow(hBookmarkAdd, FALSE); + + hBookmarkEdit = CreateWindowEx(WS_EX_WINDOWEDGE, WC_BUTTON, "Update", WS_CHILD | WS_VISIBLE, + 769, 318, 56, 22, hDlg, (HMENU)IDC_BOOKMARK_UPDATE, hInst, NULL); + SendMessage(hBookmarkEdit, WM_SETFONT, (WPARAM)hDefaultFont, 0); + EnableWindow(hBookmarkEdit, FALSE); + + hBookmarkRemove = CreateWindowEx(WS_EX_WINDOWEDGE, WC_BUTTON, "Remove", WS_CHILD | WS_VISIBLE, + 827, 318, 56, 22, hDlg, (HMENU)IDC_BOOKMARK_REMOVE, hInst, NULL); + SendMessage(hBookmarkRemove, WM_SETFONT, (WPARAM)hDefaultFont, 0); + EnableWindow(hBookmarkRemove, FALSE); if ( !GetStoredWinPos( "Memory", &X, &Y ) ) { X = (GetSystemMetrics( SM_CXSCREEN ) - WindowWidth) / 2; @@ -981,5 +1286,131 @@ void Setup_Memory_Window (HWND hDlg) { } SetWindowPos(hDlg,NULL,X,Y,WindowWidth,WindowHeight, SWP_NOZORDER | SWP_SHOWWINDOW); - +} + +unsigned int Get_Bookmark_Name_Width(char *name) { + HWND hBookmark = GetDlgItem(Memory_Win_hDlg, IDC_BOOKMARKS); + HDC hDcBookmarks = GetDC(hBookmark); + HDC hdc = CreateCompatibleDC(hDcBookmarks); + HGDIOBJ hOldFont = SelectObject(hdc, hDefaultFont); + + SIZE size = { 0 }; + GetTextExtentPoint32(hdc, name, strlen(name), &size); + + SelectObject(hdc, hOldFont); + DeleteDC(hdc); + ReleaseDC(hBookmark, hDcBookmarks); + + return size.cx + GetSystemMetrics(SM_CXEDGE) * 2; +} + +void Update_Bookmark_Width(void) { + unsigned int width_required = 0; + for (unsigned int i = 0; i < num_bookmarks; i++) { + width_required = max(width_required, bookmarks[i].width_required); + } + + HWND hBookmarks = GetDlgItem(Memory_Win_hDlg, IDC_BOOKMARKS); + if (width_required != ListBox_GetHorizontalExtent(hBookmarks)) { + ListBox_SetHorizontalExtent(hBookmarks, width_required); + } +} + +void Create_Bookmark_Name(char *name, BOOL is_virtual) { + DWORD address = selection.range[0]; + DWORD len = min(selection.range[1] - address + 1, sizeof(bookmarks->name) - (8 + 6)); + BYTE value = 0; + + if (is_virtual) { + unsigned int i = 0; + while (i < len) { + if (r4300i_LB_VAddr_NonCPU(address + i, &value) && isprint(value)) { + name[i] = value; + } else { + sprintf(name, "[0x%08X]", address); + return; + } + + i++; + } + sprintf(&name[i], " [0x%08X]", address); + } else { + sprintf(name, "PHYS [0x%08X]", address); + } +} + +void Add_Bookmark(void) { + if (num_bookmarks < MAX_MEM_BOOKMARKS && selection.enabled) { + bookmarks[num_bookmarks].selection_range[0] = selection.range[0]; + bookmarks[num_bookmarks].selection_range[1] = selection.range[1]; + bookmarks[num_bookmarks].is_virtual = (Button_GetCheck(hVAddr) == BST_CHECKED); + + Create_Bookmark_Name(bookmarks[num_bookmarks].name, bookmarks[num_bookmarks].is_virtual); + bookmarks[num_bookmarks].width_required = Get_Bookmark_Name_Width(bookmarks[num_bookmarks].name); + + ListBox_AddString(GetDlgItem(Memory_Win_hDlg, IDC_BOOKMARKS), bookmarks[num_bookmarks].name); + + num_bookmarks += 1; + + Update_Bookmark_Width(); + + Session_Save_MemBookmarks(bookmarks_cbor, bookmarks, num_bookmarks); + } +} + +void Edit_Bookmark(void) { + if (DialogBox(NULL, MAKEINTRESOURCE(IDD_MEM_BOOKMARK_EDIT), Memory_Win_hDlg, Edit_Bookmark_Proc)) { + Update_Bookmark_Width(); + + Session_Save_MemBookmarks(bookmarks_cbor, bookmarks, num_bookmarks); + } +} + +void Update_Bookmark(unsigned int item) { + bookmarks[item].selection_range[0] = selection.range[0]; + bookmarks[item].selection_range[1] = selection.range[1]; + + Session_Save_MemBookmarks(bookmarks_cbor, bookmarks, num_bookmarks); +} + +void Remove_Bookmark(unsigned int item) { + if (item < num_bookmarks - 1) { + memmove(&bookmarks[item], &bookmarks[item + 1], sizeof(struct MEM_BOOKMARK) * (num_bookmarks - item - 1)); + } + + HWND hBookmarks = GetDlgItem(Memory_Win_hDlg, IDC_BOOKMARKS); + ListBox_DeleteString(hBookmarks, item); + + num_bookmarks -= 1; + + BOOL is_virtual = (Button_GetCheck(hVAddr) == BST_CHECKED); + if (item < num_bookmarks && bookmarks[item].is_virtual == is_virtual) { + ListBox_SetCurSel(hBookmarks, item); + } else { + EnableWindow(GetDlgItem(Memory_Win_hDlg, IDC_BOOKMARK_UPDATE), FALSE); + EnableWindow(GetDlgItem(Memory_Win_hDlg, IDC_BOOKMARK_REMOVE), FALSE); + } + + Update_Bookmark_Width(); + + Session_Save_MemBookmarks(bookmarks_cbor, bookmarks, num_bookmarks); +} + +void Load_Bookmark(unsigned int item) { + selection.enabled = TRUE; + selection.dragging = FALSE; + selection.column_hex = TRUE; + selection.anchor = bookmarks[item].selection_range[0]; + selection.range[0] = bookmarks[item].selection_range[0]; + selection.range[1] = bookmarks[item].selection_range[1]; + selection.range_cmp[0] = bookmarks[item].selection_range[0]; + selection.range_cmp[1] = bookmarks[item].selection_range[1]; + + char address[10] = { 0 }; + sprintf(address, "%08X", bookmarks[item].selection_range[0]); + SetWindowText(hAddrEdit, address); + + EnableWindow(GetDlgItem(Memory_Win_hDlg, IDC_BOOKMARK_ADD), TRUE); + EnableWindow(GetDlgItem(Memory_Win_hDlg, IDC_BOOKMARK_UPDATE), TRUE); + EnableWindow(GetDlgItem(Memory_Win_hDlg, IDC_BOOKMARK_REMOVE), TRUE); } \ No newline at end of file diff --git a/resource.h b/resource.h index 834aaab..86d6eac 100644 --- a/resource.h +++ b/resource.h @@ -39,6 +39,7 @@ #define IDD_Debugger_MemDump 178 #define IDB_BITMAP2 182 #define IDB_BITMAP3 183 +#define IDD_MEM_BOOKMARK_EDIT 184 #define IDC_CLOSE_BUTTON 1001 #define RSP_LIST 1004 #define RSP_ABOUT 1006 @@ -265,6 +266,10 @@ #define IDC_CLEAR_MEMORY 1258 #define IDC_CHECK1 1259 #define IDC_USEDEBUGGER 1259 +#define IDC_END 1260 +#define IDC_BOOKMARK_VADDR 1261 +#define IDC_BOOKMARK_PADDR 1262 +#define IDC_START 1263 #define ID_FILE_OPEN_ROM 40001 #define ID_FILE_ROM_INFO 40002 #define ID_FILE_EXIT 40005 @@ -346,9 +351,9 @@ // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 183 +#define _APS_NEXT_RESOURCE_VALUE 185 #define _APS_NEXT_COMMAND_VALUE 40175 -#define _APS_NEXT_CONTROL_VALUE 1260 +#define _APS_NEXT_CONTROL_VALUE 1264 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif From de3794477af05ff7620486d89f1f1f16182bca9c Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Sun, 30 Jul 2023 17:25:07 -0700 Subject: [PATCH 9/9] Add missing session files to VS project filters --- Project64.vcxproj.filters | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Project64.vcxproj.filters b/Project64.vcxproj.filters index d1f7c70..7933a25 100644 --- a/Project64.vcxproj.filters +++ b/Project64.vcxproj.filters @@ -282,6 +282,9 @@ Source Files\Debugger Source + + Source Files + @@ -500,6 +503,12 @@ Header Files\cbor-lite Headers + + Header Files + + + Header Files +