Skip to content

M2 Pro 4.0.3

Choose a tag to compare

@jriko9 jriko9 released this 06 Jul 17:19
· 8 commits to main since this release
f44ae60

OBM2 Pro — CHANGELOG v4.0.3

⚠️ Binary removed for security reasons — download the latest release: https://github.com/ObM2/ObM2Pro/releases/latest


Fix — misleading "Error" popup on fresh installs (missing Environment.ini)

Since user-data files (Settings/obSettings.json, Settings/Environment.ini, DB, etc.) are now stripped
from the distributed package before shipping — otherwise an update would silently overwrite a user's
already-customized settings — every fresh install starts with Settings/Environment.ini missing. The old
startup code (MainWindow.xaml.cs) treated that as an exception: it caught the parse failure and popped a
MessageBox titled "Error" telling the user to "fix the issue and try again", right before silently
regenerating the default file and continuing anyway. Users reasonably read that as a crash.

Reworked with if (!File.Exists(OB.envFile)) up front instead of catching the parse failure, then
File.WriteAllText the default .ini content (was File.AppendText, which — combined with the old
exception-driven flow — could double up content instead of cleanly replacing it). Since this now fires on
every fresh install (not a rare edge case), it's silent: LogWarning/LogInfo only, no MessageBox at
all — a blocking popup on every first boot, mid-splash, was itself the wrong UX even once the wording was
fixed. The subsequent parse has its own try/catch that just logs a warning; if Environment ends up null
(genuine corruption after the rewrite), a ?? new EnvironmentSettings() fallback keeps the existing
downstream WordlistTypes.Count == 0 safety net
(which already has its own explicit user-facing message) from throwing a NullReferenceException instead.

File: OpenBullet/MainWindow.xaml.cs.

Feature — Proxy Groups: per-Runner active group

Proxies can now be tagged with a Group (RuriLib/Models/CProxy.cs, defaults to "Default" — existing
proxies keep working unchanged). No more deleting/re-adding proxies to switch pools: assign proxies to a
group from the Proxy Manager (new "Group" column + "Assign Group" bulk action in the sidebar, applies to
the selected rows), then pick which group each Runner should pull from.

  • Scope is per-Runner, not global and not on the Config: each Runner session has its own
    ActiveProxyGroup combo box (next to the "Save to text file" toggle in Runner.xaml), same pattern as
    the existing SaveHitsToTextFile session option — persisted in RunnerSessionData, restored on app
    restart, survives Runner cloning. This lets two Runners on the same Config run in parallel against
    different proxy pools (e.g. one on "USA", one on "ITA").
  • "All" is the sentinel for "no filter" — the default, fully backward-compatible with pre-existing
    sessions and configs. (Initially shipped as the Italian literal "Tutti"; renamed to the neutral,
    untranslated token "All" — consistent with other raw technical values already shown in this UI, e.g.
    ProxyMode/ProxyType enums. A compat shim in ProxyGroupFilter.Filter still treats the old "Tutti"
    value as "no filter" so sessions saved before the rename don't break; a new ProxyGroupFilter.Normalize
    rewrites the legacy value to the canonical "All" when a Runner session is restored from disk, so
    Runners created before the rename display "All" again instead of the stale "Tutti".)
  • Filtering logic extracted into RuriLib/Models/ProxyGroupFilter.cs (pure, unit-tested in
    RuriLib.Tests/ProxyGroupFilterTests.cs), wired into Runner.xaml.cs's LoadProxiesFromManager ahead
    of the existing Socks/Http type filter.
  • RunnerManager reflects the active group in both Card and List view; the "Proxy" column in List view was
    widened (80px → 150px) so short group names weren't clipped against the "Bots" column.
  • The ActiveProxyGroupCombo in Runner.xaml now auto-sizes to its content (was a fixed 110px) and has
    an explicit "Proxy Group:" label next to it.

Fix — Runner: cyclic slowdown during multi-bot runs

