diff --git a/background.js b/background.js index 4a1472b..26ea37f 100644 --- a/background.js +++ b/background.js @@ -1,8 +1,8 @@ import { + decodeBase64, uint8ArrayToBase64, SettingsManager, RemoteCDMManager, - LocalCDMManager, PSSHFromKID, stringToUTF16LEBytes, } from "./util.js"; @@ -13,6 +13,7 @@ let requests = new Map(); const sessions = new Map(); let logs = []; + chrome.webRequest.onBeforeSendHeaders.addListener( function (details) { if (details.method === "GET") { @@ -45,21 +46,25 @@ chrome.webRequest.onBeforeSendHeaders.addListener( -async function generateChallengeRemote(body, sendResponse) { +async function generateChallengeRemote(body, sendResponse, tab_url) { try { - // Decode the base64-encoded body into a binary string + console.log("[PlayReadyProxy] generateChallengeRemote called with tab_url:", tab_url); + if (!tab_url) { + console.error("[PlayReadyProxy] No tab_url provided, cannot store session"); + sendResponse(body); + return; + } + const binaryString = decodeBase64(body); const byteArray = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { byteArray[i] = binaryString.charCodeAt(i); } - // Decode using UTF-16LE encoding const decoder = new TextDecoder("utf-16le"); let xmlString = decoder.decode(byteArray); let xmlDecoded; - // Extract the Challenge element from the XML string const challengeRegex = /]*>([\s\S]*?)<\/Challenge>/i; const challengeMatch = challengeRegex.exec(xmlString); let encoding; @@ -70,7 +75,6 @@ async function generateChallengeRemote(body, sendResponse) { const encodingMatch = encodingRegex.exec(xmlString); encoding = encodingMatch ? encodingMatch[1] : null; - // If encoding is base64encoded, decode the challenge content if (encoding === "base64encoded") { const challengeBinaryString = decodeBase64(challengeContent); const challengeByteArray = new Uint8Array(challengeBinaryString.length); @@ -86,7 +90,6 @@ async function generateChallengeRemote(body, sendResponse) { return; } - // Extract the KID element const kidRegex = /([^<]+)<\/KID>/i; const kidMatch = kidRegex.exec(xmlDecoded); let kidBase64; @@ -98,7 +101,6 @@ async function generateChallengeRemote(body, sendResponse) { return; } - // Get PSSH from KID const pssh = PSSHFromKID(kidBase64); if (!pssh) { console.log("[PlayReadyProxy]", "NO_PSSH_DATA_IN_CHALLENGE"); @@ -106,7 +108,6 @@ async function generateChallengeRemote(body, sendResponse) { return; } - // Fetch the selected remote CDM and load it const selected_remote_cdm_name = await RemoteCDMManager.getSelectedRemoteCDM(); if (!selected_remote_cdm_name) { sendResponse(body); @@ -117,15 +118,8 @@ async function generateChallengeRemote(body, sendResponse) { let remoteCdmObj; try { - // Check if the selected_remote_cdm is Base64-encoded XML - if (selected_remote_cdm.startsWith("PD94bWwgdm")) { - const decodedString = decodeBase64(selected_remote_cdm); - const parser = new DOMParser(); - const xmlDoc = parseXML(decodedString); - remoteCdmObj = RemoteCdm.from_xml(xmlDoc); - } else { - remoteCdmObj = JSON.parse(selected_remote_cdm); - } + // Assume the remote CDM is always in JSON format + remoteCdmObj = JSON.parse(selected_remote_cdm); } catch (e) { console.error("Error parsing remote CDM:", e); sendResponse(body); @@ -141,20 +135,18 @@ async function generateChallengeRemote(body, sendResponse) { } console.log("[PlayReadyProxy]", "SESSION_ID", session_id); + console.log("[PlayReadyProxy] Storing session_id", session_id, "for tab_url", tab_url); + sessions.set(tab_url, session_id); const challenge = await remote_cdm.get_license_challenge(session_id, pssh); - - // Replace the challenge content in the original XML with the new challenge const newXmlString = xmlString.replace( /(]*>)([\s\S]*?)(<\/Challenge>)/i, `$1${challenge}$3` ); - // Convert the new XML string to UTF-16LE and then to base64 const utf16leBytes = stringToUTF16LEBytes(newXmlString); const responseBase64 = uint8ArrayToBase64(utf16leBytes); - // Send the base64-encoded response sendResponse(responseBase64); } catch (error) { console.error("Error in generateChallengeRemote:", error); @@ -165,9 +157,17 @@ async function generateChallengeRemote(body, sendResponse) { async function parseLicenseRemote(body, sendResponse, tab_url) { try { - const license_b64 = body; // License message is already Base64-encoded + console.log("[PlayReadyProxy] parseLicenseRemote called with tab_url:", tab_url); + if (!tab_url) { + console.error("[PlayReadyProxy] No tab_url provided, cannot retrieve session"); + sendResponse(); + return; + } + + const license_b64 = body; + console.log("[PlayReadyProxy] Current sessions:", + [...sessions.entries()].map(([url, id]) => ({ url, id }))); - // Fetch the selected remote CDM and load it const selected_remote_cdm_name = await RemoteCDMManager.getSelectedRemoteCDM(); if (!selected_remote_cdm_name) { console.error("[PlayReadyProxy] No remote CDM selected."); @@ -187,16 +187,17 @@ async function parseLicenseRemote(body, sendResponse, tab_url) { } const remote_cdm = RemoteCdm.from_object(remoteCdmObj); - const session_id = await remote_cdm.open(); - if (!session_id) { - console.error("[PlayReadyProxy] Failed to open session."); + if (!sessions.has(tab_url)) { + console.error("[PlayReadyProxy] No previous session found for URL:", tab_url); + + console.error("[PlayReadyProxy] Cannot process license without challenge first"); sendResponse(); return; } + + const session_id = sessions.get(tab_url); + console.log("[PlayReadyProxy] Using existing SESSION_ID", session_id, "for tab_url", tab_url); - console.log("[PlayReadyProxy]", "SESSION_ID", session_id); - - // Fetch keys using get_keys(session_id, license_b64) const returned_keys = await remote_cdm.get_keys(session_id, license_b64); if (!returned_keys || returned_keys.length === 0) { console.log("[PlayReadyProxy] No keys returned."); @@ -204,7 +205,6 @@ async function parseLicenseRemote(body, sendResponse, tab_url) { return; } - // Format the keys correctly const keys = returned_keys.map((s) => ({ k: s.key, kid: s.key_id, @@ -212,7 +212,6 @@ async function parseLicenseRemote(body, sendResponse, tab_url) { console.log("[PlayReadyProxy]", "KEYS", JSON.stringify(keys), tab_url); - // Store log data const log = { type: "PLAYREADY", keys: keys, @@ -222,8 +221,8 @@ async function parseLicenseRemote(body, sendResponse, tab_url) { }; logs.push(log); - // Close the session after key retrieval await remote_cdm.close(session_id); + sessions.delete(tab_url); sendResponse(); } catch (error) { @@ -235,6 +234,7 @@ async function parseLicenseRemote(body, sendResponse, tab_url) { chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { (async () => { const tab_url = sender.tab ? sender.tab.url : null; + console.log("[PlayReadyProxy] Received message type:", message.type, "for tab_url:", tab_url); switch (message.type) { case "REQUEST": @@ -252,7 +252,8 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.body) { await generateChallengeRemote( message.body, - sendResponse + sendResponse, + tab_url ); } } @@ -269,6 +270,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { await parseClearKey(message.body, sendResponse, tab_url); return; } catch (e) { + console.log("[PlayReadyProxy] parseClearKey failed, trying parseLicenseRemote", e); await parseLicenseRemote( message.body, sendResponse, @@ -298,6 +300,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { case "CLEAR": logs = []; manifests.clear(); + sessions.clear(); break; case "MANIFEST": const parsed = JSON.parse(message.body); diff --git a/content_script.js b/content_script.js index 7cb1f1a..feca09d 100644 --- a/content_script.js +++ b/content_script.js @@ -21,10 +21,7 @@ function emitAndWaitForResponse(type, data) { const responseHandler = (event) => { const { detail } = event; if (detail.substring(0, 7) === requestId) { - document.removeEventListener( - "responseReceived", - responseHandler - ); + document.removeEventListener("responseReceived", responseHandler); resolve(detail.substring(7)); } }; @@ -37,6 +34,7 @@ function emitAndWaitForResponse(type, data) { }, }); document.dispatchEvent(requestEvent); + console.log(`[ContentScript] Dispatched event: ${type}`); }); } @@ -146,17 +144,12 @@ class Evaluator { oldChallenge ); if (oldChallenge !== newChallenge) { - // Playback will fail if the challenges are the same (aka. the background script - // returned the same challenge because the addon is disabled), but I still - // override the challenge anyway, so check beforehand (in base64 form) newBody = base64toUint8Array( newChallenge ); } } else { - // trick EME Logger - // better suggestions for avoiding EME Logger interference are welcome await emitAndWaitForResponse( "REQUEST", "" @@ -298,11 +291,9 @@ XMLHttpRequest.prototype.send = function (postData) { body = this.responseText ?? this.response; break; case "json": - // TODO: untested body = JSON.stringify(this.response); break; case "arraybuffer": - // TODO: untested if (this.response.byteLength) { const response = new Uint8Array(this.response); body = uint8ArrayToString( @@ -313,9 +304,6 @@ XMLHttpRequest.prototype.send = function (postData) { ); } break; - case "document": - // todo - break; case "blob": body = await this.response.text(); break; @@ -335,4 +323,4 @@ XMLHttpRequest.prototype.send = function (postData) { } }); return send.apply(this, arguments); -}; +}; \ No newline at end of file diff --git a/images/banner.png b/images/banner.png index 097a24a..5524943 100644 Binary files a/images/banner.png and b/images/banner.png differ diff --git a/images/icon-128.png b/images/icon-128.png index 347f185..bbceca9 100644 Binary files a/images/icon-128.png and b/images/icon-128.png differ diff --git a/images/proxy_text.png b/images/proxy_text.png index 097a24a..5524943 100644 Binary files a/images/proxy_text.png and b/images/proxy_text.png differ diff --git a/manifest.json b/manifest.json index 58d075b..2f18132 100644 --- a/manifest.json +++ b/manifest.json @@ -45,4 +45,4 @@ "strict_min_version": "58.0" } } -} +} \ No newline at end of file diff --git a/message_proxy.js b/message_proxy.js index 3ba7b15..0d4c638 100644 --- a/message_proxy.js +++ b/message_proxy.js @@ -27,4 +27,4 @@ document.addEventListener("response", async (event) => { console.error("Error processing message:", error); // Optionally handle the error, maybe notify the user } -}); +}); \ No newline at end of file diff --git a/panel/panel.js b/panel/panel.js index bab9a32..c2b1efb 100644 --- a/panel/panel.js +++ b/panel/panel.js @@ -1,6 +1,5 @@ import { AsyncLocalStorage, - base64toUint8Array, stringToUint8Array, RemoteCDMManager, SettingsManager, @@ -219,11 +218,21 @@ chrome.storage.onChanged.addListener(async (changes, areaName) => { function checkLogs() { chrome.runtime.sendMessage({ type: "GET_LOGS" }, (response) => { - if (response) { - response.forEach(async (result) => { - await appendLog(result); - }); + console.log("[DEBUG] Received logs response:", response); + + if (!response) { + console.error("[ERROR] No response received from GET_LOGS."); + return; } + + if (!Array.isArray(response)) { + console.error("[ERROR] Unexpected response format. Expected an array but got:", response); + return; + } + + response.forEach(async (result) => { + await appendLog(result); + }); }); } diff --git a/remote_cdm.js b/remote_cdm.js index de8bfec..fb7f69e 100644 --- a/remote_cdm.js +++ b/remote_cdm.js @@ -88,9 +88,9 @@ export class RemoteCdm { console.log("[PlayReadyProxy] Requesting License Challenge..."); console.log("[PlayReadyProxy] SESSION_ID:", session_id); console.log("[PlayReadyProxy] PSSH:", pssh); - + const license_request = await this.fetch_with_proxy( - `${this.host}/${this.device_name}/get_challenge`, + `${this.host}/api/playready/${this.device_name}/get_challenge`, { method: "POST", headers: { @@ -103,11 +103,11 @@ export class RemoteCdm { }), } ); - + console.log("[PlayReadyProxy]", "REMOTE_CDM", "GET_LICENSE_CHALLENGE", license_request.status); const response = await license_request.json(); console.log("[PlayReadyProxy] License Challenge Response:", response); - + return response.responseData.challenge_b64; } @@ -137,4 +137,4 @@ export class RemoteCdm { return response.responseData.keys; } -} +} \ No newline at end of file diff --git a/util.js b/util.js index b362574..deb7b68 100644 --- a/util.js +++ b/util.js @@ -435,3 +435,23 @@ export function hexStringToUint8Array(hexString) { } return bytes; } + +export function decodeBase64(base64String) { + try { + const decodedData = atob(base64String); + return decodedData; + } catch (error) { + console.error("Invalid Base64 string", error); + return null; + } +} + +function parseXML(xmlStr) { + try { + const parser = new DOMParser(); + return parser.parseFromString(xmlStr, "text/xml"); + } catch (e) { + console.error("Error parsing XML:", e); + return null; + } +} \ No newline at end of file