Files
WidevineProxy2/content_script.js
2024-10-22 11:09:50 +02:00

149 lines
6.6 KiB
JavaScript

function uint8ArrayToBase64(uint8array) {
return btoa(String.fromCharCode.apply(null, uint8array));
}
function base64toUint8Array(base64_string){
return Uint8Array.from(atob(base64_string), c => c.charCodeAt(0))
}
function compareUint8Arrays(arr1, arr2) {
if (arr1.length !== arr2.length)
return false;
return Array.from(arr1).every((value, index) => value === arr2[index]);
}
function emitAndWaitForResponse(type, data) {
return new Promise((resolve) => {
const requestId = Math.random().toString(16).substring(2, 9);
const responseHandler = (event) => {
const { detail } = event;
if (detail.substring(0, 7) === requestId) {
document.removeEventListener('responseReceived', responseHandler);
resolve(detail.substring(7));
}
};
document.addEventListener('responseReceived', responseHandler);
const requestEvent = new CustomEvent('response', {
detail: {
type: type,
body: data,
requestId: requestId,
}
});
document.dispatchEvent(requestEvent);
});
}
const fnproxy = (object, func) => new Proxy(object, { apply: func });
const proxy = (object, key, func) => Object.hasOwnProperty.call(object, key) && Object.defineProperty(object, key, {
value: fnproxy(object[key], func)
});
function getEventListeners(type) {
if (this == null) return [];
const store = this[Symbol.for(getEventListeners)];
if (store == null || store[type] == null) return [];
return store[type];
}
(async () => {
if (typeof EventTarget !== 'undefined') {
proxy(EventTarget.prototype, 'addEventListener', async (_target, _this, _args) => {
if (_this != null) {
const [type, listener] = _args;
const storeKey = Symbol.for(getEventListeners);
if (!(storeKey in _this)) _this[storeKey] = {};
const store = _this[storeKey];
if (!(type in store)) store[type] = [];
const listeners = store[type];
let wrappedListener = listener;
if (!!listener && !listener._isWrapped) {
wrappedListener = async function(event) {
if (event instanceof MediaKeyMessageEvent) {
if (event._isCustomEvent) {
if (listener.handleEvent) {
listener.handleEvent(event);
} else {
listener.call(this, event);
}
return;
}
let newBody = new Uint8Array(event.message);
if (!compareUint8Arrays(new Uint8Array([0x08, 0x04]), new Uint8Array(event.message))) {
console.log("[WidevineProxy2]", "WIDEVINE_PROXY", "MESSAGE", listener);
if (listener.name !== "messageHandler") {
const oldChallenge = uint8ArrayToBase64(new Uint8Array(event.message));
const newChallenge = await emitAndWaitForResponse("REQUEST", 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", "");
}
}
const newEvent = new MediaKeyMessageEvent('message', {
isTrusted: event.isTrusted,
bubbles: event.bubbles,
cancelBubble: event.cancelBubble,
composed: event.composed,
currentTarget: event.currentTarget,
defaultPrevented: event.defaultPrevented,
eventPhase: event.eventPhase,
message: newBody.buffer,
messageType: event.messageType,
returnValue: event.returnValue,
srcElement: event.srcElement,
target: event.target,
timeStamp: event.timeStamp,
});
newEvent._isCustomEvent = true;
_this.dispatchEvent(newEvent);
event.stopImmediatePropagation();
return
}
if (listener.handleEvent) {
listener.handleEvent(event);
} else {
listener.call(this, event);
}
};
wrappedListener._isWrapped = true;
wrappedListener.originalListener = listener;
}
const alreadyAdded = listeners.some(
storedListener => storedListener && storedListener.originalListener === listener
);
if (!alreadyAdded) {
listeners.push(wrappedListener);
_args[1] = wrappedListener;
}
}
return _target.apply(_this, _args);
});
}
if (typeof MediaKeySession !== 'undefined') {
proxy(MediaKeySession.prototype, 'update', async (_target, _this, _args) => {
const [response] = _args;
console.log("[WidevineProxy2]", "WIDEVINE_PROXY", "UPDATE");
await emitAndWaitForResponse("RESPONSE", uint8ArrayToBase64(new Uint8Array(response)))
return await _target.apply(_this, _args);
});
}
})();