ffproto 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ffproto-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,47 @@
1
+ Metadata-Version: 2.4
2
+ Name: ffproto
3
+ Version: 1.0.0
4
+ Summary: Encryption/decryption library for Free Fire HTTP protobuf payloads
5
+ Home-page: https://github.com/yourusername/ffproto
6
+ Author: RedZed / PARAHEX
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.6
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: pycryptodome>=3.9.0
13
+ Requires-Dist: protobuf>=3.0.0
14
+ Dynamic: author
15
+ Dynamic: classifier
16
+ Dynamic: description
17
+ Dynamic: description-content-type
18
+ Dynamic: home-page
19
+ Dynamic: requires-dist
20
+ Dynamic: requires-python
21
+ Dynamic: summary
22
+
23
+ from setuptools import setup, find_packages
24
+
25
+ with open("README.md", "r", encoding="utf-8") as fh:
26
+ long_description = fh.read()
27
+
28
+ setup(
29
+ name="ffproto",
30
+ version="1.0.0",
31
+ author="Eren",
32
+ description="Encryption/decryption library for Free Fire HTTP protobuf payloads",
33
+ long_description=long_description,
34
+ long_description_content_type="text/markdown",
35
+ url="https://github.com/yourusername/ffproto",
36
+ packages=find_packages(),
37
+ classifiers=[
38
+ "Programming Language :: Python :: 3",
39
+ "License :: OSI Approved :: MIT License",
40
+ "Operating System :: OS Independent",
41
+ ],
42
+ python_requires=">=3.6",
43
+ install_requires=[
44
+ "pycryptodome>=3.9.0",
45
+ "protobuf>=3.0.0",
46
+ ],
47
+ )
@@ -0,0 +1,25 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ with open("README.md", "r", encoding="utf-8") as fh:
4
+ long_description = fh.read()
5
+
6
+ setup(
7
+ name="ffproto",
8
+ version="1.0.0",
9
+ author="Eren",
10
+ description="Encryption/decryption library for Free Fire HTTP protobuf payloads",
11
+ long_description=long_description,
12
+ long_description_content_type="text/markdown",
13
+ url="https://github.com/yourusername/ffproto",
14
+ packages=find_packages(),
15
+ classifiers=[
16
+ "Programming Language :: Python :: 3",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: OS Independent",
19
+ ],
20
+ python_requires=">=3.6",
21
+ install_requires=[
22
+ "pycryptodome>=3.9.0",
23
+ "protobuf>=3.0.0",
24
+ ],
25
+ )
@@ -0,0 +1,22 @@
1
+ """
2
+ ffproto – Free Fire protobuf encryption/decryption library.
3
+ """
4
+
5
+ from .core import (
6
+ build_payload,
7
+ encrypt_payload,
8
+ decrypt_payload,
9
+ decode_protobuf,
10
+ encrypt_fields,
11
+ decrypt_hex,
12
+ )
13
+
14
+ __version__ = "1.0.0"
15
+ __all__ = [
16
+ "build_payload",
17
+ "encrypt_payload",
18
+ "decrypt_payload",
19
+ "decode_protobuf",
20
+ "encrypt_fields",
21
+ "decrypt_hex",
22
+ ]
@@ -0,0 +1,54 @@
1
+ """
2
+ High-level functions: build, encrypt, decrypt, and decode.
3
+ """
4
+
5
+ from typing import Dict, Any, Union
6
+ from .protobuf import build_payload, decode_protobuf, convert_keys_to_str
7
+ from .crypto import encrypt_packet, decrypt_packet
8
+
9
+
10
+ def encrypt_fields(fields: Dict[int, Any]) -> bytes:
11
+ """
12
+ Build protobuf from dict, encrypt it, and return ciphertext bytes.
13
+ """
14
+ plaintext = build_payload(fields)
15
+ return encrypt_packet(plaintext)
16
+
17
+
18
+ def encrypt_fields_hex(fields: Dict[int, Any]) -> str:
19
+ """Same as encrypt_fields but returns hex string."""
20
+ return encrypt_fields(fields).hex()
21
+
22
+
23
+ def decrypt_hex(hex_data: str) -> Dict[int, Any]:
24
+ """
25
+ Decrypt hex string, then decode protobuf, returning dict with int keys.
26
+ If decryption fails, tries to decode as plain protobuf.
27
+ """
28
+ try:
29
+ data = bytes.fromhex(hex_data)
30
+ except ValueError:
31
+ raise ValueError("Invalid hex string")
32
+
33
+ # Try decryption
34
+ try:
35
+ plaintext = decrypt_packet(data)
36
+ except Exception:
37
+ # Maybe it's already plaintext
38
+ plaintext = data
39
+
40
+ # Decode protobuf
41
+ try:
42
+ decoded = decode_protobuf(plaintext)
43
+ except Exception as e:
44
+ raise ValueError(f"Failed to decode protobuf: {e}")
45
+
46
+ return decoded
47
+
48
+
49
+ def decrypt_hex_json(hex_data: str) -> str:
50
+ """
51
+ Decrypt and decode, then return JSON string with string keys.
52
+ """
53
+ decoded = decrypt_hex(hex_data)
54
+ return json.dumps(convert_keys_to_str(decoded), indent=2, ensure_ascii=False, default=str)
@@ -0,0 +1,43 @@
1
+ """
2
+ AES‑CBC encryption/decryption with fixed key/IV.
3
+ """
4
+
5
+ from Crypto.Cipher import AES
6
+
7
+ # Fixed key and IV (taken from the original code)
8
+ AES_KEY = bytes([89, 103, 38, 116, 99, 37, 68, 69, 117, 104, 54, 37, 90, 99, 94, 56])
9
+ AES_IV = bytes([54, 111, 121, 90, 68, 114, 50, 50, 69, 51, 121, 99, 104, 106, 77, 37])
10
+
11
+
12
+ def pkcs7_pad(data: bytes, block_size: int = 16) -> bytes:
13
+ """Apply PKCS#7 padding."""
14
+ pad_len = block_size - (len(data) % block_size)
15
+ return data + bytes([pad_len]) * pad_len
16
+
17
+
18
+ def pkcs7_unpad(data: bytes) -> bytes:
19
+ """Remove PKCS#7 padding."""
20
+ pad = data[-1]
21
+ if pad < 1 or pad > 16:
22
+ raise ValueError("Invalid padding")
23
+ return data[:-pad]
24
+
25
+
26
+ def encrypt_packet(plaintext: bytes) -> bytes:
27
+ """
28
+ Encrypt plaintext bytes with AES‑CBC (fixed key/IV).
29
+ Returns ciphertext.
30
+ """
31
+ cipher = AES.new(AES_KEY, AES.MODE_CBC, AES_IV)
32
+ padded = pkcs7_pad(plaintext)
33
+ return cipher.encrypt(padded)
34
+
35
+
36
+ def decrypt_packet(ciphertext: bytes) -> bytes:
37
+ """
38
+ Decrypt ciphertext with AES‑CBC and remove padding.
39
+ Returns plaintext bytes.
40
+ """
41
+ cipher = AES.new(AES_KEY, AES.MODE_CBC, AES_IV)
42
+ padded = cipher.decrypt(ciphertext)
43
+ return pkcs7_unpad(padded)
@@ -0,0 +1,173 @@
1
+ """
2
+ Protobuf encoding/decoding (varint, length-delimited, nested messages).
3
+ Mirrors the logic in xPARA.py and the original decoder.
4
+ """
5
+
6
+ from typing import Dict, Any, Union, List
7
+ from google.protobuf.internal.decoder import _DecodeVarint, _DecodeVarint32
8
+
9
+
10
+ def encode_varint(value: int) -> bytes:
11
+ """Encode an integer as protobuf varint."""
12
+ out = []
13
+ while True:
14
+ b = value & 0x7F
15
+ value >>= 7
16
+ if value:
17
+ out.append(b | 0x80)
18
+ else:
19
+ out.append(b)
20
+ break
21
+ return bytes(out)
22
+
23
+
24
+ def encode_packed_varints(field_num: int, values: List[int]) -> bytes:
25
+ """Encode a repeated int field as packed varints (wire type 2)."""
26
+ packed_data = b''.join(encode_varint(v) for v in values)
27
+ tag = (field_num << 3) | 2
28
+ return encode_varint(tag) + encode_varint(len(packed_data)) + packed_data
29
+
30
+
31
+ def encode_field(field_num: int, value: Any) -> bytes:
32
+ """
33
+ Encode a single field (scalar, string, bytes, or nested message).
34
+ Raises TypeError for unsupported types.
35
+ """
36
+ if isinstance(value, int):
37
+ tag = (field_num << 3) | 0
38
+ return encode_varint(tag) + encode_varint(value)
39
+ elif isinstance(value, str):
40
+ data = value.encode('utf-8')
41
+ tag = (field_num << 3) | 2
42
+ return encode_varint(tag) + encode_varint(len(data)) + data
43
+ elif isinstance(value, bytes):
44
+ tag = (field_num << 3) | 2
45
+ return encode_varint(tag) + encode_varint(len(value)) + value
46
+ elif isinstance(value, dict):
47
+ sub_payload = build_payload(value)
48
+ tag = (field_num << 3) | 2
49
+ return encode_varint(tag) + encode_varint(len(sub_payload)) + sub_payload
50
+ else:
51
+ raise TypeError(f"Unsupported type for field {field_num}: {type(value)}")
52
+
53
+
54
+ def build_payload(fields: Dict[int, Any]) -> bytes:
55
+ """
56
+ Build a protobuf payload from a dict.
57
+ Keys are field numbers (int), values are:
58
+ - int (varint)
59
+ - str (UTF-8 string)
60
+ - bytes (raw bytes)
61
+ - dict (nested message)
62
+ - list of ints (packed varints, all ints)
63
+ - list of other values (repeated fields)
64
+ """
65
+ payload = b''
66
+ for key, value in fields.items():
67
+ field_num = int(key)
68
+ if isinstance(value, list):
69
+ # If all ints, use packed encoding (wire type 2)
70
+ if value and all(isinstance(v, int) for v in value):
71
+ payload += encode_packed_varints(field_num, value)
72
+ else:
73
+ # Repeated non-int: encode each as separate field
74
+ for elem in value:
75
+ payload += encode_field(field_num, elem)
76
+ else:
77
+ payload += encode_field(field_num, value)
78
+ return payload
79
+
80
+
81
+ # ---------- Decoding ----------
82
+
83
+ def try_decode_packed_varints(data: bytes) -> List[int]:
84
+ """Attempt to parse data as packed varints."""
85
+ pos = 0
86
+ values = []
87
+ while pos < len(data):
88
+ val, pos = _DecodeVarint(data, pos)
89
+ values.append(val)
90
+ if pos != len(data):
91
+ raise ValueError("Not all bytes consumed")
92
+ return values
93
+
94
+
95
+ def smart_decode(raw: bytes) -> Any:
96
+ """
97
+ Try to decode raw bytes as protobuf, then as text, then as packed varints.
98
+ Fallback to raw bytes.
99
+ """
100
+ # Try nested protobuf
101
+ try:
102
+ return decode_protobuf(raw)
103
+ except Exception:
104
+ pass
105
+ # Try UTF-8 text (if mostly printable)
106
+ try:
107
+ text = raw.decode('utf-8')
108
+ if sum(c.isprintable() for c in text) / max(len(text), 1) > 0.8:
109
+ return text
110
+ except Exception:
111
+ pass
112
+ # Try packed varints
113
+ try:
114
+ return try_decode_packed_varints(raw)
115
+ except Exception:
116
+ pass
117
+ # Return raw bytes (hex representation in final output)
118
+ return raw
119
+
120
+
121
+ def decode_protobuf(data: bytes) -> Dict[int, Any]:
122
+ """
123
+ Decode a protobuf message into a dict with int keys.
124
+ Supports varint, length-delimited, 32-bit, and 64-bit wire types.
125
+ """
126
+ pos = 0
127
+ length = len(data)
128
+ fields = {}
129
+ while pos < length:
130
+ key, pos = _DecodeVarint(data, pos)
131
+ field_num = key >> 3
132
+ wire_type = key & 7
133
+
134
+ if wire_type == 0:
135
+ value, pos = _DecodeVarint(data, pos)
136
+ elif wire_type == 2:
137
+ size, pos = _DecodeVarint32(data, pos)
138
+ if pos + size > length:
139
+ raise ValueError(f"Truncated field {field_num}")
140
+ raw = data[pos:pos + size]
141
+ pos += size
142
+ value = smart_decode(raw)
143
+ elif wire_type == 5: # 32-bit fixed
144
+ if pos + 4 > length:
145
+ raise ValueError(f"Truncated 32-bit field {field_num}")
146
+ value = int.from_bytes(data[pos:pos + 4], "little")
147
+ pos += 4
148
+ elif wire_type == 1: # 64-bit fixed
149
+ if pos + 8 > length:
150
+ raise ValueError(f"Truncated 64-bit field {field_num}")
151
+ value = int.from_bytes(data[pos:pos + 8], "little")
152
+ pos += 8
153
+ else:
154
+ raise ValueError(f"Unsupported wire type: {wire_type}")
155
+
156
+ # Handle repeated fields: collect into list
157
+ if field_num in fields:
158
+ if not isinstance(fields[field_num], list):
159
+ fields[field_num] = [fields[field_num]]
160
+ fields[field_num].append(value)
161
+ else:
162
+ fields[field_num] = value
163
+ return fields
164
+
165
+
166
+ def convert_keys_to_str(obj: Any) -> Any:
167
+ """Recursively convert dict keys from int to str for JSON serialization."""
168
+ if isinstance(obj, dict):
169
+ return {str(k): convert_keys_to_str(v) for k, v in obj.items()}
170
+ elif isinstance(obj, list):
171
+ return [convert_keys_to_str(item) for item in obj]
172
+ else:
173
+ return obj
@@ -0,0 +1,47 @@
1
+ Metadata-Version: 2.4
2
+ Name: ffproto
3
+ Version: 1.0.0
4
+ Summary: Encryption/decryption library for Free Fire HTTP protobuf payloads
5
+ Home-page: https://github.com/yourusername/ffproto
6
+ Author: RedZed / PARAHEX
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.6
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: pycryptodome>=3.9.0
13
+ Requires-Dist: protobuf>=3.0.0
14
+ Dynamic: author
15
+ Dynamic: classifier
16
+ Dynamic: description
17
+ Dynamic: description-content-type
18
+ Dynamic: home-page
19
+ Dynamic: requires-dist
20
+ Dynamic: requires-python
21
+ Dynamic: summary
22
+
23
+ from setuptools import setup, find_packages
24
+
25
+ with open("README.md", "r", encoding="utf-8") as fh:
26
+ long_description = fh.read()
27
+
28
+ setup(
29
+ name="ffproto",
30
+ version="1.0.0",
31
+ author="Eren",
32
+ description="Encryption/decryption library for Free Fire HTTP protobuf payloads",
33
+ long_description=long_description,
34
+ long_description_content_type="text/markdown",
35
+ url="https://github.com/yourusername/ffproto",
36
+ packages=find_packages(),
37
+ classifiers=[
38
+ "Programming Language :: Python :: 3",
39
+ "License :: OSI Approved :: MIT License",
40
+ "Operating System :: OS Independent",
41
+ ],
42
+ python_requires=">=3.6",
43
+ install_requires=[
44
+ "pycryptodome>=3.9.0",
45
+ "protobuf>=3.0.0",
46
+ ],
47
+ )
@@ -0,0 +1,11 @@
1
+ README.md
2
+ setup.py
3
+ ff_decoder/__init__.py
4
+ ff_decoder/core.py
5
+ ff_decoder/crypto.py
6
+ ff_decoder/protobuf.py
7
+ ffproto.egg-info/PKG-INFO
8
+ ffproto.egg-info/SOURCES.txt
9
+ ffproto.egg-info/dependency_links.txt
10
+ ffproto.egg-info/requires.txt
11
+ ffproto.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ pycryptodome>=3.9.0
2
+ protobuf>=3.0.0
@@ -0,0 +1 @@
1
+ ff_decoder
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
ffproto-1.0.0/setup.py ADDED
@@ -0,0 +1,25 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ with open("README.md", "r", encoding="utf-8") as fh:
4
+ long_description = fh.read()
5
+
6
+ setup(
7
+ name="ffproto",
8
+ version="1.0.0",
9
+ author="RedZed / PARAHEX",
10
+ description="Encryption/decryption library for Free Fire HTTP protobuf payloads",
11
+ long_description=long_description,
12
+ long_description_content_type="text/markdown",
13
+ url="https://github.com/yourusername/ffproto",
14
+ packages=find_packages(),
15
+ classifiers=[
16
+ "Programming Language :: Python :: 3",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: OS Independent",
19
+ ],
20
+ python_requires=">=3.6",
21
+ install_requires=[
22
+ "pycryptodome>=3.9.0",
23
+ "protobuf>=3.0.0",
24
+ ],
25
+ )