Releases: iDevelopThings/bdo-data-extractor
Release list
v0.1.9
⚠ Breaking (output changed — re-extract)
- Post-market-limit
ItemUnknownsuse their corrected offsets (79,80,81,
85,89,93,97,101).unknownFooter6is removed because the current
footer ends at the crystal-group word. The Go-only raw property field is renamed
fromUnknownAfterMarketLimitSlotstoUnknownAfterMarketLimitProperties.
Fixed
- Item parsing supports the expanded 70-byte post-icon property array and six-byte
record footer used by the current client.
Full Changelog: v0.1.8...v0.1.9
v0.1.8
Fixed
- Extraction failed on items from the latest game update. Item parsing now accepts the fourth
member of the post-icon enum array (a four-byte front-packed array followed by 430x77
sentinels); treating it as three members misplaced the following flags and made the build fail
on newer items.
Added
worldmap-files [outDir]decodes the world-map pyramid to ordinary WebP tiles for external
tools, without the viewer-specifictiles.packcontainer.
Full Changelog: v0.1.7...v0.1.8
v0.1.7
Adds gear reform chains and the data behind Dawn crystal sockets, and decodes two more item
fields that were previously unknown. Recipe ingredient counts are fixed, and icon redirects are
now keyed by urn — both change the output, so re-extract.
⚠ Breaking (output changed — re-extract)
asset_redirects.jsonis keyed by urn, not by file path. Keys change from source-relative
paths (icons/123.webp,knowledge_icons/45.webp) to urns (urn::item:123,
urn::knowledge:entry:45). The values — the files on disk — are unchanged, so only lookups
break:asset_redirects["icons/123.webp"]must becomeasset_redirects["urn::item:123"].
Territories now emit redirects too (IconLargeonly).unknownAfterMarketLimit1andunknownAfterMarketLimit5are gone fromItemUnknowns. The
same bytes are now decoded asenhancementGroupKey(uint32) andenhancementType
(ItemEnhancementType) on the item itself. Byte offsets and wire values are identical — this
is a reinterpretation, not a data change — but the field names moved.- Recipe
inputschanged shape. Ingredients are now summed per item rather than listed per
XML occurrence, so arrays get shorter for any recipe that repeated an ingredient, and those
repeats now carry a real count instead of omitting it (see Fixed). IconCodecVersion2 → 3, forcing a full icon re-extraction so the urn-keyed redirects
regenerate.
Added
- Gear reform chains —
itemimprovement.dbssis now decoded into a newitem_improvements.json
(result,bases, and the rawflag, whose meaning is still unidentified). 3,736 reform rows
plus 87 item-only rows in live data. Items participating in a chain gainreformsFrom/
reformsInto, so a chain can be walked in either direction without loading the improvements
table. New public typesItemImprovements/ItemImprovement. - Dawn crystal-slot eligibility — new
ItemEnhancementTypeenum (30 values) with
UnlocksDawnCrystalSlot(), true only for Kharazad and Preonne accessories, surfaced on items as
unlocksDawnCrystalSlot. CrystalPresetSlot(24 values) describing the transfusion-preset socket layout, with
Title/Kind/CrystalGroup/SpecialSlot/DawnEquipSlot. Note this is a UI layout
description rather than decoded wire data — it has no Go consumer and exists to be used from the
generated TypeScript.texturescommand and exportedpipeline.TextureExtract(substr, outDir)— decodes matching
.ddsto PNG mirroring the archive layout, with optional uniform-grid atlas slicing
(--slice-cell,--slice-origin,--slice-grid,--slice-names). A reverse-engineering tool:
it isn't a build stage and produces nodata/*.json.Item.HasStatIds(...)(AND acrossEffects.Stats, ignoring hidden) and
Effects.AllStatGroups().
Fixed
- Recipe ingredient counts. A recipe that used the same item several times emitted one entry
per occurrence withcount: 0— and sincecountisomitempty, the key vanished entirely,
leaving consumers with no quantity at all. Ingredients parsed from the recipe XML now appear
once per distinct item, with the occurrences summed into a real count. (The synthetic imperial
delivery-box recipes still carry no count — they're generated to break a cycle in the game's own
data, not parsed from a recipe.) - Stale icon redirects are pruned again. Ownership of a redirect entry moved from its key to
its value when keys became urns, but the prune still tested the key — so it silently stopped
matching and entries for removed items accumulated forever. Pruning now runs for every
contributing source before any re-adds, since the source directories nest. - Builds hard-fail if a reform row references an unknown item id, rather than emitting a dangling
reference.
Changed
crystal_special_slots.ymlanditem_slots.ymlnow emit TypeScript alongside Go, so downstream
TS consumers get these generated rather than hand-maintained.- New
EXTENDING.md— an end-to-end guide to wiring up a new data source, and where the "the
JSON output is a public contract" rule is now written down.FORMATS.mddocuments the new item
footer fields and the reform-chain record layout.
Full Changelog: v0.1.6...v0.1.7
v0.1.6
Mainly a refactor, performance, and stability pass on the extract pipeline — shared BSS
helpers, parallel I/O, clearer build staging, and tougher fail behavior when tables are missing.
Re-extract after upgrading if you want the loc-string policy changes below.
Changed (output changed — re-extract)
- Loc strings keep Pearl Abyss
<PA…>markup (<PAColor0x…>/<PAOldColor>) instead of
stripping it, so consumers can style colored spans. - Buff loc blobs are stored in full (no first-line truncation); effect parsing still works via
line-awareParseStatFromLoc. - GameStrings consolidated onto shared text types (
Text,ItemText,KnowledgeCardText,
EntityText,TerritoryText) instead of parallel name/desc maps.
Changed
- Optional missing tables fail consistently instead of silently skipping.
- Manufacturing / PABR open misuse fixed (
PABRCountfor BKD/RID sidecars). - Dead
schema/path and thetablesubcommand removed — one decode path. - README / FORMATS updated for the CLI and perf-testing notes.
Performance
- Sibling PAZ file reads fan out in parallel (
readFiles+Indexsync.Once). - More table decoders on shared
IndexedRecords/RecordsFromEntries/RequireExhausted
(shops, character static, skilltype, enchant, mastery, zones, knowledge, and related). - Item enchant decode parallelized; world/items stages split into named helpers.
- Build progress reports
[stage]/[done](and finer[step]timings inside items); optional
--cpuprofilefor hot runs.
Full Changelog: v0.1.5...v0.1.6
v0.1.5
Adds five new datasets — adventure journals, quests, class skills, crystal transfusion rules and
lightstone combinations — plus NPC item-services and contribution-point item rentals joined onto
the existing files, and much more complete consumable/buff effect data. The build now writes its
output as an atomic, crash-recoverable transaction — which also makes a full extraction roughly
2.5× faster (~5s, down from ~14s). One JSON field changed shape: itemType is now numeric.
⚠ Breaking
Item.itemTypeis now a number, not a string, and is always emitted. Was
"itemType":"Skill"(omitempty); now"itemType":2. The Go field type changedstring→
model.ItemType(uint8). This is the only wire-breaking change — the largestat_id_enum.go
diff is gofmt whitespace only; no existingStatIdvalue changed.
Added
- Adventure journals — the Adventure Log tree (
adventure_journals.json): journal groups →
books → ordered quest pages, each page's quest, and every permanent Family-stat reward with
running totals. Quests referenced across the data now resolve to a newquests.jsoncarrying
each quest's accept/complete condition DSL verbatim. - Class skills (
class_skills.json) — per-class combat and awakening skill-tree grids, each
rank's identity and active/passive kind, and the decoded stat effects of passive ranks. - Crystal transfusion rules (
crystal_rules.json) — every transfusion group with its
max-equipped count, plus the special preset-slot restrictions. Each item'scrystalGroupnow
also carries its numerickey. - Lightstone combinations (
lightstone_combinations.json) — artifact/lightstone set bonuses
(name, description, required items, decoded effects) and the amplified-lightstone item-
equivalence alias map. - NPC item-services on
npcs.json— shops, secret shops, exchanges and contracts a character
offers (service name + client access-condition DSL); service-only NPCs are now included too. - Contribution-point item rentals on
items.json—buyItemByPointoffers (item, count,
point type and cost, condition DSL, dialogue text). - Fuller consumable/buff effect data — resolved effects now carry a canonical
statId(and
statIds), the buff's module/group, its stacking category (food / elixir / perfume / Cron
meal / …), duration, and whether it's instant; an effect set records which categories it clears
(e.g. a draught reset). This is the data a stat calculator needs to apply real in-game stacking. - New exported enums:
ItemType,FamilyStatType,SkillKind,CrystalSpecialSlot; new
StatIds (fear/heat/cold resistance, energy, beast AP, worker stamina, and more); and
LifeSkillType.ExpStat. New domain types for each dataset above; newEntityRefListhelpers
(Contains/AddUnique/Remove/IndexOf) and alightstone-combinationURN domain.
Fixed
- Cron-meal and composite-consumable effects were under-reported. v0.1.4 read only one of an
item's two skill slots, so consumables that spread their component buffs across the second slot
(Cron meals, some composite meals) lost part of their effect list. Both slots are now read and
merged, recovering the complete set.
Changed
- Build output is transactional. Stages decode, validate and register their artifacts first;
the dataset is published only after every stage succeeds. A failed or interrupted extraction no
longer partially overwrites the last good output — the next run rolls it back (or completes it
if it had already committed), and files the build doesn't own are left untouched. The output
directory gains a.build-outputs.jsonownership manifest. - Buff data is decoded more thoroughly — records are read through
buffoffset.dbsswith a
full field decode, buff modules resolve to canonicalStatIds instead of display strings, and
several previously-unmapped modules are now decoded (worker-stamina and energy recovery,
Breath/Strength/Health fitness EXP, death-penalty resistance). - Item records are decoded to @212 (a second skill-key slot), and
itemTypeis read as the
new numeric enum. Localization decoding gained three tables (skills, adventure journals,
lightstone sets). - The default JSON encoder is now
goccy/go-json(was the standard library); the encoder sits
behind an interface with standard and jettison alternatives available. - The TypeScript enum generator now emits an
<Enum>Namestype and anas constByNamemap. FORMATS.mdandREADME.mddocument all of the above (the five new datasets, the buff
offset-index rework, NPC item-services, rentals, and the item-type tooltip labels).
Performance
- A full extraction now finishes in ~5s, down from ~14s. Stages no longer each write their
JSON as they complete; all writing is deferred to the transactional publish at the end, where
the artifacts are encoded and written by a bounded pool of workers in parallel (with the two
bulk arrays,items.jsonanditem_enhancements.json, written on their own). Thegoccy/go-json
encoder swap contributes as well.
Full Changelog: v0.1.4...v0.1.5
v0.1.4
Adds item sets, playable classes with level/fitness progression, and life-skill progression,
plus a symbolic UI-string table. A large internal shift moves the game's enum-like fields —
grades, equip slots, classes, life skills, stats, effect functions — to typed enums generated
from YAML: a single documented source of truth, with compile-time type safety, shared with the
frontend as generated TypeScript. Many decoders were rewritten to consume and validate every
byte of a record, and now fail loudly on an unexpected layout instead of silently dropping rows.
⚠ Breaking
Item.gradeis now a number, not a string, and is always emitted. Was
"grade":"white"(omitempty); now"grade":0. Mapping: white 0, green 1, blue 2, yellow 3,
red 4, purple 5, none -1. The Go field type changedstring→model.ItemGrade(int8).EquipInfo.slot/slotsare now numbers, not strings."slot":"Main Weapon"→"slot":0;
slotsis now an array of numbers. Types changedstring/[]string→
model.SlotName/[]model.SlotName.slotalso lostomitempty, so Main Weapon (0) now
emits (it was silently dropped in 0.1.3 — see Fixed).WorldNode.flagremoved, replaced byenabled(bool, always emitted) and
unknown17(bool, omitempty). Anything readingworldNode.flagbreaks.src/model/dsl.goexported tables removed:EffectFuncs,EffectNamedFuncs,
EffectSectionMarker. They are superseded by the generatedEffectFuncStatenum. The JSON
effect.funcvalue is unchanged (still a string). NewEffectFuncToStatModscan now emit
severalStatMods from one DSL func, so effect arrays may be longer, andStatMod.note
wording changed (don't match on it).itemenchant/enchantstaticstatusunknown*keys renumbered. The item header/icon
layout was corrected (~+12 byte shift), so manyunknownNNNkeys initems.jsonand inside
enhancement.levels[]were added, removed, or repointed. These are debug/deviation fields
with low consumer impact, but the keys changed.paz_dirs.jsonshape changed (from theindexcommand): was
{interesting_dirs, dirs}, now a flat sorted[]stringof folder names.- Numeric enum fields serialize as bare integers (no
MarshalJSON):grade,slot,
slots, and the new class/life-skill enums.effect.funcand stat ids remain strings.
Added
- Item sets — new
item_sets.json(skillpiece.dbss), each with its N-piece bonus tiers
and localized bonus text. Every item gains anitemSetsref array, and each set lists its
memberitems. Membership is derived from effect-DSL markers/functions and an explicit
boss-gear list (Blackstar, Deboreka, Tungrad, Loggia, Geranoa, Manos/Preonne, boss armor, …). - Playable classes + progression — new
character_progression.json: the real playable
classes (name, gender, starter/preview weapons), per-class level rules (experience.bss,
incl. the level-60 AP and level-56 DP bonuses), and character fitness curves
(Breath/Strength/Health, fromfitnesslevel.dbss). - Life-skill progression — the game's life-skill XP tables are now processed: the max level
and full experience-per-level curve for each of the 15 life-skill types (lifeexp.dbss),
exposed aslife_skill_progression.json. - Symbolic UI strings — new
lua-stringsCLI command →lua_strings_<lang>.json, decoding
stringtable.bss(~48kPAGetStringkeys) resolved through loc. Not part of the default
build. - New exported enum types (generated, package
model):ItemGrade,SlotName,
CharacterClassType,LifeSkillType,LifeSkillGrade,EffectFuncStat(313 DSL funcs),
StatId(158 canonical stat ids). Each carries typed metadata andFromWire/Parse/Info
helpers. - New domain types:
ItemSet/ItemSetBonus,CharacterClass/CharacterProgression,
LifeSkillProgression, and richer enchant detail onEnchantLevel(CombatStats,
SpeciesAP,EnhancementAids,CaphrasMinLevel/MaxLevel). Newurn.ItemSetdomain and
ItemSetRefList. indexcommand filters:-ignore-exts,-only-exts,-only-dirs.- A YAML-driven enum generator (
cmd/enum_codegen,go generate ./src/model) — a large
shift in how these values are maintained. The enum-like fields above are now defined once in
src/model/enums/*.yml, self-documenting with per-value metadata, and generated into typed Go
enums (compile-time safety, replacing the old stringly-typed lookup maps) plus matching
TypeScript, so the backend and the viewer's frontend share one source of truth instead of
hand-maintaining parallel lists.
Fixed
- Caphras
MaxMPvalues were wrong — the last Caphras stat column was decoded as a
floating-point value in 0.1.3 but is actually an integer, so per-step MaxMP bonuses came out
garbled. Now read correctly. - More buff-stat translations — buff stats that previously came through untranslated (mount
HP, horse recovery, stamina/contribution/health EXP, death-penalty resistance, karma, …) now
carry English labels.
Changed
- Decoders now fail loudly on an unexpected layout. The item, enchant-curve, caphras,
region-info and exploration decoders (and the caphras build stage) moved from silently
skipping a bad row / returning a nil no-op to returning an error that aborts the whole build.
This is safer against shipping a partial or empty table, but a game patch that changes a table
layout will now block extraction until the decoder is updated, rather than dropping a row. - The item decoder was partially rewritten for stability and to capture more fields — the
fixed header now extends to @208 with the post-icon block correctly mapped, extra typed fields
are surfaced, and a malformed record is rejected rather than mis-read. - Enhancement (enchant-curve) decoding was largely rewritten, now fully decoding each curve's
per-lane melee/ranged/magic combat stats, its species-AP table, and a validated footer, rather
than reading a partial fixed skeleton. - Caphras enhancement decoding was rewritten with strict validation — totals must increase
monotonically, stat columns are re-typed to match the game, and NaN/negative stats are rejected
(see Fixed for the MaxMP correction). regioninfowas rewritten as a full sequential decode that consumes and validates every byte,
surfacing many previously-unknown region fields.- More generally, these decoders now consume and validate every byte of a record instead of
reading a fixed skeleton and trusting the rest. - UTF-16 decoding now handles surrogate pairs correctly (lone surrogates → U+FFFD).
- Unknown enum bytes now surface as
UnknownNinstead of an empty string. - New offset-index parser
ParseU8OneBasedOffsetIndex, and cursor helpers (AllZero,Zero,
Repeated) used to consume and assert reserved spans. - New dependencies:
gopkg.in/yaml.v3(enum specs),golang.org/x/tools(codegen struct
columns),github.com/klauspost/compress(loc zlib),github.com/goccy/go-json. FORMATS.mdgained large new sections (item sets, classes, fitness, life skills, the string
table, and the rewrittenitemenchant/enchantstaticstatus/regioninfolayouts);README.md
documents thelua-stringscommand and new outputs.
Performance
- Faster localization decode at startup — the loc tables now decompress via
klauspost/compressand pre-size their buffers, reducing allocations and GC pressure on boot. - Item decoding no longer holds onto the source archive buffer: each record's preserved
variable-length tail is copied into one compact arena, so decoded items don't pin the whole
decompressed table in memory. - Safer file reads — decompressed bodies are read to their declared size with explicit
EOF/overrun checks instead of an unbounded read-all.
Full Changelog: v0.1.3...v0.1.4
v0.1.3
The world map becomes a real zoomable tile pyramid, world nodes gain their full graph
(positions, links, CP costs, managers, worker products), NPCs gain client role flags, and item
acquisition resolves to entity refs instead of loose strings. Icons are now WebP.
⚠ Breaking
model.Regionis gone, anddata/regions.jsonis no longer written. Region bounds and
spawns moved ontoWorldRegioninworld.json, asBounds *Bounds(bounds) and
Spawns []Spawn(spawns). Anything readingregions.jsonmust move toworld.json.- Icons are WebP, not PNG.
icons/,knowledge_icons/,icons/territories/and the
zone-category icons.KnowledgeEntry.imageandTerritory.iconLarge/iconSmallend in
.webp, and id-redirect paths areicons/<id>.webp. Region masks (regionmaps/) stay PNG. - The world map is a packed tile pyramid, not loose PNGs.
pipeline.WorldMap()no longer
writes a flatworldmap/<layer>/<x>_<y>.pnggrid; it writes
worldmap/<layer>/tiles.pack+meta.json. Reading it needs a pack reader. - Item acquisition changed type.
Item.Vendors:[]string→*models.EntityRefList[NPC],
andItem.GatherNodes:[]string→*models.EntityRefList[WorldNode]. The JSONvendors
andgatherNodesare now refs, not names. - Territory and region links became refs.
WorldRegion.TerritoryandWorldNode.Territory:
int→*models.EntityRef[Territory].NPCSpawn.Region:uint32→
*models.EntityRef[WorldRegion], with the raw key moved to a newRegionKey(regionKey). WorldNode.Kind:int→model.WorldNodeKind(numeric value preserved in JSON).pipeline.Configuregained aregionparameter:
Configure(gameDir, dataDir, lang, region string, pretty bool).pipeline.IconsFreshremoved — superseded by per-asset provenance tracking.
NeedsExtractionis unchanged.pipeline.Maps()no longer produces region masks — it now runs only the world map.
pipeline.RegionMaps()still does.Store[T].GetAll,ResolveUrnsandResolveUrnsIncompact: URNs that don't resolve are
dropped, so the result is not positionally parallel to the input. Use the newGetAllInto
when you need alignment.- Internal, for anyone vendoring:
tables.DecodeRegionsreturnsmap[uint32][]model.Spawn
instead of[]model.Region;bss.ParseU16OffsetIndexgained a leadingname string
parameter. IconCodecVersion1 → 2, newWorldMapCodecVersion1 — both force one full rebuild of the
assets they cover on the first run of this version.
Added
- World-map tile pyramid — slippy-style
z/x/yWebP tiles with ocean fill, packed into one
tiles.packper layer (BDOTILE1, documented inpipeline/tilepack.go), plus ameta.json
carryingtilePx,unitsPerPixel, zoom range, grid extent andoceanColor. The world layer
is cropped to the playable region;morninglandis its own layer. - World-node graph —
WorldNode.PositionandLinksnow come from
waypoint_binary/mapdata_realexplore2.bwp: real in-game map positions and 2,408 directed
edges. The old exploration.bss anchor is kept asExplorationPositionwhere it differs. New
Children(non-main neighbours of a main node). - Node managers —
WorldNode.Manager(owning NPC template) andManagerNode(affiliated
node → owner), resolved viacharacterfunction.dbss: 493 owners, 417 affiliates. New
TownRepresentative(town ruler NPC). - Worker production —
WorldNode.Products(*EntityRefList[Item]), joined
plantzone.dbss→plantexchangegroup.bss→itemsubgroup.dbss; 389 of 425 plant zones
resolve. Quantities and lucky bonuses are server-side and are not inferred. - More decoded node fields —
Main,Contribution(CP cost),Radius,Knowledge,
Special,ZoneIndex,ZoneCategory,GrindZone,GrindTier,NodeIndex,AreaID,
LinkedKey,SubKey,SubKey2,GroupHash,Flag. - NPC role flags — a new
characterspawntype.dbssdecoder fillsNPC.SpawnTypes: node
managers (Explorer), repairers, stable keepers, wharf managers, market directors and more.
Role-bearing NPCs thatnpcsimplyomits but loc knows about are synthesized intonpcs.json.
NewNPCSpawnType/NPCSpawnTypes(46 consts) withIsMapRole/Has/HasMapRole, and
NPC.HasSpawnType/NPC.HasMapRole. - Service-region support — new
--regionflag (e.g.--region na) andOptions.Region.
Region spawn data layers common →regionclientdata_<lang>_.xml→
regionclientdata_<region>_.xml, so NPC and monster placements match the region you play on.
Newpipeline.AvailableRegions(gameDir)lists what an install ships;manifest.jsonrecords
theregionused. - NPC titles are localized from loc table 6 (e.g.
<Fruit Merchant>) intoNPC.Title. - Unresolved acquisition text is preserved —
Item.UnresolvedVendorsand
Item.UnresolvedGatherNodeskeep the<shop>/<node region="…">names that don't resolve
to an entity, so nothing silently vanishes from an item's acquisition. - Zone cross-links —
zones.jsonrefs now carryurn: ecology → character, topography →
world region,node.urn→ world-map node (99 of 105 zones). - New
WorldNodeKind(16 consts) and ref constructorsTerritoryRef,NPCRef,
WorldRegionRef,WorldNodeRef,WorldNodeRefList,KnowledgeEntryRefList. - New
Store[T].GetAllInto(urns, out)(positional, allocation-free resolution into a
caller-owned slice) andStoreFor[T]()/StoreForIn[T](r)(fetch a store once instead of
paying a registry lookup per URN). EntityRefList[T].AllBulk()and.GetManyByIndex([]int)— bulk, memoized resolution paying
one registry lookup per call rather than one per element.- New
utils.IconExt(".webp") andutils.IconFileName(ddsPath). - New CLI command
index(dumpspaz_files.json/paz_dirs.json) andpipeline.Index(). NPCSpawn.DialogIndex;Zone'sRefgainsURN,NodeRefgainsNode.pipeline.Loc()additionally writes per-tablelocs/<table>.json.
Fixed
- PAZ archives are opened by the name the game actually uses. They ship uppercase
(PAD00001.PAZ) while the meta beside them is lowercase (pad00000.meta); we opened the
archives aspad%05d.paz, which only ever resolved because Windows ignores case. On a
case-sensitive filesystem every archive open failed, so nothing extracted — reported on Linux
by @OniHil, who confirmed the casing fix resolves it
(#2). Linux is still not a
tested target: it cross-compiles and the paths are now correct, but no full extraction has
been run there by the maintainer. - Region spawn layering now replaces a region wholesale instead of appending, so placements
that a later layer removes or moves are no longer retained from an earlier one. - A malformed row in a
*_offset.dbssindex (zero size, out of bounds, overlapping) is logged
and skipped rather than discarding the whole table — previously one bad row from a game patch
could silently emptymentalthemeorcharacterstatic. An index where no row is usable is
still a hard error. EntityRefList.AllBulk/GetManyByIndexno longer mis-align when a URN doesn't resolve: an
unresolvable entry stays nil in place instead of shifting later entries onto the wrong entity.- Still-ICE-encrypted DDS textures are detected by their missing
DDSmagic rather than
decode-fail-then-retry — deterministic, with no spurious decode failures. exploration.bssis decoded as a validated cursor walk over the full 117-byte record with
strict footer tiling. It warns when a byte range that was always zero starts carrying data (a
patch added a field), and when an unknown node kind appears.- The build fails if a node references a product item id missing from
items.json, or if a
node-manager template has no placement for the selected language/region; it warns when a
manager's nearest spawn is more than 128,000 units from its node.
Changed
- Icons are WebP at quality 50 — roughly 40% smaller than the previous PNGs at the sizes they
render. npcsimply.bss,exploration.bssand the waypoint table are each decoded once and
memoized on theBuilder, rather than per stage. Build stages reordered:worldbefore
npcs,territoriesfolded intoworld.- World-map build: each native DDS is decoded once, WebP-encoded at the finest level and
box-downsampled in memory for coarser levels (no write-then-read-back). Tiles stream into one
tiles.packper layer instead of tens of thousands of files, with ocean fill deduped to a
single stored blob. - New dependency
github.com/deepteams/webp v1.2.7;github.com/dgravesa/go-parallelpromoted
from indirect to direct. The README no longer claims the CLI is dependency-free, and the CLI
usage text now lists every command and the--regionflag. - New decoders
internal/tables/{characterspawntype,nodemanagers,plantproducts,waypoints}.go,
each with unit tests; new tests forbss/offset,tables/regions,build/world,
model/npc_spawn_type, and benchmarks for the ref-resolution paths. FORMATS.mddocuments the exploration.bss record layout,mapdata_realexplore2.bwp,
characterspawntype.dbss, the worker-product joins, and the newworld.json/npcs.json
shapes.
Full Changelog: v0.1.2...v0.1.3
v0.1.2
Data / output changes (viewer users re-extract on update)
- Set effect sections are split by piece count — "Set Effect (2-piece)", "(4-piece)", "(6-piece)", "(8-piece)" instead of every tier collapsing into one "Set Effect".
- ~933 more items get icons — name-only items with no stat record now backfill their icon from the archive's id-named icons.
- Territory icon fix — Valencia and The Great Ocean no longer share (and overwrite) one mark texture.
- World-map tiles get clean names —
worldmap/<layer>/<x>_<y>.png(world / pack / morningland) instead of the long sanitized paths. - Merged asset redirect table — item + knowledge icon aliases now live in one
asset_redirects.jsonat the data-dir root (wasicons/redirects.json).
Features / changes
- Icons re-decode only when they need to — an app-only update reuses the existing icons instead of re-decoding tens of thousands of textures; a game patch or an icon-code bump forces a rebuild (game-fingerprint + IconCodecVersion).
- Unified image-extraction pipeline — item / knowledge / territory / zone-category / region-map / world-map now run through one interface in a single pass over the archive index.
Under the hood
- Parallel DDS/JSON work via go-parallel; lower peak memory + faster builds.
- Fixed an output-path bug that wrote files a directory above the intended output.
- Image-pipeline refactor verified byte-identical against the previous output.
Full Changelog: v0.1.1...v0.1.2
v0.1.1
Extraction manifest & staleness API
RunAllnow writesmanifest.jsoninto the data dir —{ gameFingerprint, appVersion, lang, extractedAt }.GameFingerprint(gameDir)hashes the PAZ index (pad00000.meta) +ads_version, andNeedsExtraction(dataDir, gameDir, appVersion)reports when cached output is stale (game patched, or the embedding app updated). An unreadable game dir keeps existing data rather than forcing a rebuild.OptionsgainsAppVersion.
Memory
RunAllreleases theBuilderafter the build step and hands the heap back to the OS (FreeOSMemory) on the way out, so a long-running embedder no longer retains the extraction's hundreds of MB (item/enhancement maps, loc tables, the PAZ index).
Data corrections (output changed — consumers should re-extract)
- Item grades: grade 4 relabelled
orange→red, and grade 5purpleadded. - Variant grouping now keys on the equip slot too — an appearance/costume item that reuses a real piece's name + icon is no longer merged into that combat piece.
Other
RunAllruns build + icons + knowledge-icons only; the loc dump and region-map steps aren't consumed by current embedders.- Trimmed the CLI package doc header (subcommands/flags are in the README).
Full Changelog: v0.1.0...v0.1.1