Skip to content

Commit 040455a

Browse files
committed
Да иди ты в жопу
1 parent 7d1df00 commit 040455a

8 files changed

Lines changed: 952 additions & 373 deletions

File tree

.github/rsi-schema.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,16 @@
7777
"default":"",
7878
"description":"The license for the associated icon states. Restricted to SS14-compatible asset licenses.",
7979
"enum":[
80+
"CC-BY-3.0",
81+
"CC-BY-4.0",
8082
"CC-BY-SA-3.0",
8183
"CC-BY-SA-4.0",
8284
"CC-BY-NC-3.0",
8385
"CC-BY-NC-4.0",
8486
"CC-BY-NC-SA-3.0",
8587
"CC-BY-NC-SA-4.0",
86-
"CC0-1.0"
88+
"CC0-1.0",
89+
"All Rights Reserved"
8790
],
8891
"examples":[
8992
"CC-BY-SA-3.0"

.github/validate_rsis.py

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import json
5+
import os
6+
from PIL import Image
7+
from glob import iglob
8+
from jsonschema import Draft7Validator, ValidationError
9+
from typing import Any, List, Optional
10+
11+
ALLOWED_RSI_DIR_GARBAGE = {
12+
"meta.json",
13+
".DS_Store",
14+
"thumbs.db",
15+
".directory"
16+
}
17+
18+
errors: List["RsiError"] = []
19+
20+
def main() -> int:
21+
parser = argparse.ArgumentParser("validate_rsis.py", description="Validates RSI file integrity for mistakes the engine does not catch while loading.")
22+
parser.add_argument("directories", nargs="+", help="Directories to look for RSIs in")
23+
24+
args = parser.parse_args()
25+
schema = load_schema()
26+
27+
for dir in args.directories:
28+
check_dir(dir, schema)
29+
30+
for error in errors:
31+
print(f"{error.path}: {error.message}")
32+
33+
return 1 if errors else 0
34+
35+
36+
def check_dir(dir: str, schema: Draft7Validator):
37+
for rsi_rel in iglob("**/*.rsi", root_dir=dir, recursive=True):
38+
rsi_path = os.path.join(dir, rsi_rel)
39+
try:
40+
check_rsi(rsi_path, schema)
41+
except Exception as e:
42+
add_error(rsi_path, f"Failed to validate RSI (script bug): {e}")
43+
44+
45+
def check_rsi(rsi: str, schema: Draft7Validator):
46+
meta_path = os.path.join(rsi, "meta.json")
47+
48+
# Try to load meta.json
49+
try:
50+
meta_json = read_json(meta_path)
51+
except Exception as e:
52+
add_error(rsi, f"Failed to read meta.json: {e}")
53+
return
54+
55+
# Check if meta.json passes schema.
56+
schema_errors: List[ValidationError] = list(schema.iter_errors(meta_json))
57+
if schema_errors:
58+
for error in schema_errors:
59+
add_error(rsi, f"meta.json: [{error.json_path}] {error.message}")
60+
# meta.json may be corrupt, can't safely proceed.
61+
return
62+
63+
state_names = {state["name"] for state in meta_json["states"]}
64+
65+
# Go over contents of RSI directory and ensure there is no extra garbage.
66+
for name in os.listdir(rsi):
67+
if name in ALLOWED_RSI_DIR_GARBAGE:
68+
continue
69+
70+
if not name.endswith(".png"):
71+
add_error(rsi, f"Illegal file inside RSI: {name}")
72+
continue
73+
74+
# All PNGs must be defined in the meta.json
75+
png_state_name = name[:-4]
76+
if png_state_name not in state_names:
77+
add_error(rsi, f"PNG not defined in metadata: {name}")
78+
79+
80+
# Validate state delays.
81+
for state in meta_json["states"]:
82+
state_name: str = state["name"]
83+
84+
# Validate state delays.
85+
delays: Optional[List[List[float]]] = state.get("delays")
86+
if not delays:
87+
continue
88+
89+
# Validate directions count in metadata and delays count matches.
90+
directions: int = state.get("directions", 1)
91+
if directions != len(delays):
92+
add_error(rsi, f"{state_name}: direction count ({directions}) doesn't match delay set specified ({len(delays)})")
93+
continue
94+
95+
# Validate that each direction array has the same length.
96+
lengths: List[float] = []
97+
for dir in delays:
98+
# Robust rounds to millisecond precision.
99+
lengths.append(round(sum(dir), 3))
100+
101+
if any(l != lengths[0] for l in lengths):
102+
add_error(rsi, f"{state_name}: mismatching total durations between state directions: {', '.join(map(str, lengths))}")
103+
104+
frame_width = meta_json["size"]["x"]
105+
frame_height = meta_json["size"]["y"]
106+
107+
# Validate state PNGs.
108+
# We only check they're the correct size and that they actually exist and load.
109+
for state in meta_json["states"]:
110+
state_name: str = state["name"]
111+
112+
png_name = os.path.join(rsi, f"{state_name}.png")
113+
try:
114+
image = Image.open(png_name)
115+
except Exception as e:
116+
add_error(rsi, f"{state_name}: failed to open state {state_name}.png")
117+
continue
118+
119+
# Check that size is a multiple of the metadata frame size.
120+
size = image.size
121+
if size[0] % frame_width != 0 or size[1] % frame_height != 0:
122+
add_error(rsi, f"{state_name}: sprite sheet of {size[0]}x{size[1]} is not size multiple of RSI size ({frame_width}x{frame_height}).png")
123+
continue
124+
125+
# Check that the sprite sheet is big enough to possibly fit all the frames listed in metadata.
126+
frames_w = size[0] // frame_width
127+
frames_h = size[1] // frame_height
128+
129+
directions: int = state.get("directions", 1)
130+
delays: Optional[List[List[float]]] = state.get("delays", [[1]] * directions)
131+
frame_count = sum(map(len, delays))
132+
max_sheet_frames = frames_w * frames_h
133+
134+
if frame_count > max_sheet_frames:
135+
add_error(rsi, f"{state_name}: sprite sheet of {size[0]}x{size[1]} is too small, metadata defines {frame_count} frames, but it can only fit {max_sheet_frames} at most")
136+
continue
137+
138+
# Check if state name exists
139+
for state in meta_json["states"]:
140+
state_name: str = state["name"]
141+
if state_name == "":
142+
add_error(rsi, f"state name cannot be an empty string.")
143+
return
144+
145+
# We're good!
146+
return
147+
148+
149+
def load_schema() -> Draft7Validator:
150+
base_path = os.path.dirname(os.path.realpath(__file__))
151+
schema_path = os.path.join(base_path, "rsi-schema.json")
152+
schema_json = read_json(schema_path)
153+
154+
return Draft7Validator(schema_json)
155+
156+
157+
def read_json(path: str) -> Any:
158+
with open(path, "r", encoding="utf-8-sig") as f:
159+
return json.load(f)
160+
161+
162+
def add_error(rsi: str, message: str):
163+
errors.append(RsiError(rsi, message))
164+
165+
166+
class RsiError:
167+
def __init__(self, path: str, message: str):
168+
self.path = path
169+
self.message = message
170+
171+
172+
exit(main())

.github/workflows/build-map-renderer.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ jobs:
4545
run: dotnet build Content.MapRenderer --configuration Release --no-restore /m
4646

4747
- name: Run Map Renderer
48-
run: dotnet run --project Content.MapRenderer Dev
48+
run: dotnet run --project Content.MapRenderer Empty
4949

5050
ci-success:
5151
name: Build & Test Debug

.github/workflows/validate-rgas.yml

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,10 @@ jobs:
1313
runs-on: ubuntu-latest
1414
steps:
1515
- uses: actions/checkout@v4.2.2
16-
- name: Setup Submodule
17-
run: git submodule update --init
18-
- name: Pull engine updates
19-
uses: space-wizards/submodule-dependency@v0.1.5
2016
- uses: PaulRitter/yaml-schema-validator@v1
2117
with:
22-
schema: RobustToolbox/Schemas/rga.yml
18+
schema: Tools/lua/Schemas/rga.yml
2319
path_pattern: .*attributions.ya?ml$
24-
validators_path: RobustToolbox/Schemas/rga_validators.py
20+
validators_path: Tools/lua/Schemas/rga_validators.py
2521
validators_requirements: Tools/lua/Schemas/rga_requirements.txt
22+
strict: true

.github/workflows/validate-rsis.yml

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,23 @@ on:
77
pull_request:
88
paths:
99
- '**.rsi/**'
10+
- '.github/rsi-schema.json'
11+
- '.github/validate_rsis.py'
12+
- '.github/workflows/validate-rsis.yml'
1013

1114
jobs:
1215
validate_rsis:
1316
name: Validate RSIs
1417
runs-on: ubuntu-latest
1518
steps:
1619
- uses: actions/checkout@v4.2.2
17-
- name: Setup Submodule
18-
run: git submodule update --init
19-
- name: Pull engine updates
20-
uses: space-wizards/submodule-dependency@v0.1.5
21-
- name: Set up Python 3.10 # Frontier
22-
uses: actions/setup-python@v3 # Frontier
23-
with: # Frontier
24-
python-version: "3.10" # Frontier
20+
- name: Set up Python 3.10
21+
uses: actions/setup-python@v5
22+
with:
23+
python-version: "3.10"
2524
- name: Install Python dependencies
2625
run: |
2726
pip3 install --ignore-installed --user pillow jsonschema
2827
- name: Validate RSIs
2928
run: |
30-
python3 RobustToolbox/Schemas/validate_rsis.py Resources/
29+
python3 .github/validate_rsis.py Resources/

0 commit comments

Comments
 (0)