Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/drssoccer55/RLBot
Browse files Browse the repository at this point in the history
  • Loading branch information
dtracers committed Jan 21, 2018
2 parents 7b0c01b + 956f7bb commit 62b6a0d
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 63 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# RLBot

<p align="center">
<img width="512" height="158" src="https://github.com/drssoccer55/RLBot/blob/master/images/RLBot.png">
</p>

### Short Description
Saltie is a bot that uses Neural Networks and Machine Learning to learn how to play the game.
It also has tools for training bots and collecting the replays from a lot of distributed computers
Expand Down
103 changes: 91 additions & 12 deletions RLBotInjectorSource/RLBot Injector/Injector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,57 +34,136 @@ class Injector
[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);

[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
private static extern bool GetExitCodeThread(IntPtr hThread, out uint lpExitCode);

private const int PROCESS_ALL_ACCESS = 0x1f0fff;
private const int MEM_COMMIT = 0x1000;
private const int MEM_RELEASE = 0x8000;
private const int PAGE_READWRITE = 0x4;
private const int STILL_ACTIVE = 259;

private static IntPtr hProcess = IntPtr.Zero;
private static IntPtr allocAddress = IntPtr.Zero;
private static IntPtr libThread = IntPtr.Zero;
private static byte[] dllBytes;

private static string getLastFunctionError(string functionName, int errorCode = -1)
{
return string.Format("{0} failed with error code {1}", functionName, errorCode == -1 ? Marshal.GetLastWin32Error() : errorCode);
}

public static bool Inject(int pID, string dllLocation)
private static void cleanUp()
{
if (hProcess != IntPtr.Zero)
{
if (allocAddress != IntPtr.Zero)
{
VirtualFreeEx(hProcess, allocAddress, (uint)dllBytes.Length, MEM_RELEASE);
allocAddress = IntPtr.Zero;
}

CloseHandle(hProcess);
hProcess = IntPtr.Zero;

if (dllBytes != null)
dllBytes = null;
}

if (libThread != IntPtr.Zero)
{
CloseHandle(libThread);
libThread = IntPtr.Zero;
}
}

public static bool Inject(int pID, string dllLocation, ref string error)
{
if (!File.Exists(dllLocation))
{
error = "The RLBot Dll file could not be found";
return false;
}

IntPtr hProcess = OpenProcess(PROCESS_ALL_ACCESS, true, pID);
hProcess = OpenProcess(PROCESS_ALL_ACCESS, true, pID);

if (hProcess == IntPtr.Zero)
{
error = getLastFunctionError("OpenProcess");
return false;
}

byte[] dllBytes = Encoding.ASCII.GetBytes(dllLocation);
IntPtr allocAddress = VirtualAllocEx(hProcess, IntPtr.Zero, (uint)dllBytes.Length, MEM_COMMIT, PAGE_READWRITE);
dllBytes = Encoding.Unicode.GetBytes(dllLocation);
allocAddress = VirtualAllocEx(hProcess, IntPtr.Zero, (uint)dllBytes.Length, MEM_COMMIT, PAGE_READWRITE);

if (allocAddress == IntPtr.Zero)
{
error = getLastFunctionError("VirtualAllocEx");
cleanUp();
return false;
}

IntPtr kernelMod = GetModuleHandle("kernel32.dll");
IntPtr loadLibAddr = GetProcAddress(kernelMod, "LoadLibraryA");

if (kernelMod == IntPtr.Zero | loadLibAddr == IntPtr.Zero)
if (kernelMod == IntPtr.Zero)
{
error = getLastFunctionError("GetModuleHandle");
cleanUp();
return false;
}

IntPtr loadLibAddr = GetProcAddress(kernelMod, "LoadLibraryW");

if (loadLibAddr == IntPtr.Zero)
{
error = getLastFunctionError("GetProcAddress");
cleanUp();
return false;
}

WriteProcessMemory(hProcess, allocAddress, dllBytes, (uint)dllBytes.Length, 0);
IntPtr libThread = CreateRemoteThread(hProcess, IntPtr.Zero, 0, loadLibAddr, allocAddress, 0, IntPtr.Zero);
if (WriteProcessMemory(hProcess, allocAddress, dllBytes, (uint)dllBytes.Length, 0) == false)
{
error = getLastFunctionError("WriteProcessMemory");
cleanUp();
return false;
}

libThread = CreateRemoteThread(hProcess, IntPtr.Zero, 0, loadLibAddr, allocAddress, 0, IntPtr.Zero);

if (libThread == IntPtr.Zero)
{
error = getLastFunctionError("CreateRemoteThread");
cleanUp();
return false;
}
else
{
WaitForSingleObject(libThread, 5000);
CloseHandle(libThread);
}

VirtualFreeEx(hProcess, allocAddress, (uint)dllBytes.Length, MEM_RELEASE);
CloseHandle(hProcess);
uint exitCode;

if (GetExitCodeThread(libThread, out exitCode) == false)
{
error = getLastFunctionError("GetExitCodeThread");
cleanUp();
return false;
}

if (exitCode == STILL_ACTIVE)
{
error = "The remote thread has not terminated within 5 seconds";
cleanUp();
return false;
}
else if (exitCode == 0)
{
error = "The remote LoadLibraryW call has failed";
cleanUp();
return false;
}
}

cleanUp();
return true;
}
}
Expand Down
11 changes: 7 additions & 4 deletions RLBotInjectorSource/RLBot Injector/MainForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public MainForm()

