diff --git a/Readme.MD b/Readme.MD index e2477c4..08314fa 100644 --- a/Readme.MD +++ b/Readme.MD @@ -1,60 +1,61 @@ -# Fabulous Ripper - Sketchfab Model & Texture Ripper - -**Author:** Risk - -## Installation Guide (Google Chrome) - -To use these Tampermonkey scripts, follow the steps below: - -### Step 1: Install Tampermonkey -[Download Tampermonkey from the Chrome Web Store](https://chromewebstore.google.com/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo?hl=en&pli=1) - -### Step 2: Enable Developer Mode -1. Click the **Extensions** button in Chrome. -2. Click **Manage Extensions**. -3. At the top right of the page, enable the **Developer Mode** toggle. - -### Step 3: Add the Scripts -1. Open a new tab and click the **Tampermonkey** extension icon. -2. In the dropdown menu, select **Create a new script**. -3. Open the `Sketchfab Model Ripper V5+.js` file using **VS Code** or **Notepad++**. - - If you don’t have Notepad++, [Download it here](https://github.com/notepad-plus-plus/notepad-plus-plus/releases/download/v8.7.7/npp.8.7.7.Installer.x64.exe). -4. Copy **all** the content from the script file. -5. Paste the copied data into the Tampermonkey script editor. -6. In the top left of the page, go to **File > Save**. - -### Step 4: Repeat for Texture Dumper -Follow the same steps as above to install the `Texture Dumper.js` script. - -### Step 5: Enable the Scripts -1. Open the **Tampermonkey Dashboard**. -2. Navigate to the **Installed Userscripts** tab. -3. Locate both scripts and toggle them to **Enabled**. - ---- - -## How to Use - -### Step 1: Navigate to the Model Page -1. Go to [Sketchfab](https://sketchfab.com/). -2. Open the page of the **model you want to download**. -3. **Wait** for the model to fully load. - -#### **Model Loading Indicator** -- If the model is **not fully loaded**, it will look like this: - ![Not Fully Loaded](https://raw.githubusercontent.com/gamedev44/Fabulous-Ripper/main/Pictures/FullyLoaded.png) - **Do not proceed yet. Wait until it fully loads.** - -- Once the model is **fully loaded**, you will see the download buttons appear. - -### Step 2: Downloading the Model -1. After the model fully loads, the **download buttons** will appear. -2. You will see two options: - - **Red Button (Recommended):** Use this whenever it is available for your model [it will show at top right of model viewer]. - - **White Button (Alternative):** Use this if the red button does not appear for you [it will show at top left of model viewer]. - - ![Download Buttons](https://raw.githubusercontent.com/gamedev44/Fabulous-Ripper/main/Pictures/DownloadButtons.png) - -3. Click the button to start the download. - -Once completed, the model and textures should be saved to your computer! 🚀 +# Fabulous Ripper - Sketchfab Model & Texture Ripper + +**Author:** Risk + +## Installation Guide (Google Chrome) + +To use this Tampermonkey script, follow the steps below: + +### Step 1: Install Tampermonkey +[Download Tampermonkey from the Chrome Web Store](https://chromewebstore.google.com/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo?hl=en&pli=1) + +### Step 2: Enable Developer Mode +1. Click the **Extensions** button in Chrome. +2. Click **Manage Extensions**. +3. At the top right of the page, enable the **Developer Mode** toggle. + +### Step 3: Add the Script +1. Open a new tab and click the **Tampermonkey** extension icon. +2. In the dropdown menu, select **Create a new script**. +3. Open the `SketchfabModelRipper.js` file using **VS Code** or **Notepad++**. + - If you don't have Notepad++, [Download it here](https://github.com/notepad-plus-plus/notepad-plus-plus/releases/download/v8.7.7/npp.8.7.7.Installer.x64.exe). +4. Copy **all** the content from the script file. +5. Paste the copied data into the Tampermonkey script editor. +6. In the top left of the page, go to **File > Save**. + +### Step 4: Enable the Script +1. Open the **Tampermonkey Dashboard**. +2. Navigate to the **Installed Userscripts** tab. +3. Locate the script and toggle it to **Enabled**. + +--- + +## How to Use + +### Step 1: Navigate to the Model Page +1. Go to [Sketchfab](https://sketchfab.com/). +2. Open the page of the **model you want to download**. +3. **Wait** for the model to fully load. + +#### **Model Loading Indicator** +- If the model is **not fully loaded**, it will look like this: + ![Not Fully Loaded](https://raw.githubusercontent.com/gamedev44/Fabulous-Ripper/main/Pictures/FullyLoaded.png) + **Do not proceed yet. Wait until it fully loads.** + +- Once the model is **fully loaded**, you will see the download buttons appear. + +### Step 2: Downloading the Model +1. After the model fully loads, the **download buttons** will appear. +2. You will see two options: + - **Red Button (Recommended):** Use this whenever it is available for your model [it will show at top right of model viewer]. + - **White Button (Alternative):** Use this if the red button does not appear for you [it will show at top left of model viewer]. + + ![Download Buttons](https://raw.githubusercontent.com/gamedev44/Fabulous-Ripper/main/Pictures/DownloadButtons.png) + +3. Click the button to start the download. + +### Step 3: Texture Fallback (if textures are missing) +1. Click the **TEXTURES** button to open the embed viewer. +2. Click **DOWNLOAD TEXTURES** to add them to the zip. + +Once completed, the model and textures should be saved to your computer! diff --git a/UserScripts/Sketchfab Model Ripper v5+.JS b/UserScripts/Sketchfab Model Ripper v5+.JS deleted file mode 100644 index ea5bffa..0000000 --- a/UserScripts/Sketchfab Model Ripper v5+.JS +++ /dev/null @@ -1,521 +0,0 @@ -// ==UserScript== -// @name Sketchfab Model Ripper V5+ -// @version 0.9.1 -// @description download sketchfab models -// @author Risk -// @include /^https?://(www\.)?sketchfab\.com/.* -// @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.5/jszip.min.js -// @require https://cdnjs.cloudflare.com/ajax/libs/jszip-utils/0.0.2/jszip-utils.min.js -// @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.8/FileSaver.js -// @run-at document-start -// @grant unsafeWindow -// @grant GM_download -// ==/UserScript== - -var zip = new JSZip(); -let folder = zip.folder('collection'); - -var button_dw = false; -var func_drawGeometry = /(this\._stateCache\.drawGeometry\(this\._graphicContext,t\))/g; -var fund_drawArrays = /t\.drawArrays\(t\.TRIANGLES,0,6\)/g; -//var func_renderInto1 = /x\.renderInto\(n,S,y/g; -var func_renderInto1 = /A\.renderInto\(n,E,R/g; //20 jun 2023 fix -var func_renderInto2 = /g\.renderInto=function\(e,i,r/g; -var func_getResourceImage = /getResourceImage:function\(e,t\){/g; - -var func_test = /apply:function\(e\){var t=e instanceof r\.Geometry;/g - -var addbtnfunc; - -(function() { - 'use strict'; - var window = unsafeWindow; - console.log("[UserScript]init", window); - - - window.allmodel = []; - var saveimagecache2 = {}; - var objects = {}; - - - var saveimage_to_list = function(url,file_name) - { - if (!saveimagecache2[url]) - { - var mdl = { - name: file_name - } - - saveimagecache2[url] = mdl; - } - } - - addbtnfunc = function() { - var p = document.evaluate("//div[@class='titlebar']", document, null, 9, null).singleNodeValue; - if(p && !button_dw) { - console.log("[UserScript]add btn dwnld"); - var btn = document.createElement("a"); - btn.setAttribute("class", "control"); - btn.innerHTML = "
DOWNLOAD
"; - btn.addEventListener("click", dodownload , false); - p.appendChild(btn); - button_dw = true; - } else { - console.log("[UserScript]try add btn later"); - setTimeout(addbtnfunc, 3000); - } - } - - var dodownload = function() { - console.log("[UserScript]download"); - var idx = 0; - window.allmodel.forEach(function(obj) - { - var mdl = { - name: "model_"+idx, - obj:parseobj(obj) - } - console.log(mdl); - dosavefile(mdl); - idx++; - - }) - PackAll(); - } - - var PackAll = function () - { - for (var obj in objects) { - console.log("[UserScript]save file", obj); - folder.file(obj, objects[obj], {binary:true}); - } - - var file_name = document.getElementsByClassName('model-name__label')[0].textContent; - folder.generateAsync({ type: "blob" }).then(content => saveAs(content, file_name + ".zip")); - } - - var parseobj = function(obj) - { - console.log("[UserScript]: obj", obj); - var list = []; - obj._primitives.forEach(function(p) { - if(p && p.indices) { - list.push({ - 'mode' : p.mode, - 'indices' : p.indices._elements - }); - } - }) - - var attr = obj._attributes; - return { - vertex: attr.Vertex._elements, - normal: attr.Normal ? attr.Normal._elements : [], - uv: attr.TexCoord0 ? attr.TexCoord0._elements : - attr.TexCoord1 ? attr.TexCoord1._elements : - attr.TexCoord2 ? attr.TexCoord2._elements : - attr.TexCoord2 ? attr.TexCoord2._elements : - attr.TexCoord3 ? attr.TexCoord3._elements : - attr.TexCoord4 ? attr.TexCoord4._elements : - attr.TexCoord5 ? attr.TexCoord5._elements : - attr.TexCoord6 ? attr.TexCoord6._elements : - attr.TexCoord7 ? attr.TexCoord7._elements : - attr.TexCoord8 ? attr.TexCoord8._elements : [], - primitives: list, - }; - } - - var dosavefile = function(mdl) - { - var obj = mdl.obj; - - //console.log("TEST"); - //console.log(obj); - - var str = ''; - str += 'mtllib ' + mdl.name + '.mtl\n'; - str += 'o ' + mdl.name + '\n'; - for (var i = 0; i < obj.vertex.length; i += 3) { - str += 'v '; - for (var j = 0; j < 3; ++j) { - str += obj.vertex[i + j] + ' '; - } - str += '\n'; - } - for (i = 0; i < obj.normal.length; i += 3) { - str += 'vn '; - for (j = 0; j < 3; ++j) { - str += obj.normal[i + j] + ' '; - } - str += '\n'; - } - - for (i = 0; i < obj.uv.length; i += 2) { - str += 'vt '; - for (j = 0; j < 2; ++j) { - str += obj.uv[i + j] + ' '; - } - str += '\n'; - } - //str += 'usemtl ' + mdl.name + '\n'; - str += 's on \n'; - - var vn = obj.normal.length != 0; - var vt = obj.uv.length != 0; - - for (i = 0; i < obj.primitives.length; ++i) { - var primitive = obj.primitives[i]; - if (primitive.mode == 4 || primitive.mode == 5) { - var strip = (primitive.mode == 5); - for (j = 0; j + 2 < primitive.indices.length; !strip ? j += 3 : j++) { - str += 'f '; - var order = [ 0, 1, 2]; - if (strip && (j % 2 == 1)) { - order = [ 0, 2, 1]; - } - for (var k = 0; k < 3; ++k) - { - var faceNum = primitive.indices[j + order[k]] + 1; - str += faceNum; - if (vn || vt) { - str += '/'; - if (vt) { - str += faceNum; - } - if (vn) { - str += '/' + faceNum; - } - } - str += ' '; - } - str += '\n'; - } - } - else { - console.log("[UserScript]dosavefile: unknown primitive mode", primitive); - } - } - - str += '\n'; - - var objblob = new Blob([str], {type:'text/plain'}); - - objects[mdl.name+".obj"] = objblob; - } - - - window.attachbody = function(obj) - { - if(obj._faked != true && ((obj.stateset && obj.stateset._name) || obj._name || (obj._parents && obj._parents[0]._name)) ) { - obj._faked = true; - if(obj._name == "composer layer" || obj._name == "Ground - Geometry") return; - window.allmodel.push(obj) - console.log(obj); - } - //console.log(obj); - } - - - window.hook_test = function(e, idx) - { - console.log("hooked index: "+idx); - console.log(e); - } - window.drawhookcanvas = function(e, imagemodel) - { - - if((e.width == 128 && e.height == 128) || (e.width == 32 && e.height == 32) || (e.width == 64 && e.height == 64)) - { - return e; - } - if(imagemodel) - { - var alpha = e.options.format; - var filename_image = imagemodel.attributes.name; - var uid = imagemodel.attributes.uid; - var url_image = e.url; - var max_size = 0; - var obr = e; - imagemodel.attributes.images.forEach(function(img) - { - var alpha_is_check = alpha == "A" ? img.options.format == alpha : true; - - var d = img.width; - while ( d % 2 == 0 ) - { - d = d / 2; - } - - if(img.size > max_size && alpha_is_check && d == 1) - { - max_size = img.size; - url_image = img.url; - uid = img.uid; - obr = img; - } - }); - if(!saveimagecache2[url_image]) - { - console.log(e); - saveimage_to_list(url_image, filename_image); - } - else - { - //console.log(e); - } - - return obr; - } - return e; - } - - window.drawhookimg = function(gl,t) - { - console.log(JSON.stringify(t)); - var url = t[5].currentSrc; - var width = t[5].width; - var height = t[5].height; - - if(!saveimagecache2[url]) - { - //console.log("rejected:"+url); - return; - } - else - { - //console.log("saved texture:"+url); - } - - - var data = new Uint8Array(width * height * 4); - gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, data); - - var halfHeight = height / 2 | 0; // the | 0 keeps the result an int - var bytesPerRow = width * 4; - - // make a temp buffer to hold one row - var temp = new Uint8Array(width * 4); - for (var y = 0; y < halfHeight; ++y) - { - var topOffset = y * bytesPerRow; - var bottomOffset = (height - y - 1) * bytesPerRow; - - // make copy of a row on the top half - temp.set(data.subarray(topOffset, topOffset + bytesPerRow)); - - // copy a row from the bottom half to the top - data.copyWithin(topOffset, bottomOffset, bottomOffset + bytesPerRow); - - // copy the copy of the top half row to the bottom half - data.set(temp, bottomOffset); - } - - // Create a 2D canvas to store the result - var canvas = document.createElement('canvas'); - canvas.width = width; - canvas.height = height; - var context = canvas.getContext('2d'); - - // Copy the pixels to a 2D canvas - var imageData = context.createImageData(width, height); - imageData.data.set(data); - context.putImageData(imageData, 0, 0); - - var re = /(?:\.([^.]+))?$/; - var ext = re.exec(saveimagecache2[url].name)[1]; - var name = saveimagecache2[url].name+".png"; - - if(ext == "png" || ext == "jpg" || ext == "jpeg") - { - var ret = saveimagecache2[url].name.replace('.'+ext,''); - name = ret+".png"; - } - console.log("saved texture to blob "+name); - canvas.toBlob(function(blob){objects[name] = blob;},"image/png"); - } - -})(); - -(() => { - "use strict"; - const Event = class { - constructor(script, target) { - this.script = script; - this.target = target; - - this._cancel = false; - this._replace = null; - this._stop = false; - } - - preventDefault() { - this._cancel = true; - } - stopPropagation() { - this._stop = true; - } - replacePayload(payload) { - this._replace = payload; - } - }; - - let callbacks = []; - window.addBeforeScriptExecuteListener = (f) => { - if (typeof f !== "function") { - throw new Error("Event handler must be a function."); - } - callbacks.push(f); - }; - window.removeBeforeScriptExecuteListener = (f) => { - let i = callbacks.length; - while (i--) { - if (callbacks[i] === f) { - callbacks.splice(i, 1); - } - } - }; - - const dispatch = (script, target) => { - if (script.tagName !== "SCRIPT") { - return; - } - - const e = new Event(script, target); - - if (typeof window.onbeforescriptexecute === "function") { - try { - window.onbeforescriptexecute(e); - } catch (err) { - console.error(err); - } - } - - for (const func of callbacks) { - if (e._stop) { - break; - } - try { - func(e); - } catch (err) { - console.error(err); - } - } - - if (e._cancel) { - script.textContent = ""; - script.remove(); - } else if (typeof e._replace === "string") { - script.textContent = e._replace; - } - }; - const observer = new MutationObserver((mutations) => { - for (const m of mutations) { - for (const n of m.addedNodes) { - dispatch(n, m.target); - } - } - }); - observer.observe(document, { - childList: true, - subtree: true, - }); - })(); - -(() => { - "use strict"; - - window.onbeforescriptexecute = (e) => { - var links_as_arr = Array.from(e.target.childNodes); - - links_as_arr.forEach(function(srimgc) - { - if(srimgc instanceof HTMLScriptElement) - { - if (srimgc.src.indexOf("web/dist/") >= 0 || srimgc.src.indexOf("standaloneViewer") >= 0) - { - e.preventDefault(); - e.stopPropagation(); - var req = new XMLHttpRequest(); - req.open('GET', srimgc.src, false); - req.send(''); - var jstext = req.responseText; - var ret = func_renderInto1.exec(jstext); - - if (ret) - { - var index = ret.index + ret[0].length; - var head = jstext.slice(0, index); - var tail = jstext.slice(index); - jstext = head + ",i" + tail; - console.log("[UserScript] Injection: patch_0 injected successful " + srimgc.src); - } - - ret = func_renderInto2.exec(jstext); - - if (ret) - { - var index = ret.index + ret[0].length; - var head = jstext.slice(0, index); - var tail = jstext.slice(index); - jstext = head + ",image_data" + tail; - console.log("[UserScript] Injection: patch_1 injected successful " + srimgc.src); - if (!func_renderInto1.exec(jstext)) - console.log("[UserScript] But patch_0 failed " + srimgc.src); - } - - ret = fund_drawArrays.exec(jstext); - - if (ret) - { - var index = ret.index + ret[0].length; - var head = jstext.slice(0, index); - var tail = jstext.slice(index); - jstext = head + ",window.drawhookimg(t,image_data)" + tail; - console.log("[UserScript] Injection: patch_2 injected successful " + srimgc.src); - } - - ret = func_getResourceImage.exec(jstext); - - if (ret) - { - var index = ret.index + ret[0].length; - var head = jstext.slice(0, index); - var tail = jstext.slice(index); - jstext = head + "e = window.drawhookcanvas(e,this._imageModel);" + tail; - console.log("[UserScript] Injection: patch_3 injected successful " + srimgc.src); - } - - ret = func_drawGeometry.exec(jstext); - - if (ret) - { - var index1 = ret.index + ret[1].length; - var head1 = jstext.slice(0, index1); - var tail1 = jstext.slice(index1); - jstext = head1 + ";window.attachbody(t);" + tail1; - console.log("[UserScript] Injection: patch_4 injected successful " + srimgc.src); - setTimeout(addbtnfunc, 3000); - } - //ret = func_test.exec(jstext) - var idx = 0; - // while (ret = func_test.exec(jstext)) - // { - // var index = ret.index + ret[0].length; - // var head = jstext.slice(0, index); - // var tail = jstext.slice(index); - // jstext = head +"window.attachbody(e);"+ tail; - // //jstext = head + "window.drawhook(e);" + tail; - // func_test.lastIndex = index + 1000; - // console.log("[UserScript] Injection: patch_4 injected successful" + srimgc.src); - // setTimeout(addbtnfunc, 3000); - // } - - var obj = document.createElement('script'); - obj.type = "text/javascript"; - obj.text = jstext; - document.getElementsByTagName('head')[0].appendChild(obj); - } - } - }); - }; - })(); \ No newline at end of file diff --git a/UserScripts/Sketchfab Texture Dumper v5+.JS b/UserScripts/Sketchfab Texture Dumper v5+.JS deleted file mode 100644 index 94f2cce..0000000 --- a/UserScripts/Sketchfab Texture Dumper v5+.JS +++ /dev/null @@ -1,285 +0,0 @@ -// ==UserScript== -// @name Sketchfab Texture Dumper V5+ -// @namespace Violentmonkey Scripts -// @match *://*.sketchfab.com/3d-models/** -// @match *://*.sketchfab.com/models/** -// @grant none -// @version 0.0.3 -// @author Risk -// @create-time 2023/7/31 22:59:46 -// @license MIT -// @run-at document-start -// ==/UserScript== -(function () { - - const MIN_TEXTURE_WIDTH = 256, - MIN_TEXTURE_HEIGHT = 256; - let _log = console.log; - let _warn = console.warn; - let model_name = location.pathname.split("/")[2]; - let model_name_arr = model_name.split("-"); - let model_id = model_name_arr[model_name_arr.length - 1]; - let downloadCnt = 0; - let originTextureArr = []; - if (location.href.indexOf('sketchfab.com/3d-models/') > -1) { - return; - } - if (window.self !== window.top) { - setTimeout(() => { - addViewBtn(); - }, 3000) - return; - } - _log("=======SketchfabTextureDump======="); - if (!model_id) { - _warn("can't find model_id!"); - return; - } else { - _log("model_id:", model_id); - } - - window.onload = () => { - try { - let results = window.prefetchedData[`/i/models/${model_id}/textures?optimized=1`].results; - coverTexture(results); // quality high - Object.defineProperty(window.prefetchedData[`/i/models/${model_id}/textures?optimized=1`], "results", { - get: () => { - return results; - } - }) - originTextureArr = window.prefetchedData[`/i/models/${model_id}/textures?optimized=1`].results || []; - } catch (e) { - _warn('oops~something went wrong,u can refresh the page & try again'); - } - } - - function coverTexture(results) { - results.forEach((texture) => { - let images = texture.images; - let img = images[images.length - 2]; - texture.url = img.url; - images.forEach((item, idx) => { - images[idx] = img; - }) - }) - } - - function addDownloadBtn() { - let btn = document.createElement("button"); - btn.innerHTML = "download"; - btn.style.position = "absolute" - btn.style.top = "0" - btn.style.left = "0" - document.body.appendChild(btn); - btn.onclick = dumpWebGLTextureData; - } - - function addViewBtn() { - let btn = document.createElement("button"); - btn.innerHTML = "goto download"; - btn.style.position = "absolute" - btn.style.top = "0" - btn.style.left = "0" - document.body.appendChild(btn); - btn.onclick = () => { - let link = document.createElement("a"); - link.setAttribute("href", `https://sketchfab.com/models/${model_id}/embed?autostart=1&internal=1&tracking=0&ui_ar=0&ui_infos=0&ui_snapshots=1&ui_stop=0&ui_theatre=1&ui_watermark=0`); - link.setAttribute("target", "_blank"); - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - }; - } - - let webGLTextureIdx = 0; - let webGLTextureMap = {}; - document.addEventListener("DOMContentLoaded", function () { - var str = document.querySelector("#js-dom-data-prefetched-data").innerHTML.replace("\x3C!--", "").replace("-->", "").replaceAll(""", "\""); - var prefetchedData = JSON.parse(str); - let context = prefetchedData.embedOptions.webgl2 ? WebGL2RenderingContext : WebGLRenderingContext; - hookWebGLRenderingContext(context); - }, false); - - function hookWebGLRenderingContext(context) { - //createTexture - let glCreateTexture = context.prototype.createTexture; - context.prototype.createTexture = function (...args) { - let texture = glCreateTexture.apply(this, args); - texture.name = webGLTextureIdx; - texture.gl = this; - webGLTextureMap[`${webGLTextureIdx}`] = texture; - webGLTextureIdx++; - return texture; - }; - - //bindTexture - let glBindTexture = context.prototype.bindTexture; - context.prototype.bindTexture = function (...args) { - let target = args[0], texture = args[1]; - if (texture) { - texture.target = target; - } - glBindTexture.apply(this, args); - }; - - let glDeleteTexture = context.prototype.deleteTexture; - context.prototype.deleteTexture = function (...args) { - // do notthing - }; - - //texImage - let lstTexture; - let glTexImage2D = context.prototype.texImage2D; - let hasCoverTex = 0; - let isAllTextureCover = false; - context.prototype.texImage2D = function (...args) { - let texture = this.getParameter(this.TEXTURE_BINDING_2D) || this.getParameter(this.TEXTURE_BINDING_CUBE_MAP); - let argments = parseTexImage2dArgs(args); - if (texture.target == argments.target) { - texture.args = argments; - let { width, height, src } = argments; - if ( - width > MIN_TEXTURE_WIDTH && - height > MIN_TEXTURE_HEIGHT && - (width & width - 1) === 0 && - (height & height - 1) === 0 && - texture.target === this.TEXTURE_2D - ) { - if (src) { - // let textureName = getFileNameByLink(src); - texture.src = argments.src; - lstTexture.lst = texture.name; - hasCoverTex++; - _log(`【origin texture cover】 total:${originTextureArr.length} count:${hasCoverTex}`); - if (originTextureArr.length == hasCoverTex) { - isAllTextureCover = true; - _log('all files are ready! click the 【download】 button'); - addDownloadBtn(); - } - } - lstTexture = texture; - } - } - glTexImage2D.apply(this, args); - }; - } - - function parseTexImage2dArgs(args) { - let argments = {}; - if (args.length == 6) { - let [target, level, internalformat, format, type, source] = args; - let { width, height, src } = source; - argments = { target, level, width, height, internalformat, format, type, src }; - } else if (args.length == 9) { - let [ - target, - level, - internalformat, - width, - height, - border, - format, - type, - pixels, - ] = args; - argments = { target, level, width, height, format, internalformat, type }; - } else { - let [target] = args; - argments = { target }; - } - return argments; - } - - function readWebTextureData(texture) { - let { target, gl, args, src, lst } = texture; - if (!args) { // todo maybe other func bind - return - } - let { level, width, height, format, internalformat, type } = args; - if ( - width > MIN_TEXTURE_WIDTH && - height > MIN_TEXTURE_HEIGHT && - (width & width - 1) === 0 && - (height & height - 1) === 0 && - target === gl.TEXTURE_2D && - !src - ) { - let fb = gl.createFramebuffer(); - gl.bindFramebuffer(gl.FRAMEBUFFER, fb); - gl.framebufferTexture2D( - gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0, - gl.TEXTURE_2D, - texture, - level - ); - let framebufferStatus = gl.checkFramebufferStatus(gl.FRAMEBUFFER); - if ( - framebufferStatus == gl.FRAMEBUFFER_COMPLETE - ) { - let fileName = `${Date.now()}.png`; - let originName = fileName; - let originTex = webGLTextureMap[lst]; - if (originTex) { - if (originTex && originTex.src) { - let path = originTex.src.split("/"); - let uid = path[path.length - 2]; - originName = getNameById(uid); - } - if (fileName == originName || !originName) { return } - let pixels = new Uint8Array(width * height * 4); - gl.readPixels(0, 0, width, height, format, type, pixels); - let canvas = document.createElement("canvas"); - canvas.width = width; - canvas.height = height; - let context = canvas.getContext("2d"); - let imageData = context.createImageData(width, height); - imageData.data.set(pixels); - context.putImageData(imageData, 0, 0); - downloadCnt++; - downLoadByLink(canvas.toDataURL(), originName); // todo format - } - } - } - } - - function dumpWebGLTextureData() { - downloadCnt = 0; - for (let key in webGLTextureMap) { - readWebTextureData(webGLTextureMap[key]); - } - if (downloadCnt == originTextureArr.length) { - _log(`【dump texture:${downloadCnt}/${originTextureArr.length}】 success!`); - } else { - _log(`【dump texture:${downloadCnt}/${originTextureArr.length}】oops~something went wrong,u can refresh the page & try again`); - } - } - - function getNameById(uid) { - let name; - let texture = originTextureArr.find((t) => { - return t.uid == uid; - }); - if (texture) { - name = texture.name; - } - return name; - } - - - function downLoadByLink(url, filename) { - let link, evt; - link = document.createElement('a'); - link.href = url; - filename && link.setAttribute('download', filename); - if (document.fireEvent) { - window.open(link.href); - } else { - evt = document.createEvent('MouseEvents'); - evt.initEvent('click', true, true); - link.dispatchEvent(evt); - } - }; - - window.dumpWebGLTextureData = dumpWebGLTextureData; - })(); \ No newline at end of file diff --git a/UserScripts/SketchfabModelRipper.js b/UserScripts/SketchfabModelRipper.js new file mode 100644 index 0000000..ede982e --- /dev/null +++ b/UserScripts/SketchfabModelRipper.js @@ -0,0 +1,1003 @@ +// ==UserScript== +// @name Sketchfab Model Ripper V5+ +// @version 0.10.0 +// @description download sketchfab models +// @author Risk +// @include /^https?://(www\.)?sketchfab\.com/.* +// @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.5/jszip.min.js +// @require https://cdnjs.cloudflare.com/ajax/libs/jszip-utils/0.0.2/jszip-utils.min.js +// @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.8/FileSaver.js +// @run-at document-start +// @grant unsafeWindow +// @grant GM_download +// ==/UserScript== + +var zip = new JSZip(); +let folder = zip.folder('collection'); + +var button_dw = false; +var func_drawGeometry = /(this\._stateCache\.drawGeometry\(this\._graphicContext,t\))/g; +var fund_drawArrays = /t\.drawArrays\(t\.TRIANGLES,0,6\)/g; +//var func_renderInto1 = /x\.renderInto\(n,S,y/g; +var func_renderInto1 = /A\.renderInto\(n,E,R/g; //20 jun 2023 fix +var func_renderInto2 = /g\.renderInto=function\(e,i,r/g; +var func_getResourceImage = /getResourceImage:function\(e,t\){/g; + +var func_test = /apply:function\(e\){var t=e instanceof r\.Geometry;/g + +var addbtnfunc; + +(function() { + 'use strict'; + var window = unsafeWindow; + console.log("[UserScript]init", window); + + + window.allmodel = []; + var saveimagecache2 = {}; + var objects = {}; + var DEBUG = false; + var OBJ_CHUNK_LIMIT = 1024 * 1024; + var ENABLE_TEXTURE_DUMPER = true; + var TEX_DUMPER_MIN_WIDTH = 256; + var TEX_DUMPER_MIN_HEIGHT = 256; + + var debugLog = function() { + if (DEBUG && console && console.log) { + console.log.apply(console, arguments); + } + }; + + var sanitizeFileName = function(name, fallback) { + var cleaned = (name || '').toString(); + cleaned = cleaned.replace(/[\x00-\x1f\x7f]/g, ''); + cleaned = cleaned.replace(/[\\\/:*?"<>|]+/g, '_'); + cleaned = cleaned.replace(/\s+/g, ' ').trim(); + cleaned = cleaned.replace(/^\.+/, ''); + cleaned = cleaned.replace(/\.\.+/g, '.'); + if (!cleaned) { + cleaned = fallback || 'file'; + } + if (cleaned.length > 120) { + cleaned = cleaned.slice(0, 120); + } + return cleaned; + }; + + var createObjWriter = function() { + var chunks = []; + var chunk = ''; + return { + push: function(text) { + if (!text) { + return; + } + chunk += text; + if (chunk.length >= OBJ_CHUNK_LIMIT) { + chunks.push(chunk); + chunk = ''; + } + }, + finalize: function() { + if (chunk.length) { + chunks.push(chunk); + chunk = ''; + } + return new Blob(chunks, {type:'text/plain'}); + } + }; + }; + + var pickTexCoord = function(attr) { + if (!attr) { + return []; + } + var uvAttr = attr.TexCoord0 || + attr.TexCoord1 || + attr.TexCoord2 || + attr.TexCoord3 || + attr.TexCoord4 || + attr.TexCoord5 || + attr.TexCoord6 || + attr.TexCoord7 || + attr.TexCoord8; + return uvAttr ? uvAttr._elements : []; + }; + + var isEmbedView = function() { + return /\/models\//.test(location.pathname) || /\/embed/.test(location.pathname); + }; + + var getModelIdFromPath = function() { + var pathParts = location.pathname.split('/'); + var slug = pathParts.length > 2 ? pathParts[2] : ''; + if (!slug) { + return ''; + } + var slugParts = slug.split('-'); + return slugParts[slugParts.length - 1] || ''; + }; + + var hasAnyTextures = function() { + for (var key in objects) { + if (key.toLowerCase().endsWith('.png') || key.toLowerCase().endsWith('.jpg') || key.toLowerCase().endsWith('.jpeg')) { + return true; + } + } + return false; + }; + + var textureFallbackBtnAdded = false; + var openEmbedForTextures = function() { + var modelId = getModelIdFromPath(); + if (!modelId) { + console.warn("[UserScript]texture fallback: model id not found"); + return; + } + var embedUrl = "https://sketchfab.com/models/" + modelId + "/embed?autostart=1&internal=1&tracking=0&ui_ar=0&ui_infos=0&ui_snapshots=1&ui_stop=0&ui_theatre=1&ui_watermark=0"; + window.open(embedUrl, "_blank"); + }; + + var addTextureFallbackButton = function() { + if (textureFallbackBtnAdded) { + return; + } + var p = document.evaluate("//div[@class='titlebar']", document, null, 9, null).singleNodeValue; + if (p) { + var btn = document.createElement("a"); + btn.setAttribute("class", "control"); + btn.innerHTML = "
TEXTURES
"; + btn.addEventListener("click", openEmbedForTextures, false); + p.appendChild(btn); + textureFallbackBtnAdded = true; + } else { + setTimeout(addTextureFallbackButton, 3000); + } + }; + + var maybeOfferTextureFallback = function() { + if (!ENABLE_TEXTURE_DUMPER) { + return; + } + if (isEmbedView()) { + return; + } + if (hasAnyTextures()) { + return; + } + if (Object.keys(saveimagecache2).length !== 0) { + return; + } + addTextureFallbackButton(); + }; + + + var saveimage_to_list = function(url,file_name) + { + if (!saveimagecache2[url]) + { + var mdl = { + name: sanitizeFileName(file_name, "texture") + } + + saveimagecache2[url] = mdl; + } + } + + addbtnfunc = function() { + var p = document.evaluate("//div[@class='titlebar']", document, null, 9, null).singleNodeValue; + if(p && !button_dw) { + console.log("[UserScript]add btn dwnld"); + var btn = document.createElement("a"); + btn.setAttribute("class", "control"); + btn.innerHTML = "
DOWNLOAD
"; + btn.addEventListener("click", dodownload , false); + p.appendChild(btn); + button_dw = true; + } else { + console.log("[UserScript]try add btn later"); + setTimeout(addbtnfunc, 3000); + } + } + + var dodownload = function() { + console.log("[UserScript]download"); + var models = window.allmodel; + for (var i = 0; i < models.length; i++) { + var mdl = { + name: sanitizeFileName("model_" + i, "model_" + i), + obj: parseobj(models[i]) + }; + debugLog("[UserScript]model", mdl); + dosavefile(mdl); + } + PackAll(); + } + + var PackAll = function () + { + for (var obj in objects) { + console.log("[UserScript]save file", obj); + folder.file(obj, objects[obj], {binary:true}); + } + + var file_node = document.getElementsByClassName('model-name__label')[0]; + var file_name = file_node ? sanitizeFileName(file_node.textContent, "sketchfab_model") : "sketchfab_model"; + folder.generateAsync({ type: "blob", compression: "STORE", streamFiles: true }) + .then(content => { + saveAs(content, file_name + ".zip"); + maybeOfferTextureFallback(); + }); + } + + var parseobj = function(obj) + { + debugLog("[UserScript]: obj", obj); + var list = []; + obj._primitives.forEach(function(p) { + if(p && p.indices) { + list.push({ + 'mode' : p.mode, + 'indices' : p.indices._elements + }); + } + }) + + var attr = obj._attributes; + return { + vertex: attr.Vertex._elements, + normal: attr.Normal ? attr.Normal._elements : [], + uv: pickTexCoord(attr), + primitives: list, + }; + } + + var dosavefile = function(mdl) + { + var obj = mdl.obj; + + //console.log("TEST"); + //console.log(obj); + + var writer = createObjWriter(); + writer.push('mtllib ' + mdl.name + '.mtl\n'); + writer.push('o ' + mdl.name + '\n'); + + var vertex = obj.vertex; + for (var i = 0; i < vertex.length; i += 3) { + writer.push('v ' + vertex[i] + ' ' + vertex[i + 1] + ' ' + vertex[i + 2] + '\n'); + } + + var normal = obj.normal; + for (i = 0; i < normal.length; i += 3) { + writer.push('vn ' + normal[i] + ' ' + normal[i + 1] + ' ' + normal[i + 2] + '\n'); + } + + var uv = obj.uv; + for (i = 0; i < uv.length; i += 2) { + writer.push('vt ' + uv[i] + ' ' + uv[i + 1] + '\n'); + } + //writer.push('usemtl ' + mdl.name + '\n'); + writer.push('s on\n'); + + var vn = normal.length !== 0; + var vt = uv.length !== 0; + + var formatFace; + if (vt && vn) { + formatFace = function(num) { return num + '/' + num + '/' + num; }; + } else if (vt) { + formatFace = function(num) { return num + '/' + num; }; + } else if (vn) { + formatFace = function(num) { return num + '//' + num; }; + } else { + formatFace = function(num) { return '' + num; }; + } + + var primitives = obj.primitives; + for (i = 0; i < primitives.length; ++i) { + var primitive = primitives[i]; + if (primitive.mode == 4 || primitive.mode == 5) { + var strip = (primitive.mode == 5); + var indices = primitive.indices; + for (var j = 0; j + 2 < indices.length; strip ? j++ : j += 3) { + var idx0 = indices[j] + 1; + var idx1 = indices[j + 1] + 1; + var idx2 = indices[j + 2] + 1; + if (strip && (j & 1)) { + var tmp = idx1; + idx1 = idx2; + idx2 = tmp; + } + writer.push('f ' + formatFace(idx0) + ' ' + formatFace(idx1) + ' ' + formatFace(idx2) + '\n'); + } + } + else { + console.log("[UserScript]dosavefile: unknown primitive mode", primitive); + } + } + + var objblob = writer.finalize(); + + objects[mdl.name + ".obj"] = objblob; + } + + + window.attachbody = function(obj) + { + if(obj._faked != true && ((obj.stateset && obj.stateset._name) || obj._name || (obj._parents && obj._parents[0]._name)) ) { + obj._faked = true; + if(obj._name == "composer layer" || obj._name == "Ground - Geometry") return; + window.allmodel.push(obj) + debugLog(obj); + } + //console.log(obj); + } + + + window.hook_test = function(e, idx) + { + console.log("hooked index: "+idx); + console.log(e); + } + window.drawhookcanvas = function(e, imagemodel) + { + + if((e.width == 128 && e.height == 128) || (e.width == 32 && e.height == 32) || (e.width == 64 && e.height == 64)) + { + return e; + } + if(imagemodel) + { + var alpha = e.options.format; + var filename_image = imagemodel.attributes.name; + var uid = imagemodel.attributes.uid; + var url_image = e.url; + var max_size = 0; + var obr = e; + imagemodel.attributes.images.forEach(function(img) + { + var alpha_is_check = alpha == "A" ? img.options.format == alpha : true; + + var d = img.width; + while ( d % 2 == 0 ) + { + d = d / 2; + } + + if(img.size > max_size && alpha_is_check && d == 1) + { + max_size = img.size; + url_image = img.url; + uid = img.uid; + obr = img; + } + }); + if(!saveimagecache2[url_image]) + { + debugLog(e); + saveimage_to_list(url_image, filename_image); + } + else + { + //console.log(e); + } + + return obr; + } + return e; + } + + window.drawhookimg = function(gl,t) + { + debugLog("[UserScript]drawhookimg", t); + var url = t[5].currentSrc; + var width = t[5].width; + var height = t[5].height; + + if(!saveimagecache2[url]) + { + //console.log("rejected:"+url); + return; + } + else + { + //console.log("saved texture:"+url); + } + + + var data = new Uint8Array(width * height * 4); + gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, data); + + var halfHeight = height / 2 | 0; // the | 0 keeps the result an int + var bytesPerRow = width * 4; + + // make a temp buffer to hold one row + var temp = new Uint8Array(width * 4); + for (var y = 0; y < halfHeight; ++y) + { + var topOffset = y * bytesPerRow; + var bottomOffset = (height - y - 1) * bytesPerRow; + + // make copy of a row on the top half + temp.set(data.subarray(topOffset, topOffset + bytesPerRow)); + + // copy a row from the bottom half to the top + data.copyWithin(topOffset, bottomOffset, bottomOffset + bytesPerRow); + + // copy the copy of the top half row to the bottom half + data.set(temp, bottomOffset); + } + + // Create a 2D canvas to store the result + var canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + var context = canvas.getContext('2d'); + + // Copy the pixels to a 2D canvas + var imageData = context.createImageData(width, height); + imageData.data.set(data); + context.putImageData(imageData, 0, 0); + + var re = /(?:\.([^.]+))?$/; + var raw_name = saveimagecache2[url].name || "texture"; + var ext = re.exec(raw_name)[1]; + var base = raw_name; + + if(ext == "png" || ext == "jpg" || ext == "jpeg") + { + base = raw_name.replace('.' + ext,''); + } + var name = sanitizeFileName(base, "texture") + ".png"; + console.log("saved texture to blob "+name); + canvas.toBlob(function(blob){objects[name] = blob;},"image/png"); + } + + var initTextureDumperFallback = function() { + if (!ENABLE_TEXTURE_DUMPER || !isEmbedView()) { + return; + } + + if (window.self !== window.top) { + var embedOpenBtnAdded = false; + var addEmbedOpenButton = function() { + if (embedOpenBtnAdded) { + return; + } + var btn = document.createElement("button"); + btn.textContent = "OPEN EMBED"; + btn.style.position = "absolute"; + btn.style.top = "0"; + btn.style.left = "0"; + btn.style.zIndex = "99999"; + btn.addEventListener("click", openEmbedForTextures, false); + document.body.appendChild(btn); + embedOpenBtnAdded = true; + }; + setTimeout(addEmbedOpenButton, 3000); + return; + } + + var modelId = getModelIdFromPath(); + if (!modelId) { + console.warn("[UserScript]texture fallback: model id not found"); + return; + } + + var originTextureArr = []; + var textureNameByUid = {}; + var webGLTextureIdx = 0; + var webGLTextureMap = {}; + var coveredCount = 0; + var isReady = false; + var dumpInProgress = false; + var seenSrcMap = {}; + var embedDownloadBtn = null; + var embedDownloadBtnAdded = false; + + var updateFallbackButtonState = function() { + if (!embedDownloadBtn) { + return; + } + if (originTextureArr.length && !isReady) { + embedDownloadBtn.textContent = "TEXTURES LOADING"; + } else { + embedDownloadBtn.textContent = "DOWNLOAD TEXTURES"; + } + }; + + var addEmbedDownloadButton = function() { + if (embedDownloadBtnAdded) { + return; + } + var btn = document.createElement("button"); + btn.textContent = "DOWNLOAD TEXTURES"; + btn.style.position = "absolute"; + btn.style.top = "0"; + btn.style.left = "0"; + btn.style.zIndex = "99999"; + btn.addEventListener("click", dumpWebGLTextureData, false); + document.body.appendChild(btn); + embedDownloadBtn = btn; + embedDownloadBtnAdded = true; + updateFallbackButtonState(); + }; + + var buildTextureNameMap = function(results) { + var map = {}; + for (var i = 0; i < results.length; i++) { + var texture = results[i]; + if (texture && texture.uid && texture.name) { + map[texture.uid] = texture.name; + } + } + return map; + }; + + var coverTexture = function(results) { + for (var i = 0; i < results.length; i++) { + var texture = results[i]; + if (!texture || !texture.images || texture.images.length === 0) { + continue; + } + var images = texture.images; + var img = images.length > 1 ? images[images.length - 2] : images[images.length - 1]; + if (!img) { + continue; + } + texture.url = img.url; + for (var j = 0; j < images.length; j++) { + images[j] = img; + } + } + }; + + var initFromPrefetchedData = function() { + var prefetchedData = window.prefetchedData; + if (!prefetchedData) { + var node = document.querySelector("#js-dom-data-prefetched-data"); + if (node && node.textContent) { + var text = node.textContent.replace(/^$/, ''); + text = text.replace(/"/g, '"'); + try { + prefetchedData = JSON.parse(text); + } catch (err) { + return false; + } + } + } + if (!prefetchedData) { + return false; + } + var key = "/i/models/" + modelId + "/textures?optimized=1"; + var entry = prefetchedData[key]; + if (!entry || !entry.results) { + return false; + } + var results = entry.results; + coverTexture(results); + try { + Object.defineProperty(entry, "results", { + get: function() { + return results; + } + }); + } catch (err) { + } + originTextureArr = results; + textureNameByUid = buildTextureNameMap(results); + updateFallbackButtonState(); + return true; + }; + + var parseTexImage2dArgs = function(args) { + var argments = {}; + if (args.length === 6) { + var source = args[5]; + argments.target = args[0]; + argments.level = args[1]; + argments.internalformat = args[2]; + argments.format = args[3]; + argments.type = args[4]; + argments.width = source && source.width ? source.width : 0; + argments.height = source && source.height ? source.height : 0; + argments.src = source && (source.currentSrc || source.src) ? (source.currentSrc || source.src) : ''; + } else if (args.length === 9) { + argments.target = args[0]; + argments.level = args[1]; + argments.internalformat = args[2]; + argments.width = args[3]; + argments.height = args[4]; + argments.border = args[5]; + argments.format = args[6]; + argments.type = args[7]; + } else { + argments.target = args[0]; + } + return argments; + }; + + var extractUidFromSrc = function(src) { + if (!src) { + return ''; + } + var parts = src.split('/'); + return parts.length > 1 ? parts[parts.length - 2] : ''; + }; + + var getNameByUid = function(uid) { + return uid && textureNameByUid[uid] ? textureNameByUid[uid] : ''; + }; + + var hookWebGLRenderingContext = function(context) { + if (!context || !context.prototype || context.prototype._ripperHooked) { + return; + } + context.prototype._ripperHooked = true; + + var glCreateTexture = context.prototype.createTexture; + context.prototype.createTexture = function() { + var texture = glCreateTexture.apply(this, arguments); + texture._ripperTexId = webGLTextureIdx; + texture._ripperGl = this; + webGLTextureMap[webGLTextureIdx] = texture; + webGLTextureIdx++; + return texture; + }; + + var glBindTexture = context.prototype.bindTexture; + context.prototype.bindTexture = function(target, texture) { + if (texture) { + texture._ripperTarget = target; + } + return glBindTexture.apply(this, arguments); + }; + + var glDeleteTexture = context.prototype.deleteTexture; + context.prototype.deleteTexture = function() { + }; + + var lastTexture = null; + var glTexImage2D = context.prototype.texImage2D; + context.prototype.texImage2D = function() { + var texture = this.getParameter(this.TEXTURE_BINDING_2D) || this.getParameter(this.TEXTURE_BINDING_CUBE_MAP); + var argments = parseTexImage2dArgs(arguments); + if (texture && texture._ripperTarget === argments.target) { + texture._ripperArgs = argments; + var width = argments.width || 0; + var height = argments.height || 0; + var src = argments.src; + if (width > TEX_DUMPER_MIN_WIDTH && + height > TEX_DUMPER_MIN_HEIGHT && + (width & (width - 1)) === 0 && + (height & (height - 1)) === 0 && + texture._ripperTarget === this.TEXTURE_2D) { + if (src) { + texture._ripperSrc = src; + if (lastTexture) { + lastTexture._ripperLinkId = texture._ripperTexId; + } + if (!seenSrcMap[src]) { + seenSrcMap[src] = true; + coveredCount++; + if (originTextureArr.length && coveredCount >= originTextureArr.length) { + isReady = true; + updateFallbackButtonState(); + } + } + } + lastTexture = texture; + } + } + return glTexImage2D.apply(this, arguments); + }; + }; + + var resolveTextureName = function(texture) { + var name = ''; + var linkId = texture._ripperLinkId; + if (linkId !== undefined && webGLTextureMap[linkId]) { + var originTex = webGLTextureMap[linkId]; + var uid = extractUidFromSrc(originTex && originTex._ripperSrc ? originTex._ripperSrc : ''); + name = getNameByUid(uid); + } + if (!name) { + name = "texture_" + texture._ripperTexId; + } + return name; + }; + + var readWebTextureData = function(texture, onBlob) { + var args = texture._ripperArgs; + var gl = texture._ripperGl; + if (!args || !gl) { + return false; + } + var width = args.width || 0; + var height = args.height || 0; + if (width <= TEX_DUMPER_MIN_WIDTH || height <= TEX_DUMPER_MIN_HEIGHT) { + return false; + } + if ((width & (width - 1)) !== 0 || (height & (height - 1)) !== 0) { + return false; + } + if (texture._ripperTarget !== gl.TEXTURE_2D || texture._ripperSrc) { + return false; + } + + var fb = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, fb); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, args.level || 0); + var framebufferStatus = gl.checkFramebufferStatus(gl.FRAMEBUFFER); + if (framebufferStatus !== gl.FRAMEBUFFER_COMPLETE) { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + return false; + } + + var nameBase = resolveTextureName(texture); + var fileName = sanitizeFileName(nameBase, "texture_" + texture._ripperTexId) + ".png"; + if (objects[fileName]) { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + return false; + } + + var pixels = new Uint8Array(width * height * 4); + gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + var canvas = document.createElement("canvas"); + canvas.width = width; + canvas.height = height; + var context = canvas.getContext("2d"); + var imageData = context.createImageData(width, height); + imageData.data.set(pixels); + context.putImageData(imageData, 0, 0); + + canvas.toBlob(function(blob) { + if (blob) { + objects[fileName] = blob; + } + if (onBlob) { + onBlob(fileName, blob); + } + }, "image/png"); + + return true; + }; + + var dumpWebGLTextureData = function() { + if (dumpInProgress) { + return; + } + dumpInProgress = true; + var keys = Object.keys(webGLTextureMap); + var pending = 0; + var added = 0; + var onBlobDone = function(name, blob) { + if (blob) { + added++; + } + pending--; + if (pending === 0) { + dumpInProgress = false; + if (added > 0) { + PackAll(); + } else { + console.warn("[UserScript]texture fallback: no textures captured"); + } + } + }; + + for (var i = 0; i < keys.length; i++) { + var texture = webGLTextureMap[keys[i]]; + var queued = readWebTextureData(texture, onBlobDone); + if (queued) { + pending++; + } + } + + if (pending === 0) { + dumpInProgress = false; + console.warn("[UserScript]texture fallback: no textures queued"); + } + }; + + window.dumpWebGLTextureData = dumpWebGLTextureData; + + initFromPrefetchedData(); + document.addEventListener("DOMContentLoaded", initFromPrefetchedData, false); + window.addEventListener("load", initFromPrefetchedData, false); + + hookWebGLRenderingContext(window.WebGLRenderingContext); + hookWebGLRenderingContext(window.WebGL2RenderingContext); + setTimeout(addEmbedDownloadButton, 3000); + }; + + initTextureDumperFallback(); + +})(); + +(() => { + "use strict"; + const Event = class { + constructor(script, target) { + this.script = script; + this.target = target; + + this._cancel = false; + this._replace = null; + this._stop = false; + } + + preventDefault() { + this._cancel = true; + } + stopPropagation() { + this._stop = true; + } + replacePayload(payload) { + this._replace = payload; + } + }; + + let callbacks = []; + window.addBeforeScriptExecuteListener = (f) => { + if (typeof f !== "function") { + throw new Error("Event handler must be a function."); + } + callbacks.push(f); + }; + window.removeBeforeScriptExecuteListener = (f) => { + let i = callbacks.length; + while (i--) { + if (callbacks[i] === f) { + callbacks.splice(i, 1); + } + } + }; + + const dispatch = (script, target) => { + if (script.tagName !== "SCRIPT") { + return; + } + + const e = new Event(script, target); + + if (typeof window.onbeforescriptexecute === "function") { + try { + window.onbeforescriptexecute(e); + } catch (err) { + console.error(err); + } + } + + for (const func of callbacks) { + if (e._stop) { + break; + } + try { + func(e); + } catch (err) { + console.error(err); + } + } + + if (e._cancel) { + script.textContent = ""; + script.remove(); + } else if (typeof e._replace === "string") { + script.textContent = e._replace; + } + }; + const observer = new MutationObserver((mutations) => { + for (const m of mutations) { + for (const n of m.addedNodes) { + dispatch(n, m.target); + } + } + }); + observer.observe(document, { + childList: true, + subtree: true, + }); + })(); + +(() => { + "use strict"; + + window.onbeforescriptexecute = (e) => { + var links_as_arr = Array.from(e.target.childNodes); + + links_as_arr.forEach(function(srimgc) + { + if(srimgc instanceof HTMLScriptElement) + { + if (srimgc.src.indexOf("web/dist/") >= 0 || srimgc.src.indexOf("standaloneViewer") >= 0) + { + e.preventDefault(); + e.stopPropagation(); + var req = new XMLHttpRequest(); + req.open('GET', srimgc.src, false); + req.send(''); + var jstext = req.responseText; + var ret = func_renderInto1.exec(jstext); + + if (ret) + { + var index_patch0 = ret.index + ret[0].length; + var head_patch0 = jstext.slice(0, index_patch0); + var tail_patch0 = jstext.slice(index_patch0); + jstext = head_patch0 + ",i" + tail_patch0; + console.log("[UserScript] Injection: patch_0 injected successful " + srimgc.src); + } + + ret = func_renderInto2.exec(jstext); + + if (ret) + { + var index_patch1 = ret.index + ret[0].length; + var head_patch1 = jstext.slice(0, index_patch1); + var tail_patch1 = jstext.slice(index_patch1); + jstext = head_patch1 + ",image_data" + tail_patch1; + console.log("[UserScript] Injection: patch_1 injected successful " + srimgc.src); + if (!func_renderInto1.exec(jstext)) + console.log("[UserScript] But patch_0 failed " + srimgc.src); + } + + ret = fund_drawArrays.exec(jstext); + + if (ret) + { + var index_patch2 = ret.index + ret[0].length; + var head_patch2 = jstext.slice(0, index_patch2); + var tail_patch2 = jstext.slice(index_patch2); + jstext = head_patch2 + ",window.drawhookimg(t,image_data)" + tail_patch2; + console.log("[UserScript] Injection: patch_2 injected successful " + srimgc.src); + } + + ret = func_getResourceImage.exec(jstext); + + if (ret) + { + var index_patch3 = ret.index + ret[0].length; + var head_patch3 = jstext.slice(0, index_patch3); + var tail_patch3 = jstext.slice(index_patch3); + jstext = head_patch3 + "e = window.drawhookcanvas(e,this._imageModel);" + tail_patch3; + console.log("[UserScript] Injection: patch_3 injected successful " + srimgc.src); + } + + ret = func_drawGeometry.exec(jstext); + + if (ret) + { + var index1 = ret.index + ret[1].length; + var head1 = jstext.slice(0, index1); + var tail1 = jstext.slice(index1); + jstext = head1 + ";window.attachbody(t);" + tail1; + console.log("[UserScript] Injection: patch_4 injected successful " + srimgc.src); + setTimeout(addbtnfunc, 3000); + } + //ret = func_test.exec(jstext) + var idx = 0; + // while (ret = func_test.exec(jstext)) + // { + // var index = ret.index + ret[0].length; + // var head = jstext.slice(0, index); + // var tail = jstext.slice(index); + // jstext = head +"window.attachbody(e);"+ tail; + // //jstext = head + "window.drawhook(e);" + tail; + // func_test.lastIndex = index + 1000; + // console.log("[UserScript] Injection: patch_4 injected successful" + srimgc.src); + // setTimeout(addbtnfunc, 3000); + // } + + var obj = document.createElement('script'); + obj.type = "text/javascript"; + obj.text = jstext; + document.getElementsByTagName('head')[0].appendChild(obj); + } + } + }); + }; + })();