moeru-ai/airi · error · Error
MMD ZIP must contain a .pmx or .pmd model file
Error message
MMD ZIP must contain a .pmx or .pmd model file
What it means
`loadMMDZip(file)` loads the ZIP, lists non-directory entries, and runs `detectMMDVariants(paths)`. If `variants.length === 0` it throws `MMD ZIP must contain a .pmx or .pmd model file` — the archive has no recognizable MMD model mesh file at the top level of detection.
Source
Thrown at packages/stage-ui-mmd/src/utils/mmd-zip-loader.ts:120
return byBasename.get(base) ?? requested
}
}
/**
* Loads an MMD model packaged as a ZIP (the common distribution format: a
* `.pmx`/`.pmd` plus its texture/toon/sphere-map files) into blob URLs ready
* for {@link createMMDLoaderContext}.
*
* Call `dispose()` on unmount or reload to revoke the blob URLs.
*/
export async function loadMMDZip(file: File | Blob | ArrayBuffer): Promise<MMDLoadedAssets> {
const zip = new JSZip()
const archive = await zip.loadAsync(file)
const paths = Object.keys(archive.files).filter(name => !archive.files[name].dir)
const variants = detectMMDVariants(paths)
if (variants.length === 0)
throw new Error('MMD ZIP must contain a .pmx or .pmd model file')
const blobUrls: Record<string, string> = {}
await Promise.all(paths.map(async (path) => {
const entry = archive.files[path]
if (!entry)
return
const blob = await entry.async('blob')
blobUrls[path] = URL.createObjectURL(blob)
}))
const variant = variants[0]
const modelBlobUrl = blobUrls[variant.modelPath]
return {
variant,
variants,
modelBlobUrl,
blobUrls,View on GitHub (pinned to 27111382b4)
Solutions
- Confirm the ZIP contains at least one `.pmx` or `.pmd` model file.
- Route the archive to the correct loader (Live2D for `.model3.json`, Spine for `.skel`+`.atlas`).
- Re-export from the MMD authoring tool ensuring the mesh file is included.
- Inspect `Object.keys(zip.files)` after `loadAsync` to see what was actually detected.
Example fix
// before
const assets = await loadMMDZip(blob) // throws if no pmx/pmd
// after
const zip = await new JSZip().loadAsync(blob)
const hasModel = Object.keys(zip.files).some(n => n.endsWith('.pmx') || n.endsWith('.pmd'))
if (!hasModel) throw new Error('ZIP has no .pmx/.pmd model file')
const assets = await loadMMDZip(blob) Defensive patterns
Strategy: validation
Validate before calling
const zip = await new JSZip().loadAsync(file)
const names = Object.keys(zip.files).filter(n => !zip.files[n].dir)
if (!names.some(n => n.endsWith('.pmx') || n.endsWith('.pmd')))
throw new Error('ZIP has no .pmx/.pmd model file')
await loadMMDZip(file) Type guard
function zipHasMmdModel(names: string[]): boolean {
return names.some(n => n.endsWith('.pmx') || n.endsWith('.pmd'))
} Try / catch
try {
await loadMMDZip(blob)
} catch (e) {
if (e instanceof Error && e.message.includes('must contain a .pmx or .pmd')) {
// suggest the user pick an MMD archive
} else throw e
} Prevention
- Confirm the archive is an MMD export before loading.
- Route non-MMD archives to the correct loader.
- Inspect zip.files entries when in doubt.
When it happens
Trigger: A ZIP that contains textures/audio but no `.pmx` or `.pmd`; a ZIP whose model file uses an unsupported extension (`.pmm`, `.vpd`, `.vmd` motion-only); a corrupt ZIP that `loadAsync` parsed but whose file list is incomplete; a Live2D or Spine archive loaded through the MMD path.
Common situations: User selected the wrong archive type (Live2D/Spine instead of MMD); motion-only MMD package; model file renamed to `.pmc`/typo; ZIP produced by a tool that nested the model in a way `detectMMDVariants` skips.
Related errors
- Empty settings file: ${url}
- Expected exactly one moc file, got ${mocFiles.length} ${file
- Spine ZIP must contain a .skel or .json skeleton file paired
- Spine ZIP must contain a .atlas (or .atlas.txt) file
- Spine ZIP must contain at least one skeleton+atlas pair
AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12).
Data as JSON: /api/errors/9444b798134778ed.
Report an issue: GitHub.