Skip to content

Commit 78c5229

Browse files
authored
Apply well-formed JSON checks also on peas_local.json if present. (#230)
Added user script `peas_validate_with_json_schema.py` to also validate PEAS file against the PEAS JSON Schema. Fixes #228
1 parent 9fb09eb commit 78c5229

7 files changed

Lines changed: 149 additions & 38 deletions

File tree

docs/INSTALLATION.md

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,28 @@ You can find detailled installation instructions in the respective section in th
55
- [RetroPie on Linux distros with Debian package system](https://github.com/Gemba/skyscraper/blob/master/README.md#installation-of-skyscraper-on-retropie-and-programmable-completion) (Ubuntu, RaspiOS, aso.)
66
- Other unixoid OSes first match the dependencies:
77

8-
- Linux distros with Debian package system (Ubuntu, RaspiOS, aso.), [without RetroPie](https://github.com/Gemba/skyscraper/blob/master/README.md#linux)
9-
- For other Linux distributions assure the [package dependencies](https://github.com/Gemba/skyscraper/blob/master/README.md#linux) are met for your distro
10-
- NixOS aficionados are happy with these packages: [NixOS](https://github.com/Gemba/skyscraper/blob/master/README.md#nixos)
11-
- macOS users can install the dependencies [via brew](https://github.com/Gemba/skyscraper/blob/master/README.md#macos)
8+
- Linux distros with Debian package system (Ubuntu, RaspiOS, aso.), [without
9+
RetroPie](https://github.com/Gemba/skyscraper/blob/master/README.md#linux)
10+
- For other Linux distributions assure the [package
11+
dependencies](https://github.com/Gemba/skyscraper/blob/master/README.md#linux)
12+
are met for your distro
13+
- NixOS aficionados are happy with these packages:
14+
[NixOS](https://github.com/Gemba/skyscraper/blob/master/README.md#nixos)
15+
- macOS users can install the dependencies [via
16+
brew](https://github.com/Gemba/skyscraper/blob/master/README.md#macos)
1217

1318
After having the dependencies installed proceed with the [install script](https://github.com/Gemba/skyscraper/blob/master/README.md#download-compile-and-install) or use the few [manual commands](https://github.com/Gemba/skyscraper/blob/master/README.md#installing-the-development-version).
1419
Last but not least you can also follow these paths:
1520

1621
- [Docker use](https://github.com/Gemba/skyscraper/blob/master/README.md#docker)
17-
- [Native Windows installation](https://github.com/Gemba/skyscraper/tree/master/win32#readme) (or use WSL2 with a Linux Distro and follow the Linux instructions above)
22+
- [Native Windows
23+
installation](https://github.com/Gemba/skyscraper/tree/master/win32#readme)
24+
(or use WSL2 with a Linux Distro and follow the Linux instructions above)
25+
26+
### Supplementary Scripts
27+
28+
These scripts are not essential to work with Skyscraper, but they may come in
29+
handy if you adapt the platform configuration files, documented in the [Platform
30+
page](PLATFORMS.md) or want to use the GameBase scraper. You can find an
31+
overview of the scripts in the respective
32+
[README](https://github.com/Gemba/skyscraper/blob/master/supplementary/scraperdata/README-Skyscraper-Scripts.md).

docs/PLATFORMS.md

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,11 @@ maintain local changes to the `platforms_idmap.csv` in a separate file with a
6161
!!! tip "Avoid Duplication"
6262

6363
If you need a specific folder name for a platform (on your setup or due to an
64-
EmulationStation theme) use a symbolic link (for example `megadrive` (=folder)
65-
and `genesis` (=symlink) on RetroPie setups or another example: `plus4`
66-
(=folder) and `c16` (=symlink)) instead of duplicating the platform in the JSON
67-
file.
64+
EmulationStation theme) use a symbolic link. For example `megadrive` (=folder)
65+
and `genesis` (=symlink) on RetroPie setups or `plus4` (=folder) and `c16`
66+
(=symlink). Example for ES-DE `n3ds` (=folder) and `3ds` (=symlink) Use this
67+
approach instead of duplicating the platform in the JSON file and it saves you
68+
from maintaining the `*_local*` files. Command `ln -s <folder> <symlink>`
6869

6970
### File Two: Exact platform mapping
7071

@@ -84,7 +85,11 @@ c64,66,27,40
8485

8586
!!! tip "The Games DB"
8687

87-
The game data at TGDB is in rare case in different platforms to be found. Prominent example is Sega's Genesis respecitve Mega Drive. For these edgecases you may find the platform ids in the `platforms_idmap.csv` separated with an `|`. This means all platform ids will be tried to find a match in left-to-right order of the definition.
88+
The game data at TGDB is in rare case in different platforms to be found.
89+
Prominent example is Sega's Genesis respecitve Mega Drive. For these edgecases
90+
you may find the platform ids in the `platforms_idmap.csv` separated with an
91+
`|`. This means all platform ids will be tried to find a match in left-to-right
92+
order of the definition.
8893

8994

9095
You can display the number with their platform name on each of the three
@@ -170,8 +175,13 @@ Example: Copy this excerpt from `peas.json`...
170175
```
171176

172177
If you have multiple platforms defined in your local file make sure the platform
173-
blocks are separated by a comma `,`.
174-
178+
blocks are separated by a comma `,`.
179+
To validate your `peas_local.json` file you can use the script
180+
`peas_validate_with_json_schema.py` (you can find it in the
181+
`supplementary/scraperdata` folder or sibling to the `Skyscraper` executable on
182+
a RetroPie installation (usually `/opt/retropie/supplementary/skyscraper/`)). See
183+
the comments in the script for usage, but after installing the dependency it
184+
boils down to provide the path to your JSON file as parameter to the script.
175185

176186
!!! tip "Case-sensitivity in EmulationStation Configuration"
177187

@@ -183,18 +193,18 @@ blocks are separated by a comma `,`.
183193
Outline:
184194

185195
1. Create a file `peas_local.json` sibling to `peas.json`. Enter in this file an
186-
empty `{}` JSON object.
187-
2. Create a new platform block in `peas_local.json` inside the outer (empty)
188-
block created before, or copy an existing block and adapt to your needs. For
189-
RetroPie your chosen `<platform_name>` must match the folder in
190-
`~/RetroPie/roms/<platform_name>`.
191-
3. Use `<platform_name>` also in `platforms_idmap_local.csv`. If you need to
196+
empty `{}` JSON object. Then create a new platform block in `peas_local.json`
197+
inside the outer (empty) block created before, or copy an existing block and
198+
adapt to your needs. For RetroPie your chosen `<platform_name>` must match
199+
the folder in `~/RetroPie/roms/<platform_name>`.
200+
2. Use `<platform_name>` also in `platforms_idmap_local.csv`. If you need to
192201
create an `platforms_idmap_local.csv` put in the column names
193202
`folder,screenscraper_id,mobygames_id,tgdb_id` (i.e. the first line of
194203
`platforms_idmap.csv`) . See also below for details of this CSV-file.
195-
4. If you use RetroPie do add the platform/system also to your `es_systems.cfg`
204+
3. If you use RetroPie do add the platform/system also to your `es_systems.cfg`
196205
as documented in the
197206
[RetroPie documentation](https://retropie.org.uk/docs/Add-a-New-System-in-EmulationStation/).
207+
4. That's it. Happy scraping!
198208

199209
There is also a verbatim example, you may skip the next section initially and
200210
jump directly into the [hands-on example](PLATFORMS.md#sample-usecase-adding-platform-satellaview).
@@ -235,7 +245,11 @@ folder on your filesystem where you keep your games.
235245

236246
!!! example "Use of Aliases"
237247

238-
The platforms ScummVM and Steam do not have an exact match on Mobygames, however you may scrape successfully for ScummVM and Steam games if you use 'PC', 'DOS', 'Windows', 'Linux' or similar as `"aliases": ...` in the `"scummvm": ...` or `"steam": ...` section of `peas.json`. Usually you find the platforms a game runs on if you lookup the game manually on the scraping website.
248+
The platforms ScummVM and Steam do not have an exact match on Mobygames, however
249+
you may scrape successfully for ScummVM and Steam games if you use 'PC', 'DOS',
250+
'Windows', 'Linux' or similar as `"aliases": ...` in the `"scummvm": ...` or
251+
`"steam": ...` section of `peas.json`. Usually you find the platforms a game
252+
runs on if you lookup the game manually on the scraping website.
239253

240254
### Sample Usecase: Adding Platform _Satellaview_
241255

@@ -323,12 +337,12 @@ for additional information on this.
323337

324338
- Line 3 defines the platform name, respective the folder name for your ROMs.
325339
Thus, Skyscraper expects to find ROMs in `/home/pi/RetroPie/roms/satellaview`.
326-
- Line 6 contains the extensions which are recognized by EmulationStation. These
327-
extensions should be also be present in the `"formats":` block of `peas.json`.
328-
However, Skyscraper uses case insensitive file extension mapping. The
329-
extensions `.7z` and `.zip` are added automagically by Skyscraper, thus the
330-
`"formats":` list is usually shorter than the EmulationStation `<extension/>`
331-
list.
340+
- Line 6 contains the extensions which are recognized by EmulationStation
341+
(explicitly in lowercase and UPPERCASE). These extensions should be also be
342+
present in the `"formats":` block of `peas.json`. However, Skyscraper uses
343+
case insensitive file extension mapping (lowercase only). The extensions `.7z`
344+
and `.zip` are added automagically by Skyscraper, thus the `"formats":` list
345+
is usually shorter than the EmulationStation `<extension/>` list.
332346
- Line 9: If your theme doesn't support Satellaview, you can also use `snes` as
333347
<theme> value.
334348

@@ -457,7 +471,7 @@ Forum/Skyscraper Thread](https://retropie.org.uk/forum/topic/34588). Thank you!
457471

458472
### Migrating `platforms.json` and `screenscraper.json`
459473

460-
This section is only applicable if you update from Skyscraper 3.7.7-2.
474+
This section is only applicable if you update from Skyscraper 3.7.7-2 or earlier.
461475

462476
!!! tip
463477

src/platform.cpp

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,20 @@ bool Platform::loadConfig() {
8888
QJsonDocument json(QJsonDocument::fromJson(jsonData));
8989

9090
if (json.isNull() || json.isEmpty()) {
91-
printf("\033[1;31mFile '%s' empty or no JSON format. Now "
91+
printf("\033[1;31mFile '%s' empty or invalid JSON format. Now "
9292
"quitting...\033[0m\n",
9393
fnPeas.toUtf8().constData());
9494
return false;
9595
}
9696

97-
QJsonObject jObjLocal = loadLocalConfig();
97+
bool ok = true;
98+
QJsonObject jObjLocal = loadLocalConfig(ok);
99+
if (!ok) {
100+
printf("\033[1;31mFile '%s' has invalid JSON format. Now "
101+
"quitting...\033[0m\n",
102+
fnPeasLocal.toUtf8().constData());
103+
return false;
104+
}
98105
QJsonObject jObj = json.object();
99106

100107
for (auto plit = jObjLocal.constBegin(); plit != jObjLocal.constEnd();
@@ -116,17 +123,22 @@ bool Platform::loadConfig() {
116123
return true;
117124
}
118125

119-
QJsonObject Platform::loadLocalConfig() {
126+
QJsonObject Platform::loadLocalConfig(bool &ok) {
120127
QJsonObject peasLocal;
121128
QFile configFile(fnPeasLocal);
122129

123130
if (configFile.open(QIODevice::ReadOnly)) {
124131
QByteArray jsonData = configFile.readAll();
125-
QJsonDocument json(QJsonDocument::fromJson(jsonData));
132+
QJsonParseError err;
133+
QJsonDocument json(QJsonDocument::fromJson(jsonData, &err));
126134

127135
if (!json.isNull() && !json.isEmpty()) {
128136
qDebug() << "successfully loaded" << fnPeasLocal;
129137
peasLocal = json.object();
138+
} else if (err.error != QJsonParseError::NoError) {
139+
qWarning()
140+
<< QString("JSON is malformed: %1").arg(err.errorString());
141+
ok = false;
130142
}
131143
}
132144
return peasLocal;
@@ -234,7 +246,7 @@ bool Platform::parsePlatformsIdCsv(const QString &platformsIdCsvFn) {
234246
} else {
235247
ids[col].append(-1);
236248
printf("\033[1;33mFile '%s', line '%s,%s' has "
237-
"unparsable or too negative int value '%s' (use "
249+
"unparsable or too negative number '%s' (use "
238250
"-1 for unknown platform id). Assumming -1 for "
239251
"now, please fix to mute this warning.\033[0m\n",
240252
fn, pkey.toUtf8().constData(),

src/platform.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ class Platform : public QObject {
5252
private:
5353
bool loadPlatformsIdMap();
5454
bool parsePlatformsIdCsv(const QString &fnplatformsIdCsvFn);
55-
QJsonObject loadLocalConfig();
55+
QJsonObject loadLocalConfig(bool &ok);
5656

5757
QStringList platforms;
5858
QVariantMap peas;
Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1-
# Skyscraper supplementary scripts
1+
# Skyscraper Supplementary Scripts
22

33
See also script header for information on usage. You can find additional
4-
information in Skyscraper's platform documentation (`PLATFORM.md`) for regarding
5-
this scripts.
4+
information in Skyscraper's platform documentation (`PLATFORM.md`) regarding
5+
these scripts.
66

77
`deepdiff_peas_jsonfiles.py`: Compare and diff output two `peas.json` files.
88

99
`mdb2sqlite.sh`: Convert MS Access MDB format to SQLite (only useful when you
10-
use the GameBase scraper).
10+
use the GameBase `-s gamebase` scraper).
1111

12-
`peas_and_idmap_verify.py`: Validate settings in `peas.json` and
12+
`peas_and_idmap_verify.py`: Validate (crosscheck) settings in `peas.json` and
1313
`platforms_idmap.csv` and dump a human readable output.
1414

15+
`peas_validate_with_json_schema.py`: Validate a given `peas*.json` file against
16+
the PEAS JSON Schema `peas-schema.json`.
17+
1518
`README-Skyscraper-Scripts.md`: This file.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"title": "Skyscraper's Platform, Extensions, Aliases and Scraper definition format (PEAS)",
4+
"type": "object",
5+
"additionalProperties": {
6+
"type": "object",
7+
"properties": {
8+
"aliases": {
9+
"type": "array",
10+
"description": "Alias(es) of a platform for scraping modules that do not use a platform id. They use the defined alias(es) for a platform match.",
11+
"uniqueItems": true,
12+
"items": { "type": "string" }
13+
},
14+
"formats": {
15+
"type": "array",
16+
"description": "Recognized gamefile extensions, lowercase, unique. Define extension beginning with '*.'.",
17+
"uniqueItems": true,
18+
"items": {
19+
"type": "string",
20+
"pattern": "^\\*.[a-z0-9\\.]+$"
21+
}
22+
}
23+
},
24+
"required": ["aliases", "formats"]
25+
}
26+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# /usr/bin/env python3
2+
3+
# Validates a JSON file against Skyscraper's PEAS JSON Schema.
4+
5+
# Usage:
6+
# python3 peas_validate_with_json_schema.py <path/to/peas_local.json>
7+
8+
# Preparation:
9+
# sudo apt install python3-jsonschema
10+
11+
# (c) 2026 Gemba @ GitHub
12+
# SPDX-License-Identifier: GPL-3.0-or-later
13+
14+
import json
15+
import sys
16+
from pathlib import Path
17+
from jsonschema import validate
18+
19+
if len(sys.argv) != 2:
20+
print("[!] Provide a peas.json or peas_local.json file as parameter")
21+
sys.exit(1)
22+
23+
peas_file = Path(sys.argv[1])
24+
25+
if not peas_file.exists():
26+
print(f"[!] File does not exist: '{peas_file}'")
27+
sys.exit(1)
28+
29+
with open(peas_file, "r", encoding="utf-8") as pf:
30+
peas_json = json.load(pf)
31+
32+
with open(Path(__file__).parent / "peas-schema.json", "r", encoding="utf-8") as sf:
33+
schema = json.load(sf)
34+
35+
try:
36+
validate(instance=peas_json, schema=schema)
37+
except Exception as e:
38+
print(f"[-] Validation failed for '{peas_file}':")
39+
raise e
40+
41+
print(f"[+] '{peas_file}' validated OK")

0 commit comments

Comments
 (0)