mirror of
https://github.com/moeru-ai/airi.git
synced 2026-08-14 00:48:06 +00:00
fix(stage-ui-live2d): normalize Live2D model file references (#2047)
Authored-by-agent: Codex <codex@openai.com>
This commit is contained in:
@@ -47,6 +47,19 @@ function createShisihangshiSettingsText(): string {
|
||||
})
|
||||
}
|
||||
|
||||
function createNonAsciiSettingsText(): string {
|
||||
return JSON.stringify({
|
||||
Version: 3,
|
||||
FileReferences: {
|
||||
Moc: '模型文件.moc3',
|
||||
Textures: ['模型贴图.4096/texture_00.png'],
|
||||
Physics: '模型文件.physics3.json',
|
||||
DisplayInfo: '模型文件.cdi3.json',
|
||||
},
|
||||
Groups: [],
|
||||
})
|
||||
}
|
||||
|
||||
const appleDoubleHeader = new Uint8Array([0, 5, 22, 7, 0, 2, 0, 0, 77, 97, 99, 32, 79, 83, 32, 88])
|
||||
|
||||
describe('live2d zip loader settings sanitization', () => {
|
||||
@@ -137,6 +150,89 @@ describe('live2d zip loader settings sanitization', () => {
|
||||
expect(() => settings.validateFiles(files.map(file => encodeURI(file.webkitRelativePath)))).not.toThrow()
|
||||
})
|
||||
|
||||
it('loads an OPFS-restored file directory when model3.json references non-ASCII file names', async () => {
|
||||
await import('./live2d-zip-loader')
|
||||
const { FileLoader } = await import('pixi-live2d-display/cubism4')
|
||||
|
||||
const files = [
|
||||
fileWithRelativePath(
|
||||
createNonAsciiSettingsText(),
|
||||
'模型文件.model3.json',
|
||||
'非ASCII模型26045/模型文件.model3.json',
|
||||
),
|
||||
fileWithRelativePath(
|
||||
new Uint8Array([77, 79, 67, 51]),
|
||||
'模型文件.moc3',
|
||||
'非ASCII模型26045/模型文件.moc3',
|
||||
),
|
||||
fileWithRelativePath(
|
||||
new Uint8Array([1, 2, 3]),
|
||||
'texture_00.png',
|
||||
'非ASCII模型26045/模型贴图.4096/texture_00.png',
|
||||
),
|
||||
fileWithRelativePath(
|
||||
'{}',
|
||||
'模型文件.physics3.json',
|
||||
'非ASCII模型26045/模型文件.physics3.json',
|
||||
),
|
||||
fileWithRelativePath(
|
||||
'{}',
|
||||
'模型文件.cdi3.json',
|
||||
'非ASCII模型26045/模型文件.cdi3.json',
|
||||
),
|
||||
]
|
||||
|
||||
const settings = await FileLoader.createSettings(files)
|
||||
|
||||
// ROOT CAUSE:
|
||||
//
|
||||
// pixi-live2d-display validates OPFS-restored File objects against
|
||||
// encodeURI(file.webkitRelativePath), but the settings created from the
|
||||
// original model3.json currently keep non-ASCII file references unencoded.
|
||||
//
|
||||
// Before the fix, the settings expect "模型文件.moc3" while the available
|
||||
// file list contains URI-encoded non-ASCII directory paths, so validation
|
||||
// reports that the moc3 file does not exist.
|
||||
expect(() => settings.validateFiles(files.map(file => encodeURI(file.webkitRelativePath)))).not.toThrow()
|
||||
})
|
||||
|
||||
it('does not double encode existing URI-encoded model3.json file references', async () => {
|
||||
await import('./live2d-zip-loader')
|
||||
const { FileLoader } = await import('pixi-live2d-display/cubism4')
|
||||
|
||||
const files = [
|
||||
fileWithRelativePath(
|
||||
JSON.stringify({
|
||||
Version: 3,
|
||||
FileReferences: {
|
||||
Moc: '%E6%A8%A1%E5%9E%8B%E6%96%87%E4%BB%B6.moc3',
|
||||
Textures: ['%E6%A8%A1%E5%9E%8B%E8%B4%B4%E5%9B%BE.4096/texture_00.png'],
|
||||
},
|
||||
Groups: [],
|
||||
}),
|
||||
'模型文件.model3.json',
|
||||
'非ASCII模型26045/模型文件.model3.json',
|
||||
),
|
||||
fileWithRelativePath(
|
||||
new Uint8Array([77, 79, 67, 51]),
|
||||
'模型文件.moc3',
|
||||
'非ASCII模型26045/模型文件.moc3',
|
||||
),
|
||||
fileWithRelativePath(
|
||||
new Uint8Array([1, 2, 3]),
|
||||
'texture_00.png',
|
||||
'非ASCII模型26045/模型贴图.4096/texture_00.png',
|
||||
),
|
||||
]
|
||||
|
||||
const settings = await FileLoader.createSettings(files)
|
||||
|
||||
expect(settings.moc).toBe('%E6%A8%A1%E5%9E%8B%E6%96%87%E4%BB%B6.moc3')
|
||||
expect(settings.textures).toEqual(['%E6%A8%A1%E5%9E%8B%E8%B4%B4%E5%9B%BE.4096/texture_00.png'])
|
||||
expect(settings.moc).not.toContain('%25E6')
|
||||
expect(() => settings.validateFiles(files.map(file => encodeURI(file.webkitRelativePath)))).not.toThrow()
|
||||
})
|
||||
|
||||
it('loads an OPFS-restored file directory when a macOS AppleDouble settings sidecar is present before the real settings file', async () => {
|
||||
await import('./live2d-zip-loader')
|
||||
const { FileLoader } = await import('pixi-live2d-display/cubism4')
|
||||
|
||||
@@ -109,7 +109,22 @@ function createModelSettings(text: string, url: string): ModelSettings {
|
||||
throw new Error('Unknown settings JSON')
|
||||
}
|
||||
|
||||
return runtime.createModelSettings(settingsJSON)
|
||||
// pixi-live2d-display validates files with encodeURI(file.webkitRelativePath).
|
||||
// Decode first so archives that already store URI-encoded references do not
|
||||
// get `%` encoded again.
|
||||
const settings = runtime.createModelSettings(settingsJSON)
|
||||
const normalizeFileReference = (file: string) => {
|
||||
try {
|
||||
return encodeURI(decodeURI(file))
|
||||
}
|
||||
catch {
|
||||
return encodeURI(file)
|
||||
}
|
||||
}
|
||||
settings.url = normalizeFileReference(settings.url)
|
||||
settings.replaceFiles(normalizeFileReference)
|
||||
|
||||
return settings
|
||||
}
|
||||
|
||||
export function isSettingsFile(file: string) {
|
||||
|
||||
Reference in New Issue
Block a user