@gmvjs/core 0.1.0 → 0.1.1

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 (60) hide show
  1. package/README.md +41 -35
  2. package/dist/archives/common.d.ts +9 -9
  3. package/dist/archives/common.js.map +1 -1
  4. package/dist/archives/folder.d.ts +1 -1
  5. package/dist/archives/folder.js +1 -1
  6. package/dist/archives/folder.js.map +1 -1
  7. package/dist/archives/zip.js.map +1 -1
  8. package/dist/binary.d.ts +7 -7
  9. package/dist/binary.js.map +1 -1
  10. package/dist/builtin-en.js.map +1 -1
  11. package/dist/builtin.js +1 -1
  12. package/dist/builtin.js.map +1 -1
  13. package/dist/codecs/zlib.d.ts +2 -2
  14. package/dist/codecs/zlib.js.map +1 -1
  15. package/dist/diagnostics.js.map +1 -1
  16. package/dist/geometry.d.ts +5 -5
  17. package/dist/geometry.js.map +1 -1
  18. package/dist/i18n.d.ts +4 -4
  19. package/dist/i18n.js.map +1 -1
  20. package/dist/images/bc45.d.ts +1 -1
  21. package/dist/images/bc45.js.map +1 -1
  22. package/dist/images/bc7.d.ts +1 -1
  23. package/dist/images/bc7.js.map +1 -1
  24. package/dist/images/dds.d.ts +1 -1
  25. package/dist/images/dds.js.map +1 -1
  26. package/dist/images/pcx.js.map +1 -1
  27. package/dist/images/tga.js.map +1 -1
  28. package/dist/index.d.ts +1 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/levelbuilder.d.ts +3 -3
  31. package/dist/levelbuilder.js +1 -1
  32. package/dist/levelbuilder.js.map +1 -1
  33. package/dist/math.d.ts +6 -6
  34. package/dist/math.js.map +1 -1
  35. package/dist/normals.d.ts +5 -5
  36. package/dist/normals.js.map +1 -1
  37. package/dist/plugin.d.ts +57 -57
  38. package/dist/plugin.js.map +1 -1
  39. package/dist/registry.d.ts +14 -14
  40. package/dist/registry.js +13 -13
  41. package/dist/registry.js.map +1 -1
  42. package/dist/scene.d.ts +14 -14
  43. package/dist/scene.js.map +1 -1
  44. package/dist/session.d.ts +34 -34
  45. package/dist/session.js +33 -33
  46. package/dist/session.js.map +1 -1
  47. package/dist/skeleton.d.ts +3 -3
  48. package/dist/skeleton.js.map +1 -1
  49. package/dist/surface.d.ts +4 -4
  50. package/dist/surface.js.map +1 -1
  51. package/dist/textures.d.ts +12 -12
  52. package/dist/textures.js +9 -9
  53. package/dist/textures.js.map +1 -1
  54. package/dist/types.d.ts +103 -103
  55. package/dist/types.js.map +1 -1
  56. package/dist/writers/dds.d.ts +1 -1
  57. package/dist/writers/dds.js.map +1 -1
  58. package/dist/writers/obj.js.map +1 -1
  59. package/dist/writers/zip.js.map +1 -1
  60. package/package.json +3 -3
package/README.md CHANGED
@@ -1,16 +1,18 @@
1
- # @gmvjs/core — ядро Game Model Viewer
1
+ # @gmvjs/core — the Game Model Viewer core
2
2
 
