diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..94f32f9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,37 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +# [1.0.2] - 2024-00-00 + +### Fixed + +- Corrected the regex for patching MAC IDs. +- Added a missing dependency to the requirements. + +# [1.0.1] - 2024-08-04 + +### Added + +- Added a feature to bypass XML detection, enhancing compatibility with newer DVDFab versions. +- Implemented a block for usage limit requests when the user is not connected, removing restrictions on non-connected sessions. +- Removed recommendation ads, providing a cleaner and more focused user experience. +- Added a compiled executable (`.exe`) for Windows in the `dist` directory, facilitating easier deployment on Windows systems. + +### Changed + +- Improved the reuse of session identifiers, ensuring smoother transitions and fewer disruptions during repeated use. + +### Fixed + +- Fixed an error where email replacement was not functioning correctly, ensuring that all email-related operations are handled properly. + +## [1.0.0] - 2024-08-01 + +- Initial release of the project, laying the foundation for future enhancements and features. + +[1.0.2]: https://cdm-project.com/Download-Tools/DVDFabLifetimeActivation +[1.0.1]: https://cdm-project.com/Download-Tools/DVDFabLifetimeActivation +[1.0.0]: https://cdm-project.com/Download-Tools/DVDFabLifetimeActivation diff --git a/README.md b/README.md index 152c5b4..190bf4e 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,11 @@ A method to activate DVDFab products using a proxy approach that does not impact 1. **HTTP Toolkit**: Download and install from [httptoolkit.com](https://httptoolkit.com/) 2. **DVDFab Product**: Ensure you have the DVDFab product (e.g., StreamFab) installed on your system. +3. **Python**: Ensure you have Python installed on your system. +4. **Required Python Packages**: Install the required packages using pip: + ````shell + pip install -r requirements.txt + ```` ## Installation & Usage @@ -61,13 +66,23 @@ In case you encounter errors related to MAC detection: ### Step 6: Enabling Additional Rules in HTTP Toolkit (If Needed) -1. **Access HTTP Toolkit Configuration:** +1. **Access HTTP Toolkit Configuration**: - Go to the `Mock` tab where you imported the `HTTPToolkit_DVDFab.htkrules` file. -2. **Enable Additional Rules:** +2. **Enable Additional Rules**: - In the rules configuration, locate the index `2` section. This rule is disabled by default. - Enable this rule to apply additional patches required for your specific configuration. -### Step 7: Enjoy Activated Features +### Step 7: Disabling XML Check (Optional) + +If you encounter issues related to XML checks, you can disable this check by enabling a specific rule in HTTP Toolkit: + +1. **Access HTTP Toolkit Configuration:** + - Go to the Mock tab where you imported the HTTPToolkit_DVDFab.htkrules file. +2. **Enable XML Check Disabling Rule:** + - In the rules configuration, locate the index `3` section. This rule is disabled by default. + - Enable this rule to disable the XML check. + +### Step 8: Enjoy Activated Features 1. The requests are now patched, and you can enjoy all functionalities. ![Mock Requests](docs/images/mock_requests.png) diff --git a/docs/images/favicon.ico b/docs/images/favicon.ico new file mode 100644 index 0000000..d905cb7 Binary files /dev/null and b/docs/images/favicon.ico differ diff --git a/src/requirements.txt b/src/requirements.txt new file mode 100644 index 0000000..fabb5f1 --- /dev/null +++ b/src/requirements.txt @@ -0,0 +1,5 @@ +pathlib~=1.0.1 +requests~=2.31.0 +xmltodict~=0.13.0 +Flask~=3.0.3 +requests-toolbelt~=1.0.0 \ No newline at end of file diff --git a/src/rules.py b/src/rules.py index d2c724d..5b1cdc6 100644 --- a/src/rules.py +++ b/src/rules.py @@ -36,7 +36,7 @@ if __name__ == '__main__': print('Expiration Date:', expire.isoformat()) # Additional details - version = 6191 + version = 6192 adds = True token = secrets.token_hex(16) # Generate a random token @@ -49,7 +49,7 @@ if __name__ == '__main__': ticket.append(f'AD:{int(adds)}') # Adds flag ticket.append('SUB:') # Placeholder for subscription details ticket.append('UT:0') # User type - ticket.append('ML:1-11-1') # Machine limits or other codes + ticket.append('ML:1-11-1') # Machine limits or other codes (current/total/used) ticket.append(f'S:{token}') # Security token ticket.append(f'TI:{int(current.timestamp())}') # Ticket issuance time ticket.append('TM:0') # Placeholder for additional time management diff --git a/src/server.py b/src/server.py index 7802961..4115cef 100644 --- a/src/server.py +++ b/src/server.py @@ -1,20 +1,15 @@ import re import secrets -from random import SystemRandom +import string import requests +import xmltodict from flask import Flask, request, Response from requests_toolbelt import MultipartEncoder -app = Flask(__name__) -app.config.update( - DEBUG=True, - SECRET_KEY=secrets.token_hex(16), - ALLOWED_HOSTS=['127.0.0.1', 'localhost'] -) - +# utils.py def parse_boundary(data: str) -> dict: """ Parse the multipart form data to extract fields. @@ -39,39 +34,83 @@ def parse_boundary(data: str) -> dict: return fields -def patch_boundary(data: dict) -> dict: - """ - Modify specific fields in the data dictionary based on predefined rules. +# session.py +class Session: + DOMAINS = ['hotmail.com', 'gmail.com', 'yahoo.com', 'outlook.com', 'protonmail.com', 'yandex.com'] + CHARACTERS = string.ascii_lowercase + string.digits + '.-' - Parameters: - - data (dict): A dictionary containing form field names and values. + def __init__(self): + self.email = None + self.machine_id = None + self.usage = 0 + self.__update() - Returns: - - dict: The modified dictionary with updated field values. - """ - # Generate a new machine ID - machine_id = [''.join(SystemRandom().choice('abcdef0123456789') for _ in range(2)) for _ in range(12)] - machine_id = ':'.join(['-'.join(machine_id[:6]), '-'.join(machine_id[6:])]) + def __update(self) -> None: + """ + Update the session with a new random email and machine ID. + """ + length = secrets.choice(range(5, 15)) + self.email = '{local}@{domain}'.format( + local=''.join(secrets.choice(self.CHARACTERS) for _ in range(length)), + domain=secrets.choice(Session.DOMAINS) + ) - # Update fields based on rules - for key, value in data.items(): - if key in ('TK', 'PW', 'D', 'F') and re.match(r'[0-9a-f]{32}', value): - data[key] = value # '' # Replace token - if re.match(r'^([0-9a-f]{2}-){5}[0-9a-f]{2}:([0-9a-f]{2}-){5}[0-9a-f]{2}', value): - data[key] = machine_id # Replace MAC - elif re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', value): - fake_email_user = ''.join(SystemRandom().choice('abcdefghijklmnopqrstuvwxyz.-0123456789') for _ in range(SystemRandom().randint(8, 16))) - fake_email_provider = SystemRandom().choice(["@gmail.com", "@hotmail.com", "@yahoo.com"]) - data[key] = fake_email_user + fake_email_provider # Replace email - elif re.match(r'^365$', value): - data[key] = 'trial' # Replace subscription + self.machine_id = ':'.join([ + '-'.join(f'{b:02x}' for b in secrets.token_bytes(6)), + '-'.join(f'{b:02x}' for b in secrets.token_bytes(6)) + ]) + print(f'Email: {self.email}') + print(f'Machine ID: {self.machine_id}') - return data + def __repr__(self) -> str: + return '{name}({items})'.format( + name=self.__class__.__name__, + items=', '.join([f'{k}={repr(v)}' for k, v in self.__dict__.items()]) + ) + + def patch_boundary(self, data: dict) -> dict: + """ + Modify specific fields in the data dictionary based on predefined rules. + + Parameters: + - data (dict): A dictionary containing form field names and values. + + Returns: + - dict: The modified dictionary with updated field values. + """ + if self.usage == 3: + self.usage = 0 + self.__update() + + # Update fields based on rules + for key, value in data.items(): + if key in ('TK', 'PW', 'D', 'F') and re.match(r'[0-9a-f]{32}', value): + data[key] = value # '' # Replace token + if re.match(r'^([0-9a-f]{2}-){5}[0-9a-f]{2}(:([0-9a-f]{2}-){5}[0-9a-f]{2})?', value): + data[key] = self.machine_id # Replace MAC + elif re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', value): + data[key] = self.email # Replace email + elif re.match(r'^365$', value): + data[key] = 'trial' # Replace subscription + + self.usage += 1 + return data + + +# app.py +session = Session() +app = Flask(__name__) +app.config.update( + DEBUG=True, + SECRET_KEY=secrets.token_hex(16), + ALLOWED_HOSTS=['127.0.0.1', 'localhost'] +) @app.route('/', defaults={'path': ''}) @app.route('/', methods=['POST']) def catch_all(path: str) -> Response: + global session """ Handle all POST requests, modify the data, and forward it to the same URL over HTTPS. @@ -81,34 +120,43 @@ def catch_all(path: str) -> Response: Returns: - Response: A Flask Response object with the status and content of the forwarded request. """ - # Get the raw request data - body = request.get_data().decode('utf-8') + is_auth = re.match(r'^auth/trial_disc\.php', path) - # Parse the multipart form data and modify it - boundary = parse_boundary(body) - patched = patch_boundary(boundary) - - # Create a MultipartEncoder with the modified data - mp_encoder = MultipartEncoder(patched) - - # Prepare headers for the forwarded request headers = dict(request.headers) del headers['Connection'] del headers['Content-Length'] - headers['Content-Type'] = mp_encoder.content_type + + body = request.get_data() + if not is_auth: + boundary = parse_boundary(body.decode('utf-8')) + patched = session.patch_boundary(boundary) + mp_encoder = MultipartEncoder(patched) + + headers['Content-Type'] = mp_encoder.content_type + body = mp_encoder.read() # Forward the request to the same URL over HTTPS r = requests.request( method=request.method, url=request.url.replace('http://', 'https://'), params=request.args.to_dict(), - data=mp_encoder, + data=body, headers=headers, cookies=request.cookies ) + content = r.content + + if is_auth: + data = xmltodict.parse(content) + if data.get('TrialOpenDiscInfo', {}).get('@MacID'): + data['TrialOpenDiscInfo']['@MacID'] = session.machine_id + + if data.get('TrialOpenDiscInfo', {}).get('DiscInfo'): + del data['TrialOpenDiscInfo']['DiscInfo'] + content = xmltodict.unparse(data, pretty=False, full_document=False) # Return the response from the forwarded request - return Response(status=r.status_code, response=r.content) + return Response(status=r.status_code, response=content) # Run the Flask app diff --git a/src/template.json b/src/template.json index c40116f..0e13b24 100644 --- a/src/template.json +++ b/src/template.json @@ -22,7 +22,24 @@ "handler": { "data": { "type": "Buffer", - "data": null + "data": [ + 89, + 79, + 85, + 82, + 95, + 65, + 85, + 84, + 72, + 95, + 84, + 73, + 67, + 75, + 69, + 84 + ] }, "status": 200, "headers": { @@ -50,9 +67,6 @@ "regexFlags": "" } ], - "completionChecker": { - "type": "always" - }, "handler": { "uiType": "forward-to-host", "type": "passthrough", @@ -60,6 +74,122 @@ "targetHost": "http://127.0.0.1:5000", "updateHostHeader": false } + }, + "completionChecker": { + "type": "always" + } + }, + { + "id": "21f9f6b0-a1d3-4c81-993b-d573ac224158", + "type": "http", + "activated": false, + "matchers": [ + { + "method": 1, + "type": "method", + "uiType": "POST" + }, + { + "type": "regex-path", + "regexSource": "http:\\/\\/ssl\\.dvdfab\\.cn\\/auth\\/trial_disc.php", + "regexFlags": "" + }, + { + "type": "query", + "queryObject": { + "Mode": "Download" + } + } + ], + "handler": { + "uiType": "forward-to-host", + "type": "passthrough", + "forwarding": { + "targetHost": "http://127.0.0.1:5000", + "updateHostHeader": false + } + }, + "completionChecker": { + "type": "always" + } + }, + { + "id": "ca9eadc4-1fad-43b5-907a-d128f6806cdd", + "type": "http", + "activated": true, + "matchers": [ + { + "method": 1, + "type": "method", + "uiType": "POST" + }, + { + "path": "https://ssl.dvdfab.cn/update/recommend.php", + "type": "simple-path" + } + ], + "handler": { + "data": { + "type": "Buffer", + "data": [ + 123, + 34, + 99, + 115, + 99, + 111, + 100, + 101, + 34, + 58, + 48, + 44, + 34, + 118, + 101, + 114, + 115, + 105, + 111, + 110, + 34, + 58, + 49, + 44, + 34, + 101, + 110, + 97, + 98, + 108, + 101, + 34, + 58, + 116, + 114, + 117, + 101, + 44, + 34, + 100, + 97, + 116, + 97, + 34, + 58, + 91, + 93, + 125 + ] + }, + "status": 200, + "headers": { + "content-type": "text/html" + }, + "type": "simple" + }, + "completionChecker": { + "type": "always" } }, { @@ -77,9 +207,6 @@ "type": "simple-path" } ], - "completionChecker": { - "type": "always" - }, "handler": { "data": { "type": "Buffer", @@ -100,6 +227,9 @@ "content-type": "text/html; charset=UTF-8" }, "type": "simple" + }, + "completionChecker": { + "type": "always" } }, { @@ -117,11 +247,51 @@ "type": "simple-path" } ], - "completionChecker": { - "type": "always" - }, "handler": { "type": "close-connection" + }, + "completionChecker": { + "type": "always" + } + }, + { + "id": "73edc756-fee2-46bf-ba9a-c8085e2d6f79", + "type": "http", + "activated": true, + "matchers": [ + { + "method": 1, + "type": "method", + "uiType": "POST" + }, + { + "type": "regex-path", + "regexSource": "http:\\/\\/ssl\\.dvdfab\\.cn\\/auth\\/trial_disc\\.php", + "regexFlags": "" + }, + { + "type": "query", + "queryObject": { + "Mode": "Upload" + } + } + ], + "handler": { + "data": { + "type": "Buffer", + "data": [ + 79, + 75 + ] + }, + "status": 200, + "headers": { + "content-type": "text/html" + }, + "type": "simple" + }, + "completionChecker": { + "type": "always" } }, { @@ -136,13 +306,10 @@ }, { "type": "regex-path", - "regexSource": "https:\\/\\/servo-slave-[0-9]+\\.dvdfab\\.cn\\/recommend\\/client\\/best\\/list", + "regexSource": "https:\\/\\/servo-slave-.+\\.dvdfab\\.cn\\/recommend\\/client\\/best\\/list", "regexFlags": "" } ], - "completionChecker": { - "type": "always" - }, "handler": { "data": { "type": "Buffer", @@ -196,6 +363,9 @@ "content-type": "application/json" }, "type": "simple" + }, + "completionChecker": { + "type": "always" } }, { @@ -214,9 +384,6 @@ "regexFlags": "" } ], - "completionChecker": { - "type": "always" - }, "handler": { "data": { "type": "Buffer", @@ -264,531 +431,6 @@ 34, 58, 91, - 123, - 34, - 112, - 111, - 115, - 95, - 105, - 100, - 34, - 58, - 49, - 44, - 34, - 105, - 100, - 34, - 58, - 45, - 49, - 44, - 34, - 101, - 110, - 97, - 98, - 108, - 101, - 34, - 58, - 102, - 97, - 108, - 115, - 101, - 44, - 34, - 112, - 111, - 115, - 34, - 58, - 34, - 65, - 100, - 32, - 119, - 104, - 101, - 110, - 32, - 97, - 112, - 112, - 32, - 115, - 116, - 97, - 114, - 116, - 34, - 44, - 34, - 114, - 101, - 99, - 111, - 109, - 109, - 101, - 110, - 100, - 95, - 100, - 97, - 116, - 97, - 34, - 58, - 34, - 34, - 125, - 44, - 123, - 34, - 112, - 111, - 115, - 95, - 105, - 100, - 34, - 58, - 50, - 44, - 34, - 105, - 100, - 34, - 58, - 53, - 48, - 48, - 50, - 44, - 34, - 101, - 110, - 97, - 98, - 108, - 101, - 34, - 58, - 116, - 114, - 117, - 101, - 44, - 34, - 112, - 111, - 115, - 34, - 58, - 34, - 78, - 101, - 119, - 32, - 112, - 114, - 111, - 100, - 117, - 99, - 116, - 32, - 97, - 100, - 32, - 119, - 104, - 101, - 110, - 32, - 97, - 112, - 112, - 32, - 115, - 116, - 97, - 114, - 116, - 34, - 44, - 34, - 114, - 101, - 99, - 111, - 109, - 109, - 101, - 110, - 100, - 95, - 100, - 97, - 116, - 97, - 34, - 58, - 34, - 34, - 125, - 44, - 123, - 34, - 112, - 111, - 115, - 95, - 105, - 100, - 34, - 58, - 53, - 44, - 34, - 105, - 100, - 34, - 58, - 53, - 48, - 48, - 48, - 44, - 34, - 101, - 110, - 97, - 98, - 108, - 101, - 34, - 58, - 116, - 114, - 117, - 101, - 44, - 34, - 112, - 111, - 115, - 34, - 58, - 34, - 65, - 100, - 32, - 97, - 116, - 32, - 98, - 111, - 116, - 116, - 111, - 109, - 32, - 111, - 102, - 32, - 117, - 105, - 34, - 44, - 34, - 114, - 101, - 99, - 111, - 109, - 109, - 101, - 110, - 100, - 95, - 100, - 97, - 116, - 97, - 34, - 58, - 34, - 34, - 125, - 44, - 123, - 34, - 112, - 111, - 115, - 95, - 105, - 100, - 34, - 58, - 54, - 44, - 34, - 105, - 100, - 34, - 58, - 45, - 49, - 44, - 34, - 101, - 110, - 97, - 98, - 108, - 101, - 34, - 58, - 102, - 97, - 108, - 115, - 101, - 44, - 34, - 112, - 111, - 115, - 34, - 58, - 34, - 65, - 100, - 32, - 97, - 116, - 32, - 108, - 101, - 102, - 116, - 32, - 98, - 111, - 116, - 116, - 111, - 109, - 32, - 99, - 111, - 114, - 110, - 101, - 114, - 34, - 44, - 34, - 114, - 101, - 99, - 111, - 109, - 109, - 101, - 110, - 100, - 95, - 100, - 97, - 116, - 97, - 34, - 58, - 34, - 34, - 125, - 44, - 123, - 34, - 112, - 111, - 115, - 95, - 105, - 100, - 34, - 58, - 50, - 48, - 48, - 48, - 44, - 34, - 105, - 100, - 34, - 58, - 49, - 49, - 48, - 50, - 44, - 34, - 101, - 110, - 97, - 98, - 108, - 101, - 34, - 58, - 116, - 114, - 117, - 101, - 44, - 34, - 112, - 111, - 115, - 34, - 58, - 34, - 65, - 100, - 32, - 105, - 110, - 32, - 108, - 105, - 118, - 101, - 117, - 112, - 100, - 97, - 116, - 101, - 32, - 100, - 105, - 97, - 108, - 111, - 103, - 34, - 44, - 34, - 114, - 101, - 99, - 111, - 109, - 109, - 101, - 110, - 100, - 95, - 100, - 97, - 116, - 97, - 34, - 58, - 34, - 34, - 125, - 44, - 123, - 34, - 112, - 111, - 115, - 95, - 105, - 100, - 34, - 58, - 56, - 44, - 34, - 105, - 100, - 34, - 58, - 53, - 49, - 48, - 51, - 44, - 34, - 101, - 110, - 97, - 98, - 108, - 101, - 34, - 58, - 116, - 114, - 117, - 101, - 44, - 34, - 112, - 111, - 115, - 34, - 58, - 34, - 65, - 100, - 32, - 97, - 116, - 32, - 108, - 105, - 99, - 101, - 110, - 115, - 101, - 32, - 105, - 110, - 102, - 111, - 34, - 44, - 34, - 114, - 101, - 99, - 111, - 109, - 109, - 101, - 110, - 100, - 95, - 100, - 97, - 116, - 97, - 34, - 58, - 34, - 34, - 125, 93, 125, 125 @@ -799,6 +441,9 @@ "content-type": "text/html; charset=UTF-8" }, "type": "simple" + }, + "completionChecker": { + "type": "always" } }, { @@ -952,9 +597,6 @@ "regexFlags": "" } ], - "completionChecker": { - "type": "always" - }, "handler": { "data": { "type": "Buffer", @@ -991,6 +633,9 @@ "content-type": "text/html; charset=UTF-8" }, "type": "simple" + }, + "completionChecker": { + "type": "always" } }, { @@ -1009,9 +654,6 @@ "regexFlags": "" } ], - "completionChecker": { - "type": "always" - }, "handler": { "uiType": "forward-to-host", "type": "passthrough", @@ -1019,6 +661,9 @@ "targetHost": "http://127.0.0.1:5000", "updateHostHeader": false } + }, + "completionChecker": { + "type": "always" } }, { @@ -1103,7 +748,7 @@ ], "handler": { "status": 200, - "filePath": null, + "filePath": "C:\\Users\\hyugogirubato\\AppData\\Local\\httptoolkit\\Config\\ca.pem", "headers": { "content-type": "application/x-x509-ca-cert" },