Repository navigation
OBM2 Pro — CHANGELOG v4.1.2
Tools
- ULP Tools — new "Split File" tab. Split a huge list into smaller sequential files capped at a chosen size (MB). Streams the file line by line (handles multi-GB inputs without loading them into RAM), with a configurable output filename prefix, output folder picker, drag & drop input, optional global duplicate removal and empty-line skipping, live progress, cancel support, and stats (lines written / files created / elapsed).
OB2 / OPK
- Deterministic OB2 config errors no longer freeze the runner. When an OB2/OPK config hits an error that is the same on every line — an unrecognized block or method, a missing type or interpreter, a failed compile — retrying it is pointless, but the runner used to retry it on every line (OB2's retry cap is unlimited by default), flooding the log and locking up M2 until force-closed. The OB2 engine now flags these errors as non-retryable (classified by exception type, not by message), and the runner stops cleanly with a clear message naming the error instead of looping; the current line is sent to ToCheck so it isn't lost. Transient errors (timeouts, proxy bans, socket errors) are unaffected and still retry as before.
- The "Wrong WordlistType" block at runner start now says how to fix it. When the selected Wordlist Type isn't among the ones an OB2/OPK config declares, the runner still blocks the start (the type's slices may line up by luck but the config declares something else), but the message now points to the actual fix: set the allowed type in Config Settings → Data — editable for .opk since 4.1.2 — e.g. add "Default" (USER, PASS) as a second allowed type and save. This is the common case where an .opk declares "Credentials" while its script reads
input.USER/input.PASS. The message is localized (title / body / hint) in English, Italian and Spanish. - Clearer error when an OB2/OPK config uses a slice the selected Wordlist Type doesn't have. OB2 scripts read inputs as
input.<SLICE>; if the script usesinput.USERbut the chosen Wordlist Type has e.g.USERNAME, PASSWORD(common with imported .opk: the config expectsUSER/PASSwhile M2's "Credentials" type isUSERNAME/PASSWORD), the dynamic runtime threw an opaque'System.Dynamic.ExpandoObject' does not contain a definition for 'USER'. That message is now rewritten to name the missing slice, list the slices the selected type actually has, and say how to fix it ("Pick (or define) a Wordlist Type whose slices include 'USER' — e.g. the Default type uses USER, PASS"). Flagged non-retryable so it fails fast instead of looping. - Fixed: OB2/OPK configs using IronPython failed with "The IronPython engine is not initialized". The OB2 engine read an IronPython
ScriptEnginefrom the bot (ironPyEngine) that nothing ever created, so any OB2 config running an IronPython script (e.g. aGetIronPyScope/ExecuteIronPyScriptstep) errored out immediately — and in the runner the config's retry loop on that error could lock up M2 until force-closed. The engine is now created on first use per bot (embedded IronPython, no system Python needed), mirroring how the CPython runtime is already initialized lazily. If you previously saw aMethod not found: ... GetIronPyScope ...error instead, that was a staleRuriLib.OB2.dllin the app'sBin\; rebuild the app so the latest library is copied there. - The OPK badge now also shows in the Select Config dialog. The same purple "OPK" pill used in the Config Manager list (shown on OB2
.opkconfigs) now appears in the Select Config dialog, in both the grid view (top-right corner of each card) and the table view (next to the config name), so OB2 configs are recognizable there too. - The expected WordlistType of an imported .opk is now editable in Config Settings → Data. OB2 configs declare which WordlistType they expect (e.g.
Credentials), and the runner checks the selected type against it by name. Previously that requirement was read-only (taken from the .opk) while the Data page edited the native Allowed Wordlist fields, which the runner ignores for OB2 configs — so changes there had no effect and the fields showed up empty. The Data page now binds its two Allowed Wordlist combos to the OB2 config's declared types when the config is an .opk, pre-filled from the .opk, with a hint reminding you to pick a type whose slice names match the ones the script uses (e.g.USER, PASS). Saving the config writes the edited types back into the .opk (DataSettings.AllowedWordlistTypes) so the choice survives rescan and reload.
Runner
-
Fixed: runner froze (Stop unresponsive) on configs using an undeclared variable. A config referencing a variable that no block ever creates (e.g. a literal
COOKIE: <SOME_VAR>header) made the request fail on every retry across all bots, flooding the log and locking the UI so Stop couldn't be clicked. The strict variable check now runs in the runner's start path too — before the Start button switches to Stop — so such a config is rejected up front with a clear message naming the offending block and variable, exactly like the Stacker debugger already did. The check is now always on (it is no longer an opt-in setting, and its Settings toggle has been removed) so this class of config can never freeze the runner. -
Fixed: strict variable check flagged WordlistType slices defined in the Environment. Slice names from the Environment (e.g. a custom
MACslice) are input variables filled from the data line, but the check only knew the slices of the currently selected WordlistType — so a<MAC>from any other Environment type was wrongly reported as undeclared and blocked the run. The check now treats the slices of every WordlistType in the Environment as declared, at both the runner start and the Stacker debugger. Script-declared globals (SET GVAR/SET NEWGVAR) were already recognized. -
Fixed: intermittent "the process cannot access the file because it is being used by another process" on the DB during a job. Hits are written to
OpenBullet.dbon the UI thread while the Master worker saves the run's progress on its own thread every ~120s (and on stop); with several runners, several worker threads open the DB at once. LiteDB's Shared mode serializes operations via a named mutex but does not close the race in the file open/close window between threads of the same process, so overlapping opens threw anIOException("used by another process") now and then — and "another process" was actually another thread of M2. All DB access now goes through a single process-wide gate inOB.OpenDb()(a reentrantMonitorheld from open until the database is disposed), so two opens can never overlap.LiteDBRepositorywas routed through the same gate. Useusing (var db = OB.OpenDb()) { … }as before. -
Fixed: cryptic "Object reference not set to an instance of an object" when starting the runner. If the runner started with no usable data pool — a temporary wordlist that was never given data (e.g. after a restored session) or no wordlist selected —
Rundereferenced a nullDataPool/Config.Settingsand the Master Worker died with a bareNullReferenceException, with the job never starting and the cause unreadable. Those two preconditions are now checked up front and fail with a clear message ("No data loaded! Select a wordlist…" / "The loaded Config has no settings…") instead of an NPE, so the user knows to select a wordlist (or reselect the config) and start. -
Master Worker crashes are now always written to
crash_runner.txt. When the runner fails to start (or dies mid-run) the popup only shows the short message, and the full stack trace went to the normal log — which is gated by theEnable Loggingsetting (off by default), so with logging off nothing was saved and a start-timeNullReferenceExceptionwas impossible to diagnose. The Master Worker's error (full stack trace, with config, wordlist and proxy state) is now appended tocrash_runner.txtnext to the executable regardless of the logging toggle. Best-effort, so the safety net itself never throws.
Blocks
- Fixed: importing an older config loaded the RandomString input mask into the Custom Charset field. The
RandomStringfunction gained a Custom Charset field, which changed its serialized form from a single literal (the input mask) to two ("<charset>" "<mask>"). The parser always read the first literal as the charset, so configs saved before the field existed had their input mask loaded into Custom Charset — breaking the block on import. The parser now looks ahead: two literals → charset + mask; a single literal (old format) → Custom Charset left empty and the literal restored as the input mask.
Licensing
- Fixed: a license error could wrongly block the app from starting after a restart or an unexpected shutdown. Startup is no longer refused in that case.
HTTP
- The Request block (Standard / RuriLib.Http) now sends headers verbatim, like OpenBullet 2. The engine behind the Standard Request block built the outgoing request as a
System.Net.Http.HttpRequestMessage, and .NET re-serializes "known" structured headers (Accept, Accept-Language, User-Agent, …) through its typed parsers — soapplication/json;ver=2went out asapplication/json; ver=2,it-IT,it;q=0.9,…asit-IT, it; q=0.9, …, and a manually setAccept-Encodingwas replaced withgzip, deflate(br/zstd dropped). That reformatted fingerprint differs from a real browser and made some strict servers answer403where OB2 — which writes the request raw onto the socket viaRLHttpClient— gets200. The Request block now uses the sameRLHttpClientpath (raw write, noHttpRequestMessage), so header values, order and casing reach the wire exactly as typed, and a customAccept-Encodingis honored (agzip, deflatedefault is still added only when the config sets none). AContent-Typeplaced among the custom headers is now sent verbatim and in its position too (instead of being dropped and re-emitted from the body, reformatted and at the end). The curl-impersonate library keeps its own native path. This removes the need for the TLS REQUEST block on targets that only check the HTTP layer, not the TLS fingerprint. - Request blocks now reuse one connection per bot (System.Net library). Previously every Request block built and disposed its own HTTP handler, so each request opened a brand-new TCP connection. On a VPN or proxy that NATs outgoing connections across a pool of IPs, this made every request of the same bot exit from a different IP — breaking sites that bind a token (or session) to the IP that requested it, which then rejected the follow-up request as expired. The
System.NetHTTP library now keeps a single keep-alive connection per bot (keyed by proxy and handler settings), so all requests of one bot share the same exit IP; the connection is closed when the bot ends, and different bots stay independent. This affects only theSystem.Netlibrary — theRuriLib.Httpengine opens a fresh connection per request by design and is unchanged; switch a Request block toSystem.Netif you need a stable IP across a bot's requests behind an IP-pooling VPN.
TLS REQUEST
- Local TLS proxy updated to tls-client v1.16.0. The bundled
tls-proxy.exe(the engine behind the TLS REQUEST block) was bumped from v1.15.1 to v1.16.0. This refreshes the browser impersonation stack (fhttp, utls) and brings newer TLS fingerprints, HTTP/3 over SOCKS5 proxies, header-order honored on HTTP/3, post-quantum ML-DSA signatures in TLS 1.3, and several stability fixes (ALPN-change reconnects, empty bodies, header-order corruption, FLOW_CONTROL_ERROR on slow large downloads, short reads while decompressing certificates). The v1.16.0 breaking change where HTTP/3 protocol racing fails with a non-SOCKS5 proxy is handled internally: when an HTTP(S) upstream proxy is set, the proxy falls back to HTTP/2 instead of erroring. - New Chrome 150 / 152 profiles in the TLS REQUEST block. The profile picker now offers
Chrome_152,Chrome_152_PSK,Chrome_150andChrome_150_PSK(added in tls-client v1.16.0), so you can impersonate the latest Chrome TLS fingerprints. The block's profile list and the proxy stay aligned one-to-one. The block default is unchanged (Chrome_133). - Fixed: custom header values were reformatted on the hop to the TLS proxy (
Accept,Accept-Language, etc.). The block staged custom headers in a typedHttpRequestHeaderscollection and then re-read them to build the request actually sent to the proxy. .NET parses "known" structured headers (Accept, Accept-Language, User-Agent, …) into tokens, so reading them back and re-joining produced reformatted values —application/json;ver=2→application/json; ver=2,it-IT,it;q=0.9,…→it-IT, it; q=0.9, …— a fingerprint that no longer matches the browser the config impersonates (only User-Agent/Server had a targeted workaround). Custom header values are now added to the outgoing request verbatim, straight from the config, without passing through the typed collection, so what you type is what the proxy forwards. This also stopsAccept-Encoding: gzip, deflate, br, zstdfrom being trimmed on the way out. - Fixed: the "Randomize TLS extension order" checkbox was stuck on (always
true). Unchecking it had no effect at runtime — the proxy always receivedx-tls-with-random-extension-order: true. Three TLS flags (TlsRandomExtensionOrder,TlsFollowRedirects,TlsInsecureSkipVerify) were serialized to LoliScript after the parameter tokens, outside the boolean region that the parser reads right after the URL, so a non-default value (e.g. unchecked) was silently dropped and the flag reverted to its default on every parse — and the runner re-parses the block on each execution. The three flags are now written in the boolean region next to the other block options, so their state round-trips and is honored at runtime.