lecodes-assets 0.2.0 → 2.4.10
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/README.md +738 -449
- package/dist/cli.js +19760 -8612
- package/dist/index.js +19334 -8167
- package/package.json +15 -3
- package/src/bvh/fromDocument.ts +136 -0
- package/src/bvh/toDocument.ts +202 -0
- package/src/cli-kit/command.ts +100 -0
- package/src/cli-kit/help.ts +72 -0
- package/src/cli-kit/index.ts +4 -0
- package/src/cli-kit/parse.ts +72 -0
- package/src/cli-kit/run.ts +52 -0
- package/src/cli.ts +24 -0
- package/src/commands/avatar.ts +77 -0
- package/src/commands/bvh.ts +26 -0
- package/src/commands/convert.ts +156 -0
- package/src/commands/doctor.ts +67 -0
- package/src/commands/geometry.ts +45 -0
- package/src/commands/index.ts +24 -0
- package/src/commands/marks.ts +38 -0
- package/src/commands/mirror.ts +35 -0
- package/src/commands/probe.ts +43 -0
- package/src/commands/retarget/donor.ts +63 -0
- package/src/commands/retarget/index.ts +169 -0
- package/src/commands/retarget/map.ts +81 -0
- package/src/commands/retarget/report.ts +77 -0
- package/src/commands/scene.ts +103 -0
- package/src/commands/shared.ts +121 -0
- package/src/commands/sky.ts +35 -0
- package/src/commands/terrainPack.ts +40 -0
- package/src/commands/unpack.ts +43 -0
- package/src/commands/unreal.ts +109 -0
- package/src/commands/unwrap.ts +56 -0
- package/src/doctor/cardTrim.ts +286 -0
- package/src/doctor/doctor.ts +305 -0
- package/src/doctor/lod.ts +177 -0
- package/src/doctor/merge/accessors.ts +30 -0
- package/src/doctor/merge/atlasLayout.ts +94 -0
- package/src/doctor/merge/atlasPixels.ts +114 -0
- package/src/doctor/merge/bindSpace.ts +59 -0
- package/src/doctor/merge/classify.ts +136 -0
- package/src/doctor/merge/geometry.ts +167 -0
- package/src/doctor/merge/index.ts +98 -0
- package/src/doctor/merge/matrix.ts +60 -0
- package/src/doctor/merge/plan.ts +88 -0
- package/src/doctor/merge/textures.ts +60 -0
- package/src/doctor/merge/types.ts +77 -0
- package/src/doctor/normalizeScale.ts +220 -0
- package/src/doctor/reduceGlbBones.ts +218 -0
- package/src/doctor/shading.ts +95 -0
- package/src/doctor/transcodeKtx2.ts +275 -0
- package/src/doctor/transcodeTextures.ts +101 -0
- package/src/doctor/validateGlb.ts +264 -0
- package/src/doctor/vertexAo.ts +321 -0
- package/src/fbx/convert.ts +327 -0
- package/src/fbx/read.ts +111 -0
- package/src/fbx/toDocument.ts +227 -0
- package/src/filmstrip.ts +263 -0
- package/src/geometry.ts +222 -0
- package/src/index.ts +19 -0
- package/src/mergeSkins.ts +117 -0
- package/src/probe.ts +167 -0
- package/src/props.ts +137 -0
- package/src/retarget/automap.ts +276 -0
- package/src/retarget/ghost.ts +23 -0
- package/src/retarget/heading.ts +238 -0
- package/src/retarget/humanoid.ts +572 -0
- package/src/retarget/inPlace.ts +130 -0
- package/src/retarget/marks.ts +581 -0
- package/src/retarget/mirror.ts +0 -0
- package/src/retarget/retarget.ts +2375 -0
- package/src/retarget/rig.ts +545 -0
- package/src/retarget/unityAnim.ts +171 -0
- package/src/retarget/writeMarks.ts +134 -0
- package/src/sky/analyze.ts +136 -0
- package/src/sky/cmgen.ts +71 -0
- package/src/sky/index.ts +200 -0
- package/src/terrainPack.ts +208 -0
- package/src/textures/resolveImage.ts +202 -0
- package/src/unity/colliderFit.ts +287 -0
- package/src/unity/fbxModel.ts +194 -0
- package/src/unity/foliage.ts +63 -0
- package/src/unity/gameObjects.ts +81 -0
- package/src/unity/guid.ts +104 -0
- package/src/unity/image.ts +58 -0
- package/src/unity/kindGlb.ts +364 -0
- package/src/unity/lecodesLevel.ts +381 -0
- package/src/unity/levelTerrain.ts +116 -0
- package/src/unity/matFile.ts +179 -0
- package/src/unity/materialSlots.ts +53 -0
- package/src/unity/materialWriter.ts +306 -0
- package/src/unity/materials.ts +198 -0
- package/src/unity/math.ts +82 -0
- package/src/unity/png.ts +75 -0
- package/src/unity/prefabTree.ts +285 -0
- package/src/unity/prefabs.ts +54 -0
- package/src/unity/propertyPath.ts +43 -0
- package/src/unity/scene.ts +384 -0
- package/src/unity/serialized.ts +242 -0
- package/src/unity/terrain.ts +221 -0
- package/src/unity/triBudget.ts +77 -0
- package/src/unity/triplanar.ts +122 -0
- package/src/unity/unityMesh.ts +151 -0
- package/src/unity/unpack.ts +149 -0
- package/src/unity/yaml.ts +131 -0
- package/src/unreal/anim.ts +284 -0
- package/src/unreal/clipSource.ts +25 -0
- package/src/unreal/convert.ts +67 -0
- package/src/unreal/convertAsset.ts +81 -0
- package/src/unreal/index.ts +115 -0
- package/src/unreal/list.ts +113 -0
- package/src/unreal/materialGraph.ts +224 -0
- package/src/unreal/materials.ts +399 -0
- package/src/unreal/mesh.ts +205 -0
- package/src/unreal/oodle.ts +153 -0
- package/src/unreal/package.ts +677 -0
- package/src/unreal/probe.ts +144 -0
- package/src/unreal/resolve.ts +53 -0
- package/src/unreal/rig.ts +149 -0
- package/src/unreal/sequencer.ts +193 -0
- package/src/unreal/skeleton.ts +74 -0
- package/src/unreal/sound.ts +128 -0
- package/src/unreal/texture.ts +269 -0
- package/src/unreal/toDocument.ts +367 -0
- package/src/unreal/versions.ts +108 -0
- package/src/unwrap/index.ts +167 -0
- package/src/unwrap/lightmapUv.ts +761 -0
- package/src/wasm/fbxReader.js +14 -0
- package/src/wasm/lightmapUv.js +14 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// Oodle: the codec Unreal compresses editor payloads (texture sources) with. It is proprietary —
|
|
2
|
+
// RAD Game Tools', licensed by Epic — so nothing is shipped here. The decoder is loaded from a
|
|
3
|
+
// library the machine ALREADY has: any `oo2core_*_win64.dll` (an Unreal install has one under
|
|
4
|
+
// `Engine/Binaries/ThirdParty/Oodle/`, and so does most every UE game), or the Linux/macOS twin.
|
|
5
|
+
// Only `OodleLZ_Decompress` is called, and only on bytes that came out of a `.uasset`.
|
|
6
|
+
//
|
|
7
|
+
// Two runtimes matter: the monorepo and the studio run on Bun (`bun:ffi`), while the `lecodes` CLI
|
|
8
|
+
// is a Node binary (`koffi`, an optional dependency). Whichever is present is used; when neither is,
|
|
9
|
+
// the message says exactly what to install or point at, and the caller carries on without textures.
|
|
10
|
+
|
|
11
|
+
import { existsSync, readdirSync, statSync } from "node:fs"
|
|
12
|
+
import { homedir } from "node:os"
|
|
13
|
+
import { join } from "node:path"
|
|
14
|
+
|
|
15
|
+
export type OodleDecoder = {
|
|
16
|
+
path: string
|
|
17
|
+
runtime: "bun:ffi" | "koffi"
|
|
18
|
+
/** Decompress one block. `rawSize` is exact — Oodle needs to be told the output size. */
|
|
19
|
+
decompress: (src: Uint8Array, rawSize: number) => Uint8Array
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const LIB_NAMES = process.platform === "win32" ? [ /^oo2core_\d+_win64\.dll$/i ]
|
|
23
|
+
: process.platform === "darwin" ? [ /^liboo2coremac64\..*$/i, /^liboo2core.*\.dylib$/i ]
|
|
24
|
+
: [ /^liboo2corelinux64\.so.*$/i, /^liboo2core.*\.so.*$/i ]
|
|
25
|
+
|
|
26
|
+
const isLib = (name: string): boolean => LIB_NAMES.some((re) => re.test(name))
|
|
27
|
+
|
|
28
|
+
/** Files matching an Oodle library name in `dir` (not recursive), newest SDK first. */
|
|
29
|
+
const libsIn = (dir: string): string[] => {
|
|
30
|
+
try {
|
|
31
|
+
if (!existsSync(dir) || !statSync(dir).isDirectory()) return []
|
|
32
|
+
return readdirSync(dir).filter(isLib).sort().reverse().map((n) => join(dir, n))
|
|
33
|
+
} catch { return [] }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Directories an Unreal install keeps the redistributable in (a few layouts across 5.x). */
|
|
37
|
+
const unrealDirs = (): string[] => {
|
|
38
|
+
const roots: string[] = []
|
|
39
|
+
const bases = process.platform === "win32"
|
|
40
|
+
? [ "C:/Program Files/Epic Games", "C:/Program Files (x86)/Epic Games", "D:/Epic Games", "D:/Program Files/Epic Games" ]
|
|
41
|
+
: [ join(homedir(), "UnrealEngine"), "/opt/UnrealEngine", "/Users/Shared/Epic Games" ]
|
|
42
|
+
for (const base of bases) {
|
|
43
|
+
try {
|
|
44
|
+
if (!existsSync(base)) continue
|
|
45
|
+
for (const entry of readdirSync(base)) {
|
|
46
|
+
if (!/^UE_?5/i.test(entry) && !/^Unreal/i.test(entry)) continue
|
|
47
|
+
const engine = join(base, entry, "Engine", "Binaries", "ThirdParty", "Oodle")
|
|
48
|
+
if (!existsSync(engine)) continue
|
|
49
|
+
roots.push(engine)
|
|
50
|
+
for (const sub of readdirSync(engine)) { // Win64 / Linux / Mac, or an SDK folder
|
|
51
|
+
const p = join(engine, sub)
|
|
52
|
+
if (statSync(p).isDirectory()) {
|
|
53
|
+
roots.push(p)
|
|
54
|
+
for (const sub2 of readdirSync(p)) { const q = join(p, sub2); if (statSync(q).isDirectory()) roots.push(q) }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} catch { /* an unreadable install is not an error */ }
|
|
59
|
+
}
|
|
60
|
+
return roots
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Where the library was looked for, in order — what the "not found" message lists. */
|
|
64
|
+
export const oodleSearchPath = (): string[] => [
|
|
65
|
+
...[ process.env.LECODES_OODLE, process.env.UE_OODLE_DLL ].filter((v): v is string => !!v),
|
|
66
|
+
join(homedir(), ".lecodes", "oodle"),
|
|
67
|
+
...unrealDirs(),
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
/** The library file, or null. An env var may name the file itself or the folder holding it. */
|
|
71
|
+
export const findOodle = (): string | null => {
|
|
72
|
+
for (const entry of oodleSearchPath()) {
|
|
73
|
+
if (existsSync(entry) && statSync(entry).isFile()) return entry
|
|
74
|
+
const hit = libsIn(entry)[0]
|
|
75
|
+
if (hit) return hit
|
|
76
|
+
}
|
|
77
|
+
return null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const OODLE_MISSING_MESSAGE =
|
|
81
|
+
"Unreal compresses texture sources with Oodle, which is proprietary and cannot be shipped here. " +
|
|
82
|
+
"Point LECODES_OODLE at an oo2core library — every Unreal install has one under " +
|
|
83
|
+
"Engine/Binaries/ThirdParty/Oodle/, and so does most any UE game — or drop the file into ~/.lecodes/oodle/. " +
|
|
84
|
+
"Everything else in the asset (geometry, skin, animation) reads without it."
|
|
85
|
+
|
|
86
|
+
let cached: OodleDecoder | null | undefined
|
|
87
|
+
|
|
88
|
+
/** The decoder, or null when no library/runtime is available. Loaded once per process. */
|
|
89
|
+
export const getOodle = async (): Promise<OodleDecoder | null> => {
|
|
90
|
+
if (cached !== undefined) return cached
|
|
91
|
+
const path = findOodle()
|
|
92
|
+
if (!path) return (cached = null)
|
|
93
|
+
cached = (await loadBunFfi(path)) ?? (await loadKoffi(path))
|
|
94
|
+
return cached
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Reset for tests (a decoder is otherwise loaded once and kept). */
|
|
98
|
+
export const resetOodle = (): void => { cached = undefined }
|
|
99
|
+
|
|
100
|
+
// OodleLZ_Decompress(src, srcLen, dst, dstLen, fuzzSafe=1, checkCRC=0, verbosity=0,
|
|
101
|
+
// decBufBase, decBufSize, cb, cbUser, decoderMem, decoderMemSize, threadPhase=3)
|
|
102
|
+
// The trailing arguments are the "no scratch buffer, no callback, decode in one go" defaults.
|
|
103
|
+
|
|
104
|
+
const loadBunFfi = async (path: string): Promise<OodleDecoder | null> => {
|
|
105
|
+
if (typeof (globalThis as { Bun?: unknown }).Bun === "undefined") return null
|
|
106
|
+
try {
|
|
107
|
+
const { dlopen, FFIType, ptr } = await import("bun:ffi")
|
|
108
|
+
const lib = dlopen(path, {
|
|
109
|
+
OodleLZ_Decompress: {
|
|
110
|
+
args: [ FFIType.ptr, FFIType.i64, FFIType.ptr, FFIType.i64, FFIType.i32, FFIType.i32, FFIType.i32,
|
|
111
|
+
FFIType.ptr, FFIType.i64, FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.i64, FFIType.i32 ],
|
|
112
|
+
returns: FFIType.i64,
|
|
113
|
+
},
|
|
114
|
+
})
|
|
115
|
+
return {
|
|
116
|
+
path, runtime: "bun:ffi",
|
|
117
|
+
decompress: (src, rawSize) => {
|
|
118
|
+
// Oodle may read a little past the end of the output buffer; the pad is what UE allocates too.
|
|
119
|
+
const dst = new Uint8Array(rawSize + 64)
|
|
120
|
+
const n = Number(lib.symbols.OodleLZ_Decompress(ptr(src), BigInt(src.byteLength), ptr(dst), BigInt(rawSize),
|
|
121
|
+
1, 0, 0, null, 0n, null, null, null, 0n, 3))
|
|
122
|
+
if (n !== rawSize) throw new Error(`Oodle decompressed ${n} bytes, expected ${rawSize} (${path})`)
|
|
123
|
+
return dst.subarray(0, rawSize)
|
|
124
|
+
},
|
|
125
|
+
}
|
|
126
|
+
} catch { return null }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const loadKoffi = async (path: string): Promise<OodleDecoder | null> => {
|
|
130
|
+
try {
|
|
131
|
+
// @ts-ignore — an optional peer: present only when the Node build needs it
|
|
132
|
+
const koffi = (await import("koffi")).default as {
|
|
133
|
+
load: (p: string) => { func: (signature: string) => (...args: unknown[]) => number }
|
|
134
|
+
}
|
|
135
|
+
const lib = koffi.load(path)
|
|
136
|
+
const fn = lib.func("int64_t OodleLZ_Decompress(const void *src, int64_t srcLen, void *dst, int64_t dstLen, "
|
|
137
|
+
+ "int fuzz, int crc, int verbosity, void *decBufBase, int64_t decBufSize, void *cb, void *cbUser, "
|
|
138
|
+
+ "void *decoderMem, int64_t decoderMemSize, int threadPhase)")
|
|
139
|
+
return {
|
|
140
|
+
path, runtime: "koffi",
|
|
141
|
+
decompress: (src, rawSize) => {
|
|
142
|
+
const dst = Buffer.alloc(rawSize + 64)
|
|
143
|
+
const n = Number(fn(src, src.byteLength, dst, rawSize, 1, 0, 0, null, 0, null, null, null, 0, 3))
|
|
144
|
+
if (n !== rawSize) throw new Error(`Oodle decompressed ${n} bytes, expected ${rawSize} (${path})`)
|
|
145
|
+
return new Uint8Array(dst.buffer, dst.byteOffset, rawSize)
|
|
146
|
+
},
|
|
147
|
+
}
|
|
148
|
+
} catch { return null }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export const OODLE_NO_RUNTIME_MESSAGE =
|
|
152
|
+
"an Oodle library was found but no way to call it: run the pipeline under Bun, or `npm i koffi` "
|
|
153
|
+
+ "next to lecodes-assets so the Node build can load it"
|