RO-LAST-POINT(6) Games Manual RO-LAST-POINT(6)

NAME

Ragnarok Online Last Point — A utility to instantly warp back to the last save point, designed to combat map bugs and 'blackholes'.

METADATA

Platform: Ragnarok Online
Release: 2002-03-06
Status: Source available

SYNOPSIS

LastPointV2.exe

DESCRIPTION

Last Point was a critical rescue utility for Ragnarok Online, developed to combat frequent map bugs and server-side desyncs that would trap players in “blackholes.” While the game naturally provided a “Return to Save Point” feature, it was strictly reserved for the death screen.

Starting with version 2.0, this tool became completely version-independent. Instead of relying on hardcoded memory offsets, it utilized a dynamic memory searching technique to locate the necessary subroutines in any RO English Beta client.

The archive includes both the compiled binary and relevant source components:

  • LastPointV2.exe — The compiled utility featuring dynamic memory searching.
  • hackcodes.h — Signature-based patterns and ASM subroutines for version-independent patching.
  • Process.cpp — Implementation of the signature scanner and memory injection logic.

KEY FEATURES

  • Version Independent — Uses signature scanning to remain functional across various RO client versions without requiring manual updates.
  • Always-On Save Point Button — Patches the in-game escape menu (ESC) so that the “Return To Last Save Point” option is always visible and clickable, even when the character is alive.
  • Instant Warp Hotkey — Adds a global hotkey (ALT + L) that triggers the save-point return packet instantly from any location.
  • Full Screen Support — (v2.1) Includes a fix for a common crash encountered by players running the game in full-screen mode.

NOTES

This program became extremely useful and popular due to the various warp bugs and players getting stuck in areas. It was one of the few hacks that the community generally viewed as a necessary tool rather than an unfair advantage, as it saved people from losing their characters to server-side glitches.

ATTACHMENTS (Browsing /usr/games/hacks/)

Path: /usr/games/hacks/ragnarok_online/Last Point/readme.txt2253 bytes
Ragnarok Online Last Point By Arsenic

What's new in version 2.1
-------------------------
- Fixed an error that made full screen mode user to crash when applying.

What's new in version 2.0
-------------------------
- Version independent! This program will work on all RO English Beta patches with a technique
  of dynamic memory searching/patching.
- Completely redone the coding.

What's new in version 1.4
-------------------------
- Updated to work on Beta patch 67.
- The "Return To Last Save Point" button in the in-game escape menu will always be present
  although you are not dead.

What's new in version 1.3
-------------------------
- Updated to work on Beta patch 64.

What's new in version 1.2
-------------------------
- Now works for Win2k users.

What's new in version 1.1
-------------------------
- Updated to work on Beta patch 63.

Steps to activate the hack:
1) Run your RO client.
2) Press Alt+Tab on your keyboard to minimize the game window.
3) Run this program.
4) Click on "Apply".
5) Come back to the game and enjoy.
		
What is this hack all about?
There's nothing really new in the hack, it still contains the same good old features ;)

First:
The "Return To Last Save Point" option will always appear when you open the menu with ESCAPE in
game.

Second:
Instant warp to the last save point in game by pressing the keys ALT + L on your keyboard. Doing
so is exactly the same as hitting the "Return to last save point" button when you are dead.

The purpose of this hack is to get back at your last save point whenever you feel like it. This
program has become extremely useful and popular since the latest warp bugs and players stucked
in different areas. Using this hack is the best way to get out of those "blackholes".

Enjoy.

--------------------------------------

~ Arsenic

Don't waste time e-mailing me, if you have questions or want to contact me just drop by the
dedicated Ragnarok Online hacking forum on Cheatlist at:

http://forums.cheatlist.com/phpBB2/forumdisplay.php?s=&forumid=30

You need to register to see or post messages. Everyone is welcome to join.
        
Web page : http://onesided.cjb.net 
           www.onesided.da.ru  (Mirror)
