fix(stage-ui-live2d): support CJK paths in Live2D archives (#2219)
This commit is contained in:
@@ -47,6 +47,17 @@ function createShisihangshiSettingsText(): string {
|
||||
})
|
||||
}
|
||||
|
||||
function createCjkPathSettingsText(): string {
|
||||
return JSON.stringify({
|
||||
Version: 3,
|
||||
FileReferences: {
|
||||
Moc: '测试角色.moc3',
|
||||
Textures: ['中文纹理/texture_00.png'],
|
||||
},
|
||||
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', () => {
|
||||
@@ -83,6 +94,46 @@ describe('live2d zip loader settings sanitization', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('loads a zip model whose settings and resources use CJK paths', async () => {
|
||||
await import('./live2d-zip-loader')
|
||||
const { FileLoader, Live2DModel, ZipLoader } = await import('pixi-live2d-display/cubism4')
|
||||
|
||||
const zip = new JSZip()
|
||||
zip.file('中文路径模型/测试角色.model3.json', createCjkPathSettingsText())
|
||||
zip.file('中文路径模型/测试角色.moc3', new Uint8Array([77, 79, 67, 51]))
|
||||
zip.file('中文路径模型/中文纹理/texture_00.png', new Uint8Array([1, 2, 3]))
|
||||
|
||||
const zipBytes = await zip.generateAsync({ type: 'uint8array' })
|
||||
const reader = await JSZip.loadAsync(await blobFromBytes(zipBytes).arrayBuffer())
|
||||
const settings = await ZipLoader.createSettings(reader)
|
||||
const files = Object.assign(await ZipLoader.unzip(reader, settings), { settings })
|
||||
const objectUrl = `zip://test/${settings.url}`
|
||||
Object.assign(settings, { _objectURL: objectUrl })
|
||||
|
||||
// ROOT CAUSE:
|
||||
//
|
||||
// FileLoader encoded each webkitRelativePath before comparing it with settings.resolveURL().
|
||||
// CJK names therefore became percent-encoded on only one side of the comparison.
|
||||
//
|
||||
// settings.resolveURL('测试角色.moc3') !== encodeURI(file.webkitRelativePath)
|
||||
//
|
||||
// The loader now keeps both sides as decoded archive paths, matching its unzip and upload stages.
|
||||
const context = {
|
||||
source: files,
|
||||
options: {},
|
||||
live2dModel: new Live2DModel(),
|
||||
}
|
||||
|
||||
try {
|
||||
await expect(FileLoader.factory(context, async () => {})).resolves.toBeUndefined()
|
||||
}
|
||||
finally {
|
||||
for (const resourceUrl of Object.values(FileLoader.filesMap[objectUrl] ?? {}))
|
||||
URL.revokeObjectURL(resourceUrl)
|
||||
delete FileLoader.filesMap[objectUrl]
|
||||
}
|
||||
})
|
||||
|
||||
it('loads a zip model when a macOS AppleDouble settings sidecar is present before the real settings file', async () => {
|
||||
await import('./live2d-zip-loader')
|
||||
const { ZipLoader } = await import('pixi-live2d-display/cubism4')
|
||||
@@ -134,7 +185,51 @@ describe('live2d zip loader settings sanitization', () => {
|
||||
const settings = await FileLoader.createSettings(files)
|
||||
|
||||
expect(settings.physics).toBeUndefined()
|
||||
expect(() => settings.validateFiles(files.map(file => encodeURI(file.webkitRelativePath)))).not.toThrow()
|
||||
expect(() => settings.validateFiles(files.map(file => file.webkitRelativePath))).not.toThrow()
|
||||
})
|
||||
|
||||
it('loads an OPFS-restored file directory whose settings and resources use CJK paths', async () => {
|
||||
await import('./live2d-zip-loader')
|
||||
const { FileLoader, Live2DModel } = await import('pixi-live2d-display/cubism4')
|
||||
|
||||
const files = [
|
||||
fileWithRelativePath(
|
||||
createCjkPathSettingsText(),
|
||||
'测试角色.model3.json',
|
||||
'中文路径模型/测试角色.model3.json',
|
||||
),
|
||||
fileWithRelativePath(
|
||||
new Uint8Array([77, 79, 67, 51]),
|
||||
'测试角色.moc3',
|
||||
'中文路径模型/测试角色.moc3',
|
||||
),
|
||||
fileWithRelativePath(
|
||||
new Uint8Array([1, 2, 3]),
|
||||
'texture_00.png',
|
||||
'中文路径模型/中文纹理/texture_00.png',
|
||||
),
|
||||
]
|
||||
const existingObjectUrls = new Set(Object.keys(FileLoader.filesMap))
|
||||
const context = {
|
||||
source: files,
|
||||
options: {},
|
||||
live2dModel: new Live2DModel(),
|
||||
}
|
||||
|
||||
try {
|
||||
await expect(FileLoader.factory(context, async () => {})).resolves.toBeUndefined()
|
||||
}
|
||||
finally {
|
||||
for (const objectUrl of Object.keys(FileLoader.filesMap)) {
|
||||
if (existingObjectUrls.has(objectUrl))
|
||||
continue
|
||||
|
||||
for (const resourceUrl of Object.values(FileLoader.filesMap[objectUrl] ?? {}))
|
||||
URL.revokeObjectURL(resourceUrl)
|
||||
URL.revokeObjectURL(objectUrl)
|
||||
delete FileLoader.filesMap[objectUrl]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('loads an OPFS-restored file directory when a macOS AppleDouble settings sidecar is present before the real settings file', async () => {
|
||||
|
||||
@@ -23,6 +23,15 @@ index 0d40d5d40533881d154e3c2cdda9b360c57eaaa6..8dbdb1b36e2da42da264dc3533523e61
|
||||
if (!settingsFile) {
|
||||
throw new TypeError("Settings file not found");
|
||||
}
|
||||
@@ -1411,7 +1411,7 @@ FileLoader.factory = (context, next) => __async(void 0, null, function* () {
|
||||
} else if (!settings._objectURL) {
|
||||
throw new Error('"_objectURL" must be specified in ModelSettings');
|
||||
}
|
||||
- settings.validateFiles(files.map((file) => encodeURI(file.webkitRelativePath)));
|
||||
+ settings.validateFiles(files.map((file) => file.webkitRelativePath));
|
||||
yield _FileLoader.upload(files, settings);
|
||||
settings.resolveURL = function(url2) {
|
||||
return _FileLoader.resolveURL(this._objectURL, url2);
|
||||
@@ -1458,7 +1458,7 @@ const _ZipLoader = class {
|
||||
static createSettings(reader) {
|
||||
return __async(this, null, function* () {
|
||||
@@ -45,6 +54,15 @@ index c3db490f8dbfdd63ad40648fe0e0325604f22e95..4b452db0b109ba7deaddf397ef351ae7
|
||||
if (!settingsFile) {
|
||||
throw new TypeError("Settings file not found");
|
||||
}
|
||||
@@ -1411,7 +1411,7 @@ var __async = (__this, __arguments, generator) => {
|
||||
} else if (!settings._objectURL) {
|
||||
throw new Error('"_objectURL" must be specified in ModelSettings');
|
||||
}
|
||||
- settings.validateFiles(files.map((file) => encodeURI(file.webkitRelativePath)));
|
||||
+ settings.validateFiles(files.map((file) => file.webkitRelativePath));
|
||||
yield _FileLoader.upload(files, settings);
|
||||
settings.resolveURL = function(url) {
|
||||
return _FileLoader.resolveURL(this._objectURL, url);
|
||||
@@ -1458,7 +1458,7 @@ var __async = (__this, __arguments, generator) => {
|
||||
static createSettings(reader) {
|
||||
return __async(this, null, function* () {
|
||||
@@ -67,6 +85,15 @@ index f21619f5794c00542bb2a9df340e50d4453a0367..38bd069a5d1ec37380c376c2ff2d7fed
|
||||
if (!settingsFile) {
|
||||
throw new TypeError("Settings file not found");
|
||||
}
|
||||
@@ -5050,7 +5050,7 @@ FileLoader.factory = (context, next) => __async(void 0, null, function* () {
|
||||
} else if (!settings._objectURL) {
|
||||
throw new Error('"_objectURL" must be specified in ModelSettings');
|
||||
}
|
||||
- settings.validateFiles(files.map((file) => encodeURI(file.webkitRelativePath)));
|
||||
+ settings.validateFiles(files.map((file) => file.webkitRelativePath));
|
||||
yield _FileLoader.upload(files, settings);
|
||||
settings.resolveURL = function(url2) {
|
||||
return _FileLoader.resolveURL(this._objectURL, url2);
|
||||
@@ -5097,7 +5097,7 @@ const _ZipLoader = class {
|
||||
static createSettings(reader) {
|
||||
return __async(this, null, function* () {
|
||||
@@ -89,6 +116,15 @@ index 03c1fb2b32b3b908a8b0e604aa3ee50aa9130169..99fc587e3ca7e6f433099e13f33e0e48
|
||||
if (!settingsFile) {
|
||||
throw new TypeError("Settings file not found");
|
||||
}
|
||||
@@ -5050,7 +5050,7 @@ var __async = (__this, __arguments, generator) => {
|
||||
} else if (!settings._objectURL) {
|
||||
throw new Error('"_objectURL" must be specified in ModelSettings');
|
||||
}
|
||||
- settings.validateFiles(files.map((file) => encodeURI(file.webkitRelativePath)));
|
||||
+ settings.validateFiles(files.map((file) => file.webkitRelativePath));
|
||||
yield _FileLoader.upload(files, settings);
|
||||
settings.resolveURL = function(url) {
|
||||
return _FileLoader.resolveURL(this._objectURL, url);
|
||||
@@ -5097,7 +5097,7 @@ var __async = (__this, __arguments, generator) => {
|
||||
static createSettings(reader) {
|
||||
return __async(this, null, function* () {
|
||||
@@ -111,6 +147,15 @@ index f969d304f346c4e420b6798dbd4dec81b902a5e4..7e6b4d3ef53f1ea0648030899de9debd
|
||||
if (!settingsFile) {
|
||||
throw new TypeError("Settings file not found");
|
||||
}
|
||||
@@ -1411,7 +1411,7 @@ FileLoader.factory = (context, next) => __async(void 0, null, function* () {
|
||||
} else if (!settings._objectURL) {
|
||||
throw new Error('"_objectURL" must be specified in ModelSettings');
|
||||
}
|
||||
- settings.validateFiles(files.map((file) => encodeURI(file.webkitRelativePath)));
|
||||
+ settings.validateFiles(files.map((file) => file.webkitRelativePath));
|
||||
yield _FileLoader.upload(files, settings);
|
||||
settings.resolveURL = function(url2) {
|
||||
return _FileLoader.resolveURL(this._objectURL, url2);
|
||||
@@ -1458,7 +1458,7 @@ const _ZipLoader = class {
|
||||
static createSettings(reader) {
|
||||
return __async(this, null, function* () {
|
||||
@@ -133,6 +178,15 @@ index bc3d800c45889979190175cc88cdd8251feb9b5e..d1a660708ff81e1379f173709a32c6f4
|
||||
if (!settingsFile) {
|
||||
throw new TypeError("Settings file not found");
|
||||
}
|
||||
@@ -1411,7 +1411,7 @@ var __async = (__this, __arguments, generator) => {
|
||||
} else if (!settings._objectURL) {
|
||||
throw new Error('"_objectURL" must be specified in ModelSettings');
|
||||
}
|
||||
- settings.validateFiles(files.map((file) => encodeURI(file.webkitRelativePath)));
|
||||
+ settings.validateFiles(files.map((file) => file.webkitRelativePath));
|
||||
yield _FileLoader.upload(files, settings);
|
||||
settings.resolveURL = function(url) {
|
||||
return _FileLoader.resolveURL(this._objectURL, url);
|
||||
@@ -1458,7 +1458,7 @@ var __async = (__this, __arguments, generator) => {
|
||||
static createSettings(reader) {
|
||||
return __async(this, null, function* () {
|
||||
|
||||
Generated
+3
-3
@@ -1207,7 +1207,7 @@ patchedDependencies:
|
||||
hash: 4bbfdca823ab48b74086e6e7d4b2f9baf5ee7c0ba9aee0c279b3c91c50bfd797
|
||||
path: patches/mineflayer-pathfinder.patch
|
||||
pixi-live2d-display:
|
||||
hash: 122ac09349321d5bfe9d9817aa095cd1c9c4132af86345aa9e27ba8b63dadf2c
|
||||
hash: 7881eb52793f46a10d6c04d62b1799a3d0ef79f83edc0283777baa6db339ed6d
|
||||
path: patches/pixi-live2d-display.patch
|
||||
sponsorkit@17.1.0:
|
||||
hash: f5887da52a29ae85732e01644d05c0c274231fe76b95aebeafbfab6dc4444f88
|
||||
@@ -4745,7 +4745,7 @@ importers:
|
||||
version: 4.2.0(5af797a0b5bd919a06578c24a7479046)
|
||||
pixi-live2d-display:
|
||||
specifier: 'catalog:'
|
||||
version: 0.4.0(patch_hash=122ac09349321d5bfe9d9817aa095cd1c9c4132af86345aa9e27ba8b63dadf2c)(0736f5bd67d2e4de7e9fff36ad63fa2e)
|
||||
version: 0.4.0(patch_hash=7881eb52793f46a10d6c04d62b1799a3d0ef79f83edc0283777baa6db339ed6d)(0736f5bd67d2e4de7e9fff36ad63fa2e)
|
||||
three:
|
||||
specifier: 'catalog:'
|
||||
version: 0.184.0
|
||||
@@ -33525,7 +33525,7 @@ snapshots:
|
||||
- '@pixi/settings'
|
||||
- '@pixi/utils'
|
||||
|
||||
pixi-live2d-display@0.4.0(patch_hash=122ac09349321d5bfe9d9817aa095cd1c9c4132af86345aa9e27ba8b63dadf2c)(0736f5bd67d2e4de7e9fff36ad63fa2e):
|
||||
pixi-live2d-display@0.4.0(patch_hash=7881eb52793f46a10d6c04d62b1799a3d0ef79f83edc0283777baa6db339ed6d)(0736f5bd67d2e4de7e9fff36ad63fa2e):
|
||||
dependencies:
|
||||
'@pixi/core': 6.5.10(@pixi/constants@6.5.10)(@pixi/extensions@6.5.10)(@pixi/math@6.5.10)(@pixi/runner@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))(@pixi/ticker@6.5.10(@pixi/extensions@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))
|
||||
'@pixi/display': 6.5.10(@pixi/constants@6.5.10)(@pixi/math@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))
|
||||
|
||||
Reference in New Issue
Block a user