private void MainForm_Load(object sender, EventArgs e)
{
string[] dllPaths = Directory.GetFiles(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "RLBot*.dll");
string[] dllPaths = Directory.GetFiles(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "RLBot*Core.dll");

if (dllPaths.Length == 0)
{
Expand Down Expand Up @@ -69,7 +69,7 @@ private void injectorTimer_Tick(object sender, EventArgs e)

foreach (ProcessModule module in rlProcess.Modules)
{
if (Regex.IsMatch(module.ModuleName, "RLBot.*.dll"))
if (Regex.IsMatch(module.ModuleName, "RLBot.*Core.dll"))
{
MessageBox.Show("The RLBot Dll has already been injected into Rocket League!\n" +
"Injecting it more than once is not possible.",
Expand All @@ -81,7 +81,9 @@ private void injectorTimer_Tick(object sender, EventArgs e)
}
}

if (Injector.Inject(rlProcess.Id, dllPath))
string error = string.Empty;

if (Injector.Inject(rlProcess.Id, dllPath, ref error))
{
statusLabel.Text = "Injection successful!";
statusLabel.Update();
Expand All @@ -92,7 +94,8 @@ private void injectorTimer_Tick(object sender, EventArgs e)
{
statusLabel.Text = "Injection failed!";
statusLabel.Update();
(new SoundPlayer(Properties.Resources.FailedToInjectTheDll)).PlaySync();
(new SoundPlayer(Properties.Resources.FailedToInjectTheDll)).Play();
MessageBox.Show(string.Format("Failed to inject the RLBot Dll: {0}.", error), "Injection failed", MessageBoxButtons.OK, MessageBoxIcon.Error);
Environment.Exit((int)ExitCodes.INJECTION_FAILED);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("ccman32")]
[assembly: AssemblyProduct("RLBot Injector")]
[assembly: AssemblyCopyright("Copyright © 2017 ccman32")]
[assembly: AssemblyCopyright("Copyright © 2018 ccman32")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

Expand Down
Binary file modified RLBot_Core.dll
Binary file not shown.
Binary file modified RLBot_Injector.exe
Binary file not shown.
19 changes: 18 additions & 1 deletion atba.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,21 @@ paint_finish_1_id = 1978
paint_finish_2_id = 1978
engine_audio_id = 0
trails_id = 0
goal_explosion_id = 1971
goal_explosion_id = 1971

[Participant Loadout Orange]
# Name that will be displayed in game
name = AlwaysTowardsBallAgent
team_color_id = 1
custom_color_id = 1
car_id = 23
decal_id = 0
wheels_id = 818
boost_id = 0
antenna_id = 287
hat_id = 0
paint_finish_1_id = 266
paint_finish_2_id = 266
engine_audio_id = 0
trails_id = 0
goal_explosion_id = 1971
85 changes: 62 additions & 23 deletions bot_manager.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import ctypes
from datetime import datetime
import gzip
from datetime import datetime, timedelta
import importlib
import mmap
import os
Expand All @@ -12,14 +12,18 @@
import bot_input_struct as bi
import game_data_struct as gd
import rate_limiter
import sys
import imp
import traceback


from conversions import binary_converter as compressor
from conversions.input import input_formatter

OUTPUT_SHARED_MEMORY_TAG = 'Local\\RLBotOutput'
INPUT_SHARED_MEMORY_TAG = 'Local\\RLBotInput'
RATE_LIMITED_ACTIONS_PER_SECOND = 60
GAME_TICK_PACKET_REFRESHES_PER_SECOND = 120 # 2*60. https://en.wikipedia.org/wiki/Nyquist_rate
MAX_AGENT_CALL_PERIOD = timedelta(seconds=1.0/30) # Minimum call rate when paused.
REFRESH_IN_PROGRESS = 1
REFRESH_NOT_IN_PROGRESS = 0
MAX_CARS = 10
Expand Down Expand Up @@ -50,6 +54,14 @@ def __init__(self, terminateEvent, callbackEvent, config_file, name, team, index
self.batch_size = 1000
self.upload_size = 20

def load_agent(self, agent_module):
try:
agent = agent_module.Agent(self.name, self.team, self.index, config_file=self.config_file)
except TypeError as e:
print(e)
agent = agent_module.Agent(self.name, self.team, self.index)
return agent

def run(self):
# Set up shared memory map (offset makes it so bot only writes to its own input!) and map to buffer
filename = ""
Expand All @@ -64,29 +76,27 @@ def run(self):
lock = ctypes.c_long(0)
game_tick_packet = gd.GameTickPacket() # We want to do a deep copy for game inputs so people don't mess with em

# Get bot module
agent_module = importlib.import_module(self.module_name)

# Create Ratelimiter
r = rate_limiter.RateLimiter(RATE_LIMITED_ACTIONS_PER_SECOND)
r = rate_limiter.RateLimiter(GAME_TICK_PACKET_REFRESHES_PER_SECOND)
last_tick_game_time = None # What the tick time of the last observed tick was
last_call_real_time = datetime.now() # When we last called the Agent

# Find car with same name and assign index
for i in range(MAX_CARS):
if str(bot_output.gamecars[i].wName) == self.name:
self.index = i
continue

# Get bot module
agent_module = importlib.import_module(self.module_name)
# Create bot from module
try:
agent = agent_module.Agent(self.name, self.team, self.index, config_file=self.config_file)
except TypeError as e:
print(e)
agent = agent_module.Agent(self.name, self.team, self.index)
agent = self.load_agent(agent_module)

if hasattr(agent, 'create_model_hash'):
self.model_hash = agent.create_model_hash()
else:
self.model_hash = 0 #int(hashlib.sha256(self.name.encode('utf-8')).hexdigest(), 16) % 2 ** 64
self.model_hash = 0

self.server_manager.set_model_hash(self.model_hash)

Expand All @@ -101,6 +111,8 @@ def run(self):
old_time = 0
counter = 0

last_module_modification_time = os.stat(agent_module.__file__).st_mtime

# Run until main process tells to stop
while not self.terminateEvent.is_set():
before = datetime.now()
Expand All @@ -120,18 +132,44 @@ def run(self):
print('\n\n\n\n Match has ended so ending bot loop\n\n\n\n\n')
break

# Call agent
controller_input = agent.get_output_vector(game_tick_packet)

# Write all player inputs
player_input.fThrottle = controller_input[0]
player_input.fSteer = controller_input[1]
player_input.fPitch = controller_input[2]
player_input.fYaw = controller_input[3]
player_input.fRoll = controller_input[4]
player_input.bJump = controller_input[5]
player_input.bBoost = controller_input[6]
player_input.bHandbrake = controller_input[7]
# Run the Agent only if the gameInfo has updated.
tick_game_time = game_tick_packet.gameInfo.TimeSeconds
should_call_while_paused = datetime.now() - last_call_real_time >= MAX_AGENT_CALL_PERIOD
if tick_game_time != last_tick_game_time or should_call_while_paused:
last_tick_game_time = tick_game_time
last_call_real_time = datetime.now()

try:
# Reload the Agent if it has been modified.
new_module_modification_time = os.stat(agent_module.__file__).st_mtime
if new_module_modification_time != last_module_modification_time:
last_module_modification_time = new_module_modification_time
print('Reloading Agent: ' + agent_module.__file__)
imp.reload(agent_module)
agent = self.load_agent(agent_module)

# Call agent
controller_input = agent.get_output_vector(game_tick_packet)

if not controller_input:
raise Exception('Agent "{}" did not return a player_input tuple.'.format(agent_module.__file__))

# Write all player inputs
player_input.fThrottle = controller_input[0]
player_input.fSteer = controller_input[1]
player_input.fPitch = controller_input[2]
player_input.fYaw = controller_input[3]
player_input.fRoll = controller_input[4]
player_input.bJump = controller_input[5]
player_input.bBoost = controller_input[6]
player_input.bHandbrake = controller_input[7]

except Exception as e:
traceback.print_exc()

# Workaround for windows streams behaving weirdly when not in command prompt
sys.stdout.flush()
sys.stderr.flush()

current_time = game_tick_packet.gameInfo.TimeSeconds

Expand Down Expand Up @@ -161,6 +199,7 @@ def run(self):

# Ratelimit here
after = datetime.now()

after2 = time.time()
# cant ever drop below 30 frames
if after2 - before2 > 0.02:
Expand Down
6 changes: 6 additions & 0 deletions game_data_struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,12 @@ def rotate_game_tick_packet_boost_omitted(game_tick_packet):
game_tick_packet.gameball.AngularVelocity.Y = -1 * game_tick_packet.gameball.AngularVelocity.Y
game_tick_packet.gameball.Acceleration.X = -1 * game_tick_packet.gameball.Acceleration.X
game_tick_packet.gameball.Acceleration.Y = -1 * game_tick_packet.gameball.Acceleration.Y

# ball touch data
game_tick_packet.gameball.LatestTouch.sHitLocation.X = -1 * game_tick_packet.gameball.LatestTouch.sHitLocation.X
game_tick_packet.gameball.LatestTouch.sHitLocation.Y = -1 * game_tick_packet.gameball.LatestTouch.sHitLocation.Y
game_tick_packet.gameball.LatestTouch.sHitNormal.X = -1 * game_tick_packet.gameball.LatestTouch.sHitNormal.X
game_tick_packet.gameball.LatestTouch.sHitNormal.Y = -1 * game_tick_packet.gameball.LatestTouch.sHitNormal.Y

# Rotate Yaw 180 degrees is all that is necessary.
ball_yaw = game_tick_packet.gameball.Rotation.Yaw
Expand Down
Binary file added images/RLBot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 62b6a0d

Please sign in to comment.