NextGen🎉
This commit is contained in:
10
README.md
10
README.md
@@ -1,10 +1,12 @@
|
||||
## WVGuesserExtension
|
||||
## WVGuesserExtension-NextGen
|
||||
Extension works standalone.
|
||||
Not anymore need WVCore.Server API setup!
|
||||
|
||||
### Instalation
|
||||
|
||||
1. Setup [Guesser API](https://github.com/nilaoda/WVCore.Server) ([Guide](https://github.com/nilaoda/Blog/discussions/58#discussioncomment-9052557))
|
||||
|
||||
2. Install extension
|
||||
1. Donwload or clone this code
|
||||
2. At the same directory of `manifest.json`(root directory of this extension), put the device file named `device.wvd`
|
||||
3. Install extension
|
||||
|
||||
* Firefox
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
let psshs=[];
|
||||
let requests=[];
|
||||
function convertHeaders(obj){
|
||||
return Object.fromEntries(obj.map(header => [header.name, header.value]))
|
||||
return JSON.stringify(Object.fromEntries(obj.map(header => [header.name, header.value])))
|
||||
}
|
||||
chrome.webRequest.onBeforeSendHeaders.addListener(
|
||||
function(details) {
|
||||
|
||||
@@ -26,5 +26,6 @@
|
||||
"browser_action": {
|
||||
"default_title": "Widevine Guessor"
|
||||
},
|
||||
"web_accessible_resources": ["inject.js"]
|
||||
"web_accessible_resources": ["inject.js"],
|
||||
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<title>Widevine L3 Guessor 2024</title>
|
||||
<style>div{display:none}</style>
|
||||
<script src="pyodide/pyodide.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="noEME" style="display:block">
|
||||
@@ -11,13 +12,6 @@
|
||||
</div>
|
||||
<div id="home">
|
||||
<form id="wvForm">
|
||||
<label for="guessr">Guessr API</label>
|
||||
<input type="text" id="guessr" value="https://proposed-marketa-foxrefire.koyeb.app" disabled/>
|
||||
<input type="radio" value="public" name="apiType" checked>Public
|
||||
<input type="radio" value="local" name="apiType">Local
|
||||
<input type="radio" value="custom" name="apiType">Custom
|
||||
<br>
|
||||
|
||||
<label for="pssh">PSSH</label>
|
||||
<input type="text" id="pssh" disabled/>
|
||||
<input type="hidden" id="psshIndex" />
|
||||
|
||||
25
popup.js
25
popup.js
@@ -1,6 +1,3 @@
|
||||
import CommonWV from './schemes/CommonWV.js';
|
||||
import DRMToday from './schemes/DRMToday.js';
|
||||
|
||||
let psshs=chrome.extension.getBackgroundPage().getPsshs();
|
||||
let requests=chrome.extension.getBackgroundPage().getRequests();
|
||||
|
||||
@@ -31,19 +28,15 @@ function handleRadioChange(event) {
|
||||
}
|
||||
|
||||
async function guess(){
|
||||
let WVScheme;
|
||||
switch (document.getElementById('scheme').value) {
|
||||
case "CommonWV":
|
||||
WVScheme=CommonWV;
|
||||
break;
|
||||
case "DRMToday":
|
||||
WVScheme=DRMToday;
|
||||
break;
|
||||
}
|
||||
const result=await WVScheme(document.getElementById('guessr').value,
|
||||
document.getElementById('pssh').value,
|
||||
requests[userInputs['license']]['url'],
|
||||
requests[userInputs['license']]['headers'])
|
||||
let pyodide = await loadPyodide();
|
||||
await pyodide.loadPackage(["certifi-2024.2.2-py3-none-any.whl","charset_normalizer-3.3.2-py3-none-any.whl","construct-2.8.8-py2.py3-none-any.whl","idna-3.6-py3-none-any.whl","packaging-23.2-py3-none-any.whl","protobuf-4.24.4-cp312-cp312-emscripten_3_1_52_wasm32.whl","pycryptodome-3.20.0-cp35-abi3-emscripten_3_1_52_wasm32.whl","pymp4-1.4.0-py3-none-any.whl","pyodide_http-0.2.1-py3-none-any.whl","pywidevine-1.8.0-py3-none-any.whl","requests-2.31.0-py3-none-any.whl","urllib3-2.2.1-py3-none-any.whl"].map(e=>"wheels/"+e))
|
||||
let vars=`pssh="${document.getElementById('pssh').value}"\n`
|
||||
vars+=`licUrl="${requests[userInputs['license']]['url']}"\n`
|
||||
vars+=`licHeaders='${requests[userInputs['license']]['headers'].replace(/\\/g, "\\\\")}'\n`
|
||||
let pre=await fetch('python/pre.py').then(res=>res.text())
|
||||
let after=await fetch('python/after.py').then(res=>res.text())
|
||||
let scheme=await fetch('python/schemes/CommonWV.py').then(res=>res.text())
|
||||
let result = await pyodide.runPythonAsync([vars, pre, scheme, after].join("\n"));
|
||||
document.getElementById('result').value=result;
|
||||
}
|
||||
|
||||
|
||||
134
pyodide/package.json
Normal file
134
pyodide/package.json
Normal file
@@ -0,0 +1,134 @@
|
||||
{
|
||||
"name": "pyodide",
|
||||
"version": "0.25.1",
|
||||
"description": "The Pyodide JavaScript package",
|
||||
"keywords": [
|
||||
"python",
|
||||
"webassembly"
|
||||
],
|
||||
"homepage": "https://github.com/pyodide/pyodide",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pyodide/pyodide"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/pyodide/pyodide/issues"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@types/assert": "^1.5.6",
|
||||
"@types/expect": "^24.3.0",
|
||||
"@types/mocha": "^9.1.0",
|
||||
"@types/node": "^20.8.4",
|
||||
"@types/ws": "^8.5.3",
|
||||
"chai": "^4.3.6",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"dts-bundle-generator": "^8.1.1",
|
||||
"error-stack-parser": "^2.1.4",
|
||||
"esbuild": "^0.17.12",
|
||||
"express": "^4.17.3",
|
||||
"mocha": "^9.0.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"nyc": "^15.1.0",
|
||||
"prettier": "^2.2.1",
|
||||
"ts-mocha": "^9.0.2",
|
||||
"tsd": "^0.24.1",
|
||||
"typedoc": "^0.25.1",
|
||||
"typescript": "^4.6.4",
|
||||
"wabt": "^1.0.32"
|
||||
},
|
||||
"main": "pyodide.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"require": "./pyodide.js",
|
||||
"import": "./pyodide.mjs",
|
||||
"types": "./pyodide.d.ts"
|
||||
},
|
||||
"./ffi": {
|
||||
"types": "./ffi.d.ts"
|
||||
},
|
||||
"./pyodide.asm.wasm": "./pyodide.asm.wasm",
|
||||
"./pyodide.asm.js": "./pyodide.asm.js",
|
||||
"./python_stdlib.zip": "./python_stdlib.zip",
|
||||
"./pyodide.mjs": "./pyodide.mjs",
|
||||
"./pyodide.js": "./pyodide.js",
|
||||
"./package.json": "./package.json",
|
||||
"./pyodide-lock.json": "./pyodide-lock.json"
|
||||
},
|
||||
"files": [
|
||||
"pyodide.asm.js",
|
||||
"pyodide.asm.wasm",
|
||||
"python_stdlib.zip",
|
||||
"pyodide.mjs",
|
||||
"pyodide.js.map",
|
||||
"pyodide.mjs.map",
|
||||
"pyodide.d.ts",
|
||||
"ffi.d.ts",
|
||||
"pyodide-lock.json",
|
||||
"console.html"
|
||||
],
|
||||
"browser": {
|
||||
"child_process": false,
|
||||
"crypto": false,
|
||||
"fs": false,
|
||||
"fs/promises": false,
|
||||
"path": false,
|
||||
"url": false,
|
||||
"vm": false,
|
||||
"ws": false
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc --noEmit && node esbuild.config.mjs",
|
||||
"test": "npm-run-all test:*",
|
||||
"test:unit": "cross-env TEST_NODE=1 ts-mocha --node-option=experimental-loader=./test/loader.mjs --node-option=experimental-wasm-stack-switching -p tsconfig.test.json test/unit/**/*.test.*",
|
||||
"test:node": "cross-env TEST_NODE=1 mocha test/integration/**/*.test.js",
|
||||
"test:browser": "mocha test/integration/**/*.test.js",
|
||||
"tsc": "tsc --noEmit",
|
||||
"coverage": "cross-env TEST_NODE=1 npm-run-all coverage:*",
|
||||
"coverage:build": "nyc npm run test:node"
|
||||
},
|
||||
"mocha": {
|
||||
"bail": false,
|
||||
"timeout": 30000,
|
||||
"full-trace": true,
|
||||
"inline-diffs": true,
|
||||
"check-leaks": false,
|
||||
"global": [
|
||||
"pyodide",
|
||||
"page",
|
||||
"chai"
|
||||
]
|
||||
},
|
||||
"nyc": {
|
||||
"reporter": [
|
||||
"html",
|
||||
"text-summary"
|
||||
],
|
||||
"include": [
|
||||
"*.ts"
|
||||
],
|
||||
"all": true,
|
||||
"clean": true,
|
||||
"cache": false,
|
||||
"instrument": false,
|
||||
"checkCoverage": true,
|
||||
"statements": 95,
|
||||
"functions": 95,
|
||||
"branches": 80,
|
||||
"lines": 95
|
||||
},
|
||||
"tsd": {
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"ES2017",
|
||||
"DOM"
|
||||
]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"base-64": "^1.0.0",
|
||||
"ws": "^8.5.0"
|
||||
},
|
||||
"types": "./pyodide.d.ts"
|
||||
}
|
||||
1
pyodide/pyodide-lock.json
Normal file
1
pyodide/pyodide-lock.json
Normal file
File diff suppressed because one or more lines are too long
17
pyodide/pyodide.asm.js
Normal file
17
pyodide/pyodide.asm.js
Normal file
File diff suppressed because one or more lines are too long
BIN
pyodide/pyodide.asm.wasm
Executable file
BIN
pyodide/pyodide.asm.wasm
Executable file
Binary file not shown.
12
pyodide/pyodide.js
Normal file
12
pyodide/pyodide.js
Normal file
File diff suppressed because one or more lines are too long
10
pyodide/pyodide.mjs
Normal file
10
pyodide/pyodide.mjs
Normal file
File diff suppressed because one or more lines are too long
BIN
pyodide/python_stdlib.zip
Normal file
BIN
pyodide/python_stdlib.zip
Normal file
Binary file not shown.
11
python/after.py
Normal file
11
python/after.py
Normal file
@@ -0,0 +1,11 @@
|
||||
# parse license challenge
|
||||
cdm.parse_license(session_id, licence)
|
||||
|
||||
# get keys
|
||||
keys=""
|
||||
for key in cdm.get_keys(session_id):
|
||||
keys+=f"[{key.type}] {key.kid.hex}:{key.key.hex()}\n"
|
||||
|
||||
# close session, disposes of session data
|
||||
cdm.close(session_id)
|
||||
keys
|
||||
26
python/pre.py
Normal file
26
python/pre.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from pywidevine.cdm import Cdm
|
||||
from pywidevine.device import Device
|
||||
from pywidevine.pssh import PSSH
|
||||
|
||||
import json
|
||||
from pyodide.http import pyfetch
|
||||
|
||||
# prepare pssh
|
||||
pssh = PSSH(pssh)
|
||||
|
||||
# load device
|
||||
with open('device.wvd', 'wb') as f:
|
||||
wvdExt=await (await pyfetch("device.wvd")).bytes()
|
||||
f.write(wvdExt)
|
||||
device = Device.load("device.wvd")
|
||||
|
||||
# load cdm
|
||||
cdm = Cdm.from_device(device)
|
||||
|
||||
# open cdm session
|
||||
session_id = cdm.open()
|
||||
|
||||
# get license challenge
|
||||
challenge = cdm.get_license_challenge(session_id, pssh)
|
||||
|
||||
licHeaders=json.loads(licHeaders)
|
||||
5
python/schemes/CommonWV.py
Normal file
5
python/schemes/CommonWV.py
Normal file
@@ -0,0 +1,5 @@
|
||||
licence = await (await pyfetch(licUrl,
|
||||
method="POST",
|
||||
headers=licHeaders,
|
||||
body=challenge
|
||||
)).bytes()
|
||||
@@ -1,53 +0,0 @@
|
||||
export default async function(serverAddr, pssh, licUrl,_headers) {
|
||||
console.group("fetch cert...");
|
||||
let certBuffer = await fetch(licUrl, {
|
||||
body: new Uint8Array([0x08, 0x04]),
|
||||
headers: _headers,
|
||||
method: "POST"
|
||||
}).then(resp => resp.arrayBuffer());
|
||||
let certB64 = btoa(String.fromCharCode(...new Uint8Array(certBuffer)));
|
||||
console.log(certB64);
|
||||
console.groupEnd();
|
||||
|
||||
console.group("fetch challenge...");
|
||||
let jsonC = await fetch(serverAddr + "/getchallenge", {
|
||||
body: JSON.stringify({
|
||||
"PSSH": pssh,
|
||||
"CertBase64": certB64
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
method: "POST"
|
||||
}).then(resp => resp.json());
|
||||
let challengeBase64 = jsonC.challengeBase64;
|
||||
console.log(challengeBase64);
|
||||
console.groupEnd();
|
||||
|
||||
console.group("fetch license...");
|
||||
let licBuffer = await fetch(licUrl, {
|
||||
body: Uint8Array.from(atob(challengeBase64), (c) => c.charCodeAt(0)),
|
||||
headers: _headers,
|
||||
method: "POST"
|
||||
}).then(resp => resp.arrayBuffer());
|
||||
let licB64 = btoa(String.fromCharCode(...new Uint8Array(licBuffer)));
|
||||
console.log(licB64);
|
||||
console.groupEnd();
|
||||
|
||||
console.group("get keys...");
|
||||
let jsonK = await fetch(serverAddr + "/getkeys", {
|
||||
body: JSON.stringify({
|
||||
"PSSH": pssh,
|
||||
"ChallengeBase64": challengeBase64,
|
||||
"LicenseBase64": licB64
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
method: "POST"
|
||||
}).then(resp => resp.json());
|
||||
let keys = jsonK.keys;
|
||||
console.log(keys);
|
||||
console.groupEnd();
|
||||
return keys;
|
||||
};
|
||||
@@ -1,77 +0,0 @@
|
||||
export default async function(serverAddr, pssh, licUrl,_headers) {
|
||||
console.group("fetch cert...");
|
||||
let certBuffer = await fetch(licUrl, {
|
||||
body: new Uint8Array([0x08, 0x04]),
|
||||
headers: _headers,
|
||||
method: "POST",
|
||||
}).then((resp) => resp.arrayBuffer());
|
||||
let certB64 = "";
|
||||
if (isValidJson(String.fromCharCode(...new Uint8Array(certBuffer)))) {
|
||||
let jsonData = JSON.parse(
|
||||
String.fromCharCode(...new Uint8Array(certBuffer))
|
||||
);
|
||||
if (jsonData.license) {
|
||||
certB64 = jsonData.license;
|
||||
} else {
|
||||
console.log("JSON does not contain 'license'");
|
||||
}
|
||||
} else {
|
||||
certB64 = btoa(String.fromCharCode(...new Uint8Array(certBuffer)));
|
||||
}
|
||||
console.log(certB64);
|
||||
console.groupEnd();
|
||||
|
||||
console.group("fetch challenge...");
|
||||
let jsonC = await fetch(serverAddr + "/getchallenge", {
|
||||
body: JSON.stringify({
|
||||
PSSH: pssh,
|
||||
CertBase64: certB64,
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
method: "POST",
|
||||
}).then((resp) => resp.json());
|
||||
let challengeBase64 = jsonC.challengeBase64;
|
||||
console.log(challengeBase64);
|
||||
console.groupEnd();
|
||||
|
||||
console.group("fetch license...");
|
||||
let licBuffer = await fetch(licUrl, {
|
||||
body: Uint8Array.from(atob(challengeBase64), (c) => c.charCodeAt(0)),
|
||||
headers: _headers,
|
||||
method: "POST",
|
||||
}).then((resp) => resp.arrayBuffer());
|
||||
let licB64 = "";
|
||||
if (isValidJson(String.fromCharCode(...new Uint8Array(licBuffer)))) {
|
||||
let jsonData = JSON.parse(
|
||||
String.fromCharCode(...new Uint8Array(licBuffer))
|
||||
);
|
||||
if (jsonData.license) {
|
||||
licB64 = jsonData.license;
|
||||
} else {
|
||||
console.log("JSON does not contain 'license'");
|
||||
}
|
||||
} else {
|
||||
licB64 = btoa(String.fromCharCode(...new Uint8Array(licBuffer)));
|
||||
}
|
||||
console.log(licB64);
|
||||
console.groupEnd();
|
||||
|
||||
console.group("get keys...");
|
||||
let jsonK = await fetch(serverAddr + "/getkeys", {
|
||||
body: JSON.stringify({
|
||||
PSSH: pssh,
|
||||
ChallengeBase64: challengeBase64,
|
||||
LicenseBase64: licB64,
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
method: "POST",
|
||||
}).then((resp) => resp.json());
|
||||
let keys = jsonK.keys;
|
||||
console.log(keys);
|
||||
console.groupEnd();
|
||||
return keys;
|
||||
};
|
||||
BIN
wheels/certifi-2024.2.2-py3-none-any.whl
Normal file
BIN
wheels/certifi-2024.2.2-py3-none-any.whl
Normal file
Binary file not shown.
BIN
wheels/charset_normalizer-3.3.2-py3-none-any.whl
Normal file
BIN
wheels/charset_normalizer-3.3.2-py3-none-any.whl
Normal file
Binary file not shown.
BIN
wheels/construct-2.8.8-py2.py3-none-any.whl
Normal file
BIN
wheels/construct-2.8.8-py2.py3-none-any.whl
Normal file
Binary file not shown.
BIN
wheels/idna-3.6-py3-none-any.whl
Normal file
BIN
wheels/idna-3.6-py3-none-any.whl
Normal file
Binary file not shown.
BIN
wheels/packaging-23.2-py3-none-any.whl
Normal file
BIN
wheels/packaging-23.2-py3-none-any.whl
Normal file
Binary file not shown.
BIN
wheels/protobuf-4.24.4-cp312-cp312-emscripten_3_1_52_wasm32.whl
Normal file
BIN
wheels/protobuf-4.24.4-cp312-cp312-emscripten_3_1_52_wasm32.whl
Normal file
Binary file not shown.
Binary file not shown.
BIN
wheels/pymp4-1.4.0-py3-none-any.whl
Normal file
BIN
wheels/pymp4-1.4.0-py3-none-any.whl
Normal file
Binary file not shown.
BIN
wheels/pyodide_http-0.2.1-py3-none-any.whl
Normal file
BIN
wheels/pyodide_http-0.2.1-py3-none-any.whl
Normal file
Binary file not shown.
BIN
wheels/pywidevine-1.8.0-py3-none-any.whl
Normal file
BIN
wheels/pywidevine-1.8.0-py3-none-any.whl
Normal file
Binary file not shown.
BIN
wheels/requests-2.31.0-py3-none-any.whl
Normal file
BIN
wheels/requests-2.31.0-py3-none-any.whl
Normal file
Binary file not shown.
BIN
wheels/urllib3-2.2.1-py3-none-any.whl
Normal file
BIN
wheels/urllib3-2.2.1-py3-none-any.whl
Normal file
Binary file not shown.
Reference in New Issue
Block a user