import { normalizeProjectAssetPath } from './asset-path'; export const EDITOR_RESOURCE_MAX_BYTES = 256 * 1024 * 1024; export const SHADER_BUNDLE_MAX_FILES = 256; export interface ShaderResourceSelection { batchId: string; relativePath: string } export function isEditorResourcePath(value: unknown): value is string { if (typeof value !== 'string') return false; const parts = value.split('/'), bytes = new TextEncoder(); if (parts[0] !== 'cache' || !/^[0-9a-f-]{36}$/.test(parts[1] ?? '') || parts.length < 3 || parts.length > 16 || bytes.encode('/browser/' + value).length > 1023) return false; if (parts.some(part => !part || part === '.' || part === '..' || /[\\\u0000-\u001f\u007f]/.test(part) || bytes.encode(part).length > 200)) return false; try { if (normalizeProjectAssetPath(value) !== value) return false; } catch { return false; } return parts[2] === 'shader' || parts[2] === 'model' ? parts.length > 3 : parts.length === 3 && /^[A-Za-z0-9._-]{1,200}\.(?:abc|usd|usda|usdc|usdz|btx|osl|oso)$/i.test(parts[2]); } export function modelResourcePath(batchId: string, relativePath: string): string { const path = `cache/${batchId}/model/${relativePath}`; if (!isEditorResourcePath(path)) throw new Error('请选择不含路径跳转的模型相对路径'); return path; } export function shaderResourcePath(selection: ShaderResourceSelection): string { const path = `cache/${selection.batchId}/shader/${selection.relativePath}`; if (!isEditorResourcePath(path)) throw new Error('Choose a bounded shader-relative path without traversal or renamed characters'); return path; }