Path: /usr/games/hacks/ragnarok_online/Last Point/hackcodes.h4310 bytes
#ifndef _HACKCODES
#define _HACKCODES

char *gameWindow = "Ragnarok";
char *gameExe	 = "Ragexe.exe";

//A call from the game keyboard hook to our own subroutine -> ALT + l/L
class ALTHOME_KEY_TO_LAST_POINT_PATCH
{
 public:
 	DWORD Address;	//Memory address to patch
 	BYTE *Search;	//Datas to search to find address
	int Size;	//Size of datas
	BYTE *Old;	//Old datas for unload
	int OldSize;	//Old datas size
 
	ALTHOME_KEY_TO_LAST_POINT_PATCH()
	{
		BYTE tmp[] = {0x81,0xFD,0x30,0xF0,0x00,0x00,0x77,0x15,0x74,0x25,0x8B,0xC5};
		BYTE oldbuf[] = {0x2d,0x00,0xf1,0x00,0x00};
		
		Address = 0;
		Size = sizeof(tmp);
		OldSize = sizeof(oldbuf);		
		Search = new BYTE[Size];
		memcpy( Search, tmp, Size );		
		Old = new BYTE[OldSize];
		memcpy( Old, oldbuf, OldSize );
		
		sOffset = 31;
	}
	
	~ALTHOME_KEY_TO_LAST_POINT_PATCH()
	{
		delete [] Search;
		delete [] Old;
	}
	
	void SetAddress()
	{
		if (Address)
			Address += sOffset; //Compute address to patch
	}
 private:
 	signed int sOffset; //Offset for found Address
}KeyCall;

//Special game pointer to retrieve for LastPoint game call
class GAME_POINTER_LAST_POINT_CALL
{
 public:
 	DWORD Address;	//Memory address to retrieve datas
 	BYTE *Search;	//Datas to search to find address
	int Size;	//Size of datas
 
	GAME_POINTER_LAST_POINT_CALL()
	{
		BYTE tmp[] = {0x8b,0x16,0x6a,0x00,0x6a,0x00,0x6a,0x00,0x6a,0x1b,0x8b,0xce};
		
		Address = 0;
		Size = sizeof(tmp);		
		Search = new BYTE[Size];
		memcpy( Search, tmp, Size );		
		
		sOffset = -80;
	}
	
	~GAME_POINTER_LAST_POINT_CALL()
	{
		delete [] Search;
	}
	
	void SetAddress()
	{
		if (Address)
			Address += sOffset; //Compute address to patch
	}
 private:
 	signed int sOffset; //Offset for found Address
}LPPtr;

//ESC Menu patches
class ESCAPE_MENU_PATCH
{
 public:
 	DWORD Address;	//Memory address to patch
 	BYTE *Search;	//Datas to search to find address
	int Size;	//Size of datas
	BYTE *New;	//New datas to write
	BYTE *Old;	//Old datas for unload
	int OldSize;	//Old datas size
	
 	DWORD Address2;
 	BYTE *New2;	//New datas to write
	BYTE *Old2;	//Old datas for unload
	int OldSize2;	//Old datas size
 
	ESCAPE_MENU_PATCH()
	{
		BYTE tmp[] = {0x01,0x00,0x00,0x6a,0x13,0x8b,0xce};
		BYTE newbuf[] = {0x00};
		BYTE newbuf2[] = {0x90,0xe9};
		BYTE oldbuf[] = {0x09};
		BYTE oldbuf2[] = {0x0f,0x85};
		
		Address = 0;
		Size = sizeof(tmp);
		OldSize = sizeof(oldbuf);		
		Search = new BYTE[Size];
		memcpy( Search, tmp, Size );	
		New = new BYTE[OldSize];
		memcpy( New, newbuf, OldSize );	
		Old = new BYTE[OldSize];
		memcpy( Old, oldbuf, OldSize );
		
		Address2 = 0;
		OldSize2 = sizeof(oldbuf2);
		New2 = new BYTE[OldSize2];
		memcpy( New2, newbuf2, OldSize2 );
		Old2 = new BYTE[OldSize2];
		memcpy( Old2, oldbuf2, OldSize2 );
		
		sOffset = 25;
		sOffset2 = 60;
	}
	
