public static Nullable <DateTime> GetBuildUTCTimestamp(ConsoleContent console)
        {
            using (Stream stream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("OpenGameCamera.BuildTimestamp.txt"))
            {
                if (stream == null)
                {
                    console.Error("Cannot find UTC timestamp for current build");
                    return(null);
                }

                byte[] buf = new byte[stream.Length];
                stream.Read(buf, 0, buf.Length);

                string rawData = Encoding.Unicode.GetString(buf);
                if (rawData.Length <= 50)
                {
                    console.Error("Invalid timestamp format (too short): " + rawData.Replace("\n", "\\n"));
                    return(null);
                }

                int rawTimestampStart = rawData.LastIndexOf("\n", rawData.Length - 2);
                if (rawTimestampStart < 0)
                {
                    console.Error("Invalid timestamp format: " + rawData.Replace("\n", "\\n"));
                    return(null);
                }

                string rawTimestamp = rawData.Substring(rawTimestampStart + 1);
                if (rawTimestamp.Length < 24)
                {
                    console.Error("Bad build timestamp format: " + rawTimestamp);
                    return(null);
                }

                string rawTimezone = rawTimestamp.Substring(22);

                int minOffset;

                try
                {
                    minOffset = Int32.Parse(rawTimezone, NumberStyles.AllowTrailingWhite);
                }
                catch (FormatException)
                {
                    console.Error("Timezone modifier not a number: " + rawTimezone);
                    return(null);
                }
                catch (OverflowException)
                {
                    console.Error("Timezone modifier too big: " + rawTimezone);
                    return(null);
                }

                int offsetHours = minOffset / 60;
                int offsetMins  = minOffset % 60;

                string utcStamp = String.Format("{0}{1:00}:{2:00}", rawTimestamp.Substring(0, 22), offsetHours, offsetMins);
                return(DateTime.ParseExact(utcStamp, "yyyyMMddHHmmss.ffffffzzz", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal));
            }
        }
        public static bool ExtractResourceToFile(ConsoleContent console, string resourceName, string filename)
        {
            using (Stream stream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
            {
                if (stream == null)
                {
                    console.Error("cannot find resource " + resourceName);
                    return(false);
                }

                using (FileStream fs = new FileStream(filename, FileMode.Create))
                {
                    byte[] buf = new byte[stream.Length];
                    stream.Read(buf, 0, buf.Length);

                    try
                    {
                        fs.Write(buf, 0, buf.Length);
                    }
                    catch (IOException err)
                    {
                        console.Error("cannot write to " + filename + ": " + err);
                        return(false);
                    }
                }
            }

            console.Info("OpenGameCamera extracted to " + filename);
            return(true);
        }
        public static bool Inject(ConsoleContent console, string dll_path)
        {
            IntPtr wnd = FindWindow(null, "STAR WARS Battlefront II");

            if (wnd == null)
            {
                console.Error("failed to find an active Star Wars Battlefront II game: code=" + Marshal.GetLastWin32Error());
                return(false);
            }

            uint processId = 0;

            GetWindowThreadProcessId(wnd, out processId);

            if (processId == 0)
            {
                console.Error("failed to obtain process ID for Star Wars Battlefront II game: code=" + Marshal.GetLastWin32Error());
                return(false);
            }

            console.Info("found SWBF2 at pid " + processId);

            IntPtr procHandle = OpenProcess(
                PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,
                false,
                (int)processId
                );

            if (procHandle == null)
            {
                console.Error("failed to open pid=" + processId + ": code=" + Marshal.GetLastWin32Error());
                return(false);
            }

            try
            {
                IntPtr loadLibraryPtr = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryW");

                console.Info("LoadLibraryW found at " + loadLibraryPtr.ToInt64().ToString("X16"));

                byte[] pathBuf = Encoding.Unicode.GetBytes(dll_path);

                IntPtr targetAddr = VirtualAllocEx(
                    procHandle,
                    IntPtr.Zero,
                    (uint)(pathBuf.Length + Marshal.SizeOf(typeof(ushort))),
                    MEM_COMMIT | MEM_RESERVE,
                    PAGE_READWRITE
                    );
                if (targetAddr == null)
                {
                    console.Error("failed to allocate memory in target process: code=" + Marshal.GetLastWin32Error());
                    return(false);
                }

                console.Info("memory allocated in SWBF2: 0x" + targetAddr.ToInt64().ToString("X16"));

                UIntPtr bytesWritten;

                if (!WriteProcessMemory(procHandle, targetAddr, pathBuf, (uint)pathBuf.Length, out bytesWritten))
                {
                    console.Error("failed to write memory in target process: code=" + Marshal.GetLastWin32Error());
                    return(false);
                }

                console.Info("wrote " + bytesWritten.ToUInt64() + " bytes to allocated memory");

                IntPtr threadId;

                if (CreateRemoteThread(procHandle, IntPtr.Zero, 0, loadLibraryPtr, targetAddr, 0, out threadId) == null)
                {
                    console.Error("failed to launch remote thread: code=" + Marshal.GetLastWin32Error());
                    return(false);
                }

                console.Info("OGC injected into SWBF2: threadId=" + threadId.ToInt64());
            }
            finally
            {
                CloseHandle(procHandle);
            }

            return(true);
        }