ff-decoder 1.0.0__py3-none-any.whl
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.
- ff_decoder/__init__.py +7 -0
- ff_decoder/cli.py +44 -0
- ff_decoder/decoder.py +326 -0
- ff_decoder-1.0.0.dist-info/METADATA +29 -0
- ff_decoder-1.0.0.dist-info/RECORD +8 -0
- ff_decoder-1.0.0.dist-info/WHEEL +5 -0
- ff_decoder-1.0.0.dist-info/entry_points.txt +2 -0
- ff_decoder-1.0.0.dist-info/top_level.txt +1 -0
ff_decoder/__init__.py
ADDED
ff_decoder/cli.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Free Fire payload decoder – command line interface.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
import argparse
|
|
8
|
+
from .decoder import decode_payload, format_messages
|
|
9
|
+
|
|
10
|
+
def main():
|
|
11
|
+
parser = argparse.ArgumentParser(description="Decode Free Fire encrypted payloads.")
|
|
12
|
+
parser.add_argument("file", nargs="?", help="Path to a file containing the payload")
|
|
13
|
+
parser.add_argument("--json", action="store_true", help="Output as JSON (not yet implemented, but reserved)")
|
|
14
|
+
parser.add_argument("--no-decrypt", action="store_true", help="Do not attempt outer decryption (use raw data)")
|
|
15
|
+
args = parser.parse_args()
|
|
16
|
+
|
|
17
|
+
# Read input
|
|
18
|
+
if args.file:
|
|
19
|
+
try:
|
|
20
|
+
with open(args.file, 'r', encoding='utf-8') as f:
|
|
21
|
+
raw_input = f.read().strip()
|
|
22
|
+
except Exception as e:
|
|
23
|
+
print(f"Error reading file: {e}", file=sys.stderr)
|
|
24
|
+
sys.exit(1)
|
|
25
|
+
else:
|
|
26
|
+
# If no file, read from stdin (allowing interactive paste)
|
|
27
|
+
raw_input = sys.stdin.read().strip()
|
|
28
|
+
if not raw_input:
|
|
29
|
+
# No piped input, prompt user
|
|
30
|
+
raw_input = input("Paste hex or base64 payload: ").strip()
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
messages = decode_payload(raw_input)
|
|
34
|
+
if not messages:
|
|
35
|
+
print("No valid messages decoded.")
|
|
36
|
+
else:
|
|
37
|
+
output = format_messages(messages)
|
|
38
|
+
print(output)
|
|
39
|
+
except Exception as e:
|
|
40
|
+
print(f"Decoding failed: {e}", file=sys.stderr)
|
|
41
|
+
sys.exit(1)
|
|
42
|
+
|
|
43
|
+
if __name__ == "__main__":
|
|
44
|
+
main()
|
ff_decoder/decoder.py
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Free Fire payload decoder – core module.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import binascii
|
|
6
|
+
import base64
|
|
7
|
+
import re
|
|
8
|
+
import zlib
|
|
9
|
+
import gzip
|
|
10
|
+
from io import BytesIO
|
|
11
|
+
from Crypto.Cipher import AES
|
|
12
|
+
from Crypto.Util.Padding import unpad, pad
|
|
13
|
+
|
|
14
|
+
# ==================== FIXED KEY AND IV ====================
|
|
15
|
+
KEY = bytes([89, 103, 38, 116, 99, 37, 68, 69, 117, 104, 54, 37, 90, 99, 94, 56])
|
|
16
|
+
IV = bytes([54, 111, 121, 90, 68, 114, 50, 50, 69, 51, 121, 99, 104, 106, 77, 37])
|
|
17
|
+
|
|
18
|
+
# ==================== VARINT HELPERS ====================
|
|
19
|
+
def decode_varint(data, offset):
|
|
20
|
+
value = 0
|
|
21
|
+
shift = 0
|
|
22
|
+
while True:
|
|
23
|
+
if offset >= len(data):
|
|
24
|
+
raise ValueError("Truncated varint")
|
|
25
|
+
b = data[offset]
|
|
26
|
+
value |= (b & 0x7F) << shift
|
|
27
|
+
offset += 1
|
|
28
|
+
if not (b & 0x80):
|
|
29
|
+
break
|
|
30
|
+
shift += 7
|
|
31
|
+
return value, offset
|
|
32
|
+
|
|
33
|
+
# ==================== CUSTOM ID DECRYPTION ====================
|
|
34
|
+
dec = ['80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '8a', '8b', '8c', '8d', '8e', '8f',
|
|
35
|
+
'90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '9a', '9b', '9c', '9d', '9e', '9f',
|
|
36
|
+
'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9', 'aa', 'ab', 'ac', 'ad', 'ae', 'af',
|
|
37
|
+
'b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8', 'b9', 'ba', 'bb', 'bc', 'bd', 'be', 'bf',
|
|
38
|
+
'c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9', 'ca', 'cb', 'cc', 'cd', 'ce', 'cf',
|
|
39
|
+
'd0', 'd1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9', 'da', 'db', 'dc', 'dd', 'de', 'df',
|
|
40
|
+
'e0', 'e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7', 'e8', 'e9', 'ea', 'eb', 'ec', 'ed', 'ee', 'ef',
|
|
41
|
+
'f0', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'fa', 'fb', 'fc', 'fd', 'fe', 'ff']
|
|
42
|
+
|
|
43
|
+
x_list = ['1','01', '02', '03', '04', '05', '06', '07', '08', '09', '0a', '0b', '0c', '0d', '0e', '0f',
|
|
44
|
+
'10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '1a', '1b', '1c', '1d', '1e', '1f',
|
|
45
|
+
'20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '2a', '2b', '2c', '2d', '2e', '2f',
|
|
46
|
+
'30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '3a', '3b', '3c', '3d', '3e', '3f',
|
|
47
|
+
'40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '4a', '4b', '4c', '4d', '4e', '4f',
|
|
48
|
+
'50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '5a', '5b', '5c', '5d', '5e', '5f',
|
|
49
|
+
'60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '6a', '6b', '6c', '6d', '6e', '6f',
|
|
50
|
+
'70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '7a', '7b', '7c', '7d', '7e', '7f']
|
|
51
|
+
|
|
52
|
+
def decrypt_id(da):
|
|
53
|
+
"""Decrypt custom ID format (6‑byte hex string like '30c1fab8b103')"""
|
|
54
|
+
if da is not None and len(da) == 10:
|
|
55
|
+
w = 128
|
|
56
|
+
xxx = len(da)/2 - 1
|
|
57
|
+
xxx = str(xxx)[:1]
|
|
58
|
+
for i in range(int(xxx)-1):
|
|
59
|
+
w = w * 128
|
|
60
|
+
x1 = da[:2]
|
|
61
|
+
x2 = da[2:4]
|
|
62
|
+
x3 = da[4:6]
|
|
63
|
+
x4 = da[6:8]
|
|
64
|
+
x5 = da[8:10]
|
|
65
|
+
return str(w * x_list.index(x5) + (dec.index(x2) * 128) + dec.index(x1) + (dec.index(x3) * 128 * 128) + (dec.index(x4) * 128 * 128 * 128))
|
|
66
|
+
if da is not None and len(da) == 8:
|
|
67
|
+
w = 128
|
|
68
|
+
xxx = len(da)/2 - 1
|
|
69
|
+
xxx = str(xxx)[:1]
|
|
70
|
+
for i in range(int(xxx)-1):
|
|
71
|
+
w = w * 128
|
|
72
|
+
x1 = da[:2]
|
|
73
|
+
x2 = da[2:4]
|
|
74
|
+
x3 = da[4:6]
|
|
75
|
+
x4 = da[6:8]
|
|
76
|
+
return str(w * x_list.index(x4) + (dec.index(x2) * 128) + dec.index(x1) + (dec.index(x3) * 128 * 128))
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
# ==================== DECRYPTION / DECOMPRESSION HELPERS ====================
|
|
80
|
+
def decrypt_aes_cbc(data, key=KEY, iv=IV):
|
|
81
|
+
"""Try to decrypt AES‑CBC; fallback to no‑padding if unpadding fails."""
|
|
82
|
+
cipher = AES.new(key, AES.MODE_CBC, iv)
|
|
83
|
+
try:
|
|
84
|
+
# Try with padding
|
|
85
|
+
return unpad(cipher.decrypt(data), AES.block_size)
|
|
86
|
+
except ValueError:
|
|
87
|
+
# If padding fails, assume raw decrypted data is correct
|
|
88
|
+
return cipher.decrypt(data)
|
|
89
|
+
|
|
90
|
+
def try_zlib(data):
|
|
91
|
+
"""Try to decompress as zlib. Return (decompressed, True) on success."""
|
|
92
|
+
try:
|
|
93
|
+
return zlib.decompress(data), True
|
|
94
|
+
except:
|
|
95
|
+
return data, False
|
|
96
|
+
|
|
97
|
+
def try_gzip(data):
|
|
98
|
+
"""Try to decompress as gzip. Return (decompressed, True) on success."""
|
|
99
|
+
try:
|
|
100
|
+
with gzip.GzipFile(fileobj=BytesIO(data)) as gz:
|
|
101
|
+
return gz.read(), True
|
|
102
|
+
except:
|
|
103
|
+
return data, False
|
|
104
|
+
|
|
105
|
+
# ==================== RECURSIVE PROTOBUF PARSER (SINGLE MESSAGE) ====================
|
|
106
|
+
def parse_one_message(data, start, depth=0, parent_path=""):
|
|
107
|
+
"""
|
|
108
|
+
Parse exactly one protobuf message starting at 'start'.
|
|
109
|
+
Returns (fields, next_offset). fields is a list of field dicts.
|
|
110
|
+
"""
|
|
111
|
+
fields = []
|
|
112
|
+
idx = start
|
|
113
|
+
while idx < len(data):
|
|
114
|
+
try:
|
|
115
|
+
key, idx = decode_varint(data, idx)
|
|
116
|
+
except ValueError:
|
|
117
|
+
# Not enough bytes for a varint – we've reached the end of this message
|
|
118
|
+
break
|
|
119
|
+
field_num = key >> 3
|
|
120
|
+
wire_type = key & 0x07
|
|
121
|
+
path = f"{parent_path}.{field_num}" if parent_path else f"Field {field_num}"
|
|
122
|
+
|
|
123
|
+
if wire_type == 0: # varint
|
|
124
|
+
value, idx = decode_varint(data, idx)
|
|
125
|
+
fields.append({
|
|
126
|
+
'num': field_num,
|
|
127
|
+
'type': 0,
|
|
128
|
+
'value': value,
|
|
129
|
+
'display': f"varint {value}",
|
|
130
|
+
'path': path,
|
|
131
|
+
'nested': None
|
|
132
|
+
})
|
|
133
|
+
elif wire_type == 1: # 64-bit
|
|
134
|
+
if idx + 8 > len(data):
|
|
135
|
+
raise ValueError("Truncated 64-bit")
|
|
136
|
+
value = int.from_bytes(data[idx:idx+8], 'little')
|
|
137
|
+
idx += 8
|
|
138
|
+
fields.append({
|
|
139
|
+
'num': field_num,
|
|
140
|
+
'type': 1,
|
|
141
|
+
'value': value,
|
|
142
|
+
'display': f"64-bit {value}",
|
|
143
|
+
'path': path,
|
|
144
|
+
'nested': None
|
|
145
|
+
})
|
|
146
|
+
elif wire_type == 2: # length-delimited
|
|
147
|
+
length, idx = decode_varint(data, idx)
|
|
148
|
+
if idx + length > len(data):
|
|
149
|
+
# This length would exceed remaining data – stop here (incomplete message)
|
|
150
|
+
return fields, idx
|
|
151
|
+
raw = data[idx:idx+length]
|
|
152
|
+
idx += length
|
|
153
|
+
|
|
154
|
+
display = f"bytes ({len(raw)} bytes : {raw})"
|
|
155
|
+
nested = None
|
|
156
|
+
processed_raw = raw
|
|
157
|
+
|
|
158
|
+
# --- Automatic decoding attempts ---
|
|
159
|
+
# 1) Custom ID (0x30 + 5 bytes)
|
|
160
|
+
if len(raw) == 6 and raw[0] == 0x30:
|
|
161
|
+
id_hex = raw[1:].hex()
|
|
162
|
+
decrypted_id = decrypt_id(id_hex)
|
|
163
|
+
if decrypted_id:
|
|
164
|
+
display = f"custom ID -> {decrypted_id}"
|
|
165
|
+
|
|
166
|
+
# 2) Zlib decompression
|
|
167
|
+
decompressed, ok = try_zlib(raw)
|
|
168
|
+
if ok and decompressed != raw:
|
|
169
|
+
processed_raw = decompressed
|
|
170
|
+
display = f"zlib decompressed ({len(decompressed)} bytes)"
|
|
171
|
+
# Try to parse as protobuf
|
|
172
|
+
try:
|
|
173
|
+
nested, _ = parse_one_message(decompressed, 0, depth+1, path)
|
|
174
|
+
if nested:
|
|
175
|
+
display += f" → protobuf ({len(nested)} fields)"
|
|
176
|
+
except Exception:
|
|
177
|
+
pass
|
|
178
|
+
|
|
179
|
+
# 3) Gzip decompression (only if not already handled)
|
|
180
|
+
if nested is None and not ok: # only try if previous attempts didn't succeed
|
|
181
|
+
decompressed, ok = try_gzip(raw)
|
|
182
|
+
if ok and decompressed != raw:
|
|
183
|
+
processed_raw = decompressed
|
|
184
|
+
display = f"gzip decompressed ({len(decompressed)} bytes)"
|
|
185
|
+
try:
|
|
186
|
+
nested, _ = parse_one_message(decompressed, 0, depth+1, path)
|
|
187
|
+
if nested:
|
|
188
|
+
display += f" → protobuf ({len(nested)} fields)"
|
|
189
|
+
except Exception:
|
|
190
|
+
pass
|
|
191
|
+
|
|
192
|
+
# 4) AES decryption (if length multiple of 16)
|
|
193
|
+
if nested is None and len(raw) % 16 == 0 and len(raw) > 0:
|
|
194
|
+
try:
|
|
195
|
+
decrypted = decrypt_aes_cbc(raw)
|
|
196
|
+
if decrypted != raw:
|
|
197
|
+
processed_raw = decrypted
|
|
198
|
+
display = f"AES decrypted ({len(decrypted)} bytes)"
|
|
199
|
+
# Try to parse as protobuf
|
|
200
|
+
try:
|
|
201
|
+
nested, _ = parse_one_message(decrypted, 0, depth+1, path)
|
|
202
|
+
if nested:
|
|
203
|
+
display += f" → protobuf ({len(nested)} fields)"
|
|
204
|
+
except Exception:
|
|
205
|
+
pass
|
|
206
|
+
except Exception:
|
|
207
|
+
pass
|
|
208
|
+
|
|
209
|
+
# 5) Try parsing raw (or decompressed/decrypted) as protobuf directly
|
|
210
|
+
if nested is None:
|
|
211
|
+
try:
|
|
212
|
+
nested, _ = parse_one_message(processed_raw, 0, depth+1, path)
|
|
213
|
+
if nested:
|
|
214
|
+
display = f"protobuf ({len(nested)} fields)"
|
|
215
|
+
except Exception:
|
|
216
|
+
pass
|
|
217
|
+
|
|
218
|
+
# 6) Try UTF-8 string
|
|
219
|
+
if nested is None:
|
|
220
|
+
try:
|
|
221
|
+
s = processed_raw.decode('utf-8')
|
|
222
|
+
if all(32 <= ord(c) < 127 or c in '\n\r\t' for c in s):
|
|
223
|
+
display = f"string '{s}'"
|
|
224
|
+
except UnicodeDecodeError:
|
|
225
|
+
pass
|
|
226
|
+
|
|
227
|
+
# 7) For very short bytes, try to interpret as integer
|
|
228
|
+
if nested is None:
|
|
229
|
+
if len(processed_raw) == 4:
|
|
230
|
+
val = int.from_bytes(processed_raw, 'little')
|
|
231
|
+
display = f"uint32 {val} (bytes: {processed_raw.hex()})"
|
|
232
|
+
elif len(processed_raw) == 8:
|
|
233
|
+
val = int.from_bytes(processed_raw, 'little')
|
|
234
|
+
display = f"uint64 {val} (bytes: {processed_raw.hex()})"
|
|
235
|
+
|
|
236
|
+
fields.append({
|
|
237
|
+
'num': field_num,
|
|
238
|
+
'type': 2,
|
|
239
|
+
'value': processed_raw,
|
|
240
|
+
'display': display,
|
|
241
|
+
'path': path,
|
|
242
|
+
'nested': nested
|
|
243
|
+
})
|
|
244
|
+
elif wire_type == 5: # 32-bit
|
|
245
|
+
if idx + 4 > len(data):
|
|
246
|
+
raise ValueError("Truncated 32-bit")
|
|
247
|
+
value = int.from_bytes(data[idx:idx+4], 'little')
|
|
248
|
+
idx += 4
|
|
249
|
+
fields.append({
|
|
250
|
+
'num': field_num,
|
|
251
|
+
'type': 5,
|
|
252
|
+
'value': value,
|
|
253
|
+
'display': f"32-bit {value}",
|
|
254
|
+
'path': path,
|
|
255
|
+
'nested': None
|
|
256
|
+
})
|
|
257
|
+
else:
|
|
258
|
+
raise ValueError(f"Unsupported wire type {wire_type}")
|
|
259
|
+
return fields, idx
|
|
260
|
+
|
|
261
|
+
def decode_payload(data):
|
|
262
|
+
"""
|
|
263
|
+
Decode a payload (hex string, base64 string, or bytes) and return a list of messages.
|
|
264
|
+
Each message is a list of field dicts.
|
|
265
|
+
"""
|
|
266
|
+
if isinstance(data, str):
|
|
267
|
+
# Try hex first
|
|
268
|
+
hex_chars = re.sub(r'[^0-9a-fA-F]', '', data)
|
|
269
|
+
if len(hex_chars) % 2 == 0 and len(hex_chars) > 0:
|
|
270
|
+
try:
|
|
271
|
+
data_bytes = binascii.unhexlify(hex_chars)
|
|
272
|
+
used_format = "hex"
|
|
273
|
+
except binascii.Error:
|
|
274
|
+
data_bytes = None
|
|
275
|
+
else:
|
|
276
|
+
data_bytes = None
|
|
277
|
+
|
|
278
|
+
if data_bytes is None:
|
|
279
|
+
b64_chars = re.sub(r'[^A-Za-z0-9+/=]', '', data)
|
|
280
|
+
if len(b64_chars) > 0:
|
|
281
|
+
try:
|
|
282
|
+
data_bytes = base64.b64decode(b64_chars)
|
|
283
|
+
used_format = "base64"
|
|
284
|
+
except binascii.Error:
|
|
285
|
+
data_bytes = None
|
|
286
|
+
if data_bytes is None:
|
|
287
|
+
raise ValueError("Could not decode input as hex or base64")
|
|
288
|
+
else:
|
|
289
|
+
# Assume bytes
|
|
290
|
+
data_bytes = data
|
|
291
|
+
|
|
292
|
+
# Try outer AES decryption
|
|
293
|
+
try:
|
|
294
|
+
plain = decrypt_aes_cbc(data_bytes)
|
|
295
|
+
except Exception:
|
|
296
|
+
plain = data_bytes
|
|
297
|
+
|
|
298
|
+
# Parse all messages
|
|
299
|
+
messages = []
|
|
300
|
+
pos = 0
|
|
301
|
+
while pos < len(plain):
|
|
302
|
+
try:
|
|
303
|
+
fields, pos = parse_one_message(plain, pos)
|
|
304
|
+
if not fields:
|
|
305
|
+
break
|
|
306
|
+
messages.append(fields)
|
|
307
|
+
except Exception:
|
|
308
|
+
break
|
|
309
|
+
return messages
|
|
310
|
+
|
|
311
|
+
def format_messages(messages):
|
|
312
|
+
"""Return a string representation of messages."""
|
|
313
|
+
import sys
|
|
314
|
+
out_lines = []
|
|
315
|
+
for i, msg in enumerate(messages, start=1):
|
|
316
|
+
out_lines.append(f"\n--- Message {i} ---")
|
|
317
|
+
out_lines.extend(_format_fields(msg))
|
|
318
|
+
return "\n".join(out_lines)
|
|
319
|
+
|
|
320
|
+
def _format_fields(fields, indent=""):
|
|
321
|
+
lines = []
|
|
322
|
+
for f in fields:
|
|
323
|
+
lines.append(f"{indent}{f['path']} : {f['display']}")
|
|
324
|
+
if f['nested']:
|
|
325
|
+
lines.extend(_format_fields(f['nested'], indent + " "))
|
|
326
|
+
return lines
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ff-decoder
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Decode Free Fire encrypted payloads (hex/base64) with automatic decryption and expansion.
|
|
5
|
+
Author: UNKNOWN666
|
|
6
|
+
Author-email: arbasbilal25@gmail.com
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.7
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: pycryptodome>=3.15.0
|
|
13
|
+
Dynamic: author
|
|
14
|
+
Dynamic: author-email
|
|
15
|
+
Dynamic: classifier
|
|
16
|
+
Dynamic: description
|
|
17
|
+
Dynamic: description-content-type
|
|
18
|
+
Dynamic: requires-dist
|
|
19
|
+
Dynamic: requires-python
|
|
20
|
+
Dynamic: summary
|
|
21
|
+
|
|
22
|
+
# FF Decoder
|
|
23
|
+
|
|
24
|
+
A Python library and command-line tool to decode encrypted Free Fire payloads (hex or base64). It automatically decrypts the outer AES layer, decompresses zlib/gzip, and recursively expands nested protobufs.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install ff-decoder
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
ff_decoder/__init__.py,sha256=91kpUDNNX4UOzWiafUS5j3kuoYxUU5HLbuDiYiaDJLg,137
|
|
2
|
+
ff_decoder/cli.py,sha256=HGqdhFM7_hvORAWP_2_lHEVw_Z9p3Pai39MeGvHO7K4,1509
|
|
3
|
+
ff_decoder/decoder.py,sha256=rMJxk6BOjkNw1ct0FM55DOOP-E27RkqlDVqloeBS_6Q,12695
|
|
4
|
+
ff_decoder-1.0.0.dist-info/METADATA,sha256=SEcYlwUe394niGNqMUnCf_tDPJbrw_Se9UW1yjquaQg,904
|
|
5
|
+
ff_decoder-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
6
|
+
ff_decoder-1.0.0.dist-info/entry_points.txt,sha256=C8m5DuDw6stXcRxOMLrWeCefjJyypr9QGJRPJ9y175I,50
|
|
7
|
+
ff_decoder-1.0.0.dist-info/top_level.txt,sha256=4wKXP6TNciqTKnpT8tcisiiJ5LSYbmeBSUHBm3jamoI,11
|
|
8
|
+
ff_decoder-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ff_decoder
|