	~ESCAPE_MENU_PATCH()
	{
		delete [] Search;
		delete [] New;
		delete [] New2;
		delete [] Old;
		delete [] Old2;
	}
	
	void SetAddress()
	{
		if (Address)
		{
			Address2 = Address + sOffset2;
			Address += sOffset; //Compute address to patch
		}
	}
 private:
 	signed int sOffset; //Offset for found Address
 	signed int sOffset2;
}EscPatch;

// -- Last Point ASM (The subroutine from the keyboard hook) --
#define LASTPOINTDATA	0x50	// Offset between data+code

#define PLASTPOINT	0x00
struct _LASTPOINTDATA_
{
	DWORD pLastPoint;
}lpd;

void _declspec(naked) LastPointAsm(void)
{
	__asm {
	__asm nop __asm nop __asm nop __asm nop __asm nop //Old datas overwritten -> sub eax, 0000F100
	pushad
	call getaddress
Getaddress:
	pop ebp
	sub ebp, 0x0b			//EBP = Base address of code
	add ebp, LASTPOINTDATA		//EBP = Base address of data
	cmp ebx, 0x6c
	jz LastPoint
	cmp ebx, 0x4c
	jz LastPoint
	popad
	ret	
	
LastPoint:
	mov eax, [ebp+PLASTPOINT]
	mov eax, [eax+0x04]
	mov edx, [eax]
	push 00
	push 00
	push 00
	push 0x1b
	mov ecx, eax
	call dword ptr [edx+0x14]
	popad
	ret
	}
}
void _declspec(naked) LastPointAsm_END(void){}
	
#endif

//ESC menu beta 109
//:00493D1C 7409                    je 00493D27

//Alt+l
//:00520575 81FD30F00000            cmp ebp, 0000F030
//:00520594 2D00F10000              sub eax, 0000F100 ***
Path: /usr/games/hacks/ragnarok_online/Last Point/Process.cpp10370 bytes
#include "stdafx.h"
#include "Process.h"
#include "Snapshot.h"

//Use this function with MFC
HWND FindWindow_( char *lpClassName, char *lpWindowName )
{
	return FindWindow( lpClassName, lpWindowName );
}

//Use this function with MFC
LRESULT SendMessage_( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam )
{
	return SendMessage( hWnd, Msg, wParam, lParam );
}

//Retrieves PID from a Class/Window name
DWORD GetProcessId( char *lpClass, char *lpWindow )
{
	HWND hWnd = FindWindow( lpClass, lpWindow );
	DWORD pid = 0;
		
	if ( hWnd )
		GetWindowThreadProcessId( hWnd, &pid );
		
	return pid;
}

//Returns an open handle to a process
//Process object has PROCESS_ALL_ACCESS	access
//Don't forget to close the handle
HANDLE GetProcessHandle( char *lpClass, char *lpWindow )
{
	HWND hWnd = FindWindow( lpClass, lpWindow );
	DWORD pid = 0;
	HANDLE pHandle = 0;
	
	if ( hWnd )
	{
		GetWindowThreadProcessId( hWnd, &pid );
		pHandle = OpenProcess( PROCESS_ALL_ACCESS, FALSE, pid );
	}
	
	return pHandle;
}


