using System.Collections.Generic;using System.Diagnostics;using System.Drawing.Imaging;using System.Net.Sockets;using System.Runtime.InteropServices; 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) if (l.StartsWith(key + ":", StringComparison.Ordinal)) var p = l.Substring(key.Length + 1).Trim().Split(' '); if (long.TryParse(p[0], out kb)) return kb * 1024; 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() string bot = own >= 0 ? "the bot uses " + Size(own) : "the bot's own memory isn't known"; 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 readonly object gate = new object(); static NetworkStream net; public static volatile int X, Y; static byte[] Need(int 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"); static int BigEnd(byte[] b, int at, int n) for (int i = 0; i < n; i++) v = (v << 8) | b[at + i]; client = new TcpClient("127.0.0.1", Port) { NoDelay = true }; client.ReceiveTimeout = 5000; net = client.GetStream(); 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"); if (BigEnd(Need(4), 0, 4) != 0) throw new IOException("the desktop refused the connection"); Need(BigEnd(Need(4), 0, 4)); client.ReceiveTimeout = 0; var drain = new Thread(Drain) { IsBackground = true, Name = "rfb" }; static void Drain(object stream) try { while (((NetworkStream)stream).Read(b, 0, b.Length) > 0) { } } static void Send(byte[] m) if (client == null) Connect(); net.Write(m, 0, m.Length); try { if (client != null) client.Close(); } catch (Exception) { } throw new WindowChangedException("the desktop's mouse didn't answer (" + e.Message + ")"); public static void Pointer(int x, int y, int mask) 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 static volatile IntPtr[] Taken = new IntPtr[0]; static int checkedAt = Environment.TickCount - 60000; if (unchecked(Environment.TickCount - checkedAt) < 2000) return alive; checkedAt = Environment.TickCount; 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; } } public static List<IntPtr> All() var all = new List<IntPtr>(); if (Alive()) all.Add(One); Handle = Alive() ? One : IntPtr.Zero; if (Handle == IntPtr.Zero) return false; if (Width == 0) using (var f = Grab()) { } public bool Minimized { get { return false; } } public static volatile bool ScreenFirst; public static int EmptyPictures, ScreenCopies, Asked, HalfDrawn; internal static bool TestWhite = false; public static volatile string CoveredBy; if (Handle == IntPtr.Zero || !Alive()) throw new WindowChangedException("Roblox was closed"); 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); if (mem.Length < 32) throw new WindowChangedException("the desktop gave no picture"); int at = 0, w = Number(ppm, ref at, true), h = Number(ppm, ref at, false); Number(ppm, ref at, false); 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]; Marshal.Copy(row, 0, data.Scan0 + y * data.Stride, data.Stride); return new Frame(bmp, true); static int Number(byte[] b, ref int at, bool first) while (b[at] == 32 || b[at] == 10 || b[at] == 13 || b[at] == 9) at++; while (b[at] >= 48 && b[at] <= 57) v = v * 10 + b[at++] - 48; static int otherAt = Environment.TickCount - 60000; 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; 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"; start.EnvironmentVariables["SWAYSOCK"] = sock; using (var p = Process.Start(start)) tree = p.StandardOutput.ReadToEnd(); if (tree.Trim().Length == 0) blind = "its desktop (sway) didn't answer"; var names = new List<string>(); 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); } public static Action<string> Note; static string SwaySocket() string up = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory.TrimEnd('/')); if (string.IsNullOrEmpty(up)) return null; string[] note = File.ReadAllText(Path.Combine(up, ".desktop")).Split((char[])null, StringSplitOptions.RemoveEmptyEntries); if (note.Length >= 2 && OurSway(note[1], up)) return note[1]; 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) 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); if (Handle == IntPtr.Zero || !Alive()) throw new WindowChangedException("Roblox was closed"); void MoveTo(int x, int y) 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); int PastChat(int x, int y) return x < 250 && y > 130 && y < 177 ? 178 : y; public void Click(int x, int y) Rfb.Pointer(Rfb.X, Rfb.Y, 1); Rfb.Pointer(Rfb.X, Rfb.Y, 0); LastInput = DateTime.UtcNow; public void SlowClick(int x, int y) for (int i = 0; i <= 8; i++) Rfb.Pointer(x + (8 - i) * 6, y, 0); LastInput = DateTime.UtcNow; case 0x08: return 0xff08; case 0x0D: return 0xff0d; case 0x1B: return 0xff1b; if (vk >= 0x70 && vk <= 0x7B) return 0xffbeu + (uint)(vk - 0x70); return char.ToLowerInvariant((char)vk); LastInput = DateTime.UtcNow; public void TypeKeys(string keys) 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) public void Hover(int x, int y) { MoveTo(x, y); } public void LeaveToPark() 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); public void SweepFromTop() { LeaveToPark(); } public void Scroll(int x, int y, int notches) 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; if (Handle == IntPtr.Zero) return; Rfb.Pointer(Math.Max(0, ParkAt.X - 2), ParkAt.Y, 0); Rfb.Pointer(ParkAt.X, ParkAt.Y, 0);