-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathvalidators.py
More file actions
136 lines (115 loc) · 4.67 KB
/
validators.py
File metadata and controls
136 lines (115 loc) · 4.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import hashlib
import re
import struct
import blacklist
id0_regex = re.compile(r'(?![0-9a-fA-F]{4}(01|00)[0-9a-fA-F]{18}00[0-9a-fA-F]{6})[0-9a-fA-F]{32}')
system_id_regex = re.compile(r'[a-fA-F0-9]{16}')
# splits version strings for comparison
version_split_regex = re.compile(r'[.+-]')
def is_job_key(value: str) -> bool:
if is_id0(value):
return True
if is_system_id(value):
return True
if is_friend_code(value):
return True
return False
def is_id0(value: str) -> bool:
return bool(id0_regex.fullmatch(value))
def is_system_id(value: str) -> bool:
value = value.split('-')[0]
return bool(system_id_regex.fullmatch(value))
# Modified from verify_3ds_fc @ friendcode.py by nh-server
# https://github.com/nh-server/Kurisu/blob/main/cogs/friendcode.py#L28-L37
def is_friend_code(value: str) -> bool:
try:
fc = int(value)
except ValueError:
return False
if fc > 0x7FFFFFFFFF:
return False
principal_id = fc & 0xFFFFFFFF
checksum = (fc & 0xFF00000000) >> 32
return hashlib.sha1(struct.pack('<L', principal_id)).digest()[0] >> 1 == checksum
# Verify if friend code is not statically blacklisted (vguides etc)
# Returns true if blacklisted, false otherwise
def is_blacklisted_friend_code(value: str) -> bool:
return value in blacklist.FC_knownFriendCodes
def get_key_type(key: str) -> str:
if is_friend_code(key):
return 'fc-lfcs'
elif is_system_id(key):
return 'mii-lfcs'
elif is_id0(key):
return 'msed'
def validate_job_result(job_type: str, result: bytes, key=None) -> bool:
if job_type in ['mii-lfcs', 'mii-lfcs-offset', 'fc-lfcs']:
return validate_lfcs(result)
elif 'msed' == job_type:
return validate_movable(result, key)
else:
return False
# system id -> lfcs
def validate_lfcs(lfcs: bytes) -> bool:
# shorter than 5 bytes
if len(lfcs) < 5:
return False
# first 4 bytes are 0
if b'\0\0\0\0' in lfcs[:4]:
return False
#if result[4:5] != b"\x00" && result[4:5] != b"\x02":
# return False
return True
# lfcs -> msed
def validate_movable(msed: bytes, id0: str) -> bool:
if len(msed) == 320:
# full msed file
return validate_keyy(msed[0x110:0x120], id0)
elif len(msed) == 16:
# keyy only
return validate_keyy(msed, id0)
else:
return False
# Modified from id0convert.py by zoogie
# https://github.com/zoogie/seedminer_toolbox/blob/master/id0convert.py#L4-L8
def validate_keyy(keyy: bytes, id0: str) -> bool:
keyy_sha256 = hashlib.sha256(keyy).digest()[:0x10]
keyy_id0 = (keyy_sha256[3::-1] + keyy_sha256[7:3:-1] + keyy_sha256[11:7:-1] + keyy_sha256[15:11:-1]).hex()
return keyy_id0 == id0
def enforce_client_version(client_types: dict, client_version_str: str, requested_types: set) -> set[str]:
try:
# reject if no version provided
if not client_version_str:
raise ValueError('Client version not provided')
client_type, client_version = parse_typed_version_string(client_version_str)
# reject unrecognized clients
if client_type not in client_types.keys():
raise ValueError('Unrecognized client type')
# reject outdated clients
latest_version_str = client_types[client_type]['version']
latest_version = parse_version_string(latest_version_str)
if compare_versions(client_version, latest_version) < 0:
raise ValueError(f'Outdated client version, {client_version_str} < {client_type}-{latest_version_str}')
# reject illegal job type requests
allowed_types = client_types[client_type]['allowed']
if requested_types and bool(requested_types - allowed_types):
raise ValueError(f'Requested illegal job type for {client_type} clients')
return allowed_types
except ValueError as e:
raise e
except Exception as e:
raise ValueError('Error validating client version') from e
# Modified from https://stackoverflow.com/a/28568003
def parse_typed_version_string(version: str, point_max_len=10) -> tuple[str, list]:
split = version_split_regex.split(version)
return split[0], [p.zfill(point_max_len) for p in split[1:]]
# Modified from https://stackoverflow.com/a/28568003
def parse_version_string(version: str, point_max_len=10) -> list:
return [p.zfill(point_max_len) for p in version_split_regex.split(version)]
def compare_versions(version_a: list, version_b: list) -> int:
if len(version_a) != len(version_b):
raise ValueError('Version lengths do not match')
return compare(version_a, version_b)
# removed in Python 3 lol
def compare(a, b) -> int:
return (a > b) - (a < b)