//Retrieves entry point address of a module (Exe only)
//This function searches the module image in the process memory, it only works if the
//process is running.
//To find the entry point of a module on disk, use the GetFileEntryPoint function.
DWORD GetModuleEntryPoint( char *ModuleName )
{
	DWORD EntryPoint = 0;
	DWORD ModBaseAddr;
	DWORD PE_Header_Offset;
	DWORD PE_Header_Addr;
	DWORD EntryPoint_Offset;
	
	DWORD pid = GetpidFromExe( ModuleName ); //From Snapshot.h
	ModBaseAddr = GetModBaseAddr( pid, ModuleName ); //From Snapshot.h
	
	if ( ModBaseAddr )
	{
		HANDLE hProcess = OpenProcess( PROCESS_VM_READ, FALSE, pid );		
		if (!hProcess) return 0;
		
		ReadProcessMemory( hProcess, (void*)(ModBaseAddr + DOSMZ_PE), &PE_Header_Offset, sizeof(DWORD), NULL );
		PE_Header_Addr = ModBaseAddr + PE_Header_Offset;
		ReadProcessMemory( hProcess, (void*)(PE_Header_Addr + PE_ENTRYPOINT), &EntryPoint_Offset, sizeof(DWORD), NULL );
		EntryPoint = ModBaseAddr + EntryPoint_Offset;
		
		CloseHandle( hProcess );
	}
	
	return EntryPoint;
}


//Retrieve the Window Procedure address of a window
DWORD GetWndProcAddr( char *lpClass, char *lpWindow )
{
	DWORD WndProcAddr = 0;
	HWND hWnd = FindWindow( lpClass, lpWindow );
	
	if (hWnd)
		WndProcAddr = GetClassLong( hWnd, GCL_WNDPROC );
		
	return WndProcAddr;
}

//Search byte-patterns in a module (exe or dll) and return offset
//Return 0 if not found
DWORD MemSearch( char *ModuleName, BYTE Data[], const int DataLen )
{
	ModuleInfo mi;	
	DWORD pid = GetpidFromExe( ModuleName );
	
	if (!pid) { return 0; } //Process is not running
	
	if ( GetModInfo( pid, ModuleName, mi ) )
	{
		const int BYTESCHUNK = 1000; //Chunk to be searched each time
		DWORD DataToRead = BYTESCHUNK;
		DWORD readOffset = mi.BaseAddr;
		BYTE fOffset;
		
		HANDLE hProcess = OpenProcess( PROCESS_VM_READ, FALSE, pid );
		BYTE ModData[ BYTESCHUNK ]; //Buffer for module datas			
		
		do
		{
			if ( mi.Size - readOffset < BYTESCHUNK )
				DataToRead = mi.Size - readOffset;
			
			ReadProcessBYTES(hProcess, readOffset, (void*) &ModData, DataToRead);
			
			for ( register WORD x = 0; x < DataToRead; x++ )
			{
				fOffset = 0;
				while ( *(ModData + x + fOffset) == Data[fOffset] )
				{
					if ( fOffset < DataLen - 1 )
						fOffset += 1;
						
					else //Found!
					{
						CloseHandle( hProcess );
						return readOffset + x; //Address memory corresponding
					}						
				}
			}
			
			readOffset += BYTESCHUNK;				
						
		}while ( readOffset < mi.EndAddr );
		
		CloseHandle( hProcess );
	}
	
	return 0; //Couldn't retrieve module info or didn't find searched datas
}

//Read datas from a process
// Originally mousepads code
void ReadProcessBYTES(HANDLE hProcess, DWORD lpAddress, void* buf, int len)
{
	DWORD oldprot, dummy = 0;
	VirtualProtectEx(hProcess, (void*) lpAddress, len, PAGE_READWRITE, &oldprot);
	ReadProcessMemory(hProcess, (void*) lpAddress, buf, len, 0);
	VirtualProtectEx(hProcess, (void*) lpAddress, len, oldprot, &dummy);
}

//Write datas to a process
// Originally mousepads code
void WriteProcessBYTES(HANDLE hProcess, DWORD lpAddress, void* buf, int len)
{
	DWORD oldprot,dummy = 0;
	VirtualProtectEx(hProcess, (void*) lpAddress, len, PAGE_READWRITE, &oldprot);
	WriteProcessMemory(hProcess, (void*) lpAddress, buf, len, 0);
	VirtualProtectEx(hProcess, (void*) lpAddress, len, oldprot, &dummy);
}