3
- Ядро библиотеки чтения игровых моделей. Здесь нет ни одного игрового формата: только API плагинов, общие типы модели, реестр с автоопределением формата, сессия просмотра, архивы, текстуры и вспомогательная математика. Игры подключаются пакетами движков (`@gmvjs/xray-v1`, `@gmvjs/source1`…), рендер — пакетом `@gmvjs/renderer`.
3
+ [![Patreon](https://c5.patreon.com/external/logo/become_a_patron_button.png)](https://www.patreon.com/cw/shibisty)
4
4
 
5
- Работает в браузере, в Web Worker (без DOM) и в Node ≥ 20. Только ESM, `sideEffects: false`.
5
+ The core of a library for reading game models. It has no game formats at all: only the plugin API, shared model types, a registry with format detection, a viewing session, archives, textures and helper math. Games are added with engine packages (`@gmvjs/xray-v1`, `@gmvjs/source1`…), rendering with the `@gmvjs/renderer` package.
6
6
 
7
- ## Установка
7
+ Runs in the browser, in a Web Worker (no DOM) and in Node ≥ 20. ESM only, `sideEffects: false`.
8
+
9
+ ## Installation
8
10
 
9
11
  ```sh
10
12
  npm i @gmvjs/core
11
13
  ```
12
14
 
13
- ## Быстрый старт
15
+ ## Quick start
14
16
 
15
17
  ```ts
16
18
  import { createRegistry, ViewerSession } from '@gmvjs/core';
@@ -28,7 +30,7 @@ await session.addArchives([meshesBsa, texturesBsa]);
28
30
  await session.openEntry(session.archiveModels('chair').items[0]);
29
31
  ```
30
32
 
31
- Только разбор файла, без сессии (Node, Worker):
33
+ Parsing a file only, without a session (Node, Worker):
32
34
 
33
35
  ```ts
34
36
  import { createRegistry } from '@gmvjs/core';
@@ -39,7 +41,7 @@ const det = await reg.detect(file); // { format, confidence, candidates }
39
41
  const model = await det!.format.load(file, { files: [file], archives: [], sibling: async () => null, readPath: async () => null });
40
42
  ```
41
43
 
42
- ## Свой движок
44
+ ## Your own engine
43
45
 
44
46
  ```ts
45
47
  import { defineEngine, createRegistry, binary, type Model } from '@gmvjs/core';
@@ -52,50 +54,54 @@ export const myEngine = defineEngine({
52
54
  sniff: (head) => (binary.hasMagic(head, 'MYM1') ? 1 : 0),
53
55
  load: async (main) => (await import('./parser')).parse(await main.arrayBuffer()) as Model,
54
56
  }],
55
- games: [{ id: 'my-game', group: 'Игры', title: 'My Game', engine: 'my', formats: ['my-mesh'] }],
57
+ games: [{ id: 'my-game', group: 'Games', title: 'My Game', engine: 'my', formats: ['my-mesh'] }],
56
58
  i18n: { en: { title: 'My Engine', presets: { 'my-game': { title: 'My Game' } } } },
57
59
  });
58
60
  ```
59
61
 
60
- Пакет движка по соглашению экспортирует `<id>Engine` из корня и по подпути на каждую игру: `@gmvjs/<движок>/<игра>` → `{ preset, engine }`, где `engine` — тот же движок, ограниченный пресетом этой игры (`pickGames(engine, id)`).
62
+ By convention an engine package exports `<id>Engine` from its root and a subpath per game: `@gmvjs/<engine>/<game>` → `{ preset, engine }`, where `engine` is the same engine limited to that game's preset (`pickGames(engine, id)`).
61
63
 
62
- ## Что внутри
64
+ ## What's inside
63
65
 
64
- | Модуль | Назначение |
66
+ | Module | Purpose |
65
67
  |---|---|
66
- | `types` | `Model`, `Mesh`, `Motion` (`NodeMotion`, `VertexMotion`, расширяемые движками), `SkeletonRuntime`, `Lightmap`, `ScreenFx`, `InstancedScene`, `Archive`, `VFile` |
67
- | `plugin` | `defineEngine`, `EnginePlugin`, `FormatPlugin`, `ArchivePlugin`, `ImageCodec`, `TextureDictionaryPlugin`, `MaterialResolver`, `ExporterPlugin`, `GamePreset`; новое — `pickGames()` |
68
- | `registry` | `Registry`, `createRegistry()`: сбор плагинов, автоопределение по расширению, пути и первым байтам, пресет по формату и маркерам папки |
69
- | `session` | `ViewerSession`: файлы, «Папка игры», архивы, загрузка модели, текстуры, нормали, PBR, внешние анимации, события `status` / `model` / `preset`; `loadSameDirMotions` |
70
- | `textures` | `TextureLibrary`: поиск текстур по имени во всех открытых архивах, декодирование через кодеки движков |
71
- | `normals`, `surface` | касательные, распознавание упаковки карт нормалей (RGB / RG / AG), сведение PBR-карт |
72
- | `skeleton` | `NodeSkeleton` — поза для иерархий узлов: ключи, слои, аддитивные дорожки, смешивание по сетке |
73
- | `scene`, `levelbuilder` | дерево сцены, выбор лучом, перемещение экземпляров; сборка уровня из экземпляров по материалам |
74
- | `archives` | `createArchive`, папка (`openFolder`), ZIP (`openZip`; PK3/PK4/IWD/PAK) |
68
+ | `types` | `Model`, `Mesh`, `Motion` (`NodeMotion`, `VertexMotion`, extensible by engines), `SkeletonRuntime`, `Lightmap`, `ScreenFx`, `InstancedScene`, `Archive`, `VFile` |
69
+ | `plugin` | `defineEngine`, `EnginePlugin`, `FormatPlugin`, `ArchivePlugin`, `ImageCodec`, `TextureDictionaryPlugin`, `MaterialResolver`, `ExporterPlugin`, `GamePreset`, `pickGames()` |
70
+ | `registry` | `Registry`, `createRegistry()`: plugin collection, detection by extension, path and first bytes, preset by format and folder markers |
71
+ | `session` | `ViewerSession`: files, "Game folder", archives, model loading, textures, normals, PBR, external animations, `status` / `model` / `preset` events; `loadSameDirMotions` |
72
+ | `textures` | `TextureLibrary`: texture lookup by name across all open archives, decoding through engine codecs |
73
+ | `normals`, `surface` | tangents, normal map packing detection (RGB / RG / AG), combining PBR maps |
74
+ | `skeleton` | `NodeSkeleton`: poses for node hierarchies: keys, layers, additive tracks, grid blending |
75
+ | `scene`, `levelbuilder` | scene tree, ray picking, moving instances; building a level from instances by material |
76
+ | `archives` | `createArchive`, folder (`openFolder`), ZIP (`openZip`; PK3/PK4/IWD/PAK) |
75
77
  | `codecs` | zlib / raw deflate (`DecompressionStream`) |
76
- | `images` | DDS (DXT1–5, несжатые), TGA, PCX; декодеры BC4/BC5 (`decodeBC45`) и BC7 (`decodeBC7`) для движков |
77
- | `writers` | ZIP, DDS (RGBA), OBJ + MTL (выгрузка `obj`) |
78
- | `i18n` | `setLocale('ru' / 'en')`, `tr()`, `presetText()`, `engineTitle()`, `exporterText()`: английские тексты пресетов берутся из `EnginePlugin.i18n` |
79
- | `diagnostics` | `buildDiagnostics`, `hexDump` — отчёт о неудачной загрузке |
80
- | `binary`, `math`, `geometry` | чтение двоичных данных, матрицы и кватернионы, рамки и нормали |
81
- | `builtin` | `coreEngine`: архив ZIP, кодеки DDS/TGA/PCX, выгрузка OBJ — подключается `createRegistry` всегда |
78
+ | `images` | DDS (DXT1–5, uncompressed), TGA, PCX; BC4/BC5 (`decodeBC45`) and BC7 (`decodeBC7`) decoders for engines |
79
+ | `writers` | ZIP, DDS (RGBA), OBJ + MTL (the `obj` export) |
80
+ | `i18n` | `setLocale('ru' / 'en')`, `tr()`, `presetText()`, `engineTitle()`, `exporterText()`: English preset texts come from `EnginePlugin.i18n` |
81
+ | `diagnostics` | `buildDiagnostics`, `hexDump`: a report about a failed load |
82
+ | `binary`, `math`, `geometry` | reading binary data, matrices and quaternions, bounds and normals |
83
+ | `builtin` | `coreEngine`: the ZIP archive, DDS/TGA/PCX codecs, OBJ export; `createRegistry` always attaches it |
82
84
 
83
- ## Особенности API
85
+ ## API notes
84
86
 
85
- - **`EnginePlugin.i18n`** — `{ en?: { title?, groups?, presets?: Record<id, { title?, hint?, archiveLabel?, motionLabel? }>, exporters?: Record<id, { title?, description?, fields? }> } }`. Реестр подключает тексты вместе с движком; `setLocale('en')`, затем `presetText` / `engineTitle` / `exporterText` отдают английские тексты.
86
- - **`MotionKinds`** — виды анимаций по полю `kind` (в ядре `node` и `vertex`). Движок добавляет свой вид расширением интерфейса, и сужение по `m.kind` работает в коде приложения:
87
+ - **`EnginePlugin.i18n`** — `{ en?: { title?, groups?, presets?: Record<id, { title?, hint?, archiveLabel?, motionLabel? }>, exporters?: Record<id, { title?, description?, fields? }> } }`. The registry attaches the texts together with the engine; after `setLocale('en')`, `presetText` / `engineTitle` / `exporterText` return English texts.
88
+ - **`MotionKinds`** — animation kinds by the `kind` field (the core has `node` and `vertex`). An engine adds its own kind by extending the interface, and narrowing by `m.kind` works in application code:
87
89
 
88
90
  ```ts
89
91
  declare module '@gmvjs/core/types' { interface MotionKinds { xray: XrayMotion } }
90
92
  ```
91
- - **`pickGames(engine, ...ids)`** — движок только с выбранными пресетами и форматами, на которые они ссылаются (архивы, кодеки и выгрузка — все). На нём построены подпути `@gmvjs/<движок>/<игра>`. Два среза одного движка в одном реестре объединяются.
92
- - **`Registry.presetAvailable(preset)`** — есть ли формат пресета в реестре. Пресет-мост с форматом другого пакета (`@gmvjs/source2/gltf`, `@gmvjs/cryengine3/dae` → `@gmvjs/assimp`) без него недоступен, но реестр не ломается.
93
- - **Подпути модулей** `@gmvjs/core/<модуль>` (`binary`, `geometry`, `math`, `levelbuilder`, `images/dds`, `archives/common`…) — для пакетов движков.
93
+ - **`pickGames(engine, ...ids)`** — an engine with only the chosen presets and the formats they reference (archives, codecs and export: all of them). The `@gmvjs/<engine>/<game>` subpaths are built on it. Two slices of one engine in the same registry are merged.
94
+ - **`Registry.presetAvailable(preset)`** — whether the registry has the preset's format. A bridge preset with another package's format (`@gmvjs/source2/gltf`, `@gmvjs/cryengine3/dae` → `@gmvjs/assimp`) is unavailable without it, but the registry does not break.
95
+ - **Module subpaths** `@gmvjs/core/<module>` (`binary`, `geometry`, `math`, `levelbuilder`, `images/dds`, `archives/common`…) are for engine packages.
94
96
 
95
- ## Зависимости
97
+ ## Dependencies
96
98
 
97
- Нет.
99
+ None.
98
100
 
99
- ## Лицензия
101
+ ## License
100
102
 
101
103
  MIT
104
+
105
+ [![Patreon](https://c5.patreon.com/external/logo/become_a_patron_button.png)](https://www.patreon.com/cw/shibisty)
106
+
107
+ If this project helps you, consider supporting its development on Patreon ❤️
@@ -1,5 +1,5 @@
1
1
  import type { Archive, ArchiveEntry, BlobFile } from '../types';
2
- /** Расширения, по которым запись попадает в индекс текстур (путь без расширения). */
2
+ /** Extensions that put an entry into the texture index (path without extension). */
3
3
  export declare const TEXTURE_RE: RegExp;
4
4
  export interface ArchiveSpec<E extends ArchiveEntry> {
5
5
  name: string;
@@ -7,22 +7,22 @@ export interface ArchiveSpec<E extends ArchiveEntry> {
7
7
  version: number;
8
8
  entries: E[];
9
9
  read(entry: E): Promise<Uint8Array>;
10
- /** Своя функция поиска (например, по суффиксу пути). */
10
+ /** Custom lookup function (for example, by path suffix). */
11
11
  lookup?(path: string): E | null;
12
12
  byBase?: Map<string, E>;
13
- /** Дополнительные ключи текстур (по умолчанию — полный путь без расширения). */
13
+ /** Extra texture keys (default: the full path without extension). */
14
14
  textureKeys?(entry: E): string[];
15
- /** Имя файла для показа (по умолчанию — последний сегмент пути). */
15
+ /** Display file name (default: the last path segment). */
16
16
  displayName?(entry: E): string;
17
- /** При повторе ключа текстуры оставлять первую запись (по умолчанию — последнюю, как в архивах игр). */
17
+ /** On a duplicate texture key keep the first entry (default: the last one, as in game archives). */
18
18
  keepFirstTextureKey?: boolean;
19
- /** Своё содержимое файла (без копии через read). */
19
+ /** Custom file contents (without a copy via read). */
20
20
  fileBytes?(entry: E): Promise<ArrayBuffer>;
21
- /** Исходный файл записи (папка). */
21
+ /** Source file of the entry (folder). */
22
22
  blobOf?(entry: E): BlobFile | null;
23
- /** Текстура по точному пути, которой нет в списке записей. */
23
+ /** Texture at an exact path that is not in the entry list. */
24
24
  findTexture?(path: string): E | null;
25
25
  }
26
26
  export declare function createArchive<E extends ArchiveEntry>(spec: ArchiveSpec<E>): Archive;
27
- /** Прочитать диапазон байт файла. */
27
+ /** Read a byte range of the file. */
28
28
  export declare const readRange: (file: Blob, start: number, end: number) => Promise<Uint8Array>;
@@ -1 +1 @@
1
- {"version":3,"file":"common.js","sources":["../../src/archives/common.ts"],"sourcesContent":["// Общая обвязка архива: индексы путей и текстур, «файл-подобные» объекты записей.\nimport { baseName, stripExt, toArrayBuffer } from '../binary';\nimport type { Archive, ArchiveEntry, BlobFile, VFile } from '../types';\n\n/** Расширения, по которым запись попадает в индекс текстур (путь без расширения). */\nexport const TEXTURE_RE = /\\.(dds|tga|png|jpe?g|pcx|bmp|webp|paa|pac|pct|tex|xbt)$/;\n\nexport interface ArchiveSpec<E extends ArchiveEntry> {\n name: string;\n kind: string;\n version: number;\n entries: E[];\n read(entry: E): Promise<Uint8Array>;\n /** Своя функция поиска (например, по суффиксу пути). */\n lookup?(path: string): E | null;\n byBase?: Map<string, E>;\n /** Дополнительные ключи текстур (по умолчанию — полный путь без расширения). */\n textureKeys?(entry: E): string[];\n /** Имя файла для показа (по умолчанию — последний сегмент пути). */\n displayName?(entry: E): string;\n /** При повторе ключа текстуры оставлять первую запись (по умолчанию — последнюю, как в архивах игр). */\n keepFirstTextureKey?: boolean;\n /** Своё содержимое файла (без копии через read). */\n fileBytes?(entry: E): Promise<ArrayBuffer>;\n /** Исходный файл записи (папка). */\n blobOf?(entry: E): BlobFile | null;\n /** Текстура по точному пути, которой нет в списке записей. */\n findTexture?(path: string): E | null;\n}\n\nexport function createArchive<E extends ArchiveEntry>(spec: ArchiveSpec<E>): Archive {\n const index = new Map<string, ArchiveEntry>(), byKey = new Map<string, ArchiveEntry>(), files = new Map<ArchiveEntry, VFile>();\n for (const e of spec.entries) {\n index.set(e.path, e); // повторный путь (моды, дописанные архивы): побеждает последняя запись\n if (TEXTURE_RE.test(e.path)) {\n for (const k of spec.textureKeys ? spec.textureKeys(e) : [stripExt(e.path)]) if (!spec.keepFirstTextureKey || !byKey.has(k)) byKey.set(k, e);\n }\n }\n const arch: Archive = {\n name: spec.name,\n kind: spec.kind,\n version: spec.version,\n entries: spec.entries,\n index,\n byKey,\n byBase: spec.byBase,\n lookup: (p) => (spec.lookup ? spec.lookup(p) : (index.get(p) as E | undefined) ?? null),\n read: (e) => spec.read(e as E),\n blobOf: spec.blobOf ? (e) => spec.blobOf!(e as E) : undefined,\n findTexture: spec.findTexture ? (p) => spec.findTexture!(p) : undefined,\n fileOf(e) {\n let f = files.get(e);\n if (!f) {\n f = {\n name: spec.displayName ? spec.displayName(e as E) : baseName(e.path),\n path: e.path,\n archive: arch,\n arrayBuffer: spec.fileBytes ? () => spec.fileBytes!(e as E) : async () => toArrayBuffer(await spec.read(e as E)),\n };\n files.set(e, f);\n }\n return f;\n },\n };\n return arch;\n}\n\n/** Прочитать диапазон байт файла. */\nexport const readRange = async (file: Blob, start: number, end: number): Promise<Uint8Array> =>\n new Uint8Array(await file.slice(start, end).arrayBuffer());\n"],"names":[],"mappings":";AAKO,MAAM,aAAa;AAyBnB,SAAS,cAAsC,MAA+B;AACnF,QAAM,QAAQ,oBAAI,OAA6B,4BAAY,IAAA,GAA6B,QAAQ,oBAAI,IAAA;AACpG,aAAW,KAAK,KAAK,SAAS;AAC5B,UAAM,IAAI,EAAE,MAAM,CAAC;AACnB,QAAI,WAAW,KAAK,EAAE,IAAI,GAAG;AAC3B,iBAAW,KAAK,KAAK,cAAc,KAAK,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,EAAG,KAAI,CAAC,KAAK,uBAAuB,CAAC,MAAM,IAAI,CAAC,EAAG,OAAM,IAAI,GAAG,CAAC;AAAA,IAC7I;AAAA,EACF;AACA,QAAM,OAAgB;AAAA,IACpB,MAAM,KAAK;AAAA,IACX,MAAM,KAAK;AAAA,IACX,SAAS,KAAK;AAAA,IACd,SAAS,KAAK;AAAA,IACd;AAAA,IACA;AAAA,IACA,QAAQ,KAAK;AAAA,IACb,QAAQ,CAAC,MAAO,KAAK,SAAS,KAAK,OAAO,CAAC,IAAK,MAAM,IAAI,CAAC,KAAuB;AAAA,IAClF,MAAM,CAAC,MAAM,KAAK,KAAK,CAAM;AAAA,IAC7B,QAAQ,KAAK,SAAS,CAAC,MAAM,KAAK,OAAQ,CAAM,IAAI;AAAA,IACpD,aAAa,KAAK,cAAc,CAAC,MAAM,KAAK,YAAa,CAAC,IAAI;AAAA,IAC9D,OAAO,GAAG;AACR,UAAI,IAAI,MAAM,IAAI,CAAC;AACnB,UAAI,CAAC,GAAG;AACN,YAAI;AAAA,UACF,MAAM,KAAK,cAAc,KAAK,YAAY,CAAM,IAAI,SAAS,EAAE,IAAI;AAAA,UACnE,MAAM,EAAE;AAAA,UACR,SAAS;AAAA,UACT,aAAa,KAAK,YAAY,MAAM,KAAK,UAAW,CAAM,IAAI,YAAY,cAAc,MAAM,KAAK,KAAK,CAAM,CAAC;AAAA,QAAA;AAEjH,cAAM,IAAI,GAAG,CAAC;AAAA,MAChB;AACA,aAAO;AAAA,IACT;AAAA,EAAA;AAEF,SAAO;AACT;AAGO,MAAM,YAAY,OAAO,MAAY,OAAe,QACzD,IAAI,WAAW,MAAM,KAAK,MAAM,OAAO,GAAG,EAAE,aAAa;"}
1
+ {"version":3,"file":"common.js","sources":["../../src/archives/common.ts"],"sourcesContent":["// Common archive plumbing: path and texture indexes, file-like entry objects.\nimport { baseName, stripExt, toArrayBuffer } from '../binary';\nimport type { Archive, ArchiveEntry, BlobFile, VFile } from '../types';\n\n/** Extensions that put an entry into the texture index (path without extension). */\nexport const TEXTURE_RE = /\\.(dds|tga|png|jpe?g|pcx|bmp|webp|paa|pac|pct|tex|xbt)$/;\n\nexport interface ArchiveSpec<E extends ArchiveEntry> {\n name: string;\n kind: string;\n version: number;\n entries: E[];\n read(entry: E): Promise<Uint8Array>;\n /** Custom lookup function (for example, by path suffix). */\n lookup?(path: string): E | null;\n byBase?: Map<string, E>;\n /** Extra texture keys (default: the full path without extension). */\n textureKeys?(entry: E): string[];\n /** Display file name (default: the last path segment). */\n displayName?(entry: E): string;\n /** On a duplicate texture key keep the first entry (default: the last one, as in game archives). */\n keepFirstTextureKey?: boolean;\n /** Custom file contents (without a copy via read). */\n fileBytes?(entry: E): Promise<ArrayBuffer>;\n /** Source file of the entry (folder). */\n blobOf?(entry: E): BlobFile | null;\n /** Texture at an exact path that is not in the entry list. */\n findTexture?(path: string): E | null;\n}\n\nexport function createArchive<E extends ArchiveEntry>(spec: ArchiveSpec<E>): Archive {\n const index = new Map<string, ArchiveEntry>(), byKey = new Map<string, ArchiveEntry>(), files = new Map<ArchiveEntry, VFile>();\n for (const e of spec.entries) {\n index.set(e.path, e); // duplicate path (mods, appended archives): the last entry wins\n if (TEXTURE_RE.test(e.path)) {\n for (const k of spec.textureKeys ? spec.textureKeys(e) : [stripExt(e.path)]) if (!spec.keepFirstTextureKey || !byKey.has(k)) byKey.set(k, e);\n }\n }\n const arch: Archive = {\n name: spec.name,\n kind: spec.kind,\n version: spec.version,\n entries: spec.entries,\n index,\n byKey,\n byBase: spec.byBase,\n lookup: (p) => (spec.lookup ? spec.lookup(p) : (index.get(p) as E | undefined) ?? null),\n read: (e) => spec.read(e as E),\n blobOf: spec.blobOf ? (e) => spec.blobOf!(e as E) : undefined,\n findTexture: spec.findTexture ? (p) => spec.findTexture!(p) : undefined,\n fileOf(e) {\n let f = files.get(e);\n if (!f) {\n f = {\n name: spec.displayName ? spec.displayName(e as E) : baseName(e.path),\n path: e.path,\n archive: arch,\n arrayBuffer: spec.fileBytes ? () => spec.fileBytes!(e as E) : async () => toArrayBuffer(await spec.read(e as E)),\n };\n files.set(e, f);\n }\n return f;\n },\n };\n return arch;\n}\n\n/** Read a byte range of the file. */\nexport const readRange = async (file: Blob, start: number, end: number): Promise<Uint8Array> =>\n new Uint8Array(await file.slice(start, end).arrayBuffer());\n"],"names":[],"mappings":";AAKO,MAAM,aAAa;AAyBnB,SAAS,cAAsC,MAA+B;AACnF,QAAM,QAAQ,oBAAI,OAA6B,4BAAY,IAAA,GAA6B,QAAQ,oBAAI,IAAA;AACpG,aAAW,KAAK,KAAK,SAAS;AAC5B,UAAM,IAAI,EAAE,MAAM,CAAC;AACnB,QAAI,WAAW,KAAK,EAAE,IAAI,GAAG;AAC3B,iBAAW,KAAK,KAAK,cAAc,KAAK,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,EAAG,KAAI,CAAC,KAAK,uBAAuB,CAAC,MAAM,IAAI,CAAC,EAAG,OAAM,IAAI,GAAG,CAAC;AAAA,IAC7I;AAAA,EACF;AACA,QAAM,OAAgB;AAAA,IACpB,MAAM,KAAK;AAAA,IACX,MAAM,KAAK;AAAA,IACX,SAAS,KAAK;AAAA,IACd,SAAS,KAAK;AAAA,IACd;AAAA,IACA;AAAA,IACA,QAAQ,KAAK;AAAA,IACb,QAAQ,CAAC,MAAO,KAAK,SAAS,KAAK,OAAO,CAAC,IAAK,MAAM,IAAI,CAAC,KAAuB;AAAA,IAClF,MAAM,CAAC,MAAM,KAAK,KAAK,CAAM;AAAA,IAC7B,QAAQ,KAAK,SAAS,CAAC,MAAM,KAAK,OAAQ,CAAM,IAAI;AAAA,IACpD,aAAa,KAAK,cAAc,CAAC,MAAM,KAAK,YAAa,CAAC,IAAI;AAAA,IAC9D,OAAO,GAAG;AACR,UAAI,IAAI,MAAM,IAAI,CAAC;AACnB,UAAI,CAAC,GAAG;AACN,YAAI;AAAA,UACF,MAAM,KAAK,cAAc,KAAK,YAAY,CAAM,IAAI,SAAS,EAAE,IAAI;AAAA,UACnE,MAAM,EAAE;AAAA,UACR,SAAS;AAAA,UACT,aAAa,KAAK,YAAY,MAAM,KAAK,UAAW,CAAM,IAAI,YAAY,cAAc,MAAM,KAAK,KAAK,CAAM,CAAC;AAAA,QAAA;AAEjH,cAAM,IAAI,GAAG,CAAC;AAAA,MAChB;AACA,aAAO;AAAA,IACT;AAAA,EAAA;AAEF,SAAO;AACT;AAGO,MAAM,YAAY,OAAO,MAAY,OAAe,QACzD,IAAI,WAAW,MAAM,KAAK,MAAM,OAAO,GAAG,EAAE,aAAa;"}
@@ -1,4 +1,4 @@
1
1
  import type { Archive, BlobFile } from '../types';
2
2
  export declare function openFolder(files: readonly BlobFile[]): Archive;
3
- /** То же, но кусками по chunk файлов с передачей управления браузеру (большие папки игр — сотни тысяч файлов). */
3
+ /** The same, but in chunks of `chunk` files, yielding to the browser (large game folders have hundreds of thousands of files). */
4
4
  export declare function openFolderAsync(files: readonly BlobFile[], onProgress?: (done: number, total: number) => void, chunk?: number): Promise<Archive>;
@@ -34,7 +34,7 @@ class FolderIndex {
34
34
  blobOf: (e) => e.src,
35
35
  displayName: (e) => e.src.name,
36
36
  keepFirstTextureKey: true,
37
- // ключи по всем суффиксам пути: a/b/c, b/c, c — без привязки к корню папки
37
+ // keys for every path suffix: a/b/c, b/c, c — not tied to the folder root
38
38
  textureKeys: (e) => {
39
39
  const parts = stripExt(e.path).split("/");
40
40
  return TEXTURE_RE.test(e.path) ? parts.map((_, i) => parts.slice(i).join("/")) : [];
@@ -1 +1 @@
1
- {"version":3,"file":"folder.js","sources":["../../src/archives/folder.ts"],"sourcesContent":["// «Папка игры»: loose-файлы выбранной папки как виртуальный архив (поиск по суффиксу пути, без чтения содержимого).\nimport { stripExt } from '../binary';\nimport type { Archive, ArchiveEntry, BlobFile } from '../types';\nimport { createArchive, TEXTURE_RE } from './common';\n\ninterface FolderEntry extends ArchiveEntry { src: BlobFile }\n\nconst relPath = (f: BlobFile): string => (f.webkitRelativePath || f.name).replace(/\\\\/g, '/');\n\n/** Индекс папки: пути, полные и по суффиксам (собирается кусками — см. openFolderAsync). */\nclass FolderIndex {\n entries: FolderEntry[] = []; full = new Map<string, FolderEntry>(); byBase = new Map<string, FolderEntry>(); bySuffix = new Map<string, FolderEntry>(); root = '';\n add(f: BlobFile): void {\n const rel = relPath(f), path = rel.toLowerCase();\n if (!this.root && rel.includes('/')) this.root = rel.split('/')[0]!;\n const e: FolderEntry = { path, size: f.size, src: f };\n this.entries.push(e);\n if (!this.full.has(path)) this.full.set(path, e);\n const sp = path.split('/'), base = sp[sp.length - 1]!;\n if (!this.byBase.has(base)) this.byBase.set(base, e);\n for (let i = 0; i < sp.length; i++) { const k = sp.slice(i).join('/'); if (!this.bySuffix.has(k)) this.bySuffix.set(k, e); }\n }\n archive(): Archive {\n const { full, bySuffix } = this;\n return createArchive<FolderEntry>({\n name: this.root || 'папка',\n kind: 'folder',\n version: 0,\n entries: this.entries,\n byBase: this.byBase,\n lookup: (p) => full.get(p) ?? bySuffix.get(p) ?? null,\n read: async (e) => new Uint8Array(await e.src.arrayBuffer()),\n fileBytes: (e) => e.src.arrayBuffer(),\n blobOf: (e) => e.src,\n displayName: (e) => e.src.name,\n keepFirstTextureKey: true,\n // ключи по всем суффиксам пути: a/b/c, b/c, c — без привязки к корню папки\n textureKeys: (e) => {\n const parts = stripExt(e.path).split('/');\n return TEXTURE_RE.test(e.path) ? parts.map((_, i) => parts.slice(i).join('/')) : [];\n },\n });\n }\n}\n\nexport function openFolder(files: readonly BlobFile[]): Archive {\n const ix = new FolderIndex();\n for (const f of files) ix.add(f);\n return ix.archive();\n}\n\n/** То же, но кусками по chunk файлов с передачей управления браузеру (большие папки игр — сотни тысяч файлов). */\nexport async function openFolderAsync(files: readonly BlobFile[], onProgress?: (done: number, total: number) => void, chunk = 5000): Promise<Archive> {\n const ix = new FolderIndex();\n for (let i = 0; i < files.length; i += chunk) {\n onProgress?.(i, files.length);\n await new Promise((r) => setTimeout(r, 0));\n for (let k = i; k < Math.min(files.length, i + chunk); k++) ix.add(files[k]!);\n }\n onProgress?.(files.length, files.length);\n await new Promise((r) => setTimeout(r, 0));\n return ix.archive();\n}\n"],"names":[],"mappings":";;AAOA,MAAM,UAAU,CAAC,OAAyB,EAAE,sBAAsB,EAAE,MAAM,QAAQ,OAAO,GAAG;AAG5F,MAAM,YAAY;AAAA,EAChB,UAAyB,CAAA;AAAA,EAAI,2BAAW,IAAA;AAAA,EAA4B,6BAAa,IAAA;AAAA,EAA4B,+BAAe,IAAA;AAAA,EAA4B,OAAO;AAAA,EAC/J,IAAI,GAAmB;AACrB,UAAM,MAAM,QAAQ,CAAC,GAAG,OAAO,IAAI,YAAA;AACnC,QAAI,CAAC,KAAK,QAAQ,IAAI,SAAS,GAAG,EAAG,MAAK,OAAO,IAAI,MAAM,GAAG,EAAE,CAAC;AACjE,UAAM,IAAiB,EAAE,MAAM,MAAM,EAAE,MAAM,KAAK,EAAA;AAClD,SAAK,QAAQ,KAAK,CAAC;AACnB,QAAI,CAAC,KAAK,KAAK,IAAI,IAAI,EAAG,MAAK,KAAK,IAAI,MAAM,CAAC;AAC/C,UAAM,KAAK,KAAK,MAAM,GAAG,GAAG,OAAO,GAAG,GAAG,SAAS,CAAC;AACnD,QAAI,CAAC,KAAK,OAAO,IAAI,IAAI,EAAG,MAAK,OAAO,IAAI,MAAM,CAAC;AACnD,aAAS,IAAI,GAAG,IAAI,GAAG,QAAQ,KAAK;AAAE,YAAM,IAAI,GAAG,MAAM,CAAC,EAAE,KAAK,GAAG;AAAG,UAAI,CAAC,KAAK,SAAS,IAAI,CAAC,EAAG,MAAK,SAAS,IAAI,GAAG,CAAC;AAAA,IAAG;AAAA,EAC7H;AAAA,EACA,UAAmB;AACjB,UAAM,EAAE,MAAM,SAAA,IAAa;AAC3B,WAAO,cAA2B;AAAA,MAChC,MAAM,KAAK,QAAQ;AAAA,MACnB,MAAM;AAAA,MACN,SAAS;AAAA,MACT,SAAS,KAAK;AAAA,MACd,QAAQ,KAAK;AAAA,MACb,QAAQ,CAAC,MAAM,KAAK,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK;AAAA,MACjD,MAAM,OAAO,MAAM,IAAI,WAAW,MAAM,EAAE,IAAI,aAAa;AAAA,MAC3D,WAAW,CAAC,MAAM,EAAE,IAAI,YAAA;AAAA,MACxB,QAAQ,CAAC,MAAM,EAAE;AAAA,MACjB,aAAa,CAAC,MAAM,EAAE,IAAI;AAAA,MAC1B,qBAAqB;AAAA;AAAA,MAErB,aAAa,CAAC,MAAM;AAClB,cAAM,QAAQ,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG;AACxC,eAAO,WAAW,KAAK,EAAE,IAAI,IAAI,MAAM,IAAI,CAAC,GAAG,MAAM,MAAM,MAAM,CAAC,EAAE,KAAK,GAAG,CAAC,IAAI,CAAA;AAAA,MACnF;AAAA,IAAA,CACD;AAAA,EACH;AACF;AAEO,SAAS,WAAW,OAAqC;AAC9D,QAAM,KAAK,IAAI,YAAA;AACf,aAAW,KAAK,MAAO,IAAG,IAAI,CAAC;AAC/B,SAAO,GAAG,QAAA;AACZ;AAGA,eAAsB,gBAAgB,OAA4B,YAAoD,QAAQ,KAAwB;AACpJ,QAAM,KAAK,IAAI,YAAA;AACf,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,OAAO;AAC5C,iBAAa,GAAG,MAAM,MAAM;AAC5B,UAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC;AACzC,aAAS,IAAI,GAAG,IAAI,KAAK,IAAI,MAAM,QAAQ,IAAI,KAAK,GAAG,IAAK,IAAG,IAAI,MAAM,CAAC,CAAE;AAAA,EAC9E;AACA,eAAa,MAAM,QAAQ,MAAM,MAAM;AACvC,QAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC;AACzC,SAAO,GAAG,QAAA;AACZ;"}
1
+ {"version":3,"file":"folder.js","sources":["../../src/archives/folder.ts"],"sourcesContent":["// \"Game folder\": loose files of the selected folder as a virtual archive (lookup by path suffix, without reading contents).\nimport { stripExt } from '../binary';\nimport type { Archive, ArchiveEntry, BlobFile } from '../types';\nimport { createArchive, TEXTURE_RE } from './common';\n\ninterface FolderEntry extends ArchiveEntry { src: BlobFile }\n\nconst relPath = (f: BlobFile): string => (f.webkitRelativePath || f.name).replace(/\\\\/g, '/');\n\n/** Folder index: paths, full and by suffix (built in chunks, see openFolderAsync). */\nclass FolderIndex {\n entries: FolderEntry[] = []; full = new Map<string, FolderEntry>(); byBase = new Map<string, FolderEntry>(); bySuffix = new Map<string, FolderEntry>(); root = '';\n add(f: BlobFile): void {\n const rel = relPath(f), path = rel.toLowerCase();\n if (!this.root && rel.includes('/')) this.root = rel.split('/')[0]!;\n const e: FolderEntry = { path, size: f.size, src: f };\n this.entries.push(e);\n if (!this.full.has(path)) this.full.set(path, e);\n const sp = path.split('/'), base = sp[sp.length - 1]!;\n if (!this.byBase.has(base)) this.byBase.set(base, e);\n for (let i = 0; i < sp.length; i++) { const k = sp.slice(i).join('/'); if (!this.bySuffix.has(k)) this.bySuffix.set(k, e); }\n }\n archive(): Archive {\n const { full, bySuffix } = this;\n return createArchive<FolderEntry>({\n name: this.root || 'папка',\n kind: 'folder',\n version: 0,\n entries: this.entries,\n byBase: this.byBase,\n lookup: (p) => full.get(p) ?? bySuffix.get(p) ?? null,\n read: async (e) => new Uint8Array(await e.src.arrayBuffer()),\n fileBytes: (e) => e.src.arrayBuffer(),\n blobOf: (e) => e.src,\n displayName: (e) => e.src.name,\n keepFirstTextureKey: true,\n // keys for every path suffix: a/b/c, b/c, c — not tied to the folder root\n textureKeys: (e) => {\n const parts = stripExt(e.path).split('/');\n return TEXTURE_RE.test(e.path) ? parts.map((_, i) => parts.slice(i).join('/')) : [];\n },\n });\n }\n}\n\nexport function openFolder(files: readonly BlobFile[]): Archive {\n const ix = new FolderIndex();\n for (const f of files) ix.add(f);\n return ix.archive();\n}\n\n/** The same, but in chunks of `chunk` files, yielding to the browser (large game folders have hundreds of thousands of files). */\nexport async function openFolderAsync(files: readonly BlobFile[], onProgress?: (done: number, total: number) => void, chunk = 5000): Promise<Archive> {\n const ix = new FolderIndex();\n for (let i = 0; i < files.length; i += chunk) {\n onProgress?.(i, files.length);\n await new Promise((r) => setTimeout(r, 0));\n for (let k = i; k < Math.min(files.length, i + chunk); k++) ix.add(files[k]!);\n }\n onProgress?.(files.length, files.length);\n await new Promise((r) => setTimeout(r, 0));\n return ix.archive();\n}\n"],"names":[],"mappings":";;AAOA,MAAM,UAAU,CAAC,OAAyB,EAAE,sBAAsB,EAAE,MAAM,QAAQ,OAAO,GAAG;AAG5F,MAAM,YAAY;AAAA,EAChB,UAAyB,CAAA;AAAA,EAAI,2BAAW,IAAA;AAAA,EAA4B,6BAAa,IAAA;AAAA,EAA4B,+BAAe,IAAA;AAAA,EAA4B,OAAO;AAAA,EAC/J,IAAI,GAAmB;AACrB,UAAM,MAAM,QAAQ,CAAC,GAAG,OAAO,IAAI,YAAA;AACnC,QAAI,CAAC,KAAK,QAAQ,IAAI,SAAS,GAAG,EAAG,MAAK,OAAO,IAAI,MAAM,GAAG,EAAE,CAAC;AACjE,UAAM,IAAiB,EAAE,MAAM,MAAM,EAAE,MAAM,KAAK,EAAA;AAClD,SAAK,QAAQ,KAAK,CAAC;AACnB,QAAI,CAAC,KAAK,KAAK,IAAI,IAAI,EAAG,MAAK,KAAK,IAAI,MAAM,CAAC;AAC/C,UAAM,KAAK,KAAK,MAAM,GAAG,GAAG,OAAO,GAAG,GAAG,SAAS,CAAC;AACnD,QAAI,CAAC,KAAK,OAAO,IAAI,IAAI,EAAG,MAAK,OAAO,IAAI,MAAM,CAAC;AACnD,aAAS,IAAI,GAAG,IAAI,GAAG,QAAQ,KAAK;AAAE,YAAM,IAAI,GAAG,MAAM,CAAC,EAAE,KAAK,GAAG;AAAG,UAAI,CAAC,KAAK,SAAS,IAAI,CAAC,EAAG,MAAK,SAAS,IAAI,GAAG,CAAC;AAAA,IAAG;AAAA,EAC7H;AAAA,EACA,UAAmB;AACjB,UAAM,EAAE,MAAM,SAAA,IAAa;AAC3B,WAAO,cAA2B;AAAA,MAChC,MAAM,KAAK,QAAQ;AAAA,MACnB,MAAM;AAAA,MACN,SAAS;AAAA,MACT,SAAS,KAAK;AAAA,MACd,QAAQ,KAAK;AAAA,MACb,QAAQ,CAAC,MAAM,KAAK,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK;AAAA,MACjD,MAAM,OAAO,MAAM,IAAI,WAAW,MAAM,EAAE,IAAI,aAAa;AAAA,MAC3D,WAAW,CAAC,MAAM,EAAE,IAAI,YAAA;AAAA,MACxB,QAAQ,CAAC,MAAM,EAAE;AAAA,MACjB,aAAa,CAAC,MAAM,EAAE,IAAI;AAAA,MAC1B,qBAAqB;AAAA;AAAA,MAErB,aAAa,CAAC,MAAM;AAClB,cAAM,QAAQ,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG;AACxC,eAAO,WAAW,KAAK,EAAE,IAAI,IAAI,MAAM,IAAI,CAAC,GAAG,MAAM,MAAM,MAAM,CAAC,EAAE,KAAK,GAAG,CAAC,IAAI,CAAA;AAAA,MACnF;AAAA,IAAA,CACD;AAAA,EACH;AACF;AAEO,SAAS,WAAW,OAAqC;AAC9D,QAAM,KAAK,IAAI,YAAA;AACf,aAAW,KAAK,MAAO,IAAG,IAAI,CAAC;AAC/B,SAAO,GAAG,QAAA;AACZ;AAGA,eAAsB,gBAAgB,OAA4B,YAAoD,QAAQ,KAAwB;AACpJ,QAAM,KAAK,IAAI,YAAA;AACf,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,OAAO;AAC5C,iBAAa,GAAG,MAAM,MAAM;AAC5B,UAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC;AACzC,aAAS,IAAI,GAAG,IAAI,KAAK,IAAI,MAAM,QAAQ,IAAI,KAAK,GAAG,IAAK,IAAG,IAAI,MAAM,CAAC,CAAE;AAAA,EAC9E;AACA,eAAa,MAAM,QAAQ,MAAM,MAAM;AACvC,QAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC;AACzC,SAAO,GAAG,QAAA;AACZ;"}
@@ -1 +1 @@
1
- {"version":3,"file":"zip.js","sources":["../../src/archives/zip.ts"],"sourcesContent":["// ZIP (pk3/pk4 id Tech, .pak CryEngine, .zip): читается центральный каталог, данные — по требованию (store / deflate).\nimport { normPath } from '../binary';\nimport { inflateRaw } from '../codecs/zlib';\nimport type { Archive, ArchiveEntry, BlobFile } from '../types';\nimport { createArchive, readRange } from './common';\n\ninterface ZipEntry extends ArchiveEntry { method: number; comp: number; off: number }\n\nexport async function openZip(file: BlobFile): Promise<Archive> {\n const tailLen = Math.min(file.size, 65557), tail = await readRange(file, file.size - tailLen, file.size);\n const tdv = new DataView(tail.buffer, tail.byteOffset, tail.byteLength);\n let e = -1;\n for (let i = tail.length - 22; i >= 0; i--) if (tdv.getUint32(i, true) === 0x06054b50) { e = i; break; }\n if (e < 0) throw new Error(`${file.name}: не ZIP (нет конца каталога)`);\n const cdSize = tdv.getUint32(e + 12, true), cdOff = tdv.getUint32(e + 16, true);\n if (cdOff === 0xffffffff) throw new Error(`${file.name}: ZIP64 не поддерживается`);\n const u8 = await readRange(file, cdOff, cdOff + cdSize), dv = new DataView(u8.buffer, u8.byteOffset, u8.byteLength), entries: ZipEntry[] = [];\n for (let p = 0; p + 46 <= u8.length && dv.getUint32(p, true) === 0x02014b50;) {\n const method = dv.getUint16(p + 10, true), comp = dv.getUint32(p + 20, true), size = dv.getUint32(p + 24, true);\n const nl = dv.getUint16(p + 28, true), xl = dv.getUint16(p + 30, true), cl = dv.getUint16(p + 32, true), off = dv.getUint32(p + 42, true);\n let n = '';\n for (let i = 0; i < nl; i++) n += String.fromCharCode(u8[p + 46 + i]!);\n if (!n.endsWith('/')) entries.push({ path: normPath(n), method, comp, size, off });\n p += 46 + nl + xl + cl;\n }\n return createArchive<ZipEntry>({\n name: file.name,\n kind: 'zip',\n version: 0,\n entries,\n async read(en) {\n const lh = new DataView(await file.slice(en.off, en.off + 30).arrayBuffer());\n const start = en.off + 30 + lh.getUint16(26, true) + lh.getUint16(28, true);\n const data = await readRange(file, start, start + en.comp);\n if (en.method === 0) return data;\n if (en.method === 8) return inflateRaw(data);\n throw new Error('метод сжатия ZIP ' + en.method + ' не поддерживается');\n },\n });\n}\n"],"names":[],"mappings":";;;AAQA,eAAsB,QAAQ,MAAkC;AAC9D,QAAM,UAAU,KAAK,IAAI,KAAK,MAAM,KAAK,GAAG,OAAO,MAAM,UAAU,MAAM,KAAK,OAAO,SAAS,KAAK,IAAI;AACvG,QAAM,MAAM,IAAI,SAAS,KAAK,QAAQ,KAAK,YAAY,KAAK,UAAU;AACtE,MAAI,IAAI;AACR,WAAS,IAAI,KAAK,SAAS,IAAI,KAAK,GAAG,IAAK,KAAI,IAAI,UAAU,GAAG,IAAI,MAAM,WAAY;AAAE,QAAI;AAAG;AAAA,EAAO;AACvG,MAAI,IAAI,EAAG,OAAM,IAAI,MAAM,GAAG,KAAK,IAAI,+BAA+B;AACtE,QAAM,SAAS,IAAI,UAAU,IAAI,IAAI,IAAI,GAAG,QAAQ,IAAI,UAAU,IAAI,IAAI,IAAI;AAC9E,MAAI,UAAU,WAAY,OAAM,IAAI,MAAM,GAAG,KAAK,IAAI,2BAA2B;AACjF,QAAM,KAAK,MAAM,UAAU,MAAM,OAAO,QAAQ,MAAM,GAAG,KAAK,IAAI,SAAS,GAAG,QAAQ,GAAG,YAAY,GAAG,UAAU,GAAG,UAAsB,CAAA;AAC3I,WAAS,IAAI,GAAG,IAAI,MAAM,GAAG,UAAU,GAAG,UAAU,GAAG,IAAI,MAAM,YAAa;AAC5E,UAAM,SAAS,GAAG,UAAU,IAAI,IAAI,IAAI,GAAG,OAAO,GAAG,UAAU,IAAI,IAAI,IAAI,GAAG,OAAO,GAAG,UAAU,IAAI,IAAI,IAAI;AAC9G,UAAM,KAAK,GAAG,UAAU,IAAI,IAAI,IAAI,GAAG,KAAK,GAAG,UAAU,IAAI,IAAI,IAAI,GAAG,KAAK,GAAG,UAAU,IAAI,IAAI,IAAI,GAAG,MAAM,GAAG,UAAU,IAAI,IAAI,IAAI;AACxI,QAAI,IAAI;AACR,aAAS,IAAI,GAAG,IAAI,IAAI,IAAK,MAAK,OAAO,aAAa,GAAG,IAAI,KAAK,CAAC,CAAE;AACrE,QAAI,CAAC,EAAE,SAAS,GAAG,WAAW,KAAK,EAAE,MAAM,SAAS,CAAC,GAAG,QAAQ,MAAM,MAAM,KAAK;AACjF,SAAK,KAAK,KAAK,KAAK;AAAA,EACtB;AACA,SAAO,cAAwB;AAAA,IAC7B,MAAM,KAAK;AAAA,IACX,MAAM;AAAA,IACN,SAAS;AAAA,IACT;AAAA,IACA,MAAM,KAAK,IAAI;AACb,YAAM,KAAK,IAAI,SAAS,MAAM,KAAK,MAAM,GAAG,KAAK,GAAG,MAAM,EAAE,EAAE,aAAa;AAC3E,YAAM,QAAQ,GAAG,MAAM,KAAK,GAAG,UAAU,IAAI,IAAI,IAAI,GAAG,UAAU,IAAI,IAAI;AAC1E,YAAM,OAAO,MAAM,UAAU,MAAM,OAAO,QAAQ,GAAG,IAAI;AACzD,UAAI,GAAG,WAAW,EAAG,QAAO;AAC5B,UAAI,GAAG,WAAW,EAAG,QAAO,WAAW,IAAI;AAC3C,YAAM,IAAI,MAAM,sBAAsB,GAAG,SAAS,oBAAoB;AAAA,IACxE;AAAA,EAAA,CACD;AACH;"}
1
+ {"version":3,"file":"zip.js","sources":["../../src/archives/zip.ts"],"sourcesContent":["// ZIP (id Tech pk3/pk4, CryEngine .pak, .zip): the central directory is read, data on demand (store / deflate).\nimport { normPath } from '../binary';\nimport { inflateRaw } from '../codecs/zlib';\nimport type { Archive, ArchiveEntry, BlobFile } from '../types';\nimport { createArchive, readRange } from './common';\n\ninterface ZipEntry extends ArchiveEntry { method: number; comp: number; off: number }\n\nexport async function openZip(file: BlobFile): Promise<Archive> {\n const tailLen = Math.min(file.size, 65557), tail = await readRange(file, file.size - tailLen, file.size);\n const tdv = new DataView(tail.buffer, tail.byteOffset, tail.byteLength);\n let e = -1;\n for (let i = tail.length - 22; i >= 0; i--) if (tdv.getUint32(i, true) === 0x06054b50) { e = i; break; }\n if (e < 0) throw new Error(`${file.name}: не ZIP (нет конца каталога)`);\n const cdSize = tdv.getUint32(e + 12, true), cdOff = tdv.getUint32(e + 16, true);\n if (cdOff === 0xffffffff) throw new Error(`${file.name}: ZIP64 не поддерживается`);\n const u8 = await readRange(file, cdOff, cdOff + cdSize), dv = new DataView(u8.buffer, u8.byteOffset, u8.byteLength), entries: ZipEntry[] = [];\n for (let p = 0; p + 46 <= u8.length && dv.getUint32(p, true) === 0x02014b50;) {\n const method = dv.getUint16(p + 10, true), comp = dv.getUint32(p + 20, true), size = dv.getUint32(p + 24, true);\n const nl = dv.getUint16(p + 28, true), xl = dv.getUint16(p + 30, true), cl = dv.getUint16(p + 32, true), off = dv.getUint32(p + 42, true);\n let n = '';\n for (let i = 0; i < nl; i++) n += String.fromCharCode(u8[p + 46 + i]!);\n if (!n.endsWith('/')) entries.push({ path: normPath(n), method, comp, size, off });\n p += 46 + nl + xl + cl;\n }\n return createArchive<ZipEntry>({\n name: file.name,\n kind: 'zip',\n version: 0,\n entries,\n async read(en) {\n const lh = new DataView(await file.slice(en.off, en.off + 30).arrayBuffer());\n const start = en.off + 30 + lh.getUint16(26, true) + lh.getUint16(28, true);\n const data = await readRange(file, start, start + en.comp);\n if (en.method === 0) return data;\n if (en.method === 8) return inflateRaw(data);\n throw new Error('метод сжатия ZIP ' + en.method + ' не поддерживается');\n },\n });\n}\n"],"names":[],"mappings":";;;AAQA,eAAsB,QAAQ,MAAkC;AAC9D,QAAM,UAAU,KAAK,IAAI,KAAK,MAAM,KAAK,GAAG,OAAO,MAAM,UAAU,MAAM,KAAK,OAAO,SAAS,KAAK,IAAI;AACvG,QAAM,MAAM,IAAI,SAAS,KAAK,QAAQ,KAAK,YAAY,KAAK,UAAU;AACtE,MAAI,IAAI;AACR,WAAS,IAAI,KAAK,SAAS,IAAI,KAAK,GAAG,IAAK,KAAI,IAAI,UAAU,GAAG,IAAI,MAAM,WAAY;AAAE,QAAI;AAAG;AAAA,EAAO;AACvG,MAAI,IAAI,EAAG,OAAM,IAAI,MAAM,GAAG,KAAK,IAAI,+BAA+B;AACtE,QAAM,SAAS,IAAI,UAAU,IAAI,IAAI,IAAI,GAAG,QAAQ,IAAI,UAAU,IAAI,IAAI,IAAI;AAC9E,MAAI,UAAU,WAAY,OAAM,IAAI,MAAM,GAAG,KAAK,IAAI,2BAA2B;AACjF,QAAM,KAAK,MAAM,UAAU,MAAM,OAAO,QAAQ,MAAM,GAAG,KAAK,IAAI,SAAS,GAAG,QAAQ,GAAG,YAAY,GAAG,UAAU,GAAG,UAAsB,CAAA;AAC3I,WAAS,IAAI,GAAG,IAAI,MAAM,GAAG,UAAU,GAAG,UAAU,GAAG,IAAI,MAAM,YAAa;AAC5E,UAAM,SAAS,GAAG,UAAU,IAAI,IAAI,IAAI,GAAG,OAAO,GAAG,UAAU,IAAI,IAAI,IAAI,GAAG,OAAO,GAAG,UAAU,IAAI,IAAI,IAAI;AAC9G,UAAM,KAAK,GAAG,UAAU,IAAI,IAAI,IAAI,GAAG,KAAK,GAAG,UAAU,IAAI,IAAI,IAAI,GAAG,KAAK,GAAG,UAAU,IAAI,IAAI,IAAI,GAAG,MAAM,GAAG,UAAU,IAAI,IAAI,IAAI;AACxI,QAAI,IAAI;AACR,aAAS,IAAI,GAAG,IAAI,IAAI,IAAK,MAAK,OAAO,aAAa,GAAG,IAAI,KAAK,CAAC,CAAE;AACrE,QAAI,CAAC,EAAE,SAAS,GAAG,WAAW,KAAK,EAAE,MAAM,SAAS,CAAC,GAAG,QAAQ,MAAM,MAAM,KAAK;AACjF,SAAK,KAAK,KAAK,KAAK;AAAA,EACtB;AACA,SAAO,cAAwB;AAAA,IAC7B,MAAM,KAAK;AAAA,IACX,MAAM;AAAA,IACN,SAAS;AAAA,IACT;AAAA,IACA,MAAM,KAAK,IAAI;AACb,YAAM,KAAK,IAAI,SAAS,MAAM,KAAK,MAAM,GAAG,KAAK,GAAG,MAAM,EAAE,EAAE,aAAa;AAC3E,YAAM,QAAQ,GAAG,MAAM,KAAK,GAAG,UAAU,IAAI,IAAI,IAAI,GAAG,UAAU,IAAI,IAAI;AAC1E,YAAM,OAAO,MAAM,UAAU,MAAM,OAAO,QAAQ,GAAG,IAAI;AACzD,UAAI,GAAG,WAAW,EAAG,QAAO;AAC5B,UAAI,GAAG,WAAW,EAAG,QAAO,WAAW,IAAI;AAC3C,YAAM,IAAI,MAAM,sBAAsB,GAAG,SAAS,oBAAoB;AAAA,IACxE;AAAA,EAAA,CACD;AACH;"}
package/dist/binary.d.ts CHANGED
@@ -1,20 +1,20 @@
1
- /** ASCII-строка до нуля или до n байт. */
1
+ /** ASCII string up to a zero byte or n bytes. */
2
2
  export declare function cstr(u8: Uint8Array, start: number, maxLen: number): string;
3
- /** ASCII-строка в диапазоне [a, b) до первого нуля. */
3
+ /** ASCII string in [a, b) up to the first zero byte. */
4
4
  export declare function cstrRange(u8: Uint8Array, a: number, b: number): string;
5
- /** ASCII в диапазоне [a, b) без остановки на нуле. */
5
+ /** ASCII in [a, b) without stopping at zero bytes. */
6
6
  export declare function ascii(u8: Uint8Array, a: number, b: number): string;
7
- /** Четыре символа как little-endian uint32. */
7
+ /** Four characters as a little-endian uint32. */
8
8
  export declare const fourCC: (s: string) => number;
9
9
  /** IEEE 754 half -> number. */
10
10
  export declare function half(x: number): number;
11
- /** Копия содержимого Uint8Array как отдельный ArrayBuffer. */
11
+ /** Copy of a Uint8Array's contents as a separate ArrayBuffer. */
12
12
  export declare function toArrayBuffer(u8: Uint8Array): ArrayBuffer;
13
- /** Первые n байт файла (для автоопределения). */
13
+ /** First n bytes of a file (for format detection). */
14
14
  export declare function readHead(file: Blob | {
15
15
  arrayBuffer(): Promise<ArrayBuffer>;
16
16
  }, n?: number): Promise<Uint8Array>;
17
- /** Совпадают ли первые байты с ASCII-сигнатурой. */
17
+ /** Whether the first bytes match an ASCII signature. */
18
18
  export declare function hasMagic(head: Uint8Array, magic: string, offset?: number): boolean;
19
19
  export declare const u32le: (u8: Uint8Array, o: number) => number;
20
20
  export declare const extOf: (name: string) => string;
@@ -1 +1 @@
1
- {"version":3,"file":"binary.js","sources":["../src/binary.ts"],"sourcesContent":["// Мелкие помощники чтения двоичных данных.\n\n/** ASCII-строка до нуля или до n байт. */\nexport function cstr(u8: Uint8Array, start: number, maxLen: number): string {\n let s = '';\n for (let i = 0; i < maxLen && u8[start + i]; i++) s += String.fromCharCode(u8[start + i]!);\n return s;\n}\n\n/** ASCII-строка в диапазоне [a, b) до первого нуля. */\nexport function cstrRange(u8: Uint8Array, a: number, b: number): string {\n let s = '';\n for (let i = a; i < b && u8[i]; i++) s += String.fromCharCode(u8[i]!);\n return s;\n}\n\n/** ASCII в диапазоне [a, b) без остановки на нуле. */\nexport function ascii(u8: Uint8Array, a: number, b: number): string {\n let s = '';\n for (let i = a; i < b; i++) s += String.fromCharCode(u8[i]!);\n return s;\n}\n\n/** Четыре символа как little-endian uint32. */\nexport const fourCC = (s: string): number =>\n (s.charCodeAt(0) | (s.charCodeAt(1) << 8) | (s.charCodeAt(2) << 16) | (s.charCodeAt(3) << 24)) >>> 0;\n\n/** IEEE 754 half -> number. */\nexport function half(x: number): number {\n const s = x & 0x8000 ? -1 : 1, e = (x >> 10) & 31, f = x & 1023;\n if (e === 0) return s * 5.960464477539063e-8 * f;\n if (e === 31) return f ? NaN : s * Infinity;\n return s * 2 ** (e - 15) * (1 + f / 1024);\n}\n\n/** Копия содержимого Uint8Array как отдельный ArrayBuffer. */\nexport function toArrayBuffer(u8: Uint8Array): ArrayBuffer {\n return u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength) as ArrayBuffer;\n}\n\n/** Первые n байт файла (для автоопределения). */\nexport async function readHead(file: Blob | { arrayBuffer(): Promise<ArrayBuffer> }, n = 64): Promise<Uint8Array> {\n if (typeof Blob !== 'undefined' && file instanceof Blob) return new Uint8Array(await file.slice(0, n).arrayBuffer());\n const buf = await file.arrayBuffer();\n return new Uint8Array(buf, 0, Math.min(n, buf.byteLength));\n}\n\n/** Совпадают ли первые байты с ASCII-сигнатурой. */\nexport function hasMagic(head: Uint8Array, magic: string, offset = 0): boolean {\n if (head.length < offset + magic.length) return false;\n for (let i = 0; i < magic.length; i++) if (head[offset + i] !== magic.charCodeAt(i)) return false;\n return true;\n}\n\nexport const u32le = (u8: Uint8Array, o: number): number =>\n u8.length >= o + 4 ? (u8[o]! | (u8[o + 1]! << 8) | (u8[o + 2]! << 16) | (u8[o + 3]! << 24)) >>> 0 : -1;\n\nexport const extOf = (name: string): string => {\n const i = name.lastIndexOf('.');\n return i < 0 ? '' : name.slice(i + 1).toLowerCase();\n};\nexport const normPath = (p: string): string => p.replace(/\\\\/g, '/').toLowerCase();\nexport const stripExt = (p: string): string => p.replace(/\\.[^./]+$/, '');\nexport const baseName = (p: string): string => p.replace(/\\\\/g, '/').split('/').pop() ?? p;\n\nconst decoder = new TextDecoder();\nexport const decodeText = (data: Uint8Array | ArrayBuffer): string => decoder.decode(data);\n"],"names":[],"mappings":"AAGO,SAAS,KAAK,IAAgB,OAAe,QAAwB;AAC1E,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,UAAU,GAAG,QAAQ,CAAC,GAAG,UAAU,OAAO,aAAa,GAAG,QAAQ,CAAC,CAAE;AACzF,SAAO;AACT;AAGO,SAAS,UAAU,IAAgB,GAAW,GAAmB;AACtE,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,KAAK,GAAG,CAAC,GAAG,IAAK,MAAK,OAAO,aAAa,GAAG,CAAC,CAAE;AACpE,SAAO;AACT;AAGO,SAAS,MAAM,IAAgB,GAAW,GAAmB;AAClE,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,GAAG,UAAU,OAAO,aAAa,GAAG,CAAC,CAAE;AAC3D,SAAO;AACT;AAGO,MAAM,SAAS,CAAC,OACpB,EAAE,WAAW,CAAC,IAAK,EAAE,WAAW,CAAC,KAAK,IAAM,EAAE,WAAW,CAAC,KAAK,KAAO,EAAE,WAAW,CAAC,KAAK,QAAS;AAG9F,SAAS,KAAK,GAAmB;AACtC,QAAM,IAAI,IAAI,QAAS,KAAK,GAAG,IAAK,KAAK,KAAM,IAAI,IAAI,IAAI;AAC3D,MAAI,MAAM,EAAG,QAAO,IAAI,uBAAuB;AAC/C,MAAI,MAAM,GAAI,QAAO,IAAI,MAAM,IAAI;AACnC,SAAO,IAAI,MAAM,IAAI,OAAO,IAAI,IAAI;AACtC;AAGO,SAAS,cAAc,IAA6B;AACzD,SAAO,GAAG,OAAO,MAAM,GAAG,YAAY,GAAG,aAAa,GAAG,UAAU;AACrE;AAGA,eAAsB,SAAS,MAAsD,IAAI,IAAyB;AAChH,MAAI,OAAO,SAAS,eAAe,gBAAgB,KAAM,QAAO,IAAI,WAAW,MAAM,KAAK,MAAM,GAAG,CAAC,EAAE,aAAa;AACnH,QAAM,MAAM,MAAM,KAAK,YAAA;AACvB,SAAO,IAAI,WAAW,KAAK,GAAG,KAAK,IAAI,GAAG,IAAI,UAAU,CAAC;AAC3D;AAGO,SAAS,SAAS,MAAkB,OAAe,SAAS,GAAY;AAC7E,MAAI,KAAK,SAAS,SAAS,MAAM,OAAQ,QAAO;AAChD,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,IAAK,KAAI,KAAK,SAAS,CAAC,MAAM,MAAM,WAAW,CAAC,EAAG,QAAO;AAC5F,SAAO;AACT;AAEO,MAAM,QAAQ,CAAC,IAAgB,MACpC,GAAG,UAAU,IAAI,KAAK,GAAG,CAAC,IAAM,GAAG,IAAI,CAAC,KAAM,IAAM,GAAG,IAAI,CAAC,KAAM,KAAO,GAAG,IAAI,CAAC,KAAM,QAAS,IAAI;AAE/F,MAAM,QAAQ,CAAC,SAAyB;AAC7C,QAAM,IAAI,KAAK,YAAY,GAAG;AAC9B,SAAO,IAAI,IAAI,KAAK,KAAK,MAAM,IAAI,CAAC,EAAE,YAAA;AACxC;AACO,MAAM,WAAW,CAAC,MAAsB,EAAE,QAAQ,OAAO,GAAG,EAAE,YAAA;AAC9D,MAAM,WAAW,CAAC,MAAsB,EAAE,QAAQ,aAAa,EAAE;AACjE,MAAM,WAAW,CAAC,MAAsB,EAAE,QAAQ,OAAO,GAAG,EAAE,MAAM,GAAG,EAAE,IAAA,KAAS;AAEzF,MAAM,UAAU,IAAI,YAAA;AACb,MAAM,aAAa,CAAC,SAA2C,QAAQ,OAAO,IAAI;"}
1
+ {"version":3,"file":"binary.js","sources":["../src/binary.ts"],"sourcesContent":["// Small helpers for reading binary data.\n\n/** ASCII string up to a zero byte or n bytes. */\nexport function cstr(u8: Uint8Array, start: number, maxLen: number): string {\n let s = '';\n for (let i = 0; i < maxLen && u8[start + i]; i++) s += String.fromCharCode(u8[start + i]!);\n return s;\n}\n\n/** ASCII string in [a, b) up to the first zero byte. */\nexport function cstrRange(u8: Uint8Array, a: number, b: number): string {\n let s = '';\n for (let i = a; i < b && u8[i]; i++) s += String.fromCharCode(u8[i]!);\n return s;\n}\n\n/** ASCII in [a, b) without stopping at zero bytes. */\nexport function ascii(u8: Uint8Array, a: number, b: number): string {\n let s = '';\n for (let i = a; i < b; i++) s += String.fromCharCode(u8[i]!);\n return s;\n}\n\n/** Four characters as a little-endian uint32. */\nexport const fourCC = (s: string): number =>\n (s.charCodeAt(0) | (s.charCodeAt(1) << 8) | (s.charCodeAt(2) << 16) | (s.charCodeAt(3) << 24)) >>> 0;\n\n/** IEEE 754 half -> number. */\nexport function half(x: number): number {\n const s = x & 0x8000 ? -1 : 1, e = (x >> 10) & 31, f = x & 1023;\n if (e === 0) return s * 5.960464477539063e-8 * f;\n if (e === 31) return f ? NaN : s * Infinity;\n return s * 2 ** (e - 15) * (1 + f / 1024);\n}\n\n/** Copy of a Uint8Array's contents as a separate ArrayBuffer. */\nexport function toArrayBuffer(u8: Uint8Array): ArrayBuffer {\n return u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength) as ArrayBuffer;\n}\n\n/** First n bytes of a file (for format detection). */\nexport async function readHead(file: Blob | { arrayBuffer(): Promise<ArrayBuffer> }, n = 64): Promise<Uint8Array> {\n if (typeof Blob !== 'undefined' && file instanceof Blob) return new Uint8Array(await file.slice(0, n).arrayBuffer());\n const buf = await file.arrayBuffer();\n return new Uint8Array(buf, 0, Math.min(n, buf.byteLength));\n}\n\n/** Whether the first bytes match an ASCII signature. */\nexport function hasMagic(head: Uint8Array, magic: string, offset = 0): boolean {\n if (head.length < offset + magic.length) return false;\n for (let i = 0; i < magic.length; i++) if (head[offset + i] !== magic.charCodeAt(i)) return false;\n return true;\n}\n\nexport const u32le = (u8: Uint8Array, o: number): number =>\n u8.length >= o + 4 ? (u8[o]! | (u8[o + 1]! << 8) | (u8[o + 2]! << 16) | (u8[o + 3]! << 24)) >>> 0 : -1;\n\nexport const extOf = (name: string): string => {\n const i = name.lastIndexOf('.');\n return i < 0 ? '' : name.slice(i + 1).toLowerCase();\n};\nexport const normPath = (p: string): string => p.replace(/\\\\/g, '/').toLowerCase();\nexport const stripExt = (p: string): string => p.replace(/\\.[^./]+$/, '');\nexport const baseName = (p: string): string => p.replace(/\\\\/g, '/').split('/').pop() ?? p;\n\nconst decoder = new TextDecoder();\nexport const decodeText = (data: Uint8Array | ArrayBuffer): string => decoder.decode(data);\n"],"names":[],"mappings":"AAGO,SAAS,KAAK,IAAgB,OAAe,QAAwB;AAC1E,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,UAAU,GAAG,QAAQ,CAAC,GAAG,UAAU,OAAO,aAAa,GAAG,QAAQ,CAAC,CAAE;AACzF,SAAO;AACT;AAGO,SAAS,UAAU,IAAgB,GAAW,GAAmB;AACtE,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,KAAK,GAAG,CAAC,GAAG,IAAK,MAAK,OAAO,aAAa,GAAG,CAAC,CAAE;AACpE,SAAO;AACT;AAGO,SAAS,MAAM,IAAgB,GAAW,GAAmB;AAClE,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,GAAG,UAAU,OAAO,aAAa,GAAG,CAAC,CAAE;AAC3D,SAAO;AACT;AAGO,MAAM,SAAS,CAAC,OACpB,EAAE,WAAW,CAAC,IAAK,EAAE,WAAW,CAAC,KAAK,IAAM,EAAE,WAAW,CAAC,KAAK,KAAO,EAAE,WAAW,CAAC,KAAK,QAAS;AAG9F,SAAS,KAAK,GAAmB;AACtC,QAAM,IAAI,IAAI,QAAS,KAAK,GAAG,IAAK,KAAK,KAAM,IAAI,IAAI,IAAI;AAC3D,MAAI,MAAM,EAAG,QAAO,IAAI,uBAAuB;AAC/C,MAAI,MAAM,GAAI,QAAO,IAAI,MAAM,IAAI;AACnC,SAAO,IAAI,MAAM,IAAI,OAAO,IAAI,IAAI;AACtC;AAGO,SAAS,cAAc,IAA6B;AACzD,SAAO,GAAG,OAAO,MAAM,GAAG,YAAY,GAAG,aAAa,GAAG,UAAU;AACrE;AAGA,eAAsB,SAAS,MAAsD,IAAI,IAAyB;AAChH,MAAI,OAAO,SAAS,eAAe,gBAAgB,KAAM,QAAO,IAAI,WAAW,MAAM,KAAK,MAAM,GAAG,CAAC,EAAE,aAAa;AACnH,QAAM,MAAM,MAAM,KAAK,YAAA;AACvB,SAAO,IAAI,WAAW,KAAK,GAAG,KAAK,IAAI,GAAG,IAAI,UAAU,CAAC;AAC3D;AAGO,SAAS,SAAS,MAAkB,OAAe,SAAS,GAAY;AAC7E,MAAI,KAAK,SAAS,SAAS,MAAM,OAAQ,QAAO;AAChD,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,IAAK,KAAI,KAAK,SAAS,CAAC,MAAM,MAAM,WAAW,CAAC,EAAG,QAAO;AAC5F,SAAO;AACT;AAEO,MAAM,QAAQ,CAAC,IAAgB,MACpC,GAAG,UAAU,IAAI,KAAK,GAAG,CAAC,IAAM,GAAG,IAAI,CAAC,KAAM,IAAM,GAAG,IAAI,CAAC,KAAM,KAAO,GAAG,IAAI,CAAC,KAAM,QAAS,IAAI;AAE/F,MAAM,QAAQ,CAAC,SAAyB;AAC7C,QAAM,IAAI,KAAK,YAAY,GAAG;AAC9B,SAAO,IAAI,IAAI,KAAK,KAAK,MAAM,IAAI,CAAC,EAAE,YAAA;AACxC;AACO,MAAM,WAAW,CAAC,MAAsB,EAAE,QAAQ,OAAO,GAAG,EAAE,YAAA;AAC9D,MAAM,WAAW,CAAC,MAAsB,EAAE,QAAQ,aAAa,EAAE;AACjE,MAAM,WAAW,CAAC,MAAsB,EAAE,QAAQ,OAAO,GAAG,EAAE,MAAM,GAAG,EAAE,IAAA,KAAS;AAEzF,MAAM,UAAU,IAAI,YAAA;AACb,MAAM,aAAa,CAAC,SAA2C,QAAQ,OAAO,IAAI;"}
@@ -1 +1 @@
1
- {"version":3,"file":"builtin-en.js","sources":["../src/builtin-en.ts"],"sourcesContent":["// Английские тексты движка core (русские — в самих плагинах).\nimport type { EngineTexts } from './plugin';\n\nexport const EN: EngineTexts = {\n \"title\": \"Common formats\",\n \"exporters\": {\n \"obj\": {\n \"title\": \"OBJ + MTL (Blender, 3ds Max, any 3D editor)\",\n \"description\": \"Bind-pose geometry, UVs, normals and materials with DDS textures — one ZIP. OBJ does not store bones or animations.\",\n \"fields\": {\n \"name\": {\n \"label\": \"File name\"\n },\n \"scale\": {\n \"label\": \"Scale\"\n }\n }\n }\n },\n \"groups\": {\n \"Игры\": \"Games\"\n }\n};\n"],"names":[],"mappings":"AAGO,MAAM,KAAkB;AAAA,EAC7B,SAAS;AAAA,EACT,aAAa;AAAA,IACX,OAAO;AAAA,MACL,SAAS;AAAA,MACT,eAAe;AAAA,MACf,UAAU;AAAA,QACR,QAAQ;AAAA,UACN,SAAS;AAAA,QAAA;AAAA,QAEX,SAAS;AAAA,UACP,SAAS;AAAA,QAAA;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAAA,EAEF,UAAU;AAAA,IACR,QAAQ;AAAA,EAAA;AAEZ;"}
1
+ {"version":3,"file":"builtin-en.js","sources":["../src/builtin-en.ts"],"sourcesContent":["// English texts of the core engine (Russian ones live in the plugins themselves).\nimport type { EngineTexts } from './plugin';\n\nexport const EN: EngineTexts = {\n \"title\": \"Common formats\",\n \"exporters\": {\n \"obj\": {\n \"title\": \"OBJ + MTL (Blender, 3ds Max, any 3D editor)\",\n \"description\": \"Bind-pose geometry, UVs, normals and materials with DDS textures — one ZIP. OBJ does not store bones or animations.\",\n \"fields\": {\n \"name\": {\n \"label\": \"File name\"\n },\n \"scale\": {\n \"label\": \"Scale\"\n }\n }\n }\n },\n \"groups\": {\n \"Игры\": \"Games\"\n }\n};\n"],"names":[],"mappings":"AAGO,MAAM,KAAkB;AAAA,EAC7B,SAAS;AAAA,EACT,aAAa;AAAA,IACX,OAAO;AAAA,MACL,SAAS;AAAA,MACT,eAAe;AAAA,MACf,UAAU;AAAA,QACR,QAAQ;AAAA,UACN,SAAS;AAAA,QAAA;AAAA,QAEX,SAAS;AAAA,UACP,SAAS;AAAA,QAAA;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAAA,EAEF,UAAU;AAAA,IACR,QAAQ;AAAA,EAAA;AAEZ;"}
package/dist/builtin.js CHANGED
@@ -16,7 +16,7 @@ const coreEngine = defineEngine({
16
16
  engine: "core",
17
17
  extensions: ["zip", "pk3", "pk4", "iwd", "pak"],
18
18
  sniff: (h) => hasMagic(h, "PK") || hasMagic(h, "PK") ? 1 : 0,
19
- // .pak CryEngine (Far Cry, Crysis, KCD) в «Папке игры» открываются сами: читается только центральный каталог ZIP
19
+ // CryEngine .pak files (Far Cry, Crysis, KCD) open by themselves in the "Game folder": only the ZIP central directory is read
20
20
  openFromFolder: (_h, name) => /\.pak$/i.test(name),
21
21
  open: (file) => openZip(file)
22
22
  }
@@ -1 +1 @@
1
- {"version":3,"file":"builtin.js","sources":["../src/builtin.ts"],"sourcesContent":["// Встроенный «движок» ядра: общие архивы (ZIP) и кодеки (DDS, TGA, PCX), которыми пользуются многие игры.\nimport { hasMagic } from './binary';\nimport { openZip } from './archives/zip';\nimport { decodeDDS } from './images/dds';\nimport { decodePCX } from './images/pcx';\nimport { decodeTGA } from './images/tga';\nimport { defineEngine } from './plugin';\nimport { EN } from './builtin-en';\n\nexport const coreEngine = defineEngine({\n id: 'core',\n title: 'Общие форматы',\n i18n: { en: EN },\n archives: [\n {\n id: 'zip',\n title: 'ZIP (pk3, pk4, iwd, CryEngine .pak)',\n engine: 'core',\n extensions: ['zip', 'pk3', 'pk4', 'iwd', 'pak'],\n sniff: (h) => (hasMagic(h, 'PK\\x03\\x04') || hasMagic(h, 'PK\\x05\\x06') ? 1 : 0),\n // .pak CryEngine (Far Cry, Crysis, KCD) в «Папке игры» открываются сами: читается только центральный каталог ZIP\n openFromFolder: (_h, name) => /\\.pak$/i.test(name),\n open: (file) => openZip(file),\n },\n ],\n exporters: [\n {\n id: 'obj',\n title: 'OBJ + MTL (Blender, 3ds Max, любой 3D-редактор)',\n target: 'obj',\n description: 'Геометрия в bind-позе, UV, нормали и материалы с текстурами DDS — одним ZIP. Кости и анимации OBJ не хранит.',\n fields: [\n { id: 'name', label: 'Имя файла', type: 'text', default: (_m, base) => base },\n { id: 'scale', label: 'Масштаб', type: 'number', min: 0, step: 0.01, default: 1 },\n ],\n run: async (model, values, ctx) => (await import('./writers/obj')).exportObj(model, values, ctx),\n },\n ],\n codecs: [\n { id: 'dds', extensions: ['dds'], decode: (b) => decodeDDS(b) },\n { id: 'tga', extensions: ['tga'], decode: (b) => decodeTGA(b) },\n { id: 'pcx', extensions: ['pcx'], decode: (b) => decodePCX(b) },\n ],\n});\n"],"names":[],"mappings":";;;;;;;AASO,MAAM,aAAa,aAAa;AAAA,EACrC,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,MAAM,EAAE,IAAI,GAAA;AAAA,EACZ,UAAU;AAAA,IACR;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,YAAY,CAAC,OAAO,OAAO,OAAO,OAAO,KAAK;AAAA,MAC9C,OAAO,CAAC,MAAO,SAAS,GAAG,MAAY,KAAK,SAAS,GAAG,MAAY,IAAI,IAAI;AAAA;AAAA,MAE5E,gBAAgB,CAAC,IAAI,SAAS,UAAU,KAAK,IAAI;AAAA,MACjD,MAAM,CAAC,SAAS,QAAQ,IAAI;AAAA,IAAA;AAAA,EAC9B;AAAA,EAEF,WAAW;AAAA,IACT;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,QAAQ;AAAA,QACN,EAAE,IAAI,QAAQ,OAAO,aAAa,MAAM,QAAQ,SAAS,CAAC,IAAI,SAAS,KAAA;AAAA,QACvE,EAAE,IAAI,SAAS,OAAO,WAAW,MAAM,UAAU,KAAK,GAAG,MAAM,MAAM,SAAS,EAAA;AAAA,MAAE;AAAA,MAElF,KAAK,OAAO,OAAO,QAAQ,SAAS,MAAM,OAAO,kBAAe,GAAG,UAAU,OAAO,QAAQ,GAAG;AAAA,IAAA;AAAA,EACjG;AAAA,EAEF,QAAQ;AAAA,IACN,EAAE,IAAI,OAAO,YAAY,CAAC,KAAK,GAAG,QAAQ,CAAC,MAAM,UAAU,CAAC,EAAA;AAAA,IAC5D,EAAE,IAAI,OAAO,YAAY,CAAC,KAAK,GAAG,QAAQ,CAAC,MAAM,UAAU,CAAC,EAAA;AAAA,IAC5D,EAAE,IAAI,OAAO,YAAY,CAAC,KAAK,GAAG,QAAQ,CAAC,MAAM,UAAU,CAAC,EAAA;AAAA,EAAE;AAElE,CAAC;"}
1
+ {"version":3,"file":"builtin.js","sources":["../src/builtin.ts"],"sourcesContent":["// The core's built-in \"engine\": shared archives (ZIP) and codecs (DDS, TGA, PCX) used by many games.\nimport { hasMagic } from './binary';\nimport { openZip } from './archives/zip';\nimport { decodeDDS } from './images/dds';\nimport { decodePCX } from './images/pcx';\nimport { decodeTGA } from './images/tga';\nimport { defineEngine } from './plugin';\nimport { EN } from './builtin-en';\n\nexport const coreEngine = defineEngine({\n id: 'core',\n title: 'Общие форматы',\n i18n: { en: EN },\n archives: [\n {\n id: 'zip',\n title: 'ZIP (pk3, pk4, iwd, CryEngine .pak)',\n engine: 'core',\n extensions: ['zip', 'pk3', 'pk4', 'iwd', 'pak'],\n sniff: (h) => (hasMagic(h, 'PK\\x03\\x04') || hasMagic(h, 'PK\\x05\\x06') ? 1 : 0),\n // CryEngine .pak files (Far Cry, Crysis, KCD) open by themselves in the \"Game folder\": only the ZIP central directory is read\n openFromFolder: (_h, name) => /\\.pak$/i.test(name),\n open: (file) => openZip(file),\n },\n ],\n exporters: [\n {\n id: 'obj',\n title: 'OBJ + MTL (Blender, 3ds Max, любой 3D-редактор)',\n target: 'obj',\n description: 'Геометрия в bind-позе, UV, нормали и материалы с текстурами DDS — одним ZIP. Кости и анимации OBJ не хранит.',\n fields: [\n { id: 'name', label: 'Имя файла', type: 'text', default: (_m, base) => base },\n { id: 'scale', label: 'Масштаб', type: 'number', min: 0, step: 0.01, default: 1 },\n ],\n run: async (model, values, ctx) => (await import('./writers/obj')).exportObj(model, values, ctx),\n },\n ],\n codecs: [\n { id: 'dds', extensions: ['dds'], decode: (b) => decodeDDS(b) },\n { id: 'tga', extensions: ['tga'], decode: (b) => decodeTGA(b) },\n { id: 'pcx', extensions: ['pcx'], decode: (b) => decodePCX(b) },\n ],\n});\n"],"names":[],"mappings":";;;;;;;AASO,MAAM,aAAa,aAAa;AAAA,EACrC,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,MAAM,EAAE,IAAI,GAAA;AAAA,EACZ,UAAU;AAAA,IACR;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,YAAY,CAAC,OAAO,OAAO,OAAO,OAAO,KAAK;AAAA,MAC9C,OAAO,CAAC,MAAO,SAAS,GAAG,MAAY,KAAK,SAAS,GAAG,MAAY,IAAI,IAAI;AAAA;AAAA,MAE5E,gBAAgB,CAAC,IAAI,SAAS,UAAU,KAAK,IAAI;AAAA,MACjD,MAAM,CAAC,SAAS,QAAQ,IAAI;AAAA,IAAA;AAAA,EAC9B;AAAA,EAEF,WAAW;AAAA,IACT;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,QAAQ;AAAA,QACN,EAAE,IAAI,QAAQ,OAAO,aAAa,MAAM,QAAQ,SAAS,CAAC,IAAI,SAAS,KAAA;AAAA,QACvE,EAAE,IAAI,SAAS,OAAO,WAAW,MAAM,UAAU,KAAK,GAAG,MAAM,MAAM,SAAS,EAAA;AAAA,MAAE;AAAA,MAElF,KAAK,OAAO,OAAO,QAAQ,SAAS,MAAM,OAAO,kBAAe,GAAG,UAAU,OAAO,QAAQ,GAAG;AAAA,IAAA;AAAA,EACjG;AAAA,EAEF,QAAQ;AAAA,IACN,EAAE,IAAI,OAAO,YAAY,CAAC,KAAK,GAAG,QAAQ,CAAC,MAAM,UAAU,CAAC,EAAA;AAAA,IAC5D,EAAE,IAAI,OAAO,YAAY,CAAC,KAAK,GAAG,QAAQ,CAAC,MAAM,UAAU,CAAC,EAAA;AAAA,IAC5D,EAAE,IAAI,OAAO,YAAY,CAAC,KAAK,GAAG,QAAQ,CAAC,MAAM,UAAU,CAAC,EAAA;AAAA,EAAE;AAElE,CAAC;"}
@@ -1,4 +1,4 @@
1
- /** zlib-поток (с заголовком 78 xx). */
1
+ /** zlib stream (with a 78 xx header). */
2
2
  export declare const inflateZlib: (u8: Uint8Array) => Promise<Uint8Array>;
3
- /** Сырой deflate (ZIP, метод 8). */
3
+ /** Raw deflate (ZIP, method 8). */
4
4
  export declare const inflateRaw: (u8: Uint8Array) => Promise<Uint8Array>;
@@ -1 +1 @@
1
- {"version":3,"file":"zlib.js","sources":["../../src/codecs/zlib.ts"],"sourcesContent":["// Распаковка zlib / raw deflate через стандартный DecompressionStream (браузеры, Node 18+).\nasync function run(u8: Uint8Array, format: 'deflate' | 'deflate-raw'): Promise<Uint8Array> {\n const stream = new Blob([u8 as BlobPart]).stream().pipeThrough(new DecompressionStream(format));\n return new Uint8Array(await new Response(stream).arrayBuffer());\n}\n/** zlib-поток (с заголовком 78 xx). */\nexport const inflateZlib = (u8: Uint8Array): Promise<Uint8Array> => run(u8, 'deflate');\n/** Сырой deflate (ZIP, метод 8). */\nexport const inflateRaw = (u8: Uint8Array): Promise<Uint8Array> => run(u8, 'deflate-raw');\n"],"names":[],"mappings":"AACA,eAAe,IAAI,IAAgB,QAAwD;AACzF,QAAM,SAAS,IAAI,KAAK,CAAC,EAAc,CAAC,EAAE,OAAA,EAAS,YAAY,IAAI,oBAAoB,MAAM,CAAC;AAC9F,SAAO,IAAI,WAAW,MAAM,IAAI,SAAS,MAAM,EAAE,aAAa;AAChE;AAEO,MAAM,cAAc,CAAC,OAAwC,IAAI,IAAI,SAAS;AAE9E,MAAM,aAAa,CAAC,OAAwC,IAAI,IAAI,aAAa;"}
1
+ {"version":3,"file":"zlib.js","sources":["../../src/codecs/zlib.ts"],"sourcesContent":["// zlib / raw deflate decompression via the standard DecompressionStream (browsers, Node 18+).\nasync function run(u8: Uint8Array, format: 'deflate' | 'deflate-raw'): Promise<Uint8Array> {\n const stream = new Blob([u8 as BlobPart]).stream().pipeThrough(new DecompressionStream(format));\n return new Uint8Array(await new Response(stream).arrayBuffer());\n}\n/** zlib stream (with a 78 xx header). */\nexport const inflateZlib = (u8: Uint8Array): Promise<Uint8Array> => run(u8, 'deflate');\n/** Raw deflate (ZIP, method 8). */\nexport const inflateRaw = (u8: Uint8Array): Promise<Uint8Array> => run(u8, 'deflate-raw');\n"],"names":[],"mappings":"AACA,eAAe,IAAI,IAAgB,QAAwD;AACzF,QAAM,SAAS,IAAI,KAAK,CAAC,EAAc,CAAC,EAAE,OAAA,EAAS,YAAY,IAAI,oBAAoB,MAAM,CAAC;AAC9F,SAAO,IAAI,WAAW,MAAM,IAAI,SAAS,MAAM,EAAE,aAAa;AAChE;AAEO,MAAM,cAAc,CAAC,OAAwC,IAAI,IAAI,SAAS;AAE9E,MAAM,aAAa,CAAC,OAAwC,IAAI,IAAI,aAAa;"}
@@ -1 +1 @@
1
- {"version":3,"file":"diagnostics.js","sources":["../src/diagnostics.ts"],"sourcesContent":["// Текст диагностики для отчёта об ошибке: файл, формат, сигнатура и шестнадцатеричный дамп заголовка.\nimport { readHead } from './binary';\nimport type { BlobFile, VFile } from './types';\n\nexport function hexDump(u8: Uint8Array, width = 16): string {\n const lines: string[] = [];\n for (let o = 0; o < u8.length; o += width) {\n const row = u8.subarray(o, o + width);\n const hex = Array.from(row, (b) => b.toString(16).padStart(2, '0')).join(' ').padEnd(width * 3 - 1);\n const txt = Array.from(row, (b) => (b >= 0x20 && b < 0x7f ? String.fromCharCode(b) : '.')).join('');\n lines.push(`${o.toString(16).padStart(4, '0')} ${hex} ${txt}`);\n }\n return lines.join('\\n');\n}\n\nexport interface DiagnosticsInput {\n file: VFile | BlobFile;\n format?: string;\n preset?: string;\n error: string;\n appVersion?: string;\n}\n\nexport async function buildDiagnostics(d: DiagnosticsInput, headBytes = 64): Promise<string> {\n let head: Uint8Array = new Uint8Array(0);\n try { head = await readHead(d.file, headBytes); } catch { /* файл недоступен */ }\n const size = typeof Blob !== 'undefined' && d.file instanceof Blob ? d.file.size : undefined;\n return [\n `gmv ${d.appVersion ?? ''}`.trim(),\n `Файл: ${('path' in d.file && d.file.path) || d.file.name}${size !== undefined ? ` (${size} байт)` : ''}`,\n d.preset ? `Пресет: ${d.preset}` : '',\n d.format ? `Формат: ${d.format}` : '',\n `Ошибка: ${d.error}`,\n `Сигнатура: ${Array.from(head.subarray(0, 4), (b) => (b >= 0x20 && b < 0x7f ? String.fromCharCode(b) : '.')).join('')}`,\n `Заголовок (${head.length} байт):`,\n hexDump(head),\n ].filter(Boolean).join('\\n');\n}\n"],"names":[],"mappings":";AAIO,SAAS,QAAQ,IAAgB,QAAQ,IAAY;AAC1D,QAAM,QAAkB,CAAA;AACxB,WAAS,IAAI,GAAG,IAAI,GAAG,QAAQ,KAAK,OAAO;AACzC,UAAM,MAAM,GAAG,SAAS,GAAG,IAAI,KAAK;AACpC,UAAM,MAAM,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,GAAG,EAAE,OAAO,QAAQ,IAAI,CAAC;AAClG,UAAM,MAAM,MAAM,KAAK,KAAK,CAAC,MAAO,KAAK,MAAQ,IAAI,MAAO,OAAO,aAAa,CAAC,IAAI,GAAI,EAAE,KAAK,EAAE;AAClG,UAAM,KAAK,GAAG,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,KAAK,GAAG,KAAK,GAAG,EAAE;AAAA,EACjE;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAUA,eAAsB,iBAAiB,GAAqB,YAAY,IAAqB;AAC3F,MAAI,OAAmB,IAAI,WAAW,CAAC;AACvC,MAAI;AAAE,WAAO,MAAM,SAAS,EAAE,MAAM,SAAS;AAAA,EAAG,QAAQ;AAAA,EAAwB;AAChF,QAAM,OAAO,OAAO,SAAS,eAAe,EAAE,gBAAgB,OAAO,EAAE,KAAK,OAAO;AACnF,SAAO;AAAA,IACL,OAAO,EAAE,cAAc,EAAE,GAAG,KAAA;AAAA,IAC5B,SAAU,UAAU,EAAE,QAAQ,EAAE,KAAK,QAAS,EAAE,KAAK,IAAI,GAAG,SAAS,SAAY,KAAK,IAAI,WAAW,EAAE;AAAA,IACvG,EAAE,SAAS,WAAW,EAAE,MAAM,KAAK;AAAA,IACnC,EAAE,SAAS,WAAW,EAAE,MAAM,KAAK;AAAA,IACnC,WAAW,EAAE,KAAK;AAAA,IAClB,cAAc,MAAM,KAAK,KAAK,SAAS,GAAG,CAAC,GAAG,CAAC,MAAO,KAAK,MAAQ,IAAI,MAAO,OAAO,aAAa,CAAC,IAAI,GAAI,EAAE,KAAK,EAAE,CAAC;AAAA,IACrH,cAAc,KAAK,MAAM;AAAA,IACzB,QAAQ,IAAI;AAAA,EAAA,EACZ,OAAO,OAAO,EAAE,KAAK,IAAI;AAC7B;"}
1
+ {"version":3,"file":"diagnostics.js","sources":["../src/diagnostics.ts"],"sourcesContent":["// Diagnostics text for an error report: file, format, signature and a hex dump of the header.\nimport { readHead } from './binary';\nimport type { BlobFile, VFile } from './types';\n\nexport function hexDump(u8: Uint8Array, width = 16): string {\n const lines: string[] = [];\n for (let o = 0; o < u8.length; o += width) {\n const row = u8.subarray(o, o + width);\n const hex = Array.from(row, (b) => b.toString(16).padStart(2, '0')).join(' ').padEnd(width * 3 - 1);\n const txt = Array.from(row, (b) => (b >= 0x20 && b < 0x7f ? String.fromCharCode(b) : '.')).join('');\n lines.push(`${o.toString(16).padStart(4, '0')} ${hex} ${txt}`);\n }\n return lines.join('\\n');\n}\n\nexport interface DiagnosticsInput {\n file: VFile | BlobFile;\n format?: string;\n preset?: string;\n error: string;\n appVersion?: string;\n}\n\nexport async function buildDiagnostics(d: DiagnosticsInput, headBytes = 64): Promise<string> {\n let head: Uint8Array = new Uint8Array(0);\n try { head = await readHead(d.file, headBytes); } catch { /* file unavailable */ }\n const size = typeof Blob !== 'undefined' && d.file instanceof Blob ? d.file.size : undefined;\n return [\n `gmv ${d.appVersion ?? ''}`.trim(),\n `Файл: ${('path' in d.file && d.file.path) || d.file.name}${size !== undefined ? ` (${size} байт)` : ''}`,\n d.preset ? `Пресет: ${d.preset}` : '',\n d.format ? `Формат: ${d.format}` : '',\n `Ошибка: ${d.error}`,\n `Сигнатура: ${Array.from(head.subarray(0, 4), (b) => (b >= 0x20 && b < 0x7f ? String.fromCharCode(b) : '.')).join('')}`,\n `Заголовок (${head.length} байт):`,\n hexDump(head),\n ].filter(Boolean).join('\\n');\n}\n"],"names":[],"mappings":";AAIO,SAAS,QAAQ,IAAgB,QAAQ,IAAY;AAC1D,QAAM,QAAkB,CAAA;AACxB,WAAS,IAAI,GAAG,IAAI,GAAG,QAAQ,KAAK,OAAO;AACzC,UAAM,MAAM,GAAG,SAAS,GAAG,IAAI,KAAK;AACpC,UAAM,MAAM,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,GAAG,EAAE,OAAO,QAAQ,IAAI,CAAC;AAClG,UAAM,MAAM,MAAM,KAAK,KAAK,CAAC,MAAO,KAAK,MAAQ,IAAI,MAAO,OAAO,aAAa,CAAC,IAAI,GAAI,EAAE,KAAK,EAAE;AAClG,UAAM,KAAK,GAAG,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,KAAK,GAAG,KAAK,GAAG,EAAE;AAAA,EACjE;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAUA,eAAsB,iBAAiB,GAAqB,YAAY,IAAqB;AAC3F,MAAI,OAAmB,IAAI,WAAW,CAAC;AACvC,MAAI;AAAE,WAAO,MAAM,SAAS,EAAE,MAAM,SAAS;AAAA,EAAG,QAAQ;AAAA,EAAyB;AACjF,QAAM,OAAO,OAAO,SAAS,eAAe,EAAE,gBAAgB,OAAO,EAAE,KAAK,OAAO;AACnF,SAAO;AAAA,IACL,OAAO,EAAE,cAAc,EAAE,GAAG,KAAA;AAAA,IAC5B,SAAU,UAAU,EAAE,QAAQ,EAAE,KAAK,QAAS,EAAE,KAAK,IAAI,GAAG,SAAS,SAAY,KAAK,IAAI,WAAW,EAAE;AAAA,IACvG,EAAE,SAAS,WAAW,EAAE,MAAM,KAAK;AAAA,IACnC,EAAE,SAAS,WAAW,EAAE,MAAM,KAAK;AAAA,IACnC,WAAW,EAAE,KAAK;AAAA,IAClB,cAAc,MAAM,KAAK,KAAK,SAAS,GAAG,CAAC,GAAG,CAAC,MAAO,KAAK,MAAQ,IAAI,MAAO,OAAO,aAAa,CAAC,IAAI,GAAI,EAAE,KAAK,EAAE,CAAC;AAAA,IACrH,cAAc,KAAK,MAAM;AAAA,IACzB,QAAQ,IAAI;AAAA,EAAA,EACZ,OAAO,OAAO,EAAE,KAAK,IAAI;AAC7B;"}
@@ -1,19 +1,19 @@
1
1
  import type { Bounds, Mesh, SkeletonRuntime } from './types';
2
- /** Сглаженные нормали по треугольникам (взвешены площадью). */
2
+ /** Smoothed normals from triangles (area-weighted). */
3
3
  export declare function computeNormals(pos: ArrayLike<number>, idx: ArrayLike<number>): Float32Array;
4
4
  export declare function accumulateNormals(pos: ArrayLike<number>, idx: ArrayLike<number>, nor: Float32Array): void;
5
5
  export declare function normalize3(a: Float32Array): void;
6
- /** Индексный буфер минимальной ширины. */
6
+ /** Index buffer of the smallest sufficient width. */
7
7
  export declare function makeIndices(vertexCount: number, list: ArrayLike<number>): Uint16Array | Uint32Array;
8
8
  export declare function emptyBounds(): Bounds;
9
- /** Границы по сырым позициям вершин. */
9
+ /** Bounds from raw vertex positions. */
10
10
  export declare function boundsOf(meshes: ReadonlyArray<{
11
11
  pos: ArrayLike<number>;
12
12
  }>): Bounds;
13
13
  export declare function extendBounds(b: Bounds, pos: ArrayLike<number>): void;
14
- /** Границы модели в bind-позе: к вершинам применяются skin-матрицы скелета. */
14
+ /** Model bounds in the bind pose: skeleton skin matrices are applied to the vertices. */
15
15
  export declare function skinnedBounds(sk: SkeletonRuntime, meshes: readonly Mesh[]): Bounds;
16
- /** Треугольники из полосы (strip) с чередованием обхода и пропуском вырожденных. */
16
+ /** Triangles from a strip, alternating winding and skipping degenerate ones. */
17
17
  export declare function stripToTris(idx: ArrayLike<number>): number[];
18
18
  export declare const sumTris: (meshes: readonly Mesh[]) => number;
19
19
  export declare const sumVerts: (meshes: readonly Mesh[]) => number;
@@ -1 +1 @@
1
- {"version":3,"file":"geometry.js","sources":["../src/geometry.ts"],"sourcesContent":["// Общая геометрия: нормали, границы, индексы.\nimport type { Bounds, Mesh, SkeletonRuntime } from './types';\n\n/** Сглаженные нормали по треугольникам (взвешены площадью). */\nexport function computeNormals(pos: ArrayLike<number>, idx: ArrayLike<number>): Float32Array {\n const nor = new Float32Array(pos.length);\n accumulateNormals(pos, idx, nor);\n normalize3(nor);\n return nor;\n}\n\nexport function accumulateNormals(pos: ArrayLike<number>, idx: ArrayLike<number>, nor: Float32Array): void {\n for (let t = 0; t + 2 < idx.length; t += 3) {\n const a = idx[t]! * 3, b = idx[t + 1]! * 3, c = idx[t + 2]! * 3;\n const ux = pos[b]! - pos[a]!, uy = pos[b + 1]! - pos[a + 1]!, uz = pos[b + 2]! - pos[a + 2]!;\n const vx = pos[c]! - pos[a]!, vy = pos[c + 1]! - pos[a + 1]!, vz = pos[c + 2]! - pos[a + 2]!;\n const x = uy * vz - uz * vy, y = uz * vx - ux * vz, z = ux * vy - uy * vx;\n for (const o of [a, b, c]) { nor[o] += x; nor[o + 1] += y; nor[o + 2] += z; }\n }\n}\n\nexport function normalize3(a: Float32Array): void {\n for (let k = 0; k < a.length; k += 3) {\n const l = Math.hypot(a[k]!, a[k + 1]!, a[k + 2]!) || 1;\n a[k] /= l; a[k + 1] /= l; a[k + 2] /= l;\n }\n}\n\n/** Индексный буфер минимальной ширины. */\nexport function makeIndices(vertexCount: number, list: ArrayLike<number>): Uint16Array | Uint32Array {\n return vertexCount > 65535 ? Uint32Array.from(list) : Uint16Array.from(list);\n}\n\nexport function emptyBounds(): Bounds {\n return { min: [Infinity, Infinity, Infinity], max: [-Infinity, -Infinity, -Infinity] };\n}\n\n/** Границы по сырым позициям вершин. */\nexport function boundsOf(meshes: ReadonlyArray<{ pos: ArrayLike<number> }>): Bounds {\n const b = emptyBounds();\n for (const m of meshes) extendBounds(b, m.pos);\n return b;\n}\n\nexport function extendBounds(b: Bounds, pos: ArrayLike<number>): void {\n for (let i = 0; i < pos.length; i++) {\n const k = i % 3, v = pos[i]!;\n if (v < b.min[k]!) b.min[k] = v;\n if (v > b.max[k]!) b.max[k] = v;\n }\n}\n\n/** Границы модели в bind-позе: к вершинам применяются skin-матрицы скелета. */\nexport function skinnedBounds(sk: SkeletonRuntime, meshes: readonly Mesh[]): Bounds {\n sk.pose(null, 0, false);\n const { min, max } = emptyBounds();\n for (const m of meshes)\n for (let v = 0; v < m.vertexCount; v++) {\n const x = m.pos[v * 3]!, y = m.pos[v * 3 + 1]!, z = m.pos[v * 3 + 2]!, o = [0, 0, 0];\n for (let k = 0; k < 4; k++) {\n const w = m.weight[v * 4 + k]!;\n if (!w) continue;\n const M = sk.skinV[m.bone[v * 4 + k]!]!;\n o[0] += w * (M[0]! * x + M[4]! * y + M[8]! * z + M[12]!);\n o[1] += w * (M[1]! * x + M[5]! * y + M[9]! * z + M[13]!);\n o[2] += w * (M[2]! * x + M[6]! * y + M[10]! * z + M[14]!);\n }\n for (let k = 0; k < 3; k++) {\n if (o[k]! < min[k]!) min[k] = o[k]!;\n if (o[k]! > max[k]!) max[k] = o[k]!;\n }\n }\n return { min, max };\n}\n\n/** Треугольники из полосы (strip) с чередованием обхода и пропуском вырожденных. */\nexport function stripToTris(idx: ArrayLike<number>): number[] {\n const o: number[] = [];\n for (let k = 2; k < idx.length; k++) {\n const a = idx[k - 2]!, b = idx[k - 1]!, c = idx[k]!;\n if (a === b || b === c || a === c) continue;\n if (k & 1) o.push(a, c, b); else o.push(a, b, c);\n }\n return o;\n}\n\nexport const sumTris = (meshes: readonly Mesh[]): number => meshes.reduce((a, m) => a + m.indices.length / 3, 0);\nexport const sumVerts = (meshes: readonly Mesh[]): number => meshes.reduce((a, m) => a + m.vertexCount, 0);\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"geometry.js","sources":["../src/geometry.ts"],"sourcesContent":["// Common geometry: normals, bounds, indices.\nimport type { Bounds, Mesh, SkeletonRuntime } from './types';\n\n/** Smoothed normals from triangles (area-weighted). */\nexport function computeNormals(pos: ArrayLike<number>, idx: ArrayLike<number>): Float32Array {\n const nor = new Float32Array(pos.length);\n accumulateNormals(pos, idx, nor);\n normalize3(nor);\n return nor;\n}\n\nexport function accumulateNormals(pos: ArrayLike<number>, idx: ArrayLike<number>, nor: Float32Array): void {\n for (let t = 0; t + 2 < idx.length; t += 3) {\n const a = idx[t]! * 3, b = idx[t + 1]! * 3, c = idx[t + 2]! * 3;\n const ux = pos[b]! - pos[a]!, uy = pos[b + 1]! - pos[a + 1]!, uz = pos[b + 2]! - pos[a + 2]!;\n const vx = pos[c]! - pos[a]!, vy = pos[c + 1]! - pos[a + 1]!, vz = pos[c + 2]! - pos[a + 2]!;\n const x = uy * vz - uz * vy, y = uz * vx - ux * vz, z = ux * vy - uy * vx;\n for (const o of [a, b, c]) { nor[o] += x; nor[o + 1] += y; nor[o + 2] += z; }\n }\n}\n\nexport function normalize3(a: Float32Array): void {\n for (let k = 0; k < a.length; k += 3) {\n const l = Math.hypot(a[k]!, a[k + 1]!, a[k + 2]!) || 1;\n a[k] /= l; a[k + 1] /= l; a[k + 2] /= l;\n }\n}\n\n/** Index buffer of the smallest sufficient width. */\nexport function makeIndices(vertexCount: number, list: ArrayLike<number>): Uint16Array | Uint32Array {\n return vertexCount > 65535 ? Uint32Array.from(list) : Uint16Array.from(list);\n}\n\nexport function emptyBounds(): Bounds {\n return { min: [Infinity, Infinity, Infinity], max: [-Infinity, -Infinity, -Infinity] };\n}\n\n/** Bounds from raw vertex positions. */\nexport function boundsOf(meshes: ReadonlyArray<{ pos: ArrayLike<number> }>): Bounds {\n const b = emptyBounds();\n for (const m of meshes) extendBounds(b, m.pos);\n return b;\n}\n\nexport function extendBounds(b: Bounds, pos: ArrayLike<number>): void {\n for (let i = 0; i < pos.length; i++) {\n const k = i % 3, v = pos[i]!;\n if (v < b.min[k]!) b.min[k] = v;\n if (v > b.max[k]!) b.max[k] = v;\n }\n}\n\n/** Model bounds in the bind pose: skeleton skin matrices are applied to the vertices. */\nexport function skinnedBounds(sk: SkeletonRuntime, meshes: readonly Mesh[]): Bounds {\n sk.pose(null, 0, false);\n const { min, max } = emptyBounds();\n for (const m of meshes)\n for (let v = 0; v < m.vertexCount; v++) {\n const x = m.pos[v * 3]!, y = m.pos[v * 3 + 1]!, z = m.pos[v * 3 + 2]!, o = [0, 0, 0];\n for (let k = 0; k < 4; k++) {\n const w = m.weight[v * 4 + k]!;\n if (!w) continue;\n const M = sk.skinV[m.bone[v * 4 + k]!]!;\n o[0] += w * (M[0]! * x + M[4]! * y + M[8]! * z + M[12]!);\n o[1] += w * (M[1]! * x + M[5]! * y + M[9]! * z + M[13]!);\n o[2] += w * (M[2]! * x + M[6]! * y + M[10]! * z + M[14]!);\n }\n for (let k = 0; k < 3; k++) {\n if (o[k]! < min[k]!) min[k] = o[k]!;\n if (o[k]! > max[k]!) max[k] = o[k]!;\n }\n }\n return { min, max };\n}\n\n/** Triangles from a strip, alternating winding and skipping degenerate ones. */\nexport function stripToTris(idx: ArrayLike<number>): number[] {\n const o: number[] = [];\n for (let k = 2; k < idx.length; k++) {\n const a = idx[k - 2]!, b = idx[k - 1]!, c = idx[k]!;\n if (a === b || b === c || a === c) continue;\n if (k & 1) o.push(a, c, b); else o.push(a, b, c);\n }\n return o;\n}\n\nexport const sumTris = (meshes: readonly Mesh[]): number => meshes.reduce((a, m) => a + m.indices.length / 3, 0);\nexport const sumVerts = (meshes: readonly Mesh[]): number => meshes.reduce((a, m) => a + m.vertexCount, 0);\n"],"names":[],"mappings":"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"}
package/dist/i18n.d.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import type { EnginePlugin, ExportField, ExporterPlugin, GamePreset } from './plugin';
2
- /** Добавить тексты движка (вызывает реестр при подключении). */
2
+ /** Add an engine's texts (called by the registry when the engine is attached). */
3
3
  export declare function registerTexts(e: EnginePlugin): void;
4
4
  export type Locale = 'ru' | 'en';
5
5
  export declare function setLocale(l: Locale): void;
6
6
  export declare function getLocale(): Locale;
7
- /** Текст по ключу с подстановкой {параметров}. */
7
+ /** Text by key with {parameter} substitution. */
8
8
  export declare function tr(key: string, params?: Record<string, string | number>): string;
9
- /** Тексты пресета игры на текущем языке. */
9
+ /** Game preset texts in the current language. */
10
10
  export declare function presetText(p: GamePreset): {
11
11
  title: string;
12
12
  hint: string;
@@ -15,7 +15,7 @@ export declare function presetText(p: GamePreset): {
15
15
  motionLabel?: string;
16
16
  };
17
17
  export declare function engineTitle(e: EnginePlugin | undefined, id?: string): string;
18
- /** Название, описание и подписи полей варианта экспорта на текущем языке. */
18
+ /** Name, description and field labels of an export option in the current language. */
19
19
  export declare function exporterText(x: ExporterPlugin): {
20
20
  title: string;
21
21
  description: string;
package/dist/i18n.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"i18n.js","sources":["../src/i18n.ts"],"sourcesContent":["// Язык текстов библиотеки: статусы сессии, подписи текстур, слои сцены, названия пресетов, движков и вариантов экспорта.\n// По умолчанию русский (тексты в плагинах написаны по-русски), английский — из EnginePlugin.i18n (подключает реестр).\n// Сведения о моделях (Model.info) и сообщения об ошибках форматов пока остаются на русском.\nimport type { EngineTexts, EnginePlugin, ExportField, ExporterPlugin, GamePreset } from './plugin';\n\n/** Английские тексты подключённых движков (заполняет Registry.use). */\nconst EN: Required<Pick<EngineTexts, 'groups' | 'presets' | 'exporters'>> = { groups: {}, presets: {}, exporters: {} };\n/** Добавить тексты движка (вызывает реестр при подключении). */\nexport function registerTexts(e: EnginePlugin): void {\n const t = e.i18n?.en;\n if (!t) return;\n Object.assign(EN.groups, t.groups);\n Object.assign(EN.presets, t.presets);\n Object.assign(EN.exporters, t.exporters);\n}\n\nexport type Locale = 'ru' | 'en';\nlet locale: Locale = 'ru';\n\nexport function setLocale(l: Locale): void { locale = l; }\nexport function getLocale(): Locale { return locale; }\n\nconst MSG: Record<string, { ru: string; en: string }> = {\n 'session.readingArchive': { ru: 'Чтение архива {name}…', en: 'Reading archive {name}…' },\n 'session.archiveError': { ru: 'Ошибка архива: {error}', en: 'Archive error: {error}' },\n 'session.archives': { ru: 'Архивов: {n}', en: 'Archives: {n}' },\n 'session.archivesSkipped': { ru: 'Архивов: {n}; пропущено файлов: {k} ({list})', en: 'Archives: {n}; files skipped: {k} ({list})' },\n 'session.indexing': { ru: 'Индексация папки: файлов {n}', en: 'Indexing folder: {n} files' },\n 'session.folderTextures': { ru: 'Индексация текстур папки…', en: 'Indexing folder textures…' },\n 'session.findArchives': { ru: 'Поиск архивов в папке…', en: 'Looking for archives in the folder…' },\n 'session.loadingName': { ru: 'Загрузка {name}…', en: 'Loading {name}…' },\n 'session.companions': { ru: 'Запомнено: {list} — теперь выберите сами архивы', en: 'Remembered: {list} — now choose the archives themselves' },\n 'session.folder': { ru: 'Папка «{name}»: файлов {n}', en: 'Folder \"{name}\": {n} files' },\n 'session.dict': { ru: '{id} {name}: текстур {n}', en: '{id} {name}: {n} textures' },\n 'session.dictErrors': { ru: ', ошибок {n} ({first})', en: ', {n} errors ({first})' },\n 'session.dictError': { ru: 'Ошибка {id}: {error}', en: '{id} error: {error}' },\n 'session.noModel': { ru: 'Нет файла модели, подходящего под выбранный формат', en: 'No model file matches the selected format' },\n 'session.loading': { ru: 'Загрузка…', en: 'Loading…' },\n 'session.loaded': { ru: 'Загружено: {name}', en: 'Loaded: {name}' },\n 'session.error': { ru: 'Ошибка: {error}', en: 'Error: {error}' },\n 'session.noMotionModel': { ru: 'Сначала загрузите модель, поддерживающую внешние анимации', en: 'Load a model that supports external animations first' },\n 'session.motionsAdded': { ru: 'Добавлено анимаций: {n} ({name})', en: 'Animations added: {n} ({name})' },\n 'session.motionError': { ru: 'Ошибка анимации: {error}', en: 'Animation error: {error}' },\n 'tex.noFile': { ru: 'нет файла', en: 'no file' },\n 'tex.error': { ru: 'ошибка: {error}', en: 'error: {error}' },\n 'tex.normalNoFile': { ru: 'N нет файла', en: 'N no file' },\n 'tex.pbrFactors': { ru: ' (множители)', en: ' (factors)' },\n 'scene.model': { ru: 'Модель', en: 'Model' },\n 'scene.geometry': { ru: 'Геометрия уровня', en: 'Level geometry' },\n 'scene.objects': { ru: 'Объекты', en: 'Objects' },\n 'scene.otherObjects': { ru: 'объекты', en: 'objects' },\n 'scene.mesh': { ru: 'меш {i}', en: 'mesh {i}' },\n 'scene.meshes': { ru: '{n} меш.', en: '{n} meshes' },\n 'scene.tris': { ru: '{n} тр.', en: '{n} tris' },\n};\n\n/** Текст по ключу с подстановкой {параметров}. */\nexport function tr(key: string, params: Record<string, string | number> = {}): string {\n const m = MSG[key];\n const s = m ? m[locale] : key;\n return s.replace(/\\{(\\w+)\\}/g, (_, k: string) => (params[k] !== undefined ? String(params[k]) : `{${k}}`));\n}\n\n/** Тексты пресета игры на текущем языке. */\nexport function presetText(p: GamePreset): { title: string; hint: string; group: string; archiveLabel?: string; motionLabel?: string } {\n const en = locale === 'en' ? EN.presets[p.id] : undefined;\n return {\n title: en?.title ?? p.title,\n hint: en?.hint ?? p.hint ?? '',\n group: locale === 'en' ? EN.groups[p.group] ?? p.group : p.group,\n archiveLabel: en?.archiveLabel ?? p.ui?.archiveLabel,\n motionLabel: en?.motionLabel ?? p.ui?.motionLabel,\n };\n}\n\nexport function engineTitle(e: EnginePlugin | undefined, id = ''): string {\n if (!e) return id;\n return (locale === 'en' && e.i18n?.en?.title) || e.title;\n}\n\n/** Название, описание и подписи полей варианта экспорта на текущем языке. */\nexport function exporterText(x: ExporterPlugin): { title: string; description: string; field(f: ExportField): { label: string; hint?: string; placeholder?: string } } {\n const en = locale === 'en' ? EN.exporters[x.id] : undefined;\n return {\n title: en?.title ?? x.title,\n description: en?.description ?? x.description,\n field: (f) => { const e = en?.fields?.[f.id]; return { label: e?.label ?? f.label, hint: e?.hint ?? f.hint, placeholder: e?.placeholder ?? f.placeholder }; },\n };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"i18n.js","sources":["../src/i18n.ts"],"sourcesContent":["// Language of library texts: session statuses, texture labels, scene layers, names of presets, engines and export options.\n// Russian by default (plugin texts are written in Russian); English comes from EnginePlugin.i18n (attached by the registry).\n// Model details (Model.info) and format error messages are still in Russian.\nimport type { EngineTexts, EnginePlugin, ExportField, ExporterPlugin, GamePreset } from './plugin';\n\n/** English texts of attached engines (filled by Registry.use). */\nconst EN: Required<Pick<EngineTexts, 'groups' | 'presets' | 'exporters'>> = { groups: {}, presets: {}, exporters: {} };\n/** Add an engine's texts (called by the registry when the engine is attached). */\nexport function registerTexts(e: EnginePlugin): void {\n const t = e.i18n?.en;\n if (!t) return;\n Object.assign(EN.groups, t.groups);\n Object.assign(EN.presets, t.presets);\n Object.assign(EN.exporters, t.exporters);\n}\n\nexport type Locale = 'ru' | 'en';\nlet locale: Locale = 'ru';\n\nexport function setLocale(l: Locale): void { locale = l; }\nexport function getLocale(): Locale { return locale; }\n\nconst MSG: Record<string, { ru: string; en: string }> = {\n 'session.readingArchive': { ru: 'Чтение архива {name}…', en: 'Reading archive {name}…' },\n 'session.archiveError': { ru: 'Ошибка архива: {error}', en: 'Archive error: {error}' },\n 'session.archives': { ru: 'Архивов: {n}', en: 'Archives: {n}' },\n 'session.archivesSkipped': { ru: 'Архивов: {n}; пропущено файлов: {k} ({list})', en: 'Archives: {n}; files skipped: {k} ({list})' },\n 'session.indexing': { ru: 'Индексация папки: файлов {n}', en: 'Indexing folder: {n} files' },\n 'session.folderTextures': { ru: 'Индексация текстур папки…', en: 'Indexing folder textures…' },\n 'session.findArchives': { ru: 'Поиск архивов в папке…', en: 'Looking for archives in the folder…' },\n 'session.loadingName': { ru: 'Загрузка {name}…', en: 'Loading {name}…' },\n 'session.companions': { ru: 'Запомнено: {list} — теперь выберите сами архивы', en: 'Remembered: {list} — now choose the archives themselves' },\n 'session.folder': { ru: 'Папка «{name}»: файлов {n}', en: 'Folder \"{name}\": {n} files' },\n 'session.dict': { ru: '{id} {name}: текстур {n}', en: '{id} {name}: {n} textures' },\n 'session.dictErrors': { ru: ', ошибок {n} ({first})', en: ', {n} errors ({first})' },\n 'session.dictError': { ru: 'Ошибка {id}: {error}', en: '{id} error: {error}' },\n 'session.noModel': { ru: 'Нет файла модели, подходящего под выбранный формат', en: 'No model file matches the selected format' },\n 'session.loading': { ru: 'Загрузка…', en: 'Loading…' },\n 'session.loaded': { ru: 'Загружено: {name}', en: 'Loaded: {name}' },\n 'session.error': { ru: 'Ошибка: {error}', en: 'Error: {error}' },\n 'session.noMotionModel': { ru: 'Сначала загрузите модель, поддерживающую внешние анимации', en: 'Load a model that supports external animations first' },\n 'session.motionsAdded': { ru: 'Добавлено анимаций: {n} ({name})', en: 'Animations added: {n} ({name})' },\n 'session.motionError': { ru: 'Ошибка анимации: {error}', en: 'Animation error: {error}' },\n 'tex.noFile': { ru: 'нет файла', en: 'no file' },\n 'tex.error': { ru: 'ошибка: {error}', en: 'error: {error}' },\n 'tex.normalNoFile': { ru: 'N нет файла', en: 'N no file' },\n 'tex.pbrFactors': { ru: ' (множители)', en: ' (factors)' },\n 'scene.model': { ru: 'Модель', en: 'Model' },\n 'scene.geometry': { ru: 'Геометрия уровня', en: 'Level geometry' },\n 'scene.objects': { ru: 'Объекты', en: 'Objects' },\n 'scene.otherObjects': { ru: 'объекты', en: 'objects' },\n 'scene.mesh': { ru: 'меш {i}', en: 'mesh {i}' },\n 'scene.meshes': { ru: '{n} меш.', en: '{n} meshes' },\n 'scene.tris': { ru: '{n} тр.', en: '{n} tris' },\n};\n\n/** Text by key with {parameter} substitution. */\nexport function tr(key: string, params: Record<string, string | number> = {}): string {\n const m = MSG[key];\n const s = m ? m[locale] : key;\n return s.replace(/\\{(\\w+)\\}/g, (_, k: string) => (params[k] !== undefined ? String(params[k]) : `{${k}}`));\n}\n\n/** Game preset texts in the current language. */\nexport function presetText(p: GamePreset): { title: string; hint: string; group: string; archiveLabel?: string; motionLabel?: string } {\n const en = locale === 'en' ? EN.presets[p.id] : undefined;\n return {\n title: en?.title ?? p.title,\n hint: en?.hint ?? p.hint ?? '',\n group: locale === 'en' ? EN.groups[p.group] ?? p.group : p.group,\n archiveLabel: en?.archiveLabel ?? p.ui?.archiveLabel,\n motionLabel: en?.motionLabel ?? p.ui?.motionLabel,\n };\n}\n\nexport function engineTitle(e: EnginePlugin | undefined, id = ''): string {\n if (!e) return id;\n return (locale === 'en' && e.i18n?.en?.title) || e.title;\n}\n\n/** Name, description and field labels of an export option in the current language. */\nexport function exporterText(x: ExporterPlugin): { title: string; description: string; field(f: ExportField): { label: string; hint?: string; placeholder?: string } } {\n const en = locale === 'en' ? EN.exporters[x.id] : undefined;\n return {\n title: en?.title ?? x.title,\n description: en?.description ?? x.description,\n field: (f) => { const e = en?.fields?.[f.id]; return { label: e?.label ?? f.label, hint: e?.hint ?? f.hint, placeholder: e?.placeholder ?? f.placeholder }; },\n };\n}\n"],"names":[],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- /** BC4 → серый, BC5 (two) → нормаль: R, G из блоков, B = Z. */
1
+ /** BC4 → grey, BC5 (two) → normal: R, G from the blocks, B = Z. */
2
2
  export declare function decodeBC45(u8: Uint8Array, off: number, w: number, h: number, two: boolean): Uint8Array;