The runner would start fast, then periodically slow down almost to a halt for a few seconds, then
recover — repeating in waves. Root cause: Runner.xaml.cs sets ServicePointManager.DefaultConnectionLimit = 10000 once at run start, but BlockRequest.cs (System.Net path) reset it to 100 on every single
HTTP request from every bot
. Since this is a process-wide (not per-request) setting, with N bots running
in parallel the limit kept flapping between 10000 and 100 — once concurrent connections to a host exceeded
100, extra requests queued inside the ServicePoint until slots freed, producing the ban-like stall pattern
even though the target wasn't actually blocking anything. Removed the redundant reset in
RuriLib/Blocks/BlockRequest.cs — the limit is now set only once by the Runner at startup.


Feature — Script block (RUNSCRIPT): real Node.js/Python execution

New stacker block "Script" (LS token RUNSCRIPT, RuriLib/Blocks/BlockScript.cs), same schema as
OpenBullet 2's Script block. Unlike the embedded BEGIN SCRIPT engines (Jint for JS, IronPython for
Python — pure re-implementations with no native modules), this block shells out to a real external
interpreter, so code relying on genuine runtime APIs (e.g. Node's require('crypto')) actually works.

  • Interpreter dropdown: NodeJS (via Jering.Javascript.NodeJS, a persistent out-of-process node
    pool reused across calls — no per-execution spawn overhead) or Python (external python.exe/py.exe
    process, same Process.Start pattern already used by BlockPython/PYRUN).
  • Input Variables: comma-separated names of existing bot variables, injected by name into the script.
  • Output Variables: one per line, Single name or List name (default Single), read back into
    bot variables after execution — matches M2's actual CVar.VarType (no fake Int/Bool typing).
  • Timeout (seconds): kills the process if it exceeds it (Python via Process.Kill(), Node via a
    CancellationToken).
  • Both interpreters remain external dependencies the user must have installed and in PATH — same
    tradeoff OB2 makes, not bundled to keep the app self-contained.

Legacy BEGIN SCRIPT/END SCRIPT (Jint/IronPython) and BlockPython (PYRUN) are untouched — this is
additive, for scripts that need real interpreter APIs.


Fix — Selenium blocks: FormatException on empty/non-numeric input

Several Selenium blocks called int.Parse on raw user input, throwing FormatException ("input string
'' was not in a correct format") on an empty or non-numeric value. Hardened every affected point:

  • Element Action (SBlockElementAction): WaitForElement / WaitForElementNotExists used
    int.Parse(input) inside a swallowing try/catch (also skipping variable replacement, so a
    <TIMEOUT> variable never resolved) → now int.TryParse(ReplaceValues(...)), falling back to 10s and
    honoring variables. SelectOptionByIndex used an unguarded int.Parse that crashed the run → now
    int.TryParse with a clear "requires a numeric index" message.
  • Browser Action (SBlockBrowserAction): SwitchToTab / SetWidth / SetHeight used
    int.Parse(str1) → now int.TryParse with clear errors (SwitchToTab also validates the index against
    the number of open tabs instead of throwing a raw IndexOutOfRange).
  • MouseAction (MouseActionParser): the 14 coordinate parses (MOVEBY, DRAWLINE, DRAWLINEHUMAN,
    DRAGANDDROPWITHOFFSET, SPAWN) used int.Parse on quoted literals → now a tolerant SafeInt helper
    (empty/non-numeric → 0) so a missing coordinate no longer aborts the sequence.

Not touched: the Firefox proxy-port int.Parse(data.Proxy.Port) (comes from the proxy list, not block
input; a valid proxy always carries a numeric port).


Fix — MOUSEACTION element locators: clear "not found" error + variable support

MOVETO ELEMENT/DRAGANDDROP/etc. threw a cryptic Index was out of range whenever the locator
matched nothing, because ParseElement did FindElements(...)[index] on a possibly-empty collection.
Reworked ParseElement to build the By once, then throw a clear message —
MOUSEACTION: no element matched SELECTOR "…" on the current page or index N out of range (…found) —
so the user immediately sees it's a selector/page issue, not a mouse failure. The locator value now
also goes through variable replacement (ParseLiteral(..., true, data)), so ELEMENT ID "<myId>"
works.


Fix — Selenium MOUSEACTION: "move target out of bounds" under Selenium 4

The coordinate-based MOUSEACTION paths (DRAWLINE, DRAWLINEHUMAN, DRAWPOINTS) threw
move target out of bounds on every run (Chrome 149 / Selenium 4.44). Root cause: MouseActionParser
was written for Selenium 3, where MoveToElement(body, x, y) measured the offset from the element's
top-left and out-of-viewport moves were tolerated. Selenium 4's W3C Actions measure the offset
from the element center and reject any move outside the viewport, so MoveToElement(body, 0, 0)
landed at the body center and the accumulated MoveByOffset chain quickly left the viewport →
exception.

Fix: new helper MouseActionParser.MoveAlong(actions, data, points) drives the pointer through the
absolute path points with Actions.MoveToLocation(x, y) (origin = viewport upper-left, the correct
Selenium 4 primitive for absolute coordinates), clamping each point to window.innerWidth/innerHeight
so it never goes out of bounds. The three DRAW paths now build their absolute point list and call
MoveAlong instead of MoveToElement(body, …) + accumulated MoveByOffset. MOVETO ELEMENT /
element clicks were already correct and are unchanged. The human-movement curve (WindMouse) and the
red-dot visualization are preserved.


Feat — Selenium: MOUSEACTION visual block

MOUSEACTION (advanced mouse actions: click/double/right/hold, drag & drop, move to/by, key down/up,
send keys, spawn hook, and human-like movement via DRAWLINEHUMAN/WindMouse) existed only as a
script-only LoliScript command (MouseActionParser) with no block — so users never discovered it.
Added a discoverable visual block SBlockMouseAction exposing it in the stacker.

Thin wrapper, no runtime duplication: Process calls the existing MouseActionParser.Parse(body, data) and invokes the returned action. Serialization mirrors ExecuteJS — ToLS emits
MOUSEACTION <body> with the body flattened to one line; FromLS keeps the remainder as the action
body. Registered in BlockParser (so top-level MOUSEACTION lines now parse as this block; inside
BEGIN SCRIPT they stay a command — backward compatible), BlockCatalog (Selenium / "Mouse action")
and MainWindow block mappings (new green brush).

UI (PageSBlockMouseAction, hybrid): a monospace multi-line editor for the action sequence, an
"Add action" ComboBox + Insert button that appends ready-to-edit templates for all 17 sub-actions
(e.g. DRAWLINEHUMAN "0" "0" -> "700" "700" : 150 1 1), and a fixed syntax-reference footer.
Hardcoded English (stacker convention), no new i18n keys.

Note: this makes explicit that M2 already had human-like mouse movement (DRAWLINEHUMAN) — a
capability OpenBullet 2 lacks in its Selenium blocks.


UI — Selenium blocks: per-action dynamic fields (Element/Browser Action)

Reworked the two Selenium block UIs that inherited the confusing OB1 single-Input layout, where
the same field silently changed meaning per action (output variable name, ignored, a number, or
name||value crammed together) with no on-screen explanation.

Introduced a per-action profile driven by the action ComboBox (ApplyProfile in each page's
code-behind). Each action declares an input mode: None (input field hidden — e.g. Click,
Screenshot, Maximize), Single (one field with an action-specific label/placeholder — e.g.
Timeout (s), Key name, Output variable name, Tab index, Pixels, Cookie domain), or
Pair (two labelled fields joined internally with the right separator — SetAttribute →
Attribute Name||Value, ClickAtCoordinates → X,Y; split on load, re-joined on edit, guarded
by _suspendJoin). The footer help (infoDic) was completed for every action.

Serialization is unchanged: the block model still stores a single Input string, so existing
configs keep loading/saving identically — this is a UI-only refactor of
PageSBlockElementAction/PageSBlockBrowserAction (.xaml + .xaml.cs), no RuriLib/LoliScript
changes, no new i18n keys (stacker Pages use hardcoded English by project convention).

Element Action extras: added the missing Identifier label above the selector textbox; the action
ComboBox is now an hc:ComboBox styled to the same 28px height as the textboxes (was unstyled and
shorter); the Output card is hidden for actions that don't produce output. ScreenshotPageBase64
now shows an explicit "Output variable name" field instead of a bare, ambiguous Input.


Feat — Selenium: new element/browser actions (parity with OpenBullet 2)

Filled the remaining Selenium gaps vs OpenBullet 2. Five new actions, no UI/XAML changes needed
(the action ComboBoxes are populated from Enum.GetNames, so new enum values appear automatically;
the generic Input textbox carries their parameter).

  • Element Action → SetAttribute (SBlockElementAction.cs): sets an element attribute via JS
    (arguments[0].setAttribute(...)). Input format attributeName||value (e.g. value||hello).
  • Browser Action → ClickAtCoordinates (SBlockBrowserAction.cs): clicks the page at absolute
    x,y (Input 120,340, also accepts x|y/x||y) using document.elementFromPoint +
    mousedown/mouseup/click events (works headless, no real cursor). New ClickAtJs const.
  • Browser Action → KeyDownInPage / KeyUpInPage: press/release a key separately (hold key down).
    Input is the key name (Shift, Control, Enter…), resolved to OpenQA.Selenium.Keys via reflection
    (GetSeleniumKey, falls back to the literal string); uses Actions.KeyDown/KeyUp.
  • Browser Action → ScreenshotPageBase64: full visible-page screenshot as base64. Input = output
    variable name (default SCREENSHOT), stored via InsertVariable.

Note: OB2's "Get Attribute Value All" was already covered in M2 by GetAttribute + the Recursive
checkbox (attribute of every matched element → list), so it was not duplicated. Human-like mouse movement
is also already present in M2 via the script-only LoliScript MOUSEACTION command
(RuriLib/LoliScript/Parser/MouseActionParser.cs: CLICK/DOUBLECLICK/RIGHTCLICK, DRAGANDDROP, MOVETO/MOVEBY,
KEYDOWN/KEYUP, SPAWN, DRAWLINE and DRAWLINEHUMAN = WindMouse human curve) — a feature M2 has over OB2,
which lacks it. It has no visual block yet (usable only from the LoliScript editor); a dedicated
MOUSEACTION block is tracked separately.


Feat — Selenium: UndetectedChromeDriver as a global option

Re-introduced UndetectedChromeDriver (Selenium.UndetectedChromeDriver 1.1.4, net8.0),
removed during the .NET 8 cleanup, as a second global option in RL Settings → Selenium.
New bool UseUndetectedChromeDriver in SettingsSelenium.cs (default off, handled by the
reflection-based Reset()). When enabled, the Chrome branch of SBlockBrowserAction.OpenBrowser
builds the driver via UndetectedChromeDriver.Create(...) instead of the classic ChromeDriver;
headless is passed as the Create() parameter (no double --headless on the options), and the
classic CDP stealth injection (DisableAutomation) stays only on the classic branch since UC
applies its own anti-automation patches. The flag is read only inside case BrowserType.Chrome,
so it is ignored when Browser=Firefox. Browser Action block UI untouched. New checkbox in
Selenium.xaml + i18n keys UseUndetectedChromeDriver/...Tooltip (en/it/es). Lets users
compare stealth behavior against the classic driver on the field.

Unlike the classic ChromeDriver, UndetectedChromeDriver.Create(...) does not auto-resolve
the driver (it threw Parameter driverExecutablePath is required). Added ResolveChromeDriverPath()
in SBlockBrowserAction.cs: prefers chromedriver.exe next to the executable (where
CreateDefaultService also looks), otherwise falls back to Selenium Manager
(selenium-manager.exe --browser chrome --output json, parsing driver_path) to download/locate
a Chrome-matched driver. The resolved path is passed as driverExecutablePath.

Also, the classic anti-automation options (--disable-automation, --disable-blink-features=AutomationControlled,
AddExcludedArgument("enable-automation"), useAutomationExtension=false) must be applied only to the
classic driver: UC reserializes the options and rejects them
(unrecognized chrome option: excludeSwitches), so the whole DisableAutomation block is now guarded with
!useUc (UC applies its own stealth patches). Hardened BlockBase.UpdateSeleniumData with a
data?.Driver == null early-return so a failed browser open no longer crashes with a NullReferenceException
on data.Driver.Url (applies to the classic driver too).


Fix — Selenium Browser Action: authenticated proxy fails with native auth popup

Testing SBlockBrowserAction (Open Browser) with an authenticated proxy
(host:port:user:pass), Chrome showed the native HTTP Basic Auth popup and the
run always failed/hung, regardless of credentials.

Cause: CProxy.Proxy only ever holds host:port (Username/Password are
parsed into separate fields, never re-embedded in the string), and even if they
were, Chrome's --proxy-server CLI flag ignores inline credentials by
design
(has since ~2015). The auth challenge then surfaces as a native OS-level
dialog outside the DOM — not a JS alert() — so SwitchTo().Alert() can't
reach it and Selenium has no way to answer it.

OpenBullet 2 doesn't hit this: it works around the same Chrome limitation with a
temporary MV3 extension (chrome.proxy.settings.set + chrome.webRequest.onAuthRequired)
that supplies the credentials directly, no popup ever shown.

Fix: ported the same approach. New RuriLib.Extensions.ChromeOptionsExtensions.AddProxy(CProxy):
if the proxy needs authentication, generates a temp MV3 extension (manifest.json +
background.js, zipped) with the credentials baked in and loads it via
ChromeOptions.AddExtension(zip); otherwise falls back to the plain
--proxy-server=... argument as before. SBlockBrowserAction.cs now calls
chromeop.AddProxy(data.Proxy) instead of building the --proxy-server string
inline. Works in headless too (packaged .zip extension, unlike unpacked
--load-extension which classic headless blocks).

Firefox: same underlying issue (native auth popup, network.proxy.* prefs
carry no credentials). Fixed the same way, adapted to Firefox's WebExtension
APIs: RuriLib.Extensions.FirefoxDriverExtensions.InstallProxyAuthExtension(CProxy)
builds a temp MV2 extension (browser.proxy.settings.set + blocking
browser.webRequest.onAuthRequired), packs it as .xpi and installs it via
FirefoxDriver.InstallAddOnFromFile(path, temporary: true) — a Marionette
"temporary add-on" install, so no AMO signature is required. Must run right
after the driver is created and before the first navigation, otherwise the
first request goes out unauthenticated. When the proxy needs auth, the
existing network.proxy.* profile prefs are skipped (the extension owns proxy
config in that case); unauthenticated proxies keep using the prefs path
unchanged.

Files: RuriLib/Extensions/ChromeOptionsExtensions.cs (new),
RuriLib/Extensions/FirefoxDriverExtensions.cs (new),
RuriLib/Blocks/SBlockBrowserAction.cs.


Fix — Element Action: WaitForElement/WaitForElementNotExists never wrote the Output Variable

The Element Action block's WaitForElement and WaitForElementNotExists
actions only waited/logged a timeout message — they never added anything to
the internal outputs list. Since the variable insertion at the end of
Process is gated on outputs.Count != 0, no variable was ever created for
these two actions: not even an empty one. The Output Variable field stayed
visible in the UI (misleading), the Log showed no insertion, and the Variables
tab had nothing.

Fix: both actions now push a boolean result into outputs (found for
WaitForElement, !found for WaitForElementNotExists — True means the
wait condition was satisfied, False means it timed out), so the Output
Variable is populated like the other boolean actions (IsDisplayed,
IsEnabled, ...) and can be branched on with a Keycheck.
File: RuriLib/Blocks/SBlockElementAction.cs.


Hardening — Chrome: baseline stealth script via CDP

Added a small CDP-based hardening pass for Chrome, gated by the existing
Disable Automation setting (SettingsSelenium.DisableAutomation, same
flag as --disable-blink-features=AutomationControlled).

On browser open, chromeDriver.ExecuteCdpCommand("Page.addScriptToEvaluateOnNewDocument", ...)
injects a script that runs before any page script on every new
document/frame, patching the automation signals most commonly probed beyond
navigator.webdriver (already covered by the existing Chrome flag):
navigator.plugins (non-empty fake list), navigator.languages,
window.chrome.runtime (missing on automated Chrome), and
navigator.permissions.query('notifications') (classic headless/webdriver
permission-state mismatch). Pure CDP command, no binary patching, safe across
Chrome/chromedriver versions.

File: RuriLib/Blocks/SBlockBrowserAction.cs.


Builds on: CHANGELOG_v4.0.2.md.