Skip to content

Instantly share code, notes, and snippets.

@iebb
Last active April 14, 2024 15:53
Show Gist options
  • Save iebb/26928a039b677ae43da95a434165bcce to your computer and use it in GitHub Desktop.
Save iebb/26928a039b677ae43da95a434165bcce to your computer and use it in GitHub Desktop.
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