using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Net.Sockets; using System.Runtime.InteropServices; using System.Threading; namespace IdleMafiaBot { static class Native { public struct POINT { public int X, Y; } public struct RECT { public int Left, Top, Right, Bottom; } public static uint Now { get { return unchecked((uint)Environment.TickCount); } } public static uint LastInputTick() { return 0; } public static IntPtr RealPixels() { return IntPtr.Zero; } public static void RestorePixels(IntPtr before) { } public static bool GetCursorPos(out POINT p) { p = new POINT { X = Rfb.X, Y = Rfb.Y }; return true; } public static IntPtr GetForegroundWindow() { return RobloxWindow.One; } static long Meminfo(string[] lines, string key) { foreach (var l in lines) if (l.StartsWith(key + ":", StringComparison.Ordinal)) { var p = l.Substring(key.Length + 1).Trim().Split(' '); long kb; if (long.TryParse(p[0], out kb)) return kb * 1024; } return -1; } public static long BotMemory() { try { using (var p = Process.GetCurrentProcess()) return p.WorkingSet64; } catch (Exception) { return -1; } } public static string Size(long bytes) { var ci = System.Globalization.CultureInfo.InvariantCulture; double gb = bytes / (1024.0 * 1024 * 1024); if (gb < 1) return (bytes >> 20).ToString(ci) + " MB"; return gb.ToString(gb >= 10 ? "0" : "0.0", ci) + " GB"; } public static string MemoryText() { long own = BotMemory(); string bot = own >= 0 ? "the bot uses " + Size(own) : "the bot's own memory isn't known"; try { var m = File.ReadAllLines("/proc/meminfo"); return bot + "; this machine has " + Size(Meminfo(m, "MemAvailable")) + " of " + Size(Meminfo(m, "MemTotal")) + " free"; } catch (Exception) { return bot; } } } sealed class OutOfMemoryPause : Exception { public OutOfMemoryPause(Exception e) : base("the machine ran out of memory", e) { } } sealed class WindowChangedException : Exception { public WindowChangedException(string m) : base(m) { } } sealed class UserBusyException : Exception { public UserBusyException() : base("you started using the PC") { } } static class Rfb { const int Port = 5901; static readonly object gate = new object(); static TcpClient client; static NetworkStream net; public static volatile int X, Y; static byte[] Need(int n) { var b = new byte[n]; for (int got = 0; got < n;) { int c = net.Read(b, got, n - got); if (c <= 0) throw new IOException("the desktop closed the connection"); got += c; } return b; } static int BigEnd(byte[] b, int at, int n) { int v = 0; for (int i = 0; i < n; i++) v = (v << 8) | b[at + i]; return v; } static void Connect() { client = new TcpClient("127.0.0.1", Port) { NoDelay = true }; client.ReceiveTimeout = 5000; net = client.GetStream(); var version = Need(12); net.Write(version, 0, 12); var types = Need(Need(1)[0]); if (Array.IndexOf(types, (byte)1) < 0) throw new IOException("the desktop wants a password: a wayvnc of your own on port " + Port + ", or ~/.config/wayvnc/config"); net.WriteByte(1); if (BigEnd(Need(4), 0, 4) != 0) throw new IOException("the desktop refused the connection"); net.WriteByte(1); Need(20); Need(BigEnd(Need(4), 0, 4)); client.ReceiveTimeout = 0; var drain = new Thread(Drain) { IsBackground = true, Name = "rfb" }; drain.Start(net); } static void Drain(object stream) { var b = new byte[4096]; try { while (((NetworkStream)stream).Read(b, 0, b.Length) > 0) { } } catch (Exception) { } } static void Send(byte[] m) { lock (gate) { try { if (client == null) Connect(); net.Write(m, 0, m.Length); } catch (Exception e) { try { if (client != null) client.Close(); } catch (Exception) { } client = null; throw new WindowChangedException("the desktop's mouse didn't answer (" + e.Message + ")"); } } } public static void Pointer(int x, int y, int mask) { X = x; Y = y; Send(new[] { (byte)5, (byte)mask, (byte)(x >> 8), (byte)x, (byte)(y >> 8), (byte)y }); } public static void Key(uint sym, bool down) { Send(new[] { (byte)4, (byte)(down ? 1 : 0), (byte)0, (byte)0, (byte)(sym >> 24), (byte)(sym >> 16), (byte)(sym >> 8), (byte)sym }); } } sealed class RobloxWindow { public static readonly IntPtr One = new IntPtr(1); public IntPtr Handle { get; private set; } public int Width { get; private set; } public int Height { get; private set; } public IntPtr Pinned; public bool NoFallback; public static volatile IntPtr[] Taken = new IntPtr[0]; static int checkedAt = Environment.TickCount - 60000; static bool alive; static bool Alive() { if (unchecked(Environment.TickCount - checkedAt) < 2000) return alive; checkedAt = Environment.TickCount; bool found = false; foreach (var d in Directory.GetDirectories("/proc")) { string pid = Path.GetFileName(d); if (pid.Length == 0 || !char.IsDigit(pid[0])) continue; try { if (File.ReadAllText(d + "/comm").Trim() == "sober") { found = true; break; } } catch (Exception) { } } return alive = found; } public static List All() { var all = new List(); if (Alive()) all.Add(One); return all; } public bool Find() { Handle = Alive() ? One : IntPtr.Zero; if (Handle == IntPtr.Zero) return false; if (Width == 0) using (var f = Grab()) { } return true; } public bool Minimized { get { return false; } } public static volatile bool ScreenFirst; public bool ScreenOnly; public static int EmptyPictures, ScreenCopies, Asked, HalfDrawn; internal static bool TestWhite = false; public static volatile string CoveredBy; public Frame Capture() { if (Handle == IntPtr.Zero || !Alive()) throw new WindowChangedException("Roblox was closed"); return Grab(); } Frame Grab() { byte[] ppm; var start = new ProcessStartInfo("grim", "-t ppm -") { UseShellExecute = false, RedirectStandardOutput = true, CreateNoWindow = true }; using (var p = Process.Start(start)) using (var mem = new MemoryStream(7 * 1024 * 1024)) { p.StandardOutput.BaseStream.CopyTo(mem); p.WaitForExit(); if (mem.Length < 32) throw new WindowChangedException("the desktop gave no picture"); ppm = mem.ToArray(); } int at = 0, w = Number(ppm, ref at, true), h = Number(ppm, ref at, false); Number(ppm, ref at, false); at++; var bmp = new Bitmap(w, h, PixelFormat.Format32bppArgb); var data = bmp.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); var row = new byte[data.Stride]; for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++, at += 3) { row[x * 4] = ppm[at + 2]; row[x * 4 + 1] = ppm[at + 1]; row[x * 4 + 2] = ppm[at]; row[x * 4 + 3] = 255; } Marshal.Copy(row, 0, data.Scan0 + y * data.Stride, data.Stride); } bmp.UnlockBits(data); Width = w; Height = h; return new Frame(bmp, true); } static int Number(byte[] b, ref int at, bool first) { if (first) at = 2; while (b[at] == 32 || b[at] == 10 || b[at] == 13 || b[at] == 9) at++; int v = 0; while (b[at] >= 48 && b[at] <= 57) v = v * 10 + b[at++] - 48; return v; } static int otherAt = Environment.TickCount - 60000; static string other; static readonly System.Text.RegularExpressions.Regex WindowRx = new System.Text.RegularExpressions.Regex( @"""name"":\s*""((?:[^""\\]|\\.)*)""(?:(?!""name"").)*?""pid"":\s*\d+", System.Text.RegularExpressions.RegexOptions.Singleline); public static string OtherWindow() { if (unchecked(Environment.TickCount - otherAt) < 15000) return other; otherAt = Environment.TickCount; string found = null, blind = null; try { var start = new ProcessStartInfo("swaymsg", "-t get_tree -r") { UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true }; string sock = SwaySocket(); if (sock == null) blind = "the bot's desktop (sway) wasn't found"; else { start.EnvironmentVariables["SWAYSOCK"] = sock; string tree; using (var p = Process.Start(start)) { tree = p.StandardOutput.ReadToEnd(); p.WaitForExit(5000); } if (tree.Trim().Length == 0) blind = "its desktop (sway) didn't answer"; var names = new List(); foreach (System.Text.RegularExpressions.Match m in WindowRx.Matches(tree)) names.Add(m.Groups[1].Value); if (names.Count > 1) found = names.FirstOrDefault(n => n != "Sober") ?? "a second Sober"; } } catch (Exception e) { blind = "swaymsg: " + e.Message; } if (blind != null && saidBlind == null) { var note = Note; if (note != null) note("Roblox's own windows (its Security check) can't be seen: " + blind); } saidBlind = blind; return other = found; } static string saidBlind; public static Action Note; static string SwaySocket() { string up = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory.TrimEnd('/')); if (string.IsNullOrEmpty(up)) return null; try { string[] note = File.ReadAllText(Path.Combine(up, ".desktop")).Split((char[])null, StringSplitOptions.RemoveEmptyEntries); if (note.Length >= 2 && OurSway(note[1], up)) return note[1]; } catch (Exception) { } string run = Environment.GetEnvironmentVariable("XDG_RUNTIME_DIR") ?? ""; if (run.Length == 0 || !Directory.Exists(run)) return null; return Directory.GetFiles(run, "sway-ipc.*.sock").Where(f => OurSway(f, up)).OrderByDescending(f => File.GetLastWriteTimeUtc(f)).FirstOrDefault(); } static bool OurSway(string sock, string up) { try { var m = System.Text.RegularExpressions.Regex.Match(Path.GetFileName(sock), @"^sway-ipc\.\d+\.(\d+)\.sock$"); if (!m.Success) return false; string proc = "/proc/" + m.Groups[1].Value + "/"; if (File.ReadAllText(proc + "comm").Trim() != "sway") return false; return File.ReadAllText(proc + "cmdline").Replace('\0', ' ').Contains(Path.Combine(up, "sway.conf")); } catch (Exception) { return false; } } public bool InSight() { return true; } public static void NoteUser() { } public bool UserActiveWithin(int ms) { return false; } public bool UserInputSinceBot() { return false; } public bool BeginAct() { return true; } public bool Front() { return Handle != IntPtr.Zero; } public void EndAct(bool giveFocusBack, bool park = true) { if (park) Park(); } public bool IsForeground { get { return Handle != IntPtr.Zero; } } public void GiveFocusBack(IntPtr w) { } public string FrontWindow() { return "Roblox"; } public const string PlaceId = "73897506680154"; public static void StartGame() { string script = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "game.sh"); using (Process.Start(new ProcessStartInfo("/bin/sh", "\"" + script + "\" " + PlaceId + " again") { UseShellExecute = false })) { } } public DateTime LastInput { get; private set; } public Point ParkAt = new Point(600, 170); void Live() { if (Handle == IntPtr.Zero || !Alive()) throw new WindowChangedException("Roblox was closed"); } void MoveTo(int x, int y) { Live(); x = Math.Max(0, Math.Min(Width - 1, x)); y = Math.Max(0, Math.Min(Height - 1, y)); Rfb.Pointer(Math.Max(0, x - 4), Math.Max(0, y - 4), 0); Thread.Sleep(60); Rfb.Pointer(x, y, 0); Thread.Sleep(120); } int PastChat(int x, int y) { return x < 250 && y > 130 && y < 177 ? 178 : y; } public void Click(int x, int y) { y = PastChat(x, y); MoveTo(x, y); Rfb.Pointer(Rfb.X, Rfb.Y, 1); Thread.Sleep(70); Rfb.Pointer(Rfb.X, Rfb.Y, 0); LastInput = DateTime.UtcNow; Thread.Sleep(60); Park(); } public void SlowClick(int x, int y) { Live(); y = PastChat(x, y); for (int i = 0; i <= 8; i++) { Rfb.Pointer(x + (8 - i) * 6, y, 0); Thread.Sleep(25); } Thread.Sleep(150); Rfb.Pointer(x, y, 1); Thread.Sleep(120); Rfb.Pointer(x, y, 0); LastInput = DateTime.UtcNow; Thread.Sleep(60); Park(); } static uint Sym(byte vk) { switch (vk) { case 0x08: return 0xff08; case 0x0D: return 0xff0d; case 0x1B: return 0xff1b; case 0x20: return 0x20; } if (vk >= 0x70 && vk <= 0x7B) return 0xffbeu + (uint)(vk - 0x70); return char.ToLowerInvariant((char)vk); } void Press(uint sym) { Rfb.Key(sym, true); Thread.Sleep(30); Rfb.Key(sym, false); LastInput = DateTime.UtcNow; Thread.Sleep(50); } public void TypeKeys(string keys) { Live(); foreach (char ch in keys ?? "") { byte vk = ch == (char)8 ? (byte)0x08 : ch == (char)13 ? (byte)0x0D : ch == ' ' ? (byte)0x20 : char.IsLetterOrDigit(ch) && ch < 128 ? (byte)char.ToUpperInvariant(ch) : (byte)0; if (vk != 0) Press(Sym(vk)); } } public void PressKey(byte vk) { Live(); Press(Sym(vk)); Thread.Sleep(250); } public void Hover(int x, int y) { MoveTo(x, y); } public void LeaveToPark() { Live(); int fx = Rfb.X, fy = Rfb.Y; for (int i = 1; i <= 10; i++) { Rfb.Pointer(fx + (ParkAt.X - fx) * i / 10, fy + (ParkAt.Y - fy) * i / 10, 0); Thread.Sleep(30); } } public void SweepFromTop() { LeaveToPark(); } public void Scroll(int x, int y, int notches) { MoveTo(x, y); int mask = notches > 0 ? 8 : 16; for (int i = 0; i < Math.Abs(notches); i++) { Rfb.Pointer(Rfb.X, Rfb.Y, mask); Rfb.Pointer(Rfb.X, Rfb.Y, 0); LastInput = DateTime.UtcNow; Thread.Sleep(12); } Thread.Sleep(350); Park(); } public void Park() { if (Handle == IntPtr.Zero) return; Rfb.Pointer(Math.Max(0, ParkAt.X - 2), ParkAt.Y, 0); Thread.Sleep(10); Rfb.Pointer(ParkAt.X, ParkAt.Y, 0); } } }