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/)
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)#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 ***#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