ABOUT
WHY JAVASCRIPT?
SAFETY
GET STARTED
LEGALITY
COMMON CODE
app_controller.js
game_instance.js
offsets.js
To celebrate the release of Robot, I have created some quick demos to show off its capabilities. Each day for the next eight days, I'll be showing practical examples of code which implements a part of a WoW bot. Obviously, this is nothing we haven't seen before, but it'll still be a good learning experience for many wishing to write bots. All code here will be written in JavaScript (nothing to do with Java) and is compatible with WoW 6.2.4.21463 for Windows (not being kept up to date). Hopefully by the end, you will see the benefits of writing bots in JavaScript and maybe even develop some of your own to showcase here! I look forward to your feedback and suggestions.
WHY JAVASCRIPT?
Traditionally, bots have been written in C++, C#, VB.net and AutoIt. And that's been fine for many years. The problem is that some of these languages are really complicated for beginners (C++). Some are not entirely platform-independent (C#, VB.net, and AutoIt). Some are antiquated (VB.net). And some lack the power of other programming languages (AutoIt). Furthermore, all of these languages suffer from the fact that you have to write a lot of things yourself, things like libraries to write a bot.
All these problems have been plaguing the botting community for years. But how can we fix them? Well, since the introduction of Node.js in 2009, we've seen a massive explosion of interest in JavaScript. Thousands of libraries doing all sorts of neat things have been written, and with the new language specifications, JavaScript has never looked more attractive. Some advantages of JavaScript include that it's very easy to pick up, a lot of developers already know how to use it, it has a thriving community and it's completely cross-platform. Furthermore, because there is no compile step you can just write your program and run it!
Sounds great right? But how can you use it to write bots? The answer is to use robot-js. This library is the final missing link enabling everyone to write system automating bots for any purpose. And in keeping with the JavaScript tradition, no compiler is needed. Just write and run!
All these problems have been plaguing the botting community for years. But how can we fix them? Well, since the introduction of Node.js in 2009, we've seen a massive explosion of interest in JavaScript. Thousands of libraries doing all sorts of neat things have been written, and with the new language specifications, JavaScript has never looked more attractive. Some advantages of JavaScript include that it's very easy to pick up, a lot of developers already know how to use it, it has a thriving community and it's completely cross-platform. Furthermore, because there is no compile step you can just write your program and run it!
Sounds great right? But how can you use it to write bots? The answer is to use robot-js. This library is the final missing link enabling everyone to write system automating bots for any purpose. And in keeping with the JavaScript tradition, no compiler is needed. Just write and run!
SAFETY
Exactly how safe are these bots? Well, since this is the first time we've seen something like this, I have no idea. But I suspect writing bots in JavaScript is arguably the safest method to use. All code is run by the V8 interpreter running within "Node.exe" and if ran in admin mode, regular applications will have no idea what code is actually being ran. The only way to detect it is if you were to directly modify some code in the target application.
GET STARTED
Start by downloading Node.js, as of the time of this writing I recommend getting 5.11. Then create a folder to contain the project. With the command line, cd into your folder and type "npm install robot-js" (no quotes). You're now ready to write bots. You can download any other library you wish but for the purposes of these demo's, only robot-js is required.
For these demo's, there are three files which are used by all eight demos, think of it as a primitive library for implementing common functionality. The code for these files is below. Each demo consists of a single file which is ran with "node <name_of_demo>.js" (no quotes). All demo's are console applications and have no UI associated with them.
For these demo's, there are three files which are used by all eight demos, think of it as a primitive library for implementing common functionality. The code for these files is below. Each demo consists of a single file which is ran with "node <name_of_demo>.js" (no quotes). All demo's are console applications and have no UI associated with them.
LEGALITY
None of the code in the demos infringe on anybody's copyright. The demos should also only be used for educational purposes, specifically for the purposes of learning robot-js. You should never use these demos to break the terms of service of any software. The author(s) of these demos cannot be held liable for any damages caused by the code. By downloading and using the code, you agree to do so at your own risk. All these demos are licensed under the ZLib license.
COMMON CODE
Includes three files implementing common functionality used by all eight demos. app_controller.js implements the main event loop. game_instance.js represents an instance of WoW, along with helpers to select it. offsets.js represents common memory offsets used by all eight demos.
app_controller.js
Code:
"use strict";
//----------------------------------------------------------------------------//
// Constants //
//----------------------------------------------------------------------------//
const GameInstance = require ("./game_instance");
//----------------------------------------------------------------------------//
// Export //
//----------------------------------------------------------------------------//
module.exports = class
{
////////////////////////////////////////////////////////////////////////////////
/// Creates a new application controller
constructor (updateRate, updateFunc)
{
this._updateRate = updateRate;
this._updateFunc = updateFunc;
this._gameInstance = new GameInstance();
console.log ("Select a WoW Window...");
this._heartbeat();
}
////////////////////////////////////////////////////////////////////////////////
/// Main event loop executed by the heartbeat
_eventLoop()
{
// Waits for a game to get selected
if (!this._gameInstance.isSelected())
{
this._gameInstance.selectByActiveWindow();
return;
}
// Ensures the game is still running
if (!this._gameInstance.isRunning())
{
console.log ("Select a WoW Window...");
this._gameInstance.deselect(); return;
}
// Checks whether the player is in-game
if (!this._gameInstance.memory.readBool
(this._gameInstance.offsets.GameState +
this._gameInstance.module)) return;
// Call the specified update function
this._updateFunc (this._gameInstance);
// Don't forget to reset memory cache
this._gameInstance.memory.clearCache();
}
////////////////////////////////////////////////////////////////////////////////
/// Performs heartbeat and enqueues the next one
_heartbeat()
{
this._eventLoop();
setTimeout (() =>
this._heartbeat(),
this._updateRate);
}
};
Code:
"use strict";
//----------------------------------------------------------------------------//
// Constants //
//----------------------------------------------------------------------------//
const Robot = require ("robot-js" );
const Offsets = require ("./offsets");
// Shortcuts to Robot classes
const Process = Robot.Process;
const Module = Robot.Module;
const Memory = Robot.Memory;
const Window = Robot.Window;
//----------------------------------------------------------------------------//
// Export //
//----------------------------------------------------------------------------//
module.exports = class
{
////////////////////////////////////////////////////////////////////////////////
/// Creates a new unselected game instance object
constructor() { this.deselect(); }
////////////////////////////////////////////////////////////////////////////////
/// Deselects any currently selected game instance
deselect()
{
this._window = null; // The game window
this._process = null; // The game process
this._is64Bit = false; // If game is 64Bit
this._memory = null; // The game memory
this._module = null; // Main module addr
this._offsets = null; // Correct offsets
}
////////////////////////////////////////////////////////////////////////////////
/// Selects a game instance using the specified process
selectByProcess (process)
{
// Check if arguments are correct
if (!(process instanceof Process))
throw new TypeError ("Invalid arguments");
// Attempt to select the main window
let window = process.getWindows()[0];
return window &&
// Perform window selection
this.selectByWindow (window);
}
////////////////////////////////////////////////////////////////////////////////
/// Selects a game instance using the specified window
selectByWindow (window)
{
// Check if arguments are correct
if (!(window instanceof Window))
throw new TypeError ("Invalid arguments");
// Check if the window title correctly matches
if (window.getTitle() !== "World of Warcraft")
return false;
let process = window.getProcess();
// Ensure that the process was opened
if (!process.isValid()) return false;
let module =
// Get the main executable module
process.getModules (".*\.exe")[0];
if (!module) return false;
module = module.getBase();
// Determine if game is 64Bit
let is64Bit = process.is64Bit();
let offsets = is64Bit ?
// Make sure to select correct offsets
Offsets.Offsets64 : Offsets.Offsets32;
// Create a new memory object
let memory = Memory (process);
if (memory.readString
// Ensure game build is supported
(module + offsets.GameBuild, 6) !==
Offsets.GameBuild) return false;
this._window = window;
this._process = process;
this._is64Bit = is64Bit;
this._memory = memory;
this._module = module;
this._offsets = offsets;
// Create the memory cache
this._memory.createCache
(16384, 4096, 10485760);
return true;
}
////////////////////////////////////////////////////////////////////////////////
/// Selects a game instance by scanning all open processes
selectByFindProcess()
{
for (let p of Process.getList ("Wow.*\.exe"))
{
// Select the first suitable process value
if (this.selectByProcess (p)) return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
/// Selects a game instance by scanning all open windows
selectByFindWindow()
{
for (let w of Window.getList ("World of Warcraft"))
{
// Select the first suitable window value
if (this.selectByWindow (w)) return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
/// Selects a game instance using the current active window
selectByActiveWindow()
{
// Attempt to select the current active window
return this.selectByWindow (Window.getActive());
}
////////////////////////////////////////////////////////////////////////////////
/// Returns true if a game instance is currently selected
isSelected()
{
return this._window !== null;
}
////////////////////////////////////////////////////////////////////////////////
/// Returns true if the selected game instance is running
isRunning()
{
// Ensure a game window is selected
if (!this.isSelected()) return false;
return !this._process.hasExited() &&
this._window .isValid ();
}
////////////////////////////////////////////////////////////////////////////////
/// Various properties to extract game instance information
get window () { return this._window; }
get process() { return this._process; }
get is64Bit() { return this._is64Bit; }
get memory () { return this._memory; }
get module () { return this._module; }
get offsets() { return this._offsets; }
};
Code:
"use strict";
//----------------------------------------------------------------------------//
// Offsets //
//----------------------------------------------------------------------------//
module.exports =
{
GameBuild : "21463",
Offsets32:
{
GameHash : 0xC7FEDA45,
IconHash : 0xA118EC28,
GameBuild : 0x9B24E4,
GameState : 0xDA55B2,
LocalPlayer : 0xD2E4C0,
LocalCont : 0xBB3F7C,
LocalZone : 0xBC0A64,
IsLooting : 0xE165C8,
IsTexting : 0xC1EB08,
MouseGuid : 0xDA5988,
TargetGuid : 0xDA59B8,
Camera:
{
Struct : 0xDA5D58,
Offset : 0x7610,
Origin : 0x08,
Matrix : 0x14,
Fov : 0x38
},
Entity:
{
TableBase : 0xC9D530,
EntryFirst : 0x0C,
EntryNext : 0x3C,
Entry:
{
Type : 0x0C,
Descriptors : 0x04,
Desc:
{
GlobalID : 0x00,
EntityID : 0x24,
DynFlags : 0x28
}
},
Unit:
{
Transport : 0xAB0,
Origin : 0xAC0,
Angle : 0xAD0,
Casting : 0xF98,
CastingStarted : 0xFB0,
CastingWillEnd : 0xFB4,
Channel : 0xFB8,
ChannelStarted : 0xFBC,
ChannelWillEnd : 0xFC0,
Aura:
{
Count1 : 0x1588,
Count2 : 0x1108,
TableBase1 : 0x1108,
TableBase2 : 0x110C,
EntrySize : 0x48,
Entry:
{
Owner : 0x20,
SpellID : 0x30,
Flags : 0x38,
Stacks : 0x39,
Level : 0x3A,
EndTime : 0x40
}
},
Desc:
{
Creator : 0x080,
Health : 0x0F0,
Power : 0x0F4,
HealthMax : 0x10C,
PowerMax : 0x110,
Level : 0x158,
Flags : 0x17C
}
},
NPC:
{
Name1 : 0xC38,
Name2 : 0x07C
},
Player:
{
Money1 : 0x190C,
Money2 : 0x1890,
Arch : 0x1910,
ArchCount : 0x08,
ArchSites : 0x10,
Target : 0x41E8
},
Object:
{
Bobbing : 0x104,
Transport : 0x130,
Origin : 0x140,
Rotation : 0x150,
Transform : 0x278,
Name1 : 0x274,
Name2 : 0x0B4,
Desc:
{
Creator : 0x030,
Display : 0x040
}
}
},
NameCache:
{
TableBase : 0xC6043C,
EntryNext : 0x00,
Entry:
{
Guid : 0x10,
Name : 0x21,
Race : 0x70,
Class : 0x78
}
},
Cooldown:
{
TableBase : 0xC8AC88,
EntryNext : 0x04,
Entry:
{
SpellID : 0x08,
ItemID : 0x0C,
SpellStartTime : 0x10,
SpellDuration : 0x14,
GroupID : 0x18,
GroupStartTime : 0x1C,
GroupDuration : 0x20,
IsActive : 0x24,
GcdStartTime : 0x28,
GcdDuration : 0x30
}
},
BMAH:
{
Count : 0xE536D0,
TableBase : 0xE536D4,
EntrySize : 0x70,
Entry:
{
MarketID : 0x00,
ItemID : 0x08,
MinimumBid : 0x48,
MaximumInc : 0x50,
CurrentBid : 0x58,
TimeLeft : 0x60,
BidCount : 0x68
}
},
Chat:
{
Position : 0xE01894,
TableBase : 0xDA7518,
EntrySize : 0x17E8,
Entry:
{
SenderGuid : 0x0000,
SenderName : 0x0034,
FullMessage : 0x0065,
OnlyMessage : 0x0C1D,
ChannelNum : 0x17D8,
TimeStamp : 0x17E4
}
}
},
Offsets64:
{
GameHash : 0x64C90819,
IconHash : 0xA118EC28,
GameBuild : 0x0F415FC,
GameState : 0x1519A7E,
LocalPlayer : 0x147E680,
LocalCont : 0x124B40C,
LocalZone : 0x125F694,
IsLooting : 0x158D1A4,
IsTexting : 0x12CD4B0,
MouseGuid : 0x151A0B8,
TargetGuid : 0x151A0E8,
Camera:
{
Struct : 0x151A520,
Offset : 0x7768,
Origin : 0x10,
Matrix : 0x1C,
Fov : 0x40
},
Entity:
{
TableBase : 0x135D120,
EntryFirst : 0x18,
EntryNext : 0x68,
Entry:
{
Type : 0x18,
Descriptors : 0x08,
Desc:
{
GlobalID : 0x00,
EntityID : 0x24,
DynFlags : 0x28
}
},
Unit:
{
Transport : 0x1538,
Origin : 0x1548,
Angle : 0x1558,
Casting : 0x1B98,
CastingStarted : 0x1BB0,
CastingWillEnd : 0x1BB4,
Channel : 0x1BB8,
ChannelStarted : 0x1BBC,
ChannelWillEnd : 0x1BC0,
Aura:
{
Count1 : 0x2390,
Count2 : 0x1D10,
TableBase1 : 0x1D10,
TableBase2 : 0x1D18,
EntrySize : 0x68,
Entry:
{
Owner : 0x40,
SpellID : 0x50,
Flags : 0x58,
Stacks : 0x59,
Level : 0x5A,
EndTime : 0x60
}
},
Desc:
{
Creator : 0x080,
Health : 0x0F0,
Power : 0x0F4,
HealthMax : 0x10C,
PowerMax : 0x110,
Level : 0x158,
Flags : 0x17C
}
},
NPC:
{
Name1 : 0x16F0,
Name2 : 0x00A0
},
Player:
{
Money1 : 0x2790,
Money2 : 0x1890,
Arch : 0x2798,
ArchCount : 0x08,
ArchSites : 0x18,
Target : 0x77E8,
},
Object:
{
Bobbing : 0x1E0,
Transport : 0x238,
Origin : 0x248,
Rotation : 0x258,
Transform : 0x4A0,
Name1 : 0x498,
Name2 : 0x0D8,
Desc:
{
Creator : 0x030,
Display : 0x040
}
}
},
NameCache:
{
TableBase : 0x1316E98,
EntryNext : 0x00,
Entry:
{
Guid : 0x20,
Name : 0x31,
Race : 0x88,
Class : 0x90
}
},
Cooldown:
{
TableBase : 0x1354D50,
EntryNext : 0x08,
Entry:
{
SpellID : 0x10,
ItemID : 0x14,
SpellStartTime : 0x18,
SpellDuration : 0x1C,
GroupID : 0x20,
GroupStartTime : 0x24,
GroupDuration : 0x28,
IsActive : 0x2C,
GcdStartTime : 0x30,
GcdDuration : 0x38
}
},
BMAH:
{
Count : 0x15CE6E8,
TableBase : 0x15CE6F0,
EntrySize : 0xA8,
Entry:
{
MarketID : 0x00,
ItemID : 0x08,
MinimumBid : 0x80,
MaximumInc : 0x88,
CurrentBid : 0x90,
TimeLeft : 0x98,
BidCount : 0xA0
}
},
Chat:
{
Position : 0x157627C,
TableBase : 0x151BD20,
EntrySize : 0x17F0,
Entry:
{
SenderGuid : 0x0000,
SenderName : 0x0034,
FullMessage : 0x0065,
OnlyMessage : 0x0C1D,
ChannelNum : 0x17D8,
TimeStamp : 0x17E8
}
}
}
};