fix: owned items showed a price because they were served as base avatar items

defaultbaseavataritems returned the first 100 catalog items. The 2023
client flags every row of that list as a custom-clothing template
(AvatarItemData.IsBaseAvatarItem, keyed by AvatarItemId) and skips
templates when it rebuilds the owned outfit-menu entries, so those 100
ordinary items (80sHipHopJacket, Beard Close, ...) showed a price even
when owned and equipped, while the cart (which checks the owned set)
said "owned".

- defaultbaseavataritems returns only the UGC templates, UGCTee_Shirt
  (AvatarItemId 2184) and UGCPulloverHoodie_Shirt (2918), as the real
  service served them
- those two keep the same ids in every list and carry IsBaseAvatarItem
- they're no longer sold (existing shop rows are removed on seed) or
  listed as owned clothing
This commit is contained in:
Alexa
2026-10-15 22:07:00 +03:00
parent ee7d614262
commit 7f507702b9
4 changed files with 79 additions and 9 deletions
+2 -1
View File
@@ -217,6 +217,7 @@ verified against the binary before the change, and the full test suite passes.
| `GET api/avatar/v4/items` (colored variants) | a colorway reused its base item's AvatarItemId, so owning a base + colorways collided in the id-keyed wardrobe | id hashed from the full descriptor |
| `GET api/avatar/v4/items` / `defaultunlocked` / `lockeditems` (default items) | the client REPLACES its unlocked set with each downloaded list (OutfitManager.GFEIDFKKNMO clears field +0x70) and never merges its own defaults, so the free `default_unlocked_variants` items (e.g. Beard Close) showed a price | every list includes the 239 default items; `defaultunlocked` serves them before login; `lockeditems` never reports them |
| `GET api/avatar/v4/items` / `defaultunlocked` / `lockeditems` (metadata) | names came from the catalog dump (`(Shirt_Winter)` for colour variants), rarity was always 0 and tooltips empty, so 1,864 items disagreed with their shop tile | each owned row takes FriendlyName / Tooltip / Rarity from its shop tile (`StoreService.LoadAvatarItemStoreInfoAsync`), matched on the exact AvatarItemDesc |
| `GET api/avatar/v1/defaultbaseavataritems` | returned the first 100 catalog items; the client flags every row as a custom-clothing template (`AvatarItemData.IsBaseAvatarItem`, keyed by AvatarItemId) and skips templates in the owned rebuild (`OutfitManager.MBPOFMEHDIJ`), so those 100 ordinary items (80sHipHopJacket, Beard Close, ...) showed a price even when owned and equipped | only the UGC templates `UGCTee_Shirt` (AvatarItemId 2184) and `UGCPulloverHoodie_Shirt` (2918), `IsBaseAvatarItem: true`; they're no longer sold or listed as owned clothing |
| `GET api/customAvatarItems/v2/fromCreator/{id}` | bare array; client reads {Results,TotalResults} | paged container |
| `POST econ/customAvatarItems/v1/{id}/purchase` | 404 — route only existed under api/ | econ/ alias added |
| `GET api/ugcPurchasables/v1/items/room/{roomId}` | 404 — only the ?roomId= form existed | path form added |
@@ -1349,7 +1350,7 @@ Verified all 37 real client HTTP calls against the DorkNet server source (every
| Verb | Route | Request | Response |
|---|---|---|---|
| GET | `api/avatar/v1/defaultbaseavataritems` | none | JSON array of the same UnlockedAvatarItem shape as v4/items |
| GET | `api/avatar/v1/defaultbaseavataritems` | none | JSON array of the same UnlockedAvatarItem shape as v4/items: only the custom-clothing templates (UGCTee_Shirt 2184, UGCPulloverHoodie_Shirt 2918) with `IsBaseAvatarItem: true`. Every row becomes a template client-side, so never list ordinary clothing here |
| GET | `api/avatar/v1/defaultunlocked` | none | Same array shape as v4/items (server aliases it to the same handler; logged out it returns the `default_unlocked_variants` items, logged in the full owned set including them) |
| GET | `api/avatar/v1/lockeditems` | Query: repeated key "desc" carrying avatar-item desc strings (list added via helper 0x181F2E250 with key literal "desc") | JSON array of List<IFNGBPJGINL> — the same element as v4/items (CLIANCNHNIE reader: AvatarItemType, AvatarItemDesc, FriendlyName, Tooltip, Rarity, TagList, AvatarItemId, IsBaseAvatarItem) for each requested desc the caller does NOT own |
| GET | `api/avatar/v2` | none | Object with keys (exact literals in reader EKHJKDNHOPB, case-tolerant Pascal/camel/lower): OutfitSelections(string), OutfitSelectionsV2(string), FaceFeatures(string), SkinColor(string), HairColor(string), CustomAvatarIte |