/*
What's The Pointer?
By: Thomas Powers

An experiment in everything you're never supposed to do with pointers
This is a memory peek/poke program

You can browse memory as long as it's not a chunk of memory allocated to another app.
When you get pages of ?'s symbols that means that area of memory is inaccessible

Use the scan up (insert) or scan down (delete) key to search for the next block
of memory that is readable

You can use the 'w' key to alter the currently selected byte.
*/

#include <iostream>
#include <iomanip>
using namespace std;
#include <conio.h>	// needed for the _getch() function
#include <windows.h>
#include <string>
#include <cmath> 

const int ROWS=15;
const int COLS=16;
const int BLOCK_SIZE=ROWS*COLS;

void displayBlock(unsigned char *start, const unsigned char *homeAddr, unsigned int=0, unsigned int=0);
unsigned char *scan(unsigned char *, int);
void write(unsigned char *addr, unsigned int rowOffet=0, unsigned int colOffset=0);

enum COLOURS {
	COLOUR_DEFAULT	= 15,
	COLOUR_HIGHLIGHT= 42,
	COLOUR_OTHER	= 14
};

enum KEYS {
	ARROW_KEY	= -32,
	KEY_UP		= 72,
	KEY_DOWN	= 80,
	KEY_LEFT	= 75,
	KEY_RIGHT	= 77,
	HOME		= 71,
	PG_UP		= 73,
	PG_DOWN		= 81,
	INS		= 82,
	DEL		= 83
};

const int LOW_INVALID_CHAR=7;
const int HIGH_INVALID_CHAR=13;
const string INVALID_CHAR_NAME[7]={"Bell", "Backspace", "Tab", "Line Feed", "Home", "Form Feed", "New Line"};
enum CHARS {
	CHR_BELL	= 7,
	CHR_BACKSPACE	= 8,
	CHR_TAB		= 9,
	CHR_LINEFEED	= 10,
	CHR_HOME	= 11,
	CHR_FORMFEED	= 12,
	CHR_NEWLINE	= 13	
};

int main(int argc, char *argv[]) {
	
	// this is our magic floating pointer (which is not a floating point, just a char)
	unsigned char *p = new unsigned char;

	// It might be interesting to start at a variable, focusing us somewhere in main's stackframe
	// otherwise, just let p remain uninitialized
	unsigned char s[]="[Welcome Home]";

	// check the command line arguments
	for ( int i=1; i < argc; i++ ) {
		if ( strcmp(argv[i],"-mainframe")==0 ) { 
			p=s;
			break;
		}
		if ( strcmp(argv[i],"-?")==0 ) {
			cout << "Usage: To try to start you out near main()'s stack frame, use '-mainframe'\n"
				 << "This will point to a local variable (a char array) in main()\n";
			system("pause");
		}
	}

	unsigned int selectRow=0, selectCol=0;

	// make a copy of p so that we can always get back home
	unsigned char *home=p;

	char inp;  // some place to get a key input from the user

	// the main loop
	do {
		displayBlock(p,home,selectRow,selectCol);
		
		inp=_getch();
		if ( inp == ARROW_KEY ) {
			// arrow keys, home, ins, del, etc.. actually give _getch() two chars
			// if the first one was -32 we know this is happening
			inp=_getch();  // read from the buffer the 2nd character (which arrow key?)
			switch ( inp ) 
			{
				case KEY_RIGHT:
					selectCol++;
					selectCol%=COLS;
					break;
				case KEY_LEFT:
					selectCol--;
					selectCol%=COLS;
					break;
				case KEY_UP:
					selectRow--;
					selectRow%=ROWS;
					break;
				case KEY_DOWN:
					selectRow++;
					selectRow%=ROWS;
					break;
				case PG_UP:
					p=p-BLOCK_SIZE;
					break;
				case PG_DOWN:
					p=p+BLOCK_SIZE;
					break;
				case HOME:
					p=home;
					selectRow=0;
					selectCol=0;
					break;
				case 88:
					// uppercase X
					cout << "\nGoodbye\n";
					break;
				case 120:	
					// lowercase x
					cout << "\nGoodbye\n";
					break;	
				case INS:
					p=scan(p,-1);
					break;
				case DEL:
					p=scan(p,1);
					break;
			}
		} else {
			// non arrow/control key
			if ( inp == 'w' || inp == 'W' ) {
				// write (poke)
				write(p,selectRow,selectCol);
			}
		}
	} while (inp != 'x' && inp != 'X');

	return 0;
}

