Modding
Wall Street Raider - Modding Guide
A reference for everything you can mod in WSR with nothing but a text editor. The Electron frontend has no build step and the install ships with asar: false, so every file under resources/app/ is a normal editable file. Edit, restart, see your change.
What you can't mod without recompiling: the PowerBASIC engine (wsr.exe), the C++ bridge (ui.dll), and the binary .DAT/.PRM files.
TL;DR - what's moddable
| I want to… | Do this |
|---|---|
| Re-theme the game | Edit css/variables.css, then css/theme.css / css/components/
|
| Change UI text / layout / behavior | Edit the relevant file in js/components/
|
| Add a new panel or feature | New file in js/components/, import it from a parent or app.js
|
| Replace a background video or image | Drop a same-named file into assets/
|
| Rewrite the manual | Edit assets/help/wsrbook.htm
|
| Add a language | New js/locale/<code>.js + register in localeManager.js
|
| Change / add contextual hints | Edit files in js/data/hints/
|
| Rename companies / tickers / nations | Edit CORPNAME.DAT (plain text). Effective on next New Game.
|
| Drive the game from JS | Call an api.js function (§api.js)
|
| Drive the game from a third-party app | Hit the REST API directly (§REST API) |
| Read game state | useGameStore(s => s.gameState.<field>) or GET /gamestate (§gameState)
|
| Persist your mod's data in saves | setCustomData({ myKey: … }) (§CustomData)
|
| Share a mod with other players | Steam Workshop (§Workshop) |
| Change a core simulation rule | Not moddable. Requires engine source + recompile. |
Architecture in one breath
PowerBASIC engine → C++ bridge DLL (REST + WebSocket) → Electron UI wsr.exe ui.dll everything you see
At launch, ui.dll picks ephemeral REST + WS ports and writes them to %LOCALAPPDATA%\Wall Street Raider\runtime.json:
{ "pid": 12345, "rest_port": 54321, "ws_port": 54322 }
Electron reads that file and hands the ports to the renderer; js/api.js builds http://127.0.0.1:<rest> and ws://127.0.0.1:<ws>. WS pushes a full snapshot on connect, then JSON-patch diffs; if WS drops, the UI polls GET /gamestate.
Third-party apps: read runtime.json the same way. Ports change every launch.
File layout
In source (electron/) and in an installed copy (<install>/resources/app/) the layout is identical - the install is a verbatim copy of the source tree (see extraFiles in electron/package.json):
resources/app/ ├── index.html # entry point ├── main.js # Electron main process ├── js/ # ← all frontend code (editable) │ ├── app.js # Preact root, WS wiring, hotkeys │ ├── api.js # REST/WS client, gameStore, ID constants, endpoint wrappers │ ├── components/ # 100+ Preact panels/modals/tables/tabs │ ├── hooks/ # useCookie, useInterval, usePanelSelection, … │ ├── services/ # hintMatcher.js, … │ ├── utils/ # misc │ ├── icons.js # inline SVG │ ├── debug-log.js # frontend logging │ ├── lib/ # vendored: preact, zustand, tailwind, fast-json-patch │ ├── locale/ # ← translations │ └── data/hints/ # ← contextual hints ├── css/ # ← all styling (editable) ├── assets/ # ← images / videos / icons / help (editable) ├── CORPNAME.DAT # ← plain-text company database (editable) ├── wsr.exe # engine (binary) ├── ui.dll # bridge (binary) └── *.DAT / *.PRM # other game data (binary/encoded - leave alone)
Frontend code (js/)
Components use Preact + htm (JSX-like template literals, no compile step) and Zustand for state. Minimal example:
import { html, Component } from '../lib/preact.standalone.module.js';
import { useGameStore } from '../api.js';
export function MyPanel() {
const cash = useGameStore(s => s.gameState.cash);
return html`<div class="p-4">Cash: ${cash}</div>`;
}
You can:
- Edit any existing component (layout, text, colors, behavior).
- Add new components and import them into
app.jsor any parent. - Add new hotkeys (grep
hotkey; handling lives inapp.js+HotkeyButtonBar.js). - Add new buttons that call any API endpoint (§api.js).
- Add new derived displays from
gameState(§gameState).
Styling (css/)
css/ ├── style.css # the file index.html links - imports the rest ├── base.css # resets / base element styles ├── variables.css # ← CSS custom properties (theme palette lives here) ├── theme.css # theme layer ├── layout.css # app layout ├── chart-styles.js # chart styling injected as JS ├── components/ # buttons, dropdown, forms, modals, panels, tables, tabs ├── features/ # assets, calculator, help, market, menu, notes, ticker, tutorial └── pages/ # game, main-menu, quotes
Tailwind utilities are also available via js/lib/tailwind.module.js, so most components use Tailwind inline + the modular CSS for anything custom.
To re-theme: start with variables.css - override the custom properties and the whole app follows.
Assets (assets/)
| What | Files |
|---|---|
| Background videos | the many *.mp4 (Wall Street footage, industry b-roll, …)
|
| Event images | blackswan.png, ponzi.jpg, soupline.jpg, zimbabwe.jpg, helicopter.jpg, yacht.mp4
|
| Branding / logos | wallstreetraider_logo.png, hackjackgames_logo.png, roninsoft_logo.png
|
| Social widgets | discord-widget.png, reddit-widget.png
|
| Loading spinner | loading.gif
|
| App icon | wsr.ico
|
Replace any of these with a same-named file of the same type. Keep filenames identical - components reference them by name.
Help content (assets/help/)
wsrbook.htm is the in-game manual - one ~1 MB plain HTML file. Edit it directly. The *.jpg files in the same folder are diagrams it embeds. electron/UI_HELP_CATALOG.txt is a developer catalog of help sections.
Translations (js/locale/)
Pure data - no engine changes needed to add a language.
How it works (localeManager.js):
LANGUAGE_OPTIONSmaps locale code →{ name, dictionary, warning? }.- Each
dictionaryis a flat{ "English source": "translation", … }. - The
translatordoes runtime lookup/replacement on UI strings. - Existing locales:
zh-CN.js,es-419.js,ja-JP.js,pt-BR.js,ru-RU.js.
To add a language:
// js/locale/fr-FR.js
export const frFR = {
"New Game": "Nouvelle partie",
"Buy Stock": "Acheter des actions",
// … one entry per source string you want translated
};
// js/locale/localeManager.js
import { frFR } from './fr-FR.js';
// …
'fr-FR': { name: 'Français', dictionary: frFR },
The chosen locale persists in CFIG.WSR, surfaces as gameState.locale, and switches via POST /set_locale.
Contextual hints (js/data/hints/)
Hint files are grouped by view and aggregated by index.js:
hints/ ├── index.js # aggregates all sets ├── global.js # fallback hints ├── company.js # while viewing a company ├── industry.js ├── market.js ├── modals.js ├── player.js ├── portfolio.js └── swap-info-text.js
Each entry:
{
id: 'company-overview-controlled-etf',
match: { view: 'company', tab: 'overview', controls: true, entityKind: 'mutual-fund' },
title: 'Advising an ETF',
content: `<p>HTML body shown to the player…</p>`,
}
js/services/hintMatcher.js picks the most specific match for the current screen. Rewrite text, add new hints, retarget match conditions - no engine changes.
Company names (CORPNAME.DAT)
Plain pipe-delimited text:
0011|00|JPB | J.P. MULLINS WALL ST. BANK | 0012|00|UBC | URBAN BANCORP | 0015|26|KYOB | KYOTO BANK |
Columns: company ID | nation code | ticker | name. Read by the engine's GetCorpNames at new-game time, so edits take effect on the next new game (not on already-saved games). Rename companies, change tickers, change a company's home nation.
Keep the format exact: 4-digit zero-padded ID, 2-digit nation code, fixed-width symbol field, and the surrounding | + spacing. CORPNAME.ORI is the pristine original - keep as your backup.
Other game data (don't bother)
| File(s) | Content | Why limited |
|---|---|---|
QUOTEWSR.DAT |
quote of the day | encoded; quotehs.prm is a near-plaintext source the engine doesn't read
|
MSCNEWS.DAT |
news / scenario headlines | encoded; same story with MSCNEWS.PRM
|
scenwin1-4.prm |
scenario window text | engine-internal format |
GAME01-50.DAT |
save slots / scenario data | proprietary binary |
WSR101.DAT, REGINFO.DAT, … |
engine data | proprietary binary |
Treat as read-only. CORPNAME.DAT is the one truly editable game-data file.
Calling the API from a frontend mod
js/api.js is the wrapper layer for the REST API: each endpoint has a named async function with semantic parameters, plus the live gameStore, plus exported ID constants (entity IDs, industry indices, special asset IDs, UI_* report IDs). Frontend mods should call api.js functions, not raw HTTP.
import * as api from '../api.js'; await api.buyStock(companyId); // /buy_stock await api.setDividend(actingAsId); // /set_dividend await api.merger(targetId, actingAsId); // /merger await api.cheatAddCash(); // /cheat_add_cash const state = await api.getGameState(); // GET /gamestate
For endpoints that don't have a named wrapper yet, four request shapes are also exported:
| Helper | Body shape | Use |
|---|---|---|
postNoArg(path) |
{} |
actions with no parameter |
postIdArg(path, id) |
{ id } |
actions on a specific entity / slot |
postIdArgWithActingAs(path, id, actingAsId) |
{ id, intParam2 } |
most corporate / trade actions |
postOptionsTradeWithActingAs(path, id, actingAsId, underlyingId) |
{ id, intParam2, underlyingId } |
options trades |
postStringArg(path, str) |
{ str } |
actions taking a string (e.g. save-as filename) |
getJSON(path) |
- | GET endpoints |
For wire-level details (the underlying routes, the JSON body the bridge actually receives, what each generic field means per endpoint), see §REST API reference.
REST API reference
For third-party apps and direct integrations. Frontend mods should prefer api.js (§api.js).
Conventions
- Base URL:
http://127.0.0.1:<rest_port>. Readrest_portfrom%LOCALAPPDATA%\Wall Street Raider\runtime.json; ports change every launch. - Content-Type:
application/jsonon all POSTs. - Generic body schema: the bridge accepts a single envelope
{ id?, intParam2?, underlyingId?, str?, answer?, value?, filename? }. Each endpoint uses 0-3 of these fields with endpoint-specific meanings - the per-endpoint tables below spell out which fields are read and what they mean in context. - Omitted fields default to 0 / empty. Most action endpoints can be called as
{}if they don't strictly need params (the engine often falls back to "use the currently active entity" or "show the picker modal"). - Most actions open a modal rather than executing immediately. After POSTing, the engine pushes a modal state via WS /
gameState.modalType; respond withPOST /modal_result.
The actingAsId pattern
Most corporate / trade endpoints accept an actingAsId (sent as the JSON field intParam2): the entity that executes the action.
0(or omitted) = act as the player.- Any other ID = act as that controlled company. The engine momentarily switches context, runs the action as that company, then restores.
When the per-endpoint tables below show intParam2, it means actingAsId unless noted otherwise.
Response shape
GET endpoints return endpoint-specific JSON (documented in the GET table). POST endpoints return { status: "ok" } on success and a { error: "…" } with HTTP 4xx/5xx on failure. Side effects show up via the next WS broadcast.
GET endpoints
| Method | Endpoint | Returns |
|---|---|---|
| GET | /status |
health check |
| GET | /gamestate |
full game-state JSON (§gameState) |
| GET | /quote |
{ quote: string } - quote of the day
|
| POST | /asset_chart |
body { id }; returns 60 months of price history for an asset ({ prices, highs, lows, xAxisTitle, yAxisTitle, baseMonth, baseYear })
|
| GET | /database_data |
searchable database of all companies (industry, financials, ratings, …) |
| GET | /ownership_tree |
ownership hierarchy of the active entity (who owns it, at what %) |
| GET | /subsidiaries_tree |
what the active entity owns (its subsidiaries, at what %) |
Session / clock
| Endpoint | Body | Description |
|---|---|---|
POST /newgame |
{} |
Start a new game. |
POST /loadgame |
{} |
Open the load-game flow. |
POST /load_specific_save |
{ filename } |
Load a specific save. filename = save basename; must not contain .., /, \, :.
|
POST /savegame |
{} |
Save to the default slot. |
POST /savegameas |
{ filename } |
Save under a name. Empty filename shows the Save-As dialog.
|
POST /exit_game |
{} |
Exit to main menu. |
POST /check_scoreboard |
{} |
Open the scoreboard view. |
POST /start_ticker |
{} |
Start the market ticker animations. |
POST /run_ticker |
{} |
Advance the ticker by one queue item. |
POST /stop_ticker |
{} |
Stop the ticker. |
POST /set_ticker_speed |
{ id } |
Set ticker speed. id = speed enum.
|
POST /ticker_advance |
{} |
Pop the front of the ticker queue, append a new item. |
POST /clear_event_string |
{} |
Clear the event message log. |
POST /splash_screen_played |
{} |
Mark splash as shown so it's skipped next time. |
Trading - stocks & bonds
| Endpoint | Body | Description |
|---|---|---|
POST /buy_stock |
{ id, intParam2? } |
Buy stock. id = company to buy. intParam2 = actingAsId.
|
POST /sell_stock |
{ id, intParam2? } |
Sell stock. id = company to sell. intParam2 = actingAsId.
|
POST /short_stock |
{ id, intParam2? } |
Open short. id = company to short. intParam2 = actingAsId.
|
POST /cover_short_stock |
{ id, intParam2? } |
Cover an existing short. id = company. intParam2 = actingAsId.
|
POST /buy_corporate_bond |
{ id, intParam2? } |
Buy a corporate bond. id = issuing company. intParam2 = actingAsId.
|
POST /sell_corporate_bond |
{ id, intParam2? } |
Sell a corporate bond. id = issuing company. intParam2 = actingAsId.
|
POST /buy_long_govt_bonds |
{ intParam2 } |
Buy long-term govt bonds. intParam2 = actingAsId. id ignored.
|
POST /sell_long_govt_bonds |
{ intParam2 } |
Sell long-term govt bonds. intParam2 = actingAsId.
|
POST /buy_short_govt_bonds |
{ intParam2 } |
Buy short-term govt bonds. intParam2 = actingAsId.
|
POST /sell_short_govt_bonds |
{ intParam2 } |
Sell short-term govt bonds. intParam2 = actingAsId.
|
Trading - commodities & crypto
| Endpoint | Body | Description |
|---|---|---|
POST /buy_commodity_futures |
{ id, intParam2? } |
Buy commodity futures. id = commodity ID (e.g. OIL_ID, GOLD_ID). intParam2 = actingAsId.
|
POST /sell_commodity_futures |
{ id, intParam2? } |
Sell commodity futures. id = commodity ID. intParam2 = actingAsId.
|
POST /close_long_commodity_futures_by_slot |
{ id, intParam2? } |
Close a specific long position. id = portfolio slot index (not a commodity ID). intParam2 = actingAsId.
|
POST /short_commodity_futures |
{ id, intParam2? } |
Short commodity futures. id = commodity ID. intParam2 = actingAsId.
|
POST /cover_short_commodity_futures |
{ id, intParam2? } |
Cover commodity short. id = commodity ID. intParam2 = actingAsId.
|
POST /cover_short_commodity_futures_by_slot |
{ id, intParam2? } |
Cover a specific short position. id = portfolio slot index. intParam2 = actingAsId.
|
POST /buy_physical_commodity |
{ id, intParam2? } |
Buy physical commodity. id = commodity ID. intParam2 = actingAsId.
|
POST /sell_physical_commodity |
{ id, intParam2? } |
Sell physical commodity. id = commodity ID. intParam2 = actingAsId.
|
POST /buy_physical_crypto |
{ id, intParam2? } |
Buy physical crypto. id = crypto ID (e.g. BITCOIN_ID). intParam2 = actingAsId.
|
POST /sell_physical_crypto |
{ id } |
Sell physical crypto. id = crypto ID. (No actingAs param on this one.)
|
POST /buy_crypto_futures |
{ id, intParam2? } |
Buy crypto futures. id = crypto ID. intParam2 = actingAsId.
|
POST /sell_crypto_futures |
{ id, intParam2? } |
Sell crypto futures. id = crypto ID. intParam2 = actingAsId.
|
Trading - options
| Endpoint | Body | Description |
|---|---|---|
POST /buy_calls |
{ id, intParam2?, underlyingId? } |
Buy calls. id = strike-price ID (or 0 to show the strike picker). intParam2 = actingAsId. underlyingId = company being optioned (when id>0 and underlyingId>0, modal is skipped — used by CLI flows like "CALL ABC").
|
POST /sell_calls |
{ id, intParam2?, underlyingId? } |
Sell calls. Same param semantics as /buy_calls.
|
POST /buy_puts |
{ id, intParam2?, underlyingId? } |
Buy puts. Same param semantics as /buy_calls.
|
POST /sell_puts |
{ id, intParam2?, underlyingId? } |
Sell puts. Same param semantics as /buy_calls.
|
POST /advanced_options_trading |
{ intParam2 } |
Open the advanced options panel (spreads, collars). intParam2 = actingAsId.
|
POST /exercise_call_options_early |
{ id, intParam2? } |
Exercise a call early. id = call contract ID. intParam2 = actingAsId.
|
POST /exercise_put_options_early |
{ id, intParam2? } |
Exercise a put early. id = put contract ID. intParam2 = actingAsId.
|
Corporate management
All of these operate on the acting-as entity. id is unused unless noted; pass 0 or omit.
| Endpoint | Body | Description |
|---|---|---|
POST /prepay_taxes |
{ intParam2 } |
Prepay taxes. intParam2 = actingAsId.
|
POST /elect_ceo |
{ intParam2 } |
Open CEO-election picker for the entity. intParam2 = actingAsId.
|
POST /resign_as_ceo |
{ intParam2 } |
Acting-as entity resigns as CEO. intParam2 = actingAsId.
|
POST /change_managers |
{ intParam2 } |
Hire / fire managers. intParam2 = actingAsId.
|
POST /set_dividend |
{ intParam2 } |
Set the company's dividend. intParam2 = actingAsId (dividend issuer).
|
POST /set_productivity |
{ intParam2 } |
Set productivity. intParam2 = actingAsId.
|
POST /set_growth_rate |
{ intParam2 } |
Set growth rate. intParam2 = actingAsId.
|
POST /restructure |
{ intParam2 } |
Open restructuring modal. intParam2 = actingAsId.
|
POST /buy_corporate_assets |
{ intParam2 } |
Buy plant / equipment. intParam2 = actingAsId.
|
POST /sell_corporate_assets |
{ intParam2 } |
Sell plant / equipment. intParam2 = actingAsId.
|
POST /offer_corporate_assets_for_sale |
{ intParam2 } |
List assets for other entities to buy. intParam2 = actingAsId.
|
POST /view_for_sale_items |
{ intParam2 } |
Browse assets currently for sale. intParam2 = actingAsId.
|
POST /sell_subsidiary_stock |
{ id, intParam2? } |
Sell stock the entity holds in a subsidiary. id = subsidiary company ID. intParam2 = actingAsId (seller).
|
POST /rebrand |
{ intParam2 } |
Rename the company. intParam2 = actingAsId.
|
POST /toggle_company_autopilot |
{ id } |
Toggle one company's autopilot. id = company.
|
POST /toggle_global_autopilot |
{ intParam2 } |
Toggle autopilot across all of the entity's holdings. intParam2 = actingAsId.
|
POST /become_etf_advisor |
{ intParam2 } |
Become ETF advisor. intParam2 = actingAsId.
|
POST /set_advisory_fee |
{ intParam2 } |
Set the ETF advisory fee. intParam2 = actingAsId.
|
POST /decrease_earnings |
{ intParam2 } |
Accounting adjustment to lower reported earnings. intParam2 = actingAsId.
|
POST /increase_earnings |
{ intParam2 } |
Accounting adjustment to raise reported earnings. intParam2 = actingAsId.
|
M&A / corporate finance
| Endpoint | Body | Description |
|---|---|---|
POST /merger |
{ id, intParam2? } |
Start a merger. id = target company. intParam2 = actingAsId (acquirer).
|
POST /greenmail |
{ id, intParam2? } |
Greenmail (coercive buyback). id = target. intParam2 = actingAsId.
|
POST /lbo |
{ id, intParam2? } |
Leveraged buyout. id = target. intParam2 = actingAsId.
|
POST /startup |
{ intParam2 } |
Found a new startup. intParam2 = actingAsId (founder).
|
POST /capital_contribution |
{ intParam2 } |
Inject cash into a subsidiary. intParam2 = actingAsId.
|
POST /public_stock_offering |
{ intParam2 } |
IPO. intParam2 = actingAsId (company going public).
|
POST /private_stock_offering |
{ intParam2 } |
Private placement. intParam2 = actingAsId.
|
POST /issue_new_corp_bonds |
{ intParam2 } |
Issue new corporate bonds. intParam2 = actingAsId (issuer).
|
POST /redeem_corp_bonds |
{ intParam2 } |
Retire outstanding bonds. intParam2 = actingAsId.
|
POST /extraordinary_dividend |
{ intParam2 } |
One-time special dividend. intParam2 = actingAsId.
|
POST /tax_free_liquidation |
{ intParam2 } |
Liquidate with tax deferral. intParam2 = actingAsId.
|
POST /taxable_liquidation |
{ intParam2 } |
Liquidate; pay tax on gains. intParam2 = actingAsId.
|
POST /spin_off |
{ id, intParam2? } |
Spin off a subsidiary. id = subsidiary to spin off. intParam2 = actingAsId (parent).
|
POST /split_stock |
{ intParam2 } |
Forward stock split. intParam2 = actingAsId.
|
POST /reverse_split_stock |
{ intParam2 } |
Reverse stock split. intParam2 = actingAsId.
|
Banking / loans / swaps
| Endpoint | Body | Description |
|---|---|---|
POST /borrow_money |
{ intParam2 } |
Take out a loan. intParam2 = actingAsId (borrower).
|
POST /repay_loan |
{ intParam2 } |
Pay down / repay. intParam2 = actingAsId.
|
POST /advance_funds |
{ intParam2 } |
Lender advances cash against securities. intParam2 = actingAsId (lender).
|
POST /call_in_advance |
{ id } |
Terminate an advance. id = advance ID.
|
POST /interest_rate_swaps |
{ id, intParam2? } |
Open the swaps modal. id = asset ID. intParam2 = actingAsId.
|
POST /view_swap_details |
{ id, intParam2? } |
View one swap's details. id = swap ID. intParam2 = actingAsId.
|
POST /terminate_swap |
{ id, intParam2? } |
Terminate a swap early. id = swap ID. intParam2 = actingAsId.
|
POST /set_bank_allocation |
{ intParam2 } |
Distribute cash across banks. intParam2 = actingAsId.
|
POST /trade_tbills |
{ intParam2 } |
Trade T-bills. intParam2 = actingAsId.
|
POST /list_bank_loans |
{ intParam2 } |
List the entity's outstanding bank loans (when acting as a bank). intParam2 = actingAsId.
|
POST /change_bank |
{ intParam2 } |
Change primary bank. intParam2 = actingAsId.
|
POST /call_in_loan |
{ id } |
Demand immediate repayment. id = loan ID.
|
POST /buy_bank_loans |
{} |
Open the bank-loans investment picker. |
POST /buy_business_loans |
{ intParam2 } |
Buy business-loan portfolio. intParam2 = actingAsId (investor).
|
POST /sell_business_loan |
{ id } |
Sell one business-loan holding. id = loan ID.
|
POST /buy_consumer_loans |
{ intParam2 } |
Buy consumer-loan portfolio. intParam2 = actingAsId.
|
POST /sell_consumer_loans |
{ intParam2 } |
Sell consumer-loan holdings. intParam2 = actingAsId.
|
POST /buy_prime_mortgages |
{ intParam2 } |
Buy prime mortgages. intParam2 = actingAsId.
|
POST /sell_prime_mortgages |
{ intParam2 } |
Sell prime mortgages. intParam2 = actingAsId.
|
POST /buy_subprime_mortgages |
{ intParam2 } |
Buy subprime mortgages. intParam2 = actingAsId.
|
POST /sell_subprime_mortgages |
{ intParam2 } |
Sell subprime mortgages. intParam2 = actingAsId.
|
POST /list_etfs |
{} |
Show available ETFs. |
POST /freeze_all_loans |
{ intParam2 } |
Freeze all loan repayments across the lender's portfolio. intParam2 = actingAsId.
|
POST /freeze_loan |
{ id } |
Freeze one specific loan. id = loan ID.
|
Legal / dirty tricks
| Endpoint | Body | Description |
|---|---|---|
POST /change_law_firm |
{ intParam2 } |
Hire a law firm. intParam2 = actingAsId.
|
POST /credit_info |
{ intParam2 } |
Show credit info (rating, history). intParam2 = actingAsId.
|
POST /antitrust_lawsuit |
{ id, intParam2? } |
Sue for antitrust. id = target company. intParam2 = actingAsId (plaintiff).
|
POST /harrassing_lawsuit |
{ id, intParam2? } |
File a frivolous lawsuit. id = target. intParam2 = actingAsId.
|
POST /spread_rumors |
{ id, intParam2? } |
Spread rumors to damage stock / reputation. id = target company. intParam2 = actingAsId.
|
Reports / views / navigation
| Endpoint | Body | Description |
|---|---|---|
POST /set_active_ui_report |
{ id } |
Switch active report panel. id = a UI_* enum (see api.js constants).
|
POST /set_view_asset |
{ id } |
Navigate to a company / player. id = entity ID. Updates nav history.
|
POST /set_view_industry |
{ id } |
Navigate to an industry overview. id = industry index, or -2 = trigger DB Search.
|
POST /database_search |
{} |
Open the database search interface. |
POST /clear_chart |
{} |
Clear the active price chart. |
POST /growth_throttle |
{} |
Open growth-throttle settings. |
POST /clear_stream_list |
{} |
Clear the streaming-quotes watchlist. |
POST /fill_stream_list |
{} |
Populate the streaming-quotes watchlist with defaults. |
POST /toggle_streaming_quote |
{ id } |
Toggle live updates for one asset. id = company / asset.
|
POST /nav_back |
{} |
Nav history: back. |
POST /nav_forward |
{} |
Nav history: forward. |
POST /nav_clear |
{} |
Clear nav history. |
POST /nav_goto |
{ id } |
Jump to a nav-history index. id = position in history stack.
|
POST /nav_set_history |
JSON array | Restore nav history from saved state. Body = array of { id, type } where type is "asset" or "industry", most-recent-first.
|
POST /set_who_owns_filter |
{ value } |
Set the "who owns what" filter. value = filter enum.
|
POST /view_current_interest_rates |
{} |
Show current interest rates. |
POST /whos_ahead |
{} |
Show the leaderboard. |
POST /db_research_tool |
{} |
Open the research database tool. |
POST /economic_stats |
{} |
Show macro stats. |
POST /most_cash_report |
{} |
Most-cash leaderboard. |
POST /largest_market_cap |
{} |
Market-cap leaderboard. |
POST /largest_tax_losses |
{} |
Largest carried-forward tax losses. |
POST /industry_summary |
{} |
Industry summary stats. |
POST /industry_projections |
{} |
Industry growth / decline projections. |
POST /view_corp_assets_for_sale |
{} |
All corporate assets currently for sale. |
Settings / toggles
These all open a settings menu / cycle the toggle. No params except where noted.
| Endpoint | Body | Description |
|---|---|---|
POST /supp_earn_select |
{} |
Suppress earnings-warnings menu. |
POST /currency_select |
{} |
Currency selection. |
POST /supp_warn_select |
{} |
Suppress-warnings menu. |
POST /suppress_select |
{} |
Suppress messages / alerts menu. |
POST /autosave_select |
{} |
Autosave settings. |
POST /exercise_select |
{} |
Auto-exercise options settings. |
POST /sweep_select |
{} |
Cash-sweep settings. |
POST /makedelivery_select |
{} |
Make-delivery settings (commodity/crypto). |
POST /takedelivery_select |
{} |
Take-delivery settings. |
POST /tooltips_select |
{} |
Tooltip enable / disable. |
POST /shareholdergraph_select |
{} |
Shareholder-graph display settings. |
POST /disablehotkeys_select |
{} |
Hotkey enable / disable. |
POST /autoadd_select |
{} |
Auto-add-to-watchlist settings. |
POST /set_chart_type |
{ id } |
Set chart style. id = chart type enum.
|
POST /set_locale |
{ str } |
Set UI locale. str = locale code (e.g. "en-US", "ja-JP").
|
Cheat menu
| Endpoint | Body | Description |
|---|---|---|
POST /cheat_disable |
{} |
Lock cheats off. |
POST /cheat_disable_lawsuits |
{} |
Disable lawsuits entirely. |
POST /cheat_merger_info |
{} |
Reveal hidden merger info. |
POST /cheat_earnings_info |
{} |
Reveal earnings forecasts and hidden financials. |
POST /cheat_add_cash |
{} |
Add a fixed amount of cash. (No "set cash to N" - this is fixed.) |
Modals
| Endpoint | Body | Description |
|---|---|---|
POST /close_modal |
{} |
Close the current modal. |
POST /modal_result |
{ answer } or { str } |
Submit the player's modal response. Use answer for numeric / choice modals; str for text-entry modals.
|
Tutorial
| Endpoint | Body | Description |
|---|---|---|
POST /set_tutorial_step |
{ id } |
Jump tutorial to a step. id = step number.
|
POST /set_tutorial_enabled |
{ id } |
Enable / disable tutorial. id = 1 on, 0 off.
|
Price alerts
| Endpoint | Body | Description |
|---|---|---|
POST /show_price_alerts |
{} |
Open the alerts management dialog. |
POST /create_price_alert |
{ str } |
direction|targetPrice"; direction is "up" or "down".
|
POST /delete_price_alert |
{ id } |
Delete one alert. id = alert slot index.
|
CustomData
| Endpoint | Body | Description |
|---|---|---|
POST /set_custom_data |
JSON object | Shallow-merge mod data into customData. See §CustomData.
|
gameState reference
GET /gamestate returns one JSON object - ~90 top-level fields - that is the complete read model. In a frontend mod, subscribe to only the fields you need so you only re-render on those:
const cash = useGameStore(s => s.gameState.cash); const year = useGameStore(s => s.gameState.currentYear);
| Group | Fields |
|---|---|
| Player | cash, otherAssets, totalAssets, totalDebt, netWorth, playerId, playerName, chairedCompanyId
|
| Clock / session | currentYear, currentQuarter, currentMonth, currentDay, currentTime, nextEarningsDate, gameLoaded, gameOver, readyToRestart, isTickerRunning, tickSpeed, splashScreenPlayed
|
| Active selection | activeEntityNum, activeEntityName, activeEntitySymbol, activeIndustryNum, activeIndustryId, actingAsId, actingAsName, actingAsSymbol, actingAsIndustryId, actingAs
|
| Modal state | modalType, modalText, modalTitle, modalDefault, modalFilter
|
| Collections | allCompanies, allPlayers, allSecurities, allIndustries, controlledCompanies, tickerQueue, streamingQuotesList, newsHeadlines, trendingNews, navHistory, navPointerIndex
|
| Active entity detail | activeEntityData, activeEntityFinancials (deep: cash/assets/debt/equity/EPS/cash-flow/…), activeEntityPlayerFinancials, financialProfile, cashflowProjection, portfolio, portHoldings, swapsPortfolio, optionsList, commodityList, shareholdersList, researchReport, advisorySummary, earningsReport, advances, loansReport, myCorporationsReport
|
| Market reports | whoOwns*Report (futures, physical commodities, options, swaps, stocks, investment contracts), industrySummaryReport, industryProjectionReport, industryGrowthRatesReport, mostMarketShareReport, mostTaxLossReport, mostCashReport, mostMarketCapReport, economicDataReport, interestRatesReport, whosAheadReport
|
| Settings | suppEarnSetting, suppWarnSetting, supPopupSetting, autosaveSetting, exerciseItSetting, sweepSetting, makeDeliverySetting, takeDeliverySetting, tooltipsSetting, shareholderGraphSetting, unethicalSetting, disableHotkeysSetting, chartType, locale
|
| Tutorial / misc | tutorialEnabled, tutorialStep, alerts, frozenAllLoans, dlrSign, euro, customData (§CustomData)
|
The exact shape can change between versions - the authoritative move is to GET /gamestate from a running game. Ctrl+Shift+I opens DevTools; gameStore.getState().gameState in the console dumps the live object.
CustomData - your persistent storage
customData is a free-form JSON object that rides along inside the save file. The engine never reads it for simulation - it stores it verbatim, writes it into the .WSR/.DAT save, restores it on load, and exposes it on every gameState as gameState.customData.
This is the supported way for a mod to persist its own state per-save.
Read
const customData = useGameStore(s => s.gameState?.customData ?? {});
const myStuff = customData.myModKey;
Write
import { setCustomData } from '../api.js';
await setCustomData({ myModKey: { highScore: 42, notes: 'hello' } });
Semantics
- Shallow merge.
POST /set_custom_datamerges into the existingcustomDataat the top level - incoming top-level keys overwrite, others are preserved. Namespace your data under one top-level key (e.g.myModKey) and write that whole sub-object each time. - Round-trips. New value comes back on the next
gameStatebroadcast. - Inert. Cannot change cash, prices, or any engine value - only action endpoints can (§REST API).
- Per-save. Saved/loaded with the game; a fresh New Game starts empty.
Reserved top-level keys
The base UI already stores a few things in customData. Don't reuse these keys:
| Key | Used by | Contents |
|---|---|---|
notes |
Notes modal | free-text notes |
navHistory |
Navigation system | saved nav history |
| (DB search criteria) | Database Search view | saved filters / sort |
| (holdings prefs) | Portfolio Holdings table | saved filters / sort / hide-subsidiaries |
Pick a unique key for your mod (e.g. customData["mymod:state"]).
Limits - what you can't do
- No raw memory / pointer writes. Only curated endpoints. The only free-form write is
customData, and it's inert. - No intercepting the update loop. WS delivers a snapshot. Read and react; you cannot mutate values mid-update or inject into serialization.
- No new engine behavior without recompiling. New mechanic / event type / endpoint = edit PowerBASIC source (
src/main/wsr/*.inc, ~178K lines), rebuildwsr.exe(PB 9.80) and usuallyui.dll(VS C++). That's engine development, not modding. - No editing binary game data. Saves and most
.DAT/.PRMare proprietary binary or encoded.CORPNAME.DATis the one safe exception.
Workflow
Edit / test
- Edit a file under
js/,css/,assets/, orCORPNAME.DAT. - Restart the game (no hot reload).
Ctrl+Shift+Ifor DevTools - console errors, thegameStore, network tab against the REST API.
Packaging for others
A mod is a set of replacement files. Simplest distribution: a zip mirroring the resources/app/ structure:
my-dark-theme/ ├── css/variables.css └── css/theme.css
Install instructions: "drop these into resources/app/, overwriting."
Good practice:
- Keep mods additive where possible - new component files wired in with one import line, not rewrites of core files. Two additive mods are less likely to collide.
- Document which base files you replace.
- Tell users that Steam's "verify integrity of game files" reverts changes (their uninstall path) and to back up originals.
Steam Workshop
WSR ships with Steam Workshop support for sharing mods. Subscribed Workshop items are downloaded by Steam, copied into a per-user staging directory at startup, and overlaid on top of the install directory at the file-protocol layer. The install directory is never modified - Steam's "verify integrity of game files" stays a no-op for Workshop content, and mods survive a full reinstall.
How it works for players
- Open the game's Workshop page in Steam, subscribe to a mod.
- Steam downloads the mod content to its UGC folder.
- Next time you launch Wall Street Raider, the game mirrors that folder into
%LOCALAPPDATA%\Wall Street Raider\workshop\installed\<id>\and builds a merged overlay at%LOCALAPPDATA%\Wall Street Raider\workshop\overlay\. - When the renderer asks for a file (
js/locale/de.js,css/variables.css, etc.), Electron's file-protocol interceptor checks the overlay first and falls through to the install dir if the file isn't there.
In-game: Main Menu → Workshop opens the subscribed-mods list with an unsubscribe button per item and a "Browse Workshop in Steam" button that launches the Steam overlay on the WSR Workshop page.
What can be in a Workshop mod
A Workshop mod is a zip of replacement files mirroring the resources/app/ tree. Everything listed earlier in this guide is fair game:
css/variables.css,css/theme.css,css/components/*.css(themes)js/locale/<code>.jsplus an updatedjs/locale/localeManager.js(locales)js/data/hints/*.js(contextual hints)assets/*.png/*.jpg/*.mp4/*.ico(asset reskins, same filenames)assets/help/wsrbook.htm(rewritten manual)CORPNAME.DAT(renamed companies / tickers)- New Preact component files plus a one-line import in
js/app.jsor a parent component (additive UI mods are the cleanest way to avoid collisions with other mods)
You cannot mod through Workshop:
wsr.exeorui.dll(binaries, blocked from the overlay for safety)- Engine simulation rules (engine doesn't read mods)
- Save format
Conflict resolution
If two subscribed mods both ship a css/variables.css, last-loaded wins and a warning is logged to wsr-stdio.log listing the conflict. There is no in-game enable/disable toggle in v1 - if two mods collide and you don't like the result, unsubscribe from the one whose changes you don't want.
Publishing a mod
Use the WSR Mod Uploader - a small companion app downloadable from the wiki download page (TODO: link once the tool ships). The uploader is a thin GUI around ISteamUGC::CreateItem / SubmitItemUpdate. Workflow:
- Build your mod folder - the
resources/app/-shaped tree of files you want to ship. - Pick a preview image (PNG or JPG, under 1 MB - Workshop limit).
- Fill in title, description, tags, visibility, changelog. Click Publish.
- The uploader stores the resulting
PublishedFileId_tin a.wsrmod-idfile inside the mod folder; subsequent re-publishes from the same folder update the existing Workshop item rather than creating a duplicate.
You can also use SteamCMD directly if you prefer scripting; the same metadata is required.
Standard tags
The Wall Street Raider Workshop uses these top-level tags:
| Tag | What it means |
|---|---|
Theme |
CSS theme / re-skin (palette, fonts, layout tweaks) |
Locale |
Translation pack (full or partial) |
Hints |
New / rewritten contextual hints |
Assets |
Replacement images, videos, icons |
Companies |
Alternate company-name database (CORPNAME.DAT)
|
UI Extension |
New Preact components or feature additions |
Other |
Anything that doesn't fit the categories above |
Pick at least one. Multiple tags are fine when a mod spans categories (e.g. a "1929 era" mod might tag Theme + Companies + Assets).
For the WSR maintainer: Steamworks partner-site setup
One-time configuration on partner.steamgames.com:
- App Admin → Workshop: enable for AppID
4080310(and3525620if you want the playtest depot to host Workshop too). - Workshop type: choose "Ready-To-Use Items". (Items are subscribed and Steam auto-downloads them. The alternative, "Generic", makes the game responsible for download orchestration; we don't want that.)
- Tags: define the top-level tags listed above. Add new ones as the community grows.
- Legal agreement: paste the Workshop Submission Agreement text below. Players acknowledge it on their first item submission. Steam stores the acknowledgement per-user per-app.
- Visibility rules: default - only owners can subscribe. Loosen later if Workshop adoption stays slow.
- Publish the configuration. Allow 1-2 hours for propagation.
Workshop Submission Agreement (copy verbatim into the partner site)
Wall Street Raider Workshop Submission Agreement
By submitting an item ("Submission") to the Wall Street Raider Steam Workshop, you confirm and agree that:
- Ownership / Originality. You are the sole author of the Submission, or have obtained all rights necessary to publish and license it. You will not submit content that infringes any third party's copyright, trademark, patent, trade secret, privacy, or publicity rights.
- License to Hackjack Games and Roninsoft. You grant Hackjack Games and Roninsoft (collectively, the "Publisher") and other Wall Street Raider players a perpetual, worldwide, non-exclusive, royalty-free license to use, reproduce, modify, publish, distribute, publicly display, and create derivative works of the Submission for the purpose of operating, promoting, and improving Wall Street Raider and the Workshop.
- License to other Workshop users. You grant other Steam users a perpetual, worldwide, non-exclusive, royalty-free license to download, install, use, and modify the Submission for personal, non-commercial use in Wall Street Raider.
- Content standards. Submissions must not contain unlawful, obscene, hateful, harassing, malicious, or otherwise objectionable content. Submissions must not include executable binaries that replace
wsr.exeorui.dll, malware, tracking code, or any mechanism that exfiltrates user data.- Steam Subscriber Agreement. Your Submission is also governed by the Steam Subscriber Agreement and the Steam Workshop Terms.
- Removal. The Publisher and Valve reserve the right to remove any Submission at any time for any reason, with or without notice. You may unpublish your own Submission at any time; existing subscribers may retain installed copies.
- No compensation. Submissions are unpaid. You are not entitled to any compensation, revenue share, or attribution beyond your Workshop author byline.
- Indemnification. You agree to indemnify and hold harmless the Publisher and Valve from any claims arising from your Submission or your breach of this Agreement.
By clicking "Submit," you accept this Agreement.
Troubleshooting
- Subscribed mod isn't visible. Re-launch the game - mod changes apply at startup only.
- Workshop menu says "not available." The game must be launched via Steam. Workshop calls require a logged-in Steam client.
- Mod's files aren't loading. Check
%LOCALAPPDATA%\Wall Street Raider\workshop\manifest.jsonto see which mods were detected and which file paths the overlay owns. If your mod isn't in themodslist, the loader couldn't see its install folder (rare - usually means the Steam content download is still in progress; wait and re-launch). - Two mods conflict. Check the same
manifest.jsonfor theconflictsarray. Last-wins means whichever mod sorted later in the loader's iteration order replaced the conflicting file. Unsubscribe from one of the two.