diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c1f3f91 --- /dev/null +++ b/.gitignore @@ -0,0 +1,132 @@ +# ---> Node +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + diff --git a/index.js b/index.js new file mode 100644 index 0000000..70fc558 --- /dev/null +++ b/index.js @@ -0,0 +1,198 @@ +/* + DVDFab Server Emulator v1.1.3 +*/ + +// Libraries +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); +const mockttp = require('mockttp'); + +// Classes +class Session { + constructor() { + this.currentMachineId = null; + this.usage = 0; + this.generateNewIdentity(); + } + + generateMacAddress() { + const macBytes = Array.from({ length: 12 }, () => Math.floor(Math.random() * 256)); + const firstMac = macBytes.slice(0, 6).map(_ => _.toString(16).padStart(2, '0')).join('-'); + const secondMac = macBytes.slice(6).map(_ => _.toString(16).padStart(2, '0')).join('-'); + return `${firstMac}:${secondMac}`; + } + + generateNewIdentity() { + this.currentMachineId = this.generateMacAddress(); + } + + patchBoundary(data) { + if (this.usage === 3) { + this.usage = 0; + this.generateNewIdentity(); + } + + const macRegex = /^([0-9a-f]{2}-){5}[0-9a-f]{2}(:([0-9a-f]{2}-){5}[0-9a-f]{2})?/; + const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; + const subscriptionRegex = /^365$/; + + for (const key in data) { + const value = data[key]; + if (macRegex.test(value)) { + data[key] = this.currentMachineId; + } else if (emailRegex.test(value)) { + data[key] = this.currentMachineId; + } else if (subscriptionRegex.test(value)) { + data[key] = 'trial'; + } + } + + this.usage++; + return data; + } +} + +// Constants +const productList = fs.readFileSync(path.join(__dirname, 'products.txt'), 'utf8').trim().split('\n').map(Number); +const daysLeft = Math.floor((new Date('9999-12-31T23:59:59.999Z') - new Date()) / 1000 / 60 / 60 / 24); +const currentDate = new Date(); +const currentVersion = fs.readFileSync(path.join(__dirname, 'version.txt'), 'utf8').trim(); +const randomToken = crypto.randomBytes(16).toString('hex'); + +// Functions +function parseBoundary(data, boundary) { + const parts = data.split(`--${boundary}`).filter(part => part.trim() && part !== '--'); + const formData = {}; + + parts.forEach(part => { + const match = /Content-Disposition: form-data; name="([^"]+)"\r\n\r\n([\s\S]+)/.exec(part); + if (match) { + formData[match[1]] = match[2].trim(); + } + }); + + return formData; +} + +function buildMultipartBody(data, boundary) { + let body = ''; + for (const [key, value] of Object.entries(data)) { + body += `--${boundary}\r\nContent-Disposition: form-data; name="${key}"\r\n\r\n${value}\r\n`; + } + body += `--${boundary}--\r\n`; + return body; +} + +function modifyDRMRequest(session, request) { + if (request.headers['content-type'] && request.headers['content-type'].includes('multipart/form-data')) { + const boundaryMatch = /boundary=(.+)/.exec(request.headers['content-type']); + if (boundaryMatch) { + const boundary = boundaryMatch[1]; + const formData = parseBoundary(request.body.buffer.toString(), boundary); + const patchedData = session.patchBoundary(formData); + const newBody = buildMultipartBody(patchedData, boundary); + + request.body = Buffer.from(newBody); + request.headers['content-length'] = Buffer.byteLength(newBody).toString(); + } + } + + return request; +} + +// Runtime +const ticket = ['1']; +productList.forEach((product) => { + ticket.push(`${product}:${253402300799}`); +}); +ticket.push(`VP:${daysLeft}`); +ticket.push(`OV:${currentVersion}`); +ticket.push('BV:'); +ticket.push(`AD:1`); +ticket.push('SUB:'); +ticket.push('UT:0'); +ticket.push('ML:1-11-1'); +ticket.push(`S:${randomToken}`); +ticket.push(`TI:${Math.floor(currentDate.getTime() / 1000)}`); +ticket.push('TM:0'); + +// Proxy +(async () => { + const https = await mockttp.generateCACertificate(); + const proxyServer = mockttp.getLocal({ https }); + const session = new Session(); + + proxyServer.forPost(/https:\/\/.+\/auth\/v[0-5]+\//).thenReply(200, + ticket.join('|') + ); + + proxyServer.forPost(/https:\/\/.+\/ak\/uc_v[0-9]+\//).thenPassThrough({ + beforeRequest: async (request) => { + modifyDRMRequest(session, request); + } + }); + + proxyServer.forPost(/http:\/\/ssl\.dvdfab\.cn\/auth\/trial_disc.php/).thenPassThrough({ + beforeRequest: async (request) => { + modifyDRMRequest(session, request); + } + }); + + proxyServer.forPost(/https:\/\/ssl.dvdfab.cn\/update\/recommend.php/).thenReply(200, + '{"cscode":0,"version":1,"enable":true,"data":[]}', + { 'content-type': 'text/html' } + ); + + proxyServer.forPost(/https:\/\/ssl-jp.dvdfab.cn\/ak\/v[0-9]+\/st\//).thenPassThrough({ + beforeRequest: async (request) => { + modifyDRMRequest(session, request); + } + }); + + proxyServer.forPost(/https:\/\/.+\/client\/command/).thenCloseConnection(); + + proxyServer.forPost(/https:\/\/servo-slave-.+\.dvdfab\.cn\/recommend\/client\/best\/list/).thenReply(200, + '{"cscode":200,"version":"1.0.0","data":[]}', + { 'content-type': 'application/json' } + ); + + proxyServer.forPost(/https:\/\/app-api-c[0-9]+\.dvdfab\.cn\/api\/info_control\//).thenReply(200, + '{"result":{"res":"true","msg":"ok","data":[]}}', + { 'content-type': 'text/html; charset=UTF-8' } + ); + + proxyServer.forPost(/https:\/\/drm-u[0-9]+\.dvdfab\.cn\/ak\/re\/downloadex\//).thenReply(200, + '{"R":"0","num":100,"count":"100"}', + { 'content-type': 'text/html; charset=UTF-8' } + ); + + proxyServer.forPost(/https:\/\/drm-u[0-9]+\.dvdfab\.cn\/ak\/re\/keycheck\//).thenReply(200, + '{"R":"0","ret":"success"}', + { 'content-type': 'text/html; charset=UTF-8' } + ); + + proxyServer.forPost(/https:\/\/app-api-c[0-9]+\.dvdfab\.cn\/api\/common_json_post\//).thenReply(200, + '{"result":{"res":true,"msg":"NO_ERROR"}}' + ); + + proxyServer.forPost(/https:\/\/.+\/\/products\/client_upgrade_license_v2/).thenCloseConnection(); + + await proxyServer.forUnmatchedRequest().thenPassThrough(); + + proxyServer.start(8000) + + console.log(); + console.log(' +=============================================================+'); + console.log(' | DVDFab Server Emulator has started... |'); + console.log(' | You may now use any DVDFab software. |'); + console.log(' +=============================================================+'); + console.log(' | If you got this script from anywhere apart from |'); + console.log(' | The CDM-Project, you likely have a malicious copy. |'); + console.log(' +=============================================================+'); + console.log(' | ONCE YOU ARE READY, DO NOT JUST CLOSE THE EMULATOR! |'); + console.log(' | Using CTRL-C will properly disconnect you from the proxy. |'); + console.log(' | If you used the CLOSE BUTTON, run the \'stop.bat\' script. |'); + console.log(' +=============================================================+'); + console.log(); +})(); diff --git a/stop.bat b/stop.bat new file mode 100644 index 0000000..a2e3c84 --- /dev/null +++ b/stop.bat @@ -0,0 +1,5 @@ +@echo off +taskkill /f /im "node.exe*" +setx http_proxy "" +setx https_proxy "" +exit /b 0 diff --git a/version.txt b/version.txt new file mode 100644 index 0000000..6c06e84 --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +6197 \ No newline at end of file