//Return true if datas at address are exact
bool IsPatchOk( HANDLE hProcess, DWORD address, BYTE Data )
{	
	BYTE buffer;
	
	ReadProcessBYTES(hProcess, address, (void*) &buffer, 1);
	
	if ( buffer == Data )
		return true;
	
	return false;
}

//Compute the opcodes for a x86 call instruction
void MakeCall( BYTE opcodes[5], DWORD lpSource, DWORD lpDest )
{
	opcodes[0] = 0xe8;
	*(DWORD*) (opcodes+1) = lpDest - (lpSource + 5);
}

//Compute the opcodes for a x86 jmp instruction
void MakeJump( BYTE opcodes[5], DWORD lpSource, DWORD lpDest )
{
	opcodes[0] = 0xe9;
	*(DWORD*) (opcodes+1) = lpDest - (lpSource + 5);
}

// Originally mousepads/thohell code
// Copy original codes to the beggining of the Dest function
void InterceptCopy(HANDLE hProcess, int inst, DWORD lpSource, DWORD lpDest, int len)
{
	BYTE* buffer = new BYTE[len];
	ReadProcessBYTES(hProcess, lpSource, buffer, len);
	WriteProcessBYTES(hProcess, lpDest, buffer, len);

	if ( inst == 0xe8 )
		MakeCall( buffer, lpSource, lpDest );
	else
		MakeJump( buffer, lpSource, lpDest );
		
	memset(buffer + 5, 0x90, len - 5);		// nops

	WriteProcessBYTES(hProcess, lpSource, buffer, len);
	
	delete buffer;
}

//Modified function of mousepad/thohell, doesn't copy overwritten datas
void Intercept( HANDLE hProcess, int inst, DWORD lpSource, DWORD lpDest, int len )
{
	BYTE* buffer = new BYTE[len];
	
	if ( inst == 0xe8 )
		MakeCall( buffer, lpSource, lpDest );
	else
		MakeJump( buffer, lpSource, lpDest );
		
	memset(buffer + 5, 0x90, len - 5);	// NOPS

	WriteProcessBYTES(hProcess, lpSource, buffer, len);
	
	delete buffer;
}	

/***************************** Memory allocation ******************************/

#define DATA			0x200	// Offset between data+code
//Offsets for mem alloc structure:
#define PGETPROCESSHEAP		0x00
#define PHEAPALLOC		0x04
#define ALLOCSIZE		0x08
#define PALLOCREGION		0x0c
#define OLDWNDPROC		0x10
struct MEMALLOCDATA
{
	DWORD pGetProcessHeap; 	//GetProcessHeap API
	DWORD pHeapAlloc;	//HeapAlloc API
	DWORD AllocSize;	//Size in bytes to allocate
	DWORD pAllocRegion; 	//Pointer to the allocated memory region to retrieve
	BYTE OldWndProc[5];	//WndProc datas that we temporarily overwrite
};
	
void _declspec(naked) AllocAsm(void)
{
	__asm {
	pushad
	call getaddress
Getaddress:
	pop ebp
	sub ebp, 0x06			//EBP = Base address of code
	add ebp, DATA			//EBP = Base address of data
	mov eax, [esp+0x2c]		//uMsg
	cmp eax, WM_APP
	jz AllocMem
Quit:
	mov ebx, [esp+0x20]		//The return address
	sub ebx, 5
	mov [esp+0x20], ebx		//Little trick to return at the WndProc beginning ;)
	mov eax, [ebp+OLDWNDPROC]
	mov [ebx], eax			//Rewrite old WndProc datas
	add ebp, 4
	add ebx, 4
	mov al, [ebp+OLDWNDPROC]
	mov [ebx], al			//Rewrite old WndProc datas
	popad
	ret				//Return to the beginning of WndProc!	
	
AllocMem:
	mov eax, [ebp+ALLOCSIZE]
	push eax			//Size
	push HEAP_ZERO_MEMORY		//Flags
	mov eax, [ebp+PGETPROCESSHEAP]
	call eax
	push eax			//Default heap handle returned
	mov eax, [ebp+PHEAPALLOC]
	call eax			//Call HeapAlloc
	mov [ebp+PALLOCREGION], eax	//Put base address returned into struct data member
	jmp Quit
	}
}	
void _declspec(naked) AllocAsm_END(void){}

