2025-06-01 12:40:24 -04:00
|
|
|
let widevineDeviceInfo = null;
|
|
|
|
let playreadyDeviceInfo = null;
|
2025-07-20 12:18:15 +07:00
|
|
|
let originalChallenge = null;
|
2025-06-16 00:24:53 -04:00
|
|
|
let serviceCertFound = false;
|
|
|
|
let drmType = "NONE";
|
|
|
|
let psshFound = false;
|
2025-06-26 21:45:26 -04:00
|
|
|
let foundWidevinePssh = null;
|
|
|
|
let foundPlayreadyPssh = null;
|
|
|
|
let drmDecided = null;
|
2025-06-09 12:01:39 -04:00
|
|
|
let drmOverride = "DISABLED";
|
|
|
|
let interceptType = "DISABLED";
|
2025-06-16 00:24:53 -04:00
|
|
|
let remoteCDM = null;
|
|
|
|
let generateRequestCalled = false;
|
|
|
|
let remoteListenerMounted = false;
|
|
|
|
let injectionSuccess = false;
|
2025-06-16 01:21:17 -04:00
|
|
|
let foundChallengeInBody = false;
|
2025-06-16 00:24:53 -04:00
|
|
|
let licenseResponseCounter = 0;
|
2025-06-29 12:07:18 -04:00
|
|
|
let keysRetrieved = false;
|
2025-06-01 17:38:07 -04:00
|
|
|
|
2025-07-21 11:54:40 +07:00
|
|
|
const DRM_SIGNATURES = {
|
|
|
|
WIDEVINE: "CAES",
|
|
|
|
PLAYREADY: "PD94",
|
|
|
|
SERVICE_CERT: "CAUS",
|
|
|
|
WIDEVINE_INIT: "CAQ=",
|
|
|
|
};
|
|
|
|
|
2025-06-09 12:01:39 -04:00
|
|
|
// Post message to content.js to get DRM override
|
2025-06-01 17:38:07 -04:00
|
|
|
window.postMessage({ type: "__GET_DRM_OVERRIDE__" }, "*");
|
|
|
|
|
2025-06-09 12:01:39 -04:00
|
|
|
// Add listener for DRM override messages
|
2025-07-20 12:18:15 +07:00
|
|
|
window.addEventListener("message", function (event) {
|
|
|
|
if (event.source !== window) return;
|
2025-06-01 17:38:07 -04:00
|
|
|
if (event.data.type === "__DRM_OVERRIDE__") {
|
2025-07-20 12:18:15 +07:00
|
|
|
drmOverride = event.data.drmOverride || "DISABLED";
|
|
|
|
console.log("DRM Override set to:", drmOverride);
|
2025-06-01 17:38:07 -04:00
|
|
|
}
|
|
|
|
});
|
2025-06-01 12:40:24 -04:00
|
|
|
|
2025-06-09 12:01:39 -04:00
|
|
|
// Post message to content.js to get injection type
|
2025-06-01 12:40:24 -04:00
|
|
|
window.postMessage({ type: "__GET_INJECTION_TYPE__" }, "*");
|
|
|
|
|
2025-06-09 12:01:39 -04:00
|
|
|
// Add listener for injection type messages
|
2025-07-20 12:18:15 +07:00
|
|
|
window.addEventListener("message", function (event) {
|
|
|
|
if (event.source !== window) return;
|
2025-06-01 12:40:24 -04:00
|
|
|
|
2025-07-20 12:18:15 +07:00
|
|
|
if (event.data.type === "__INJECTION_TYPE__") {
|
|
|
|
interceptType = event.data.injectionType || "DISABLED";
|
|
|
|
console.log("Injection type set to:", interceptType);
|
|
|
|
}
|
2025-06-01 12:40:24 -04:00
|
|
|
});
|
|
|
|
|
2025-06-09 12:01:39 -04:00
|
|
|
// Post message to get CDM devices
|
2025-06-01 12:40:24 -04:00
|
|
|
window.postMessage({ type: "__GET_CDM_DEVICES__" }, "*");
|
|
|
|
|
2025-06-09 12:01:39 -04:00
|
|
|
// Add listener for CDM device messages
|
2025-07-20 12:18:15 +07:00
|
|
|
window.addEventListener("message", function (event) {
|
|
|
|
if (event.source !== window) return;
|
2025-06-01 12:40:24 -04:00
|
|
|
|
2025-07-20 12:18:15 +07:00
|
|
|
if (event.data.type === "__CDM_DEVICES__") {
|
|
|
|
const { widevine_device, playready_device } = event.data;
|
2025-06-01 12:40:24 -04:00
|
|
|
|
2025-07-20 12:18:15 +07:00
|
|
|
console.log("Received device info:", widevine_device, playready_device);
|
2025-06-01 12:40:24 -04:00
|
|
|
|
2025-07-20 12:18:15 +07:00
|
|
|
widevineDeviceInfo = widevine_device;
|
|
|
|
playreadyDeviceInfo = playready_device;
|
|
|
|
}
|
2025-06-01 12:40:24 -04:00
|
|
|
});
|
|
|
|
|
2025-07-20 12:18:15 +07:00
|
|
|
function safeHeaderShellEscape(str) {
|
|
|
|
return str
|
|
|
|
.replace(/\\/g, "\\\\")
|
|
|
|
.replace(/"/g, '\\"')
|
|
|
|
.replace(/\$/g, "\\$") // escape shell expansion
|
|
|
|
.replace(/`/g, "\\`")
|
|
|
|
.replace(/\n/g, ""); // strip newlines
|
|
|
|
}
|
|
|
|
|
2025-07-21 11:41:49 +07:00
|
|
|
function headersToFlags(headersObj) {
|
|
|
|
return Object.entries(headersObj)
|
|
|
|
.map(
|
|
|
|
([key, val]) =>
|
|
|
|
'--add-headers "' +
|
|
|
|
safeHeaderShellEscape(key) +
|
|
|
|
": " +
|
|
|
|
safeHeaderShellEscape(val) +
|
|
|
|
'"'
|
|
|
|
)
|
|
|
|
.join(" ");
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleManifestDetection(url, headersObj, contentType, source) {
|
|
|
|
window.postMessage({ type: "__MANIFEST_URL__", data: url }, "*");
|
|
|
|
console.log(`[Manifest][${source}]`, url, contentType);
|
|
|
|
|
|
|
|
const headerFlags = headersToFlags(headersObj);
|
|
|
|
|
|
|
|
window.postMessage(
|
|
|
|
{
|
|
|
|
type: "__MANIFEST_HEADERS__",
|
|
|
|
url,
|
|
|
|
headers: headerFlags,
|
|
|
|
},
|
|
|
|
"*"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2025-07-20 12:18:15 +07:00
|
|
|
// Intercep network to find manifest
|
|
|
|
function injectManifestInterceptor() {
|
2025-07-20 17:43:13 +07:00
|
|
|
// Execute the interceptor code directly instead of injecting a script
|
|
|
|
(function () {
|
|
|
|
function isProbablyManifest(text = "", contentType = "") {
|
|
|
|
const lowerCT = contentType?.toLowerCase() ?? "";
|
|
|
|
const sample = text.slice(0, 2000);
|
|
|
|
|
|
|
|
const isHLSMime = lowerCT.includes("mpegurl");
|
|
|
|
const isDASHMime = lowerCT.includes("dash+xml");
|
|
|
|
const isSmoothMime = lowerCT.includes("sstr+xml");
|
|
|
|
|
|
|
|
const isHLSKeyword = sample.includes("#EXTM3U") || sample.includes("#EXT-X-STREAM-INF");
|
|
|
|
const isDASHKeyword = sample.includes("<MPD") || sample.includes("<AdaptationSet");
|
|
|
|
const isSmoothKeyword = sample.includes("<SmoothStreamingMedia");
|
|
|
|
const isJsonManifest = sample.includes('"playlist"') && sample.includes('"segments"');
|
|
|
|
|
|
|
|
return (
|
|
|
|
isHLSMime ||
|
|
|
|
isDASHMime ||
|
|
|
|
isSmoothMime ||
|
|
|
|
isHLSKeyword ||
|
|
|
|
isDASHKeyword ||
|
|
|
|
isSmoothKeyword ||
|
|
|
|
isJsonManifest
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const originalFetch = window.fetch;
|
|
|
|
window.fetch = async function (input, init) {
|
|
|
|
const response = await originalFetch.apply(this, arguments);
|
|
|
|
|
|
|
|
try {
|
|
|
|
const clone = response.clone();
|
|
|
|
const contentType = clone.headers.get("content-type") || "";
|
|
|
|
const text = await clone.text();
|
|
|
|
|
|
|
|
const url = typeof input === "string" ? input : input.url;
|
|
|
|
|
|
|
|
if (isProbablyManifest(text, contentType)) {
|
|
|
|
const headersObj = {};
|
|
|
|
clone.headers.forEach((value, key) => {
|
|
|
|
headersObj[key] = value;
|
|
|
|
});
|
2025-07-21 11:41:49 +07:00
|
|
|
handleManifestDetection(url, headersObj, contentType, "fetch");
|
2025-07-20 17:43:13 +07:00
|
|
|
}
|
|
|
|
} catch (e) {}
|
|
|
|
|
|
|
|
return response;
|
|
|
|
};
|
|
|
|
|
|
|
|
const originalXHROpen = XMLHttpRequest.prototype.open;
|
|
|
|
const originalXHRSend = XMLHttpRequest.prototype.send;
|
|
|
|
|
|
|
|
XMLHttpRequest.prototype.open = function (method, url) {
|
|
|
|
this.__url = url;
|
|
|
|
return originalXHROpen.apply(this, arguments);
|
|
|
|
};
|
|
|
|
|
|
|
|
XMLHttpRequest.prototype.send = function (body) {
|
|
|
|
this.addEventListener("load", function () {
|
2025-07-20 12:18:15 +07:00
|
|
|
try {
|
2025-07-20 17:43:13 +07:00
|
|
|
const contentType = this.getResponseHeader("content-type") || "";
|
|
|
|
const text = this.responseText;
|
2025-07-20 12:18:15 +07:00
|
|
|
|
|
|
|
if (isProbablyManifest(text, contentType)) {
|
2025-07-20 17:43:13 +07:00
|
|
|
const xhrHeaders = {};
|
|
|
|
const rawHeaders = this.getAllResponseHeaders().trim().split(/\r?\n/);
|
|
|
|
rawHeaders.forEach((line) => {
|
|
|
|
const parts = line.split(": ");
|
|
|
|
if (parts.length === 2) {
|
|
|
|
xhrHeaders[parts[0]] = parts[1];
|
|
|
|
}
|
2025-07-20 12:18:15 +07:00
|
|
|
});
|
2025-07-21 11:41:49 +07:00
|
|
|
handleManifestDetection(this.__url, xhrHeaders, contentType, "xhr");
|
2025-07-20 17:43:13 +07:00
|
|
|
}
|
|
|
|
} catch (e) {}
|
|
|
|
});
|
|
|
|
return originalXHRSend.apply(this, arguments);
|
|
|
|
};
|
|
|
|
})();
|
2025-07-20 12:18:15 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
injectManifestInterceptor();
|
2025-06-01 12:40:24 -04:00
|
|
|
|
2025-07-21 11:41:49 +07:00
|
|
|
class RemoteCDMBase {
|
|
|
|
constructor({ host, secret, device_name, security_level }) {
|
2025-06-01 12:40:24 -04:00
|
|
|
this.host = host;
|
|
|
|
this.secret = secret;
|
|
|
|
this.device_name = device_name;
|
2025-07-21 11:41:49 +07:00
|
|
|
this.security_level = security_level;
|
2025-06-01 12:40:24 -04:00
|
|
|
this.session_id = null;
|
|
|
|
this.challenge = null;
|
2025-06-09 12:01:39 -04:00
|
|
|
this.keys = null;
|
2025-06-01 12:40:24 -04:00
|
|
|
}
|
|
|
|
|
2025-07-21 11:41:49 +07:00
|
|
|
openSession(path) {
|
|
|
|
const url = `${this.host}${path}/open`;
|
2025-06-16 00:24:53 -04:00
|
|
|
const xhr = new XMLHttpRequest();
|
2025-07-20 12:18:15 +07:00
|
|
|
xhr.open("GET", url, false);
|
|
|
|
xhr.setRequestHeader("Content-Type", "application/json");
|
2025-06-16 00:24:53 -04:00
|
|
|
xhr.send();
|
|
|
|
const jsonData = JSON.parse(xhr.responseText);
|
|
|
|
if (jsonData.data?.session_id) {
|
2025-06-09 12:01:39 -04:00
|
|
|
this.session_id = jsonData.data.session_id;
|
2025-07-21 11:41:49 +07:00
|
|
|
console.log("Session opened:", this.session_id);
|
2025-06-09 12:01:39 -04:00
|
|
|
} else {
|
2025-07-21 11:41:49 +07:00
|
|
|
console.error("Failed to open session:", jsonData.message);
|
|
|
|
throw new Error("Failed to open session");
|
2025-06-01 12:40:24 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-07-21 11:41:49 +07:00
|
|
|
getChallenge(path, body) {
|
|
|
|
const url = `${this.host}${path}/get_license_challenge`;
|
2025-06-16 00:24:53 -04:00
|
|
|
const xhr = new XMLHttpRequest();
|
2025-07-20 12:18:15 +07:00
|
|
|
xhr.open("POST", url, false);
|
|
|
|
xhr.setRequestHeader("Content-Type", "application/json");
|
2025-06-16 00:24:53 -04:00
|
|
|
xhr.send(JSON.stringify(body));
|
|
|
|
const jsonData = JSON.parse(xhr.responseText);
|
|
|
|
if (jsonData.data?.challenge) {
|
2025-06-15 18:25:54 -04:00
|
|
|
this.challenge = btoa(jsonData.data.challenge);
|
2025-07-21 11:41:49 +07:00
|
|
|
console.log("Challenge received:", this.challenge);
|
|
|
|
} else if (jsonData.data?.challenge_b64) {
|
|
|
|
this.challenge = jsonData.data.challenge_b64;
|
|
|
|
console.log("Challenge received:", this.challenge);
|
2025-06-09 12:01:39 -04:00
|
|
|
} else {
|
2025-07-21 11:41:49 +07:00
|
|
|
console.error("Failed to get challenge:", jsonData.message);
|
|
|
|
throw new Error("Failed to get challenge");
|
2025-06-01 12:40:24 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-07-21 11:41:49 +07:00
|
|
|
parseLicense(path, body) {
|
|
|
|
const url = `${this.host}${path}/parse_license`;
|
2025-06-16 00:24:53 -04:00
|
|
|
const xhr = new XMLHttpRequest();
|
2025-07-20 12:18:15 +07:00
|
|
|
xhr.open("POST", url, false);
|
|
|
|
xhr.setRequestHeader("Content-Type", "application/json");
|
2025-06-16 00:24:53 -04:00
|
|
|
xhr.send(JSON.stringify(body));
|
|
|
|
const jsonData = JSON.parse(xhr.responseText);
|
2025-07-21 11:41:49 +07:00
|
|
|
if (jsonData.status === 200 || jsonData.message?.includes("parsed and loaded")) {
|
|
|
|
console.log("License response parsed successfully");
|
2025-06-09 12:01:39 -04:00
|
|
|
return true;
|
|
|
|
} else {
|
2025-07-21 11:41:49 +07:00
|
|
|
console.error("Failed to parse license response:", jsonData.message);
|
|
|
|
throw new Error("Failed to parse license response");
|
2025-06-01 12:40:24 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-07-21 11:41:49 +07:00
|
|
|
getKeys(path, body, extraPath = "") {
|
|
|
|
const url = `${this.host}${path}/get_keys${extraPath}`;
|
2025-06-16 00:24:53 -04:00
|
|
|
const xhr = new XMLHttpRequest();
|
2025-07-20 12:18:15 +07:00
|
|
|
xhr.open("POST", url, false);
|
|
|
|
xhr.setRequestHeader("Content-Type", "application/json");
|
2025-06-16 00:24:53 -04:00
|
|
|
xhr.send(JSON.stringify(body));
|
|
|
|
const jsonData = JSON.parse(xhr.responseText);
|
|
|
|
if (jsonData.data?.keys) {
|
2025-06-09 12:01:39 -04:00
|
|
|
this.keys = jsonData.data.keys;
|
2025-07-21 11:41:49 +07:00
|
|
|
console.log("Keys received:", this.keys);
|
2025-06-09 12:01:39 -04:00
|
|
|
} else {
|
2025-07-21 11:41:49 +07:00
|
|
|
console.error("Failed to get keys:", jsonData.message);
|
|
|
|
throw new Error("Failed to get keys");
|
2025-06-09 12:01:39 -04:00
|
|
|
}
|
|
|
|
}
|
2025-06-01 12:40:24 -04:00
|
|
|
|
2025-07-21 11:41:49 +07:00
|
|
|
closeSession(path) {
|
|
|
|
const url = `${this.host}${path}/close/${this.session_id}`;
|
2025-06-16 00:24:53 -04:00
|
|
|
const xhr = new XMLHttpRequest();
|
2025-07-20 12:18:15 +07:00
|
|
|
xhr.open("GET", url, false);
|
|
|
|
xhr.setRequestHeader("Content-Type", "application/json");
|
2025-06-16 00:24:53 -04:00
|
|
|
xhr.send();
|
|
|
|
const jsonData = JSON.parse(xhr.responseText);
|
|
|
|
if (jsonData) {
|
2025-07-21 11:41:49 +07:00
|
|
|
console.log("Session closed successfully");
|
2025-06-09 12:01:39 -04:00
|
|
|
} else {
|
2025-07-21 11:41:49 +07:00
|
|
|
console.error("Failed to close session:", jsonData.message);
|
|
|
|
throw new Error("Failed to close session");
|
2025-06-01 12:40:24 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-07-21 11:41:49 +07:00
|
|
|
// PlayReady Remote CDM Class
|
|
|
|
class remotePlayReadyCDM extends RemoteCDMBase {
|
|
|
|
constructor(security_level, host, secret, device_name) {
|
|
|
|
super({ host, secret, device_name, security_level });
|
|
|
|
}
|
|
|
|
|
|
|
|
openSession() {
|
|
|
|
super.openSession(`/remotecdm/playready/${this.device_name}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
getChallenge(init_data) {
|
|
|
|
super.getChallenge(`/remotecdm/playready/${this.device_name}`, {
|
|
|
|
session_id: this.session_id,
|
|
|
|
init_data: init_data,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
parseLicense(license_message) {
|
|
|
|
return super.parseLicense(`/remotecdm/playready/${this.device_name}`, {
|
|
|
|
session_id: this.session_id,
|
|
|
|
license_message: license_message,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
getKeys() {
|
|
|
|
super.getKeys(`/remotecdm/playready/${this.device_name}`, {
|
|
|
|
session_id: this.session_id,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
closeSession() {
|
|
|
|
super.closeSession(`/remotecdm/playready/${this.device_name}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-06-09 12:01:39 -04:00
|
|
|
// Widevine Remote CDM Class
|
2025-07-21 11:41:49 +07:00
|
|
|
class remoteWidevineCDM extends RemoteCDMBase {
|
2025-07-20 12:18:15 +07:00
|
|
|
constructor(device_type, system_id, security_level, host, secret, device_name) {
|
2025-07-21 11:41:49 +07:00
|
|
|
super({ host, secret, device_name, security_level });
|
2025-07-20 12:18:15 +07:00
|
|
|
this.device_type = device_type;
|
|
|
|
this.system_id = system_id;
|
|
|
|
}
|
2025-06-01 12:40:24 -04:00
|
|
|
|
2025-07-20 12:18:15 +07:00
|
|
|
openSession() {
|
2025-07-21 11:41:49 +07:00
|
|
|
super.openSession(`/remotecdm/widevine/${this.device_name}`);
|
2025-06-01 12:40:24 -04:00
|
|
|
}
|
|
|
|
|
2025-06-16 00:24:53 -04:00
|
|
|
setServiceCertificate(certificate) {
|
2025-06-01 12:40:24 -04:00
|
|
|
const url = `${this.host}/remotecdm/widevine/${this.device_name}/set_service_certificate`;
|
2025-06-16 00:24:53 -04:00
|
|
|
const xhr = new XMLHttpRequest();
|
2025-07-20 12:18:15 +07:00
|
|
|
xhr.open("POST", url, false);
|
|
|
|
xhr.setRequestHeader("Content-Type", "application/json");
|
2025-06-01 12:40:24 -04:00
|
|
|
const body = {
|
|
|
|
session_id: this.session_id,
|
2025-07-20 12:18:15 +07:00
|
|
|
certificate: certificate ?? null,
|
|
|
|
};
|
2025-06-16 00:24:53 -04:00
|
|
|
xhr.send(JSON.stringify(body));
|
|
|
|
const jsonData = JSON.parse(xhr.responseText);
|
|
|
|
if (jsonData.status === 200) {
|
2025-06-09 12:01:39 -04:00
|
|
|
console.log("Service certificate set successfully");
|
|
|
|
} else {
|
|
|
|
console.error("Failed to set service certificate:", jsonData.message);
|
|
|
|
throw new Error("Failed to set service certificate");
|
2025-06-01 12:40:24 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-07-20 12:18:15 +07:00
|
|
|
getChallenge(init_data, license_type = "STREAMING") {
|
2025-06-01 12:40:24 -04:00
|
|
|
const url = `${this.host}/remotecdm/widevine/${this.device_name}/get_license_challenge/${license_type}`;
|
2025-06-16 00:24:53 -04:00
|
|
|
const xhr = new XMLHttpRequest();
|
2025-07-20 12:18:15 +07:00
|
|
|
xhr.open("POST", url, false);
|
|
|
|
xhr.setRequestHeader("Content-Type", "application/json");
|
2025-06-01 12:40:24 -04:00
|
|
|
const body = {
|
|
|
|
session_id: this.session_id,
|
|
|
|
init_data: init_data,
|
2025-07-20 12:18:15 +07:00
|
|
|
privacy_mode: serviceCertFound,
|
2025-06-01 12:40:24 -04:00
|
|
|
};
|
2025-06-16 00:24:53 -04:00
|
|
|
xhr.send(JSON.stringify(body));
|
|
|
|
const jsonData = JSON.parse(xhr.responseText);
|
|
|
|
if (jsonData.data?.challenge_b64) {
|
2025-06-09 12:01:39 -04:00
|
|
|
this.challenge = jsonData.data.challenge_b64;
|
|
|
|
console.log("Widevine challenge received:", this.challenge);
|
|
|
|
} else {
|
|
|
|
console.error("Failed to get Widevine challenge:", jsonData.message);
|
|
|
|
throw new Error("Failed to get Widevine challenge");
|
2025-06-01 12:40:24 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-06-16 00:24:53 -04:00
|
|
|
parseLicense(license_message) {
|
2025-07-21 11:41:49 +07:00
|
|
|
return super.parseLicense(`/remotecdm/widevine/${this.device_name}`, {
|
2025-06-01 12:40:24 -04:00
|
|
|
session_id: this.session_id,
|
2025-07-20 12:18:15 +07:00
|
|
|
license_message: license_message,
|
2025-07-21 11:41:49 +07:00
|
|
|
});
|
2025-06-01 12:40:24 -04:00
|
|
|
}
|
|
|
|
|
2025-06-16 00:24:53 -04:00
|
|
|
getKeys() {
|
2025-07-21 11:41:49 +07:00
|
|
|
super.getKeys(
|
|
|
|
`/remotecdm/widevine/${this.device_name}`,
|
|
|
|
{
|
|
|
|
session_id: this.session_id,
|
|
|
|
},
|
|
|
|
"/ALL"
|
|
|
|
);
|
2025-06-09 12:01:39 -04:00
|
|
|
}
|
2025-06-01 12:40:24 -04:00
|
|
|
|
2025-06-16 00:24:53 -04:00
|
|
|
closeSession() {
|
2025-07-21 11:41:49 +07:00
|
|
|
super.closeSession(`/remotecdm/widevine/${this.device_name}`);
|
2025-06-01 12:40:24 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-06-09 12:01:39 -04:00
|
|
|
// Utility functions
|
2025-06-26 21:45:26 -04:00
|
|
|
function hexStrToU8(hexString) {
|
2025-07-20 12:18:15 +07:00
|
|
|
return Uint8Array.from(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
|
2025-06-26 21:45:26 -04:00
|
|
|
}
|
2025-06-01 12:40:24 -04:00
|
|
|
|
2025-06-26 21:45:26 -04:00
|
|
|
function u8ToHexStr(bytes) {
|
2025-07-20 12:18:15 +07:00
|
|
|
return bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), "");
|
2025-06-26 21:45:26 -04:00
|
|
|
}
|
2025-06-01 12:40:24 -04:00
|
|
|
|
2025-06-26 21:45:26 -04:00
|
|
|
function b64ToHexStr(b64) {
|
2025-07-20 12:18:15 +07:00
|
|
|
return [...atob(b64)].map((c) => c.charCodeAt(0).toString(16).padStart(2, "0")).join``;
|
2025-06-26 21:45:26 -04:00
|
|
|
}
|
2025-06-01 12:40:24 -04:00
|
|
|
|
2025-07-21 11:54:40 +07:00
|
|
|
function jsonContainsValue(obj, prefix = DRM_SIGNATURES.WIDEVINE) {
|
2025-06-19 22:42:40 -04:00
|
|
|
if (typeof obj === "string") return obj.startsWith(prefix);
|
2025-07-20 12:18:15 +07:00
|
|
|
if (Array.isArray(obj)) return obj.some((val) => jsonContainsValue(val, prefix));
|
2025-06-01 12:40:24 -04:00
|
|
|
if (typeof obj === "object" && obj !== null) {
|
2025-07-20 12:18:15 +07:00
|
|
|
return Object.values(obj).some((val) => jsonContainsValue(val, prefix));
|
2025-06-01 12:40:24 -04:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2025-06-29 12:07:18 -04:00
|
|
|
function jsonReplaceValue(obj, newValue) {
|
2025-06-01 12:40:24 -04:00
|
|
|
if (typeof obj === "string") {
|
2025-07-21 11:54:40 +07:00
|
|
|
return obj.startsWith(DRM_SIGNATURES.WIDEVINE) || obj.startsWith(DRM_SIGNATURES.PLAYREADY)
|
|
|
|
? newValue
|
|
|
|
: obj;
|
2025-06-01 12:40:24 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (Array.isArray(obj)) {
|
2025-07-20 12:18:15 +07:00
|
|
|
return obj.map((item) => jsonReplaceValue(item, newValue));
|
2025-06-01 12:40:24 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof obj === "object" && obj !== null) {
|
|
|
|
const newObj = {};
|
|
|
|
for (const key in obj) {
|
|
|
|
if (Object.hasOwn(obj, key)) {
|
2025-06-29 12:07:18 -04:00
|
|
|
newObj[key] = jsonReplaceValue(obj[key], newValue);
|
2025-06-01 12:40:24 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return newObj;
|
|
|
|
}
|
|
|
|
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
2025-06-26 21:45:26 -04:00
|
|
|
function isJson(str) {
|
2025-06-01 12:40:24 -04:00
|
|
|
try {
|
|
|
|
JSON.parse(str);
|
|
|
|
return true;
|
|
|
|
} catch (e) {
|
|
|
|
return false;
|
|
|
|
}
|
2025-06-26 21:45:26 -04:00
|
|
|
}
|
2025-06-01 12:40:24 -04:00
|
|
|
|
|
|
|
function getWidevinePssh(buffer) {
|
|
|
|
const hex = u8ToHexStr(new Uint8Array(buffer));
|
|
|
|
const match = hex.match(/000000(..)?70737368.*/);
|
|
|
|
if (!match) return null;
|
|
|
|
|
|
|
|
const boxHex = match[0];
|
|
|
|
const bytes = hexStrToU8(boxHex);
|
|
|
|
return window.btoa(String.fromCharCode(...bytes));
|
|
|
|
}
|
|
|
|
|
|
|
|
function getPlayReadyPssh(buffer) {
|
|
|
|
const u8 = new Uint8Array(buffer);
|
|
|
|
const systemId = "9a04f07998404286ab92e65be0885f95";
|
|
|
|
const hex = u8ToHexStr(u8);
|
|
|
|
const index = hex.indexOf(systemId);
|
|
|
|
if (index === -1) return null;
|
|
|
|
const psshBoxStart = hex.lastIndexOf("70737368", index);
|
|
|
|
if (psshBoxStart === -1) return null;
|
|
|
|
const lenStart = psshBoxStart - 8;
|
|
|
|
const boxLen = parseInt(hex.substr(lenStart, 8), 16) * 2;
|
|
|
|
const psshHex = hex.substr(lenStart, boxLen);
|
|
|
|
const psshBytes = hexStrToU8(psshHex);
|
|
|
|
return window.btoa(String.fromCharCode(...psshBytes));
|
|
|
|
}
|
|
|
|
|
|
|
|
function getClearkey(response) {
|
2025-07-20 12:18:15 +07:00
|
|
|
let obj = JSON.parse(new TextDecoder("utf-8").decode(response));
|
|
|
|
return obj["keys"].map((o) => ({
|
|
|
|
key_id: b64ToHexStr(o["kid"].replace(/-/g, "+").replace(/_/g, "/")),
|
|
|
|
key: b64ToHexStr(o["k"].replace(/-/g, "+").replace(/_/g, "/")),
|
2025-06-26 21:45:26 -04:00
|
|
|
}));
|
2025-06-01 12:40:24 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
function base64ToUint8Array(base64) {
|
2025-06-09 12:01:39 -04:00
|
|
|
const binaryStr = atob(base64);
|
2025-06-01 12:40:24 -04:00
|
|
|
const len = binaryStr.length;
|
|
|
|
const bytes = new Uint8Array(len);
|
|
|
|
for (let i = 0; i < len; i++) {
|
|
|
|
bytes[i] = binaryStr.charCodeAt(i);
|
|
|
|
}
|
|
|
|
return bytes;
|
|
|
|
}
|
|
|
|
|
|
|
|
function arrayBufferToBase64(uint8array) {
|
2025-07-20 12:18:15 +07:00
|
|
|
let binary = "";
|
2025-06-26 21:45:26 -04:00
|
|
|
const len = uint8array.length;
|
2025-06-01 12:40:24 -04:00
|
|
|
|
2025-06-26 21:45:26 -04:00
|
|
|
for (let i = 0; i < len; i++) {
|
|
|
|
binary += String.fromCharCode(uint8array[i]);
|
|
|
|
}
|
2025-06-01 12:40:24 -04:00
|
|
|
|
2025-06-26 21:45:26 -04:00
|
|
|
return window.btoa(binary);
|
2025-06-16 00:24:53 -04:00
|
|
|
}
|
|
|
|
|
2025-06-16 01:21:17 -04:00
|
|
|
// Challenge generator interceptor
|
2025-06-16 00:24:53 -04:00
|
|
|
const originalGenerateRequest = MediaKeySession.prototype.generateRequest;
|
2025-07-20 12:18:15 +07:00
|
|
|
MediaKeySession.prototype.generateRequest = function (initDataType, initData) {
|
2025-06-29 02:50:36 -04:00
|
|
|
const session = this;
|
|
|
|
let playReadyPssh = getPlayReadyPssh(initData);
|
|
|
|
if (playReadyPssh) {
|
|
|
|
console.log("[DRM Detected] PlayReady");
|
|
|
|
foundPlayreadyPssh = playReadyPssh;
|
2025-07-20 12:18:15 +07:00
|
|
|
console.log("[PlayReady PSSH found] " + playReadyPssh);
|
2025-06-29 02:50:36 -04:00
|
|
|
}
|
2025-07-20 12:18:15 +07:00
|
|
|
let wideVinePssh = getWidevinePssh(initData);
|
2025-06-29 02:50:36 -04:00
|
|
|
if (wideVinePssh) {
|
|
|
|
// Widevine code
|
|
|
|
console.log("[DRM Detected] Widevine");
|
|
|
|
foundWidevinePssh = wideVinePssh;
|
2025-07-20 12:18:15 +07:00
|
|
|
console.log("[Widevine PSSH found] " + wideVinePssh);
|
2025-06-29 02:50:36 -04:00
|
|
|
}
|
|
|
|
// Challenge message interceptor
|
|
|
|
if (!remoteListenerMounted) {
|
|
|
|
remoteListenerMounted = true;
|
|
|
|
session.addEventListener("message", function messageInterceptor(event) {
|
|
|
|
event.stopImmediatePropagation();
|
|
|
|
const uint8Array = new Uint8Array(event.message);
|
|
|
|
const base64challenge = arrayBufferToBase64(uint8Array);
|
2025-07-21 11:54:40 +07:00
|
|
|
if (
|
|
|
|
base64challenge === DRM_SIGNATURES.WIDEVINE_INIT &&
|
|
|
|
interceptType !== "DISABLED" &&
|
|
|
|
!serviceCertFound
|
|
|
|
) {
|
2025-07-20 12:18:15 +07:00
|
|
|
const { device_type, system_id, security_level, host, secret, device_name } =
|
|
|
|
widevineDeviceInfo;
|
|
|
|
remoteCDM = new remoteWidevineCDM(
|
|
|
|
device_type,
|
|
|
|
system_id,
|
|
|
|
security_level,
|
|
|
|
host,
|
|
|
|
secret,
|
|
|
|
device_name
|
|
|
|
);
|
2025-06-29 02:50:36 -04:00
|
|
|
remoteCDM.openSession();
|
|
|
|
}
|
2025-07-21 11:54:40 +07:00
|
|
|
if (
|
|
|
|
!injectionSuccess &&
|
|
|
|
base64challenge !== DRM_SIGNATURES.WIDEVINE_INIT &&
|
|
|
|
interceptType !== "DISABLED"
|
|
|
|
) {
|
2025-06-29 02:50:36 -04:00
|
|
|
if (interceptType === "EME") {
|
|
|
|
injectionSuccess = true;
|
2025-06-16 00:24:53 -04:00
|
|
|
}
|
2025-06-29 02:50:36 -04:00
|
|
|
if (!originalChallenge) {
|
|
|
|
originalChallenge = base64challenge;
|
|
|
|
}
|
2025-07-21 11:54:40 +07:00
|
|
|
if (originalChallenge.startsWith(DRM_SIGNATURES.WIDEVINE)) {
|
2025-06-29 02:50:36 -04:00
|
|
|
window.postMessage({ type: "__DRM_TYPE__", data: "Widevine" }, "*");
|
|
|
|
window.postMessage({ type: "__PSSH_DATA__", data: foundWidevinePssh }, "*");
|
|
|
|
if (interceptType === "EME" && !remoteCDM) {
|
|
|
|
const {
|
2025-07-20 12:18:15 +07:00
|
|
|
device_type,
|
|
|
|
system_id,
|
|
|
|
security_level,
|
|
|
|
host,
|
|
|
|
secret,
|
|
|
|
device_name,
|
2025-06-29 02:50:36 -04:00
|
|
|
} = widevineDeviceInfo;
|
2025-07-20 12:18:15 +07:00
|
|
|
remoteCDM = new remoteWidevineCDM(
|
|
|
|
device_type,
|
|
|
|
system_id,
|
|
|
|
security_level,
|
|
|
|
host,
|
|
|
|
secret,
|
|
|
|
device_name
|
|
|
|
);
|
2025-06-29 02:50:36 -04:00
|
|
|
remoteCDM.openSession();
|
|
|
|
remoteCDM.getChallenge(foundWidevinePssh);
|
2025-07-20 12:18:15 +07:00
|
|
|
}
|
|
|
|
}
|
2025-07-21 11:54:40 +07:00
|
|
|
if (!originalChallenge.startsWith(DRM_SIGNATURES.WIDEVINE)) {
|
2025-06-29 02:50:36 -04:00
|
|
|
const buffer = event.message;
|
2025-07-20 12:18:15 +07:00
|
|
|
const decoder = new TextDecoder("utf-16");
|
2025-06-29 02:50:36 -04:00
|
|
|
const decodedText = decoder.decode(buffer);
|
2025-07-20 12:18:15 +07:00
|
|
|
const match = decodedText.match(
|
|
|
|
/<Challenge encoding="base64encoded">([^<]+)<\/Challenge>/
|
|
|
|
);
|
2025-06-29 02:50:36 -04:00
|
|
|
if (match) {
|
|
|
|
window.postMessage({ type: "__DRM_TYPE__", data: "PlayReady" }, "*");
|
2025-07-20 12:18:15 +07:00
|
|
|
window.postMessage(
|
|
|
|
{ type: "__PSSH_DATA__", data: foundPlayreadyPssh },
|
|
|
|
"*"
|
|
|
|
);
|
2025-06-29 02:50:36 -04:00
|
|
|
originalChallenge = match[1];
|
2025-07-20 12:18:15 +07:00
|
|
|
if (interceptType === "EME" && !remoteCDM) {
|
|
|
|
const { security_level, host, secret, device_name } =
|
|
|
|
playreadyDeviceInfo;
|
|
|
|
remoteCDM = new remotePlayReadyCDM(
|
|
|
|
security_level,
|
|
|
|
host,
|
|
|
|
secret,
|
|
|
|
device_name
|
|
|
|
);
|
2025-06-26 21:45:26 -04:00
|
|
|
remoteCDM.openSession();
|
2025-06-29 02:50:36 -04:00
|
|
|
remoteCDM.getChallenge(foundPlayreadyPssh);
|
|
|
|
}
|
2025-07-20 12:18:15 +07:00
|
|
|
}
|
|
|
|
}
|
2025-06-29 02:50:36 -04:00
|
|
|
if (interceptType === "EME" && remoteCDM) {
|
|
|
|
const uint8challenge = base64ToUint8Array(remoteCDM.challenge);
|
|
|
|
const challengeBuffer = uint8challenge.buffer;
|
|
|
|
const syntheticEvent = new MessageEvent("message", {
|
|
|
|
data: event.data,
|
|
|
|
origin: event.origin,
|
|
|
|
lastEventId: event.lastEventId,
|
|
|
|
source: event.source,
|
2025-07-20 12:18:15 +07:00
|
|
|
ports: event.ports,
|
2025-06-29 02:50:36 -04:00
|
|
|
});
|
|
|
|
Object.defineProperty(syntheticEvent, "message", {
|
2025-07-20 12:18:15 +07:00
|
|
|
get: () => challengeBuffer,
|
2025-06-29 02:50:36 -04:00
|
|
|
});
|
2025-07-20 12:18:15 +07:00
|
|
|
console.log("Intercepted EME Challenge and injected custom one.");
|
2025-06-29 02:50:36 -04:00
|
|
|
session.dispatchEvent(syntheticEvent);
|
2025-06-16 00:24:53 -04:00
|
|
|
}
|
2025-06-29 02:50:36 -04:00
|
|
|
}
|
2025-07-20 12:18:15 +07:00
|
|
|
});
|
2025-06-29 02:50:36 -04:00
|
|
|
console.log("Message interceptor mounted.");
|
2025-06-16 01:21:17 -04:00
|
|
|
}
|
2025-07-20 12:18:15 +07:00
|
|
|
return originalGenerateRequest.call(session, initDataType, initData);
|
2025-06-16 01:21:17 -04:00
|
|
|
};
|
2025-06-16 00:24:53 -04:00
|
|
|
|
|
|
|
// Message update interceptors
|
|
|
|
const originalUpdate = MediaKeySession.prototype.update;
|
2025-07-20 12:18:15 +07:00
|
|
|
MediaKeySession.prototype.update = function (response) {
|
2025-06-16 00:24:53 -04:00
|
|
|
const uint8 = response instanceof Uint8Array ? response : new Uint8Array(response);
|
|
|
|
const base64Response = window.btoa(String.fromCharCode(...uint8));
|
2025-07-21 11:54:40 +07:00
|
|
|
if (
|
|
|
|
base64Response.startsWith(DRM_SIGNATURES.SERVICE_CERT) &&
|
|
|
|
foundWidevinePssh &&
|
|
|
|
remoteCDM &&
|
|
|
|
!serviceCertFound
|
|
|
|
) {
|
2025-06-16 00:24:53 -04:00
|
|
|
remoteCDM.setServiceCertificate(base64Response);
|
2025-06-29 12:07:18 -04:00
|
|
|
if (interceptType === "EME" && !remoteCDM.challenge) {
|
2025-06-26 21:45:26 -04:00
|
|
|
remoteCDM.getChallenge(foundWidevinePssh);
|
|
|
|
}
|
|
|
|
window.postMessage({ type: "__DRM_TYPE__", data: "Widevine" }, "*");
|
|
|
|
window.postMessage({ type: "__PSSH_DATA__", data: foundWidevinePssh }, "*");
|
2025-06-29 12:07:18 -04:00
|
|
|
serviceCertFound = true;
|
2025-06-16 00:24:53 -04:00
|
|
|
}
|
2025-07-20 12:18:15 +07:00
|
|
|
if (
|
2025-07-21 11:54:40 +07:00
|
|
|
!base64Response.startsWith(DRM_SIGNATURES.SERVICE_CERT) &&
|
2025-07-20 12:18:15 +07:00
|
|
|
(foundWidevinePssh || foundPlayreadyPssh) &&
|
|
|
|
!keysRetrieved
|
|
|
|
) {
|
2025-06-16 01:59:26 -04:00
|
|
|
if (licenseResponseCounter === 1 || foundChallengeInBody) {
|
2025-06-16 00:24:53 -04:00
|
|
|
remoteCDM.parseLicense(base64Response);
|
|
|
|
remoteCDM.getKeys();
|
|
|
|
remoteCDM.closeSession();
|
2025-06-29 12:07:18 -04:00
|
|
|
keysRetrieved = true;
|
2025-06-16 00:24:53 -04:00
|
|
|
window.postMessage({ type: "__KEYS_DATA__", data: remoteCDM.keys }, "*");
|
|
|
|
}
|
|
|
|
licenseResponseCounter++;
|
|
|
|
}
|
|
|
|
const updatePromise = originalUpdate.call(this, response);
|
2025-06-26 21:45:26 -04:00
|
|
|
if (!foundPlayreadyPssh && !foundWidevinePssh) {
|
2025-06-16 00:24:53 -04:00
|
|
|
updatePromise
|
|
|
|
.then(() => {
|
|
|
|
let clearKeys = getClearkey(response);
|
|
|
|
if (clearKeys && clearKeys.length > 0) {
|
2025-07-20 12:18:15 +07:00
|
|
|
console.log("[CLEARKEY] ", clearKeys);
|
|
|
|
const drmType = {
|
|
|
|
type: "__DRM_TYPE__",
|
|
|
|
data: "ClearKey",
|
|
|
|
};
|
|
|
|
window.postMessage(drmType, "*");
|
|
|
|
const keysData = {
|
|
|
|
type: "__KEYS_DATA__",
|
|
|
|
data: clearKeys,
|
|
|
|
};
|
|
|
|
window.postMessage(keysData, "*");
|
2025-06-16 00:24:53 -04:00
|
|
|
}
|
|
|
|
})
|
2025-07-20 12:18:15 +07:00
|
|
|
.catch((e) => {
|
2025-06-16 00:24:53 -04:00
|
|
|
console.log("[CLEARKEY] Not found");
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return updatePromise;
|
2025-06-16 01:21:17 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
// fetch POST interceptor
|
2025-07-20 12:18:15 +07:00
|
|
|
(function () {
|
|
|
|
const originalFetch = window.fetch;
|
|
|
|
|
|
|
|
window.fetch = async function (resource, config = {}) {
|
|
|
|
const method = (config.method || "GET").toUpperCase();
|
|
|
|
|
|
|
|
if (method === "POST") {
|
|
|
|
let body = config.body;
|
|
|
|
if (body) {
|
|
|
|
if (body instanceof ArrayBuffer || body instanceof Uint8Array) {
|
|
|
|
const buffer = body instanceof Uint8Array ? body : new Uint8Array(body);
|
|
|
|
const base64Body = window.btoa(String.fromCharCode(...buffer));
|
|
|
|
if (
|
2025-07-21 11:54:40 +07:00
|
|
|
(base64Body.startsWith(DRM_SIGNATURES.WIDEVINE) ||
|
|
|
|
base64Body.startsWith(DRM_SIGNATURES.PLAYREADY)) &&
|
2025-07-20 12:18:15 +07:00
|
|
|
(!remoteCDM ||
|
|
|
|
remoteCDM.challenge === null ||
|
|
|
|
base64Body !== remoteCDM.challenge) &&
|
|
|
|
interceptType === "EME"
|
|
|
|
) {
|
|
|
|
foundChallengeInBody = true;
|
|
|
|
window.postMessage({ type: "__LICENSE_URL__", data: resource }, "*");
|
|
|
|
// Block the request
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (
|
2025-07-21 11:54:40 +07:00
|
|
|
(base64Body.startsWith(DRM_SIGNATURES.WIDEVINE) ||
|
|
|
|
base64Body.startsWith(DRM_SIGNATURES.PLAYREADY)) &&
|
2025-07-20 12:18:15 +07:00
|
|
|
interceptType == "LICENSE" &&
|
|
|
|
!foundChallengeInBody
|
|
|
|
) {
|
|
|
|
foundChallengeInBody = true;
|
|
|
|
window.postMessage({ type: "__LICENSE_URL__", data: resource }, "*");
|
|
|
|
if (!remoteCDM) {
|
2025-07-21 11:54:40 +07:00
|
|
|
if (base64Body.startsWith(DRM_SIGNATURES.WIDEVINE)) {
|
2025-07-20 12:18:15 +07:00
|
|
|
const {
|
|
|
|
device_type,
|
|
|
|
system_id,
|
|
|
|
security_level,
|
|
|
|
host,
|
|
|
|
secret,
|
|
|
|
device_name,
|
|
|
|
} = widevineDeviceInfo;
|
|
|
|
remoteCDM = new remoteWidevineCDM(
|
|
|
|
device_type,
|
|
|
|
system_id,
|
|
|
|
security_level,
|
|
|
|
host,
|
|
|
|
secret,
|
|
|
|
device_name
|
|
|
|
);
|
|
|
|
remoteCDM.openSession();
|
|
|
|
remoteCDM.getChallenge(foundWidevinePssh);
|
|
|
|
}
|
2025-07-21 11:54:40 +07:00
|
|
|
if (base64Body.startsWith(DRM_SIGNATURES.PLAYREADY)) {
|
2025-07-20 12:18:15 +07:00
|
|
|
const { security_level, host, secret, device_name } =
|
|
|
|
playreadyDeviceInfo;
|
|
|
|
remoteCDM = new remotePlayReadyCDM(
|
|
|
|
security_level,
|
|
|
|
host,
|
|
|
|
secret,
|
|
|
|
device_name
|
|
|
|
);
|
|
|
|
remoteCDM.openSession();
|
|
|
|
remoteCDM.getChallenge(foundPlayreadyPssh);
|
|
|
|
}
|
2025-06-29 12:07:18 -04:00
|
|
|
}
|
2025-07-20 12:18:15 +07:00
|
|
|
if (remoteCDM && remoteCDM.challenge === null) {
|
|
|
|
remoteCDM.getChallenge(foundWidevinePssh);
|
2025-06-29 12:07:18 -04:00
|
|
|
}
|
2025-07-20 12:18:15 +07:00
|
|
|
const injectedBody = base64ToUint8Array(remoteCDM.challenge);
|
|
|
|
config.body = injectedBody;
|
|
|
|
return originalFetch(resource, config);
|
2025-06-29 12:07:18 -04:00
|
|
|
}
|
|
|
|
}
|
2025-07-20 12:18:15 +07:00
|
|
|
if (typeof body === "string" && !isJson(body)) {
|
|
|
|
const base64EncodedBody = btoa(body);
|
|
|
|
if (
|
2025-07-21 11:54:40 +07:00
|
|
|
(base64EncodedBody.startsWith(DRM_SIGNATURES.WIDEVINE) ||
|
|
|
|
base64EncodedBody.startsWith(DRM_SIGNATURES.PLAYREADY)) &&
|
2025-07-20 12:18:15 +07:00
|
|
|
(!remoteCDM ||
|
|
|
|
remoteCDM.challenge === null ||
|
|
|
|
base64EncodedBody !== remoteCDM.challenge) &&
|
|
|
|
interceptType === "EME"
|
|
|
|
) {
|
|
|
|
foundChallengeInBody = true;
|
|
|
|
window.postMessage({ type: "__LICENSE_URL__", data: resource }, "*");
|
|
|
|
// Block the request
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (
|
2025-07-21 11:54:40 +07:00
|
|
|
(base64EncodedBody.startsWith(DRM_SIGNATURES.WIDEVINE) ||
|
|
|
|
base64EncodedBody.startsWith(DRM_SIGNATURES.PLAYREADY)) &&
|
2025-07-20 12:18:15 +07:00
|
|
|
interceptType == "LICENSE" &&
|
|
|
|
!foundChallengeInBody
|
|
|
|
) {
|
|
|
|
foundChallengeInBody = true;
|
|
|
|
window.postMessage({ type: "__LICENSE_URL__", data: resource }, "*");
|
|
|
|
if (!remoteCDM) {
|
2025-07-21 11:54:40 +07:00
|
|
|
if (base64EncodedBody.startsWith(DRM_SIGNATURES.WIDEVINE)) {
|
2025-07-20 12:18:15 +07:00
|
|
|
const {
|
|
|
|
device_type,
|
|
|
|
system_id,
|
|
|
|
security_level,
|
|
|
|
host,
|
|
|
|
secret,
|
|
|
|
device_name,
|
|
|
|
} = widevineDeviceInfo;
|
|
|
|
remoteCDM = new remoteWidevineCDM(
|
|
|
|
device_type,
|
|
|
|
system_id,
|
|
|
|
security_level,
|
|
|
|
host,
|
|
|
|
secret,
|
|
|
|
device_name
|
|
|
|
);
|
|
|
|
remoteCDM.openSession();
|
|
|
|
remoteCDM.getChallenge(foundWidevinePssh);
|
|
|
|
}
|
2025-07-21 11:54:40 +07:00
|
|
|
if (base64EncodedBody.startsWith(DRM_SIGNATURES.PLAYREADY)) {
|
2025-07-20 12:18:15 +07:00
|
|
|
const { security_level, host, secret, device_name } =
|
|
|
|
playreadyDeviceInfo;
|
|
|
|
remoteCDM = new remotePlayReadyCDM(
|
|
|
|
security_level,
|
|
|
|
host,
|
|
|
|
secret,
|
|
|
|
device_name
|
|
|
|
);
|
|
|
|
remoteCDM.openSession();
|
|
|
|
remoteCDM.getChallenge(foundPlayreadyPssh);
|
|
|
|
}
|
2025-06-29 12:07:18 -04:00
|
|
|
}
|
2025-07-20 12:18:15 +07:00
|
|
|
if (remoteCDM && remoteCDM.challenge === null) {
|
|
|
|
remoteCDM.getChallenge(foundWidevinePssh);
|
2025-06-29 12:07:18 -04:00
|
|
|
}
|
2025-07-20 12:18:15 +07:00
|
|
|
const injectedBody = atob(remoteCDM.challenge);
|
|
|
|
config.body = injectedBody;
|
|
|
|
return originalFetch(resource, config);
|
2025-06-29 12:07:18 -04:00
|
|
|
}
|
|
|
|
}
|
2025-07-20 12:18:15 +07:00
|
|
|
if (typeof body === "string" && isJson(body)) {
|
|
|
|
const jsonBody = JSON.parse(body);
|
2025-06-29 12:07:18 -04:00
|
|
|
|
2025-07-20 12:18:15 +07:00
|
|
|
if (
|
2025-07-21 11:54:40 +07:00
|
|
|
(jsonContainsValue(jsonBody, DRM_SIGNATURES.WIDEVINE) ||
|
|
|
|
jsonContainsValue(jsonBody, DRM_SIGNATURES.PLAYREADY)) &&
|
2025-07-20 12:18:15 +07:00
|
|
|
(!remoteCDM || remoteCDM.challenge === null) &&
|
|
|
|
interceptType === "EME"
|
|
|
|
) {
|
|
|
|
foundChallengeInBody = true;
|
|
|
|
window.postMessage({ type: "__LICENSE_URL__", data: resource }, "*");
|
|
|
|
// Block the request
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
2025-07-21 11:54:40 +07:00
|
|
|
(jsonContainsValue(jsonBody, DRM_SIGNATURES.WIDEVINE) ||
|
|
|
|
jsonContainsValue(jsonBody, DRM_SIGNATURES.PLAYREADY)) &&
|
2025-07-20 12:18:15 +07:00
|
|
|
interceptType === "LICENSE" &&
|
|
|
|
!foundChallengeInBody
|
|
|
|
) {
|
|
|
|
foundChallengeInBody = true;
|
|
|
|
window.postMessage({ type: "__LICENSE_URL__", data: resource }, "*");
|
|
|
|
if (!remoteCDM) {
|
2025-07-21 11:54:40 +07:00
|
|
|
if (jsonContainsValue(jsonBody, DRM_SIGNATURES.WIDEVINE)) {
|
2025-07-20 12:18:15 +07:00
|
|
|
const {
|
|
|
|
device_type,
|
|
|
|
system_id,
|
|
|
|
security_level,
|
|
|
|
host,
|
|
|
|
secret,
|
|
|
|
device_name,
|
|
|
|
} = widevineDeviceInfo;
|
|
|
|
remoteCDM = new remoteWidevineCDM(
|
|
|
|
device_type,
|
|
|
|
system_id,
|
|
|
|
security_level,
|
|
|
|
host,
|
|
|
|
secret,
|
|
|
|
device_name
|
|
|
|
);
|
|
|
|
remoteCDM.openSession();
|
|
|
|
remoteCDM.getChallenge(foundWidevinePssh);
|
|
|
|
}
|
2025-07-21 11:54:40 +07:00
|
|
|
if (jsonContainsValue(jsonBody, DRM_SIGNATURES.PLAYREADY)) {
|
2025-07-20 12:18:15 +07:00
|
|
|
const { security_level, host, secret, device_name } =
|
|
|
|
playreadyDeviceInfo;
|
|
|
|
remoteCDM = new remotePlayReadyCDM(
|
|
|
|
security_level,
|
|
|
|
host,
|
|
|
|
secret,
|
|
|
|
device_name
|
|
|
|
);
|
|
|
|
remoteCDM.openSession();
|
|
|
|
remoteCDM.getChallenge(foundPlayreadyPssh);
|
|
|
|
}
|
2025-06-29 12:07:18 -04:00
|
|
|
}
|
2025-07-20 12:18:15 +07:00
|
|
|
if (remoteCDM && remoteCDM.challenge === null) {
|
|
|
|
remoteCDM.getChallenge(foundWidevinePssh);
|
2025-06-29 12:07:18 -04:00
|
|
|
}
|
2025-07-20 12:18:15 +07:00
|
|
|
const injectedBody = jsonReplaceValue(jsonBody, remoteCDM.challenge);
|
|
|
|
config.body = JSON.stringify(injectedBody);
|
2025-06-29 12:07:18 -04:00
|
|
|
}
|
|
|
|
}
|
2025-06-19 22:42:40 -04:00
|
|
|
}
|
|
|
|
}
|
2025-06-16 01:21:17 -04:00
|
|
|
|
2025-07-20 12:18:15 +07:00
|
|
|
return originalFetch(resource, config);
|
|
|
|
};
|
2025-06-16 01:21:17 -04:00
|
|
|
})();
|
|
|
|
|
|
|
|
// XHR POST interceptor
|
2025-07-20 12:18:15 +07:00
|
|
|
(function () {
|
|
|
|
const originalOpen = XMLHttpRequest.prototype.open;
|
|
|
|
const originalSend = XMLHttpRequest.prototype.send;
|
|
|
|
|
|
|
|
XMLHttpRequest.prototype.open = function (method, url, async, user, password) {
|
|
|
|
this._method = method;
|
|
|
|
this._url = url;
|
|
|
|
return originalOpen.apply(this, arguments);
|
|
|
|
};
|
|
|
|
|
|
|
|
XMLHttpRequest.prototype.send = function (body) {
|
|
|
|
if (this._method && this._method.toUpperCase() === "POST") {
|
|
|
|
if (body) {
|
|
|
|
if (body instanceof ArrayBuffer || body instanceof Uint8Array) {
|
|
|
|
const buffer = body instanceof Uint8Array ? body : new Uint8Array(body);
|
|
|
|
const base64Body = window.btoa(String.fromCharCode(...buffer));
|
|
|
|
if (
|
2025-07-21 11:54:40 +07:00
|
|
|
(base64Body.startsWith(DRM_SIGNATURES.WIDEVINE) ||
|
|
|
|
base64Body.startsWith(DRM_SIGNATURES.PLAYREADY)) &&
|
2025-07-20 12:18:15 +07:00
|
|
|
(!remoteCDM ||
|
|
|
|
remoteCDM.challenge === null ||
|
|
|
|
base64Body !== remoteCDM.challenge) &&
|
|
|
|
interceptType === "EME"
|
|
|
|
) {
|
|
|
|
foundChallengeInBody = true;
|
|
|
|
window.postMessage({ type: "__LICENSE_URL__", data: this._url }, "*");
|
|
|
|
// Block the request
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (
|
2025-07-21 11:54:40 +07:00
|
|
|
(base64Body.startsWith(DRM_SIGNATURES.WIDEVINE) ||
|
|
|
|
base64Body.startsWith(DRM_SIGNATURES.PLAYREADY)) &&
|
2025-07-20 12:18:15 +07:00
|
|
|
interceptType == "LICENSE" &&
|
|
|
|
!foundChallengeInBody
|
|
|
|
) {
|
|
|
|
foundChallengeInBody = true;
|
|
|
|
window.postMessage({ type: "__LICENSE_URL__", data: this._url }, "*");
|
|
|
|
if (!remoteCDM) {
|
2025-07-21 11:54:40 +07:00
|
|
|
if (base64Body.startsWith(DRM_SIGNATURES.WIDEVINE)) {
|
2025-07-20 12:18:15 +07:00
|
|
|
const {
|
|
|
|
device_type,
|
|
|
|
system_id,
|
|
|
|
security_level,
|
|
|
|
host,
|
|
|
|
secret,
|
|
|
|
device_name,
|
|
|
|
} = widevineDeviceInfo;
|
|
|
|
remoteCDM = new remoteWidevineCDM(
|
|
|
|
device_type,
|
|
|
|
system_id,
|
|
|
|
security_level,
|
|
|
|
host,
|
|
|
|
secret,
|
|
|
|
device_name
|
|
|
|
);
|
|
|
|
remoteCDM.openSession();
|
|
|
|
remoteCDM.getChallenge(foundWidevinePssh);
|
|
|
|
}
|
2025-07-21 11:54:40 +07:00
|
|
|
if (base64Body.startsWith(DRM_SIGNATURES.PLAYREADY)) {
|
2025-07-20 12:18:15 +07:00
|
|
|
const { security_level, host, secret, device_name } =
|
|
|
|
playreadyDeviceInfo;
|
|
|
|
remoteCDM = new remotePlayReadyCDM(
|
|
|
|
security_level,
|
|
|
|
host,
|
|
|
|
secret,
|
|
|
|
device_name
|
|
|
|
);
|
|
|
|
remoteCDM.openSession();
|
|
|
|
remoteCDM.getChallenge(foundPlayreadyPssh);
|
|
|
|
}
|
2025-06-26 21:45:26 -04:00
|
|
|
}
|
2025-07-20 12:18:15 +07:00
|
|
|
if (remoteCDM && remoteCDM.challenge === null) {
|
|
|
|
remoteCDM.getChallenge(foundWidevinePssh);
|
2025-06-26 21:45:26 -04:00
|
|
|
}
|
2025-07-20 12:18:15 +07:00
|
|
|
const injectedBody = base64ToUint8Array(remoteCDM.challenge);
|
|
|
|
return originalSend.call(this, injectedBody);
|
2025-06-26 21:45:26 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-07-20 12:18:15 +07:00
|
|
|
if (typeof body === "string" && !isJson(body)) {
|
|
|
|
const base64EncodedBody = btoa(body);
|
|
|
|
if (
|
2025-07-21 11:54:40 +07:00
|
|
|
(base64EncodedBody.startsWith(DRM_SIGNATURES.WIDEVINE) ||
|
|
|
|
base64EncodedBody.startsWith(DRM_SIGNATURES.PLAYREADY)) &&
|
2025-07-20 12:18:15 +07:00
|
|
|
(!remoteCDM ||
|
|
|
|
remoteCDM.challenge === null ||
|
|
|
|
base64EncodedBody !== remoteCDM.challenge) &&
|
|
|
|
interceptType === "EME"
|
|
|
|
) {
|
|
|
|
foundChallengeInBody = true;
|
|
|
|
window.postMessage({ type: "__LICENSE_URL__", data: this._url }, "*");
|
|
|
|
// Block the request
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (
|
2025-07-21 11:54:40 +07:00
|
|
|
(base64EncodedBody.startsWith(DRM_SIGNATURES.WIDEVINE) ||
|
|
|
|
base64EncodedBody.startsWith(DRM_SIGNATURES.PLAYREADY)) &&
|
2025-07-20 12:18:15 +07:00
|
|
|
interceptType == "LICENSE" &&
|
|
|
|
!foundChallengeInBody
|
|
|
|
) {
|
|
|
|
foundChallengeInBody = true;
|
|
|
|
window.postMessage({ type: "__LICENSE_URL__", data: this._url }, "*");
|
|
|
|
if (!remoteCDM) {
|
2025-07-21 11:54:40 +07:00
|
|
|
if (base64EncodedBody.startsWith(DRM_SIGNATURES.WIDEVINE)) {
|
2025-07-20 12:18:15 +07:00
|
|
|
const {
|
|
|
|
device_type,
|
|
|
|
system_id,
|
|
|
|
security_level,
|
|
|
|
host,
|
|
|
|
secret,
|
|
|
|
device_name,
|
|
|
|
} = widevineDeviceInfo;
|
|
|
|
remoteCDM = new remoteWidevineCDM(
|
|
|
|
device_type,
|
|
|
|
system_id,
|
|
|
|
security_level,
|
|
|
|
host,
|
|
|
|
secret,
|
|
|
|
device_name
|
|
|
|
);
|
|
|
|
remoteCDM.openSession();
|
|
|
|
remoteCDM.getChallenge(foundWidevinePssh);
|
|
|
|
}
|
2025-07-21 11:54:40 +07:00
|
|
|
if (base64EncodedBody.startsWith(DRM_SIGNATURES.PLAYREADY)) {
|
2025-07-20 12:18:15 +07:00
|
|
|
const { security_level, host, secret, device_name } =
|
|
|
|
playreadyDeviceInfo;
|
|
|
|
remoteCDM = new remotePlayReadyCDM(
|
|
|
|
security_level,
|
|
|
|
host,
|
|
|
|
secret,
|
|
|
|
device_name
|
|
|
|
);
|
|
|
|
remoteCDM.openSession();
|
|
|
|
remoteCDM.getChallenge(foundPlayreadyPssh);
|
|
|
|
}
|
2025-06-26 21:45:26 -04:00
|
|
|
}
|
2025-07-20 12:18:15 +07:00
|
|
|
if (remoteCDM && remoteCDM.challenge === null) {
|
|
|
|
remoteCDM.getChallenge(foundWidevinePssh);
|
2025-06-26 21:45:26 -04:00
|
|
|
}
|
2025-07-20 12:18:15 +07:00
|
|
|
const injectedBody = atob(remoteCDM.challenge);
|
|
|
|
return originalSend.call(this, injectedBody);
|
2025-06-26 21:45:26 -04:00
|
|
|
}
|
2025-06-16 01:59:26 -04:00
|
|
|
}
|
2025-06-29 12:07:18 -04:00
|
|
|
|
2025-07-20 12:18:15 +07:00
|
|
|
if (typeof body === "string" && isJson(body)) {
|
|
|
|
const jsonBody = JSON.parse(body);
|
2025-06-29 12:07:18 -04:00
|
|
|
|
2025-07-20 12:18:15 +07:00
|
|
|
if (
|
2025-07-21 11:54:40 +07:00
|
|
|
(jsonContainsValue(jsonBody, DRM_SIGNATURES.WIDEVINE) ||
|
|
|
|
jsonContainsValue(jsonBody, DRM_SIGNATURES.PLAYREADY)) &&
|
2025-07-20 12:18:15 +07:00
|
|
|
(!remoteCDM || remoteCDM.challenge === null) &&
|
|
|
|
interceptType === "EME"
|
|
|
|
) {
|
|
|
|
foundChallengeInBody = true;
|
|
|
|
window.postMessage({ type: "__LICENSE_URL__", data: this._url }, "*");
|
|
|
|
// Block the request
|
|
|
|
return;
|
|
|
|
}
|
2025-06-29 12:07:18 -04:00
|
|
|
|
2025-07-20 12:18:15 +07:00
|
|
|
if (
|
2025-07-21 11:54:40 +07:00
|
|
|
(jsonContainsValue(jsonBody, DRM_SIGNATURES.WIDEVINE) ||
|
|
|
|
jsonContainsValue(jsonBody, DRM_SIGNATURES.PLAYREADY)) &&
|
2025-07-20 12:18:15 +07:00
|
|
|
interceptType === "LICENSE" &&
|
|
|
|
!foundChallengeInBody
|
|
|
|
) {
|
|
|
|
foundChallengeInBody = true;
|
|
|
|
window.postMessage({ type: "__LICENSE_URL__", data: this._url }, "*");
|
|
|
|
if (!remoteCDM) {
|
2025-07-21 11:54:40 +07:00
|
|
|
if (jsonContainsValue(jsonBody, DRM_SIGNATURES.WIDEVINE)) {
|
2025-07-20 12:18:15 +07:00
|
|
|
const {
|
|
|
|
device_type,
|
|
|
|
system_id,
|
|
|
|
security_level,
|
|
|
|
host,
|
|
|
|
secret,
|
|
|
|
device_name,
|
|
|
|
} = widevineDeviceInfo;
|
|
|
|
remoteCDM = new remoteWidevineCDM(
|
|
|
|
device_type,
|
|
|
|
system_id,
|
|
|
|
security_level,
|
|
|
|
host,
|
|
|
|
secret,
|
|
|
|
device_name
|
|
|
|
);
|
|
|
|
remoteCDM.openSession();
|
|
|
|
remoteCDM.getChallenge(foundWidevinePssh);
|
|
|
|
}
|
2025-07-21 11:54:40 +07:00
|
|
|
if (jsonContainsValue(jsonBody, DRM_SIGNATURES.PLAYREADY)) {
|
2025-07-20 12:18:15 +07:00
|
|
|
const { security_level, host, secret, device_name } =
|
|
|
|
playreadyDeviceInfo;
|
|
|
|
remoteCDM = new remotePlayReadyCDM(
|
|
|
|
security_level,
|
|
|
|
host,
|
|
|
|
secret,
|
|
|
|
device_name
|
|
|
|
);
|
|
|
|
remoteCDM.openSession();
|
|
|
|
remoteCDM.getChallenge(foundPlayreadyPssh);
|
|
|
|
}
|
2025-06-29 12:07:18 -04:00
|
|
|
}
|
2025-07-20 12:18:15 +07:00
|
|
|
if (remoteCDM && remoteCDM.challenge === null) {
|
|
|
|
remoteCDM.getChallenge(foundWidevinePssh);
|
2025-06-29 12:07:18 -04:00
|
|
|
}
|
2025-07-20 12:18:15 +07:00
|
|
|
const injectedBody = jsonReplaceValue(jsonBody, remoteCDM.challenge);
|
|
|
|
return originalSend.call(this, JSON.stringify(injectedBody));
|
2025-06-29 12:07:18 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-06-16 01:21:17 -04:00
|
|
|
}
|
2025-07-20 12:18:15 +07:00
|
|
|
return originalSend.apply(this, arguments);
|
|
|
|
};
|
|
|
|
})();
|