I made something to do exactly that a while ago. The source code is freely available somewhere (I think I posted it on Halomods). I made a few classes for it in C++.
If you're looking to do this on your own for your own benefit, you'll want to look into ReadProcessMemory() and WriteProcessMemory().
EDIT: Here are the classes. God my C++ skills sucked back in 2004. As CrashTECH pointed out, the locations are liable to change from version to version, but IIRC the locations I chose are never deleted and reallocated. You can be sure they are valid once you've found the correct offset. The offset has likely changed since I wrote this. You should use a memory searching tool (TSearch works very well) to find the initial offset, upon which all others are based.
Sorry for the length.
Player.h:
Code:
/*
Player.h
Contains a class that provides functions for reading in and retrieving player information
in Halo.
Copyright 2004 Kybo Ren
*/
#include <windows.h>
#include <string>
#include <algorithm>
#include <cmath>
#include <cstring>
/*
#ifdef _DEBUG
#define CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#endif
//this is a class I have to detect memory leaks for me -- don't worry about it
*/
#define MAX_INSTANCES 12
#define MAX_NAMELENGTH 24
#define PC_DS 1
#define PC_GM 2
#define CE_DS 3
#define CE_GM 4//pc and ce ingame and DS servers
#define PC_DS_CLASS 0x4029CF64
#define PC_DS_SCORE 0x63A090
#define CE_GM_CLASS 0x402AB07A
#define CE_GM_SCORE 0x64C280
#define PC_GM_CLASS 0x0
#define PC_GM_SCORE 0x0//PC ingame is obviously not supported right now.
int my_itoa(const int number, char *data);//base can only be 10, and ASSUMES the data buffer supplied is big enough to hold the number
class Player
{
int player_score;
short player_deaths;
short player_assists;
short player_kills;
short player_betrayals;
short player_suicides;
char player_team;
char player_ingame;
std::string player_name;
std::string player_ipadd;
std::string player_cdkey;
//end of player data
//start of class data
int SIZE;//offset between players
int ASSISTS;//offset of assists from kills
int DEATHS;//offset of deaths from kills (beginning of player info structure)
int SUICIDES;//offset of suicides from kills
int TKS;//offset of TKs for each player from kills
int NAMES;//offset of name from player's kills.
int TEAMS;//IMPORTANT: this is the offset of player teams from player 1 kills! this is an array of characters! ***NOT PART OF THE STRUCTURE***
int PLAYERIN;//IMPORTANT: this is the offset of player teams from player 1 score! this is an array of separate structures! ***NOT PART OF THE PLAYER INFO STRUCTURE**
int PLAYERSZ;//Size of the abovementioned structure
//start of individual class data
unsigned int player_class_location;
unsigned int player_score_location;
unsigned int player_number;
unsigned long process_PID;
HANDLE hProc;//the handle to the process
public:
Player();
Player(const unsigned long &PID, const unsigned int &type, const unsigned int &player_number);
~Player();
int Score();
short Deaths();
short Kills();
short Betrayals();
short Assists();
short Suicides();
char Team();
char In_game();
const char *Name();
int Refresh();
int Refresh_Score();
int Refresh_Kills();
int Refresh_Assists();
int Refresh_Deaths();
int Refresh_Suicides();
int Refresh_Betrayals();
int Refresh_Team();
int Refresh_In_Game();
int Refresh_Name();
//end of player info
//start of class info
void Set_PID(const unsigned long &PID);
void Set_Location(const unsigned long &class_location, const unsigned long &score_location, const unsigned long &ipadd_location);
void Set_Player_Number(const unsigned int &player_num);
void Set_Size(const unsigned long &size);
void Set_Type(const unsigned long &type);
bool Handle_Is_Null();
unsigned int Open_Halo();
};//our player class
Player::Player()
{
SIZE = 0x200;
ASSISTS = 0x8;
DEATHS = 0x12;
SUICIDES= 0x14;
TKS = 0x44;
NAMES = -0x54;
TEAMS = -0x7C;
PLAYERIN= -0x890;
PLAYERSZ= 0x30;
player_score = 0;
player_deaths = 0;
player_kills = 0;
player_betrayals = 0;
player_team = 0;
player_ingame = 0;
player_name = "<NULL>";
player_class_location = 0;
player_score_location = 0;
player_ipadd_location = 0;
player_number = 0;
process_PID = 0;
}
Player::Player(const unsigned long& PID, const unsigned int &type, const unsigned int &player_num)
{
switch(type)
{
case PC_DS:
player_class_location = PC_DS_CLASS;
player_score_location = PC_DS_SCORE;
break;
case CE_GM:
player_class_location = CE_GM_CLASS;
player_score_location = CE_GM_SCORE;
break;
case PC_GM:
player_class_location = PC_GM_CLASS;
player_score_location = PC_GM_SCORE;
break;
default:
break;
}
if( (player_num <= 16) && (player_num >= 1) )//if valid range
{
player_number = player_num;
}
process_PID = PID;//assign PID
SIZE = 0x200;
ASSISTS = 0x8;
DEATHS = 0x12;
SUICIDES= 0x14;
TKS = 0x44;
NAMES = -0x54;
TEAMS = -0x7C;
PLAYERIN= -0x890;
PLAYERSZ= 0x30;
player_score = 0;
player_deaths = 0;
player_kills = 0;
player_team = 0;
player_ingame = 0;
player_name = "<NULL>";
hProc = OpenProcess(PROCESS_VM_READ, FALSE, process_PID);//attempt to open the process
if(hProc == NULL)
{
hProc = NULL;//assign a NULL value
}//we failed. Oh. Noes. We'll try again when refreshing
}
Player::~Player()
{
CloseHandle(hProc);//close our handle
}
int Player::Score()
{
return player_score;
}
short Player::Deaths()
{
return player_deaths;
}
short Player::Suicides()
{
return player_suicides;
}
short Player::Kills()
{
return player_kills;
}
short Player::Betrayals()
{
return player_betrayals;
}
short Player::Assists()
{
return player_assists;
}
char Player::Team()
{
return player_team;
}
char Player::In_game()
{
return player_ingame;
}
const char * Player::Name()
{
return player_name.c_str();
}
int Player::Refresh()
{
int result = 1;// we start with one so we can mutiply
char name[13];//temp buffer for name
ipaddr_union address;//for the IP address
if(hProc == NULL)
{
hProc = OpenProcess(PROCESS_VM_READ, FALSE, process_PID);
if(hProc == NULL)
{
MessageBox(0, "Couldn't open the Halo process for reading. \nIf you are sure Halo is running and you have access, send an email to Kybo Ren at:\nkyboren@gmail.com", "Warning", MB_OK | MB_ICONWARNING);
return 0;//0 is fail
}//we try once more then fail
}
result *= ReadProcessMemory(hProc, reinterpret_cast<void*>((player_score_location) + ((player_number-1)*4) + 0 ), (void*)&player_score , 4, 0);//read scores
result *= ReadProcessMemory(hProc, reinterpret_cast<void*>((player_class_location) + ((player_number-1)*SIZE) + 0 ), (void*)&player_kills , 2, 0);//read kills
result *= ReadProcessMemory(hProc, reinterpret_cast<void*>((player_class_location) + ((player_number-1)*SIZE) + DEATHS ), (void*)&player_deaths , 2, 0);//read deaths
result *= ReadProcessMemory(hProc, reinterpret_cast<void*>((player_class_location) + ((player_number-1)*SIZE) +SUICIDES), (void*)&player_suicides , 2, 0);//read suicides
result *= ReadProcessMemory(hProc, reinterpret_cast<void*>((player_class_location) + ((player_number-1)*SIZE) + ASSISTS), (void*)&player_assists , 2, 0);//read assists
result *= ReadProcessMemory(hProc, reinterpret_cast<void*>((player_class_location) + ((player_number-1)*SIZE) + TKS ), (void*)&player_betrayals, 2, 0);//read TKs
result *= ReadProcessMemory(hProc, reinterpret_cast<void*>((player_score_location) + ((player_number-1)*PLAYERSZ)+ PLAYERIN), (void*)&player_ingame , 1, 0);//read ingame
result *= ReadProcessMemory(hProc, reinterpret_cast<void*>((player_class_location) + ((player_number-1)*SIZE) + TEAMS ), (void*)&player_team , 1, 0);//read team
for(unsigned int name_index = 0; name_index < MAX_NAMELENGTH; name_index += 2)
{
result *= ReadProcessMemory(hProc, reinterpret_cast<void*>((player_class_location) + ((player_number-1)*SIZE) + NAMES + name_index), (void*)&name[(name_index / 2)], 1, 0);//read name
}
name[12] = '\0';//nullify the name
player_name = name;//the name is what we just read
return result;
}
int Player::Refresh_Score()
{
if(hProc == NULL)
{
hProc = OpenProcess(PROCESS_VM_READ, FALSE, process_PID);
if(hProc == NULL)
{
MessageBox(0, "Couldn't open the Halo process for reading. \nIf you are sure Halo is running and you have access, send an email to Kybo Ren at:\nkyboren@gmail.com", "Warning", MB_OK | MB_ICONWARNING);
return 0;//0 is fail
}//we try once more then fail
}//if hProc still is messed
int result = 1;
result *= ReadProcessMemory(hProc, reinterpret_cast<void*>((player_score_location) + ((player_number-1)*4) + 0 ), (void*)&player_score , 4, 0);//read scores
return result;
}
int Player::Refresh_Kills()
{
if(hProc == NULL)
{
hProc = OpenProcess(PROCESS_VM_READ, FALSE, process_PID);
if(hProc == NULL)
{
MessageBox(0, "Couldn't open the Halo process for reading. \nIf you are sure Halo is running and you have access, send an email to Kybo Ren at:\nkyboren@gmail.com", "Warning", MB_OK | MB_ICONWARNING);
return 0;//0 is fail
}//we try once more then fail
}//if hProc still is messed
int result = 1;
result *= ReadProcessMemory(hProc, reinterpret_cast<void*>((player_class_location) + ((player_number-1)*SIZE) + 0 ), (void*)&player_kills , 2, 0);//read kills
return result;
}
int Player::Refresh_Betrayals()
{
if(hProc == NULL)
{
hProc = OpenProcess(PROCESS_VM_READ, FALSE, process_PID);
if(hProc == NULL)
{
MessageBox(0, "Couldn't open the Halo process for reading. \nIf you are sure Halo is running and you have access, send an email to Kybo Ren at:\nkyboren@gmail.com", "Warning", MB_OK | MB_ICONWARNING);
return 0;//0 is fail
}//we try once more then fail
}//if hProc still is messed
int result = 1;
result *= ReadProcessMemory(hProc, reinterpret_cast<void*>((player_class_location) + ((player_number-1)*SIZE) + TKS ), (void*)&player_betrayals, 2, 0);//read TKs
return result;
}
int Player::Refresh_Assists()
{
if(hProc == NULL)
{
hProc = OpenProcess(PROCESS_VM_READ, FALSE, process_PID);
if(hProc == NULL)
{
MessageBox(0, "Couldn't open the Halo process for reading. \nIf you are sure Halo is running and you have access, send an email to Kybo Ren at:\nkyboren@gmail.com", "Warning", MB_OK | MB_ICONWARNING);
return 0;//0 is fail
}//we try once more then fail
}//if hProc still is messed
int result = 1;
result *= ReadProcessMemory(hProc, reinterpret_cast<void*>((player_class_location) + ((player_number-1)*SIZE) + ASSISTS), (void*)&player_assists , 2, 0);//read assists
return result;
}
int Player::Refresh_Deaths()
{
if(hProc == NULL)
{
hProc = OpenProcess(PROCESS_VM_READ, FALSE, process_PID);
if(hProc == NULL)
{
MessageBox(0, "Couldn't open the Halo process for reading. \nIf you are sure Halo is running and you have access, send an email to Kybo Ren at:\nkyboren@gmail.com", "Warning", MB_OK | MB_ICONWARNING);
return 0;//0 is fail
}//we try once more then fail
}//if hProc still is messed
int result = 1;
result *= ReadProcessMemory(hProc, reinterpret_cast<void*>((player_class_location) + ((player_number-1)*SIZE) + DEATHS ), (void*)&player_deaths , 2, 0);//read deaths
return result;
}
int Player::Refresh_Suicides()
{
if(hProc == NULL)
{
hProc = OpenProcess(PROCESS_VM_READ, FALSE, process_PID);
if(hProc == NULL)
{
MessageBox(0, "Couldn't open the Halo process for reading. \nIf you are sure Halo is running and you have access, send an email to Kybo Ren at:\nkyboren@gmail.com", "Warning", MB_OK | MB_ICONWARNING);
return 0;//0 is fail
}//we try once more then fail
}//if hProc still is messed
int result = 1;
result *= ReadProcessMemory(hProc, reinterpret_cast<void*>((player_class_location) + ((player_number-1)*SIZE) +SUICIDES), (void*)&player_suicides , 2, 0);//read suicides
return result;
}
int Player::Refresh_Team()
{
if(hProc == NULL)
{
hProc = OpenProcess(PROCESS_VM_READ, FALSE, process_PID);
if(hProc == NULL)
{
MessageBox(0, "Couldn't open the Halo process for reading. \nIf you are sure Halo is running and you have access, send an email to Kybo Ren at:\nkyboren@gmail.com", "Warning", MB_OK | MB_ICONWARNING);
return 0;//0 is fail
}//we try once more then fail
}//if hProc still is messed
int result = 1;
result *= ReadProcessMemory(hProc, reinterpret_cast<void*>((player_class_location) + ((player_number-1)*SIZE) + TEAMS ), (void*)&player_team , 1, 0);//read team
return result;
}
int Player::Refresh_In_Game()
{
if(hProc == NULL)
{
hProc = OpenProcess(PROCESS_VM_READ, FALSE, process_PID);
if(hProc == NULL)
{
MessageBox(0, "Couldn't open the Halo process for reading. \nIf you are sure Halo is running and you have access, send an email to Kybo Ren at:\nkyboren@gmail.com", "Warning", MB_OK | MB_ICONWARNING);
return 0;//0 is fail
}//we try once more then fail
}//if hProc still is messed
int result = 1;
result *= ReadProcessMemory(hProc, reinterpret_cast<void*>((player_score_location) + ((player_number-1)*PLAYERSZ) + PLAYERIN ), (void*)&player_ingame , 1, 0);//read ingame
return result;
}
int Player::Refresh_Name()
{
if(hProc == NULL)
{
hProc = OpenProcess(PROCESS_VM_READ, FALSE, process_PID);
if(hProc == NULL)
{
MessageBox(0, "Couldn't open the Halo process for reading. \nIf you are sure Halo is running and you have access, send an email to Kybo Ren at:\nkyboren@gmail.com", "Warning", MB_OK | MB_ICONWARNING);
return 0;//0 is fail
}//we try once more then fail
}//if hProc still is messed
int result = 1;
char name[13];
for(unsigned int name_index = 0; name_index < MAX_NAMELENGTH; name_index += 2)
{
result *= ReadProcessMemory(hProc, reinterpret_cast<void*>((player_class_location) + ((player_number-1)*SIZE) + NAMES + name_index), (void*)&name[(name_index / 2)], 1, 0);//read name
}
name[12] = '\0';//nullify the name
player_name = name;//the name is what we just read
return result;
}
void Player::Set_PID(const unsigned long &PID)
{
process_PID=PID;
}
void Player::Set_Location(const unsigned long &class_location, const unsigned long &score_location, const unsigned long &ipadd_location)
{
player_class_location = class_location;
player_score_location = score_location;
player_ipadd_location = ipadd_location;
}
void Player::Set_Player_Number(const unsigned int &player_num)
{
player_number = player_num;
}
void Player::Set_Size(const unsigned long &size)
{
SIZE = size;
}
void Player::Set_Type(const unsigned long &type)
{
switch(type)
{
case PC_DS:
player_class_location = PC_DS_CLASS;
player_score_location = PC_DS_SCORE;
break;
case CE_GM:
player_class_location = CE_GM_CLASS;
player_score_location = CE_GM_SCORE;
break;
case PC_GM:
player_class_location = PC_GM_CLASS;
player_score_location = PC_GM_SCORE;
break;
default:
break;
}
}
bool Player::Handle_Is_Null()
{
return (hProc == NULL);
}
unsigned int Player::Open_Halo()
{
hProc = OpenProcess(PROCESS_VM_READ, FALSE, process_PID);
return (hProc != NULL);//return 1 for success
}
int my_itoa(const int number, char *data)
{
std::string buf;
std::string buffer;
int num = std::abs(number);
const unsigned int numdigits = (log((float)num) / log(10.0f)) + 1;
unsigned int dig_buf;
for(int i = 1; i <= numdigits; ++i)
{
dig_buf = static_cast<int>(std::pow(10.0f, i-1));
buffer = (num % static_cast<int>(std::pow(10.0f, i))) / dig_buf + '0';
buf += buffer;
}
if(number < 0)
{
buf += '-';
}
std::reverse(buf.begin(), buf.end());
std::strcpy(data, buf.c_str());
return (buf.size() > 0);
};
Server.h:
Code:
/*
Server.h
Provides a class interface to be used between your application and Halo.
Copyright 2004 Kybo Ren
*/
#include <vector>
#include "Player.h"
#define MAX_PLAYERS 16
#define ERROR 0
#define PC_DS 1
#define PC_GM 2
#define CE_DS 3
#define CE_GM 4//pc and ce ingame and DS servers
#define PC_DS_GAMETYPE 0x6712D8 //1 = CTF, 2 = Slayer, 3 = Oddball, 4 = KOTH, 5 = Race
#define CE_GM_GAMETYPE 0x0
#define PC_GM_GAMETYPE 0x0//obviously these aren't implemented#nwe
const int CTF = 1;
const int SLAYER = 2;
const int ODDBALL = 3;
const int KOTH = 4;
const int RACE = 5;//gametypes
const char gametypes[5][15] = {"CTF","Slayer","Oddball","KOTH","Race"};//gametype strings
class Server
{
unsigned long server_PID;//the PID of the server
unsigned long server_isEnabled;//is it enabled? did the user check off this box?
unsigned long server_Number;//i.e. 1 for "Halo Console (1)", 2 for "Halo Console (2)"
unsigned long server_Type;//type of server, i.e. Halo PC DS, Halo CE game
unsigned long server_Gametype;//gametype on server
unsigned long server_gametype_loc;//location of gametype on server proc
HANDLE hProc;//handle to the process
HANDLE server_thread;//handle to the thread for this process
public:
Server();//default constructor
~Server();//destructor
std::vector<Player*> Players;//vector of POINTERS to Player objects
ConsoleLogger Console;//for logging the console data if it is a DS
const char *Gametype();//returns gametype string of current gametype on server
const unsigned long PID();
HANDLE Thread();//returns a handle to the current thread
const unsigned long isEnabled();
const unsigned long Number();
const unsigned long Type();//echoes the variables above
const int Refresh(void);//refresh all
const int Set_PID(const unsigned long PID);
const int Set_Thread(HANDLE hThread);
const int Set_Enabled(const unsigned long isEnabled);
const int Set_Number(const unsigned long Number);//sets the variables above
const int Set_Type(const unsigned int Type);//set type
const int Set_All(const unsigned long PID, HANDLE hThread, const unsigned long isEnabled, const unsigned long Number, const unsigned long Type);
};
Server::Server()
{
server_PID = 0;
server_thread = 0;
server_isEnabled = 0;
server_Number = 0;
server_Type = 0;
server_thread = 0;//0 the values
for(unsigned int i=0; i < MAX_PLAYERS; ++i)
{
Players.push_back(new Player(server_PID, server_Type, (i+1)));//insert a new player
}
}
const int Server::Set_All(const unsigned long PID, HANDLE hThread, const unsigned long isEnabled, const unsigned long Number, const unsigned long Type)
{
server_PID = PID;
server_thread = hThread;
server_isEnabled = isEnabled;
server_Number = Number;
server_Type = Type;
switch(Type)
{
case PC_DS:
server_gametype_loc = PC_DS_GAMETYPE;
break;
case CE_GM:
server_gametype_loc = CE_GM_GAMETYPE;
break;
case PC_GM:
server_gametype_loc = PC_GM_GAMETYPE;
break;
default:
server_gametype_loc = PC_DS_GAMETYPE;//weird type, assume PC dedicated server
break;
}
return 0;
}
Server::~Server()
{
for(unsigned int i=0; i < MAX_PLAYERS; ++i)
{
delete Players[i];//delete memory
}
CloseHandle(hProc);//close the handle to the proc
CloseHandle(server_thread);//close the thread handle
}
const unsigned long Server::PID()
{
return server_PID;
}
HANDLE Server::Thread()
{
return server_thread;
}
const unsigned long Server::isEnabled()
{
return server_isEnabled;
}
const unsigned long Server::Number()
{
return server_Number;
}
const unsigned long Server::Type()
{
return server_Type;
}
const int Server::Refresh()
{
int result = 1;
if(hProc == NULL)
{
hProc = OpenProcess(PROCESS_VM_READ, FALSE, server_PID);
if(hProc == NULL)
{
MessageBox(0, "Couldn't open the Halo process for reading. \nIf you are sure Halo is running and you have access, send an email to Kybo Ren at:\nkyboren@gmail.com", "Warning", MB_OK | MB_ICONWARNING);
return 0;//0 is fail
}//we try once more, then fail
}//if hProc still is messed
for(unsigned int i = 0; i < MAX_PLAYERS; ++i)
{
if(Players[i]->Handle_Is_Null())//null handle
{
Players[i]->Open_Halo();//try to open the process
}
result *= Players[i]->Refresh();
}
result *= ReadProcessMemory(hProc, reinterpret_cast<void*>(server_gametype_loc), (void*)&server_Gametype, 1, 0);//read gametype
return result;
}
const int Server::Set_PID(const unsigned long PID)
{
server_PID = PID;
for(unsigned int i=0; i < MAX_PLAYERS; ++i)
{
Players[i]->Set_PID(PID);
}
return 0;
}
const int Server::Set_Thread(HANDLE hThread)
{
if(hThread = NULL)
{
return 1;
}
server_thread = hThread;
return 0;
}
const int Server::Set_Enabled(const unsigned long isEnabled)
{
server_isEnabled = isEnabled;
return 0;
}
const int Server::Set_Number(const unsigned long Number)
{
server_Number = Number;
return 0;
}
const int Server::Set_Type(const unsigned int Type)
{
server_Type = Type;
for(unsigned int i = 0; i < MAX_PLAYERS; ++i)
{
Players[i]->Set_Type(Type);
}//set the type
return Type;
}
const char *Server::Gametype()
{
if(hProc == NULL)
{
hProc = OpenProcess(PROCESS_VM_READ, FALSE, server_PID);
if(hProc == NULL)
{
MessageBox(0, "Couldn't open the Halo process for reading. \nIf you are sure Halo is running and you have access, send an email to Kybo Ren at:\nkyboren@gmail.com", "Warning", MB_OK | MB_ICONWARNING);
return 0;//0 is fail
}//we try once more, then fail
}//if hProc still is messed
int result = 1;
result *= ReadProcessMemory(hProc, reinterpret_cast<void*>(server_gametype_loc), (void*)&server_Gametype, 1, 0);//read gametype
return gametypes[server_Gametype];
}
No guarantees on anything, but it did work when I wrote it. Even if you want to do this as a personal project, this could be a helpful reference. Good luck.