UTM Format (Merchant Blueprint)
Description: The Merchant (.utm) blueprint natively handles the interactive storefront data for merchants and shops. Because shops strictly behave as container interfaces that dynamically buy, sell, and map economic value onto spawned .uti items, the structure of a .utm is highly compact, primarily consisting of economic markups and inventory sorting parameters.
At a Glance
| Property | Value |
|---|---|
| Extension(s) | .utm |
| Magic Signature | UTM / V3.2 |
| Type | Merchant Blueprint |
| Rust Reference | View rakata_generics::Utm in Rustdocs |
Data Model Structure
Rakata maps a Merchant into the rakata_generics::Utm struct. The struct’s Rustdocs document every field’s binary schema and GFF mapping; the table below is the high-level anatomy.
| Category | Covers | Representative fields |
|---|---|---|
| Core Identity | The shop’s name and tag | Tag, LocName |
| Economic Metrics | Price scaling when buying or selling, plus basic shop rules | MarkUp, MarkDown, BuySellFlag |
| Store Inventory | The items in stock, including rules for infinite restocking | ItemList |
rakata-lint validates these fields against the engine constraints documented below.
Engine Audits & Decompilation
Because .utm evaluating is structurally straightforward, the engine bypasses heavy memory allocations and maps fields in an incredibly fast iteration.
(Decompilation logic for this section was entirely audited and verified via native Ghidra pipeline against swkotor.exe, explicitly pulling from CSWSStore::LoadStore at 0x005c7180.)
Structural Load Phasing
| Function | Size | Behavior |
|---|---|---|
LoadStore | 1341 B | The primary parser that pulls the merchant’s basic identity, economic constraints (MarkUp/MarkDown), and buying capabilities. |
ItemList Read | – | Iterates through the list of store stock, actively pulling either explicitly saved item instances or generating them freshly from templates (InventoryRes). |
AddItemToInventory | – | Pushes the fully sorted loot stack into the physical storefront container so the player can actually interact with and purchase them. |
Core Structural Findings
| Engine Rule | Runtime Behavior |
|---|---|
| Cost Sorting | When building the store inventory, the engine actively sorts the merchant’s final stock from cheapest to most expensive by checking the cost of each item. This completely overrides whatever custom display order you try to dictate statically. |
| Dynamic Economics | The engine relies entirely on the MarkUp and MarkDown integers to control shop prices. These act as simple percentages that mathematically bump or slash the base cost of every item the merchant sells or buys. |
| Buy/Sell Bit Flags | BuySellFlag is split into basic toggles: bit 0 controls whether you are allowed to sell your gear to the merchant, and bit 1 controls whether the merchant will actually sell anything to you. |
| BuySellFlag Fallback | Unlike most of the merchant’s fields, BuySellFlag falls back to whatever value the store already holds when the field is missing, rather than resetting to a fixed literal. A freshly constructed store (never loaded from any file) starts at 3 – buy and sell both allowed. |
| Infinite Stacking | If an item is flagged as Infinite, the engine specifically locks that item in memory so that no matter how many times a player buys it, the shop never physically runs out of stock. |
| Save vs. Template Inventory | On the savegame load path, every ItemList entry is a fully self-contained item snapshot – the item’s complete field set, not a reference. InventoryRes is only consulted on the template/blueprint load path, where it names a .uti blueprint the engine expands via CSWSItem::LoadFromTemplate; that resref is never written back into a save. |
| Remaining Absent-Field Defaults | Tag and LocName are unconditional literal stamps (empty string, empty localized string) with no presence check consulted afterward. MarkUp and MarkDown are unconditional literal 0 – a missing markup/markdown is price-neutral, not an error state. OnOpenStore is an unconditional empty-resref default, the standard “no script” sentinel. Comment and ID are dead: neither field-name string exists anywhere in swkotor.exe, so LoadStore cannot read either under any circumstance – ID is dead more thoroughly than the earlier “deprecated” framing suggested, and Comment doesn’t even have the Repos_PosX/Repos_PosY-style “read but ignored” fate below, it’s simply never looked up. |
Infinite vs. Dropable: One Belongs to the Store Entry, One Doesn’t | Infinite is read directly off each ItemList entry with an unconditional literal 0 default, setting a bit the store loader owns exclusively – the underlying .uti item has no competing say in it. Dropable is different: LoadStore’s own ItemList loop never reads it at all. It’s entirely inherited from the item load chain instead. A freshly constructed item defaults droppable to true, but CSWSItem::LoadDataFromGff always re-reads Dropable with an unconditional literal 0 default, overwriting that true regardless of source. For inventory entries that resolve a linked .uti (via EquippedRes/InventoryRes), there’s a second, narrower read directly off the store entry afterward that CAN override the item’s own value – but only if the store entry supplies it; if absent, whatever the item’s own load already set (already false by that point, not the original constructor default) stands. On the pure template-store path, the ItemList entry is never asked for Dropable at all – it comes solely from the linked .uti. |
Legacy & Ignored Data
| Finding Type | Explanation |
|---|---|
| Legacy Interface Configurations | Some older tools expose positional values like Repos_PosX or Repos_PosY inherited from other Odyssey games, but the engine completely ignores them. The game physically builds its shop UI dynamically when you open it, rendering those grid coordinates totally useless. |
Implemented Linter Rules (Rakata-Lint)
Phase 1 (intra-resource, no context)
Implemented under rakata_lint::rules::utm.
- UTM-001 (Legacy Grid Coordinates): Informs when inventory items contain non-zero
Repos_PosXorRepos_PosY; the engine builds its shop UI dynamically and ignores these coordinates. - UTM-002 (Unknown Buy/Sell Flags): Warns when
BuySellFlaghas bits set outside the canonical buy (bit 0) and sell (bit 1) toggles. - UTM-003 (Legacy Store UI Fallback): Warns when
BuySellFlag == 0(missing or empty); the engine falls back to legacy UI behaviors and forcefully clampsMarkUpto 100.
Phase 2 (resource existence, requires LintContext)
Implemented under rakata_lint::rules::utm_range.
- UTM-004 (Resref Existence): Warns when
OnOpenStore(.ncs) or anyItemList[i].InventoryRes(.uti) does not resolve in the configured resource sources. The toolset-only top-levelResRef(merchant template) is intentionally skipped – it is never read by the engine.