Last active
April 14, 2024 15:53
-
-
Save iebb/26928a039b677ae43da95a434165bcce to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import json | |
import os | |
import UnityPy | |
import requests | |
def parse_track(filename): | |
env = UnityPy.load(filename) | |
track = { | |
'Corners': [], | |
'Sectors': [{}, {}, {}], | |
'DRS': [], | |
} | |
read_mesh_ids = set() | |
def read_mesh(data): | |
read_mesh_ids.add(data.path_id) | |
r = (0, data.m_VertexCount - 1, 1) | |
if data.name == "Track" or data.name.startswith("DRS "): | |
r = (1, data.m_VertexCount, 4) | |
mesh_vertices = [] | |
for i in range(*r): | |
t = tuple((i + j) / 2 for i, j in zip(data.m_Vertices[3 * i:3 * i + 3], data.m_Vertices[3 * i + 3:3 * i + 6])) | |
mesh_vertices.append(t) | |
return mesh_vertices | |
segments = [] | |
sectors = [] | |
paths = {} | |
labels = {} | |
has_corner = False | |
for obj in env.objects: | |
if obj.type.name in ["MonoBehaviour"]: | |
data = obj.read() | |
mono_bh = data.m_Script.read() | |
if mono_bh.name == "CircuitAsset": | |
valid_from_year = max(data._validFrom.Year, 2018) # Live Timing from 2018 | |
track['Code'] = data.MeetingCode | |
track['CircuitKey'] = data.CircuitKey | |
track['Year'] = valid_from_year | |
track['Metadata'] = { | |
'CountryKey': data.CountryKey, | |
'CircuitKey': data.CircuitKey, | |
'MeetingKey': data.MeetingKey, | |
'MeetingCode': data.MeetingCode, | |
'Year': valid_from_year, | |
'ValidFrom': "%04d%02d%02d" % (valid_from_year, data._validFrom.Month, data._validFrom.Day), | |
'ValidUpto': "%04d%02d%02d" % (data._validUpto.Year, data._validUpto.Month, data._validUpto.Day), | |
} | |
elif mono_bh.name == "CircuitComponent": | |
track['Scale'] = round(data.Scale, 3) | |
track['BoundsRadius'] = data.BoundsRadius | |
track['BoundsOffset'] = [data.BoundsOffset.x, data.BoundsOffset.y, data.BoundsOffset.z] | |
track['Rotation'] = round(data._circuitRotationInY, 3) | |
elif mono_bh.name == "Segment": | |
game_obj = data.m_GameObject.read() | |
mesh_filter = game_obj.m_MeshFilter.read() | |
mesh = mesh_filter.m_Mesh.read() | |
segments.append({ | |
'Sector': data.SectorType, | |
'Segment': data.SegmentNumber, | |
'Path': read_mesh(mesh), | |
}) | |
elif mono_bh.name == "Sector": | |
game_obj = data.m_GameObject.read() | |
mesh_filter = game_obj.m_MeshFilter.read() | |
mesh = mesh_filter.m_Mesh.read() | |
sectors.append({ | |
'Sector': data.SectorType, | |
'Path': read_mesh(mesh), | |
}) | |
elif mono_bh.name == "TextMeshPro": | |
pass | |
elif mono_bh.name == "MapZoomConfig": | |
pass | |
elif mono_bh.name == "CornerLabel": | |
game_obj = data.m_GameObject.read() | |
transform = game_obj.m_Transform.read() | |
t = [transform.m_LocalPosition.X, transform.m_LocalPosition.Y, transform.m_LocalPosition.Z] | |
tf_father = transform.m_Father.read() | |
ft = [tf_father.m_LocalPosition.X, tf_father.m_LocalPosition.Y, tf_father.m_LocalPosition.Z] | |
t = [t[i]+ft[i] for i in [0, 1, 2]] | |
if game_obj.m_Layer == 8: | |
track['Corners'].append({ | |
'Position': t, | |
'Corner': game_obj.name, | |
}) | |
else: | |
if " " in game_obj.name: | |
game_obj.name = game_obj.name.split(" ")[0] | |
labels[game_obj.name] = { | |
'Position': t, | |
'Labels': game_obj.name, | |
} | |
else: | |
pass | |
for obj in env.objects: | |
if obj.type.name in ["Mesh"]: | |
data = obj.read() | |
if data.path_id not in read_mesh_ids: | |
paths[data.name] = read_mesh(data) | |
for sector in sectors: | |
sector['Segments'] = [] | |
sector['Label'] = labels[f'sector_{sector["Sector"] + 1}'] | |
del labels[f'sector_{sector["Sector"] + 1}'] | |
track['Sectors'][sector['Sector']] = sector | |
for segment in sorted(segments, key=lambda x: x['Sector'] * 100 + x['Segment']): | |
sector = track['Sectors'][segment['Sector']] | |
assert len(sector['Segments']) == segment['Segment'] | |
sector['Segments'].append(segment) | |
track["Corners"].sort(key=lambda x: x['Corner']) | |
if not has_corner: | |
track["Corners"] = [] | |
for drs_tag in sorted(filter(lambda x: x.startswith('DRS '), paths)): | |
drs_id = int(drs_tag[4]) | |
drs = { | |
'ID': drs_id + 1, | |
'Tag': drs_tag, | |
'Path': paths[drs_tag], | |
} | |
del paths[drs_tag] | |
track['DRS'].append(drs) | |
track['Labels'] = labels | |
track['Track'] = paths['Track'] | |
track['Pit'] = paths['Pit'] | |
open("output/%d-%d.json" % (track['CircuitKey'], track['Year']), "w+").write( | |
json.dumps(track, indent=2, sort_keys=True) | |
) | |
catalog_url = "https://livetiming.formula1.com/static/webgl/StreamingAssets/aa/catalog.json" | |
catalog = requests.get(catalog_url).json() | |
for iid in catalog["m_InternalIds"]: | |
if "{UnityEngine.AddressableAssets.Addressables.RuntimePath}/WebGL/content-circuits_assets_assets/" not in iid: | |
continue | |
url = iid.replace("{UnityEngine.AddressableAssets.Addressables.RuntimePath}", "https://livetiming.formula1.com/static/webgl/StreamingAssets/aa") | |
download_file = "data/" + os.path.basename(url) | |
if not os.path.exists(download_file): | |
downloaded = requests.get(url).content | |
open(download_file, "wb+").write(downloaded) | |
parse_track(download_file) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment