using System; using System.Collections.Generic; using System.Linq; 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 (distance <= MoveSpeed) public const int MoveSpeed = 30; // This server-side value represents the client's connection time public static long ConnectionTime = DateTime.UtcNow.Ticks; // 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. var timestamp = DateTime.UtcNow.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(); // Perform the actual simulation Simulate("client", clientSeed, mob, timestamp); Console.WriteLine(); // This is the packet that the client sends to the server to represent the simulation var packet = new Packet { Timestamp = timestamp, Target = mob }; // This represents a packet being sent to the server for validation ServerValidate(packet); Console.ReadKey(); } /// /// 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; if (packet.Timestamp < ConnectionTime) { Console.WriteLine("The client sent a packet prior to his connection time. (Buffered hack)"); return; } // Is this the first snapshot? if (LastTimestamp > 0) { delta = packet.Timestamp - LastTimestamp; if (delta <= 0) { Console.WriteLine("The client's packet is hacked, invalid delta. (Delta hack)"); return; } // Check packet timestamp based on movement / attack speed if (delta < (packet.Target / 3)) { Console.WriteLine("The client's packet is hacked, invalid delta. (Distance hack"); return; } } // Save the last timestamp, we will use this to calculate deltas for future snapshots 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! Note, the server should also check the delta to ensure travel is possible if (packet.Target > MoveSpeed) { Console.WriteLine("The client's packet is hacked, SPEEDHACK!"); return; } // The server simulates the attack using the seed Simulate("server", serverSeed, packet.Target, packet.Timestamp); // Packet timestamp is not within the required bounds if (packet.Timestamp > DateTime.UtcNow.Ticks) { Console.WriteLine("The client's packet is hacked, buffered 'future' packets."); return; } Console.WriteLine("The packet is valid!"); } /// /// This represents the actual game simulation, which both the client and server use /// /// The seed used for this simulation /// The particular action/target /// The base timestamp (start) public static void Simulate(String environment, Random seed, int target, long timestamp) { // For the sake of simulation, we will perform 1 calculation per 'distance' of the target for (var count = 0; count < target; count++) { // This is the actual randomly generated number we will use for this simulation, it's using the seed for each 'Next()' var rng = (seed.Next() % timestamp); // For the sake of explanation, this damage represents an actual 'damage roll' var damage = new Random(Convert.ToInt32(rng)).Next(1, 10); // This output will be the same on the client/server due to the deterministic seed Console.WriteLine(environment + " simulation output: " + damage); } } public class Packet { public long Timestamp { get; set; } public int Target { get; set; } public String Signature { get; set; } } // The server uses this delta to validate snapshots private static long LastTimestamp { get; set; } } }