// Makes the Basis Universal transcoder that three.js 0.160.1 ships for its // KTX2Loader run under a Content Security Policy without 'unsafe-eval', and // writes it, with the unmodified .wasm, to vendor/local/basis@1/. // // node wasm-src/basis/patch.mjs (from frontend/) // // The JavaScript glue is Emscripten output whose embind layer generates source // text and compiles it with new Function in four places: createNamedFunction, // craftInvokerFunction, __emval_get_method_caller and craftEmvalAllocator. The // site's CSP forbids that, so the 3D Model Viewer could not decode KTX2 // textures. Each is replaced by a closure doing the same steps in the same // order, as Emscripten does when built with -sDYNAMIC_EXECUTION=0, the same // treatment as wasm-src/occt/patch.mjs. Nothing else changes. import { createHash } from 'node:crypto'; import { mkdirSync, writeFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; const here = dirname(fileURLToPath(import.meta.url)); const out = join(here, '..', '..', 'vendor', 'local', 'basis@1'); const BASE = 'https://cdn.jsdelivr.net/npm/three@0.160.1/examples/jsm/libs/basis/'; const FILES = { 'basis_transcoder.js': '+HVwcKS+KxiFvM8L/5uF8ao5eOF3XKCNjgmgsDaBHRHNUhSQ+bDYb8M4TwHu+uUh', // The hash vendor.lock.json held for it. 'basis_transcoder.wasm': 'IepP9ATxmX8VViM/sMCldlp27yXZkp+o43K4Td38/HXAefQFz1IrmPBo12JEYG2t', }; const fetched = {}; for (const [name, sha384] of Object.entries(FILES)) { const bytes = Buffer.from(await (await fetch(BASE + name)).arrayBuffer()); const got = createHash('sha384').update(bytes).digest('base64'); if (got !== sha384) throw new Error(`${name} is not the expected file (sha384 ${got})`); fetched[name] = bytes; } let js = fetched['basis_transcoder.js'].toString('utf8'); /** Replaces the whole function that starts with `head`, after checking it is the code-generating version. */ function replaceFunction(head, mustContain, replacement) { const start = js.indexOf(head); if (start < 0) throw new Error(`${head} not found`); let depth = 0; let end = js.indexOf('{', start); for (; end < js.length; end++) { if (js[end] === '{') depth++; else if (js[end] === '}' && --depth === 0) break; } if (!js.slice(start, end + 1).includes(mustContain)) throw new Error(`${head} is not the code-generating version`); js = js.slice(0, start) + replacement + js.slice(end + 1); } const NOTE = '/* Patched by johnmcm.com: no generated code (see wasm-src/basis/patch.mjs). */'; // A function with a given name that forwards to body, so it can also be used with new. replaceFunction('function createNamedFunction(', 'new Function', `function createNamedFunction(name,body){ ${NOTE} var f=function(){return body.apply(this,arguments)}; try{Object.defineProperty(f,"name",{value:makeLegalFunctionName(name)})}catch(e){} return f }`); replaceFunction('function craftInvokerFunction(', 'new_(Function,args1)', `function craftInvokerFunction(humanName,argTypes,classType,cppInvokerFunc,cppTargetFunc){ ${NOTE} var argCount=argTypes.length; if(argCount<2){throwBindingError("argTypes array size mismatch! Must at least get return value and 'this' types!")} var isClassMethodFunc=argTypes[1]!==null&&classType!==null; var needsDestructorStack=false; for(var i=1;i>>2)+i],"parameter "+i); vals.push(argType.readValueFromPointer(args)); args+=argType["argPackAdvance"]; } var obj=new (Function.prototype.bind.apply(constructor,[null].concat(vals))); return __emval_register(obj) } }`); const left = js.match(/new_\(Function|new Function|\beval\(/g); if (left) throw new Error(`string evaluation left in the glue: ${left.join(', ')}`); const header = '/* Basis Universal transcoder (Apache-2.0) from three.js 0.160.1, patched by johnmcm.com to run without eval: see wasm-src/basis/patch.mjs */\n'; mkdirSync(out, { recursive: true }); writeFileSync(join(out, 'basis_transcoder.js'), header + js); writeFileSync(join(out, 'basis_transcoder.wasm'), fetched['basis_transcoder.wasm']); console.log(`wrote ${out}`);