ff9mapkit 1.0.0b3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. ff9mapkit/__init__.py +18 -0
  2. ff9mapkit/__main__.py +36 -0
  3. ff9mapkit/_animdb.py +2994 -0
  4. ff9mapkit/_animdb_all.py +14125 -0
  5. ff9mapkit/_fieldtable.py +1516 -0
  6. ff9mapkit/_fieldtext.py +845 -0
  7. ff9mapkit/_held_poses.py +44 -0
  8. ff9mapkit/_itemdb.py +65 -0
  9. ff9mapkit/_modeldb.py +725 -0
  10. ff9mapkit/_narrowmap_data.py +10 -0
  11. ff9mapkit/_npcparams.py +634 -0
  12. ff9mapkit/_regen_animdb.py +72 -0
  13. ff9mapkit/_regen_animdb_all.py +66 -0
  14. ff9mapkit/_regen_fieldtable.py +95 -0
  15. ff9mapkit/_regen_fieldtext.py +66 -0
  16. ff9mapkit/_regen_modeldb.py +67 -0
  17. ff9mapkit/_regen_npcparams.py +123 -0
  18. ff9mapkit/_regen_scenedb.py +57 -0
  19. ff9mapkit/_scenedb.py +869 -0
  20. ff9mapkit/abilities.py +225 -0
  21. ff9mapkit/animations.py +120 -0
  22. ff9mapkit/archetypes.py +218 -0
  23. ff9mapkit/areatitle.py +76 -0
  24. ff9mapkit/battle/__init__.py +21 -0
  25. ff9mapkit/battle/abilityfeatures.py +294 -0
  26. ff9mapkit/battle/actiondelta.py +441 -0
  27. ff9mapkit/battle/aiauthor.py +305 -0
  28. ff9mapkit/battle/ailint.py +140 -0
  29. ff9mapkit/battle/aipatch.py +175 -0
  30. ff9mapkit/battle/battleai.py +148 -0
  31. ff9mapkit/battle/battlecsv.py +390 -0
  32. ff9mapkit/battle/battlepatch.py +395 -0
  33. ff9mapkit/battle/build.py +558 -0
  34. ff9mapkit/battle/camera_codec.py +332 -0
  35. ff9mapkit/battle/camera_data.py +128 -0
  36. ff9mapkit/battle/characterdelta.py +789 -0
  37. ff9mapkit/battle/event_data.py +72 -0
  38. ff9mapkit/battle/extract.py +540 -0
  39. ff9mapkit/battle/fbx.py +223 -0
  40. ff9mapkit/battle/reskin.py +149 -0
  41. ff9mapkit/battle/scene_codec.py +314 -0
  42. ff9mapkit/battle/scene_data.py +369 -0
  43. ff9mapkit/battle/scenelint.py +125 -0
  44. ff9mapkit/battle/seqasm.py +131 -0
  45. ff9mapkit/battle/seqauthor.py +220 -0
  46. ff9mapkit/battle/seqcodec.py +300 -0
  47. ff9mapkit/battle/seqdis.py +106 -0
  48. ff9mapkit/battle/seqpatch.py +137 -0
  49. ff9mapkit/battle_bgm.py +133 -0
  50. ff9mapkit/binutils.py +60 -0
  51. ff9mapkit/build.py +5445 -0
  52. ff9mapkit/campaign.py +1276 -0
  53. ff9mapkit/catalog.py +316 -0
  54. ff9mapkit/chain.py +358 -0
  55. ff9mapkit/cli.py +3114 -0
  56. ff9mapkit/config.py +360 -0
  57. ff9mapkit/content/__init__.py +13 -0
  58. ff9mapkit/content/areatitle.py +36 -0
  59. ff9mapkit/content/ate.py +118 -0
  60. ff9mapkit/content/camera.py +123 -0
  61. ff9mapkit/content/chest.py +186 -0
  62. ff9mapkit/content/choice.py +163 -0
  63. ff9mapkit/content/conductor.py +217 -0
  64. ff9mapkit/content/cutscene.py +290 -0
  65. ff9mapkit/content/encounter.py +41 -0
  66. ff9mapkit/content/entry_settle.py +50 -0
  67. ff9mapkit/content/equipment.py +93 -0
  68. ff9mapkit/content/event.py +191 -0
  69. ff9mapkit/content/gateway.py +101 -0
  70. ff9mapkit/content/inventory.py +59 -0
  71. ff9mapkit/content/itemdata.py +644 -0
  72. ff9mapkit/content/itemtext.py +168 -0
  73. ff9mapkit/content/jump.py +114 -0
  74. ff9mapkit/content/ladder.py +633 -0
  75. ff9mapkit/content/movement.py +53 -0
  76. ff9mapkit/content/music.py +97 -0
  77. ff9mapkit/content/npc.py +348 -0
  78. ff9mapkit/content/object.py +340 -0
  79. ff9mapkit/content/onentry.py +135 -0
  80. ff9mapkit/content/party.py +111 -0
  81. ff9mapkit/content/pathfind.py +138 -0
  82. ff9mapkit/content/platform.py +314 -0
  83. ff9mapkit/content/player.py +168 -0
  84. ff9mapkit/content/prop.py +75 -0
  85. ff9mapkit/content/region.py +340 -0
  86. ff9mapkit/content/reinit.py +59 -0
  87. ff9mapkit/content/savepoint.py +90 -0
  88. ff9mapkit/content/shop.py +178 -0
  89. ff9mapkit/content/sps_trigger.py +66 -0
  90. ff9mapkit/content/startup.py +71 -0
  91. ff9mapkit/content/synthesis.py +106 -0
  92. ff9mapkit/content/text.py +183 -0
  93. ff9mapkit/content/textcarry.py +290 -0
  94. ff9mapkit/content/verbatim.py +86 -0
  95. ff9mapkit/content/walkmesh_hotfix.py +38 -0
  96. ff9mapkit/data/__init__.py +48 -0
  97. ff9mapkit/data/_regen_provenance.py +142 -0
  98. ff9mapkit/data/provenance/blank.es.patch +1 -0
  99. ff9mapkit/data/provenance/blank.fr.patch +1 -0
  100. ff9mapkit/data/provenance/blank.gr.patch +1 -0
  101. ff9mapkit/data/provenance/blank.it.patch +1 -0
  102. ff9mapkit/data/provenance/blank.jp.patch +1 -0
  103. ff9mapkit/data/provenance/blank.uk.patch +1 -0
  104. ff9mapkit/data/provenance/blank.us.patch +1 -0
  105. ff9mapkit/data/provenance/manifest.json +65 -0
  106. ff9mapkit/data/provenance/region_template.patch +1 -0
  107. ff9mapkit/data/reference_arcs.toml +89 -0
  108. ff9mapkit/data/region_catalog.toml +593 -0
  109. ff9mapkit/deploystack.py +358 -0
  110. ff9mapkit/dialogue.py +803 -0
  111. ff9mapkit/eb/__init__.py +12 -0
  112. ff9mapkit/eb/_exprtable.py +59 -0
  113. ff9mapkit/eb/_membertable.py +38 -0
  114. ff9mapkit/eb/_optables.py +537 -0
  115. ff9mapkit/eb/_regen_optables.py +76 -0
  116. ff9mapkit/eb/cmdasm.py +323 -0
  117. ff9mapkit/eb/disasm.py +332 -0
  118. ff9mapkit/eb/edit.py +439 -0
  119. ff9mapkit/eb/exprasm.py +158 -0
  120. ff9mapkit/eb/model.py +178 -0
  121. ff9mapkit/eb/opcodes.py +463 -0
  122. ff9mapkit/eblint.py +177 -0
  123. ff9mapkit/editor/__init__.py +20 -0
  124. ff9mapkit/editor/app.py +950 -0
  125. ff9mapkit/editor/battle_forms.py +240 -0
  126. ff9mapkit/editor/breadcrumb.py +89 -0
  127. ff9mapkit/editor/dialogs.py +116 -0
  128. ff9mapkit/editor/feedback.py +208 -0
  129. ff9mapkit/editor/forms.py +632 -0
  130. ff9mapkit/editor/graphview.py +350 -0
  131. ff9mapkit/editor/jobs.py +342 -0
  132. ff9mapkit/editor/model.py +243 -0
  133. ff9mapkit/editor/picker.py +120 -0
  134. ff9mapkit/editor/theme.py +212 -0
  135. ff9mapkit/eventscan.py +1441 -0
  136. ff9mapkit/extract.py +2279 -0
  137. ff9mapkit/flags.py +693 -0
  138. ff9mapkit/forkreport.py +1383 -0
  139. ff9mapkit/hub.py +477 -0
  140. ff9mapkit/idgated.py +101 -0
  141. ff9mapkit/infohub.py +580 -0
  142. ff9mapkit/items.py +63 -0
  143. ff9mapkit/itemstats.py +346 -0
  144. ff9mapkit/journey.py +1902 -0
  145. ff9mapkit/keyitems.py +93 -0
  146. ff9mapkit/logic_add.py +632 -0
  147. ff9mapkit/logic_edit.py +728 -0
  148. ff9mapkit/logic_map.py +526 -0
  149. ff9mapkit/pack.py +175 -0
  150. ff9mapkit/playerswap.py +231 -0
  151. ff9mapkit/prop_archetypes.py +228 -0
  152. ff9mapkit/provision.py +282 -0
  153. ff9mapkit/refarc.py +825 -0
  154. ff9mapkit/save.py +337 -0
  155. ff9mapkit/save_items.py +1673 -0
  156. ff9mapkit/scene/__init__.py +11 -0
  157. ff9mapkit/scene/arena.py +63 -0
  158. ff9mapkit/scene/bgart.py +140 -0
  159. ff9mapkit/scene/bgi.py +732 -0
  160. ff9mapkit/scene/bgs.py +174 -0
  161. ff9mapkit/scene/bgx.py +185 -0
  162. ff9mapkit/scene/cam.py +345 -0
  163. ff9mapkit/scene/guide.py +311 -0
  164. ff9mapkit/scene/paint.py +506 -0
  165. ff9mapkit/scene/placeholder.py +107 -0
  166. ff9mapkit/sjbinary.py +285 -0
  167. ff9mapkit/sps/__init__.py +17 -0
  168. ff9mapkit/sps/author.py +294 -0
  169. ff9mapkit/sps/catalog.py +88 -0
  170. ff9mapkit/sps/codec.py +264 -0
  171. ff9mapkit/sps/edit.py +184 -0
  172. ff9mapkit/sps/lint.py +58 -0
  173. ff9mapkit/sps/render.py +116 -0
  174. ff9mapkit/sps/templates.py +47 -0
  175. ff9mapkit/sps/texture.py +131 -0
  176. ff9mapkit/walkmesh_hotfixes.py +163 -0
  177. ff9mapkit/workspace/__init__.py +18 -0
  178. ff9mapkit/workspace/battledoc.py +985 -0
  179. ff9mapkit/workspace/builddoc.py +607 -0
  180. ff9mapkit/workspace/forms_qt.py +586 -0
  181. ff9mapkit/workspace/importdoc.py +665 -0
  182. ff9mapkit/workspace/mapview.py +131 -0
  183. ff9mapkit/workspace/palette.py +85 -0
  184. ff9mapkit/workspace/savedoc.py +664 -0
  185. ff9mapkit/workspace/shell.py +6907 -0
  186. ff9mapkit/workspace/style.py +105 -0
  187. ff9mapkit/workspace/tuningdialog.py +223 -0
  188. ff9mapkit-1.0.0b3.dist-info/METADATA +155 -0
  189. ff9mapkit-1.0.0b3.dist-info/RECORD +193 -0
  190. ff9mapkit-1.0.0b3.dist-info/WHEEL +5 -0
  191. ff9mapkit-1.0.0b3.dist-info/entry_points.txt +5 -0
  192. ff9mapkit-1.0.0b3.dist-info/licenses/LICENSE +31 -0
  193. ff9mapkit-1.0.0b3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,290 @@
