// ==UserScript==
// @name tarkov help unlocker
// @namespace https://greasyfork.org/ru/scripts/521315-tarkov-help-unlocker
// @version 1.1.1
// @description Разблокировывает карты tarkov.help, закрытые за премиумом
// @author GlassySundew
// @match https://tarkov.help/ru/map/*
// @match https://api.tarkov.help/*
// @license GNU GPLv3
// @run-at document-start
// @grant GM.xmlHttpRequest
// @connect tarkov.help
// ==/UserScript==
(function () {
"use strict";
let streetsData,
shorelineData,
labsData,
interchangeData,
reserveData,
lighthouseData,
mapLinkContainer;
const interceptionRequired = {
ulici: true,
bereg: true,
laboratoriya: true,
razvyazka: true,
rezerv: true,
mayak: true,
};
const mapAlias = {
ulici: "streets",
bereg: "shoreline",
zavod: "factory",
laboratoriya: "labs",
les: "woods",
razvyazka: "interchange",
rezerv: "reserve",
tamozhnya: "customs",
mayak: "lighthouse",
};
const mapAliasInv = {
ulici: "streets",
bereg: "shoreline",
zavod: "factory",
laboratoriya: "labs",
les: "woods",
razvyazka: "interchange",
rezerv: "reserve",
tamozhnya: "customs",
mayak: "lighthouse",
};
function setupData(data) {
streetsData = {
info: data.fakeStreetsInfo,
markers: data.fakeStreetsMarkers,
containers: data.fakeStreetsContainers,
spawns: data.fakeStreetsSpawns,
};
shorelineData = {
info: data.fakeShorelineInfo,
markers: data.fakeShorelineMarkers,
containers: data.fakeShorelineContainers,
spawns: data.fakeShorelineSpawns,
};
labsData = {
info: data.fakeLabsInfo,
markers: data.fakeLabsMarkers,
containers: data.fakeLabsContainers,
spawns: data.fakeLabsSpawns,
};
interchangeData = {
info: data.fakeInterchangeInfo,
markers: data.fakeInterchangeMarkers,
containers: data.fakeInterchangeContainers,
spawns: data.fakeInterchangeSpawns,
};
reserveData = {
info: data.fakeReserveInfo,
markers: data.fakeReserveMarkers,
containers: data.fakeReserveContainers,
spawns: data.fakeReserveSpawns,
};
lighthouseData = {
info: data.fakeLighthouseInfo,
markers: data.fakeLighthouseMarkers,
containers: data.fakeLighthouseContainers,
spawns: data.fakeLighthouseSpawns,
};
mapLinkContainer = {
11: streetsData,
ulici: streetsData,
4: shorelineData,
bereg: shorelineData,
5: labsData,
laboratoriya: labsData,
8: interchangeData,
razvyazka: interchangeData,
9: reserveData,
rezerv: reserveData,
6: lighthouseData,
mayak: lighthouseData,
};
}
const observer = new MutationObserver((mutations) => {
for (const m of mutations) {
for (const node of m.addedNodes) {
if (
node.tagName === "SCRIPT" &&
node.src.includes("/static/js/main.")
) {
console.log("Tampermonkey: Detected script", node.src);
node.parentNode.removeChild(node);
GM.xmlHttpRequest({
method: "GET",
url: node.src,
onload: function (res) {
if (res.status === 200) {
let text = res.responseText;
text = text.replace(
/throw\s+new\s+Error/g,
"console.log"
);
text = text.replace(
/throw\s+new\s+TypeError/g,
"console.log"
);
const newScript =
document.createElement("script");
newScript.textContent = text;
document.head.appendChild(newScript);
console.log(
"Tampermonkey: Patched main.js inserted"
);
}
},
});
}
}
}
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
const dataPromise = fetch("https://glassysundew.github.io/mock.json")
.then((response) => response.json())
.then((data) => setupData(data));
const originalCall = Function.prototype.call;
const originalCallToString = originalCall.toString();
Function.prototype.call = function _(...args) {
let result;
if (
args[0] &&
args[1] &&
(args[0] == args[0].window || args[1] == args[1].window)
) {
return originalCall.apply(this, args);
}
try {
if (this.name == 300) {
result = originalCall.apply(this, args);
if (args[1].exports) {
const originalExports = args[1].exports;
args[1].exports = new Proxy(originalExports, {
get(target, prop) {
if (prop === "d4") {
const originalD4 = target[prop];
return function (...innerArgs) {
var selector = originalD4.apply(
this,
innerArgs
);
if (Array.isArray(selector)) {
for (const value of selector) {
if (value["seo_link"]) {
value.premium = false;
}
}
}
return selector;
};
}
return target[prop];
},
});
console.log("Proxy для exports установлен.");
}
} else {
result = originalCall.apply(this, args);
}
} catch (error) {
console.error("Ошибка при перехвате вызова:", error);
}
return result;
};
Object.defineProperty(Function.prototype.call, "toString", {
value: function () {
return originalCallToString;
},
writable: false,
configurable: false,
});
console.log("Function.prototype.call успешно переопределён.");
//*======================
function isIFrameInstance() {
return window.self !== window.top;
}
function createPromise() {
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
promise.resolve = resolve;
promise.reject = reject;
return promise;
}
let isTrueMapResolved = false;
let trueMapName = null;
let trueMapNamePromise = createPromise();
trueMapNamePromise.then((val) => {
isTrueMapResolved = true;
console.log("found true map name: " + val);
});
let decoyMapName = "ground-zero";
const mapNameRegex = /(?<=map\/)[^\/]+/g;
window.addEventListener("message", (event) => {
if (event.data.trueMapName != null) {
console.log(
"got message, setting trueMapName, was: " + trueMapNamePromise,
" becomes: " + event.data.trueMapName
);
trueMapName = event.data.trueMapName;
trueMapNamePromise.resolve(trueMapName);
return;
}
});
Object.defineProperty(HTMLImageElement.prototype, "src", {
set(value) {
console.log("[Image Intercept] Original src:", value);
if (trueMapName != null) {
// Проверяем и модифицируем src
if (
interceptionRequired[trueMapName] &&
value.includes(".webp")
) {
trueMapNamePromise.then((mapName) => {
let replacer = mapName;
if (mapName != null) {
var alias = mapAlias[mapName];
replacer = alias == null ? mapName : alias;
}
const newSrc = value.replace(decoyMapName, replacer);
console.log("[Image Intercept] Modified src:", newSrc);
this.setAttribute("src", newSrc);
});
return;
}
}
this.setAttribute("src", value);
},
});
function tryExtractTrueMapName(url) {
if (isTrueMapResolved) return;
var match = url.match(mapNameRegex);
if (!match) return;
trueMapName = match[0];
trueMapNamePromise.resolve(trueMapName);
console.log("true map name resolved: " + match[0]);
}
if (!isIFrameInstance()) tryExtractTrueMapName(window.location.href);
const originalCreateElement = Document.prototype.createElement;
Document.prototype.createElement = function qwe(tagName, options) {
const el = originalCreateElement.call(this, tagName, options);
if (tagName.toLowerCase() === "iframe") {
console.log("[Iframe lookat] doc-request:", tagName, options);
const originalSetAttribute = el.setAttribute;
function sendUrlToIFrame() {
setTimeout(() => {
const iframe = document.querySelector("iframe");
iframe.addEventListener("load", () => {
iframe.contentWindow.postMessage(
{trueMapName: trueMapName},
"*"
);
});
}, 0);
}
el.removeAttribute("sandbox");
el.setAttribute = function (name, value) {
let match = value.match(mapNameRegex);
if (
match &&
interceptionRequired[match[0]] &&
name.toLowerCase() === "src"
) {
console.log("[Iframe Intercept] doc-request:", value);
const newSrc =
"https://api.tarkov.help/api/ru/map/ground-zero/amogus";
originalSetAttribute.call(this, "src", newSrc);
sendUrlToIFrame();
return;
}
sendUrlToIFrame();
return originalSetAttribute.call(this, name, value);
};
}
return el;
};
//*======================
const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;
const originalSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
let didAskForIFrameThroughXHR = false;
XMLHttpRequest.prototype.open = function (method, url) {
console.log("[XHR Intercept] open:", method, url);
this._headers = [];
trueMapNamePromise.then((trueMapName) => {
if (!isTrueMapResolved && !isIFrameInstance())
tryExtractTrueMapName(url);
if (trueMapName != null && url.includes("/map")) {
if (isIFrameInstance() || didAskForIFrameThroughXHR) {
url = url.replace(decoyMapName, trueMapName);
} else {
url = url.replace(mapNameRegex, decoyMapName);
didAskForIFrameThroughXHR = true;
}
}
if (url.includes("/map")) {
let match = url.match(mapNameRegex);
if (match && interceptionRequired[match[0]]) {
this._intercepted = true;
}
}
if (url.includes("/subscriptions")) {
this._intercepted = true;
this._prebakedData = JSON.stringify(fakeSub);
}
if (url.includes("/session")) {
this._intercepted = true;
this._prebakedData = JSON.stringify({
data: {
session: "123123123123",
expires: "1237998129378178923798",
},
});
}
console.log(
"overriden url: " +
url +
" isIntercepted: " +
this._intercepted +
" isTrueKeyFetched: " +
isTrueMapResolved
);
this._customData = {
url: url,
};
originalOpen.apply(this, [method, url]);
this._headers.forEach(({name, value}) => {
originalSetRequestHeader.call(this, name, value);
});
});
};
XMLHttpRequest.prototype.send = function (body) {
const sendArgs = arguments;
trueMapNamePromise.then((trueMapName) => {
try {
if (this._intercepted) {
var url = this._customData.url.replace(
decoyMapName,
trueMapName
);
console.log(
"[XHR Intercept] send body:",
body,
"url: " + url
);
dataPromise.then((data) => {
respondWithData(this, url, body, sendArgs);
});
return;
} else {
console.log("ignoring request: " + this._customData.url);
return originalSend.apply(this, arguments);
}
} catch (error) {
console.error("Ошибка при перехвате вызова:", error);
}
});
};
XMLHttpRequest.prototype.setRequestHeader = function (name, value) {
if (!this._headers) {
return originalSetRequestHeader.call(this, name, value);
}
this._headers.push({name, value});
};
function respondWithData(xhr, url, body, sendArgs) {
let responseText = null;
if ("_prebakedData" in xhr) {
responseText = xhr._prebakedData;
} else {
responseText = JSON.stringify(getFakeMapResponse(url, body));
}
if (responseText == "null") {
console.log(
"не получилось найти данные для перехваченного запроса: " + url
);
return originalSend.apply(xhr, sendArgs);
}
const fakeStatus = 200;
const fakeStatusText = "OK";
const fakeResponseURL = xhr._url;
xhr._fakeStatus = fakeStatus;
xhr._fakeStatusText = fakeStatusText;
xhr._fakeResponseText = responseText;
xhr._fakeResponseURL = fakeResponseURL;
xhr._fakeReadyState = 4;
Object.defineProperty(xhr, "status", {
get: () => xhr._fakeStatus,
});
Object.defineProperty(xhr, "statusText", {
get: () => xhr._fakeStatusText,
});
Object.defineProperty(xhr, "responseText", {
get: () => xhr._fakeResponseText,
});
Object.defineProperty(xhr, "response", {
get: () => xhr._fakeResponseText,
});
Object.defineProperty(xhr, "readyState", {
get: () => xhr._fakeReadyState,
});
Object.defineProperty(xhr, "responseURL", {
get: () => xhr._fakeResponseURL,
});
Object.defineProperty(xhr, "getAllResponseHeaders", {
value: function () {
return "Content-Type: application/json\r\n";
},
configurable: true,
writable: true,
});
delete xhr.getResponseHeader;
Object.defineProperty(xhr, "getResponseHeader", {
configurable: true,
writable: true,
value: function (name) {
if (name.toLowerCase() === "content-type") {
return "application/json";
}
return null;
},
});
setTimeout(() => {
if (typeof xhr.onreadystatechange === "function") {
xhr.onreadystatechange();
}
xhr.dispatchEvent(new Event("readystatechange"));
if (typeof xhr.onload === "function") {
xhr.onload();
}
xhr.dispatchEvent(new Event("load"));
if (typeof xhr.onloadend === "function") {
xhr.onloadend();
}
xhr.dispatchEvent(new Event("loadend"));
}, 0);
}
function getFakeMapResponse(url, body) {
console.log("fake map data requested: " + url, body);
const mapNameMatch = url.match(mapNameRegex);
var mapName = mapNameMatch ? mapNameMatch[0] : null;
let mapPart = url.split("/map/")[1];
const typeMatch = mapPart.match(/\/([^\/\?]+)(?:[\/\?]|$)/);
var type = typeMatch ? typeMatch[1] : null;
var mapData = mapLinkContainer[mapName];
if (mapData == null) return null;
var mapTypeData = mapData[type];
if (mapTypeData == null) mapTypeData = mapData.info;
console.log("сырые данные по урл: " + url, mapTypeData);
const response = JSON.parse(JSON.stringify(mapTypeData));
if (body !== null) {
let parsedBody;
try {
parsedBody = JSON.parse(body);
} catch (e) {
console.error("Ошибка при разборе JSON:", e);
}
if (
parsedBody !== null &&
parsedBody.limit !== undefined &&
parsedBody.offset !== undefined
) {
response.data = response.data.slice(
parsedBody.offset,
parsedBody.offset + parsedBody.limit
);
}
}
return response;
}
})();