void displayBlock(unsigned char *start, const unsigned char *homeAddr, unsigned int selectRow, unsigned int selectCol) {
	
	HANDLE hConsole;
	hConsole = GetStdHandle(STD_OUTPUT_HANDLE);

	// set default color (white)
	SetConsoleTextAttribute(hConsole,COLOUR_OTHER);

	system ("cls");
	cout << "Block starting at address: " << hex << long(start);
	cout << " ("<< dec << long( fabs(double(start+selectCol+(selectRow*COLS)-homeAddr)) ) << " Bytes from home)";
	cout << endl;
	cout << "--------------------------------------------------------------------------------\n";

	unsigned char chr[ROWS][COLS];
	
	int r=0, c=0;

	// read the block into a 2d array
	for ( int i=0; i < BLOCK_SIZE; i++ ) {
		// next row?
		if ( i>0 && i%COLS==0 ) { r++; c=0; }

		// try to get the byte from this location, otherwise use '?' if it's inaccessible memory
		__try { chr[r][c]=start[i]; } __except ( true ) { chr[r][c]='?'; }

		// next column
		c++;
	}

	// display the array
	for ( r=0; r < ROWS; r++ ) {
		SetConsoleTextAttribute(hConsole,COLOUR_DEFAULT);
		// print row address
		cout << hex << long( start+(COLS*r) ) << " :";

		// print hex values
		for ( c=0; c < COLS; c++ ) {
			cout << " ";  // leading space, not highlighted
			if ( r==selectRow && c==selectCol ) { 
				SetConsoleTextAttribute(hConsole,COLOUR_HIGHLIGHT); // highlight (inverted cyan)
			} else {
				SetConsoleTextAttribute(hConsole,COLOUR_DEFAULT); // white
			}
			cout << setw(2) << setfill('0') << hex << int(chr[r][c]);
			SetConsoleTextAttribute(hConsole,15); // white
		}
		
		// print ascii characters
		cout << " : ";
		for ( c=0; c < COLS; c++ ) {
			if ( r==selectRow && c==selectCol ) 
				SetConsoleTextAttribute(hConsole,COLOUR_HIGHLIGHT); // highlight (inverted cyan)
			else
				SetConsoleTextAttribute(hConsole,COLOUR_DEFAULT); // white
			// unless it's a character that's going to be a trouble maker
			if ( chr[r][c] != NULL && ( chr[r][c] < LOW_INVALID_CHAR || chr[r][c] > HIGH_INVALID_CHAR ) ) {
				cout << chr[r][c]; 
			} else {
				cout << ' ';
			}
		}

		// new line, next row
		cout << endl;
	}

	SetConsoleTextAttribute(hConsole,COLOUR_OTHER); // yellow
	cout << "\n[Arrow Keys] select byte, [PG UP]/[PG DN] read previous/next block"
		 << "\n[INS]/[DEL] Scan up/down, [HOME] starting location, [X] exit, [W] write\n";	
	
	cout << "\nSelected Byte: [";
	if ( chr[selectRow][selectCol] != NULL && (
		 chr[selectRow][selectCol] < LOW_INVALID_CHAR || 
		 chr[selectRow][selectCol] > HIGH_INVALID_CHAR) )
	{
		cout << chr[selectRow][selectCol];
	} else if ( chr[selectRow][selectCol] == NULL )	{
		cout << "NULL";
	} else {
		cout << INVALID_CHAR_NAME[ chr[selectRow][selectCol] - LOW_INVALID_CHAR ];
	}
	cout << "] HEX:" << hex << int(chr[selectRow][selectCol]) << " Decimal: " << dec << int(chr[selectRow][selectCol]) << endl;
	cout << "Address: " << hex << long( start+(selectCol)+(selectRow*COLS) );


	SetConsoleTextAttribute(hConsole,COLOUR_DEFAULT);
}

unsigned char *scan(unsigned char *p, int direction) {
	cout << "\nScanning for accessible memory, this may take... forever.....";
	char chr=NULL;
	bool readOK=false;

	do {
		__try { 
			chr=*p; 
			readOK=true;
		} __except ( true ) {
			p+=(BLOCK_SIZE*direction);
			readOK=false;			
		}
	} while ( !readOK );

	return p;
}

void write(unsigned char *addr, unsigned int rowOffset, unsigned int colOffset)
{
	unsigned int writeVal;

	addr+=colOffset+(rowOffset*COLS);

	cout << "\nEnter byte value (in decimal, 0-255, any invalid value (such as -1) will abort)\nByte Value: ";
	cin >> writeVal;
	
	if ( writeVal >=0 && writeVal <=255 ) {
		__try { 
			*addr=char(writeVal); 
		} __except ( true ) {
			cout << "\nWrite failed\n";
			system("pause");
		}
	} else {
		cout << "\nValue is invalid, aborting.\n";
		system("pause");
	}
}