NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Majsoul Plus ResourcePack Loader (JP) // @namespace majsoul // @version 0.0.5 // @description Apply *multiple* majsoul plus resource pack using UserScript, added ingame announce data replacer. // @author YF-DEV, SHINJJANGGU, rishubil, Yudong, Luvie // @license MIT // @include https://game.mahjongsoul.com/* // @grant unsafeWindow // @run-at document-start // @updateURL https://openuserjs.org/meta/Luvie/Majsoul_Plus_ResourcePack_Loader_(JP).meta.js // @downloadURL https://openuserjs.org/install/Luvie/Majsoul_Plus_ResourcePack_Loader_(JP).user.js // ==/UserScript== (async function () { 'use strict'; const GAME_BASE_URL = 'https://game.mahjongsoul.com/'; const version_re = /v\d+\.\d+\.\d+\.w\//i; /** 적용할 리소스팩의 이름, Url, 적용 여부(적용하려면 true, 적용하지 않으려면 false)를 아래 resourceArray에 작성하면 됩니다. resourceUrl의 root에 majsoul plus 2.0의 포맷에 맞는 resourcepack.json 파일이 있어야합니다. 기본적인 replace구문만 지원하며 from/to 구문은 지원하지 않습니다. (https://github.com/MajsoulPlus/majsoul-plus/wiki/v2_resourcepack) 각각의 resourcepack.json에 정의된 리소스 파일을 게임 시작시 교체해줍니다. resourceUrl의 root에서 원본과 동일한 path에 교체할 리소스 파일이 업로드 되어 있어야 합니다. 여러 리소스팩을 동시에 적용할 수 있도록 작성한 코드입니다. 또한 한글 패치 스크립트에 신규 추가된 공지사항 번역 기능도 같이 추가되어 있습니다. (announce 변수를 참조하십시오) 한글화 패치의 리소스 및 스크립트 코드를 상당 부분 그대로 사용하였음을 밝힙니다. 감사합니다. (https://openuserjs.org/scripts/Shijjanggu/JakhonHangle_JP_SERVER) */ let resourceArray = [ {name: "majsoul_eng_tiles", resourceUrl: "https://luviels.github.io/majsoulEngTiles/", isEnable: true}, {name: "majsoul_korean_pack", resourceUrl: "https://shinjjanggu.github.io/jakhonplus/korean/", isEnable: true} ]; let announce = await(await fetch("https://shinjjanggu.github.io/jakhonplus/korean/announce.json")).json() resourceArray.forEach(async function(data) { if (!data.resourceUrl.endsWith("/")) { console.log("resourceUrl not endsWith slash, force add slash.") data.resourceUrl = data.resourceUrl + "/"; } let jsonData = await(await fetch(data.resourceUrl + "resourcepack.json")).json() data.json = jsonData }); console.log(resourceArray); replaceXhrOpen(); // metadata(json, fnt, atlas...) replace when XMLHttpRequest is open. replaceCodeScript(); // image, sound, ttf replace when code.js is onload. function replaceCodeScript() { let observer = null; observer = new MutationObserver(function (mutations) { mutations.forEach(function (mutation) { const scripts = document.getElementsByTagName('script'); for (let i = 0; i < scripts.length; i++) { const script = scripts[i]; if (script.src && script.src.indexOf('code.js') !== -1) { script.onload = function () { replaceLayaLoadImage(); replaceLayaLoadSound(); replaceLayaLoadTtf(); replaceAnnounce(); }; observer.disconnect(); } } }); }); const config = { childList: true, subtree: true }; observer.observe(document, config); } function updateUrl(url) { const original_url = url; if (url.startsWith(GAME_BASE_URL)) { url = url.substring(GAME_BASE_URL.length); } url = url.replace(version_re, ''); for (var idx in resourceArray){ if (resourceArray[idx].isEnable && resourceArray[idx].json.replace.includes(url)) { url = resourceArray[idx].resourceUrl + 'assets/' + url; return url; } }; return original_url; } function replaceXhrOpen() { const original_function = window.XMLHttpRequest.prototype.open; window.XMLHttpRequest.prototype.open = function (method, url, async, user, password) { return original_function.call(this, method, updateUrl(url), async, user, password); }; } function replaceLayaLoadImage() { const original_function = Laya.Loader.prototype._loadImage; Laya.Loader.prototype._loadImage = function (url) { return original_function.call(this, updateUrl(url)); } } function replaceLayaLoadSound() { const original_function = Laya.Loader.prototype._loadSound; Laya.Loader.prototype._loadSound = function (url) { return original_function.call(this, updateUrl(url)); } } function replaceLayaLoadTtf() { const original_function = Laya.Loader.prototype._loadTTF; Laya.Loader.prototype._loadTTF = function (url) { return original_function.call(this, updateUrl(url)); } } // replace via announcements' array index (somewhat risky?) function replaceAnnounce() { const original_function = uiscript.UI_Info._refreshAnnouncements uiscript.UI_Info._refreshAnnouncements = function (t) { t.announcements.forEach((a)=> { if (announce[a.id]) { a.title = announce[a.id].title a.content = announce[a.id].content } }) return original_function.call(this, t) } } })();