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)
|
| 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.