//Allocates memory in a target process
//Returns the base addr of the allocated memory space
//Returns NULL if failed
DWORD MemAllocProcess( char *lpWindow, char *ExeName, DWORD size )
{
	MEMALLOCDATA mad;	
	DWORD WndProcAddr, PatchAddr, DataAddr;
	HANDLE hProcess;
	BYTE oldWndProcData[5];
	HWND hWnd = FindWindow( lpWindow, lpWindow );
	
	WndProcAddr = GetClassLong( hWnd, GCL_WNDPROC ); //Get WndProc address
	if (!WndProcAddr) { return 0; } //Wrong window name, or isn't running
	DWORD pid = GetpidFromExe( ExeName );	
	if (!pid) { return 0; }		//Wrong exe name, or isn't running
	PatchAddr = GetModBaseAddr( pid, ExeName );
	PatchAddr += 0x400;
	DataAddr = PatchAddr + DATA;
	
	//Initiate the structure datas
	mad.AllocSize = size;
	mad.pAllocRegion = NULL;
	
	HMODULE hModule = GetModuleHandle("Kernel32.dll");
		if (!hModule) { MessageBox(NULL, "Unable to get handle of KERNEL32.DLL.", "Error", MB_ICONERROR); return -1;}
		
	mad.pGetProcessHeap = (DWORD)GetProcAddress(hModule, "GetProcessHeap");
	if (!mad.pGetProcessHeap ) { MessageBox(NULL, "Unable to get address of GetProcessHeap in KERNEL32.DLL.", "Error!", MB_ICONERROR); return -1;}
	
	mad.pHeapAlloc = (DWORD)GetProcAddress(hModule, "HeapAlloc");
	if (!mad.pHeapAlloc ) { MessageBox(NULL, "Unable to get address of HeapAlloc in KERNEL32.DLL.", "Error!", MB_ICONERROR); return -1;}
			
	hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, pid ); //Open process
	
	if ( hProcess )
	{
		//Copy old datas from WndProc
		ReadProcessBYTES( hProcess, WndProcAddr, oldWndProcData, 5 );
		memcpy( &mad.OldWndProc, &oldWndProcData, 5 );
		
		//Change access protection
		DWORD dum = 0;
		VirtualProtectEx(hProcess,(void*)DataAddr,sizeof(MEMALLOCDATA),PAGE_READWRITE,&dum);
		VirtualProtectEx(hProcess,(void*)WndProcAddr,5,PAGE_READWRITE,&dum);
		
		// Patch in our code
		WriteProcessBYTES( hProcess, PatchAddr, AllocAsm, FUNCTLEN(AllocAsm));	
		// Patch in our data
		WriteProcessBYTES( hProcess, DataAddr, &mad, sizeof(MEMALLOCDATA) );
				
		//Hook the WndProc and make it calls to our own function
		Intercept( hProcess, 0xe8, WndProcAddr, PatchAddr, 5 );
		
		// Send WM_APP to trigger the memory allocation
		SendMessage( hWnd, WM_APP,0,0);
		
		//Retrieve base address of the new allocated memory space
		ReadProcessBYTES( hProcess, DataAddr+PALLOCREGION, (void*)&mad.pAllocRegion, 4 );
		
		CloseHandle( hProcess );
		return mad.pAllocRegion;
	}
	
	return 0;
}
/***************************** Memory allocation END **************************/

TECHNOLOGIES

  • C++
  • Memory Patching
  • Exploit
int03h.com circa 2002 RO-LAST-POINT(6)