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 +47 -0
- ffproto-1.0.0/README.md +25 -0
- ffproto-1.0.0/ff_decoder/__init__.py +22 -0
- ffproto-1.0.0/ff_decoder/core.py +54 -0
- ffproto-1.0.0/ff_decoder/crypto.py +43 -0
- ffproto-1.0.0/ff_decoder/protobuf.py +173 -0
- ffproto-1.0.0/ffproto.egg-info/PKG-INFO +47 -0
- ffproto-1.0.0/ffproto.egg-info/SOURCES.txt +11 -0
- ffproto-1.0.0/ffproto.egg-info/dependency_links.txt +1 -0
- ffproto-1.0.0/ffproto.egg-info/requires.txt +2 -0
- ffproto-1.0.0/ffproto.egg-info/top_level.txt +1 -0
- ffproto-1.0.0/setup.cfg +4 -0
- ffproto-1.0.0/setup.py +25 -0
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
|
+
)
|
ffproto-1.0.0/README.md
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="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 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ff_decoder
|
ffproto-1.0.0/setup.cfg
ADDED
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
|
+
)
|