diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..38a2fa4
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+parsers
+__pycache__
+Plugins/__pycache__
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..a8c3483
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "valorlib"]
+ path = valorlib
+ url = https://github.com/swrlly/valorlib.git
diff --git a/ConditionEffect.py b/ConditionEffect.py
new file mode 100644
index 0000000..975d16b
--- /dev/null
+++ b/ConditionEffect.py
@@ -0,0 +1,70 @@
+effect0 = {
+ "Quiet" : 2,
+ "Weak" : 4,
+ "Slowed" : 8,
+ "Sick" : 16,
+ "Dazed" : 32,
+ "Stunned" : 64,
+ "Blind" : 128,
+ "Drunk" : 512,
+ "Confused" : 1024,
+ "StunImmune" : 2048,
+ "Invisible" : 4096,
+ "Paralyzed" : 8192,
+ "Speedy" : 16384,
+ "NinjaSpeedy" : 268435456,
+ "Hallucinating" : 256,
+ "Healing" : 131072,
+ "Damaging" : 262144,
+ "Berserk" : 524288,
+ "Paused" : 1048576,
+ "Stasis" : 2097152,
+ "Invincible" : 8388608,
+ "Invulnerable" : 16777216,
+ "Armored" : 33554432,
+ "ArmorBroken" : 67108864,
+ "ArmorBrokenImmune" : 65536,
+ "SlowedImmune" : 2147483648,
+ "Unstable" : 536870912,
+ "Darkness" : 1073741824,
+ "Bleeding" : 32768
+}
+
+effect1 = {
+ "Swiftness" : 16,
+ "ParalyzeImmune" : 2,
+ "DazedImmune" : 1,
+ "Petrified" : 4,
+ "PetrifiedImmune" : 8,
+ "Cursed" : 32,
+ "CursedImmune" : 64,
+ "Hidden" : 32768,
+ "SamuraiBerserk" : 8388608,
+ "Relentless" : 67108864,
+ "Vengeance" : 134217728,
+ "Alliance" : 536870912,
+ "Grasp" : 4194304,
+ "Bravery" : 262144,
+ "Exhausted" : 524288,
+ "JacketOffense" : 2,
+ "JacketDefense" : 4
+}
+
+effect2 = {
+ "EmpoweredImmunity" : 8,
+ "ConfusedImmunity" : 16,
+ "WeakImmunity" : 32,
+ "BlindImmunity" : 64,
+ "QuietImmunity" : 128,
+ "BleedingImmunity" : 256,
+ "SickImmunity" : 512,
+ "DrunkImmunity" : 1024,
+ "HallucinatingImmunity" : 2048,
+ "HexedImmunity" : 4096,
+ "UnstableImmunity" : 8192,
+ "DarknessImmunity" : 16384,
+ "ExhaustedImmunity" : 32768,
+ "StasisImmune" : 4194304,
+ "CorruptedImmune" : 65536
+}
+
diff --git a/PluginManager.py b/PluginManager.py
new file mode 100644
index 0000000..0f07b84
--- /dev/null
+++ b/PluginManager.py
@@ -0,0 +1,54 @@
+class PluginManager:
+
+
+ def __init__(self):
+ # key is plugin class, value is true if on else false
+ self.plugins = {}
+ # key is packet type, value is set of plugin classes (key for self.plugins)
+ self.hooks = {}
+
+ """
+ initialize all plugins.
+ creates a dictionary with key
+ returns true if success, false o.w.
+ """
+ def initializePlugins(self) -> bool:
+
+ import os
+ for plugin in os.listdir("Plugins"):
+ if plugin[-3:] == ".py":
+ t = plugin.replace(".py", "")
+ exec("from Plugins.{} import *".format(t))
+ try:
+ # by default, the plugin is not active.
+ if eval(t).load == True:
+ self.plugins.update({eval(t + "()") : False})
+ except Exception as e:
+ print("There was an error when loading plugins. Make sure you follow the naming convention when writing your own plugins.")
+ print("Error:", e)
+ return False
+
+ print("[Initializer]: Successfully loaded {} plugins.".format(len(self.plugins)))
+ return True
+
+
+
+ """
+ Creates a dictionary with key = PacketType, value = key of plugins
+ allows us to just call specific plugins on specific packets
+ """
+ def initializeHookDictionary(self):
+ for plugin in self.plugins:
+ for hook in plugin.hooks:
+ # add new packettype hook
+ if hook not in self.hooks:
+ self.hooks.update({hook : {plugin}})
+ # already exists
+ else:
+ self.hooks[hook].add(plugin)
+
+ def initialize(self):
+ if not self.initializePlugins():
+ return False
+ self.initializeHookDictionary()
+ return True
\ No newline at end of file
diff --git a/Plugins/Godmode.py b/Plugins/Godmode.py
new file mode 100644
index 0000000..bdd3027
--- /dev/null
+++ b/Plugins/Godmode.py
@@ -0,0 +1,44 @@
+# these imports are always necessary
+from valorlib.Packets.Packet import *
+from client import Client
+
+# this is a short tutorial on how to write a plugin that will **only edit packets, not send new ones.**
+# make sure the class name is capitalized, and matches the file's name.
+class Godmode:
+
+ """
+ for each plugin, you need to instantiate a *class variable* called hook.
+ make sure this is a set.
+ this will tell the program what packets you intend to hook
+ why? suppose you have 10 plugins that utilize NewTick. You don't want to reread
+ newtick 10 times. Also, you only want to call the plugins which contain a newtick
+ hook. Remember, the faster this proxy is, the faster it can route packets.
+ """
+
+ hooks = {PacketTypes.PlayerHit, PacketTypes.GroundDamage}
+
+ # also, make sure you put this class variable to tell the PluginManager whether to load this plugin or not. If this is absent,
+ # the manager will throw an exception.
+ load = True
+
+ """
+ Next, you need to write functions that will handle each packet type in your hooks.
+ Make sure your function name is on + the capitalization found in PacketTypes.py, otherwise your function will not be called.
+ This is all you need to write. Here is an example
+
+ def onPacketType(self, client: Client, packet: Packet, send: bool)
+ client is an instance of client
+ packet is an instance of the specific packet type your function will handle.
+ send is whether or not this packet will be sent
+ returns: (updated packet, send)
+ send = true if you wish to send the packet, else false
+
+ Below is an example of these handlers. Since godmode is just blocking the packet, we can just set send to false
+
+ """
+
+ def onPlayerHit(self, client: Client, packet: PlayerHit, send: bool) -> (PlayerHit, bool):
+ return (packet, False)
+
+ def onGroundDamage(self, client: Client, packet: GroundDamage, send: bool) -> (GroundDamage, bool):
+ return (packet, False)
diff --git a/Plugins/Invulnerable.py b/Plugins/Invulnerable.py
new file mode 100644
index 0000000..aa39556
--- /dev/null
+++ b/Plugins/Invulnerable.py
@@ -0,0 +1,72 @@
+from valorlib.Packets.Packet import *
+from ConditionEffect import *
+from client import Client
+
+"""
+this does not work. works for like one bullet or something
+"""
+
+class Invulnerable:
+
+ hooks = {PacketTypes.NewTick}
+ load = False
+
+ def onNewTick(self, client: Client, packet: NewTick, send: bool) -> (NewTick, bool):
+
+ for obj in range(len(packet.statuses)):
+
+ # got a packet that updates our stats
+ if packet.statuses[obj].objectID == client.objectID:
+
+ for s in range(len(packet.statuses[obj].stats)):
+
+ if packet.statuses[obj].stats[s].statType == 29:
+ packet.statuses[obj].stats[s].statValue |= effect0['Invulnerable']
+ break
+
+ # if we didn't receive a packet that will update our stats, just add a new statdata object that does
+ else:
+ s = StatData()
+ s.statType = 29
+ s.statValue = client.effect0bits | effect0['Invulnerable']
+ client.effect0bits |= effect0['Invulnerable']
+ packet.statuses[obj].stats.append(s)
+ break
+
+ # else if the newtick doesn't modify our stats, create a new objectstatusdata that gives speedy
+ else:
+ o = ObjectStatusData()
+ o.objectID = client.objectID
+ # doesn't even matter if position isn't valid
+ o.pos = WorldPosData()
+ s = StatData()
+ s.statType = 29
+ s.statValue = client.effect0bits | effect0['Invulnerable']
+ client.effect0bits |= effect0['Invulnerable']
+ packet.statuses.append(o)
+
+
+ return (packet, send)
+
+ # here we also utilize packet injection to turn off the hack
+
+ def shutdown(self, client: Client) -> None:
+
+ packet2 = NewTick()
+ packet2.tickID = 0
+ packet2.tickTime = 0
+ o = ObjectStatusData()
+ o.objectID = client.objectID
+ # doesn't even matter if position isn't valid
+ o.pos = WorldPosData()
+ s = StatData()
+ s.statType = 29
+ # remove speedy
+ if client.effect1bits & effect0['Invulnerable']:
+ s.statValue = client.effect0bits - effect0['Invulnerable']
+ else:
+ s.statValue = client.effect0bits
+ o.stats.append(s)
+ packet2.statuses.append(o)
+ packet2 = CreatePacket(packet2)
+ client.SendPacketToClient(packet2)
\ No newline at end of file
diff --git a/Plugins/NoDebuff.py b/Plugins/NoDebuff.py
new file mode 100644
index 0000000..2fdfe6b
--- /dev/null
+++ b/Plugins/NoDebuff.py
@@ -0,0 +1,53 @@
+from valorlib.Packets.Packet import *
+from ConditionEffect import *
+from client import Client
+
+class NoDebuff:
+
+ """
+ Need to research a bit more into blocking the effect entirely. Only removes after new tick happens; client is in control of the behavior of status effect.
+ This means you need some type of dict that tells you each enemy's bullet IDs, then join bullet ID with object ID in update packet probably.
+ Search XML's next
+ """
+
+ hooks = {PacketTypes.NewTick}
+ load = True
+ effect0Remove = ["Quiet", "Hallucinating", "Weak", "Slowed", "Sick", "Stunned", "Blind", "Drunk", "Confused", "Paralyzed", "Stasis", "ArmorBroken", "Darkness", "Unstable", "Bleeding"]
+
+ def onNewTick(self, client: Client, packet: NewTick, send: bool) -> (NewTick, bool):
+
+
+ for obj in range(len(packet.statuses)):
+ if packet.statuses[obj].objectID == client.objectID:
+ for s in range(len(packet.statuses[obj].stats)):
+ # if we got a packet representing us and a conditioneffect statdata
+ if packet.statuses[obj].stats[s].statType == 29:
+ # for each debuff
+ for remove in self.effect0Remove:
+ # if the bit is on
+ if packet.statuses[obj].stats[s].statValue & (1 << self.getExponent(effect0[remove])):
+ # remove the bit
+ packet.statuses[obj].stats[s].statValue -= effect0[remove]
+ # keep track of the state
+ client.effect0bits -= effect0[remove]
+ break
+ # if we did not get a packet, just inject a new one without bad status effects
+ else:
+ s = StatData()
+ s.statType = 29
+ for remove in self.effect0Remove:
+ if client.effect0bits & (1 << self.getExponent(effect0[remove])):
+ client.effect0bits -= effect0[remove]
+ s.statValue = client.effect0bits
+ packet.statuses[obj].stats.append(s)
+
+ return (packet, send)
+
+ # given a number of the form 2^k, k >= 1 returns k
+ # if you give it a number x where 2^k <= x < 2^{k+1}, it will return k.
+ def getExponent(self, num):
+ cnt = 0
+ while num != 1:
+ num = num // 2
+ cnt += 1
+ return cnt
\ No newline at end of file
diff --git a/Plugins/NoProjectile.py b/Plugins/NoProjectile.py
new file mode 100644
index 0000000..edfeee8
--- /dev/null
+++ b/Plugins/NoProjectile.py
@@ -0,0 +1,10 @@
+from valorlib.Packets.Packet import *
+from client import Client
+
+class NoProjectile:
+
+ hooks = {PacketTypes.EnemyShoot}
+ load = True
+
+ def onEnemyShoot(self, client: Client, packet: Packet, send: bool) -> (EnemyShoot, bool):
+ return (packet, False)
\ No newline at end of file
diff --git a/Plugins/Speedy.py b/Plugins/Speedy.py
new file mode 100644
index 0000000..1581de5
--- /dev/null
+++ b/Plugins/Speedy.py
@@ -0,0 +1,73 @@
+from valorlib.Packets.Packet import *
+from ConditionEffect import *
+from client import Client
+
+"""
+here is a more involved situation where we edit the NewTick packet.
+"""
+
+class Speedy:
+
+ hooks = {PacketTypes.NewTick}
+ load = True
+
+ def onNewTick(self, client: Client, packet: NewTick, send: bool) -> (NewTick, bool):
+
+ for obj in range(len(packet.statuses)):
+
+ # got a packet that updates our stats
+ if packet.statuses[obj].objectID == client.objectID:
+
+ for s in range(len(packet.statuses[obj].stats)):
+
+ if packet.statuses[obj].stats[s].statType == 29:
+ packet.statuses[obj].stats[s].statValue |= effect0['Speedy']
+ break
+
+ # if we didn't receive a packet that will update our stats, just add a new statdata object that does
+ else:
+ s = StatData()
+ s.statType = 29
+ s.statValue = client.effect0bits | effect0['Speedy']
+ # update the internal bit state to account for 1+ status effect mods
+ client.effect0bits |= effect0['Speedy']
+ packet.statuses[obj].stats.append(s)
+ break
+
+ # else if the newtick doesn't modify our stats, create a new objectstatusdata that gives speedy
+ else:
+ o = ObjectStatusData()
+ o.objectID = client.objectID
+ # doesn't even matter if position isn't valid
+ o.pos = WorldPosData()
+ s = StatData()
+ s.statType = 29
+ s.statValue = client.effect0bits | effect0['Speedy']
+ client.effect0bits |= effect0['Speedy']
+ packet.statuses.append(o)
+
+
+
+ return (packet, send)
+
+ def shutdown(self, client: Client) -> None:
+
+ packet2 = NewTick()
+ packet2.tickID = 0
+ packet2.tickTime = 0
+ o = ObjectStatusData()
+ o.objectID = client.objectID
+ # doesn't even matter if position isn't valid
+ o.pos = WorldPosData()
+ s = StatData()
+ s.statType = 29
+ # remove speedy, this is why we keep the internal state
+ if client.effect0bits & effect0['Speedy']:
+ s.statValue = client.effect0bits - effect0['Speedy']
+ client.effect0bits -= effect0['Speedy']
+ else:
+ s.statValue = client.effect0bits
+ o.stats.append(s)
+ packet2.statuses.append(o)
+ packet2 = CreatePacket(packet2)
+ client.SendPacketToClient(packet2)
\ No newline at end of file
diff --git a/Plugins/Swiftness.py b/Plugins/Swiftness.py
new file mode 100644
index 0000000..06b0062
--- /dev/null
+++ b/Plugins/Swiftness.py
@@ -0,0 +1,76 @@
+from valorlib.Packets.Packet import *
+from ConditionEffect import *
+from client import Client
+
+"""
+Stacks with speedy!
+"""
+
+class Swiftness:
+
+ hooks = {PacketTypes.NewTick}
+ load = True
+ word = "Swiftness"
+
+ def onNewTick(self, client: Client, packet: NewTick, send: bool) -> (NewTick, bool):
+
+
+
+ for obj in range(len(packet.statuses)):
+
+ # got a packet that updates our stats
+ if packet.statuses[obj].objectID == client.objectID:
+
+ for s in range(len(packet.statuses[obj].stats)):
+
+ if packet.statuses[obj].stats[s].statType == 96:
+ packet.statuses[obj].stats[s].statValue |= effect1[self.word]
+ break
+
+ # if we didn't receive a packet that will update our stats, just add a new statdata object that does
+ else:
+ s = StatData()
+ s.statType = 96
+ s.statValue = client.effect1bits | effect1[self.word]
+ # update the internal bit state to account for 1+ status effect mods
+ client.effect1bits |= effect1[self.word]
+ packet.statuses[obj].stats.append(s)
+ break
+
+ # else if the newtick doesn't modify our stats, create a new objectstatusdata that gives speedy
+ else:
+ o = ObjectStatusData()
+ o.objectID = client.objectID
+ # doesn't even matter if position isn't valid
+ o.pos = WorldPosData()
+ s = StatData()
+ s.statType = 96
+ s.statValue = client.effect1bits | effect1[self.word]
+ client.effect1bits |= effect1[self.word]
+ packet.statuses.append(o)
+
+
+
+ return (packet, send)
+
+ def shutdown(self, client: Client) -> None:
+
+ packet2 = NewTick()
+ packet2.tickID = 0
+ packet2.tickTime = 0
+ o = ObjectStatusData()
+ o.objectID = client.objectID
+ # doesn't even matter if position isn't valid
+ o.pos = WorldPosData()
+ s = StatData()
+ s.statType = 96
+ # remove speedy
+ if client.effect1bits & effect1[self.word]:
+ s.statValue = client.effect1bits - effect1[self.word]
+ client.effect1bits -= effect1[self.word]
+ else:
+ s.statValue = client.effect1bits
+ o.stats.append(s)
+ packet2.statuses.append(o)
+ packet2 = CreatePacket(packet2)
+ client.SendPacketToClient(packet2)
\ No newline at end of file
diff --git a/RC4.py b/RC4.py
new file mode 100644
index 0000000..711958e
--- /dev/null
+++ b/RC4.py
@@ -0,0 +1,42 @@
+"""
+Decrypts a stream of bytes, maintaining an internal state.
+"""
+
+class RC4:
+
+ def __init__(self, key):
+
+ self.key = key
+ self.reset()
+
+ """ every time you enter a new map, reset ciphers"""
+ def reset(self):
+
+ self.S = list(range(256))
+ j = 0
+
+ for i in range(256):
+ j = j + self.S[i] + self.key[i % len(self.key)] & 255
+ tmp = self.S[i]
+ self.S[i] = self.S[j]
+ self.S[j] = tmp
+
+ self.i = 0
+ self.j = 0
+
+
+
+ def encrypt(self, data):
+
+ for idx in range(len(data)):
+
+ self.i = self.i + 1 & 255
+ self.j = self.j + self.S[self.i] & 255
+ tmp = self.S[self.i]
+ self.S[self.i] = self.S[self.j]
+ self.S[self.j] = tmp;
+
+ data[idx] = data[idx] ^ self.S[tmp + self.S[self.i] & 255]
+
+ def decrypt(self, data):
+ self.encrypt(data)
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5fdfc2b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,59 @@
+# vrelay
+
+A man-in-the-middle proxy server for the RotMG private server Valor. This is the Python version of [KRelay](https://github.com/TheKronks/KRelay) but for Valor RotMG.
+
+The goal is to learn more about networking, reverse engineering, and writing hacks for RotMG.
+
+**Updated for Valor version 3.2.2.**
+
+
+
+
+
+
+## How to use
+
+1. Install Python 64-bit. You can find installations of Python (here)[https://www.python.org/downloads/]. Install the 64-bit version or the GUI will not work.
+2. A download link to the hack will be provided below. In this step, download the hack and unzip it.
+ - *Note*: Github is not a virus site. It's just a site to collaborate on software engineering projects. It is widely used in tech companies.
+3. Replace your original `Valor.swf` with the `Valor.swf` in the unzipped folder. This `.swf` file has been modded so you can connect to the proxy server. If you only see `Valor`, that means you have your file extensions off.
+4. Open the command line (type `⊞ + r` and type in `cmd`, press enter. the command line will now be open), `cd` into the folder containing the code.
+ - This will involve typing `cd C:\path\to\folder` then pressing enter.
+ - If you downloaded the hack in another drive, such as `Z:` or `D:`, then you need to type the drive name (`Z:` or `D:`, respectively) in order to tell command prompt you wish to be in that drive.
+5. Once you're in the folder in the command prompt, type `python proxy.py` in the command line to start the proxy. If you have previously installed Python, also try `py proxy.py` or `python3 proxy.py` if this does not work.
+6. Connect to the proxy server in the server list and you're good to go.
+
+
+## Features
+
+All features have a button in the GUI to turn the hack on or off.
+
+- **Godmode**: Immune to all bullet and ground damage. Does not block AoE damage. See this [YouTube video for a demo.](https://www.youtube.com/watch?v=cNerTN7HwhM)
+- **No projectile**: Hides all projectiles from appearing; essentially another godmode (but very obvious to others as you do not know where to dodge).
+ - Does not hide AoE damage (you will still take damage from AoE).
+- **Speedy**: Apply speedy to yourself!
+- **Swiftness**: Apply swiftness (stronger form of Speedy) to yourself! Stacks with Speedy.
+- **Remove client-side debuffs**: This will remove client-side debuffs one tick after they are applied. These will not remove server-sided debuffs like bleeding, quiet, etc.
+- **Shut down all plugins**: This will turn off all active plugins. Reduces amount of clicking you need to do if you have many plugins active.
+- Ability to write your own plugins!
+- Ability to inject packets.
+
+## Writing your own plugins
+Will document this section later. For now:
+
+- See `Plugins/Godmode.py` for an explanation on what functions to implement.
+- See `Plugins/Speedy.py` to see an in-depth example on how the `NewTick` packet was modified to apply speedy.
+
+
+## Notes
+- You can use `/dep` to put potions into potion vault anywhere (in dungeons and realms). Currently spams your screen tho.
+
+## Credits
+- [JPEXS](https://www.free-decompiler.com/flash/download/) for reverse engineering and modifying the client.
+
+#### TODO:
+- Actual KA instead of enemy-dependent
+- Auto Aim?
+- Block status effects
+- Add a force restart
+- There is a bug where if Godmode and either Speedy/Swiftness are both active, all negative status effects are cancelled. While this is great, this is a bug from a software persepective.
\ No newline at end of file
diff --git a/Valor.swf b/Valor.swf
new file mode 100644
index 0000000..980ad40
Binary files /dev/null and b/Valor.swf differ
diff --git a/client.py b/client.py
new file mode 100644
index 0000000..2c3e73c
--- /dev/null
+++ b/client.py
@@ -0,0 +1,514 @@
+import socket
+import struct
+import select
+import time
+
+from valorlib.Packets.Packet import *
+from PluginManager import *
+from RC4 import RC4
+
+class Client:
+ """
+ This class holds data relevant to clients.
+ in here, we will also put the sockets. this allows the client class to send packets.
+ from a design persepctive, this also makes sense since this can scale to clientless
+ """
+
+ def __init__(self, pm: PluginManager):
+ self.remoteHostAddr = "51.222.11.213"
+ self.remoteHostPort = 2050
+ self.objectID = None
+ self.charID = None
+ self.reconnecting = False
+ self.connected = False
+ self.clientSendKey = RC4(bytearray.fromhex("BA15DE"))
+ self.clientReceiveKey = RC4(bytearray.fromhex("612a806cac78114ba5013cb531"))
+ self.serverSendKey = RC4(bytearray.fromhex("612a806cac78114ba5013cb531"))
+ self.serverReceiveKey = RC4(bytearray.fromhex("BA15DE"))
+ self.gameSocket = None
+ self.serverSocket = None
+ self.pluginManager = pm
+ self.currentMap = "None"
+ self.gameIDs = {
+ -1 : "Nexus",
+ -2 : "Nexus",
+ -5 : "Vault",
+ -15 : "Marketplace",
+ -16 : "Ascension Enclave",
+ -17 : "Aspect Hall"
+ }
+
+ # stuff to ignore when debugging
+ #self.ignoreIn = []
+ #self.ignoreOut = []
+ self.ignoreOut = [PacketTypes.Message, PacketTypes.Move, PacketTypes.Pong, PacketTypes.GotoAck, PacketTypes.PlayerShoot, PacketTypes.ShootAck]
+ self.ignoreIn = [PacketTypes.Ping, PacketTypes.Goto, PacketTypes.Update, PacketTypes.NewTick]
+
+ # client state syncs, these are public
+ self.disableSpeedy = False
+ self.disableInvulnerable = False
+ self.disableSwiftness = False
+ self.disableVengeance = False
+
+ # containerType of the weapon you are using
+ self.containerType = 0
+ # first bullet time. allows server sync
+ self.firstBulletTime = None
+ # internal bullet ID to spoof packets
+ self.internalBulletID = 0
+ # last time enemies damaged themselves
+ self.lastEnemySelfDamage = 0
+ # last time you multiplied damage
+ self.lastEnemyHitSpam = 0
+
+ self.effect0bits = 0
+ self.effect1bits = 0
+ self.effect2bits = 0
+
+
+ self.lastReconnect = 0
+ self.lastReconKey = []
+ self.lastGameID = 0
+ self.useDisconnectedKey = None
+
+ # disconnect the client from the proxy
+ # disconnect the proxy from the server
+ def Disconnect(self):
+ self.connected = False
+ if self.serverSocket:
+ self.serverSocket.shutdown(socket.SHUT_RDWR)
+ self.serverSocket.close()
+ if self.gameSocket:
+ self.gameSocket.shutdown(socket.SHUT_RDWR)
+ self.gameSocket.close()
+ self.gameSocket = None
+ self.serverSocket = None
+
+ # reset ciphers to default state
+ def ResetCipher(self):
+ self.clientSendKey.reset()
+ self.clientReceiveKey.reset()
+ self.serverSendKey.reset()
+ self.serverReceiveKey.reset()
+
+ # for now, we can just recon lazily
+ def Reconnect(self):
+ self.ConnectRemote(self.remoteHostAddr, self.remoteHostPort)
+ self.connected = True
+
+ # Connect to remote host. Block until client connected
+ def ConnectRemote(self, host, port):
+ # the invalid recon key bug is when client doesn't connect to the proxy server's socket
+ # reduced sleep time and it seems to be ok now
+ while self.gameSocket == None:
+ time.sleep(0.005)
+
+ self.serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.serverSocket.connect((host, port))
+
+ # closes both sockets
+ def Close(self):
+ self.gameSocket.close()
+ self.serverSocket.close()
+
+ # restart entire connection
+ def reset(self):
+ self.internalBulletID = 0
+ self.firstBulletTime = None
+ self.Disconnect()
+ self.ResetCipher()
+ self.Reconnect()
+
+ # call this fcn when you reset connection
+ def resetMapName(self):
+ self.currentMap = "Nexus"
+
+ """
+ Listens to packets from the client.
+ """
+ def ListenToClient(self):
+
+ header = self.gameSocket.recv(5)
+
+ if len(header) == 0 or self.reconnecting:
+ # try and fix this possible bug? why is the client sending nothing??
+ # this only happens when you nexus
+ # print("got length 0 packet from client")
+ self.reset()
+ return
+
+ #while len(header) != 5:
+ # header += self.gameSocket.recv(5 - len(header))
+
+ packetID = header[4]
+ expectedPacketLength = struct.unpack("!i", header[:4])[0]
+
+ # read the packet, subtract 5 cuz you already read header
+ leftToRead = expectedPacketLength - 5
+ data = bytearray()
+
+ while (leftToRead > 0):
+ buf = bytearray(self.gameSocket.recv(leftToRead))
+ data += buf
+ leftToRead -= len(buf)
+
+ # decipher it to update our internal state
+ self.clientSendKey.decrypt(data)
+ packet = Packet(header, data, packetID)
+ send = True
+
+ """
+ # for debugging
+ try:
+ if packet.ID not in self.ignoreOut:
+ print("Client sent:", PacketTypes.reverseDict[packet.ID])
+ except:
+ print("Got unknown packet from client")
+ """
+
+ # hooks
+ if packet.ID == PacketTypes.PlayerText:
+ self.onPlayerText(packet)
+
+ elif packet.ID == PacketTypes.Hello:
+ # hello is always sent, try another map update name here
+ p = Hello()
+ p.read(packet.data)
+
+ if p.gameID in self.gameIDs:
+ self.currentMap = self.gameIDs[p.gameID]
+
+ # plugin management
+ elif packet.ID == PacketTypes.PlayerHit:
+ packet, send = self.routePacket(packet, send, self.onPlayerHit)
+
+ elif packet.ID == PacketTypes.GroundDamage:
+ packet, send = self.routePacket(packet, send, self.onGroundDamage)
+
+ elif packet.ID == PacketTypes.EnemyHit:
+ packet, send = self.routePacket(packet, send, self.onEnemyHit)
+
+ elif packet.ID == PacketTypes.PlayerShoot:
+ # make sure you're tracking the internal state
+ # update the id now since this will percolate downstream to enemyhit
+ self.internalBulletID = (self.internalBulletID + 1) % 128
+ packet, send = self.routePacket(packet, send, self.onPlayerShoot)
+
+
+ elif packet.ID == PacketTypes.OtherHit:
+ # seems to help with the KA packet rejection rate
+ # send = False
+ pass
+
+ if not send:
+ return
+ else:
+ self.SendPacketToServer(packet)
+
+ """
+ Listens to packets from the server.
+ """
+ def ListenToServer(self):
+
+ header = self.serverSocket.recv(5)
+
+ if len(header) == 0 or self.reconnecting:
+ # try and fix this possible bug? this happens every so often.
+ # why this bug happens: client sends hello, but then server sends nothing.
+ # doesn't matter if you keep trying to read bytes, server always sends nothing.
+ # print("got 0 length packet from server")
+ self.reset()
+ return
+
+
+ #while len(header) != 5:
+ # header += self.serverSocket.recv(5 - len(header))
+
+ packetID = header[4]
+ expectedPacketLength = struct.unpack("!i", header[:4])[0]
+ # read the packet, subtract 5 cuz you already read header
+ leftToRead = expectedPacketLength - 5
+ data = bytearray()
+
+ while (leftToRead > 0):
+ buf = bytearray(self.serverSocket.recv(leftToRead))
+ data += buf
+ leftToRead -= len(buf)
+
+ # decipher it to update our internal state
+ self.serverSendKey.decrypt(data)
+ packet = Packet(header, data, packetID)
+ send = True
+
+ """
+ # for debugging
+ try:
+ if packet.ID not in self.ignoreIn:
+ print("Server sent:", PacketTypes.reverseDict[packet.ID])
+ except:
+ print("Got unknown packet from server, id", packet.ID)
+ """
+
+ # hooks
+ if packet.ID == PacketTypes.CreateSuccess:
+ self.OnCreateSuccess(packet)
+
+ elif packet.ID == PacketTypes.Reconnect:
+ # update map name.
+ p = Reconnect()
+ p.read(packet.data)
+ #p.PrintString()
+ self.lastReconnect = time.time()
+ self.lastGameID = p.gameID
+ self.lastReconKey = p.key
+ self.currentMap = p.name
+ self.reconnecting = True
+
+ # update internal effect state
+ elif packet.ID == PacketTypes.NewTick:
+ """
+ p = NewTick()
+ p.read(packet.data)
+ p.PrintString()
+ for obj in range(len(p.statuses)):
+ if p.statuses[obj].objectID != self.objectID:
+ p.statuses[obj].PrintString()
+ for s in range(len(p.statuses[obj].stats)):
+ p.statuses[obj].stats[s].PrintString()
+ """
+ packet, send = self.routePacket(packet, send, self.onNewTick)
+
+ elif packet.ID == PacketTypes.EnemyShoot:
+ packet, send = self.routePacket(packet, send, self.onEnemyShoot)
+
+ elif packet.ID == PacketTypes.Failure:
+ packet, send = self.routePacket(packet, send, self.onFailure)
+
+
+ if send:
+ self.SendPacketToClient(packet)
+
+ """
+ Starts to listen for packets
+ """
+ def Listen(self):
+
+ while True:
+ try:
+
+ ready = select.select([self.gameSocket, self.serverSocket], [], [])[0]
+
+ # prioritize reconnects
+ if self.reconnecting:
+
+ if self.gameSocket in ready:
+ self.gameSocket.recv(50000)
+
+ if self.serverSocket in ready:
+ self.serverSocket.recv(50000)
+
+ self.onReconnect()
+ self.reconnecting = False
+ return
+
+ elif self.connected:
+
+ # client has data ready to send to server
+ if self.gameSocket in ready:
+ self.ListenToClient()
+ # server has data ready to send to client
+ if self.serverSocket in ready:
+ self.ListenToServer()
+
+ except ConnectionAbortedError as e:
+ print("Connection was aborted:", e)
+ self.reset()
+ self.resetMapName()
+
+ except ConnectionResetError as e:
+ print("Connection was reset")
+ self.reset()
+ self.resetMapName()
+
+ except KeyboardInterrupt:
+ print("User aborted. Shutting down proxy.")
+ self.connected = False
+ self.Close()
+ return
+
+ #except Exception as e:
+ # print("Something went terribly wrong.")
+ # print("Error:", e)
+ # print("Restarting...")
+ # self.reset()
+
+ # activate godmode (actually not needed)
+ def ActivateGodmode(self, packet) -> Packet:
+ p = PlayerHit()
+ p.read(packet.data)
+ p.objectID = 0
+ packet = CreatePacket(p)
+ return packet
+
+
+ """
+
+ Given a specific packet type, call the relevant client onPacketType function to read the packet
+ Then, iterate through hook dict to call plugins which hook this packet.
+
+ :param packet: A Packet object
+ :param send: whether or not to send the packet
+ :param onPacketType: The implemented callback inside a plugin when this packet type is encountered.
+ This function will be defined within the Client class.
+
+ returns: (Packet, send)
+ """
+ def routePacket(self, packet: Packet, send, onPacketType) -> (Packet, bool):
+
+ p = onPacketType(packet)
+ #modified = False
+
+ if packet.ID in self.pluginManager.hooks:
+ for plugin in self.pluginManager.hooks[packet.ID]:
+ # if the plugin is active
+ if self.pluginManager.plugins[plugin]:
+ # at each step, we are editing the packet on the wire
+ # also make sure you're spelling your class methods correctly.
+ p, send = getattr(plugin, "on" + PacketTypes.reverseDict[packet.ID])(self, p, send)
+ modified = True
+
+ # always create a new packet; this ensures our internal bullet ID state is synced
+ # with our real shots
+ #if modified:
+ packet = CreatePacket(p)
+ return (packet, send)
+
+
+ # server -> client
+ def SendPacketToClient(self, packet):
+ self.clientReceiveKey.encrypt(packet.data)
+ self.gameSocket.sendall(packet.format())
+
+ # client -> server
+ def SendPacketToServer(self, packet):
+ self.serverReceiveKey.encrypt(packet.data)
+ self.serverSocket.sendall(packet.format())
+
+ # set objid to client's
+ def OnCreateSuccess(self, packet):
+ p = CreateSuccess()
+ p.read(packet.data)
+ self.objectID = p.objectID
+ self.charID = p.charID
+
+
+##########################
+# various necessary hooks
+##########################
+
+ def onFailure(self, packet: Packet) -> Failure:
+ p = Failure()
+ p.read(packet.data)
+ #p.PrintString()
+ return p
+
+ def onPlayerHit(self, packet: Packet) -> PlayerHit:
+ p = PlayerHit()
+ p.read(packet.data)
+ return p
+
+ def onGroundDamage(self, packet: Packet) -> GroundDamage:
+ p = GroundDamage()
+ p.read(packet.data)
+ return p
+
+ def onEnemyShoot(self, packet: Packet) -> EnemyShoot:
+ p = EnemyShoot()
+ p.read(packet.data)
+ return p
+
+ def onEnemyHit(self, packet: Packet) -> EnemyHit:
+ p = EnemyHit()
+ p.read(packet.data)
+ # since we have plugins that modify the bulletID, we need to make sure bulletID is synced as well
+ #p.bulletID = self.internalBulletID
+ return p
+
+ def onPlayerShoot(self, packet: Packet) -> PlayerShoot:
+ p = PlayerShoot()
+ p.read(packet.data)
+ self.containerType = p.containerType
+ # to get bullet clock syncs, do int(time.time() * 1000 - self.firstBulletTime)
+ # fix this later lmfao
+ if self.firstBulletTime == None:
+ self.firstBulletTime = time.time() * 1000 - p.time
+ # this is causing bugs for multishot piercing weapons. ex: decimator goes from 0 -> 6 -> 12 every single playershoot
+ #p.bulletID = self.internalBulletID
+ return p
+
+ def onNewTick(self, packet: Packet) -> NewTick:
+ p = NewTick()
+ p.read(packet.data)
+
+ for obj in range(len(p.statuses)):
+ # got a packet that updates our stats
+ if p.statuses[obj].objectID == self.objectID:
+ for s in range(len(p.statuses[obj].stats)):
+ # 29, effect 0
+ # 96, effect 1
+ # 205, effect 2
+ # armored, damaging serversided
+
+ if p.statuses[obj].stats[s].statType == 29:
+ self.effect0bits = p.statuses[obj].stats[s].statValue
+
+ elif p.statuses[obj].stats[s].statType == 96:
+ self.effect1bits = p.statuses[obj].stats[s].statValue
+
+ elif p.statuses[obj].stats[s].statType == 205:
+ self.effect2bits = p.statuses[obj].stats[s].statValue
+
+ # now cover edge cases in plugins
+ # pretty terrible engineering
+ # this ensures our injected status effects are turned off and doesn't interfere with real speedy (like from warrior)
+ self.turnOffInjectedStatuses()
+
+ return p
+
+ def turnOffInjectedStatuses(self):
+
+ for plugin in self.pluginManager.plugins:
+ if self.disableSpeedy and type(plugin).__name__ == "Speedy":
+ plugin.shutdown(self)
+ self.disableSpeedy = False
+
+ elif self.disableSwiftness and type(plugin).__name__ == "Swiftness":
+ plugin.shutdown(self)
+ self.disableSwiftness = False
+
+ elif self.disableInvulnerable and type(plugin).__name__ == "Invulnerable":
+ plugin.shutdown(self)
+ self.disableInvulnerable = False
+
+ elif self.disableVengeance and type(plugin).__name__ == "Vengeance":
+ plugin.shutdown(self)
+ self.disableVengeance = False
+
+ # playertext hook
+ def onPlayerText(self, packet) -> None:
+ p = PlayerText()
+ p.read(packet.data)
+
+ if p.text == "/dep":
+ for i in range(0, 12):
+ pp = PotionStorageInteraction()
+ pp.type = i
+ pp.action = 0
+ self.SendPacketToServer(CreatePacket(pp))
+
+ return p
+
+ # when server sends reconnect
+ def onReconnect(self):
+
+ self.reset()
\ No newline at end of file
diff --git a/gui.py b/gui.py
new file mode 100644
index 0000000..97fcd2d
--- /dev/null
+++ b/gui.py
@@ -0,0 +1,267 @@
+import time
+import sys
+
+from tkinter import *
+from PluginManager import *
+from client import Client
+
+class GUI:
+
+ COLWIDTH = 20
+ PADX = 10
+ PADY = 5
+ HEIGHT = 2
+ RED = "#CF0029"
+ GREEN = "#43810D"
+
+
+ def __init__(self, pm: PluginManager, client: Client, proxy):
+
+ # new gui
+ self.pluginManager = pm
+ self.client = client
+ self.proxy = proxy
+ self.root = Tk()
+ self.root.geometry("460x220")
+ self.root.resizable(False, False)
+ self.root.title("vrelay by swrlly - for valor v3.2.2")
+
+ self.buttonFrame = Frame(self.root)
+ self.buttonFrame.grid(row = 0, column = 0)
+
+ # godmode text
+ self.godmodelabel = Label(self.buttonFrame, text = "Godmode", width = self.COLWIDTH, height = self.HEIGHT)
+ self.godmodelabel.grid(row = 0, column = 0, padx = self.PADX, pady = self.PADY)
+
+ # godmode button
+ self.godmodetxt = StringVar()
+ self.godmodetxt.set("OFF")
+ self.godmodebtn = Button(self.buttonFrame, bd = 5, textvariable = self.godmodetxt, command = self.godmodeHandler, fg = self.RED)
+ self.godmodebtn.grid(row = 0, column = 1, padx = self.PADX, pady = self.PADY)
+
+ # noprojectile text
+ self.noprojectilelabel = Label(self.buttonFrame, text = "Hide projectiles", width = self.COLWIDTH, height = self.HEIGHT)
+ self.noprojectilelabel.grid(row = 1, column = 0, padx = self.PADX, pady = self.PADY)
+
+ # noprojectile button
+ self.noprojectiletxt = StringVar()
+ self.noprojectiletxt.set("OFF")
+ self.noprojectilebtn = Button(self.buttonFrame, bd = 5, textvariable = self.noprojectiletxt, command = self.noProjectileHandler, fg = self.RED)
+ self.noprojectilebtn.grid(row = 1, column = 1, padx = self.PADX, pady = self.PADY)
+
+ # speedy text
+ self.speedylabel = Label(self.buttonFrame, text = "Speedy", width = self.COLWIDTH, height = self.HEIGHT)
+ self.speedylabel.grid(row = 0, column = 2, padx = self.PADX, pady = self.PADY)
+
+ # speedy button
+ self.speedytxt = StringVar()
+ self.speedytxt.set("OFF")
+ self.speedybtn = Button(self.buttonFrame, bd = 5, textvariable = self.speedytxt, command = self.speedyHandler, fg = self.RED)
+ self.speedybtn.grid(row = 0, column = 3, padx = self.PADX, pady = self.PADY)
+
+ # swiftness text
+ self.swiftnesslabel = Label(self.buttonFrame, text = "Swiftness", width = self.COLWIDTH, height = self.HEIGHT)
+ self.swiftnesslabel.grid(row = 1, column = 2, padx = self.PADX, pady = self.PADY)
+
+ # swiftness button
+ self.swiftnesstxt = StringVar()
+ self.swiftnesstxt.set("OFF")
+ self.swiftnessbtn = Button(self.buttonFrame, bd = 5, textvariable = self.swiftnesstxt, command = self.swiftnessHandler, fg = self.RED)
+ self.swiftnessbtn.grid(row = 1, column = 3, padx = self.PADX, pady = self.PADY)
+
+ # extradamage text
+ #self.extradamagelabel = Label(self.buttonFrame, text = "Extra damage\n(non-piercing only!)", width = self.COLWIDTH, height = self.HEIGHT)
+ #self.extradamagelabel.grid(row = 2, column = 0, padx = self.PADX, pady = self.PADY)
+
+ # extradamage button
+ # self.extradamagetxt = StringVar()
+ # self.extradamagetxt.set("OFF")
+ # self.extradamagebtn = Button(self.buttonFrame, bd = 5, textvariable = self.extradamagetxt, command = self.extraDamageHandler, fg = self.RED)
+ # self.extradamagebtn.grid(row = 2, column = 1, padx = self.PADX, pady = self.PADY)
+
+ # nodebuff text
+ self.nodebufflabel = Label(self.buttonFrame, text = "Remove client-side debuffs", width = self.COLWIDTH, height = self.HEIGHT)
+ self.nodebufflabel.grid(row = 2, column = 0, padx = self.PADX, pady = self.PADY)
+
+ # nodebuff button
+ self.nodebufftxt = StringVar()
+ self.nodebufftxt.set("OFF")
+ self.nodebuffbtn = Button(self.buttonFrame, bd = 5, textvariable = self.nodebufftxt, command = self.noDebuffHandler, fg = self.RED)
+ self.nodebuffbtn.grid(row = 2, column = 1, padx = self.PADX, pady = self.PADY)
+
+ # ka text
+ #self.kalabel = Label(self.buttonFrame, text = "Enemy-dependent kill aura \n (non-piercing only!)", width = self.COLWIDTH, height = self.HEIGHT)
+ #self.kalabel.grid(row = 3, column = 0, padx = self.PADX, pady = self.PADY)
+
+ # ka button
+ # self.katxt = StringVar()
+ # self.katxt.set("OFF")
+ # self.kabtn = Button(self.buttonFrame, bd = 5, textvariable = self.katxt, command = self.badKillAuraHandler, fg = self.RED)
+ # self.kabtn.grid(row = 3, column = 1, padx = self.PADX, pady = self.PADY)
+
+ # realm text
+
+ self.textFrame = Frame(self.root)
+ self.textFrame.grid(row = 1, column = 0)
+
+ self.shutdownbtn = Button(self.textFrame, bd = 5, text = "Shut down all plugins", command = self.shutdownHandler)
+ self.shutdownbtn.grid(row = 0, column = 0, padx = 20, pady = 20)
+
+ self.location = StringVar()
+ self.location.set("Connected to {}!".format(client.currentMap))
+ self.locationentry = Label(self.textFrame, bd = 1, textvariable = self.location)
+ self.locationentry.grid(row = 0, column = 1, padx = 20, pady = 20)
+
+
+
+ def shutdownHandler(self):
+
+ # turn off hooks
+ for plugin in self.pluginManager.plugins:
+ self.pluginManager.plugins[plugin] = False
+
+ # change UI
+ self.godmodetxt.set("OFF")
+ self.godmodebtn.config(fg = self.RED)
+ self.noprojectiletxt.set("OFF")
+ self.noprojectilebtn.config(fg = self.RED)
+ #self.extradamagetxt.set("OFF")
+ #self.extradamagebtn.config(fg = self.RED)
+ self.speedytxt.set("OFF")
+ self.speedybtn.config(fg = self.RED)
+ self.swiftnesstxt.set("OFF")
+ self.swiftnessbtn.config(fg = self.RED)
+ #self.katxt.set("OFF")
+ #self.kabtn.config(fg = self.RED)
+ self.nodebufftxt.set("OFF")
+ self.nodebuffbtn.config(fg = self.RED)
+
+ # next new tick packet will actually send shutdown packet
+ self.client.disableSpeedy = True
+ self.client.disableSwiftness = True
+
+ """
+ # restart text
+ self.restartlabel = Label(text = "Restart Proxy", width = self.COLWIDTH, height = self.HEIGHT)
+ self.restartlabel.grid(row = 2, column = 0, padx = self.PADX, pady = self.PADY)
+
+ # restart button
+ self.restartlabeltxt = StringVar()
+ self.restartlabeltxt.set("Restart")
+ self.restartbtn = Button(self.root, bd = 5, textvariable = self.restartlabeltxt, command = self.restartHandler)
+ self.restartbtn.grid(row = 2, column = 1, padx = self.PADX, pady = self.PADY)
+ """
+
+
+ # when the button is clicked
+ def godmodeHandler(self):
+ if self.pluginManager.plugins[self.findClass("Godmode")]:
+ self.pluginManager.plugins[self.findClass("Godmode")] = False
+ self.godmodetxt.set("OFF")
+ self.godmodebtn.config(fg = self.RED)
+ else:
+ self.pluginManager.plugins[self.findClass("Godmode")] = True
+ self.godmodetxt.set("ON")
+ self.godmodebtn.config(fg = self.GREEN)
+
+ # when the button is clicked
+ def noDebuffHandler(self):
+ if self.pluginManager.plugins[self.findClass("NoDebuff")]:
+ self.pluginManager.plugins[self.findClass("NoDebuff")] = False
+ self.nodebufftxt.set("OFF")
+ self.nodebuffbtn.config(fg = self.RED)
+ else:
+ self.pluginManager.plugins[self.findClass("NoDebuff")] = True
+ self.nodebufftxt.set("ON")
+ self.nodebuffbtn.config(fg = self.GREEN)
+
+ # when the button is clicked
+ def noProjectileHandler(self):
+ if self.pluginManager.plugins[self.findClass("NoProjectile")]:
+ self.pluginManager.plugins[self.findClass("NoProjectile")] = False
+ self.noprojectiletxt.set("OFF")
+ self.noprojectilebtn.config(fg = self.RED)
+ else:
+ self.pluginManager.plugins[self.findClass("NoProjectile")] = True
+ self.noprojectiletxt.set("ON")
+ self.noprojectilebtn.config(fg = self.GREEN)
+
+ # when the button is clicked
+ def speedyHandler(self):
+ if self.pluginManager.plugins[self.findClass("Speedy")]:
+ self.client.disableSpeedy = True
+ self.pluginManager.plugins[self.findClass("Speedy")] = False
+ self.speedytxt.set("OFF")
+ self.speedybtn.config(fg = self.RED)
+ else:
+ self.client.disableSpeedy = False
+ self.pluginManager.plugins[self.findClass("Speedy")] = True
+ self.speedytxt.set("ON")
+ self.speedybtn.config(fg = self.GREEN)
+
+ # when the button is clicked
+ def extraDamageHandler(self):
+ if self.pluginManager.plugins[self.findClass("ExtraDamage")]:
+ self.pluginManager.plugins[self.findClass("ExtraDamage")] = False
+ self.extradamagetxt.set("OFF")
+ self.extradamagebtn.config(fg = self.RED)
+ else:
+ self.pluginManager.plugins[self.findClass("ExtraDamage")] = True
+ self.extradamagetxt.set("ON")
+ self.extradamagebtn.config(fg = self.GREEN)
+
+ # when the button is clicked
+ def swiftnessHandler(self):
+ if self.pluginManager.plugins[self.findClass("Swiftness")]:
+ self.client.disableSwiftness = True
+ self.pluginManager.plugins[self.findClass("Swiftness")] = False
+ self.swiftnesstxt.set("OFF")
+ self.swiftnessbtn.config(fg = self.RED)
+ else:
+ self.client.disableSwiftness = False
+ self.pluginManager.plugins[self.findClass("Swiftness")] = True
+ self.swiftnesstxt.set("ON")
+ self.swiftnessbtn.config(fg = self.GREEN)
+
+ # when the button is clicked
+ def badKillAuraHandler(self):
+ if self.pluginManager.plugins[self.findClass("BadKillAura")]:
+ self.pluginManager.plugins[self.findClass("BadKillAura")] = False
+ self.katxt.set("OFF")
+ self.kabtn.config(fg = self.RED)
+ else:
+ self.pluginManager.plugins[self.findClass("BadKillAura")] = True
+ self.katxt.set("ON")
+ self.kabtn.config(fg = self.GREEN)
+
+ """
+ # when user wishes to restart
+ def restartHandler(self):
+ # break out of Listen thread
+ self.proxy.Restart()
+ """
+
+ # returns relevant class you searched for
+ def findClass(self, text: str):
+ for plugin in self.pluginManager.plugins:
+ if type(plugin).__name__ == text:
+ return plugin
+
+
+ def start(self):
+ while True:
+ #self.root.update_idletasks()
+ try:
+ self.root.update()
+ self.location.set("Connected to {}!".format(self.client.currentMap))
+ except KeyboardInterrupt:
+ return
+ except TclError:
+ print("Closed GUI. Shutting down proxy.")
+ return
+ time.sleep(0.005)
+
+
+
+
+
\ No newline at end of file
diff --git a/proxy.py b/proxy.py
new file mode 100644
index 0000000..61874b9
--- /dev/null
+++ b/proxy.py
@@ -0,0 +1,79 @@
+import threading
+import sys
+
+from client import *
+from PluginManager import *
+from gui import GUI
+
+class Proxy:
+
+ def __init__(self, pm: PluginManager, client: Client):
+
+ self.localHostAddr = "127.0.0.1"
+ self.localHostPort = 2050 # look up 843 and flash
+ self.managerSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.pluginManager = pm
+ self.client = client
+
+ self.active = False
+ self.serverMonitorThread = None
+
+ """
+ the purpose of this function is to allow client -> proxy then server -> proxy reconnects.
+ it's in a thread to "monitor" when our client requires a new socket to the proxy
+ in a sense, it is the server socket.
+ """
+ def ServerMonitor(self):
+ self.managerSocket.bind((self.localHostAddr, self.localHostPort))
+ self.managerSocket.listen(3)
+ # always listening for client connect
+ while True:
+ self.client.gameSocket, addr = self.managerSocket.accept()
+
+ def Start(self):
+ self.active = True
+ # start up server socket
+ self.serverMonitorThread = threading.Thread(target = self.ServerMonitor, daemon = True)
+ self.serverMonitorThread.start()
+ self.Connect()
+
+ def Connect(self):
+ # connect sockets first
+ self.client.ConnectRemote(self.client.remoteHostAddr, self.client.remoteHostPort)
+ self.client.connected = True
+
+ # listen for packets
+ while True:
+ self.client.Listen()
+
+ """
+ def Restart(self):
+ self.client.Disconnect()
+ self.client.ResetCipher()
+ threading.Thread(target = self.Connect, daemon = False).start()
+ """
+
+def main():
+ print("[Initializer]: Loading plugins...")
+ plugins = PluginManager()
+ if not plugins.initialize():
+ print("Shutting down.")
+ return
+
+ print("[Initializer]: Starting proxy...")
+ client = Client(plugins)
+ proxy = Proxy(plugins, client)
+
+ threading.Thread(target = proxy.Start, daemon = True).start()
+
+ print("[Initializer]: Proxy started!")
+
+ print("[Initializer]: Starting GUI...")
+ gui = GUI(plugins, client, proxy)
+ print("[Initializer]: GUI started!")
+
+ gui.start()
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/valorlib b/valorlib
new file mode 160000
index 0000000..7b0cd11
--- /dev/null
+++ b/valorlib
@@ -0,0 +1 @@
+Subproject commit 7b0cd1121ad436eb8fe3015211afa91ac7293ed6
diff --git a/vrelay v1.png b/vrelay v1.png
new file mode 100644
index 0000000..a51f89c
Binary files /dev/null and b/vrelay v1.png differ