using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; namespace POEExample { class Program { // This is the shared seed that the client and server both use for the simulation public const int SharedSeed = 12345; // This represents the player's movement speed, he can only attack things in range public const int MoveSpeed = 30; // This functions returns a list of mobs that the client/server knows about static List GetMobs() { var mobs = new List(); // Lets add mobs in a few locations, the number is their distance from the player mobs.Add(10); mobs.Add(15); mobs.Add(30); mobs.Add(50); mobs.Add(70); mobs.Add(100); return mobs; } static void Main(string[] args) { // This is the client seed var clientSeed = new Random(SharedSeed); Console.WriteLine(Environment.NewLine); // Every snapshot requires a timestamp, which starts at the beginning of each snapshot. // The client cannot change this without the server knowing! var timestamp = DateTime.Now.Ticks; // This is the mob you decide to target, it must be valid and within distance or the server knows you cheated var mob = GetMobs().First(); // Move to and attack the mob, you can insert ANY mob you want into here as long as it's a valid target Attack(clientSeed, mob, timestamp); Console.WriteLine(); // This is the packet that the client sends to the server. If you modify it, the server can identify that it's fake. var packet = new Packet { Timestamp = timestamp, Signature = Hash(clientSeed.Next().ToString(), timestamp.ToString()), Target = mob }; // Send the packet to the server to see if you cheated ServerValidate(packet); Console.ReadKey(); } // In order to attack a mob, the player must perform 1 calculation per 5 distance of the mob. The server can determine if these calculations were performed or not. private static void Attack(Random seed, int mob, long baseTimestamp) { for (var count = 0; count < (mob / 5); count++) { // This is the seed used for a combat calculation. The client can hack this all he wants, because the server is an authority Console.WriteLine("Attack seed: " + (seed.Next() * baseTimestamp)); } } /// /// The server will validate the packet to see if the player has cheated at all. This represents the API function on the server-side of PoE /// private static void ServerValidate(Packet packet) { // This is the server seed var serverSeed = new Random(SharedSeed); // How much time has passed since the last snapshot? long delta = 0; // Is this the first snapshot? if (LastTimestamp > 0) { delta = packet.Timestamp - LastTimestamp; if (delta <= 0) { Console.WriteLine("The client's packet is hacked due to an invalid delta."); return; } // Check packet timestamp based on movement / attack speed if (delta < (packet.Target / 3)) { Console.WriteLine("The client's packet is hacked, invalid delta."); return; } } // Save the last timestamp, we will use this to calculate deltas LastTimestamp = packet.Timestamp; // The mob doesn't exist, cheater! if (GetMobs().All(each => each != packet.Target)) { Console.WriteLine("The client's packet is hacked due to the mob not existing."); return; } // The player attacked a mob out of range, cheater! if (packet.Target > MoveSpeed) { Console.WriteLine("The client's packet is hacked, SPEEDHACK!"); return; } // The server simulates the attack Attack(serverSeed, packet.Target, packet.Timestamp); // Packet timestamp is not within the required bounds if (packet.Timestamp > DateTime.Now.Ticks) { Console.WriteLine("The client's packet is hacked, buffered 'future' packets."); return; } Console.WriteLine(); // The server can now validate the signature to make sure this packet wasn't forged if (Hash(serverSeed.Next().ToString(), packet.Timestamp.ToString()) == packet.Signature) { Console.WriteLine("The client's packet is valid"); } else { Console.WriteLine("The client's packet is hacked."); } } public class Packet { public long Timestamp { get; set; } public int Target { get; set; } public String Signature { get; set; } } public static String Hash(String seed, String value) { return Convert.ToBase64String(SHA.ComputeHash(Encoding.UTF8.GetBytes(seed + value))); } private static readonly SHA1CryptoServiceProvider SHA = new SHA1CryptoServiceProvider(); // The server uses this delta to validate snapshots private static long LastTimestamp { get; set; } } }