{ 
	Prog->MemView - Live Memory View.
	
	@author(Kieron Morris <kjm@kieronmorris.me>)
}
unit memview;

interface

uses
    console, terminal, keyboard, util, strings, tracer;

procedure init();

implementation

type
    TMode = (tmHex, tmChar, tmStackHex, tmStackChar);

var
    Handle  : HWND = 0;
    MEM_LOC : uint32;
    NEW_LOC : Boolean;
    Colors  : uint32;
    Changed : uint32;
    Data    : Array[0..176] of byte;
    Mode    : TMode;

procedure printmemoryaschar(source : uint32; length : uint32; col : uint32; delim : PChar; offset_row : boolean);
var
    buf : puint8;
    i   : uint32;

begin
    tracer.push_trace('memview.printmemory');
    buf:= puint8(source);
    if NEW_LOC then begin
        NEW_LOC:= false;
        for i:=0 to length-1 do begin
            Data[i]:= buf[i];
        end;
    end;
    console.writestringlnExWND(' ', Colors, Handle);
    //console.writestringlnExWND(' ', Colors, Handle);
    console.writestringExWND('        Mode: ', Colors, Handle);
    case mode of
        tmChar:console.writestringlnExWND('Memory', Colors, Handle);
        tmStackChar:console.writestringlnExWND('Stack', Colors, Handle);
    end;
    for i:=0 to length-1 do begin
        if offset_row and (i = 0) then begin
            console.writestringExWND('  ', Colors, Handle);
            console.writehexExWND(source + (i), Colors, Handle);
            console.writestringExWND(': ', Colors, Handle);
        end; 
        if buf[i] = Data[i] then console.writecharExWND(char(buf[i]), Colors, Handle) else console.writecharExWND(char(buf[i]), Changed, Handle);
        console.writecharExWND(' ', Colors, Handle);
        if i<>length-1 then begin
            if ((i+1) MOD col) = 0 then begin
                console.writestringlnExWND(' ', Colors, Handle);  
                if offset_row then begin
                    console.writestringExWND('  ', Colors, Handle);
                    console.writehexExWND(source + (i + 1), Colors, Handle);
                    console.writestringExWND(': ', Colors, Handle);
                end;  
            end else begin
                console.writestringExWND(delim, Colors, Handle);
            end;
        end;
    end;
    //console.writestringlnExWND(' ', Colors, Handle);  
    console.writestringlnExWND(' ', Colors, Handle); 
    console.writestringExWND('     Display: ', Colors, Handle);
    console.writestringExWND('ASCII', Colors, Handle);
end;

procedure printmemoryashex(source : uint32; length : uint32; col : uint32; delim : PChar; offset_row : boolean);
var
    buf : puint8;
    i   : uint32;

begin
    tracer.push_trace('memview.printmemory');
    buf:= puint8(source);
    if NEW_LOC then begin
        NEW_LOC:= false;
        for i:=0 to length-1 do begin
            Data[i]:= buf[i];
        end;
    end;
    console.writestringlnExWND(' ', Colors, Handle);
    //console.writestringlnExWND(' ', Colors, Handle);
    console.writestringExWND('        Mode: ', Colors, Handle);
    case mode of
        tmHex:console.writestringlnExWND('Memory', Colors, Handle);
        tmStackHex:console.writestringlnExWND('Stack', Colors, Handle);
    end;
    for i:=0 to length-1 do begin
        if offset_row and (i = 0) then begin
            console.writestringExWND('  ', Colors, Handle);
            console.writehexExWND(source + (i), Colors, Handle);
            console.writestringExWND(': ', Colors, Handle);
        end; 
        if buf[i] = Data[i] then console.writehexpairExWND(buf[i], Colors, Handle) else console.writehexpairExWND(buf[i], Changed, Handle);
        if i<>length-1 then begin
            if ((i+1) MOD col) = 0 then begin
                console.writestringlnExWND(' ', Colors, Handle);  
                if offset_row then begin
                    console.writestringExWND('  ', Colors, Handle);
                    console.writehexExWND(source + (i + 1), Colors, Handle);
                    console.writestringExWND(': ', Colors, Handle);
                end;  
            end else begin
                console.writestringExWND(delim, Colors, Handle);
            end;
        end;
    end;
    console.writestringlnExWND(' ', Colors, Handle);
    console.writestringExWND('     Display: ', Colors, Handle);
    console.writestringExWND('Hex', Colors, Handle);
    //console.writestringlnExWND(' ', Colors, Handle);   
end;

procedure OnClose();
begin
    Handle:= 0;
