Sketchfab Model Downloader

Download Sketchfab models

  1. // ==UserScript==
  2. // @name Sketchfab Model Downloader
  3. // @version 0.9.2
  4. // @description Download Sketchfab models
  5. // @author hoosnick
  6. // @include /^https?://(www\.)?sketchfab\.com/.*
  7. // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.5/jszip.min.js
  8. // @require https://cdnjs.cloudflare.com/ajax/libs/jszip-utils/0.0.2/jszip-utils.min.js
  9. // @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.8/FileSaver.js
  10. // @run-at document-start
  11. // @grant unsafeWindow
  12. // @grant GM_download
  13. // @namespace https://gf.qytechs.cn/users/1213259
  14. // ==/UserScript==
  15.  
  16. var zip = new JSZip();
  17. let folder = zip.folder('collection');
  18.  
  19. var button_dw = true;
  20.  
  21. var func_drawGeometry = /(this\._stateCache\.drawGeometry\(this\._graphicContext,t\))/g;
  22. var fund_drawArrays = /t\.drawArrays\(t\.TRIANGLES,0,6\)/g;
  23. var func_renderInto1 = /A\.renderInto\(n,E,R/g;
  24. var func_renderInto2 = /g\.renderInto=function\(e,i,r/g;
  25. var func_getResourceImage = /getResourceImage:function\(e,t\){/g;
  26. var func_test = /apply:function\(e\){var t=e instanceof r\.Geometry;/g
  27.  
  28. var addbtnfunc;
  29.  
  30. (function () {
  31. 'use strict';
  32. var window = unsafeWindow;
  33. console.log("[UserScript] init", window);
  34.  
  35. window.allmodel = [];
  36. var saveimagecache2 = {};
  37. var objects = {};
  38.  
  39.  
  40. var save_image_to_list = function (url, file_name) {
  41. if (!saveimagecache2[url]) {
  42. var mdl = {
  43. name: file_name
  44. }
  45.  
  46. saveimagecache2[url] = mdl;
  47. }
  48. }
  49.  
  50. addbtnfunc = function () {
  51. var p = document.evaluate("//div[@class='titlebar']", document, null, 9, null).singleNodeValue;
  52. if (p && !button_dw) {
  53. console.log("[UserScript] add btn download");
  54. var btn = document.createElement("a");
  55. btn.setAttribute("class", "control");
  56. btn.innerHTML = "<pre style='color: red; background-color: #e74c3c; border: none; color: white; padding: 10px 20px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer; border-radius: 8px;'>DOWNLOAD</pre>";
  57. btn.addEventListener("click", dodownload, false);
  58. p.appendChild(btn);
  59. button_dw = true;
  60. } else {
  61. console.log("[UserScript] try add btn later");
  62. setTimeout(addbtnfunc, 3000);
  63. }
  64. }
  65.  
  66. var dodownload = function () {
  67. console.log("[UserScript] download");
  68. var idx = 0;
  69.  
  70. window.allmodel.forEach(function (obj) {
  71. var mdl = {
  72. name: "model_" + idx,
  73. obj: parseobj(obj)
  74. }
  75. console.log(mdl);
  76. dosavefile(mdl);
  77. idx++;
  78.  
  79. })
  80. PackAll();
  81. }
  82.  
  83. var PackAll = function () {
  84. for (var obj in objects) {
  85. console.log("[UserScript] save file", obj);
  86. folder.file(obj, objects[obj], {
  87. binary: true
  88. });
  89. }
  90.  
  91. var file_name = document.getElementsByClassName('model-name__label')[0].textContent;
  92. folder.generateAsync({
  93. type: "blob"
  94. }).then(content => saveAs(content, file_name + ".zip"));
  95. }
  96.  
  97. var parseobj = function (obj) {
  98. console.log("[UserScript]: obj", obj);
  99. var list = [];
  100. obj._primitives.forEach(function (p) {
  101. if (p && p.indices) {
  102. list.push({
  103. 'mode': p.mode,
  104. 'indices': p.indices._elements
  105. });
  106. }
  107. })
  108.  
  109. var attr = obj._attributes;
  110. return {
  111. vertex: attr.Vertex._elements,
  112. normal: attr.Normal ? attr.Normal._elements : [],
  113. uv: attr.TexCoord0 ? attr.TexCoord0._elements :
  114. attr.TexCoord1 ? attr.TexCoord1._elements :
  115. attr.TexCoord2 ? attr.TexCoord2._elements :
  116. attr.TexCoord2 ? attr.TexCoord2._elements :
  117. attr.TexCoord3 ? attr.TexCoord3._elements :
  118. attr.TexCoord4 ? attr.TexCoord4._elements :
  119. attr.TexCoord5 ? attr.TexCoord5._elements :
  120. attr.TexCoord6 ? attr.TexCoord6._elements :
  121. attr.TexCoord7 ? attr.TexCoord7._elements :
  122. attr.TexCoord8 ? attr.TexCoord8._elements : [],
  123. primitives: list,
  124. };
  125. }
  126.  
  127. var dosavefile = function (mdl) {
  128. var obj = mdl.obj;
  129.  
  130. var str = '';
  131. str += 'mtllib ' + mdl.name + '.mtl\n';
  132. str += 'o ' + mdl.name + '\n';
  133. for (var i = 0; i < obj.vertex.length; i += 3) {
  134. str += 'v ';
  135. for (var j = 0; j < 3; ++j) {
  136. str += obj.vertex[i + j] + ' ';
  137. }
  138. str += '\n';
  139. }
  140. for (i = 0; i < obj.normal.length; i += 3) {
  141. str += 'vn ';
  142. for (j = 0; j < 3; ++j) {
  143. str += obj.normal[i + j] + ' ';
  144. }
  145. str += '\n';
  146. }
  147.  
  148. for (i = 0; i < obj.uv.length; i += 2) {
  149. str += 'vt ';
  150. for (j = 0; j < 2; ++j) {
  151. str += obj.uv[i + j] + ' ';
  152. }
  153. str += '\n';
  154. }
  155. str += 's on \n';
  156.  
  157. var vn = obj.normal.length != 0;
  158. var vt = obj.uv.length != 0;
  159.  
  160. for (i = 0; i < obj.primitives.length; ++i) {
  161. var primitive = obj.primitives[i];
  162. if (primitive.mode == 4 || primitive.mode == 5) {
  163. var strip = (primitive.mode == 5);
  164. for (j = 0; j + 2 < primitive.indices.length; !strip ? j += 3 : j++) {
  165. str += 'f ';
  166. var order = [0, 1, 2];
  167. if (strip && (j % 2 == 1)) {
  168. order = [0, 2, 1];
  169. }
  170. for (var k = 0; k < 3; ++k) {
  171. var faceNum = primitive.indices[j + order[k]] + 1;
  172. str += faceNum;
  173. if (vn || vt) {
  174. str += '/';
  175. if (vt) {
  176. str += faceNum;
  177. }
  178. if (vn) {
  179. str += '/' + faceNum;
  180. }
  181. }
  182. str += ' ';
  183. }
  184. str += '\n';
  185. }
  186. } else {
  187. console.log("[UserScript] dosavefile: unknown primitive mode", primitive);
  188. }
  189. }
  190.  
  191. str += '\n';
  192.  
  193. var objblob = new Blob([str], {
  194. type: 'text/plain'
  195. });
  196.  
  197. objects[mdl.name + ".obj"] = objblob;
  198. }
  199.  
  200. window.attachbody = function (obj) {
  201. if (obj._faked != true && ((obj.stateset && obj.stateset._name) || obj._name || (obj._parents && obj._parents[0]._name))) {
  202. obj._faked = true;
  203. if (obj._name == "composer layer" || obj._name == "Ground - Geometry") return;
  204. window.allmodel.push(obj)
  205. console.log(obj);
  206. }
  207. }
  208.  
  209. window.hook_test = function (e, idx) {
  210. console.log("hooked index: " + idx);
  211. console.log(e);
  212. }
  213.  
  214. window.drawhookcanvas = function (e, imagemodel) {
  215. if ((e.width == 128 && e.height == 128) || (e.width == 32 && e.height == 32) || (e.width == 64 && e.height == 64)) {
  216. return e;
  217. }
  218.  
  219. if (imagemodel) {
  220. var alpha = e.options.format;
  221. var filename_image = imagemodel.attributes.name;
  222. var uid = imagemodel.attributes.uid;
  223. var url_image = e.url;
  224. var max_size = 0;
  225. var obr = e;
  226.  
  227. imagemodel.attributes.images.forEach(function (img) {
  228. var alpha_is_check = alpha == "A" ? img.options.format == alpha : true;
  229.  
  230. var d = img.width;
  231. while (d % 2 == 0) {
  232. d = d / 2;
  233. }
  234.  
  235. if (img.size > max_size && alpha_is_check && d == 1) {
  236. max_size = img.size;
  237. url_image = img.url;
  238. uid = img.uid;
  239. obr = img;
  240. }
  241. });
  242.  
  243. if (!saveimagecache2[url_image]) {
  244. console.log(e);
  245. save_image_to_list(url_image, filename_image);
  246. } else {
  247. console.log(e);
  248. }
  249.  
  250. return obr;
  251. }
  252. return e;
  253. }
  254.  
  255. window.drawhookimg = function (gl, t) {
  256. console.log(JSON.stringify(t));
  257. var url = t[5].currentSrc;
  258. var width = t[5].width;
  259. var height = t[5].height;
  260.  
  261. if (!saveimagecache2[url]) {
  262. return;
  263. } else {
  264. console.log("saved texture:" + url);
  265. }
  266.  
  267.  
  268. var data = new Uint8Array(width * height * 4);
  269. gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, data);
  270.  
  271. var halfHeight = height / 2 | 0; // the | 0 keeps the result an int
  272. var bytesPerRow = width * 4;
  273.  
  274. // make a temp buffer to hold one row
  275. var temp = new Uint8Array(width * 4);
  276. for (var y = 0; y < halfHeight; ++y) {
  277. var topOffset = y * bytesPerRow;
  278. var bottomOffset = (height - y - 1) * bytesPerRow;
  279.  
  280. // make copy of a row on the top half
  281. temp.set(data.subarray(topOffset, topOffset + bytesPerRow));
  282.  
  283. // copy a row from the bottom half to the top
  284. data.copyWithin(topOffset, bottomOffset, bottomOffset + bytesPerRow);
  285.  
  286. // copy the copy of the top half row to the bottom half
  287. data.set(temp, bottomOffset);
  288. }
  289.  
  290. // Create a 2D canvas to store the result
  291. var canvas = document.createElement('canvas');
  292. canvas.width = width;
  293. canvas.height = height;
  294. var context = canvas.getContext('2d');
  295.  
  296. // Copy the pixels to a 2D canvas
  297. var imageData = context.createImageData(width, height);
  298. imageData.data.set(data);
  299. context.putImageData(imageData, 0, 0);
  300.  
  301. var re = /(?:\.([^.]+))?$/;
  302. var ext = re.exec(saveimagecache2[url].name)[1];
  303. var name = saveimagecache2[url].name + ".png";
  304.  
  305. if (ext == "png" || ext == "jpg" || ext == "jpeg") {
  306. var ret = saveimagecache2[url].name.replace('.' + ext, '');
  307. name = ret + ".png";
  308. }
  309. console.log("saved texture to blob " + name);
  310. canvas.toBlob(function (blob) {
  311. objects[name] = blob;
  312. }, "image/png");
  313. }
  314.  
  315. })();
  316.  
  317. (() => {
  318. "use strict";
  319. const Event = class {
  320. constructor(script, target) {
  321. this.script = script;
  322. this.target = target;
  323.  
  324. this._cancel = false;
  325. this._replace = null;
  326. this._stop = false;
  327. }
  328.  
  329. preventDefault() {
  330. this._cancel = true;
  331. }
  332. stopPropagation() {
  333. this._stop = true;
  334. }
  335. replacePayload(payload) {
  336. this._replace = payload;
  337. }
  338. };
  339.  
  340. let callbacks = [];
  341. window.addBeforeScriptExecuteListener = (f) => {
  342. if (typeof f !== "function") {
  343. throw new Error("Event handler must be a function.");
  344. }
  345. callbacks.push(f);
  346. };
  347. window.removeBeforeScriptExecuteListener = (f) => {
  348. let i = callbacks.length;
  349. while (i--) {
  350. if (callbacks[i] === f) {
  351. callbacks.splice(i, 1);
  352. }
  353. }
  354. };
  355.  
  356. const dispatch = (script, target) => {
  357. if (script.tagName !== "SCRIPT") {
  358. return;
  359. }
  360.  
  361. const e = new Event(script, target);
  362.  
  363. if (typeof window.onbeforescriptexecute === "function") {
  364. try {
  365. window.onbeforescriptexecute(e);
  366. } catch (err) {
  367. console.error(err);
  368. }
  369. }
  370.  
  371. for (const func of callbacks) {
  372. if (e._stop) {
  373. break;
  374. }
  375. try {
  376. func(e);
  377. } catch (err) {
  378. console.error(err);
  379. }
  380. }
  381.  
  382. if (e._cancel) {
  383. script.textContent = "";
  384. script.remove();
  385. } else if (typeof e._replace === "string") {
  386. script.textContent = e._replace;
  387. }
  388. };
  389. const observer = new MutationObserver((mutations) => {
  390. for (const m of mutations) {
  391. for (const n of m.addedNodes) {
  392. dispatch(n, m.target);
  393. }
  394. }
  395. });
  396. observer.observe(document, {
  397. childList: true,
  398. subtree: true,
  399. });
  400. })();
  401.  
  402. (() => {
  403. "use strict";
  404.  
  405. window.onbeforescriptexecute = (e) => {
  406. var links_as_arr = Array.from(e.target.childNodes);
  407.  
  408. links_as_arr.forEach(function (srimgc) {
  409. if (srimgc instanceof HTMLScriptElement) {
  410. if (srimgc.src.indexOf("web/dist/") >= 0 || srimgc.src.indexOf("standaloneViewer") >= 0) {
  411. e.preventDefault();
  412. e.stopPropagation();
  413.  
  414. var req = new XMLHttpRequest();
  415.  
  416. req.open('GET', srimgc.src, false);
  417. req.send('');
  418.  
  419. var jstext = req.responseText;
  420. var ret = func_renderInto1.exec(jstext);
  421.  
  422. if (ret) {
  423. var index = ret.index + ret[0].length;
  424. var head = jstext.slice(0, index);
  425. var tail = jstext.slice(index);
  426. jstext = head + ",i" + tail;
  427.  
  428. console.log("[UserScript] Injection: patch_0 injected successful " + srimgc.src);
  429. }
  430.  
  431. ret = func_renderInto2.exec(jstext);
  432.  
  433. if (ret) {
  434. var index = ret.index + ret[0].length;
  435. var head = jstext.slice(0, index);
  436. var tail = jstext.slice(index);
  437.  
  438. jstext = head + ",image_data" + tail;
  439.  
  440. console.log("[UserScript] Injection: patch_1 injected successful " + srimgc.src);
  441.  
  442. if (!func_renderInto1.exec(jstext))
  443. console.log("[UserScript] But patch_0 failed " + srimgc.src);
  444. }
  445.  
  446. ret = fund_drawArrays.exec(jstext);
  447.  
  448. if (ret) {
  449. var index = ret.index + ret[0].length;
  450. var head = jstext.slice(0, index);
  451. var tail = jstext.slice(index);
  452.  
  453. jstext = head + ",window.drawhookimg(t,image_data)" + tail;
  454.  
  455. console.log("[UserScript] Injection: patch_2 injected successful " + srimgc.src);
  456. }
  457.  
  458. ret = func_getResourceImage.exec(jstext);
  459.  
  460. if (ret) {
  461. var index = ret.index + ret[0].length;
  462. var head = jstext.slice(0, index);
  463. var tail = jstext.slice(index);
  464.  
  465. jstext = head + "e = window.drawhookcanvas(e,this._imageModel);" + tail;
  466.  
  467. console.log("[UserScript] Injection: patch_3 injected successful " + srimgc.src);
  468. }
  469.  
  470. ret = func_drawGeometry.exec(jstext);
  471.  
  472. if (ret) {
  473. var index1 = ret.index + ret[1].length;
  474. var head1 = jstext.slice(0, index1);
  475. var tail1 = jstext.slice(index1);
  476. jstext = head1 + ";window.attachbody(t);" + tail1;
  477.  
  478. console.log("[UserScript] Injection: patch_4 injected successful " + srimgc.src);
  479.  
  480. setTimeout(addbtnfunc, 3000);
  481. }
  482.  
  483. var obj = document.createElement('script');
  484. obj.type = "text/javascript";
  485. obj.text = jstext;
  486.  
  487. document.getElementsByTagName('head')[0].appendChild(obj);
  488. }
  489. }
  490. });
  491. };
  492. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址