Repository navigation
M2 Pro 4.0.2
OBM2 Pro — CHANGELOG v4.0.2
⚠️ Binary removed for security reasons — download the latest release: https://github.com/ObM2/ObM2Pro/releases/latest
Post-4.0.1 release. Headline work: full migration of the HTTP engine from the
legacy internal Extreme.Net library to RuriLib.Http (OpenBullet 2), done
incrementally behind a flag and then completed by deleting Extreme.Net entirely.
Existing configs keep working unchanged.
Feat — GenerateGUID function: version (V4/V7) + format (D/N/B/P)
Aligned the GenerateGUID block with OpenBullet 2. It used to emit only a v4 GUID
in the fixed D format; it now exposes Version (V4 random / V7 time-ordered) and
Format (D dashes, N compact, B braces, P parentheses). New GuidVersion/GuidFormat
enums and properties in BlockFunction.cs, parsed/serialized in FromLS/ToLS
(backward-compatible: old configs without params stay V4/D). Since RuriLib targets .NET 8
(where Guid.CreateVersion7() doesn't exist — .NET 9+), v7 is built by hand per RFC 9562
(48-bit ms timestamp + version/variant bits + random, with byte reorder for the textual
layout). UI: new tab in PageBlockFunction with the two ComboBoxes + i18n keys
Blk_GuidVersion/Blk_GuidFormat (en/it/es).
Fix — ULP to Combo: HTML sources, dirty tag output + lost combos
The ULP to Combo tool on HTML-marked files produced correct mails but wrong passwords
(truncated digits) and skipped good lines. Cause: the regex required mail:password with the
password right after the colon, but in HTML sources a tag sits between mail and password
(mail@x.com:<span>pass</span>), so the match failed or grabbed a numeric token further along.
Fix in UlpTools.xaml.cs: strip HTML tags before parsing (StripHtml); mail/password separator
is now a [\s:=;|]+ class instead of just :; password is [^\s<>]+; ExtractCombos() with
Regex.Matches pulls every pair on the line. Output is clean username:pass, one per line,
backward-compatible with classic combos. No UI change.
Fix — TLS Request: non-ASCII invisible chars in headers → send error
Custom headers pasted from devtools/formatted sources could contain invisible non-ASCII chars
(NBSP U+00A0, zero-width U+200B/C/D, BOM U+FEFF) in place of spaces — values that look ASCII but
failed sending with HttpRequestException: Request headers must contain only ASCII characters.
(.NET validates ASCII only at send, after TryAddWithoutValidation). Added
BlockTlsRequest.CleanHeaderToken(): NBSP→space, zero-width/BOM removed, other chars >0x7F
dropped; applied to header name+value with a log warning when it cleans something.
The same error could come from the URL: the resolved URL is carried in the x-tls-url
header to the TLS proxy, so a non-ASCII char in the URL made sending fail looking like a header
problem. Added SanitizeUrlForHeader(): strips zero-width/BOM and percent-encodes every char
0x7F in UTF-8 (valid URL, no data loss), applied to
localUrlbefore thex-tls-urlheader.
Security/Fix — License dialog: no license-server hostname leak + localized messages
On an unreachable backend (network down, 403, DNS), the license activation dialog showed a raw
ex.Message that included the license server hostname (the raw "No such host" error exposed the
backend host:port). Added LicenseGate.ScrubHost(), which
replaces the backend URL/host with [license server]: the user still sees the network error
detail but not the server name. All outcome messages (previously hardcoded in Italian inside
LicenseGate) are now localized in the 3 dictionaries via a thread-safe L() helper (Dispatcher
for the off-UI heartbeat). New keys Dialog_License_NoLicenseFound/Valid/ServerUnreachable/ BadResponse/BadSignature/WrongInstance/ReplayNonce/ReplayExpired.
UI — VPN Proxy tool: modern Tool restyle
IpVanishTool.xaml rewritten from the old 3-column Grid layout to the standard Tool UserControl
pattern (like Convert Tool): header icon+title+subtitle, credentials card with violet primary
Login + secondary actions, Log card (dark monospace green RichTextBox, RtbContextMenu, VS-style
scrollbar) and a generated-proxies card separated by a GridSplitter. Code-behind unchanged (all
x:Name and handlers preserved). New i18n keys
Tools_IPVanish_Title/Subtitle/Credentials/Actions/GeneratedProxies.
Fix — Runner: settings not saved / empty runner on restart
Two distinct causes, both fixed:
1. Runner session wipe (non-atomic persistence). SaveSession() did _repo.RemoveAll()
(DropCollection) then _repo.Add() over two separate Shared LiteDB connections: if the insert
failed, the collection stayed empty and all runners vanished. Added
LiteDBRepository.Replace(IEnumerable) which clears and re-inserts inside a single transaction
(DeleteAll+InsertBulk+Commit/Checkpoint); SaveSession now materializes the list and uses
Replace.
2. Restore abandoning the runner when the wordlist file was missing. With a wordlist on a
disconnected external drive, FileToWordlist (File.ReadLines().First()) threw → the runner lost
config/wordlist and on the next save the path was overwritten with an empty string (progress reset
to zero). Added WordlistManagerViewModel.FileToWordlistSafe, which keeps the path without reading
the file; RestoreSession no longer throws: it keeps config, wordlist/cookie path and
StartingPoint even with files temporarily absent.
UI — Fixed-height Custom Headers/Cookies with grip (OB2 style)
In the Request and TLS Request blocks the Custom Headers and Custom Cookies
RichTextBoxes no longer auto-grow with content. They now have a fixed height (80/100 px) with
vertical scroll and a bottom-right grip to resize height only (width fixed), like OpenBullet 2
textareas. New VResizeGripThumb style in App.xaml (Thumb, SizeNS cursor); ResizeGrip_DragDelta
handlers in PageBlockRequest.xaml.cs and PageBlockTlsRequest.xaml.cs (drag → rtb.Height, capped
at MinHeight→600); each RichTextBox wrapped in a Grid with an overlaid bottom-right Thumb.
New block — Cap Solve (Cap.js PoW captcha)
Added the Cap Solve stacker block (CAPSOLVE), which solves Cap.js proof-of-work captchas
(two-POST challenge) headless, with no browser.
Full flow: the block does the first POST to the Challenge URL, receives
{challenge{c,s,d}, token, instrumentation}, solves the PoW in native C#, reproduces the anti-bot
state with Jint, then does the second POST to the Redeem URL and stores the final token in
<SOLUTION> (plus <CAP_EXPIRES>).
- Cap.js PoW: salt/target derived from
PRNG(FNV1a + xorshift32)over the token; nonce search
whereSHA256hex(salt+nonce).startsWith(target). Native C# port (verified 80/80 against a real
challenge). - Instrumentation (anti-bot): the obfuscated script served by the server (raw deflate) runs ~30
anti-headless checks (Node.js detector viaprocess/global/require/module, webgl SwiftShader,
node:internalstack, eval/Function.toString tampering,Object.prototype.toStringtag) and
produces 4 deterministicstatenumbers. Run with Jint 4.9.2 (already a dependency) in a
global with DOM/navigator/Symbol.hasInstance/toString-tag stubs — it passes the gate and
reproduces the exactstate. No WebView2: runs on any runner bot thread. - Params: Challenge URL, Redeem URL, Challenge Body (opt.), Custom Headers, User Agent, Use Proxy,
Output Variable (defaultSOLUTION), Is Capture. LoliScript round-trip verified.
Registration: BlockParser (CAPSOLVE), BlockCatalog (Captchas category), MainWindow mapping
(satellite BlockPage), red ShieldKeyOutline icon. File: RuriLib/Blocks/BlockCapSolve.cs.
Fixes found in testing (validated end-to-end against a live server):
- MultiLine in LoliScript: Challenge Body and Custom Headers contain real newlines that
broke the LS line (ERROR: Expected Literal value for 'HEADERS'). Fix: newlines encoded as\n
on a single line inToLS, restored inFromLS(EncodeNl/DecodeNl). - Compressed response: without
AutomaticDecompression, gzip/br responses arrived as raw bytes
→JObject.Parsefailed with "Unexpected character… position 0". Fix:
HttpClientHandler.AutomaticDecompression = GZip|Deflate|Brotli+ defaultAccept: application/json. - Diagnostic log: each POST prints
status, N bytes, first 300 chars; JSON parse with a clear
message (empty / non-JSON / anti-bot block) instead of Json.NET's cryptic error.
Usage notes:
- Challenge Body = first POST payload (empty if the challenge has no body).
- Custom Headers = headers shared by both POSTs (Origin/Referer/x-*). Do not put
User-Agent
here: use the dedicated User Agent field (used both in HTTP headers and innavigator.userAgent
inside Jint).Content-Type: application/jsonis automatic. - Use Proxy = local gate
data.UseProxies && UseProxy && data.Proxy != null(same scheme as
SolveCaptcha).
Runner — fix crash "Index was outside the bounds of the array"
On Runner start the error "The Master Worker has encountered an error: Index was outside the bounds
of the array" happened often.
Cause: System.Random is not thread-safe. A single shared instance
(RunnerViewModel.random) was used in parallel by all the runner's bots in blocks (RandomNum,
RandomString, etc.). Concurrent access corrupts Random's internal SeedArray and produces
IndexOutOfRangeException. The lock(randomLocker) only covered BotData construction, not the
Random use inside blocks on bot threads → race on every multi-bot run.
Fix: each bot gets its own Random instance, uniquely seeded
(Guid.NewGuid().GetHashCode() ^ id ^ TickCount) at BotData creation. No cross-thread sharing → no
corruption. (this.random remains only as a historical constructor fallback, no longer passed to
bots.) File: RuriLib/Runner/RunnerViewModel.cs.
Home dashboard — Memory card + resource monitor
- Fix flat memory line:
Process.WorkingSet64is cached → withoutProcess.Refresh()the value
stuck at the first sample (flat sparkline). AddedRefresh()per tick. - Memory card redesigned OB2-style: the sparkline is replaced by a progress bar (track + fill)
showing the M2 process memory (WorkingSet) as a fraction of total system RAM, with usage
percentage andused MB / total MBdetail (e.g.341 MB / 32,687 MB·1.0%). The bar is
measured on the process, not the system. ResourceMonitor/ResourceStats: newMemoryPercentandTotalSystemMemoryMBfields (total RAM
viaGC.GetGCMemoryInfo().TotalAvailableMemoryBytes, computed once).- CPU: the measure is correct (
cpuDelta / (nCore × wallDelta) × 100= process % normalized over
cores); at idle it really is ~0%. No change to the calculation.
File: OpenBullet/Services/ResourceMonitor.cs, OpenBullet/Views/Main/Home.xaml(.cs).
HTTP Engine — Extreme.Net → RuriLib.Http migration
The old internal Extreme.Net fork (a fork of xNet, referenced by RuriLib) has been completely
replaced by RuriLib.Http 1.0.1 + RuriLib.Proxies 1.0.2 (NuGet; last series compatible
with net8 — 2.0.0 requires net10). Carried out in phases (PoC → proxy adapter → engine wrapper →
selectable branch → residual cleanup → removal) to keep every build green.
Why
- Header order control on the wire (anti-bot signature) via
ProxyClientHandler. - Native SOCKS4/4a/5 support — no more transport fallbacks.
- Cookies captured reliably via
CookieContainer. - Drops a large, unmaintained internal library in favour of the OB2 stack.
HTTP Request block (REQUEST)
- New engine wrapper
RuriRequest(ProxyClientHandler+System.Net.Http.HttpClient): preserves
header order, native SOCKS, cookies viaCookieContainer,gzip/deflateauto-decompression. - The
HTTP libraryselector no longer listsExtremeNet(the library no longer exists). The
dropdown now offers onlyRuriLibHttpandSystemNet. TheExtremeNetenum value is kept
internally for.loliparse compatibility, but any config using it is auto-migrated to
RuriLibHttp— both at parse time (FromLS) and on block open. Old configs keep working. - The
System.Netbranch's SOCKS fallback now routes to RuriLib.Http (native SOCKS) instead of
the removed Extreme.Net path.
TLS Request block (TLSREQUEST)
- Added the
HTTP libraryselector to the TLS Request block too (inherits fromBlockRequest).
Here the library only governs the fallback engine used when localtls-proxy.exeis
unreachable — the normal path always goes through the TLS proxy. ExcludesExtremeNetand
auto-migrates it toRuriLibHttp. HttpLibraryis now serialized in the TLS block LoliScript (HTTPLIBtoken) — previously it
was inherited but never saved, so the choice was lost on reload.
Engine internals (validated by PoC)
- Uses
ProxyClientHandler+HttpClient, notRLHttpClient: the latter preserves header order
but dropsSet-Cookie(Headersis aDictionary<string,string>), whileProxyClientHandler
gives header order and cookies and SOCKS. UseCookies = true+CookieContaineris mandatory (withfalse, ProxyClientHandler 1.0.1 throws
NullReferenceException).
Other migrated consumers
- Download block /
Download.RemoteFile→ ProxyClientHandler + HttpClient. - BASIC OCR / OCR blocks — image-from-URL download →
HttpClient+CookieContainer(cookies
merged back into the bot jar); the second OCR block now usesRuriRequest. - Runner — webhook hit POST and remote proxy source download
(GetProxiesFromRemoteSourcesync/async) →HttpClient. - Add Proxies dialog (download list from API URL) →
HttpClient. - VPN Proxy tool login flow →
HttpClient+ sharedCookieContainer(per-request headers via
HttpRequestMessage). Transport-only refactor; does not fix the VPN provider's server-side login changes. - UserAgent — random UA generators ported into RuriLib (
UaGenerator), no longer depend on
Extreme.Net.Http.
Removal (Extreme.Net deleted)
- New native enums
RuriLib.Enums.ProxyTypeandRuriLib.Enums.HttpMethod, member names identical
to the old ones (serialized by name in.loli— not renamed). Request.csreduced to shared types only (SecurityProtocolenum,MultipartContentstruct); the
Extreme-basedRequestclass removed,GenerateMultipartBoundarymoved intoRuriRequest,
IsExtremeNetReserved→RuriRequest.IsReservedHeader.CProxy.GetClient()(+ chain helpers) removed;GetRuriClient()is now the only factory.CookieDictionary(Extreme type) →Dictionary<string,string>forGlobalCookies.using Extreme.Netswapped tousing RuriLib.Enumsacross ~30 files.- Removed the
Extreme.NetProjectReferencefrom RuriLib, removed the project from the solution,
deleted the project folder; the diagnostictools/PoC.RuriHttpremoved too. - Build: 0 errors; RuriLib.Tests: 34 green (proxy factory mapping + LoliScript round-trip).
Networking / Fixes
RuriRequest.Performtimeouts now surface cleanly. Send/read used.Result, wrapping timeouts
inAggregateException. Switched to.GetAwaiter().GetResult()so the runner sees a clean
TaskCanceledException(same as old Extreme.Net), classified as a normal fail/retry.System.Net(HttpClient) branch — same unwrap. AllTask.Run(...).Resultcalls in
BlockRequest→.GetAwaiter().GetResult(), so a dead proxy logs a clean
HttpRequestException/SocketExceptioninstead ofAggregateException.ProxyClientHandlerTLS callback crash fixed. Setting
ServerCertificateCustomValidationCallbackthrewInvalidOperationException: 'RemoteCertificateValidationCallback' option was already set in the SslStream constructor—
ProxyClientHandler 1.0.1 already sets it internally. Removed our setter inRuriRequestand
Download.Content-Typewith parameters (e.g.; charset=utf-8) no longer breaks the request. Configs
withContent-Type: application/json; charset=utf-8failed withERROR: The format of value '...' is invalid.RuriRequestset the content type via theStringContent/MediaTypeHeaderValue
constructor, which accepts only the bare media type and throwsFormatExceptionon the
parameter. Now a helper usesMediaTypeHeaderValue.TryParsewith aTryAddWithoutValidation
fallback. Applies to Standard and Raw bodies. TheSystem.Netbranch had the same bug — fixed
with a sharedSetSystemNetContentTypehelper. Works on both engines.
Proxy Manager / Fixes
- Country resolution no longer freezes the UI at the end of a check.
GeolocateBatchAsynccalled
Update()once per proxy, andLiteDBRepository.Update(T)opens/closes the LiteDB file on every
call → ~1000 open/close cycles on the UI thread. Countries are now assigned in the loop
(PropertyChanged only) and persisted in a single bulkUpdate(IEnumerable)off the UI thread. - Send to Debugger now includes proxy credentials. The "Send proxy to Debugger" right-click sent
onlyhost:port, dropping user/pass of authenticated proxies. Now, if the proxy has credentials
(NeedsAuthentication), it's sent ashost:port:username:password. - Authenticated SOCKS5 check now works. The checker used
HttpClient+WebProxy, which does NOT
support SOCKS5 user/pass auth → all authenticated SOCKS5 came back "not working". For SOCKS proxies
the check now usesRuriLib.Http.ProxyClientHandler(same engine as the runner).
Tools — VPN Proxy (reactivated)
- Old VPN Proxy tool restored and wired into the sidebar (Page→UserControl, obsolete FontAwesome icons
→ Material). Login rewritten on the current flow (CognitoInitiateAuthBearer + API v4 JSON:
proxy-credentials,server/list/socks5,subscription), all requests routed viatls-proxy.exe
(without TLS fingerprint → 403). Generates the 18 SOCKS5*.socks.ipvanish.com:1080.
Backward Compatibility
- Existing configs are unaffected. The
ExtremeNetvalue is kept in the enum for.loliparse
compatibility but is no longer shown in the UI and is auto-migrated toRuriLib.Http; configs that
default toExtremeNet(noHTTPLIBtoken) also keep working. - Proxy type names (
Http/Socks4/Socks4a/Socks5/Chain) and HTTP method names are unchanged, so
serialized configs round-trip identically. - Removed: proxy chaining (
ChainProxyClient) has no equivalent in RuriLib.Proxies — only the
current proxy node is used (documented gap, was already rarely used).
Notes (TLS fingerprint / curl-impersonate)
- RuriLib.Http 1.0.1 is a managed client: header order + gzip/deflate only, no TLS fingerprint
impersonation. Unchanged vs Extreme.Net (which had none either). - Real TLS impersonation in M2 continues to come from the external
tls-proxy.exe(TLS Request
block), independent of the HTTP engine. - RuriLib.Http 2.0.0 adds native curl-impersonate + brotli/zstd, but targets .NET 10 — out
of reach until M2 moves off net8. Future option.
Runner Manager / UI
- New in-line List view, toggle in the toolbar. A second display mode beside the cards: a compact
table (#,Status,Configuration,Proxies,Bots,CPM,Progress,Hits,Custom,
To Check,Data Pool, actions⋮). Two toolbar toggle buttons (grid / list) switch view; the
choice is persisted inobSettings(General.RunnerManagerViewMode) and restored at boot. - Built with
ItemsControl+ rowBorder, not aListView: fixedGridcolumns, two*
columns (Configuration + Data Pool) keep the⋮button anchored far right. Rows are never
selected — only a hover highlight; left-click opens the Runner. Colors use the M2 dynamic
resources. - New i18n keys (en/it/es):
Runner_Col_Status,Runner_Col_DataPool,Runner_View_Card,
Runner_View_List; existingStat_*keys reused. - Dead-code cleanup. Removed the never-wired per-item click handlers in
RunnerManager.xaml.cs
(selectConfig_MouseDown,selectWordlist_MouseDown,selectProxies_MouseDown,
selectBots_MouseDown) and their stale XAMLMouseDownbindings.
OpenBullet 2 block/function coverage (config parity)
Closed the real gaps vs OpenBullet 2 2.0.0 (excluding browser-automation:
Puppeteer/Selenium/playwright).
FUNCTION block — new functions
- JWT (Create/Verify, HMAC HS256/384/512) — helper
Crypto.JwtEncode/JwtDecodeVerify. - XOR (XOR cipher with repeated key, Base64/HEX output) —
Crypto.XorCipher. - AWS4Signature (AWS Signature v4) —
Crypto.Aws4Signature. - MaxInt/MinInt/MaxFloat/MinFloat and MergeByteArrays (second operand).
- ShellCommand (run a command via
cmd.exe, capture stdout).
PARSE block — XPATH
- Implemented
ParseType.XPATH(previouslyNotImplementedException):Parse.XPATHvia
HtmlAgilityPack (new pkg), XPATH pill+tab in the Parse UI, attribute/innerHTML/outerHTML/
index/recursive support.
UTILITY block — Dictionary + extras
- New Dictionary group: Create, Add, RemoveByKey, GetByKey, GetKeyByValue, Keys, Values.
- New List actions: IndexOf, CreateListOfNumbers.
- New BIGINT conversion (Bytes⇄big integer base-10).
TCP block — full read
- New Read (read-only), ReadBytes, SendReadBytes (byte-array output) commands.
New blocks
- SSH (
SSH.NET): Connect (Password/None/PrivateKey), RunCommand, Disconnect. - DNS (
System.Net.Dns): A / AAAA / Any / Reverse lookup → list.
SMTP block
- New SendMailAdvanced command: CC, BCC, Reply-To, multiple attachments, HTML/plain body.
Not included
- NodeJS interop (needs
node.exe, analogous to BlockPython) — deferred. - Modern captchas (Turnstile/DataDome/GeeTest v4/...) — mapped as TODO only; depend on external
captcha provider support.
Bugfix — LS Code block disabling (historical OB1/M2 bug)
The LS Code block could not be disabled: the toggle had no runtime effect (the script ran anyway)
and the block didn't grey out live.
Cause: BlockLSCode.ToLS returned the raw script ignoring Disabled → no ! prefix, so at
runtime (config runs as LoliScript text where IsEmptyOrCommentOrDisabled skips !-prefixed lines)
the lines always executed. Also, StackerBlockViewModel.Disable()/DisableColor() had a dedicated
early-return for BlockLSCode (historical exclusion): the toggle set neither Block.Disabled nor
the colour. Grey appeared only after a ToLS→FromLS round-trip.
Fix: ToLS prefixes ! to every non-empty line when Disabled; FromLS round-trips (if all
non-empty lines start with !, mark Disabled = true and strip one for a clean editor view);
StackerBlockViewModel exclusion guards removed, so the LS block disables live like the others.
File: RuriLib/Blocks/BlockLSCode.cs, OpenBullet/ViewModels/StackerBlockViewModel.cs.
Bugfix + UI — CurrentUnixTime: unit as ComboBox
The CurrentUnixTime block failed with ERROR: Object reference not set to an instance of an object if the unit was lowercase (milliseconds...). Process() resolved the method via
case-sensitive reflection GetMethod("ToUnixTime" + Input): only capitalized Seconds/Milliseconds
matched, any other casing returned null → NRE on .Invoke. Root cause was a free text field.
Fix: Input changed from string to enum UnixTimeUnit (Seconds, Milliseconds,
Microseconds, Nanoseconds) with ParamType = BlockParamType.Enum → the UI auto-generates a
ComboBox. Process() now uses a switch (no reflection, no NRE). FromLS parses with
Enum.TryParse(ignoreCase: true) + Seconds fallback for legacy/invalid tokens; ToLS unchanged
(100% backward-compatible). Bonus: Microseconds and Nanoseconds now work too.
File: RuriLib/Blocks/BlockCurrentUnixTime.cs.
Captcha — Cloudflare Turnstile (OB2 2.0.0 parity) + selector crash fix
Added Cloudflare Turnstile support to the SolveCaptcha block, matching OB2 2.0.0. CaptchaSharp
2.1.0 (already referenced) exposes the method on the base CaptchaService, so no dependency
change — only wiring.
New Turnstile type
BlockSolveCaptcha: newcase CaptchaType.CloudflareTurnstileinFromLS/ToLS/GetResponse. New
CDataandPageDataproperties. LS:
SOLVECAPTCHA CloudflareTurnstile "siteKey" "siteUrl" "action" "cData" "pageData".GetResponse: callsSolveCloudflareTurnstileAsync(...).Process: newCloudflareTurnstileResponse
case → token in<SOLUTION>+ solver User-Agent in<CF_USERAGENT>(reuse it in the site
submit).- UI
PageBlockSolveCaptcha: new tab with the 5 fields; i18n keysBlk_CData/Blk_PageData.
Captcha selector latent-crash fix (2.1.0 upgrade regression)
The captcha-type combobox listed all 24 values of CaptchaSharp 2.1.0's CaptchaType enum, but the
code-behind mapped only 9 types via a Dictionary → selecting an unhandled type (DataDome, GeeTestV4,
AmazonWaf, etc.) threw KeyNotFoundException and crashed the page. Rewritten with a SupportedTypes
array (order == tab order): the combobox lists only types with a tab, combobox index maps 1:1 to tab
index, no dict, no crash. Bound by enum value, not index.
BlockBypassCF — output as named variable (OB2 parity)
OB2 writes the Challenge Page result into a capturable Output variable; M2 wrote only the
cf_clearance cookie. Added VariableName + IsCapture properties (auto-generated fields).
Process now writes the value both to the cf_clearance cookie (unchanged) and to a
named/capturable CVar if VariableName is set. LS extended with an optional arrow:
BYPASSCF "url" "html" (UseProxy=True) "ua" -> CAP "cfClearance". Backward-compatible (old BYPASSCF
without a variable stays identical). OB2 SafeMode NOT ported (M2 has no per-block runtime exception
handling). File: RuriLib/Blocks/BlockBypassCF.cs.
BlockSolveCaptcha — IsCapture on the solution (OB2 parity)
In OB2 every captcha block has an Is Capture toggle marking the solution variable as capturable
(logged among hits). M2's cumulative SolveCaptcha inserted variables with isCapture hardcoded
false → the solution was never capturable. Added IsCapture property + UI checkbox. Process
now passes IsCapture to the solution variables (SOLUTION, GT_CHALLENGE/GT_VALIDATE/
GT_SECCODE, Turnstile token); CAPTCHAID and CF_USERAGENT stay metadata. Serialized as
Boolean IsCapture in trailing LS tokens (backward-compatible).
Captcha — DataDome + GeeTest v4 (OB2 2.0.0 parity)
Two more modern types added to SolveCaptcha, same scheme as Turnstile (methods already exposed by
CaptchaSharp 2.1.0):
- DataDome (
CaptchaType.DataDome): newCaptchaUrlproperty; SiteUrl/CaptchaUrl fields.
SolveDataDomeAsync(...)returnsStringResponse→ output in<SOLUTION>(valid datadome
cookie). - GeeTest v4 (
CaptchaType.GeeTestV4): newCaptchaIdproperty; CaptchaId/SiteUrl fields.
SolveGeeTestV4Async(...)returnsGeeTestV4Response→ new case exposing
<GT4_CAPTCHAID>/<GT4_LOTNUMBER>/<GT4_PASSTOKEN>/<GT4_GENTIME>/<GT4_CAPTCHAOUTPUT>.
RespectsIsCapture. - UI: 2 new tabs + extended
SupportedTypes; i18n keysBlk_CaptchaUrl/Blk_CaptchaId.
BlockSolveCaptcha — renamable Output Variable (OB2-style)
Added an Output Variable field (like OB2): new VariableName property + SolutionVar helper
(= VariableName if set, else "SOLUTION"). Renames the single-token solution variable (reCaptcha
v2/v3, hCaptcha, FunCaptcha, KeyCaptcha, Capy, Image, Text, Turnstile, DataDome) → recalled as
<chosenName>. Multi-output types (GeeTest v3/v4) and auxiliaries (CAPTCHAID, CF_USERAGENT) keep
fixed names. Serialized at LS end with the arrow as delimiter (... "ua" -> "myToken"); written
only if set, parsed only if present → backward-compatible.
BlockSolveCaptcha — per-solver info footer (OB2 gap)
In OB2 each solver is a separate block with its own info line. M2's cumulative block showed nothing
type-specific. Added a fixed non-scrolling footer (outside the ScrollViewer) showing the selected
type's description + output variables produced, updated in captchaTypeCombobox_SelectionChanged.
TypeInfo dictionary in the code-behind (EN). E.g. GeeTest v4 → "Output is a set (in order):
<GT4_CAPTCHAID>, <GT4_LOTNUMBER>, ...".
Captcha — low-priority types (full OB2 2.0.0 parity)
Added the last 9 CaptchaSharp 2.1.0 captcha types to SolveCaptcha, same scheme (all return
StringResponse → token in <SOLUTION>): AmazonWaf, LeminCropped, AtbCaptcha,
CutCaptcha, ReCaptchaMobile, CyberSiAra, FriendlyCaptcha, MtCaptcha,
TencentCaptcha. New dedicated properties + 9 UI tabs + extended SupportedTypes/TypeInfo.
AudioCaptcha NOT ported (different AudioCaptchaOptions signature, niche — deferred).
SolveCaptcha now exposes 21 types (the 2.1.0 enum has 23: the 2 excluded are
CloudflareChallengePage, covered by the separate BlockBypassCF, and AudioCaptcha, deferred).
Total coverage 22/23 — parity with OB2's separate blocks, only AudioCaptcha missing. The
2026-06-26-captcha-moderni-todo.md TODO is closed.
i18n + UI — License Activation dialog
The license activation dialog (LicenseActivationWindow) had 3 Italian strings hardcoded in the
code-behind — now localized, plus a channel link.
"Inserisci una license key.","Verifica...","Attiva"moved into the vocabularies (en/it/es)
asDialog_License_EmptyKey,Dialog_License_Verifying(reusingDialog_License_Activate). The
code-behind uses aRes(key)helper →Application.Current.TryFindResourcewith key fallback.- New Telegram channel link below the buttons for users without a license (Send icon +
Hyperlink, localizedDialog_License_NoLicense, handlerChannelLink_RequestNavigateopens the
default browser viaProcess.Start … UseShellExecute).
File: OpenBullet/Views/Dialogs/LicenseActivationWindow.xaml(.cs), 3× StringResources.
Builds on: CHANGELOG_v4.0.1.md.