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

  1. Inspect the settings JSON and the ZIP entry list side by side; make referenced paths match entries exactly (separator '/', no './', correct case)
  2. 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)
  3. Avoid URL-encodable characters in file names inside model archives (spaces, #, %)
  4. 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

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


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)