Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

PropertyValue
Extension(s).utm
Magic SignatureUTM / V3.2
TypeMerchant Blueprint
Rust ReferenceView 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.

CategoryCoversRepresentative fields
Core IdentityThe shop’s name and tagTag, LocName
Economic MetricsPrice scaling when buying or selling, plus basic shop rulesMarkUp, MarkDown, BuySellFlag
Store InventoryThe items in stock, including rules for infinite restockingItemList

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

FunctionSizeBehavior
LoadStore1341 BThe primary parser that pulls the merchant’s basic identity, economic constraints (MarkUp/MarkDown), and buying capabilities.
ItemList ReadIterates through the list of store stock, actively pulling either explicitly saved item instances or generating them freshly from templates (InventoryRes).
AddItemToInventoryPushes the fully sorted loot stack into the physical storefront container so the player can actually interact with and purchase them.

Core Structural Findings

Engine RuleRuntime Behavior
Cost SortingWhen 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 EconomicsThe 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 FlagsBuySellFlag 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 FallbackUnlike 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 StackingIf 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 InventoryOn 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 DefaultsTag 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’tInfinite 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 TypeExplanation
Legacy Interface ConfigurationsSome 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.

  1. UTM-001 (Legacy Grid Coordinates): Informs when inventory items contain non-zero Repos_PosX or Repos_PosY; the engine builds its shop UI dynamically and ignores these coordinates.
  2. UTM-002 (Unknown Buy/Sell Flags): Warns when BuySellFlag has bits set outside the canonical buy (bit 0) and sell (bit 1) toggles.
  3. UTM-003 (Legacy Store UI Fallback): Warns when BuySellFlag == 0 (missing or empty); the engine falls back to legacy UI behaviors and forcefully clamps MarkUp to 100.

Phase 2 (resource existence, requires LintContext)

Implemented under rakata_lint::rules::utm_range.

  1. UTM-004 (Resref Existence): Warns when OnOpenStore (.ncs) or any ItemList[i].InventoryRes (.uti) does not resolve in the configured resource sources. The toolset-only top-level ResRef (merchant template) is intentionally skipped – it is never read by the engine.