From 03474670a2c2126843dc8d975deb051b9baeaed4 Mon Sep 17 00:00:00 2001 From: Makito Date: Thu, 18 Dec 2025 02:31:41 +0900 Subject: [PATCH] fix(stage-ui): use a middleware to encode possible filenames in a live2d model (#815) --- Cargo.toml | 9 +++++- .../src/utils/live2d-opfs-registration.ts | 12 +++++++- .../src/utils/live2d-uri-encode-filenames.ts | 29 +++++++++++++++++++ .../stage-ui/src/utils/live2d-zip-loader.ts | 18 +----------- 4 files changed, 49 insertions(+), 19 deletions(-) create mode 100644 packages/stage-ui/src/utils/live2d-uri-encode-filenames.ts diff --git a/Cargo.toml b/Cargo.toml index 968e7929b..d6643325e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,12 @@ [workspace] -members = [ "crates/tauri-plugin-ipc-audio-transcription-ort", "crates/tauri-plugin-ipc-audio-vad-ort", "crates/tauri-plugin-mcp", "crates/tauri-plugin-rdev", "crates/tauri-plugin-window-pass-through-on-hover", "crates/tauri-plugin-window-router-link" ] +members = [ + "crates/tauri-plugin-ipc-audio-transcription-ort", + "crates/tauri-plugin-ipc-audio-vad-ort", + "crates/tauri-plugin-mcp", + "crates/tauri-plugin-rdev", + "crates/tauri-plugin-window-pass-through-on-hover", + "crates/tauri-plugin-window-router-link" +] resolver = "2" [workspace.package] diff --git a/packages/stage-ui/src/utils/live2d-opfs-registration.ts b/packages/stage-ui/src/utils/live2d-opfs-registration.ts index a71f15fcc..456dea773 100644 --- a/packages/stage-ui/src/utils/live2d-opfs-registration.ts +++ b/packages/stage-ui/src/utils/live2d-opfs-registration.ts @@ -1,5 +1,6 @@ -import { Live2DFactory, ZipLoader } from 'pixi-live2d-display/cubism4' +import { FileLoader, Live2DFactory, ZipLoader } from 'pixi-live2d-display/cubism4' +import { live2dEncodeFilenamesMiddleware } from './live2d-uri-encode-filenames' import { OPFSCache } from './opfs-loader' const zipLoaderIndex = Live2DFactory.live2DModelMiddlewares.indexOf(ZipLoader.factory) @@ -16,3 +17,12 @@ else if (zipLoaderIndex !== -1) { else { console.warn('[OPFS] ZipLoader not found in middlewares, caching disabled') } + +// A middleware to URI-encode possible filenames in settings to handle filenames with UTF-8 characters. +if (!Live2DFactory.live2DModelMiddlewares.includes(live2dEncodeFilenamesMiddleware)) { + // Insert before FileLoader + const insertBefore = Live2DFactory.live2DModelMiddlewares.indexOf(FileLoader.factory) + if (insertBefore >= 0) { + Live2DFactory.live2DModelMiddlewares.splice(insertBefore, 0, live2dEncodeFilenamesMiddleware) + } +} diff --git a/packages/stage-ui/src/utils/live2d-uri-encode-filenames.ts b/packages/stage-ui/src/utils/live2d-uri-encode-filenames.ts new file mode 100644 index 000000000..ccdfa1193 --- /dev/null +++ b/packages/stage-ui/src/utils/live2d-uri-encode-filenames.ts @@ -0,0 +1,29 @@ +import type { Live2DFactoryContext, Middleware, ModelSettings } from 'pixi-live2d-display/cubism4' + +function tryEncode(obj: any, prop: string | number) { + if (obj?.[prop] && typeof obj[prop] === 'string') { + obj[prop] = encodeURI(obj[prop]) + } +} + +// A middleware to URI-encode possible filenames in settings to handle filenames with UTF-8 characters. +export const live2dEncodeFilenamesMiddleware: Middleware = (context, next) => { + if (typeof context.source !== 'object' || !context.source) + return next() + + // Be skeptical + const settings = context.source.settings as Partial | undefined + if (!settings) + return next() + + tryEncode(settings, 'moc') + if (Array.isArray(settings.textures)) { + for (let i = 0; i < settings.textures.length; i++) { + tryEncode(settings.textures, i) + } + } + tryEncode(settings, 'physics') + tryEncode(settings, 'url') + + return next() +} diff --git a/packages/stage-ui/src/utils/live2d-zip-loader.ts b/packages/stage-ui/src/utils/live2d-zip-loader.ts index 945402950..cd62d9d43 100644 --- a/packages/stage-ui/src/utils/live2d-zip-loader.ts +++ b/packages/stage-ui/src/utils/live2d-zip-loader.ts @@ -2,23 +2,7 @@ import type { ModelSettings } from 'pixi-live2d-display/cubism4' import JSZip from 'jszip' -import { Cubism4ModelSettings, ModelSettings as ModelSettingsClass, ZipLoader } from 'pixi-live2d-display/cubism4' - -// TODO: this is a bit hacky, do we have other choice? -// NOTICE: Patch to handle UTF-8 encoded file paths in model settings -const originalValidateFiles = ModelSettingsClass.prototype.validateFiles -ModelSettingsClass.prototype.validateFiles = function (files: string[]): string[] { - const normalizedFiles = files.map((f) => { - try { - return decodeURI(f) - } - catch { - // Not a valid URI, return original string. - return f - } - }) - return originalValidateFiles.call(this, normalizedFiles) -} +import { Cubism4ModelSettings, ZipLoader } from 'pixi-live2d-display/cubism4' ZipLoader.zipReader = (data: Blob, _url: string) => JSZip.loadAsync(data)