end;

procedure Draw();
begin
    tracer.push_trace('memview.draw');
    if Handle <> 0 then begin
        clearWNDEx(Handle, Colors);
        case mode of
            tmHex:printmemoryashex(MEM_LOC, 176, 16, ' ', true);
            tmChar:printmemoryaschar(MEM_LOC, 176, 16, ' ', true);
            tmStackHex:printmemoryashex(MEM_LOC, 44, 4, ' ', true);
            tmStackChar:printmemoryaschar(MEM_LOC, 44, 4, ' ', true);
        end;
    end;
end;

procedure OnKeyPressed(info : TKeyInfo);
var
    line : uint32;
    window : uint32;

begin
    if (Mode = tmStackHex) or (Mode = tmStackChar) then begin
        line:= 4;
        window:= 44;
    end else begin
        line:= 16;
        window:= 176;
    end;
    if info.CTRL_DOWN then begin
        if info.key_code = 16 then begin
            dec(MEM_LOC, window);
            NEW_LOC:= true;
        end;
        if info.key_code = 18 then begin
            inc(MEM_LOC, window);
            NEW_LOC:= true;
        end;
    end else begin
        if info.key_code = 16 then begin
            dec(MEM_LOC, line);
            NEW_LOC:= true;
        end;
        if info.key_code = 18 then begin
            inc(MEM_LOC, line);
            NEW_LOC:= true;
        end;
    end;
    if info.key_code = uint8('c') then begin
        if (Mode <> tmStackHex) and (Mode <> tmStackChar) then begin
            Mode:= tmChar;
        end else begin
            Mode:= tmStackChar;
        end;
    end;
    if info.key_code = uint8('h') then begin
        if (Mode <> tmStackHex) and (Mode <> tmStackChar) then begin
            Mode:= tmHex;
        end else begin
            Mode:= tmStackHex;
        end;
    end;
    if info.key_code = uint8('s') then begin
        if (Mode <> tmStackHex) and (Mode <> tmStackChar) then begin
            Mode:= tmStackHex;
        end else begin
            Mode:= tmHex;
        end;
        MEM_LOC:= getESP;
        NEW_LOC:= true;
    end;
end;

procedure run(Params : PParamList);
var
    loc : PChar;

begin
    tracer.push_trace('memview.run');
    if ParamCount(Params) < 1 then begin
        writestringlnWND('Memview Utility', getTerminalHWND);
        writestringlnWND(' ', getTerminalHWND);
        writestringlnWND('A tool to display the bytes stored at a memory location for debugging.', getTerminalHWND);
        writestringlnWND('UP/DOWN on the keyboard can be used to decrement/increment the view location by 16-bytes.', getTerminalHWND);
        writestringlnWND(' ', getTerminalHWND);
        writestringlnWND('Usage: ', getTerminalHWND);
        writestringlnWND('      memview <dLocation> - Display 176 bytes @ <dLocation>', getTerminalHWND);
        writestringlnWND(' ', getTerminalHWND);
    end else begin
        loc:= GetParam(0, Params);
        if StringEquals(loc, 'close') then begin
            tracer.push_trace('memview.close');
            if Handle <> 0 then begin
                closeWindow(Handle);
                writestringlnWND('Memview closed.', getTerminalHWND);
            end else begin
                writestringlnWND('Memview not open.', getTerminalHWND);   
            end;
        end else begin
            if (loc[0] = 'x') or (loc[0] = 'X') then MEM_LOC:= HexStringToInt(@loc[1]) else MEM_LOC:= stringToInt(loc);
            NEW_LOC:= true;
            if Handle = 0 then begin
                Handle:= newWindow(20, 40, 63, 14, 'MEMVIEW');
                registerEventHandler(Handle, EVENT_DRAW, void(@Draw));
                registerEventHandler(Handle, EVENT_CLOSE, void(@OnClose));
                registerEventHandler(Handle, EVENT_KEY_PRESSED, void(@OnKeyPressed));
                writestringWND('Memview started at location: ', getTerminalHWND);
            end else begin
                writestringWND('Memview location changed to: ', getTerminalHWND);
            end;
            writehexlnWND(MEM_LOC, getTerminalHWND);
        end;
    end;
end;

procedure init();
begin
    tracer.push_trace('memview.init');
    terminal.registerCommand('MEMVIEW', @Run, 'View a location in memory [MEMVIEW <Location>]');
    Colors:= combineColors($0000, $FFFF);
    Changed:= combineColors($F800, $FFFF);
    Mode:= tmHex;
end;

end.