moeru-ai/airi · error · Error
Cannot find file
Error message
Cannot find file: ${path} What it means
Thrown by the ZipLoader.readText override when jsZip.file(path) returns null, i.e. the settings file references a path that does not exist as an entry in the loaded archive. Note that settings get archive-path resolution (normalizeLive2DArchivePath via useArchivePathResolution), but after normalization the entry must still exist verbatim in the ZIP.
Solutions
- Inspect the settings JSON and the ZIP entry list side by side; make referenced paths match entries exactly (separator '/', no './', correct case)
- Re-zip so the .model3.json and its referenced files share the same root (zip the contents, not the wrapping folder, or vice versa to match references)
- Avoid URL-encodable characters in file names inside model archives (spaces, #, %)
- If you control generation, normalize references at build time to plain relative POSIX paths
Example fix
// before
// model3.json contains "FileReferences": { "Moc": "model.moc3", "Textures": ["tex (1).png"] }
// archive entries: 'tex_%20(1).png' -> Cannot find file: tex (1).png
// after
// rename archive entries to match the references exactly (no %20, matching case), or fix the references:
"Textures": ["tex_1.png"] Defensive patterns
Strategy: try-catch
Validate before calling
const exists = (p: string) => Boolean(zip.file(p) ?? zip.file(p.replace(/^\.\//, '')))
const missing = referencedPaths.filter(p => !exists(p))
if (missing.length) throw new Error(`Archive is missing referenced files: ${missing.join(', ')}`) Try / catch
catch (e) { if (e.message.startsWith('Cannot find file:')) { const p = e.message.slice('Cannot find file: '.length); suggestRepack(p); return } throw e } Prevention
- Keep archive entry paths byte-identical to settings references (case, separators, no './')
- Avoid spaces/URL-encodable characters in model file names
- Re-zip from the editor export without renaming
When it happens
Trigger: A .model3.json whose FileReferences use Windows backslashes, URL-encoded characters (%20), './' prefixes, or different case than the actual ZIP entries; a settings JSON referencing textures/motions that were not packed into the archive; entries stored with a leading './' while settings omit it (or vice versa).
Common situations: Zipping on Windows producing different separators than the editor-exported references; archives where the model3.json sits at the root but references files with a prefix folder (or the reverse — zipping the folder vs its contents); hand-edited settings paths; case-sensitive matching failing on macOS-extracted then re-zipped files.
Related errors
- Archive entry not found
- Empty settings file
- Expected a JSON object in
- Expected exactly one moc file, got
- Textures not found
AI-assisted analysis of moeru-ai/airi@9c213115f8 (2026-08-18).
Data as JSON: /api/errors/a1cfe03c5ac611ac.
Report an issue: GitHub.
Appendix: source
Thrown at packages/stage-ui-live2d/src/utils/live2d-zip-loader.ts:211
'': motions.map(motion => ({ File: motion })),
}
: undefined,
},
})
settings.name = modelName
// provide this property for FileLoader
Object.assign(settings, { _objectURL: `example://${settings.url}` })
return settings
}
ZipLoader.readText = async (jsZip: JSZip, path: string) => {
const file = jsZip.file(path)
if (!file) {
throw new Error(`Cannot find file: ${path}`)
}
const text = await file.async('text')
return isSettingsFile(path) ? sanitizeModelSettingsText(text) : text
}
const defaultFileLoaderReadText = FileLoader.readText
FileLoader.createSettings = async (files: File[]) => {
const settingsFile = files.find(file => isSettingsFile(file.webkitRelativePath || file.name))
if (!settingsFile) {
throw new TypeError('Settings file not found')
}
const settingsUrl = settingsFile.webkitRelativePath || settingsFile.name
const settingsText = await FileLoader.readText(settingsFile)
const settings = createModelSettings(settingsText, settingsUrl)View on GitHub (pinned to 9c213115f8)