@gmvjs/dunia1 0.0.0-stage → 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.
- package/LICENSE +21 -0
- package/README.md +72 -2
- package/dist/fat.d.ts +57 -0
- package/dist/fat.js +226 -0
- package/dist/fat.js.map +1 -0
- package/dist/fcb.d.ts +14 -0
- package/dist/fcb.js +86 -0
- package/dist/fcb.js.map +1 -0
- package/dist/games/farcry2.d.ts +7 -0
- package/dist/games/farcry2.js +11 -0
- package/dist/games/farcry2.js.map +1 -0
- package/dist/i18n.d.ts +2 -0
- package/dist/i18n.js +12 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +68 -0
- package/dist/index.js.map +1 -0
- package/dist/level.d.ts +120 -0
- package/dist/level.js +751 -0
- package/dist/level.js.map +1 -0
- package/dist/mab.d.ts +40 -0
- package/dist/mab.js +228 -0
- package/dist/mab.js.map +1 -0
- package/dist/realtree.d.ts +27 -0
- package/dist/realtree.js +127 -0
- package/dist/realtree.js.map +1 -0
- package/dist/rml.d.ts +15 -0
- package/dist/rml.js +77 -0
- package/dist/rml.js.map +1 -0
- package/dist/xbg.d.ts +115 -0
- package/dist/xbg.js +524 -0
- package/dist/xbg.js.map +1 -0
- package/dist/xbt.d.ts +3 -0
- package/dist/xbt.js +14 -0
- package/dist/xbt.js.map +1 -0
- package/package.json +54 -4
package/dist/index.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { defineEngine } from "@gmvjs/core/plugin";
|
|
2
|
+
import { EN } from "./i18n.js";
|
|
3
|
+
const ui = { archives: true, archiveExts: ["fat", "dat"], archiveLabel: "Открыть *.fat + *.dat…" };
|
|
4
|
+
const dunia1Engine = defineEngine({
|
|
5
|
+
id: "dunia1",
|
|
6
|
+
title: "Dunia Engine 1 (Far Cry 2)",
|
|
7
|
+
i18n: { en: EN },
|
|
8
|
+
codecs: [{ id: "xbt", extensions: ["xbt"], decode: async (buf) => (await import("./xbt.js")).decodeXbt(buf) }],
|
|
9
|
+
formats: [
|
|
10
|
+
{
|
|
11
|
+
id: "dunia-xbg",
|
|
12
|
+
title: "Far Cry 2: модель .xbg",
|
|
13
|
+
engine: "dunia1",
|
|
14
|
+
extensions: ["xbg"],
|
|
15
|
+
motionExtensions: ["mab"],
|
|
16
|
+
status: "beta",
|
|
17
|
+
sniff: (h) => String.fromCharCode(...h.subarray(0, 4)) === "HSEM" ? 1 : 0,
|
|
18
|
+
load: async (main, ctx) => (await import("./xbg.js")).loadXbg(main, ctx),
|
|
19
|
+
afterLoad: async (ctx) => {
|
|
20
|
+
const arc = ctx.main.archive;
|
|
21
|
+
if (!arc || !ctx.model.loadMotions) return;
|
|
22
|
+
const list = (await import("./xbg.js")).listMabMotions(ctx.model, arc);
|
|
23
|
+
if (list.length) ctx.addMotions(list, "animations");
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "dunia-level",
|
|
28
|
+
title: "Far Cry 2: уровень (worlds/<мир>/maps/*.fc2level — карта 1×1 км с объектами; <мир>.fc2level — весь мир)",
|
|
29
|
+
engine: "dunia1",
|
|
30
|
+
extensions: ["fc2level"],
|
|
31
|
+
status: "beta",
|
|
32
|
+
sniff: (h) => String.fromCharCode(...h.subarray(0, 12)) === "DUNIA-LEVEL\n" ? 1 : 0,
|
|
33
|
+
load: async (main, ctx) => {
|
|
34
|
+
const text = new TextDecoder().decode(await main.arrayBuffer());
|
|
35
|
+
return (await import("./level.js")).loadDuniaLevel(text, main, ctx);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
archives: [
|
|
40
|
+
{
|
|
41
|
+
id: "dunia-fat",
|
|
42
|
+
title: "Far Cry 2: архивы .fat + .dat",
|
|
43
|
+
engine: "dunia1",
|
|
44
|
+
extensions: ["fat"],
|
|
45
|
+
companionExtensions: ["dat"],
|
|
46
|
+
openFromFolder: true,
|
|
47
|
+
sniff: (h) => String.fromCharCode(...h.subarray(0, 4)) === "2TAF" ? 1 : 0,
|
|
48
|
+
open: async (file, siblings) => (await import("./fat.js")).openDuniaArchive(file, siblings)
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
games: [
|
|
52
|
+
{
|
|
53
|
+
id: "farcry2",
|
|
54
|
+
group: "Игры",
|
|
55
|
+
title: "Far Cry 2 (2008)",
|
|
56
|
+
engine: "dunia1",
|
|
57
|
+
formats: ["dunia-xbg", "dunia-level"],
|
|
58
|
+
hint: "Откройте папку игры (или Data_Win32): архивы .fat/.dat подключатся сами, имена файлов берутся из списков ресурсов игры. Модели — graphics/…/*.xbg, уровни — worlds/<мир>/maps/*.fc2level (карта 1×1 км: ландшафт и объекты) и worlds/<мир>/<мир>.fc2level (весь мир — ландшафт).",
|
|
59
|
+
ui,
|
|
60
|
+
status: "beta",
|
|
61
|
+
markers: ["common.fat", "worlds.fat"]
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
});
|
|
65
|
+
export {
|
|
66
|
+
dunia1Engine
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["// Dunia Engine 1 (Far Cry 2): .fat/.dat archives (names by hash, from the game's XML resource lists), .xbg models, .xbm materials,\n// .xbt textures, .mab animations (skeleton: *_ref.skeleton in the model folder or above).\nimport { defineEngine, type GamePreset } from '@gmvjs/core/plugin';\nimport { EN } from './i18n';\n\nconst ui: GamePreset['ui'] = { archives: true, archiveExts: ['fat', 'dat'], archiveLabel: 'Открыть *.fat + *.dat…' };\n\nexport const dunia1Engine = defineEngine({\n id: 'dunia1',\n title: 'Dunia Engine 1 (Far Cry 2)',\n i18n: { en: EN },\n codecs: [{ id: 'xbt', extensions: ['xbt'], decode: async (buf) => (await import('./xbt')).decodeXbt(buf) }],\n formats: [\n {\n id: 'dunia-xbg',\n title: 'Far Cry 2: модель .xbg',\n engine: 'dunia1',\n extensions: ['xbg'],\n motionExtensions: ['mab'],\n status: 'beta',\n sniff: (h) => (String.fromCharCode(...h.subarray(0, 4)) === 'HSEM' ? 1 : 0),\n load: async (main, ctx) => (await import('./xbg')).loadXbg(main, ctx),\n afterLoad: async (ctx) => {\n const arc = ctx.main.archive;\n if (!arc || !ctx.model.loadMotions) return;\n const list = (await import('./xbg')).listMabMotions(ctx.model, arc);\n if (list.length) ctx.addMotions(list, 'animations');\n },\n },\n {\n id: 'dunia-level',\n title: 'Far Cry 2: уровень (worlds/<мир>/maps/*.fc2level — карта 1×1 км с объектами; <мир>.fc2level — весь мир)',\n engine: 'dunia1',\n extensions: ['fc2level'],\n status: 'beta',\n sniff: (h) => (String.fromCharCode(...h.subarray(0, 12)) === 'DUNIA-LEVEL\\n' ? 1 : 0),\n load: async (main, ctx) => {\n const text = new TextDecoder().decode(await main.arrayBuffer());\n return (await import('./level')).loadDuniaLevel(text, main, ctx);\n },\n },\n ],\n archives: [\n {\n id: 'dunia-fat',\n title: 'Far Cry 2: архивы .fat + .dat',\n engine: 'dunia1',\n extensions: ['fat'],\n companionExtensions: ['dat'],\n openFromFolder: true,\n sniff: (h) => (String.fromCharCode(...h.subarray(0, 4)) === '2TAF' ? 1 : 0),\n open: async (file, siblings) => (await import('./fat')).openDuniaArchive(file, siblings),\n },\n ],\n games: [\n {\n id: 'farcry2',\n group: 'Игры',\n title: 'Far Cry 2 (2008)',\n engine: 'dunia1',\n formats: ['dunia-xbg', 'dunia-level'],\n hint: 'Откройте папку игры (или Data_Win32): архивы .fat/.dat подключатся сами, имена файлов берутся из списков ресурсов игры. Модели — graphics/…/*.xbg, уровни — worlds/<мир>/maps/*.fc2level (карта 1×1 км: ландшафт и объекты) и worlds/<мир>/<мир>.fc2level (весь мир — ландшафт).',\n ui,\n status: 'beta',\n markers: ['common.fat', 'worlds.fat'],\n },\n ],\n});\n"],"names":[],"mappings":";;AAKA,MAAM,KAAuB,EAAE,UAAU,MAAM,aAAa,CAAC,OAAO,KAAK,GAAG,cAAc,yBAAA;AAEnF,MAAM,eAAe,aAAa;AAAA,EACvC,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,MAAM,EAAE,IAAI,GAAA;AAAA,EACZ,QAAQ,CAAC,EAAE,IAAI,OAAO,YAAY,CAAC,KAAK,GAAG,QAAQ,OAAO,SAAS,MAAM,OAAO,UAAO,GAAG,UAAU,GAAG,GAAG;AAAA,EAC1G,SAAS;AAAA,IACP;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,YAAY,CAAC,KAAK;AAAA,MAClB,kBAAkB,CAAC,KAAK;AAAA,MACxB,QAAQ;AAAA,MACR,OAAO,CAAC,MAAO,OAAO,aAAa,GAAG,EAAE,SAAS,GAAG,CAAC,CAAC,MAAM,SAAS,IAAI;AAAA,MACzE,MAAM,OAAO,MAAM,SAAS,MAAM,OAAO,UAAO,GAAG,QAAQ,MAAM,GAAG;AAAA,MACpE,WAAW,OAAO,QAAQ;AACxB,cAAM,MAAM,IAAI,KAAK;AACrB,YAAI,CAAC,OAAO,CAAC,IAAI,MAAM,YAAa;AACpC,cAAM,QAAQ,MAAM,OAAO,UAAO,GAAG,eAAe,IAAI,OAAO,GAAG;AAClE,YAAI,KAAK,OAAQ,KAAI,WAAW,MAAM,YAAY;AAAA,MACpD;AAAA,IAAA;AAAA,IAEF;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,YAAY,CAAC,UAAU;AAAA,MACvB,QAAQ;AAAA,MACR,OAAO,CAAC,MAAO,OAAO,aAAa,GAAG,EAAE,SAAS,GAAG,EAAE,CAAC,MAAM,kBAAkB,IAAI;AAAA,MACnF,MAAM,OAAO,MAAM,QAAQ;AACzB,cAAM,OAAO,IAAI,YAAA,EAAc,OAAO,MAAM,KAAK,aAAa;AAC9D,gBAAQ,MAAM,OAAO,YAAS,GAAG,eAAe,MAAM,MAAM,GAAG;AAAA,MACjE;AAAA,IAAA;AAAA,EACF;AAAA,EAEF,UAAU;AAAA,IACR;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,YAAY,CAAC,KAAK;AAAA,MAClB,qBAAqB,CAAC,KAAK;AAAA,MAC3B,gBAAgB;AAAA,MAChB,OAAO,CAAC,MAAO,OAAO,aAAa,GAAG,EAAE,SAAS,GAAG,CAAC,CAAC,MAAM,SAAS,IAAI;AAAA,MACzE,MAAM,OAAO,MAAM,cAAc,MAAM,OAAO,UAAO,GAAG,iBAAiB,MAAM,QAAQ;AAAA,IAAA;AAAA,EACzF;AAAA,EAEF,OAAO;AAAA,IACL;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,SAAS,CAAC,aAAa,aAAa;AAAA,MACpC,MAAM;AAAA,MACN;AAAA,MACA,QAAQ;AAAA,MACR,SAAS,CAAC,cAAc,YAAY;AAAA,IAAA;AAAA,EACtC;AAEJ,CAAC;"}
|
package/dist/level.d.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { LoadContext } from '@gmvjs/core/plugin';
|
|
2
|
+
import type { Mesh, Model, VFile } from '@gmvjs/core/types';
|
|
3
|
+
import { type DuniaArchiveApi, type DuniaWorld, type FatRef } from './fat';
|
|
4
|
+
import { type FcbObject } from './fcb';
|
|
5
|
+
export interface TerrainLayer {
|
|
6
|
+
name: string;
|
|
7
|
+
texture: string;
|
|
8
|
+
normal: string;
|
|
9
|
+
tiling: number;
|
|
10
|
+
}
|
|
11
|
+
/** Terrain layers and the world grid from game.xml. */
|
|
12
|
+
export declare function worldDescriptor(u: Uint8Array): {
|
|
13
|
+
layers: TerrainLayer[];
|
|
14
|
+
countX: number;
|
|
15
|
+
countY: number;
|
|
16
|
+
};
|
|
17
|
+
export interface TerrainSector {
|
|
18
|
+
id: number;
|
|
19
|
+
min: number;
|
|
20
|
+
max: number;
|
|
21
|
+
layers: number[];
|
|
22
|
+
height: Float32Array;
|
|
23
|
+
mask: Uint8Array;
|
|
24
|
+
water: number;
|
|
25
|
+
}
|
|
26
|
+
/** Sector terrain (see the file header). */
|
|
27
|
+
export declare function parseTerrainSector(u: Uint8Array): TerrainSector;
|
|
28
|
+
/**
|
|
29
|
+
* Vertex layer: the highest set bit of the mask's low nibble whose slot is used (the top painted layer);
|
|
30
|
+
* an empty slot is 255; with no bits (sector edge), the first used slot.
|
|
31
|
+
*/
|
|
32
|
+
export declare function vertexLayer(mask: number, slots: readonly number[]): number;
|
|
33
|
+
/**
|
|
34
|
+
* Object angles (degrees) -> quaternion [w, x, y, z]: R = Rz(z)·Ry(y)·Rx(x) (X first, then Y, then Z, the yaw).
|
|
35
|
+
* Verified on objects on slopes: the object's "up" axis matches the ground normal (see the level test).
|
|
36
|
+
*/
|
|
37
|
+
export declare function anglesQuat(a: readonly number[]): number[];
|
|
38
|
+
/** Level object: model, visible parts (empty = all), pose. */
|
|
39
|
+
export interface LevelEntity {
|
|
40
|
+
name: string;
|
|
41
|
+
model: string;
|
|
42
|
+
parts: string[];
|
|
43
|
+
pos: number[];
|
|
44
|
+
angles: number[];
|
|
45
|
+
layer: string;
|
|
46
|
+
archetype?: string;
|
|
47
|
+
}
|
|
48
|
+
/** entitylibrary.fcb archetypes: hidName -> model and parts. */
|
|
49
|
+
export declare function entityLibrary(root: FcbObject): Map<string, {
|
|
50
|
+
model: string;
|
|
51
|
+
parts: string[];
|
|
52
|
+
}>;
|
|
53
|
+
/** Sector objects (WorldSector) with models; archetypes from the library. */
|
|
54
|
+
export interface LevelMarker {
|
|
55
|
+
kind: 'particle' | 'light';
|
|
56
|
+
name: string;
|
|
57
|
+
pos: number[];
|
|
58
|
+
}
|
|
59
|
+
export declare function sectorEntities(root: FcbObject, lib: Map<string, {
|
|
60
|
+
model: string;
|
|
61
|
+
parts: string[];
|
|
62
|
+
}>): {
|
|
63
|
+
list: LevelEntity[];
|
|
64
|
+
noModel: number;
|
|
65
|
+
unknownArchetype: Set<string>;
|
|
66
|
+
markers: LevelMarker[];
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Sector collections (CCollectionComponent of CSectorEntity: rocks, debris, vegetation placed with the editor brush):
|
|
70
|
+
* 0x114CFEE7 is a set (frame 0x17E7C62E–0x2BEAF977), its data 0xA18C8484: 0x89C8C095 = model path hashes (like the names
|
|
71
|
+
* in .fat), 0x58AD43E2 = group count per model, 0x798C33D5 = groups (u8 instance count, then the first index),
|
|
72
|
+
* 0xB0484974 = group centers and radii; instances: 0xD28100C0 = u16 x, u16 y (decimetres), 0x8E2752CA = f32 z,
|
|
73
|
+
* 0x98C678BD = u32 (low 16 bits: rotation about Z, full circle = 65536; high bits not decoded), 0x9D1CAE67 = colors.
|
|
74
|
+
* Every array is a u32 count followed by the elements.
|
|
75
|
+
*/
|
|
76
|
+
export declare function sectorCollections(root: FcbObject): Array<{
|
|
77
|
+
hash: number;
|
|
78
|
+
pos: number[];
|
|
79
|
+
yaw: number;
|
|
80
|
+
}>;
|
|
81
|
+
/** Terrain grid step (m): a power of two so that the vertex count stays within the budget. */
|
|
82
|
+
export declare const terrainStep: (w: number, h: number, budget?: number) => number;
|
|
83
|
+
/** Terrain meshes of an area: one mesh per layer (a triangle goes by the layer of its corner), UVs tile the layer (Tiling repeats per sector). */
|
|
84
|
+
export declare function terrainMeshes(spec: {
|
|
85
|
+
x0: number;
|
|
86
|
+
y0: number;
|
|
87
|
+
w: number;
|
|
88
|
+
h: number;
|
|
89
|
+
}, sectors: Map<number, TerrainSector>, widthInSectors: number, layers: TerrainLayer[], step: number): Mesh[];
|
|
90
|
+
/** Water: for each sector with a water level, grid cells where the ground is below it, as a plane at that level; translucent. */
|
|
91
|
+
export declare function waterMesh(spec: {
|
|
92
|
+
x0: number;
|
|
93
|
+
y0: number;
|
|
94
|
+
w: number;
|
|
95
|
+
h: number;
|
|
96
|
+
}, sectors: Map<number, TerrainSector>, widthInSectors: number, step: number): Mesh | null;
|
|
97
|
+
/** World index: world files, terrain by sector number, object files (WorldSector) by X, Y. Built once per archive. */
|
|
98
|
+
interface WorldIndex {
|
|
99
|
+
files: FatRef[];
|
|
100
|
+
terrain: Map<number, FatRef>;
|
|
101
|
+
terrainEnd: number;
|
|
102
|
+
objects?: Promise<Map<number, FatRef[]>>;
|
|
103
|
+
full?: boolean;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* World index. Normally a world's files are the .dat range between the previous world's .sectorsdep.fcb and its own (worlds.dat). If
|
|
107
|
+
* that range has no terrain or sector numbers repeat (dlc1.dat: first the sectors of all worlds, then their named files), the k-th .dat terrain block belongs to the k-th world
|
|
108
|
+
* in .sectorsdep.fcb order, and the world's files run from the start of its block to the start of the next.
|
|
109
|
+
*/
|
|
110
|
+
export declare function worldIndex(api: DuniaArchiveApi, world: DuniaWorld): Promise<WorldIndex>;
|
|
111
|
+
/**
|
|
112
|
+
* World object files: .fcb with a WorldSector root. In large worlds they follow the terrain block (mission layer objects; next to
|
|
113
|
+
* the terrain are only vegetation collection sectors), in small worlds they are mixed with the terrain. X, Y are the root's first
|
|
114
|
+
* values, read from the start of the file.
|
|
115
|
+
*/
|
|
116
|
+
export declare function worldObjects(api: DuniaArchiveApi, world: DuniaWorld, idx: WorldIndex): Promise<Map<number, FatRef[]>>;
|
|
117
|
+
/** X, Y from the start of an .fcb with a WorldSector root (otherwise null). */
|
|
118
|
+
export declare function worldSectorXY(h: Uint8Array): [number, number] | null;
|
|
119
|
+
export declare function loadDuniaLevel(text: string, main: VFile, ctx: LoadContext): Promise<Model>;
|
|
120
|
+
export {};
|