1
+ """Faithful TEXT carry -- ship a donor field's referenced ``.mes`` text VERBATIM + remap the txids.
2
+
3
+ The object graft (:mod:`content.object`) and player-function graft (:mod:`content.player`) carry a real
4
+ field's NPCs/props + their interactions byte-for-byte. But a window those grafted bytes open
5
+ (``WindowSync``/``WindowAsync[Ex]``) names a donor ``.mes`` TXID -- and a fork ships its OWN ``.mes`` block
6
+ (authored lines from :func:`build.collect_text`, at a high band), so that donor id resolves to nothing: an
7
+ EMPTY window. This module closes that last gap: it CARRIES the donor's referenced field text (per language,
8
+ verbatim) and REMAPS each grafted window's TXID to a fresh band, so the forked interactions show the real
9
+ words. It is the faithful counterpart to ``import --dialogue`` (which appends EDITABLE ``[[npc]]`` stubs the
10
+ author re-writes); carry ships the donor strings unchanged.
11
+
12
+ It is IMPORT-ONLY and OPT-IN (the ``import --carry-text`` flag). It never touches the authored-dialogue path
13
+ (:mod:`content.text` / :func:`build.collect_text` stay byte-for-byte; the hut golden is preserved): carried
14
+ text is APPENDED after the authored block, in a disjoint txid band.
15
+
16
+ Engine / format facts this relies on (verified against the real bytes -- see docs/TEXT_CARRY.md):
17
+
18
+ * A window's TXID is a 2-BYTE immediate operand (operand 2 for ``WindowSync``/``WindowAsync`` 0x1F/0x20,
19
+ operand 3 for the ``...Ex`` variants 0x95/0x96 -- :data:`dialogue.WINDOW_OPS`). Re-emitting at a band
20
+ >= :data:`CARRY_BASE_TXID` (1000) keeps the new id in 2 bytes -> a SAME-LENGTH in-place patch via
21
+ :func:`content.object._arg_byte_offset` (the exact primitive the slot/uid remap uses). FF9 NEVER computes
22
+ a window txid (census: 0/24166 expression txids), so every one is statically remappable.
23
+ * The carry BAND must clear the base game's real txids (census max = 863, field 1607) AND the fork's own
24
+ authored band (:data:`content.text.DEFAULT_BASE_TXID` = 500). 1000 is the first unconditionally-safe
25
+ floor (no real field uses a txid >= 1000) and still a 2-byte immediate (<= 65535).
26
+ * ``.mes`` text is PER-LANGUAGE (the 7 :data:`config.LANGS`). The carried text is shipped in EVERY language
27
+ the build ships; a per-language entry is carried VERBATIM -- an empty entry stays empty (field 357 txid
28
+ 470 is empty in us/uk but populated in fr/gr/it/jp -- a us-fallback would WIPE the French/German text).
29
+ """
30
+ from __future__ import annotations
31
+
32
+ import json
33
+ from dataclasses import dataclass
34
+ from typing import Optional
35
+
36
+ from .. import dialogue as _dialogue
37
+ from ..config import LANGS
38
+ from ..eb import EbScript
39
+ from ..eb.disasm import argsize
40
+ from . import object as _object
41
+
42
+ # The carried-text txid band: clear of the base game (max real txid 863) AND the fork's authored 500+ band,
43
+ # and still a 2-byte immediate so every window remap is a same-length in-place patch. See the module note.
44
+ CARRY_BASE_TXID = 1000
45
+
46
+
47
+ @dataclass(frozen=True)
48
+ class CarriedEntry:
49
+ """One donor ``.mes`` entry carried per-language. ``texts[lang]`` is the verbatim string for that
50
+ language (``''`` for an empty/absent donor entry -- carried as-is, never us-filled). ``strt``/``tail``
51
+ are the donor's window-geometry tags, preserved verbatim from the language the scan was keyed on (they
52
+ are language-independent geometry, not text)."""
53
+ donor_txid: int
54
+ new_txid: int
55
+ texts: dict # lang -> verbatim text (may be "")
56
+ strt: Optional[str] = None
57
+ tail: Optional[str] = None
58
+
59
+
60
+ # --------------------------------------------------------------- which txids a graft will SHOW ---
61
+ def _grafted_object_windows(donor_eb, specs) -> set:
62
+ """Every TXID a CARRIED object's grafted bytes will open. For each spec, the carried funcs are its
63
+ ``carry_tags`` (``None`` = whole entry); we decode the donor entry and collect each window operand's txid
64
+ in the kept funcs. A REFUSED spec (``graft_safety == "refuse"``) carries nothing -> skipped, matching
65
+ :func:`content.object.graft_objects`."""
66
+ eb = donor_eb if isinstance(donor_eb, EbScript) else EbScript.from_bytes(donor_eb)
67
+ out: set = set()
68
+ for s in specs:
69
+ if s.get("graft_safety") == "refuse":
70
+ continue
71
+ slot = int(s["donor_idx"])
72
+ if not 0 <= slot < eb.entry_count:
73
+ continue
74
+ keep = s.get("carry_tags")
75
+ keep = None if keep is None else {int(t) for t in keep}
76
+ e = eb.entry(slot)
77
+ if e.empty:
78
+ continue
79
+ for f in e.funcs:
80
+ if keep is not None and f.tag not in keep:
81
+ continue
82
+ for ins in eb.instrs(f):
83
+ opnd = _dialogue.WINDOW_OPS.get(ins.op)
84
+ if opnd is None:
85
+ continue
86
+ if opnd < len(ins.arg_is_expr) and ins.arg_is_expr[opnd]:
87
+ continue # computed txid (does not occur in real fields) -- skip
88
+ txid = ins.imm(opnd)
89
+ if txid is not None:
90
+ out.add(int(txid))
91
+ return out
92
+
93
+
94
+ def _grafted_player_windows(player_specs) -> set:
95
+ """Every TXID a grafted PLAYER function will open. Only ``clean`` and ``text`` player funcs are carried
96
+ (a ``text`` func is graftable once its windows are remapped -- the whole point of carry); we decode each
97
+ grafted body and collect its window txids. Other safety classes are not grafted -> skipped."""
98
+ out: set = set()
99
+ for s in player_specs:
100
+ if s.get("safety") not in ("clean", "text"):
101
+ continue
102
+ body = s.get("body")
103
+ if not body:
104
+ continue
105
+ for ins in _object_iter(body):
106
+ opnd = _dialogue.WINDOW_OPS.get(ins.op)
107
+ if opnd is None:
108
+ continue
109
+ if opnd < len(ins.arg_is_expr) and ins.arg_is_expr[opnd]:
110
+ continue
111
+ txid = ins.imm(opnd)
112
+ if txid is not None:
113
+ out.add(int(txid))
114
+ return out
115
+
116
+
117
+ def _object_iter(body):
118
+ from ..eb.disasm import iter_code
119
+ return iter_code(bytes(body), 0, len(body))
120
+
121
+
122
+ # --------------------------------------------------------------- collect the carry plan ---
123
+ def collect_carry(donor_eb, object_specs, player_specs, field, lang_loader) -> list:
124
+ """Build the carry plan: the donor txids the grafts will SHOW, each assigned a fresh band txid and its
125
+ per-language text. ``donor_eb`` is the donor field's ``.eb`` bytes; ``object_specs`` / ``player_specs``
126
+ are the graft specs (:func:`eventscan.scan_objects_verbatim` / :func:`scan_player_funcs`). ``field`` is
127
+ the donor field id (for the text loader). ``lang_loader(txids, lang) -> {txid: MesEntry}`` reads the
128
+ donor's per-language ``.mes`` (injected so this stays install-free + testable -- in the kit it is
129
+ :func:`_field_text_loader`).
130
+
131
+ Returns an ordered ``list[CarriedEntry]`` (sorted by donor txid for determinism), one per distinct
132
+ referenced txid. ``new_txid`` is :data:`CARRY_BASE_TXID` + i. An empty plan (no grafted windows) returns
133
+ ``[]`` -- the build then carries no text (byte-identical to no-carry).
134
+ """
135
+ wanted = _grafted_object_windows(donor_eb, object_specs) | _grafted_player_windows(player_specs)
136
+ if not wanted:
137
+ return []
138
+ donor_txids = sorted(wanted)
139
+
140
+ # per-language text. The geometry tags (strt/tail) are read once from the primary language (us); they
141
+ # are language-independent window geometry. Each language's TEXT is carried independently (verbatim).
142
+ per_lang: dict = {}
143
+ for lang in LANGS:
144
+ per_lang[lang] = lang_loader(donor_txids, lang) or {}
145
+ primary = per_lang.get(LANGS[0], {})
146
+
147
+ plan = []
148
+ for i, dt in enumerate(donor_txids):
149
+ texts = {}
150
+ for lang in LANGS:
151
+ e = per_lang[lang].get(dt)
152
+ texts[lang] = e.text if (e is not None and e.text is not None) else ""
153
+ pe = primary.get(dt)
154
+ plan.append(CarriedEntry(donor_txid=dt, new_txid=CARRY_BASE_TXID + i, texts=texts,
155
+ strt=(pe.strt if pe else None), tail=(pe.tail if pe else None)))
156
+ return plan
157
+
158
+
159
+ def _field_text_loader(field, game=None):
160
+ """The kit's real per-language loader for :func:`collect_carry`: reads the donor field's ``<zone>.mes``
161
+ block from the install (the authoritative ``eventIDToMESID`` zone), picking the requested language. A
162
+ thin wrapper over :func:`dialogue._load_field_text` keyed on the field's text zone -- so the SAME block
163
+ selection :func:`dialogue.read_field_dialogue` proved is used (the want-txids coverage + language score
164
+ pick the right per-language copy)."""
165
+ from .._fieldtext import EVENT_ID_TO_MES
166
+ zone_id = EVENT_ID_TO_MES.get(int(field))
167
+
168
+ def _load(txids, lang):
169
+ return _dialogue._load_field_text(txids, lang, game=game, zone_id=zone_id)
170
+ return _load
171
+
172
+
173
+ # --------------------------------------------------------------- remap the grafted windows ---
174
+ def _remap_windows_in_entry(eb_bytes, entry_index, txid_map, carry_tags=None) -> bytes:
175
+ """Same-length, in-place remap of every window TXID a grafted entry's CARRIED funcs reference, donor ->
176
+ carried band, via the decoder-derived 2-byte offset (:func:`content.object._arg_byte_offset`). Only the
177
+ ``carry_tags`` funcs are patched (``None`` = whole entry), mirroring what was actually grafted. A txid
178
+ not in ``txid_map`` (e.g. a system window pointing at a base id we chose not to carry) is left
179
+ untouched. The patch is byte-for-byte length-preserving (2-byte immediate -> 2-byte immediate)."""
180
+ eb = EbScript.from_bytes(eb_bytes)
181
+ b = bytearray(eb_bytes)
182
+ keep = None if carry_tags is None else {int(t) for t in carry_tags}
183
+ for f in eb.entry(entry_index).funcs:
184
+ if keep is not None and f.tag not in keep:
185
+ continue
186
+ for ins in eb.instrs(f):
187
+ opnd = _dialogue.WINDOW_OPS.get(ins.op)
188
+ if opnd is None:
189
+ continue
190
+ if opnd < len(ins.arg_is_expr) and ins.arg_is_expr[opnd]:
191
+ continue
192
+ txid = ins.imm(opnd)
193
+ if txid is None or int(txid) not in txid_map:
194
+ continue
195
+ new = txid_map[int(txid)]
196
+ bo = _object._arg_byte_offset(ins, opnd)
197
+ if bo is None or argsize(ins.op, opnd) != 2:
198
+ continue # only same-length 2-byte window operands are patchable
199
+ b[ins.off + bo] = new & 0xFF
200
+ b[ins.off + bo + 1] = (new >> 8) & 0xFF
201
+ return bytes(b)
202
+
203
+
204
+ def remap_object_windows(eb_bytes, object_specs, slot_map, txid_map) -> bytes:
205
+ """Patch each carried OBJECT's grafted entry (now at its fork slot ``slot_map[donor_idx]``) so its
206
+ window TXIDs point at the carried band. ``slot_map`` is the donor_idx -> fork-slot map the object graft
207
+ built; ``txid_map`` is donor_txid -> carried_txid. A refused/un-slotted spec is skipped."""
208
+ data = eb_bytes
209
+ for s in object_specs:
210
+ if s.get("graft_safety") == "refuse":
211
+ continue
212
+ slot = slot_map.get(int(s["donor_idx"]))
213
+ if slot is None:
214
+ continue
215
+ data = _remap_windows_in_entry(data, slot, txid_map, s.get("carry_tags"))
216
+ return data
217
+
218
+
219
+ def remap_player_func_windows(eb_bytes, player_entry, tag_map, txid_map) -> bytes:
220
+ """Patch each grafted PLAYER function (now at its fork tag ``tag_map[donor_tag]`` on the player entry) so
221
+ its window TXIDs point at the carried band. ``tag_map`` is donor_tag -> fork-tag; only the grafted tags
222
+ are touched. Same same-length 2-byte patch as the object path."""
223
+ eb = EbScript.from_bytes(eb_bytes)
224
+ fork_tags = set(tag_map.values())
225
+ data = eb_bytes
226
+ for f in eb.entry(player_entry).funcs:
227
+ if f.tag not in fork_tags:
228
+ continue
229
+ data = _remap_windows_in_entry(data, player_entry, txid_map, [f.tag])
230
+ return data
231
+
232
+
233
+ # --------------------------------------------------------------- emit the carried .mes ---
234
+ def _mes_entry_verbatim(entry: CarriedEntry, lang: str) -> str:
235
+ """One carried ``.mes`` entry for ``lang``, re-emitted FAITHFULLY at its new txid: the donor's exact STRT
236
+ (window geometry, kept verbatim) + its TAIL only if the donor had one + the language's verbatim text. NOT
237
+ :func:`content.text.mes_entry` (which forces a default STRT/TAIL) -- a carried entry must preserve the
238
+ donor's own geometry tags, else the window resizes."""
239
+ strt = entry.strt if entry.strt is not None else "10,1"
240
+ tail = f"[TAIL={entry.tail}]" if entry.tail else ""
241
+ return f"_[TXID={entry.new_txid}][STRT={strt}]{tail}{entry.texts.get(lang, '')}[ENDN]"
242
+
243
+
244
+ def carried_mes_body(plan, lang: str) -> str:
245
+ """The carried ``.mes`` block for one language: every :class:`CarriedEntry` re-emitted at its new txid,
246
+ verbatim. Each entry carries its own ``[TXID=]`` re-index (so it is position-independent), letting this
247
+ block be APPENDED after the fork's authored block without disturbing it. Empty plan -> ``''``."""
248
+ if not plan:
249
+ return ""
250
+ return "\n".join(_mes_entry_verbatim(e, lang) for e in plan) + "\n"
251
+
252
+
253
+ # --------------------------------------------------------------- the sidecar (import write / build read) ---
254
+ SIDECAR_VERSION = 1
255
+
256
+
257
+ def plan_to_sidecar(plan, *, field=None) -> dict:
258
+ """Serialise the carry plan to a JSON-able dict (the ``<name>.carrytext.json`` sidecar). One record per
259
+ carried entry: the donor/new txids, the donor geometry tags, and the per-language verbatim text. The
260
+ strings are SE-derived, so the sidecar is gitignored (mirrors ``.object*.bin`` / ``.dialogue.json``)."""
261
+ return {
262
+ "version": SIDECAR_VERSION,
263
+ "field": (int(field) if field is not None else None),
264
+ "base_txid": CARRY_BASE_TXID,
265
+ "langs": list(LANGS),
266
+ "entries": [{"donor_txid": e.donor_txid, "new_txid": e.new_txid,
267
+ "strt": e.strt, "tail": e.tail, "texts": dict(e.texts)} for e in plan],
268
+ }
269
+
270
+
271
+ def write_sidecar(path, plan, *, field=None) -> None:
272
+ """Write the carry sidecar to ``path`` (UTF-8 JSON). No-op file is still written for an empty plan so the
273
+ build's ``[carry_text] bin`` ref always resolves."""
274
+ from pathlib import Path
275
+ Path(path).write_text(json.dumps(plan_to_sidecar(plan, field=field), ensure_ascii=False, indent=1),
276
+ encoding="utf-8")
277
+
278
+
279
+ def load_sidecar(path) -> list:
280
+ """Read a ``<name>.carrytext.json`` sidecar back into an ordered ``list[CarriedEntry]`` (the build's
281
+ consume side). Each entry's ``texts`` is filled for every shipped language (missing -> ``''``, so an
282
+ absent language never errors -- it ships an empty window, harmless)."""
283
+ from pathlib import Path
284
+ raw = json.loads(Path(path).read_text(encoding="utf-8"))
285
+ out = []
286
+ for rec in raw.get("entries", []):
287
+ texts = {lang: (rec.get("texts", {}).get(lang) or "") for lang in LANGS}
288
+ out.append(CarriedEntry(donor_txid=int(rec["donor_txid"]), new_txid=int(rec["new_txid"]),
289
+ texts=texts, strt=rec.get("strt"), tail=rec.get("tail")))
290
+ return out
@@ -0,0 +1,86 @@
1
+ """Verbatim-`.eb` fork -- ship a real field's WHOLE event script instead of re-synthesizing it.
2
+
3
+ The faithful realization of "entry-0 carry" (docs/FORK_FIDELITY.md): a story field's entry-0 ``Main_Init``
4
+ arms its objects/regions and gates the cast by ScenarioCounter, and the gated doors read MAP vars it sets --
5
+ so the only way those references resolve is to keep the donor's WHOLE entry layout. This mode does exactly
6
+ that: the build ships the donor's `.eb` verbatim (entry-0 + every object + every gateway, slots intact) and
7
+ only **remaps the `Field()` destinations**; the field then runs its real logic. Proven in-game on Dali Inn
8
+ (the gated door opens, the cast gates by story beat).
9
+
10
+ The declarative content blocks ([[npc]]/[[gateway]]/...) are NOT used in this mode -- the `.eb` is whole, so
11
+ there is nothing to synthesize. Pair with a `[startup]` block to boot a chosen beat. LIMITS (vs a perfect
12
+ clone): the donor `.mes` text is a separate carry (TXIDs may not resolve until then), and a fork reached by
13
+ F6-warp has no entrance fade to mask first-frame model streaming.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import struct
19
+
20
+ from ..eb import EbScript
21
+
22
+ FIELD_OP = 0x2B # Field(dest) -- the warp; dest is a 2-byte literal at instruction offset +2
23
+
24
+
25
+ def remap_fields(eb_bytes: bytes, retarget: dict) -> bytes:
26
+ """Patch every ``Field(id)`` literal whose id is in ``retarget`` (real destination -> fork id). Ids NOT in
27
+ the map are left as live seams (the door warps back into the real game). Empty ``retarget`` -> unchanged."""
28
+ if not retarget:
29
+ return eb_bytes
30
+ eb = EbScript.from_bytes(eb_bytes)
31
+ buf = bytearray(eb_bytes)
32
+ for e in eb.entries:
33
+ if e.empty:
34
+ continue
35
+ for f in e.funcs:
36
+ for i in eb.instrs(f):
37
+ if i.op == FIELD_OP and i.imm(0) in retarget:
38
+ struct.pack_into("<H", buf, i.off + 2, int(retarget[i.imm(0)]) & 0xFFFF)
39
+ return bytes(buf)
40
+
41
+
42
+ def render_retarget(dests, id_remap=None):
43
+ """The ``[verbatim_eb] retarget`` portion for a verbatim fork's ``Field()`` exits, plus the count of
44
+ exits actually retargeted.
45
+
46
+ ``dests`` = the field's distinct ``Field(id)`` destinations (real ids). With ``id_remap`` (a
47
+ ``{real_id: fork_id}`` map from import-chain) this emits a LIVE ``retarget = {...}`` table for the
48
+ in-chain destinations and a comment listing the rest (left as live seams back into the real game) --
49
+ so a forked CHAIN's doors warp into its OWN member forks. Without ``id_remap`` (single-field
50
+ ``import --verbatim``) it emits the commented-out fill-in template the author edits by hand, BYTE-FOR-BYTE
51
+ as before (so the single-field golden is unchanged). Returns ``(toml_text, n_retargeted)``."""
52
+ dests = list(dests)
53
+ if id_remap:
54
+ inchain = [(d, int(id_remap[d])) for d in dests if d in id_remap]
55
+ if inchain:
56
+ seams = [d for d in dests if d not in id_remap]
57
+ tbl = "retarget = { " + ", ".join(f"{a} = {b}" for a, b in inchain) + " }\n"
58
+ note = ("# (the rest are live seams back into the real game -- not in this chain: "
59
+ + ", ".join(map(str, seams)) + ")\n") if seams else ""
60
+ return tbl + note, len(inchain)
61
+ body = "".join(f"# {d} = 0\n" for d in dests) or "# (this field has no Field() exits)\n"
62
+ return ("# retarget = {\n" + body + "# }\n"), 0
63
+
64
+
65
+ def verbatim_eb(project):
66
+ """The verbatim `.eb` to ship for ``project`` (from its ``[verbatim_eb]`` block, ``bin`` + optional
67
+ ``retarget``), Field-remapped -- or ``None`` if the project isn't a verbatim fork (the build then
68
+ synthesizes from the field.toml as usual). The same bytecode ships for every language (it is
69
+ language-identical; only the cosmetic name field differs, which the donor's already carries)."""
70
+ spec = project.raw.get("verbatim_eb")
71
+ if not spec or not spec.get("bin"):
72
+ return None
73
+ retarget = {int(k): int(v) for k, v in (spec.get("retarget") or {}).items()}
74
+ return remap_fields(project.path(spec["bin"]).read_bytes(), retarget)
75
+
76
+
77
+ def verbatim_mes(project, lang: str):
78
+ """The donor field's WHOLE `.mes` text body to ship for ``lang`` (from the ``[verbatim_eb] text`` JSON
79
+ sidecar, ``{lang: body}``) -- the verbatim `.eb`'s index-txids resolve straight into it. Falls back to the
80
+ ``us`` body for a language the dialogue reader couldn't distinguish. ``None`` if the fork carries no text."""
81
+ spec = project.raw.get("verbatim_eb") or {}
82
+ tf = spec.get("text")
83
+ if not tf:
84
+ return None
85
+ data = json.loads(project.path(tf).read_text(encoding="utf-8"))
86
+ return data.get(lang) or data.get("us")
@@ -0,0 +1,38 @@
1
+ """Reproduce a real field's LOAD-TIME engine walkmesh hotfix in a fork.
2
+
3
+ A few real fields rely on a hardcoded Memoria hotfix (keyed on the real ``fldMapNo``) that toggles
4
+ walkmesh-triangle active-state at field load -- e.g. Gulug/Room (2356) deactivates the broken-wall triangles
5
+ so the player can't walk through the gap. A verbatim/native fork runs at a custom id (>= 4000), so that
6
+ ``fldMapNo`` guard is false and the hotfix never fires -> the forked walkmesh is wrong there. See the catalog
7
+ and the two tractability classes in :mod:`ff9mapkit.walkmesh_hotfixes`.
8
+
9
+ This module reproduces the AUTO (load-time, unconditional) class: it prepends ``EnablePathTriangle(tri, state)``
10
+ -- opcode 0x9A, whose engine handler IS ``WalkMesh.BGI_triSetActive`` -- to ``Main_Init`` (entry-0 tag-0), so
11
+ the triangles are in the right state from the first frame, exactly as the engine sets them at load. The
12
+ ``.bgi`` stays byte-verbatim (the fix lives in the script layer). A tag-0 prepend (``rel_off == 0``) is
13
+ shift-safe even on a jump-table donor, and ``EnablePathTriangle`` is language-identical. No toggles -> the eb
14
+ is returned unchanged. Mirrors :mod:`ff9mapkit.content.areatitle` / :mod:`ff9mapkit.content.startup`.
15
+ """
16
+ from __future__ import annotations
17
+
18
+ from ..eb import edit, opcodes
19
+
20
+ ENABLE_PATH_TRIANGLE = 0x9A # EnablePathTriangle(triId, active) -- the engine handler is BGI_triSetActive
21
+
22
+
23
+ def toggles_body(toggles) -> bytes:
24
+ """The bare ``EnablePathTriangle(tri, state)`` sequence for ``toggles`` (an iterable of ``(tri, state)``),
25
+ or ``b""`` when there are none. ``state`` is coerced to 0/1 (1 = active/walkable)."""
26
+ out = b""
27
+ for tri, state in (toggles or ()):
28
+ out += opcodes.encode(ENABLE_PATH_TRIANGLE, int(tri), 1 if int(state) else 0)
29
+ return out
30
+
31
+
32
+ def apply_tri_toggles(eb_bytes, toggles) -> bytes:
33
+ """Prepend the load-time triangle toggles to ``Main_Init`` (entry-0 tag-0). Returns ``eb_bytes`` unchanged
34
+ when ``toggles`` is empty (so a field with no walkmesh hotfix builds byte-for-byte as before)."""
35
+ body = toggles_body(toggles)
36
+ if not body:
37
+ return eb_bytes
38
+ return edit.insert_in_function(eb_bytes, 0, 0, 0, body)
@@ -0,0 +1,48 @@
1
+ """Bundled binary data + accessors.
2
+
3
+ Contents
4
+ --------
5
+ blank_field/<lang>.eb.bytes
6
+ The canonical *blank field* event script (956 bytes), one per language. This is the
7
+ proven minimal playable field used as the starting point for every built field: a clean
8
+ Main_Init (no stray popups, standard movement) plus a single player object. Content
9
+ injectors clone/extend it; the builder writes it (with content) per language.
10
+
11
+ region_template.bin
12
+ The 272-byte field-exit region body (a SetRegion polygon -> CalculateExitPosition /
13
+ ExitField -> PreloadField -> set FieldEntrance -> Field(target)). The gateway injector
14
+ patches its polygon / entrance / target and appends it as a new entry.
15
+
16
+ Provenance / distribution note
17
+ ------------------------------
18
+ These blobs are DERIVED from Final Fantasy IX field data (the blank field is a cleaned clone of a
19
+ base field; the region template is a base field's exit region). To avoid redistributing Square Enix
20
+ game bytes, the public repo ships **none** of them -- they are regenerated from the user's own,
21
+ legally-owned FF9 install by ``ff9mapkit extract-templates`` (see :mod:`ff9mapkit.provision` and
22
+ docs/PROVENANCE.md) into a local, gitignored cache. The accessors below read that cache and raise a
23
+ clear "run extract-templates" message if it isn't present yet.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ from ..config import LANGS
29
+ from .. import provision
30
+
31
+
32
+ def blank_field_bytes(lang: str = "us") -> bytes:
33
+ """Bytes of the blank field event script for *lang* (defaults to 'us'). Regenerated from the
34
+ user's FF9 install by ``ff9mapkit extract-templates``; raises if that hasn't been run."""
35
+ if lang not in LANGS:
36
+ raise ValueError(f"unknown language {lang!r}; expected one of {LANGS}")
37
+ p = provision.blank_dir() / f"{lang}.eb.bytes"
38
+ if not p.is_file():
39
+ raise FileNotFoundError(provision.MISSING_MSG)
40
+ return p.read_bytes()
41
+
42
+
43
+ def region_template() -> bytes:
44
+ """The 272-byte field-exit region template (regenerated from the user's install)."""
45
+ p = provision.region_template_path()
46
+ if not p.is_file():
47
+ raise FileNotFoundError(provision.MISSING_MSG)
48
+ return p.read_bytes()
@@ -0,0 +1,142 @@
1
+ """MAINTAINER tool: regenerate the provenance artifacts (patches + manifest) from a vanilla FF9
2
+ install, so the public repo can ship ZERO Square Enix bytes.
3
+
4
+ Run from a repo checkout with $FF9_GAME_PATH set (or --game), against an UNMODIFIED install:
5
+
6
+ python -m ff9mapkit.data._regen_provenance
7
+
8
+ It writes ff9mapkit/data/provenance/{manifest.json, blank.<lang>.patch, region_template.patch}
9
+ -- all OURS (copy/insert diffs + hashes + source field names; no game bytes) -- and verifies that
10
+ `ff9mapkit extract-templates` would reproduce the current on-disk blobs byte-for-byte. The actual
11
+ game-derived blobs (blank_field/, region_template.bin, the binary test fixtures) are NOT committed;
12
+ end users regenerate them locally from their own install.
13
+
14
+ This is the inverse bookend of provision.extract_templates: this AUTHORS the patches; that APPLIES them.
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import hashlib
19
+ import json
20
+ import sys
21
+ from pathlib import Path
22
+
23
+ from .. import provision
24
+ from ..config import LANGS
25
+
26
+ HERE = Path(__file__).resolve().parent
27
+ PROV = HERE / "provenance"
28
+
29
+ # --- the base fields the kit's assets are derived from (all present in a vanilla install) ----------
30
+ BLANK_SRC = "fbg_n11_ldbm_map203_lb_hng_0" # field 1357 (L.Castle/Hangar) -> the cleaned blank field
31
+ REGION_SRC = "fbg_n01_alxt_map031_at_wpn_0" # ALEX3_AT_WEAPON -> the exit-region template
32
+ ALEX_SRC = "fbg_n01_alxt_map016_at_msa_0" # ALEX1_AT_STREET_A (vanilla field 100) -> eventscan oracle
33
+ GRGR_SRC = "fbg_n21_grgr_map420_gr_cen_0" # Gargan Roo centre -> camera fixture
34
+ MULTIFLOOR_SRC = "fbg_n00_tshp_map008_th_upr_0" # Prima Vista upper deck (3 floors) -> multi-floor walkmesh
35
+ # (chosen because its real .bgi round-trips byte-exact through the kit's codec AND is seam-clean:
36
+ # fully walk-reachable, strands on obj re-export, reconciles -- so it exercises the seam machinery.)
37
+
38
+ # the Session-12 Alexandria door, re-injected onto the vanilla field so the eventscan oracle keeps its
39
+ # "3 real exits + 1 injected door" shape without redistributing AlternateFantasy's modified bytes.
40
+ ALEX_DOOR = {"target": 4000, "entrance": 0,
41
+ "zone": [[-700, 2200], [200, 2200], [200, 3400], [-700, 3400]]}
42
+
43
+ sha = lambda b: hashlib.sha256(b).hexdigest()
44
+
45
+
46
+ def main(argv=None) -> int:
47
+ import argparse
48
+ from .. import extract
49
+ ap = argparse.ArgumentParser(description="regenerate ff9mapkit provenance patches + manifest")
50
+ ap.add_argument("--game", help="FF9 install path (else $FF9_GAME_PATH / config)")
51
+ args = ap.parse_args(argv)
52
+ game = args.game
53
+
54
+ PROV.mkdir(parents=True, exist_ok=True)
55
+ manifest: dict = {
56
+ "_note": ("ff9mapkit ships NO Final Fantasy IX game data. These entries describe how to "
57
+ "regenerate the small set of base assets the kit needs from YOUR OWN install via "
58
+ "`ff9mapkit extract-templates`. The .patch files contain only our edits + copy "
59
+ "offsets (never game bytes). See docs/PROVENANCE.md."),
60
+ }
61
+
62
+ # 1) blank field: per-language copy/insert patch (1357 -> our cleaned blank)
63
+ blank_sha = {}
64
+ for lang in LANGS:
65
+ src = extract.extract_event_script(BLANK_SRC, game=game, lang=lang)
66
+ dst = (HERE / "blank_field" / f"{lang}.eb.bytes").read_bytes() # current on-disk blank
67
+ patch = provision.make_patch(src, dst)
68
+ (PROV / f"blank.{lang}.patch").write_text(json.dumps(patch), encoding="utf-8")
69
+ assert provision.apply_patch(src, patch) == dst, f"blank {lang} patch round-trip failed"
70
+ bad = provision.patch_game_runs(src, patch)
71
+ assert not bad, f"blank {lang}: patch would ship game-byte runs {bad}"
72
+ blank_sha[lang] = sha(dst)
73
+ print(f" blank.{lang}.patch insert={patch['insert_bytes']}B (no game runs) -> reproduces blank OK")
74
+ manifest["blank"] = {"source_fbg": BLANK_SRC, "patch": "blank.{lang}.patch", "sha256": blank_sha}
75
+
76
+ # 2) region template: single patch (ALEX3_AT_WEAPON -> our 272B exit-region template)
77
+ src = extract.extract_event_script(REGION_SRC, game=game, lang="us")
78
+ dst = (HERE / "region_template.bin").read_bytes()
79
+ patch = provision.make_patch(src, dst)
80
+ (PROV / "region_template.patch").write_text(json.dumps(patch), encoding="utf-8")
81
+ assert provision.apply_patch(src, patch) == dst, "region patch round-trip failed"
82
+ assert not provision.patch_game_runs(src, patch), "region patch would ship game-byte runs"
83
+ manifest["region_template"] = {"source_fbg": REGION_SRC, "lang": "us",
84
+ "patch": "region_template.patch", "sha256": sha(dst)}
85
+ print(f" region_template.patch insert={patch['insert_bytes']}B -> reproduces template OK")
86
+
87
+ # 3) test fixtures, regenerated from the install (sha recorded so extract-templates self-verifies)
88
+ fixtures = {}
89
+ fix_dir = HERE.parent.parent / "tests" / "fixtures"
90
+
91
+ # alex100: vanilla field 100 + the kit's own door injection (no AlternateFantasy bytes)
92
+ from ..content import gateway as _gw
93
+ van = extract.extract_event_script(ALEX_SRC, game=game, lang="us")
94
+ door = _gw.inject_gateway(van, ALEX_DOOR["target"], entrance=ALEX_DOOR["entrance"],
95
+ zone=_gw.quad_zone(ALEX_DOOR["zone"]))
96
+ fixtures["alex100-us.eb.bytes"] = {"source_fbg": ALEX_SRC, "kind": "event_with_gateway",
97
+ "lang": "us", "gateway": ALEX_DOOR, "sha256": sha(door)}
98
+ if fix_dir.is_dir():
99
+ (fix_dir / "alex100-us.eb.bytes").write_bytes(door)
100
+
101
+ # grgr.bgx: the real GRGR camera (extracted as a borrowable .bgx)
102
+ # multifloor.bgi.bytes: a real 3-floor walkmesh that round-trips byte-exact (codec + seam tests)
103
+ import tempfile
104
+ tg = Path(tempfile.mkdtemp())
105
+ extract.extract_field(GRGR_SRC, tg, game=game)
106
+ grgr_bgx = (tg / "camera.bgx").read_bytes()
107
+ tm = Path(tempfile.mkdtemp())
108
+ extract.extract_field(MULTIFLOOR_SRC, tm, game=game)
109
+ mf_bgi = (tm / "walkmesh.bgi").read_bytes()
110
+ fixtures["grgr.bgx"] = {"source_fbg": GRGR_SRC, "kind": "camera_bgx", "sha256": sha(grgr_bgx)}
111
+ fixtures["multifloor.bgi.bytes"] = {"source_fbg": MULTIFLOOR_SRC, "kind": "walkmesh_verbatim",
112
+ "sha256": sha(mf_bgi)}
113
+ if fix_dir.is_dir():
114
+ (fix_dir / "grgr.bgx").write_bytes(grgr_bgx)
115
+ (fix_dir / "multifloor.bgi.bytes").write_bytes(mf_bgi)
116
+ manifest["fixtures"] = fixtures
117
+ print(f" fixtures: alex100-us({len(door)}B) grgr.bgx({len(grgr_bgx)}B) "
118
+ f"multifloor.bgi({len(mf_bgi)}B)")
119
+
120
+ # 4) build goldens: the hut example's build outputs are DERIVATIVE (embed the blank), so we ship a
121
+ # hash, not the bytes. The build test compares fresh build output's hash to this.
122
+ manifest["goldens"] = _golden_hashes(game)
123
+ print(f" goldens (hash-only): {list(manifest['goldens'])}")
124
+
125
+ (PROV / "manifest.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8")
126
+ print(f"\nwrote {PROV/'manifest.json'} + patches")
127
+ return 0
128
+
129
+
130
+ def _golden_hashes(game) -> dict:
131
+ """SHA-256 of the hut example's build outputs (the independent build-golden reference)."""
132
+ import tempfile
133
+ from ..build import FieldProject, build_mod, ModLayout
134
+ example = HERE.parent.parent / "examples" / "vivi-hut" / "hut_int.field.toml"
135
+ out = Path(tempfile.mkdtemp())
136
+ build_mod([FieldProject.load(example)], out, mod_name="GoldenCheck")
137
+ L = ModLayout(out)
138
+ return {"EVT_HUT_INT.eb.bytes/us": sha(L.eb_path("us", "EVT_HUT_INT.eb.bytes").read_bytes())}
139
+
140
+
141
+ if __name__ == "__main__":
142
+ sys.exit(main())
@@ -0,0 +1 @@
1
+ {"src_sha256": "b536766f4e15836b9e83851bae513512be410b291829de3187d8345ef45c28b2", "out_len": 956, "out_sha256": "eb6ac7333a273333b9d7e296408a2cfad41b059acd06679c3fb2a50b815234ac", "insert_bytes": 16, "ops": [["c", 0, 4], ["i", "826b"], ["c", 12, 4], ["c", 4, 8], ["c", 26, 3], ["i", "85"], ["c", 32, 12], ["i", "82928294816682938140"], ["c", 44, 86], ["i", "6c"], ["c", 131, 302], ["i", "ffff"], ["c", 435, 29], ["c", 532, 105], ["c", 68, 60], ["c", 68, 8], ["c", 637, 319]]}
@@ -0,0 +1 @@
1
+ {"src_sha256": "2ccd924b63800c95703ce7e766dde0036a9d6dd94b619d0bcfeb848ce1a7c49e", "out_len": 956, "out_sha256": "b06d7768c8485ee4c48443e2384730a0d447d2af8e84d87a20a3ee2a23c45f6b", "insert_bytes": 22, "ops": [["c", 0, 4], ["i", "826b"], ["c", 22, 5], ["i", "62828182938294828c8285815e"], ["c", 4, 12], ["i", "8294"], ["c", 16, 1], ["i", "66"], ["c", 18, 1], ["i", "93"], ["c", 24, 2], ["c", 44, 86], ["i", "6c"], ["c", 131, 302], ["i", "ffff"], ["c", 435, 29], ["c", 532, 105], ["c", 68, 60], ["c", 68, 8], ["c", 637, 319]]}
@@ -0,0 +1 @@
1
+ {"src_sha256": "08b0cba27ddafe91062b7d565e4aece258c6b51d0fa82bfc2a999b14caa0fd09", "out_len": 956, "out_sha256": "5676ef381393767a4fef715cdfe1eca947fef10b1d0ec80dd247a0301fa66a34", "insert_bytes": 21, "ops": [["c", 0, 5], ["i", "6b81448140"], ["c", 6, 1], ["i", "62"], ["c", 8, 1], ["i", "8182938294"], ["c", 10, 3], ["i", "85"], ["c", 24, 2], ["c", 28, 13], ["i", "948166"], ["c", 42, 1], ["i", "938140"], ["c", 44, 86], ["i", "6c"], ["c", 131, 302], ["i", "ffff"], ["c", 435, 29], ["c", 532, 105], ["c", 68, 60], ["c", 68, 8], ["c", 637, 319]]}
@@ -0,0 +1 @@
1
+ {"src_sha256": "251dfb52f8c962a0cc5868c5c7723066c445058cc09acf187b48c99fa8262569", "out_len": 956, "out_sha256": "d968b0d1bafe3b2c60ee02c91a435986f71c23b4afb1e78fa65a67294c5b290b", "insert_bytes": 15, "ops": [["c", 0, 5], ["i", "6b"], ["c", 6, 5], ["i", "62"], ["c", 12, 1], ["i", "81"], ["c", 14, 1], ["i", "93"], ["c", 16, 1], ["i", "94"], ["c", 20, 3], ["i", "85"], ["c", 26, 15], ["i", "948166"], ["c", 42, 1], ["i", "938140"], ["c", 44, 86], ["i", "6c"], ["c", 131, 302], ["i", "ffff"], ["c", 435, 29], ["c", 532, 105], ["c", 70, 58], ["c", 70, 10], ["c", 637, 319]]}
@@ -0,0 +1 @@
1
+ {"src_sha256": "89ef1de34133a8b8e22a1502cf4fb65ab2c77237a80ef63d9b5548066c40edf7", "out_len": 956, "out_sha256": "e5595df0fcd0685bf8702b2286ac28bc0f8c54169e4c881e61ff5c0ec09c1219", "insert_bytes": 39, "ops": [["c", 0, 4], ["i", "826b814481408262828182"], ["c", 7, 1], ["i", "8294828c828581"], ["c", 19, 1], ["i", "82678281828e8287828182928294816682"], ["c", 23, 1], ["c", 26, 1], ["i", "40"], ["c", 44, 86], ["i", "6c"], ["c", 131, 302], ["i", "ffff"], ["c", 435, 29], ["c", 532, 105], ["c", 64, 64], ["c", 28, 4], ["c", 637, 319]]}
@@ -0,0 +1 @@
1
+ {"src_sha256": "ccb9439159f288a85b231bff945e52dac7b5dc01662f09484479a06d0c627df1", "out_len": 956, "out_sha256": "2289085a5dc1030d8cd1541619e34a0bd722a0957d0849bfaf077c7e30e90452", "insert_bytes": 3, "ops": [["c", 0, 130], ["i", "6c"], ["c", 131, 302], ["i", "ffff"], ["c", 435, 29], ["c", 532, 105], ["c", 70, 58], ["c", 70, 10], ["c", 637, 319]]}
@@ -0,0 +1 @@
1
+ {"src_sha256": "ccb9439159f288a85b231bff945e52dac7b5dc01662f09484479a06d0c627df1", "out_len": 956, "out_sha256": "2289085a5dc1030d8cd1541619e34a0bd722a0957d0849bfaf077c7e30e90452", "insert_bytes": 3, "ops": [["c", 0, 130], ["i", "6c"], ["c", 131, 302], ["i", "ffff"], ["c", 435, 29], ["c", 532, 105], ["c", 70, 58], ["c", 70, 10], ["c", 637, 319]]}