mirror of
https://github.com/rommapp/romm.git
synced 2026-10-12 02:21:58 +00:00
* feat: searchable alternative titles for ROMs Add an editable "Alternative titles" field to manual metadata, folded into the search titles alongside provider aliases. IGDB localization titles now join the ROM's alternative names, and the canonical IGDB name is kept as one when a regional title is displayed. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * fix: list hand-added titles in alternative_names, test the field Address Greptile review on #5111: the response's alternative_names now appends the hand-added titles to the first provider's, and the Alternative titles field gets a component test. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * test: type the alternative titles field lookup Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * fix: code-review pass on alternative titles Validate title lists from client-written metadata in one helper, list hand-added titles first so the search text cap never drops them, and let the titles combobox keep commas inside a title. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * refactor: simplify pass on alternative titles Dedupe IGDB titles once in build_igdb_rom, inline the manual title source, and reword the combobox comma comment. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * docs: review-polish pass on alternative titles Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * docs: humanizer pass on alternative titles Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * fix: hand-set alternative titles override, providers join A non-empty manual list replaces every provider's titles, in the list and in search; otherwise every provider's titles are joined. IGDB and Hasheous store every title they know, the canonical name and regional localizations included, and the ROM's displayed name is left out only when listing them. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * fix: validate manual metadata on ROM update A malformed raw_manual_metadata is rejected with a 422 instead of being stored and failing every later response for that ROM. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * fix: list each alternative title once however it is spaced Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
1625 lines
44 KiB
Python
1625 lines
44 KiB
Python
import json
|
|
from collections.abc import Callable
|
|
from typing import Any, Final, NotRequired, TypedDict
|
|
|
|
import httpx2
|
|
import pydash
|
|
import yarl
|
|
from fastapi import status
|
|
|
|
from adapters.services.response_validation import parse_response
|
|
from config import DEV_MODE, HASHEOUS_API_ENABLED, HASHEOUS_API_URL
|
|
from handler.filesystem.base_handler import (
|
|
normalize_provider_values,
|
|
provider_language_name,
|
|
provider_region_name,
|
|
)
|
|
from logger.logger import log
|
|
from models.rom import RomFile
|
|
from utils import get_version
|
|
from utils.context import ctx_httpx_client
|
|
from utils.datetime import parse_utc_timestamp
|
|
from utils.platform_slugs import UniversalPlatformSlug as UPS
|
|
|
|
from .base_handler import BaseRom, MetadataHandler, unavailable
|
|
from .igdb_handler import (
|
|
IGDB_AGE_RATINGS,
|
|
IGDBMetadata,
|
|
IGDBMetadataPlatform,
|
|
)
|
|
|
|
|
|
class HasheousMetadata(TypedDict):
|
|
tosec_match: bool
|
|
mame_arcade_match: bool
|
|
mame_mess_match: bool
|
|
nointro_match: bool
|
|
redump_match: bool
|
|
mame_redump_match: bool
|
|
whdload_match: bool
|
|
ra_match: bool
|
|
fbneo_match: bool
|
|
puredos_match: bool
|
|
# What the dump our hashes matched says of itself, kept by the scan.
|
|
dump_regions: NotRequired[list[str]]
|
|
dump_languages: NotRequired[list[str]]
|
|
dump_tags: NotRequired[list[str]]
|
|
|
|
|
|
class HasheousPlatform(TypedDict):
|
|
slug: str
|
|
hasheous_id: int | None
|
|
name: NotRequired[str]
|
|
igdb_id: NotRequired[int | None]
|
|
tgdb_id: NotRequired[int | None]
|
|
ra_id: NotRequired[int | None]
|
|
|
|
|
|
class HasheousRom(BaseRom):
|
|
hasheous_id: int | None
|
|
regions: NotRequired[list[str]]
|
|
languages: NotRequired[list[str]]
|
|
igdb_id: NotRequired[int | None]
|
|
slug: NotRequired[str]
|
|
igdb_metadata: NotRequired[IGDBMetadata]
|
|
tgdb_id: NotRequired[int | None]
|
|
hasheous_metadata: NotRequired[HasheousMetadata]
|
|
|
|
|
|
ACCEPTABLE_FILE_EXTENSIONS_BY_PLATFORM_SLUG = {UPS.DC: ["bin", "chd", "cue"]}
|
|
|
|
# Signature sources whose region data is preferred, as Hasheous spells them.
|
|
# The curated per-region dumps describe one release; the rest lump variants
|
|
# together or, for ScreenScraper, carry the game's whole release spread.
|
|
PREFERRED_SIGNATURE_SOURCES: Final = ("NoIntros", "Redump")
|
|
|
|
# Codes Hasheous prints as ISO-3166 that a filename shortcode claims for
|
|
# somewhere else: CH is Switzerland here and China in a No-Intro name, AS is
|
|
# American Samoa and Asia. The name printed beside them decides instead.
|
|
_AMBIGUOUS_COUNTRY_CODES: Final = frozenset({"ch", "as"})
|
|
|
|
|
|
def _country_name(code: str) -> str | None:
|
|
if code.strip().lower() in _AMBIGUOUS_COUNTRY_CODES:
|
|
return None
|
|
return provider_region_name(code)
|
|
|
|
|
|
def _tags_from_signatures(
|
|
signatures: dict[str, Any],
|
|
field: str,
|
|
resolve: Callable[[str], str | None],
|
|
) -> list[str]:
|
|
"""Read one dump's countries or languages out of the matched signatures.
|
|
|
|
Only `rom` is read, never the `game` beside it, which spans every release
|
|
of the title. A resolved code wins over the name Hasheous printed.
|
|
"""
|
|
if not isinstance(signatures, dict):
|
|
return []
|
|
|
|
ordered_sources = [
|
|
source for source in PREFERRED_SIGNATURE_SOURCES if source in signatures
|
|
] + [source for source in signatures if source not in PREFERRED_SIGNATURE_SOURCES]
|
|
|
|
# Every field below comes straight off the wire, so none of its shapes are
|
|
# assumed: a raise here would abort the scan of the rom.
|
|
for source in ordered_sources:
|
|
entries = signatures.get(source)
|
|
for entry in entries if isinstance(entries, list) else []:
|
|
tags = pydash.get(entry, ["rom", field])
|
|
if not isinstance(tags, dict):
|
|
continue
|
|
|
|
values = normalize_provider_values(
|
|
(
|
|
resolve(code) or (name if isinstance(name, str) else "")
|
|
for code, name in tags.items()
|
|
if isinstance(code, str)
|
|
),
|
|
resolve,
|
|
)
|
|
if values:
|
|
return values
|
|
|
|
return []
|
|
|
|
|
|
def _involved_company_names(rom: dict[str, Any], role: str) -> list[str]:
|
|
"""Company names for an IGDB involvement role.
|
|
|
|
The proxy keys its expanded lists by id, so involvements arrive as a dict
|
|
rather than the list IGDB itself returns.
|
|
"""
|
|
involved = pydash.values(rom.get("involved_companies", {}))
|
|
return pydash.compact(
|
|
pydash.map_([c for c in involved if c.get(role)], "company.name")
|
|
)
|
|
|
|
|
|
def extract_metadata_from_igdb_rom(rom: dict[str, Any]) -> IGDBMetadata:
|
|
return IGDBMetadata(
|
|
{
|
|
"youtube_video_id": (
|
|
list(rom["videos"].values())[0]["video_id"]
|
|
if rom.get("videos")
|
|
else None
|
|
),
|
|
"total_rating": str(round(rom.get("total_rating", 0.0), 2)),
|
|
"total_rating_count": rom.get("total_rating_count"),
|
|
"aggregated_rating": str(round(rom.get("aggregated_rating", 0.0), 2)),
|
|
"first_release_date": parse_utc_timestamp(
|
|
rom.get("first_release_date"), iso=True
|
|
),
|
|
"genres": pydash.map_(rom.get("genres", {}), "name"),
|
|
"franchises": pydash.compact(
|
|
[rom.get("franchise.name", None)]
|
|
+ pydash.map_(rom.get("franchises", {}), "name")
|
|
),
|
|
"alternative_names": pydash.uniq(
|
|
pydash.compact(
|
|
[
|
|
rom.get("name"),
|
|
*pydash.map_(rom.get("alternative_names", {}), "name"),
|
|
*pydash.map_(rom.get("game_localizations", {}), "name"),
|
|
]
|
|
)
|
|
),
|
|
"collections": pydash.map_(rom.get("collections", {}), "name"),
|
|
"game_modes": pydash.map_(rom.get("game_modes", {}), "name"),
|
|
# Not in `expandColumns`, so the proxy returns bare ids with no names.
|
|
"keywords": [],
|
|
"themes": [],
|
|
"player_perspectives": [],
|
|
"companies": pydash.compact(
|
|
pydash.map_(rom.get("involved_companies", {}), "company.name")
|
|
),
|
|
"publishers": _involved_company_names(rom, "publisher"),
|
|
"developers": _involved_company_names(rom, "developer"),
|
|
"platforms": [
|
|
IGDBMetadataPlatform(igdb_id=p.get("id", ""), name=p.get("name", ""))
|
|
for p in pydash.map_(rom.get("platforms", {}))
|
|
],
|
|
"age_ratings": [
|
|
IGDB_AGE_RATINGS[r]
|
|
for r in pydash.map_(rom.get("age_ratings", {}), "rating_category")
|
|
if r in IGDB_AGE_RATINGS
|
|
],
|
|
"expansions": [],
|
|
"dlcs": [],
|
|
"ports": [],
|
|
"remakes": [],
|
|
"remasters": [],
|
|
"similar_games": [],
|
|
"expanded_games": [],
|
|
# TODO: extract multiplayer modes and derive player count
|
|
"multiplayer_modes": [],
|
|
"player_count": "1",
|
|
}
|
|
)
|
|
|
|
|
|
class HasheousHandler(MetadataHandler):
|
|
def __init__(self) -> None:
|
|
self.BASE_URL = HASHEOUS_API_URL
|
|
# Cover art is linked relative to the site root, not the API path.
|
|
try:
|
|
self.BASE_ORIGIN = str(yarl.URL(self.BASE_URL).origin())
|
|
except ValueError:
|
|
log.warning(
|
|
"Invalid HASHEOUS_API_URL %r, cover art URLs may be wrong",
|
|
self.BASE_URL,
|
|
)
|
|
self.BASE_ORIGIN = ""
|
|
self.healthcheck_endpoint = f"{self.BASE_URL}/HealthCheck"
|
|
self.platform_endpoint = f"{self.BASE_URL}/Lookup/Platforms"
|
|
self.games_endpoint = f"{self.BASE_URL}/Lookup/ByHash"
|
|
self.proxy_igdb_game_endpoint = f"{self.BASE_URL}/MetadataProxy/IGDB/Game"
|
|
self.proxy_igdb_cover_endpoint = f"{self.BASE_URL}/MetadataProxy/IGDB/Cover"
|
|
self.app_api_key = (
|
|
"UUvh9ef_CddMM4xXO1iqxl9FqEt764v33LU-UiGFc0P34odXjMP9M6MTeE4JZRxZ"
|
|
if DEV_MODE
|
|
else "JNoFBA-jEh4HbxuxEHM6MVzydKoAXs9eCcp2dvcg5LRCnpp312voiWmjuaIssSzS"
|
|
)
|
|
|
|
@classmethod
|
|
def is_enabled(cls) -> bool:
|
|
"""Return whether this metadata handler is enabled."""
|
|
return HASHEOUS_API_ENABLED
|
|
|
|
async def heartbeat(self) -> bool:
|
|
if not self.is_enabled():
|
|
return False
|
|
|
|
httpx_client = ctx_httpx_client.get()
|
|
try:
|
|
response = await httpx_client.get(self.healthcheck_endpoint)
|
|
response.raise_for_status()
|
|
except Exception as e:
|
|
log.error("Error checking Hasheous API: %s", e)
|
|
return False
|
|
|
|
return bool(response)
|
|
|
|
async def _request(
|
|
self,
|
|
url: str,
|
|
method: str = "POST",
|
|
params: dict[str, Any] | None = None,
|
|
data: dict[str, Any] | list[Any] | None = None,
|
|
) -> dict[str, Any]:
|
|
httpx_client = ctx_httpx_client.get()
|
|
|
|
# Normalize method to uppercase
|
|
method = method.upper()
|
|
if method not in ["GET", "POST"]:
|
|
raise ValueError(f"Unsupported HTTP method: {method}")
|
|
|
|
try:
|
|
log.debug(
|
|
"API request: Method=%s, URL=%s, Params=%s, Data=%s",
|
|
method,
|
|
url,
|
|
params,
|
|
data,
|
|
)
|
|
|
|
# Prepare request kwargs
|
|
request_kwargs: dict[str, Any] = {
|
|
"url": url,
|
|
"params": params,
|
|
"headers": {
|
|
"Content-Type": "application/json-patch+json",
|
|
"User-Agent": f"RomM/{get_version()}",
|
|
"X-Client-API-Key": self.app_api_key,
|
|
},
|
|
"timeout": 120,
|
|
}
|
|
|
|
# Add method-specific parameters
|
|
if method == "POST":
|
|
request_kwargs["json"] = data
|
|
|
|
res = await httpx_client.request(method, **request_kwargs)
|
|
res.raise_for_status()
|
|
return parse_response(dict[str, Any], res.content, source="Hasheous") or {}
|
|
except httpx2.HTTPStatusError as exc:
|
|
# Check if its a 404 error
|
|
if exc.response.status_code == status.HTTP_404_NOT_FOUND:
|
|
log.debug("Game not found in Hasheous API")
|
|
return {}
|
|
|
|
log.error(
|
|
"Hasheous API returned an error: %s %s",
|
|
exc.response.status_code,
|
|
exc.response.text,
|
|
)
|
|
raise unavailable("Hasheous") from exc
|
|
except httpx2.NetworkError as exc:
|
|
log.critical("Connection error: can't connect to Hasheous")
|
|
raise unavailable("Hasheous") from exc
|
|
except json.decoder.JSONDecodeError as exc:
|
|
# Log the error and return an empty dict if the response is not valid JSON
|
|
log.error(exc)
|
|
return {}
|
|
except httpx2.TimeoutException as exc:
|
|
log.error("Hasheous API timed out: %s", exc)
|
|
raise unavailable("Hasheous") from exc
|
|
|
|
def get_platform(self, slug: str) -> HasheousPlatform:
|
|
if slug not in HASHEOUS_PLATFORM_LIST:
|
|
return HasheousPlatform(hasheous_id=None, slug=slug)
|
|
|
|
platform = HASHEOUS_PLATFORM_LIST[UPS(slug)]
|
|
return HasheousPlatform(
|
|
hasheous_id=platform["id"],
|
|
slug=slug,
|
|
name=platform["name"],
|
|
igdb_id=platform["igdb_id"],
|
|
tgdb_id=platform["tgdb_id"],
|
|
ra_id=platform["ra_id"],
|
|
)
|
|
|
|
async def _lookup_by_hashes(
|
|
self, data: list[dict[str, str | None]]
|
|
) -> dict[str, Any]:
|
|
return await self._request(
|
|
self.games_endpoint,
|
|
params={
|
|
"returnAllSources": "true",
|
|
"returnFields": "Signatures, Metadata, Attributes",
|
|
},
|
|
data=data,
|
|
)
|
|
|
|
async def lookup_rom(
|
|
self, platform_slug: str, files: list[RomFile]
|
|
) -> tuple[HasheousRom, bool]:
|
|
"""Identify a ROM by its file hashes.
|
|
|
|
Returns a HasheousRom with the matched IDs, or an empty match if the
|
|
lookup fails. The lookup is best-effort and never raises to the caller,
|
|
so an unreachable Hasheous can't abort a scan.
|
|
|
|
The second value tells the two empty matches apart: True means Hasheous
|
|
answered and knows nothing about these hashes, False means we never got
|
|
an answer (disabled, no hashes to send, or the request failed). Only the
|
|
former is safe to treat as "this ROM is not in any database".
|
|
"""
|
|
fallback_rom = HasheousRom(hasheous_id=None, igdb_id=None, tgdb_id=None)
|
|
|
|
if not self.is_enabled():
|
|
return fallback_rom, False
|
|
|
|
filtered_files = [
|
|
file
|
|
for file in files
|
|
if file.file_size_bytes > 0
|
|
and file.is_top_level
|
|
and file.file_extension.lower() != "m3u"
|
|
and (
|
|
UPS(platform_slug) not in ACCEPTABLE_FILE_EXTENSIONS_BY_PLATFORM_SLUG
|
|
or file.file_extension
|
|
in ACCEPTABLE_FILE_EXTENSIONS_BY_PLATFORM_SLUG[UPS(platform_slug)]
|
|
)
|
|
]
|
|
|
|
# One request carries every top-level file's hashes, but Hasheous only
|
|
# answers it when it knows all of them.
|
|
data: list[dict[str, Any]] = []
|
|
for file in filtered_files:
|
|
hashes = file.lookup_hashes
|
|
file_hashes: dict[str, str | None] = {
|
|
"mD5": hashes.md5,
|
|
"shA1": hashes.sha1,
|
|
"crc": hashes.crc,
|
|
}
|
|
|
|
# Drop empty hashes and skip files that have none.
|
|
file_hashes = {key: value for key, value in file_hashes.items() if value}
|
|
if file_hashes:
|
|
data.append(file_hashes)
|
|
|
|
if not data:
|
|
log.warning(
|
|
"No hashes provided for Hasheous lookup. "
|
|
"At least one of md5, sha1, or crc is required."
|
|
)
|
|
return fallback_rom, False
|
|
|
|
try:
|
|
hasheous_game = await self._lookup_by_hashes(data)
|
|
# A sidecar file or an off-set disc hides the match the rest would
|
|
# make, so each file is asked alone and every answer must agree.
|
|
per_file_games: dict[Any, dict[str, Any]] = {}
|
|
if not hasheous_game and len(data) > 1:
|
|
for file_hashes in data:
|
|
game = await self._lookup_by_hashes([file_hashes])
|
|
if game:
|
|
per_file_games[game.get("id")] = game
|
|
if len(per_file_games) > 1:
|
|
break
|
|
except Exception as exc:
|
|
log.error("Hasheous hash lookup failed, skipping: %s", exc)
|
|
return fallback_rom, False
|
|
|
|
if len(per_file_games) > 1:
|
|
log.warning(
|
|
"Hasheous matched files of one rom to different games %s, skipping",
|
|
list(per_file_games),
|
|
)
|
|
return fallback_rom, False
|
|
if per_file_games:
|
|
hasheous_game = next(iter(per_file_games.values()))
|
|
|
|
if not hasheous_game:
|
|
return fallback_rom, True
|
|
|
|
metadata = hasheous_game.get("metadata", [])
|
|
attributes = hasheous_game.get("attributes", [])
|
|
signatures = hasheous_game.get("signatures", {})
|
|
|
|
igdb_id = None
|
|
tgdb_id = None
|
|
|
|
for meta in metadata:
|
|
if meta["source"] == "IGDB":
|
|
try:
|
|
# TEMP: Hasheous is slowly replacing slugs with IDs
|
|
igdb_id = int(meta["immutableId"])
|
|
except ValueError, TypeError:
|
|
log.debug(
|
|
f"Found an IGDB slug instead of an ID: {meta['immutableId']}"
|
|
)
|
|
pass
|
|
elif meta["source"] == "TheGamesDb":
|
|
tgdb_id = meta["immutableId"]
|
|
|
|
url_cover = ""
|
|
for attr in attributes:
|
|
if attr["attributeName"] == "Logo":
|
|
url_cover = f"{self.BASE_ORIGIN}{attr['link']}"
|
|
break
|
|
|
|
return (
|
|
HasheousRom(
|
|
hasheous_id=hasheous_game["id"],
|
|
name=hasheous_game.get("name", ""),
|
|
regions=_tags_from_signatures(signatures, "country", _country_name),
|
|
languages=_tags_from_signatures(
|
|
signatures, "language", provider_language_name
|
|
),
|
|
igdb_id=int(igdb_id) if igdb_id else None,
|
|
tgdb_id=int(tgdb_id) if tgdb_id else None,
|
|
url_cover=url_cover,
|
|
# Keys are Hasheous' SignatureSourceType names, spelled exactly
|
|
# as its API returns them.
|
|
hasheous_metadata=HasheousMetadata(
|
|
tosec_match="TOSEC" in signatures,
|
|
mame_arcade_match="MAMEArcade" in signatures,
|
|
mame_mess_match="MAMEMess" in signatures,
|
|
nointro_match="NoIntros" in signatures,
|
|
redump_match="Redump" in signatures,
|
|
mame_redump_match="MAMERedump" in signatures,
|
|
whdload_match="WHDLoad" in signatures,
|
|
ra_match="RetroAchievements" in signatures,
|
|
fbneo_match="FBNeo" in signatures,
|
|
puredos_match="PureDOSDAT" in signatures,
|
|
),
|
|
),
|
|
True,
|
|
)
|
|
|
|
async def get_igdb_game(self, hasheous_rom: HasheousRom) -> HasheousRom:
|
|
if not self.is_enabled():
|
|
return hasheous_rom
|
|
|
|
igdb_id = hasheous_rom.get("igdb_id", None)
|
|
|
|
if igdb_id is None:
|
|
log.info("No IGDB ID provided for Hasheous IGDB game lookup.")
|
|
return hasheous_rom
|
|
|
|
igdb_game = await self._request(
|
|
self.proxy_igdb_game_endpoint,
|
|
params={
|
|
"Id": igdb_id,
|
|
"expandColumns": "age_ratings, alternative_names, collections, cover, dlcs, expanded_games, franchise, franchises, game_localizations, game_modes, genres, involved_companies, platforms, ports, remakes, screenshots, similar_games, videos",
|
|
},
|
|
method="GET",
|
|
)
|
|
|
|
if not igdb_game:
|
|
log.debug(f"No Hasheous game found for IGDB ID {igdb_id}.")
|
|
return hasheous_rom
|
|
|
|
return HasheousRom(
|
|
{
|
|
**hasheous_rom,
|
|
"slug": igdb_game.get("slug") or hasheous_rom.get("slug") or "",
|
|
"name": igdb_game.get("name") or hasheous_rom.get("name") or "",
|
|
"summary": igdb_game.get("summary", ""),
|
|
"url_cover": self.normalize_cover_url(
|
|
pydash.get(igdb_game, "cover.url", "")
|
|
).replace("t_thumb", "t_1080p")
|
|
or hasheous_rom.get("url_cover", ""),
|
|
"url_screenshots": [
|
|
self.normalize_cover_url(s.get("url", "")).replace(
|
|
"t_thumb", "t_720p"
|
|
)
|
|
for s in pydash.get(igdb_game, "screenshots", {}).values()
|
|
]
|
|
or hasheous_rom.get("url_screenshots", []),
|
|
"igdb_metadata": extract_metadata_from_igdb_rom(igdb_game),
|
|
}
|
|
)
|
|
|
|
|
|
class SlugToHasheousId(TypedDict):
|
|
id: int
|
|
name: str
|
|
igdb_id: int | None
|
|
igdb_slug: str | None
|
|
tgdb_id: int | None
|
|
ra_id: int | None
|
|
|
|
|
|
HASHEOUS_PLATFORM_LIST: dict[UPS, SlugToHasheousId] = {
|
|
UPS._3DO: {
|
|
"id": 161825,
|
|
"igdb_id": 50,
|
|
"igdb_slug": "3do",
|
|
"name": "3DO Interactive Multiplayer",
|
|
"ra_id": 43,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.N3DS: {
|
|
"id": 62,
|
|
"igdb_id": 37,
|
|
"igdb_slug": "3ds",
|
|
"name": "Nintendo 3DS",
|
|
"ra_id": 62,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.N64DD: {
|
|
"id": 65,
|
|
"igdb_id": 416,
|
|
"igdb_slug": "64dd",
|
|
"name": "Nintendo 64DD",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.ACORN_ARCHIMEDES: {
|
|
"id": 24,
|
|
"igdb_id": 116,
|
|
"igdb_slug": "acorn-archimedes",
|
|
"name": "Acorn Archimedes",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.ACORN_ELECTRON: {
|
|
"id": 25,
|
|
"igdb_id": 134,
|
|
"igdb_slug": "acorn-electron",
|
|
"name": "Acorn Electron",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.ACPC: {
|
|
"id": 28,
|
|
"igdb_id": 25,
|
|
"igdb_slug": "acpc",
|
|
"name": "Amstrad CPC",
|
|
"ra_id": 37,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.ACTION_MAX: {
|
|
"id": 232983,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "Action Max",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.ADVENTURE_VISION: {
|
|
"id": 234388,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "Entex Adventure Vision",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.ALTAIR_8800: {
|
|
"id": 234456,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "MITS Altair 8800",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.AMIGA: {
|
|
"id": 3,
|
|
"igdb_id": 16,
|
|
"igdb_slug": "amiga",
|
|
"name": "Commodore Amiga",
|
|
"ra_id": 35,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.AMIGA_CD32: {
|
|
"id": 161823,
|
|
"igdb_id": 114,
|
|
"igdb_slug": "amiga-cd32",
|
|
"name": "Commodore CD32",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.AMSTRAD_GX4000: {
|
|
"id": 61540,
|
|
"igdb_id": 506,
|
|
"igdb_slug": "amstrad-gx4000",
|
|
"name": "Amstrad GX4000",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.AMSTRAD_PCW: {
|
|
"id": 29,
|
|
"igdb_id": 154,
|
|
"igdb_slug": "amstrad-pcw",
|
|
"name": "Amstrad PCW",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.APF: {
|
|
"id": 61738,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "APF Imagination Machine",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.APPLE: {
|
|
"id": 61885,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "Apple I",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.APPLE_IIGS: {
|
|
"id": 21,
|
|
"igdb_id": 115,
|
|
"igdb_slug": "apple-iigs",
|
|
"name": "Apple IIGS",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.APPLE_LISA: {
|
|
"id": 69659,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "Apple Lisa",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.APPLE_PIPPIN: {
|
|
"id": 22,
|
|
"igdb_id": 476,
|
|
"igdb_slug": "apple-pippin",
|
|
"name": "Apple Pippin",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.APPLEII: {
|
|
"id": 20,
|
|
"igdb_id": 75,
|
|
"igdb_slug": "appleii",
|
|
"name": "Apple II",
|
|
"ra_id": 38,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.APPLEIII: {
|
|
"id": 63154,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "Apple III",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.ARCADE: {
|
|
"id": 178,
|
|
"igdb_id": 52,
|
|
"igdb_slug": "arcade",
|
|
"name": "Arcade",
|
|
"ra_id": 27,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.ARDUBOY: {
|
|
"id": 244294,
|
|
"igdb_id": 438,
|
|
"igdb_slug": "arduboy",
|
|
"name": "Arduboy",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.ASTROCADE: {
|
|
"id": 31,
|
|
"igdb_id": 91,
|
|
"igdb_slug": "astrocade",
|
|
"name": "Bally Astrocade",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.ATARI_ST: {
|
|
"id": 15,
|
|
"igdb_id": 63,
|
|
"igdb_slug": "atari-st",
|
|
"name": "Atari ST/STE",
|
|
"ra_id": 36,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.ATARI2600: {
|
|
"id": 12,
|
|
"igdb_id": 59,
|
|
"igdb_slug": "atari2600",
|
|
"name": "Atari 2600",
|
|
"ra_id": 25,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.ATARI5200: {
|
|
"id": 17,
|
|
"igdb_id": 66,
|
|
"igdb_slug": "atari5200",
|
|
"name": "Atari 5200",
|
|
"ra_id": 50,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.ATARI7800: {
|
|
"id": 16,
|
|
"igdb_id": 60,
|
|
"igdb_slug": "atari7800",
|
|
"name": "Atari 7800",
|
|
"ra_id": 51,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.ATARI8BIT: {
|
|
"id": 18,
|
|
"igdb_id": 65,
|
|
"igdb_slug": "atari8bit",
|
|
"name": "Atari 8-bit",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.ATOM: {
|
|
"id": 55099,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "Acorn Atom",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.BBCMICRO: {
|
|
"id": 26,
|
|
"igdb_id": 69,
|
|
"igdb_slug": "bbcmicro",
|
|
"name": "BBC Micro",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.BEENA: {
|
|
"id": 82,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "Sega Advanced Pico Beena",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.BIT_90: {
|
|
"id": 97614,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "Bit Corporation BIT 90",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.C_PLUS_4: {
|
|
"id": 7,
|
|
"igdb_id": 94,
|
|
"igdb_slug": "c-plus-4",
|
|
"name": "Commodore Plus/4",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.C128: {
|
|
"id": 8,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "Commodore 128",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.C16: {
|
|
"id": 6,
|
|
"igdb_id": 93,
|
|
"igdb_slug": "c16",
|
|
"name": "Commodore 16",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.C64: {
|
|
"id": 5,
|
|
"igdb_id": 15,
|
|
"igdb_slug": "c64",
|
|
"name": "Commodore MAX",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.CAMPUTERS_LYNX: {
|
|
"id": 97720,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "Camputers Lynx",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.CASIO_CFX_9850: {
|
|
"id": 97839,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "Casio CFX-9850",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.CASIO_FP_1000: {
|
|
"id": 98757,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "Casio FP-1000 & FP-1100",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.CASIO_LOOPY: {
|
|
"id": 37,
|
|
"igdb_id": 380,
|
|
"igdb_slug": "casio-loopy",
|
|
"name": "Casio Loopy",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.CASIO_PB_1000: {
|
|
"id": 98771,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "Casio PB-1000",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.CASIO_PV_1000: {
|
|
"id": 98793,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "Casio PV-1000",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.CASIO_PV_2000: {
|
|
"id": 98811,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "Casio PV-2000",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.COLECOVISION: {
|
|
"id": 39,
|
|
"igdb_id": 68,
|
|
"igdb_slug": "colecovision",
|
|
"name": "ColecoVision",
|
|
"ra_id": 44,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.COMMANDER_X16: {
|
|
"id": 54769,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "8-Bit Productions Commander X16",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.COMMODORE_CDTV: {
|
|
"id": 9,
|
|
"igdb_id": 158,
|
|
"igdb_slug": "commodore-cdtv",
|
|
"name": "Commodore CDTV",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.CPET: {
|
|
"id": 10,
|
|
"igdb_id": 90,
|
|
"igdb_slug": "cpet",
|
|
"name": "Commodore PET",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.DC: {
|
|
"id": 54694,
|
|
"igdb_id": 23,
|
|
"igdb_slug": "dc",
|
|
"name": "Sega Dreamcast",
|
|
"ra_id": 40,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.DOOM: {
|
|
"id": 645195,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "PrBoom",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.DOS: {
|
|
"id": 233075,
|
|
"igdb_id": 13,
|
|
"igdb_slug": "dos",
|
|
"name": "Microsoft DOS",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.EXCALIBUR_64: {
|
|
"id": 97612,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "BGR Computers Excalibur 64",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.FAIRCHILD_CHANNEL_F: {
|
|
"id": 43,
|
|
"igdb_id": 127,
|
|
"igdb_slug": "fairchild-channel-f",
|
|
"name": "Fairchild Channel F",
|
|
"ra_id": 57,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.FAMICOM: {
|
|
"id": 68,
|
|
"igdb_id": 18,
|
|
"igdb_slug": "nes",
|
|
"name": "Nintendo Entertainment System",
|
|
"ra_id": 7,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.FDS: {
|
|
"id": 54692,
|
|
"igdb_id": 51,
|
|
"igdb_slug": "fds",
|
|
"name": "Nintendo Famicom Disk System",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.FM_TOWNS: {
|
|
"id": 238902,
|
|
"igdb_id": 118,
|
|
"igdb_slug": "fm-towns",
|
|
"name": "Fujitsu - FM Towns",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.GAMATE: {
|
|
"id": 97616,
|
|
"igdb_id": 378,
|
|
"igdb_slug": "gamate",
|
|
"name": "Bit Corporation Gamate",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.GAMEGEAR: {
|
|
"id": 84,
|
|
"igdb_id": 35,
|
|
"igdb_slug": "gamegear",
|
|
"name": "Sega Game Gear",
|
|
"ra_id": 15,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.GB: {
|
|
"id": 70,
|
|
"igdb_id": 33,
|
|
"igdb_slug": "gb",
|
|
"name": "Nintendo GameBoy",
|
|
"ra_id": 4,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.GBA: {
|
|
"id": 71,
|
|
"igdb_id": 24,
|
|
"igdb_slug": "gba",
|
|
"name": "Nintendo Game Boy Advance",
|
|
"ra_id": 5,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.GBC: {
|
|
"id": 72,
|
|
"igdb_id": 22,
|
|
"igdb_slug": "gbc",
|
|
"name": "Nintendo Game Boy Color",
|
|
"ra_id": 6,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.GENESIS: {
|
|
"id": 86,
|
|
"igdb_id": 29,
|
|
"igdb_slug": "genesis-slash-megadrive",
|
|
"name": "Sega Mega Drive / Genesis",
|
|
"ra_id": 1,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.INTELLIVISION: {
|
|
"id": 52,
|
|
"igdb_id": 67,
|
|
"igdb_slug": "intellivision",
|
|
"name": "Mattel Intellivision",
|
|
"ra_id": 45,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.JAGUAR: {
|
|
"id": 13,
|
|
"igdb_id": 62,
|
|
"igdb_slug": "jaguar",
|
|
"name": "Atari Jaguar",
|
|
"ra_id": 17,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.LINUX: {
|
|
"id": 233076,
|
|
"igdb_id": 3,
|
|
"igdb_slug": "linux",
|
|
"name": "Linux",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.LYNX: {
|
|
"id": 14,
|
|
"igdb_id": 61,
|
|
"igdb_slug": "lynx",
|
|
"name": "Atari Lynx",
|
|
"ra_id": 13,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.MAC: {
|
|
"id": 30,
|
|
"igdb_id": 14,
|
|
"igdb_slug": "mac",
|
|
"name": "Apple Mac",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.AQUARIUS: {
|
|
"id": 51,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "Mattel Aquarius",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.MICROBEE: {
|
|
"id": 69714,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "Applied Technology MicroBee",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.MSX: {
|
|
"id": 53,
|
|
"igdb_id": 27,
|
|
"igdb_slug": "msx",
|
|
"name": "MSX",
|
|
"ra_id": 29,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.MSX2: {
|
|
"id": 54,
|
|
"igdb_id": 53,
|
|
"igdb_slug": "msx2",
|
|
"name": "MSX 2",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.MULTIVISION: {
|
|
"id": 52922,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "Tsukuda Original Othello Multivision",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.N64: {
|
|
"id": 64,
|
|
"igdb_id": 4,
|
|
"igdb_slug": "n64",
|
|
"name": "Nintendo 64",
|
|
"ra_id": 2,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.NDS: {
|
|
"id": 66,
|
|
"igdb_id": 20,
|
|
"igdb_slug": "nds",
|
|
"name": "Nintendo DS",
|
|
"ra_id": 18,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.NEC_PC_6000_SERIES: {
|
|
"id": 58,
|
|
"igdb_id": 157,
|
|
"igdb_slug": "nec-pc-6000-series",
|
|
"name": "NEC PC-6000",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.NEO_GEO_CD: {
|
|
"id": 161829,
|
|
"igdb_id": 136,
|
|
"igdb_slug": "neo-geo-cd",
|
|
"name": "Neo Geo CD",
|
|
"ra_id": 56,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.NEO_GEO_POCKET: {
|
|
"id": 97,
|
|
"igdb_id": 119,
|
|
"igdb_slug": "neo-geo-pocket",
|
|
"name": "Neo Geo Pocket",
|
|
"ra_id": 14,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.NEO_GEO_POCKET_COLOR: {
|
|
"id": 98,
|
|
"igdb_id": 120,
|
|
"igdb_slug": "neo-geo-pocket-color",
|
|
"name": "Neo Geo Pocket Color",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.NEOGEOAES: {
|
|
"id": 96,
|
|
"igdb_id": 80,
|
|
"igdb_slug": "neogeoaes",
|
|
"name": "Neo Geo",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.NES: {
|
|
"id": 68,
|
|
"igdb_id": 18,
|
|
"igdb_slug": "nes",
|
|
"name": "Nintendo Entertainment System",
|
|
"ra_id": 7,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.NEW_NINTENDON3DS: {
|
|
"id": 63,
|
|
"igdb_id": 137,
|
|
"igdb_slug": "new-nintendo-3ds",
|
|
"name": "Nintendo New 3DS",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.NGC: {
|
|
"id": 73,
|
|
"igdb_id": 21,
|
|
"igdb_slug": "ngc",
|
|
"name": "Nintendo GameCube",
|
|
"ra_id": 16,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.NINTENDO_DSI: {
|
|
"id": 67,
|
|
"igdb_id": 159,
|
|
"igdb_slug": "nintendo-dsi",
|
|
"name": "Nintendo DSi",
|
|
"ra_id": 78,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.ODYSSEY: {
|
|
"id": 48,
|
|
"igdb_id": 88,
|
|
"igdb_slug": "odyssey--1",
|
|
"name": "Magnavox Odyssey",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.ODYSSEY_2: {
|
|
"id": 49,
|
|
"igdb_id": 133,
|
|
"igdb_slug": "odyssey-2-slash-videopac-g7000",
|
|
"name": "Magnavox Odyssey 2",
|
|
"ra_id": 23,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.PC_8800_SERIES: {
|
|
"id": 57,
|
|
"igdb_id": 125,
|
|
"igdb_slug": "pc-8800-series",
|
|
"name": "NEC PC-8800",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.PC_9800_SERIES: {
|
|
"id": 59,
|
|
"igdb_id": 149,
|
|
"igdb_slug": "pc-9800-series",
|
|
"name": "NEC PC-9000",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.PC_JR: {
|
|
"id": 233269,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "IBM PCjr",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.PHILIPS_CD_I: {
|
|
"id": 161827,
|
|
"igdb_id": 117,
|
|
"igdb_slug": "philips-cd-i",
|
|
"name": "Philips CD-i",
|
|
"ra_id": 42,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.POCKET_CHALLENGE_V2: {
|
|
"id": 97550,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "Benesse Pocket Challenge V2",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.POCKET_CHALLENGE_W: {
|
|
"id": 97577,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "Benesse Pocket Challenge W",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.POCKETSTATION: {
|
|
"id": 103,
|
|
"igdb_id": 441,
|
|
"igdb_slug": "pocketstation",
|
|
"name": "Sony PocketStation",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.POKEMON_MINI: {
|
|
"id": 244733,
|
|
"igdb_id": 166,
|
|
"igdb_slug": "pokemon-mini",
|
|
"name": "Nintendo Pokemon Mini",
|
|
"ra_id": 24,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.PS2: {
|
|
"id": 101,
|
|
"igdb_id": 8,
|
|
"igdb_slug": "ps2",
|
|
"name": "Sony PlayStation 2",
|
|
"ra_id": 21,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.PS3: {
|
|
"id": 161830,
|
|
"igdb_id": 9,
|
|
"igdb_slug": "ps3",
|
|
"name": "Sony Playstation 3",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.PS4: {
|
|
"id": 232986,
|
|
"igdb_id": 48,
|
|
"igdb_slug": "ps4--1",
|
|
"name": "Sony Playstation 4",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.PS5: {
|
|
"id": 232987,
|
|
"igdb_id": 167,
|
|
"igdb_slug": "ps5",
|
|
"name": "Sony Playstation 5",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.PSP: {
|
|
"id": 161831,
|
|
"igdb_id": 38,
|
|
"igdb_slug": "psp",
|
|
"name": "Sony Playstation Portable",
|
|
"ra_id": 41,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.PSVITA: {
|
|
"id": 102,
|
|
"igdb_id": 46,
|
|
"igdb_slug": "psvita",
|
|
"name": "Sony PlayStation Vita",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.PSX: {
|
|
"id": 100,
|
|
"igdb_id": 7,
|
|
"igdb_slug": "ps",
|
|
"name": "Sony PlayStation",
|
|
"ra_id": 12,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.RCA_STUDIO_II: {
|
|
"id": 234745,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "RCA Studio II",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.SATURN: {
|
|
"id": 54695,
|
|
"igdb_id": 32,
|
|
"igdb_slug": "saturn",
|
|
"name": "Sega Saturn",
|
|
"ra_id": 39,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.SC3000: {
|
|
"id": 52165,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "Sega Computer 3000",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.SEGA_PICO: {
|
|
"id": 81,
|
|
"igdb_id": 339,
|
|
"igdb_slug": "sega-pico",
|
|
"name": "Sega Pico",
|
|
"ra_id": 68,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.SEGA32: {
|
|
"id": 80,
|
|
"igdb_id": 30,
|
|
"igdb_slug": "sega32",
|
|
"name": "Sega 32X",
|
|
"ra_id": 10,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.SEGACD: {
|
|
"id": 161828,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "Sega Mega CD / Sega CD",
|
|
"ra_id": 9,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.SERIES_X_S: {
|
|
"id": 232984,
|
|
"igdb_id": 169,
|
|
"igdb_slug": "series-x-s",
|
|
"name": "Microsoft Xbox Series X",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.SFAM: {
|
|
"id": 233081,
|
|
"igdb_id": 58,
|
|
"igdb_slug": "sfam",
|
|
"name": "Super Famicom",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.SG1000: {
|
|
"id": 244470,
|
|
"igdb_id": 84,
|
|
"igdb_slug": "sg1000",
|
|
"name": "SG-1000",
|
|
"ra_id": 33,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.SHARP_X68000: {
|
|
"id": 90,
|
|
"igdb_id": 121,
|
|
"igdb_slug": "sharp-x68000",
|
|
"name": "Sharp X68000",
|
|
"ra_id": 52,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.SINCLAIR_QL: {
|
|
"id": 92,
|
|
"igdb_id": 406,
|
|
"igdb_slug": "sinclair-ql",
|
|
"name": "Sinclair QL",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.SMS: {
|
|
"id": 85,
|
|
"igdb_id": 64,
|
|
"igdb_slug": "sms",
|
|
"name": "Sega Master System",
|
|
"ra_id": 11,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.SNES: {
|
|
"id": 74,
|
|
"igdb_id": 19,
|
|
"igdb_slug": "snes",
|
|
"name": "Super Nintendo Entertainment System",
|
|
"ra_id": 3,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.SUPER_VISION_8000: {
|
|
"id": 97267,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "Bandai Super Vision 8000",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.SWITCH: {
|
|
"id": 233067,
|
|
"igdb_id": 130,
|
|
"igdb_slug": "switch",
|
|
"name": "Nintendo Switch",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.TG16: {
|
|
"id": 245372,
|
|
"igdb_id": 86,
|
|
"igdb_slug": "turbografx16--1",
|
|
"name": "TurboGrafx-16/PC Engine",
|
|
"ra_id": 8,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.TI_82: {
|
|
"id": 47973,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "Texas Instruments TI-82",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.TI_83: {
|
|
"id": 243852,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "Texas Instruments TI-83",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.TRS_80: {
|
|
"id": 105,
|
|
"igdb_id": 126,
|
|
"igdb_slug": "trs-80",
|
|
"name": "Tandy/RadioShack TRS-80",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.TRS_80_COLOR_COMPUTER: {
|
|
"id": 106,
|
|
"igdb_id": 151,
|
|
"igdb_slug": "trs-80-color-computer",
|
|
"name": "Tandy/RadioShack TRS-80 Color Computer",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.TURBOGRAFX_CD: {
|
|
"id": 247350,
|
|
"igdb_id": 150,
|
|
"igdb_slug": "turbografx-16-slash-pc-engine-cd",
|
|
"name": "Turbografx-16/PC Engine CD",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.VECTREX: {
|
|
"id": 45,
|
|
"igdb_id": 70,
|
|
"igdb_slug": "vectrex",
|
|
"name": "Vectrex",
|
|
"ra_id": 46,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.VIC_20: {
|
|
"id": 4,
|
|
"igdb_id": 71,
|
|
"igdb_slug": "vic-20",
|
|
"name": "Commodore VIC20",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.VIRTUALBOY: {
|
|
"id": 75,
|
|
"igdb_id": 87,
|
|
"igdb_slug": "virtualboy",
|
|
"name": "Nintendo Virtual Boy",
|
|
"ra_id": 28,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.SUPERVISION: {
|
|
"id": 244828,
|
|
"igdb_id": 415,
|
|
"igdb_slug": "watara-slash-quickshot-supervision",
|
|
"name": "Watara Supervision",
|
|
"ra_id": 63,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.WII: {
|
|
"id": 76,
|
|
"igdb_id": 5,
|
|
"igdb_slug": "wii",
|
|
"name": "Nintendo Wii",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.WIIU: {
|
|
"id": 77,
|
|
"igdb_id": 41,
|
|
"igdb_slug": "wiiu",
|
|
"name": "Nintendo WiiU",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.WIN: {
|
|
"id": 233074,
|
|
"igdb_id": 6,
|
|
"igdb_slug": "win",
|
|
"name": "Microsoft Windows",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.WONDERSWAN: {
|
|
"id": 34,
|
|
"igdb_id": 57,
|
|
"igdb_slug": "wonderswan",
|
|
"name": "Bandai WonderSwan",
|
|
"ra_id": 53,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.WONDERSWAN_COLOR: {
|
|
"id": 35,
|
|
"igdb_id": 123,
|
|
"igdb_slug": "wonderswan-color",
|
|
"name": "Bandai WonderSwan Color",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.X1: {
|
|
"id": 89,
|
|
"igdb_id": 77,
|
|
"igdb_slug": "x1",
|
|
"name": "Sharp X1",
|
|
"ra_id": 64,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.XBOX: {
|
|
"id": 54696,
|
|
"igdb_id": 11,
|
|
"igdb_slug": "xbox",
|
|
"name": "Microsoft Xbox",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.XBOX360: {
|
|
"id": 54697,
|
|
"igdb_id": 12,
|
|
"igdb_slug": "xbox360",
|
|
"name": "Microsoft Xbox 360",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.XBOXONE: {
|
|
"id": 161824,
|
|
"igdb_id": 49,
|
|
"igdb_slug": "xboxone",
|
|
"name": "Microsoft Xbox One",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.Z88: {
|
|
"id": 97718,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "Cambridge Computer Z88",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.ZX80: {
|
|
"id": 232985,
|
|
"igdb_id": None,
|
|
"igdb_slug": "",
|
|
"name": "Sinclair ZX80",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.ZX81: {
|
|
"id": 94,
|
|
"igdb_id": 373,
|
|
"igdb_slug": "sinclair-zx81",
|
|
"name": "Sinclair ZX81",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
UPS.ZXS: {
|
|
"id": 93,
|
|
"igdb_id": 26,
|
|
"igdb_slug": "zxs",
|
|
"name": "Sinclair ZX Spectrum",
|
|
"ra_id": None,
|
|
"tgdb_id": None,
|
|
},
|
|
}
|