XRumer xuser.ini - Runtime Engine Policy From On-Disk Config
XRumer is a Windows HTTP form robot. Almost all site knowledge lives in text under LogicFiles/. The binaries supply a GUI, a worker pool, HTTP and proxy glue, and captcha plugins. The file that tells those workers how to run is xuser.ini.
This post starts a cycle on XRumer files and XRumer logic. The work is ongoing reverse engineering of a packed Delphi product: disk configs and rule files are the high-confidence source, not decompiled call stacks. Later posts will cover LogicFiles, project XML, proxy lists, captcha INIs, and the scheduler XML. This one stays on xuser.ini.
The file is Windows INI. Sections in brackets, Key=Value lines, often CRLF. The GUI loads it at start, binds controls to keys, and often rewrites it on option change or exit. Treat it as live state, not a once-written manifest.
Config is stacked. Changing one layer does not replace the others.
Layer 1 xuser.ini global engine policy
Layer 2 LogicFiles/<variant> discovery, form, and response rules
Layer 3 Project XML fill values, macros, mailbox host
Layer 4 Links/*.txt target URLs
Layer 5 Specialized INIs proxy.ini, captcha *.ini, masspm.ini
Layer 6 Runtime side files control.ini, logs, last-URL cursor
xuser.ini is Layer 1. It does not name the next URL. It does not classify HTML. It sets threads, proxy force, GET/POST caps, pipeline flags, captcha breakers, mailbox poll, and scheduler on/off.
A useful split:
- xuser.ini - how the engine behaves
- LogicFiles - how a page is understood
- Project XML - what gets typed into a form
Samples across product trees show about 150 to 170 keys in one file, and a union of about 180 unique keys. Most of them live in [AdvOptions].
The packed GUI (xpymep.exe) is the coordinator. The worker engine (xmulticore.exe) is spawned on Start. Binary internals are still a stub on disk. The file-level flow is stable:
xpymep start
-> read xuser.ini (plus tiny config.ini / control.ini)
-> pick LogicFiles variant
-> compile rule tables
-> init proxy pool and captcha stack
-> if DoLoadPlugins=1, load Plugins/SocPlugin.dll
-> show UI, wait for Start
-> pass limits and flags to workers
-> rewrite xuser.ini on option change / exit
Workers do not re-parse the INI on every URL. They get an in-memory snapshot: thread cap, proxy policy, mode bits, captcha hosts, mail flags. Per-request state is cookies, the leased proxy, and the current URL.
Because the GUI rewrites the file, a hand edit can lose to an exit save. Diffs of xuser.ini are noisy. Backup before a batch edit.
A trimmed skeleton (no secrets, no window geometry):
[Credits]
Amount=100000
[FormOptions]
Mode=Normal
CurrentLanguage=1
DeleteAfterRead=1
[AdvOptions]
ThreadsLowNumber=5
ThreadLimit=450
AutoThread=0
DontPostWithoutProxy=1
AllowNoProxyToPrior=0
GETLimit=50
POSTLimit=150
MaxPageSize=1100
MaxLinksPerPage=650
ParseFrames=1
TimeoutRetry=1
AggressiveMode=1
ExpressMode=0
URLLOAD_CASHTIME=120
LOCALFILE_CASHTIME=120
[ActivationOptions]
ActivationMode=1
ActivationPeriod=5
ActivationFilterDoubles=1
[Scheduler]
SchedulerEnabled=1
SchedulerName=Circle.xml
CurrentJob=0
[AI_Options]
AI=1110111112 1
HideXAS=0
HideXAS_AI=0
HideTextCaptchas=0
Keys are not nested. [AdvOptions] is a flat bag. Typos in vendor names are part of the format (SwithOFFprocessorStats). A parser has to match those strings exactly.
CurrentLanguage is an index into Langpack/ (0/1, typically two language packs). Mode=Normal is UI chrome, not the posting pipeline.
The hot path is one URL per worker:
dequeue URL -> lease proxy + UA -> GET -> find form -> fill -> POST -> classify -> log
The pool size comes from:
[AdvOptions]
ThreadsLowNumber=5
ThreadLimit=450
AutoThread=0
DontLowerThreads=1
EnableSleepMode=0
AggressiveMode=1
ExpressMode=0
TimeoutRetry=1
| Key | Role |
|---|---|
ThreadsLowNumber | floor of the pool |
ThreadLimit | max concurrent workers |
AutoThread | 1 lets the product scale the pool; 0 keeps ThreadLimit fixed |
DontLowerThreads | do not shrink the pool during mailbox download |
EnableSleepMode | idle throttle |
AggressiveMode | shorter delays between requests |
ExpressMode | extra speed profile |
TimeoutRetry | retry on network timeout |
Standard and high-throughput samples use ThreadLimit=450 with AutoThread=0. That is a Delphi TThread pool, not OS threads per HTTP wait in a modern runtime. A reimplementation maps this to a semaphore or worker concurrency knob. The behavior to copy is “N parallel URL attempts with isolated cookie jars”, not the Delphi type.
SwitchOFFThreadsTable=1 and SwithOFFprocessorStats=1 are UI load cuts. They do not change the HTTP path.
[AdvOptions]
DontPostWithoutProxy=1
AllowNoProxyToPrior=0
DeleteOldproxy=0
GETLimit=50
POSTLimit=150
MaxPageSize=1100
MaxLinksPerPage=650
ParseFrames=1
StopForumSpamCheck=1
URLLOAD_CASHTIME=120
LOCALFILE_CASHTIME=120
DontPostWithoutProxy=1 is the hard gate: no proxy, no POST. AllowNoProxyToPrior=0 closes the bypass for “prior” (preferred) forum sections. Direct posting is a product option; many installs turn it off.
The proxy list is not in xuser.ini. SOCKS and HTTP endpoints live in xsocks.txt / xproxy.txt. Anonymity radio state is UI plus proxy.ini. Layer 1 only says whether a missing proxy is fatal.
GETLimit and POSTLimit cap method counts per attempt window. MaxPageSize is a page-size cap in product units (treat as KB-ish). MaxLinksPerPage caps link extraction. ParseFrames=1 walks frame / iframe documents.
URLLOAD_CASHTIME and LOCALFILE_CASHTIME are cache TTLs in seconds for fetched bodies and local file reads. StopForumSpamCheck=1 is a pre-check against a public IP reputation service before spending a full form attempt.
A typed mapping that keeps the same policy:
thread_limit: 450
threads_low: 5
auto_thread: false
require_proxy: true
allow_direct_for_prior: false
get_limit: 50
post_limit: 150
max_page_size: 1100
max_links_per_page: 650
parse_frames: true
url_cache_seconds: 120
timeout_retry: true
aggressive_mode: true
The Linux-style names are not in the vendor file. They are the same knobs with stable identifiers.
Posting mode is not one enum. It is a set of independent 0/1 keys that the GUI writes and workers read.
[AdvOptions]
PostingOrMassPM=0
OnlyRegistering=0
RegisteringPlusPosting=0
FromRegistered=0
EditProfileAfterLogin=1
PostNewMode=1
LogInIfBusy=1
LogInIfBusy_New=1
ProfileMeansSuccess=1
CheckPostLevel=2
CheckForActiveLink=1
AutocontinueEnable=1
BBtoHTML=1
FillAllTextarea=0
Read them as pipeline selectors:
| Key | When 0 | When 1 |
|---|---|---|
PostingOrMassPM | public form path | mass private-message path (masspm.ini) |
OnlyRegistering | continue past the first success form | stop after the register-style form |
RegisteringPlusPosting | single path | register-style form then public post in one run |
FromRegistered | fresh session path | reuse stored session material, then post |
EditProfileAfterLogin | skip profile form | run the profile-edit path after auth |
BBtoHTML | leave BBCode | convert BBCode where the form wants HTML |
FillAllTextarea | fill classified MESSAGE fields | fill every textarea (honeypot risk) |
PostNewMode is a small integer (UI enum) for how “new message” navigation is preferred. CheckPostLevel is verification depth after a claimed success (0 = skip, higher = more xcheck* work). CheckForActiveLink asks whether a posted URL is still present and not stripped to nofollow.
Rule files still use tokens such as PM_NEWMESSAGE, PM_REGISTER, PM_LOGIN, PM_EPROFILE, PM_RANDOMREPLY. Those tokens are the worker modes. xuser.ini flags choose which chain of modes a campaign runs. A clean reimplementation can expose a single mode: field and optional extra steps, instead of packing the choice into five booleans.
Related speed/content flags in the same section:
EnableRefspam=0
OnlyRefspam=0
EnableRefspam_New19=0
RefspamQueriesNumber=1
RefspamRandomizeUA=1
RefspamRandomizePR=1
UploadAvatars=0
UploadAvatarsR=0
UploadAvatarsE=1
UploadAvatarsN=0
GennickNewFormat=1
Refspam is a referrer-hit path, not the public post path. Avatar keys pick which image folder is used on the profile form (R / E / N sets under img/Avatars/). GennickNewFormat selects the newer #gennick[...] macro style for generated nicks. Macros themselves live in project XML, not here.
Captcha is split across xuser.ini (policy) and RuCaptcha.ini / AntiCaptcha.ini / local SVM packs (endpoints and models).
[AdvOptions]
DecaptchaMode=1
ManualDecaptcha=0
ManualTextcaptcha=0
ManualGraphcaptcha=0
TextcaptchaPriority=0
MaxPictocodRetry=13
MaxPictocodRetryMan=9
SaveGoodTC=1
BreakRecaptcha=1
BreakRecaptcha_ExtHost=rucaptcha.com
BreakRecaptcha_ExtKey=
BreakKeyCaptcha=0
BreakKeyCaptcha_ExtHost=rucaptcha.com
BreakKeyCaptcha_ExtKey=
BreakHCaptcha=0
BreakHCaptcha_ExtHost=rucaptcha.com
BreakHCaptcha_ExtKey=
BreakHCaptcha_6=1
DecaptchaMode is the UI “speed vs success” mix of local auto and manual. Break* keys enable an external HTTP breaker per challenge family (reCAPTCHA, KeyCaptcha, hCaptcha). Host and key are paired. Samples often leave *_ExtKey empty until an operator fills them. Do not commit live keys.
v23 adds explicit ports and two extra families:
BreakRecaptcha_ExtPort=80
BreakHCaptcha_ExtPort=80
BreakKeyCaptcha_ExtPort=80
BreakCFCaptcha=0
BreakCFCaptcha_ExtHost=rucaptcha.com
BreakCFCaptcha_ExtKey=
BreakCFCaptcha_ExtPort=80
XEVIL_FOR23_HOST=127.0.0.1
XEVIL_FOR23_PORT=80
XEVIL_FOR23_KEY=
BreakCFCaptcha is a Cloudflare challenge breaker. XEVIL_FOR23_* points at a local solver HTTP endpoint. A reimplementation can retarget every Break*_ExtHost at that loopback service. Port 80 on localhost often collides with other listeners; product samples still default to 80.
SaveGoodTC=1 persists successful text-captcha answers into a local list for reuse. [CapForms.HC] and [CapForms.TC] are only window geometry for the manual solve dialogs.
Local ML assets (SVM/, xca.dll, xcs.dll, textcaptcha_*.txt, FieldsForAI/) are not selected in xuser.ini beyond DecaptchaMode and the Hide* AI flags. Those files get their own post.
Some forms reply with “check your mail”. The product then pulls POP3 or IMAP, matches LogicFiles/*/xpop.txt patterns, extracts a confirm URL, and GETs it on the same session when it can.
Layer 1 flags:
[AdvOptions]
DownloadAllEMail=1
DownloadEmailOnFinish=1
DownloadEMailMaxTime_New=120
SaveActivationToZ=1
MakePoplogs=0
UseIMAP=1
[ActivationOptions]
ActivationMode=1
ActivationPeriod=5
ActivationFilterDoubles=1
| Key | Role |
|---|---|
ActivationMode | 0 off, 1 run the mail path after a matching response |
ActivationPeriod | poll budget (product units; treat as a short multi-minute window) |
ActivationFilterDoubles | skip duplicate messages and duplicate links |
DownloadAllEMail | poll during the run, not only at the end |
DownloadEmailOnFinish | one more poll when the campaign stops |
DownloadEMailMaxTime_New | time cap for a mail phase, in seconds, in some later copies |
UseIMAP | try IMAP first, then POP3 |
MakePoplogs | verbose mail logs under Debug/POP_Logs |
Mailbox host, identity, and secret are not in xuser.ini. They come from project XML (EmailPOP, and the related fill fields). xpop.txt supplies host globs, SSL host lists, header keywords, ignore lists, and link extract patterns.
A typed YAML mapping that preserves the same split:
activation:
mode: 1
period_minutes: 5
filter_doubles: true
download_all_email: true
download_on_finish: true
prefer_imap_then_pop3: true
make_pop_logs: false
# project XML supplies host / identity / secret
# LogicFiles xpop.txt supplies match and extract rules
LastMailAutoreg is leftover UI state for an auto-provisioned mailbox provider. Skip it in a clean config.
[Scheduler]
SchedulerEnabled=1
SchedulerEmailNotify=0
SchedulerMailForNotify=http://yourdomain.com/yourscript.php
CurrentJob=0
SchedulerName=Circle.xml
When SchedulerEnabled=1, the GUI loads Schedules/<SchedulerName>. Circle.xml is the usual sample. Jobs and events are a large enum in the language pack (Job1..Job36, Event1..Event16): start, stop, rotate lists, change project, and similar operator actions.
CurrentJob is the index of the active or next job. The product writes it back. SchedulerMailForNotify is a webhook-style URL, despite the “Mail” name.
xuser.ini does not contain the job graph. It only points at the XML and stores the cursor. The XML format is a later post in this cycle.
[Credits] - vendor credit counter shown in the UI. Not used by an offline engine.
[FormOptions] - UI language and form chrome.
[ReportCrossLinks] - auto anchor generation for reports:
[ReportCrossLinks]
AnchorsGenEnable=1
AnchorsGenType=0
AnchorsGenFrom=0
AnchorsGenFor=0
[LinksConverter] - templates that wrap a URL as HTML or BBCode:
[LinksConverter]
PatternNum=1
Pattern1=<a href={url}>{keyword}</a>
Pattern2=[url={url}]{keyword}[/url]
Pattern3={url} - {keyword}
AnchorNum=6
AnchorsFromList=2
GenMethod1=1
Separator=,
GenMethod2=4
Placeholders are {url} and {keyword}. UrlConvMask in [AdvOptions] is the default one-line mask (<a href=URL>ANCHOR</a>).
[AI_Options] - packed feature word plus hide flags:
[AI_Options]
AI=1110111112 1
HideXAS=0
HideXAS_AI=0
HideTextCaptchas=0
The AI= value is a bitmask plus a trailing flag. Exact bit meanings are only partly decoded. HideXAS_AI=0 keeps the AI-assisted field path (FieldsForAI/, xas_AI.txt) available. HideXAS=1 hides ordinary xas rules in debug UI.
[Form] - main window theme, skin, and pixel geometry (Top / Left / Width / Height). Drop this in a headless port.
[Settings] - ProjectFormat=xml is the important one. Projects are XML, not the older binary layout.
[Antispam] - AntispamEnable=0 by default. When on, the GUI talks to Antispam/AntiSpamServer*.exe for “does this text look on-topic” checks.
[Warnings] - “do not show this dialog again” bits (WarnMessage8=1, and similar). UI only.
[CapForms.HC] / [CapForms.TC] - manual captcha window rectangles.
Vendor-login material and tool entitlement blobs also appear as extra sections in some trees. They are not engine policy. An offline reimplementation omits them.
DetailedReports=1
DoLogCategories=0
DoLogRealInitURL=0
MustDelReports=0
MustDelBases=0
ViewReportsMethod=2
DoLoadPlugins=1
MasspmOnlyLoad=0
MasspmOnlyParse=0
MassPMLinks_Max=5000
MassPMLinks_Step=50
MassDebug=0
ToolPost1=1
ToolPost2=0
ToolPost3=0
ToolDup1=1
ToolDup2=0
ToolDup3=0
DetailedReports=1 makes Success/Others lines verbose (proxy used, nick chosen, result token). DoLoadPlugins=1 loads Plugins/SocPlugin.dll when present. Mass-PM keys cap list size and batch step; they only matter when PostingOrMassPM=1. ToolPost* / ToolDup* are Tools-menu strategy toggles.
XGrabDontsaveMaskInLog and XGrabDeepParsing belong to the XGrab helper, not the main post loop.
The same filename grows across product versions. Compare by section, not by file size.
v23-only keys that matter:
Break*_ExtPort- breaker HTTP portBreakCFCaptcha*- Cloudflare familyXEVIL_FOR23_*- local solverSwithOFFprocessorStats_x23- UI stats off, x23 name
Keys that show up in high-throughput copies but not in every Business tree:
UseIMAPDontLowerThreadsDownloadEMailMaxTime_NewManualTextcaptcha/ManualGraphcaptchaMasspmMaxUsers/MasspmMinPause/MasspmMaxMessagesSwitchOFFThreadsTable/SpeedBarPos
A parser should be key-tolerant: unknown keys stay as extra map entries. The product is happy to add a key without a schema bump.
Operator checklist that does not need the GUI:
- Confirm it is INI with
[AdvOptions]as the bulk of the file. - Read
ThreadLimit,ThreadsLowNumber,AutoThread. - Read
DontPostWithoutProxyandAllowNoProxyToPrior. - Read the pipeline booleans (
PostingOrMassPM,OnlyRegistering,RegisteringPlusPosting,FromRegistered,EditProfileAfterLogin). - Read captcha
Break*hosts (keys should be empty in a sample). - Read
[ActivationOptions]plusUseIMAP/Download*EMail. - Read
[Scheduler]name andCurrentJob. - Ignore
[Form],[CapForms.*],[Warnings], and credit counters unless you are matching a screenshot.
A tiny reader in Python (stdlib only):
from configparser import ConfigParser
cfg = ConfigParser()
cfg.optionxform = str # keep vendor key case
cfg.read("xuser.ini", encoding="cp1251")
adv = cfg["AdvOptions"]
print("threads", adv.get("ThreadsLowNumber"), "..", adv.get("ThreadLimit"))
print("require_proxy", adv.get("DontPostWithoutProxy"))
print("post_or_masspm", adv.get("PostingOrMassPM"))
print("activation", cfg["ActivationOptions"].get("ActivationMode"))
Vendor trees are often CP1251. UTF-8 works for the ASCII keys above. Keep original key case: SwithOFF... is not a typo you can “fix” in place.
Keep a typed snapshot, not a free-form INI, for the engine:
EngineConfig
threads: { low, limit, auto }
network: { require_proxy, get_limit, post_limit, max_page_size, cache_ttl }
pipeline: { mode, extra_steps[] }
captcha: { decaptcha_mode, breakers[{family, host, port}] }
mail: { enabled, period, filter_doubles, prefer_imap }
scheduler: { enabled, file } # optional
Workers receive that snapshot as immutable. UI geometry, warning dismissals, and vendor credit counters stay out.
An INI importer is a one-way adapter into that struct. Round-tripping xuser.ini is optional. The GUI rewrite behavior is a Windows product quirk, not a requirement for a headless worker.
What not to copy from Layer 1:
- window pixels and skins
- language-pack index as an engine setting
- packed
AI=until bits are decoded - any vendor-login section
The EXEs are custom-packed. Static disassembly of xpymep / xmulticore on disk sees the unpacker stub, not the INI loader. Claims in this post are from:
- INI text across v19 Standard, v19 Business, v23 Business, and related copies
- language-pack strings that name the same options
- specialized INIs and LogicFiles that consume the flags
- observed GUI rewrite of keys
That is high confidence for what the keys mean. It is low confidence for Delphi method names. The AI= bitfield is still only partly mapped. Units for ActivationPeriod and MaxPageSize are product-specific; treat the numbers as relative until a live trace confirms them.
Planned posts, same tag xrumer:
- xuser.ini (this post) - Layer 1 policy
- LogicFiles -
xurl,xignoreforms,xas,xbuttons,xmessages(the actual site brain) - Project XML - fill macros, prior keywords, mailbox host fields
- Proxy lists and proxy.ini - SOCKS vs HTTP, anonymity radios
- Captcha files - SVM packs, text lists, breaker INIs
- Scheduler XML -
Schedules/Circle.xmljobs and events - xpop.txt - mailbox match and link extract rules
The through-line is the same: XRumer is data-driven. If you want to understand a run, read the text files first. xuser.ini tells you the policy. LogicFiles tell you how a page is classified. Project XML tells you what is typed into the form.