[
  {
    "repo": "dnunezx/LUNA",
    "url": "https://api.github.com/repos/dnunezx/LUNA/compare/386f352cf1c2d74a010b23f519c680d31826405d...78096fb32900d561b2fca7716a2d029cbce11caa",
    "comparison": {
      "url": "https://api.github.com/repos/dnunezx/LUNA/compare/386f352cf1c2d74a010b23f519c680d31826405d...78096fb32900d561b2fca7716a2d029cbce11caa",
      "html_url": "https://github.com/dnunezx/LUNA/compare/386f352cf1c2d74a010b23f519c680d31826405d...78096fb32900d561b2fca7716a2d029cbce11caa",
      "permalink_url": "https://github.com/dnunezx/LUNA/compare/dnunezx:386f352...dnunezx:78096fb",
      "diff_url": "https://github.com/dnunezx/LUNA/compare/386f352cf1c2d74a010b23f519c680d31826405d...78096fb32900d561b2fca7716a2d029cbce11caa.diff",
      "patch_url": "https://github.com/dnunezx/LUNA/compare/386f352cf1c2d74a010b23f519c680d31826405d...78096fb32900d561b2fca7716a2d029cbce11caa.patch",
      "base_commit": {
        "sha": "386f352cf1c2d74a010b23f519c680d31826405d",
        "node_id": "C_kwDOUlXFcNoAKDM4NmYzNTJjZjFjMmQ3NGEwMTBiMjNmNTE5YzY4MGQzMTgyNjQwNWQ",
        "commit": {
          "author": {
            "name": "Danny Nunez",
            "email": "dnunezx@users.noreply.github.com",
            "date": "2026-10-24T18:42:40Z"
          },
          "committer": {
            "name": "Danny Nunez",
            "email": "dnunezx@users.noreply.github.com",
            "date": "2026-10-24T18:42:40Z"
          },
          "message": "Update frontend with MMCe scan fix and restored debug colors",
          "tree": {
            "sha": "a3b3409140c352acb7a1cad6ad72dfc20c5a7227",
            "url": "https://api.github.com/repos/dnunezx/LUNA/git/trees/a3b3409140c352acb7a1cad6ad72dfc20c5a7227"
          },
          "url": "https://api.github.com/repos/dnunezx/LUNA/git/commits/386f352cf1c2d74a010b23f519c680d31826405d",
          "comment_count": 0,
          "verification": {
            "verified": false,
            "reason": "unsigned",
            "signature": null,
            "payload": null,
            "verified_at": null
          }
        },
        "url": "https://api.github.com/repos/dnunezx/LUNA/commits/386f352cf1c2d74a010b23f519c680d31826405d",
        "html_url": "https://github.com/dnunezx/LUNA/commit/386f352cf1c2d74a010b23f519c680d31826405d",
        "comments_url": "https://api.github.com/repos/dnunezx/LUNA/commits/386f352cf1c2d74a010b23f519c680d31826405d/comments",
        "author": {
          "login": "dnunezx",
          "id": 156245780,
          "node_id": "U_kgDOCVAfFA",
          "avatar_url": "https://avatars.githubusercontent.com/u/156245780?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/dnunezx",
          "html_url": "https://github.com/dnunezx",
          "followers_url": "https://api.github.com/users/dnunezx/followers",
          "following_url": "https://api.github.com/users/dnunezx/following{/other_user}",
          "gists_url": "https://api.github.com/users/dnunezx/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/dnunezx/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/dnunezx/subscriptions",
          "organizations_url": "https://api.github.com/users/dnunezx/orgs",
          "repos_url": "https://api.github.com/users/dnunezx/repos",
          "events_url": "https://api.github.com/users/dnunezx/events{/privacy}",
          "received_events_url": "https://api.github.com/users/dnunezx/received_events",
          "type": "User",
          "user_view_type": "public",
          "site_admin": false
        },
        "committer": {
          "login": "dnunezx",
          "id": 156245780,
          "node_id": "U_kgDOCVAfFA",
          "avatar_url": "https://avatars.githubusercontent.com/u/156245780?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/dnunezx",
          "html_url": "https://github.com/dnunezx",
          "followers_url": "https://api.github.com/users/dnunezx/followers",
          "following_url": "https://api.github.com/users/dnunezx/following{/other_user}",
          "gists_url": "https://api.github.com/users/dnunezx/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/dnunezx/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/dnunezx/subscriptions",
          "organizations_url": "https://api.github.com/users/dnunezx/orgs",
          "repos_url": "https://api.github.com/users/dnunezx/repos",
          "events_url": "https://api.github.com/users/dnunezx/events{/privacy}",
          "received_events_url": "https://api.github.com/users/dnunezx/received_events",
          "type": "User",
          "user_view_type": "public",
          "site_admin": false
        },
        "parents": [
          {
            "sha": "19e3d4e8d43e8d4879690737a932b1e91fe6a2af",
            "url": "https://api.github.com/repos/dnunezx/LUNA/commits/19e3d4e8d43e8d4879690737a932b1e91fe6a2af",
            "html_url": "https://github.com/dnunezx/LUNA/commit/19e3d4e8d43e8d4879690737a932b1e91fe6a2af"
          }
        ]
      },
      "merge_base_commit": {
        "sha": "386f352cf1c2d74a010b23f519c680d31826405d",
        "node_id": "C_kwDOUlXFcNoAKDM4NmYzNTJjZjFjMmQ3NGEwMTBiMjNmNTE5YzY4MGQzMTgyNjQwNWQ",
        "commit": {
          "author": {
            "name": "Danny Nunez",
            "email": "dnunezx@users.noreply.github.com",
            "date": "2026-10-24T18:42:40Z"
          },
          "committer": {
            "name": "Danny Nunez",
            "email": "dnunezx@users.noreply.github.com",
            "date": "2026-10-24T18:42:40Z"
          },
          "message": "Update frontend with MMCe scan fix and restored debug colors",
          "tree": {
            "sha": "a3b3409140c352acb7a1cad6ad72dfc20c5a7227",
            "url": "https://api.github.com/repos/dnunezx/LUNA/git/trees/a3b3409140c352acb7a1cad6ad72dfc20c5a7227"
          },
          "url": "https://api.github.com/repos/dnunezx/LUNA/git/commits/386f352cf1c2d74a010b23f519c680d31826405d",
          "comment_count": 0,
          "verification": {
            "verified": false,
            "reason": "unsigned",
            "signature": null,
            "payload": null,
            "verified_at": null
          }
        },
        "url": "https://api.github.com/repos/dnunezx/LUNA/commits/386f352cf1c2d74a010b23f519c680d31826405d",
        "html_url": "https://github.com/dnunezx/LUNA/commit/386f352cf1c2d74a010b23f519c680d31826405d",
        "comments_url": "https://api.github.com/repos/dnunezx/LUNA/commits/386f352cf1c2d74a010b23f519c680d31826405d/comments",
        "author": {
          "login": "dnunezx",
          "id": 156245780,
          "node_id": "U_kgDOCVAfFA",
          "avatar_url": "https://avatars.githubusercontent.com/u/156245780?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/dnunezx",
          "html_url": "https://github.com/dnunezx",
          "followers_url": "https://api.github.com/users/dnunezx/followers",
          "following_url": "https://api.github.com/users/dnunezx/following{/other_user}",
          "gists_url": "https://api.github.com/users/dnunezx/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/dnunezx/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/dnunezx/subscriptions",
          "organizations_url": "https://api.github.com/users/dnunezx/orgs",
          "repos_url": "https://api.github.com/users/dnunezx/repos",
          "events_url": "https://api.github.com/users/dnunezx/events{/privacy}",
          "received_events_url": "https://api.github.com/users/dnunezx/received_events",
          "type": "User",
          "user_view_type": "public",
          "site_admin": false
        },
        "committer": {
          "login": "dnunezx",
          "id": 156245780,
          "node_id": "U_kgDOCVAfFA",
          "avatar_url": "https://avatars.githubusercontent.com/u/156245780?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/dnunezx",
          "html_url": "https://github.com/dnunezx",
          "followers_url": "https://api.github.com/users/dnunezx/followers",
          "following_url": "https://api.github.com/users/dnunezx/following{/other_user}",
          "gists_url": "https://api.github.com/users/dnunezx/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/dnunezx/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/dnunezx/subscriptions",
          "organizations_url": "https://api.github.com/users/dnunezx/orgs",
          "repos_url": "https://api.github.com/users/dnunezx/repos",
          "events_url": "https://api.github.com/users/dnunezx/events{/privacy}",
          "received_events_url": "https://api.github.com/users/dnunezx/received_events",
          "type": "User",
          "user_view_type": "public",
          "site_admin": false
        },
        "parents": [
          {
            "sha": "19e3d4e8d43e8d4879690737a932b1e91fe6a2af",
            "url": "https://api.github.com/repos/dnunezx/LUNA/commits/19e3d4e8d43e8d4879690737a932b1e91fe6a2af",
            "html_url": "https://github.com/dnunezx/LUNA/commit/19e3d4e8d43e8d4879690737a932b1e91fe6a2af"
          }
        ]
      },
      "status": "ahead",
      "ahead_by": 3,
      "behind_by": 0,
      "total_commits": 3,
      "commits": [
        {
          "sha": "a6fa529c592bd35b3951ade7178d1b7b10495d0e",
          "node_id": "C_kwDOUlXFcNoAKGE2ZmE1MjljNTkyYmQzNWIzOTUxYWRlNzE3OGQxYjdiMTA0OTVkMGU",
          "commit": {
            "author": {
              "name": "Danny Nunez",
              "email": "dnunezx@users.noreply.github.com",
              "date": "2026-10-24T21:17:48Z"
            },
            "committer": {
              "name": "Danny Nunez",
              "email": "dnunezx@users.noreply.github.com",
              "date": "2026-10-24T21:17:48Z"
            },
            "message": "Update PSXCore to 1cb5c92 after build validation",
            "tree": {
              "sha": "f1b7982cc0739f3d8573085901dd22e6c864df37",
              "url": "https://api.github.com/repos/dnunezx/LUNA/git/trees/f1b7982cc0739f3d8573085901dd22e6c864df37"
            },
            "url": "https://api.github.com/repos/dnunezx/LUNA/git/commits/a6fa529c592bd35b3951ade7178d1b7b10495d0e",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/dnunezx/LUNA/commits/a6fa529c592bd35b3951ade7178d1b7b10495d0e",
          "html_url": "https://github.com/dnunezx/LUNA/commit/a6fa529c592bd35b3951ade7178d1b7b10495d0e",
          "comments_url": "https://api.github.com/repos/dnunezx/LUNA/commits/a6fa529c592bd35b3951ade7178d1b7b10495d0e/comments",
          "author": {
            "login": "dnunezx",
            "id": 156245780,
            "node_id": "U_kgDOCVAfFA",
            "avatar_url": "https://avatars.githubusercontent.com/u/156245780?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/dnunezx",
            "html_url": "https://github.com/dnunezx",
            "followers_url": "https://api.github.com/users/dnunezx/followers",
            "following_url": "https://api.github.com/users/dnunezx/following{/other_user}",
            "gists_url": "https://api.github.com/users/dnunezx/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/dnunezx/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/dnunezx/subscriptions",
            "organizations_url": "https://api.github.com/users/dnunezx/orgs",
            "repos_url": "https://api.github.com/users/dnunezx/repos",
            "events_url": "https://api.github.com/users/dnunezx/events{/privacy}",
            "received_events_url": "https://api.github.com/users/dnunezx/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "dnunezx",
            "id": 156245780,
            "node_id": "U_kgDOCVAfFA",
            "avatar_url": "https://avatars.githubusercontent.com/u/156245780?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/dnunezx",
            "html_url": "https://github.com/dnunezx",
            "followers_url": "https://api.github.com/users/dnunezx/followers",
            "following_url": "https://api.github.com/users/dnunezx/following{/other_user}",
            "gists_url": "https://api.github.com/users/dnunezx/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/dnunezx/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/dnunezx/subscriptions",
            "organizations_url": "https://api.github.com/users/dnunezx/orgs",
            "repos_url": "https://api.github.com/users/dnunezx/repos",
            "events_url": "https://api.github.com/users/dnunezx/events{/privacy}",
            "received_events_url": "https://api.github.com/users/dnunezx/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "386f352cf1c2d74a010b23f519c680d31826405d",
              "url": "https://api.github.com/repos/dnunezx/LUNA/commits/386f352cf1c2d74a010b23f519c680d31826405d",
              "html_url": "https://github.com/dnunezx/LUNA/commit/386f352cf1c2d74a010b23f519c680d31826405d"
            }
          ]
        },
        {
          "sha": "8a79955b59a4df09b026b867be163ea7e294bb5d",
          "node_id": "C_kwDOUlXFcNoAKDhhNzk5NTViNTlhNGRmMDliMDI2Yjg2N2JlMTYzZWE3ZTI5NGJiNWQ",
          "commit": {
            "author": {
              "name": "Danny Nunez",
              "email": "dnunezx@users.noreply.github.com",
              "date": "2026-10-25T00:55:23Z"
            },
            "committer": {
              "name": "Danny Nunez",
              "email": "dnunezx@users.noreply.github.com",
              "date": "2026-10-25T00:55:23Z"
            },
            "message": "Build isolated 3.1.3 with MMCE EOF and scan recovery hotfixes",
            "tree": {
              "sha": "f0fcbe9387b77493a5fb342851330a39286d9c2b",
              "url": "https://api.github.com/repos/dnunezx/LUNA/git/trees/f0fcbe9387b77493a5fb342851330a39286d9c2b"
            },
            "url": "https://api.github.com/repos/dnunezx/LUNA/git/commits/8a79955b59a4df09b026b867be163ea7e294bb5d",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/dnunezx/LUNA/commits/8a79955b59a4df09b026b867be163ea7e294bb5d",
          "html_url": "https://github.com/dnunezx/LUNA/commit/8a79955b59a4df09b026b867be163ea7e294bb5d",
          "comments_url": "https://api.github.com/repos/dnunezx/LUNA/commits/8a79955b59a4df09b026b867be163ea7e294bb5d/comments",
          "author": {
            "login": "dnunezx",
            "id": 156245780,
            "node_id": "U_kgDOCVAfFA",
            "avatar_url": "https://avatars.githubusercontent.com/u/156245780?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/dnunezx",
            "html_url": "https://github.com/dnunezx",
            "followers_url": "https://api.github.com/users/dnunezx/followers",
            "following_url": "https://api.github.com/users/dnunezx/following{/other_user}",
            "gists_url": "https://api.github.com/users/dnunezx/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/dnunezx/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/dnunezx/subscriptions",
            "organizations_url": "https://api.github.com/users/dnunezx/orgs",
            "repos_url": "https://api.github.com/users/dnunezx/repos",
            "events_url": "https://api.github.com/users/dnunezx/events{/privacy}",
            "received_events_url": "https://api.github.com/users/dnunezx/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "dnunezx",
            "id": 156245780,
            "node_id": "U_kgDOCVAfFA",
            "avatar_url": "https://avatars.githubusercontent.com/u/156245780?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/dnunezx",
            "html_url": "https://github.com/dnunezx",
            "followers_url": "https://api.github.com/users/dnunezx/followers",
            "following_url": "https://api.github.com/users/dnunezx/following{/other_user}",
            "gists_url": "https://api.github.com/users/dnunezx/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/dnunezx/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/dnunezx/subscriptions",
            "organizations_url": "https://api.github.com/users/dnunezx/orgs",
            "repos_url": "https://api.github.com/users/dnunezx/repos",
            "events_url": "https://api.github.com/users/dnunezx/events{/privacy}",
            "received_events_url": "https://api.github.com/users/dnunezx/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "a6fa529c592bd35b3951ade7178d1b7b10495d0e",
              "url": "https://api.github.com/repos/dnunezx/LUNA/commits/a6fa529c592bd35b3951ade7178d1b7b10495d0e",
              "html_url": "https://github.com/dnunezx/LUNA/commit/a6fa529c592bd35b3951ade7178d1b7b10495d0e"
            }
          ]
        },
        {
          "sha": "78096fb32900d561b2fca7716a2d029cbce11caa",
          "node_id": "C_kwDOUlXFcNoAKDc4MDk2ZmIzMjkwMGQ1NjFiMmZjYTc3MTZhMmQwMjljYmNlMTFjYWE",
          "commit": {
            "author": {
              "name": "Danny Nunez",
              "email": "dnunezx@users.noreply.github.com",
              "date": "2026-10-25T16:37:36Z"
            },
            "committer": {
              "name": "Danny Nunez",
              "email": "dnunezx@users.noreply.github.com",
              "date": "2026-10-25T16:37:36Z"
            },
            "message": "Update PSXCore to 5d4de02 after build validation",
            "tree": {
              "sha": "9cdfe59a662ca8c790bb4eae39dea73058987299",
              "url": "https://api.github.com/repos/dnunezx/LUNA/git/trees/9cdfe59a662ca8c790bb4eae39dea73058987299"
            },
            "url": "https://api.github.com/repos/dnunezx/LUNA/git/commits/78096fb32900d561b2fca7716a2d029cbce11caa",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/dnunezx/LUNA/commits/78096fb32900d561b2fca7716a2d029cbce11caa",
          "html_url": "https://github.com/dnunezx/LUNA/commit/78096fb32900d561b2fca7716a2d029cbce11caa",
          "comments_url": "https://api.github.com/repos/dnunezx/LUNA/commits/78096fb32900d561b2fca7716a2d029cbce11caa/comments",
          "author": {
            "login": "dnunezx",
            "id": 156245780,
            "node_id": "U_kgDOCVAfFA",
            "avatar_url": "https://avatars.githubusercontent.com/u/156245780?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/dnunezx",
            "html_url": "https://github.com/dnunezx",
            "followers_url": "https://api.github.com/users/dnunezx/followers",
            "following_url": "https://api.github.com/users/dnunezx/following{/other_user}",
            "gists_url": "https://api.github.com/users/dnunezx/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/dnunezx/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/dnunezx/subscriptions",
            "organizations_url": "https://api.github.com/users/dnunezx/orgs",
            "repos_url": "https://api.github.com/users/dnunezx/repos",
            "events_url": "https://api.github.com/users/dnunezx/events{/privacy}",
            "received_events_url": "https://api.github.com/users/dnunezx/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "dnunezx",
            "id": 156245780,
            "node_id": "U_kgDOCVAfFA",
            "avatar_url": "https://avatars.githubusercontent.com/u/156245780?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/dnunezx",
            "html_url": "https://github.com/dnunezx",
            "followers_url": "https://api.github.com/users/dnunezx/followers",
            "following_url": "https://api.github.com/users/dnunezx/following{/other_user}",
            "gists_url": "https://api.github.com/users/dnunezx/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/dnunezx/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/dnunezx/subscriptions",
            "organizations_url": "https://api.github.com/users/dnunezx/orgs",
            "repos_url": "https://api.github.com/users/dnunezx/repos",
            "events_url": "https://api.github.com/users/dnunezx/events{/privacy}",
            "received_events_url": "https://api.github.com/users/dnunezx/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "8a79955b59a4df09b026b867be163ea7e294bb5d",
              "url": "https://api.github.com/repos/dnunezx/LUNA/commits/8a79955b59a4df09b026b867be163ea7e294bb5d",
              "html_url": "https://github.com/dnunezx/LUNA/commit/8a79955b59a4df09b026b867be163ea7e294bb5d"
            }
          ]
        }
      ],
      "files": [
        {
          "sha": "78080ccb1933dd8cc019526f64577895a48d4a5e",
          "filename": ".github/workflows/build-v3.1.3-scan-hotfix.yml",
          "status": "added",
          "additions": 212,
          "deletions": 0,
          "changes": 212,
          "blob_url": "https://github.com/dnunezx/LUNA/blob/78096fb32900d561b2fca7716a2d029cbce11caa/.github%2Fworkflows%2Fbuild-v3.1.3-scan-hotfix.yml",
          "raw_url": "https://github.com/dnunezx/LUNA/raw/78096fb32900d561b2fca7716a2d029cbce11caa/.github%2Fworkflows%2Fbuild-v3.1.3-scan-hotfix.yml",
          "contents_url": "https://api.github.com/repos/dnunezx/LUNA/contents/.github%2Fworkflows%2Fbuild-v3.1.3-scan-hotfix.yml?ref=78096fb32900d561b2fca7716a2d029cbce11caa",
          "patch": "@@ -0,0 +1,212 @@\n+name: Build 3.1.3 MMCE and scan hotfixes\n+\n+on:\n+  workflow_dispatch:\n+\n+permissions:\n+  contents: read\n+\n+concurrency:\n+  group: build-v3.1.3-scan-hotfix\n+  cancel-in-progress: false\n+\n+jobs:\n+  build:\n+    runs-on: ubuntu-24.04\n+    timeout-minutes: 50\n+    env:\n+      BASE_SHA: 9c5be408df787000a7e650f66f0a43411060e1d7\n+      FRONTEND_BASE_SHA: 127d012b07322dc0805ce62f877e5ba5e8cd6dbc\n+      FRONTEND_SHA: a288a1c4423625ae53d645ff0c46df92daea314d\n+      MMCE_SHA: 0e78d1b35c71d1ee43b880c433586d830249b6f0\n+      CORE_SHA: 360a788ff7bb7c6853702a8edec6860cae49c0a2\n+      ORIGINAL_SHA256: cb07cd7079b8f39f9a7197d0d3c8937c4c51a5dcabf376a5fe159cfcc6e7318e\n+      BUILD_IMAGE: ps2max/dev@sha256:e390699d46818e7c736f414ae5b1789d3ef7d3d914ce646a8a7bd027bd7e4521\n+    steps:\n+      - name: Check out the original 3.1.3 source\n+        uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6\n+        with:\n+          ref: 9c5be408df787000a7e650f66f0a43411060e1d7\n+          path: release\n+          fetch-depth: 1\n+          persist-credentials: false\n+\n+      - name: Select the isolated frontend containing only the two hotfixes\n+        working-directory: release\n+        shell: bash\n+        run: |\n+          set -euo pipefail\n+          test \"$(git rev-parse HEAD)\" = \"$BASE_SHA\"\n+          test \"$(git rev-parse HEAD:nhddl)\" = \"$FRONTEND_BASE_SHA\"\n+          test \"$(git rev-parse HEAD:psxcore)\" = \"$CORE_SHA\"\n+          git submodule update --init --recursive nhddl psxcore\n+          git -C nhddl fetch --depth=1 origin \"$FRONTEND_SHA\"\n+          git -C nhddl checkout --detach \"$FRONTEND_SHA\"\n+          test \"$(git -C nhddl/iop/mmceman rev-parse HEAD)\" = \"$MMCE_SHA\"\n+          test \"$(git -C psxcore rev-parse HEAD)\" = \"$CORE_SHA\"\n+          test -z \"$(git -C nhddl status --porcelain)\"\n+          test -z \"$(git -C psxcore status --porcelain)\"\n+          python3 - <<'PY'\n+          import os\n+          import subprocess\n+          expected = {\n+              'include/devices/devices.h', 'include/storage.h',\n+              'iop/mmceman-eof.cmake', 'irx.cmake', 'src/devices/iso.c',\n+              'src/storage.c', 'src/ui/file_manager.c', 'src/ui/language_catalog.inc',\n+          }\n+          changed = subprocess.check_output([\n+              'git', '-C', 'nhddl', 'diff', '--name-only',\n+              os.environ['FRONTEND_BASE_SHA'], os.environ['FRONTEND_SHA'],\n+          ], text=True).splitlines()\n+          assert set(changed) == expected, changed\n+          subprocess.run(['git', '-C', 'nhddl', 'diff', '--check',\n+                          os.environ['FRONTEND_BASE_SHA'], os.environ['FRONTEND_SHA']], check=True)\n+          print('Verified exact eight-file hotfix scope. Core, SDK and other engines unchanged.')\n+          PY\n+\n+      - name: Download and verify the original package\n+        env:\n+          GH_TOKEN: ${{ github.token }}\n+        shell: bash\n+        run: |\n+          set -euo pipefail\n+          gh release download v3.1.3 --repo dnunezx/LUNA \\\n+            --pattern LUNA-v3.1.3-FMCB-mc0.zip --dir original\n+          printf '%s  %s\\n' \"$ORIGINAL_SHA256\" original/LUNA-v3.1.3-FMCB-mc0.zip | sha256sum -c -\n+\n+      - name: Build the hardware launcher against the original core\n+        shell: bash\n+        run: |\n+          set -euo pipefail\n+          docker run --rm -v \"$PWD/release:/workspace\" \\\n+            -e LUNA_BUILD_IMAGE=\"$BUILD_IMAGE\" \"$BUILD_IMAGE\" sh -eu -c '\n+              if [ ! -e \"$PS2SDK/ps2dev.cmake\" ]; then\n+                ln -s ../share/ps2dev.cmake \"$PS2SDK/ps2dev.cmake\"\n+              fi\n+              export LUNA_BUILD_DIR=/workspace/nhddl/build-v3.1.3-hotfix2\n+              export LUNA_EMULATOR_OPTION=OFF\n+              export LUNA_PSXCORE_DEVELOPMENT=OFF\n+              sh /workspace/tools/bootstrap-ps2-libtiff.sh\n+              cmake -S /workspace/nhddl -B \"$LUNA_BUILD_DIR\" \\\n+                -DCMAKE_BUILD_TYPE=Release -DENABLE_PRINTF=OFF \\\n+                -DLUNA_RELEASE_VERSION=v3.1.3 -DLUNA_EMULATOR_BUILD=OFF \\\n+                -DLUNA_ENABLE_PSXCORE=ON -DLUNA_PSXCORE_DEVELOPMENT=OFF \\\n+                -DLUNA_PSXCORE_SOURCE_DIR=/workspace/psxcore\n+              sh /workspace/tools/build-luna-psxcore.sh\n+            '\n+\n+      - name: Package only the updated launcher and verify every other file\n+        shell: bash\n+        run: |\n+          set -euo pipefail\n+          python3 - <<'PY'\n+          from pathlib import Path, PurePosixPath\n+          import hashlib\n+          import os\n+          import re\n+          import subprocess\n+          import zipfile\n+\n+          source = Path('release')\n+          build = source / 'nhddl/build-v3.1.3-hotfix2'\n+          original = Path('original/LUNA-v3.1.3-FMCB-mc0.zip')\n+          output = Path('output')\n+          output.mkdir()\n+          sha = lambda data: hashlib.sha256(data).hexdigest()\n+          def git(directory, *args):\n+              return subprocess.check_output(['git', '-C', str(directory), *args], text=True).strip()\n+          for directory, expected in (\n+              (source, os.environ['BASE_SHA']),\n+              (source / 'nhddl', os.environ['FRONTEND_SHA']),\n+              (source / 'nhddl/iop/mmceman', os.environ['MMCE_SHA']),\n+              (source / 'psxcore', os.environ['CORE_SHA']),\n+          ):\n+              assert git(directory, 'rev-parse', 'HEAD') == expected\n+          assert git(source / 'nhddl', 'status', '--porcelain') == ''\n+          assert git(source / 'nhddl/iop/mmceman', 'status', '--porcelain') == ''\n+          assert git(source / 'psxcore', 'status', '--porcelain') == ''\n+          assert sha(original.read_bytes()) == os.environ['ORIGINAL_SHA256']\n+          prefix = 'LUNA-v3.1.3-FMCB-mc0/'\n+          with zipfile.ZipFile(original) as archive:\n+              assert archive.testzip() is None\n+              names = [i.filename for i in archive.infolist() if not i.is_dir()]\n+              assert len(names) == len(set(names))\n+              assert all(n.startswith(prefix) and '..' not in PurePosixPath(n).parts for n in names)\n+              files = {n: archive.read(n) for n in names}\n+          previous = files.copy()\n+          manifest = prefix + 'SHA256SUMS.txt'\n+          for line in files[manifest].decode('ascii').splitlines():\n+              digest, relative = line.split('  ', 1)\n+              assert sha(files[prefix + relative]) == digest\n+          metadata = (build / 'psxcore-build.txt').read_text()\n+          for flag in ('emulator_build=OFF', 'development=OFF',\n+                       'runtime_source=matching-checkout', 'igr_return=ON',\n+                       'disc_swapping=ON', 'igr_return_path=mc0:/APP_LUNA/luna.elf',\n+                       'psxcore_revision=' + os.environ['CORE_SHA'],\n+                       'frontend_revision=' + os.environ['FRONTEND_SHA'],\n+                       'frontend_dirty=no'):\n+              assert flag in metadata.splitlines(), flag\n+          cache = (build / 'CMakeCache.txt').read_text()\n+          for key, value in (('ENABLE_PRINTF', 'OFF'), ('CMAKE_BUILD_TYPE', 'Release'),\n+                             ('LUNA_EMULATOR_BUILD', 'OFF'), ('LUNA_ENABLE_PSXCORE', 'ON'),\n+                             ('LUNA_PSXCORE_DEVELOPMENT', 'OFF'), ('LUNA_RELEASE_VERSION', 'v3.1.3')):\n+              assert re.search(r'^' + key + r':[^=]+=' + value + '$', cache, re.MULTILINE), key\n+          for line in (build / 'psxcore-build.sha256').read_text().splitlines():\n+              digest, relative = line.split('  ', 1)\n+              assert sha((build / relative).read_bytes()) == digest\n+          launcher = prefix + 'APP_LUNA/luna.elf'\n+          files[launcher] = (build / 'luna.elf').read_bytes()\n+          metadata += (\n+              '\\nrelease_version=v3.1.3\\n'\n+              'release_variant=hotfix2: MMCE EOF and tolerant scan recovery\\n'\n+              'base_revision=' + os.environ['BASE_SHA'] + '\\n'\n+              'frontend_base_revision=' + os.environ['FRONTEND_BASE_SHA'] + '\\n'\n+              'mmceman_revision=' + os.environ['MMCE_SHA'] + '\\n'\n+              'runtime_packaging=original-release-byte-identical\\n'\n+              'original_package_sha256=' + os.environ['ORIGINAL_SHA256'] + '\\n'\n+          )\n+          files[prefix + 'BUILD-INFO.txt'] = metadata.encode()\n+          files[manifest] = ''.join(\n+              f'{sha(data)}  {name[len(prefix):]}\\n'\n+              for name, data in sorted(files.items()) if name != manifest\n+          ).encode('ascii')\n+          changed = {n for n in files if files[n] != previous[n]}\n+          expected = {launcher, prefix + 'BUILD-INFO.txt', manifest}\n+          assert set(files) == set(previous) and changed == expected, changed\n+          # The original runtime/module list is the package allowlist.\n+          assert files[prefix + 'APP_LUNA/psxcore-runtime-bootstrap.elf'] == previous[prefix + 'APP_LUNA/psxcore-runtime-bootstrap.elf']\n+          for name in files:\n+              parts = PurePosixPath(name).parts\n+              assert not any(p.lower() in {'tests', 'fixtures', 'local', 'diagnostics', 'psxcore-tester'} for p in parts)\n+              assert PurePosixPath(name).suffix.lower() not in {'.map', '.log', '.o', '.a', '.exe', '.py', '.sh', '.c', '.h'}\n+          archive_path = output / 'LUNA-v3.1.3-hotfix2-FMCB-mc0.zip'\n+          with zipfile.ZipFile(archive_path, 'w', zipfile.ZIP_DEFLATED, compresslevel=9) as archive:\n+              for name, data in sorted(files.items()):\n+                  archive.writestr(name, data)\n+          with zipfile.ZipFile(archive_path) as archive:\n+              assert archive.testzip() is None\n+              assert all(archive.read(n) == data for n, data in files.items())\n+          (output / 'LUNA-v3.1.3-hotfix2.elf').write_bytes(files[launcher])\n+          (output / 'BUILD-INFO.txt').write_text(metadata)\n+          checksums = ''.join(f'{sha(p.read_bytes())}  {p.name}\\n' for p in sorted(output.iterdir()))\n+          (output / 'SHA256SUMS.txt').write_text(checksums, encoding='ascii')\n+          summary = (\n+              '## LUNA 3.1.3 \u2014 two hotfixes only\\n\\n'\n+              'Includes MMCE directory-end handling and tolerant library scan recovery.\\n\\n'\n+              f'Frontend: `{os.environ[\"FRONTEND_SHA\"]}` based on the original 3.1.3 frontend.\\n\\n'\n+              'Only the launcher ELF, build metadata and checksums differ from the original package. '\n+              'All runtimes, modules, settings and license files are byte-identical.\\n\\n'\n+              'Physical MMCE/gameplay testing remains. The public release and tag were not changed.\\n'\n+          )\n+          with open(os.environ['GITHUB_STEP_SUMMARY'], 'a') as report:\n+              report.write(summary)\n+          print(summary)\n+          PY\n+\n+      - name: Upload downloadable build files\n+        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2\n+        with:\n+          name: LUNA-v3.1.3-hotfix2-${{ github.run_number }}\n+          path: output/*\n+          if-no-files-found: error\n+          retention-days: 30"
        },
        {
          "sha": "df394982c12e331dd7a18390ac055b6ba957fbbb",
          "filename": "nhddl",
          "status": "modified",
          "additions": 1,
          "deletions": 1,
          "changes": 2,
          "blob_url": null,
          "raw_url": null,
          "contents_url": "https://api.github.com/repos/dnunezx/LUNA/contents/nhddl?ref=78096fb32900d561b2fca7716a2d029cbce11caa",
          "patch": "@@ -1 +1 @@\n-Subproject commit 15f01bd180f076d8098529b5c5d5df4d07b13f46\n+Subproject commit df394982c12e331dd7a18390ac055b6ba957fbbb"
        },
        {
          "sha": "5d4de02f57933f70c48335d938c1d4c8b69634ad",
          "filename": "psxcore",
          "status": "modified",
          "additions": 1,
          "deletions": 1,
          "changes": 2,
          "blob_url": null,
          "raw_url": null,
          "contents_url": "https://api.github.com/repos/dnunezx/LUNA/contents/psxcore?ref=78096fb32900d561b2fca7716a2d029cbce11caa",
          "patch": "@@ -1 +1 @@\n-Subproject commit 636961dd666f71d5fe2fd2a0d6c8aa95c0b110ae\n+Subproject commit 5d4de02f57933f70c48335d938c1d4c8b69634ad"
        }
      ]
    }
  },
  {
    "repo": "dnunezx/PSXCore",
    "url": "https://api.github.com/repos/dnunezx/PSXCore/compare/636961dd666f71d5fe2fd2a0d6c8aa95c0b110ae...5d4de02f57933f70c48335d938c1d4c8b69634ad",
    "comparison": {
      "url": "https://api.github.com/repos/dnunezx/PSXCore/compare/636961dd666f71d5fe2fd2a0d6c8aa95c0b110ae...5d4de02f57933f70c48335d938c1d4c8b69634ad",
      "html_url": "https://github.com/dnunezx/PSXCore/compare/636961dd666f71d5fe2fd2a0d6c8aa95c0b110ae...5d4de02f57933f70c48335d938c1d4c8b69634ad",
      "permalink_url": "https://github.com/dnunezx/PSXCore/compare/dnunezx:636961d...dnunezx:5d4de02",
      "diff_url": "https://github.com/dnunezx/PSXCore/compare/636961dd666f71d5fe2fd2a0d6c8aa95c0b110ae...5d4de02f57933f70c48335d938c1d4c8b69634ad.diff",
      "patch_url": "https://github.com/dnunezx/PSXCore/compare/636961dd666f71d5fe2fd2a0d6c8aa95c0b110ae...5d4de02f57933f70c48335d938c1d4c8b69634ad.patch",
      "base_commit": {
        "sha": "636961dd666f71d5fe2fd2a0d6c8aa95c0b110ae",
        "node_id": "C_kwDOUz-U1doAKDYzNjk2MWRkNjY2ZjcxZDVmZTJmZDJhMGQ2YzhhYTk1YzBiMTEwYWU",
        "commit": {
          "author": {
            "name": "Danny Nunez",
            "email": "dnunezx@users.noreply.github.com",
            "date": "2026-10-24T03:21:49Z"
          },
          "committer": {
            "name": "Danny Nunez",
            "email": "dnunezx@users.noreply.github.com",
            "date": "2026-10-24T03:21:49Z"
          },
          "message": "Schedule native GTE screen-offset load during division",
          "tree": {
            "sha": "b8d9b760689a0f422b9e54da9338e9f42814888b",
            "url": "https://api.github.com/repos/dnunezx/PSXCore/git/trees/b8d9b760689a0f422b9e54da9338e9f42814888b"
          },
          "url": "https://api.github.com/repos/dnunezx/PSXCore/git/commits/636961dd666f71d5fe2fd2a0d6c8aa95c0b110ae",
          "comment_count": 0,
          "verification": {
            "verified": false,
            "reason": "unsigned",
            "signature": null,
            "payload": null,
            "verified_at": null
          }
        },
        "url": "https://api.github.com/repos/dnunezx/PSXCore/commits/636961dd666f71d5fe2fd2a0d6c8aa95c0b110ae",
        "html_url": "https://github.com/dnunezx/PSXCore/commit/636961dd666f71d5fe2fd2a0d6c8aa95c0b110ae",
        "comments_url": "https://api.github.com/repos/dnunezx/PSXCore/commits/636961dd666f71d5fe2fd2a0d6c8aa95c0b110ae/comments",
        "author": {
          "login": "dnunezx",
          "id": 156245780,
          "node_id": "U_kgDOCVAfFA",
          "avatar_url": "https://avatars.githubusercontent.com/u/156245780?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/dnunezx",
          "html_url": "https://github.com/dnunezx",
          "followers_url": "https://api.github.com/users/dnunezx/followers",
          "following_url": "https://api.github.com/users/dnunezx/following{/other_user}",
          "gists_url": "https://api.github.com/users/dnunezx/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/dnunezx/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/dnunezx/subscriptions",
          "organizations_url": "https://api.github.com/users/dnunezx/orgs",
          "repos_url": "https://api.github.com/users/dnunezx/repos",
          "events_url": "https://api.github.com/users/dnunezx/events{/privacy}",
          "received_events_url": "https://api.github.com/users/dnunezx/received_events",
          "type": "User",
          "user_view_type": "public",
          "site_admin": false
        },
        "committer": {
          "login": "dnunezx",
          "id": 156245780,
          "node_id": "U_kgDOCVAfFA",
          "avatar_url": "https://avatars.githubusercontent.com/u/156245780?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/dnunezx",
          "html_url": "https://github.com/dnunezx",
          "followers_url": "https://api.github.com/users/dnunezx/followers",
          "following_url": "https://api.github.com/users/dnunezx/following{/other_user}",
          "gists_url": "https://api.github.com/users/dnunezx/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/dnunezx/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/dnunezx/subscriptions",
          "organizations_url": "https://api.github.com/users/dnunezx/orgs",
          "repos_url": "https://api.github.com/users/dnunezx/repos",
          "events_url": "https://api.github.com/users/dnunezx/events{/privacy}",
          "received_events_url": "https://api.github.com/users/dnunezx/received_events",
          "type": "User",
          "user_view_type": "public",
          "site_admin": false
        },
        "parents": [
          {
            "sha": "827b7cad0157aa6af7f41d42d7152db9c2d6837c",
            "url": "https://api.github.com/repos/dnunezx/PSXCore/commits/827b7cad0157aa6af7f41d42d7152db9c2d6837c",
            "html_url": "https://github.com/dnunezx/PSXCore/commit/827b7cad0157aa6af7f41d42d7152db9c2d6837c"
          }
        ]
      },
      "merge_base_commit": {
        "sha": "636961dd666f71d5fe2fd2a0d6c8aa95c0b110ae",
        "node_id": "C_kwDOUz-U1doAKDYzNjk2MWRkNjY2ZjcxZDVmZTJmZDJhMGQ2YzhhYTk1YzBiMTEwYWU",
        "commit": {
          "author": {
            "name": "Danny Nunez",
            "email": "dnunezx@users.noreply.github.com",
            "date": "2026-10-24T03:21:49Z"
          },
          "committer": {
            "name": "Danny Nunez",
            "email": "dnunezx@users.noreply.github.com",
            "date": "2026-10-24T03:21:49Z"
          },
          "message": "Schedule native GTE screen-offset load during division",
          "tree": {
            "sha": "b8d9b760689a0f422b9e54da9338e9f42814888b",
            "url": "https://api.github.com/repos/dnunezx/PSXCore/git/trees/b8d9b760689a0f422b9e54da9338e9f42814888b"
          },
          "url": "https://api.github.com/repos/dnunezx/PSXCore/git/commits/636961dd666f71d5fe2fd2a0d6c8aa95c0b110ae",
          "comment_count": 0,
          "verification": {
            "verified": false,
            "reason": "unsigned",
            "signature": null,
            "payload": null,
            "verified_at": null
          }
        },
        "url": "https://api.github.com/repos/dnunezx/PSXCore/commits/636961dd666f71d5fe2fd2a0d6c8aa95c0b110ae",
        "html_url": "https://github.com/dnunezx/PSXCore/commit/636961dd666f71d5fe2fd2a0d6c8aa95c0b110ae",
        "comments_url": "https://api.github.com/repos/dnunezx/PSXCore/commits/636961dd666f71d5fe2fd2a0d6c8aa95c0b110ae/comments",
        "author": {
          "login": "dnunezx",
          "id": 156245780,
          "node_id": "U_kgDOCVAfFA",
          "avatar_url": "https://avatars.githubusercontent.com/u/156245780?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/dnunezx",
          "html_url": "https://github.com/dnunezx",
          "followers_url": "https://api.github.com/users/dnunezx/followers",
          "following_url": "https://api.github.com/users/dnunezx/following{/other_user}",
          "gists_url": "https://api.github.com/users/dnunezx/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/dnunezx/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/dnunezx/subscriptions",
          "organizations_url": "https://api.github.com/users/dnunezx/orgs",
          "repos_url": "https://api.github.com/users/dnunezx/repos",
          "events_url": "https://api.github.com/users/dnunezx/events{/privacy}",
          "received_events_url": "https://api.github.com/users/dnunezx/received_events",
          "type": "User",
          "user_view_type": "public",
          "site_admin": false
        },
        "committer": {
          "login": "dnunezx",
          "id": 156245780,
          "node_id": "U_kgDOCVAfFA",
          "avatar_url": "https://avatars.githubusercontent.com/u/156245780?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/dnunezx",
          "html_url": "https://github.com/dnunezx",
          "followers_url": "https://api.github.com/users/dnunezx/followers",
          "following_url": "https://api.github.com/users/dnunezx/following{/other_user}",
          "gists_url": "https://api.github.com/users/dnunezx/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/dnunezx/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/dnunezx/subscriptions",
          "organizations_url": "https://api.github.com/users/dnunezx/orgs",
          "repos_url": "https://api.github.com/users/dnunezx/repos",
          "events_url": "https://api.github.com/users/dnunezx/events{/privacy}",
          "received_events_url": "https://api.github.com/users/dnunezx/received_events",
          "type": "User",
          "user_view_type": "public",
          "site_admin": false
        },
        "parents": [
          {
            "sha": "827b7cad0157aa6af7f41d42d7152db9c2d6837c",
            "url": "https://api.github.com/repos/dnunezx/PSXCore/commits/827b7cad0157aa6af7f41d42d7152db9c2d6837c",
            "html_url": "https://github.com/dnunezx/PSXCore/commit/827b7cad0157aa6af7f41d42d7152db9c2d6837c"
          }
        ]
      },
      "status": "ahead",
      "ahead_by": 2,
      "behind_by": 0,
      "total_commits": 2,
      "commits": [
        {
          "sha": "1cb5c92f06ff582d56437f7015cc69063318d228",
          "node_id": "C_kwDOUz-U1doAKDFjYjVjOTJmMDZmZjU4MmQ1NjQzN2Y3MDE1Y2M2OTA2MzMxOGQyMjg",
          "commit": {
            "author": {
              "name": "Danny Nunez",
              "email": "dnunezx@users.noreply.github.com",
              "date": "2026-10-24T20:21:57Z"
            },
            "committer": {
              "name": "Danny Nunez",
              "email": "dnunezx@users.noreply.github.com",
              "date": "2026-10-24T20:21:57Z"
            },
            "message": "Keep startup diagnostic display out of production bootstrap",
            "tree": {
              "sha": "31168f4e91c9e1e17c7c2856642ac500f9c3c7fd",
              "url": "https://api.github.com/repos/dnunezx/PSXCore/git/trees/31168f4e91c9e1e17c7c2856642ac500f9c3c7fd"
            },
            "url": "https://api.github.com/repos/dnunezx/PSXCore/git/commits/1cb5c92f06ff582d56437f7015cc69063318d228",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/dnunezx/PSXCore/commits/1cb5c92f06ff582d56437f7015cc69063318d228",
          "html_url": "https://github.com/dnunezx/PSXCore/commit/1cb5c92f06ff582d56437f7015cc69063318d228",
          "comments_url": "https://api.github.com/repos/dnunezx/PSXCore/commits/1cb5c92f06ff582d56437f7015cc69063318d228/comments",
          "author": {
            "login": "dnunezx",
            "id": 156245780,
            "node_id": "U_kgDOCVAfFA",
            "avatar_url": "https://avatars.githubusercontent.com/u/156245780?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/dnunezx",
            "html_url": "https://github.com/dnunezx",
            "followers_url": "https://api.github.com/users/dnunezx/followers",
            "following_url": "https://api.github.com/users/dnunezx/following{/other_user}",
            "gists_url": "https://api.github.com/users/dnunezx/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/dnunezx/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/dnunezx/subscriptions",
            "organizations_url": "https://api.github.com/users/dnunezx/orgs",
            "repos_url": "https://api.github.com/users/dnunezx/repos",
            "events_url": "https://api.github.com/users/dnunezx/events{/privacy}",
            "received_events_url": "https://api.github.com/users/dnunezx/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "dnunezx",
            "id": 156245780,
            "node_id": "U_kgDOCVAfFA",
            "avatar_url": "https://avatars.githubusercontent.com/u/156245780?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/dnunezx",
            "html_url": "https://github.com/dnunezx",
            "followers_url": "https://api.github.com/users/dnunezx/followers",
            "following_url": "https://api.github.com/users/dnunezx/following{/other_user}",
            "gists_url": "https://api.github.com/users/dnunezx/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/dnunezx/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/dnunezx/subscriptions",
            "organizations_url": "https://api.github.com/users/dnunezx/orgs",
            "repos_url": "https://api.github.com/users/dnunezx/repos",
            "events_url": "https://api.github.com/users/dnunezx/events{/privacy}",
            "received_events_url": "https://api.github.com/users/dnunezx/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "636961dd666f71d5fe2fd2a0d6c8aa95c0b110ae",
              "url": "https://api.github.com/repos/dnunezx/PSXCore/commits/636961dd666f71d5fe2fd2a0d6c8aa95c0b110ae",
              "html_url": "https://github.com/dnunezx/PSXCore/commit/636961dd666f71d5fe2fd2a0d6c8aa95c0b110ae"
            }
          ]
        },
        {
          "sha": "5d4de02f57933f70c48335d938c1d4c8b69634ad",
          "node_id": "C_kwDOUz-U1doAKDVkNGRlMDJmNTc5MzNmNzBjNDgzMzVkOTM4YzFkNGM4YjY5NjM0YWQ",
          "commit": {
            "author": {
              "name": "Danny Nunez",
              "email": "dnunezx@users.noreply.github.com",
              "date": "2026-10-25T13:29:54Z"
            },
            "committer": {
              "name": "Danny Nunez",
              "email": "dnunezx@users.noreply.github.com",
              "date": "2026-10-25T13:29:54Z"
            },
            "message": "Increase native CD thread stack to 16 KiB for USB launches",
            "tree": {
              "sha": "41f42a965cffa5c318c403a07bc3bb391885f5f6",
              "url": "https://api.github.com/repos/dnunezx/PSXCore/git/trees/41f42a965cffa5c318c403a07bc3bb391885f5f6"
            },
            "url": "https://api.github.com/repos/dnunezx/PSXCore/git/commits/5d4de02f57933f70c48335d938c1d4c8b69634ad",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/dnunezx/PSXCore/commits/5d4de02f57933f70c48335d938c1d4c8b69634ad",
          "html_url": "https://github.com/dnunezx/PSXCore/commit/5d4de02f57933f70c48335d938c1d4c8b69634ad",
          "comments_url": "https://api.github.com/repos/dnunezx/PSXCore/commits/5d4de02f57933f70c48335d938c1d4c8b69634ad/comments",
          "author": {
            "login": "dnunezx",
            "id": 156245780,
            "node_id": "U_kgDOCVAfFA",
            "avatar_url": "https://avatars.githubusercontent.com/u/156245780?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/dnunezx",
            "html_url": "https://github.com/dnunezx",
            "followers_url": "https://api.github.com/users/dnunezx/followers",
            "following_url": "https://api.github.com/users/dnunezx/following{/other_user}",
            "gists_url": "https://api.github.com/users/dnunezx/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/dnunezx/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/dnunezx/subscriptions",
            "organizations_url": "https://api.github.com/users/dnunezx/orgs",
            "repos_url": "https://api.github.com/users/dnunezx/repos",
            "events_url": "https://api.github.com/users/dnunezx/events{/privacy}",
            "received_events_url": "https://api.github.com/users/dnunezx/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "dnunezx",
            "id": 156245780,
            "node_id": "U_kgDOCVAfFA",
            "avatar_url": "https://avatars.githubusercontent.com/u/156245780?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/dnunezx",
            "html_url": "https://github.com/dnunezx",
            "followers_url": "https://api.github.com/users/dnunezx/followers",
            "following_url": "https://api.github.com/users/dnunezx/following{/other_user}",
            "gists_url": "https://api.github.com/users/dnunezx/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/dnunezx/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/dnunezx/subscriptions",
            "organizations_url": "https://api.github.com/users/dnunezx/orgs",
            "repos_url": "https://api.github.com/users/dnunezx/repos",
            "events_url": "https://api.github.com/users/dnunezx/events{/privacy}",
            "received_events_url": "https://api.github.com/users/dnunezx/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "1cb5c92f06ff582d56437f7015cc69063318d228",
              "url": "https://api.github.com/repos/dnunezx/PSXCore/commits/1cb5c92f06ff582d56437f7015cc69063318d228",
              "html_url": "https://github.com/dnunezx/PSXCore/commit/1cb5c92f06ff582d56437f7015cc69063318d228"
            }
          ]
        }
      ],
      "files": [
        {
          "sha": "c451b5e64773f8ac3d972e697f37f77d14795b52",
          "filename": "Makefile",
          "status": "modified",
          "additions": 1,
          "deletions": 1,
          "changes": 2,
          "blob_url": "https://github.com/dnunezx/PSXCore/blob/5d4de02f57933f70c48335d938c1d4c8b69634ad/Makefile",
          "raw_url": "https://github.com/dnunezx/PSXCore/raw/5d4de02f57933f70c48335d938c1d4c8b69634ad/Makefile",
          "contents_url": "https://api.github.com/repos/dnunezx/PSXCore/contents/Makefile?ref=5d4de02f57933f70c48335d938c1d4c8b69634ad",
          "patch": "@@ -85,7 +85,7 @@ runtime: | build\n \tcp runtime/gamepatches/*.c build/runtime/gamepatches/\n \tcp runtime/igrobserve.S runtime/igrobserve.h build/runtime/\n \tcp src/hdtv.c runtime/hdtv.S build/runtime/\n-\tcp runtime/startup_diag.c runtime/startup_diag.h build/runtime/\n+\tcp runtime/startup_failure.c runtime/startup_diag.h build/runtime/\n \tcp runtime/igrwait.S runtime/igrwait.h build/runtime/\n \tcp runtime/igrquit.S runtime/igrquit.h build/runtime/\n \tcp runtime/igrreturn.h runtime/igrmenu.h build/runtime/"
        },
        {
          "sha": "b15db86843da12d11b6c57d74ff4085d6587f526",
          "filename": "runtime/Makefile",
          "status": "modified",
          "additions": 1,
          "deletions": 1,
          "changes": 2,
          "blob_url": "https://github.com/dnunezx/PSXCore/blob/5d4de02f57933f70c48335d938c1d4c8b69634ad/runtime%2FMakefile",
          "raw_url": "https://github.com/dnunezx/PSXCore/raw/5d4de02f57933f70c48335d938c1d4c8b69634ad/runtime%2FMakefile",
          "contents_url": "https://api.github.com/repos/dnunezx/PSXCore/contents/runtime%2FMakefile?ref=5d4de02f57933f70c48335d938c1d4c8b69634ad",
          "patch": "@@ -1,7 +1,7 @@\n EE_BIN = psxcore.elf\n .DEFAULT_GOAL := all\n FILE_MODULES = iomanX fileXio ps2dev9 bdm bdmfs_fatfs ata_bd usbd usbmass_bd poweroff psxcore_powerbutton psxcore_nativegate psxcore_filebridge psxcore_iomanx bdma_usb_fs bdma_usb_bd bdma_ata_fs bdma_ata_bd psxcore_massproxy\n-EE_OBJS = reverbexact.o main.o startup_diag.o file_storage.o file.o file_patch.o vcd.o sha1.o transfer.o hooks.o memoryaccess.o memoryaccess_asm.o returndelay.o gtecolor.o rgb24.o gp0.o loopcycles.o codeguard_asm.o codeguard.o boot_elf.o boot_sha256.o compat.o launch.o patch.o runtime_corrections.o $(addsuffix _irx.o,$(FILE_MODULES))\n+EE_OBJS = reverbexact.o main.o startup_failure.o file_storage.o file.o file_patch.o vcd.o sha1.o transfer.o hooks.o memoryaccess.o memoryaccess_asm.o returndelay.o gtecolor.o rgb24.o gp0.o loopcycles.o codeguard_asm.o codeguard.o boot_elf.o boot_sha256.o compat.o launch.o patch.o runtime_corrections.o $(addsuffix _irx.o,$(FILE_MODULES))\n EE_INCS = -I../../include\n EE_OBJS += gamepatches.o\n EE_OBJS += hdtv.o hdtv_asm.o"
        },
        {
          "sha": "d98a20bb9b66fcd7b621ba72f5aa7fbd0afe3814",
          "filename": "runtime/file_storage.c",
          "status": "modified",
          "additions": 28,
          "deletions": 10,
          "changes": 38,
          "blob_url": "https://github.com/dnunezx/PSXCore/blob/5d4de02f57933f70c48335d938c1d4c8b69634ad/runtime%2Ffile_storage.c",
          "raw_url": "https://github.com/dnunezx/PSXCore/raw/5d4de02f57933f70c48335d938c1d4c8b69634ad/runtime%2Ffile_storage.c",
          "contents_url": "https://api.github.com/repos/dnunezx/PSXCore/contents/runtime%2Ffile_storage.c?ref=5d4de02f57933f70c48335d938c1d4c8b69634ad",
          "patch": "@@ -62,8 +62,7 @@ static const char *storage_driver(uint32_t backend) {\n }\n static int load(const char *name,const unsigned char *bytes,unsigned size,const char *args,unsigned length) {\n     int result=-1;\n-    char operation[96];snprintf(operation,sizeof(operation),\"Load IOP module: %s\",name);\n-    np_startup_operation(operation);\n+    np_startup_operationf(\"Load IOP module: %s\",name);\n     storage_trace(\"PSXCORE file-module=%s phase=begin\\n\",name);\n     int id=SifExecModuleBuffer((void *)bytes,size,length,args,&result);\n     storage_trace(\"PSXCORE file-module=%s id=%d result=%d\\n\",name,id,result);\n@@ -229,15 +228,21 @@ static void serial(int fd,uint64_t bytes,char out[16]) {\n }\n /* Fixed-size header and bounded ISO metadata only. No full-image hash or\n  * per-game metadata sidecar is required before or after the IOP reset. */\n+#if NP_STARTUP_DIAGNOSTICS\n static char inspect_context[137];\n+#endif\n static const char *inspect(const char *path,NpFileLaunchRequest *r,int initial) {\n     iox_stat_t before,after;unsigned char head[NP_FILE_HEADER_HASH_BYTES];\n     memset(&before,0,sizeof(before));\n+#if NP_STARTUP_DIAGNOSTICS\n     snprintf(inspect_context,sizeof(inspect_context),\"%.68s\",path);\n+#endif\n     np_startup_context(\"%.68s\",path);\n     int status=fileXioGetStat(path,&before);\n     if(status<0 || !FIO_S_ISREG(before.mode)) {\n+#if NP_STARTUP_DIAGNOSTICS\n         snprintf(inspect_context,sizeof(inspect_context),\"%.68s\\nGetStat=%d mode=%08x\",path,status,(unsigned)before.mode);\n+#endif\n         np_startup_context(\"%s\",inspect_context);\n         return status<0?\"file-image-stat\":\"file-image-not-regular\";\n     }\n@@ -331,7 +336,10 @@ static const char *select_initial_once(NpFileLaunchRequest *r,char selected[NP_F\n     unsigned matches=0,endpoints=0;\n     NpFileLaunchRequest chosen;\n     char chosen_path[NP_FILE_PATH_BYTES+8]={0};\n-    const char *failure=NULL;char failure_context[137]={0};\n+    const char *failure=NULL;\n+#if NP_STARTUP_DIAGNOSTICS\n+    char failure_context[137]={0};\n+#endif\n     for(unsigned i=0;i<10;++i) {\n         char root[8],path[NP_FILE_PATH_BYTES+8];\n         snprintf(root,sizeof(root),\"mass%u:\",i);\n@@ -341,15 +349,23 @@ static const char *select_initial_once(NpFileLaunchRequest *r,char selected[NP_F\n         /* A supplied ID disambiguates partitions before touching their VCDs. */\n         if(np_file_volume_id_present(r->disc_volume)) {\n             const char *error=volume_check(root,candidate.disc_volume);\n-            if(error){failure=error;snprintf(failure_context,sizeof(failure_context),\"Volume %s did not match selected ID\",root);continue;}\n+            if(error){\n+                failure=error;\n+#if NP_STARTUP_DIAGNOSTICS\n+                snprintf(failure_context,sizeof(failure_context),\"Volume %s did not match selected ID\",root);\n+#endif\n+                continue;\n+            }\n         }\n         if(join(path,sizeof(path),root,r->disc_path))return \"file-image-path\";\n         const char *error=inspect(path,&candidate,1);\n         if(error) {\n             /* Preserve a substantive failure over another absent filename. */\n             if(!failure || !strcmp(failure,\"file-image-stat\")) {\n                 failure=error;\n+#if NP_STARTUP_DIAGNOSTICS\n                 memcpy(failure_context,inspect_context,sizeof(failure_context));\n+#endif\n             }\n             continue;\n         }\n@@ -360,7 +376,9 @@ static const char *select_initial_once(NpFileLaunchRequest *r,char selected[NP_F\n     }\n     if(matches!=1) {\n         if(!endpoints)return \"file-image-endpoint-not-found\";\n+#if NP_STARTUP_DIAGNOSTICS\n         if(failure_context[0])np_startup_context(\"%s\",failure_context);\n+#endif\n         return failure?failure:\"file-image-not-found-or-changed\";\n     }\n     *r=chosen;memcpy(selected,chosen_path,strlen(chosen_path)+1);\n@@ -677,17 +695,17 @@ const char *np_file_runtime_path(const NpFileLaunchRequest *r,const char *name,c\n     return !mounted[0] || n<0 || (size_t)n>=size?\"file-runtime-path\":NULL;\n }\n int np_file_read_asset(const NpFileLaunchRequest *r,const char *name,void *target,unsigned bytes,const unsigned char expected[32]) {\n-    char path[160],operation[96];iox_stat_t st;\n+    char path[160];iox_stat_t st;\n     if(np_file_runtime_path(r,name,path,sizeof(path))){np_startup_error(\"asset-path\");return -1;}\n-    snprintf(operation,sizeof(operation),\"Check asset size: %s\",name);np_startup_operation(operation);\n+    np_startup_operationf(\"Check asset size: %s\",name);\n     np_startup_context(\"%s\",path);\n     int status=fileXioGetStat(path,&st);\n     if(status<0){np_startup_context(\"%s stat=%d\",path,status);np_startup_error(\"asset-stat\");return -1;}\n     if(st.hisize || st.size!=bytes){np_startup_context(\"%s size=%u:%u expected=%u\",name,(unsigned)st.hisize,(unsigned)st.size,bytes);np_startup_error(\"asset-size\");return -1;}\n-    np_startup_done();snprintf(operation,sizeof(operation),\"Open asset: %s\",name);np_startup_operation(operation);\n+    np_startup_done();np_startup_operationf(\"Open asset: %s\",name);\n     int fd=fileXioOpen(path,FIO_O_RDONLY,0);\n     if(fd<0){np_startup_context(\"%s open=%d\",path,fd);np_startup_error(\"asset-open\");return -1;}\n-    np_startup_done();snprintf(operation,sizeof(operation),\"Read asset: %s\",name);np_startup_operation(operation);\n+    np_startup_done();np_startup_operationf(\"Read asset: %s\",name);\n     unsigned done=0;int result=0;\n     while(done<bytes) {\n         unsigned chunk=bytes-done;if(chunk>65536)chunk=65536;\n@@ -697,11 +715,11 @@ int np_file_read_asset(const NpFileLaunchRequest *r,const char *name,void *targe\n         done+=(unsigned)result;\n     }\n     if(done!=bytes){np_startup_context(\"%s read=%u/%u result=%d\",name,done,bytes,result);np_startup_error(\"asset-read\");}\n-    else {np_startup_done();snprintf(operation,sizeof(operation),\"Close asset: %s\",name);np_startup_operation(operation);}\n+    else {np_startup_done();np_startup_operationf(\"Close asset: %s\",name);}\n     int closed=fileXioClose(fd);\n     if(done!=bytes)return -1;\n     if(closed<0){np_startup_context(\"%s close=%d\",name,closed);np_startup_error(\"asset-close\");return -1;}\n-    np_startup_done();snprintf(operation,sizeof(operation),\"Verify SHA-256: %s\",name);np_startup_operation(operation);\n+    np_startup_done();np_startup_operationf(\"Verify SHA-256: %s\",name);\n     unsigned char hash[32];np_boot_sha256(target,bytes,hash);\n     if(memcmp(hash,expected,32)){np_startup_error(\"asset-hash\");return -1;}\n     return 0;"
        },
        {
          "sha": "11a78ead08f077d7b40e9db891a257afa44ff558",
          "filename": "runtime/main.c",
          "status": "modified",
          "additions": 7,
          "deletions": 0,
          "changes": 7,
          "blob_url": "https://github.com/dnunezx/PSXCore/blob/5d4de02f57933f70c48335d938c1d4c8b69634ad/runtime%2Fmain.c",
          "raw_url": "https://github.com/dnunezx/PSXCore/raw/5d4de02f57933f70c48335d938c1d4c8b69634ad/runtime%2Fmain.c",
          "contents_url": "https://api.github.com/repos/dnunezx/PSXCore/contents/runtime%2Fmain.c?ref=5d4de02f57933f70c48335d938c1d4c8b69634ad",
          "patch": "@@ -1025,6 +1025,13 @@ int main(int argc,char **argv) {\n     if(file_launch && np_file_storage_plan(file_request.runtime,&patch_set)) {\n         trace(\"PSXCORE rejected=file-storage-patch-plan\\n\");goto rejected;\n     }\n+    /* Match POPStarter's USB stack allocation for native IOPCD's RPC thread.\n+     * The separate filesystem worker does not enlarge this native stack. */\n+    if(file_launch && file_request.environment==NP_ENV_PS2 &&\n+       file_request.disc.backend==NP_FILE_BACKEND_USB &&\n+       queue_word(0x2fb598,0x24020800,0x24024000)) {\n+        trace(\"PSXCORE rejected=usb-iopcd-stack-patch-plan\\n\");goto rejected;\n+    }\n     error=np_runtime_correct(NP_RUNTIME_KNOWN,&patch_set);\n     if(error){trace(\"PSXCORE rejected=%s\\n\",error);goto rejected;}\n     if(native && install_clut_refresh()) {"
        },
        {
          "sha": "a50b7a629ebf372af6eca72b25be96d4048ebde9",
          "filename": "runtime/startup_diag.c",
          "status": "removed",
          "additions": 0,
          "deletions": 80,
          "changes": 80,
          "blob_url": "https://github.com/dnunezx/PSXCore/blob/636961dd666f71d5fe2fd2a0d6c8aa95c0b110ae/runtime%2Fstartup_diag.c",
          "raw_url": "https://github.com/dnunezx/PSXCore/raw/636961dd666f71d5fe2fd2a0d6c8aa95c0b110ae/runtime%2Fstartup_diag.c",
          "contents_url": "https://api.github.com/repos/dnunezx/PSXCore/contents/runtime%2Fstartup_diag.c?ref=636961dd666f71d5fe2fd2a0d6c8aa95c0b110ae",
          "patch": "@@ -1,80 +0,0 @@\n-/* Original bounded EE/GS-only startup display. No FILEIO/RPC or resident hooks. */\n-#include \"startup_diag.h\"\n-#include <stdarg.h>\n-#include <stdio.h>\n-#include <string.h>\n-#ifndef NP_HOST_TEST\n-#include <debug.h>\n-#endif\n-#ifndef NP_STARTUP_BUILD\n-#define NP_STARTUP_BUILD \"startup-20261012-r8\"\n-#endif\n-enum { WIDTH=68, ROWS=22 };\n-static char previous[ROWS][WIDTH],frame[ROWS][WIDTH];\n-static char operation[137],completed[137],selection[137],path_text[205];\n-static char error_text[137],context[205],environment_text[69];\n-static unsigned stage_number;\n-static int ready,stopped;\n-static void copy(char *out,size_t size,const char *in) {\n-    snprintf(out,size,\"%s\",in?in:\"\");\n-}\n-static void put(unsigned row,unsigned lines,const char *text) {\n-    unsigned col=0;\n-    for(;*text && lines && row<ROWS;++text) {\n-        unsigned char ch=(unsigned char)*text;\n-        if(ch=='\\n' || col==WIDTH) {\n-            ++row;--lines;col=0;\n-            if(!lines || row==ROWS)break;\n-            if(ch=='\\n')continue;\n-        }\n-        frame[row][col++]=(ch>=32 && ch<127)?(char)ch:'?';\n-    }\n-}\n-static void draw(void) {\n-    if(!ready)return;\n-    char title[69];memset(frame,' ',sizeof(frame));\n-    put(0,1,stopped?\"PSXCore startup stopped\":\"PSXCore startup\");\n-    put(1,1,NP_STARTUP_BUILD);\n-    snprintf(title,sizeof(title),\"Stage %02u: %s\",stage_number,stopped?\"ERROR\":\"BEGIN / waiting for return\");\n-    put(3,1,title);put(4,2,operation);\n-    put(7,2,selection);put(9,1,environment_text);put(10,3,path_text);\n-    put(14,1,\"Last completed:\");put(15,2,completed);\n-    if(error_text[0]) {put(17,1,\"Error:\");put(18,2,error_text);}\n-    put(error_text[0]?20:19,error_text[0]?2:3,context);\n-    for(unsigned y=0;y<ROWS;++y)for(unsigned x=0;x<WIDTH;++x)\n-        if(previous[y][x]!=frame[y][x]) {\n-#ifndef NP_HOST_TEST\n-            scr_putchar((x+6)*8,(y+2)*8,stopped?0x8080ff:0xffffff,(unsigned char)frame[y][x]);\n-#endif\n-            previous[y][x]=frame[y][x];\n-        }\n-}\n-void np_startup_init(void) {\n-#ifndef NP_HOST_TEST\n-    init_scr();scr_setCursor(0);\n-#endif\n-    memset(previous,0,sizeof(previous));ready=1;stopped=0;stage_number=0;\n-    operation[0]=completed[0]=selection[0]=path_text[0]=error_text[0]=context[0]=environment_text[0]=0;\n-    draw();\n-}\n-void np_startup_begin(unsigned stage,const char *op) {\n-    stage_number=stage;np_startup_operation(op);\n-}\n-void np_startup_operation(const char *op) {\n-    copy(operation,sizeof(operation),op);if(!error_text[0])context[0]=0;draw();\n-}\n-void np_startup_done(void) {copy(completed,sizeof(completed),operation);draw();}\n-void np_startup_selection(const char *backend,unsigned device,const char *save_backend,\n-        unsigned save_device,unsigned environment,const char *path) {\n-    snprintf(selection,sizeof(selection),\"Disc: %s device %u; saves: %s device %u\",backend,device,save_backend,save_device);\n-    snprintf(environment_text,sizeof(environment_text),\"Environment: %s\",environment==1?\"physical PS2\":\"unknown\");\n-    copy(path_text,sizeof(path_text),path);draw();\n-}\n-void np_startup_context(const char *format,...) {\n-    if(error_text[0])return;\n-    va_list args;va_start(args,format);vsnprintf(context,sizeof(context),format,args);va_end(args);draw();\n-}\n-void np_startup_error(const char *code) {\n-    if(!error_text[0]) {copy(error_text,sizeof(error_text),code);draw();}\n-}\n-void np_startup_stop(const char *fallback) {np_startup_error(fallback);stopped=1;memset(previous,0,sizeof(previous));draw();}"
        },
        {
          "sha": "885051214d4b57d13dd29ae60510b2e9329e24cf",
          "filename": "runtime/startup_diag.h",
          "status": "modified",
          "additions": 18,
          "deletions": 3,
          "changes": 21,
          "blob_url": "https://github.com/dnunezx/PSXCore/blob/5d4de02f57933f70c48335d938c1d4c8b69634ad/runtime%2Fstartup_diag.h",
          "raw_url": "https://github.com/dnunezx/PSXCore/raw/5d4de02f57933f70c48335d938c1d4c8b69634ad/runtime%2Fstartup_diag.h",
          "contents_url": "https://api.github.com/repos/dnunezx/PSXCore/contents/runtime%2Fstartup_diag.h?ref=5d4de02f57933f70c48335d938c1d4c8b69634ad",
          "patch": "@@ -1,13 +1,28 @@\n-/* Bootstrap-only display; never call after np_runtime_transfer. */\n+/* Startup progress is available only in the separate local diagnostic build. */\n #ifndef PSXCORE_STARTUP_DIAG_H\n #define PSXCORE_STARTUP_DIAG_H\n+#ifndef NP_STARTUP_DIAGNOSTICS\n+#define NP_STARTUP_DIAGNOSTICS 0\n+#endif\n+void np_startup_error(const char *code);\n+void np_startup_stop(const char *fallback);\n+#if NP_STARTUP_DIAGNOSTICS\n void np_startup_init(void);\n void np_startup_begin(unsigned stage,const char *operation);\n void np_startup_operation(const char *operation);\n+void np_startup_operationf(const char *format,...) __attribute__((format(printf,1,2)));\n void np_startup_done(void);\n void np_startup_selection(const char *backend,unsigned device,const char *save_backend,\n     unsigned save_device,unsigned environment,const char *path);\n void np_startup_context(const char *format,...) __attribute__((format(printf,1,2)));\n-void np_startup_error(const char *code);\n-void np_startup_stop(const char *fallback);\n+#else\n+/* Do not evaluate diagnostic arguments or retain their text in production. */\n+#define np_startup_init(...) ((void)0)\n+#define np_startup_begin(...) ((void)0)\n+#define np_startup_operation(...) ((void)0)\n+#define np_startup_operationf(...) ((void)0)\n+#define np_startup_done(...) ((void)0)\n+#define np_startup_selection(...) ((void)0)\n+#define np_startup_context(...) ((void)0)\n+#endif\n #endif"
        },
        {
          "sha": "00058c9194869cf7610445a5b04ef3b5b3dff5fe",
          "filename": "runtime/startup_failure.c",
          "status": "added",
          "additions": 17,
          "deletions": 0,
          "changes": 17,
          "blob_url": "https://github.com/dnunezx/PSXCore/blob/5d4de02f57933f70c48335d938c1d4c8b69634ad/runtime%2Fstartup_failure.c",
          "raw_url": "https://github.com/dnunezx/PSXCore/raw/5d4de02f57933f70c48335d938c1d4c8b69634ad/runtime%2Fstartup_failure.c",
          "contents_url": "https://api.github.com/repos/dnunezx/PSXCore/contents/runtime%2Fstartup_failure.c?ref=5d4de02f57933f70c48335d938c1d4c8b69634ad",
          "patch": "@@ -0,0 +1,17 @@\n+/* Production startup stays silent until a checked launch failure. */\n+#include \"startup_diag.h\"\n+#include <stdio.h>\n+#ifndef NP_HOST_TEST\n+#include <debug.h>\n+#endif\n+static char first_error[137];\n+void np_startup_error(const char *code) {\n+    if(!first_error[0] && code)snprintf(first_error,sizeof(first_error),\"%s\",code);\n+}\n+void np_startup_stop(const char *fallback) {\n+    np_startup_error(fallback);\n+#ifndef NP_HOST_TEST\n+    init_scr();scr_setCursor(0);\n+    scr_printf(\"PSXCore could not start the game.\\nError: %s\\n\",first_error);\n+#endif\n+}"
        }
      ]
    }
  },
  {
    "repo": "NathanNeurotic/Launchstation",
    "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/compare/3e03e640a1434a6d23dd1b80498fa0fbb7c88672...b815d10da29274200572e61467d72a718004a708",
    "comparison": {
      "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/compare/3e03e640a1434a6d23dd1b80498fa0fbb7c88672...b815d10da29274200572e61467d72a718004a708",
      "html_url": "https://github.com/NathanNeurotic/Launchstation/compare/3e03e640a1434a6d23dd1b80498fa0fbb7c88672...b815d10da29274200572e61467d72a718004a708",
      "permalink_url": "https://github.com/NathanNeurotic/Launchstation/compare/NathanNeurotic:3e03e64...NathanNeurotic:b815d10",
      "diff_url": "https://github.com/NathanNeurotic/Launchstation/compare/3e03e640a1434a6d23dd1b80498fa0fbb7c88672...b815d10da29274200572e61467d72a718004a708.diff",
      "patch_url": "https://github.com/NathanNeurotic/Launchstation/compare/3e03e640a1434a6d23dd1b80498fa0fbb7c88672...b815d10da29274200572e61467d72a718004a708.patch",
      "base_commit": {
        "sha": "3e03e640a1434a6d23dd1b80498fa0fbb7c88672",
        "node_id": "C_kwDOUr5zH9oAKDNlMDNlNjQwYTE0MzRhNmQyM2RkMWI4MDQ5OGZhMGZiYjdjODg2NzI",
        "commit": {
          "author": {
            "name": "NathanNeurotic (Ripto)",
            "email": "109461996+NathanNeurotic@users.noreply.github.com",
            "date": "2026-10-24T11:46:04Z"
          },
          "committer": {
            "name": "NathanNeurotic (Ripto)",
            "email": "109461996+NathanNeurotic@users.noreply.github.com",
            "date": "2026-10-24T11:46:04Z"
          },
          "message": "Revert repository state to 2e140ab6baa720334a90da4aad031228ab3d9baa\n\nReverts changes introduced between 2e140ab and master (PRs #191, #192, #193, #194, #195, #196) to restore known-working file copying and device behavior.",
          "tree": {
            "sha": "ad9e84028cd8ab9f9b3893f17759fa0d0d95c06f",
            "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/trees/ad9e84028cd8ab9f9b3893f17759fa0d0d95c06f"
          },
          "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/commits/3e03e640a1434a6d23dd1b80498fa0fbb7c88672",
          "comment_count": 0,
          "verification": {
            "verified": false,
            "reason": "unsigned",
            "signature": null,
            "payload": null,
            "verified_at": null
          }
        },
        "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/3e03e640a1434a6d23dd1b80498fa0fbb7c88672",
        "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/3e03e640a1434a6d23dd1b80498fa0fbb7c88672",
        "comments_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/3e03e640a1434a6d23dd1b80498fa0fbb7c88672/comments",
        "author": {
          "login": "NathanNeurotic",
          "id": 109461996,
          "node_id": "U_kgDOBoZB7A",
          "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/NathanNeurotic",
          "html_url": "https://github.com/NathanNeurotic",
          "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
          "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
          "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
          "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
          "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
          "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
          "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
          "type": "User",
          "user_view_type": "public",
          "site_admin": false
        },
        "committer": {
          "login": "NathanNeurotic",
          "id": 109461996,
          "node_id": "U_kgDOBoZB7A",
          "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/NathanNeurotic",
          "html_url": "https://github.com/NathanNeurotic",
          "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
          "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
          "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
          "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
          "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
          "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
          "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
          "type": "User",
          "user_view_type": "public",
          "site_admin": false
        },
        "parents": [
          {
            "sha": "625e9272502cb5701b21d5c6ef1c92531883f908",
            "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/625e9272502cb5701b21d5c6ef1c92531883f908",
            "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/625e9272502cb5701b21d5c6ef1c92531883f908"
          }
        ]
      },
      "merge_base_commit": {
        "sha": "3e03e640a1434a6d23dd1b80498fa0fbb7c88672",
        "node_id": "C_kwDOUr5zH9oAKDNlMDNlNjQwYTE0MzRhNmQyM2RkMWI4MDQ5OGZhMGZiYjdjODg2NzI",
        "commit": {
          "author": {
            "name": "NathanNeurotic (Ripto)",
            "email": "109461996+NathanNeurotic@users.noreply.github.com",
            "date": "2026-10-24T11:46:04Z"
          },
          "committer": {
            "name": "NathanNeurotic (Ripto)",
            "email": "109461996+NathanNeurotic@users.noreply.github.com",
            "date": "2026-10-24T11:46:04Z"
          },
          "message": "Revert repository state to 2e140ab6baa720334a90da4aad031228ab3d9baa\n\nReverts changes introduced between 2e140ab and master (PRs #191, #192, #193, #194, #195, #196) to restore known-working file copying and device behavior.",
          "tree": {
            "sha": "ad9e84028cd8ab9f9b3893f17759fa0d0d95c06f",
            "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/trees/ad9e84028cd8ab9f9b3893f17759fa0d0d95c06f"
          },
          "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/commits/3e03e640a1434a6d23dd1b80498fa0fbb7c88672",
          "comment_count": 0,
          "verification": {
            "verified": false,
            "reason": "unsigned",
            "signature": null,
            "payload": null,
            "verified_at": null
          }
        },
        "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/3e03e640a1434a6d23dd1b80498fa0fbb7c88672",
        "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/3e03e640a1434a6d23dd1b80498fa0fbb7c88672",
        "comments_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/3e03e640a1434a6d23dd1b80498fa0fbb7c88672/comments",
        "author": {
          "login": "NathanNeurotic",
          "id": 109461996,
          "node_id": "U_kgDOBoZB7A",
          "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/NathanNeurotic",
          "html_url": "https://github.com/NathanNeurotic",
          "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
          "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
          "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
          "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
          "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
          "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
          "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
          "type": "User",
          "user_view_type": "public",
          "site_admin": false
        },
        "committer": {
          "login": "NathanNeurotic",
          "id": 109461996,
          "node_id": "U_kgDOBoZB7A",
          "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/NathanNeurotic",
          "html_url": "https://github.com/NathanNeurotic",
          "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
          "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
          "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
          "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
          "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
          "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
          "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
          "type": "User",
          "user_view_type": "public",
          "site_admin": false
        },
        "parents": [
          {
            "sha": "625e9272502cb5701b21d5c6ef1c92531883f908",
            "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/625e9272502cb5701b21d5c6ef1c92531883f908",
            "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/625e9272502cb5701b21d5c6ef1c92531883f908"
          }
        ]
      },
      "status": "ahead",
      "ahead_by": 17,
      "behind_by": 0,
      "total_commits": 17,
      "commits": [
        {
          "sha": "9b2644e6890f35b4801c90df087023d06e5e9ca9",
          "node_id": "C_kwDOUr5zH9oAKDliMjY0NGU2ODkwZjM1YjQ4MDFjOTBkZjA4NzAyM2QwNmU1ZTljYTk",
          "commit": {
            "author": {
              "name": "NathanNeurotic (Ripto)",
              "email": "109461996+NathanNeurotic@users.noreply.github.com",
              "date": "2026-10-24T19:36:18Z"
            },
            "committer": {
              "name": "NathanNeurotic (Ripto)",
              "email": "109461996+NathanNeurotic@users.noreply.github.com",
              "date": "2026-10-24T19:36:53Z"
            },
            "message": "Recover stable dashboard feedback and localize core UI in nine languages\n\nPreserve staged copy recovery while resolving ambiguous legacy stat results and MCMAN creation flags. Restore transfer layout, viewport synchronization and Neutrino option audio; omit offscreen carousel cards. Append dashboard labels to all nine language tables with focused regression coverage.\n\nCo-authored-by: OpenAI ChatGPT <noreply@openai.com>",
            "tree": {
              "sha": "a13f5d28cfafd57ab6e4ef8f1a237c73dd6103da",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/trees/a13f5d28cfafd57ab6e4ef8f1a237c73dd6103da"
            },
            "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/commits/9b2644e6890f35b4801c90df087023d06e5e9ca9",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/9b2644e6890f35b4801c90df087023d06e5e9ca9",
          "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/9b2644e6890f35b4801c90df087023d06e5e9ca9",
          "comments_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/9b2644e6890f35b4801c90df087023d06e5e9ca9/comments",
          "author": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "3e03e640a1434a6d23dd1b80498fa0fbb7c88672",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/3e03e640a1434a6d23dd1b80498fa0fbb7c88672",
              "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/3e03e640a1434a6d23dd1b80498fa0fbb7c88672"
            }
          ]
        },
        {
          "sha": "bf9d7eda209f00b97bbdc5684d3875388d178c4e",
          "node_id": "C_kwDOUr5zH9oAKGJmOWQ3ZWRhMjA5ZjAwYjk3YmJkYzU2ODRkMzg3NTM4OGQxNzhjNGU",
          "commit": {
            "author": {
              "name": "NathanNeurotic (Ripto)",
              "email": "109461996+NathanNeurotic@users.noreply.github.com",
              "date": "2026-10-24T19:38:58Z"
            },
            "committer": {
              "name": "NathanNeurotic (Ripto)",
              "email": "109461996+NathanNeurotic@users.noreply.github.com",
              "date": "2026-10-24T19:38:58Z"
            },
            "message": "Stamp ps2build diagnostics with the committed source identity\n\nGenerate githash.h before CI target builds so ignored headers cannot misidentify the recovery artifacts. Verify stale-header replacement and unchanged reruns.\n\nCo-authored-by: OpenAI ChatGPT <noreply@openai.com>",
            "tree": {
              "sha": "96f1d9e0943db93dbfe7eb04397f95ed74dd5cbd",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/trees/96f1d9e0943db93dbfe7eb04397f95ed74dd5cbd"
            },
            "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/commits/bf9d7eda209f00b97bbdc5684d3875388d178c4e",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/bf9d7eda209f00b97bbdc5684d3875388d178c4e",
          "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/bf9d7eda209f00b97bbdc5684d3875388d178c4e",
          "comments_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/bf9d7eda209f00b97bbdc5684d3875388d178c4e/comments",
          "author": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "9b2644e6890f35b4801c90df087023d06e5e9ca9",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/9b2644e6890f35b4801c90df087023d06e5e9ca9",
              "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/9b2644e6890f35b4801c90df087023d06e5e9ca9"
            }
          ]
        },
        {
          "sha": "8565c9037a730c6f31d8807c3ffcb795916eb98b",
          "node_id": "C_kwDOUr5zH9oAKDg1NjVjOTAzN2E3MzBjNmYzMWQ4ODA3YzNmZmNiNzk1OTE2ZWI5OGI",
          "commit": {
            "author": {
              "name": "NathanNeurotic (Ripto)",
              "email": "noahtkevans@gmail.com",
              "date": "2026-10-24T21:14:58Z"
            },
            "committer": {
              "name": "NathanNeurotic (Ripto)",
              "email": "noahtkevans@gmail.com",
              "date": "2026-10-24T21:14:58Z"
            },
            "message": "fix: reject unsafe external dashboard format overrides",
            "tree": {
              "sha": "7b237802c39ff66fbba917aef024e8536284e86f",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/trees/7b237802c39ff66fbba917aef024e8536284e86f"
            },
            "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/commits/8565c9037a730c6f31d8807c3ffcb795916eb98b",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/8565c9037a730c6f31d8807c3ffcb795916eb98b",
          "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/8565c9037a730c6f31d8807c3ffcb795916eb98b",
          "comments_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/8565c9037a730c6f31d8807c3ffcb795916eb98b/comments",
          "author": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "bf9d7eda209f00b97bbdc5684d3875388d178c4e",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/bf9d7eda209f00b97bbdc5684d3875388d178c4e",
              "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/bf9d7eda209f00b97bbdc5684d3875388d178c4e"
            }
          ]
        },
        {
          "sha": "eb24d30a2d64304f601dbba072831f6894663a3f",
          "node_id": "C_kwDOUr5zH9oAKGViMjRkMzBhMmQ2NDMwNGY2MDFkYmJhMDcyODMxZjY4OTQ2NjNhM2Y",
          "commit": {
            "author": {
              "name": "NathanNeurotic (Ripto)",
              "email": "noahtkevans@gmail.com",
              "date": "2026-10-24T21:15:29Z"
            },
            "committer": {
              "name": "NathanNeurotic (Ripto)",
              "email": "noahtkevans@gmail.com",
              "date": "2026-10-24T21:15:29Z"
            },
            "message": "test: cover translation format injection and native MC O_EXCL handling",
            "tree": {
              "sha": "3c6f09b3386bdc88c76f9ebe0a34ecac7094ff1e",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/trees/3c6f09b3386bdc88c76f9ebe0a34ecac7094ff1e"
            },
            "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/commits/eb24d30a2d64304f601dbba072831f6894663a3f",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/eb24d30a2d64304f601dbba072831f6894663a3f",
          "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/eb24d30a2d64304f601dbba072831f6894663a3f",
          "comments_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/eb24d30a2d64304f601dbba072831f6894663a3f/comments",
          "author": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "8565c9037a730c6f31d8807c3ffcb795916eb98b",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/8565c9037a730c6f31d8807c3ffcb795916eb98b",
              "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/8565c9037a730c6f31d8807c3ffcb795916eb98b"
            }
          ]
        },
        {
          "sha": "17079a4377fe17fa070d0338c3e6a611b53dce44",
          "node_id": "C_kwDOUr5zH9oAKDE3MDc5YTQzNzdmZTE3ZmEwNzBkMDMzOGMzZTZhNjExYjUzZGNlNDQ",
          "commit": {
            "author": {
              "name": "NathanNeurotic (Ripto)",
              "email": "noahtkevans@gmail.com",
              "date": "2026-10-24T21:44:15Z"
            },
            "committer": {
              "name": "NathanNeurotic (Ripto)",
              "email": "noahtkevans@gmail.com",
              "date": "2026-10-24T21:44:15Z"
            },
            "message": "fix: disambiguate EISDIR copy preflight and bypass unsupported MMCE chstat",
            "tree": {
              "sha": "ce221cda9d1633910e9126ead09ee45e1e231d25",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/trees/ce221cda9d1633910e9126ead09ee45e1e231d25"
            },
            "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/commits/17079a4377fe17fa070d0338c3e6a611b53dce44",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/17079a4377fe17fa070d0338c3e6a611b53dce44",
          "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/17079a4377fe17fa070d0338c3e6a611b53dce44",
          "comments_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/17079a4377fe17fa070d0338c3e6a611b53dce44/comments",
          "author": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "eb24d30a2d64304f601dbba072831f6894663a3f",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/eb24d30a2d64304f601dbba072831f6894663a3f",
              "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/eb24d30a2d64304f601dbba072831f6894663a3f"
            }
          ]
        },
        {
          "sha": "53bec1c9f2eb84e0848798b0bd8d3931c5b23789",
          "node_id": "C_kwDOUr5zH9oAKDUzYmVjMWM5ZjJlYjg0ZTA4NDg3OThiMGJkOGQzOTMxYzViMjM3ODk",
          "commit": {
            "author": {
              "name": "NathanNeurotic (Ripto)",
              "email": "noahtkevans@gmail.com",
              "date": "2026-10-24T21:44:38Z"
            },
            "committer": {
              "name": "NathanNeurotic (Ripto)",
              "email": "noahtkevans@gmail.com",
              "date": "2026-10-24T21:44:38Z"
            },
            "message": "test: reproduce EISDIR lookup on MC and MMCE without risking directories",
            "tree": {
              "sha": "5ab38d59929919bd38dc7e6999896fa7ce1985f1",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/trees/5ab38d59929919bd38dc7e6999896fa7ce1985f1"
            },
            "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/commits/53bec1c9f2eb84e0848798b0bd8d3931c5b23789",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/53bec1c9f2eb84e0848798b0bd8d3931c5b23789",
          "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/53bec1c9f2eb84e0848798b0bd8d3931c5b23789",
          "comments_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/53bec1c9f2eb84e0848798b0bd8d3931c5b23789/comments",
          "author": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "17079a4377fe17fa070d0338c3e6a611b53dce44",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/17079a4377fe17fa070d0338c3e6a611b53dce44",
              "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/17079a4377fe17fa070d0338c3e6a611b53dce44"
            }
          ]
        },
        {
          "sha": "c99724094af4c0d25c5f198048c9078d4d182fdd",
          "node_id": "C_kwDOUr5zH9oAKGM5OTcyNDA5NGFmNGMwZDI1YzVmMTk4MDQ4YzkwNzhkNGQxODJmZGQ",
          "commit": {
            "author": {
              "name": "NathanNeurotic (Ripto)",
              "email": "noahtkevans@gmail.com",
              "date": "2026-10-24T21:45:11Z"
            },
            "committer": {
              "name": "NathanNeurotic (Ripto)",
              "email": "noahtkevans@gmail.com",
              "date": "2026-10-24T21:45:11Z"
            },
            "message": "test: exercise MC-to-MMCE and cross-device paste with EISDIR stat faults",
            "tree": {
              "sha": "518999a5183e4ee5acc6091f0c47236415f939f6",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/trees/518999a5183e4ee5acc6091f0c47236415f939f6"
            },
            "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/commits/c99724094af4c0d25c5f198048c9078d4d182fdd",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/c99724094af4c0d25c5f198048c9078d4d182fdd",
          "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/c99724094af4c0d25c5f198048c9078d4d182fdd",
          "comments_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/c99724094af4c0d25c5f198048c9078d4d182fdd/comments",
          "author": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "53bec1c9f2eb84e0848798b0bd8d3931c5b23789",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/53bec1c9f2eb84e0848798b0bd8d3931c5b23789",
              "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/53bec1c9f2eb84e0848798b0bd8d3931c5b23789"
            }
          ]
        },
        {
          "sha": "d1fb656e3d37c3f5962a3200cd71740063bfe68b",
          "node_id": "C_kwDOUr5zH9oAKGQxZmI2NTZlM2QzN2MzZjU5NjJhMzIwMGNkNzE3NDAwNjNiZmU2OGI",
          "commit": {
            "author": {
              "name": "NathanNeurotic (Ripto)",
              "email": "noahtkevans@gmail.com",
              "date": "2026-10-24T21:46:20Z"
            },
            "committer": {
              "name": "NathanNeurotic (Ripto)",
              "email": "noahtkevans@gmail.com",
              "date": "2026-10-24T21:46:20Z"
            },
            "message": "test: scope EISDIR fallback fixture to device-qualified paths",
            "tree": {
              "sha": "b1b73efb3c626dbffb98f5372fb1c51020c19c16",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/trees/b1b73efb3c626dbffb98f5372fb1c51020c19c16"
            },
            "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/commits/d1fb656e3d37c3f5962a3200cd71740063bfe68b",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/d1fb656e3d37c3f5962a3200cd71740063bfe68b",
          "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/d1fb656e3d37c3f5962a3200cd71740063bfe68b",
          "comments_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/d1fb656e3d37c3f5962a3200cd71740063bfe68b/comments",
          "author": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "c99724094af4c0d25c5f198048c9078d4d182fdd",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/c99724094af4c0d25c5f198048c9078d4d182fdd",
              "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/c99724094af4c0d25c5f198048c9078d4d182fdd"
            }
          ]
        },
        {
          "sha": "833f54ea7967cd7f192750fcb7bddcc884bdf5a6",
          "node_id": "C_kwDOUr5zH9oAKDgzM2Y1NGVhNzk2N2NkN2YxOTI3NTBmY2I3YmRkY2M4ODRiZGY1YTY",
          "commit": {
            "author": {
              "name": "NathanNeurotic (Ripto)",
              "email": "noahtkevans@gmail.com",
              "date": "2026-10-24T21:50:06Z"
            },
            "committer": {
              "name": "NathanNeurotic (Ripto)",
              "email": "noahtkevans@gmail.com",
              "date": "2026-10-24T21:50:06Z"
            },
            "message": "fix: verify MC/MMCE missing-name reports before risking existing files",
            "tree": {
              "sha": "0322521af1a5353e96176ce22ba5d4fc0fd96c3b",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/trees/0322521af1a5353e96176ce22ba5d4fc0fd96c3b"
            },
            "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/commits/833f54ea7967cd7f192750fcb7bddcc884bdf5a6",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/833f54ea7967cd7f192750fcb7bddcc884bdf5a6",
          "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/833f54ea7967cd7f192750fcb7bddcc884bdf5a6",
          "comments_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/833f54ea7967cd7f192750fcb7bddcc884bdf5a6/comments",
          "author": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "d1fb656e3d37c3f5962a3200cd71740063bfe68b",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/d1fb656e3d37c3f5962a3200cd71740063bfe68b",
              "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/d1fb656e3d37c3f5962a3200cd71740063bfe68b"
            }
          ]
        },
        {
          "sha": "78de16eea0736c65640a4855ee94293ca399341c",
          "node_id": "C_kwDOUr5zH9oAKDc4ZGUxNmVlYTA3MzZjNjU2NDBhNDg1NWVlOTQyOTNjYTM5OTM0MWM",
          "commit": {
            "author": {
              "name": "NathanNeurotic (Ripto)",
              "email": "noahtkevans@gmail.com",
              "date": "2026-10-24T21:50:25Z"
            },
            "committer": {
              "name": "NathanNeurotic (Ripto)",
              "email": "noahtkevans@gmail.com",
              "date": "2026-10-24T21:50:25Z"
            },
            "message": "test: align host file-mode macros with PS2SDK lookup validation",
            "tree": {
              "sha": "0e5f78d38601164d81451f584bc3e3c9d47decf7",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/trees/0e5f78d38601164d81451f584bc3e3c9d47decf7"
            },
            "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/commits/78de16eea0736c65640a4855ee94293ca399341c",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/78de16eea0736c65640a4855ee94293ca399341c",
          "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/78de16eea0736c65640a4855ee94293ca399341c",
          "comments_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/78de16eea0736c65640a4855ee94293ca399341c/comments",
          "author": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "833f54ea7967cd7f192750fcb7bddcc884bdf5a6",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/833f54ea7967cd7f192750fcb7bddcc884bdf5a6",
              "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/833f54ea7967cd7f192750fcb7bddcc884bdf5a6"
            }
          ]
        },
        {
          "sha": "dfd192e7fff46aee449b8b4bbb912be89a0997e1",
          "node_id": "C_kwDOUr5zH9oAKGRmZDE5MmU3ZmZmNDZhZWU0NDliOGI0YmJiOTEyYmU4OWEwOTk3ZTE",
          "commit": {
            "author": {
              "name": "NathanNeurotic (Ripto)",
              "email": "noahtkevans@gmail.com",
              "date": "2026-10-24T21:52:17Z"
            },
            "committer": {
              "name": "NathanNeurotic (Ripto)",
              "email": "noahtkevans@gmail.com",
              "date": "2026-10-24T21:52:17Z"
            },
            "message": "fix: guard MMCE read-side wire paths and test non-destructive rename failures",
            "tree": {
              "sha": "195891cc52e142480a2c9ffdaffd17a23bb819d8",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/trees/195891cc52e142480a2c9ffdaffd17a23bb819d8"
            },
            "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/commits/dfd192e7fff46aee449b8b4bbb912be89a0997e1",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/dfd192e7fff46aee449b8b4bbb912be89a0997e1",
          "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/dfd192e7fff46aee449b8b4bbb912be89a0997e1",
          "comments_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/dfd192e7fff46aee449b8b4bbb912be89a0997e1/comments",
          "author": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "78de16eea0736c65640a4855ee94293ca399341c",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/78de16eea0736c65640a4855ee94293ca399341c",
              "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/78de16eea0736c65640a4855ee94293ca399341c"
            }
          ]
        },
        {
          "sha": "936df6d70b3a9ddc7c9f32946e86aa0f5f625d13",
          "node_id": "C_kwDOUr5zH9oAKDkzNmRmNmQ3MGIzYTlkZGM3YzlmMzI5NDZlODZhYTBmNWY2MjVkMTM",
          "commit": {
            "author": {
              "name": "NathanNeurotic (Ripto)",
              "email": "noahtkevans@gmail.com",
              "date": "2026-10-24T21:56:19Z"
            },
            "committer": {
              "name": "NathanNeurotic (Ripto)",
              "email": "noahtkevans@gmail.com",
              "date": "2026-10-24T21:56:19Z"
            },
            "message": "fix: scope strict card lookup typing without breaking non-card filesystems",
            "tree": {
              "sha": "b3f53a6ec02157a0320c53e159eb99ddbaf3934e",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/trees/b3f53a6ec02157a0320c53e159eb99ddbaf3934e"
            },
            "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/commits/936df6d70b3a9ddc7c9f32946e86aa0f5f625d13",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/936df6d70b3a9ddc7c9f32946e86aa0f5f625d13",
          "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/936df6d70b3a9ddc7c9f32946e86aa0f5f625d13",
          "comments_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/936df6d70b3a9ddc7c9f32946e86aa0f5f625d13/comments",
          "author": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "dfd192e7fff46aee449b8b4bbb912be89a0997e1",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/dfd192e7fff46aee449b8b4bbb912be89a0997e1",
              "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/dfd192e7fff46aee449b8b4bbb912be89a0997e1"
            }
          ]
        },
        {
          "sha": "df55ed4974435f7f7f9a2f0482a23fc6a43b937c",
          "node_id": "C_kwDOUr5zH9oAKGRmNTVlZDQ5NzQ0MzVmN2Y3ZjlhMmYwNDgyYTIzZmM2YTQzYjkzN2M",
          "commit": {
            "author": {
              "name": "NathanNeurotic (Ripto)",
              "email": "109461996+NathanNeurotic@users.noreply.github.com",
              "date": "2026-10-25T16:09:38Z"
            },
            "committer": {
              "name": "NathanNeurotic (Ripto)",
              "email": "109461996+NathanNeurotic@users.noreply.github.com",
              "date": "2026-10-25T16:09:38Z"
            },
            "message": "Fix FatFs copy status domain and confirm card destination entries\n\nRestore stat-only FatFs error normalization lost in baseline recovery. Confirm card lookup success through parent entries and exercise raw status failures before writes.\n\nCo-authored-by: OpenAI ChatGPT <noreply@openai.com>",
            "tree": {
              "sha": "af7c929ad9cf45ab1132c69a477c30c49edf79d2",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/trees/af7c929ad9cf45ab1132c69a477c30c49edf79d2"
            },
            "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/commits/df55ed4974435f7f7f9a2f0482a23fc6a43b937c",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/df55ed4974435f7f7f9a2f0482a23fc6a43b937c",
          "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/df55ed4974435f7f7f9a2f0482a23fc6a43b937c",
          "comments_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/df55ed4974435f7f7f9a2f0482a23fc6a43b937c/comments",
          "author": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "936df6d70b3a9ddc7c9f32946e86aa0f5f625d13",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/936df6d70b3a9ddc7c9f32946e86aa0f5f625d13",
              "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/936df6d70b3a9ddc7c9f32946e86aa0f5f625d13"
            }
          ]
        },
        {
          "sha": "dfb62bf1a3d3493e0db1f60afa17c3a542ab4ecc",
          "node_id": "C_kwDOUr5zH9oAKGRmYjYyYmYxYTNkMzQ5M2UwZGIxZjYwYWZhMTdjM2E1NDJhYjRlY2M",
          "commit": {
            "author": {
              "name": "NathanNeurotic (Ripto)",
              "email": "109461996+NathanNeurotic@users.noreply.github.com",
              "date": "2026-10-25T19:22:33Z"
            },
            "committer": {
              "name": "NathanNeurotic (Ripto)",
              "email": "109461996+NathanNeurotic@users.noreply.github.com",
              "date": "2026-10-25T19:22:33Z"
            },
            "message": "Restore upstream R3Z direct file overwrite behavior\n\nRemove destination stat and enumeration preflight, staging, checksums, backup/rename publication and their status/flag workarounds. Keep dashboard presentation and existing device/source integration. Replace transaction tests with direct-copy compatibility coverage.\n\nCo-authored-by: OpenAI ChatGPT <noreply@openai.com>",
            "tree": {
              "sha": "d66777c6f8ef92c0018df72834763dade6ee8336",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/trees/d66777c6f8ef92c0018df72834763dade6ee8336"
            },
            "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/commits/dfb62bf1a3d3493e0db1f60afa17c3a542ab4ecc",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/dfb62bf1a3d3493e0db1f60afa17c3a542ab4ecc",
          "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/dfb62bf1a3d3493e0db1f60afa17c3a542ab4ecc",
          "comments_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/dfb62bf1a3d3493e0db1f60afa17c3a542ab4ecc/comments",
          "author": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "df55ed4974435f7f7f9a2f0482a23fc6a43b937c",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/df55ed4974435f7f7f9a2f0482a23fc6a43b937c",
              "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/df55ed4974435f7f7f9a2f0482a23fc6a43b937c"
            }
          ]
        },
        {
          "sha": "3b43215652cf97db23ed2cfd46015984b958e560",
          "node_id": "C_kwDOUr5zH9oAKDNiNDMyMTU2NTJjZjk3ZGIyM2VkMmNmZDQ2MDE1OTg0Yjk1OGU1NjA",
          "commit": {
            "author": {
              "name": "NathanNeurotic (Ripto)",
              "email": "109461996+NathanNeurotic@users.noreply.github.com",
              "date": "2026-10-25T20:03:44Z"
            },
            "committer": {
              "name": "NathanNeurotic (Ripto)",
              "email": "109461996+NathanNeurotic@users.noreply.github.com",
              "date": "2026-10-25T20:03:44Z"
            },
            "message": "Finish restoring R3Z ordinary file transfer behavior\n\nUse upstream single-call read/write dispatch, original buffer sizing and close-error output retention. Keep dashboard presentation, source checks, device support and dedicated backup/recovery features. Verify cut/move source deletion follows successful copy only.\n\nCo-authored-by: OpenAI ChatGPT <noreply@openai.com>",
            "tree": {
              "sha": "606d82566111d4f2aeeec8cc3f4dbd38a98f794a",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/trees/606d82566111d4f2aeeec8cc3f4dbd38a98f794a"
            },
            "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/commits/3b43215652cf97db23ed2cfd46015984b958e560",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/3b43215652cf97db23ed2cfd46015984b958e560",
          "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/3b43215652cf97db23ed2cfd46015984b958e560",
          "comments_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/3b43215652cf97db23ed2cfd46015984b958e560/comments",
          "author": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "dfb62bf1a3d3493e0db1f60afa17c3a542ab4ecc",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/dfb62bf1a3d3493e0db1f60afa17c3a542ab4ecc",
              "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/dfb62bf1a3d3493e0db1f60afa17c3a542ab4ecc"
            }
          ]
        },
        {
          "sha": "9ae82190c3e50896cbd53270ca917137e0d48408",
          "node_id": "C_kwDOUr5zH9oAKDlhZTgyMTkwYzNlNTA4OTZjYmQ1MzI3MGNhOTE3MTM3ZTBkNDg0MDg",
          "commit": {
            "author": {
              "name": "NathanNeurotic (Ripto)",
              "email": "109461996+NathanNeurotic@users.noreply.github.com",
              "date": "2026-10-25T20:13:06Z"
            },
            "committer": {
              "name": "NathanNeurotic (Ripto)",
              "email": "109461996+NathanNeurotic@users.noreply.github.com",
              "date": "2026-10-25T20:13:06Z"
            },
            "message": "Resolve final Beta 1 review findings\n\nCorrect release wording, localize section keys and reject unrelated parent-checkout build identities. Add focused regression coverage and brace the MMCE pathname guards.\n\nCo-authored-by: OpenAI ChatGPT <noreply@openai.com>",
            "tree": {
              "sha": "0bb5c16861a4703a351210d37cc8197d4673d722",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/trees/0bb5c16861a4703a351210d37cc8197d4673d722"
            },
            "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/commits/9ae82190c3e50896cbd53270ca917137e0d48408",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/9ae82190c3e50896cbd53270ca917137e0d48408",
          "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/9ae82190c3e50896cbd53270ca917137e0d48408",
          "comments_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/9ae82190c3e50896cbd53270ca917137e0d48408/comments",
          "author": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "3b43215652cf97db23ed2cfd46015984b958e560",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/3b43215652cf97db23ed2cfd46015984b958e560",
              "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/3b43215652cf97db23ed2cfd46015984b958e560"
            }
          ]
        },
        {
          "sha": "b815d10da29274200572e61467d72a718004a708",
          "node_id": "C_kwDOUr5zH9oAKGI4MTVkMTBkYTI5Mjc0MjAwNTcyZTYxNDY3ZDcyYTcxODAwNGE3MDg",
          "commit": {
            "author": {
              "name": "NathanNeurotic (Ripto)",
              "email": "noahtkevans@gmail.com",
              "date": "2026-10-25T20:15:28Z"
            },
            "committer": {
              "name": "GitHub",
              "email": "noreply@github.com",
              "date": "2026-10-25T20:15:28Z"
            },
            "message": "Merge pull request #206: restore R3Z file handling and dashboard feedback\n\nCo-authored-by: OpenAI ChatGPT <noreply@openai.com>",
            "tree": {
              "sha": "0bb5c16861a4703a351210d37cc8197d4673d722",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/trees/0bb5c16861a4703a351210d37cc8197d4673d722"
            },
            "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/git/commits/b815d10da29274200572e61467d72a718004a708",
            "comment_count": 0,
            "verification": {
              "verified": true,
              "reason": "valid",
              "signature": "-----BEGIN PGP SIGNATURE-----\n\nwsFcBAABCAAQBQJq3mNgCRC1aQ7uu5UhlAAAY2IQAKBAtRq6YJ/N+Eq+wUuQzkTG\nx70v64/JI39K+zG+QLd7o/ghnqknqc7yoH5JZnA39wYQlgLXGT197+TqrPegrJTi\nF7UPBxWfsV2GtksUkmDGjh4lqbKCkSkepgJ9GVH91u2BrFzR3fhPTPTc1kEk88UL\nt123STWJ2Wa8SXXcpOweVdWwDGLNK4PKIQBImSShZaLbpy+v1SQ8O/AZl7AqSyyI\n6bsE2nd3HZPqXmUAOdhCq3VxDtrAwERM6vy3gPA2u4+8RbHqVfNUQxYzvpkWnSeV\nENjZpshY2TBwCVGaP3HMqLD/3g9QrMilWv7L+VOf2/E7pWUynwoU2BK8zZM6BlAr\n1ISoGkvitAMHRyybcIapiAkTyLJ+Vh+RqeZQia1l882sZgDCwvW3DU8KacBhlSt4\nmWPR53KBz1rOroFNzMZ45b8ZWFhtahBmlUyNtEVXdXAAFm+ylH85OWQqpHe9yCbK\njveoTj5uPmBHWUBbpoNMHyItrFT+t/dOeJURpHdC3W67xywJ5jAB9CyXGCRwSiGl\nUb846mLR2mc7m5HCw+LXxDy4p4AfnoW4SAwGt7cG0hQczu8PQWY5bUnkbpqlTjw6\nve0bBUMcl5SHge5EOL6izzpaWhPCleDJ1OrAHwwElQapoFqAbmpQiRgGOHoUAa0V\nvC6whq+oLu5ZohBmmWIq\n=C1Nd\n-----END PGP SIGNATURE-----\n",
              "payload": "tree 0bb5c16861a4703a351210d37cc8197d4673d722\nparent 3e03e640a1434a6d23dd1b80498fa0fbb7c88672\nparent 9ae82190c3e50896cbd53270ca917137e0d48408\nauthor NathanNeurotic (Ripto) <noahtkevans@gmail.com> 1792959328 -0700\ncommitter GitHub <noreply@github.com> 1792959328 -0700\n\nMerge pull request #206: restore R3Z file handling and dashboard feedback\n\nCo-authored-by: OpenAI ChatGPT <noreply@openai.com>",
              "verified_at": "2026-10-25T20:15:28Z"
            }
          },
          "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/b815d10da29274200572e61467d72a718004a708",
          "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/b815d10da29274200572e61467d72a718004a708",
          "comments_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/b815d10da29274200572e61467d72a718004a708/comments",
          "author": {
            "login": "NathanNeurotic",
            "id": 109461996,
            "node_id": "U_kgDOBoZB7A",
            "avatar_url": "https://avatars.githubusercontent.com/u/109461996?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/NathanNeurotic",
            "html_url": "https://github.com/NathanNeurotic",
            "followers_url": "https://api.github.com/users/NathanNeurotic/followers",
            "following_url": "https://api.github.com/users/NathanNeurotic/following{/other_user}",
            "gists_url": "https://api.github.com/users/NathanNeurotic/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/NathanNeurotic/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/NathanNeurotic/subscriptions",
            "organizations_url": "https://api.github.com/users/NathanNeurotic/orgs",
            "repos_url": "https://api.github.com/users/NathanNeurotic/repos",
            "events_url": "https://api.github.com/users/NathanNeurotic/events{/privacy}",
            "received_events_url": "https://api.github.com/users/NathanNeurotic/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "web-flow",
            "id": 19864447,
            "node_id": "MDQ6VXNlcjE5ODY0NDQ3",
            "avatar_url": "https://avatars.githubusercontent.com/u/19864447?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/web-flow",
            "html_url": "https://github.com/web-flow",
            "followers_url": "https://api.github.com/users/web-flow/followers",
            "following_url": "https://api.github.com/users/web-flow/following{/other_user}",
            "gists_url": "https://api.github.com/users/web-flow/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/web-flow/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/web-flow/subscriptions",
            "organizations_url": "https://api.github.com/users/web-flow/orgs",
            "repos_url": "https://api.github.com/users/web-flow/repos",
            "events_url": "https://api.github.com/users/web-flow/events{/privacy}",
            "received_events_url": "https://api.github.com/users/web-flow/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "3e03e640a1434a6d23dd1b80498fa0fbb7c88672",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/3e03e640a1434a6d23dd1b80498fa0fbb7c88672",
              "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/3e03e640a1434a6d23dd1b80498fa0fbb7c88672"
            },
            {
              "sha": "9ae82190c3e50896cbd53270ca917137e0d48408",
              "url": "https://api.github.com/repos/NathanNeurotic/Launchstation/commits/9ae82190c3e50896cbd53270ca917137e0d48408",
              "html_url": "https://github.com/NathanNeurotic/Launchstation/commit/9ae82190c3e50896cbd53270ca917137e0d48408"
            }
          ]
        }
      ],
      "files": [
        {
          "sha": "d2ca6743f030fbf491a2397c579af51974a45b75",
          "filename": ".github/workflows/compile.yml",
          "status": "modified",
          "additions": 6,
          "deletions": 1,
          "changes": 7,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/.github%2Fworkflows%2Fcompile.yml",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/.github%2Fworkflows%2Fcompile.yml",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/.github%2Fworkflows%2Fcompile.yml?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -32,13 +32,17 @@ jobs:\n           python3 tests/test_pccf.py\n           python3 tests/test_pccf_owner.py\n           python3 tests/test_pccf_fs.py\n-          python3 tests/test_paste_replace.py\n           python3 tests/test_copy_short_io.py\n           python3 tests/test_copy_pipeline.py\n           python3 tests/test_udpfs_directory_errors.py\n           python3 tests/test_copy_test_pack.py\n           python3 tests/test_transfer_stack_preflight.py\n           python3 tests/test_transfer_progress.py\n+          python3 tests/test_file_operation_overlay.py\n+          python3 tests/test_neutrino_menu_audio.py\n+          python3 tests/test_carousel_culling.py\n+          python3 tests/test_dashboard_languages.py\n+          python3 tests/test_build_identity.py\n           python3 tests/test_clock_save_action.py\n           python3 tests/test_storage_probe_frames.py\n           python3 tests/test_mmce_bus_restore.py\n@@ -204,6 +208,7 @@ jobs:\n \n       - name: Build Launchstation with ps2build\n         run: |\n+          python3 scripts/gen_githash.py --require-git\n           ps2build build\n \n       - name: Build optional DS34 controller variant"
        },
        {
          "sha": "66cb65474ebb22b930ce39c7c56ba0165cc22432",
          "filename": "CHANGELOG.md",
          "status": "modified",
          "additions": 6,
          "deletions": 10,
          "changes": 16,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/CHANGELOG.md",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/CHANGELOG.md",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/CHANGELOG.md?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -2,16 +2,12 @@\n \n ## Rolling\n \n-- **Infinite list navigation wrap-around:** dashboard lists now cycle at boundaries (up from first selects last, down from last selects first), including File Browser views, app/library lists, Files/Devices pickers, Tools, Settings and HDD Manager.\n-- **Safer file replacement:** an existing destination is retained until its replacement has been written and checked. Failed publication rolls back; recovery files survive interrupted operations.\n-- **Copy failure details:** compact stage/error, device, byte-count and build information make failed transfers diagnosable. A failed source-folder listing is no longer reported as an empty successful copy.\n-- **Cross-device copying:** positive short reads and writes complete the remaining bytes. Slot-2 card preparation happens before opening source files when leaving MX4SIO.\n-- **Clearer file operations:** the dashboard shows the filename, remaining megabytes and a progress bar while copying, with progress updates during deletion and verification.\n-- **Release-page clarity:** the public release page now includes a concise Rolling changelog summary and links directly to full release notes and changelog history.\n-- **Consistent storage names:** Home and the application library use MASS labels for mass-storage applications.\n-- **Software clock:** setting a time now offers to save it immediately and waits for verified storage completion. Save current clock retains later runtime. The clock resumes the saved checkpoint after restart; elapsed time while unplugged is unavailable.\n-- **Storage loading:** returned mount waits redraw more frequently while retaining the existing device discovery windows. MMCE returns each probed port's bus state to the normal card/controller drivers.\n-- **Power-off:** setup starts only the power-off service, without loading unused DEV9 drivers, and detects failed initialization.\n+- **Dashboard languages:** navigation, Settings labels, common actions and transfer progress use the nine built-in language tables. External LNG overrides also apply to these labels. User filenames, paths and application metadata keep their original text; some extended help remains in English.\n+- **File-copy compatibility:** restore upstream wLaunchELF_R3Z direct overwrite behavior. Remove the custom destination-stat, staging, verification, backup and rename transaction so ordinary paste does not depend on those additional operations.\n+- **Clearer file operations:** copy/delete popups give filenames their own full-width row, show remaining MB or completed selected items, and finish with a full progress bar. Paste failures include a readable reason, stage and numeric error.\n+- **Navigation and rendering:** wrapped list selections stay visible in the new viewport. Carousel rendering omits fully offscreen cards while retaining visible geometry and order. Physical-console acceptance remains unverified.\n+- **Neutrino options:** menu audio stays active until launch is committed, with ordinary ELF muting and failure recovery preserved.\n+- **Retained improvements:** concise release notes, dashboard free-space display, MASS display names, verified software-clock saves, lazy storage loading and power-off service initialization remain available.\n \n ## Earlier changes\n "
        },
        {
          "sha": "fc6d9000eab7d913634f30b4e870454ce67ffb9f",
          "filename": "Lang/DASHBOARD.inc",
          "status": "added",
          "additions": 657,
          "deletions": 0,
          "changes": 657,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/Lang%2FDASHBOARD.inc",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/Lang%2FDASHBOARD.inc",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/Lang%2FDASHBOARD.inc?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -0,0 +1,657 @@\n+/* Sorted source-label to language-ID map; checked by test_dashboard_languages.py. */\n+    {\"  (resumed)\", LANG_PC_Recv_Resumed},\n+    {\" + Details\", LANG__plus_Details},\n+    {\"%s canceled on mc%d:.\", LANG_Exploit_Mode_canceled},\n+    {\"%s failed on mc%d:.\\nError %d\", LANG_Exploit_Mode_failed},\n+    {\"%s payload\", LANG_Exploit_Custom_ELF_Label},\n+    {\"'Default' Timeout\", LANG_Default_Timeout},\n+    {\"- OSDMenu\", LANG_Exploit_Info_Line_9},\n+    {\"- Playstation 2 Basic Bootloader Extended (PS2BBLE)\", LANG_Exploit_Info_Line_8},\n+    {\"- any program that initializes PS2/PSX on startup.\", LANG_Exploit_Info_Line_10},\n+    {\"ADVANCED SETTINGS\", LANG_Advanced_Settings},\n+    {\"About Launchstation\", LANG_Dashboard_About_Launchstation},\n+    {\"About wLE\", LANG_About_uLE},\n+    {\"Accent\", LANG_Dashboard_Accent},\n+    {\"Add\", LANG_Add},\n+    {\"After IGR, open\", LANG_Dashboard_After_IGR_open},\n+    {\"All\", LANG_Dashboard_All},\n+    {\"Allow writing over the network\", LANG_Dashboard_Allow_writing_over_the_network},\n+    {\"App artwork background\", LANG_Dashboard_App_artwork_background},\n+    {\"Appearance\", LANG_Dashboard_Appearance},\n+    {\"Application artwork\", LANG_Dashboard_Application_artwork},\n+    {\"Application folders...\", LANG_Dashboard_Application_folders},\n+    {\"Application library\", LANG_Dashboard_Application_library},\n+    {\"Application options...\", LANG_Dashboard_Application_options},\n+    {\"Application view\", LANG_Dashboard_Application_view},\n+    {\"Applications\", LANG_Dashboard_Applications},\n+    {\"Apply GUI Skin\", LANG_Apply_GUI_Skin},\n+    {\"Apply New Skin\", LANG_Apply_New_Skin},\n+    {\"Artwork background\", LANG_Dashboard_Artwork_background},\n+    {\"Artwork dimming\", LANG_Dashboard_Artwork_dimming},\n+    {\"Auto-start MMCE SD card\", LANG_Dashboard_Auto_start_MMCE_SD_card},\n+    {\"Auto-start MX4SIO\", LANG_Dashboard_Auto_start_MX4SIO},\n+    {\"Auto-start USB storage\", LANG_Dashboard_Auto_start_USB_storage},\n+    {\"Auto-start exFAT HDD\", LANG_Dashboard_Auto_start_exFAT_HDD},\n+    {\"Auto-start iLink / FireWire\", LANG_Dashboard_Auto_start_iLink_FireWire},\n+    {\"Auto-start internal HDD\", LANG_Dashboard_Auto_start_internal_HDD},\n+    {\"Auto-start network (UDPFS)\", LANG_Dashboard_Auto_start_network_UDPFS},\n+    {\"Automatic start & boot\", LANG_Dashboard_Automatic_start_boot},\n+    {\"Automatic start timeout\", LANG_Dashboard_Automatic_start_timeout},\n+    {\"Automatic sync\", LANG_Dashboard_Automatic_sync},\n+    {\"Average Speed\", LANG_Average_Speed},\n+    {\"BOOKMARKS (PATHPAD)\", LANG_Dashboard_BOOKMARKS_PATHPAD},\n+    {\"BROWSE\", LANG_Dashboard_BROWSE},\n+    {\"Back\", LANG_Back},\n+    {\"Back to Browser\", LANG_Back_to_Browser},\n+    {\"BackSpace\", LANG_BackSpace},\n+    {\"Backgr\", LANG_Backgr},\n+    {\"Background\", LANG_Dashboard_Background},\n+    {\"Background motion\", LANG_Dashboard_Background_motion},\n+    {\"Background music\", LANG_Dashboard_Background_music},\n+    {\"Backup folder\", LANG_Dashboard_Backup_folder},\n+    {\"Backup format\", LANG_Dashboard_Backup_format},\n+    {\"Backups per card\", LANG_Dashboard_Backups_per_card},\n+    {\"Bogus\", LANG_Bogus},\n+    {\"Bold\", LANG_Dashboard_Bold},\n+    {\"Borders\", LANG_Dashboard_Borders},\n+    {\"Brightness\", LANG_Brightness},\n+    {\"Browse\", LANG_Browse},\n+    {\"Build information\", LANG_Dashboard_Build_information},\n+    {\"BuildInfo\", LANG_Build_Info},\n+    {\"Button Settings\", LANG_Button_Settings},\n+    {\"CANCEL\", LANG_CANCEL},\n+    {\"CAPS\", LANG_CAPS},\n+    {\"CNF Path\", LANG_CNF_Path},\n+    {\"CNF Path override\", LANG_CNF_Path_override},\n+    {\"CONNECTED\", LANG_CONNECTED},\n+    {\"COPY\", LANG_COPY},\n+    {\"CRT scanlines\", LANG_Dashboard_CRT_scanlines},\n+    {\"CUT\", LANG_CUT},\n+    {\"Cancel\", LANG_Cancel},\n+    {\"Cancel and return to MC Exploit Installer\", LANG_Exploit_Cancel_return_installer},\n+    {\"Cancel receiving this game?\", LANG_PC_Recv_Cancel},\n+    {\"Carousel\", LANG_Dashboard_Carousel},\n+    {\"Caution: Creating protected partition.\\nThese cannot be deleted within Launchstation\\nContinue ?\", LANG_Protected_Partition_Create_Warning},\n+    {\"Caution: Exploit folder for other region consoles!\", LANG_Exploit_Folder_Other_Region_Warning},\n+    {\"Caution: Exploit folder for this console!\", LANG_Exploit_Folder_This_Console_Warning},\n+    {\"Caution: Exploit folder!\", LANG_Exploit_Folder_Warning},\n+    {\"Change\", LANG_Change},\n+    {\"Change view\", LANG_Dashboard_Change_view},\n+    {\"Check for updates\", LANG_Dashboard_Check_for_updates},\n+    {\"Checking Size...\", LANG_Checking_Size},\n+    {\"Checking...\", LANG_Dashboard_Checking},\n+    {\"Choose\", LANG_Choose},\n+    {\"Choose ELF payload\", LANG_Exploit_Custom_ELF_Title},\n+    {\"Choose other ELF\", LANG_Exploit_Payload_Custom_ELF},\n+    {\"Circle\", LANG_Circle},\n+    {\"Clear\", LANG_Clear},\n+    {\"Clear recent applications\", LANG_Dashboard_Clear_recent_applications},\n+    {\"Clock\", LANG_Dashboard_Clock},\n+    {\"Close\", LANG_Close},\n+    {\"Close KB\", LANG_Close_KB},\n+    {\"Close Without Saving ?\", LANG_Close_Without_Saving},\n+    {\"Color\", LANG_Color},\n+    {\"Colour\", LANG_Dashboard_Colour},\n+    {\"Command List\", LANG_Command_List},\n+    {\"Completed items: %d / %d\", LANG_Dashboard_Completed_items_d_d},\n+    {\"Config search order top to bottom!\", LANG_Config_Search_Order},\n+    {\"Configuration folder\", LANG_Dashboard_Configuration_folder},\n+    {\"Configure\", LANG_Configure},\n+    {\"Confirm button\", LANG_Dashboard_Confirm_button},\n+    {\"Connection to the PC dropped at %u MB: reconnecting (%d of %d)...\", LANG_PC_Recv_Reconnecting},\n+    {\"Console does not support signed exploits.\\nInstall OpenTuna instead.\", LANG_Exploit_Unsupported_signed_exploits},\n+    {\"Continue\", LANG_Dashboard_Continue},\n+    {\"Continue and overwrite\", LANG_Exploit_Continue_overwrite},\n+    {\"Continue transfer ?\", LANG_Continue_transfer},\n+    {\"Control icon backings\", LANG_Dashboard_Control_icon_backings},\n+    {\"Copied to the Clipboard\", LANG_Copied_to_the_Clipboard},\n+    {\"Copy\", LANG_Copy},\n+    {\"Corners\", LANG_Dashboard_Corners},\n+    {\"Cr\", LANG_Cr},\n+    {\"Cr/Lf\", LANG_CrLf},\n+    {\"Create\", LANG_Create},\n+    {\"Create New PSU if filename exists\", LANG_Create_New_PSU_if_filename_exists},\n+    {\"Create New Partition ?\", LANG_Create_New_Partition},\n+    {\"Create PSU\", LANG_Create_PSU},\n+    {\"Create PSU with Date\", LANG_Create_PSU_with_Date},\n+    {\"Create PSU with ID and Game Title\", LANG_Create_PSU_with_ID_and_Game_Title},\n+    {\"Create title.cfg\", LANG_title_cfg},\n+    {\"Created folder\", LANG_Created_folder},\n+    {\"Creating New Partition...\", LANG_Creating_New_Partition},\n+    {\"Cross\", LANG_Cross},\n+    {\"Cross model\", LANG_Exploit_Mode_Cross_model},\n+    {\"Cross model and region\", LANG_Exploit_Mode_Cross_model_region},\n+    {\"Cross region\", LANG_Exploit_Mode_Cross_region},\n+    {\"Current Speed\", LANG_Current_Speed},\n+    {\"Custom ELF KELF generation failed for %s:\\n%s\\nError %d\", LANG_Exploit_Custom_ELF_Sign_failed},\n+    {\"Custom ELF payload installed to mc%d:.\", LANG_Exploit_Complete_Custom_ELF},\n+    {\"Custom app icon image...\", LANG_Dashboard_Custom_app_icon_image},\n+    {\"Customize theme\", LANG_Dashboard_Customize_theme},\n+    {\"Cut\", LANG_Cut},\n+    {\"DEFAULT\", LANG_DEFAULT},\n+    {\"Dashboard labels, file operations and tools.\", LANG_Dashboard_Dashboard_labels_file_operations_and_tools},\n+    {\"Date\", LANG_Dashboard_Date},\n+    {\"Debug Info\", LANG_Debug_Info},\n+    {\"Default\", LANG_Dashboard_Default},\n+    {\"Default app icon\", LANG_Dashboard_Default_app_icon},\n+    {\"Delay\", LANG_Delay},\n+    {\"Delete\", LANG_Delete},\n+    {\"Delete Failed\", LANG_Delete_Failed},\n+    {\"Delete folder(s)\", LANG_Exploit_Delete_update_folders},\n+    {\"Deleting file\", LANG_Dashboard_Deleting_file},\n+    {\"Detecting Disc\", LANG_Detecting_Disc},\n+    {\"Devices\", LANG_Dashboard_Devices},\n+    {\"Disable Ember support\", LANG_Dashboard_Disable_Ember_support},\n+    {\"Disable Icons in File Browser\", LANG_Disable_Icons_in_File_Browser},\n+    {\"Disable Neutrino support\", LANG_Dashboard_Disable_Neutrino_support},\n+    {\"Disable POPStarter support\", LANG_Dashboard_Disable_POPStarter_support},\n+    {\"Disable RetroGem Game ID for Discs\", LANG_Disable_RetroGem_Game_ID_for_Discs},\n+    {\"Disc & PS1 loaders\", LANG_Dashboard_Disc_PS1_loaders},\n+    {\"Disc & image loaders\", LANG_Dashboard_Disc_image_loaders},\n+    {\"Disc Drive\", LANG_Dashboard_Disc_Drive},\n+    {\"Disc control\", LANG_Disc_control},\n+    {\"Do you wish to overwrite it\", LANG_Do_you_wish_to_overwrite_it},\n+    {\"Down\", LANG_Down},\n+    {\"ELFs\", LANG_ELFs},\n+    {\"END\", LANG_END},\n+    {\"ESR loader\", LANG_Dashboard_ESR_loader},\n+    {\"Edit\", LANG_Edit},\n+    {\"Edit Title\", LANG_Edit_Title},\n+    {\"Ember directory\", LANG_Dashboard_Ember_directory},\n+    {\"Enable Host write\", LANG_Enable_Host_write},\n+    {\"Enable Network write\", LANG_Enable_Network_write},\n+    {\"Enter\", LANG_Enter},\n+    {\"Enter File Name.\", LANG_Enter_File_Name},\n+    {\"Enter New Partition Name.\", LANG_Enter_New_Partition_Name},\n+    {\"Exception: DTL-H10000(S) (use ProtoPwn), DTL-H3000X, DTL-T1XXXX\", LANG_Exploit_Info_Line_3},\n+    {\"Existing system update folder(s) found on mc%d:\", LANG_Exploit_Update_folders_found},\n+    {\"Existing system update folder(s) found on mc%d:.\\nDelete selected update folder(s) first?\", LANG_Exploit_Delete_update_folders_prompt},\n+    {\"Exit\", LANG_Exit},\n+    {\"Exit To Jpg Browser\", LANG_Exit_To_Jpg_Browser},\n+    {\"Exit Without Saving ?\", LANG_Exit_Without_Saving},\n+    {\"Exiting Editor...\", LANG_Exiting_Editor},\n+    {\"Expand\", LANG_Expand},\n+    {\"Expand Current Partition ?\", LANG_Expand_Current_Partition},\n+    {\"Expanding Current Partition...\", LANG_Expanding_Current_Partition},\n+    {\"Export theme to folder...\", LANG_Dashboard_Export_theme_to_folder},\n+    {\"Extract PSU\", LANG_Extract_PSU},\n+    {\"FORMATED\", LANG_FORMATED},\n+    {\"FREE\", LANG_FREE},\n+    {\"Fade\", LANG_Fade},\n+    {\"Failed\", LANG_Failed},\n+    {\"Failed Creating File\", LANG_Failed_Creating_File},\n+    {\"Failed Creating New Partition\", LANG_Failed_Creating_New_Partition},\n+    {\"Failed Expanding Current Partition\", LANG_Failed_Expanding_Current_Partition},\n+    {\"Failed Opening File\", LANG_Failed_Opening_File},\n+    {\"Failed Removing Current Partition\", LANG_Failed_Removing_Current_Partition},\n+    {\"Failed Renaming Game\", LANG_Failed_Renaming_Game},\n+    {\"Failed Renaming Partition\", LANG_Failed_Renaming_Partition},\n+    {\"Failed Saving File\", LANG_Failed_Saving_File},\n+    {\"Failed To Load\", LANG_Failed_To_Load},\n+    {\"Failed To Save\", LANG_Failed_To_Save},\n+    {\"Failed to save signed payload:\\n%s\\nError %d\", LANG_Exploit_Custom_ELF_Save_failed},\n+    {\"Failed writing\", LANG_Failed_writing},\n+    {\"Favorite\", LANG_Dashboard_Favorite},\n+    {\"Favorites\", LANG_Dashboard_Favorites},\n+    {\"File %s Closed\", LANG_File_Closed},\n+    {\"File Browser\", LANG_Dashboard_File_Browser},\n+    {\"File Created\", LANG_File_Created},\n+    {\"File Not Yet Saved\", LANG_File_Not_Yet_Saved},\n+    {\"File Not Yet Saved Closed\", LANG_File_Not_Yet_Saved_Closed},\n+    {\"File Opened\", LANG_File_Opened},\n+    {\"File Saved\", LANG_File_Saved},\n+    {\"FileBrowser\", LANG_FileBrowser},\n+    {\"Filename\", LANG_Filename},\n+    {\"Files\", LANG_Dashboard_Files},\n+    {\"Folders\", LANG_Folders},\n+    {\"Font\", LANG_Dashboard_Font},\n+    {\"Font File\", LANG_Font_File},\n+    {\"Font viewer\", LANG_Dashboard_Font_viewer},\n+    {\"Format\", LANG_Format},\n+    {\"Format entire HDD (destroys all partitions) ?\", LANG_Format_HDD},\n+    {\"Formating HDD...\", LANG_Formating_HDD},\n+    {\"Found\", LANG_Found},\n+    {\"Frames\", LANG_Frames},\n+    {\"Free Window\", LANG_Free_Window},\n+    {\"FullScreen Mode\", LANG_FullScreen_Mode},\n+    {\"GAME INFORMATION\", LANG_GAME_INFORMATION},\n+    {\"GB\", LANG_GB},\n+    {\"GUI\", LANG_GUI},\n+    {\"Game ID launch signal for apps\", LANG_Dashboard_Game_ID_launch_signal_for_apps},\n+    {\"Game ID launch signal for discs\", LANG_Dashboard_Game_ID_launch_signal_for_discs},\n+    {\"Game ID output\", LANG_Dashboard_Game_ID_output},\n+    {\"Game Renamed\", LANG_Game_Renamed},\n+    {\"Game Title\", LANG_Game_Title},\n+    {\"Games from the PC: udpfs server not found\", LANG_PC_Recv_No_Server},\n+    {\"Gateway\", LANG_Gateway},\n+    {\"Get Size\", LANG_Get_Size},\n+    {\"Glow\", LANG_Dashboard_Glow},\n+    {\"Google account\", LANG_Dashboard_Google_account},\n+    {\"Graph\", LANG_Graph},\n+    {\"Grid\", LANG_Dashboard_Grid},\n+    {\"HDD FREE\", LANG_HDD_FREE},\n+    {\"HDD Format Failed\", LANG_HDD_Format_Failed},\n+    {\"HDD Formated\", LANG_HDD_Formated},\n+    {\"HDD Information Read\", LANG_HDD_Information_Read},\n+    {\"HDD Information Read (truncated)\", LANG_HDD_Information_Read_Overflow},\n+    {\"HDD Manager\", LANG_Dashboard_HDD_Manager},\n+    {\"HDD SIZE\", LANG_HDD_SIZE},\n+    {\"HDD STATUS\", LANG_HDD_STATUS},\n+    {\"HDD USED\", LANG_HDD_USED},\n+    {\"HDL SIZE\", LANG_HDL_SIZE},\n+    {\"HOME\", LANG_HOME},\n+    {\"Halt\", LANG_Halt},\n+    {\"Halted\", LANG_Halted},\n+    {\"HddManager\", LANG_HddManager},\n+    {\"Header Source\", LANG_Header_Source},\n+    {\"Hide empty memory card slots\", LANG_Dashboard_Hide_empty_memory_card_slots},\n+    {\"Hide full ELF paths\", LANG_Hide_full_ELF_paths},\n+    {\"Home\", LANG_Dashboard_Home},\n+    {\"Home & applications\", LANG_Dashboard_Home_applications},\n+    {\"Home quick-launch shortcuts\", LANG_Dashboard_Home_quick_launch_shortcuts},\n+    {\"Hours\", LANG_Dashboard_Hours},\n+    {\"IGR application\", LANG_Dashboard_IGR_application},\n+    {\"IGR helper\", LANG_Dashboard_IGR_helper},\n+    {\"INSERT\", LANG_INSERT},\n+    {\"IP Address\", LANG_IP_Address},\n+    {\"IP address\", LANG_Dashboard_IP_address},\n+    {\"Icon Title\", LANG_Icon_Title},\n+    {\"Icon colour\", LANG_Dashboard_Icon_colour},\n+    {\"IconText\", LANG_IconText},\n+    {\"Icons\", LANG_Dashboard_Icons},\n+    {\"Inaccessible\", LANG_Inaccessible},\n+    {\"Info\", LANG_Exploit_Mode_Info},\n+    {\"Info not loaded\", LANG_Info_not_loaded},\n+    {\"Init Delay\", LANG_Init_Delay},\n+    {\"Initial Delay\", LANG_Initial_Delay},\n+    {\"Inject\", LANG_Header_Inject_Mode},\n+    {\"Inject Header\", LANG_Inject_Header},\n+    {\"Input\", LANG_Dashboard_Input},\n+    {\"Ins\", LANG_Ins},\n+    {\"Install %s to mc%d:?\\nRequired: %d KB\\nAvailable after cleanup: %d KB\\nExisting files may be overwritten.\", LANG_Exploit_Confirm_install},\n+    {\"Install LoadBOOTer\", LANG_Exploit_Payload_LoadBOOTer},\n+    {\"Install desired app at mc%d:/BOOT/BOOT.ELF\\nand any supporting files before\\nrebooting or powering off.\\nSuggested to install one of these:\\n- PS2BBL Extended\\n- OSDMenu\\n- Launchstation\", LANG_Exploit_Complete_boot_missing},\n+    {\"Installing: %s\", LANG_Exploit_Installing_file},\n+    {\"Interface colours\", LANG_Dashboard_Interface_colours},\n+    {\"Interface sound volume\", LANG_Dashboard_Interface_sound_volume},\n+    {\"Interface sounds\", LANG_Dashboard_Interface_sounds},\n+    {\"Interlace\", LANG_Interlace},\n+    {\"Internal HDD / ATA units\", LANG_Dashboard_Internal_HDD_ATA_units},\n+    {\"Invalid command from the PC (made for another version?)\", LANG_PC_Cmd_Bad},\n+    {\"Invalid job from the PC (made for another version?)\", LANG_PC_Recv_Bad_Job},\n+    {\"Invalid launch args file.\", LANG_Launch_Args_Invalid},\n+    {\"Invalid output path/name:\\n%s\", LANG_Exploit_Custom_ELF_Output_invalid},\n+    {\"Jpg Viewer\", LANG_Jpg_Viewer},\n+    {\"Kbytes\", LANG_Kbytes},\n+    {\"Keyboard map\", LANG_Dashboard_Keyboard_map},\n+    {\"L1/R1: Slideshow Timer\", LANG_L1R1_Slideshow_Timer},\n+    {\"L2/R2: Slideshow Transition\", LANG_L2R2_Slideshow_Transition},\n+    {\"LEFT\", LANG_LEFT},\n+    {\"LINE DOWN\", LANG_LINE_DOWN},\n+    {\"LINE UP\", LANG_LINE_UP},\n+    {\"Language\", LANG_Language},\n+    {\"Language File\", LANG_Language_File},\n+    {\"Language file\", LANG_Dashboard_Language_file},\n+    {\"Last folder\", LANG_Dashboard_Last_folder},\n+    {\"Launch arg line too long.\\nMaximum: %d chars\", LANG_Launch_Arg_Too_Long},\n+    {\"Launch args cached: %d\", LANG_Launch_Args_Loaded},\n+    {\"Launch args too large.\\nMaximum: %d bytes\", LANG_Launch_Args_Too_Large},\n+    {\"Launch keys\", LANG_Dashboard_Launch_keys},\n+    {\"LaunchELF with Args\", LANG_Launch_With_Args},\n+    {\"Launching\", LANG_Dashboard_Launching},\n+    {\"Left\", LANG_Left},\n+    {\"Left Joystick: Panorama\", LANG_Left_Joystick_Panorama},\n+    {\"Left/Right Pad: Prev/Next Picture\", LANG_LeftRight_Pad_PrevNext_Picture},\n+    {\"Legacy (unused)\", LANG_JpgViewer},\n+    {\"Legacy Ethernet drivers\", LANG_Dashboard_Legacy_Ethernet_drivers},\n+    {\"Lf\", LANG_Lf},\n+    {\"List\", LANG_List},\n+    {\"Load HDL Game Info\", LANG_Load_HDL_Game_Info},\n+    {\"Load Skin CNF\", LANG_Load_Skin_CNF},\n+    {\"Load theme from folder...\", LANG_Dashboard_Load_theme_from_folder},\n+    {\"LoadBOOTer MC Exploit Installer\", LANG_Exploit_Installer_Title},\n+    {\"LoadBOOTer memory card exploit\", LANG_Exploit_Info_Line_1},\n+    {\"Loaded Config\", LANG_Loaded_Config},\n+    {\"Loading %s drivers...\", LANG_Loading_Drivers},\n+    {\"Loading Flash Modules...\", LANG_Loading_Flash_Modules},\n+    {\"Loading HDD Modules...\", LANG_Loading_HDD_Modules},\n+    {\"Loading HDL Info Module...\", LANG_Loading_HDL_Info_Module},\n+    {\"Loading NetFS and FTP Server Modules...\", LANG_Loading_NetFS_and_FTP_Server_Modules},\n+    {\"Lock Main LaunchKey/PathPad Paths\", LANG_Lock_Main_LaunchKey_PathPad_Paths},\n+    {\"Lock shortcuts & bookmarks\", LANG_Dashboard_Lock_shortcuts_bookmarks},\n+    {\"MARK\", LANG_MARK},\n+    {\"MB\", LANG_MB},\n+    {\"MC Exploit Installer\", LANG_Exploit_Installer},\n+    {\"MENU\", LANG_MENU},\n+    {\"MISC\", LANG_MISC},\n+    {\"MMCE backup & sync\", LANG_Dashboard_MMCE_backup_sync},\n+    {\"MMCE backups\", LANG_Dashboard_MMCE_backups},\n+    {\"MagicGate Test\", LANG_Exploit_Mode_MagicGate_Test},\n+    {\"MagicGate Test failed on mc%d:.\\nError %d\\nInstall OSDMenu MBR on xfrom:/BIEXEC-SYSTEM/xosdmain.elf\\nand optionally OSDMenu (recommended)\\nhttps://github.com/pcm720/OSDMenu\", LANG_Exploit_MagicGate_failed_psx},\n+    {\"MagicGate Test passed on mc%d:.\", LANG_Exploit_MagicGate_passed},\n+    {\"MagicGate card bind failed for custom ELF.\\nSystem: %s\\nHeader: %s\\nError %d\", LANG_Exploit_Custom_ELF_Bind_failed},\n+    {\"MagicGate test failed on mc%d:\\nError %d\\nInstall OpenTuna for %s,\\nthen retry.\", LANG_Exploit_MagicGate_failed_opentuna},\n+    {\"Manual Edit\", LANG_Manual_Edit},\n+    {\"Mark\", LANG_Mark},\n+    {\"Mark Files Delete ?\", LANG_Mark_Files_Delete},\n+    {\"Matching and create missing partitions\", LANG_Header_Inject_Matching_Create_Missing},\n+    {\"Matching partitions\", LANG_Header_Inject_Matching_Partitions},\n+    {\"Memory Card\", LANG_Exploit_Memory_Card},\n+    {\"Memory Card 1 (mc0:)\", LANG_Exploit_Memory_Card_1},\n+    {\"Memory Card 2 (mc1:)\", LANG_Exploit_Memory_Card_2},\n+    {\"Memory card backup files\", LANG_Dashboard_Memory_card_backup_files},\n+    {\"Menu\", LANG_Menu},\n+    {\"Menu Frame\", LANG_Menu_Frame},\n+    {\"Menu Title\", LANG_Menu_Title},\n+    {\"Menu animation\", LANG_Dashboard_Menu_animation},\n+    {\"Mode\", LANG_Mode},\n+    {\"Model: %s\", LANG_Exploit_Model_Label},\n+    {\"Modify\", LANG_Modify},\n+    {\"Mount\", LANG_Mount},\n+    {\"Mounted image\", LANG_Dashboard_Mounted_image},\n+    {\"Moving file\", LANG_Dashboard_Moving_file},\n+    {\"Music volume\", LANG_Dashboard_Music_volume},\n+    {\"Muted text\", LANG_Dashboard_Muted_text},\n+    {\"NETWORK SETTINGS\", LANG_NETWORK_SETTINGS},\n+    {\"NO\", LANG_NO},\n+    {\"NONE\", LANG_NONE},\n+    {\"NULL\", LANG_NULL},\n+    {\"Name conflict for this folder\", LANG_Name_conflict_for_this_folder},\n+    {\"Net Config\", LANG_Net_Config},\n+    {\"Netmask\", LANG_Netmask},\n+    {\"Network\", LANG_Dashboard_Network},\n+    {\"Network (UDPFS)\", LANG_Dashboard_Network_UDPFS},\n+    {\"Network Host\", LANG_Dashboard_Network_Host},\n+    {\"Network Settings\", LANG_Network_Settings},\n+    {\"New\", LANG_New},\n+    {\"New -> Old\", LANG_New_to_Old},\n+    {\"New Dir\", LANG_New_Dir},\n+    {\"New Icon\", LANG_New_Icon},\n+    {\"New Partition Created\", LANG_New_Partition_Created},\n+    {\"NewDir Failed\", LANG_NewDir_Failed},\n+    {\"No Disc\", LANG_No_Disc},\n+    {\"No Sort\", LANG_No_Sort},\n+    {\"No launch args found.\", LANG_Launch_Args_Empty},\n+    {\"Normal\", LANG_Normal},\n+    {\"Not enough free space to install.\\nRequired: %d KB\\nAvailable after cleanup: %d KB\", LANG_Exploit_Not_enough_space},\n+    {\"Not enough free space to install.\\nRequired: %d KB\\nAvailable after cleanup: %d KB\\nIncludes %d KB BOOT.ELF reserve.\", LANG_Exploit_Not_enough_space_with_reserve},\n+    {\"Not enough free space to install.\\nRequired: %d KB\\nAvailable: %d KB\", LANG_Exploit_Not_enough_space_without_cleanup},\n+    {\"Not enough free space to install.\\nRequired: %d KB\\nAvailable: %d KB\\nIncludes %d KB BOOT.ELF reserve.\", LANG_Exploit_Not_enough_space_without_cleanup_with_reserve},\n+    {\"Not set\", LANG_Dashboard_Not_set},\n+    {\"OFF\", LANG_OFF},\n+    {\"OK\", LANG_OK},\n+    {\"ON\", LANG_ON},\n+    {\"OSDSYS\", LANG_OSDSYS},\n+    {\"OSDSYS program\", LANG_Dashboard_OSDSYS_program},\n+    {\"Off\", LANG_Off},\n+    {\"Old -> New\", LANG_Old_to_New},\n+    {\"On\", LANG_On},\n+    {\"On-screen keyboard layout\", LANG_Dashboard_On_screen_keyboard_layout},\n+    {\"Open\", LANG_Open},\n+    {\"Open KeyBoard\", LANG_Open_KeyBoard},\n+    {\"Optional .LNG overrides, including dashboard labels.\", LANG_Dashboard_Optional_LNG_overrides_including_dashboard_labels},\n+    {\"Options\", LANG_Dashboard_Options},\n+    {\"Overwrite existing file?\\n%s\", LANG_Exploit_Custom_ELF_Overwrite},\n+    {\"PAGE DOWN\", LANG_PAGE_DOWN},\n+    {\"PAGE UP\", LANG_PAGE_UP},\n+    {\"PASTE\", LANG_PASTE},\n+    {\"PFS FREE\", LANG_PFS_FREE},\n+    {\"PFS SIZE\", LANG_PFS_SIZE},\n+    {\"PFS USED\", LANG_PFS_USED},\n+    {\"PS2 HDD MANAGER\", LANG_PS2_HDD_MANAGER},\n+    {\"PS2 TEXT EDITOR\", LANG_PS2_TEXT_EDITOR},\n+    {\"PS2Browser\", LANG_PS2Browser},\n+    {\"PS2Disc\", LANG_PS2Disc},\n+    {\"PS2KEYS.dat does not contain valid %s keys.\\nError %d\", LANG_Exploit_KEYS_DAT_invalid},\n+    {\"PS2KEYS.dat was not found next to:\\n%s\", LANG_Exploit_KEYS_DAT_missing},\n+    {\"PS2Link host\", LANG_Dashboard_PS2Link_host},\n+    {\"PS2Net\", LANG_PS2Net},\n+    {\"PS2PowerOff\", LANG_PS2PowerOff},\n+    {\"PSU Action\", LANG_psuAction},\n+    {\"PSU: add date to names\", LANG_Dashboard_PSU_add_date_to_names},\n+    {\"PSU: add game ID and title to names\", LANG_Dashboard_PSU_add_game_ID_and_title_to_names},\n+    {\"PSU: never overwrite existing files\", LANG_Dashboard_PSU_never_overwrite_existing_files},\n+    {\"PSX DESR\", LANG_Exploit_Mode_PSX_DESR},\n+    {\"PUSH ANY BUTTON or D-PAD\", LANG_PUSH_ANY_BUTTON_or_DPAD},\n+    {\"Pad mapping\", LANG_Pad_mapping},\n+    {\"Page left/right\", LANG_Page_leftright},\n+    {\"Panel transparency\", LANG_Dashboard_Panel_transparency},\n+    {\"Panels\", LANG_Dashboard_Panels},\n+    {\"Partition Expanded\", LANG_Partition_Expanded},\n+    {\"Partition Removed\", LANG_Partition_Removed},\n+    {\"Partition Renamed\", LANG_Partition_Renamed},\n+    {\"Paste\", LANG_Paste},\n+    {\"Paste Failed\", LANG_Paste_Failed},\n+    {\"Paste failed\", LANG_Dashboard_Paste_failed},\n+    {\"Paste+Rename\", LANG_PasteRename},\n+    {\"Pasting file\", LANG_Pasting_file},\n+    {\"Pasting...\", LANG_Pasting},\n+    {\"Path\", LANG_Path},\n+    {\"PathPad\", LANG_PathPad},\n+    {\"Picture\", LANG_Picture},\n+    {\"Please wait\", LANG_Dashboard_Please_wait},\n+    {\"Popups Opaque\", LANG_Popups_Opaque},\n+    {\"Position\", LANG_Dashboard_Position},\n+    {\"Power off\", LANG_Dashboard_Power_off},\n+    {\"Powering Off Console...\", LANG_Powering_Off_Console},\n+    {\"Preparing MagicGate signer\", LANG_Exploit_Preparing_MagicGate_signer},\n+    {\"Preparing install\", LANG_Exploit_Preparing_install},\n+    {\"RET CR\", LANG_RET_CR},\n+    {\"RET CR/LF\", LANG_RET_CRLF},\n+    {\"RET LF\", LANG_RET_LF},\n+    {\"RETURN\", LANG_RETURN},\n+    {\"RIGHT\", LANG_RIGHT},\n+    {\"ROMVER %s\", LANG_Exploit_ROMVER_format},\n+    {\"ROMVER: %s\", LANG_Exploit_ROMVER_Label},\n+    {\"Raw SIZE\", LANG_Raw_SIZE},\n+    {\"Reading HDD Information...\", LANG_Reading_HDD_Information},\n+    {\"Reading SYSTEM.CNF...\", LANG_Reading_SYSTEMCNF},\n+    {\"Ready\", LANG_Dashboard_Ready},\n+    {\"Reboot IOP\", LANG_Reboot_IOP},\n+    {\"Reboot IOP when loading ELF\", LANG_Reboot_IOP_loading_ELF},\n+    {\"Rebooting IOP...\", LANG_Rebooting_IOP},\n+    {\"Received from the PC: %d of %d game(s)\", LANG_PC_Recv_Done},\n+    {\"Receiving %d of %d (%.3s): %.255s\", LANG_PC_Recv_Item},\n+    {\"Receiving %d/%d: %.28s  %u%%  %u/%u MB  %u KB/s  %u min left%s\", LANG_PC_Recv_Progress},\n+    {\"Receiving games from the PC: reserving space on the HDD...\", LANG_PC_Recv_Reserving},\n+    {\"Receiving games from the PC: switching back to FTP...\", LANG_PC_Recv_Back_To_FTP},\n+    {\"Receiving games from the PC: switching the network to udpfs (FTP stops)...\", LANG_PC_Recv_To_Udpfs},\n+    {\"Recent applications\", LANG_Dashboard_Recent_applications},\n+    {\"Recommended to use as BOOT.ELF:\", LANG_Exploit_Info_Line_7},\n+    {\"Recover saved card image...\", LANG_Dashboard_Recover_saved_card_image},\n+    {\"Refresh\", LANG_Dashboard_Refresh},\n+    {\"Refresh at startup\", LANG_Dashboard_Refresh_at_startup},\n+    {\"Refresh library\", LANG_Dashboard_Refresh_library},\n+    {\"Refresh library now\", LANG_Dashboard_Refresh_library_now},\n+    {\"Reload UDPFS\", LANG_Reload_UDPFS},\n+    {\"RemainSize\", LANG_Remain_Size},\n+    {\"RemainTime\", LANG_Remain_Time},\n+    {\"Remaining size: %.2f MB\", LANG_Dashboard_Remaining_size_2f_MB},\n+    {\"Remove\", LANG_Remove},\n+    {\"Remove Current Partition ?\", LANG_Remove_Current_Partition},\n+    {\"Removing Current Partition...\", LANG_Removing_Current_Partition},\n+    {\"Rename\", LANG_Rename},\n+    {\"Rename Current Game ?\", LANG_Rename_Current_Game},\n+    {\"Rename Current Partition ?\", LANG_Rename_Current_Partition},\n+    {\"Rename Failed\", LANG_Rename_Failed},\n+    {\"Renaming Game...\", LANG_Renaming_Game},\n+    {\"Renaming Partition...\", LANG_Renaming_Partition},\n+    {\"Reserved for system\", LANG_Reserved_for_system},\n+    {\"Reset\", LANG_Dashboard_Reset},\n+    {\"Reset IOP\", LANG_Reset_IOP},\n+    {\"Reset IOP before launch\", LANG_Dashboard_Reset_IOP_before_launch},\n+    {\"Reset appearance\", LANG_Dashboard_Reset_appearance},\n+    {\"Reset background\", LANG_Dashboard_Reset_background},\n+    {\"Reset clock\", LANG_Dashboard_Reset_clock},\n+    {\"Reset colours\", LANG_Dashboard_Reset_colours},\n+    {\"Reset entire theme\", LANG_Dashboard_Reset_entire_theme},\n+    {\"Reset icons\", LANG_Dashboard_Reset_icons},\n+    {\"Reset screen settings\", LANG_Dashboard_Reset_screen_settings},\n+    {\"Reset style\", LANG_Dashboard_Reset_style},\n+    {\"Reset text\", LANG_Dashboard_Reset_text},\n+    {\"Reset wallpaper\", LANG_Dashboard_Reset_wallpaper},\n+    {\"Restore default theme\", LANG_Dashboard_Restore_default_theme},\n+    {\"RetroGem Game ID\", LANG_RetroGem_Game_ID},\n+    {\"Return\", LANG_Return},\n+    {\"RevMark\", LANG_RevMark},\n+    {\"Right\", LANG_Right},\n+    {\"Right D-Pad to Edit\", LANG_Right_DPad_to_Edit},\n+    {\"Right Joystick Vertical: Zoom\", LANG_Right_Joystick_Vertical_Zoom},\n+    {\"SAS Timestamps\", LANG_SAS_Timestamps},\n+    {\"SIZE\", LANG_SIZE},\n+    {\"SKIN SETTINGS\", LANG_SKIN_SETTINGS},\n+    {\"SPACE\", LANG_SPACE},\n+    {\"SPECIAL\", LANG_SPECIAL},\n+    {\"STARTUP\", LANG_STARTUP},\n+    {\"STARTUP SETTINGS\", LANG_STARTUP_SETTINGS},\n+    {\"Save\", LANG_Save},\n+    {\"Save As\", LANG_Save_As},\n+    {\"Save Skin CNF\", LANG_Save_Skin_CNF},\n+    {\"Save launch & tool settings to...\", LANG_Dashboard_Save_launch_tool_settings_to},\n+    {\"Save signed payload\", LANG_Exploit_Custom_ELF_Save_As},\n+    {\"Save to\", LANG_Save_to},\n+    {\"Saved\", LANG_Saved},\n+    {\"Saved Config\", LANG_Saved_Config},\n+    {\"Saved Failed\", LANG_Saved_Failed},\n+    {\"Screen Settings\", LANG_Screen_Settings},\n+    {\"Screen X offset\", LANG_Screen_X_offset},\n+    {\"Screen Y offset\", LANG_Screen_Y_offset},\n+    {\"Screen calibration\", LANG_Dashboard_Screen_calibration},\n+    {\"Screen position X\", LANG_Dashboard_Screen_position_X},\n+    {\"Screen position Y\", LANG_Dashboard_Screen_position_Y},\n+    {\"Second colour\", LANG_Dashboard_Second_colour},\n+    {\"Secondary text\", LANG_Dashboard_Secondary_text},\n+    {\"Seconds\", LANG_Dashboard_Seconds},\n+    {\"Sel\", LANG_Sel},\n+    {\"Select\", LANG_Select},\n+    {\"Select A File For Editing\", LANG_Select_A_File_For_Editing},\n+    {\"Select New Partition Size In MB.\", LANG_Select_New_Partition_Size_In_MB},\n+    {\"Selected file is not an ELF:\\n%s\", LANG_Exploit_Custom_ELF_invalid},\n+    {\"Selected text\", LANG_Dashboard_Selected_text},\n+    {\"Selection\", LANG_Dashboard_Selection},\n+    {\"Selection style\", LANG_Dashboard_Selection_style},\n+    {\"Set\", LANG_Set},\n+    {\"Set *Tuna Date\", LANG_time_manip},\n+    {\"Set Custom Date\", LANG_Set_Custom_Date},\n+    {\"Set PS2 clock manually...\", LANG_Dashboard_Set_PS2_clock_manually},\n+    {\"Set software clock...\", LANG_Dashboard_Set_software_clock},\n+    {\"Settings\", LANG_Dashboard_Settings},\n+    {\"Show Content as\", LANG_Show_Content_as},\n+    {\"Show Menu\", LANG_Show_Menu},\n+    {\"Show applications\", LANG_Dashboard_Show_applications},\n+    {\"Show background\", LANG_Dashboard_Show_background},\n+    {\"Show launch titles\", LANG_Show_launch_titles},\n+    {\"Show unavailable devices\", LANG_Dashboard_Show_unavailable_devices},\n+    {\"Show welcome message again\", LANG_Dashboard_Show_welcome_message_again},\n+    {\"Show: %s\", LANG_Dashboard_Show_s},\n+    {\"ShowFont\", LANG_ShowFont},\n+    {\"Sign as DNASLOAD payload\", LANG_Exploit_Header_DNASLOAD},\n+    {\"Sign as FMCB payload\", LANG_Exploit_Header_FMCB},\n+    {\"Signed payload saved:\\n%s\", LANG_Exploit_Custom_ELF_Save_done},\n+    {\"Size\", LANG_Size},\n+    {\"Size test Failed\", LANG_Size_test_Failed},\n+    {\"Skin Path\", LANG_Skin_Path},\n+    {\"Skin Preview\", LANG_Skin_Preview},\n+    {\"Skin Settings\", LANG_Skin_Settings},\n+    {\"Skip\", LANG_Skip},\n+    {\"SlideShow\", LANG_SlideShow},\n+    {\"Slot empty\", LANG_Dashboard_Slot_empty},\n+    {\"Some extended help remains in English.\", LANG_Dashboard_Some_extended_help_remains_in_English},\n+    {\"Sort Content by\", LANG_Sort_Content_by},\n+    {\"Sound\", LANG_Dashboard_Sound},\n+    {\"Square\", LANG_Square},\n+    {\"Start\", LANG_Dashboard_Start},\n+    {\"Start devices automatically\", LANG_Dashboard_Start_devices_automatically},\n+    {\"Start screen\", LANG_Dashboard_Start_screen},\n+    {\"Start: Start/Stop Slideshow\", LANG_Start_StartStop_Slideshow},\n+    {\"Startup Settings\", LANG_Startup_Settings},\n+    {\"Startup delay\", LANG_Dashboard_Startup_delay},\n+    {\"Status Info\", LANG_Status_Info},\n+    {\"Stop Disc\", LANG_Stop_Disc},\n+    {\"Storage & devices\", LANG_Dashboard_Storage_devices},\n+    {\"Subtract\", LANG_Subtract},\n+    {\"Sync PS2 clock from Internet\", LANG_Dashboard_Sync_PS2_clock_from_Internet},\n+    {\"Sync all cards\", LANG_Dashboard_Sync_all_cards},\n+    {\"System\", LANG_Dashboard_System},\n+    {\"System font file\", LANG_Dashboard_System_font_file},\n+    {\"System information\", LANG_Dashboard_System_information},\n+    {\"TAB\", LANG_TAB},\n+    {\"TIMEOUT\", LANG_TIMEOUT},\n+    {\"TV mode\", LANG_TV_mode},\n+    {\"TYPE: CDGAME\", LANG_TYPE_CD_GAME},\n+    {\"TYPE: DVD GAME\", LANG_TYPE_DVD_GAME},\n+    {\"Text\", LANG_Text},\n+    {\"Text shadow\", LANG_Dashboard_Text_shadow},\n+    {\"Text size\", LANG_Dashboard_Text_size},\n+    {\"TextEditor\", LANG_TextEditor},\n+    {\"Theme files & resets\", LANG_Dashboard_Theme_files_resets},\n+    {\"Theme name...\", LANG_Dashboard_Theme_name},\n+    {\"Theme preset\", LANG_Dashboard_Theme_preset},\n+    {\"This console\", LANG_Exploit_Mode_This_console},\n+    {\"This exploit simply boots mc?:/BOOT/BOOT.ELF\", LANG_Exploit_Info_Line_4},\n+    {\"This file isn't an ELF\", LANG_This_file_isnt_an_ELF},\n+    {\"This partition only\", LANG_Header_Inject_This_Partition},\n+    {\"Thumb\", LANG_Thumb},\n+    {\"Tile style\", LANG_Dashboard_Tile_style},\n+    {\"Tiles & menus\", LANG_Dashboard_Tiles_menus},\n+    {\"Timer\", LANG_Timer},\n+    {\"Timestamp\", LANG_Timestamp},\n+    {\"TitleOFF\", LANG_TitleOFF},\n+    {\"TitleON\", LANG_TitleON},\n+    {\"Too many launch args.\\nMaximum: %d\", LANG_Launch_Args_Too_Many},\n+    {\"Tools\", LANG_Dashboard_Tools},\n+    {\"Transition\", LANG_Transition},\n+    {\"Triangle\", LANG_Triangle},\n+    {\"UDPFS Server not found!\", LANG_UDPFS_Server_not_found},\n+    {\"USB Keyboard IRX\", LANG_USB_Keyboard_IRX},\n+    {\"USB Keyboard Map\", LANG_USB_Keyboard_Map},\n+    {\"USB Keyboard Used\", LANG_USB_Keyboard_Used},\n+    {\"USB Mass IRX\", LANG_USB_Mass_IRX},\n+    {\"USB keyboard\", LANG_Dashboard_USB_keyboard},\n+    {\"USB keyboard driver\", LANG_Dashboard_USB_keyboard_driver},\n+    {\"USBD IRX\", LANG_USBD_IRX},\n+    {\"Unable to check free space on mc%d:.\\nError %d\", LANG_Exploit_Unable_to_check_space},\n+    {\"Unable to reload source device for:\\n%s\\nError %d\", LANG_Exploit_Custom_ELF_Source_unavailable},\n+    {\"Undefined (Not a gamesave)\", LANG_Undefined_Not_a_gamesave},\n+    {\"Unknown\", LANG_Unknown},\n+    {\"Unknown command from the PC: %.60s\", LANG_PC_Cmd_Unknown},\n+    {\"Unload HDL Game Info\", LANG_Unload_HDL_Game_Info},\n+    {\"Up\", LANG_Up},\n+    {\"Up/Down Pad: Rotate Picture\", LANG_UpDown_Pad_Rotate_Picture},\n+    {\"Updating timestamp: %s\", LANG_Updating_Timestamp},\n+    {\"Use\", LANG_Use},\n+    {\"Use Default Screen Settings\", LANG_Use_Default_Screen_Settings},\n+    {\"Use PS2 hardware clock\", LANG_Dashboard_Use_PS2_hardware_clock},\n+    {\"Using %s keys from: %s\", LANG_Exploit_Custom_ELF_Keyset},\n+    {\"Valid\", LANG_Valid},\n+    {\"Verifying file\", LANG_Dashboard_Verifying_file},\n+    {\"Video mode\", LANG_Dashboard_Video_mode},\n+    {\"View\", LANG_View},\n+    {\"View SYSTEM.CNF\", LANG_View_SYSTEM_CNF},\n+    {\"Virtual Keyboard Layout\", LANG_Virtual_Keyboard_Layout},\n+    {\"Wallpaper image...\", LANG_Dashboard_Wallpaper_image},\n+    {\"Warnings\", LANG_Dashboard_Warnings},\n+    {\"Window Not Yet Saved\", LANG_Window_Not_Yet_Saved},\n+    {\"Windows\", LANG_Windows},\n+    {\"With gamesave title\", LANG_With_gamesave_title},\n+    {\"Written Total\", LANG_Written_Total},\n+    {\"YES\", LANG_YES},\n+    {\"Zoom\", LANG_Zoom},\n+    {\"Zoom+Fade\", LANG_ZoomFade},\n+    {\"another exploit, thanks to pcm720.\", LANG_Exploit_Info_Line_6},\n+    {\"auto\", LANG_auto},\n+    {\"bytes\", LANG_bytes},\n+    {\"change timestamp of:\", LANG_change_timestamp_of},\n+    {\"deleting\", LANG_deleting},\n+    {\"directory already exists\", LANG_directory_already_exists},\n+    {\"file already exists\", LANG_file_alredy_exists},\n+    {\"free\", LANG_free},\n+    {\"is Not Found\", LANG_is_Not_Found},\n+    {\"mc%d: is not a formatted PS2 memory card.\\nError %d\", LANG_Exploit_Not_formatted_PS2_MC},\n+    {\"mc%d:/BOOT/BOOT.ELF exists.\\nMake sure BOOT.ELF is your desired app\\nwith supporting config files such as:\\n- PS2BBL Extended\\n- OSDMenu\\n- Launchstation\", LANG_Exploit_Complete_boot_exists},\n+    {\"mctype\", LANG_mctype},\n+    {\"pasting\", LANG_pasting},\n+    {\"size result\", LANG_size_result},\n+    {\"so that you are free to change without signing\", LANG_Exploit_Info_Line_5},\n+    {\"this console ROM version\", LANG_Exploit_this_console_ROM_version},\n+    {\"works for ROMVER 1.00-2.20\", LANG_Exploit_Info_Line_2},"
        },
        {
          "sha": "f843a42c71e4e823b23e3c0361e69048f0e20f27",
          "filename": "Lang/ENG.LNG",
          "status": "modified",
          "additions": 196,
          "deletions": 0,
          "changes": 196,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/Lang%2FENG.LNG",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/Lang%2FENG.LNG",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/Lang%2FENG.LNG?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -466,3 +466,199 @@ lang(464, PC_Cmd_Bad, \"Invalid command from the PC (made for another version?)\")\n lang(465, PC_Cmd_Unknown, \"Unknown command from the PC: %.60s\")\n lang(466, Old_to_New, \"Old -> New\")\n lang(467, New_to_Old, \"New -> Old\")\n+lang(468, Dashboard_Home, \"Home\")\n+lang(469, Dashboard_Applications, \"Applications\")\n+lang(470, Dashboard_Files, \"Files\")\n+lang(471, Dashboard_Tools, \"Tools\")\n+lang(472, Dashboard_Settings, \"Settings\")\n+lang(473, Dashboard_Favorites, \"Favorites\")\n+lang(474, Dashboard_Devices, \"Devices\")\n+lang(475, Dashboard_Options, \"Options\")\n+lang(476, Dashboard_Grid, \"Grid\")\n+lang(477, Dashboard_Carousel, \"Carousel\")\n+lang(478, Dashboard_All, \"All\")\n+lang(479, Dashboard_Default, \"Default\")\n+lang(480, Dashboard_Not_set, \"Not set\")\n+lang(481, Dashboard_Continue, \"Continue\")\n+lang(482, Dashboard_Reset, \"Reset\")\n+lang(483, Dashboard_Refresh, \"Refresh\")\n+lang(484, Dashboard_Appearance, \"Appearance\")\n+lang(485, Dashboard_Home_applications, \"Home & applications\")\n+lang(486, Dashboard_Sound, \"Sound\")\n+lang(487, Dashboard_Input, \"Input\")\n+lang(488, Dashboard_Launching, \"Launching\")\n+lang(489, Dashboard_Storage_devices, \"Storage & devices\")\n+lang(490, Dashboard_Network, \"Network\")\n+lang(491, Dashboard_System, \"System\")\n+lang(492, Dashboard_Theme_preset, \"Theme preset\")\n+lang(493, Dashboard_Customize_theme, \"Customize theme\")\n+lang(494, Dashboard_Screen_calibration, \"Screen calibration\")\n+lang(495, Dashboard_Background_motion, \"Background motion\")\n+lang(496, Dashboard_Menu_animation, \"Menu animation\")\n+lang(497, Dashboard_Background, \"Background\")\n+lang(498, Dashboard_Colour, \"Colour\")\n+lang(499, Dashboard_Second_colour, \"Second colour\")\n+lang(500, Dashboard_Accent, \"Accent\")\n+lang(501, Dashboard_Glow, \"Glow\")\n+lang(502, Dashboard_Borders, \"Borders\")\n+lang(503, Dashboard_Corners, \"Corners\")\n+lang(504, Dashboard_Panels, \"Panels\")\n+lang(505, Dashboard_Panel_transparency, \"Panel transparency\")\n+lang(506, Dashboard_Secondary_text, \"Secondary text\")\n+lang(507, Dashboard_Muted_text, \"Muted text\")\n+lang(508, Dashboard_Selected_text, \"Selected text\")\n+lang(509, Dashboard_Selection, \"Selection\")\n+lang(510, Dashboard_Selection_style, \"Selection style\")\n+lang(511, Dashboard_Font, \"Font\")\n+lang(512, Dashboard_Bold, \"Bold\")\n+lang(513, Dashboard_Text_shadow, \"Text shadow\")\n+lang(514, Dashboard_Text_size, \"Text size\")\n+lang(515, Dashboard_Icons, \"Icons\")\n+lang(516, Dashboard_Icon_colour, \"Icon colour\")\n+lang(517, Dashboard_Clock, \"Clock\")\n+lang(518, Dashboard_Date, \"Date\")\n+lang(519, Dashboard_Hours, \"Hours\")\n+lang(520, Dashboard_Seconds, \"Seconds\")\n+lang(521, Dashboard_Position, \"Position\")\n+lang(522, Dashboard_Warnings, \"Warnings\")\n+lang(523, Dashboard_Application_view, \"Application view\")\n+lang(524, Dashboard_Application_artwork, \"Application artwork\")\n+lang(525, Dashboard_Application_library, \"Application library\")\n+lang(526, Dashboard_Application_folders, \"Application folders...\")\n+lang(527, Dashboard_Recent_applications, \"Recent applications\")\n+lang(528, Dashboard_Clear_recent_applications, \"Clear recent applications\")\n+lang(529, Dashboard_Refresh_library, \"Refresh library\")\n+lang(530, Dashboard_Refresh_library_now, \"Refresh library now\")\n+lang(531, Dashboard_Refresh_at_startup, \"Refresh at startup\")\n+lang(532, Dashboard_Start_screen, \"Start screen\")\n+lang(533, Dashboard_Interface_sounds, \"Interface sounds\")\n+lang(534, Dashboard_Interface_sound_volume, \"Interface sound volume\")\n+lang(535, Dashboard_Background_music, \"Background music\")\n+lang(536, Dashboard_Music_volume, \"Music volume\")\n+lang(537, Dashboard_Confirm_button, \"Confirm button\")\n+lang(538, Dashboard_Keyboard_map, \"Keyboard map\")\n+lang(539, Dashboard_USB_keyboard, \"USB keyboard\")\n+lang(540, Dashboard_USB_keyboard_driver, \"USB keyboard driver\")\n+lang(541, Dashboard_On_screen_keyboard_layout, \"On-screen keyboard layout\")\n+lang(542, Dashboard_Launch_keys, \"Launch keys\")\n+lang(543, Dashboard_Automatic_start_boot, \"Automatic start & boot\")\n+lang(544, Dashboard_Automatic_start_timeout, \"Automatic start timeout\")\n+lang(545, Dashboard_Startup_delay, \"Startup delay\")\n+lang(546, Dashboard_Disc_image_loaders, \"Disc & image loaders\")\n+lang(547, Dashboard_Reset_IOP_before_launch, \"Reset IOP before launch\")\n+lang(548, Dashboard_Show_unavailable_devices, \"Show unavailable devices\")\n+lang(549, Dashboard_Start_devices_automatically, \"Start devices automatically\")\n+lang(550, Dashboard_Auto_start_USB_storage, \"Auto-start USB storage\")\n+lang(551, Dashboard_Auto_start_internal_HDD, \"Auto-start internal HDD\")\n+lang(552, Dashboard_Auto_start_exFAT_HDD, \"Auto-start exFAT HDD\")\n+lang(553, Dashboard_Auto_start_MMCE_SD_card, \"Auto-start MMCE SD card\")\n+lang(554, Dashboard_Auto_start_MX4SIO, \"Auto-start MX4SIO\")\n+lang(555, Dashboard_Auto_start_iLink_FireWire, \"Auto-start iLink / FireWire\")\n+lang(556, Dashboard_Auto_start_network_UDPFS, \"Auto-start network (UDPFS)\")\n+lang(557, Dashboard_Hide_empty_memory_card_slots, \"Hide empty memory card slots\")\n+lang(558, Dashboard_Allow_writing_over_the_network, \"Allow writing over the network\")\n+lang(559, Dashboard_IP_address, \"IP address\")\n+lang(560, Dashboard_Backup_folder, \"Backup folder\")\n+lang(561, Dashboard_Backup_format, \"Backup format\")\n+lang(562, Dashboard_Backups_per_card, \"Backups per card\")\n+lang(563, Dashboard_MMCE_backups, \"MMCE backups\")\n+lang(564, Dashboard_Memory_card_backup_files, \"Memory card backup files\")\n+lang(565, Dashboard_Automatic_sync, \"Automatic sync\")\n+lang(566, Dashboard_Sync_all_cards, \"Sync all cards\")\n+lang(567, Dashboard_Google_account, \"Google account\")\n+lang(568, Dashboard_Configuration_folder, \"Configuration folder\")\n+lang(569, Dashboard_Language_file, \"Language file\")\n+lang(570, Dashboard_Check_for_updates, \"Check for updates\")\n+lang(571, Dashboard_About_Launchstation, \"About Launchstation\")\n+lang(572, Dashboard_File_Browser, \"File Browser\")\n+lang(573, Dashboard_HDD_Manager, \"HDD Manager\")\n+lang(574, Dashboard_Network_Host, \"Network Host\")\n+lang(575, Dashboard_PS2Link_host, \"Files shared by a PS2Link PC over the network\")\n+lang(576, Dashboard_Disc_Drive, \"Disc Drive\")\n+lang(577, Dashboard_Network_UDPFS, \"Network (UDPFS)\")\n+lang(578, Dashboard_Change_view, \"Change view\")\n+lang(579, Dashboard_Application_options, \"Application options...\")\n+lang(580, Dashboard_Show_applications, \"Show applications\")\n+lang(581, Dashboard_Show_s, \"Show: %s\")\n+lang(582, Dashboard_Moving_file, \"Moving file\")\n+lang(583, Dashboard_Deleting_file, \"Deleting file\")\n+lang(584, Dashboard_Verifying_file, \"Verifying file\")\n+lang(585, Dashboard_Remaining_size_2f_MB, \"Remaining size: %.2f MB\")\n+lang(586, Dashboard_Completed_items_d_d, \"Completed items: %d / %d\")\n+lang(587, Dashboard_Paste_failed, \"Paste failed\")\n+lang(588, Dashboard_Please_wait, \"Please wait\")\n+lang(589, Dashboard_Power_off, \"Power off\")\n+lang(590, Dashboard_System_information, \"System information\")\n+lang(591, Dashboard_Build_information, \"Build information\")\n+lang(592, Dashboard_Font_viewer, \"Font viewer\")\n+lang(593, Dashboard_After_IGR_open, \"After IGR, open\")\n+lang(594, Dashboard_App_artwork_background, \"App artwork background\")\n+lang(595, Dashboard_Artwork_background, \"Artwork background\")\n+lang(596, Dashboard_Artwork_dimming, \"Artwork dimming\")\n+lang(597, Dashboard_CRT_scanlines, \"CRT scanlines\")\n+lang(598, Dashboard_Control_icon_backings, \"Control icon backings\")\n+lang(599, Dashboard_Custom_app_icon_image, \"Custom app icon image...\")\n+lang(600, Dashboard_Default_app_icon, \"Default app icon\")\n+lang(601, Dashboard_Disable_Ember_support, \"Disable Ember support\")\n+lang(602, Dashboard_Disable_Neutrino_support, \"Disable Neutrino support\")\n+lang(603, Dashboard_Disable_POPStarter_support, \"Disable POPStarter support\")\n+lang(604, Dashboard_ESR_loader, \"ESR loader\")\n+lang(605, Dashboard_Ember_directory, \"Ember directory\")\n+lang(606, Dashboard_Export_theme_to_folder, \"Export theme to folder...\")\n+lang(607, Dashboard_Load_theme_from_folder, \"Load theme from folder...\")\n+lang(608, Dashboard_Game_ID_launch_signal_for_apps, \"Game ID launch signal for apps\")\n+lang(609, Dashboard_Game_ID_launch_signal_for_discs, \"Game ID launch signal for discs\")\n+lang(610, Dashboard_Game_ID_output, \"Game ID output\")\n+lang(611, Dashboard_Home_quick_launch_shortcuts, \"Home quick-launch shortcuts\")\n+lang(612, Dashboard_IGR_application, \"IGR application\")\n+lang(613, Dashboard_IGR_helper, \"IGR helper\")\n+lang(614, Dashboard_Interface_colours, \"Interface colours\")\n+lang(615, Dashboard_Internal_HDD_ATA_units, \"Internal HDD / ATA units\")\n+lang(616, Dashboard_Legacy_Ethernet_drivers, \"Legacy Ethernet drivers\")\n+lang(617, Dashboard_Lock_shortcuts_bookmarks, \"Lock shortcuts & bookmarks\")\n+lang(618, Dashboard_OSDSYS_program, \"OSDSYS program\")\n+lang(619, Dashboard_PSU_add_date_to_names, \"PSU: add date to names\")\n+lang(620, Dashboard_PSU_add_game_ID_and_title_to_names, \"PSU: add game ID and title to names\")\n+lang(621, Dashboard_PSU_never_overwrite_existing_files, \"PSU: never overwrite existing files\")\n+lang(622, Dashboard_Recover_saved_card_image, \"Recover saved card image...\")\n+lang(623, Dashboard_Reset_appearance, \"Reset appearance\")\n+lang(624, Dashboard_Reset_background, \"Reset background\")\n+lang(625, Dashboard_Reset_clock, \"Reset clock\")\n+lang(626, Dashboard_Reset_colours, \"Reset colours\")\n+lang(627, Dashboard_Reset_entire_theme, \"Reset entire theme\")\n+lang(628, Dashboard_Reset_icons, \"Reset icons\")\n+lang(629, Dashboard_Reset_screen_settings, \"Reset screen settings\")\n+lang(630, Dashboard_Reset_style, \"Reset style\")\n+lang(631, Dashboard_Reset_text, \"Reset text\")\n+lang(632, Dashboard_Reset_wallpaper, \"Reset wallpaper\")\n+lang(633, Dashboard_Restore_default_theme, \"Restore default theme\")\n+lang(634, Dashboard_Save_launch_tool_settings_to, \"Save launch & tool settings to...\")\n+lang(635, Dashboard_Screen_position_X, \"Screen position X\")\n+lang(636, Dashboard_Screen_position_Y, \"Screen position Y\")\n+lang(637, Dashboard_Set_PS2_clock_manually, \"Set PS2 clock manually...\")\n+lang(638, Dashboard_Set_software_clock, \"Set software clock...\")\n+lang(639, Dashboard_Show_welcome_message_again, \"Show welcome message again\")\n+lang(640, Dashboard_Sync_PS2_clock_from_Internet, \"Sync PS2 clock from Internet\")\n+lang(641, Dashboard_System_font_file, \"System font file\")\n+lang(642, Dashboard_Theme_files_resets, \"Theme files & resets\")\n+lang(643, Dashboard_Theme_name, \"Theme name...\")\n+lang(644, Dashboard_Tile_style, \"Tile style\")\n+lang(645, Dashboard_Tiles_menus, \"Tiles & menus\")\n+lang(646, Dashboard_Use_PS2_hardware_clock, \"Use PS2 hardware clock\")\n+lang(647, Dashboard_Video_mode, \"Video mode\")\n+lang(648, Dashboard_Wallpaper_image, \"Wallpaper image...\")\n+lang(649, Dashboard_Start, \"Start\")\n+lang(650, Dashboard_Favorite, \"Favorite\")\n+lang(651, Dashboard_Show_background, \"Show background\")\n+lang(652, Dashboard_Ready, \"Ready\")\n+lang(653, Dashboard_Checking, \"Checking...\")\n+lang(654, Dashboard_Slot_empty, \"Slot empty\")\n+lang(655, Dashboard_Mounted_image, \"Mounted image\")\n+lang(656, Dashboard_Disc_PS1_loaders, \"Disc & PS1 loaders\")\n+lang(657, Dashboard_MMCE_backup_sync, \"MMCE backup & sync\")\n+lang(658, Dashboard_Dashboard_labels_file_operations_and_tools, \"Dashboard labels, file operations and tools.\")\n+lang(659, Dashboard_Optional_LNG_overrides_including_dashboard_labels, \"Optional .LNG overrides, including dashboard labels.\")\n+lang(660, Dashboard_Some_extended_help_remains_in_English, \"Some extended help remains in English.\")\n+lang(661, Dashboard_Last_folder, \"Last folder\")\n+lang(662, Dashboard_BROWSE, \"BROWSE\")\n+lang(663, Dashboard_BOOKMARKS_PATHPAD, \"BOOKMARKS (PATHPAD)\")"
        },
        {
          "sha": "b6a119de0617c01f3b923c88fd2e0c49c9b7b6af",
          "filename": "Lang/FRE.LNG",
          "status": "modified",
          "additions": 196,
          "deletions": 0,
          "changes": 196,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/Lang%2FFRE.LNG",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/Lang%2FFRE.LNG",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/Lang%2FFRE.LNG?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -466,3 +466,199 @@ lang(464, PC_Cmd_Bad, \"Invalid command from the PC (made for another version?)\")\n lang(465, PC_Cmd_Unknown, \"Unknown command from the PC: %.60s\")\n lang(466, Old_to_New, \"Ancien -> Recent\")\n lang(467, New_to_Old, \"Recent -> Ancien\")\n+lang(468, Dashboard_Home, \"Accueil\")\n+lang(469, Dashboard_Applications, \"Applications\")\n+lang(470, Dashboard_Files, \"Fichiers\")\n+lang(471, Dashboard_Tools, \"Outils\")\n+lang(472, Dashboard_Settings, \"Parametres\")\n+lang(473, Dashboard_Favorites, \"Favoris\")\n+lang(474, Dashboard_Devices, \"Peripheriques\")\n+lang(475, Dashboard_Options, \"Options\")\n+lang(476, Dashboard_Grid, \"Grille\")\n+lang(477, Dashboard_Carousel, \"Carrousel\")\n+lang(478, Dashboard_All, \"Tout\")\n+lang(479, Dashboard_Default, \"Par defaut\")\n+lang(480, Dashboard_Not_set, \"Non defini\")\n+lang(481, Dashboard_Continue, \"Continuer\")\n+lang(482, Dashboard_Reset, \"Reinitialiser\")\n+lang(483, Dashboard_Refresh, \"Actualiser\")\n+lang(484, Dashboard_Appearance, \"Apparence\")\n+lang(485, Dashboard_Home_applications, \"Accueil et applications\")\n+lang(486, Dashboard_Sound, \"Son\")\n+lang(487, Dashboard_Input, \"Commandes\")\n+lang(488, Dashboard_Launching, \"Lancement\")\n+lang(489, Dashboard_Storage_devices, \"Stockage et peripheriques\")\n+lang(490, Dashboard_Network, \"Reseau\")\n+lang(491, Dashboard_System, \"Systeme\")\n+lang(492, Dashboard_Theme_preset, \"Theme predefini\")\n+lang(493, Dashboard_Customize_theme, \"Personnaliser le theme\")\n+lang(494, Dashboard_Screen_calibration, \"Calibrage de l'ecran\")\n+lang(495, Dashboard_Background_motion, \"Mouvement du fond\")\n+lang(496, Dashboard_Menu_animation, \"Animation du menu\")\n+lang(497, Dashboard_Background, \"Fond\")\n+lang(498, Dashboard_Colour, \"Couleur\")\n+lang(499, Dashboard_Second_colour, \"Seconde couleur\")\n+lang(500, Dashboard_Accent, \"Accent\")\n+lang(501, Dashboard_Glow, \"Lueur\")\n+lang(502, Dashboard_Borders, \"Bordures\")\n+lang(503, Dashboard_Corners, \"Coins\")\n+lang(504, Dashboard_Panels, \"Panneaux\")\n+lang(505, Dashboard_Panel_transparency, \"Transparence des panneaux\")\n+lang(506, Dashboard_Secondary_text, \"Texte secondaire\")\n+lang(507, Dashboard_Muted_text, \"Texte attenue\")\n+lang(508, Dashboard_Selected_text, \"Texte selectionne\")\n+lang(509, Dashboard_Selection, \"Selection\")\n+lang(510, Dashboard_Selection_style, \"Style de selection\")\n+lang(511, Dashboard_Font, \"Police\")\n+lang(512, Dashboard_Bold, \"Gras\")\n+lang(513, Dashboard_Text_shadow, \"Ombre du texte\")\n+lang(514, Dashboard_Text_size, \"Taille du texte\")\n+lang(515, Dashboard_Icons, \"Icones\")\n+lang(516, Dashboard_Icon_colour, \"Couleur des icones\")\n+lang(517, Dashboard_Clock, \"Horloge\")\n+lang(518, Dashboard_Date, \"Date\")\n+lang(519, Dashboard_Hours, \"Heures\")\n+lang(520, Dashboard_Seconds, \"Secondes\")\n+lang(521, Dashboard_Position, \"Position\")\n+lang(522, Dashboard_Warnings, \"Avertissements\")\n+lang(523, Dashboard_Application_view, \"Vue des applications\")\n+lang(524, Dashboard_Application_artwork, \"Illustrations des applications\")\n+lang(525, Dashboard_Application_library, \"Bibliotheque d'applications\")\n+lang(526, Dashboard_Application_folders, \"Dossiers d'applications...\")\n+lang(527, Dashboard_Recent_applications, \"Applications recentes\")\n+lang(528, Dashboard_Clear_recent_applications, \"Effacer les applications recentes\")\n+lang(529, Dashboard_Refresh_library, \"Actualiser la bibliotheque\")\n+lang(530, Dashboard_Refresh_library_now, \"Actualiser maintenant\")\n+lang(531, Dashboard_Refresh_at_startup, \"Actualiser au demarrage\")\n+lang(532, Dashboard_Start_screen, \"Ecran de demarrage\")\n+lang(533, Dashboard_Interface_sounds, \"Sons de l'interface\")\n+lang(534, Dashboard_Interface_sound_volume, \"Volume de l'interface\")\n+lang(535, Dashboard_Background_music, \"Musique de fond\")\n+lang(536, Dashboard_Music_volume, \"Volume musical\")\n+lang(537, Dashboard_Confirm_button, \"Bouton de validation\")\n+lang(538, Dashboard_Keyboard_map, \"Disposition du clavier\")\n+lang(539, Dashboard_USB_keyboard, \"Clavier USB\")\n+lang(540, Dashboard_USB_keyboard_driver, \"Pilote du clavier USB\")\n+lang(541, Dashboard_On_screen_keyboard_layout, \"Clavier a l'ecran\")\n+lang(542, Dashboard_Launch_keys, \"Touches de lancement\")\n+lang(543, Dashboard_Automatic_start_boot, \"Demarrage automatique\")\n+lang(544, Dashboard_Automatic_start_timeout, \"Delai de demarrage automatique\")\n+lang(545, Dashboard_Startup_delay, \"Delai de demarrage\")\n+lang(546, Dashboard_Disc_image_loaders, \"Chargeurs de disques et images\")\n+lang(547, Dashboard_Reset_IOP_before_launch, \"Reinitialiser l'IOP avant lancement\")\n+lang(548, Dashboard_Show_unavailable_devices, \"Afficher les peripheriques indisponibles\")\n+lang(549, Dashboard_Start_devices_automatically, \"Demarrer les peripheriques automatiquement\")\n+lang(550, Dashboard_Auto_start_USB_storage, \"Demarrer le stockage USB\")\n+lang(551, Dashboard_Auto_start_internal_HDD, \"Demarrer le disque interne\")\n+lang(552, Dashboard_Auto_start_exFAT_HDD, \"Demarrer le HDD exFAT\")\n+lang(553, Dashboard_Auto_start_MMCE_SD_card, \"Demarrer la carte SD MMCE\")\n+lang(554, Dashboard_Auto_start_MX4SIO, \"Demarrer MX4SIO\")\n+lang(555, Dashboard_Auto_start_iLink_FireWire, \"Demarrer iLink / FireWire\")\n+lang(556, Dashboard_Auto_start_network_UDPFS, \"Demarrer le reseau (UDPFS)\")\n+lang(557, Dashboard_Hide_empty_memory_card_slots, \"Masquer les ports de carte vides\")\n+lang(558, Dashboard_Allow_writing_over_the_network, \"Autoriser l'ecriture reseau\")\n+lang(559, Dashboard_IP_address, \"Adresse IP\")\n+lang(560, Dashboard_Backup_folder, \"Dossier de sauvegarde\")\n+lang(561, Dashboard_Backup_format, \"Format de sauvegarde\")\n+lang(562, Dashboard_Backups_per_card, \"Sauvegardes par carte\")\n+lang(563, Dashboard_MMCE_backups, \"Sauvegardes MMCE\")\n+lang(564, Dashboard_Memory_card_backup_files, \"Sauvegardes de carte memoire\")\n+lang(565, Dashboard_Automatic_sync, \"Synchronisation automatique\")\n+lang(566, Dashboard_Sync_all_cards, \"Synchroniser toutes les cartes\")\n+lang(567, Dashboard_Google_account, \"Compte Google\")\n+lang(568, Dashboard_Configuration_folder, \"Dossier de configuration\")\n+lang(569, Dashboard_Language_file, \"Fichier de langue\")\n+lang(570, Dashboard_Check_for_updates, \"Rechercher des mises a jour\")\n+lang(571, Dashboard_About_Launchstation, \"A propos de Launchstation\")\n+lang(572, Dashboard_File_Browser, \"Explorateur de fichiers\")\n+lang(573, Dashboard_HDD_Manager, \"Gestionnaire HDD\")\n+lang(574, Dashboard_Network_Host, \"PC reseau (PS2Link)\")\n+lang(575, Dashboard_PS2Link_host, \"Fichiers partages par un PC PS2Link\")\n+lang(576, Dashboard_Disc_Drive, \"Lecteur de disque\")\n+lang(577, Dashboard_Network_UDPFS, \"Reseau (UDPFS)\")\n+lang(578, Dashboard_Change_view, \"Changer de vue\")\n+lang(579, Dashboard_Application_options, \"Options de l'application...\")\n+lang(580, Dashboard_Show_applications, \"Afficher les applications\")\n+lang(581, Dashboard_Show_s, \"Afficher : %s\")\n+lang(582, Dashboard_Moving_file, \"Deplacement du fichier\")\n+lang(583, Dashboard_Deleting_file, \"Suppression du fichier\")\n+lang(584, Dashboard_Verifying_file, \"Verification du fichier\")\n+lang(585, Dashboard_Remaining_size_2f_MB, \"Restant : %.2f MB\")\n+lang(586, Dashboard_Completed_items_d_d, \"Termines : %d / %d\")\n+lang(587, Dashboard_Paste_failed, \"Echec de copie\")\n+lang(588, Dashboard_Please_wait, \"Veuillez patienter\")\n+lang(589, Dashboard_Power_off, \"Eteindre\")\n+lang(590, Dashboard_System_information, \"Informations systeme\")\n+lang(591, Dashboard_Build_information, \"Informations de compilation\")\n+lang(592, Dashboard_Font_viewer, \"Visionneuse de police\")\n+lang(593, Dashboard_After_IGR_open, \"Apres IGR, ouvrir\")\n+lang(594, Dashboard_App_artwork_background, \"Fond d'illustration d'application\")\n+lang(595, Dashboard_Artwork_background, \"Fond d'illustration\")\n+lang(596, Dashboard_Artwork_dimming, \"Assombrissement de l'image\")\n+lang(597, Dashboard_CRT_scanlines, \"Lignes CRT\")\n+lang(598, Dashboard_Control_icon_backings, \"Fond des icones de commande\")\n+lang(599, Dashboard_Custom_app_icon_image, \"Icone d'application personnalisee...\")\n+lang(600, Dashboard_Default_app_icon, \"Icone d'application par defaut\")\n+lang(601, Dashboard_Disable_Ember_support, \"Desactiver Ember\")\n+lang(602, Dashboard_Disable_Neutrino_support, \"Desactiver Neutrino\")\n+lang(603, Dashboard_Disable_POPStarter_support, \"Desactiver POPStarter\")\n+lang(604, Dashboard_ESR_loader, \"Chargeur ESR\")\n+lang(605, Dashboard_Ember_directory, \"Dossier Ember\")\n+lang(606, Dashboard_Export_theme_to_folder, \"Exporter le theme vers un dossier...\")\n+lang(607, Dashboard_Load_theme_from_folder, \"Charger le theme depuis un dossier...\")\n+lang(608, Dashboard_Game_ID_launch_signal_for_apps, \"Signal Game ID pour applications\")\n+lang(609, Dashboard_Game_ID_launch_signal_for_discs, \"Signal Game ID pour disques\")\n+lang(610, Dashboard_Game_ID_output, \"Sortie Game ID\")\n+lang(611, Dashboard_Home_quick_launch_shortcuts, \"Raccourcis de l'accueil\")\n+lang(612, Dashboard_IGR_application, \"Application IGR\")\n+lang(613, Dashboard_IGR_helper, \"Assistant IGR\")\n+lang(614, Dashboard_Interface_colours, \"Couleurs de l'interface\")\n+lang(615, Dashboard_Internal_HDD_ATA_units, \"Disques internes HDD / ATA\")\n+lang(616, Dashboard_Legacy_Ethernet_drivers, \"Pilotes Ethernet classiques\")\n+lang(617, Dashboard_Lock_shortcuts_bookmarks, \"Verrouiller raccourcis et favoris\")\n+lang(618, Dashboard_OSDSYS_program, \"Programme OSDSYS\")\n+lang(619, Dashboard_PSU_add_date_to_names, \"PSU : ajouter la date aux noms\")\n+lang(620, Dashboard_PSU_add_game_ID_and_title_to_names, \"PSU : ajouter Game ID et titre\")\n+lang(621, Dashboard_PSU_never_overwrite_existing_files, \"PSU : ne jamais ecraser les fichiers\")\n+lang(622, Dashboard_Recover_saved_card_image, \"Recuperer l'image de carte...\")\n+lang(623, Dashboard_Reset_appearance, \"Reinitialiser l'apparence\")\n+lang(624, Dashboard_Reset_background, \"Reinitialiser le fond\")\n+lang(625, Dashboard_Reset_clock, \"Reinitialiser l'horloge\")\n+lang(626, Dashboard_Reset_colours, \"Reinitialiser les couleurs\")\n+lang(627, Dashboard_Reset_entire_theme, \"Reinitialiser tout le theme\")\n+lang(628, Dashboard_Reset_icons, \"Reinitialiser les icones\")\n+lang(629, Dashboard_Reset_screen_settings, \"Reinitialiser l'ecran\")\n+lang(630, Dashboard_Reset_style, \"Reinitialiser le style\")\n+lang(631, Dashboard_Reset_text, \"Reinitialiser le texte\")\n+lang(632, Dashboard_Reset_wallpaper, \"Reinitialiser l'image de fond\")\n+lang(633, Dashboard_Restore_default_theme, \"Restaurer le theme par defaut\")\n+lang(634, Dashboard_Save_launch_tool_settings_to, \"Enregistrer lancement et outils vers...\")\n+lang(635, Dashboard_Screen_position_X, \"Position horizontale\")\n+lang(636, Dashboard_Screen_position_Y, \"Position verticale\")\n+lang(637, Dashboard_Set_PS2_clock_manually, \"Regler l'horloge PS2 manuellement...\")\n+lang(638, Dashboard_Set_software_clock, \"Regler l'horloge logicielle...\")\n+lang(639, Dashboard_Show_welcome_message_again, \"Reafficher le message d'accueil\")\n+lang(640, Dashboard_Sync_PS2_clock_from_Internet, \"Synchroniser l'horloge PS2 par Internet\")\n+lang(641, Dashboard_System_font_file, \"Fichier de police systeme\")\n+lang(642, Dashboard_Theme_files_resets, \"Fichiers et reinitialisation du theme\")\n+lang(643, Dashboard_Theme_name, \"Nom du theme...\")\n+lang(644, Dashboard_Tile_style, \"Style des tuiles\")\n+lang(645, Dashboard_Tiles_menus, \"Tuiles et menus\")\n+lang(646, Dashboard_Use_PS2_hardware_clock, \"Utiliser l'horloge materielle PS2\")\n+lang(647, Dashboard_Video_mode, \"Mode video\")\n+lang(648, Dashboard_Wallpaper_image, \"Image de fond...\")\n+lang(649, Dashboard_Start, \"Demarrer\")\n+lang(650, Dashboard_Favorite, \"Favori\")\n+lang(651, Dashboard_Show_background, \"Afficher le fond\")\n+lang(652, Dashboard_Ready, \"Pret\")\n+lang(653, Dashboard_Checking, \"Verification...\")\n+lang(654, Dashboard_Slot_empty, \"Port vide\")\n+lang(655, Dashboard_Mounted_image, \"Image montee\")\n+lang(656, Dashboard_Disc_PS1_loaders, \"Chargeurs de disques et PS1\")\n+lang(657, Dashboard_MMCE_backup_sync, \"Sauvegarde et synchronisation MMCE\")\n+lang(658, Dashboard_Dashboard_labels_file_operations_and_tools, \"Menus, operations sur fichiers et outils.\")\n+lang(659, Dashboard_Optional_LNG_overrides_including_dashboard_labels, \"Fichier .LNG facultatif, menus inclus.\")\n+lang(660, Dashboard_Some_extended_help_remains_in_English, \"Certaines aides detaillees restent en anglais.\")\n+lang(661, Dashboard_Last_folder, \"Dernier dossier\")\n+lang(662, Dashboard_BROWSE, \"PARCOURIR\")\n+lang(663, Dashboard_BOOKMARKS_PATHPAD, \"FAVORIS (PATHPAD)\")"
        },
        {
          "sha": "43648b5499822eb91ecbd323c22143cbab6e8384",
          "filename": "Lang/GER.LNG",
          "status": "modified",
          "additions": 196,
          "deletions": 0,
          "changes": 196,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/Lang%2FGER.LNG",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/Lang%2FGER.LNG",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/Lang%2FGER.LNG?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -466,3 +466,199 @@ lang(464, PC_Cmd_Bad, \"Invalid command from the PC (made for another version?)\")\n lang(465, PC_Cmd_Unknown, \"Unknown command from the PC: %.60s\")\n lang(466, Old_to_New, \"Alt -> Neu\")\n lang(467, New_to_Old, \"Neu -> Alt\")\n+lang(468, Dashboard_Home, \"Start\")\n+lang(469, Dashboard_Applications, \"Anwendungen\")\n+lang(470, Dashboard_Files, \"Dateien\")\n+lang(471, Dashboard_Tools, \"Werkzeuge\")\n+lang(472, Dashboard_Settings, \"Einstellungen\")\n+lang(473, Dashboard_Favorites, \"Favoriten\")\n+lang(474, Dashboard_Devices, \"Gerate\")\n+lang(475, Dashboard_Options, \"Optionen\")\n+lang(476, Dashboard_Grid, \"Raster\")\n+lang(477, Dashboard_Carousel, \"Karussell\")\n+lang(478, Dashboard_All, \"Alle\")\n+lang(479, Dashboard_Default, \"Standard\")\n+lang(480, Dashboard_Not_set, \"Nicht festgelegt\")\n+lang(481, Dashboard_Continue, \"Weiter\")\n+lang(482, Dashboard_Reset, \"Zurucksetzen\")\n+lang(483, Dashboard_Refresh, \"Aktualisieren\")\n+lang(484, Dashboard_Appearance, \"Darstellung\")\n+lang(485, Dashboard_Home_applications, \"Start und Anwendungen\")\n+lang(486, Dashboard_Sound, \"Ton\")\n+lang(487, Dashboard_Input, \"Eingabe\")\n+lang(488, Dashboard_Launching, \"Programmstart\")\n+lang(489, Dashboard_Storage_devices, \"Speicher und Gerate\")\n+lang(490, Dashboard_Network, \"Netzwerk\")\n+lang(491, Dashboard_System, \"System\")\n+lang(492, Dashboard_Theme_preset, \"Themenvorlage\")\n+lang(493, Dashboard_Customize_theme, \"Thema anpassen\")\n+lang(494, Dashboard_Screen_calibration, \"Bildschirmkalibrierung\")\n+lang(495, Dashboard_Background_motion, \"Hintergrundbewegung\")\n+lang(496, Dashboard_Menu_animation, \"Menuanimation\")\n+lang(497, Dashboard_Background, \"Hintergrund\")\n+lang(498, Dashboard_Colour, \"Farbe\")\n+lang(499, Dashboard_Second_colour, \"Zweite Farbe\")\n+lang(500, Dashboard_Accent, \"Akzent\")\n+lang(501, Dashboard_Glow, \"Leuchten\")\n+lang(502, Dashboard_Borders, \"Rahmen\")\n+lang(503, Dashboard_Corners, \"Ecken\")\n+lang(504, Dashboard_Panels, \"Flachen\")\n+lang(505, Dashboard_Panel_transparency, \"Flachentransparenz\")\n+lang(506, Dashboard_Secondary_text, \"Sekundarer Text\")\n+lang(507, Dashboard_Muted_text, \"Gedampfter Text\")\n+lang(508, Dashboard_Selected_text, \"Ausgewahlter Text\")\n+lang(509, Dashboard_Selection, \"Auswahl\")\n+lang(510, Dashboard_Selection_style, \"Auswahlstil\")\n+lang(511, Dashboard_Font, \"Schriftart\")\n+lang(512, Dashboard_Bold, \"Fett\")\n+lang(513, Dashboard_Text_shadow, \"Textschatten\")\n+lang(514, Dashboard_Text_size, \"Textgrosse\")\n+lang(515, Dashboard_Icons, \"Symbole\")\n+lang(516, Dashboard_Icon_colour, \"Symbolfarbe\")\n+lang(517, Dashboard_Clock, \"Uhr\")\n+lang(518, Dashboard_Date, \"Datum\")\n+lang(519, Dashboard_Hours, \"Stunden\")\n+lang(520, Dashboard_Seconds, \"Sekunden\")\n+lang(521, Dashboard_Position, \"Position\")\n+lang(522, Dashboard_Warnings, \"Warnungen\")\n+lang(523, Dashboard_Application_view, \"Anwendungsansicht\")\n+lang(524, Dashboard_Application_artwork, \"Anwendungsbilder\")\n+lang(525, Dashboard_Application_library, \"Anwendungsbibliothek\")\n+lang(526, Dashboard_Application_folders, \"Anwendungsordner...\")\n+lang(527, Dashboard_Recent_applications, \"Letzte Anwendungen\")\n+lang(528, Dashboard_Clear_recent_applications, \"Letzte Anwendungen loschen\")\n+lang(529, Dashboard_Refresh_library, \"Bibliothek aktualisieren\")\n+lang(530, Dashboard_Refresh_library_now, \"Bibliothek jetzt aktualisieren\")\n+lang(531, Dashboard_Refresh_at_startup, \"Beim Start aktualisieren\")\n+lang(532, Dashboard_Start_screen, \"Startbildschirm\")\n+lang(533, Dashboard_Interface_sounds, \"Oberflachentone\")\n+lang(534, Dashboard_Interface_sound_volume, \"Oberflachenlautstarke\")\n+lang(535, Dashboard_Background_music, \"Hintergrundmusik\")\n+lang(536, Dashboard_Music_volume, \"Musiklautstarke\")\n+lang(537, Dashboard_Confirm_button, \"Bestatigungstaste\")\n+lang(538, Dashboard_Keyboard_map, \"Tastaturbelegung\")\n+lang(539, Dashboard_USB_keyboard, \"USB-Tastatur\")\n+lang(540, Dashboard_USB_keyboard_driver, \"USB-Tastaturtreiber\")\n+lang(541, Dashboard_On_screen_keyboard_layout, \"Bildschirmtastatur\")\n+lang(542, Dashboard_Launch_keys, \"Starttasten\")\n+lang(543, Dashboard_Automatic_start_boot, \"Automatischer Start\")\n+lang(544, Dashboard_Automatic_start_timeout, \"Autostart-Wartezeit\")\n+lang(545, Dashboard_Startup_delay, \"Startverzogerung\")\n+lang(546, Dashboard_Disc_image_loaders, \"Disc- und Abbildlader\")\n+lang(547, Dashboard_Reset_IOP_before_launch, \"IOP vor Start zurucksetzen\")\n+lang(548, Dashboard_Show_unavailable_devices, \"Nicht verfugbare Gerate anzeigen\")\n+lang(549, Dashboard_Start_devices_automatically, \"Gerate automatisch starten\")\n+lang(550, Dashboard_Auto_start_USB_storage, \"USB-Speicher automatisch starten\")\n+lang(551, Dashboard_Auto_start_internal_HDD, \"Interne HDD automatisch starten\")\n+lang(552, Dashboard_Auto_start_exFAT_HDD, \"exFAT-HDD automatisch starten\")\n+lang(553, Dashboard_Auto_start_MMCE_SD_card, \"MMCE-SD-Karte automatisch starten\")\n+lang(554, Dashboard_Auto_start_MX4SIO, \"MX4SIO automatisch starten\")\n+lang(555, Dashboard_Auto_start_iLink_FireWire, \"iLink / FireWire automatisch starten\")\n+lang(556, Dashboard_Auto_start_network_UDPFS, \"Netzwerk automatisch starten (UDPFS)\")\n+lang(557, Dashboard_Hide_empty_memory_card_slots, \"Leere Kartensteckplatze ausblenden\")\n+lang(558, Dashboard_Allow_writing_over_the_network, \"Schreiben uber Netzwerk erlauben\")\n+lang(559, Dashboard_IP_address, \"IP-Adresse\")\n+lang(560, Dashboard_Backup_folder, \"Sicherungsordner\")\n+lang(561, Dashboard_Backup_format, \"Sicherungsformat\")\n+lang(562, Dashboard_Backups_per_card, \"Sicherungen je Karte\")\n+lang(563, Dashboard_MMCE_backups, \"MMCE-Sicherungen\")\n+lang(564, Dashboard_Memory_card_backup_files, \"Speicherkartensicherungen\")\n+lang(565, Dashboard_Automatic_sync, \"Automatische Synchronisierung\")\n+lang(566, Dashboard_Sync_all_cards, \"Alle Karten synchronisieren\")\n+lang(567, Dashboard_Google_account, \"Google-Konto\")\n+lang(568, Dashboard_Configuration_folder, \"Konfigurationsordner\")\n+lang(569, Dashboard_Language_file, \"Sprachdatei\")\n+lang(570, Dashboard_Check_for_updates, \"Nach Aktualisierungen suchen\")\n+lang(571, Dashboard_About_Launchstation, \"Uber Launchstation\")\n+lang(572, Dashboard_File_Browser, \"Dateibrowser\")\n+lang(573, Dashboard_HDD_Manager, \"HDD-Verwaltung\")\n+lang(574, Dashboard_Network_Host, \"Netzwerk-PC (PS2Link)\")\n+lang(575, Dashboard_PS2Link_host, \"Dateien eines PS2Link-PCs\")\n+lang(576, Dashboard_Disc_Drive, \"Disc-Laufwerk\")\n+lang(577, Dashboard_Network_UDPFS, \"Netzwerk (UDPFS)\")\n+lang(578, Dashboard_Change_view, \"Ansicht andern\")\n+lang(579, Dashboard_Application_options, \"Anwendungsoptionen...\")\n+lang(580, Dashboard_Show_applications, \"Anwendungen anzeigen\")\n+lang(581, Dashboard_Show_s, \"Anzeigen: %s\")\n+lang(582, Dashboard_Moving_file, \"Datei verschieben\")\n+lang(583, Dashboard_Deleting_file, \"Datei loschen\")\n+lang(584, Dashboard_Verifying_file, \"Datei prufen\")\n+lang(585, Dashboard_Remaining_size_2f_MB, \"Verbleibend: %.2f MB\")\n+lang(586, Dashboard_Completed_items_d_d, \"Abgeschlossen: %d / %d\")\n+lang(587, Dashboard_Paste_failed, \"Einfugen fehlgeschlagen\")\n+lang(588, Dashboard_Please_wait, \"Bitte warten\")\n+lang(589, Dashboard_Power_off, \"Ausschalten\")\n+lang(590, Dashboard_System_information, \"Systeminformationen\")\n+lang(591, Dashboard_Build_information, \"Build-Informationen\")\n+lang(592, Dashboard_Font_viewer, \"Schriftanzeige\")\n+lang(593, Dashboard_After_IGR_open, \"Nach IGR offnen\")\n+lang(594, Dashboard_App_artwork_background, \"Anwendungsbild als Hintergrund\")\n+lang(595, Dashboard_Artwork_background, \"Bildhintergrund\")\n+lang(596, Dashboard_Artwork_dimming, \"Bildabdunklung\")\n+lang(597, Dashboard_CRT_scanlines, \"CRT-Bildzeilen\")\n+lang(598, Dashboard_Control_icon_backings, \"Hintergrund der Steuersymbole\")\n+lang(599, Dashboard_Custom_app_icon_image, \"Eigenes Anwendungssymbol...\")\n+lang(600, Dashboard_Default_app_icon, \"Standard-Anwendungssymbol\")\n+lang(601, Dashboard_Disable_Ember_support, \"Ember deaktivieren\")\n+lang(602, Dashboard_Disable_Neutrino_support, \"Neutrino deaktivieren\")\n+lang(603, Dashboard_Disable_POPStarter_support, \"POPStarter deaktivieren\")\n+lang(604, Dashboard_ESR_loader, \"ESR-Lader\")\n+lang(605, Dashboard_Ember_directory, \"Ember-Verzeichnis\")\n+lang(606, Dashboard_Export_theme_to_folder, \"Thema in Ordner exportieren...\")\n+lang(607, Dashboard_Load_theme_from_folder, \"Thema aus Ordner laden...\")\n+lang(608, Dashboard_Game_ID_launch_signal_for_apps, \"Game-ID-Signal fur Anwendungen\")\n+lang(609, Dashboard_Game_ID_launch_signal_for_discs, \"Game-ID-Signal fur Discs\")\n+lang(610, Dashboard_Game_ID_output, \"Game-ID-Ausgabe\")\n+lang(611, Dashboard_Home_quick_launch_shortcuts, \"Schnellstart-Verknupfungen\")\n+lang(612, Dashboard_IGR_application, \"IGR-Anwendung\")\n+lang(613, Dashboard_IGR_helper, \"IGR-Helfer\")\n+lang(614, Dashboard_Interface_colours, \"Oberflachenfarben\")\n+lang(615, Dashboard_Internal_HDD_ATA_units, \"Interne HDD-/ATA-Gerate\")\n+lang(616, Dashboard_Legacy_Ethernet_drivers, \"Alte Ethernet-Treiber\")\n+lang(617, Dashboard_Lock_shortcuts_bookmarks, \"Verknupfungen und Lesezeichen sperren\")\n+lang(618, Dashboard_OSDSYS_program, \"OSDSYS-Programm\")\n+lang(619, Dashboard_PSU_add_date_to_names, \"PSU: Datum im Namen\")\n+lang(620, Dashboard_PSU_add_game_ID_and_title_to_names, \"PSU: Game ID und Titel im Namen\")\n+lang(621, Dashboard_PSU_never_overwrite_existing_files, \"PSU: Dateien niemals uberschreiben\")\n+lang(622, Dashboard_Recover_saved_card_image, \"Gesichertes Kartenabbild wiederherstellen...\")\n+lang(623, Dashboard_Reset_appearance, \"Darstellung zurucksetzen\")\n+lang(624, Dashboard_Reset_background, \"Hintergrund zurucksetzen\")\n+lang(625, Dashboard_Reset_clock, \"Uhr zurucksetzen\")\n+lang(626, Dashboard_Reset_colours, \"Farben zurucksetzen\")\n+lang(627, Dashboard_Reset_entire_theme, \"Gesamtes Thema zurucksetzen\")\n+lang(628, Dashboard_Reset_icons, \"Symbole zurucksetzen\")\n+lang(629, Dashboard_Reset_screen_settings, \"Bildschirmeinstellungen zurucksetzen\")\n+lang(630, Dashboard_Reset_style, \"Stil zurucksetzen\")\n+lang(631, Dashboard_Reset_text, \"Text zurucksetzen\")\n+lang(632, Dashboard_Reset_wallpaper, \"Hintergrundbild zurucksetzen\")\n+lang(633, Dashboard_Restore_default_theme, \"Standardthema wiederherstellen\")\n+lang(634, Dashboard_Save_launch_tool_settings_to, \"Start- und Werkzeugeinstellungen speichern...\")\n+lang(635, Dashboard_Screen_position_X, \"Horizontale Bildposition\")\n+lang(636, Dashboard_Screen_position_Y, \"Vertikale Bildposition\")\n+lang(637, Dashboard_Set_PS2_clock_manually, \"PS2-Uhr manuell einstellen...\")\n+lang(638, Dashboard_Set_software_clock, \"Softwareuhr einstellen...\")\n+lang(639, Dashboard_Show_welcome_message_again, \"Begrussung erneut anzeigen\")\n+lang(640, Dashboard_Sync_PS2_clock_from_Internet, \"PS2-Uhr uber Internet synchronisieren\")\n+lang(641, Dashboard_System_font_file, \"Systemschriftdatei\")\n+lang(642, Dashboard_Theme_files_resets, \"Themendateien und Zurucksetzen\")\n+lang(643, Dashboard_Theme_name, \"Themenname...\")\n+lang(644, Dashboard_Tile_style, \"Kachelstil\")\n+lang(645, Dashboard_Tiles_menus, \"Kacheln und Menus\")\n+lang(646, Dashboard_Use_PS2_hardware_clock, \"PS2-Hardwareuhr verwenden\")\n+lang(647, Dashboard_Video_mode, \"Videomodus\")\n+lang(648, Dashboard_Wallpaper_image, \"Hintergrundbild...\")\n+lang(649, Dashboard_Start, \"Starten\")\n+lang(650, Dashboard_Favorite, \"Favorit\")\n+lang(651, Dashboard_Show_background, \"Hintergrund anzeigen\")\n+lang(652, Dashboard_Ready, \"Bereit\")\n+lang(653, Dashboard_Checking, \"Prufung...\")\n+lang(654, Dashboard_Slot_empty, \"Steckplatz leer\")\n+lang(655, Dashboard_Mounted_image, \"Abbild eingebunden\")\n+lang(656, Dashboard_Disc_PS1_loaders, \"Disc- und PS1-Lader\")\n+lang(657, Dashboard_MMCE_backup_sync, \"MMCE-Sicherung und Synchronisierung\")\n+lang(658, Dashboard_Dashboard_labels_file_operations_and_tools, \"Menus, Dateioperationen und Werkzeuge.\")\n+lang(659, Dashboard_Optional_LNG_overrides_including_dashboard_labels, \"Optionale .LNG-Datei, einschliesslich Menus.\")\n+lang(660, Dashboard_Some_extended_help_remains_in_English, \"Einige Hilfetexte bleiben auf Englisch.\")\n+lang(661, Dashboard_Last_folder, \"Letzter Ordner\")\n+lang(662, Dashboard_BROWSE, \"DURCHSUCHEN\")\n+lang(663, Dashboard_BOOKMARKS_PATHPAD, \"LESEZEICHEN (PATHPAD)\")"
        },
        {
          "sha": "a5e87fd96374e7addc830cf0c2ad47fd536c5417",
          "filename": "Lang/HUN.LNG",
          "status": "modified",
          "additions": 196,
          "deletions": 0,
          "changes": 196,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/Lang%2FHUN.LNG",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/Lang%2FHUN.LNG",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/Lang%2FHUN.LNG?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -466,3 +466,199 @@ lang(464, PC_Cmd_Bad, \"Invalid command from the PC (made for another version?)\")\n lang(465, PC_Cmd_Unknown, \"Unknown command from the PC: %.60s\")\n lang(466, Old_to_New, \"Regi -> Uj\")\n lang(467, New_to_Old, \"Uj -> Regi\")\n+lang(468, Dashboard_Home, \"Kezdolap\")\n+lang(469, Dashboard_Applications, \"Alkalmazasok\")\n+lang(470, Dashboard_Files, \"Fajlok\")\n+lang(471, Dashboard_Tools, \"Eszkozok\")\n+lang(472, Dashboard_Settings, \"Beallitasok\")\n+lang(473, Dashboard_Favorites, \"Kedvencek\")\n+lang(474, Dashboard_Devices, \"Eszkozok\")\n+lang(475, Dashboard_Options, \"Beallitasok\")\n+lang(476, Dashboard_Grid, \"Racs\")\n+lang(477, Dashboard_Carousel, \"Korhinta\")\n+lang(478, Dashboard_All, \"Mind\")\n+lang(479, Dashboard_Default, \"Alapertelmezett\")\n+lang(480, Dashboard_Not_set, \"Nincs beallitva\")\n+lang(481, Dashboard_Continue, \"Folytatas\")\n+lang(482, Dashboard_Reset, \"Visszaallitas\")\n+lang(483, Dashboard_Refresh, \"Frissites\")\n+lang(484, Dashboard_Appearance, \"Megjelenes\")\n+lang(485, Dashboard_Home_applications, \"Kezdolap es alkalmazasok\")\n+lang(486, Dashboard_Sound, \"Hang\")\n+lang(487, Dashboard_Input, \"Bevitel\")\n+lang(488, Dashboard_Launching, \"Inditas\")\n+lang(489, Dashboard_Storage_devices, \"Tarolok es eszkozok\")\n+lang(490, Dashboard_Network, \"Halozat\")\n+lang(491, Dashboard_System, \"Rendszer\")\n+lang(492, Dashboard_Theme_preset, \"Tema alapbeallitas\")\n+lang(493, Dashboard_Customize_theme, \"Tema testreszabasa\")\n+lang(494, Dashboard_Screen_calibration, \"Kepernyo kalibralasa\")\n+lang(495, Dashboard_Background_motion, \"Hattermozgatas\")\n+lang(496, Dashboard_Menu_animation, \"Menu animacio\")\n+lang(497, Dashboard_Background, \"Hatter\")\n+lang(498, Dashboard_Colour, \"Szin\")\n+lang(499, Dashboard_Second_colour, \"Masodik szin\")\n+lang(500, Dashboard_Accent, \"Kiemeles\")\n+lang(501, Dashboard_Glow, \"Ragyogas\")\n+lang(502, Dashboard_Borders, \"Szegelyek\")\n+lang(503, Dashboard_Corners, \"Sarkok\")\n+lang(504, Dashboard_Panels, \"Panelek\")\n+lang(505, Dashboard_Panel_transparency, \"Panel atlatszosag\")\n+lang(506, Dashboard_Secondary_text, \"Masodlagos szoveg\")\n+lang(507, Dashboard_Muted_text, \"Halvany szoveg\")\n+lang(508, Dashboard_Selected_text, \"Kijelolt szoveg\")\n+lang(509, Dashboard_Selection, \"Kijeloles\")\n+lang(510, Dashboard_Selection_style, \"Kijeloles stilusa\")\n+lang(511, Dashboard_Font, \"Betutipus\")\n+lang(512, Dashboard_Bold, \"Felkover\")\n+lang(513, Dashboard_Text_shadow, \"Szovegarnyek\")\n+lang(514, Dashboard_Text_size, \"Szovegmeret\")\n+lang(515, Dashboard_Icons, \"Ikonok\")\n+lang(516, Dashboard_Icon_colour, \"Ikon szine\")\n+lang(517, Dashboard_Clock, \"Ora\")\n+lang(518, Dashboard_Date, \"Datum\")\n+lang(519, Dashboard_Hours, \"Orak\")\n+lang(520, Dashboard_Seconds, \"Masodpercek\")\n+lang(521, Dashboard_Position, \"Helyzet\")\n+lang(522, Dashboard_Warnings, \"Figyelmeztetesek\")\n+lang(523, Dashboard_Application_view, \"Alkalmazasnezet\")\n+lang(524, Dashboard_Application_artwork, \"Alkalmazaskepek\")\n+lang(525, Dashboard_Application_library, \"Alkalmazaskonyvtar\")\n+lang(526, Dashboard_Application_folders, \"Alkalmazasmappak...\")\n+lang(527, Dashboard_Recent_applications, \"Legutobbi alkalmazasok\")\n+lang(528, Dashboard_Clear_recent_applications, \"Legutobbi alkalmazasok torlese\")\n+lang(529, Dashboard_Refresh_library, \"Konyvtar frissitese\")\n+lang(530, Dashboard_Refresh_library_now, \"Konyvtar frissitese most\")\n+lang(531, Dashboard_Refresh_at_startup, \"Frissites inditaskor\")\n+lang(532, Dashboard_Start_screen, \"Kezdokepernyo\")\n+lang(533, Dashboard_Interface_sounds, \"Kezelofelulet hangjai\")\n+lang(534, Dashboard_Interface_sound_volume, \"Kezelofelulet hangereje\")\n+lang(535, Dashboard_Background_music, \"Hatterzene\")\n+lang(536, Dashboard_Music_volume, \"Zene hangereje\")\n+lang(537, Dashboard_Confirm_button, \"Megerosito gomb\")\n+lang(538, Dashboard_Keyboard_map, \"Billentyuzetkiosztas\")\n+lang(539, Dashboard_USB_keyboard, \"USB billentyuzet\")\n+lang(540, Dashboard_USB_keyboard_driver, \"USB billentyuzet illeszto\")\n+lang(541, Dashboard_On_screen_keyboard_layout, \"Kepernyobillentyuzet\")\n+lang(542, Dashboard_Launch_keys, \"Inditogombok\")\n+lang(543, Dashboard_Automatic_start_boot, \"Automatikus inditas\")\n+lang(544, Dashboard_Automatic_start_timeout, \"Automatikus inditas varakozasa\")\n+lang(545, Dashboard_Startup_delay, \"Inditasi kesleltetes\")\n+lang(546, Dashboard_Disc_image_loaders, \"Lemez es kepbetoltok\")\n+lang(547, Dashboard_Reset_IOP_before_launch, \"IOP ujrainditasa inditas elott\")\n+lang(548, Dashboard_Show_unavailable_devices, \"Nem elerheto eszkozok mutatasa\")\n+lang(549, Dashboard_Start_devices_automatically, \"Eszkozok automatikus inditasa\")\n+lang(550, Dashboard_Auto_start_USB_storage, \"USB tarolo automatikus inditasa\")\n+lang(551, Dashboard_Auto_start_internal_HDD, \"Belso HDD automatikus inditasa\")\n+lang(552, Dashboard_Auto_start_exFAT_HDD, \"exFAT HDD automatikus inditasa\")\n+lang(553, Dashboard_Auto_start_MMCE_SD_card, \"MMCE SD kartya automatikus inditasa\")\n+lang(554, Dashboard_Auto_start_MX4SIO, \"MX4SIO automatikus inditasa\")\n+lang(555, Dashboard_Auto_start_iLink_FireWire, \"iLink / FireWire automatikus inditasa\")\n+lang(556, Dashboard_Auto_start_network_UDPFS, \"Halozat automatikus inditasa (UDPFS)\")\n+lang(557, Dashboard_Hide_empty_memory_card_slots, \"Ures kartyafoglalatok elrejtese\")\n+lang(558, Dashboard_Allow_writing_over_the_network, \"Halozati iras engedelyezese\")\n+lang(559, Dashboard_IP_address, \"IP-cim\")\n+lang(560, Dashboard_Backup_folder, \"Biztonsagi mentes mappaja\")\n+lang(561, Dashboard_Backup_format, \"Mentes formatuma\")\n+lang(562, Dashboard_Backups_per_card, \"Mentesek kartyankent\")\n+lang(563, Dashboard_MMCE_backups, \"MMCE mentesek\")\n+lang(564, Dashboard_Memory_card_backup_files, \"Memoriakartya mentesek\")\n+lang(565, Dashboard_Automatic_sync, \"Automatikus szinkronizalas\")\n+lang(566, Dashboard_Sync_all_cards, \"Minden kartya szinkronizalasa\")\n+lang(567, Dashboard_Google_account, \"Google-fiok\")\n+lang(568, Dashboard_Configuration_folder, \"Beallitasok mappaja\")\n+lang(569, Dashboard_Language_file, \"Nyelvi fajl\")\n+lang(570, Dashboard_Check_for_updates, \"Frissitesek keresese\")\n+lang(571, Dashboard_About_Launchstation, \"A Launchstation nevjegye\")\n+lang(572, Dashboard_File_Browser, \"Fajlbongeszo\")\n+lang(573, Dashboard_HDD_Manager, \"HDD kezelo\")\n+lang(574, Dashboard_Network_Host, \"Halozati PC (PS2Link)\")\n+lang(575, Dashboard_PS2Link_host, \"PS2Link PC megosztott fajljai\")\n+lang(576, Dashboard_Disc_Drive, \"Lemezmeghajto\")\n+lang(577, Dashboard_Network_UDPFS, \"Halozat (UDPFS)\")\n+lang(578, Dashboard_Change_view, \"Nezet valtasa\")\n+lang(579, Dashboard_Application_options, \"Alkalmazas beallitasai...\")\n+lang(580, Dashboard_Show_applications, \"Alkalmazasok mutatasa\")\n+lang(581, Dashboard_Show_s, \"Mutatas: %s\")\n+lang(582, Dashboard_Moving_file, \"Fajl athelyezese\")\n+lang(583, Dashboard_Deleting_file, \"Fajl torlese\")\n+lang(584, Dashboard_Verifying_file, \"Fajl ellenorzese\")\n+lang(585, Dashboard_Remaining_size_2f_MB, \"Hatravan: %.2f MB\")\n+lang(586, Dashboard_Completed_items_d_d, \"Kesz: %d / %d\")\n+lang(587, Dashboard_Paste_failed, \"Beillesztesi hiba\")\n+lang(588, Dashboard_Please_wait, \"Kerem varjon\")\n+lang(589, Dashboard_Power_off, \"Kikapcsolas\")\n+lang(590, Dashboard_System_information, \"Rendszerinformacio\")\n+lang(591, Dashboard_Build_information, \"Build informaciok\")\n+lang(592, Dashboard_Font_viewer, \"Betutipus megjelenito\")\n+lang(593, Dashboard_After_IGR_open, \"IGR utan megnyitas\")\n+lang(594, Dashboard_App_artwork_background, \"Alkalmazaskep hatterkent\")\n+lang(595, Dashboard_Artwork_background, \"Kephatter\")\n+lang(596, Dashboard_Artwork_dimming, \"Kep sotetitese\")\n+lang(597, Dashboard_CRT_scanlines, \"CRT pasztazasi vonalak\")\n+lang(598, Dashboard_Control_icon_backings, \"Vezerloikonok hattere\")\n+lang(599, Dashboard_Custom_app_icon_image, \"Egyeni alkalmazasikon...\")\n+lang(600, Dashboard_Default_app_icon, \"Alapertelmezett alkalmazasikon\")\n+lang(601, Dashboard_Disable_Ember_support, \"Ember letiltasa\")\n+lang(602, Dashboard_Disable_Neutrino_support, \"Neutrino letiltasa\")\n+lang(603, Dashboard_Disable_POPStarter_support, \"POPStarter letiltasa\")\n+lang(604, Dashboard_ESR_loader, \"ESR betolto\")\n+lang(605, Dashboard_Ember_directory, \"Ember mappa\")\n+lang(606, Dashboard_Export_theme_to_folder, \"Tema exportalasa mappaba...\")\n+lang(607, Dashboard_Load_theme_from_folder, \"Tema betoltese mappabol...\")\n+lang(608, Dashboard_Game_ID_launch_signal_for_apps, \"Game ID jel alkalmazasokhoz\")\n+lang(609, Dashboard_Game_ID_launch_signal_for_discs, \"Game ID jel lemezekhez\")\n+lang(610, Dashboard_Game_ID_output, \"Game ID kimenet\")\n+lang(611, Dashboard_Home_quick_launch_shortcuts, \"Kezdolapi gyorsinditok\")\n+lang(612, Dashboard_IGR_application, \"IGR alkalmazas\")\n+lang(613, Dashboard_IGR_helper, \"IGR seged\")\n+lang(614, Dashboard_Interface_colours, \"Kezelofelulet szinei\")\n+lang(615, Dashboard_Internal_HDD_ATA_units, \"Belso HDD / ATA meghajtok\")\n+lang(616, Dashboard_Legacy_Ethernet_drivers, \"Regi Ethernet illesztok\")\n+lang(617, Dashboard_Lock_shortcuts_bookmarks, \"Parancsikonok es konyvjelzok zarolasa\")\n+lang(618, Dashboard_OSDSYS_program, \"OSDSYS program\")\n+lang(619, Dashboard_PSU_add_date_to_names, \"PSU: datum hozzaadasa\")\n+lang(620, Dashboard_PSU_add_game_ID_and_title_to_names, \"PSU: Game ID es cim hozzaadasa\")\n+lang(621, Dashboard_PSU_never_overwrite_existing_files, \"PSU: fajlok felulirasa nelkul\")\n+lang(622, Dashboard_Recover_saved_card_image, \"Mentett kartyakep helyreallitasa...\")\n+lang(623, Dashboard_Reset_appearance, \"Megjelenes visszaallitasa\")\n+lang(624, Dashboard_Reset_background, \"Hatter visszaallitasa\")\n+lang(625, Dashboard_Reset_clock, \"Ora visszaallitasa\")\n+lang(626, Dashboard_Reset_colours, \"Szinek visszaallitasa\")\n+lang(627, Dashboard_Reset_entire_theme, \"Teljes tema visszaallitasa\")\n+lang(628, Dashboard_Reset_icons, \"Ikonok visszaallitasa\")\n+lang(629, Dashboard_Reset_screen_settings, \"Kepernyobeallitasok visszaallitasa\")\n+lang(630, Dashboard_Reset_style, \"Stilus visszaallitasa\")\n+lang(631, Dashboard_Reset_text, \"Szoveg visszaallitasa\")\n+lang(632, Dashboard_Reset_wallpaper, \"Hatterkep visszaallitasa\")\n+lang(633, Dashboard_Restore_default_theme, \"Alapertelmezett tema visszaallitasa\")\n+lang(634, Dashboard_Save_launch_tool_settings_to, \"Inditasi es eszkozbeallitasok mentese...\")\n+lang(635, Dashboard_Screen_position_X, \"Kepernyo vizszintes helyzete\")\n+lang(636, Dashboard_Screen_position_Y, \"Kepernyo fuggoleges helyzete\")\n+lang(637, Dashboard_Set_PS2_clock_manually, \"PS2 ora kezi beallitasa...\")\n+lang(638, Dashboard_Set_software_clock, \"Szoftverora beallitasa...\")\n+lang(639, Dashboard_Show_welcome_message_again, \"Udvozlo uzenet ujra\")\n+lang(640, Dashboard_Sync_PS2_clock_from_Internet, \"PS2 ora internetes szinkronizalasa\")\n+lang(641, Dashboard_System_font_file, \"Rendszerbetutipus fajlja\")\n+lang(642, Dashboard_Theme_files_resets, \"Temafajlok es visszaallitas\")\n+lang(643, Dashboard_Theme_name, \"Tema neve...\")\n+lang(644, Dashboard_Tile_style, \"Csempe stilusa\")\n+lang(645, Dashboard_Tiles_menus, \"Csempek es menuk\")\n+lang(646, Dashboard_Use_PS2_hardware_clock, \"PS2 hardverora hasznalata\")\n+lang(647, Dashboard_Video_mode, \"Videomod\")\n+lang(648, Dashboard_Wallpaper_image, \"Hatterkep...\")\n+lang(649, Dashboard_Start, \"Inditas\")\n+lang(650, Dashboard_Favorite, \"Kedvenc\")\n+lang(651, Dashboard_Show_background, \"Hatter mutatasa\")\n+lang(652, Dashboard_Ready, \"Kesz\")\n+lang(653, Dashboard_Checking, \"Ellenorzes...\")\n+lang(654, Dashboard_Slot_empty, \"Ures foglalat\")\n+lang(655, Dashboard_Mounted_image, \"Csatolt kep\")\n+lang(656, Dashboard_Disc_PS1_loaders, \"Lemez es PS1 betoltok\")\n+lang(657, Dashboard_MMCE_backup_sync, \"MMCE mentes es szinkronizalas\")\n+lang(658, Dashboard_Dashboard_labels_file_operations_and_tools, \"Menuk, fajlmuveletek es eszkozok.\")\n+lang(659, Dashboard_Optional_LNG_overrides_including_dashboard_labels, \"Opcionalis .LNG fajl, menukkel egyutt.\")\n+lang(660, Dashboard_Some_extended_help_remains_in_English, \"Egyes reszletes sugok angolul maradnak.\")\n+lang(661, Dashboard_Last_folder, \"Legutobbi mappa\")\n+lang(662, Dashboard_BROWSE, \"BONGESZES\")\n+lang(663, Dashboard_BOOKMARKS_PATHPAD, \"KONYVJELZOK (PATHPAD)\")"
        },
        {
          "sha": "707c2da7e4fce31d2a12d929df51ad0c505a8d54",
          "filename": "Lang/ITA.LNG",
          "status": "modified",
          "additions": 196,
          "deletions": 0,
          "changes": 196,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/Lang%2FITA.LNG",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/Lang%2FITA.LNG",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/Lang%2FITA.LNG?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -466,3 +466,199 @@ lang(464, PC_Cmd_Bad, \"Invalid command from the PC (made for another version?)\")\n lang(465, PC_Cmd_Unknown, \"Unknown command from the PC: %.60s\")\n lang(466, Old_to_New, \"Meno recente -> Piu recente\")\n lang(467, New_to_Old, \"Piu recente -> Meno recente\")\n+lang(468, Dashboard_Home, \"Home\")\n+lang(469, Dashboard_Applications, \"Applicazioni\")\n+lang(470, Dashboard_Files, \"File\")\n+lang(471, Dashboard_Tools, \"Strumenti\")\n+lang(472, Dashboard_Settings, \"Impostazioni\")\n+lang(473, Dashboard_Favorites, \"Preferiti\")\n+lang(474, Dashboard_Devices, \"Dispositivi\")\n+lang(475, Dashboard_Options, \"Opzioni\")\n+lang(476, Dashboard_Grid, \"Griglia\")\n+lang(477, Dashboard_Carousel, \"Carosello\")\n+lang(478, Dashboard_All, \"Tutte\")\n+lang(479, Dashboard_Default, \"Predefinito\")\n+lang(480, Dashboard_Not_set, \"Non impostato\")\n+lang(481, Dashboard_Continue, \"Continua\")\n+lang(482, Dashboard_Reset, \"Ripristina\")\n+lang(483, Dashboard_Refresh, \"Aggiorna\")\n+lang(484, Dashboard_Appearance, \"Aspetto\")\n+lang(485, Dashboard_Home_applications, \"Home e applicazioni\")\n+lang(486, Dashboard_Sound, \"Audio\")\n+lang(487, Dashboard_Input, \"Comandi\")\n+lang(488, Dashboard_Launching, \"Avvio\")\n+lang(489, Dashboard_Storage_devices, \"Memoria e dispositivi\")\n+lang(490, Dashboard_Network, \"Rete\")\n+lang(491, Dashboard_System, \"Sistema\")\n+lang(492, Dashboard_Theme_preset, \"Tema predefinito\")\n+lang(493, Dashboard_Customize_theme, \"Personalizza tema\")\n+lang(494, Dashboard_Screen_calibration, \"Calibrazione schermo\")\n+lang(495, Dashboard_Background_motion, \"Movimento sfondo\")\n+lang(496, Dashboard_Menu_animation, \"Animazione menu\")\n+lang(497, Dashboard_Background, \"Sfondo\")\n+lang(498, Dashboard_Colour, \"Colore\")\n+lang(499, Dashboard_Second_colour, \"Secondo colore\")\n+lang(500, Dashboard_Accent, \"Accento\")\n+lang(501, Dashboard_Glow, \"Bagliore\")\n+lang(502, Dashboard_Borders, \"Bordi\")\n+lang(503, Dashboard_Corners, \"Angoli\")\n+lang(504, Dashboard_Panels, \"Pannelli\")\n+lang(505, Dashboard_Panel_transparency, \"Trasparenza pannelli\")\n+lang(506, Dashboard_Secondary_text, \"Testo secondario\")\n+lang(507, Dashboard_Muted_text, \"Testo attenuato\")\n+lang(508, Dashboard_Selected_text, \"Testo selezionato\")\n+lang(509, Dashboard_Selection, \"Selezione\")\n+lang(510, Dashboard_Selection_style, \"Stile selezione\")\n+lang(511, Dashboard_Font, \"Carattere\")\n+lang(512, Dashboard_Bold, \"Grassetto\")\n+lang(513, Dashboard_Text_shadow, \"Ombra testo\")\n+lang(514, Dashboard_Text_size, \"Dimensione testo\")\n+lang(515, Dashboard_Icons, \"Icone\")\n+lang(516, Dashboard_Icon_colour, \"Colore icone\")\n+lang(517, Dashboard_Clock, \"Orologio\")\n+lang(518, Dashboard_Date, \"Data\")\n+lang(519, Dashboard_Hours, \"Ore\")\n+lang(520, Dashboard_Seconds, \"Secondi\")\n+lang(521, Dashboard_Position, \"Posizione\")\n+lang(522, Dashboard_Warnings, \"Avvisi\")\n+lang(523, Dashboard_Application_view, \"Vista applicazioni\")\n+lang(524, Dashboard_Application_artwork, \"Immagini applicazioni\")\n+lang(525, Dashboard_Application_library, \"Libreria applicazioni\")\n+lang(526, Dashboard_Application_folders, \"Cartelle applicazioni...\")\n+lang(527, Dashboard_Recent_applications, \"Applicazioni recenti\")\n+lang(528, Dashboard_Clear_recent_applications, \"Cancella applicazioni recenti\")\n+lang(529, Dashboard_Refresh_library, \"Aggiorna libreria\")\n+lang(530, Dashboard_Refresh_library_now, \"Aggiorna libreria ora\")\n+lang(531, Dashboard_Refresh_at_startup, \"Aggiorna all'avvio\")\n+lang(532, Dashboard_Start_screen, \"Schermata iniziale\")\n+lang(533, Dashboard_Interface_sounds, \"Suoni interfaccia\")\n+lang(534, Dashboard_Interface_sound_volume, \"Volume interfaccia\")\n+lang(535, Dashboard_Background_music, \"Musica di sottofondo\")\n+lang(536, Dashboard_Music_volume, \"Volume musica\")\n+lang(537, Dashboard_Confirm_button, \"Tasto conferma\")\n+lang(538, Dashboard_Keyboard_map, \"Mappa tastiera\")\n+lang(539, Dashboard_USB_keyboard, \"Tastiera USB\")\n+lang(540, Dashboard_USB_keyboard_driver, \"Driver tastiera USB\")\n+lang(541, Dashboard_On_screen_keyboard_layout, \"Tastiera su schermo\")\n+lang(542, Dashboard_Launch_keys, \"Tasti di avvio\")\n+lang(543, Dashboard_Automatic_start_boot, \"Avvio automatico\")\n+lang(544, Dashboard_Automatic_start_timeout, \"Attesa avvio automatico\")\n+lang(545, Dashboard_Startup_delay, \"Ritardo avvio\")\n+lang(546, Dashboard_Disc_image_loaders, \"Loader dischi e immagini\")\n+lang(547, Dashboard_Reset_IOP_before_launch, \"Riavvia IOP prima dell'avvio\")\n+lang(548, Dashboard_Show_unavailable_devices, \"Mostra dispositivi non disponibili\")\n+lang(549, Dashboard_Start_devices_automatically, \"Avvia dispositivi automaticamente\")\n+lang(550, Dashboard_Auto_start_USB_storage, \"Avvia memoria USB\")\n+lang(551, Dashboard_Auto_start_internal_HDD, \"Avvia HDD interno\")\n+lang(552, Dashboard_Auto_start_exFAT_HDD, \"Avvia HDD exFAT\")\n+lang(553, Dashboard_Auto_start_MMCE_SD_card, \"Avvia scheda SD MMCE\")\n+lang(554, Dashboard_Auto_start_MX4SIO, \"Avvia MX4SIO\")\n+lang(555, Dashboard_Auto_start_iLink_FireWire, \"Avvia iLink / FireWire\")\n+lang(556, Dashboard_Auto_start_network_UDPFS, \"Avvia rete (UDPFS)\")\n+lang(557, Dashboard_Hide_empty_memory_card_slots, \"Nascondi slot schede vuoti\")\n+lang(558, Dashboard_Allow_writing_over_the_network, \"Consenti scrittura in rete\")\n+lang(559, Dashboard_IP_address, \"Indirizzo IP\")\n+lang(560, Dashboard_Backup_folder, \"Cartella backup\")\n+lang(561, Dashboard_Backup_format, \"Formato backup\")\n+lang(562, Dashboard_Backups_per_card, \"Backup per scheda\")\n+lang(563, Dashboard_MMCE_backups, \"Backup MMCE\")\n+lang(564, Dashboard_Memory_card_backup_files, \"Backup scheda di memoria\")\n+lang(565, Dashboard_Automatic_sync, \"Sincronizzazione automatica\")\n+lang(566, Dashboard_Sync_all_cards, \"Sincronizza tutte le schede\")\n+lang(567, Dashboard_Google_account, \"Account Google\")\n+lang(568, Dashboard_Configuration_folder, \"Cartella configurazione\")\n+lang(569, Dashboard_Language_file, \"File lingua\")\n+lang(570, Dashboard_Check_for_updates, \"Cerca aggiornamenti\")\n+lang(571, Dashboard_About_Launchstation, \"Informazioni su Launchstation\")\n+lang(572, Dashboard_File_Browser, \"Gestore file\")\n+lang(573, Dashboard_HDD_Manager, \"Gestore HDD\")\n+lang(574, Dashboard_Network_Host, \"PC di rete (PS2Link)\")\n+lang(575, Dashboard_PS2Link_host, \"File condivisi da un PC PS2Link\")\n+lang(576, Dashboard_Disc_Drive, \"Unita disco\")\n+lang(577, Dashboard_Network_UDPFS, \"Rete (UDPFS)\")\n+lang(578, Dashboard_Change_view, \"Cambia vista\")\n+lang(579, Dashboard_Application_options, \"Opzioni applicazione...\")\n+lang(580, Dashboard_Show_applications, \"Mostra applicazioni\")\n+lang(581, Dashboard_Show_s, \"Mostra: %s\")\n+lang(582, Dashboard_Moving_file, \"Sposta file\")\n+lang(583, Dashboard_Deleting_file, \"Elimina file\")\n+lang(584, Dashboard_Verifying_file, \"Verifica file\")\n+lang(585, Dashboard_Remaining_size_2f_MB, \"Rimanenti: %.2f MB\")\n+lang(586, Dashboard_Completed_items_d_d, \"Completati: %d / %d\")\n+lang(587, Dashboard_Paste_failed, \"Incolla non riuscito\")\n+lang(588, Dashboard_Please_wait, \"Attendere\")\n+lang(589, Dashboard_Power_off, \"Spegni\")\n+lang(590, Dashboard_System_information, \"Informazioni sistema\")\n+lang(591, Dashboard_Build_information, \"Informazioni compilazione\")\n+lang(592, Dashboard_Font_viewer, \"Visualizzatore caratteri\")\n+lang(593, Dashboard_After_IGR_open, \"Dopo IGR, apri\")\n+lang(594, Dashboard_App_artwork_background, \"Sfondo immagine applicazione\")\n+lang(595, Dashboard_Artwork_background, \"Sfondo illustrazione\")\n+lang(596, Dashboard_Artwork_dimming, \"Attenuazione immagine\")\n+lang(597, Dashboard_CRT_scanlines, \"Linee CRT\")\n+lang(598, Dashboard_Control_icon_backings, \"Sfondo icone comandi\")\n+lang(599, Dashboard_Custom_app_icon_image, \"Icona applicazione personalizzata...\")\n+lang(600, Dashboard_Default_app_icon, \"Icona applicazione predefinita\")\n+lang(601, Dashboard_Disable_Ember_support, \"Disabilita Ember\")\n+lang(602, Dashboard_Disable_Neutrino_support, \"Disabilita Neutrino\")\n+lang(603, Dashboard_Disable_POPStarter_support, \"Disabilita POPStarter\")\n+lang(604, Dashboard_ESR_loader, \"Loader ESR\")\n+lang(605, Dashboard_Ember_directory, \"Cartella Ember\")\n+lang(606, Dashboard_Export_theme_to_folder, \"Esporta tema in cartella...\")\n+lang(607, Dashboard_Load_theme_from_folder, \"Carica tema da cartella...\")\n+lang(608, Dashboard_Game_ID_launch_signal_for_apps, \"Segnale Game ID per applicazioni\")\n+lang(609, Dashboard_Game_ID_launch_signal_for_discs, \"Segnale Game ID per dischi\")\n+lang(610, Dashboard_Game_ID_output, \"Uscita Game ID\")\n+lang(611, Dashboard_Home_quick_launch_shortcuts, \"Scorciatoie Home\")\n+lang(612, Dashboard_IGR_application, \"Applicazione IGR\")\n+lang(613, Dashboard_IGR_helper, \"Assistente IGR\")\n+lang(614, Dashboard_Interface_colours, \"Colori interfaccia\")\n+lang(615, Dashboard_Internal_HDD_ATA_units, \"Unita interne HDD / ATA\")\n+lang(616, Dashboard_Legacy_Ethernet_drivers, \"Driver Ethernet classici\")\n+lang(617, Dashboard_Lock_shortcuts_bookmarks, \"Blocca scorciatoie e segnalibri\")\n+lang(618, Dashboard_OSDSYS_program, \"Programma OSDSYS\")\n+lang(619, Dashboard_PSU_add_date_to_names, \"PSU: aggiungi data ai nomi\")\n+lang(620, Dashboard_PSU_add_game_ID_and_title_to_names, \"PSU: aggiungi Game ID e titolo\")\n+lang(621, Dashboard_PSU_never_overwrite_existing_files, \"PSU: non sovrascrivere file\")\n+lang(622, Dashboard_Recover_saved_card_image, \"Recupera immagine scheda...\")\n+lang(623, Dashboard_Reset_appearance, \"Ripristina aspetto\")\n+lang(624, Dashboard_Reset_background, \"Ripristina sfondo\")\n+lang(625, Dashboard_Reset_clock, \"Ripristina orologio\")\n+lang(626, Dashboard_Reset_colours, \"Ripristina colori\")\n+lang(627, Dashboard_Reset_entire_theme, \"Ripristina intero tema\")\n+lang(628, Dashboard_Reset_icons, \"Ripristina icone\")\n+lang(629, Dashboard_Reset_screen_settings, \"Ripristina schermo\")\n+lang(630, Dashboard_Reset_style, \"Ripristina stile\")\n+lang(631, Dashboard_Reset_text, \"Ripristina testo\")\n+lang(632, Dashboard_Reset_wallpaper, \"Ripristina immagine sfondo\")\n+lang(633, Dashboard_Restore_default_theme, \"Ripristina tema predefinito\")\n+lang(634, Dashboard_Save_launch_tool_settings_to, \"Salva impostazioni avvio e strumenti...\")\n+lang(635, Dashboard_Screen_position_X, \"Posizione orizzontale\")\n+lang(636, Dashboard_Screen_position_Y, \"Posizione verticale\")\n+lang(637, Dashboard_Set_PS2_clock_manually, \"Imposta orologio PS2 manualmente...\")\n+lang(638, Dashboard_Set_software_clock, \"Imposta orologio software...\")\n+lang(639, Dashboard_Show_welcome_message_again, \"Mostra di nuovo il benvenuto\")\n+lang(640, Dashboard_Sync_PS2_clock_from_Internet, \"Sincronizza orologio PS2 da Internet\")\n+lang(641, Dashboard_System_font_file, \"File carattere di sistema\")\n+lang(642, Dashboard_Theme_files_resets, \"File e ripristino tema\")\n+lang(643, Dashboard_Theme_name, \"Nome tema...\")\n+lang(644, Dashboard_Tile_style, \"Stile riquadri\")\n+lang(645, Dashboard_Tiles_menus, \"Riquadri e menu\")\n+lang(646, Dashboard_Use_PS2_hardware_clock, \"Usa orologio hardware PS2\")\n+lang(647, Dashboard_Video_mode, \"Modalita video\")\n+lang(648, Dashboard_Wallpaper_image, \"Immagine di sfondo...\")\n+lang(649, Dashboard_Start, \"Avvia\")\n+lang(650, Dashboard_Favorite, \"Preferito\")\n+lang(651, Dashboard_Show_background, \"Mostra sfondo\")\n+lang(652, Dashboard_Ready, \"Pronto\")\n+lang(653, Dashboard_Checking, \"Controllo...\")\n+lang(654, Dashboard_Slot_empty, \"Slot vuoto\")\n+lang(655, Dashboard_Mounted_image, \"Immagine montata\")\n+lang(656, Dashboard_Disc_PS1_loaders, \"Loader dischi e PS1\")\n+lang(657, Dashboard_MMCE_backup_sync, \"Backup e sincronizzazione MMCE\")\n+lang(658, Dashboard_Dashboard_labels_file_operations_and_tools, \"Menu, operazioni sui file e strumenti.\")\n+lang(659, Dashboard_Optional_LNG_overrides_including_dashboard_labels, \"File .LNG opzionale, inclusi i menu.\")\n+lang(660, Dashboard_Some_extended_help_remains_in_English, \"Alcuni testi di aiuto restano in inglese.\")\n+lang(661, Dashboard_Last_folder, \"Ultima cartella\")\n+lang(662, Dashboard_BROWSE, \"SFOGLIA\")\n+lang(663, Dashboard_BOOKMARKS_PATHPAD, \"SEGNALIBRI (PATHPAD)\")"
        },
        {
          "sha": "a1deb98f2d896eff5f4b9fba86fdd6b2eec9185b",
          "filename": "Lang/POL.LNG",
          "status": "modified",
          "additions": 196,
          "deletions": 0,
          "changes": 196,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/Lang%2FPOL.LNG",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/Lang%2FPOL.LNG",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/Lang%2FPOL.LNG?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -466,3 +466,199 @@ lang(464, PC_Cmd_Bad, \"Invalid command from the PC (made for another version?)\")\n lang(465, PC_Cmd_Unknown, \"Unknown command from the PC: %.60s\")\n lang(466, Old_to_New, \"Stare -> Nowe\")\n lang(467, New_to_Old, \"Nowe -> Stare\")\n+lang(468, Dashboard_Home, \"Start\")\n+lang(469, Dashboard_Applications, \"Aplikacje\")\n+lang(470, Dashboard_Files, \"Pliki\")\n+lang(471, Dashboard_Tools, \"Narzedzia\")\n+lang(472, Dashboard_Settings, \"Ustawienia\")\n+lang(473, Dashboard_Favorites, \"Ulubione\")\n+lang(474, Dashboard_Devices, \"Urzadzenia\")\n+lang(475, Dashboard_Options, \"Opcje\")\n+lang(476, Dashboard_Grid, \"Siatka\")\n+lang(477, Dashboard_Carousel, \"Karuzela\")\n+lang(478, Dashboard_All, \"Wszystkie\")\n+lang(479, Dashboard_Default, \"Domyslne\")\n+lang(480, Dashboard_Not_set, \"Nie ustawiono\")\n+lang(481, Dashboard_Continue, \"Kontynuuj\")\n+lang(482, Dashboard_Reset, \"Przywroc\")\n+lang(483, Dashboard_Refresh, \"Odswiez\")\n+lang(484, Dashboard_Appearance, \"Wyglad\")\n+lang(485, Dashboard_Home_applications, \"Start i aplikacje\")\n+lang(486, Dashboard_Sound, \"Dzwiek\")\n+lang(487, Dashboard_Input, \"Sterowanie\")\n+lang(488, Dashboard_Launching, \"Uruchamianie\")\n+lang(489, Dashboard_Storage_devices, \"Pamiec i urzadzenia\")\n+lang(490, Dashboard_Network, \"Siec\")\n+lang(491, Dashboard_System, \"System\")\n+lang(492, Dashboard_Theme_preset, \"Gotowy motyw\")\n+lang(493, Dashboard_Customize_theme, \"Dostosuj motyw\")\n+lang(494, Dashboard_Screen_calibration, \"Kalibracja ekranu\")\n+lang(495, Dashboard_Background_motion, \"Ruch tla\")\n+lang(496, Dashboard_Menu_animation, \"Animacja menu\")\n+lang(497, Dashboard_Background, \"Tlo\")\n+lang(498, Dashboard_Colour, \"Kolor\")\n+lang(499, Dashboard_Second_colour, \"Drugi kolor\")\n+lang(500, Dashboard_Accent, \"Akcent\")\n+lang(501, Dashboard_Glow, \"Poswiata\")\n+lang(502, Dashboard_Borders, \"Obramowania\")\n+lang(503, Dashboard_Corners, \"Narozniki\")\n+lang(504, Dashboard_Panels, \"Panele\")\n+lang(505, Dashboard_Panel_transparency, \"Przezroczystosc paneli\")\n+lang(506, Dashboard_Secondary_text, \"Tekst dodatkowy\")\n+lang(507, Dashboard_Muted_text, \"Tekst przygaszony\")\n+lang(508, Dashboard_Selected_text, \"Zaznaczony tekst\")\n+lang(509, Dashboard_Selection, \"Zaznaczenie\")\n+lang(510, Dashboard_Selection_style, \"Styl zaznaczenia\")\n+lang(511, Dashboard_Font, \"Czcionka\")\n+lang(512, Dashboard_Bold, \"Pogrubienie\")\n+lang(513, Dashboard_Text_shadow, \"Cien tekstu\")\n+lang(514, Dashboard_Text_size, \"Rozmiar tekstu\")\n+lang(515, Dashboard_Icons, \"Ikony\")\n+lang(516, Dashboard_Icon_colour, \"Kolor ikon\")\n+lang(517, Dashboard_Clock, \"Zegar\")\n+lang(518, Dashboard_Date, \"Data\")\n+lang(519, Dashboard_Hours, \"Godziny\")\n+lang(520, Dashboard_Seconds, \"Sekundy\")\n+lang(521, Dashboard_Position, \"Pozycja\")\n+lang(522, Dashboard_Warnings, \"Ostrzezenia\")\n+lang(523, Dashboard_Application_view, \"Widok aplikacji\")\n+lang(524, Dashboard_Application_artwork, \"Grafika aplikacji\")\n+lang(525, Dashboard_Application_library, \"Biblioteka aplikacji\")\n+lang(526, Dashboard_Application_folders, \"Foldery aplikacji...\")\n+lang(527, Dashboard_Recent_applications, \"Ostatnie aplikacje\")\n+lang(528, Dashboard_Clear_recent_applications, \"Wyczysc ostatnie aplikacje\")\n+lang(529, Dashboard_Refresh_library, \"Odswiez biblioteke\")\n+lang(530, Dashboard_Refresh_library_now, \"Odswiez biblioteke teraz\")\n+lang(531, Dashboard_Refresh_at_startup, \"Odswiez przy starcie\")\n+lang(532, Dashboard_Start_screen, \"Ekran startowy\")\n+lang(533, Dashboard_Interface_sounds, \"Dzwieki interfejsu\")\n+lang(534, Dashboard_Interface_sound_volume, \"Glosnosc interfejsu\")\n+lang(535, Dashboard_Background_music, \"Muzyka w tle\")\n+lang(536, Dashboard_Music_volume, \"Glosnosc muzyki\")\n+lang(537, Dashboard_Confirm_button, \"Przycisk potwierdzenia\")\n+lang(538, Dashboard_Keyboard_map, \"Mapa klawiatury\")\n+lang(539, Dashboard_USB_keyboard, \"Klawiatura USB\")\n+lang(540, Dashboard_USB_keyboard_driver, \"Sterownik klawiatury USB\")\n+lang(541, Dashboard_On_screen_keyboard_layout, \"Klawiatura ekranowa\")\n+lang(542, Dashboard_Launch_keys, \"Klawisze uruchamiania\")\n+lang(543, Dashboard_Automatic_start_boot, \"Autostart i rozruch\")\n+lang(544, Dashboard_Automatic_start_timeout, \"Czas autostartu\")\n+lang(545, Dashboard_Startup_delay, \"Opoznienie startu\")\n+lang(546, Dashboard_Disc_image_loaders, \"Loadery plyt i obrazow\")\n+lang(547, Dashboard_Reset_IOP_before_launch, \"Restart IOP przed uruchomieniem\")\n+lang(548, Dashboard_Show_unavailable_devices, \"Pokaz niedostepne urzadzenia\")\n+lang(549, Dashboard_Start_devices_automatically, \"Uruchamiaj urzadzenia automatycznie\")\n+lang(550, Dashboard_Auto_start_USB_storage, \"Autostart pamieci USB\")\n+lang(551, Dashboard_Auto_start_internal_HDD, \"Autostart wewnetrznego HDD\")\n+lang(552, Dashboard_Auto_start_exFAT_HDD, \"Autostart HDD exFAT\")\n+lang(553, Dashboard_Auto_start_MMCE_SD_card, \"Autostart karty SD MMCE\")\n+lang(554, Dashboard_Auto_start_MX4SIO, \"Autostart MX4SIO\")\n+lang(555, Dashboard_Auto_start_iLink_FireWire, \"Autostart iLink / FireWire\")\n+lang(556, Dashboard_Auto_start_network_UDPFS, \"Autostart sieci (UDPFS)\")\n+lang(557, Dashboard_Hide_empty_memory_card_slots, \"Ukryj puste gniazda kart\")\n+lang(558, Dashboard_Allow_writing_over_the_network, \"Zezwol na zapis przez siec\")\n+lang(559, Dashboard_IP_address, \"Adres IP\")\n+lang(560, Dashboard_Backup_folder, \"Folder kopii\")\n+lang(561, Dashboard_Backup_format, \"Format kopii\")\n+lang(562, Dashboard_Backups_per_card, \"Kopie na karte\")\n+lang(563, Dashboard_MMCE_backups, \"Kopie MMCE\")\n+lang(564, Dashboard_Memory_card_backup_files, \"Kopie karty pamieci\")\n+lang(565, Dashboard_Automatic_sync, \"Automatyczna synchronizacja\")\n+lang(566, Dashboard_Sync_all_cards, \"Synchronizuj wszystkie karty\")\n+lang(567, Dashboard_Google_account, \"Konto Google\")\n+lang(568, Dashboard_Configuration_folder, \"Folder konfiguracji\")\n+lang(569, Dashboard_Language_file, \"Plik jezyka\")\n+lang(570, Dashboard_Check_for_updates, \"Sprawdz aktualizacje\")\n+lang(571, Dashboard_About_Launchstation, \"O Launchstation\")\n+lang(572, Dashboard_File_Browser, \"Przegladarka plikow\")\n+lang(573, Dashboard_HDD_Manager, \"Menedzer HDD\")\n+lang(574, Dashboard_Network_Host, \"Komputer sieciowy (PS2Link)\")\n+lang(575, Dashboard_PS2Link_host, \"Pliki udostepnione przez PC PS2Link\")\n+lang(576, Dashboard_Disc_Drive, \"Naped plyt\")\n+lang(577, Dashboard_Network_UDPFS, \"Siec (UDPFS)\")\n+lang(578, Dashboard_Change_view, \"Zmien widok\")\n+lang(579, Dashboard_Application_options, \"Opcje aplikacji...\")\n+lang(580, Dashboard_Show_applications, \"Pokaz aplikacje\")\n+lang(581, Dashboard_Show_s, \"Pokaz: %s\")\n+lang(582, Dashboard_Moving_file, \"Przenoszenie pliku\")\n+lang(583, Dashboard_Deleting_file, \"Usuwanie pliku\")\n+lang(584, Dashboard_Verifying_file, \"Weryfikacja pliku\")\n+lang(585, Dashboard_Remaining_size_2f_MB, \"Pozostalo: %.2f MB\")\n+lang(586, Dashboard_Completed_items_d_d, \"Ukonczono: %d / %d\")\n+lang(587, Dashboard_Paste_failed, \"Blad wklejania\")\n+lang(588, Dashboard_Please_wait, \"Prosze czekac\")\n+lang(589, Dashboard_Power_off, \"Wylacz\")\n+lang(590, Dashboard_System_information, \"Informacje o systemie\")\n+lang(591, Dashboard_Build_information, \"Informacje o kompilacji\")\n+lang(592, Dashboard_Font_viewer, \"Podglad czcionek\")\n+lang(593, Dashboard_After_IGR_open, \"Po IGR otworz\")\n+lang(594, Dashboard_App_artwork_background, \"Tlo grafiki aplikacji\")\n+lang(595, Dashboard_Artwork_background, \"Tlo grafiki\")\n+lang(596, Dashboard_Artwork_dimming, \"Przyciemnienie grafiki\")\n+lang(597, Dashboard_CRT_scanlines, \"Linie CRT\")\n+lang(598, Dashboard_Control_icon_backings, \"Tlo ikon sterowania\")\n+lang(599, Dashboard_Custom_app_icon_image, \"Wlasna ikona aplikacji...\")\n+lang(600, Dashboard_Default_app_icon, \"Domyslna ikona aplikacji\")\n+lang(601, Dashboard_Disable_Ember_support, \"Wylacz Ember\")\n+lang(602, Dashboard_Disable_Neutrino_support, \"Wylacz Neutrino\")\n+lang(603, Dashboard_Disable_POPStarter_support, \"Wylacz POPStarter\")\n+lang(604, Dashboard_ESR_loader, \"Loader ESR\")\n+lang(605, Dashboard_Ember_directory, \"Folder Ember\")\n+lang(606, Dashboard_Export_theme_to_folder, \"Eksportuj motyw do folderu...\")\n+lang(607, Dashboard_Load_theme_from_folder, \"Wczytaj motyw z folderu...\")\n+lang(608, Dashboard_Game_ID_launch_signal_for_apps, \"Sygnal Game ID dla aplikacji\")\n+lang(609, Dashboard_Game_ID_launch_signal_for_discs, \"Sygnal Game ID dla plyt\")\n+lang(610, Dashboard_Game_ID_output, \"Wyjscie Game ID\")\n+lang(611, Dashboard_Home_quick_launch_shortcuts, \"Skroty szybkiego startu\")\n+lang(612, Dashboard_IGR_application, \"Aplikacja IGR\")\n+lang(613, Dashboard_IGR_helper, \"Pomocnik IGR\")\n+lang(614, Dashboard_Interface_colours, \"Kolory interfejsu\")\n+lang(615, Dashboard_Internal_HDD_ATA_units, \"Dyski wewnetrzne HDD / ATA\")\n+lang(616, Dashboard_Legacy_Ethernet_drivers, \"Starsze sterowniki Ethernet\")\n+lang(617, Dashboard_Lock_shortcuts_bookmarks, \"Zablokuj skroty i zakladki\")\n+lang(618, Dashboard_OSDSYS_program, \"Program OSDSYS\")\n+lang(619, Dashboard_PSU_add_date_to_names, \"PSU: dodaj date do nazw\")\n+lang(620, Dashboard_PSU_add_game_ID_and_title_to_names, \"PSU: dodaj Game ID i tytul\")\n+lang(621, Dashboard_PSU_never_overwrite_existing_files, \"PSU: nie nadpisuj plikow\")\n+lang(622, Dashboard_Recover_saved_card_image, \"Odzyskaj obraz karty...\")\n+lang(623, Dashboard_Reset_appearance, \"Przywroc wyglad\")\n+lang(624, Dashboard_Reset_background, \"Przywroc tlo\")\n+lang(625, Dashboard_Reset_clock, \"Przywroc zegar\")\n+lang(626, Dashboard_Reset_colours, \"Przywroc kolory\")\n+lang(627, Dashboard_Reset_entire_theme, \"Przywroc caly motyw\")\n+lang(628, Dashboard_Reset_icons, \"Przywroc ikony\")\n+lang(629, Dashboard_Reset_screen_settings, \"Przywroc ustawienia ekranu\")\n+lang(630, Dashboard_Reset_style, \"Przywroc styl\")\n+lang(631, Dashboard_Reset_text, \"Przywroc tekst\")\n+lang(632, Dashboard_Reset_wallpaper, \"Przywroc tapete\")\n+lang(633, Dashboard_Restore_default_theme, \"Przywroc domyslny motyw\")\n+lang(634, Dashboard_Save_launch_tool_settings_to, \"Zapisz ustawienia startu i narzedzi...\")\n+lang(635, Dashboard_Screen_position_X, \"Pozycja pozioma\")\n+lang(636, Dashboard_Screen_position_Y, \"Pozycja pionowa\")\n+lang(637, Dashboard_Set_PS2_clock_manually, \"Ustaw zegar PS2 recznie...\")\n+lang(638, Dashboard_Set_software_clock, \"Ustaw zegar programowy...\")\n+lang(639, Dashboard_Show_welcome_message_again, \"Pokaz ponownie powitanie\")\n+lang(640, Dashboard_Sync_PS2_clock_from_Internet, \"Synchronizuj zegar PS2 z Internetem\")\n+lang(641, Dashboard_System_font_file, \"Plik czcionki systemowej\")\n+lang(642, Dashboard_Theme_files_resets, \"Pliki i reset motywu\")\n+lang(643, Dashboard_Theme_name, \"Nazwa motywu...\")\n+lang(644, Dashboard_Tile_style, \"Styl kafelkow\")\n+lang(645, Dashboard_Tiles_menus, \"Kafelki i menu\")\n+lang(646, Dashboard_Use_PS2_hardware_clock, \"Uzyj zegara sprzetowego PS2\")\n+lang(647, Dashboard_Video_mode, \"Tryb wideo\")\n+lang(648, Dashboard_Wallpaper_image, \"Obraz tapety...\")\n+lang(649, Dashboard_Start, \"Uruchom\")\n+lang(650, Dashboard_Favorite, \"Ulubione\")\n+lang(651, Dashboard_Show_background, \"Pokaz tlo\")\n+lang(652, Dashboard_Ready, \"Gotowe\")\n+lang(653, Dashboard_Checking, \"Sprawdzanie...\")\n+lang(654, Dashboard_Slot_empty, \"Puste gniazdo\")\n+lang(655, Dashboard_Mounted_image, \"Zamontowany obraz\")\n+lang(656, Dashboard_Disc_PS1_loaders, \"Loadery plyt i PS1\")\n+lang(657, Dashboard_MMCE_backup_sync, \"Kopie i synchronizacja MMCE\")\n+lang(658, Dashboard_Dashboard_labels_file_operations_and_tools, \"Menu, operacje na plikach i narzedzia.\")\n+lang(659, Dashboard_Optional_LNG_overrides_including_dashboard_labels, \"Opcjonalny plik .LNG, takze dla menu.\")\n+lang(660, Dashboard_Some_extended_help_remains_in_English, \"Czesc szczegolowej pomocy pozostaje po angielsku.\")\n+lang(661, Dashboard_Last_folder, \"Ostatni folder\")\n+lang(662, Dashboard_BROWSE, \"PRZEGLADAJ\")\n+lang(663, Dashboard_BOOKMARKS_PATHPAD, \"ZAKLADKI (PATHPAD)\")"
        },
        {
          "sha": "4bfe999f2fe6469e6f4c5fe9b4f90f38b3b80280",
          "filename": "Lang/POR.LNG",
          "status": "modified",
          "additions": 196,
          "deletions": 0,
          "changes": 196,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/Lang%2FPOR.LNG",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/Lang%2FPOR.LNG",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/Lang%2FPOR.LNG?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -466,3 +466,199 @@ lang(464, PC_Cmd_Bad, \"Comando do PC invalido (feito para outra versao?)\")\n lang(465, PC_Cmd_Unknown, \"Comando do PC desconhecido: %.60s\")\n lang(466, Old_to_New, \"Antiga -> Recente\")\n lang(467, New_to_Old, \"Recente -> Antiga\")\n+lang(468, Dashboard_Home, \"Inicio\")\n+lang(469, Dashboard_Applications, \"Aplicacoes\")\n+lang(470, Dashboard_Files, \"Ficheiros\")\n+lang(471, Dashboard_Tools, \"Ferramentas\")\n+lang(472, Dashboard_Settings, \"Definicoes\")\n+lang(473, Dashboard_Favorites, \"Favoritos\")\n+lang(474, Dashboard_Devices, \"Dispositivos\")\n+lang(475, Dashboard_Options, \"Opcoes\")\n+lang(476, Dashboard_Grid, \"Grelha\")\n+lang(477, Dashboard_Carousel, \"Carrossel\")\n+lang(478, Dashboard_All, \"Todas\")\n+lang(479, Dashboard_Default, \"Predefinido\")\n+lang(480, Dashboard_Not_set, \"Nao definido\")\n+lang(481, Dashboard_Continue, \"Continuar\")\n+lang(482, Dashboard_Reset, \"Repor\")\n+lang(483, Dashboard_Refresh, \"Atualizar\")\n+lang(484, Dashboard_Appearance, \"Aparencia\")\n+lang(485, Dashboard_Home_applications, \"Inicio e aplicacoes\")\n+lang(486, Dashboard_Sound, \"Som\")\n+lang(487, Dashboard_Input, \"Entrada\")\n+lang(488, Dashboard_Launching, \"Arranque\")\n+lang(489, Dashboard_Storage_devices, \"Armazenamento e dispositivos\")\n+lang(490, Dashboard_Network, \"Rede\")\n+lang(491, Dashboard_System, \"Sistema\")\n+lang(492, Dashboard_Theme_preset, \"Tema predefinido\")\n+lang(493, Dashboard_Customize_theme, \"Personalizar tema\")\n+lang(494, Dashboard_Screen_calibration, \"Calibracao do ecra\")\n+lang(495, Dashboard_Background_motion, \"Movimento do fundo\")\n+lang(496, Dashboard_Menu_animation, \"Animacao dos menus\")\n+lang(497, Dashboard_Background, \"Fundo\")\n+lang(498, Dashboard_Colour, \"Cor\")\n+lang(499, Dashboard_Second_colour, \"Segunda cor\")\n+lang(500, Dashboard_Accent, \"Destaque\")\n+lang(501, Dashboard_Glow, \"Brilho suave\")\n+lang(502, Dashboard_Borders, \"Contornos\")\n+lang(503, Dashboard_Corners, \"Cantos\")\n+lang(504, Dashboard_Panels, \"Paineis\")\n+lang(505, Dashboard_Panel_transparency, \"Transparencia dos paineis\")\n+lang(506, Dashboard_Secondary_text, \"Texto secundario\")\n+lang(507, Dashboard_Muted_text, \"Texto atenuado\")\n+lang(508, Dashboard_Selected_text, \"Texto selecionado\")\n+lang(509, Dashboard_Selection, \"Selecao\")\n+lang(510, Dashboard_Selection_style, \"Estilo de selecao\")\n+lang(511, Dashboard_Font, \"Tipo de letra\")\n+lang(512, Dashboard_Bold, \"Negrito\")\n+lang(513, Dashboard_Text_shadow, \"Sombra do texto\")\n+lang(514, Dashboard_Text_size, \"Tamanho do texto\")\n+lang(515, Dashboard_Icons, \"Icones\")\n+lang(516, Dashboard_Icon_colour, \"Cor dos icones\")\n+lang(517, Dashboard_Clock, \"Relogio\")\n+lang(518, Dashboard_Date, \"Data\")\n+lang(519, Dashboard_Hours, \"Horas\")\n+lang(520, Dashboard_Seconds, \"Segundos\")\n+lang(521, Dashboard_Position, \"Posicao\")\n+lang(522, Dashboard_Warnings, \"Avisos\")\n+lang(523, Dashboard_Application_view, \"Vista das aplicacoes\")\n+lang(524, Dashboard_Application_artwork, \"Imagens das aplicacoes\")\n+lang(525, Dashboard_Application_library, \"Biblioteca de aplicacoes\")\n+lang(526, Dashboard_Application_folders, \"Pastas de aplicacoes...\")\n+lang(527, Dashboard_Recent_applications, \"Aplicacoes recentes\")\n+lang(528, Dashboard_Clear_recent_applications, \"Limpar aplicacoes recentes\")\n+lang(529, Dashboard_Refresh_library, \"Atualizar biblioteca\")\n+lang(530, Dashboard_Refresh_library_now, \"Atualizar biblioteca agora\")\n+lang(531, Dashboard_Refresh_at_startup, \"Atualizar no arranque\")\n+lang(532, Dashboard_Start_screen, \"Ecra inicial\")\n+lang(533, Dashboard_Interface_sounds, \"Sons da interface\")\n+lang(534, Dashboard_Interface_sound_volume, \"Volume da interface\")\n+lang(535, Dashboard_Background_music, \"Musica de fundo\")\n+lang(536, Dashboard_Music_volume, \"Volume da musica\")\n+lang(537, Dashboard_Confirm_button, \"Botao de confirmacao\")\n+lang(538, Dashboard_Keyboard_map, \"Mapa do teclado\")\n+lang(539, Dashboard_USB_keyboard, \"Teclado USB\")\n+lang(540, Dashboard_USB_keyboard_driver, \"Controlador do teclado USB\")\n+lang(541, Dashboard_On_screen_keyboard_layout, \"Teclado no ecra\")\n+lang(542, Dashboard_Launch_keys, \"Teclas de arranque\")\n+lang(543, Dashboard_Automatic_start_boot, \"Arranque automatico\")\n+lang(544, Dashboard_Automatic_start_timeout, \"Espera de arranque automatico\")\n+lang(545, Dashboard_Startup_delay, \"Atraso no arranque\")\n+lang(546, Dashboard_Disc_image_loaders, \"Carregadores de discos e imagens\")\n+lang(547, Dashboard_Reset_IOP_before_launch, \"Reiniciar IOP antes do arranque\")\n+lang(548, Dashboard_Show_unavailable_devices, \"Mostrar dispositivos indisponiveis\")\n+lang(549, Dashboard_Start_devices_automatically, \"Iniciar dispositivos automaticamente\")\n+lang(550, Dashboard_Auto_start_USB_storage, \"Iniciar armazenamento USB\")\n+lang(551, Dashboard_Auto_start_internal_HDD, \"Iniciar disco interno\")\n+lang(552, Dashboard_Auto_start_exFAT_HDD, \"Iniciar HDD exFAT\")\n+lang(553, Dashboard_Auto_start_MMCE_SD_card, \"Iniciar cartao SD MMCE\")\n+lang(554, Dashboard_Auto_start_MX4SIO, \"Iniciar MX4SIO\")\n+lang(555, Dashboard_Auto_start_iLink_FireWire, \"Iniciar iLink / FireWire\")\n+lang(556, Dashboard_Auto_start_network_UDPFS, \"Iniciar rede (UDPFS)\")\n+lang(557, Dashboard_Hide_empty_memory_card_slots, \"Ocultar ranhuras de cartao vazias\")\n+lang(558, Dashboard_Allow_writing_over_the_network, \"Permitir escrita pela rede\")\n+lang(559, Dashboard_IP_address, \"Endereco IP\")\n+lang(560, Dashboard_Backup_folder, \"Pasta de copias\")\n+lang(561, Dashboard_Backup_format, \"Formato de copia\")\n+lang(562, Dashboard_Backups_per_card, \"Copias por cartao\")\n+lang(563, Dashboard_MMCE_backups, \"Copias MMCE\")\n+lang(564, Dashboard_Memory_card_backup_files, \"Copias do cartao de memoria\")\n+lang(565, Dashboard_Automatic_sync, \"Sincronizacao automatica\")\n+lang(566, Dashboard_Sync_all_cards, \"Sincronizar todos os cartoes\")\n+lang(567, Dashboard_Google_account, \"Conta Google\")\n+lang(568, Dashboard_Configuration_folder, \"Pasta de configuracao\")\n+lang(569, Dashboard_Language_file, \"Ficheiro de idioma\")\n+lang(570, Dashboard_Check_for_updates, \"Procurar atualizacoes\")\n+lang(571, Dashboard_About_Launchstation, \"Sobre o Launchstation\")\n+lang(572, Dashboard_File_Browser, \"Explorador de ficheiros\")\n+lang(573, Dashboard_HDD_Manager, \"Gestor de HDD\")\n+lang(574, Dashboard_Network_Host, \"PC de rede (PS2Link)\")\n+lang(575, Dashboard_PS2Link_host, \"Ficheiros partilhados por um PC PS2Link\")\n+lang(576, Dashboard_Disc_Drive, \"Unidade de disco\")\n+lang(577, Dashboard_Network_UDPFS, \"Rede (UDPFS)\")\n+lang(578, Dashboard_Change_view, \"Mudar vista\")\n+lang(579, Dashboard_Application_options, \"Opcoes da aplicacao...\")\n+lang(580, Dashboard_Show_applications, \"Mostrar aplicacoes\")\n+lang(581, Dashboard_Show_s, \"Mostrar: %s\")\n+lang(582, Dashboard_Moving_file, \"A mover ficheiro\")\n+lang(583, Dashboard_Deleting_file, \"A eliminar ficheiro\")\n+lang(584, Dashboard_Verifying_file, \"A verificar ficheiro\")\n+lang(585, Dashboard_Remaining_size_2f_MB, \"Restam: %.2f MB\")\n+lang(586, Dashboard_Completed_items_d_d, \"Concluidos: %d / %d\")\n+lang(587, Dashboard_Paste_failed, \"Falha ao colar\")\n+lang(588, Dashboard_Please_wait, \"Aguarde\")\n+lang(589, Dashboard_Power_off, \"Desligar\")\n+lang(590, Dashboard_System_information, \"Informacoes do sistema\")\n+lang(591, Dashboard_Build_information, \"Informacoes da compilacao\")\n+lang(592, Dashboard_Font_viewer, \"Visualizador de tipos de letra\")\n+lang(593, Dashboard_After_IGR_open, \"Apos IGR, abrir\")\n+lang(594, Dashboard_App_artwork_background, \"Fundo da imagem da aplicacao\")\n+lang(595, Dashboard_Artwork_background, \"Fundo com imagem\")\n+lang(596, Dashboard_Artwork_dimming, \"Escurecimento da imagem\")\n+lang(597, Dashboard_CRT_scanlines, \"Linhas CRT\")\n+lang(598, Dashboard_Control_icon_backings, \"Fundo dos icones de controlo\")\n+lang(599, Dashboard_Custom_app_icon_image, \"Icone de aplicacao personalizado...\")\n+lang(600, Dashboard_Default_app_icon, \"Icone de aplicacao predefinido\")\n+lang(601, Dashboard_Disable_Ember_support, \"Desativar Ember\")\n+lang(602, Dashboard_Disable_Neutrino_support, \"Desativar Neutrino\")\n+lang(603, Dashboard_Disable_POPStarter_support, \"Desativar POPStarter\")\n+lang(604, Dashboard_ESR_loader, \"Carregador ESR\")\n+lang(605, Dashboard_Ember_directory, \"Pasta do Ember\")\n+lang(606, Dashboard_Export_theme_to_folder, \"Exportar tema para pasta...\")\n+lang(607, Dashboard_Load_theme_from_folder, \"Carregar tema de pasta...\")\n+lang(608, Dashboard_Game_ID_launch_signal_for_apps, \"Sinal Game ID para aplicacoes\")\n+lang(609, Dashboard_Game_ID_launch_signal_for_discs, \"Sinal Game ID para discos\")\n+lang(610, Dashboard_Game_ID_output, \"Saida Game ID\")\n+lang(611, Dashboard_Home_quick_launch_shortcuts, \"Atalhos rapidos do inicio\")\n+lang(612, Dashboard_IGR_application, \"Aplicacao IGR\")\n+lang(613, Dashboard_IGR_helper, \"Auxiliar IGR\")\n+lang(614, Dashboard_Interface_colours, \"Cores da interface\")\n+lang(615, Dashboard_Internal_HDD_ATA_units, \"Discos internos HDD / ATA\")\n+lang(616, Dashboard_Legacy_Ethernet_drivers, \"Controladores Ethernet antigos\")\n+lang(617, Dashboard_Lock_shortcuts_bookmarks, \"Bloquear atalhos e marcadores\")\n+lang(618, Dashboard_OSDSYS_program, \"Programa OSDSYS\")\n+lang(619, Dashboard_PSU_add_date_to_names, \"PSU: adicionar data aos nomes\")\n+lang(620, Dashboard_PSU_add_game_ID_and_title_to_names, \"PSU: adicionar Game ID e titulo\")\n+lang(621, Dashboard_PSU_never_overwrite_existing_files, \"PSU: nunca substituir ficheiros\")\n+lang(622, Dashboard_Recover_saved_card_image, \"Recuperar imagem de cartao...\")\n+lang(623, Dashboard_Reset_appearance, \"Repor aparencia\")\n+lang(624, Dashboard_Reset_background, \"Repor fundo\")\n+lang(625, Dashboard_Reset_clock, \"Repor relogio\")\n+lang(626, Dashboard_Reset_colours, \"Repor cores\")\n+lang(627, Dashboard_Reset_entire_theme, \"Repor todo o tema\")\n+lang(628, Dashboard_Reset_icons, \"Repor icones\")\n+lang(629, Dashboard_Reset_screen_settings, \"Repor definicoes do ecra\")\n+lang(630, Dashboard_Reset_style, \"Repor estilo\")\n+lang(631, Dashboard_Reset_text, \"Repor texto\")\n+lang(632, Dashboard_Reset_wallpaper, \"Repor imagem de fundo\")\n+lang(633, Dashboard_Restore_default_theme, \"Restaurar tema predefinido\")\n+lang(634, Dashboard_Save_launch_tool_settings_to, \"Guardar definicoes de arranque e ferramentas...\")\n+lang(635, Dashboard_Screen_position_X, \"Posicao horizontal\")\n+lang(636, Dashboard_Screen_position_Y, \"Posicao vertical\")\n+lang(637, Dashboard_Set_PS2_clock_manually, \"Acertar relogio PS2 manualmente...\")\n+lang(638, Dashboard_Set_software_clock, \"Acertar relogio de software...\")\n+lang(639, Dashboard_Show_welcome_message_again, \"Mostrar boas-vindas novamente\")\n+lang(640, Dashboard_Sync_PS2_clock_from_Internet, \"Sincronizar relogio PS2 pela Internet\")\n+lang(641, Dashboard_System_font_file, \"Ficheiro do tipo de letra do sistema\")\n+lang(642, Dashboard_Theme_files_resets, \"Ficheiros e reposicao de tema\")\n+lang(643, Dashboard_Theme_name, \"Nome do tema...\")\n+lang(644, Dashboard_Tile_style, \"Estilo dos mosaicos\")\n+lang(645, Dashboard_Tiles_menus, \"Mosaicos e menus\")\n+lang(646, Dashboard_Use_PS2_hardware_clock, \"Usar relogio fisico da PS2\")\n+lang(647, Dashboard_Video_mode, \"Modo de video\")\n+lang(648, Dashboard_Wallpaper_image, \"Imagem de fundo...\")\n+lang(649, Dashboard_Start, \"Iniciar\")\n+lang(650, Dashboard_Favorite, \"Favorito\")\n+lang(651, Dashboard_Show_background, \"Mostrar fundo\")\n+lang(652, Dashboard_Ready, \"Pronto\")\n+lang(653, Dashboard_Checking, \"A verificar...\")\n+lang(654, Dashboard_Slot_empty, \"Ranhura vazia\")\n+lang(655, Dashboard_Mounted_image, \"Imagem montada\")\n+lang(656, Dashboard_Disc_PS1_loaders, \"Carregadores de discos e PS1\")\n+lang(657, Dashboard_MMCE_backup_sync, \"Copia e sincronizacao MMCE\")\n+lang(658, Dashboard_Dashboard_labels_file_operations_and_tools, \"Menus, operacoes de ficheiros e ferramentas.\")\n+lang(659, Dashboard_Optional_LNG_overrides_including_dashboard_labels, \"Ficheiro .LNG opcional, incluindo menus.\")\n+lang(660, Dashboard_Some_extended_help_remains_in_English, \"Alguma ajuda detalhada continua em ingles.\")\n+lang(661, Dashboard_Last_folder, \"Ultima pasta\")\n+lang(662, Dashboard_BROWSE, \"EXPLORAR\")\n+lang(663, Dashboard_BOOKMARKS_PATHPAD, \"MARCADORES (PATHPAD)\")"
        },
        {
          "sha": "554207ad46b392b3b89467f392435822b1660ac6",
          "filename": "Lang/PTBR.LNG",
          "status": "modified",
          "additions": 196,
          "deletions": 0,
          "changes": 196,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/Lang%2FPTBR.LNG",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/Lang%2FPTBR.LNG",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/Lang%2FPTBR.LNG?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -466,3 +466,199 @@ lang(464, PC_Cmd_Bad, \"Comando do PC invalido (feito para outra versao?)\")\n lang(465, PC_Cmd_Unknown, \"Comando do PC desconhecido: %.60s\")\n lang(466, Old_to_New, \"Antiga -> Recente\")\n lang(467, New_to_Old, \"Recente -> Antiga\")\n+lang(468, Dashboard_Home, \"Inicio\")\n+lang(469, Dashboard_Applications, \"Aplicativos\")\n+lang(470, Dashboard_Files, \"Arquivos\")\n+lang(471, Dashboard_Tools, \"Ferramentas\")\n+lang(472, Dashboard_Settings, \"Configuracoes\")\n+lang(473, Dashboard_Favorites, \"Favoritos\")\n+lang(474, Dashboard_Devices, \"Dispositivos\")\n+lang(475, Dashboard_Options, \"Opcoes\")\n+lang(476, Dashboard_Grid, \"Grade\")\n+lang(477, Dashboard_Carousel, \"Carrossel\")\n+lang(478, Dashboard_All, \"Todos\")\n+lang(479, Dashboard_Default, \"Padrao\")\n+lang(480, Dashboard_Not_set, \"Nao definido\")\n+lang(481, Dashboard_Continue, \"Continuar\")\n+lang(482, Dashboard_Reset, \"Redefinir\")\n+lang(483, Dashboard_Refresh, \"Atualizar\")\n+lang(484, Dashboard_Appearance, \"Aparencia\")\n+lang(485, Dashboard_Home_applications, \"Inicio e aplicativos\")\n+lang(486, Dashboard_Sound, \"Som\")\n+lang(487, Dashboard_Input, \"Entrada\")\n+lang(488, Dashboard_Launching, \"Inicializacao\")\n+lang(489, Dashboard_Storage_devices, \"Armazenamento e dispositivos\")\n+lang(490, Dashboard_Network, \"Rede\")\n+lang(491, Dashboard_System, \"Sistema\")\n+lang(492, Dashboard_Theme_preset, \"Tema predefinido\")\n+lang(493, Dashboard_Customize_theme, \"Personalizar tema\")\n+lang(494, Dashboard_Screen_calibration, \"Calibracao da tela\")\n+lang(495, Dashboard_Background_motion, \"Movimento do fundo\")\n+lang(496, Dashboard_Menu_animation, \"Animacao dos menus\")\n+lang(497, Dashboard_Background, \"Fundo\")\n+lang(498, Dashboard_Colour, \"Cor\")\n+lang(499, Dashboard_Second_colour, \"Segunda cor\")\n+lang(500, Dashboard_Accent, \"Destaque\")\n+lang(501, Dashboard_Glow, \"Brilho suave\")\n+lang(502, Dashboard_Borders, \"Bordas\")\n+lang(503, Dashboard_Corners, \"Cantos\")\n+lang(504, Dashboard_Panels, \"Paineis\")\n+lang(505, Dashboard_Panel_transparency, \"Transparencia dos paineis\")\n+lang(506, Dashboard_Secondary_text, \"Texto secundario\")\n+lang(507, Dashboard_Muted_text, \"Texto atenuado\")\n+lang(508, Dashboard_Selected_text, \"Texto selecionado\")\n+lang(509, Dashboard_Selection, \"Selecao\")\n+lang(510, Dashboard_Selection_style, \"Estilo de selecao\")\n+lang(511, Dashboard_Font, \"Fonte\")\n+lang(512, Dashboard_Bold, \"Negrito\")\n+lang(513, Dashboard_Text_shadow, \"Sombra do texto\")\n+lang(514, Dashboard_Text_size, \"Tamanho do texto\")\n+lang(515, Dashboard_Icons, \"Icones\")\n+lang(516, Dashboard_Icon_colour, \"Cor dos icones\")\n+lang(517, Dashboard_Clock, \"Relogio\")\n+lang(518, Dashboard_Date, \"Data\")\n+lang(519, Dashboard_Hours, \"Horas\")\n+lang(520, Dashboard_Seconds, \"Segundos\")\n+lang(521, Dashboard_Position, \"Posicao\")\n+lang(522, Dashboard_Warnings, \"Avisos\")\n+lang(523, Dashboard_Application_view, \"Visualizacao dos aplicativos\")\n+lang(524, Dashboard_Application_artwork, \"Imagens dos aplicativos\")\n+lang(525, Dashboard_Application_library, \"Biblioteca de aplicativos\")\n+lang(526, Dashboard_Application_folders, \"Pastas de aplicativos...\")\n+lang(527, Dashboard_Recent_applications, \"Aplicativos recentes\")\n+lang(528, Dashboard_Clear_recent_applications, \"Limpar aplicativos recentes\")\n+lang(529, Dashboard_Refresh_library, \"Atualizar biblioteca\")\n+lang(530, Dashboard_Refresh_library_now, \"Atualizar biblioteca agora\")\n+lang(531, Dashboard_Refresh_at_startup, \"Atualizar ao iniciar\")\n+lang(532, Dashboard_Start_screen, \"Tela inicial\")\n+lang(533, Dashboard_Interface_sounds, \"Sons da interface\")\n+lang(534, Dashboard_Interface_sound_volume, \"Volume da interface\")\n+lang(535, Dashboard_Background_music, \"Musica de fundo\")\n+lang(536, Dashboard_Music_volume, \"Volume da musica\")\n+lang(537, Dashboard_Confirm_button, \"Botao de confirmacao\")\n+lang(538, Dashboard_Keyboard_map, \"Mapa do teclado\")\n+lang(539, Dashboard_USB_keyboard, \"Teclado USB\")\n+lang(540, Dashboard_USB_keyboard_driver, \"Driver do teclado USB\")\n+lang(541, Dashboard_On_screen_keyboard_layout, \"Teclado na tela\")\n+lang(542, Dashboard_Launch_keys, \"Teclas de inicializacao\")\n+lang(543, Dashboard_Automatic_start_boot, \"Inicializacao automatica\")\n+lang(544, Dashboard_Automatic_start_timeout, \"Espera da inicializacao automatica\")\n+lang(545, Dashboard_Startup_delay, \"Atraso na inicializacao\")\n+lang(546, Dashboard_Disc_image_loaders, \"Carregadores de discos e imagens\")\n+lang(547, Dashboard_Reset_IOP_before_launch, \"Reiniciar IOP antes de iniciar\")\n+lang(548, Dashboard_Show_unavailable_devices, \"Mostrar dispositivos indisponiveis\")\n+lang(549, Dashboard_Start_devices_automatically, \"Iniciar dispositivos automaticamente\")\n+lang(550, Dashboard_Auto_start_USB_storage, \"Iniciar armazenamento USB\")\n+lang(551, Dashboard_Auto_start_internal_HDD, \"Iniciar disco interno\")\n+lang(552, Dashboard_Auto_start_exFAT_HDD, \"Iniciar HDD exFAT\")\n+lang(553, Dashboard_Auto_start_MMCE_SD_card, \"Iniciar cartao SD MMCE\")\n+lang(554, Dashboard_Auto_start_MX4SIO, \"Iniciar MX4SIO\")\n+lang(555, Dashboard_Auto_start_iLink_FireWire, \"Iniciar iLink / FireWire\")\n+lang(556, Dashboard_Auto_start_network_UDPFS, \"Iniciar rede (UDPFS)\")\n+lang(557, Dashboard_Hide_empty_memory_card_slots, \"Ocultar slots de cartao vazios\")\n+lang(558, Dashboard_Allow_writing_over_the_network, \"Permitir escrita pela rede\")\n+lang(559, Dashboard_IP_address, \"Endereco IP\")\n+lang(560, Dashboard_Backup_folder, \"Pasta de backups\")\n+lang(561, Dashboard_Backup_format, \"Formato de backup\")\n+lang(562, Dashboard_Backups_per_card, \"Backups por cartao\")\n+lang(563, Dashboard_MMCE_backups, \"Backups MMCE\")\n+lang(564, Dashboard_Memory_card_backup_files, \"Backups do cartao de memoria\")\n+lang(565, Dashboard_Automatic_sync, \"Sincronizacao automatica\")\n+lang(566, Dashboard_Sync_all_cards, \"Sincronizar todos os cartoes\")\n+lang(567, Dashboard_Google_account, \"Conta Google\")\n+lang(568, Dashboard_Configuration_folder, \"Pasta de configuracao\")\n+lang(569, Dashboard_Language_file, \"Arquivo de idioma\")\n+lang(570, Dashboard_Check_for_updates, \"Procurar atualizacoes\")\n+lang(571, Dashboard_About_Launchstation, \"Sobre o Launchstation\")\n+lang(572, Dashboard_File_Browser, \"Explorador de arquivos\")\n+lang(573, Dashboard_HDD_Manager, \"Gerenciador de HDD\")\n+lang(574, Dashboard_Network_Host, \"PC da rede (PS2Link)\")\n+lang(575, Dashboard_PS2Link_host, \"Arquivos compartilhados por um PC PS2Link\")\n+lang(576, Dashboard_Disc_Drive, \"Unidade de disco\")\n+lang(577, Dashboard_Network_UDPFS, \"Rede (UDPFS)\")\n+lang(578, Dashboard_Change_view, \"Mudar visualizacao\")\n+lang(579, Dashboard_Application_options, \"Opcoes do aplicativo...\")\n+lang(580, Dashboard_Show_applications, \"Mostrar aplicativos\")\n+lang(581, Dashboard_Show_s, \"Mostrar: %s\")\n+lang(582, Dashboard_Moving_file, \"Movendo arquivo\")\n+lang(583, Dashboard_Deleting_file, \"Excluindo arquivo\")\n+lang(584, Dashboard_Verifying_file, \"Verificando arquivo\")\n+lang(585, Dashboard_Remaining_size_2f_MB, \"Restam: %.2f MB\")\n+lang(586, Dashboard_Completed_items_d_d, \"Concluidos: %d / %d\")\n+lang(587, Dashboard_Paste_failed, \"Falha ao colar\")\n+lang(588, Dashboard_Please_wait, \"Aguarde\")\n+lang(589, Dashboard_Power_off, \"Desligar\")\n+lang(590, Dashboard_System_information, \"Informacoes do sistema\")\n+lang(591, Dashboard_Build_information, \"Informacoes da compilacao\")\n+lang(592, Dashboard_Font_viewer, \"Visualizador de fontes\")\n+lang(593, Dashboard_After_IGR_open, \"Apos IGR, abrir\")\n+lang(594, Dashboard_App_artwork_background, \"Fundo da imagem do aplicativo\")\n+lang(595, Dashboard_Artwork_background, \"Fundo com imagem\")\n+lang(596, Dashboard_Artwork_dimming, \"Escurecimento da imagem\")\n+lang(597, Dashboard_CRT_scanlines, \"Linhas CRT\")\n+lang(598, Dashboard_Control_icon_backings, \"Fundo dos icones de controle\")\n+lang(599, Dashboard_Custom_app_icon_image, \"Icone de aplicativo personalizado...\")\n+lang(600, Dashboard_Default_app_icon, \"Icone de aplicativo padrao\")\n+lang(601, Dashboard_Disable_Ember_support, \"Desativar Ember\")\n+lang(602, Dashboard_Disable_Neutrino_support, \"Desativar Neutrino\")\n+lang(603, Dashboard_Disable_POPStarter_support, \"Desativar POPStarter\")\n+lang(604, Dashboard_ESR_loader, \"Carregador ESR\")\n+lang(605, Dashboard_Ember_directory, \"Pasta do Ember\")\n+lang(606, Dashboard_Export_theme_to_folder, \"Exportar tema para pasta...\")\n+lang(607, Dashboard_Load_theme_from_folder, \"Carregar tema de pasta...\")\n+lang(608, Dashboard_Game_ID_launch_signal_for_apps, \"Sinal Game ID para aplicativos\")\n+lang(609, Dashboard_Game_ID_launch_signal_for_discs, \"Sinal Game ID para discos\")\n+lang(610, Dashboard_Game_ID_output, \"Saida Game ID\")\n+lang(611, Dashboard_Home_quick_launch_shortcuts, \"Atalhos rapidos do inicio\")\n+lang(612, Dashboard_IGR_application, \"Aplicativo IGR\")\n+lang(613, Dashboard_IGR_helper, \"Auxiliar IGR\")\n+lang(614, Dashboard_Interface_colours, \"Cores da interface\")\n+lang(615, Dashboard_Internal_HDD_ATA_units, \"Discos internos HDD / ATA\")\n+lang(616, Dashboard_Legacy_Ethernet_drivers, \"Drivers Ethernet antigos\")\n+lang(617, Dashboard_Lock_shortcuts_bookmarks, \"Bloquear atalhos e favoritos\")\n+lang(618, Dashboard_OSDSYS_program, \"Programa OSDSYS\")\n+lang(619, Dashboard_PSU_add_date_to_names, \"PSU: adicionar data aos nomes\")\n+lang(620, Dashboard_PSU_add_game_ID_and_title_to_names, \"PSU: adicionar Game ID e titulo\")\n+lang(621, Dashboard_PSU_never_overwrite_existing_files, \"PSU: nunca sobrescrever arquivos\")\n+lang(622, Dashboard_Recover_saved_card_image, \"Recuperar imagem de cartao...\")\n+lang(623, Dashboard_Reset_appearance, \"Redefinir aparencia\")\n+lang(624, Dashboard_Reset_background, \"Redefinir fundo\")\n+lang(625, Dashboard_Reset_clock, \"Redefinir relogio\")\n+lang(626, Dashboard_Reset_colours, \"Redefinir cores\")\n+lang(627, Dashboard_Reset_entire_theme, \"Redefinir todo o tema\")\n+lang(628, Dashboard_Reset_icons, \"Redefinir icones\")\n+lang(629, Dashboard_Reset_screen_settings, \"Redefinir configuracoes da tela\")\n+lang(630, Dashboard_Reset_style, \"Redefinir estilo\")\n+lang(631, Dashboard_Reset_text, \"Redefinir texto\")\n+lang(632, Dashboard_Reset_wallpaper, \"Redefinir papel de parede\")\n+lang(633, Dashboard_Restore_default_theme, \"Restaurar tema padrao\")\n+lang(634, Dashboard_Save_launch_tool_settings_to, \"Salvar configuracoes de inicio e ferramentas...\")\n+lang(635, Dashboard_Screen_position_X, \"Posicao horizontal\")\n+lang(636, Dashboard_Screen_position_Y, \"Posicao vertical\")\n+lang(637, Dashboard_Set_PS2_clock_manually, \"Ajustar relogio PS2 manualmente...\")\n+lang(638, Dashboard_Set_software_clock, \"Ajustar relogio de software...\")\n+lang(639, Dashboard_Show_welcome_message_again, \"Mostrar boas-vindas novamente\")\n+lang(640, Dashboard_Sync_PS2_clock_from_Internet, \"Sincronizar relogio PS2 pela Internet\")\n+lang(641, Dashboard_System_font_file, \"Arquivo da fonte do sistema\")\n+lang(642, Dashboard_Theme_files_resets, \"Arquivos e redefinicao de tema\")\n+lang(643, Dashboard_Theme_name, \"Nome do tema...\")\n+lang(644, Dashboard_Tile_style, \"Estilo dos blocos\")\n+lang(645, Dashboard_Tiles_menus, \"Blocos e menus\")\n+lang(646, Dashboard_Use_PS2_hardware_clock, \"Usar relogio fisico do PS2\")\n+lang(647, Dashboard_Video_mode, \"Modo de video\")\n+lang(648, Dashboard_Wallpaper_image, \"Papel de parede...\")\n+lang(649, Dashboard_Start, \"Iniciar\")\n+lang(650, Dashboard_Favorite, \"Favorito\")\n+lang(651, Dashboard_Show_background, \"Mostrar fundo\")\n+lang(652, Dashboard_Ready, \"Pronto\")\n+lang(653, Dashboard_Checking, \"Verificando...\")\n+lang(654, Dashboard_Slot_empty, \"Slot vazio\")\n+lang(655, Dashboard_Mounted_image, \"Imagem montada\")\n+lang(656, Dashboard_Disc_PS1_loaders, \"Carregadores de discos e PS1\")\n+lang(657, Dashboard_MMCE_backup_sync, \"Backup e sincronizacao MMCE\")\n+lang(658, Dashboard_Dashboard_labels_file_operations_and_tools, \"Menus, operacoes de arquivos e ferramentas.\")\n+lang(659, Dashboard_Optional_LNG_overrides_including_dashboard_labels, \"Arquivo .LNG opcional, incluindo menus.\")\n+lang(660, Dashboard_Some_extended_help_remains_in_English, \"Algumas ajudas detalhadas continuam em ingles.\")\n+lang(661, Dashboard_Last_folder, \"Ultima pasta\")\n+lang(662, Dashboard_BROWSE, \"EXPLORAR\")\n+lang(663, Dashboard_BOOKMARKS_PATHPAD, \"FAVORITOS (PATHPAD)\")"
        },
        {
          "sha": "36c6c0bc8ab0b97ddabf5397f3f4345e3be8be7b",
          "filename": "Lang/SPA.LNG",
          "status": "modified",
          "additions": 196,
          "deletions": 0,
          "changes": 196,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/Lang%2FSPA.LNG",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/Lang%2FSPA.LNG",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/Lang%2FSPA.LNG?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -466,3 +466,199 @@ lang(464, PC_Cmd_Bad, \"Invalid command from the PC (made for another version?)\")\n lang(465, PC_Cmd_Unknown, \"Unknown command from the PC: %.60s\")\n lang(466, Old_to_New, \"Antigua -> Reciente\")\n lang(467, New_to_Old, \"Reciente -> Antigua\")\n+lang(468, Dashboard_Home, \"Inicio\")\n+lang(469, Dashboard_Applications, \"Aplicaciones\")\n+lang(470, Dashboard_Files, \"Archivos\")\n+lang(471, Dashboard_Tools, \"Herramientas\")\n+lang(472, Dashboard_Settings, \"Ajustes\")\n+lang(473, Dashboard_Favorites, \"Favoritos\")\n+lang(474, Dashboard_Devices, \"Dispositivos\")\n+lang(475, Dashboard_Options, \"Opciones\")\n+lang(476, Dashboard_Grid, \"Cuadricula\")\n+lang(477, Dashboard_Carousel, \"Carrusel\")\n+lang(478, Dashboard_All, \"Todas\")\n+lang(479, Dashboard_Default, \"Predeterminado\")\n+lang(480, Dashboard_Not_set, \"Sin definir\")\n+lang(481, Dashboard_Continue, \"Continuar\")\n+lang(482, Dashboard_Reset, \"Restablecer\")\n+lang(483, Dashboard_Refresh, \"Actualizar\")\n+lang(484, Dashboard_Appearance, \"Apariencia\")\n+lang(485, Dashboard_Home_applications, \"Inicio y aplicaciones\")\n+lang(486, Dashboard_Sound, \"Sonido\")\n+lang(487, Dashboard_Input, \"Entrada\")\n+lang(488, Dashboard_Launching, \"Inicio de programas\")\n+lang(489, Dashboard_Storage_devices, \"Almacenamiento y dispositivos\")\n+lang(490, Dashboard_Network, \"Red\")\n+lang(491, Dashboard_System, \"Sistema\")\n+lang(492, Dashboard_Theme_preset, \"Tema predefinido\")\n+lang(493, Dashboard_Customize_theme, \"Personalizar tema\")\n+lang(494, Dashboard_Screen_calibration, \"Calibracion de pantalla\")\n+lang(495, Dashboard_Background_motion, \"Movimiento del fondo\")\n+lang(496, Dashboard_Menu_animation, \"Animacion del menu\")\n+lang(497, Dashboard_Background, \"Fondo\")\n+lang(498, Dashboard_Colour, \"Color\")\n+lang(499, Dashboard_Second_colour, \"Segundo color\")\n+lang(500, Dashboard_Accent, \"Acento\")\n+lang(501, Dashboard_Glow, \"Resplandor\")\n+lang(502, Dashboard_Borders, \"Bordes\")\n+lang(503, Dashboard_Corners, \"Esquinas\")\n+lang(504, Dashboard_Panels, \"Paneles\")\n+lang(505, Dashboard_Panel_transparency, \"Transparencia de paneles\")\n+lang(506, Dashboard_Secondary_text, \"Texto secundario\")\n+lang(507, Dashboard_Muted_text, \"Texto atenuado\")\n+lang(508, Dashboard_Selected_text, \"Texto seleccionado\")\n+lang(509, Dashboard_Selection, \"Seleccion\")\n+lang(510, Dashboard_Selection_style, \"Estilo de seleccion\")\n+lang(511, Dashboard_Font, \"Fuente\")\n+lang(512, Dashboard_Bold, \"Negrita\")\n+lang(513, Dashboard_Text_shadow, \"Sombra del texto\")\n+lang(514, Dashboard_Text_size, \"Tamano del texto\")\n+lang(515, Dashboard_Icons, \"Iconos\")\n+lang(516, Dashboard_Icon_colour, \"Color de iconos\")\n+lang(517, Dashboard_Clock, \"Reloj\")\n+lang(518, Dashboard_Date, \"Fecha\")\n+lang(519, Dashboard_Hours, \"Horas\")\n+lang(520, Dashboard_Seconds, \"Segundos\")\n+lang(521, Dashboard_Position, \"Posicion\")\n+lang(522, Dashboard_Warnings, \"Advertencias\")\n+lang(523, Dashboard_Application_view, \"Vista de aplicaciones\")\n+lang(524, Dashboard_Application_artwork, \"Imagenes de aplicaciones\")\n+lang(525, Dashboard_Application_library, \"Biblioteca de aplicaciones\")\n+lang(526, Dashboard_Application_folders, \"Carpetas de aplicaciones...\")\n+lang(527, Dashboard_Recent_applications, \"Aplicaciones recientes\")\n+lang(528, Dashboard_Clear_recent_applications, \"Borrar aplicaciones recientes\")\n+lang(529, Dashboard_Refresh_library, \"Actualizar biblioteca\")\n+lang(530, Dashboard_Refresh_library_now, \"Actualizar biblioteca ahora\")\n+lang(531, Dashboard_Refresh_at_startup, \"Actualizar al iniciar\")\n+lang(532, Dashboard_Start_screen, \"Pantalla inicial\")\n+lang(533, Dashboard_Interface_sounds, \"Sonidos de interfaz\")\n+lang(534, Dashboard_Interface_sound_volume, \"Volumen de interfaz\")\n+lang(535, Dashboard_Background_music, \"Musica de fondo\")\n+lang(536, Dashboard_Music_volume, \"Volumen de musica\")\n+lang(537, Dashboard_Confirm_button, \"Boton de confirmar\")\n+lang(538, Dashboard_Keyboard_map, \"Mapa de teclado\")\n+lang(539, Dashboard_USB_keyboard, \"Teclado USB\")\n+lang(540, Dashboard_USB_keyboard_driver, \"Controlador de teclado USB\")\n+lang(541, Dashboard_On_screen_keyboard_layout, \"Teclado en pantalla\")\n+lang(542, Dashboard_Launch_keys, \"Teclas de inicio\")\n+lang(543, Dashboard_Automatic_start_boot, \"Inicio automatico y arranque\")\n+lang(544, Dashboard_Automatic_start_timeout, \"Espera de inicio automatico\")\n+lang(545, Dashboard_Startup_delay, \"Retardo de inicio\")\n+lang(546, Dashboard_Disc_image_loaders, \"Cargadores de discos e imagenes\")\n+lang(547, Dashboard_Reset_IOP_before_launch, \"Reiniciar IOP antes de iniciar\")\n+lang(548, Dashboard_Show_unavailable_devices, \"Mostrar dispositivos no disponibles\")\n+lang(549, Dashboard_Start_devices_automatically, \"Iniciar dispositivos automaticamente\")\n+lang(550, Dashboard_Auto_start_USB_storage, \"Iniciar almacenamiento USB\")\n+lang(551, Dashboard_Auto_start_internal_HDD, \"Iniciar disco duro interno\")\n+lang(552, Dashboard_Auto_start_exFAT_HDD, \"Iniciar HDD exFAT\")\n+lang(553, Dashboard_Auto_start_MMCE_SD_card, \"Iniciar tarjeta SD MMCE\")\n+lang(554, Dashboard_Auto_start_MX4SIO, \"Iniciar MX4SIO\")\n+lang(555, Dashboard_Auto_start_iLink_FireWire, \"Iniciar iLink / FireWire\")\n+lang(556, Dashboard_Auto_start_network_UDPFS, \"Iniciar red (UDPFS)\")\n+lang(557, Dashboard_Hide_empty_memory_card_slots, \"Ocultar ranuras de tarjeta vacias\")\n+lang(558, Dashboard_Allow_writing_over_the_network, \"Permitir escritura por red\")\n+lang(559, Dashboard_IP_address, \"Direccion IP\")\n+lang(560, Dashboard_Backup_folder, \"Carpeta de copias\")\n+lang(561, Dashboard_Backup_format, \"Formato de copia\")\n+lang(562, Dashboard_Backups_per_card, \"Copias por tarjeta\")\n+lang(563, Dashboard_MMCE_backups, \"Copias MMCE\")\n+lang(564, Dashboard_Memory_card_backup_files, \"Copias de tarjeta de memoria\")\n+lang(565, Dashboard_Automatic_sync, \"Sincronizacion automatica\")\n+lang(566, Dashboard_Sync_all_cards, \"Sincronizar todas las tarjetas\")\n+lang(567, Dashboard_Google_account, \"Cuenta de Google\")\n+lang(568, Dashboard_Configuration_folder, \"Carpeta de configuracion\")\n+lang(569, Dashboard_Language_file, \"Archivo de idioma\")\n+lang(570, Dashboard_Check_for_updates, \"Buscar actualizaciones\")\n+lang(571, Dashboard_About_Launchstation, \"Acerca de Launchstation\")\n+lang(572, Dashboard_File_Browser, \"Explorador de archivos\")\n+lang(573, Dashboard_HDD_Manager, \"Gestor de HDD\")\n+lang(574, Dashboard_Network_Host, \"PC de red (PS2Link)\")\n+lang(575, Dashboard_PS2Link_host, \"Archivos compartidos por un PC PS2Link\")\n+lang(576, Dashboard_Disc_Drive, \"Unidad de disco\")\n+lang(577, Dashboard_Network_UDPFS, \"Red (UDPFS)\")\n+lang(578, Dashboard_Change_view, \"Cambiar vista\")\n+lang(579, Dashboard_Application_options, \"Opciones de aplicacion...\")\n+lang(580, Dashboard_Show_applications, \"Mostrar aplicaciones\")\n+lang(581, Dashboard_Show_s, \"Mostrar: %s\")\n+lang(582, Dashboard_Moving_file, \"Moviendo archivo\")\n+lang(583, Dashboard_Deleting_file, \"Borrando archivo\")\n+lang(584, Dashboard_Verifying_file, \"Verificando archivo\")\n+lang(585, Dashboard_Remaining_size_2f_MB, \"Restante: %.2f MB\")\n+lang(586, Dashboard_Completed_items_d_d, \"Completados: %d / %d\")\n+lang(587, Dashboard_Paste_failed, \"Error al pegar\")\n+lang(588, Dashboard_Please_wait, \"Espera\")\n+lang(589, Dashboard_Power_off, \"Apagar\")\n+lang(590, Dashboard_System_information, \"Informacion del sistema\")\n+lang(591, Dashboard_Build_information, \"Informacion de compilacion\")\n+lang(592, Dashboard_Font_viewer, \"Visor de fuentes\")\n+lang(593, Dashboard_After_IGR_open, \"Despues de IGR, abrir\")\n+lang(594, Dashboard_App_artwork_background, \"Fondo con imagen de aplicacion\")\n+lang(595, Dashboard_Artwork_background, \"Fondo con imagen\")\n+lang(596, Dashboard_Artwork_dimming, \"Atenuacion de imagen\")\n+lang(597, Dashboard_CRT_scanlines, \"Lineas CRT\")\n+lang(598, Dashboard_Control_icon_backings, \"Fondo de iconos de control\")\n+lang(599, Dashboard_Custom_app_icon_image, \"Icono de aplicacion personalizado...\")\n+lang(600, Dashboard_Default_app_icon, \"Icono de aplicacion predeterminado\")\n+lang(601, Dashboard_Disable_Ember_support, \"Desactivar Ember\")\n+lang(602, Dashboard_Disable_Neutrino_support, \"Desactivar Neutrino\")\n+lang(603, Dashboard_Disable_POPStarter_support, \"Desactivar POPStarter\")\n+lang(604, Dashboard_ESR_loader, \"Cargador ESR\")\n+lang(605, Dashboard_Ember_directory, \"Directorio de Ember\")\n+lang(606, Dashboard_Export_theme_to_folder, \"Exportar tema a carpeta...\")\n+lang(607, Dashboard_Load_theme_from_folder, \"Cargar tema de carpeta...\")\n+lang(608, Dashboard_Game_ID_launch_signal_for_apps, \"Senal Game ID para aplicaciones\")\n+lang(609, Dashboard_Game_ID_launch_signal_for_discs, \"Senal Game ID para discos\")\n+lang(610, Dashboard_Game_ID_output, \"Salida Game ID\")\n+lang(611, Dashboard_Home_quick_launch_shortcuts, \"Accesos rapidos de inicio\")\n+lang(612, Dashboard_IGR_application, \"Aplicacion IGR\")\n+lang(613, Dashboard_IGR_helper, \"Ayudante IGR\")\n+lang(614, Dashboard_Interface_colours, \"Colores de interfaz\")\n+lang(615, Dashboard_Internal_HDD_ATA_units, \"Discos internos HDD / ATA\")\n+lang(616, Dashboard_Legacy_Ethernet_drivers, \"Controladores Ethernet antiguos\")\n+lang(617, Dashboard_Lock_shortcuts_bookmarks, \"Bloquear accesos y marcadores\")\n+lang(618, Dashboard_OSDSYS_program, \"Programa OSDSYS\")\n+lang(619, Dashboard_PSU_add_date_to_names, \"PSU: anadir fecha a nombres\")\n+lang(620, Dashboard_PSU_add_game_ID_and_title_to_names, \"PSU: anadir Game ID y titulo\")\n+lang(621, Dashboard_PSU_never_overwrite_existing_files, \"PSU: no sobrescribir archivos\")\n+lang(622, Dashboard_Recover_saved_card_image, \"Recuperar imagen de tarjeta...\")\n+lang(623, Dashboard_Reset_appearance, \"Restablecer apariencia\")\n+lang(624, Dashboard_Reset_background, \"Restablecer fondo\")\n+lang(625, Dashboard_Reset_clock, \"Restablecer reloj\")\n+lang(626, Dashboard_Reset_colours, \"Restablecer colores\")\n+lang(627, Dashboard_Reset_entire_theme, \"Restablecer todo el tema\")\n+lang(628, Dashboard_Reset_icons, \"Restablecer iconos\")\n+lang(629, Dashboard_Reset_screen_settings, \"Restablecer pantalla\")\n+lang(630, Dashboard_Reset_style, \"Restablecer estilo\")\n+lang(631, Dashboard_Reset_text, \"Restablecer texto\")\n+lang(632, Dashboard_Reset_wallpaper, \"Restablecer imagen de fondo\")\n+lang(633, Dashboard_Restore_default_theme, \"Restaurar tema predeterminado\")\n+lang(634, Dashboard_Save_launch_tool_settings_to, \"Guardar ajustes de inicio y herramientas...\")\n+lang(635, Dashboard_Screen_position_X, \"Posicion horizontal\")\n+lang(636, Dashboard_Screen_position_Y, \"Posicion vertical\")\n+lang(637, Dashboard_Set_PS2_clock_manually, \"Ajustar reloj PS2 manualmente...\")\n+lang(638, Dashboard_Set_software_clock, \"Ajustar reloj de software...\")\n+lang(639, Dashboard_Show_welcome_message_again, \"Mostrar bienvenida de nuevo\")\n+lang(640, Dashboard_Sync_PS2_clock_from_Internet, \"Sincronizar reloj PS2 por Internet\")\n+lang(641, Dashboard_System_font_file, \"Archivo de fuente del sistema\")\n+lang(642, Dashboard_Theme_files_resets, \"Archivos de tema y reinicios\")\n+lang(643, Dashboard_Theme_name, \"Nombre del tema...\")\n+lang(644, Dashboard_Tile_style, \"Estilo de mosaico\")\n+lang(645, Dashboard_Tiles_menus, \"Mosaicos y menus\")\n+lang(646, Dashboard_Use_PS2_hardware_clock, \"Usar reloj fisico de PS2\")\n+lang(647, Dashboard_Video_mode, \"Modo de video\")\n+lang(648, Dashboard_Wallpaper_image, \"Imagen de fondo...\")\n+lang(649, Dashboard_Start, \"Iniciar\")\n+lang(650, Dashboard_Favorite, \"Favorito\")\n+lang(651, Dashboard_Show_background, \"Mostrar fondo\")\n+lang(652, Dashboard_Ready, \"Listo\")\n+lang(653, Dashboard_Checking, \"Comprobando...\")\n+lang(654, Dashboard_Slot_empty, \"Ranura vacia\")\n+lang(655, Dashboard_Mounted_image, \"Imagen montada\")\n+lang(656, Dashboard_Disc_PS1_loaders, \"Cargadores de discos y PS1\")\n+lang(657, Dashboard_MMCE_backup_sync, \"Copia y sincronizacion MMCE\")\n+lang(658, Dashboard_Dashboard_labels_file_operations_and_tools, \"Menus, operaciones de archivos y herramientas.\")\n+lang(659, Dashboard_Optional_LNG_overrides_including_dashboard_labels, \"Archivo .LNG opcional, incluidos menus.\")\n+lang(660, Dashboard_Some_extended_help_remains_in_English, \"Algunas ayudas ampliadas siguen en ingles.\")\n+lang(661, Dashboard_Last_folder, \"Ultima carpeta\")\n+lang(662, Dashboard_BROWSE, \"EXPLORAR\")\n+lang(663, Dashboard_BOOKMARKS_PATHPAD, \"MARCADORES (PATHPAD)\")"
        },
        {
          "sha": "784b703895da3416e60f9dc43bf472a6c35d7e66",
          "filename": "docs/dashboard.md",
          "status": "modified",
          "additions": 14,
          "deletions": 0,
          "changes": 14,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/docs%2Fdashboard.md",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/docs%2Fdashboard.md",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/docs%2Fdashboard.md?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -233,6 +233,20 @@ a launch key held when the delay ends starts its application, and a\n configured `LK_auto` path starts after the classic timeout unless a button\n is pressed (a countdown banner is shown).\n \n+## Language\n+\n+Settings > System > Language selects English, Spanish, French, Italian,\n+Polish, Portuguese, Brazilian Portuguese, German or Hungarian. Dashboard\n+navigation, Settings labels, common actions and copy/delete progress use the\n+selected language. An optional external `.LNG` file can override the same\n+message IDs; existing files remain compatible. Some extended help and\n+specialist dialogs still use English.\n+\n+Filenames, application titles and descriptions, bookmarks, paths and protocol\n+identifiers retain their original text. `Network Host (host:)` means files\n+shared by a computer running the PS2Link host service over the network. It is\n+separate from the UDPFS server entry.\n+\n ## Themes\n \n Settings > Appearance chooses among 19 built-in themes (OSD Classic, PS2"
        },
        {
          "sha": "bddb44c1e6e8827f8c2100b21d6e9e8e39b6c592",
          "filename": "docs/plans/2026-10-24-stable-issue-recovery.md",
          "status": "added",
          "additions": 39,
          "deletions": 0,
          "changes": 39,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/docs%2Fplans%2F2026-10-24-stable-issue-recovery.md",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/docs%2Fplans%2F2026-10-24-stable-issue-recovery.md",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/docs%2Fplans%2F2026-10-24-stable-issue-recovery.md?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -0,0 +1,39 @@\n+# Stable-baseline issue recovery\n+\n+Base: `3e03e640a1434a6d23dd1b80498fa0fbb7c88672`, whose tree matches `2e140ab6baa720334a90da4aad031228ab3d9baa`. All work is scoped to `codex/stable-issue-recovery` and one review PR. Existing untracked work/build directories are retained. No hardware report is automatically closed and no release is published by this branch.\n+\n+| Issue | Implementation or verified state | Remaining acceptance |\n+| --- | --- | --- |\n+| [#182](https://github.com/NathanNeurotic/Launchstation/issues/182) | The baseline already contains the maintained Rolling summary and generator. Verified the live `latest` release at `3e03e64` contains Downloads, Changelog, documentation/compare links and folded verification/hashes. Update the summary for this branch's final scope. | No release publication from this branch. |\n+| [#184](https://github.com/NathanNeurotic/Launchstation/issues/184) | Dedicated copy/delete overlay: action, full-width filename, remaining MB or completed selected items, themed progress and a full completion frame. Generic loading feedback retains its layout. | Host drawing/timing checks pass; console theme/background continuity remains pending. Blocking IOP calls cannot redraw before returning. |\n+| [#187](https://github.com/NathanNeurotic/Launchstation/issues/187) | Restore upstream R3Z direct overwrite: remove destination, create with WRONLY/TRUNC/CREAT, copy and close. Remove the custom stat/staging/checksum/backup/rename transaction and its workarounds. Retain dashboard progress and readable errors. | Host cross-device tests and target builds do not establish affected-console acceptance. |\n+| [#189](https://github.com/NathanNeurotic/Launchstation/issues/189) | Retain baseline wrapping and synchronize highlight/viewports in file browser, applications list, Files, Tools, Settings, picker, HDD Manager and modal menus. Adjacent movement remains animated. | Console long-list/controller coverage. |\n+| [#198](https://github.com/NathanNeurotic/Launchstation/issues/198) | Cull fully offscreen carousel cards with an outline/glow margin, avoiding their drawing, binds and artwork requests. Reference comparison verifies identical visible geometry and order over fractional positions. No background-time or quality-setting changes. | Removes demonstrated redundant work; not proof of the reporter's stall root cause. Repeat SELECT cycling on the affected console/theme. |\n+| [#200](https://github.com/NathanNeurotic/Launchstation/issues/200) | Keep Neutrino options audible until existing committed-launch cleanup. Preserve ordinary ELF muting, cancel and failure recovery. | Production-code host branches tested; physical sound output pending. |\n+| [#205](https://github.com/NathanNeurotic/Launchstation/issues/205) | Append dashboard IDs consistently across all nine catalogs. Localize Settings controls/pages, navigation, common actions, device labels and transfer progress at UI-owned call sites. Resolve the current language/override without retaining translated pointers. Explain PS2Link host files and bound long tab labels. | Some extended help/specialist dialogs remain English, explicitly stated by the Language page. Native-language and target-font/layout review pending. |\n+\n+## Upstream file-handling restoration\n+\n+On 2026-10-25 the maintainer explicitly requested upstream wLaunchELF_R3Z file handling and removal of the custom replacement transaction. This supersedes the earlier staged-replacement design and validation notes.\n+\n+Reference: saildot4k/wLaunchELF_R3Z master `06b06d000072d358c93d98104d64d2f081e47ba7`, `src/filer_copy.c` and `src/filer_fileops.c`.\n+\n+Normal and recursive file replacement now use the upstream operation sequence: `genRemove(out)`, then `genOpen(out, FIO_O_WRONLY | FIO_O_TRUNC | FIO_O_CREAT)`, transfer and close. No destination-stat preflight, parent-directory scan, exclusive-create flag, sibling staging file, checksum verification, backup publication or rename is required. Remove the FatFs stat reinterpretation introduced for that transaction; `genGetStat` again passes through driver results. Existing device-path integration remains.\n+\n+The dashboard progress/error presentation, worker/device preparation, source handling and supported-device metadata dispatch remain integrated. The follow-up restores upstream's single read/write per chunk, buffer sizing, and retention of the output after close failure. Custom short-I/O completion loops and the dashboard-specific MC/VMC buffer cap are removed. Same-path protection avoids deleting the source. MMCE's unsupported timestamp operation is skipped. No user firmware change is required by copying or replacement.\n+\n+Scope: normal copy/move/delete/rename, folders, metadata and their dashboard call paths. The inherited rename/delete operations remain in use; cut/move deletes its source only after successful copying. Preserve CF/i.Link support, driver lifecycle coordination, launcher handoffs, timestamp-editor UI, backup/history/recovery features, translations and dashboard rendering. This correction does not revert those features or claim their complete source identity with upstream.\n+\n+Direct replacement has upstream's failure behavior: once the old destination is removed, a failed copy does not retain the original. There is no automatic backup or rollback promise. Existing unrelated backup/history features are unchanged.\n+\n+## Localization contract\n+\n+IDs 0-467 retain their order and content. New IDs are appended to all nine tables with matching format signatures. Older external LNG files inherit built-in defaults for appended IDs. Missing/empty resolver values fall back to source text. Static UI metadata retains source labels rather than pointers into a replaceable external language buffer.\n+\n+The renderer is unchanged. User filenames, application metadata, bookmarks, paths, config keys, protocol tokens, custom theme names and native language names remain outside automatic translation. Catalogs follow the existing ASCII convention. Untranslated extended help is disclosed and is not counted as completed coverage.\n+\n+## Validation\n+\n+CI includes the overlay, Neutrino-audio, carousel-culling and language fixtures alongside the existing host suite. Copy cases cover repeated direct overwrite, smaller and empty replacements, cross-device MC/MMCE/USB copies, source failures, write/close failures and recursive directories. Destination stat and rename adapters abort if unexpectedly called. POSIX filesystem fixtures run under WSL/Ubuntu rather than Windows MinGW.\n+\n+Host regressions, standard/DS34 builds, controller/UDPFS checks, loader layout and package verification are distinct evidence layers. No emulator or physical console result is claimed. Keep #187 and #198 open pending the affected-runtime report."
        },
        {
          "sha": "9ffc7fcec0608bc7a3f9eaa3902b72a90731a5b4",
          "filename": "include/dash_internal.h",
          "status": "modified",
          "additions": 2,
          "deletions": 0,
          "changes": 2,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/include%2Fdash_internal.h",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/include%2Fdash_internal.h",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/include%2Fdash_internal.h?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -866,6 +866,7 @@ int dashModalChoice(const char *title, const char *const *options, int n, int cu\n \n /* Synchronous work feedback: draws a full frame immediately. */\n void dashLoadingBegin(const char *title);\n+void dashFileLoadingBegin(const char *title);\n void dashLoadingStatus(const char *status, int done, int total);\n void dashLoadingFrame(const char *status, int done, int total); /* always draws */\n void dashLoadingSetCancel(int cancelable); /* shows \"Back: keep working in background\" */\n@@ -943,6 +944,7 @@ const char *dashToolsGoogleStatus(void);\n void dashToolsCheckUpdates(void);\n \n const char *dashSectionName(int section);\n+const char *dashSectionLabel(int section);\n int dashSectionIcon(int section);\n void dashSwitchSection(int section);\n int dashConfirmLeave(void); /* asks the current section; 0 = stay */"
        },
        {
          "sha": "50b947e078795528ce4acfb906ed32930137307f",
          "filename": "include/launchelf.h",
          "status": "modified",
          "additions": 2,
          "deletions": 0,
          "changes": 2,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/include%2Flaunchelf.h",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/include%2Flaunchelf.h",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/include%2Flaunchelf.h?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -683,6 +683,8 @@ int getVirtualKeyboardEditorNextKey(int layout, int editor_index, int dx, int dy\n void Init_Default_Language(void);\n void Load_External_Language(void);\n void Set_Language(int language);\n+const char *localizeDashboardText(const char *source);\n+const char *localizeDashboardFormat(const char *source);\n int normalizeBuiltinLanguage(int language);\n int getBuiltinLanguageByConfigName(const char *name);\n const char *getBuiltinLanguageConfigName(int language);"
        },
        {
          "sha": "b444844bc27196e38c017ee6d21bb32ad1c51d91",
          "filename": "iop/mmceman/src/mmce_fs.c",
          "status": "modified",
          "additions": 16,
          "deletions": 4,
          "changes": 20,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/iop%2Fmmceman%2Fsrc%2Fmmce_fs.c",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/iop%2Fmmceman%2Fsrc%2Fmmce_fs.c",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/iop%2Fmmceman%2Fsrc%2Fmmce_fs.c?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -238,8 +238,9 @@ int mmce_fs_open(iomanX_iop_file_t *file, const char *name, int flags, int mode)\n \n     /* Reject unrepresentable firmware paths before a single SIO2 command.\n      * The synthetic active-card handle has one canonical read-only path. */\n-    if (!mmce_fs_wire_name_ok(name))\n+    if (!mmce_fs_wire_name_ok(name)) {\n         return -ENAMETOOLONG;\n+    }\n     if (mmce_fs_reserved_component(name) && !mmce_fs_is_active_name(name))\n         return -EROFS;\n \n@@ -619,8 +620,9 @@ int mmce_fs_remove(iomanX_iop_file_t *file, const char *name)\n     u8 wrbuf[0x4];\n     u8 rdbuf[0x3];\n \n-    if (!mmce_fs_wire_name_ok(name))\n+    if (!mmce_fs_wire_name_ok(name)) {\n         return -ENAMETOOLONG;\n+    }\n     if (mmce_fs_reserved_component(name))\n         return -EROFS;\n     DPRINTF(\"%s name: %s\\n\", __func__, name);\n@@ -682,8 +684,9 @@ int mmce_fs_mkdir(iomanX_iop_file_t *file, const char *name, int flags)\n     u8 wrbuf[0x4];\n     u8 rdbuf[0x3];\n \n-    if (!mmce_fs_wire_name_ok(name))\n+    if (!mmce_fs_wire_name_ok(name)) {\n         return -ENAMETOOLONG;\n+    }\n     if (mmce_fs_reserved_component(name))\n         return -EROFS;\n     DPRINTF(\"%s name: %s\\n\", __func__, name);\n@@ -745,8 +748,9 @@ int mmce_fs_rmdir(iomanX_iop_file_t *file, const char *name)\n     u8 wrbuf[0x4];\n     u8 rdbuf[0x3];\n \n-    if (!mmce_fs_wire_name_ok(name))\n+    if (!mmce_fs_wire_name_ok(name)) {\n         return -ENAMETOOLONG;\n+    }\n     if (mmce_fs_reserved_component(name))\n         return -EROFS;\n     DPRINTF(\"%s name: %s\\n\", __func__, name);\n@@ -808,6 +812,10 @@ int mmce_fs_dopen(iomanX_iop_file_t *file, const char *name)\n     u8 wrbuf[0x5];\n     u8 rdbuf[0x3];\n \n+    /* Firmware uses a one-byte pathname length; fail before narrowing or I/O. */\n+    if (!mmce_fs_wire_name_ok(name)) {\n+        return -ENAMETOOLONG;\n+    }\n     DPRINTF(\"%s name: %s\\n\", __func__, name);\n \n     mmce_fs_update_unit(file->unit);\n@@ -1041,6 +1049,10 @@ int mmce_fs_getstat(iomanX_iop_file_t *file, const char *name, iox_stat_t *stat)\n     u8 wrbuf[0xFF];\n     u8 rdbuf[0x2B];\n \n+    /* The MMCE wire protocol cannot represent 255+ byte pathnames. */\n+    if (!mmce_fs_wire_name_ok(name)) {\n+        return -ENAMETOOLONG;\n+    }\n     u8 len = strlen(name) + 1;\n \n     DPRINTF(\"%s filename: %s\\n\", __func__, name);"
        },
        {
          "sha": "4560230f47d9b27a33cdfbaabc3068ac327dfb4d",
          "filename": "scripts/copy_test_pack.py",
          "status": "modified",
          "additions": 1,
          "deletions": 2,
          "changes": 3,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/scripts%2Fcopy_test_pack.py",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/scripts%2Fcopy_test_pack.py",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/scripts%2Fcopy_test_pack.py?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -34,10 +34,9 @@ def manifest():\n    Record source/destination, whether a file already existed, and the server build.\n \n Payload: 246018 bytes; normal MC filesystem overhead is additional.\n-Replacement requires room for old and new files. Avoid delete/move for this pass.\n+Replacement uses upstream wLaunchELF_R3Z direct overwrite behavior.\n The files are data, not executable ELFs. Test an ELF separately from the app ZIP;\n never overwrite the running launcher or your only known-good boot ELF.\n-LS-COPY-*.TMP / LS-OLD-*.BAK are recovery files: preserve them after failures.\n No console or adapter behavior is established by a PC verification result.\n \"\"\"\n "
        },
        {
          "sha": "0a75322495162f03b2ba77754047c8be00b6a8c6",
          "filename": "scripts/gen_githash.py",
          "status": "added",
          "additions": 43,
          "deletions": 0,
          "changes": 43,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/scripts%2Fgen_githash.py",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/scripts%2Fgen_githash.py",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/scripts%2Fgen_githash.py?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -0,0 +1,43 @@\n+\"\"\"Stamp ps2build artifacts with this checkout's committed source identity.\"\"\"\n+import argparse\n+from pathlib import Path\n+import re\n+import subprocess\n+\n+ROOT = Path(__file__).resolve().parents[1]\n+\n+\n+def revision():\n+    top = subprocess.run(['git', 'rev-parse', '--show-toplevel'],\n+                         cwd=ROOT, capture_output=True, text=True)\n+    if top.returncode or Path(top.stdout.strip()).resolve() != ROOT.resolve():\n+        return None\n+    result = subprocess.run(['git', 'show', '-s', '--format=%H%n%cs', 'HEAD'],\n+                            cwd=ROOT, capture_output=True, text=True)\n+    lines = result.stdout.splitlines()\n+    if result.returncode or len(lines) != 2:\n+        return None\n+    sha, date = lines\n+    if not re.fullmatch(r'[0-9a-f]{40}', sha) or not re.fullmatch(r'\\d{4}-\\d{2}-\\d{2}', date):\n+        return None\n+    return sha[:12], date\n+\n+\n+def main():\n+    parser = argparse.ArgumentParser(description=__doc__)\n+    parser.add_argument('--output', type=Path, default=ROOT / 'githash.h')\n+    parser.add_argument('--require-git', action='store_true')\n+    args = parser.parse_args()\n+    identity = revision()\n+    if identity is None and args.require_git:\n+        parser.error('A Git revision is required for an identified build')\n+    sha, date = identity or ('local', 'unknown')\n+    content = (f'#ifndef ULE_VERDATE\\n#define ULE_VERDATE \"{date}\"\\n#endif\\n'\n+               f'#ifndef GIT_HASH\\n#define GIT_HASH \"{sha}\"\\n#endif\\n')\n+    if not args.output.exists() or args.output.read_text() != content:\n+        args.output.write_text(content, encoding='ascii')\n+    print(f'Build identity: {sha} ({date})')\n+\n+\n+if __name__ == '__main__':\n+    main()"
        },
        {
          "sha": "1ac33d73140cbf366d55a66ec97100b6e5b1f2b5",
          "filename": "src/dash_apps.c",
          "status": "modified",
          "additions": 25,
          "deletions": 13,
          "changes": 38,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/src%2Fdash_apps.c",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/src%2Fdash_apps.c",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/src%2Fdash_apps.c?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -164,7 +164,7 @@ static void editMetadata(DashApp *a)\n \t\t                   \"# cover = cover.png\\r\\n\"\n \t\t                   \"# arg = first argument (one per line)\\r\\n\",\n \t\t                   a->path, a->title);\n-\t\tif (!dashModalConfirm(\"Create metadata file?\", \"A small text file describing this application will be created next to it. You can then edit it in the Text Editor.\", \"Create\", 0))\n+\t\tif (!dashModalConfirm(\"Create metadata file?\", \"A small text file describing this application will be created next to it. You can then edit it in the Text Editor.\", localizeDashboardText(\"Create\"), 0))\n \t\t\treturn;\n \t\t/* the UI thread writes directly: background work must finish first */\n \t\tif (!dashWorkerAwaitAll(\"Finishing up\"))\n@@ -192,7 +192,7 @@ void dashAppActions(DashApp *a)\n \tint ids[9];\n \tint n = 0, r;\n \n-\titems[n].icon = DI_PLAY, items[n].label = \"Start\", items[n].detail = NULL, items[n].disabled = 0, ids[n++] = 0;\n+\titems[n].icon = DI_PLAY, items[n].label = localizeDashboardText(\"Start\"), items[n].detail = NULL, items[n].disabled = 0, ids[n++] = 0;\n \titems[n].icon = DI_EDIT, items[n].label = \"Start with arguments...\", items[n].detail = NULL, items[n].disabled = 0, ids[n++] = 1;\n \titems[n].icon = DI_STAR, items[n].label = fav ? \"Remove from Favorites\" : \"Add to Favorites\", items[n].detail = NULL, items[n].disabled = 0, ids[n++] = 2;\n \titems[n].icon = DI_PIN, items[n].label = pin ? \"Unpin from Home\" : \"Pin to Home\", items[n].detail = NULL, items[n].disabled = 0, ids[n++] = 3;\n@@ -252,15 +252,15 @@ static void appsMenu(void)\n \tint n = 0, r;\n \tchar filt[48];\n \n-\tsnprintf(filt, sizeof(filt), \"Show: %s\", filter_names[filter]);\n+\tsnprintf(filt, sizeof(filt), localizeDashboardFormat(\"Show: %s\"), localizeDashboardText(filter_names[filter]));\n \tif (a != NULL) {\n-\t\titems[n].icon = DI_APP, items[n].label = \"Application options...\", items[n].detail = NULL, items[n].disabled = 0, n++;\n+\t\titems[n].icon = DI_APP, items[n].label = localizeDashboardText(\"Application options...\"), items[n].detail = NULL, items[n].disabled = 0, n++;\n \t}\n-\titems[n].icon = DI_VIEW_GRID + dashCfg.view, items[n].label = \"Change view\", items[n].detail = NULL, items[n].disabled = 0, n++;\n+\titems[n].icon = DI_VIEW_GRID + dashCfg.view, items[n].label = localizeDashboardText(\"Change view\"), items[n].detail = NULL, items[n].disabled = 0, n++;\n \titems[n].icon = DI_STAR, items[n].label = filt, items[n].detail = NULL, items[n].disabled = 0, n++;\n-\titems[n].icon = DI_REFRESH, items[n].label = \"Refresh library\", items[n].detail = \"scan now\", items[n].disabled = 0, n++;\n+\titems[n].icon = DI_REFRESH, items[n].label = localizeDashboardText(\"Refresh library\"), items[n].detail = \"scan now\", items[n].disabled = 0, n++;\n \n-\tr = dashModalMenu(\"Applications\", NULL, items, n, 0);\n+\tr = dashModalMenu(localizeDashboardText(\"Applications\"), NULL, items, n, 0);\n \tif (r < 0)\n \t\treturn;\n \tif (a == NULL)\n@@ -270,8 +270,8 @@ static void appsMenu(void)\n \t\t\tdashAppActions(a);\n \t\t\tbreak;\n \t\tcase 1: {\n-\t\t\tstatic const char *const views[DASH_VIEW_COUNT] = {\"Grid\", \"List\", \"Carousel\"};\n-\t\t\tint v = dashModalChoice(\"View\", views, DASH_VIEW_COUNT, dashCfg.view);\n+\t\t\tconst char *const views[DASH_VIEW_COUNT] = {localizeDashboardText(\"Grid\"), localizeDashboardText(\"List\"), localizeDashboardText(\"Carousel\")};\n+\t\t\tint v = dashModalChoice(localizeDashboardText(\"View\"), views, DASH_VIEW_COUNT, dashCfg.view);\n \t\t\tif (v >= 0 && v != dashCfg.view) {\n \t\t\t\tdashCfg.view = v;\n \t\t\t\tview_anim = 0.0f;\n@@ -280,7 +280,9 @@ static void appsMenu(void)\n \t\t\tbreak;\n \t\t}\n \t\tcase 2: {\n-\t\t\tint f = dashModalChoice(\"Show applications\", filter_names, FILTER_COUNT, filter);\n+\t\t\tconst char *names[FILTER_COUNT];\n+\t\t\tfor (int i = 0; i < FILTER_COUNT; i++) names[i] = localizeDashboardText(filter_names[i]);\n+\t\t\tint f = dashModalChoice(localizeDashboardText(\"Show applications\"), names, FILTER_COUNT, filter);\n \t\t\tif (f >= 0) {\n \t\t\t\tfilter = f;\n \t\t\t\tdashCfg.app_filter = f;\n@@ -416,10 +418,10 @@ static void drawTitleBar(void)\n \tdashThemeSetFocusApp(a);\n \n \tif (filter != FILTER_ALL)\n-\t\tsnprintf(sub, sizeof(sub), \"%d shown - %s\", norder, filter_names[filter]);\n+\t\tsnprintf(sub, sizeof(sub), \"%d shown - %s\", norder, localizeDashboardText(filter_names[filter]));\n \telse\n \t\tsnprintf(sub, sizeof(sub), \"%d %s\", norder, norder == 1 ? \"application\" : \"applications\");\n-\tdashDrawSectionTitle(48, 68, \"Applications\", sub);\n+\tdashDrawSectionTitle(48, 68, localizeDashboardText(\"Applications\"), sub);\n \tfor (v = 0; v < DASH_VIEW_COUNT; v++) {\n \t\tfloat x = 526 + v * 26;\n \t\tint on = (v == dashCfg.view);\n@@ -499,6 +501,8 @@ static void drawList(void)\n \t\ttop = norder - LIST_ROWS;\n \tif (top < 0)\n \t\ttop = 0;\n+\tif (sel_anim < top || sel_anim >= top + LIST_ROWS)\n+\t\tsel_anim = (float)sel;\n \n \tdashDrawPanelBox(lx - 6, ly - 6, lw + 12, LIST_ROWS * LIST_ROW_H + 12, 14);\n \t{\n@@ -582,9 +586,17 @@ static void drawCarousel(void)\n \t\t\t\tfloat xo = (ad < 1.0f) ? off * 150.0f : (off > 0 ? 1 : -1) * (150.0f + (ad - 1.0f) * 88.0f);\n \t\t\t\tfloat alpha = dashClampf(1.25f - ad * 0.28f, 0.0f, 1.0f);\n \t\t\t\tfloat f = dashClampf(1.0f - ad, 0.0f, 1.0f);\n+\t\t\t\tfloat left = cx + xo - s * 0.5f;\n+\n+\t\t\t\t/* The outer cards can be wholly outside the virtual screen.\n+\t\t\t\t * Do not submit their procedural tiles, texture binds or artwork\n+\t\t\t\t * requests. Keep a margin for outlines and glow; partially visible\n+\t\t\t\t * cards retain their exact geometry and back-to-front ordering. */\n+\t\t\t\tif (left + s < -8.0f || left > DASH_VW + 8.0f)\n+\t\t\t\t\tcontinue;\n \n \t\t\t\tdashGfxPushAlpha(alpha);\n-\t\t\t\tdashDrawAppArt(&dashApps[order[idx]], cx + xo - s * 0.5f, cy - s * 0.5f + ad * 6.0f, s, s, f, (int)(ad + 0.5f));\n+\t\t\t\tdashDrawAppArt(&dashApps[order[idx]], left, cy - s * 0.5f + ad * 6.0f, s, s, f, (int)(ad + 0.5f));\n \t\t\t\t/* Distance is already represented by alpha. A second solid\n \t\t\t\t * rectangle tinted to bg2 created a black square around\n \t\t\t\t * nonfocused artwork in Coverflow, especially with transparent"
        },
        {
          "sha": "4a5c3dfc58a6b71e5e85df9892b3f160e87f3f87",
          "filename": "src/dash_browser.c",
          "status": "modified",
          "additions": 31,
          "deletions": 9,
          "changes": 40,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/src%2Fdash_browser.c",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/src%2Fdash_browser.c",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/src%2Fdash_browser.c?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -158,6 +158,8 @@ static void revealSelection(void)\n \t\tfb.top = 0;\n \tif (fb.n > FB_ROWS && fb.top > fb.n - FB_ROWS)\n \t\tfb.top = fb.n - FB_ROWS;\n+\tif (fb.sel_anim < fb.top || fb.sel_anim >= fb.top + FB_ROWS)\n+\t\tfb.sel_anim = (float)fb.sel;\n }\n \n static u64 fbFileBytes(const FILEINFO *f)\n@@ -695,6 +697,25 @@ enum {\n \tPASTE_PSU\n };\n \n+static void showPasteFailure(int psu)\n+{\n+\tchar *detail = strchr(fb.msg, '\\n');\n+\tchar *p;\n+\n+\t/* The copy diagnostic begins with stage/code and a readable reason.\n+\t * Keep those in the body; scroll the remaining context on the detail\n+\t * line so the six-line body limit cannot hide it. */\n+\tif (detail != NULL)\n+\t\tdetail = strchr(detail + 1, '\\n');\n+\tif (detail != NULL) {\n+\t\t*detail++ = '\\0';\n+\t\tfor (p = detail; *p; p++)\n+\t\t\tif (*p == '\\n' || *p == '\\r')\n+\t\t\t\t*p = ' ';\n+\t}\n+\tdashModalMessage(DI_WARNING, psu ? \"PSU failed\" : localizeDashboardText(\"Paste failed\"), fb.msg, detail);\n+}\n+\n static void opPaste(int mode)\n {\n \tint psu = (mode == PASTE_PSU);\n@@ -708,14 +729,14 @@ static void opPaste(int mode)\n \tif (filerConfirmExploitModify(fb.dir, NULL) <= 0)\n \t\treturn;\n \tfb.msg[0] = '\\0';\n-\tdashLoadingBegin(psu ? \"PSU\" : (browser_cut ? \"Moving\" : \"Pasting\"));\n+\tdashFileLoadingBegin(psu ? \"PSU\" : (browser_cut ? localizeDashboardText(\"Moving file\") : localizeDashboardText(\"Pasting file\")));\n \tif (psu)\n \t\tfilerPsuPasteInto(fb.msg, fb.dir);\n \telse\n \t\tfilerPasteModeInto(fb.msg, fb.dir, mode == PASTE_RENAME);\n \topDone();\n \tif (fb.msg[0]) /* the classic paste only leaves a message when it failed */\n-\t\tdashModalMessage(DI_WARNING, psu ? \"PSU failed\" : \"Paste failed\", fb.msg, NULL);\n+\t\tshowPasteFailure(psu);\n \telse\n \t\tdashToast(DASH_TOAST_OK, browser_cut ? \"Moved\" : \"Pasted\");\n \trefresh();\n@@ -733,16 +754,17 @@ static void opDelete(void)\n \t\tsnprintf(body, sizeof(body), \"Delete %s%s? This cannot be undone.\", fb.files[idx[0]].name, isDir(&fb.files[idx[0]]) ? \"/ and everything in it\" : \"\");\n \telse\n \t\tsnprintf(body, sizeof(body), \"Delete the %d selected items? This cannot be undone.\", n);\n-\tif (!dashModalConfirm(\"Delete\", body, \"Delete\", 1))\n+\tif (!dashModalConfirm(localizeDashboardText(\"Delete\"), body, localizeDashboardText(\"Delete\"), 1))\n \t\treturn;\n \tif (!prepareOp())\n \t\treturn;\n-\tdashLoadingBegin(\"Deleting\");\n+\tdashFileLoadingBegin(localizeDashboardText(\"Deleting file\"));\n \tfor (i = 0; i < n; i++) {\n \t\tFILEINFO *f = &fb.files[idx[i]];\n \t\tif (filerIsExploitProtectedPath(fb.dir, f) && filerConfirmExploitDelete(fb.dir, f) < 0)\n \t\t\tcontinue;\n-\t\tdashLoadingStatus(f->name, i, n);\n+\t\tdashLoadingStatus(f->name, done, n);\n+\t\tdashLoadingFrame(f->name, done, n);\n \t\tret = delete (fb.dir, f);\n \t\tif (ret < 0)\n \t\t\tbreak;\n@@ -2084,11 +2106,11 @@ static int tsEdit(FilerDateEditor *e)\n \t\t{\n \t\t\tDashHint hn[6];\n \t\t\thn[0].button = DI_BTN_LEFT, hn[0].key = NULL, hn[0].label = \"Field\";\n-\t\t\thn[1].button = DI_BTN_UP, hn[1].key = NULL, hn[1].label = \"Change\";\n+\t\t\thn[1].button = DI_BTN_UP, hn[1].key = NULL, hn[1].label = localizeDashboardText(\"Change\");\n \t\t\thn[2].button = DI_BTN_R1, hn[2].key = NULL, hn[2].label = \"Folder\";\n \t\t\thn[3].button = DI_BTN_R2, hn[3].key = NULL, hn[3].label = \"Next to\";\n \t\t\thn[4].button = dashOkIcon(), hn[4].key = NULL, hn[4].label = \"Apply\";\n-\t\t\thn[5].button = dashBackIcon(), hn[5].key = NULL, hn[5].label = \"Cancel\";\n+\t\t\thn[5].button = dashBackIcon(), hn[5].key = NULL, hn[5].label = localizeDashboardText(\"Cancel\");\n \t\t\tdashDrawHints(hn, 6);\n \t\t}\n \t\tdashGfxPresent();\n@@ -2394,7 +2416,7 @@ static void actionsMenu(void)\n \tADD(DI_HOME, \"Go to Launchstation's folder\", NULL, 0, F_HOME);\n \tADD(DI_REFRESH, \"Refresh\", NULL, 0, F_REFRESH);\n #undef ADD\n-\tr = dashModalMenu(\"File Browser\", fb.dir[0] ? fb.dir : \"Devices\", items, n, 0);\n+\tr = dashModalMenu(localizeDashboardText(\"File Browser\"), fb.dir[0] ? fb.dir : localizeDashboardText(\"Devices\"), items, n, 0);\n \tif (r < 0 || items[r].disabled)\n \t\treturn;\n \tswitch (ids[r]) {\n@@ -2603,7 +2625,7 @@ static void browserDrawBody(void)\n \t * background a second time; modal callers still get a complete scene. */\n \tif (!fb_tab_active) {\n \t\tdashIcon(DI_FOLDER, FB_X, 20, 24, DASH_C_ACCENT);\n-\t\tdashTextS(FB_X + 32, 20, \"File Browser\", DASH_FS_TITLE, DASH_C_TEXT);\n+\t\tdashTextS(FB_X + 32, 20, localizeDashboardText(\"File Browser\"), DASH_FS_TITLE, DASH_C_TEXT);\n \t}\n \t/* Give the current folder its own aligned lane above the file list. */\n \tdashDrawPanelBox(FB_X - 6, fb_tab_active ? 68 : 52, FB_W + 12, 24, 6);"
        },
        {
          "sha": "4704cf52f5130be9a44e6c544e7994823f8e8e82",
          "filename": "src/dash_devices.c",
          "status": "modified",
          "additions": 2,
          "deletions": 2,
          "changes": 4,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/src%2Fdash_devices.c",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/src%2Fdash_devices.c",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/src%2Fdash_devices.c?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -262,10 +262,10 @@ static DashDevice *add(DashDevice *out, int *n, int max, int icon, int state,\n \td->icon = icon;\n \td->state = state;\n \td->fill = -1.0f;\n-\tdashStrCopy(d->name, name, sizeof(d->name));\n+\tdashStrCopy(d->name, localizeDashboardText(name), sizeof(d->name));\n \tdashStrCopy(d->mount, mount, sizeof(d->mount));\n \tdashStrCopy(d->browse, browse, sizeof(d->browse));\n-\tdashStrCopy(d->status, status, sizeof(d->status));\n+\tdashStrCopy(d->status, localizeDashboardText(status), sizeof(d->status));\n \treturn d;\n }\n "
        },
        {
          "sha": "b73375c5b011ef00e79636291f08437c4fe664e7",
          "filename": "src/dash_editor.c",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/src%2Fdash_editor.c",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/src%2Fdash_editor.c",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/src%2Fdash_editor.c?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -515,7 +515,7 @@ static int docWrite(EdDoc *d, const char *path)\n \t}\n \tdashStrCopy(d->path, path, sizeof(d->path));\n \td->dirty = 0;\n-\tdashToast(DASH_TOAST_OK, \"Saved\");\n+\tdashToast(DASH_TOAST_OK, localizeDashboardText(\"Saved\"));\n \treturn 1;\n }\n \n@@ -795,7 +795,7 @@ static void copySelection(EdDoc *d, int cut)\n \t\tdocDelete(d, a, b);\n \telse\n \t\td->mark = -1;\n-\tdashToast(DASH_TOAST_INFO, cut ? \"Cut\" : \"Copied\");\n+\tdashToast(DASH_TOAST_INFO, cut ? localizeDashboardText(\"Cut\") : \"Copied\");\n }\n \n /* returns an M_* action, or -1 */\n@@ -966,7 +966,7 @@ static void drawKeyboard(void)\n \t\t\t\tdashDrawSelection(ax, ay, aw, 24, 8, 1.0f);\n \t\t\telse\n \t\t\t\tdashPanel(ax, ay, aw, 24, 8, DC(255, 255, 255, (a == KA_CAPS && kb_caps) ? 40 : 18));\n-\t\t\tdashTextCenter(ax + aw * 0.5f, ay + 4, (a == KA_CAPS && kb_caps) ? \"CAPS\" : ka_names[a], DASH_FS_SMALL, dashItemColor(on));\n+\t\t\tdashTextCenter(ax + aw * 0.5f, ay + 4, (a == KA_CAPS && kb_caps) ? localizeDashboardText(\"CAPS\") : ka_names[a], DASH_FS_SMALL, dashItemColor(on));\n \t\t}\n \t}\n }"
        },
        {
          "sha": "b8effa8c8e7304e0a3758cbacf2db1999d22a68e",
          "filename": "src/dash_files.c",
          "status": "modified",
          "additions": 8,
          "deletions": 6,
          "changes": 14,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/src%2Fdash_files.c",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/src%2Fdash_files.c",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/src%2Fdash_files.c?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -184,7 +184,7 @@ static void fAdd(int kind, int icon, int index, const char *label, const char *d\n \te->kind = kind;\n \te->icon = icon;\n \te->index = index;\n-\tdashStrCopy(e->label, label, sizeof(e->label));\n+\tdashStrCopy(e->label, kind == FE_BOOKMARK ? label : localizeDashboardText(label), sizeof(e->label));\n \tdashStrCopy(e->detail, detail ? detail : \"\", sizeof(e->detail));\n }\n \n@@ -307,7 +307,7 @@ static void filesInput(const DashInput *in)\n \t\t    {DI_FOLDER, \"Open File Browser\", NULL, 0},\n \t\t    {DI_TEXT, \"Text Editor\", NULL, 0},\n \t\t    {DI_REFRESH, \"Refresh devices\", NULL, 0}};\n-\t\tint r = dashModalMenu(\"Files\", NULL, items, 3, 0);\n+\t\tint r = dashModalMenu(localizeDashboardText(\"Files\"), NULL, items, 3, 0);\n \t\tif (r == 0)\n \t\t\tfilesActivate();\n \t\telse if (r == 1)\n@@ -339,14 +339,16 @@ static void filesDraw(void)\n \tint i;\n \tFileEntry *e = &fentries[fsel];\n \n-\tdashDrawSectionTitle(48, 68, \"Files\", \"the complete Launchstation file manager\");\n+\tdashDrawSectionTitle(48, 68, localizeDashboardText(\"Files\"), \"the complete Launchstation file manager\");\n \n \tif (fsel < ftop + 1)\n \t\tftop = fsel - 1;\n \tif (fsel >= ftop + rows)\n \t\tftop = fsel - rows + 1;\n \tif (ftop < 0)\n \t\tftop = 0;\n+\tif (fsel_anim < ftop || fsel_anim >= ftop + rows)\n+\t\tfsel_anim = (float)fsel;\n \n \tdashDrawPanelBox(lx - 6, ly - 6, lw + 12, rows * row_h + 12, 14);\n \tdashGfxScissor(lx - 6, ly - 4, lw + 12, rows * row_h + 8);\n@@ -523,13 +525,13 @@ static void devInput(const DashInput *in)\n \t\tchar display_mount[16];\n \t\tint n = 0, r, ids[5];\n \n-\t\titems[n].icon = DI_FOLDER, items[n].label = \"Browse\",\n+\t\titems[n].icon = DI_FOLDER, items[n].label = localizeDashboardText(\"Browse\"),\n \t\titems[n].detail = deviceDisplayMount(d, display_mount, sizeof(display_mount)),\n \t\titems[n].disabled = 0, ids[n++] = 0;\n \t\titems[n].icon = DI_REFRESH, items[n].label = (d->state == DASH_DEV_READY) ? \"Check again\" : \"Detect now\", items[n].detail = NULL,\n \t\titems[n].disabled = (d->icon == DI_MC || d->icon == DI_DISC), ids[n++] = 1;\n \t\tif (d->icon == DI_HDD && !strncmp(d->mount, \"hdd\", 3))\n-\t\t\titems[n].icon = DI_HDD, items[n].label = \"HDD Manager\", items[n].detail = NULL, items[n].disabled = 0, ids[n++] = 2;\n+\t\t\titems[n].icon = DI_HDD, items[n].label = localizeDashboardText(\"HDD Manager\"), items[n].detail = NULL, items[n].disabled = 0, ids[n++] = 2;\n \t\tif (d->icon == DI_DISC)\n \t\t\titems[n].icon = DI_PLAY, items[n].label = \"Start disc\", items[n].detail = NULL, items[n].disabled = 0, ids[n++] = 3;\n \t\t/* Exact model discovery happens only through this user-selected sheet. */\n@@ -591,7 +593,7 @@ static void devDraw(void)\n \t\tif (devs[i].state == DASH_DEV_READY)\n \t\t\tready++;\n \tsnprintf(sub, sizeof(sub), \"%d ready\", ready);\n-\tdashDrawSectionTitle(48, 68, \"Devices\", sub);\n+\tdashDrawSectionTitle(48, 68, localizeDashboardText(\"Devices\"), sub);\n \n \tdashGfxScissor(DASH_SAFE_L - 10, 98, DASH_SAFE_R - DASH_SAFE_L + 20, 296);\n \tfor (i = 0; i < ndevs; i++) {"
        },
        {
          "sha": "a16be7df348970acabcf8a4f50633b50439e7d8d",
          "filename": "src/dash_hdd.c",
          "status": "modified",
          "additions": 12,
          "deletions": 10,
          "changes": 22,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/src%2Fdash_hdd.c",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/src%2Fdash_hdd.c",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/src%2Fdash_hdd.c?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -526,7 +526,7 @@ static int pickSize(const char *title, int min_mb, int max_mb, int start_mb)\n \t\t\t\thx += dashDrawButton(hx, hy, -1, \"L2\") + 2;\n \t\t\t\thx += dashDrawButton(hx, hy, -1, \"R2\") + 4;\n \t\t\t\thx = dashTextS(hx, hy + 4.5f, \"10 GB\", DASH_FS_SMALL, DASH_C_TEXT2) + 12;\n-\t\t\t\tdashModalHintRow(hx, hy, dashOkIcon(), \"OK\", dashBackIcon(), \"Cancel\");\n+\t\t\t\tdashModalHintRow(hx, hy, dashOkIcon(), localizeDashboardText(\"OK\"), dashBackIcon(), localizeDashboardText(\"Cancel\"));\n \t\t\t}\n \t\t\tdashGfxPresent();\n \t\t}\n@@ -556,7 +556,7 @@ static void actCreate(void)\n \tif (!dashModalKeyboard(\"New partition name\", name, MAX_PART_NAME - 3) || name[0] == '\\0')\n \t\treturn;\n \tif (!strncmp(name, \"__\", 2) &&\n-\t    !dashModalConfirm(\"System partition name\", \"Names starting with \\\"__\\\" are reserved for the system and for PSBBN. Create it anyway?\", \"Create\", 1))\n+\t    !dashModalConfirm(\"System partition name\", \"Names starting with \\\"__\\\" are reserved for the system and for PSBBN. Create it anyway?\", localizeDashboardText(\"Create\"), 1))\n \t\treturn;\n \tmax_mb = hm.free_mb < MAX_PART_MB ? (int)hm.free_mb : MAX_PART_MB;\n \tif (max_mb < 128) {\n@@ -568,7 +568,7 @@ static void actCreate(void)\n \t\treturn;\n \tuniqueName(name, unique, sizeof(unique), -1);\n \tsnprintf(body, sizeof(body), \"Create hdd0:%s with %d MB?\", unique, size);\n-\tif (!dashModalConfirm(\"Create partition\", body, \"Create\", 0))\n+\tif (!dashModalConfirm(\"Create partition\", body, localizeDashboardText(\"Create\"), 0))\n \t\treturn;\n \tj = jobNew(HOP_CREATE);\n \tif (j == NULL)\n@@ -592,7 +592,7 @@ static void actRemove(HddPart *p)\n \tHddJob *j;\n \n \tsnprintf(body, sizeof(body), \"Delete hdd0:%s and everything on it? This cannot be undone.\", p->name);\n-\tif (!dashModalConfirm(\"Remove partition\", body, \"Remove\", 1))\n+\tif (!dashModalConfirm(\"Remove partition\", body, localizeDashboardText(\"Remove\"), 1))\n \t\treturn;\n \tdashStrCopy(name, p->name, sizeof(name));\n \tj = jobNew(HOP_REMOVE);\n@@ -675,9 +675,9 @@ static void actFormat(void)\n \t\tdashModalMessage(DI_WARNING, \"Not available\", \"The PSX internal HDD cannot be formatted from Launchstation.\", NULL);\n \t\treturn;\n \t}\n-\tif (!dashModalConfirm(\"Format the HDD\", \"Every partition, game and file on the internal HDD will be erased.\", \"Continue\", 1))\n+\tif (!dashModalConfirm(\"Format the HDD\", \"Every partition, game and file on the internal HDD will be erased.\", localizeDashboardText(\"Continue\"), 1))\n \t\treturn;\n-\tif (!dashModalConfirm(\"Are you sure?\", \"This erases the whole HDD and cannot be undone.\", \"Format\", 1))\n+\tif (!dashModalConfirm(\"Are you sure?\", \"This erases the whole HDD and cannot be undone.\", localizeDashboardText(\"Format\"), 1))\n \t\treturn;\n \tj = jobNew(HOP_FORMAT);\n \tif (j == NULL)\n@@ -739,7 +739,7 @@ static void injectOne(HddPart *p, const char *src)\n \tsnprintf(body, sizeof(body),\n \t         \"Write the APA header of hdd0:%s from %s__Headers/%s/? Required: system.cnf; PS2 titles also icon.sys and list.ico.\",\n \t         p->name, src, p->name);\n-\tif (!dashModalConfirm(\"Inject header\", body, \"Inject\", 1))\n+\tif (!dashModalConfirm(\"Inject header\", body, localizeDashboardText(\"Inject\"), 1))\n \t\treturn;\n \tj = jobNew(HOP_INJECT);\n \tif (j == NULL)\n@@ -784,7 +784,7 @@ static void injectMatching(const char *src, int create_missing)\n \t}\n \tsnprintf(body, sizeof(body), \"%d header folder%s in %s__Headers/. Inject them into the matching partitions%s?\",\n \t         list->nsources, list->nsources == 1 ? \"\" : \"s\", src, create_missing ? \", creating missing ones\" : \"\");\n-\tif (!dashModalConfirm(\"Inject headers\", body, \"Inject\", 1)) {\n+\tif (!dashModalConfirm(\"Inject headers\", body, localizeDashboardText(\"Inject\"), 1)) {\n \t\thddJobFree(list);\n \t\treturn;\n \t}\n@@ -949,7 +949,7 @@ static void partitionMenu(void)\n \tif (!console_is_PSX && hm.present)\n \t\tADD(DI_WARNING, \"Format HDD...\", NULL, 0, A_FORMAT);\n #undef ADD\n-\tr = dashModalMenu(\"HDD Manager\", p ? p->name : \"hdd0:\", items, n, 0);\n+\tr = dashModalMenu(localizeDashboardText(\"HDD Manager\"), p ? p->name : \"hdd0:\", items, n, 0);\n \tif (r < 0)\n \t\treturn;\n \tswitch (ids[r]) {\n@@ -1140,7 +1140,7 @@ static void hddScene(void)\n \tdashGfxBeginFrame();\n \tdashBgDraw();\n \tdashIcon(DI_HDD, 40, 20, 24, DASH_C_ACCENT);\n-\tdashTextS(72, 20, \"HDD Manager\", DASH_FS_TITLE, DASH_C_TEXT);\n+\tdashTextS(72, 20, localizeDashboardText(\"HDD Manager\"), DASH_FS_TITLE, DASH_C_TEXT);\n \tsnprintf(line, sizeof(line), \"Status: %s\", hddStatusShortText(hm.status));\n \tdashTextRight(DASH_SAFE_R, 26, line, DASH_FS_SMALL, hm.status == 0 ? DASH_C_OK : DASH_C_WARM);\n \n@@ -1248,6 +1248,8 @@ void dashHddManager(void)\n \t\t\ttop = sel;\n \t\tif (sel >= top + ROWS)\n \t\t\ttop = sel - ROWS + 1;\n+\t\tif (sel_anim < top || sel_anim >= top + ROWS)\n+\t\t\tsel_anim = (float)sel;\n \t\t/* Keep PFS usage polling as before; HDL title RPCs are explicit\n \t\t * (R1 or Rename game) and never run on cursor movement. */\n \t\tif (hm.n > 0 && Timer() - last_move > 180)"
        },
        {
          "sha": "21eb8b718adea2459729633c61de7d827472431a",
          "filename": "src/dash_home.c",
          "status": "modified",
          "additions": 2,
          "deletions": 2,
          "changes": 4,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/src%2Fdash_home.c",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/src%2Fdash_home.c",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/src%2Fdash_home.c?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -421,9 +421,9 @@ static void homeDraw(void)\n \tdrawHero();\n \n \t/* Continue row */\n-\tdashText(48, y0 - 33, \"Continue\", DASH_FS_SMALL, row == 0 ? DASH_C_TEXT : DASH_C_TEXT3);\n+\tdashText(48, y0 - 33, localizeDashboardText(\"Continue\"), DASH_FS_SMALL, row == 0 ? DASH_C_TEXT : DASH_C_TEXT3);\n \tif (row == 0)\n-\t\tdashRect(48, y0 - 15, dashTextWidth(\"Continue\", DASH_FS_SMALL), 2, DASH_C_ACCENT);\n+\t\tdashRect(48, y0 - 15, dashTextWidth(localizeDashboardText(\"Continue\"), DASH_FS_SMALL), 2, DASH_C_ACCENT);\n \tif (nhome == 0) {\n \t\tdashDrawPanelBox(48, y0, 552, cw, 14);\n \t\tdashIcon(DI_CLOCK, 70, y0 + 25, 40, DASH_C_TEXT3);"
        },
        {
          "sha": "46e5f7a6653814cb3137d1d01fcaef4443f58514",
          "filename": "src/dash_info.c",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/src%2Fdash_info.c",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/src%2Fdash_info.c",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/src%2Fdash_info.c?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -111,7 +111,7 @@ static void sheetShow(int icon, const char *title, const char *subtitle, const S\n \t\t\tdashGfxScissorReset();\n \t\t\tif (max_scroll > 0.0f)\n \t\t\t\tdashDrawScrollbar(x + w - 14, body_y, body_h, (int)scroll, (int)body_h, (int)total);\n-\t\t\tdashModalHintRow(x + 24, y + h - 36, dashOkIcon(), \"Close\", -1, NULL);\n+\t\t\tdashModalHintRow(x + 24, y + h - 36, dashOkIcon(), localizeDashboardText(\"Close\"), -1, NULL);\n \t\t\tif (max_scroll > 0.0f) /* one D-pad glyph, up/down highlighted */\n \t\t\t\tdashDrawButton(x + w - 24 - 22, y + h - 36, DI_DPAD_UD, NULL);\n \t\t\tdashGfxPopOffset();\n@@ -395,7 +395,7 @@ void dashShowFontViewer(void)\n \t\tdashModalBackdrop(dashEaseOutCubic(open));\n \t\tdashModalPanel(x, y, w, h, 1.0f);\n \t\tdashIcon(DI_TEXT, x + 24, y + 18, 22, DASH_C_ACCENT);\n-\t\tdashTextS(x + 56, y + 18, \"Font viewer\", DASH_FS_LABEL, DASH_C_TEXT);\n+\t\tdashTextS(x + 56, y + 18, localizeDashboardText(\"Font viewer\"), DASH_FS_LABEL, DASH_C_TEXT);\n \t\tdashTextRight(x + w - 24, y + 22, setting->font_file[0] ? setting->font_file : \"built-in font\", DASH_FS_CAPTION, DASH_C_TEXT3);\n \t\tfor (r = 0; r <= 16; r++) {\n \t\t\tfor (c = 0; c <= 16; c++) {\n@@ -417,7 +417,7 @@ void dashShowFontViewer(void)\n \t\t\t\t                   (int)((cy + 3) * sy), gsColor(DASH_C_TEXT));\n \t\t\t}\n \t\t}\n-\t\tdashModalHintRow(x + 24, y + h - 36, dashOkIcon(), \"Close\", -1, NULL);\n+\t\tdashModalHintRow(x + 24, y + h - 36, dashOkIcon(), localizeDashboardText(\"Close\"), -1, NULL);\n \t\tdashGfxPresent();\n \t}\n \tdashSoundPlay(DASH_SND_CANCEL);"
        },
        {
          "sha": "1029ec21986048e62648be3f73fc8433de377de9",
          "filename": "src/dash_main.c",
          "status": "modified",
          "additions": 15,
          "deletions": 6,
          "changes": 21,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/src%2Fdash_main.c",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/src%2Fdash_main.c",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/src%2Fdash_main.c?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -48,6 +48,12 @@ const char *dashSectionName(int s)\n \treturn (s >= 0 && s < DASH_SEC_COUNT) ? section_names[s] : \"\";\n }\n \n+const char *dashSectionLabel(int s)\n+{\n+\tstatic const char *const labels[DASH_SEC_COUNT] = {\"Home\", \"Applications\", \"Files\", \"Devices\", \"Tools\", \"Settings\"};\n+\treturn (s >= 0 && s < DASH_SEC_COUNT) ? labels[s] : \"\";\n+}\n+\n int dashSectionIcon(int s)\n {\n \treturn (s >= 0 && s < DASH_SEC_COUNT) ? section_icons[s] : DI_HOME;\n@@ -468,7 +474,7 @@ static void friendlyLaunchError(const DashApp *app, const char *tech)\n \tdashModalMessage(DI_WARNING, not_elf ? \"Not an application\" : \"Application unavailable\", body,\n \t                 (tech && tech[0]) ? tech : app->path);\n \tif (not_found && (dashStoreIsFavorite(app->path) || dashStoreIsPinned(app->path) || (app->flags & DASH_APP_TRANSIENT))) {\n-\t\tif (dashModalConfirm(\"Remove shortcut?\", \"Remove this application from Home, Favorites and Recent?\", \"Remove\", 0))\n+\t\tif (dashModalConfirm(\"Remove shortcut?\", \"Remove this application from Home, Favorites and Recent?\", localizeDashboardText(\"Remove\"), 0))\n \t\t\tdashStoreRemoveEverywhere(app->path);\n \t}\n }\n@@ -537,9 +543,12 @@ void dashLaunchApp(DashApp *app, int with_args_prompt)\n \t/*\n \t * Ordinary ELF launches used to leave audsrv's looping BGM voice alive.\n \t * Mute until the legacy launcher validates the target; final cleanup\n-\t * stops audsrv. A failed validation can restore the existing audio below.\n+\t * stops audsrv. Neutrino still presents an interactive options menu;\n+\t * its committed-launch cleanup stops audio after the user chooses Run.\n+\t * A failed validation can restore the existing audio below.\n \t */\n-\tdashSoundSuspend();\n+\tif (!IsNeutrinoIsoPath(path))\n+\t\tdashSoundSuspend();\n \tdashGfxRestoreLegacyState();\n \tdashClassicUiInstall(0); /* launch-time prompts stay classic */\n \tExecuteMainAction(path, &exec_ctx);\n@@ -701,11 +710,11 @@ static void globalMenu(void)\n \t\t\tdashSwitchSection(DASH_SEC_SETTINGS);\n \t\t\tbreak;\n \t\tcase 4:\n-\t\t\tif (dashModalConfirm(\"Reboot IOP\", \"All loaded device drivers will be restarted. Devices will be detected again when used.\", \"Reboot\", 0))\n+\t\t\tif (dashModalConfirm(localizeDashboardText(\"Reboot IOP\"), \"All loaded device drivers will be restarted. Devices will be detected again when used.\", \"Reboot\", 0))\n \t\t\t\tdashRunMisc(setting->Misc_Reboot_IOP);\n \t\t\tbreak;\n \t\tcase 5:\n-\t\t\tif (dashModalConfirm(\"Power off\", \"Turn off the PlayStation 2 now?\", \"Power off\", 1))\n+\t\t\tif (dashModalConfirm(localizeDashboardText(\"Power off\"), \"Turn off the PlayStation 2 now?\", localizeDashboardText(\"Power off\"), 1))\n \t\t\t\tdashRunMisc(setting->Misc_PS2PowerOff);\n \t\t\tbreak;\n \t}\n@@ -857,7 +866,7 @@ static void quickLaunchDraw(void)\n \tdashGfxPushAlpha(k);\n \tdashModalPanel(x, y, w, h, k);\n \tdashIcon(DI_KEY, x + 24, y + 18, 24, DASH_C_ACCENT);\n-\tdashTextS(x + 58, y + 20, \"Launch keys\", DASH_FS_LABEL, DASH_C_TEXT);\n+\tdashTextS(x + 58, y + 20, localizeDashboardText(\"Launch keys\"), DASH_FS_LABEL, DASH_C_TEXT);\n \tdashText(x + 58, y + 44, \"Keep SELECT held and press a button\", DASH_FS_CAPTION, DASH_C_TEXT3);\n \tif (n == 0)\n \t\tdashText(x + 28, y + 72, \"Set launch keys in Settings > Launching > Launch keys.\", DASH_FS_SMALL, DASH_C_TEXT2);"
        },
        {
          "sha": "56b31c301b9cfd052076583ccc9928b4b94b99f1",
          "filename": "src/dash_settings.c",
          "status": "modified",
          "additions": 38,
          "deletions": 32,
          "changes": 70,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/src%2Fdash_settings.c",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/src%2Fdash_settings.c",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/src%2Fdash_settings.c?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -368,8 +368,8 @@ static const SetItem net_items[] = {\n };\n \n static const SetItem language_items[] = {\n-    {ST_CHOICE, S_LANGUAGE, \"Language\", \"Language of the messages from file operations and tools.\"},\n-    {ST_PATH, S_LANG_FILE, \"Language file\", \"External .LNG translation for the messages of the file operations and tools.\"},\n+    {ST_CHOICE, S_LANGUAGE, \"Language\", \"Dashboard labels, file operations and tools.\"},\n+    {ST_PATH, S_LANG_FILE, \"Language file\", \"Optional .LNG overrides, including dashboard labels.\"},\n };\n \n static const SetItem gameid_items[] = {\n@@ -490,7 +490,7 @@ static const SetPage pages[P_COUNT] = {\n     [P_MMCE_BACKUP] = PAGE(\"MMCE backup & sync\", DI_MC, \"Backup, cloud, IGR return and maintenance controls in one place.\", mmce_backup_items),\n     [P_NETWORK] = PAGE(\"Network\", DI_NETWORK, \"Console address for FTP, PS2Link and UDPFS.\", net_items),\n     [P_SYSTEM] = PAGE(\"System\", DI_TOOLS, \"Language, backup file rules and settings locations.\", system_items),\n-    [P_LANGUAGE] = PAGE(\"Language\", DI_TEXT, \"These translations apply to file operations and tools.\", language_items),\n+    [P_LANGUAGE] = PAGE(\"Language\", DI_TEXT, \"Some extended help remains in English.\", language_items),\n     [P_PSU] = PAGE(\"Memory card backup files\", DI_MC, \"Naming and overwrite behavior when creating PSU backups.\", psu_items),\n     [P_THEME_BG] = PAGE(\"Background\", DI_IMAGE, \"Scene, brightness and static wallpaper.\", tbg_items),\n     [P_THEME_COLORS] = PAGE(\"Interface colours\", DI_PALETTE, \"Colours and opacity for text, focus and panels.\", tcol_items),\n@@ -1164,7 +1164,7 @@ static void colorEditor(const char *title, int id)\n \t\t\t\tdashIcon(dashOkIcon(), hx, hy + 3, 14, DASH_C_TEXT2);\n \t\t\t\thx = dashTextS(hx + 18, hy + 4, \"Done\", DASH_FS_CAPTION, DASH_C_TEXT2) + 10;\n \t\t\t\tdashIcon(dashBackIcon(), hx, hy + 3, 14, DASH_C_TEXT2);\n-\t\t\t\tdashTextS(hx + 18, hy + 4, \"Cancel\", DASH_FS_CAPTION, DASH_C_TEXT2);\n+\t\t\t\tdashTextS(hx + 18, hy + 4, localizeDashboardText(\"Cancel\"), DASH_FS_CAPTION, DASH_C_TEXT2);\n \t\t\t}\n \t\t}\n \t\tdashGfxPopAlpha();\n@@ -1440,7 +1440,7 @@ static int numberInfo(int id, int *val, int *lo, int *hi, char *text, int text_s\n \t\t\tif (*val)\n \t\t\t\tsnprintf(text, text_size, \"%d s\", *val);\n \t\t\telse\n-\t\t\t\tsnprintf(text, text_size, \"Off\");\n+\t\t\t\tsnprintf(text, text_size, \"%s\", localizeDashboardText(\"Off\"));\n \t\t\treturn 1;\n \t\tcase S_TIMEOUT:\n \t\t\t*val = setting->timeout, *lo = 0, *hi = 99;\n@@ -1449,7 +1449,7 @@ static int numberInfo(int id, int *val, int *lo, int *hi, char *text, int text_s\n \t\tcase S_MMCE_BACKUP_KEEP:\n \t\t\t*val = dashCfg.mmce_backup_keep, *lo = 0, *hi = 99;\n \t\t\tif (*val == 0)\n-\t\t\t\tsnprintf(text, text_size, \"All\");\n+\t\t\t\tsnprintf(text, text_size, \"%s\", localizeDashboardText(\"All\"));\n \t\t\telse\n \t\t\t\tsnprintf(text, text_size, \"%d\", *val);\n \t\t\treturn 1;\n@@ -1618,7 +1618,7 @@ static void editPath(const SetItem *it)\n \tif (!pathInfo(it->id, &ps))\n \t\treturn;\n \tstartDirOf(ps.field, start, sizeof(start));\n-\tsnprintf(title, sizeof(title), \"%s\", it->label);\n+\tsnprintf(title, sizeof(title), \"%s\", localizeDashboardText(it->label));\n \tout[0] = '\\0';\n \tif (!dashPickPath(title, start, ps.mode, ps.ext, out, sizeof(out)))\n \t\treturn;\n@@ -1653,7 +1653,7 @@ static void editIp(const SetItem *it)\n \tchar buf[16];\n \n \tdashStrCopy(buf, ipField(it->id), sizeof(buf));\n-\tif (!dashModalIp(it->label, it->help, buf, sizeof(buf)) || !strcmp(buf, ipField(it->id)))\n+\tif (!dashModalIp(localizeDashboardText(it->label), localizeDashboardText(it->help), buf, sizeof(buf)) || !strcmp(buf, ipField(it->id)))\n \t\treturn;\n \tdashStrCopy(ipField(it->id), buf, 16);\n \tupdateIpConfigRuntimeState();\n@@ -1685,7 +1685,7 @@ static void editLaunchKey(const SetItem *it, int what)\n \tif (lkeyLocked())\n \t\treturn;\n \tif (what == 0) {\n-\t\tsnprintf(title, sizeof(title), \"Launch key: %s\", it->label);\n+\t\tsnprintf(title, sizeof(title), \"Launch key: %s\", localizeDashboardText(it->label));\n \t\tstartDirOf(setting->LK_Path[lk], start, sizeof(start));\n \t\tout[0] = '\\0';\n \t\tif (!dashPickPath(title, start, DASH_PICK_LAUNCH, \"ELF\", out, sizeof(out)))\n@@ -1953,7 +1953,7 @@ static int settingsSaveSoftwareClock(const char *timestamp)\n {\n \tif (!dashModalConfirm(\"Save clock and settings?\",\n \t                      \"Save this time and your current Settings changes before starting the software clock?\",\n-\t                      \"Save\", 0))\n+\t                      localizeDashboardText(\"Save\"), 0))\n \t\treturn 0;\n \tdashStrCopy(dashCfg.software_clock, timestamp, sizeof(dashCfg.software_clock));\n \tmenuTitleUpdateAsync(1);\n@@ -2153,7 +2153,7 @@ static void editScanPaths(void)\n \t\tif (r < dashCfg.nscan) {\n \t\t\tchar body[DASH_PATH_MAX + 48];\n \t\t\tsnprintf(body, sizeof(body), \"Stop looking for applications in %s?\", dashCfg.scan[r]);\n-\t\t\tif (dashModalConfirm(\"Remove folder\", body, \"Remove\", 0)) {\n+\t\t\tif (dashModalConfirm(\"Remove folder\", body, localizeDashboardText(\"Remove\"), 0)) {\n \t\t\t\tfor (i = r; i < dashCfg.nscan - 1; i++)\n \t\t\t\t\tmemcpy(dashCfg.scan[i], dashCfg.scan[i + 1], DASH_PATH_MAX);\n \t\t\t\tdashCfg.nscan--;\n@@ -2188,7 +2188,7 @@ static void editScanPaths(void)\n \n static void resetScreen(void)\n {\n-\tif (!dashModalConfirm(\"Reset screen settings\", \"Video mode Auto and a centred picture?\", \"Reset\", 0))\n+\tif (!dashModalConfirm(localizeDashboardText(\"Reset screen settings\"), \"Video mode Auto and a centred picture?\", localizeDashboardText(\"Reset\"), 0))\n \t\treturn;\n \tsetting->color[COLOR_BACKGR] = DEF_COLOR1;\n \tsetting->color[COLOR_FRAME] = DEF_COLOR2;\n@@ -2209,7 +2209,7 @@ static void resetScreen(void)\n \n static void resetDisplayThemeDefaults(void)\n {\n-\tif (!dashModalConfirm(\"Reset display & theme defaults\", \"Restore the screen, motion, animation and OSD Classic theme defaults?\", \"Reset\", 0))\n+\tif (!dashModalConfirm(\"Reset display & theme defaults\", \"Restore the screen, motion, animation and OSD Classic theme defaults?\", localizeDashboardText(\"Reset\"), 0))\n \t\treturn;\n \tsetting->color[COLOR_BACKGR] = DEF_COLOR1;\n \tsetting->color[COLOR_FRAME] = DEF_COLOR2;\n@@ -2364,7 +2364,7 @@ static void themeSave(void)\n \t\tstrcpy(name, \"MyTheme\");\n \tsnprintf(body, sizeof(body), \"Save the current theme as %sTHEMES/%s/theme.cfg? An existing theme with that name is replaced.\",\n \t         dashStoreDir(), name);\n-\tif (!dashModalConfirm(\"Save theme\", body, \"Save\", 0))\n+\tif (!dashModalConfirm(\"Save theme\", body, localizeDashboardText(\"Save\"), 0))\n \t\treturn;\n \tj = (ThemeIoJob *)malloc(sizeof(ThemeIoJob));\n \tif (j == NULL)\n@@ -2520,7 +2520,7 @@ static void runThemeAction(int id)\n \t\t\tthemeSave();\n \t\t\tbreak;\n \t\tcase S_T_DEFAULT:\n-\t\t\tif (dashModalConfirm(\"Restore default theme\", \"Replace the current theme with OSD Classic? Saved theme folders are not affected.\", \"Restore\", 0)) {\n+\t\t\tif (dashModalConfirm(localizeDashboardText(\"Restore default theme\"), \"Replace the current theme with OSD Classic? Saved theme folders are not affected.\", \"Restore\", 0)) {\n \t\t\t\tdashThemeSetPreset(0);\n \t\t\t\tdashCfg.effects = 100;\n \t\t\t\tdashCfg.anim = 100;\n@@ -2532,7 +2532,7 @@ static void runThemeAction(int id)\n \t\t\tchar body[160];\n \t\t\tint p = dashThemeBasePreset();\n \t\t\tsnprintf(body, sizeof(body), \"Undo every change and return to \\\"%s\\\"?\", dashThemePresetInfo(p)->name);\n-\t\t\tif (dashModalConfirm(\"Reset entire theme\", body, \"Reset\", 0)) {\n+\t\t\tif (dashModalConfirm(localizeDashboardText(\"Reset entire theme\"), body, localizeDashboardText(\"Reset\"), 0)) {\n \t\t\t\tdashThemeSetPreset(p);\n \t\t\t\tdashCfg.effects = 100;\n \t\t\t\tdashCfg.anim = 100;\n@@ -2591,7 +2591,7 @@ static void runAction(int id)\n \t\t\tdashRescanLibrary(1);\n \t\t\tbreak;\n \t\tcase S_CLEAR_RECENT:\n-\t\t\tif (dashModalConfirm(\"Clear recent applications\", \"Remove every entry from the recent list? Applications are not affected.\", \"Clear\", 0)) {\n+\t\t\tif (dashModalConfirm(localizeDashboardText(\"Clear recent applications\"), \"Remove every entry from the recent list? Applications are not affected.\", localizeDashboardText(\"Clear\"), 0)) {\n \t\t\t\tdashStoreClearRecent();\n \t\t\t\tdashHomeRebuild();\n \t\t\t\tdashToast(DASH_TOAST_OK, \"Recent list cleared\");\n@@ -2788,7 +2788,7 @@ static const char *disabledHelp(const SetItem *it)\n \t\tcase S_T_DATE: return \"Enable the clock to configure its placement and format.\";\n \t\tcase S_TIMEOUT: return \"Choose an automatic start target before setting its timeout.\";\n \t\tcase S_IGR_AFTER_APP: return \"Set 'After IGR, open' to Application first.\";\n-\t\tdefault: return it->help;\n+\t\tdefault: return localizeDashboardText(it->help);\n \t}\n }\n \n@@ -2818,7 +2818,7 @@ static void editPathMenu(const SetItem *it)\n \tif (!pathInfo(it->id, &ps))\n \t\treturn;\n \tmenu[1].disabled = !ps.field[0];\n-\tr = dashModalMenu(it->label, ps.field[0] ? ps.field : \"Default\", menu, NITEMS(menu), 0);\n+\tr = dashModalMenu(localizeDashboardText(it->label), ps.field[0] ? ps.field : localizeDashboardText(\"Default\"), menu, NITEMS(menu), 0);\n \tif (r == 0)\n \t\teditPath(it);\n \telse if (r == 1)\n@@ -2835,7 +2835,7 @@ static void editLaunchMenu(const SetItem *it)\n \tif (lkeyLocked())\n \t\treturn;\n \tmenu[2].disabled = !setting->LK_Path[lk][0];\n-\tr = dashModalMenu(it->label, setting->LK_Path[lk][0] ? setting->LK_Path[lk] : \"Not set\", menu, NITEMS(menu), 0);\n+\tr = dashModalMenu(localizeDashboardText(it->label), setting->LK_Path[lk][0] ? setting->LK_Path[lk] : localizeDashboardText(\"Not set\"), menu, NITEMS(menu), 0);\n \tif (r >= 0)\n \t\teditLaunchKey(it, r);\n }\n@@ -2892,7 +2892,7 @@ static void settingsInput(const DashInput *in)\n \t} else if (it->type == ST_PAGE && (in->pressed & (DASH_IN_OK | DASH_IN_RIGHT))) {\n \t\topenPage(it->id);\n \t} else if (it->type == ST_COLOR && (in->pressed & DASH_IN_OK)) {\n-\t\tcolorEditor(it->label, it->id);\n+\t\tcolorEditor(localizeDashboardText(it->label), it->id);\n \t} else if (it->type == ST_CHOICE && it->id == S_T_PRESET && (in->pressed & DASH_IN_OK)) {\n \t\tchoosePreset();\n \t} else if (it->type == ST_NUMBER && (in->pressed & (DASH_IN_LEFT | DASH_IN_RIGHT))) {\n@@ -2921,7 +2921,11 @@ static void settingsInput(const DashInput *in)\n \t\t\telse if (in->pressed & DASH_IN_RIGHT)\n \t\t\t\tcur = (cur + 1) % n;\n \t\t\telse if (n > 2) {\n-\t\t\t\tint r = dashModalChoice(it->label, names, n, cur);\n+\t\t\t\tconst char *display[128];\n+\t\t\t\tint choice;\n+\t\t\t\tfor (choice = 0; choice < n && choice < 128; choice++)\n+\t\t\t\t\tdisplay[choice] = it->id == S_LANGUAGE || it->id == S_T_PRESET ? names[choice] : localizeDashboardText(names[choice]);\n+\t\t\t\tint r = dashModalChoice(localizeDashboardText(it->label), n <= 128 ? display : names, n, cur);\n \t\t\t\tif (r < 0)\n \t\t\t\t\treturn;\n \t\t\t\tcur = r;\n@@ -2973,12 +2977,12 @@ static void settingsDraw(void)\n \ttrail[0] = '\\0';\n \tfor (i = 0; i < page_depth; i++) {\n \t\tint len = strlen(trail);\n-\t\tsnprintf(trail + len, sizeof(trail) - len, \"%s%s\", i ? \" > \" : \"\", pages[page_stack[i].page].name);\n+\t\tsnprintf(trail + len, sizeof(trail) - len, \"%s%s\", i ? \" > \" : \"\", localizeDashboardText(pages[page_stack[i].page].name));\n \t}\n \tif (page_depth)\n \t\tdashTextFit(ix, 66, iw - 126, trail, DASH_FS_CAPTION, DASH_C_TEXT3);\n-\tdashDrawSectionTitle(ix, 82, c->name, NULL);\n-\tdashTextFit(ix, 112, iw, c->help, DASH_FS_CAPTION, DASH_C_TEXT3);\n+\tdashDrawSectionTitle(ix, 82, localizeDashboardText(c->name), NULL);\n+\tdashTextFit(ix, 112, iw, localizeDashboardText(c->help), DASH_FS_CAPTION, DASH_C_TEXT3);\n \t{\n \t\tint dirty = anyDirtyCached();\n \t\tint pending = cfg_pending[0] || cfg_pending[1] || dashStoreSettingsPending();\n@@ -2994,6 +2998,8 @@ static void settingsDraw(void)\n \t\titem_top = item;\n \tif (item >= item_top + ITEM_ROWS)\n \t\titem_top = item - ITEM_ROWS + 1;\n+\tif (item_anim < item_top || item_anim >= item_top + ITEM_ROWS)\n+\t\titem_anim = (float)item;\n \n \t/* items */\n \tdashDrawPanelBox(ix - 6, iy - 6, iw + 12, ITEM_ROWS * irow + 12, 14);\n@@ -3016,19 +3022,19 @@ static void settingsDraw(void)\n \t\tif (it->type == ST_INFO) {\n \t\t\tchar buf[64];\n \t\t\tsnprintf(buf, sizeof(buf), \"%s / %s\", ip, netmask);\n-\t\t\tdashText(ix + 38, y + 4.0f, it->label, DASH_FS_SMALL, DASH_C_TEXT3);\n+\t\t\tdashText(ix + 38, y + 4.0f, localizeDashboardText(it->label), DASH_FS_SMALL, DASH_C_TEXT3);\n \t\t\tdashTextRight(ix + iw - 12, y + 4.0f, buf, DASH_FS_SMALL, DASH_C_TEXT2);\n \t\t\tcontinue;\n \t\t}\n \t\tif (it->type == ST_PAGE) {\n-\t\t\tdashTextFit(ix + 38, y + 4.0f, iw - 70, it->label, DASH_FS_SMALL, col);\n+\t\t\tdashTextFit(ix + 38, y + 4.0f, iw - 70, localizeDashboardText(it->label), DASH_FS_SMALL, col);\n \t\t\tdashIcon(DI_PLAY, ix + iw - 22, y + 9, 12, DASH_C_TEXT3);\n \t\t\tcontinue;\n \t\t}\n \t\t{\n \t\t\tconst char *ast = it->type == ST_ACTION ? actionStatus(it->id) : NULL;\n \t\t\tfloat label_w = it->type == ST_ACTION ? (ast ? iw * 0.58f : iw - 70) : iw * 0.56f - 50;\n-\t\t\tdashTextFit(ix + 38, y + 4.0f, label_w, it->label, DASH_FS_SMALL, col);\n+\t\t\tdashTextFit(ix + 38, y + 4.0f, label_w, localizeDashboardText(it->label), DASH_FS_SMALL, col);\n \t\t}\n \t\tif (it->type == ST_COLOR) {\n \t\t\tint alpha;\n@@ -3047,7 +3053,7 @@ static void settingsDraw(void)\n \t\t\tint cur, n = choiceInfo(it->id, &cur, &names, &single);\n \t\t\tif (n > 0 && cur >= 0 && cur < n) {\n \t\t\t\tfloat maxw = iw * 0.40f;\n-\t\t\t\tfloat vw = dashTextWidth(names[cur], DASH_FS_SMALL);\n+\t\t\t\tfloat vw = dashTextWidth((it->id == S_LANGUAGE || it->id == S_T_PRESET ? names[cur] : localizeDashboardText(names[cur])), DASH_FS_SMALL);\n \t\t\t\tfloat vx;\n \t\t\t\tif (vw > maxw)\n \t\t\t\t\tvw = maxw;\n@@ -3057,9 +3063,9 @@ static void settingsDraw(void)\n \t\t\t\t\tdashIcon(DI_PLAY, ix + iw - 16, y + 8, 10, DASH_C_ACCENT);\n \t\t\t\t}\n \t\t\t\tif (on)\n-\t\t\t\t\tdashTextScroll(vx, y + 4.0f, maxw, names[cur], DASH_FS_SMALL, disabled ? DASH_C_TEXT3 : (n == 2 && names == onoff && cur == 1) ? DASH_C_OK : DASH_C_ACCENT);\n+\t\t\t\t\tdashTextScroll(vx, y + 4.0f, maxw, (it->id == S_LANGUAGE || it->id == S_T_PRESET ? names[cur] : localizeDashboardText(names[cur])), DASH_FS_SMALL, disabled ? DASH_C_TEXT3 : (n == 2 && names == onoff && cur == 1) ? DASH_C_OK : DASH_C_ACCENT);\n \t\t\t\telse\n-\t\t\t\t\tdashTextFit(vx, y + 4.0f, maxw, names[cur], DASH_FS_SMALL, disabled ? DASH_C_TEXT3 : DASH_C_ACCENT);\n+\t\t\t\t\tdashTextFit(vx, y + 4.0f, maxw, (it->id == S_LANGUAGE || it->id == S_T_PRESET ? names[cur] : localizeDashboardText(names[cur])), DASH_FS_SMALL, disabled ? DASH_C_TEXT3 : DASH_C_ACCENT);\n \t\t\t}\n \t\t} else if (it->type == ST_NUMBER || it->type == ST_PATH || it->type == ST_IP || it->type == ST_LKEY) {\n \t\t\tchar text[MAX_PATH], leaf[MAX_PATH];\n@@ -3112,7 +3118,7 @@ static void settingsDraw(void)\n \t/* help line */\n \t{\n \t\tconst SetItem *it = curItem();\n-\t\tconst char *help = itemDisabled(it) ? disabledHelp(it) : it->help;\n+\t\tconst char *help = itemDisabled(it) ? disabledHelp(it) : localizeDashboardText(it->help);\n \t\tif (it->type == ST_CHOICE && it->id == S_T_PRESET)\n \t\t\thelp = dashThemePresetInfo(dashThemeBasePreset())->desc;\n \t\tif (help)"
        },
        {
          "sha": "a6b8327de6af887feca67c72d70c8d6d78216088",
          "filename": "src/dash_tools.c",
          "status": "modified",
          "additions": 14,
          "deletions": 12,
          "changes": 26,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/src%2Fdash_tools.c",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/src%2Fdash_tools.c",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/src%2Fdash_tools.c?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -126,7 +126,7 @@ void dashToolsIgrBackupSetup(void)\n \tif (action == 1) {\n \t\tif (!dashModalConfirm(\"Remove OPL IGR backup helper?\",\n \t\t                      \"Removes WLE-IGR.ELF from the locations managed by this Launchstation installation. Existing card backups are not deleted.\",\n-\t\t                      \"Remove\", 1))\n+\t\t                      localizeDashboardText(\"Remove\"), 1))\n \t\t\treturn;\n \t\tdashLoadingBegin(\"Removing OPL IGR helper\");\n \t\tr = wleIgrRemove(&stats, result, sizeof(result));\n@@ -283,7 +283,7 @@ static void cloudLink(void)\n \t\tdashTextWrap(x + 24, y + 222, w - 48, 2,\n \t\t             \"No Google Cloud setup or payment details are needed. Launchstation can only see files it creates.\",\n \t\t             DASH_FS_CAPTION, DASH_C_TEXT3, 15);\n-\t\tdashModalHintRow(x + 24, y + h - 36, -1, NULL, dashBackIcon(), \"Cancel\");\n+\t\tdashModalHintRow(x + 24, y + h - 36, -1, NULL, dashBackIcon(), localizeDashboardText(\"Cancel\"));\n \t\tdashGfxPopOffset();\n \t\tdashGfxPopAlpha();\n \t\tdashGfxPresent();\n@@ -531,11 +531,11 @@ void dashToolsRefreshMmceStatus(void)\n \tif (j->root[0] == '\\0')\n \t\tsnprintf(mmce_status_text, sizeof(mmce_status_text), \"Set destination\");\n \telse if (j->scan_mmce)\n-\t\tsnprintf(mmce_status_text, sizeof(mmce_status_text), \"Checking...\");\n+\t\tsnprintf(mmce_status_text, sizeof(mmce_status_text), \"%s\", localizeDashboardText(\"Checking...\"));\n \telse\n \t\tsnprintf(mmce_status_text, sizeof(mmce_status_text), \"Not checked\");\n #if defined(ETH) && defined(WLE_NATIVE_CLOUD)\n-\tsnprintf(google_status_text, sizeof(google_status_text), \"Checking...\");\n+\tsnprintf(google_status_text, sizeof(google_status_text), \"%s\", localizeDashboardText(\"Checking...\"));\n #endif\n \n \tid = dashWorkerSubmit(\"Checking MMCE backup status\",\n@@ -1446,12 +1446,12 @@ static void runTool(const Tool *t)\n {\n \tif (t->confirm) {\n \t\tchar body[200];\n-\t\tconst char *action_label = t->confirm == 2 ? t->name : \"Continue\";\n+\t\tconst char *action_label = t->confirm == 2 ? localizeDashboardText(t->name) : \"Continue\";\n \t\tif (t->action == TA_MISC &&\n \t\t    (t->misc_offset == MISC(Misc_PS2Browser) || t->misc_offset == MISC(Misc_OSDSYS)))\n \t\t\taction_label = \"Leave\";\n-\t\tsnprintf(body, sizeof(body), \"%s\", t->desc);\n-\t\tif (!dashModalConfirm(t->name, body, action_label, t->confirm == 2))\n+\t\tsnprintf(body, sizeof(body), \"%s\", localizeDashboardText(t->desc));\n+\t\tif (!dashModalConfirm(localizeDashboardText(t->name), body, action_label, t->confirm == 2))\n \t\t\treturn;\n \t}\n \tswitch (t->action) {\n@@ -1514,13 +1514,15 @@ static void toolsDraw(void)\n \tconst Tool *t = &tools[tsel];\n \tint i;\n \n-\tdashDrawSectionTitle(48, 68, \"Tools\", \"everything Launchstation can do\");\n+\tdashDrawSectionTitle(48, 68, localizeDashboardText(\"Tools\"), \"everything Launchstation can do\");\n \tif (tsel < ttop + 1)\n \t\tttop = tsel - 1;\n \tif (tsel >= ttop + rows)\n \t\tttop = tsel - rows + 1;\n \tif (ttop < 0)\n \t\tttop = 0;\n+\tif (tsel_anim < ttop || tsel_anim >= ttop + rows)\n+\t\ttsel_anim = (float)tsel;\n \n \tdashDrawPanelBox(lx - 6, ly - 6, lw + 12, rows * row_h + 12, 14);\n \tdashGfxScissor(lx - 6, ly - 4, lw + 12, rows * row_h + 8);\n@@ -1532,11 +1534,11 @@ static void toolsDraw(void)\n \t\tfloat y = ly + (i - ttop) * row_h;\n \n \t\tif (tools[i].action == TA_HEADER) {\n-\t\t\tdashText(lx + 8, y + 7, tools[i].name, DASH_FS_CAPTION, DASH_C_ACCENT);\n+\t\t\tdashText(lx + 8, y + 7, localizeDashboardText(tools[i].name), DASH_FS_CAPTION, DASH_C_ACCENT);\n \t\t\tcontinue;\n \t\t}\n \t\tdashIcon(tools[i].icon, lx + 10, y + 5, 16, dashItemColor(i == tsel));\n-\t\tdashTextFit(lx + 34, y + 3.0f, lw - 44, tools[i].name, DASH_FS_SMALL, dashItemColor(i == tsel));\n+\t\tdashTextFit(lx + 34, y + 3.0f, lw - 44, localizeDashboardText(tools[i].name), DASH_FS_SMALL, dashItemColor(i == tsel));\n \t\tif (tools[i].confirm == 2 || tools[i].note)\n \t\t\tdashGlow(lx + lw - 14, y + 13, 4, 4, tools[i].confirm == 2 ? DASH_C_DANGER : DASH_C_WARM);\n \t}\n@@ -1549,8 +1551,8 @@ static void toolsDraw(void)\n \t\tdashDrawCard(dx, dy, dw, dh, 16);\n \t\tdashAccentGlow(dx + dw * 0.5f, dy + 66, 80, 60, 1.0f);\n \t\tdashIcon(t->icon, dx + dw * 0.5f - 30, dy + 34, 60, DASH_C_TEXT);\n-\t\tdashTextCenter(dx + dw * 0.5f, dy + 108, t->name, DASH_FS_BODY, DASH_C_TEXT);\n-\t\tdashTextWrap(dx + 16, dy + 142, dw - 32, 4, t->desc, DASH_FS_CAPTION, DASH_C_TEXT2, 19);\n+\t\tdashTextCenter(dx + dw * 0.5f, dy + 108, localizeDashboardText(t->name), DASH_FS_BODY, DASH_C_TEXT);\n+\t\tdashTextWrap(dx + 16, dy + 142, dw - 32, 4, localizeDashboardText(t->desc), DASH_FS_CAPTION, DASH_C_TEXT2, 19);\n \t\tif (t->note) {\n \t\t\tDashColor wb = dashColorScale(DASH_C_WARM, 0.24f);\n \t\t\twb.a = 130;"
        },
        {
          "sha": "f094fa2735167de779682a42f7d1e2af1ac2d59e",
          "filename": "src/dash_ui.c",
          "status": "modified",
          "additions": 74,
          "deletions": 22,
          "changes": 96,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/src%2Fdash_ui.c",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/src%2Fdash_ui.c",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/src%2Fdash_ui.c?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -218,15 +218,22 @@ static float drawButton(float x, float y, int icon, const char *key)\n \n void dashDrawHints(const DashHint *hints, int n)\n {\n-\tfloat total = 0.0f, x;\n+\tfloat total = 0.0f, x, labels = 0.0f, label_ratio = 1.0f;\n \tfloat y = 400.0f;\n \tint i;\n \n \t/* Measure exactly what the drawing loop below advances by, so every\n \t * screen's hint bar ends at the same right edge whatever its keys are\n \t * (START/SELECT may be pad icons or text badges). */\n-\tfor (i = 0; i < n; i++)\n-\t\ttotal += buttonWidth(hints[i].button, hints[i].key) + 8.0f + dashTextWidth(hints[i].label, DASH_FS_SMALL) + 24.0f;\n+\tfor (i = 0; i < n; i++) {\n+\t\ttotal += buttonWidth(hints[i].button, hints[i].key) + 8.0f + 24.0f;\n+\t\tlabels += dashTextWidth(localizeDashboardText(hints[i].label), DASH_FS_SMALL);\n+\t}\n+\t/* Long translated action names share the remaining label space. Keep\n+\t * button glyphs/readable type sizes intact, ellipsizing labels as needed. */\n+\tif (labels > 0.0f)\n+\t\tlabel_ratio = dashClampf((DASH_SAFE_R - DASH_SAFE_L + 12.0f - total) / labels, 0.0f, 1.0f);\n+\ttotal += labels * label_ratio;\n \tx = DASH_SAFE_R - total + 12.0f;\n \tif (x < DASH_SAFE_L)\n \t\tx = DASH_SAFE_L;\n@@ -244,10 +251,14 @@ void dashDrawHints(const DashHint *hints, int n)\n \t\tdashRectGradH(x - 40, y - 6, DASH_SAFE_R - x + 60, 34, sh, DC(sh.r, sh.g, sh.b, 150));\n \t}\n \tfor (i = 0; i < n; i++) {\n+\t\tconst char *label = localizeDashboardText(hints[i].label);\n+\t\tfloat label_w = dashTextWidth(label, DASH_FS_SMALL) * label_ratio;\n \t\tx += drawButton(x, y, hints[i].button, hints[i].key) + 8.0f;\n \t\t/* x-height centre (y + 16 * scale) about 1 px under the 22 px button's\n \t\t * centre, so mixed-case labels read as centred on the button */\n-\t\tx = dashTextS(x, y + 1.5f, hints[i].label, DASH_FS_SMALL, DASH_C_TEXT) + 24.0f;\n+\t\tif (label_w > 0.0f)\n+\t\t\tdashTextFit(x, y + 1.5f, label_w, label, DASH_FS_SMALL, DASH_C_TEXT);\n+\t\tx += label_w + 24.0f;\n \t}\n }\n \n@@ -374,7 +385,12 @@ void dashDrawHeader(int section, float section_anim)\n \t\tfloat k = dashClampf(1.0f - dist, 0.0f, 1.0f);\n \t\tDashColor c = dashColorLerp(DASH_C_TEXT3, DASH_C_SELTEXT, k);\n \t\tdashIcon(dashSectionIcon(actual), cx - 8, tab_y + 3, 16, c);\n-\t\tdashTextCenter(cx, tab_y + 20, dashSectionName(actual), DASH_FS_CAPTION * 0.92f, c);\n+\t\tconst char *name = localizeDashboardText(dashSectionLabel(actual));\n+\t\tfloat scale = DASH_FS_CAPTION * 0.92f;\n+\t\tif (dashTextWidth(name, scale) <= tab_w - 8)\n+\t\t\tdashTextCenter(cx, tab_y + 20, name, scale, c);\n+\t\telse\n+\t\t\tdashTextFit(cx - (tab_w - 8) * 0.5f, tab_y + 20, tab_w - 8, name, scale, c);\n \t}\n \t{\n \t\tfloat strip_l = tab_x0 - 40, strip_r = tab_x0 + tab_w * vcount + 40;\n@@ -791,6 +807,8 @@ int dashModalMenu(const char *title, const char *subtitle, const DashMenuItem *i\n \t\t\ttop = sel;\n \t\tif (sel >= top + rows)\n \t\t\ttop = sel - rows + 1;\n+\t\tif (sel_anim < top || sel_anim >= top + rows)\n+\t\t\tsel_anim = (float)sel;\n \t\tif (preview != NULL) {\n \t\t\tif (sel != previewed) {\n \t\t\t\tpreview(sel);\n@@ -1009,6 +1027,7 @@ static int loading_done, loading_total;\n static u64 loading_last_draw;\n static float loading_open;\n static int loading_cancel;\n+static int loading_file_operation;\n \n int dashLoadingActive(void)\n {\n@@ -1017,26 +1036,41 @@ int dashLoadingActive(void)\n \n static void loadingDraw(void)\n {\n-\tfloat w = 420.0f, h = loading_remaining[0] ? 158.0f : loading_cancel ? 150.0f : 128.0f;\n+\tfloat w = loading_file_operation ? 480.0f : 420.0f;\n+\tfloat h = loading_file_operation ? (loading_cancel ? 210.0f : 174.0f) : loading_remaining[0] ? 158.0f : loading_cancel ? 150.0f : 128.0f;\n \tfloat x = (DASH_VW - w) * 0.5f, y = (DASH_VH - h) * 0.5f;\n-\tfloat bar_y = y + (loading_remaining[0] ? 126.0f : 102.0f);\n+\tfloat bar_y = y + (loading_file_operation ? 142.0f : loading_remaining[0] ? 126.0f : 102.0f);\n \tfloat dt = dashVisualUpdate();\n \n \tloading_open = dashApproach(loading_open, 1.0f, 12.0f, dt);\n \tdashRenderScene();\n \tmodalBackdrop(loading_open);\n \tdashGfxPushAlpha(loading_open);\n \tmodalPanel(x, y, w, h, loading_open);\n-\tdashGfxBlend(DASH_BLEND_ADD);\n-\tdashDrawSpinner(x + 46, y + 50, 16, DASH_C_ACCENT);\n-\tdashGfxBlend(DASH_BLEND_NORMAL);\n-\tdashTextFit(x + 84, y + 26, w - 110, loading_title, DASH_FS_LABEL, DASH_C_TEXT);\n-\tdashTextFit(x + 84, y + 58, w - 110, loading_status, DASH_FS_SMALL, DASH_C_TEXT2);\n-\tif (loading_remaining[0])\n-\t\tdashTextFit(x + 84, y + 84, w - 110, loading_remaining, DASH_FS_SMALL, DASH_C_TEXT2);\n+\tif (loading_file_operation) {\n+\t\tchar line[64];\n+\t\tdashTextFit(x + 24, y + 24, w - 48, localizeDashboardText(loading_title), DASH_FS_LABEL, DASH_C_TEXT);\n+\t\tdashTextFit(x + 24, y + 62, w - 48, loading_status, DASH_FS_BODY, DASH_C_TEXT);\n+\t\tif (loading_remaining[0])\n+\t\t\tdashTextFit(x + 24, y + 100, w - 48, loading_remaining, DASH_FS_SMALL, DASH_C_TEXT2);\n+\t\telse if (loading_total > 0) {\n+\t\t\tsnprintf(line, sizeof(line), localizeDashboardFormat(\"Completed items: %d / %d\"), loading_done, loading_total);\n+\t\t\tdashTextFit(x + 24, y + 100, w - 48, line, DASH_FS_SMALL, DASH_C_TEXT2);\n+\t\t}\n+\t} else {\n+\t\tdashGfxBlend(DASH_BLEND_ADD);\n+\t\tdashDrawSpinner(x + 46, y + 50, 16, DASH_C_ACCENT);\n+\t\tdashGfxBlend(DASH_BLEND_NORMAL);\n+\t\tdashTextFit(x + 84, y + 26, w - 110, localizeDashboardText(loading_title), DASH_FS_LABEL, DASH_C_TEXT);\n+\t\tdashTextFit(x + 84, y + 58, w - 110, loading_status, DASH_FS_SMALL, DASH_C_TEXT2);\n+\t\tif (loading_remaining[0])\n+\t\t\tdashTextFit(x + 84, y + 84, w - 110, loading_remaining, DASH_FS_SMALL, DASH_C_TEXT2);\n+\t}\n \tif (loading_total > 0) {\n \t\tfloat p = dashClampf(loading_done / (float)loading_total, 0.0f, 1.0f);\n-\t\tloading_fraction = dashApproach(loading_fraction, p, 14.0f, dt);\n+\t\t/* The final frame is followed by closing the overlay. It must show\n+\t\t * completion rather than disappear with a partly filled bar. */\n+\t\tloading_fraction = p >= 1.0f ? 1.0f : dashApproach(loading_fraction, p, 14.0f, dt);\n \t\tdashPanel(x + 30, bar_y, w - 60, 8, 4, dashColorA(DASH_C_TEXT3, 0.3f));\n \t\tif (loading_fraction > 0.0f)\n \t\t\tdashPanel(x + 30, bar_y, (w - 60) * loading_fraction, 8, 4, DASH_C_ACCENT);\n@@ -1048,8 +1082,9 @@ static void loadingDraw(void)\n \tloading_last_draw = Timer();\n }\n \n-void dashLoadingBegin(const char *title)\n+static void loadingBegin(const char *title, int file_operation)\n {\n+\tloading_file_operation = file_operation;\n \tloading_active = 1;\n \tloading_open = 0.3f;\n \tloading_cancel = 0;\n@@ -1061,6 +1096,16 @@ void dashLoadingBegin(const char *title)\n \tloadingDraw();\n }\n \n+void dashLoadingBegin(const char *title)\n+{\n+\tloadingBegin(title, 0);\n+}\n+\n+void dashFileLoadingBegin(const char *title)\n+{\n+\tloadingBegin(title, 1);\n+}\n+\n void dashLoadingSetCancel(int cancelable)\n {\n \tloading_cancel = cancelable;\n@@ -1088,6 +1133,7 @@ void dashLoadingStatus(const char *status, int done, int total)\n \t}\n \tloadingProgress(status, done, total);\n \tif (!loading_active) {\n+\t\tloading_file_operation = 0;\n \t\t/* one-shot status (e.g. a driver load started by a legacy path) */\n \t\tloading_active = 1;\n \t\tloading_open = 0.6f;\n@@ -1102,27 +1148,32 @@ void dashLoadingStatus(const char *status, int done, int total)\n \n int DashFileProgress(const char *action, const char *name, u64 remaining, u64 total)\n {\n-\tint done;\n+\tint done, changed;\n \tif (!DashIsActive() || dashWorkerOnThread()) return 0;\n-\tif (!loading_active) dashLoadingBegin(action);\n-\tif (strncmp(loading_status, name ? name : \"\", sizeof(loading_status) - 1) ||\n-\t    strncmp(loading_title, action, sizeof(loading_title) - 1))\n+\tif (!loading_active) dashFileLoadingBegin(action);\n+\tchanged = !loading_file_operation ||\n+\t          strncmp(loading_status, name ? name : \"\", sizeof(loading_status) - 1) ||\n+\t          strncmp(loading_title, action, sizeof(loading_title) - 1) ||\n+\t          (remaining > 0 && loading_total > 0 && loading_done >= loading_total);\n+\tloading_file_operation = 1;\n+\tif (changed)\n \t\tloading_fraction = 0.0f;\n \tdashStrCopy(loading_title, action, sizeof(loading_title));\n \tdashStrCopy(loading_status, name ? name : \"\", sizeof(loading_status));\n \tif (remaining > total) remaining = total;\n-\tsnprintf(loading_remaining, sizeof(loading_remaining), \"Remaining size: %.2f MB\",\n+\tsnprintf(loading_remaining, sizeof(loading_remaining), localizeDashboardFormat(\"Remaining size: %.2f MB\"),\n \t         (double)remaining / (1024.0 * 1024.0));\n \tdone = total ? (int)((1.0 - (double)remaining / (double)total) * 10000.0) : 10000;\n \tloading_done = done;\n \tloading_total = 10000;\n-\tif (Timer() - loading_last_draw >= 16 || remaining == 0) loadingDraw();\n+\tif (changed || Timer() - loading_last_draw >= 16 || remaining == 0) loadingDraw();\n \treturn 1;\n }\n \n void DashFileDeleteProgress(const char *name)\n {\n \tif (!DashIsActive() || dashWorkerOnThread() || !loading_active) return;\n+\tloading_file_operation = 1;\n \tif (loading_remaining[0]) {\n \t\tloading_done = loading_total = 0;\n \t\tloading_fraction = 0.0f;\n@@ -1156,6 +1207,7 @@ void DashStorageProbeStatus(const char *status)\n void dashLoadingEnd(void)\n {\n \tloading_active = 0;\n+\tloading_file_operation = 0;\n \tloading_cancel = 0;\n \tdashInputFlush();\n }"
        },
        {
          "sha": "2061645204a3d419a3c63704e9f9ff5932a0d003",
          "filename": "src/dash_widgets.c",
          "status": "modified",
          "additions": 9,
          "deletions": 7,
          "changes": 16,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/src%2Fdash_widgets.c",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/src%2Fdash_widgets.c",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/src%2Fdash_widgets.c?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -274,7 +274,7 @@ int dashModalKeyboard(const char *title, char *buf, int max_len)\n \t\t\t\thx += dashDrawButton(hx, hy, dashOkIcon(), NULL) + 4;\n \t\t\t\thx = dashTextS(hx, hy + 4.5f, \"Type\", DASH_FS_SMALL, DASH_C_TEXT2) + 12;\n \t\t\t\thx += dashDrawButton(hx, hy, dashBackIcon(), NULL) + 4;\n-\t\t\t\thx = dashTextS(hx, hy + 4.5f, \"Delete\", DASH_FS_SMALL, DASH_C_TEXT2) + 12;\n+\t\t\t\thx = dashTextS(hx, hy + 4.5f, localizeDashboardText(\"Delete\"), DASH_FS_SMALL, DASH_C_TEXT2) + 12;\n \t\t\t\thx += dashDrawButton(hx, hy, DI_BTN_SQUARE, NULL) + 4;\n \t\t\t\thx = dashTextS(hx, hy + 4.5f, \"Caps\", DASH_FS_SMALL, DASH_C_TEXT2) + 12;\n \t\t\t\thx += dashDrawButton(hx, hy, -1, \"L1\") + 2;\n@@ -283,7 +283,7 @@ int dashModalKeyboard(const char *title, char *buf, int max_len)\n \t\t\t\thx += dashDrawButton(hx, hy, -1, \"START\") + 4;\n \t\t\t\thx = dashTextS(hx, hy + 4.5f, \"Done\", DASH_FS_SMALL, DASH_C_TEXT2) + 12;\n \t\t\t\thx += dashDrawButton(hx, hy, DI_BTN_TRIANGLE, NULL) + 4;\n-\t\t\t\tdashTextS(hx, hy + 4.5f, \"Cancel\", DASH_FS_SMALL, DASH_C_TEXT2);\n+\t\t\t\tdashTextS(hx, hy + 4.5f, localizeDashboardText(\"Cancel\"), DASH_FS_SMALL, DASH_C_TEXT2);\n \t\t\t}\n \t\t\tdashGfxPopOffset();\n \t\t\tdashGfxPopAlpha();\n@@ -486,11 +486,11 @@ int dashModalIp(const char *title, const char *help, char *ip_str, int size)\n \t\t\t\t * Match the 12px gap used by the credits sheet. */\n \t\t\t\thx += dashDrawButton(hx, hy, DI_BTN_UP, NULL) + 12;\n \t\t\t\thx += dashDrawButton(hx, hy, DI_BTN_DOWN, NULL) + 4;\n-\t\t\t\thx = dashTextS(hx, hy + 4.5f, \"Change\", DASH_FS_SMALL, DASH_C_TEXT2) + 12;\n+\t\t\t\thx = dashTextS(hx, hy + 4.5f, localizeDashboardText(\"Change\"), DASH_FS_SMALL, DASH_C_TEXT2) + 12;\n \t\t\t\thx += dashDrawButton(hx, hy, -1, \"L1\") + 2;\n \t\t\t\thx += dashDrawButton(hx, hy, -1, \"R1\") + 4;\n \t\t\t\thx = dashTextS(hx, hy + 4.5f, \"10\", DASH_FS_SMALL, DASH_C_TEXT2) + 12;\n-\t\t\t\tdashModalHintRow(hx, hy, dashOkIcon(), \"OK\", dashBackIcon(), \"Cancel\");\n+\t\t\t\tdashModalHintRow(hx, hy, dashOkIcon(), localizeDashboardText(\"OK\"), dashBackIcon(), localizeDashboardText(\"Cancel\"));\n \t\t\t}\n \t\t\tdashGfxPopOffset();\n \t\t\tdashGfxPopAlpha();\n@@ -863,6 +863,8 @@ int dashPickPath(const char *title, const char *start_dir, int mode, const char\n \t\t\ttop = sel;\n \t\tif (sel >= top + rows)\n \t\t\ttop = sel - rows + 1;\n+\t\tif (sel_anim < top || sel_anim >= top + rows)\n+\t\t\tsel_anim = (float)sel;\n \t\tfloat dt = dashVisualUpdate();\n \t\tsel_anim = dashApproach(sel_anim, (float)sel, 22.0f * dashAnimSpeed(), dt);\n \t\topen = dashApproach(open, 1.0f, 16.0f * dashAnimSpeed(), dt);\n@@ -926,9 +928,9 @@ int dashPickPath(const char *title, const char *start_dir, int mode, const char\n \t\t\t{\n \t\t\t\tfloat hx = x + 24, hy = y + h - 36;\n \t\t\t\thx += dashDrawButton(hx, hy, dashOkIcon(), NULL) + 4;\n-\t\t\t\thx = dashTextS(hx, hy + 4.5f, \"Open\", DASH_FS_SMALL, DASH_C_TEXT2) + 14;\n+\t\t\t\thx = dashTextS(hx, hy + 4.5f, localizeDashboardText(\"Open\"), DASH_FS_SMALL, DASH_C_TEXT2) + 14;\n \t\t\t\thx += dashDrawButton(hx, hy, dashBackIcon(), NULL) + 4;\n-\t\t\t\thx = dashTextS(hx, hy + 4.5f, pk.dir[0] ? \"Up\" : \"Cancel\", DASH_FS_SMALL, DASH_C_TEXT2) + 14;\n+\t\t\t\thx = dashTextS(hx, hy + 4.5f, pk.dir[0] ? localizeDashboardText(\"Up\") : localizeDashboardText(\"Cancel\"), DASH_FS_SMALL, DASH_C_TEXT2) + 14;\n \t\t\t\tif (mode == DASH_PICK_FOLDER && pk.dir[0]) {\n \t\t\t\t\thx += dashDrawButton(hx, hy, -1, \"START\") + 4;\n \t\t\t\t\thx = dashTextS(hx, hy + 4.5f, \"Use this folder\", DASH_FS_SMALL, DASH_C_TEXT2) + 14;\n@@ -937,7 +939,7 @@ int dashPickPath(const char *title, const char *start_dir, int mode, const char\n \t\t\t\t\thx = dashTextS(hx, hy + 4.5f, \"uLE:/ path\", DASH_FS_SMALL, DASH_C_TEXT2) + 14;\n \t\t\t\t}\n \t\t\t\thx += dashDrawButton(hx, hy, DI_BTN_TRIANGLE, NULL) + 4;\n-\t\t\t\tdashTextS(hx, hy + 4.5f, \"Cancel\", DASH_FS_SMALL, DASH_C_TEXT2);\n+\t\t\t\tdashTextS(hx, hy + 4.5f, localizeDashboardText(\"Cancel\"), DASH_FS_SMALL, DASH_C_TEXT2);\n \t\t\t}\n \t\t\tdashGfxPopOffset();\n \t\t\tdashGfxPopAlpha();"
        },
        {
          "sha": "c0cee4349ac8057c8d3a408e0934d39324992ebe",
          "filename": "src/filer_copy.c",
          "status": "modified",
          "additions": 27,
          "deletions": 188,
          "changes": 215,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/src%2Ffiler_copy.c",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/src%2Ffiler_copy.c",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/src%2Ffiler_copy.c?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -65,132 +65,31 @@ static int copyDiagFail(int result)\n \n void filerCopyDiagFormat(char *out, int size, int result)\n {\n-\tsnprintf(out, size, \"%s (%d)\\n%.39s\\n%s -> %s\\n%llu/%llu B\\nIOP %u>%u; %.12s\",\n-\t         copy_diag.stage ? copy_diag.stage : \"Copy failed\", copy_diag.error ? copy_diag.error : result,\n+\tint error = copy_diag.error ? copy_diag.error : result;\n+\tconst char *reason;\n+\n+\tswitch (error) {\n+\t\tcase -ENOSPC:\n+\t\t\treason = \"Not enough free space.\";\n+\t\t\tbreak;\n+\t\tcase -EACCES: reason = \"Access denied by the device. Check file or card protection.\"; break;\n+\t\tcase -EROFS: reason = \"The destination filesystem is read-only.\"; break;\n+\t\tcase -ENOENT: reason = \"A source or destination file or folder was not found.\"; break;\n+\t\tcase -ENODEV: reason = \"The device is unavailable.\"; break;\n+\t\tcase -EIO: reason = \"The device reported an I/O failure.\"; break;\n+\t\tcase -ETIMEDOUT: reason = \"The device did not respond in time.\"; break;\n+\t\tcase -ENOMEM: reason = \"Not enough memory to complete the transfer.\"; break;\n+\t\tcase -EEXIST: reason = \"A destination file or folder already exists.\"; break;\n+\t\tcase -EISDIR: reason = \"The destination name refers to a directory.\"; break;\n+\t\tdefault: reason = \"The operation failed. Keep the error code and device details below for diagnosis.\"; break;\n+\t}\n+\tsnprintf(out, size, \"%s (%d)\\n%s\\n%.39s\\n%s -> %s\\n%llu/%llu B\\nIOP %u>%u; %.12s\",\n+\t         copy_diag.stage ? copy_diag.stage : \"Copy failed\", error, reason,\n \t         copy_diag.name, copy_diag.source, copy_diag.destination,\n \t         (unsigned long long)copy_diag.done, (unsigned long long)copy_diag.total,\n \t         copy_diag.generation, getIopResetGeneration(), GIT_HASH);\n }\n \n-/* File replacements keep the old entry until a complete sibling copy has\n- * been closed and read back. Never delete an existing file to make room. */\n-static int copyPathMissing(int result)\n-{\n-\t/* genGetStat uses fileXio errno values, not raw libmc result codes. */\n-\treturn result == -ENOENT;\n-}\n-\n-static u32 copyChecksum(u32 hash, const void *data, int size)\n-{\n-\tconst unsigned char *p = data;\n-\twhile (size-- > 0)\n-\t\thash = (hash ^ *p++) * 16777619u;\n-\treturn hash;\n-}\n-\n-static int copyRenameFile(const char *from, const char *to)\n-{\n-\tiox_stat_t stat;\n-\tint ret = genGetStat(to, &stat);\n-\tif (!copyPathMissing(ret))\n-\t\treturn ret < 0 ? ret : -EEXIST;\n-\tif (!strncmp(from, \"mc\", 2)) {\n-\t\tsceMcTblGetDir info;\n-\t\tconst char *leaf = strrchr(to, '/');\n-\t\tif (leaf == NULL) leaf = strchr(to, ':');\n-\t\tif (leaf == NULL || !leaf[1] || strlen(leaf + 1) > sizeof(info.EntryName))\n-\t\t\treturn -EINVAL;\n-\t\tmemset(&info, 0, sizeof(info));\n-\t\tmemcpy(info.EntryName, leaf + 1, strlen(leaf + 1));\n-\t\tensureMemoryCardPortAccessible(from[2] - '0');\n-\t\tret = mcSetFileInfo(from[2] - '0', 0, from + 4, &info, 0x0010);\n-\t\tif (ret < 0) return ret;\n-\t\tif (mcSync(0, NULL, &ret) <= 0) return -EIO;\n-\t\tif (ret < 0) return ret;\n-\t} else {\n-\t\tret = fileXioRename(from, to);\n-\t}\n-\tif (ret < 0) return ret;\n-\t/* Do not discard a recovery copy on an ambiguous/false success. */\n-\tret = genGetStat(from, &stat);\n-\tif (!copyPathMissing(ret)) return -EIO;\n-\treturn genGetStat(to, &stat) < 0 ? -EIO : 0;\n-}\n-\n-static int copyPrepareTarget(char *out, char *target, char *backup)\n-{\n-\tiox_stat_t stat;\n-\tchar stage[MAX_PATH];\n-\tconst char *leaf;\n-\tint ret = genGetStat(out, &stat), parent, i;\n-\ttarget[0] = backup[0] = '\\0';\n-\tif (copyPathMissing(ret)) return 0;\n-\tif (ret < 0) return ret;\n-\tif (FIO_S_ISDIR(stat.mode)) return -EISDIR;\n-\tleaf = strrchr(out, '/');\n-\tif (leaf == NULL) leaf = strchr(out, ':');\n-\tif (leaf == NULL) return -EINVAL;\n-\tparent = (int)(leaf - out + 1);\n-\tfor (i = 0; i < 1000; i++) {\n-\t\tif (snprintf(stage, sizeof(stage), \"%.*sLS-COPY-%03d.TMP\", parent, out, i) >= (int)sizeof(stage) ||\n-\t\t    snprintf(backup, MAX_PATH, \"%.*sLS-OLD-%03d.BAK\", parent, out, i) >= MAX_PATH)\n-\t\t\treturn -ENAMETOOLONG;\n-\t\tret = genGetStat(stage, &stat);\n-\t\tif (ret >= 0) continue;\n-\t\tif (!copyPathMissing(ret)) return ret;\n-\t\tret = genGetStat(backup, &stat);\n-\t\tif (ret >= 0) continue;\n-\t\tif (!copyPathMissing(ret)) return ret;\n-\t\tstrcpy(target, out);\n-\t\tstrcpy(out, stage);\n-\t\treturn 0;\n-\t}\n-\treturn -EEXIST;\n-}\n-\n-static int copyVerifyTarget(const char *path, const char *name, u64 size, u32 checksum)\n-{\n-\tunsigned char buffer[4096];\n-\tu64 total = size;\n-\tu32 hash = 2166136261u;\n-\tint fd = genOpen(path, FIO_O_RDONLY), count, ret = -EIO;\n-\tif (fd < 0) return fd;\n-\twhile (size > 0) {\n-\t\tDashFileProgress(\"Verifying file\", name, size, total);\n-\t\tint request = size < sizeof(buffer) ? (int)size : (int)sizeof(buffer);\n-\t\tcount = genRead(fd, buffer, request);\n-\t\tif (count < 0) { ret = count; goto done; }\n-\t\tif (count == 0 || count > request) goto done;\n-\t\thash = copyChecksum(hash, buffer, count);\n-\t\tsize -= count;\n-\t}\n-\tif (hash == checksum) {\n-\t\tcount = genRead(fd, buffer, 1);\n-\t\tret = count < 0 ? count : (count == 0 ? 0 : -EIO);\n-\t}\n-done:\n-\tcount = genClose(fd);\n-\tif (ret < 0) return ret;\n-\tif (count < 0) return count;\n-\tDashFileProgress(\"Verifying file\", name, 0, total);\n-\treturn 0;\n-}\n-\n-static int copyPublishTarget(char *stage, const char *target, char *backup)\n-{\n-\tint ret = copyRenameFile(target, backup);\n-\tif (ret < 0) return ret;\n-\tret = copyRenameFile(stage, target);\n-\tif (ret < 0) {\n-\t\t/* Preserve both recoverable copies if rollback itself is refused. */\n-\t\tcopyRenameFile(backup, target);\n-\t\treturn ret;\n-\t}\n-\tgenRemove(backup); /* A failed cleanup retains the old copy, never the new one. */\n-\tstrcpy(stage, target);\n-\treturn 0;\n-}\n-\n static int isMemoryCardLikePath(const char *path)\n {\n \treturn (!strncmp(path, \"mc\", 2) || !strncmp(path, \"vmc\", 3)\n@@ -210,34 +109,6 @@ static int isHddCommonParty(const char *party)\n \treturn (!strncmp(party, \"hdd\", 3) && party[3] >= '0' && party[3] <= '9' && !strcmp(party + 4, \":__common\"));\n }\n \n-/* File drivers may return a positive short transfer. Complete only the\n- * remaining bytes, never replay already-consumed data or retry an error.\n- * A zero-byte result before the measured length is exhausted is failure. */\n-static int copyReadChunk(int fd, void *buffer, int size)\n-{\n-\tint done = 0;\n-\twhile (done < size) {\n-\t\tint count = genRead(fd, (char *)buffer + done, size - done);\n-\t\tif (count < 0) return count;\n-\t\tif (count == 0 || count > size - done) return -EIO;\n-\t\tdone += count;\n-\t}\n-\treturn done;\n-}\n-\n-static int copyWriteChunk(int fd, void *buffer, int size)\n-{\n-\tint done = 0;\n-\twhile (done < size) {\n-\t\tint count = genWrite(fd, (char *)buffer + done, size - done);\n-\t\tif (count < 0) return count;\n-\t\tif (count == 0 || count > size - done) return -EIO;\n-\t\tdone += count;\n-\t\tcopy_diag.done += count;\n-\t}\n-\treturn done;\n-}\n-\n static int calcTransferSpeed(u64 bytes, int millis)\n {\n \tu64 bytes_per_second;\n@@ -562,8 +433,6 @@ int copy(char *outPath, const char *inPath, FILEINFO file, int recurses)\n \tint nfiles, i;\n \tu64 size = 0;\n \tu64 original_size = 0;\n-\tu32 copy_hash = 2166136261u;\n-\tchar replace_target[MAX_PATH] = \"\", replace_backup[MAX_PATH] = \"\";\n \tint ret = -1, pfsout = -1, pfsin = -1, in_fd = -1, out_fd = -1, buffSize, bytesRead, bytesWritten;\n \tint buffCount = 1, buffIndex = 0;\n \tint dummy;\n@@ -1013,27 +882,15 @@ int copy(char *outPath, const char *inPath, FILEINFO file, int recurses)\n \t\t\tgoto copy_file_exit;\n \t\t}\n \t} else {            //Any other PasteMode than PM_PSU_BACKUP needs a new output file\n-\t\tint open_mode = FIO_O_WRONLY | FIO_O_CREAT | FIO_O_EXCL;\n #if defined(ETH) || defined(UDPFS)\n \t\tif (!strncmp(out, \"host:/\", 6))\n \t\t\tmakeHostPath(out, out);\n #endif\n \t\tgenLimObjName(out, 0);                                //Limit dest file name\n \t\tif (!strcmp(in, out)) { ret = 0; goto copy_file_exit; }\n-#ifdef ETH\n-\t\t/* The legacy host driver has no stat/rename operations. Retain its\n-\t\t * direct-write behavior; sibling replacement requires those operations. */\n-\t\tif (!strncmp(out, \"host:\", 5))\n-\t\t\topen_mode = FIO_O_WRONLY | FIO_O_CREAT | FIO_O_TRUNC;\n-\t\telse\n-#endif\n-\t\t{\n-\t\t\tfilerCopyDiagStage(\"Checking destination\");\n-\t\t\tret = copyPrepareTarget(out, replace_target, replace_backup);\n-\t\t\tif (ret < 0) goto copy_file_exit;\n-\t\t}\n \t\tfilerCopyDiagStage(\"Opening destination\");\n-\t\tout_fd = genOpen(out, open_mode);\n+\t\tdummy = genRemove(out);  // Upstream: remove the old file before creating its replacement.\n+\t\tout_fd = genOpen(out, FIO_O_WRONLY | FIO_O_TRUNC | FIO_O_CREAT);\n \t\tif (out_fd < 0) {\n \t\t\tret = out_fd;\n #if FILEOP_TRACE\n@@ -1075,12 +932,6 @@ int copy(char *outPath, const char *inPath, FILEINFO file, int recurses)\n \n \tif (size < (u64)buffSize)\n \t\tbuffSize = (int)size;\n-\t/* Bound returned I/O chunks on slow card media so the dashboard gets\n-\t * regular opportunities to draw its scene and handle cancellation. */\n-\tif (DashIsActive() && buffSize > 16384 &&\n-\t    (!strncmp(in, \"mc\", 2) || !strncmp(out, \"mc\", 2) ||\n-\t     !strncmp(in, \"vmc\", 3) || !strncmp(out, \"vmc\", 3)))\n-\t\tbuffSize = 16384;\n \n \tif (buffSize == 0) {\n #if FILEOP_TRACE\n@@ -1233,11 +1084,11 @@ int copy(char *outPath, const char *inPath, FILEINFO file, int recurses)\n \t\t\t}\n \t\t\tcopyBuff = (buffIndex == 0) ? buff : buff2;\n \t\t\tfilerCopyDiagStage(\"Reading source\");\n-\t\t\tbytesRead = copyReadChunk(in_fd, copyBuff, buffSize);\n+\t\t\tbytesRead = genRead(in_fd, copyBuff, buffSize);\n \t\t\tbytesWritten = 0;\n \t\t\tif (bytesRead == buffSize) {\n \t\t\t\tfilerCopyDiagStage(\"Writing destination\");\n-\t\t\t\tbytesWritten = copyWriteChunk(out_fd, copyBuff, buffSize);\n+\t\t\t\tbytesWritten = genWrite(out_fd, copyBuff, buffSize);\n \t\t\t}\n #if FILEOP_TRACE\n \t\t\tchunk_remaining_before = size;\n@@ -1260,7 +1111,6 @@ int copy(char *outPath, const char *inPath, FILEINFO file, int recurses)\n \t\t\t\tret = bytesRead < 0 ? bytesRead : (bytesWritten < 0 ? bytesWritten : -EIO);\n \t\t\t\tgoto copy_file_exit;\n \t\t\t}\n-\t\t\tcopy_hash = copyChecksum(copy_hash, copyBuff, buffSize);\n \t\t\tsize -= buffSize;\n \t\t\twritten_size += buffSize;\n \t\t\tcopy_diag.done = original_size - size;\n@@ -1308,20 +1158,8 @@ int copy(char *outPath, const char *inPath, FILEINFO file, int recurses)\n \t\telse if ((isPcCardCfPath(out) && !isPcCardCfOnline()) || (isPcCardCfPath(in) && !isPcCardCfOnline()))\n \t\t\tret = -EIO;\n \t\tout_fd = -1;  //prevent dual closure attempt\n-\t\tif (ret < 0) {\n-\t\t\tif (PM_flag[recurses] != PM_PSU_BACKUP)\n-\t\t\t\tgenRemove(out);\n+\t\tif (ret < 0)\n \t\t\tgoto copy_file_exit;\n-\t\t}\n-\t}\n-\n-\tif (replace_target[0]) {\n-\t\tfilerCopyDiagStage(\"Verifying replacement\");\n-\t\tret = copyVerifyTarget(out, file.name, original_size, copy_hash);\n-\t\tif (ret < 0) { genRemove(out); goto copy_file_exit; }\n-\t\tfilerCopyDiagStage(\"Publishing replacement\");\n-\t\tret = copyPublishTarget(out, replace_target, replace_backup);\n-\t\tif (ret < 0) goto copy_file_exit;\n \t}\n \n \tif (!strncmp(out, \"mc\", 2)) {                                 //Handle file copied to MC\n@@ -1355,7 +1193,8 @@ int copy(char *outPath, const char *inPath, FILEINFO file, int recurses)\n #endif\n \t} else {                                          //Handle file copied to non-MC\n \t\tif (!strncmp(out, \"host\", 4) || !strncmp(out, \"udpfs\", 5)) {  //for files copied to host/udpfs: we skip Chstat\n-\t\t} else if (!strncmp(out, \"mass\", 4)) {  //for files copied to mass: we skip Chstat\n+\t\t} else if (!strncmp(out, \"mmce\", 4)) {  //MMCE has no Chstat operation\n+\t\t\t} else if (!strncmp(out, \"mass\", 4)) {  //for files copied to mass: we skip Chstat\n #ifdef MX4SIO\n \t\t\t} else if (!strncmp(out, \"mx4sio\", 6)) {  //for files copied to mx4sio: we skip Chstat\n #endif"
        },
        {
          "sha": "02e2dbde101c90fb6527b21487e5d5d09ff748cd",
          "filename": "src/lang.c",
          "status": "modified",
          "additions": 69,
          "deletions": 0,
          "changes": 69,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/src%2Flang.c",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/src%2Flang.c",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/src%2Flang.c?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -571,3 +571,72 @@ void Load_External_Language(void)\n //---------------------------------------------------------------------------\n //End of file:  lang.c\n //---------------------------------------------------------------------------\n+\n+/* Only UI-owned labels/formats opt in. Resolve against the current table on\n+ * every call, so changing language or replacing an external LNG cannot leave\n+ * stale translated pointers in static dashboard metadata. */\n+const char *localizeDashboardText(const char *source)\n+{\n+\tstatic const struct { const char *source; int id; } labels[] = {\n+#include \"../Lang/DASHBOARD.inc\"\n+\t};\n+\tint lo = 0, hi = (int)(sizeof(labels) / sizeof(labels[0]));\n+\tif (source == NULL)\n+\t\treturn source;\n+\twhile (lo < hi) {\n+\t\tint mid = lo + (hi - lo) / 2;\n+\t\tint cmp = strcmp(source, labels[mid].source);\n+\t\tif (cmp == 0) {\n+\t\t\tconst char *translated = Lang_String[labels[mid].id].String;\n+\t\t\treturn translated != NULL && translated[0] ? translated : source;\n+\t\t}\n+\t\tif (cmp < 0)\n+\t\t\thi = mid;\n+\t\telse\n+\t\t\tlo = mid + 1;\n+\t}\n+\treturn source;\n+}\n+\n+/* External LNG overrides are untrusted: accept only the argument conversions\n+ * expected by a dashboard format string. Invalid overrides use English. */\n+static const char *dashboardNextFormatConversion(const char *cursor)\n+{\n+\twhile (*cursor) {\n+\t\tif (*cursor != '%')\n+\t\t\tcursor++;\n+\t\telse if (cursor[1] == '%')\n+\t\t\tcursor += 2; /* escaped literal percent: no argument */\n+\t\telse\n+\t\t\treturn cursor;\n+\t}\n+\treturn NULL;\n+}\n+\n+const char *localizeDashboardFormat(const char *source)\n+{\n+\tconst char *expected, *translated, *candidate;\n+\tsize_t length;\n+\tif (source == NULL)\n+\t\treturn NULL;\n+\ttranslated = localizeDashboardText(source);\n+\texpected = source;\n+\tcandidate = translated;\n+\twhile ((expected = dashboardNextFormatConversion(expected)) != NULL) {\n+\t\tcandidate = dashboardNextFormatConversion(candidate);\n+\t\tif (candidate == NULL)\n+\t\t\treturn source;\n+\t\t/* Refuse unsupported source signatures instead of guessing. */\n+\t\tif (!strncmp(expected, \"%d\", 2) || !strncmp(expected, \"%s\", 2))\n+\t\t\tlength = 2;\n+\t\telse if (!strncmp(expected, \"%.2f\", 4))\n+\t\t\tlength = 4;\n+\t\telse\n+\t\t\treturn source;\n+\t\tif (strncmp(candidate, expected, length))\n+\t\t\treturn source;\n+\t\texpected += length;\n+\t\tcandidate += length;\n+\t}\n+\treturn dashboardNextFormatConversion(candidate) == NULL ? translated : source;\n+}"
        },
        {
          "sha": "3b51ea3c79818dd92ea199650abfaa590f09a938",
          "filename": "tests/test_build_identity.py",
          "status": "added",
          "additions": 40,
          "deletions": 0,
          "changes": 40,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_build_identity.py",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_build_identity.py",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/tests%2Ftest_build_identity.py?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -0,0 +1,40 @@\n+\"\"\"Exercise identity stamping independently of any old ignored build header.\"\"\"\n+from pathlib import Path\n+import subprocess\n+import sys\n+import shutil\n+import tempfile\n+\n+root = Path(__file__).resolve().parents[1]\n+sha = subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=root, text=True).strip()\n+date = subprocess.check_output(['git', 'show', '-s', '--format=%cs', 'HEAD'], cwd=root, text=True).strip()\n+with tempfile.TemporaryDirectory(prefix='build-identity-') as folder:\n+    output = Path(folder) / 'githash.h'\n+    output.write_text('#define GIT_HASH \"stale-build\"\\n')\n+    args = [sys.executable, str(root / 'scripts/gen_githash.py'), '--require-git', '--output', str(output)]\n+    subprocess.run(args, cwd=folder, check=True)\n+    content = output.read_text()\n+    assert f'#define GIT_HASH \"{sha[:12]}\"' in content\n+    assert f'#define ULE_VERDATE \"{date}\"' in content\n+    timestamp = output.stat().st_mtime_ns\n+    subprocess.run(args, cwd=folder, check=True)\n+    assert output.read_text() == content and output.stat().st_mtime_ns == timestamp\n+workflow = (root / '.github/workflows/compile.yml').read_text()\n+assert workflow.index('python3 scripts/gen_githash.py --require-git') < workflow.index('ps2build build\\n')\n+print('PASS: stale header replaced with checkout identity; stable reruns; CI stamps before target builds')\n+\n+# An unpacked source tree nested in another checkout has no own identity.\n+with tempfile.TemporaryDirectory(prefix='archive-identity-') as folder:\n+    parent = Path(folder)\n+    subprocess.run(['git', 'init', str(parent)], check=True, capture_output=True)\n+    subprocess.run(['git', '-C', str(parent), '-c', 'user.name=Fixture',\n+                    '-c', 'user.email=fixture@example.invalid', 'commit',\n+                    '--allow-empty', '-m', 'Parent repository'], check=True, capture_output=True)\n+    archive = parent / 'unpacked'\n+    (archive / 'scripts').mkdir(parents=True)\n+    shutil.copyfile(root / 'scripts/gen_githash.py', archive / 'scripts/gen_githash.py')\n+    script = archive / 'scripts/gen_githash.py'\n+    result = subprocess.run([sys.executable, str(script), '--require-git'], capture_output=True)\n+    assert result.returncode != 0 and not (archive / 'githash.h').exists()\n+    subprocess.run([sys.executable, str(script)], check=True, capture_output=True)\n+    assert '#define GIT_HASH \"local\"' in (archive / 'githash.h').read_text()"
        },
        {
          "sha": "690136289472d62a68ac61fda19abc52f7380d28",
          "filename": "tests/test_carousel_culling.py",
          "status": "added",
          "additions": 61,
          "deletions": 0,
          "changes": 61,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_carousel_culling.py",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_carousel_culling.py",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/tests%2Ftest_carousel_culling.py?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -0,0 +1,61 @@\n+\"\"\"Execute carousel drawing and compare its visible cards with the old loop.\"\"\"\n+from pathlib import Path\n+import shutil,subprocess,tempfile\n+root=Path(__file__).resolve().parents[1]\n+source=(root/'src/dash_apps.c').read_text()\n+a=source.index('static void drawCarousel('); b=source.index('static void appsDraw(',a)\n+production=source[a:b]\n+reference=production.replace('drawCarousel(void)','drawCarouselReference(void)').replace('if (left + s < -8.0f || left > DASH_VW + 8.0f)','if (0)')\n+code=r'''\n+#include <assert.h>\n+#include <math.h>\n+#include <stdio.h>\n+#include <string.h>\n+#define DASH_VW 640\n+#define DASH_BLEND_ADD 1\n+#define DASH_BLEND_NORMAL 0\n+#define DASH_C_ACCENT 0\n+#define DASH_C_TEXT 0\n+#define DASH_C_TEXT2 0\n+#define DASH_C_TEXT3 0\n+#define DASH_FS_TITLE 1\n+#define DASH_FS_SMALL 1\n+#define DASH_FS_CAPTION 1\n+typedef struct {char title[16],desc[16],path[16];} DashApp;\n+static DashApp dashApps[100];static int order[100],norder,sel;\n+static float sel_anim;\n+static struct {int glow;} dashTheme;\n+static DashApp *selApp(void){return norder?&dashApps[sel]:NULL;}\n+static void dashGfxBlend(int x){(void)x;}\n+static int dashColorA(int c,float a){(void)a;return c;}\n+static void dashGlow(float x,float y,float w,float h,int c){(void)x;(void)y;(void)w;(void)h;(void)c;}\n+static float dashClampf(float x,float a,float b){return fminf(b,fmaxf(a,x));}\n+static float dashLerpf(float a,float b,float t){return a+(b-a)*t;}\n+static void dashGfxPushAlpha(float a){(void)a;}static void dashGfxPopAlpha(void){}\n+static void dashDrawTileFocus(float x,float y,float w,float h,float r,float f){(void)x;(void)y;(void)w;(void)h;(void)r;(void)f;}\n+static void dashTextScrollCenter(float x,float y,float w,const char*s,float f,int c){(void)x;(void)y;(void)w;(void)s;(void)f;(void)c;}\n+static void dashTextCenter(float x,float y,const char*s,float f,int c){(void)x;(void)y;(void)s;(void)f;(void)c;}\n+static void dashLibDeviceLabel(const char*p,char*b,int n){(void)p;snprintf(b,n,\"device\");}\n+typedef struct {int idx;float x,y,w,h,f;int prio;} Draw;\n+static Draw calls[16];static int count;\n+static void dashDrawAppArt(DashApp*a,float x,float y,float w,float h,float f,int p){assert(count<16);calls[count++]=(Draw){(int)(a-dashApps),x,y,w,h,f,p};}\n+'''\n+code+=production+reference\n+code+=r'''\n+int main(void){\n+ for(int i=0;i<100;i++)order[i]=i;\n+ int saved=0;\n+ for(norder=1;norder<=100;norder+=3)for(sel=0;sel<norder;sel++)for(int phase=-4;phase<=4;phase++){\n+  sel_anim=sel+phase*0.125f;count=0;drawCarouselReference();\n+  Draw expected[16];int n=0,old=count;\n+  for(int i=0;i<count;i++)if(!(calls[i].x+calls[i].w < -8 || calls[i].x > DASH_VW+8))expected[n++]=calls[i];\n+  count=0;drawCarousel();assert(count==n);saved+=old-count;\n+  for(int i=0;i<n;i++)assert(calls[i].idx==expected[i].idx&&calls[i].x==expected[i].x&&calls[i].y==expected[i].y&&calls[i].w==expected[i].w&&calls[i].f==expected[i].f&&calls[i].prio==expected[i].prio);\n+ }\n+ assert(saved>0);puts(\"PASS: identical visible carousel geometry/order across fractional poses; offscreen artwork/tiles omitted\");\n+}\n+'''\n+with tempfile.TemporaryDirectory(prefix='carousel-culling-') as folder:\n+ p=Path(folder);(p/'test.c').write_text(code)\n+ subprocess.run([shutil.which('gcc'),'-std=c99','-Wall','-Wextra','-Werror',str(p/'test.c'),'-lm','-o',str(p/'test')],check=True)\n+ subprocess.run([str(p/'test')],check=True)"
        },
        {
          "sha": "c5cc6721a0377fb261a66460439c4228baa2afaf",
          "filename": "tests/test_clock_save_action.py",
          "status": "modified",
          "additions": 1,
          "deletions": 1,
          "changes": 2,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_clock_save_action.py",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_clock_save_action.py",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/tests%2Ftest_clock_save_action.py?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -74,6 +74,6 @@ def function(path, signature):\n }\n '''\n with tempfile.TemporaryDirectory(prefix='clock-save-') as folder:\n- p=Path(folder);(p/'test.c').write_text(code)\n+ p=Path(folder);(p/'test.c').write_text(\"#define localizeDashboardText(s) (s)\\n\" + code)\n  subprocess.run([shutil.which('gcc'),'-std=c99','-Wall','-Wextra','-Werror',str(p/'test.c'),'-o',str(p/'test')],check=True)\n  subprocess.run([str(p/'test')],cwd=p,check=True)"
        },
        {
          "sha": "a3aa576bd2c916d6c41a58d4a456c801cb01e034",
          "filename": "tests/test_cloud_menu.py",
          "status": "modified",
          "additions": 1,
          "deletions": 1,
          "changes": 2,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_cloud_menu.py",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_cloud_menu.py",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/tests%2Ftest_cloud_menu.py?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -159,6 +159,6 @@ def function(signature):\n with tempfile.TemporaryDirectory(prefix=\"wle-cloud-menu-\") as tmp:\n     harness = Path(tmp) / \"cloud.c\"\n     exe = Path(tmp) / \"cloud.exe\"\n-    harness.write_text(code, encoding=\"utf-8\")\n+    harness.write_text(\"#define localizeDashboardText(s) (s)\\n\" + code, encoding=\"utf-8\")\n     subprocess.run([compiler, \"-std=gnu11\", str(harness), \"-o\", str(exe)], check=True)\n     subprocess.run([str(exe)], check=True)"
        },
        {
          "sha": "c9069e6c532dadb086b86a1482f9d8cb7fe323ad",
          "filename": "tests/test_copy_pipeline.py",
          "status": "modified",
          "additions": 70,
          "deletions": 21,
          "changes": 91,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_copy_pipeline.py",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_copy_pipeline.py",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/tests%2Ftest_copy_pipeline.py?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -1,7 +1,7 @@\n \ufeff\"\"\"Run the full production clipboard/paste/copy sequence on POSIX fixtures.\n \n Drivers/RPCs are replaced by bounded file adapters and injected failures. The\n-copy orchestration, recursion, replacement transaction and diagnostics are real.\n+copy orchestration, recursion, direct overwrite and diagnostics are real.\n This does not emulate a UDPFS server, libmc or physical memory cards.\n \"\"\"\n from pathlib import Path\n@@ -11,6 +11,11 @@\n root=Path(__file__).resolve().parents[1]\n copy=(root/'src/filer_copy.c').read_text()\n browser=(root/'src/filer_browser.c').read_text()\n+assert 'dummy = genRemove(out);' in copy\n+assert 'out_fd = genOpen(out, FIO_O_WRONLY | FIO_O_TRUNC | FIO_O_CREAT);' in copy\n+for removed in ('copyPrepareTarget', 'copyLookupPath', 'copyVerifyTarget',\n+                'copyPublishTarget', 'copyOpenTarget', 'LS-COPY-', 'LS-OLD-', 'FIO_O_EXCL'):\n+    assert removed not in copy, removed\n \n def function(source, signature):\n     a=source.index(signature);b=source.index('{',a)+1;depth=1\n@@ -24,6 +29,7 @@ def function(source, signature):\n #include <stdio.h>\n #include <stdlib.h>\n #include <string.h>\n+#include <strings.h>\n #include <errno.h>\n #include <fcntl.h>\n #include <unistd.h>\n@@ -65,6 +71,7 @@ def function(source, signature):\n #define FIO_O_EXCL O_EXCL\n #define FIO_O_TRUNC O_TRUNC\n #define FIO_S_ISDIR S_ISDIR\n+#define FIO_S_ISREG S_ISREG\n #define FIO_CST_ATTR 1\n #define FIO_CST_CT 2\n #define FIO_CST_AT 4\n@@ -82,8 +89,8 @@ def function(source, signature):\n static FILEINFO clipFiles[MAX_ENTRY];static char clipPath[MAX_PATH];\n static unsigned clipIopResetGeneration,generation;\n static u64 written_size,PasteTime,clock_ms;\n-static int source_ready=1,load_ok=1,reset_preflight,read_cap=4096,write_cap=37,cancel_copy;\n-static int fail_open,fail_rewind,fail_read,fail_write,fail_close,fail_listing,fail_stat,fail_out_open,early_eof,fail_measure,corrupt_verify,fail_publish;\n+static int source_ready=1,load_ok=1,reset_preflight,read_cap=1048576,write_cap=1048576,cancel_copy;\n+static int fail_open,fail_rewind,fail_read,fail_write,fail_close,fail_listing,fail_out_open,early_eof,fail_measure;\n static int fd_kind[1024],handles,read_calls,write_calls;\n static unsigned fd_generation[1024];\n static u64 Timer(void){return ++clock_ms;}\n@@ -103,7 +110,7 @@ def function(source, signature):\n static void ensureMemoryCardPortAccessible(int p){assert(p==0||p==1);}\n static char *makeHostPath(char *d,char *s){if(d!=s)strcpy(d,s);return d;}\n static void genLimObjName(char *p,int reserve){(void)p;(void)reserve;}\n-static int genGetStat(const char *p,iox_stat_t *out){struct stat s;if(!strncmp(p,\"host:\",5))return -EIO;if(fail_stat&&!strncmp(p,\"mc0:\",4))return -EACCES;if(stat(p,&s))return -errno;out->mode=s.st_mode;return 0;}\n+static int genGetStat(const char *p,iox_stat_t *out){(void)p;(void)out;assert(!\"paste must not stat the destination\");return -EIO;}\n static int genOpen(const char *p,int flags){\n  if(fail_open&&flags==O_RDONLY&&!strncmp(p,\"udpfs:\",6))return -ENOENT;\n  if(fail_out_open&&(flags&O_WRONLY))return -EACCES;\n@@ -113,17 +120,17 @@ def function(source, signature):\n }\n static int genClose(int fd){int kind=fd_kind[fd];assert(kind&&handles>0);fd_kind[fd]=0;handles--;int r=close(fd);return fail_close&&kind==2?-EIO:r;}\n static s64 genLseek(int fd,s64 n,int whence){assert(fd_generation[fd]==generation);if(fail_measure&&whence==SEEK_END)return -EIO;if(fail_rewind&&whence==SEEK_SET)return -EIO;return lseek(fd,n,whence);}\n-static int genRead(int fd,void *p,int n){assert(fd_generation[fd]==generation);read_calls++;if(early_eof&&fd_kind[fd]==1&&read_calls>1)return 0;if(fail_read&&fd_kind[fd]==1)return -ETIMEDOUT;int r=read(fd,p,n>read_cap?read_cap:n);if(corrupt_verify&&fd_kind[fd]==3&&r>0)((char*)p)[0]^=1;return r<0?-errno:r;}\n-static int genWrite(int fd,void *p,int n){assert(fd_generation[fd]==generation);write_calls++;if(fail_write&&write_calls>=2)return -ENOSPC;int r=write(fd,p,n>write_cap?write_cap:n);return r<0?-errno:r;}\n+static int genRead(int fd,void *p,int n){assert(fd_generation[fd]==generation);read_calls++;if(early_eof&&fd_kind[fd]==1)return 0;if(fail_read&&fd_kind[fd]==1)return -ETIMEDOUT;int r=read(fd,p,n>read_cap?read_cap:n);return r<0?-errno:r;}\n+static int genWrite(int fd,void *p,int n){assert(fd_generation[fd]==generation);write_calls++;if(fail_write)return -ENOSPC;int r=write(fd,p,n>write_cap?write_cap:n);return r<0?-errno:r;}\n static int genRemove(char *p){return unlink(p)?-errno:0;}\n-static int fileXioRename(const char *a,const char *b){return fail_publish?-EIO:rename(a,b)?-errno:0;}\n+static int fileXioRename(const char *a,const char *b){assert(!\"paste must not require rename\");(void)a;(void)b;return -EIO;}\n static int mc_result;\n static int mcSetFileInfo(int port,int slot,const char *from,const sceMcTblGetDir *info,int flags){\n  (void)slot;if(flags==0x10){char a[MAX_PATH],b[MAX_PATH];snprintf(a,sizeof(a),\"mc%d:/%s\",port,from);strcpy(b,a);char *leaf=strrchr(b,'/');assert(leaf);snprintf(leaf+1,sizeof(b)-(leaf+1-b),\"%.32s\",info->EntryName);mc_result=fileXioRename(a,b);}else mc_result=0;return 0;\n }\n static int mcSync(int mode,void *c,int *r){(void)mode;(void)c;if(r)*r=mc_result;return 1;}\n static int mcGetInfo(int p,int s,int *type,int *a,int *b){(void)p;(void)s;if(type)*type=2;if(a)*a=1000;if(b)*b=1000;return 0;}\n-static int fileXioChStat(const char *p,const iox_stat_t *s,int f){(void)p;(void)s;(void)f;return 0;}\n+static int fileXioChStat(const char *p,const iox_stat_t *s,int f){assert(strncmp(p,\"mmce\",4));(void)p;(void)s;(void)f;return 0;}\n static int getHddParty(const char *p,const FILEINFO *f,char *a,char *b){(void)p;(void)f;(void)a;(void)b;assert(0);return -1;}\n static int mountParty(const char *p){(void)p;assert(0);return -1;}\n static int fileShowTitles(void){return 0;}\n@@ -132,7 +139,8 @@ def function(source, signature):\n static int filerConfirmExploitDelete(const char *p,const FILEINFO *f){(void)p;(void)f;return 1;}\n static int filerConfirmExploitModify(const char *p,const FILEINFO *f){(void)p;(void)f;return 1;}\n static int filerIsExploitProtectedPath(const char *p,const FILEINFO *f){(void)p;(void)f;return 0;}\n-static int delete(const char *p,const FILEINFO *f){(void)p;(void)f;assert(0);return -1;}\n+static int move_deletes;\n+static int delete(const char *p,const FILEINFO *f){char source[MAX_PATH];assert(!handles);assert(!access(\"mmce0:/MOVE.BIN\",F_OK));snprintf(source,sizeof(source),\"%s%s\",p,f->name);move_deletes++;return genRemove(source);}\n static void markMx4sioDestinationAfterWrite(const char *p){(void)p;}\n static int genCmpFileExt(const char *p,const char *e){(void)p;(void)e;return 0;}\n static int newdir(const char *parent,const char *name){char p[MAX_PATH];snprintf(p,sizeof(p),\"%s%s\",parent,name);return mkdir(p,0700)?-errno:0;}\n@@ -149,12 +157,12 @@ def function(source, signature):\n #define psu_restore_open_root_image(...) unexpected()\n #define psu_restore_apply_entry_stats_to_mc(...) unexpected()\n static int applyMcInfoToVmcPath(const char *p,const sceMcTblGetDir *s){(void)p;(void)s;return 0;}\n+\n '''\n # Use the real PSU layout declarations; these branches are compiled but not executed.\n psu=(root/'include/psu_types.h').read_text();code+=psu[psu.index('typedef struct'):psu.rindex('#endif')]\n a=copy.index('/* One paste context');b=copy.index('static int isMemoryCardLikePath(',a);code+=copy[a:b]\n for sig in ('static int isMemoryCardLikePath(', 'static int isHddCommonParty(',\n-            'static int copyReadChunk(', 'static int copyWriteChunk(',\n             'static int calcTransferSpeed(', 'static int calcRemainingTime(',\n             'static int smoothTransferSpeed(', 'static int applyMcInfoToMcPath('):\n     code+=function(copy,sig)\n@@ -165,14 +173,14 @@ def function(source, signature):\n static void put(const char *p,const unsigned char *data,int n){int fd=open(p,O_CREAT|O_TRUNC|O_WRONLY,0600);assert(fd>=0);assert(write(fd,data,n)==n);assert(!close(fd));}\n static int matches(const char *p,const unsigned char *data,int n){unsigned char out[70000];int fd=open(p,O_RDONLY);if(fd<0)return 0;int count=read(fd,out,sizeof(out));close(fd);return count==n&&!memcmp(data,out,n);}\n static unsigned char input[65537];\n-static void reset(void){assert(!handles);source_ready=load_ok=1;reset_preflight=0;fail_open=fail_rewind=fail_read=fail_write=fail_close=fail_listing=cancel_copy=fail_stat=fail_out_open=early_eof=fail_measure=corrupt_verify=fail_publish=0;read_calls=write_calls=0;browser_cut=0;browser_cd=0;generation=1;clipIopResetGeneration=1;PasteMode=PM_NORMAL;strcpy(clipPath,\"udpfs:/\");nclipFiles=1;memset(clipFiles,0,sizeof(clipFiles));strcpy(clipFiles[0].name,\"BOOT.ELF\");clipFiles[0].stats.FileSizeByte=sizeof(input);}\n+static void reset(void){assert(!handles);source_ready=load_ok=1;reset_preflight=0;fail_open=fail_rewind=fail_read=fail_write=fail_close=fail_listing=cancel_copy=fail_out_open=early_eof=fail_measure=0;read_calls=write_calls=0;browser_cut=0;browser_cd=0;generation=1;clipIopResetGeneration=1;PasteMode=PM_NORMAL;strcpy(clipPath,\"udpfs:/\");nclipFiles=1;memset(clipFiles,0,sizeof(clipFiles));strcpy(clipFiles[0].name,\"BOOT.ELF\");clipFiles[0].stats.FileSizeByte=sizeof(input);}\n int main(void){\n  assert(sizeof(copy_diag)<=128); /* fixed runtime budget, no log allocation */\n- mkdir(\"udpfs:\",0700);mkdir(\"mc0:\",0700);mkdir(\"mc1:\",0700);mkdir(\"usb0:\",0700);mkdir(\"host:\",0700);\n+ mkdir(\"udpfs:\",0700);mkdir(\"mc0:\",0700);mkdir(\"mc1:\",0700);mkdir(\"usb0:\",0700);mkdir(\"host:\",0700);mkdir(\"mmce0:\",0700);mkdir(\"mmce1:\",0700);\n  for(int i=0;i<(int)sizeof(input);i++)input[i]=(unsigned char)(i*19+7);\n  put(\"udpfs:/BOOT.ELF\",input,sizeof(input));\n- const char *destinations[]={\"mc0:/\",\"mc1:/\",\"usb0:/\"};char message[MAX_PATH],target[MAX_PATH];\n- for(int device=0;device<3;device++)for(int attempt=0;attempt<3;attempt++){\n+ const char *destinations[]={\"mc0:/\",\"mc1:/\",\"usb0:/\",\"mmce0:/\",\"mmce1:/\"};char message[MAX_PATH],target[MAX_PATH];\n+ for(int device=0;device<5;device++)for(int attempt=0;attempt<3;attempt++){\n   reset();message[0]=0;char dest[12];strcpy(dest,destinations[device]);\n   subfunc_Paste(message,dest);assert(!message[0]&&!handles);\n   snprintf(target,sizeof(target),\"%sBOOT.ELF\",dest);assert(matches(target,input,sizeof(input)));\n@@ -195,20 +203,61 @@ def function(source, signature):\n   }\n  }\n  const unsigned char old[]=\"original ELF\";\n- const char *stages[]={\"Opening source\",\"Rewinding source\",\"Reading source\",\"Writing destination\",\"Closing destination\",\"Copy cancelled\",\"Preparing devices\",\"Checking source\",\"Checking destination\",\"Opening destination\",\"Reading source\",\"Measuring source\",\"Verifying replacement\",\"Publishing replacement\"};\n- for(int failure=0;failure<14;failure++){\n+ const char *stages[]={\"Opening source\",\"Rewinding source\",\"Reading source\",\"Writing destination\",\"Closing destination\",\"Copy cancelled\",\"Preparing devices\",\"Checking source\",\"Opening destination\",\"Reading source\",\"Measuring source\"};\n+ for(int failure=0;failure<11;failure++){\n   reset();put(\"mc0:/BOOT.ELF\",old,sizeof(old));\n   if(failure==0)fail_open=1;if(failure==1)fail_rewind=1;if(failure==2)fail_read=1;\n   if(failure==3)fail_write=1;if(failure==4)fail_close=1;if(failure==5)cancel_copy=1;\n   if(failure==6)load_ok=0;if(failure==7){source_ready=0;reset_preflight=1;}\n-  if(failure==8)fail_stat=1;if(failure==9)fail_out_open=1;if(failure==10)early_eof=1;\n-  if(failure==11)fail_measure=1;if(failure==12)corrupt_verify=1;if(failure==13)fail_publish=1;\n+  if(failure==8)fail_out_open=1;if(failure==9)early_eof=1;if(failure==10)fail_measure=1;\n   message[0]=0;subfunc_Paste(message,\"mc0:/\");\n   assert(message[0]&&strstr(message,stages[failure])&&strstr(message,\"fixture-head\"));\n-  assert(!handles&&matches(\"mc0:/BOOT.ELF\",old,sizeof(old)));\n-  if(failure==3){assert(strstr(message,\"37/65537 B\")&&strstr(message,\"(-28)\"));}\n+  assert(!handles);\n+  if(failure==0||failure==1||failure==6||failure==7||failure==10)\n+   assert(matches(\"mc0:/BOOT.ELF\",old,sizeof(old)));\n+  else if(failure==4) assert(matches(\"mc0:/BOOT.ELF\",input,sizeof(input))); /* close error retains output, as upstream */\n+  else assert(access(\"mc0:/BOOT.ELF\",F_OK)); /* upstream direct replacement cleanup */\n+  if(failure==3){assert(strstr(message,\"0/65537 B\")&&strstr(message,\"(-28)\"));}\n   if(failure==7)assert(strstr(message,\"IOP 1>2\"));\n  }\n+ /* Direct copying does not depend on a driver's missing-entry stat status. */\n+ reset();unlink(\"mc1:/BOOT.ELF\");\n+ for(int attempt=0;attempt<3;attempt++){\n+  message[0]=0;subfunc_Paste(message,\"mc1:/\");assert(!message[0]&&!handles);\n+  assert(matches(\"mc1:/BOOT.ELF\",input,sizeof(input)));\n+ }\n+ /* Cross-device source/destination matrix. Any destination stat/rename call\n+  * aborts in the adapters, including ordinary MC-to-MMCE replacements. */\n+ const char *cross_sources[]={\"usb0:/\",\"mc0:/\",\"mmce0:/\"};\n+ const char *cross_destinations[]={\"mc1:/\",\"mmce0:/\",\"mmce1:/\",\"usb0:/\"};\n+ put(\"usb0:/EISDIR.ELF\",input,sizeof(input));\n+ put(\"mc0:/EISDIR.ELF\",input,sizeof(input));\n+ put(\"mmce0:/EISDIR.ELF\",input,sizeof(input));\n+ for(int source=0;source<3;source++)for(int destination=0;destination<4;destination++) {\n+  if(!strcmp(cross_sources[source],cross_destinations[destination]))continue;\n+  snprintf(target,sizeof(target),\"%sEISDIR.ELF\",cross_destinations[destination]);\n+  unlink(target);\n+  for(int attempt=0;attempt<2;attempt++){\n+   reset();\n+   strcpy(clipPath,cross_sources[source]);\n+   strcpy(clipFiles[0].name,\"EISDIR.ELF\");\n+   message[0]=0;subfunc_Paste(message,(char*)cross_destinations[destination]);\n+   assert(!message[0]&&!handles);\n+   assert(matches(target,input,sizeof(input)));\n+  }\n+ }\n+\n+ /* Nested ordinary folders use the same production recursion. */\n+ reset();put(\"mc0:/MOVE.BIN\",input,sizeof(input));\n+ strcpy(clipPath,\"mc0:/\");strcpy(clipFiles[0].name,\"MOVE.BIN\");browser_cut=1;\n+ message[0]=0;subfunc_Paste(message,\"mmce0:/\");\n+ assert(!message[0]&&move_deletes==1&&!nclipFiles);\n+ assert(access(\"mc0:/MOVE.BIN\",F_OK)&&matches(\"mmce0:/MOVE.BIN\",input,sizeof(input)));\n+ reset();put(\"mc0:/MOVE.BIN\",input,sizeof(input));\n+ strcpy(clipPath,\"mc0:/\");strcpy(clipFiles[0].name,\"MOVE.BIN\");browser_cut=1;fail_write=1;\n+ message[0]=0;subfunc_Paste(message,\"mmce0:/\");\n+ assert(message[0]&&move_deletes==1&&nclipFiles==1);\n+ assert(matches(\"mc0:/MOVE.BIN\",input,sizeof(input))&&!handles);\n  /* Nested ordinary folders use the same production recursion. */\n  reset();mkdir(\"udpfs:/NESTED\",0700);put(\"udpfs:/NESTED/CHILD.BIN\",input,257);\n  strcpy(clipFiles[0].name,\"NESTED\");clipFiles[0].stats.AttrFile=sceMcFileAttrSubdir;\n@@ -222,7 +271,7 @@ def function(source, signature):\n  /* Repeated operations clear prior error state, and formatting is bounded. */\n  reset();message[0]=0;subfunc_Paste(message,\"mc0:/\");assert(!message[0]);\n  char bounded[17];memset(bounded,'x',sizeof(bounded));filerCopyDiagFormat(bounded,16,-1);assert(!bounded[15]&&bounded[16]=='x');\n- puts(\"PASS: full paste/copy sequence, both slots, replacement, failures, recursion and bounded diagnostics\");\n+ puts(\"PASS: full paste/copy sequence, both slots, upstream direct overwrite, failures, recursion and bounded diagnostics\");\n }\n '''\n with tempfile.TemporaryDirectory(prefix='copy-pipeline-') as folder:"
        },
        {
          "sha": "b50ff97e01f3250f966450ec1280f27ea98e3499",
          "filename": "tests/test_copy_short_io.py",
          "status": "modified",
          "additions": 20,
          "deletions": 21,
          "changes": 41,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_copy_short_io.py",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_copy_short_io.py",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/tests%2Ftest_copy_short_io.py?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -1,7 +1,7 @@\n-\ufeff\"\"\"Production copy I/O against short-transfer and failure fixtures.\n+\"\"\"Exercise upstream single-call copy dispatch and terminal short-I/O behavior.\n \n-Covers the common network/card transfer boundary, not live UDPFS or libmc.\n-An optional git ref demonstrates the old loop rejecting positive short reads.\n+The production read/write statements are extracted directly; short transfers\n+must report failure rather than invoking a custom completion/retry loop.\n \"\"\"\n from pathlib import Path\n import shutil\n@@ -28,7 +28,6 @@ def function(signature):\n #include <stdint.h>\n #include <stdio.h>\n #include <string.h>\n-static struct { unsigned long long done; } copy_diag;\n static void __attribute__((unused)) filerCopyDiagStage(const char *s) { (void)s; }\n static unsigned char input[131081], output[131081], buffer[16384];\n static int pos_in,pos_out,read_cap,write_cap,read_calls,write_calls;\n@@ -48,13 +47,11 @@ def function(signature):\n  memcpy(output+pos_out,p,count);pos_out+=count;return count;\n }\n static void reset(void) {\n- copy_diag.done=0;\n  pos_in=pos_out=read_calls=write_calls=read_fault=write_fault=0;\n  read_cap=write_cap=sizeof(buffer);memset(output,0xA5,sizeof(output));\n }\n '''\n-if 'static int copyReadChunk(' in source:\n-    code += function('static int copyReadChunk(') + function('static int copyWriteChunk(')\n+assert 'copyReadChunk' not in source and 'copyWriteChunk' not in source\n # Exercise the exact production dispatch statements, including write suppression.\n a = source.index('bytesRead = ', source.index('copyBuff = (buffIndex'))\n b = source.index('\\n#if FILEOP_TRACE', a)\n@@ -63,28 +60,30 @@ def function(signature):\n code += r'''\n int main(void) {\n  for(int i=0;i<(int)sizeof(input);i++) input[i]=(unsigned char)(i*17+3);\n- const int caps[][2]={{16384,16384},{4096,16384},{16384,17},{7,23},{1,1}};\n+ reset();\n+ for(int left=sizeof(input);left>0;) {\n+  int n=left<(int)sizeof(buffer)?left:(int)sizeof(buffer);\n+  assert(!transfer(n));left-=n;\n+ }\n+ assert(pos_in==sizeof(input)&&pos_out==sizeof(input));\n+ assert(!memcmp(input,output,sizeof(input)));\n+ const int caps[][2]={{4096,16384},{16384,17},{7,23},{1,1}};\n  for(unsigned c=0;c<sizeof(caps)/sizeof(caps[0]);c++) {\n   reset();read_cap=caps[c][0];write_cap=caps[c][1];\n-  for(int left=sizeof(input);left>0;) {\n-   int n=left<(int)sizeof(buffer)?left:(int)sizeof(buffer);\n-   assert(!transfer(n));left-=n;\n-  }\n-  assert(pos_in==sizeof(input)&&pos_out==sizeof(input));\n-  assert(!memcmp(input,output,sizeof(input)));\n+  assert(transfer(16384)==-EIO);\n+  assert(read_calls==1);\n+  assert(write_calls==(read_cap==16384?1:0));\n  }\n- /* No EOF/zero/error is retried indefinitely; a failed read never writes. */\n  const int errors[]={0,-ETIMEDOUT,-ENOSPC,20000};\n  for(unsigned e=0;e<sizeof(errors)/sizeof(errors[0]);e++) {\n-  reset();read_cap=100;read_fault=2;fault_value=errors[e];\n+  reset();read_fault=1;fault_value=errors[e];\n   assert(transfer(16384)==(errors[e]<0?errors[e]:-EIO));\n-  assert(read_calls==2&&write_calls==0&&pos_out==0);\n-  reset();write_cap=100;write_fault=2;fault_value=errors[e];\n+  assert(read_calls==1&&write_calls==0&&pos_out==0);\n+  reset();write_fault=1;fault_value=errors[e];\n   assert(transfer(16384)==(errors[e]<0?errors[e]:-EIO));\n-  assert(read_calls==1&&write_calls==2&&pos_out==100);\n-  assert(!memcmp(input,output,100)&&output[100]==0xA5);\n+  assert(read_calls==1&&write_calls==1&&pos_out==0);\n  }\n- puts(\"PASS: production copy dispatch preserves short reads/writes, byte order and terminal errors\");\n+ puts(\"PASS: upstream single-call copy dispatch, exact bytes, short-I/O failure and no retry\");\n }\n '''\n if len(sys.argv) == 1:"
        },
        {
          "sha": "d2afee1fc0eff98b12812b6c8ef582025dff15bd",
          "filename": "tests/test_dashboard_languages.py",
          "status": "added",
          "additions": 124,
          "deletions": 0,
          "changes": 124,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_dashboard_languages.py",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_dashboard_languages.py",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/tests%2Ftest_dashboard_languages.py?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -0,0 +1,124 @@\n+\"\"\"Check table compatibility and execute the production dashboard resolver.\n+\n+UI labels opt in; the text renderer must never translate application metadata\n+or filenames. This test checks catalogs and resolution, not target font layout.\n+\"\"\"\n+from pathlib import Path\n+import ast\n+import re\n+import shutil\n+import subprocess\n+import tempfile\n+\n+root = Path(__file__).resolve().parents[1]\n+files = ('ENG', 'SPA', 'FRE', 'ITA', 'POL', 'POR', 'PTBR', 'GER', 'HUN')\n+pattern = r'lang\\((\\d+),\\s*(\\w+),\\s*(\"(?:[^\"\\\\]|\\\\.)*\")\\)'\n+tables = []\n+for name in files:\n+    entries = [(int(m[1]), m[2], ast.literal_eval(m[3]))\n+               for m in re.finditer(pattern, (root / 'Lang' / (name + '.LNG')).read_text())]\n+    assert [entry[0] for entry in entries] == list(range(len(entries))), name\n+    tables.append(entries)\n+assert all([(n, key) for n, key, _ in entries] ==\n+           [(n, key) for n, key, _ in tables[0]] for entries in tables)\n+\n+# Appended IDs preserve the 468 legacy/external-LNG positions.\n+assert tables[0][467][1] == 'New_to_Old'\n+assert all(key.startswith('Dashboard_') for _, key, _ in tables[0][468:])\n+formats = re.compile(r'%(?:[-+ #0]*)(?:\\d+|\\*)?(?:\\.(?:\\d+|\\*))?(?:hh|ll|[hljztL])?[diouxXfFeEgGaAcspn%]')\n+def signature(text):\n+    return [(m[0].count('*'), re.sub(r'^%[-+ #0\\d.*]*', '%', m[0]))\n+            for m in formats.finditer(text) if m[0] != '%%']\n+for table in tables:\n+    for english, translated in zip(tables[0][468:], table[468:]):\n+        assert translated[2], translated\n+        assert signature(english[2]) == signature(translated[2]), (english, translated)\n+\n+mapping = (root / 'Lang/DASHBOARD.inc').read_text()\n+keys = [ast.literal_eval(m[1]) for m in re.finditer(r'\\{(\"(?:[^\"\\\\]|\\\\.)*\"), LANG_', mapping)]\n+assert keys == sorted(set(keys))\n+settings = (root / 'src/dash_settings.c').read_text()\n+labels = set(re.findall(r'\\{ST_\\w+,\\s*\\w+,\\s*\"([^\"]+)\"', settings))\n+assert labels <= set(keys) | {'Neutrino', 'POPStarter'}, labels - set(keys)\n+assert 'localizeDashboardText' not in (root / 'src/dash_font.c').read_text()\n+\n+source = (root / 'src/lang.c').read_text()\n+function = source[source.index('const char *localizeDashboardText('):]\n+code = '#include <assert.h>\\n#include <stddef.h>\\n#include <stdio.h>\\n#include <string.h>\\n'\n+code += 'typedef struct { const char *String; } Language;\\nenum {\\n'\n+code += ''.join(f'LANG_{key} = {n},\\n' for n, key, _ in tables[0]) + 'LANG_COUNT };\\n'\n+for name in files:\n+    path = (root / 'Lang' / (name + '.LNG')).as_posix()\n+    code += f'Language table_{name}[] = {{\\n#define lang(id, name, value) {{value}},\\n#include \"{path}\"\\n#undef lang\\n}};\\n'\n+code += 'Language Lang_String[LANG_COUNT];\\n'\n+code += function.replace('#include \"../Lang/DASHBOARD.inc\"', mapping)\n+main_source = (root / 'src/dash_main.c').read_text()\n+start = main_source.index('const char *dashSectionLabel(')\n+end = main_source.index('\\n}', start) + 2\n+code += '\\n#define DASH_SEC_COUNT 6\\n' + main_source[start:end] + '\\n'\n+assert 'localizeDashboardText(dashSectionLabel(actual))' in (root / 'src/dash_ui.c').read_text()\n+code += '''\n+int main(void) {\n+    Language *tables[] = {table_ENG, table_SPA, table_FRE, table_ITA,\n+        table_POL, table_POR, table_PTBR, table_GER, table_HUN};\n+    assert(!strcmp(localizeDashboardText(\"Files\"), \"Files\"));\n+    for (int language = 0; language < 9; language++) {\n+        memcpy(Lang_String, tables[language], sizeof(Lang_String));\n+        const int section_ids[] = {LANG_Dashboard_Home, LANG_Dashboard_Applications,\n+            LANG_Dashboard_Files, LANG_Dashboard_Devices, LANG_Dashboard_Tools,\n+            LANG_Dashboard_Settings};\n+        for (int section = 0; section < 6; section++)\n+            assert(!strcmp(localizeDashboardText(dashSectionLabel(section)),\n+                tables[language][section_ids[section]].String));\n+        assert(!strcmp(localizeDashboardText(\"Home\"),\n+            tables[language][LANG_Dashboard_Home].String));\n+        assert(!strcmp(localizeDashboardText(\"Copy\"),\n+            tables[language][LANG_Copy].String));\n+        assert(!strcmp(localizeDashboardText(\"Remaining size: %.2f MB\"),\n+            tables[language][LANG_Dashboard_Remaining_size_2f_MB].String));\n+        assert(!strcmp(localizeDashboardText(\"mass0:/Files/BOOT.ELF\"), \"mass0:/Files/BOOT.ELF\"));\n+        assert(localizeDashboardText(NULL) == NULL);\n+        assert(localizeDashboardFormat(NULL) == NULL);\n+        assert(!strcmp(localizeDashboardFormat(\"Remaining size: %.2f MB\"),\n+            tables[language][LANG_Dashboard_Remaining_size_2f_MB].String));\n+        assert(!strcmp(localizeDashboardFormat(\"Show: %s\"),\n+            tables[language][LANG_Dashboard_Show_s].String));\n+        assert(!strcmp(localizeDashboardFormat(\"Completed items: %d / %d\"),\n+            tables[language][LANG_Dashboard_Completed_items_d_d].String));\n+        /* Simulate malicious external LNG overrides. */\n+        Lang_String[LANG_Dashboard_Remaining_size_2f_MB].String = \"Bad: %n\";\n+        assert(!strcmp(localizeDashboardFormat(\"Remaining size: %.2f MB\"),\n+            \"Remaining size: %.2f MB\"));\n+        Lang_String[LANG_Dashboard_Remaining_size_2f_MB].String = \"Bad: %s\";\n+        assert(!strcmp(localizeDashboardFormat(\"Remaining size: %.2f MB\"),\n+            \"Remaining size: %.2f MB\"));\n+        Lang_String[LANG_Dashboard_Remaining_size_2f_MB].String =\n+            tables[language][LANG_Dashboard_Remaining_size_2f_MB].String;\n+        Lang_String[LANG_Dashboard_Show_s].String = \"Bad: %s%n\";\n+        assert(!strcmp(localizeDashboardFormat(\"Show: %s\"), \"Show: %s\"));\n+        Lang_String[LANG_Dashboard_Show_s].String = \"Bad: %*s\";\n+        assert(!strcmp(localizeDashboardFormat(\"Show: %s\"), \"Show: %s\"));\n+        Lang_String[LANG_Dashboard_Show_s].String = \"Safe: %s (%%)\";\n+        assert(!strcmp(localizeDashboardFormat(\"Show: %s\"), \"Safe: %s (%%)\"));\n+        Lang_String[LANG_Dashboard_Show_s].String =\n+            tables[language][LANG_Dashboard_Show_s].String;\n+        Lang_String[LANG_Dashboard_Completed_items_d_d].String = \"Bad: %d / %s\";\n+        assert(!strcmp(localizeDashboardFormat(\"Completed items: %d / %d\"),\n+            \"Completed items: %d / %d\"));\n+        Lang_String[LANG_Dashboard_Completed_items_d_d].String =\n+            tables[language][LANG_Dashboard_Completed_items_d_d].String;\n+        Lang_String[LANG_Dashboard_Home].String = \"external override\";\n+        assert(!strcmp(localizeDashboardText(\"Home\"), \"external override\"));\n+        Lang_String[LANG_Dashboard_Home].String = NULL;\n+        assert(!strcmp(localizeDashboardText(\"Home\"), \"Home\"));\n+    }\n+    puts(\"PASS: nine catalogs, legacy IDs, formats, live language/override resolution and unchanged user paths\");\n+}\n+'''\n+with tempfile.TemporaryDirectory(prefix='dashboard-languages-') as folder:\n+    path = Path(folder)\n+    (path / 'test.c').write_text(code)\n+    compiler = shutil.which('gcc') or shutil.which('clang')\n+    assert compiler, 'Host C compiler required'\n+    subprocess.run([compiler, '-std=c99', '-Wall', '-Wextra', '-Werror', str(path / 'test.c'), '-o', str(path / 'test')], check=True)\n+    subprocess.run([str(path / 'test')], check=True)"
        },
        {
          "sha": "bad9f8aa577312e76d5bd3d850ff8412d0e83590",
          "filename": "tests/test_device_input.py",
          "status": "modified",
          "additions": 1,
          "deletions": 1,
          "changes": 2,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_device_input.py",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_device_input.py",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/tests%2Ftest_device_input.py?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -401,7 +401,7 @@ def function(source, signature):\n     for name, code in ((\"input\", padcode), (\"devices\", devcode), (\"pad-waits\", waitcode), (\"startup\", autocode), (\"theme-io\", themecode)):\n         source = Path(tmp) / f\"{name}.c\"\n         exe = Path(tmp) / f\"{name}.exe\"\n-        source.write_text(code)\n+        source.write_text(\"#define localizeDashboardText(s) (s)\\n\" + code)\n         try:\n             subprocess.run([compiler, \"-std=gnu11\", \"-O1\", str(source), \"-o\", str(exe)], check=True)\n             subprocess.run([str(exe)], check=True, timeout=5)"
        },
        {
          "sha": "5bb33f70125697a611254451d3b64d4b27bb01ce",
          "filename": "tests/test_file_operation_overlay.py",
          "status": "added",
          "additions": 201,
          "deletions": 0,
          "changes": 201,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_file_operation_overlay.py",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_file_operation_overlay.py",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/tests%2Ftest_file_operation_overlay.py?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -0,0 +1,201 @@\n+\"\"\"Execute production overlay drawing and progress updates with a host draw sink.\n+\n+This checks labels, layout, completion and scene ticking, not console rendering.\n+An optional output directory receives an HTML draw-call preview.\n+\"\"\"\n+from pathlib import Path\n+import html\n+import shutil\n+import subprocess\n+import sys\n+import tempfile\n+\n+root = Path(__file__).resolve().parents[1]\n+source = (root / \"src/dash_ui.c\").read_text()\n+gfx = (root / \"src/dash_gfx.c\").read_text()\n+def function(text, signature):\n+    start = text.index(signature)\n+    pos = text.index(\"{\", start) + 1\n+    depth = 1\n+    while depth:\n+        depth += (text[pos] == \"{\") - (text[pos] == \"}\")\n+        pos += 1\n+    return text[start:pos] + \"\\n\"\n+\n+delete_op = function((root / \"src/dash_browser.c\").read_text(), \"static void opDelete(\")\n+assert delete_op.index(\"dashLoadingFrame(f->name, done, n)\") < delete_op.index(\"ret = delete (fb.dir, f)\")\n+\n+code = r\"\"\"\n+#include <assert.h>\n+#include <math.h>\n+#include <stdint.h>\n+#include <stdio.h>\n+#include <string.h>\n+typedef uint64_t u64;\n+typedef int DashColor;\n+#define DASH_VW 640\n+#define DASH_VH 448\n+#define DASH_FS_BODY 0.8f\n+#define DASH_FS_SMALL 0.66f\n+#define DASH_FS_LABEL 0.92f\n+#define DASH_C_ACCENT 1\n+#define DASH_C_TEXT 2\n+#define DASH_C_TEXT2 3\n+#define DASH_C_TEXT3 4\n+#define DASH_BLEND_ADD 1\n+#define DASH_BLEND_NORMAL 0\n+static int active=1, worker, scenes, ticks, presents, spinners, texts, bars, emit, preview;\n+static u64 tick;\n+static float px,py,pw,ph,fill,track,bar_y;\n+static char lines[3][256];\n+static float widths[3], text_bottom;\n+static int DashIsActive(void) { return active; }\n+static int dashWorkerOnThread(void) { return worker; }\n+static int isStartupDriverStatusQuiet(void) { return 0; }\n+static u64 Timer(void) { return tick; }\n+static void dashInputFlush(void) {}\n+static void dashWorkerStatus(const char *s,int done,int total) { (void)s;(void)done;(void)total; }\n+static void dashStrCopy(char *out,const char *in,int n) { snprintf(out,n,\"%s\",in); }\n+static float dashClampf(float v,float lo,float hi) { return v<lo?lo:v>hi?hi:v; }\n+static float dashVisualUpdate(void) { ticks++; return 0.016f; }\n+static void dashRenderScene(void) { scenes++; texts=bars=spinners=0;fill=track=0;text_bottom=0; }\n+static void dashGfxPresent(void) { presents++; }\n+static void modalPanel(float x,float y,float w,float h,float alpha) {\n+ (void)alpha;px=x;py=y;pw=w;ph=h;\n+ assert(x>=32 && y>=32 && x+w<=608 && y+h<=416);\n+ if(emit)printf(\"BOX|%.2f|%.2f|%.2f|%.2f\\n\",x,y,w,h);\n+}\n+static void dashDrawSpinner(float x,float y,float radius,DashColor c) {\n+ (void)x;(void)y;(void)radius;(void)c;spinners++;\n+}\n+static void dashTextFit(float x,float y,float w,const char *s,float fs,DashColor c) {\n+ (void)c; assert(texts<3);widths[texts]=w;snprintf(lines[texts++],256,\"%s\",s);\n+ assert(y>=text_bottom);text_bottom=y+32*fs;\n+ assert(x>=px+20 && x+w<=px+pw-20 && y>=py+20 && y+32*fs<=py+ph-20);\n+ if(emit)printf(\"TEXT|%.2f|%.2f|%.2f|%.2f|%s\\n\",x,y,w,fs,s);\n+}\n+static void dashPanel(float x,float y,float w,float h,float radius,DashColor c) {\n+ (void)radius;(void)c;\n+ assert(x>=px && x+w<=px+pw && y>=py && y+h<=py+ph && w>=0);\n+ if(!bars) { assert(y>=text_bottom+12);track=w;bar_y=y; } else { fill=w;assert(w<=track); }\n+ bars++;\n+ if(emit)printf(\"BAR|%.2f|%.2f|%.2f|%.2f|%d\\n\",x,y,w,h,bars);\n+}\n+static void modalHintRow(float x,float y,int a,const char *b,int c,const char *d) {\n+ (void)x;(void)a;(void)b;(void)c;(void)d;assert(y>=bar_y+20);\n+}\n+#define modalBackdrop(...) ((void)0)\n+#define dashGfxPushAlpha(...) ((void)0)\n+#define dashGfxPopAlpha(...) ((void)0)\n+#define dashGfxBlend(...) ((void)0)\n+#define dashBackIcon() 0\n+#define dashColorA(c,a) (c)\n+\"\"\"\n+code += r\"\"\"\n+#define DI_WARNING 1\n+static struct { char msg[1024]; } fb;\n+static char failure_title[32], failure_body[1024], failure_detail[1024];\n+static void dashModalMessage(int icon,const char *title,const char *body,const char *detail) {\n+ assert(icon==DI_WARNING);snprintf(failure_title,sizeof(failure_title),\"%s\",title);\n+ snprintf(failure_body,sizeof(failure_body),\"%s\",body);\n+ snprintf(failure_detail,sizeof(failure_detail),\"%s\",detail?detail:\"\");\n+}\n+\"\"\"\n+code += function((root/\"src/dash_browser.c\").read_text(), \"static void showPasteFailure(\")\n+code += function(gfx, \"float dashApproach(\")\n+a=source.index(\"static int loading_active;\")\n+b=source.index(\"int dashLoadingActive(\",a)\n+code += source[a:b]\n+for sig in [\"static void loadingDraw(\", \"static void loadingBegin(\", \"void dashLoadingBegin(\",\n+            \"void dashFileLoadingBegin(\", \"static void loadingProgress(\", \"int DashFileProgress(\",\n+            \"void DashFileDeleteProgress(\", \"void dashLoadingFrame(\", \"void dashLoadingStatus(\", \"void dashLoadingEnd(\"]:\n+    code += function(source,sig)\n+code += r\"\"\"\n+static void capture(const char *name) {\n+ texts=bars=spinners=0;fill=track=0;\n+ emit=preview;\n+ if(emit)printf(\"CASE|%s\\n\",name);\n+ loadingDraw();\n+ emit=0;\n+ assert(scenes==ticks && ticks==presents);\n+}\n+int main(int argc,char **argv) {\n+ (void)argv; preview=argc>1;\n+ strcpy(fb.msg,\"Writing destination (-28)\\nNot enough free space.\\nBOOT.ELF\\nusb0: -> mc0:\\n0/1024 B\\nIOP 1>1; fixture-head\");\n+ showPasteFailure(0);\n+ assert(!strcmp(failure_title,\"Paste failed\"));\n+ assert(!strcmp(failure_body,\"Writing destination (-28)\\nNot enough free space.\"));\n+ assert(strstr(failure_detail,\"BOOT.ELF\") && strstr(failure_detail,\"usb0: -> mc0:\") && strstr(failure_detail,\"fixture-head\"));\n+ assert(!strchr(failure_detail,'\\n'));\n+ strcpy(fb.msg,\"Invalid PSU file\");showPasteFailure(1);\n+ assert(!strcmp(failure_title,\"PSU failed\") && !strcmp(failure_body,\"Invalid PSU file\") && !failure_detail[0]);\n+ dashFileLoadingBegin(\"Pasting file\");\n+ /* The filename must be published immediately, before the first blocking IO. */\n+ int before=presents;\n+ assert(DashFileProgress(\"Pasting file\",\"BOOT.ELF\",64ULL<<20,64ULL<<20));\n+ assert(presents==before+1);\n+ capture(\"Copy starting\");\n+ assert(texts==3 && !strcmp(lines[0],\"Pasting file\"));\n+ assert(!strcmp(lines[1],\"BOOT.ELF\") && widths[1]==432);\n+ assert(!strcmp(lines[2],\"Remaining size: 64.00 MB\") && !spinners && fill==0);\n+ tick+=20;DashFileProgress(\"Pasting file\",\"BOOT.ELF\",32ULL<<20,64ULL<<20);\n+ float first=loading_fraction;capture(\"Copy halfway\");\n+ assert(loading_fraction>first && loading_fraction<0.5f && fill>0 && fill<track);\n+ tick+=20;DashFileProgress(\"Pasting file\",\"BOOT.ELF\",0,64ULL<<20);\n+ capture(\"Copy complete\");assert(fill==track && loading_fraction==1);\n+ /* A new same-named file is a new operation even without a phase change. */\n+ tick+=1;DashFileProgress(\"Pasting file\",\"BOOT.ELF\",64ULL<<20,64ULL<<20);\n+ assert(loading_fraction==0);\n+ char long_name[256];memset(long_name,'X',255);long_name[255]=0;\n+ DashFileProgress(\"Verifying file\",long_name,1024,1024);capture(\"Long filename\");\n+ assert(texts==3 && !spinners && !strcmp(lines[1],loading_status) && strlen(lines[1])==sizeof(loading_status)-1 && widths[1]==432);\n+ dashFileLoadingBegin(\"Deleting file\");\n+ dashLoadingFrame(\"folder\",0,2);tick+=20;DashFileDeleteProgress(\"nested.bin\");\n+ capture(\"Deleting selected items\");\n+ assert(texts==3 && !strcmp(lines[0],\"Deleting file\") && !strcmp(lines[1],\"nested.bin\"));\n+ assert(!strcmp(lines[2],\"Completed items: 0 / 2\") && !spinners);\n+ dashLoadingFrame(\"folder\",1,2);capture(\"Deletion halfway\");assert(fill>0 && fill<track);\n+ dashLoadingFrame(\"second.bin\",2,2);capture(\"Deletion complete\");assert(fill==track);\n+ loading_cancel=1;capture(\"Cancelable transfer\");\n+ dashLoadingEnd();assert(!loading_active && !loading_file_operation);\n+ /* One-shot device status must never inherit the file-operation layout. */\n+ loading_file_operation=1;tick+=40;dashLoadingStatus(\"USB\",0,0);\n+ assert(!loading_active && !loading_file_operation && spinners==1);\n+ dashLoadingBegin(\"Starting device\");capture(\"Generic device loading\");\n+ assert(!loading_file_operation && spinners==1 && texts==2);\n+ int old=presents;active=0;assert(!DashFileProgress(\"Pasting file\",\"classic\",1,1));\n+ active=1;worker=1;assert(!DashFileProgress(\"Pasting file\",\"worker\",1,1));\n+ DashFileDeleteProgress(\"worker\");assert(old==presents);\n+ puts(\"PASS: compact copy/delete rendering, immediate filename, full completion, bounded fill and one scene tick\");\n+ return 0;\n+}\n+\"\"\"\n+cc=shutil.which(\"gcc\") or shutil.which(\"cc\")\n+assert cc\n+with tempfile.TemporaryDirectory(prefix=\"file-overlay-\") as folder:\n+    path=Path(folder);(path/\"test.c\").write_text(\"#define localizeDashboardText(s) (s)\\n#define localizeDashboardFormat(s) (s)\\n\" + code)\n+    exe=path/\"test.exe\"\n+    subprocess.run([cc,\"-std=c99\",\"-Wall\",\"-Wextra\",\"-Werror\",str(path/\"test.c\"),\"-lm\",\"-o\",str(exe)],check=True)\n+    result=subprocess.run([str(exe),*( [\"preview\"] if len(sys.argv)>1 else [])],capture_output=True,text=True,check=False)\n+    assert result.returncode==0,result.stdout+result.stderr\n+print(result.stdout.splitlines()[-1])\n+if len(sys.argv)>1:\n+    out=Path(sys.argv[1]);out.mkdir(parents=True,exist_ok=True)\n+    cards=[];svg=[];title=\"\"\n+    def finish():\n+        if svg: cards.append(\"<figure><figcaption>\"+html.escape(title)+\"</figcaption><svg viewBox='0 0 640 448'>\"+\"\".join(svg)+\"</svg></figure>\")\n+    for line in result.stdout.splitlines():\n+        parts=line.split(\"|\")\n+        if parts[0]==\"CASE\":\n+            finish();title=parts[1];svg=[\"<rect width='640' height='448' fill='#101829'/>\"]\n+        elif parts[0]==\"BOX\":\n+            x,y,w,h=parts[1:];svg.append(f\"<rect x='{x}' y='{y}' width='{w}' height='{h}' rx='16' fill='#243149'/>\")\n+        elif parts[0]==\"TEXT\":\n+            x,y,w,fs,text=parts[1:];size=32*float(fs)\n+            svg.append(f\"<svg x='{x}' y='{y}' width='{w}' height='{size+4}'><text y='{size*0.8}' fill='#f0f3ff' font-size='{size}' font-family='sans-serif'>{html.escape(text)}</text></svg>\")\n+        elif parts[0]==\"BAR\":\n+            x,y,w,h,n=parts[1:];color=\"#8bbcff\" if n==\"2\" else \"#53617a\"\n+            svg.append(f\"<rect x='{x}' y='{y}' width='{w}' height='{h}' rx='4' fill='{color}'/>\")\n+    finish()\n+    (out/\"index.html\").write_text(\"<!doctype html><meta charset='utf-8'><title>Transfer overlay host draw capture</title><style>body{font:16px sans-serif;background:#0d1320;color:#eee;margin:24px}main{display:grid;grid-template-columns:repeat(auto-fit,minmax(400px,1fr));gap:16px}figure{margin:0}svg{width:100%}figcaption{padding:12px}</style><h1>Transfer overlay: host draw-call capture</h1><p>Production drawing coordinates and labels, with mocked fonts/colors/background. This is not a console screenshot or hardware acceptance.</p><main>\"+\"\".join(cards)+\"</main>\")\n+    print(\"Preview:\",out/\"index.html\")"
        },
        {
          "sha": "9b8d57b047a2fc6819243c6131ea719454703537",
          "filename": "tests/test_gage_alignment.py",
          "status": "modified",
          "additions": 4,
          "deletions": 1,
          "changes": 5,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_gage_alignment.py",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_gage_alignment.py",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/tests%2Ftest_gage_alignment.py?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -19,6 +19,9 @@\n hints = hints[: hints.index(\"\\n}\\n\") if \"\\n}\\n\" in hints else hints.index(\"\\r\\n}\\r\\n\")]\n assert \"total += buttonWidth(hints[i].button, hints[i].key) + 8.0f\" in hints\n assert \"x += drawButton(x, y, hints[i].button, hints[i].key) + 8.0f;\" in hints\n+assert \"total += labels * label_ratio;\" in hints\n+assert \"x += label_w + 24.0f;\" in hints\n+assert \"dashTextFit(x, y + 1.5f, label_w, label\" in hints\n assert \"+ 16.0f;\" not in hints\n \n FS_SMALL = 0.66\n@@ -33,7 +36,7 @@ def num(pattern: str) -> float:\n tile = num(r\"#define HOME_TILE ([\\d.]+)f\")\n zoom = num(r\"#define HOME_TILE_ZOOM ([\\d.]+)f\")\n y0 = num(r\"float y0 = ([\\d.]+), cw = HOME_TILE;\")\n-underline = y0 - num(r\"dashRect\\(48, y0 - ([\\d.]+), dashTextWidth\\(\\\"Continue\\\"\")\n+underline = y0 - num(r\"dashRect\\(48, y0 - ([\\d.]+), dashTextWidth\\(localizeDashboardText\\(\\\"Continue\\\"\")\n cy = num(r\"float cy = ([\\d.]+);\")\n quick_label = cy - num(r\"dashText\\(48, cy - ([\\d.]+), \\\"Browse & shortcuts\\\"\")\n "
        },
        {
          "sha": "11a6793b7fc451081e4c06a45264dc4159521afa",
          "filename": "tests/test_mmce_fs_restore_guard.py",
          "status": "modified",
          "additions": 2,
          "deletions": 0,
          "changes": 2,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_mmce_fs_restore_guard.py",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_mmce_fs_restore_guard.py",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/tests%2Ftest_mmce_fs_restore_guard.py?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -19,6 +19,8 @@\n \n mutations = {\n     \"int mmce_fs_open(\": \"if (!mmce_fs_wire_name_ok(name))\",\n+    \"int mmce_fs_dopen(\": \"if (!mmce_fs_wire_name_ok(name))\",\n+    \"int mmce_fs_getstat(\": \"if (!mmce_fs_wire_name_ok(name))\",\n     \"int mmce_fs_remove(\": \"if (mmce_fs_reserved_component(name))\",\n     \"int mmce_fs_mkdir(\": \"if (mmce_fs_reserved_component(name))\",\n     \"int mmce_fs_rmdir(\": \"if (mmce_fs_reserved_component(name))\","
        },
        {
          "sha": "0144caf74576460d3b372877730a29dd5db92eb6",
          "filename": "tests/test_navigation_wraparound.py",
          "status": "modified",
          "additions": 14,
          "deletions": 0,
          "changes": 14,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_navigation_wraparound.py",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_navigation_wraparound.py",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/tests%2Ftest_navigation_wraparound.py?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -57,3 +57,17 @@ def src(name):\n assert \"int next = wrapCol(col[row] + 1, n);\" in home\n \n print(\"PASS: dashboard list navigation wraps across file, menu and browser views\")\n+\n+# Wrapped/jumped viewports must not animate their highlight through clipped rows.\n+for text, guard, snap in (\n+    (browser, 'fb.sel_anim < fb.top || fb.sel_anim >= fb.top + FB_ROWS', 'fb.sel_anim = (float)fb.sel;'),\n+    (apps, 'sel_anim < top || sel_anim >= top + LIST_ROWS', 'sel_anim = (float)sel;'),\n+    (files, 'fsel_anim < ftop || fsel_anim >= ftop + rows', 'fsel_anim = (float)fsel;'),\n+    (tools, 'tsel_anim < ttop || tsel_anim >= ttop + rows', 'tsel_anim = (float)tsel;'),\n+    (settings, 'item_anim < item_top || item_anim >= item_top + ITEM_ROWS', 'item_anim = (float)item;'),\n+    (widgets, 'sel_anim < top || sel_anim >= top + rows', 'sel_anim = (float)sel;'),\n+    (hdd, 'sel_anim < top || sel_anim >= top + ROWS', 'sel_anim = (float)sel;'),\n+    (src('dash_ui.c'), 'sel_anim < top || sel_anim >= top + rows', 'sel_anim = (float)sel;'),\n+):\n+    assert guard in text and snap in text\n+print('PASS: wrapped highlight remains in the viewport across eight list/modal controllers')"
        },
        {
          "sha": "9408b0375f500026b4dd446ac09cecfdcf76a033",
          "filename": "tests/test_neutrino_cancel_feedback.py",
          "status": "modified",
          "additions": 1,
          "deletions": 1,
          "changes": 2,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_neutrino_cancel_feedback.py",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_neutrino_cancel_feedback.py",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/tests%2Ftest_neutrino_cancel_feedback.py?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -40,7 +40,7 @@\n }\n \"\"\"\n with tempfile.TemporaryDirectory(prefix='neutrino-cancel-') as folder:\n- p=Path(folder);(p/'test.c').write_text(c);exe=p/'test.exe'\n+ p=Path(folder);(p/'test.c').write_text(\"#define localizeDashboardText(s) (s)\\n\" + c);exe=p/'test.exe'\n  subprocess.run([shutil.which('gcc') or shutil.which('cc'),'-std=c11','-Wall','-Wextra','-Werror',str(p/'test.c'),'-o',str(exe)],check=True)\n  subprocess.run([str(exe)],check=True,timeout=10)\n print('PASS: cancelled Neutrino launch is quiet; actual errors and ELF fallback still warn')"
        },
        {
          "sha": "8755a9a81d94c4e8a6c6baba750dac2dbee6c10c",
          "filename": "tests/test_neutrino_menu_audio.py",
          "status": "added",
          "additions": 183,
          "deletions": 0,
          "changes": 183,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_neutrino_menu_audio.py",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_neutrino_menu_audio.py",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/tests%2Ftest_neutrino_menu_audio.py?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -0,0 +1,183 @@\n+\"\"\"Execute the dashboard launch path: Neutrino prompts precede audio teardown.\"\"\"\n+from pathlib import Path\n+import shutil\n+import subprocess\n+import tempfile\n+\n+root = Path(__file__).resolve().parents[1]\n+dash_src = (root / \"src/dash_main.c\").read_text()\n+actions_src = (root / \"src/main_actions.c\").read_text()\n+neutrino_src = (root / \"src/neutrino_iso.c\").read_text()\n+\n+\n+def extract_func(text, sig):\n+    start = text.index(sig)\n+    pos = text.index(\"{\", start) + 1\n+    depth = 1\n+    while depth:\n+        depth += (text[pos] == \"{\") - (text[pos] == \"}\")\n+        pos += 1\n+    return text[start:pos]\n+\n+\n+code = r'''\n+#include <assert.h>\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <string.h>\n+#include <setjmp.h>\n+#define MAX_PATH 1024\n+#define DASH_TITLE_MAX 64\n+#define DASH_LAUNCH_PENDING_ARGS 2\n+#define DASH_APP_HAS_ARGS 2\n+#define DI_EDIT 1\n+#define DI_WARNING 2\n+#define TEXTEDITOR_RESULT_LAUNCH_ARGS 1\n+#define NEUTRINO_GAME_ID_SIZE 16\n+#define NEUTRINO_ISO_PVD_OFFSET (16 * 2048)\n+#define POPSTARTER_OK 0\n+#define SEEK_SET 0\n+typedef struct { char path[MAX_PATH], title[DASH_TITLE_MAX]; int flags; } DashApp;\n+typedef struct { char main_msg[MAX_PATH]; } MainExecuteContext;\n+typedef struct { int count; } POPSTARTER_CANDIDATE_LIST;\n+typedef struct { int dummy; } NEUTRINO_LAUNCH_OPTIONS;\n+typedef struct { char argv[4][32]; } NEUTRINO_OPTION_ARGS;\n+typedef struct { int disable_neutrino, cdrom_disable_gameid; char neutrino_file[MAX_PATH]; } Setting;\n+static Setting *setting;\n+static void *elisaFnt, *External_Lang_Buffer;\n+static MainExecuteContext exec_ctx;\n+static void (*mainActionLaunchHook)(const char *);\n+static void (*drawMsgOverride)(const char *);\n+static int dash_active = 1;\n+static int muted, suspended, restored, bgm, calls, commit;\n+static int shutdown_calls, pad_ended;\n+static jmp_buf launched;\n+static void dashLaunchHook(const char *p) { (void)p; }\n+static void dashMsgHook(const char *p) { (void)p; }\n+static void dashStrCopy(char *d, const char *s, size_t n) { snprintf(d,n,\"%s\",s); }\n+static void LaunchArgsClear(void) {}\n+static int LaunchArgsLoadFromBuffer(const char *p, char *a, size_t n, char *m, size_t s) {\n+    (void)p; (void)a; (void)n; (void)m; (void)s; return 1;\n+}\n+static void dashModalMessage(int i, const char *t, const char *b, const char *d) {\n+    (void)i; (void)t; (void)b; (void)d;\n+}\n+static int dashTextEditorEx(const char *p, int n) { (void)p; (void)n; return 0; }\n+static int settleBackgroundWork(const char *t) { (void)t; return 1; }\n+static int argSidecarExists(const char *p) { (void)p; return 0; }\n+static int dashLibLoadArgs(DashApp *a, char *b, int n) { (void)a; (void)b; (void)n; return 0; }\n+static int dashArtTakeDirty(void) { return 0; }\n+static void dashLibSaveCache(void) {}\n+static void dashStorePushRecent(const char *p) { (void)p; }\n+static void filerRootMcPollIdle(void) {}\n+static void dashBgFlash(float f) { (void)f; }\n+static void dashLoadingBegin(const char *t) { (void)t; }\n+static void dashLoadingStatus(const char *t, int n, int m) { (void)t; (void)n; (void)m; }\n+static int genCmpFileExt(const char *p, const char *ext) {\n+    const char *dot = strrchr(p, '.');\n+    return dot != NULL && !strcasecmp(dot + 1, ext);\n+}\n+static void dashSoundSuspend(void) { muted = 1; suspended++; }\n+static void dashSoundShutdown(void) { muted = 1; shutdown_calls++; }\n+static void fadeScreenToBlack(void) {}\n+static void DashThemeSetLegacyEnabled(int e) { (void)e; }\n+static void clearScreenToBlack(void) {}\n+static void padPortClose(int p, int s) { (void)p; (void)s; }\n+static void padEnd(void) { pad_ended++; }\n+static void closeKeyboardIfOpened(void) {}\n+static void dashGfxRestoreLegacyState(void) {}\n+static void dashClassicUiInstall(int n) { (void)n; }\n+static int openPathForRead(const char *p, char *o, size_t n) {\n+    if (o && n) snprintf(o, n, \"%s\", p);\n+    return (commit < 0 && (strstr(p, \"neutrino.elf\") || strstr(p, \"NEUTRINO.ELF\"))) ? -1 : 1;\n+}\n+static long genLseek(int fd, long off, int w) { (void)fd; (void)w; return off; }\n+static int genRead(int fd, void *buf, size_t sz) {\n+    unsigned char *b = (unsigned char *)buf;\n+    (void)fd;\n+    memset(b, 0, sz);\n+    if (sz == 4) { b[0] = 0x7f; b[1] = 'E'; b[2] = 'L'; b[3] = 'F'; }\n+    else if (sz >= 6) { b[0] = 1; memcpy(b + 1, \"CD001\", 5); }\n+    return (int)sz;\n+}\n+static void genClose(int fd) { (void)fd; }\n+static int addPopstarterCandidate(POPSTARTER_CANDIDATE_LIST *c, const char *p) { (void)p; c->count++; return 1; }\n+static void initPopstarterCandidates(POPSTARTER_CANDIDATE_LIST *c) { c->count = 0; }\n+static int buildNeutrinoOptionArgs(const NEUTRINO_LAUNCH_OPTIONS *o, NEUTRINO_OPTION_ARGS *a) { (void)o; (void)a; return 0; }\n+static int DashIsActive(void) { return 1; }\n+static int prepareNeutrinoGameID(const char *p, char *id, size_t n) { (void)p; snprintf(id, n, \"SLUS_123.45\"); return 1; }\n+static int showNeutrinoLaunchOptionsForGame(NEUTRINO_LAUNCH_OPTIONS *o, const char *id) {\n+    (void)o; (void)id;\n+    /* Menu prompts run while audio is still unmuted. */\n+    assert(!muted);\n+    return commit != 0;\n+}\n+static int showNeutrinoLaunchOptions(NEUTRINO_LAUNCH_OPTIONS *o) { return showNeutrinoLaunchOptionsForGame(o, \"\"); }\n+static int preparePopstarterCandidateLaunch(POPSTARTER_CANDIDATE_LIST *c, char *s, size_t sn, char *f, size_t fn, char *p, size_t pn, int *k) {\n+    (void)c;\n+    snprintf(s, sn, \"usb0:/NEUTRINO/neutrino.elf\");\n+    snprintf(f, fn, \"usb0:/NEUTRINO/neutrino.elf\");\n+    if (pn > 0) p[0] = '\\0';\n+    *k = 1;\n+    return POPSTARTER_OK;\n+}\n+static void displayRetroGemGameID(const char *id, int m) { (void)id; (void)m; }\n+static void RunLoaderElfKeepIOP(char *f, char *p, const char *s, int k) {\n+    (void)f; (void)p; (void)s; (void)k;\n+    assert(muted && shutdown_calls >= 2 && pad_ended == 1);\n+    longjmp(launched, 1);\n+}\n+static void cleanUpFree(void *p) { (void)p; }\n+#define free cleanUpFree\n+'''\n+code += extract_func(dash_src, \"int DashPrepareKeepIopHandoff(\") + \"\\n\"\n+code += extract_func(actions_src, \"static void cleanUpForExecInternal(\") + \"\\n\"\n+code += extract_func(actions_src, \"int CleanUpForKeepIopExec(\") + \"\\n\"\n+code += \"#undef free\\n\"\n+code += neutrino_src[neutrino_src.index(\"static void setMessage(\"):] + \"\\n\"\n+code += r'''\n+static void ExecuteMainAction(char *p, const MainExecuteContext *ctx) {\n+    calls++;\n+    if (IsNeutrinoIsoPath(p)) {\n+        LaunchArgsClear();\n+        LaunchNeutrinoIso(p, (char *)ctx->main_msg, MAX_PATH);\n+        return;\n+    }\n+    assert(muted);\n+}\n+static void dashGfxCheckScreenChange(void) {}\n+static void dashSoundMaintain(void) {}\n+static void dashSoundApplyVolume(void) { muted = 0; restored++; }\n+static void dashBgmStart(void) { bgm++; }\n+static void dashLoadingEnd(void) {}\n+static void friendlyLaunchError(const DashApp *a, const char *m) { (void)a; (void)m; }\n+''' + extract_func(dash_src, \"void dashLaunchApp(\") + r'''\n+int main(void) {\n+    DashApp app = {\"usb0:/game.iso\", \"Game\", 0};\n+    setting = calloc(1, sizeof(*setting));\n+    dashLaunchApp(NULL, 0); assert(!calls);\n+    for (int failure = 0; failure <= 1; failure++) {\n+        commit = -failure;\n+        dashLaunchApp(&app, 0);\n+        assert(!muted && !suspended && !shutdown_calls && restored == failure+1 && bgm == failure+1);\n+    }\n+    commit = 1;\n+    if (!setjmp(launched)) { dashLaunchApp(&app, 0); assert(0); }\n+    assert(muted && !suspended && shutdown_calls >= 2 && pad_ended == 1 && restored == 2);\n+    setting = calloc(1, sizeof(*setting));\n+    muted = commit = shutdown_calls = pad_ended = 0;\n+    strcpy(app.path,\"usb0:/app.elf\");\n+    dashLaunchApp(&app, 0);\n+    assert(!muted && suspended == 1 && restored == 3 && bgm == 3);\n+    free(setting);\n+    puts(\"PASS: ISO menu audio, cancellation/failure recovery, committed launch and ordinary ELF muting\");\n+}\n+'''\n+with tempfile.TemporaryDirectory(prefix=\"neutrino-menu-audio-\") as folder:\n+    path = Path(folder)\n+    (path / \"test.c\").write_text(code)\n+    exe = path / \"test.exe\"\n+    compiler = shutil.which(\"gcc\") or shutil.which(\"clang\")\n+    assert compiler, \"Host C compiler required\"\n+    subprocess.run([compiler, \"-std=gnu11\", \"-Wall\", \"-Wextra\", \"-Werror\", str(path / \"test.c\"), \"-o\", str(exe)], check=True)\n+    subprocess.run([str(exe)], check=True, timeout=10)"
        },
        {
          "sha": "cdf44ef8e891c0b9e9adb3b4a4c44e3e6c24aaba",
          "filename": "tests/test_paste_replace.py",
          "status": "removed",
          "additions": 0,
          "deletions": 126,
          "changes": 126,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/3e03e640a1434a6d23dd1b80498fa0fbb7c88672/tests%2Ftest_paste_replace.py",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/3e03e640a1434a6d23dd1b80498fa0fbb7c88672/tests%2Ftest_paste_replace.py",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/tests%2Ftest_paste_replace.py?ref=3e03e640a1434a6d23dd1b80498fa0fbb7c88672",
          "patch": "@@ -1,126 +0,0 @@\n-\"\"\"Execute production replacement transactions on real files with injected failures.\"\"\"\n-from pathlib import Path\n-import shutil\n-import subprocess\n-import tempfile\n-\n-root = Path(__file__).resolve().parents[1]\n-source = (root / 'src/filer_copy.c').read_text()\n-start = source.index('static int copyPathMissing(')\n-end = source.index('static int isMemoryCardLikePath(', start)\n-production = source[start:end]\n-# These guards connect the tested transaction to normal/recursive/PSU-restored files.\n-copy = source[source.index('int copy('):]\n-assert 'dummy = genRemove(out)' not in copy\n-assert 'copyPrepareTarget(out, replace_target, replace_backup)' in copy\n-assert copy.index('genClose(out_fd);', copy.index('copy_file_data_done:')) < copy.index('copyVerifyTarget(out, file.name, original_size, copy_hash)') < copy.index('copyPublishTarget(out, replace_target, replace_backup)')\n-assert 'copy_hash = copyChecksum(copy_hash, copyBuff, buffSize)' in copy\n-header = r'''\n-#include <assert.h>\n-#include <stdint.h>\n-#include <stdio.h>\n-#include <string.h>\n-#include <errno.h>\n-#include <fcntl.h>\n-#include <unistd.h>\n-#include <sys/stat.h>\n-typedef uint64_t u64;\n-typedef uint32_t u32;\n-typedef struct { unsigned mode; } iox_stat_t;\n-typedef struct { char EntryName[32]; } sceMcTblGetDir;\n-#define MAX_PATH 1024\n-#define FIO_O_RDONLY O_RDONLY\n-#define FIO_S_ISDIR S_ISDIR\n-static int rename_calls, fail_rename, fail_rollback, mc_result, denied, removed, fail_close;\n-static int false_rename, sync_calls, fail_sync, fail_read, fail_eof, verified;\n-static int DashFileProgress(const char *a,const char *n,u64 r,u64 t) {\n- assert(!strcmp(a,\"Verifying file\")&&!strcmp(n,\"BOOT.ELF\"));assert(r<=t);\n- if(!r)verified++;\n- return 1;\n-}\n-static void ensureMemoryCardPortAccessible(int p) { assert(p==0 || p==1); }\n-static int genGetStat(const char *p,iox_stat_t *s) {\n- struct stat st; if(denied) return -denied;\n- if(stat(p,&st)) return -errno;\n- s->mode=st.st_mode; return 0;\n-}\n-static int rename_file(const char *from,const char *to) {\n- rename_calls++;\n- if(false_rename) return 0;\n- if(rename_calls==fail_rename || (fail_rollback && rename_calls>fail_rename)) return -EIO;\n- return rename(from,to) ? -errno : 0;\n-}\n-static int fileXioRename(const char *a,const char *b) { return rename_file(a,b); }\n-static int mcSetFileInfo(int p,int slot,const char *from,sceMcTblGetDir *s,int flag) {\n- char a[MAX_PATH],b[MAX_PATH], *leaf; assert(!slot && flag==0x10);\n- snprintf(a,sizeof(a),\"mc%d:%s\",p,from); strcpy(b,a); leaf=strrchr(b,'/');\n- assert(leaf); snprintf(leaf+1,sizeof(b)-(leaf+1-b),\"%.32s\",s->EntryName);\n- mc_result=rename_file(a,b); return 0;\n-}\n-static int mcSync(int m,void *c,int *r) { (void)m;(void)c;if(++sync_calls==fail_sync)return -1;*r=mc_result;return 1; }\n-static int genOpen(const char *p,int flags) { int r=open(p,flags,0600);return r<0?-errno:r; }\n-static int genRead(int fd,void *p,int n) { if(fail_read)return -ETIMEDOUT;if(fail_eof&&n==1)return -EACCES;return read(fd,p,n>17?17:n); }\n-static int genClose(int fd) { int r=close(fd);return fail_close?-EIO:r; }\n-static int genRemove(char *p) { removed++;return unlink(p); }\n-static void put(const char *p,const char *s) { int f=open(p,O_WRONLY|O_TRUNC|O_CREAT,0600);assert(f>=0);assert(write(f,s,strlen(s))==(int)strlen(s));assert(close(f)==0); }\n-static void expect(const char *p,const char *s) { char b[128]={0};int f=open(p,O_RDONLY);assert(f>=0);assert(read(f,b,sizeof(b))==(int)strlen(s));close(f);assert(!strcmp(b,s)); }\n-'''\n-main = r'''\n-int main(void) {\n- char out[MAX_PATH],target[MAX_PATH],backup[MAX_PATH];\n- const char *fresh=\"new ELF bytes: complete replacement\";\n- const char *paths[]={\"disk/APP.ELF\",\"mc0:/BOOT.ELF\",\"mc1:/BOOT.ELF\"};\n- mkdir(\"disk\",0700);mkdir(\"mc0:\",0700);mkdir(\"mc1:\",0700);\n- for(int device=0;device<3;device++) for(int scenario=0;scenario<6;scenario++) {\n-  rename_calls=fail_rename=fail_rollback=denied=removed=fail_close=0;\n-  put(paths[device],\"original ELF\");strcpy(out,paths[device]);\n-  assert(!copyPrepareTarget(out,target,backup));\n-  assert(strcmp(out,target) && !strcmp(target,paths[device]));\n-  expect(target,\"original ELF\");put(out,fresh);\n-  u32 hash=copyChecksum(2166136261u,fresh,strlen(fresh));\n-  verified=0;assert(!copyVerifyTarget(out,\"BOOT.ELF\",strlen(fresh),hash));assert(verified==1);\n-  fail_read=1;assert(copyVerifyTarget(out,\"BOOT.ELF\",strlen(fresh),hash)==-ETIMEDOUT);fail_read=0;\n-  fail_eof=1;assert(copyVerifyTarget(out,\"BOOT.ELF\",strlen(fresh),hash)==-EACCES);fail_eof=0;\n-  if(scenario==1) { /* write/readback corruption: old entry is untouched */\n-   put(out,\"partial\");assert(copyVerifyTarget(out,\"BOOT.ELF\",strlen(fresh),hash)<0);\n-   expect(target,\"original ELF\");unlink(out);continue;\n-  }\n-  if(scenario==2) fail_rename=1; /* cannot park the old file */\n-  if(scenario==3) fail_rename=2; /* publish fails and rolls back */\n-  if(scenario==4) {fail_rename=2;fail_rollback=1;}\n-  if(scenario==5) { /* readback close failure */\n-   fail_close=1;verified=0;assert(copyVerifyTarget(out,\"BOOT.ELF\",strlen(fresh),hash)<0);assert(!verified);\n-   fail_close=0;expect(target,\"original ELF\");unlink(out);continue;\n-  }\n-  int r=copyPublishTarget(out,target,backup);\n-  if(!scenario) {assert(!r);expect(target,fresh);assert(access(backup,F_OK));}\n-  else if(scenario==4) {assert(r<0);expect(backup,\"original ELF\");expect(out,fresh);unlink(backup);unlink(out);}\n-  else {assert(r<0);expect(target,\"original ELF\");expect(out,fresh);unlink(out);}\n- }\n- strcpy(out,\"disk/new.elf\");assert(!copyPrepareTarget(out,target,backup) && !target[0]);\n- denied=EACCES;strcpy(out,\"disk/APP.ELF\");assert(copyPrepareTarget(out,target,backup)==-EACCES);denied=0;\n- denied=EINTR;strcpy(out,\"mc0:/BOOT.ELF\");assert(copyPrepareTarget(out,target,backup)==-EINTR);denied=0;\n- /* Stale recovery artifacts are never reused or deleted. */\n- put(\"disk/LS-COPY-000.TMP\",\"recover me\");put(\"disk/LS-OLD-001.BAK\",\"old recovery\");\n- strcpy(out,\"disk/APP.ELF\");assert(!copyPrepareTarget(out,target,backup));assert(strstr(out,\"002\"));\n- expect(\"disk/LS-COPY-000.TMP\",\"recover me\");expect(\"disk/LS-OLD-001.BAK\",\"old recovery\");\n- /* Drivers reporting success without renaming cannot discard the old file. */\n- strcpy(out,\"disk/APP.ELF\");put(out,\"original ELF\");assert(!copyPrepareTarget(out,target,backup));put(out,fresh);\n- false_rename=1;assert(copyPublishTarget(out,target,backup)<0);false_rename=0;\n- expect(target,\"original ELF\");expect(out,fresh);unlink(out);\n- /* An ambiguous libmc completion preserves all surviving copies. */\n- for(int which=1;which<=2;which++) {\n-  rename_calls=fail_rename=fail_rollback=sync_calls=0;fail_sync=which;\n-  strcpy(out,\"mc0:/BOOT.ELF\");put(out,\"original ELF\");assert(!copyPrepareTarget(out,target,backup));put(out,fresh);\n-  assert(copyPublishTarget(out,target,backup)<0);expect(backup,\"original ELF\");\n-  if(which==1)expect(out,fresh);else expect(target,fresh);\n-  unlink(out);unlink(backup);fail_sync=0;\n- }\n- puts(\"PASS: verified replacements, MC slots, short readback, rollback, retained recovery and permission failures\");\n-}\n-'''\n-with tempfile.TemporaryDirectory(prefix='paste-replace-') as folder:\n-    path = Path(folder)\n-    (path / 'test.c').write_text(header + production + main)\n-    subprocess.run([shutil.which('gcc'), '-std=c99', '-Wall', '-Wextra', '-Werror', str(path / 'test.c'), '-o', str(path / 'test')], check=True)\n-    subprocess.run([str(path / 'test')], cwd=path, check=True)"
        },
        {
          "sha": "aa5b9e44dc7e9873f45302af525e11d814f5a9e3",
          "filename": "tests/test_selector_centering.py",
          "status": "modified",
          "additions": 2,
          "deletions": 2,
          "changes": 4,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_selector_centering.py",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_selector_centering.py",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/tests%2Ftest_selector_centering.py?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -20,10 +20,10 @@ def num(text, pattern):\n lists = []\n s = src(\"dash_settings.c\")\n irow = num(s, r\"irow = (\\d+)\")\n-lists.append((\"Settings\", 2, irow - 5, s, r\"dashTextFit\\(ix \\+ 38, y \\+ ([\\d.]+)f, iw - 70, it->label\", SMALL))\n+lists.append((\"Settings\", 2, irow - 5, s, r\"dashTextFit\\(ix \\+ 38, y \\+ ([\\d.]+)f, iw - 70, localizeDashboardText\\(it->label\", SMALL))\n lists.append((\"Settings value\", 2, irow - 5, s, r\"dashTextFit\\(vx, y \\+ ([\\d.]+)f, maxw, v,\", SMALL))\n t = src(\"dash_tools.c\")\n-lists.append((\"Tools\", 0, num(t, r\"ly = 102, row_h = (\\d+)\") - 3, t, r\"dashTextFit\\(lx \\+ 34, y \\+ ([\\d.]+)f, lw - 44, tools\\[i\\]\\.name\", SMALL))\n+lists.append((\"Tools\", 0, num(t, r\"ly = 102, row_h = (\\d+)\") - 3, t, r\"dashTextFit\\(lx \\+ 34, y \\+ ([\\d.]+)f, lw - 44, localizeDashboardText\\(tools\\[i\\]\\.name\", SMALL))\n f = src(\"dash_files.c\")\n lists.append((\"Files\", 0, num(f, r\"ly = 102, row_h = (\\d+)\") - 3, f, r\"dashTextFit\\(lx \\+ 34, y \\+ ([\\d.]+), lw - 50, f->label\", SMALL))\n a = src(\"dash_apps.c\")"
        },
        {
          "sha": "13a8a1960534671fb4016ab5fb79bc539aa1fa60",
          "filename": "tests/test_settings_navigation.py",
          "status": "modified",
          "additions": 1,
          "deletions": 1,
          "changes": 2,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_settings_navigation.py",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_settings_navigation.py",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/tests%2Ftest_settings_navigation.py?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -191,6 +191,6 @@ def function(source, signature):\n with tempfile.TemporaryDirectory(prefix=\"wle-settings-\") as tmp:\n     source = Path(tmp) / \"settings.c\"\n     exe = Path(tmp) / \"settings.exe\"\n-    source.write_text(code)\n+    source.write_text(\"#define localizeDashboardText(s) (s)\\n\" + code)\n     subprocess.run([compiler, \"-std=gnu11\", str(source), \"-o\", str(exe)], check=True)\n     subprocess.run([str(exe)], check=True)"
        },
        {
          "sha": "9ff2633cf0c8e978044e11028a559a1c5352f094",
          "filename": "tests/test_transfer_progress.py",
          "status": "modified",
          "additions": 8,
          "deletions": 8,
          "changes": 16,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_transfer_progress.py",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_transfer_progress.py",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/tests%2Ftest_transfer_progress.py?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -13,7 +13,7 @@\n #include <stdio.h>\n #include <string.h>\n typedef uint64_t u64;\n-static int active=1, worker, loading_active=1, draws, loading_done,loading_total;\n+static int active=1, worker, loading_active=1, draws, loading_done,loading_total,loading_file_operation;\n static float loading_fraction;\n static char loading_title[64],loading_status[128],loading_remaining[64];\n static u64 tick,loading_last_draw;\n@@ -22,16 +22,16 @@\n static u64 Timer(void){return tick;}\n static void dashStrCopy(char *a,const char *b,int cap){snprintf(a,cap,\"%s\",b);}\n static void loadingDraw(void){draws++;loading_last_draw=tick;}\n-static void dashLoadingBegin(const char *s){loading_active=1;snprintf(loading_title,sizeof(loading_title),\"%s\",s);}\n+static void dashFileLoadingBegin(const char *s){loading_active=1;snprintf(loading_title,sizeof(loading_title),\"%s\",s);}\n '''+source[a:b]+r'''\n int main(void){\n  assert(DashFileProgress(\"Pasting file\",\"BOOT.ELF\",8ULL<<30,8ULL<<30));\n  assert(!strcmp(loading_status,\"BOOT.ELF\")&&!strcmp(loading_remaining,\"Remaining size: 8192.00 MB\"));\n- assert(loading_done==0&&loading_total==10000&&!draws);\n+ assert(loading_done==0&&loading_total==10000&&draws==1);\n  tick=20;DashFileProgress(\"Pasting file\",\"BOOT.ELF\",4ULL<<30,8ULL<<30);\n- assert(loading_done==5000&&draws==1);\n- tick=21;DashFileProgress(\"Pasting file\",\"BOOT.ELF\",4ULL<<30,8ULL<<30);assert(draws==1);\n- DashFileProgress(\"Pasting file\",\"BOOT.ELF\",0,8ULL<<30);assert(loading_done==10000&&draws==2);\n+ assert(loading_done==5000&&draws==2);\n+ tick=21;DashFileProgress(\"Pasting file\",\"BOOT.ELF\",4ULL<<30,8ULL<<30);assert(draws==2);\n+ DashFileProgress(\"Pasting file\",\"BOOT.ELF\",0,8ULL<<30);assert(loading_done==10000&&draws==3);\n  loading_fraction=1;DashFileProgress(\"Pasting file\",\"SECOND.ELF\",1024,1024);\n  assert(loading_fraction==0&&loading_done==0&&!strcmp(loading_remaining,\"Remaining size: 0.00 MB\"));\n  DashFileProgress(\"Pasting file\",\"empty\",0,0);assert(loading_done==10000);\n@@ -57,9 +57,9 @@\n assert 'dashTextFit(x + 84, y + 26' in renderer\n copy=(root/'src/filer_copy.c').read_text()\n assert 'if (!DashFileProgress(\"Pasting file\", file.name, size, original_size))' in copy\n-assert 'buffSize = 16384' in copy\n+assert 'buffSize = 16384' not in copy\n assert 'DashFileDeleteProgress(file->name)' in (root/'src/filer_actions.c').read_text()\n with tempfile.TemporaryDirectory(prefix='transfer-progress-') as folder:\n- p=Path(folder);(p/'test.c').write_text(code)\n+ p=Path(folder);(p/'test.c').write_text(\"#define localizeDashboardText(s) (s)\\n#define localizeDashboardFormat(s) (s)\\n\" + code)\n  subprocess.run([shutil.which('gcc'),'-std=c99','-Wall','-Wextra','-Werror',str(p/'test.c'),'-o',str(p/'test')],check=True)\n  subprocess.run([str(p/'test')],check=True)"
        },
        {
          "sha": "6720ca4a43ec1f0d288e5b0b418ff1753ee78946",
          "filename": "tests/test_zack_round3.py",
          "status": "modified",
          "additions": 1,
          "deletions": 1,
          "changes": 2,
          "blob_url": "https://github.com/NathanNeurotic/Launchstation/blob/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_zack_round3.py",
          "raw_url": "https://github.com/NathanNeurotic/Launchstation/raw/b815d10da29274200572e61467d72a718004a708/tests%2Ftest_zack_round3.py",
          "contents_url": "https://api.github.com/repos/NathanNeurotic/Launchstation/contents/tests%2Ftest_zack_round3.py?ref=b815d10da29274200572e61467d72a718004a708",
          "patch": "@@ -28,7 +28,7 @@ def case(text, name):\n \n # #135: the breadcrumb clears the L1/R1 bar (ends at y=62).\n assert \"dashTextFit(ix, 66, iw - 126, trail\" in settings\n-assert \"dashDrawSectionTitle(ix, 82, c->name\" in settings\n+assert \"dashDrawSectionTitle(ix, 82, localizeDashboardText(c->name)\" in settings\n \n # #141: HOME names memory cards like the Files tab.\n assert '\"mc0 (Slot 1)\"' in lib and '\"mc1 (Slot 2)\"' in lib"
        }
      ]
    }
  },
  {
    "repo": "akilluminati47/RIPPS2",
    "url": "https://api.github.com/repos/akilluminati47/RIPPS2/compare/28bc44d0d3a31b084d6b7e55ff91745f9a09c026...e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
    "comparison": {
      "url": "https://api.github.com/repos/akilluminati47/RIPPS2/compare/28bc44d0d3a31b084d6b7e55ff91745f9a09c026...e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
      "html_url": "https://github.com/akilluminati47/RIPPS2/compare/28bc44d0d3a31b084d6b7e55ff91745f9a09c026...e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
      "permalink_url": "https://github.com/akilluminati47/RIPPS2/compare/akilluminati47:28bc44d...akilluminati47:e1e3504",
      "diff_url": "https://github.com/akilluminati47/RIPPS2/compare/28bc44d0d3a31b084d6b7e55ff91745f9a09c026...e1e3504f4a8bfd6b2f88425d284eb845393cfcb9.diff",
      "patch_url": "https://github.com/akilluminati47/RIPPS2/compare/28bc44d0d3a31b084d6b7e55ff91745f9a09c026...e1e3504f4a8bfd6b2f88425d284eb845393cfcb9.patch",
      "base_commit": {
        "sha": "28bc44d0d3a31b084d6b7e55ff91745f9a09c026",
        "node_id": "C_kwDOUx-BJtoAKDI4YmM0NGQwZDNhMzFiMDg0ZDZiN2U1NWZmOTE3NDVmOWEwOWMwMjY",
        "commit": {
          "author": {
            "name": "akilluminati47",
            "email": "acaradimos@gmail.com",
            "date": "2026-10-22T19:24:24Z"
          },
          "committer": {
            "name": "akilluminati47",
            "email": "acaradimos@gmail.com",
            "date": "2026-10-22T19:24:24Z"
          },
          "message": "Build 114 plan: FifthFox's iLink and size report, Berion's APA warning; psx-place first post for v1.1.3\n\nThe plan gains the field reports (iLink Neutrino / POPSTARTER and upstream #1018, the slow USB / MX4SIO / iLink\nlists, a 2 MB size target with the v1.1.3 map's numbers and a lite ELF), the APA chain check before any partition\ndelete or remove, and two decisions. notes/psxplace113.txt is the first post rewritten for v1.1.3 plus a v1.1.0 to\nv1.1.3 bump reply; next114.md is as posted on Discord.\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>",
          "tree": {
            "sha": "921d18525a49e6ae95a6223ebdad706671ee3b21",
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/921d18525a49e6ae95a6223ebdad706671ee3b21"
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/28bc44d0d3a31b084d6b7e55ff91745f9a09c026",
          "comment_count": 0,
          "verification": {
            "verified": false,
            "reason": "unsigned",
            "signature": null,
            "payload": null,
            "verified_at": null
          }
        },
        "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/28bc44d0d3a31b084d6b7e55ff91745f9a09c026",
        "html_url": "https://github.com/akilluminati47/RIPPS2/commit/28bc44d0d3a31b084d6b7e55ff91745f9a09c026",
        "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/28bc44d0d3a31b084d6b7e55ff91745f9a09c026/comments",
        "author": {
          "login": "akilluminati47",
          "id": 149444865,
          "node_id": "U_kgDOCOhZAQ",
          "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/akilluminati47",
          "html_url": "https://github.com/akilluminati47",
          "followers_url": "https://api.github.com/users/akilluminati47/followers",
          "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
          "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
          "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
          "repos_url": "https://api.github.com/users/akilluminati47/repos",
          "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
          "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
          "type": "User",
          "user_view_type": "public",
          "site_admin": false
        },
        "committer": {
          "login": "akilluminati47",
          "id": 149444865,
          "node_id": "U_kgDOCOhZAQ",
          "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/akilluminati47",
          "html_url": "https://github.com/akilluminati47",
          "followers_url": "https://api.github.com/users/akilluminati47/followers",
          "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
          "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
          "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
          "repos_url": "https://api.github.com/users/akilluminati47/repos",
          "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
          "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
          "type": "User",
          "user_view_type": "public",
          "site_admin": false
        },
        "parents": [
          {
            "sha": "fd54d6627a9b874ed8f15d475021d735b5104f21",
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/fd54d6627a9b874ed8f15d475021d735b5104f21",
            "html_url": "https://github.com/akilluminati47/RIPPS2/commit/fd54d6627a9b874ed8f15d475021d735b5104f21"
          }
        ]
      },
      "merge_base_commit": {
        "sha": "28bc44d0d3a31b084d6b7e55ff91745f9a09c026",
        "node_id": "C_kwDOUx-BJtoAKDI4YmM0NGQwZDNhMzFiMDg0ZDZiN2U1NWZmOTE3NDVmOWEwOWMwMjY",
        "commit": {
          "author": {
            "name": "akilluminati47",
            "email": "acaradimos@gmail.com",
            "date": "2026-10-22T19:24:24Z"
          },
          "committer": {
            "name": "akilluminati47",
            "email": "acaradimos@gmail.com",
            "date": "2026-10-22T19:24:24Z"
          },
          "message": "Build 114 plan: FifthFox's iLink and size report, Berion's APA warning; psx-place first post for v1.1.3\n\nThe plan gains the field reports (iLink Neutrino / POPSTARTER and upstream #1018, the slow USB / MX4SIO / iLink\nlists, a 2 MB size target with the v1.1.3 map's numbers and a lite ELF), the APA chain check before any partition\ndelete or remove, and two decisions. notes/psxplace113.txt is the first post rewritten for v1.1.3 plus a v1.1.0 to\nv1.1.3 bump reply; next114.md is as posted on Discord.\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>",
          "tree": {
            "sha": "921d18525a49e6ae95a6223ebdad706671ee3b21",
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/921d18525a49e6ae95a6223ebdad706671ee3b21"
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/28bc44d0d3a31b084d6b7e55ff91745f9a09c026",
          "comment_count": 0,
          "verification": {
            "verified": false,
            "reason": "unsigned",
            "signature": null,
            "payload": null,
            "verified_at": null
          }
        },
        "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/28bc44d0d3a31b084d6b7e55ff91745f9a09c026",
        "html_url": "https://github.com/akilluminati47/RIPPS2/commit/28bc44d0d3a31b084d6b7e55ff91745f9a09c026",
        "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/28bc44d0d3a31b084d6b7e55ff91745f9a09c026/comments",
        "author": {
          "login": "akilluminati47",
          "id": 149444865,
          "node_id": "U_kgDOCOhZAQ",
          "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/akilluminati47",
          "html_url": "https://github.com/akilluminati47",
          "followers_url": "https://api.github.com/users/akilluminati47/followers",
          "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
          "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
          "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
          "repos_url": "https://api.github.com/users/akilluminati47/repos",
          "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
          "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
          "type": "User",
          "user_view_type": "public",
          "site_admin": false
        },
        "committer": {
          "login": "akilluminati47",
          "id": 149444865,
          "node_id": "U_kgDOCOhZAQ",
          "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/akilluminati47",
          "html_url": "https://github.com/akilluminati47",
          "followers_url": "https://api.github.com/users/akilluminati47/followers",
          "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
          "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
          "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
          "repos_url": "https://api.github.com/users/akilluminati47/repos",
          "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
          "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
          "type": "User",
          "user_view_type": "public",
          "site_admin": false
        },
        "parents": [
          {
            "sha": "fd54d6627a9b874ed8f15d475021d735b5104f21",
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/fd54d6627a9b874ed8f15d475021d735b5104f21",
            "html_url": "https://github.com/akilluminati47/RIPPS2/commit/fd54d6627a9b874ed8f15d475021d735b5104f21"
          }
        ]
      },
      "status": "ahead",
      "ahead_by": 32,
      "behind_by": 0,
      "total_commits": 32,
      "commits": [
        {
          "sha": "33a8dc28ffc1b8220ef361fa92b631d782a32967",
          "node_id": "C_kwDOUx-BJtoAKDMzYThkYzI4ZmZjMWI4MjIwZWYzNjFmYTkyYjYzMWQ3ODJhMzI5Njc",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T00:30:41Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T00:30:41Z"
            },
            "message": "Build 114: All Games, hide and delete, the launch guard, one HDD, Color Themes and the Color Sweep, compression to ZSO\n\nRiptOPL 57c45b4 merged; All Games from the first frame; Hide Game and Delete (the APA table checked\nfirst); the launchers found once (Game Sources > Cache > Look now); STATUS drives first; launch write\nsafety and the launch guard panel; the settings audit (every page on one screen but Network); one HDD\nStart Mode; Tangerine, Flamingo, Turquoise, Moonlight and Custom with the Color Sweep; one variable\nRoboto with Text Weight; the theme list on a held SELECT; Compress to ZSO (experimental, OPL core);\nwrites in whole aligned pieces and RiptOPL's guarded exFAT driver. Squashed from b114 (86 commits).\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>",
            "tree": {
              "sha": "f7ac9df9d5d6e08999e87d999137bc1d786b7c77",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/f7ac9df9d5d6e08999e87d999137bc1d786b7c77"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/33a8dc28ffc1b8220ef361fa92b631d782a32967",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/33a8dc28ffc1b8220ef361fa92b631d782a32967",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/33a8dc28ffc1b8220ef361fa92b631d782a32967",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/33a8dc28ffc1b8220ef361fa92b631d782a32967/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "28bc44d0d3a31b084d6b7e55ff91745f9a09c026",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/28bc44d0d3a31b084d6b7e55ff91745f9a09c026",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/28bc44d0d3a31b084d6b7e55ff91745f9a09c026"
            }
          ]
        },
        {
          "sha": "fe671429a4fa6afa1d302f5c836d3f86a825511a",
          "node_id": "C_kwDOUx-BJtoAKGZlNjcxNDI5YTRmYTZhZmExZDMwMmY1YzgzNmQzZjg2YTgyNTUxMWE",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T00:37:42Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T00:37:42Z"
            },
            "message": "v1.1.4-beta: README, release, Discord and psx-place notes, the build 114 pictures\n\nRelease notes (release114.md) with the new pictures cut from the 10/24 takes (delete's plan, the launch\nguard, compression's hold and bar, the theme list, Tangerine, Custom, the Color Sweep, Hidden Games), the\nfirst post's tour, the Build 114 banner, and the psx-place first post and bump with REEL_ID / SHORT_ID left\nfor the YouTube links. Discord notes cut to fit one message.\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>",
            "tree": {
              "sha": "b84777a3fa147ed21d079a653ca93ebc8009f97a",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/b84777a3fa147ed21d079a653ca93ebc8009f97a"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/fe671429a4fa6afa1d302f5c836d3f86a825511a",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/fe671429a4fa6afa1d302f5c836d3f86a825511a",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/fe671429a4fa6afa1d302f5c836d3f86a825511a",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/fe671429a4fa6afa1d302f5c836d3f86a825511a/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "33a8dc28ffc1b8220ef361fa92b631d782a32967",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/33a8dc28ffc1b8220ef361fa92b631d782a32967",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/33a8dc28ffc1b8220ef361fa92b631d782a32967"
            }
          ]
        },
        {
          "sha": "d8cf88c36763135c78d749c1f6758c2eccd5de1f",
          "node_id": "C_kwDOUx-BJtoAKGQ4Y2Y4OGMzNjc2MzEzNWM3OGQ3NDljMWY2NzU4YzJlY2NkNWRlMWY",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T00:44:53Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T00:44:53Z"
            },
            "message": "Banner: the towers lowered, clear of the line under the title (the user, 10/24)\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>",
            "tree": {
              "sha": "928debec2394bcb59346c819b1eea4fbbca811e2",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/928debec2394bcb59346c819b1eea4fbbca811e2"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/d8cf88c36763135c78d749c1f6758c2eccd5de1f",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/d8cf88c36763135c78d749c1f6758c2eccd5de1f",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/d8cf88c36763135c78d749c1f6758c2eccd5de1f",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/d8cf88c36763135c78d749c1f6758c2eccd5de1f/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "fe671429a4fa6afa1d302f5c836d3f86a825511a",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/fe671429a4fa6afa1d302f5c836d3f86a825511a",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/fe671429a4fa6afa1d302f5c836d3f86a825511a"
            }
          ]
        },
        {
          "sha": "5a9b986cbf8e5b2e20d84bfbbf26a444c29c6ebc",
          "node_id": "C_kwDOUx-BJtoAKDVhOWI5ODZjYmY4ZTViMmUyMGQ4NGJmYmJmMjZhNDQ0YzI5YzZlYmM",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T00:51:52Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T00:51:52Z"
            },
            "message": "The v1.1.4 wheel Short (720x1280 copy for GitHub): compression's question with Back (keeps both)\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>",
            "tree": {
              "sha": "c713f70ed4da80ba967383ae6cb7d2306ab7d39b",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/c713f70ed4da80ba967383ae6cb7d2306ab7d39b"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/5a9b986cbf8e5b2e20d84bfbbf26a444c29c6ebc",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/5a9b986cbf8e5b2e20d84bfbbf26a444c29c6ebc",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/5a9b986cbf8e5b2e20d84bfbbf26a444c29c6ebc",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/5a9b986cbf8e5b2e20d84bfbbf26a444c29c6ebc/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "d8cf88c36763135c78d749c1f6758c2eccd5de1f",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/d8cf88c36763135c78d749c1f6758c2eccd5de1f",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/d8cf88c36763135c78d749c1f6758c2eccd5de1f"
            }
          ]
        },
        {
          "sha": "759ffc5aee1e23351fd1cbf139c39bea178d5c7e",
          "node_id": "C_kwDOUx-BJtoAKDc1OWZmYzVhZWUxZTIzMzUxZmQxY2JmMTM5YzM5YmVhMTc4ZDVjN2U",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T01:04:43Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T01:04:43Z"
            },
            "message": "README and v1.1.4-beta notes: the wheel Short (its GitHub Pages copy)\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>",
            "tree": {
              "sha": "5dbfd5d8f37946e6d4356e21d5bf8cc853686263",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/5dbfd5d8f37946e6d4356e21d5bf8cc853686263"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/759ffc5aee1e23351fd1cbf139c39bea178d5c7e",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/759ffc5aee1e23351fd1cbf139c39bea178d5c7e",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/759ffc5aee1e23351fd1cbf139c39bea178d5c7e",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/759ffc5aee1e23351fd1cbf139c39bea178d5c7e/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "5a9b986cbf8e5b2e20d84bfbbf26a444c29c6ebc",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/5a9b986cbf8e5b2e20d84bfbbf26a444c29c6ebc",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/5a9b986cbf8e5b2e20d84bfbbf26a444c29c6ebc"
            }
          ]
        },
        {
          "sha": "3e94bd611294f8fa3ea1230e6d706a3085d81e01",
          "node_id": "C_kwDOUx-BJtoAKDNlOTRiZDYxMTI5NGY4ZmEzZWExMjMwZTZkNzA2YTMwODVkODFlMDE",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T01:28:24Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T01:28:24Z"
            },
            "message": "Website embed: launch physical media, rip it into your library; 33 languages (the user's line)\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>",
            "tree": {
              "sha": "d6a6e340ec0f749ac57b66dde352fa2fd8e64fdc",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/d6a6e340ec0f749ac57b66dde352fa2fd8e64fdc"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/3e94bd611294f8fa3ea1230e6d706a3085d81e01",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/3e94bd611294f8fa3ea1230e6d706a3085d81e01",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/3e94bd611294f8fa3ea1230e6d706a3085d81e01",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/3e94bd611294f8fa3ea1230e6d706a3085d81e01/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "759ffc5aee1e23351fd1cbf139c39bea178d5c7e",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/759ffc5aee1e23351fd1cbf139c39bea178d5c7e",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/759ffc5aee1e23351fd1cbf139c39bea178d5c7e"
            }
          ]
        },
        {
          "sha": "92a1b0d324269f1d47ebda0a0f639181f3cc7782",
          "node_id": "C_kwDOUx-BJtoAKDkyYTFiMGQzMjQyNjlmMWQ0N2ViZGEwYTBmNjM5MTgxZjNjYzc3ODI",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T01:30:14Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T01:30:14Z"
            },
            "message": "Website embed: Homebrew Game Loader\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>",
            "tree": {
              "sha": "1a0b798f9417d5fec374f85522323da5490abb49",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/1a0b798f9417d5fec374f85522323da5490abb49"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/92a1b0d324269f1d47ebda0a0f639181f3cc7782",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/92a1b0d324269f1d47ebda0a0f639181f3cc7782",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/92a1b0d324269f1d47ebda0a0f639181f3cc7782",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/92a1b0d324269f1d47ebda0a0f639181f3cc7782/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "3e94bd611294f8fa3ea1230e6d706a3085d81e01",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/3e94bd611294f8fa3ea1230e6d706a3085d81e01",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/3e94bd611294f8fa3ea1230e6d706a3085d81e01"
            }
          ]
        },
        {
          "sha": "e1aada2069bb6029f53b9807da994cb5f4448fa4",
          "node_id": "C_kwDOUx-BJtoAKGUxYWFkYTIwNjliYjYwMjlmNTNiOTgwN2RhOTk0Y2I1ZjQ0NDhmYTQ",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T01:33:29Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T01:33:29Z"
            },
            "message": "Website embed: Homebrew Game Loader, without Free Modern\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>",
            "tree": {
              "sha": "d1b64e8d75bb6b54e470b937f088cc60fa44323e",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/d1b64e8d75bb6b54e470b937f088cc60fa44323e"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/e1aada2069bb6029f53b9807da994cb5f4448fa4",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/e1aada2069bb6029f53b9807da994cb5f4448fa4",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/e1aada2069bb6029f53b9807da994cb5f4448fa4",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/e1aada2069bb6029f53b9807da994cb5f4448fa4/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "92a1b0d324269f1d47ebda0a0f639181f3cc7782",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/92a1b0d324269f1d47ebda0a0f639181f3cc7782",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/92a1b0d324269f1d47ebda0a0f639181f3cc7782"
            }
          ]
        },
        {
          "sha": "d017d28ac07dcb048db83e6322543ffa12f9609b",
          "node_id": "C_kwDOUx-BJtoAKGQwMTdkMjhhYzA3ZGNiMDQ4ZGI4M2U2MzIyNTQzZmZhMTJmOTYwOWI",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T01:46:17Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T01:46:17Z"
            },
            "message": "Build 114 hotfix: compression asks an MMCE card if it can rename before it starts\n\nCompression's last step renames the checked .part into the game's place, and an MMCE card renames only from\nsd2psXtd 1.4.0 on. On MMCE, RIPPS2 now writes and renames a tiny file beside the game first: older firmware is\ntold in a second to update (sd2psxtd.github.io/download), with nothing changed, instead of after the whole\nwork. In all 32 languages.\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>",
            "tree": {
              "sha": "e2499d46855d019751a1ec0911e63016b1f0cb73",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/e2499d46855d019751a1ec0911e63016b1f0cb73"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/d017d28ac07dcb048db83e6322543ffa12f9609b",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/d017d28ac07dcb048db83e6322543ffa12f9609b",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/d017d28ac07dcb048db83e6322543ffa12f9609b",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/d017d28ac07dcb048db83e6322543ffa12f9609b/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "e1aada2069bb6029f53b9807da994cb5f4448fa4",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/e1aada2069bb6029f53b9807da994cb5f4448fa4",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/e1aada2069bb6029f53b9807da994cb5f4448fa4"
            }
          ]
        },
        {
          "sha": "e6bf70d11145fd111ab58999482a67d5b168c18e",
          "node_id": "C_kwDOUx-BJtoAKGU2YmY3MGQxMTE0NWZkMTExYWI1ODk5OTQ4MmE2N2Q1YjE2OGMxOGU",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T01:48:10Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T01:48:10Z"
            },
            "message": "MMCE: keep sd2psXtd up to date; compressing on the card needs 1.4.0, and the card has no room check (README credits, changelog, release and psx-place notes)\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>",
            "tree": {
              "sha": "c3bc0ee9589bd1f51f3d3b20ac151a25cf457f4d",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/c3bc0ee9589bd1f51f3d3b20ac151a25cf457f4d"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/e6bf70d11145fd111ab58999482a67d5b168c18e",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/e6bf70d11145fd111ab58999482a67d5b168c18e",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/e6bf70d11145fd111ab58999482a67d5b168c18e",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/e6bf70d11145fd111ab58999482a67d5b168c18e/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "d017d28ac07dcb048db83e6322543ffa12f9609b",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/d017d28ac07dcb048db83e6322543ffa12f9609b",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/d017d28ac07dcb048db83e6322543ffa12f9609b"
            }
          ]
        },
        {
          "sha": "4b1adf4c3544db438775e9d46695e8ad63097250",
          "node_id": "C_kwDOUx-BJtoAKDRiMWFkZjRjMzU0NGRiNDM4Nzc1ZTlkNDY2OTVlOGFkNjMwOTcyNTA",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T05:21:58Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T05:21:58Z"
            },
            "message": "Build 114 hotfix: RIPGRID without Adapt's side bar\n\nRIPGRID kept Adapt's frosted side bar (the dark strip and its blue edge, main2/main3 and info2/info3) though\nit has no sidebar: Circle picks the source there. The four strips are gone and the elements after them\nrenumbered; build 102's test follows the new numbers.\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>",
            "tree": {
              "sha": "b7b97be4d9679fbeec9375e4c55601ff53520cb2",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/b7b97be4d9679fbeec9375e4c55601ff53520cb2"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/4b1adf4c3544db438775e9d46695e8ad63097250",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/4b1adf4c3544db438775e9d46695e8ad63097250",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/4b1adf4c3544db438775e9d46695e8ad63097250",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/4b1adf4c3544db438775e9d46695e8ad63097250/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "e6bf70d11145fd111ab58999482a67d5b168c18e",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/e6bf70d11145fd111ab58999482a67d5b168c18e",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/e6bf70d11145fd111ab58999482a67d5b168c18e"
            }
          ]
        },
        {
          "sha": "f0f0e8b8f1a3daa3d0b0a9c2f86dc76f36fb107f",
          "node_id": "C_kwDOUx-BJtoAKGYwZjBlOGI4ZjFhM2RhYTNkMGIwYTljMmY4NmRjNzZmMzZmYjEwN2Y",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T05:36:44Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T05:36:44Z"
            },
            "message": "Add RIPPS2 Discord component embed preview",
            "tree": {
              "sha": "ffe1322d888a575a17ebe795472313f40510ebd4",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/ffe1322d888a575a17ebe795472313f40510ebd4"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/f0f0e8b8f1a3daa3d0b0a9c2f86dc76f36fb107f",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/f0f0e8b8f1a3daa3d0b0a9c2f86dc76f36fb107f",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/f0f0e8b8f1a3daa3d0b0a9c2f86dc76f36fb107f",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/f0f0e8b8f1a3daa3d0b0a9c2f86dc76f36fb107f/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "4b1adf4c3544db438775e9d46695e8ad63097250",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/4b1adf4c3544db438775e9d46695e8ad63097250",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/4b1adf4c3544db438775e9d46695e8ad63097250"
            }
          ]
        },
        {
          "sha": "dc7b0922593dd3f6e6adee7b380f2dc61b863668",
          "node_id": "C_kwDOUx-BJtoAKGRjN2IwOTIyNTkzZGQzZjZlNmFkZWU3YjM4MGYyZGM2MWI4NjM2Njg",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T05:37:09Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T05:37:30Z"
            },
            "message": "Build 114 hotfix: no 400 box on a console with no HDD\n\nAt boot the BDM side looks for an internal exFAT HDD (whenever HDD Start Mode is not Off), and the boot folder\nsearch's last step loads the ATA stack too. With no HDD interface (a slim, a fat without the network adapter)\nthe failed load posted \"400: hard drive not detected\" as a box on every boot, HDD Start Mode Manual included\n(the lab with no HDD, builds 113 and 114). Those two background checks are now quiet (hddSetQuietProbe); an\nHDD page you start still says the drive is missing.\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>",
            "tree": {
              "sha": "f567b50b79b33295f4583e72c1b7ed570d5fa1e3",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/f567b50b79b33295f4583e72c1b7ed570d5fa1e3"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/dc7b0922593dd3f6e6adee7b380f2dc61b863668",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/dc7b0922593dd3f6e6adee7b380f2dc61b863668",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/dc7b0922593dd3f6e6adee7b380f2dc61b863668",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/dc7b0922593dd3f6e6adee7b380f2dc61b863668/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "f0f0e8b8f1a3daa3d0b0a9c2f86dc76f36fb107f",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/f0f0e8b8f1a3daa3d0b0a9c2f86dc76f36fb107f",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/f0f0e8b8f1a3daa3d0b0a9c2f86dc76f36fb107f"
            }
          ]
        },
        {
          "sha": "2eda46ed5ba1085f3b045bf993b322553960ed57",
          "node_id": "C_kwDOUx-BJtoAKDJlZGE0NmVkNWJhMTA4NWYzYjA0NWJmOTkzYjMyMjU1Mzk2MGVkNTc",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T05:38:07Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T05:38:35Z"
            },
            "message": "Document live Discord component embed",
            "tree": {
              "sha": "4c0aab578ccacf5d9dfaf6819093ad1825abf1fa",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/4c0aab578ccacf5d9dfaf6819093ad1825abf1fa"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/2eda46ed5ba1085f3b045bf993b322553960ed57",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/2eda46ed5ba1085f3b045bf993b322553960ed57",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/2eda46ed5ba1085f3b045bf993b322553960ed57",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/2eda46ed5ba1085f3b045bf993b322553960ed57/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "dc7b0922593dd3f6e6adee7b380f2dc61b863668",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/dc7b0922593dd3f6e6adee7b380f2dc61b863668",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/dc7b0922593dd3f6e6adee7b380f2dc61b863668"
            }
          ]
        },
        {
          "sha": "b20b196f4e4c545d1333cc1199ff0092745cc534",
          "node_id": "C_kwDOUx-BJtoAKGIyMGIxOTZmNGU0YzU0NWQxMzMzY2MxMTk5ZmYwMDkyNzQ1Y2M1MzQ",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T05:43:40Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T05:43:42Z"
            },
            "message": "Build 114 hotfix: the two RiptOPL host tests that compile hddLoadModules bare declare hddQuietProbe\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>",
            "tree": {
              "sha": "cc851ecd48336971bb0a534b3a45e2ba14b91881",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/cc851ecd48336971bb0a534b3a45e2ba14b91881"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/b20b196f4e4c545d1333cc1199ff0092745cc534",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/b20b196f4e4c545d1333cc1199ff0092745cc534",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/b20b196f4e4c545d1333cc1199ff0092745cc534",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/b20b196f4e4c545d1333cc1199ff0092745cc534/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "2eda46ed5ba1085f3b045bf993b322553960ed57",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/2eda46ed5ba1085f3b045bf993b322553960ed57",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/2eda46ed5ba1085f3b045bf993b322553960ed57"
            }
          ]
        },
        {
          "sha": "8d2316d5c30d546da7d1831336543b27c815440c",
          "node_id": "C_kwDOUx-BJtoAKDhkMjMxNmQ1YzMwZDU0NmRhN2QxODMxMzM2NTQzYjI3YzgxNTQ0MGM",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T05:50:21Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T05:50:21Z"
            },
            "message": "Use top-level heading in Discord link card",
            "tree": {
              "sha": "24fa99740bab4985a52fd55ec35e4111aa30905c",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/24fa99740bab4985a52fd55ec35e4111aa30905c"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/8d2316d5c30d546da7d1831336543b27c815440c",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/8d2316d5c30d546da7d1831336543b27c815440c",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/8d2316d5c30d546da7d1831336543b27c815440c",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/8d2316d5c30d546da7d1831336543b27c815440c/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "b20b196f4e4c545d1333cc1199ff0092745cc534",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/b20b196f4e4c545d1333cc1199ff0092745cc534",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/b20b196f4e4c545d1333cc1199ff0092745cc534"
            }
          ]
        },
        {
          "sha": "f96d9d9030a347737cb5a7cd8a551bfe9c724252",
          "node_id": "C_kwDOUx-BJtoAKGY5NmQ5ZDkwMzBhMzQ3NzM3Y2I1YTdjZDhhNTUxYmZlOWM3MjQyNTI",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T05:53:01Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T05:53:01Z"
            },
            "message": "Separate Discord card heading from description",
            "tree": {
              "sha": "aac96bce90089be9689cd5d26c609af2bd4f0589",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/aac96bce90089be9689cd5d26c609af2bd4f0589"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/f96d9d9030a347737cb5a7cd8a551bfe9c724252",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/f96d9d9030a347737cb5a7cd8a551bfe9c724252",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/f96d9d9030a347737cb5a7cd8a551bfe9c724252",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/f96d9d9030a347737cb5a7cd8a551bfe9c724252/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "8d2316d5c30d546da7d1831336543b27c815440c",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/8d2316d5c30d546da7d1831336543b27c815440c",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/8d2316d5c30d546da7d1831336543b27c815440c"
            }
          ]
        },
        {
          "sha": "f59abffde0794debdadaee526c175414a4b12b21",
          "node_id": "C_kwDOUx-BJtoAKGY1OWFiZmZkZTA3OTRkZWJkYWRhZWU1MjZjMTc1NDE0YTRiMTJiMjE",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T05:53:52Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T05:53:52Z"
            },
            "message": "v1.1.4-beta hotfixes in the changelog and release notes: no 400 box without an HDD, RIPGRID without the side bar, MMCE asked first\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>",
            "tree": {
              "sha": "b7eb77be2f2b67c43ee3baf76b134e730931c8bb",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/b7eb77be2f2b67c43ee3baf76b134e730931c8bb"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/f59abffde0794debdadaee526c175414a4b12b21",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/f59abffde0794debdadaee526c175414a4b12b21",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/f59abffde0794debdadaee526c175414a4b12b21",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/f59abffde0794debdadaee526c175414a4b12b21/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "f96d9d9030a347737cb5a7cd8a551bfe9c724252",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/f96d9d9030a347737cb5a7cd8a551bfe9c724252",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/f96d9d9030a347737cb5a7cd8a551bfe9c724252"
            }
          ]
        },
        {
          "sha": "6c0d207db19c445747419f5990879863b8cfdc22",
          "node_id": "C_kwDOUx-BJtoAKDZjMGQyMDdkYjE5YzQ0NTc0NzQxOWY1OTkwODc5ODYzYjhjZmRjMjI",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T06:01:56Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T06:01:56Z"
            },
            "message": "Emphasize Discord component embed heading",
            "tree": {
              "sha": "3ce35123209601178a1e7af27e0bb24628ea78b5",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/3ce35123209601178a1e7af27e0bb24628ea78b5"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/6c0d207db19c445747419f5990879863b8cfdc22",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/6c0d207db19c445747419f5990879863b8cfdc22",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/6c0d207db19c445747419f5990879863b8cfdc22",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/6c0d207db19c445747419f5990879863b8cfdc22/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "f59abffde0794debdadaee526c175414a4b12b21",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/f59abffde0794debdadaee526c175414a4b12b21",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/f59abffde0794debdadaee526c175414a4b12b21"
            }
          ]
        },
        {
          "sha": "fbe8d333deb47ad50b06c2d0714b30e06147ed6b",
          "node_id": "C_kwDOUx-BJtoAKGZiZThkMzMzZGViNDdhZDUwYjA2YzJkMDcxNGIzMGUwNjE0N2VkNmI",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T06:04:48Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T06:04:48Z"
            },
            "message": "Separate Discord embed bullet lines",
            "tree": {
              "sha": "62191a5d27316d9da74be2570271d918c0c8b30f",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/62191a5d27316d9da74be2570271d918c0c8b30f"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/fbe8d333deb47ad50b06c2d0714b30e06147ed6b",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/fbe8d333deb47ad50b06c2d0714b30e06147ed6b",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/fbe8d333deb47ad50b06c2d0714b30e06147ed6b",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/fbe8d333deb47ad50b06c2d0714b30e06147ed6b/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "6c0d207db19c445747419f5990879863b8cfdc22",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/6c0d207db19c445747419f5990879863b8cfdc22",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/6c0d207db19c445747419f5990879863b8cfdc22"
            }
          ]
        },
        {
          "sha": "5a732e2a36adb1d705fbd2a5788ed490ffd86278",
          "node_id": "C_kwDOUx-BJtoAKDVhNzMyZTJhMzZhZGIxZDcwNWZiZDJhNTc4OGVkNDkwZmZkODYyNzg",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T06:05:28Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T06:05:28Z"
            },
            "message": "Render language support note in smaller text",
            "tree": {
              "sha": "2ee54260c9873d63b632c0105edce021e578d58c",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/2ee54260c9873d63b632c0105edce021e578d58c"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/5a732e2a36adb1d705fbd2a5788ed490ffd86278",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/5a732e2a36adb1d705fbd2a5788ed490ffd86278",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/5a732e2a36adb1d705fbd2a5788ed490ffd86278",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/5a732e2a36adb1d705fbd2a5788ed490ffd86278/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "fbe8d333deb47ad50b06c2d0714b30e06147ed6b",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/fbe8d333deb47ad50b06c2d0714b30e06147ed6b",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/fbe8d333deb47ad50b06c2d0714b30e06147ed6b"
            }
          ]
        },
        {
          "sha": "2e6a114839b4cb75066063033d9fc6ca580fbcc1",
          "node_id": "C_kwDOUx-BJtoAKDJlNmExMTQ4MzliNGNiNzUwNjYwNjMwMzNkOWZjNmNhNTgwZmJjYzE",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T06:43:28Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T06:43:28Z"
            },
            "message": "v1.1.4 reel and wheel Short on YouTube: README, release notes, the psx-place posts ready to paste\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>",
            "tree": {
              "sha": "dc9efc1282135f999f14c5db39ae60be7f15497e",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/dc9efc1282135f999f14c5db39ae60be7f15497e"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/2e6a114839b4cb75066063033d9fc6ca580fbcc1",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/2e6a114839b4cb75066063033d9fc6ca580fbcc1",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/2e6a114839b4cb75066063033d9fc6ca580fbcc1",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/2e6a114839b4cb75066063033d9fc6ca580fbcc1/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "5a732e2a36adb1d705fbd2a5788ed490ffd86278",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/5a732e2a36adb1d705fbd2a5788ed490ffd86278",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/5a732e2a36adb1d705fbd2a5788ed490ffd86278"
            }
          ]
        },
        {
          "sha": "d3666befab95f55bf7da9bf16d567ac5bfcc2cdd",
          "node_id": "C_kwDOUx-BJtoAKGQzNjY2YmVmYWI5NWY1NWJmN2RhOWJmMTZkNTY3YWM1YmZjYzJjZGQ",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T06:46:28Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T06:46:28Z"
            },
            "message": "v1.1.4-beta notes: the wheel Short out of the release (the reel alone)\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>",
            "tree": {
              "sha": "7a1f74fa9dda1a5ebb3dc3e70ad06fe0e9bebeec",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/7a1f74fa9dda1a5ebb3dc3e70ad06fe0e9bebeec"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/d3666befab95f55bf7da9bf16d567ac5bfcc2cdd",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/d3666befab95f55bf7da9bf16d567ac5bfcc2cdd",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/d3666befab95f55bf7da9bf16d567ac5bfcc2cdd",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/d3666befab95f55bf7da9bf16d567ac5bfcc2cdd/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "2e6a114839b4cb75066063033d9fc6ca580fbcc1",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/2e6a114839b4cb75066063033d9fc6ca580fbcc1",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/2e6a114839b4cb75066063033d9fc6ca580fbcc1"
            }
          ]
        },
        {
          "sha": "f5c32aea44419c54f365e7342391cbda3ea01f89",
          "node_id": "C_kwDOUx-BJtoAKGY1YzMyYWVhNDQ0MTljNTRmMzY1ZTczNDIzOTFjYmRhM2VhMDFmODk",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T06:53:45Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T06:53:45Z"
            },
            "message": "psx-place posts: Neutrino linked once, beside RiptOPL\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>",
            "tree": {
              "sha": "072a00d9e598625326877d72774909f07decf7cc",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/072a00d9e598625326877d72774909f07decf7cc"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/f5c32aea44419c54f365e7342391cbda3ea01f89",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/f5c32aea44419c54f365e7342391cbda3ea01f89",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/f5c32aea44419c54f365e7342391cbda3ea01f89",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/f5c32aea44419c54f365e7342391cbda3ea01f89/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "d3666befab95f55bf7da9bf16d567ac5bfcc2cdd",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/d3666befab95f55bf7da9bf16d567ac5bfcc2cdd",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/d3666befab95f55bf7da9bf16d567ac5bfcc2cdd"
            }
          ]
        },
        {
          "sha": "4dd8f7331d030ce4a9d5791e442747eb5019d1a4",
          "node_id": "C_kwDOUx-BJtoAKDRkZDhmNzMzMWQwMzBjZTRhOWQ1NzkxZTQ0Mjc0N2ViNTAxOWQxYTQ",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T10:18:37Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T10:18:37Z"
            },
            "message": "Build 115 plan: the first SD2PSXTD hardware run (MMCE input and launch, the in-game exit, cache, sources, tooltips)\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>",
            "tree": {
              "sha": "4f6d791a98c1dbff02a9a4163122d8a881c8e7c2",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/4f6d791a98c1dbff02a9a4163122d8a881c8e7c2"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/4dd8f7331d030ce4a9d5791e442747eb5019d1a4",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/4dd8f7331d030ce4a9d5791e442747eb5019d1a4",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/4dd8f7331d030ce4a9d5791e442747eb5019d1a4",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/4dd8f7331d030ce4a9d5791e442747eb5019d1a4/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "f5c32aea44419c54f365e7342391cbda3ea01f89",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/f5c32aea44419c54f365e7342391cbda3ea01f89",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/f5c32aea44419c54f365e7342391cbda3ea01f89"
            }
          ]
        },
        {
          "sha": "c3a647bbfc6cc1e36e05950913af6e21571d97bd",
          "node_id": "C_kwDOUx-BJtoAKGMzYTY0N2JiZmM2Y2MxZTM2ZTA1OTUwOTEzYWY2ZTIxNTcxZDk3YmQ",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T10:30:27Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T10:30:27Z"
            },
            "message": "Build 115 plan: what the SD showed (speed, half-made GameID cards, boot channel 8, stale exit path) and OPL Manager art names\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>",
            "tree": {
              "sha": "02684745483b21d88e71ea31a4b02556db5dd441",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/02684745483b21d88e71ea31a4b02556db5dd441"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/c3a647bbfc6cc1e36e05950913af6e21571d97bd",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/c3a647bbfc6cc1e36e05950913af6e21571d97bd",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/c3a647bbfc6cc1e36e05950913af6e21571d97bd",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/c3a647bbfc6cc1e36e05950913af6e21571d97bd/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "4dd8f7331d030ce4a9d5791e442747eb5019d1a4",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/4dd8f7331d030ce4a9d5791e442747eb5019d1a4",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/4dd8f7331d030ce4a9d5791e442747eb5019d1a4"
            }
          ]
        },
        {
          "sha": "0dd48e0b657ed6291f532e75676ac3bfe29f5ff6",
          "node_id": "C_kwDOUx-BJtoAKDBkZDQ4ZTBiNjU3ZWQ2MjkxZjUzMmU3NTY3NmFjM2JmZTI5ZjVmZjY",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T10:50:19Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T10:50:19Z"
            },
            "message": "Build 115 (in progress): game art read under every name it goes by\n\nThe art index keeps the folder's real picture names, so a lookup finds .png, .jpg or .jpeg in any letter case and\nopens the name the drive lists (PFS minds case). A picture is decoded by what it holds and shrunk when too big. OPL\nManager's _BG_00, _SCR_00 and _SCR_01 stand in for _BG, _SCR and _SCR2 (and back), and a PS1 game's art is also\nlooked for by its disc ID and its POPStarter ELF name (akilluminati47/RIPPS2#2).\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>",
            "tree": {
              "sha": "cbe41cf7b2ecf90b9514deaf7fe4a54fab85d203",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/cbe41cf7b2ecf90b9514deaf7fe4a54fab85d203"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/0dd48e0b657ed6291f532e75676ac3bfe29f5ff6",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/0dd48e0b657ed6291f532e75676ac3bfe29f5ff6",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/0dd48e0b657ed6291f532e75676ac3bfe29f5ff6",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/0dd48e0b657ed6291f532e75676ac3bfe29f5ff6/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "c3a647bbfc6cc1e36e05950913af6e21571d97bd",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/c3a647bbfc6cc1e36e05950913af6e21571d97bd",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/c3a647bbfc6cc1e36e05950913af6e21571d97bd"
            }
          ]
        },
        {
          "sha": "329c0051c0e8697eaf6d1bc146c9dd0258ffd755",
          "node_id": "C_kwDOUx-BJtoAKDMyOWMwMDUxYzBlODY5N2VhZjZkMWJjMTQ2YzlkZDAyNThmZmQ3NTU",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T11:08:45Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T11:08:45Z"
            },
            "message": "Lab driver: find, grab, name and close PCSX2's own message boxes\n\nFrom 01:30 on 10/25 no key reached the hidden lab: PCSX2 opened an Error box at every launch (the PC's audio output\nhad gone, so cubeb could not start), and the box, modal and unseen on the lab's desktop, disabled the main window.\nBefore the first key and before each step the driver now looks for a Qt window PCSX2's main window owns, grabs it to\ngrabs/dialog_*.png, says so, and closes it with WM_CLOSE (a key does not reach it). PS2BG_FOCUSMSG posts activation\nmessages first (tried while finding this; kept as a switch).\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>",
            "tree": {
              "sha": "e44cdd09efe7375aef855bfd4bd5a572058c9e5f",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/e44cdd09efe7375aef855bfd4bd5a572058c9e5f"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/329c0051c0e8697eaf6d1bc146c9dd0258ffd755",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/329c0051c0e8697eaf6d1bc146c9dd0258ffd755",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/329c0051c0e8697eaf6d1bc146c9dd0258ffd755",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/329c0051c0e8697eaf6d1bc146c9dd0258ffd755/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "0dd48e0b657ed6291f532e75676ac3bfe29f5ff6",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/0dd48e0b657ed6291f532e75676ac3bfe29f5ff6",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/0dd48e0b657ed6291f532e75676ac3bfe29f5ff6"
            }
          ]
        },
        {
          "sha": "ec408f4adfb86d3de5952f9ff358d0986eb4bb32",
          "node_id": "C_kwDOUx-BJtoAKGVjNDA4ZjRhZGZiODZkM2RlNTk1MmY5ZmYzNThkMDk4NmViNGJiMzI",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T11:21:27Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T11:21:27Z"
            },
            "message": "Build 115 (in progress): pads, MMCE busy and launch wait, Trust Cache, All Games at boot\n\n- A DualShock left in digital mode is asked for analog again in a quiet second (a dead right stick after a starved boot).\n- MMCE: nothing is read while the card says busy (copying or making a memory card); a launch from the card writes the\n  play history first and waits while the card makes the game's memory card.\n- SELECT refreshes coalesce; Trust Cache keeps a cached list until a refresh (MMCE, SMB, BDM).\n- All Games builds its list at boot even when every source is Manual.\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>",
            "tree": {
              "sha": "2e61abe10125ee6e453b19e6cd2ad5b989cdbda6",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/2e61abe10125ee6e453b19e6cd2ad5b989cdbda6"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/ec408f4adfb86d3de5952f9ff358d0986eb4bb32",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/ec408f4adfb86d3de5952f9ff358d0986eb4bb32",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/ec408f4adfb86d3de5952f9ff358d0986eb4bb32",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/ec408f4adfb86d3de5952f9ff358d0986eb4bb32/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "329c0051c0e8697eaf6d1bc146c9dd0258ffd755",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/329c0051c0e8697eaf6d1bc146c9dd0258ffd755",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/329c0051c0e8697eaf6d1bc146c9dd0258ffd755"
            }
          ]
        },
        {
          "sha": "1221c49bdbda1a85d7509675750d5c09e5938676",
          "node_id": "C_kwDOUx-BJtoAKDEyMjFjNDliZGJkYTFhODVkNzUwOTY3NTc1MGQ1YzA5ZTU5Mzg2NzY",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T18:26:54Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T18:26:54Z"
            },
            "message": "Build 115 (in progress): Neutrino for every install, its newest build carried\n\n- A Neutrino RIPPS2 did not install (XEB+'s, an older kit) gets only the options it knows: RIPPS2 reads its\n  version.txt, or its strings, or for an older kit its config files, before the teardown. Neutrino quits at any option\n  it does not know, so an older one went straight back to the browser. A Neutrino too old for the APA HDD is refused\n  with a message instead.\n- Without -qb Neutrino resets the IOP and opens the game on mass: (unit 0); RIPPS2 sent its own unit, so an internal\n  exFAT HDD after a USB stick (mass1:) was never found and Neutrino quit to the browser (ahavasi on Discord). The game\n  and VMC paths now say mass: whenever no -qb goes out.\n- RIPPS2 carries Neutrino's development build v1.8.0-61-g7be8de2 (31 August 2026) instead of 1.8.0: MMCE, CDVDMAN and\n  CDVDFSV fixes, more compat entries, cheats. tools/dev/pack_neutrino.py repacks a release the same way.\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>",
            "tree": {
              "sha": "70c2ee26679bebbb83d45d490ea6c4c82d130fcf",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/70c2ee26679bebbb83d45d490ea6c4c82d130fcf"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/1221c49bdbda1a85d7509675750d5c09e5938676",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/1221c49bdbda1a85d7509675750d5c09e5938676",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/1221c49bdbda1a85d7509675750d5c09e5938676",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/1221c49bdbda1a85d7509675750d5c09e5938676/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "ec408f4adfb86d3de5952f9ff358d0986eb4bb32",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/ec408f4adfb86d3de5952f9ff358d0986eb4bb32",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/ec408f4adfb86d3de5952f9ff358d0986eb4bb32"
            }
          ]
        },
        {
          "sha": "d4e963173ac32e81654cc60cd7ab5b1bb6568a57",
          "node_id": "C_kwDOUx-BJtoAKGQ0ZTk2MzE3M2FjMzJlODE2NTRjYzYwY2Q3YWI1YjFiYjY1NjhhNTc",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T18:37:40Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T18:37:40Z"
            },
            "message": "Build 115 (in progress): Trust Cache row, EXIT back to RIPPS2, lab-checked Neutrino on the exFAT HDD\n\n- Game Sources > Cache > Trust Cache (ripps2_trust_cache, on): a cached list stands until SELECT.\n- The In-Game Menu's EXIT goes back to the RIPPS2 that started when the IGR Path is empty (a memory card or USB), and\n  passes over a memory-card IGR Path that names no file (the user's stale mc1:/APPS/RIPPS2.elf), checked while the\n  boot card is in.\n- Lab, CI build 1221c49: Neutrino v1.8.0-61 from the internal exFAT HDD (mass: path, version.txt read) reaches the\n  PS2 logo and Burnout 3's intro.\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>",
            "tree": {
              "sha": "a4e5b8ebeb174528f59879bf7a33d10184587b9b",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/a4e5b8ebeb174528f59879bf7a33d10184587b9b"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/d4e963173ac32e81654cc60cd7ab5b1bb6568a57",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/d4e963173ac32e81654cc60cd7ab5b1bb6568a57",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/d4e963173ac32e81654cc60cd7ab5b1bb6568a57",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/d4e963173ac32e81654cc60cd7ab5b1bb6568a57/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "1221c49bdbda1a85d7509675750d5c09e5938676",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/1221c49bdbda1a85d7509675750d5c09e5938676",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/1221c49bdbda1a85d7509675750d5c09e5938676"
            }
          ]
        },
        {
          "sha": "e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "node_id": "C_kwDOUx-BJtoAKGUxZTM1MDRmNGE4YmZkNmIyZjg4NDI1ZDI4NGViODQ1MzkzY2ZjYjk",
          "commit": {
            "author": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T18:50:22Z"
            },
            "committer": {
              "name": "akilluminati47",
              "email": "acaradimos@gmail.com",
              "date": "2026-10-25T18:50:22Z"
            },
            "message": "Build 115 (in progress): Recent Games page, PC Card CF folded into the HDD\n\n- Recent Games: a page of its own in the side bar (the atlas's Last Played glyph), the games launched last, newest\n  first; Game Sources > Recent Games keeps 0 (off) to 99. Built by All Games' code from the same sources, so a recent\n  game on a Manual drive is a waiting row that starts its drive when picked. Starred Games shows starred games only.\n  A config from before turns the page on once, at 10.\n- PC Card CF has no switch of its own: on the SCPH-10000/15000/18000 it follows the HDD source, elsewhere it never\n  runs; the reel build's side bar no longer shows a PC Card row.\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>",
            "tree": {
              "sha": "1593f1c39cb34eb042d90a3124111dbdcedc1926",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/trees/1593f1c39cb34eb042d90a3124111dbdcedc1926"
            },
            "url": "https://api.github.com/repos/akilluminati47/RIPPS2/git/commits/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "html_url": "https://github.com/akilluminati47/RIPPS2/commit/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "comments_url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/comments",
          "author": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "akilluminati47",
            "id": 149444865,
            "node_id": "U_kgDOCOhZAQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/149444865?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/akilluminati47",
            "html_url": "https://github.com/akilluminati47",
            "followers_url": "https://api.github.com/users/akilluminati47/followers",
            "following_url": "https://api.github.com/users/akilluminati47/following{/other_user}",
            "gists_url": "https://api.github.com/users/akilluminati47/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/akilluminati47/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/akilluminati47/subscriptions",
            "organizations_url": "https://api.github.com/users/akilluminati47/orgs",
            "repos_url": "https://api.github.com/users/akilluminati47/repos",
            "events_url": "https://api.github.com/users/akilluminati47/events{/privacy}",
            "received_events_url": "https://api.github.com/users/akilluminati47/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "d4e963173ac32e81654cc60cd7ab5b1bb6568a57",
              "url": "https://api.github.com/repos/akilluminati47/RIPPS2/commits/d4e963173ac32e81654cc60cd7ab5b1bb6568a57",
              "html_url": "https://github.com/akilluminati47/RIPPS2/commit/d4e963173ac32e81654cc60cd7ab5b1bb6568a57"
            }
          ]
        }
      ],
      "files": [
        {
          "sha": "d9a99b589f4c42206c7e49ac8ff7bd9d7365eac3",
          "filename": "CHANGELOG.md",
          "status": "modified",
          "additions": 200,
          "deletions": 0,
          "changes": 200,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/CHANGELOG.md",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/CHANGELOG.md",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/CHANGELOG.md?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -1,5 +1,205 @@\n # RIPPS2 changelog\n \n+## Build 114 (beta, v1.1.4-beta when released), changes since build 113\n+\n+The user's plans of 10/22: All Games before the drives answer, hide and delete games, the launchers found and\n+remembered, a smaller ELF for memory cards, and the field reports from FifthFox (iLink, size) and Berion (APA). And of\n+10/23: launches that never run into RIPPS2's own reading and writing, every settings page on one screen, one HDD, new\n+Color Themes and the Color Sweep, and one newer Roboto with weights. Then: a theme list on the SELECT hold, games\n+compressed to ZSO and back, a launch guard that only shows when a wait is long, and RiptOPL's guarded exFAT driver.\n+\n+### RiptOPL brought up to date\n+- **RiptOPL rebuild/main 57c45b4 merged** (192 commits since 3cd7f89): i.LINK logs back in after an IOP reset and\n+  RiptOPL's i.LINK driver rides with Neutrino and POPSTARTER (#1018, FifthFox's iLink report), the in-game menu\n+  from the VBLANK handler (#984), **compatibility Mode 8, Slow Reads** (#924: Accurate Reads at the CD rate for DVD\n+  games, OPL core only, ripto's fix for games that fail on fast reads), the USB / MX4SIO / iLink discovery waits skipped, VMC and ZSO fixes, Ember's\n+  starting disc from discs.txt, and the translation format checks. RIPPS2's own sound voices, in-game menu and\n+  keyed language files kept their build 104 to 113 behaviour on top.\n+\n+### All Games, whole from the first frame\n+- **The master list.** Every game All Games lists is kept in `allgames.bin` beside the settings (mc0:/RIPPS2, or\n+  where Save Settings To points). At boot All Games shows all of them at once: a game whose drive has not answered\n+  yet is listed and waits for it, and is swapped for the real row the moment its drive's list comes in.\n+- **Drives on Manual stay instant.** Their games are listed too; Cross on one starts its drive, waits for its list\n+  and launches the game. A drive that is turned off, or started and silent for 40 seconds, drops its saved games.\n+- Written in place only when the list changed and no started drive is still to answer, never with Write\n+  Operations or Fast Cache off; a short or damaged file is ignored, never half read.\n+\n+### The launchers you keep, found once\n+- **RIPPS2 searches the started drives** for POPSTARTER, the POPS files, Ember, Ember's BIOS and Neutrino and\n+  remembers where each one is, by drive kind (`launchers.txt` beside the settings). Every launch reads them from\n+  there after the places RIPPS2 always looked, and every launch that finds one notes it.\n+- **Settings > Game Sources > Cache** shows each of the five as found (and where) or not found yet, with **Look\n+  now**. A launch that finds none offers the search, and the place found is kept for every launch after.\n+- **Ember** needs its files beside the games: when RIPPS2 knows a copy elsewhere, START copies Ember (or its BIOS)\n+  there and starts the game. **RIPPOPS** copies the POPS files from where RIPPS2 found them. Both copy with the\n+  RIPPS2 progress bar (compression's panel), where they showed one still line for seconds.\n+- **Neutrino** is looked for at Settings' path, on the game's drive, on the memory cards and on every started\n+  drive before anything is offered; RIPPS2's own 1.8.0 is offered once, only when none is found anywhere.\n+\n+### STATUS: drives first\n+- **DRIVES** opens the page: every started drive by kind, the internal HDD (APA)'s free space, each memory card's\n+  free KB, the MMCE card, and what is off. **START EVERY DRIVE** starts the rest.\n+- **LAUNCHERS**: one line each for Neutrino, POPSTARTER, the POPS files, Ember and Ember's BIOS, and **LOOK FOR\n+  THEM** (it can start every drive first). Then the homebrew and BIBLE as before. The page is now titled Drives,\n+  launchers and homebrew.\n+\n+### Hide a game\n+- **Hide Game** in a game's Triangle options (on All Games and Starred Games rows too): the game leaves every list,\n+  All Games, Starred Games, Recents and Random Game included. The hidden list is kept on the game's own drive\n+  (`RIPPS2.HID` beside RIPPS2.LST), so it travels with the drive.\n+- **Settings > Game Sources > Hidden Games** lists every started drive's hidden games: Show Again, or Delete.\n+\n+### Delete a game completely\n+- **Delete** now takes everything of the game's: every part of the image (ul.cfg rebuilt for a UL game), its CFG,\n+  its art and its cheats; on the internal HDD (APA) its partition, then its CFG, art and cheats in +OPL. Delete\n+  works on APA games, All Games and Starred Games rows (found on their drive at that moment), and hidden games.\n+- **Saves stay unless you say.** Its VMC, POPSTARTER saves and RIPSHOT pictures are kept unless you answer Yes,\n+  and a CFG or art another copy of the game still uses is kept and named. A last screen only START gets past.\n+- **Berion's APA check.** Before any partition goes (the HDD MANAGER's remove too), RIPPS2 walks the whole table:\n+  every header's magic and checksum, each link forward and back, every partition inside this disk, no size or\n+  alignment this disk cannot have, the chain closing at __mbr. A table written from an image of another disk size\n+  is refused with the reason, and nothing is touched. The walk runs again after, and `REMOVED.TXT` in mc0:/RIPPS2\n+  keeps what went, from where, the space freed and the table's state.\n+\n+### A smaller RIPPS2.elf\n+- **One ELF, as before**, carrying BIBLE and Neutrino; POPSTARTER and Ember are found where you keep them, as\n+  since build 113. It is about 26 KB smaller than v1.1.3 with everything above in it: FreeType is started with only\n+  the modules RIPPS2 reads (TrueType, sfnt, the autohinter, the smooth renderer, CFF), so Type 1, CID, PFR, BDF,\n+  PCF, WinFNT, SDF and SVG leave the link, and the built-in fonts lose glyph names, extra name records and\n+  substitution tables (render-checked identical at 14 to 32 px).\n+\n+### Safe launches, and the launch guard\n+- **No write runs into a launch.** FifthFox lost two card images and a USB stick, likely to a write cut by a launch's\n+  reset. A launch now stops RIPPS2's background writes the moment the game is picked (RIPPS2.LST, allgames.bin, the\n+  hidden lists, art, RIPSHOT, the games list cache), waits for one already started, and stops a drive's list read at its\n+  next entry.\n+- **Big writes start in step with the drive.** In the lab, a big write that started at an odd place in a file and ran\n+  across a 32 KB cluster now and then landed a cluster too early on exFAT, and the file's chain broke after it (found on\n+  a compressed game, studied on the PC). RIPPS2.LST, the list cache every started drive keeps since build 110, had that\n+  shape once a library's list passed one cluster: it is now one write from the file's start. Compression, RIPDISC's raw\n+  and audio rips, RIPSHOT's pictures and BIOS Music's WAV now write whole 256 KB pieces at multiples of it.\n+- **Every drive known for what it is.** RiptOPL's exFAT/FAT driver, as RIPPS2 builds it, now says a drive's format,\n+  cluster size and free space. STATUS shows them for every USB, MX4SIO, iLink and exFAT HDD drive. RIPDISC checks the\n+  room before a rip, and a game over 4 GB goes in 1 GB parts only on a drive that really is FAT32: an exFAT stick takes\n+  the ISO whole (before, the PS2 could not tell, so every drive but the internal HDD got parts). Memory Files checks the\n+  room and FAT32's 4 GB a file before a copy, and copies files over 2 GB right (a 32-bit count failed its own check).\n+  Compression, rips and copies write in whole clusters of the drive.\n+- **MMCE: mmceman brought up to ps2-mmce's main** (de28936f): its hook no longer blocks the second memory card port,\n+  which only MX4SIO needed. Compressing on an MMCE card renames the finished image into place, which sd2psXtd does from\n+  firmware 1.4.0 on. **Hotfix (10/24):** on an MMCE card RIPPS2 first writes and renames a tiny file beside the game, so\n+  older firmware is told in a second to update ([sd2psxtd.github.io/download](https://sd2psxtd.github.io/download)), with nothing changed, instead of\n+  after the whole work.\n+- **One background write at a time.** RIPPS2's own background writes now wait for each other (about 3 seconds at most;\n+  one that cannot wait is simply written next time), and none starts while Memory Files pastes, unpacks or deletes,\n+  while RIPDISC rips, or while a game is compressed.\n+- **RiptOPL's guarded exFAT driver.** RIPPS2 shipped the SDK's stock exFAT/FAT driver; it now ships the one RiptOPL's\n+  own releases build. A game on a damaged FAT chain is refused at launch (#986) instead of handing the game's\n+  driver wrong sectors to write its VMC to, and the merged RiptOPL's new VMC maker can reserve a card in one piece on\n+  USB and the other BDM drives (with the stock driver it refused).\n+- **The launch guard.** A game picked while RIPPS2 is still reading a drive or saving a list waits its turn: a glass\n+  panel fades in as RIPDISC's does, names what RIPPS2 is finishing, and RIPDISC's notches fill toward when it will be\n+  done (each drive's last list read is timed). The game starts by itself when the work is done. **START NOW** stops\n+  the work safely and starts at once; **Back** returns to the page the launch began on, unchanged. Music plays on.\n+- **Only when it is needed.** A wait of a second or two shows the loading icon over the page and nothing else; the\n+  panel fades in when the work will take longer, or has run past what it was expected to take.\n+\n+### Settings, every page on one screen\n+- **Game Sources**: the BDM drives (USB, MX4SIO, PC Card, DESR, iLink) on a **BDM Devices** page of their own; the\n+  greyed Protocol row gone (the Network page has it). **Interface**: Video Mode, Widescreen, H-POS, V-POS and Overscan\n+  on a **Screen** page; VCD Games List Settings on PS Emulation only. **Flow and Grid** is **Layout**, with the\n+  category rows; **Colors and More** is **Colors and Text**, with **Text Weight**. Network keeps its rows and scrolls.\n+- The hub's names are shorter (PS Emulation, Controllers, Audio); General & System has its title; the POPSTARTER\n+  network page has one name and a notice that fits; the VCD list's label lines up; the Neutrino arguments page has\n+  its row's name; MMCE says On and Off; STATUS's BIBLE line fits; Artwork Settings gets **Missing Covers** (the Network\n+  page keeps its Cover Art download too); a game's options end with one **Achievements** row that opens the three.\n+\n+### One HDD\n+- A PS2 has one internal HDD, APA or exFAT. **HDD Start Mode** (Off, Manual, Auto) is the drive's, whatever its format,\n+  and an exFAT drive no longer has an empty APA page beside it: Storage shows one HDD Games. An APA and exFAT hybrid\n+  keeps both pages, named by format. **BDM Devices Start Mode** Off turns off the drives on the BDM Devices page\n+  only: an exFAT internal HDD on Auto stays listed.\n+- **Network Connectivity** Off on Game Sources turns the network page off again (since the Protocol row moved, it\n+  had stayed in Storage until a restart).\n+\n+### Colors\n+- **Color Theme** gains **Tangerine**, **Flamingo**, **Turquoise**, **Moonlight** (a ghostly white) and **Custom**:\n+  the new **Theme Color** picker shows each theme's color and is yours to pick on Custom, as the text colors are.\n+- **Color Sweep**: five colors the theme fades through, each for a few seconds, never fast enough to flash.\n+  **Rainbow**, then a sweep made from each Color Theme (an all-pink Flamingo, a ghostly Moonlight). **Sweep Colors**\n+  opens its five pickers and its speed (Slow, Normal, Lively), seen live; change one of RIPPS2's and it is kept as\n+  your own, under the name you give it (up to four; left as it is, \"Rainbow\" becomes \"Rainbow 2\").\n+- **Color Sweep, kinder**: a speed picked for one of RIPPS2's sweeps is kept as that sweep's own (no copy under a new\n+  name; a copy is made only when its colours change), and your own sweeps can be deleted (Sweep Colors > Delete This\n+  Sweep). While a sweep is on, Color Theme greys out and says Sweep, and the Theme Color box beside it shows plain\n+  grey; your Color Theme is kept and comes back when the sweep is Off.\n+- **The notch panels line up**: RIPDISC, the launch guard and compression share one frame, the same width and place,\n+  with the same wheel.\n+\n+### The theme list\n+- **Hold SELECT** on a game list opens your themes in a glass panel, eight to a page, in the Settings font: pick one and\n+  the panel fades out and the theme loads (the one in use just refreshes the page). The built-in themes (RIPPS2, RIPFLOW,\n+  RIPGRID) come first, together, then the themes on your drives, in every theme list. **Settings > Interface > Hold\n+  SELECT**: List (the default), Cycle (the next theme, as before) or Off.\n+\n+### Hide Game and Compression\n+- A game's Triangle options say **Hide Game and Compression** for an ISO or ZSO (USB, MX4SIO, iLink, the exFAT HDD,\n+  MMCE), named either way (Name.iso, or OPL Manager's SCUS_XXX.XX.Name.iso): **Hide Game** as before, **Compress to\n+  ZSO** (the game stays in your lists), **Hide and Compress** (hidden once the ZSO is made and checked; a cancel or a\n+  failure hides nothing), or for a ZSO **Uncompress to ISO**.\n+- **Room first.** Before anything starts, the drive is asked for its free space: the new image needs about the old one's\n+  size beside it while it works (the original stays to the end). Short of room, it says how much is needed and how much\n+  is free, and nothing changes. (RiptOPL's exFAT/FAT driver, as RIPPS2 builds it, now answers that question; MMCE cannot\n+  say, and there the work stops cleanly if the drive fills.)\n+- **Experimental, and asked twice**: a panel with RIPDISC's notches shows the game, its size, its drive and about how\n+  long it takes, and fills while you hold SELECT; then a second yes. A RIPPS2 progress bar runs the whole way\n+  (COMPRESSING 1 OF 2, 2 OF 2, CHECKING EVERY PIECE) with CANCEL, which removes what was written. In the lab a 1.2 GB\n+  game on the internal exFAT HDD took about 10 minutes and came out 82% of its size; the first estimate is set from\n+  that, and the bar's own estimate follows what it really gets.\n+- **Nothing is lost.** The ISO stays until the ZSO has been read back and every piece checked against it, and only then\n+  does RIPPS2 ask before removing it (Circle reads **Back (keeps both)** there: the ISO and the ZSO both stay). The lab showed why the check is there: written in pieces of any length, the\n+  driver now and then put a 32 KB cluster over the one before it, and the check caught it every time. The new image now\n+  goes to the drive in whole aligned 256 KB pieces, as RIPDISC's rips and Memory Files' copies always did. The ZSO starts with the OPL core (Neutrino does not read ZSO); its VMC, settings,\n+  art and cheats are the same game's. Uncompress makes the ISO again the same way.\n+- **Hidden Games too**: Settings > Game Sources > Hidden Games offers Compress to ZSO (or Uncompress to ISO) beside\n+  Show Again and Delete, and marks a compressed one PS2 ZSO. A hidden game stays hidden once compressed.\n+\n+### Fonts\n+- **One Roboto Condensed 3.015** (SIL OFL), variable from 400 to 700, replaces the 2011 Bold and 2014 Regular for\n+  RIPFLOW and Settings; the Settings rows at 700 and 18 px.\n+- **A weight for any font**: themes can set `default_font_weight` and `font1_weight` ... `font15_weight` (100 to 900);\n+  `builtin:roboto` takes it as designed, any other face is drawn heavier in code.\n+- **Text Weight** (Colors and Text): Regular, Medium or Bold for the Settings tooltips, STATUS and About.\n+- **The language pack** carries the same Roboto, cut to each language's letters, for every Latin, Greek, Cyrillic and\n+  Vietnamese one, so Settings in Russian or Greek draw one typeface. RIPPS2-themes edition 114: Adapt and Adapt Rx\n+  use the built-in fonts (b.ttf, c.ttf and d.ttf gone), and the key glossary lists every built-in font.\n+\n+### Achievements\n+- **BADGES starts Off**, and its note says what it gives: the games with achievements marked in your lists, with how\n+  many you have unlocked. It needs TELEMETRY: while TELEMETRY is off the row is greyed out, and it comes back with it.\n+\n+### Credits\n+- **Neutrino beside RiptOPL** on the About page: rickgaiser's Neutrino named in the opening lines as the second\n+  game core, with its link, in a BUILT ON RIPTOPL, WITH NEUTRINO section.\n+- **TESTERS** closes with \"Special thanks to all the YouTube love as well: glad to help however you reach me.\"\n+\n+### Hotfixes in v1.1.4-beta (10/24 and 10/25)\n+- **No \"400: hard drive not detected\" box at boot on a console without an HDD** (a slim, or a fat with no network\n+  adapter). At boot RIPPS2 looks for an internal exFAT HDD whenever HDD Start Mode is not Off, and the boot folder\n+  search's last step loads the HDD driver too; with no HDD interface their failure came up as that box on every boot,\n+  HDD Start Mode Manual included (builds 113 and 114, found in the lab with the HDD taken out). Both checks are quiet\n+  now; an HDD page you start, or HDD Start Mode Auto, still says the drive is missing.\n+- **RIPGRID without Adapt's side bar**: the dark strip and its blue edge down the left are gone. RIPGRID has no\n+  sidebar; Circle picks the source there.\n+- **Compressing on an MMCE card asks the card first** whether it can rename (sd2psXtd 1.4.0 or newer), so older\n+  firmware is told in a second, not after the whole work.\n+\n+### Underneath\n+- The new words in all 33 languages.\n+- Host test `test_ripps2_build114.py`: every part statically, the APA check against six synthetic disks, the\n+  master list's round trip and its refusal of a damaged or short file, the one-at-a-time write gate, the ZSO written in\n+  one stream, and elf/build.sh installing RiptOPL's guarded driver.\n+\n ## Build 113 (beta, v1.1.3-beta), changes since build 112\n \n The user's plans of 10/21 and 10/22: the Adapt sidebar, the legacy theme key, cutting the launchers RIPPS2 carried"
        },
        {
          "sha": "8706647b08aca2eebc6a7fc5ec65fd99f7106324",
          "filename": "README.md",
          "status": "modified",
          "additions": 7,
          "deletions": 6,
          "changes": 13,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/README.md",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/README.md",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/README.md?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -1,16 +1,16 @@\n <p align=\"center\"><sub><b>RIPPS2</b> &nbsp;&middot;&nbsp; <a href=\"https://github.com/akilluminati47/RIPPS2/releases/latest/download/RIPPS2.elf\">Download</a> &nbsp;&middot;&nbsp; <a href=\"https://github.com/akilluminati47/RIPPS2-themes\">Themes</a> &nbsp;&middot;&nbsp; <a href=\"https://github.com/akilluminati47/RIPPCSX2\">RIPPCSX2</a> &nbsp;&middot;&nbsp; <a href=\"https://github.com/akilluminati47/RIPPS2-fonts\">Fonts</a> &nbsp;&middot;&nbsp; <a href=\"https://akilluminati47.github.io/RIPPS2/\">Website</a> &nbsp;&middot;&nbsp; <a href=\"https://www.psx-place.com/threads/ripps2-a-ps2-front-end-in-one-elf-launch-disc-storage-memory-files.51316/\">psx-place</a> &nbsp;&middot;&nbsp; <a href=\"https://discord.gg/ggkCDWqhE\">Discord</a></sub></p> <!-- ripps2-nav -->\n \n-<p align=\"center\"><img src=\"media/ripps2-test-me.png\" alt=\"RIPPS2: Rip it, build 113, the beta, the dead elf in its blue hat over the towers\"></p>\n+<p align=\"center\"><img src=\"media/ripps2-test-me.png\" alt=\"RIPPS2: Rip it, build 114, the beta, the dead elf in its blue hat over the towers\"></p>\n \n # RIPPS2\n \n-> ### Rip it: RIPPS2 build 113, v1.1.3 beta\n+> ### Rip it: RIPPS2 build 114, v1.1.4 beta\n > **RIP PS2, the ELF killer.** ELF-sufficient, never ELF-destructive: your other ELFs stay safe on the [shelf](https://akilluminati47.github.io/RIPPS2/) (nothing is deleted unless you ask), they just get a well-earned rest.\n-> 1. **[Download RIPPS2.elf](https://github.com/akilluminati47/RIPPS2/releases/latest/download/RIPPS2.elf)** (always the newest release) (one file, 2.7 MB), and the **[theme pack](https://github.com/akilluminati47/RIPPS2-themes/releases/latest)** if you like (Adapt, Adapt Rx, edition 113 with the sidebar; RIPGRID and RIPFLOW are built in), and **[RIPPS2-languages.zip](https://github.com/akilluminati47/RIPPS2/releases/latest/download/RIPPS2-languages.zip)** for RIPPS2 in your language (33 of them).\n+> 1. **[Download RIPPS2.elf](https://github.com/akilluminati47/RIPPS2/releases/latest/download/RIPPS2.elf)** (always the newest release) (one file, 2.7 MB), and the **[theme pack](https://github.com/akilluminati47/RIPPS2-themes/releases/latest)** if you like (Adapt, Adapt Rx, edition 114 on the built-in fonts; RIPGRID and RIPFLOW are built in), and **[RIPPS2-languages.zip](https://github.com/akilluminati47/RIPPS2/releases/latest/download/RIPPS2-languages.zip)** for RIPPS2 in your language (33 of them).\n > 2. Copy it to a USB stick or memory card, and launch it from FreeMcBoot, PS2BBL, wLaunchELF or whatever you already use to start homebrew. In PCSX2: **System > Start File** and pick `RIPPS2.elf`.\n > 3. Tell us how it went: [the psx-place thread](https://www.psx-place.com/threads/ripps2-a-ps2-front-end-in-one-elf-launch-disc-storage-memory-files.51316/), [an issue](https://github.com/akilluminati47/RIPPS2/issues), or **#RIPPS2** on the developers' Discord, [discord.gg/ggkCDWqhE](https://discord.gg/ggkCDWqhE) (invite good for October 2026).\n >\n-> [What's new in build 113](https://github.com/akilluminati47/RIPPS2/releases/tag/v1.1.3-beta) | [Changelog](CHANGELOG.md) | **[Watch the v1.1.0 reel](https://youtu.be/Q8ZLd6ArV8s)** (3:57, chapters in its description) and **[the v1.1.2 sounds walkthrough](https://akilluminati47.github.io/RIPPS2/media/b112/ripps2-v112-sounds.mp4)** (2:11, one take: the boot sound, the menu music, then every sound in turn); earlier: [the beta reel](https://youtu.be/LPjdK_E1jjM), [the build 99 Short](https://www.youtube.com/shorts/Ov5iA-tMcI8), [build 88's](https://www.youtube.com/shorts/Sqprz22tdtg), [the build 83 reel](https://youtu.be/A27N9s6tumk) and [its Short](https://youtube.com/shorts/4QBe5UO2KxM)) | [Every release](https://github.com/akilluminati47/RIPPS2/releases) | [Talk about it on psx-place](https://www.psx-place.com/threads/ripps2-a-ps2-front-end-in-one-elf-launch-disc-storage-memory-files.51316/)<br>\n+> [What's new in build 114](https://github.com/akilluminati47/RIPPS2/releases/tag/v1.1.4-beta) | [Changelog](CHANGELOG.md) | **[Watch the v1.1.4 reel](https://youtu.be/UwuKWfRtUEs)** (7:55, chapters in its description) and **[the v1.1.4 wheel Short](https://youtube.com/shorts/iQqR9lsar9Q)** ([or on GitHub](https://akilluminati47.github.io/RIPPS2/media/b114/ripps2-v114-wheels-short.mp4); 1:10, one wheel, four jobs: RIPDISC, the launch guard, compression, the theme list); earlier: [the v1.1.0 reel](https://youtu.be/Q8ZLd6ArV8s), [the v1.1.2 sounds walkthrough](https://akilluminati47.github.io/RIPPS2/media/b112/ripps2-v112-sounds.mp4), [the beta reel](https://youtu.be/LPjdK_E1jjM), [the build 99 Short](https://www.youtube.com/shorts/Ov5iA-tMcI8), [build 88's](https://www.youtube.com/shorts/Sqprz22tdtg), [the build 83 reel](https://youtu.be/A27N9s6tumk) and [its Short](https://youtube.com/shorts/4QBe5UO2KxM)) | [Every release](https://github.com/akilluminati47/RIPPS2/releases) | [Talk about it on psx-place](https://www.psx-place.com/threads/ripps2-a-ps2-front-end-in-one-elf-launch-disc-storage-memory-files.51316/)<br>\n > [Theme RIPPS2](https://github.com/akilluminati47/RIPPS2-themes) (no rebuild, agent friendly) | [Fill your AUDIO folder](#the-sound-ripps2-was-made-with) with RIPPS2 Audio Studio | [See a theme in true 720p and 1080i: RIPPCSX2](https://github.com/akilluminati47/RIPPCSX2/releases/latest) (PCSX2 with HDTV output)\n \n RIPPS2 is a PS2 front end in a single `.elf`: a teardown and rebuild of [RiptOPL](https://github.com/NathanNeurotic/Open-PS2-Loader) (the Open PS2 Loader fork), with rickgaiser's [Neutrino](https://github.com/rickgaiser/neutrino) inside as its second game core, and its own look, launchers, file browser and save tools. Glass towers stand behind your games and orbit formations behind your files, all drawn live by the PS2.\n@@ -25,7 +25,7 @@ It is **beta** from build 100: a hundred builds of alpha, tested on consoles and\n \n - **Themes you can make your own.** Frosted glass panels over each game's art, a cover **Grid** element, right-stick tilt that moves a whole page as one sheet, and the first pack, **[RIPPS2-themes](https://github.com/akilluminati47/RIPPS2-themes)**: Adapt and Adapt Rx, each config opening with a glossary of every key, with a checker and a key reference for anyone (or any agent) making more. RIPGRID and RIPFLOW are built in. Hold **SELECT** for 4.2 seconds to step through your themes. On RIPGRID and RIPFLOW, hold the right stick to one side for 0.42 s and the chosen case turns over to its back cover, and the grid turns its pages with a slide.\n - **RIPFLOW**, the built-in cover carousel (and RIPGRID, built in too), a launch disc that pops in or spins as a game starts, **Hints and Glyphs** (your own button hints, badges and four glyph sets), and **Colors and More**: six color themes (RIPPS2, Ember, Blood, Ectoplasm, Amethyst, Bone), D-pad glyphs, fading hints and category bar, the source name as a toast in the middle of the page; themes can pick all of it for themselves, and your own settings always win.\n-- **What it needs, on board; what you have, found.** [Neutrino](https://github.com/rickgaiser/neutrino) 1.8.0 and BIBLE ride inside the one `.elf` and install themselves where you pick; your own POPSTARTER and Ember are found beside your games and launched. **STATUS** on the START menu shows where each one is and installs or sets it up.\n+- **What it needs, on board; what you have, found.** [Neutrino](https://github.com/rickgaiser/neutrino) (its newest build) and BIBLE ride inside the one `.elf` and install themselves where you pick; your own POPSTARTER and Ember are found beside your games and launched. **STATUS** on the START menu shows where each one is and installs or sets it up.\n - **Your settings where you want them** (builds 109 and 110): Settings > General > Save Settings To keeps them in RIPPS2's save on a memory card, in a RIPPS2 folder on the internal HDD (exFAT, or the APA HDD's OPL partition), a USB drive, MX4SIO, iLink or MMCE (read first at boot), or in a folder you type.\n - **BIBLE**: PS2BBL 1.2.0 set up for RIPPS2, on a memory card or the internal HDD, so the console boots straight into it. Boot Loader > Manage shows what each card starts.\n - **An in-game menu**, on from build 108 (Settings > General > In-Game Menu): L1+L2+R1+R2+SELECT+START pauses a game, silent, over its own picture, under Resume, Exit Game, **Screenshot** (RIPSHOT, [below](#screenshots-in-game-ripshot)), Achievements and Power Off (a second press turns the PS2 off right there); RESUME gives back the exact frame, and Achievements lists what you have left to hunt and how to earn it.\n@@ -146,12 +146,13 @@ Add `?t=12` to skip the pillars intro, or `orbs.html?f=3` to open a specific for\n RIPPS2 is designed and directed by akilluminati47 and built with Claude (Anthropic). Testers: ripto, nuno6573, zackcage6, eliminator1403, FifthFox, Mr.sus.60, lukeatmynose. Special thanks to all the YouTube love as well: glad to help however you reach me.\n \n - [RiptOPL](https://github.com/NathanNeurotic/Open-PS2-Loader) by NathanNeurotic and the [Open PS2 Loader](https://github.com/ps2homebrew/Open-PS2-Loader) team (AFL-3.0), which RIPPS2 is built on. Their full credits are on RIPPS2's About page.\n-- [Neutrino](https://github.com/rickgaiser/neutrino) by [rickgaiser](https://github.com/rickgaiser) (AFL-3.0), the small, fast and modular device emulator that is RIPPS2's second game core: RIPPS2.elf carries its 1.8.0 release unmodified and installs it where you pick ([`elf/neutrino`](elf/neutrino/README.md)). Choose it as the Default Loader Core in Settings > Game Launching.\n+- [Neutrino](https://github.com/rickgaiser/neutrino) by [rickgaiser](https://github.com/rickgaiser) (AFL-3.0), the small, fast and modular device emulator that is RIPPS2's second game core: RIPPS2.elf carries its newest build (v1.8.0-61-g7be8de2, 31 August 2026) unmodified and installs it where you pick, and your own Neutrino of any version comes first ([`elf/neutrino`](elf/neutrino/README.md)). Choose it as the Default Loader Core in Settings > Game Launching.\n - [xeRAbora](https://github.com/hacan359/xerabora) by [hacan359](https://github.com/hacan359): the RetroAchievements PC client and the RetroAchievements code on the console side that RIPPS2 inherits through RiptOPL (MIT / AFL-3.0). **LEFT TO HUNT** reads the interface page's `GET /state` on port 18280, which is not part of the documented console protocol and can change between alphas, so RIPPS2 is pinned to **[v0.1.0-alpha.18](https://github.com/hacan359/xerabora/releases/tag/v0.1.0-alpha.18)**, the release its author recommends (alpha.10 is unstable, and the `checked_only` key LEFT TO HUNT reads is in `/state` only from alpha.11 on); checked against that release's `/state` code, other versions are untested.\n - [ESR](https://gitlab.com/ffgriever/esr/) by ffgriever: Launch Disc starts your own copy for ESR discs.\n - [Ember](https://github.com/Gageformer/Ember/releases) by Gageformer, the PS1 emulator for the PS2: RIPPS2 launches your own `ember.elf` from the `EMBER` folder of the drive your games are on (builds 71 to 112 carried its Beta-2 build; [`elf/ember`](elf/ember/README.md)). Bring your own PS1 BIOS.\n - [POPSTARTER](https://www.psx-place.com/resources/popstarter.683/) by krHACKen starts PS1 games from your own copy, which RIPPS2 finds on your drives and memory cards ([`elf/popstarter`](elf/popstarter/README.md)).\n - [PS2BBL](https://github.com/israpps/PlayStation2-Basic-BootLoader) by El Isra (israpps) and [PS2BBL Extended](https://github.com/saildot4k/PlayStation2-Basic-BootLoader-Extended) by saildot4k: BOOT LOCK edits their config so the console starts RIPPS2. From build 69, RIPPS2.elf carries El Isra's signed PS2BBL 1.2.0 unchanged, as data, for **Boot Loader > Manage** (BIBLE) to install on a memory card. PS2BBL is GPL-3.0: its license and the exact source it was built from ([commit a063bb3](https://github.com/israpps/PlayStation2-Basic-BootLoader/tree/a063bb3)) are in [`elf/bible`](elf/bible/README.md). The card binding uses ps2sdk's open SECRMAN (AFL); no keys are involved.\n+- **MMCE cards**: [sd2psXtd](https://sd2psxtd.github.io/) firmware on an SD2PSX, such as the [SD2PSXTD](https://github.com/LinkRetro/SD2PSXTD-Hardware) card, read through [mmceman](https://github.com/ps2-mmce/mmceman) (ps2-mmce): games, VMCs and settings on the card's SD. **Keep the firmware up to date** ([sd2psxtd.github.io/download](https://sd2psxtd.github.io/download)). Compressing a game on the card needs sd2psXtd 1.4.0 or newer (it renames the finished image into place); RIPPS2 asks the card first, so older firmware is told before anything starts and cannot compress there. The card cannot report its free space, so even on 1.4.0 there is no room check before compressing: leave space for the game and its ZSO side by side (if the card fills, the work stops and removes what it wrote).\n - [wLaunchELF](https://github.com/ps2homebrew/wLaunchELF) by the ps2homebrew team, and wLaunchELF R3Z by NathanNeurotic: the behavior references for the Memory Files browser and its settings grid.\n - [Apollo Save Tool](https://github.com/bucanero/apollo-ps2) by bucanero: the reference for the save tools (PSU, card images). RIPPS2's own code, written from the formats.\n - [ps2sdk](https://github.com/ps2dev/ps2sdk) by the ps2dev team: the memory card file system (mcman) and the USB keyboard driver."
        },
        {
          "sha": "023af61322107a536ca300237b80c5779a4dd970",
          "filename": "docs/plans/b114-audit-fonts-guard.md",
          "status": "added",
          "additions": 41,
          "deletions": 0,
          "changes": 41,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/docs%2Fplans%2Fb114-audit-fonts-guard.md",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/docs%2Fplans%2Fb114-audit-fonts-guard.md",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/docs%2Fplans%2Fb114-audit-fonts-guard.md?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -0,0 +1,41 @@\n+# Build 114: the settings audit answers, the fonts and the new launch guard (plan, 10/23, nothing coded yet)\n+\n+Audit: https://claude.ai/artifact/8QRQ1DU2cu2eohZ94BsY1o. Guard CI 39908371655 passed (build + host tests).\n+\n+## The user's answers\n+Yes: 1 BDM Devices sub-page (6 device rows, no indent, Modify row under BDM Devices Start Mode), 2 drop Game Sources'\n+greyed Protocol row (guiSourcesNetRows*), 3 diaConfig header _STR_GENERAL_SYSTEM, 7 POPS notice \"No POPSTARTER SMB setup\n+found yet. Fill in the rows below.\" (literal in both dialogs + gui.c:2384 _t), 8 title \"POPSTARTER Network\", rows\n+\"POPSTARTER UDPFS Network\" / \"POPSTARTER SMB Network\", 9 Screen sub-page (diaDisplayConfig without Show GameID Barcode,\n+which moves to diaUIConfig; Interface composes diaUIConfig only, updater NULL; guiShowScreenConfig keeps live offsets,\n+restores on Back, asks about a new video mode), 10 drop VCD row from Interface, 11 five category rows (CATLAYOUT, CATBAR,\n+CATSWAP, TRANSITION, SRCNAME) to diaCoverflowConfig renamed \"Layout\" (live read + revert there), 12 \"Colors and Text\"\n+with Text Weight (Regular/Medium/Bold), 13 \"HDD POPS partitions\" + hint without \"--\", 14 Neutrino page header literal\n+\"Neutrino Advanced Arguments\", note \"RIPPS2 adds the drive, ISO, VMCs and video mode itself.\", 16 hub _t(\"PS Emulation\"),\n+_t(\"Controllers\"), _t(\"Audio\") (hub only), 17 STATUS \"What each card starts; install or repair BIBLE.\", 18 MMCE alarms\n+enum _l(_STR_OFF)/_l(_STR_ON), 19 one game-options row _STR_RIPPS2_ACHIEVEMENTS -> ripps2PickList of the 3 RA rows\n+(gameMenu and VCD appMenu; GAME_RA_MENU mapped before the dispatch).\n+No: 4, 5, 15. 6 = both: Network keeps Cover Art; Artwork Settings gets \"Missing Covers\" + Download (ripps2ArtDownload).\n+Network still scrolls after this: measure after the 18 px font, then offer another fix.\n+New ids after RIPPS2_HIDDEN_BUTTON (300+ range). Changed OPL labels: use literals/_t (translated by English) or new\n+HINT_RIPPS2_* labels (labels: fill), never reword an old label's meaning.\n+\n+## Launch guard, the user's redesign (10/23)\n+A RIPDISC-style glass panel (ripps2DrawHintBoxA fade 0.6 s, fonts[4] heading, fonts[5] line, rdRing's 12 notches exported\n+from ripps2disc.c). The launch waits at the END of the queue and starts by itself when reads and writes finish: art loads\n+removed, ioBlockOpsTimed(1, 0) so the queue has an end, unblocked before returning (deinit's drain unchanged). Notches fill\n+by estimate: ioman records each list read's last duration per mode; unknown = a chase. Cross = start now (ioLaunchBegin,\n+ioCancelScans, remove list reads, \"STOPPING\" on the same panel); Circle = back out (unblock). Music streams on its own\n+threads (bgmThread/bgmIoThread), not the IO worker: never guarded, keeps playing. Update test_ripps2_build114.py.\n+\n+## Fonts (A-E, all into 114)\n+A: misc/ripps2roboto.ttf = Roboto-VariableFont_wdth,wght instanced wdth 75, wght 400-700, Latin-1 cut, strip_fonts;\n+builtin:ripflow -> 400, builtin:ripflowbold -> 700, builtin:roboto; diaFont 18 px; OFL replaces ROBOTO-LICENSE.\n+B: font_t weight/embolden; fntSetWeight (wght design coords; static faces FT_Outline_EmboldenXY scaled by ppem, about\n+1 px at 18 ppem per +300, advance grows); alt faces take the weight when variable; theme keys default_font_weight,\n+fontN_weight in thmLoadFonts. C: gRipps2TextWeight (config ripps2_text_weight): diaTipFont 400/500/600, STATUS/About page\n+faces own+0/100/200 (load page faces on the RIPPS2 theme too when > 0). D: RIPPS2-themes Adapt/Adapt Rx b,c ->\n+builtin:roboto + weights, d -> builtin:ripps2, comments list every built-in. E: ripps2lang.py pick_font tries the variable\n+Roboto (elf/lang/fonts) first, cut per language, OFL text in the pack.\n+\n+Then: translations (32 languages), regen patch, CI, lab (pages fit, guard), retakes, Queen reel."
        },
        {
          "sha": "02358ffe40d8c57b2813c0ab41a7dfb6fe4a7956",
          "filename": "docs/plans/build-115.md",
          "status": "added",
          "additions": 123,
          "deletions": 0,
          "changes": 123,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/docs%2Fplans%2Fbuild-115.md",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/docs%2Fplans%2Fbuild-115.md",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/docs%2Fplans%2Fbuild-115.md?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -0,0 +1,123 @@\n+# Build 115: the first real-hardware run on the SD2PSXTD (the user's report, 10/25/2026)\n+\n+Hardware: the user's PS2 with LinkRetro's SD2PSXTD v1.2 (sd2psx board) on sd2psXtd firmware 1.4.0, a 32 GB microSD\n+formatted MBR + exFAT (32 KB clusters) with the lean BOOT card (channel 1: sd2psXtd's PS2BBL Extended card, OPL and\n+uLaunchELF removed, RIPPS2/RIPPS2.ELF = the v1.1.4 hotfix ELF, PS2BBL AUTO -> RIPPS2), 9 PS2 games in DVD/, their art\n+and CFGs, EMBER (Digimon World) and POPS/POPSTARTER.ELF. RIPPS2 boots from the card; Burnout 3 from the internal HDD\n+launches fine.\n+\n+Status: plan, nothing built yet. Talk it over with the user first (\"we can go over it tomorrow\").\n+\n+## A. MMCE (first)\n+\n+### A1. Input blocked and erratic while MMCE works\n+- Booting with the last used device = MMCE, the pad is dead or erratic until the card's list is in, even before the\n+  cache shows; the right stick tilt misbehaves too (the user: \"it even blocks the right stick\", \"the controller is\n+  bugging terribly when mmce is loading stuff\"). Browsing and menus should work while it loads, as on the HDD.\n+- Likely cause: MMCE and the pads share SIO2. Long MMCE transfers (mmceman) starve padman's reads, so presses are\n+  dropped and the stick reads stale or zero. To check: where the MMCE list read runs (mmcesupport.c on the IO thread?),\n+  how big each request is, and whether anything yields between them. Fix direction: show the cached list at once, read\n+  the card in small pieces with a DelayThread between them, never during a pad read window, and pause MMCE work while\n+  the user is pressing buttons (as MX4SIO got in build 110).\n+- The \"right stick tilt bug on everything from covers to the PNG viewer\" is probably this; confirm with the user what\n+  the tilt does (stuck, drifting, jumping) and whether it also happens with no MMCE inserted.\n+\n+### A2. Games from MMCE: very slow, then a hang\n+- Burnout 3 from MMCE: the PS2 logo's sound came over 5 minutes late, then it hung, then a white screen.\n+- The game files are good: all 8 retail ISOs on the card match Redump byte for byte (size, CRC32, SHA-1, checked\n+  10/25 against libretro's Redump PS2 DAT: Auto Modellista, Aqua Teen, Burnout 3 (USA, Canada), Katamari Damacy,\n+  We Love Katamari, Ratchet & Clank, Going Commando v2.00, Up Your Arsenal; GTA VC Debug is a prototype, not in Redump).\n+  Card copies were checked by size after robocopy; hash them on the card next time it is in the PC.\n+- \"The sizes are off from the ones on my HDD\": the HDD copies came from another source; the card's are the Redump ones.\n+- To find: which core launched it (OPL core with mmcedrv, or Neutrino), the game-ID card switch at launch, and the\n+  read speed the console got. Next session: a debug ELF with EE SIO logging is not possible on hardware, so add a short\n+  on-screen or file log of the launch (core, mode, the MMCE slot, the switch result) and test the same game with each\n+  core. Also check sd2psXtd's own notes on MMCE game loading (settings for speed, known slow games).\n+\n+### A2b. What the SD showed (10/25, card back in the PC)\n+- The microSD is not slow: through the PC's USB reader, random 2 KB reads 0.59 ms median (0.9 ms worst), sequential\n+  31 MB/s, an 8 MB card written in 512-byte write-through pieces in 16.7 s (490 KB/s, worst write 24 ms). All 9\n+  ISOs and the boot card are one extent each (no fragmentation).\n+- GameID cards: MemoryCards/PS2/SLUS-21050 (Burnout 3) and SLUS-20642 (Auto Modellista) are 4.2 and 4.6 MB, not 8, and\n+  Burnout 3 has two .crp files of ~88 KB (sd2psXtd renames a wrong-size card to .crp and makes it again). The\n+  firmware writes a new card only while ps2_mmceman_fs_idle(), 512 B at a time with a flush per slice; a game\n+  streaming over MMCE keeps it busy, so the card never finishes before power off. RIPPS2 has mmce_gameid=0, so these\n+  came from the device's own history-file tracking. Test MMCE launches with the device's GameID off, to take the\n+  switch and the card writes out of the picture.\n+- BOOT/BootCard-8.mcd exists (a blank formatted card): boot channel 8 was selected at some point (channel down from 1\n+  wraps to 8, and the device keeps the last boot channel). A power on on channel 8 has no PS2BBL: that is the likely\n+  \"the exploit fails sometimes\".\n+- The boot card (RIPPS2's settings live there): 3,811 KB free; RIPPS2/AUDIO was never made.\n+- The user's settings_riptopl.cfg: exit_path=mc1:/APPS/RIPPS2.elf (a stale value from the old setup: the \"hardcoded\n+  mc1 path\"), mmce_mode=0, mmce_slot=2, mmce_wait_cycles=5, mmce_use_alarms=1, default_core=0 (OPL core),\n+  popstarter_path=usb:/POPS/POPSTARTER.ELF, enable_pccf=0, ripps2_recents=0, last device 15.\n+\n+### A3. Other card items\n+- BIOS Sounds rip: \"The sounds could not be written to mc0:/RIPPS2/AUDIO/. Is the drive full or write protected?\" on\n+  the BOOT card. sd2psXtd's card manager has no read-only BOOT card; the lean card had 3.98 MB free before RIPPS2's\n+  settings. Check the space left, how RIPPS2 makes the AUDIO folder on a card (mkdir of a nested path on mcman), and\n+  offer the MMCE's SD (mmce0:/AUDIO) when the card is short.\n+\n+## B. The in-game menu's exit\n+- The user: the in-game menu should know the exit path to the RIPPS2.ELF that launched it, not a hardcoded mc1 path,\n+  and show it properly.\n+- Today: gExitPath empty means \"Browser\"; ee_core's exit loads only from mc and mass (USB) (padhook.c reloads\n+  SIO2MAN/MCMAN and, for mass, USBD from SYS-CONF); ripps2BootLockIgrPath fills it from the boot path only for mc/mass.\n+- Plan: fill the exit path from RIPPS2's own boot path at every launch unless the user set one, and show it in the\n+  menu. The catch on an SD2PSXTD: with GameID on, the device switches to the game's own card at launch, so\n+  mc0:/RIPPS2/RIPPS2.ELF is not on the card in the slot when the user exits. Options: switch the MMCE back to the BOOT\n+  card before loading the exit ELF (mmceman's switch is not in ee_core's exit path today), exit to the browser and let\n+  PS2BBL start RIPPS2 again, or keep a copy of RIPPS2 where the exit can reach it. Decide with the user.\n+\n+## C. Cache: trust it, per device, and a light gate file\n+- The user: the cache lets the list show, but settings still take a while and the cached list then refreshes itself,\n+  a hitch. Show the cached list and trust it (launch from it, starting the game's drive on launch) until a refresh is\n+  asked for (SELECT) or a device's own refresh writes its cache.\n+- A Cache page per device that has one, in an order the user sets; boot stops at the first cache it finds.\n+- \"Create master cache\" in the Cache page: one very light file, suggested next to the ELF on the memory card, so boot\n+  reads it first. If it has to live elsewhere, a tiny gate file next to the ELF points to the settings and the caches.\n+- STATUS gets an optimisation part: where settings and caches are read from, which is fastest, and a button to put\n+  the master cache and settings next to the ELF. Tooltips that explain how to get the fastest boot.\n+- Drives on Manual (the default) start on launch while their list shows at once from the cache.\n+- STATUS and Cache link to each other; going back and forth does not stack (Back returns once, then to the parent).\n+\n+## D. Storage sources\n+- PC Card CF: hide its page; it is the fat's HDD-bay CF adapter, so fold its detection into the HDD's.\n+- Recent Games: show it in the sidebar like the drives, with its own on/off; one row with a count 1 to 99\n+  (today ripps2_recents 0..9). Ask: \"manual start for drives of whatever game chosen is in the list\" means a recent\n+  game's drive starts when you launch it, as All Games does.\n+\n+## D2. Art names from the OPL Manager database (issue akilluminati47/RIPPS2#2)\n+- The OPL Manager GameArt database (and tools built on it) names backgrounds `<ID>_BG_00.png` to `_BG_06.png` and\n+  screenshots `_SCR_00`, `_SCR_01`; RIPPS2 reads `<ID>_BG` and `_SCR`, `_SCR2`... only (png, then jpg). Fall back to\n+  `_BG_00` when `_BG` is missing, and `_SCR_00`, `_SCR_01` for `_SCR`, `_SCR2`, through the same artIndexMayExist\n+  check so a miss stays free.\n+- PS1 art keys: VCD file name without .VCD, Ember's EMBER/games/ folder name, apps' ELF name with .ELF (as mapped in\n+  Luden02/OrbitPS2-Manager#48, which OrbitPS2 Manager follows).\n+\n+## E. Settings tooltips\n+- When the selected row is low on the page, the tooltip slides up so the rows above and below stay visible.\n+\n+## F. Lab\n+- 10/25 night: the background driver's key presses stopped reaching PCSX2 for every ELF and drive (the same RIPGRID\n+  walk that worked at 01:28 failed at 03:14). Shift/Ctrl/Alt not stuck. Find out before the next lab run (does the\n+  foreground driver work, did the hidden desktop change, NVIDIA overlay or Xbox app focus).\n+- The 1080i info-page captures for the thumbnail (all 9 games, RIPPS2 theme) are still to do; t114-sdmirror.raw is a\n+  lab drive with the card's games, art and CFGs.\n+\n+## Progress (10/25, end of session: usage limit reached)\n+Done in patch 0115 (pushed, CI building, NOT lab-tested yet as a whole):\n+- Art read under every name (index keeps real names: any case, .png/.jpg/.jpeg; decode by content, shrink big\n+  pictures; OPL Manager _BG_00/_SCR_00/_SCR_01 both ways; PS1 disc ID and XX.<name>.ELF keys). Host tests\n+  test_art_names.py + test_udpfs_art.py pass locally; issue #2 answered.\n+- Pad: DualShock mode asked for again in a quiet second when a pad is left digital (dead right stick).\n+- MMCE: nothing read while the card says busy (mmceCardBusy, devctl 0x2); OPL-core MMCE launch writes the play history\n+  first and waits (on screen) while the card makes the game's memory card (mmceReadyGameCard).\n+- SELECT refreshes coalesce (ioPutRequestUnlessWaiting); Trust Cache (gRipps2TrustCache=1, sbSnapTrusted) in MMCE,\n+  SMB, BDM: a cached list stands until a refresh.\n+- All Games starts at boot even when every source is Manual (lab repro on 1.1.4: empty \"Start Device\" for 45 s).\n+- Lab: ps2bg.ps1 closes PCSX2's hidden Error dialogs (the PC's audio device was gone).\n+Left: Trust Cache row + its strings on the Cache page (config key ripps2_trust_cache), exit path following the boot\n+path, PC Card CF fold, Recent Games source 1-99, tooltip slide, BIOS sounds space check, STATUS<->Cache link and the\n+STATUS loading section, translations for new _t strings, lab rounds, release v1.1.5-beta, the Short, Discord post,\n+the 1.1.5 ELF into the SD's boot cards (BootCard-1/2/4/6/8). SD card hash result: scratchpad isohash-card.tsv."
        },
        {
          "sha": "7f080d5388ba138f82348f7baa0235e2cc1dcc8d",
          "filename": "docs/plans/builds-104-on.md",
          "status": "modified",
          "additions": 135,
          "deletions": 30,
          "changes": 165,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/docs%2Fplans%2Fbuilds-104-on.md",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/docs%2Fplans%2Fbuilds-104-on.md",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/docs%2Fplans%2Fbuilds-104-on.md?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -213,7 +213,11 @@ pre-allocate a file before launch and write its sectors as VMC writes do. The de\n - [x] Mockup: docs/plans/mockups/b113-sidebar-sheet.png (make_b113_sidebar.py: the three glyphs drawn as vectors,\n       the sidebar on real Adapt captures of Storage, Launch Disc, Memory Files, the Left focus and Circle's sources).\n \n-## Build 114 (the user 10/22: All Games before the drives answer, hide and delete games): planned\n+## Build 114 (the user 10/22: All Games before the drives answer, hide and delete games): built, not released\n+Where it stands (10/22 evening): every part below is in patch 0114 on `b114` with the RiptOPL merge, the launcher search, STATUS's\n+drives, the FreeType trim and the new words in all 33 languages; CI green, host tests clean, the hidden lab passes listed\n+under Tests and lab done. Not released and not posted: the user decides after the showcase. Decisions 1 to 5 were\n+taken as recommended (the user left them to me on 10/22, to recap): see the end of this section.\n One patch, `elf/patches/0114-ripps2-games.patch`, on branch `b114`; test `elf/tests/test_ripps2_build114.py`. Three parts,\n built in this order: A (the riskiest: All Games crashed a console in build 66 when its records were freed under a\n cover lookup), then C (delete, which B's page uses), then B. Part D is a decision, not work yet.\n@@ -248,95 +252,131 @@ cover lookup), then C (delete, which B's page uses), then B. Part D is a decisio\n   remove touch the chain; both get the check in part C.\n \n ### Credits (the user 10/22, first: small and independent)\n-- [ ] **Neutrino beside RiptOPL** on the About page: its own section sits mid-page while BUILT ON RIPTOPL closes it,\n+- [x] **Neutrino beside RiptOPL** on the About page: its own section sits mid-page while BUILT ON RIPTOPL closes it,\n       so RiptOPL overshadows it. The opening lines name rickgaiser's Neutrino as the second game core with its\n       link (github.com/rickgaiser/neutrino), and its section moves up beside BUILT ON RIPTOPL. (README done on main\n       10/22: the intro, the on-board line and a credits entry right after RiptOPL's.)\n-- [ ] **TESTERS** ends with \"Special thanks to all the YouTube love as well: glad to help however you reach me.\"\n+- [x] **TESTERS** ends with \"Special thanks to all the YouTube love as well: glad to help however you reach me.\"\n       (the user's words), in all 33 languages; every tester list from now on carries it (README done 10/22).\n \n ### A. All Games from a master list\n What is there now: each source keeps its own last list on itself (RIPPS2.LST, build 110), so a source's page fills\n the moment that source answers. All Games (mixsupport.c) is built only from sources that answered, so at boot it\n fills source by source, and with a Manual source it shows nothing until START is pressed.\n-- [ ] **allgames.bin beside the settings** (where favourites.bin and recents.bin are: favGetNamedPath, so mc0:/RIPPS2\n+- [x] **allgames.bin beside the settings** (where favourites.bin and recents.bin are: favGetNamedPath, so mc0:/RIPPS2\n       or the drive Save Settings To chose; never on a game source, which is what has not answered yet). Header\n       `RIPALL01`, count, FNV-1a sum; then per game: kind (PS2 ISO / VCD / CUE), source family (HDD APA, BDM by its\n       driver type: usb, ata, sdc, ieee, udp; MMCE, SMB, UDPFS, HTTP), media (CD / DVD, for the icon), startup, name.\n       About 50 bytes a game: 1,000 games is about 50 KB, a few hundred ms on a memory card. Written staged (a\n-      `.new` file, then renamed) as favourites.bin is, on the IO worker, only when the sum changed, only once\n+      `.new` file, then renamed) as favourites.bin is (built: written in place, since a memory card has no rename\n+      and the lab found the `.new` copy left behind; a short or damaged file fails its sum and is not read), on the IO worker, only when the sum changed, only once\n       every Auto source has answered (or 20 s after boot), never with Write Operations or Fast Cache off.\n-- [ ] **Waiting rows.** The first All Games pass reads allgames.bin and shows every game at once, sorted and with\n+- [x] **Waiting rows.** The first All Games pass reads allgames.bin and shows every game at once, sorted and with\n       duplicates dropped by the same rules as now (mixCompare). A row whose source has not answered has no owner\n       list yet (owner NULL): mixRow, mixGetImage and mixGetArtMode already skip a NULL owner, so no reader can\n       reach a source through it. Its art comes when its source does.\n-- [ ] **The handover.** When a source publishes its list (opl.c already queues mixRequestUpdate behind it), its\n+- [x] **The handover.** When a source publishes its list (opl.c already queues mixRequestUpdate behind it), its\n       waiting rows are swapped for its real ones in the one locked publish mixUpdateItemList does now (mixSema\n       held only for the pointer swap, never across a device read or a wait). The cursor stays on its game:\n       updateMenuFromGameList keeps the selection by startup, and waiting rows carry their startup.\n-- [ ] **Rows that go.** A source's waiting rows are dropped when its list comes (anything it no longer has is gone),\n-      or when it is known absent: start mode Off, no HDD (hddCheck), no card in the MMCE slot, a BDM driver type\n+- [x] **Rows that go.** A source's waiting rows are dropped when its list comes (anything it no longer has is gone),\n+      or when it is known absent (built: start mode Off, or started and silent for 40 s): start mode Off, no HDD (hddCheck), no card in the MMCE slot, a BDM driver type\n       with no device after BDM settles, SMB failing to connect. Before that, a moved stick only changes its BDM\n       slot, and rows match by family and name, never by slot.\n-- [ ] **Manual sources stay instant.** RIPPS2 defaults BDM to Manual for the instant boot. With the master list,\n+- [x] **Manual sources stay instant.** RIPPS2 defaults BDM to Manual for the instant boot. With the master list,\n       All Games shows those games too: Cross on one starts that source (as All Games' START starts every source\n       today), waits for its list, then launches. So boot stays instant and the list is still whole.\n-- [ ] **Cross on a waiting row**: a message box \"Starting USB Drive...\" (the source's own name), Circle cancels. On\n+- [x] **Cross on a waiting row**: a message box \"Starting USB Drive...\" (the source's own name), Circle cancels. On\n       the list's arrival the game is found by name (mixOwnerId already does this) and launched; a game the\n       source no longer has says so and leaves the list; 30 s with no answer says the drive did not answer.\n-- [ ] **Other buttons on a waiting row**: Square shows the info page with what the master list knows (name,\n+- [ ] (part, to 115) **Other buttons on a waiting row**: Delete and Hide refuse it; Square and R3 have nothing\n+      of their own yet.: Square shows the info page with what the master list knows (name,\n       startup, source) and \"starting\"; R3 (star), Triangle's rename / hide / delete say to wait for the drive (a\n       toast: this is the game list, where toasts draw).\n-- [ ] **Cache page lines** (carried from 113): under Fast Cache, one line per source with its saved list (games,\n+- [ ] (to 115; the Cache page got the launcher lines instead) **Cache page lines** (carried from 113): under Fast Cache, one line per source with its saved list (games,\n       date) and All Games' (games, where it is kept); each support module gets an accessor for its sb_snap_t.\n-- [ ] The new words in all 33 languages.\n+- [x] The new words in all 33 languages (94 strings and 6 labels, 10/22).\n \n ### B. Hidden games\n-- [ ] **Hide Game** in a game's Triangle options (and on All Games and Starred Games rows, proxied to the source).\n+- [x] **Hide Game** in a game's Triangle options (and on All Games and Starred Games rows, proxied to the source).\n       One confirm, since it can be undone. Stored **on the source**, as RIPPS2.HID beside RIPPS2.LST (the device\n       root; on APA in +OPL; the SMB share root; the MMCE card), so a hidden game stays hidden when the drive goes\n       to another console. Records: kind and name (the file name: two dumps of one game share a startup).\n-- [ ] **Hidden everywhere** but one place: skipped when updateMenuFromGameList builds a source page (rows keep their\n+- [x] **Hidden everywhere** but one place: skipped when updateMenuFromGameList builds a source page (rows keep their\n       ids into the source's own array, so launch, config and art are untouched), in mixCollect and allgames.bin,\n       in Starred Games and Recents (the star is kept, so Show Again brings the game back starred), in RIPFLOW and\n       RIPGRID (they draw the same submenu), in Random Game (L2 + R2) and in the game count.\n-- [ ] **Settings > Game Sources > Hidden Games**: every started source's hidden games under their source's name,\n+- [x] **Settings > Game Sources > Hidden Games**: every started source's hidden games under their source's name,\n       with size. Cross on one: Show Again, Delete (part C). Asks the Parental Lock password when one is set.\n-- [ ] Rename and delete keep RIPPS2.HID in step (sbRename, part C).\n+- [x] Rename and delete keep RIPPS2.HID in step (sbRename, part C).\n \n ### C. Delete a game completely and cleanly\n What is there now: OPL's delete (Triangle > Delete) unlinks only the image's files (sbDelete, ul.cfg rebuilt for\n UL); its CFG, art, cheats and VMC stay behind. APA games have no delete at all (hddsupport's itemDelete is NULL),\n All Games' and Starred Games' are NULL too, and a starred game refuses (\"Delete is not allowed while the game is starred.\").\n RIPPS2's APA driver already takes a user partition's delete for the HDD MANAGER (build 73, hddRemove: never a __\n name, -EBUSY when open, every header write through the table fence).\n-- [ ] **A removal plan first**, from one pure function (host-tested): the game and its source in, the list of\n+- [x] **A removal plan first**, from one pure function (host-tested): the game and its source in, the list of\n       what goes and what stays out, with sizes. On BDM, MMCE, SMB and UDPFS: every part of the image (sbDelete's\n       paths, the browse subfolder included), ul.cfg rebuilt for a UL game; CFG/<startup>.cfg; ART/<startup>_*\n       (COV, COV2, ICO, LAB, LGO, BG, SCR, SCR2 to SCR9); CHT/<startup>.cht. On APA: the game's partition (PP.*)\n       through hddRemove, then its CFG, art and cheats inside +OPL (files in the PFS; the +OPL partition itself is\n       never touched). PS1: the VCD (POPS) or the game's EMBER/games/<Title> folder.\n-- [ ] **Kept unless ticked**: the game's VMC (it holds saves; named in its CFG), POPSTARTER's saves folder\n-      (POPS/<name>/), Ember's saves (where Ember keeps them: to check), RIPSHOT's MEDIA/<title> shots. A CFG or art\n+- [x] **Kept unless ticked**: the game's VMC (it holds saves; named in its CFG), POPSTARTER's saves folder\n+      (POPS/<name>/), Ember's saves (where Ember keeps them: to check), RIPSHOT's MEDIA/<title> shots (built as one\n+      question, \"Delete its saves and RIPSHOT pictures too? No keeps them.\"). A CFG or art\n       another copy of the same startup on that source still uses is kept, and the screen says why.\n-- [ ] **Two screens, as the HDD MANAGER**: what goes, what stays, and the space freed; then a last screen only START\n+- [x] **Two screens, as the HDD MANAGER**: what goes, what stays, and the space freed; then a last screen only START\n       gets past (REMOVE FOR GOOD). Never with Write Operations off; Parental Lock asked first; refused for the\n       partition RIPPS2 runs from or keeps its settings on (hmKept's rules) and for any __ partition.\n-- [ ] **Order**: the image first (a cut-off delete leaves stray CFG or art, never a half game listed), then the rest;\n+- [x] **Order**: the image first (a cut-off delete leaves stray CFG or art, never a half game listed), then the rest;\n       APA under ioBlockOps, then hddPartitionsChanged so games.bin is rebuilt. The star and the Recents entry go,\n       RIPPS2.HID drops it, the source rescans (RIPPS2.LST follows), All Games and allgames.bin follow.\n-- [ ] **The APA table checked before any partition goes** (Berion's warning; the HDD MANAGER's remove too): a\n+- [x] **The APA table checked before any partition goes** (Berion's warning; the HDD MANAGER's remove too): a\n       read-only walk of the whole chain from __mbr: every header's magic and checksum, each start equal to the\n       last one's next and each prev pointing back, every partition inside the disk (start + length within\n       HDIOC_TOTALSECTOR), no length above what this disk allows (apaGetPartitionMax of its own size) or off its\n       power-of-two alignment, and the chain closing at 0. Anything off: no delete, and the screen says the table\n       does not fit this disk (written from an image of another size?) and to repair it on a PC first. The walk\n-      runs again after the delete, and the result goes into REMOVED.TXT.\n-- [ ] **Delete on All Games and Starred Games** proxies to the source row found by name at that moment\n+      runs again after the delete, and the result goes into REMOVED.TXT. (The HDD MANAGER's remove runs the same\n+      walk first since 10/22 evening.)\n+- [x] **Delete on All Games and Starred Games** proxies to the source row found by name at that moment\n       (mixOwnerId), never the stored id; refused on a waiting row.\n-- [ ] **A receipt**: mc0:/RIPPS2/REMOVED.TXT gets what was removed, from where, and the bytes freed (as LAUNCH.TXT\n+- [x] **A receipt**: mc0:/RIPPS2/REMOVED.TXT gets what was removed, from where, and the bytes freed (as LAUNCH.TXT\n       reports a launch), and the list shows \"REMOVED <title>: 3.1 GB FREED\".\n \n+### E. The launchers RIPPS2 does not carry, found and remembered (the user 10/22; plain words on screen since\n+10/23: the user found the code name SEANCE off-putting for a small feature, so the screen says RIPPS2)\n+- [x] **ripps2seance.c**: POPSTARTER, the POPS files, Ember, Ember's BIOS and Neutrino searched on the drives RIPPS2\n+      has started (never loading a driver to look), skipping the game and art folders; kept by drive kind (usb,\n+      ata, mx4sio, ilink, mmce, hdd, smb) in `launchers.txt` beside the settings, so a stick in another slot still\n+      matches. Every launch that finds one notes it.\n+- [x] **Used at launch**: POPSTARTER after Settings' path and the game's drive, before the cards; RIPPOPS copies the\n+      POPS files from the folder found; Neutrino after the cards, then a search, and RIPPS2's own copy offered once,\n+      only when none is found anywhere; Ember and its BIOS copied beside the games by a pre-flight before the\n+      launch (START), so RiptOPL's launch legs and guiMsgBoxMissing stay as upstream has them (their host\n+      harnesses copy them word for word: a weak seanceHook is NULL there).\n+- [x] **Settings > Game Sources > Cache**: five Found / Not found lines and **Look now** (no on/off: it only helps).\n+- [x] Lab 10/22: \"knows 5 of the 5\" on the 114 disk, each at its odd path; Cache rows and Look now.\n+\n+### F. STATUS: the drives before the homebrew (the user 10/22)\n+- [x] **DRIVES** first: every started drive by kind, the internal HDD (APA)'s free space, each memory card's free\n+      KB (asked twice after START DRIVES: a changed card answers short once), the MMCE card, what is off; **START\n+      EVERY DRIVE**. Then **LAUNCHERS** (one line each, LOOK FOR THEM starting every drive first on Cross), then the\n+      homebrew and BIBLE. The subtitle reads Drives, launchers and homebrew (10/22 evening).\n+\n+### G. The font trim, and no lite ELF (FifthFox's 2 MB; the user 10/23: \"ideally there is no lite elf we just ship\n+the one that makes the most sense\": the lite variant was built, then removed before release)\n+- [x] ~~RIPPS2-lite.elf~~ removed 10/23 (RIPPS2_LITE=1, its own CI artifact): no BIBLE KELFs, no Neutrino tar, no OPL background;\n+      under 2 MB. It says where Neutrino goes and that BIBLE installs once from the full ELF. Lab 10/22: boots,\n+      START menu, STATUS with DRIVES and LAUNCHERS.\n+- [x] **FreeType** started with RIPPS2's own module list (TrueType, sfnt, autofit, smooth; CFF and its helpers in\n+      the full ELF) and freed without ftinit.c, so Type 1, CID, Type 42, PFR, BDF, PCF, WinFNT, SDF, SVG and the mono\n+      rasteriser leave the link. **strip_fonts.py** strips the compiled-in fonts (render-checked identical).\n+- Where the rest of the size is: the fonts are about 75 KB packed, and the packer is near LZMA's best on what is\n+  left; the big wins were the carried blobs (lite) and FreeType. Next candidate: libjpeg-turbo's 12 and 16-bit\n+  decoders.\n+\n ### D. Compress on hide (ISO to ZSO): the user's call before any code\n The user's idea (10/22): hiding can compress the game into the hidden list; Show Again un-compresses when the\n drive has room. What stands in the way:\n@@ -355,19 +395,23 @@ the lab at EECycleRate -3, then on a console) and decide compression for 115, in\n the time it will take shown before it starts and the game's core checked first.\n \n ### Tests and lab\n-- [ ] Host (test_ripps2_build114.py, the module compiled with -DRIPPS2_MIX_HOST_TEST as earlier builds do):\n+- [x] Host (test_ripps2_build114.py, the module compiled with -DRIPPS2_MIX_HOST_TEST as earlier builds do):\n       allgames.bin round trip; a short, corrupt or oversized file is ignored, never half read; the merge of\n       waiting and live rows (live replaces, absent drops, Manual stays, cursor key kept); RIPPS2.HID filtering;\n       the removal planner for every format and source (nothing outside the source's prefix, shared CFG kept,\n       __ and kept partitions refused, APA = partition + PFS files).\n-- [ ] Lab (ps2bg, hidden): reel HDD with allgames.bin, grabs at 3 s (every game listed before the drive answers)\n+- [x] Lab (ps2bg, hidden, 10/22): boot 1 writes allgames.bin on the card; boot 2 on a fresh card with only that\n+      file and BDM on Manual lists the game before its drive answers; Hide, Hidden Games and Show Again; delete\n+      on exFAT (ISO, CFG, art, cheats gone, VMC kept, read back with exfat_ls) and on a scratch APA disk (PP.TEST.HDL\n+      gone, the chain in order: apa_ls and REMOVED.TXT agree); the lite ELF's smoke run (since removed). Not in the lab: Cross on a\n+      waiting row (the lab's games are dummies, so nothing is launched there). The plan was: reel HDD with allgames.bin, grabs at 3 s (every game listed before the drive answers)\n       and after the handover (same cursor); BDM on Manual: rows listed, Cross starts the drive and launches;\n       hide and Show Again on exFAT (RIPPS2.HID read back with the exFAT tools); delete on a scratch copy of\n       ripps2-apa.raw, then pfsshell: the PP partition gone, +OPL whole, the CFG and art gone from it.\n - [ ] Consoles (the Discord test list): MMCE with a big library (lukeatmynose's 180 games), SMB, a real APA\n       delete (after a backup), boot to a whole All Games with every source on Manual.\n \n-### Decisions for the user\n+### Decisions (taken 10/22 as recommended, the user to review in the recap)\n 1. Compression (part D): measure first and decide for 115 (recommended), or build it into 114.\n 2. The hidden list on each drive (travels with it; recommended) or one list in the settings.\n 3. Delete keeps saves (VMC, POPS saves) and RIPSHOT shots unless ticked (recommended), or removes everything.\n@@ -380,6 +424,67 @@ the time it will take shown before it starts and the game's core checked first.\n 5. Size: RIPPS2-lite.elf beside the full ELF (recommended: keeps Neutrino and BIBLE in the full one, as decided),\n    or Neutrino and BIBLE found rather than carried in the one ELF.\n \n+Taken: 1. measure first, ZSO decided for 115 (no code in 114); 2. the hidden list on each drive (RIPPS2.HID);\n+3. saves and RIPSHOT pictures kept unless the one question gets Yes; 4. merged first, in 114 (57c45b4, so the\n+iLink fixes reach testers now); 5. RIPPS2-lite.elf beside the full ELF, built and then removed on 10/23 (the user:\n+one ELF; POPSTARTER and Ember found by association, as since 113).\n+\n+### Console checks the lab cannot make\n+- IGR on the merged VBLANK path (#984) on a real game; the ee_core stack (0xC80, as v1.1.3).\n+- FifthFox's iLink: Neutrino and POPSTARTER from the HD-338 (the #1018 fixes).\n+- A real APA delete after a backup, and the HDD MANAGER refusing a cloned table of another size.\n+- Boot with every source on Manual to a whole All Games, then Cross on a waiting row: the drive starts, the game launches.\n+- MMCE with a big library (lukeatmynose's 180 games) for the master list's size and speed.\n+\n+## Build 115 (the user 10/23: fonts, settings without scrolling, FifthFox's reports): planned\n+\n+### Storage safety first (FifthFox, 10/22: \"I crashed two .MCDs and my USB ... Maybe it was writing something in the\n+background while I was trying to launch a game and had to restart?\")\n+His setup: SCPH-39001; slot 1 MMCE (its card images, .MCD, on its own SD card), slot 2 MX4SIO, USB, iLink (FAT32,\n+slow to list), APA through a GameStar adapter; RIPPS2 started from USB, Neutrino and POPSTARTER on the memory card.\n+Earlier: 10/12 (v1.0.0) POPSTARTER from MMCE crashed to wLE and the MMCE's boot card had to be restored; 10/14 the\n+POPSTARTER BDMA equip put usbd.irx and usbhdfsd.irx on the card with the exFAT variant for a FAT32 iLink drive.\n+\n+What the code does (read 10/23, nothing run yet):\n+- A launch's teardown (deinitEx) waits for the IO worker at most LAUNCH_IO_DRAIN_TICKS, about 10 s, then goes on;\n+  ioEnd() only raises a flag. The worker keeps running the request in hand, and the queue behind it, while the\n+  launch unmounts and resets the IOP.\n+- A scan of a slow drive (MMCE libraries take minutes; FifthFox's USB, MX4SIO and iLink are slow) easily outlasts\n+  10 s, and RIPPS2's scans END IN A WRITE when the list changed: RIPPS2.LST on the drive itself (on an MMCE that is\n+  mmce0:/, the SD card holding the .MCD files) since builds 109 and 110, and in 114 allgames.bin beside the settings\n+  (mc0: on his console, which is the MMCE). Upstream's worker mostly reads, and a cut read is harmless; a cut write\n+  leaves FAT or exFAT half updated. A launch that then stalls and is power-cycled does the same.\n+- recents.bin and conf_last.cfg are written at launch on the GUI thread and closed before the teardown: finished.\n+- The PS1 launch copies POPSTARTER's files and the BDMA driver pair onto mc?:/POPSTARTER before POPSTARTER's own\n+  IOP reset (upstream RiptOPL). The iLink variant is RiptOPL's #1018 (merged in 114).\n+\n+Plan:\n+- [ ] A launch stops RIPPS2's background writes before anything else: a flag raised at the top of deinit and\n+      deinitEx; RIPPS2.LST, allgames.bin, the hidden list, art and RIPSHOT writes check it and skip (each is a cache\n+      or can wait for the next boot).\n+- [ ] A write already started is waited for, whatever the 10 s drain does: a count of writes in flight, which the\n+      teardown waits on (writes are short; bounded at 20 s with a log line if one never ends).\n+- [ ] Scans give up when a launch begins, so the drain finishes and no worker is reading a drive into an IOP reset.\n+- [ ] RIPPS2.LST written so that a cut write cannot hurt the drive's file table: a new file beside it, renamed over\n+      it where the driver renames (USB, exFAT HDD, MX4SIO, iLink), and on the MMCE kept beside the settings rather\n+      than on the MMCE's SD card next to its .MCD files.\n+- [ ] Ask FifthFox: which build; what was launched, from which drive, by which core; did the launch stall; what\n+      exactly was lost on the cards and the stick; MMCE firmware and per-game cards (GameID) on or off.\n+- Until then he can turn Settings > Game Sources > Cache > Fast Cache off (no RIPPS2.LST writes) and let the lists\n+  finish before launching.\n+\n+### Fonts (the user's answers to the font audit, 10/23)\n+- [ ] A: one variable Roboto Condensed 3.015 (the newest) in the ELF for Settings and RIPFLOW, weights 400 to 700,\n+      cut to Latin-1; Settings rows at 700 and 18 px.\n+- [ ] B: default_font_weight and font1_weight ... font15_weight (100 to 900): a variable face at that weight, a\n+      static face grown in code up to about 1 px.\n+- [ ] C: Text Weight (Regular, Medium, Bold) in Colors and More. And no settings page should need scrolling:\n+      rearrange the pages that do.\n+- [ ] D: Adapt and Adapt Rx: b.ttf and c.ttf become the built-in variable Roboto by weight keys, d.ttf becomes\n+      builtin:ripps2, and their conf_theme.cfg lists every built-in font in its comments so theme makers find them.\n+- [ ] E: the language pack on Roboto Condensed 3.015 cut per language, in step with the ELF's.\n+- F: all of it in 115; 114 ships with its reel, the forum post and the Discord post first.\n+\n ## Skipped\n - **RIPCON** (format conversion, as OrbitOPL Toolbox does on a PC): not done on PS2 hardware yet, likely failure\n   prone, and a smaller format can be less supported by the loaders. Skipped by the user (2026-10-18)."
        },
        {
          "sha": "516af3a616c891b72f92468bab671d35c5fb81e9",
          "filename": "docs/plans/discord-boot-empty-games.md",
          "status": "added",
          "additions": 38,
          "deletions": 0,
          "changes": 38,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/docs%2Fplans%2Fdiscord-boot-empty-games.md",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/docs%2Fplans%2Fdiscord-boot-empty-games.md",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/docs%2Fplans%2Fdiscord-boot-empty-games.md?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -0,0 +1,38 @@\n+# Discord website embed: boot to empty games\n+\n+This is a local preview of the website's existing Discord rich embed. It keeps the original title and description, uses the RIPPS2 brand icon, adds a general project introduction below the GIF, and provides Website, PSX-Place, GitHub, and guns.lol buttons. Nothing has been posted.\n+\n+## GIF sequence\n+\n+The loop uses a fresh launch of the RIPPS2 ELF in the isolated PCSX2 lab. It opens on the close tower animation, settles on the empty All Games list, pauses on the Start menu for about 10 seconds, then returns to the list before looping. It does not enter the Settings page. The capture shows 60 fps source video with no HDD error; the Discord GIF is 800 px wide at 12 fps to keep the full loop within a practical attachment size.\n+\n+## Lab and source capture\n+\n+- PCSX2 lab: `B:\\Emulation\\PlayStation\\PCSX2\\ripps2-lab-emptygames-capture`\n+- ELF: `RIPPS2.elf` in the lab folder.\n+- Virtual HDD: zero-filled `hdd-blank-2gb.raw`, enabled in this lab's `PCSX2.ini`.\n+- Memory cards: lab scratch cards only; the user's original cards and reel disks are not used.\n+- Source video: `videos\\RIPPS2 [_]_RIPPS2_20261025000556.mp4`, 1920x1080, 60 fps, 28.05 seconds.\n+- GIF: `media/b114/ripps2-boot-empty-games.gif`, 560x315, 12 fps, 28 seconds, 11,056,703 bytes.\n+\n+Recreate the GIF with:\n+\n+```powershell\n+python tools/discord/make_boot_gif.py `\n+  --video 'B:\\Emulation\\PlayStation\\PCSX2\\ripps2-lab-emptygames-capture\\videos\\RIPPS2 [_]_RIPPS2_20261025000556.mp4' `\n+  --trim-start 0 --duration 28 --fps 12 --width 560 `\n+  --out media/b114/ripps2-boot-empty-games.gif\n+```\n+\n+## Preview files\n+\n+- `out/boot-empty-games/embed-preview.html` \u2014 rendered local preview.\n+- `out/boot-empty-games/component-embed.json` \u2014 Discord Component Embed payload for review.\n+- `out/boot-empty-games/ripps2-boot-empty-games.gif` \u2014 copied GIF attachment.\n+- `tools/discord/make_boot_embed.py` \u2014 rebuilds the preview and payload from the website embed copy.\n+\n+## Website link preview\n+\n+`index.html` contains Discord's inline `<script id=\"discord:component-embed\" type=\"application/json\">` payload. Discord reads it when someone posts the website URL and renders the title, existing description, brand icon, GIF gallery, general introduction bullets, and four URL buttons as a Component Embed. The payload is under Discord's 3,000-byte limit. The Open Graph and Twitter tags remain as fallbacks.\n+\n+The Component Embed and GIF are published from `main` in commit `f0f0e8b`. GitHub Pages reports the build as complete. A live fetch using Discordbot's user agent returned the inline payload and the GIF with HTTP 200; the payload is 1,544 bytes and the GIF is 11,056,703 bytes. Component previews require Discord's crawler to reach the page and all media within 10 seconds; the GIF was reduced to 560x315 while retaining the 12 fps, 28-second sequence. The live asset fetched in about one second from the build host; Discord's Embed Debugger can be used to inspect the rendered card if needed."
        },
        {
          "sha": "cbfd6d0773620f9dfbbe1315b105ae5ad4d0fadd",
          "filename": "docs/plans/reel-114.md",
          "status": "added",
          "additions": 54,
          "deletions": 0,
          "changes": 54,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/docs%2Fplans%2Freel-114.md",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/docs%2Fplans%2Freel-114.md",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/docs%2Fplans%2Freel-114.md?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -0,0 +1,54 @@\n+# The build 114 reel (v1.1.4): storyboard\n+\n+The user (10/23): a calm preface that shows the plain things first, then a montage of the features on Queen's\n+\"Don't Stop Me Now\" (its upbeat part and beats; the title phrase may be on screen, no lyric lines), faded out early\n+into mr.msa's \"Money Longer (Instrumental)\" with stronger effects: it opens on the pepecook picture and the\n+larry-themaliciouslarry clip, carries into a colour party (the new Color Themes, Custom, the Color Sweep), then the\n+most interesting features. No photos of the sidebar this time: the sidebar moves, in footage. No upload.\n+\n+Music: the console's menu wave (menu_rip.wav, ripped from the BIOS by RIPPS2) under the preface and the end card;\n+Queen for the montage; Money Longer for the colours and the finale. Every cut on a measured beat (tools/beats.py).\n+\n+## 0. Preface, menu wave (about 25 s)\n+- Title card: Rip it | v1.1.4, \"RIPPS2 beta / build 114\".\n+- The Settings hub, then each page with nothing below the fold: Game Sources (one HDD Start Mode, BDM Devices as\n+  its own page), Interface (Screen, Layout, Colors and Text), Network, Audio. Caption: every page on one screen.\n+- STATUS: drives with free space, launchers found, BIBLE's line. About: Neutrino beside RiptOPL in the credits.\n+\n+## 1. Don't Stop Me Now (the montage)\n+- The upbeat entry: the Storage sidebar with every source on (All Games, USB, HDD Games, MMCE, Network, Apps,\n+  Starred), cut on the beat to Game Sources where Network Connectivity and BDM Devices Start Mode go Off, then the\n+  sidebar again with them gone. One HDD Games, APA or exFAT, never two.\n+- All Games: every drive's games in one list (v_master), kept for the next boot.\n+- Hide Game and Show Again (v_hide), Delete completely (v_delete: ISO, CFG, ART, CHT, the VMC kept), the APA\n+  check refusing a damaged table (v_apa_no).\n+- Look now: the launchers found on the started drives (Cache page, STATUS handover).\n+- The launch guard (a RIPPS2_GUARD_TEST build): a game picked while a drive's list is read; the glass panel\n+  fades in, the notches fill toward the estimate, the game starts by itself; once more with START NOW.\n+- Text Weight Regular to Bold on the Settings tooltips; Japanese in the same faces (v_lang_ja).\n+- The title phrase on the chorus hit, then Queen fades early.\n+\n+## 2. Money Longer (the colours, then the finale)\n+- The drop: pepecook.png and the larry clip, glitch cuts into the colours.\n+- Color Theme: Tangerine, Flamingo, Turquoise, Moonlight (the ghostly white), Custom with its own picker.\n+- Color Sweep: Rainbow over the towers, Flamingo's all-pink sweep, the Sweep Colors page, a sweep saved under a new\n+  name; the sweep on RIPFLOW and RIPGRID.\n+- The finale on the hardest beats: the guard panel, the sidebar, the themes (t_ripps2, t_adapt, t_ripgrid,\n+  t_ripflow, t_disc), fast cuts with RGB split and punch zooms.\n+\n+## 3. Moonlight (the user, 10/23), to the end\n+- As Money Longer ends, Memory Files in the Moonlight Color Theme: the browser, the HDD's MEDIA folder, the song's\n+  AVI (XXXTENTACION, Moonlight: a ghost-white moon visualiser, XviD with MP3, made for the reel) and its info, Cross:\n+  the player opens it, and the song carries the rest (laid from the FLAC on the player's own start).\n+- The reel's colours go Moonlight's ghostly white from here (a grade), back to colour under the hat.\n+- The player, then beside it the type: what RIPPS2 has done from build 1 to 114, a page every four bars (a kicker, a\n+  title, short points one beat apart), glowing on the beat; \"114 builds\" and a thank you at the song's break.\n+- The end card when the beat comes back: v1.1.4, github.com/akilluminati47/RIPPS2, psx-place, the testers, the\n+  credits and music lines, then the dead elf's hat. No lyric lines on screen.\n+\n+## Takes needed on the final ELF (takes114v.ps1)\n+p_hub, p_sources, p_bdm, p_interface, p_screen, p_layout, p_colors, p_status, p_about (preface); s_all_on,\n+s_off_settings, s_after (sidebar); g_wait, g_now (guard, test ELF); w_weight (Text Weight); c_tangerine,\n+c_flamingo, c_turquoise, c_moonlight, c_custom, c_sweep_rainbow, c_sweep_flamingo, c_sweep_page, c_sweep_ripflow;\n+m_moon (Memory Files to the player, takes114r.ps1);\n+plus the 114 takes kept (v_master, v_hide, v_delete, v_apa_no, v_cache, v_handover, v_lang_ja, t_*)."
        },
        {
          "sha": "9b244cd32f74a25850a45e73af0224ec97a52f7f",
          "filename": "docs/plans/showcase-114.md",
          "status": "added",
          "additions": 108,
          "deletions": 0,
          "changes": 108,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/docs%2Fplans%2Fshowcase-114.md",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/docs%2Fplans%2Fshowcase-114.md",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/docs%2Fplans%2Fshowcase-114.md?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -0,0 +1,108 @@\n+# Showcase for build 114: the reel, the first post's tour, the bump's pictures\n+\n+The user (10/22): \"when making the screenshots for the first post choose better images story board it nicer so its\n+not like a latest changelog showing the sidebar right away ... be thorough in the planning of video and images like\n+we have done with text.\" Nothing here is uploaded or posted: the user watches the reel, picks, uploads, and posts.\n+\n+Rules every frame keeps (from the reel memories): show only what RIPPS2 does on this build, in the lab's real\n+captures; captions beside the picture, never on it, and readable at phone size; never a Cross on the game list (the\n+lab's games launch); one theme per section so the eye is not jumping; no em dashes.\n+\n+## 1. Who sees what, in what order\n+\n+| Where | Reader | What they need first |\n+|---|---|---|\n+| psx-place first post (the front door) | someone who has never seen RIPPS2 | what it looks like with games in it, then how deep it goes, then that it is alive |\n+| psx-place bump reply for v1.1.4 | a follower of the thread | what changed since v1.1.3, each in one picture |\n+| YouTube / the bump's video | both | the reel below: 114's story, framed by what RIPPS2 is |\n+| Discord Test me post (when the user says) | testers | the test list; the reel link if it is up |\n+\n+## 2. The reel: \"Rip it | v1.1.4\", about 2:50, 2560x1440 50 fps\n+\n+Built in the v108 reel kit (`plan114.py` on `reel109.py` / `reel_engine108.py`), on RIPPS2's own music: the PS2's menu\n+music (ripped by RIPPS2) under the title card and the hat, and **Pillars** (RIPPS2 original, 84 BPM, a bar is 2.857 s)\n+for the body, bars 0 to 48 and its outro. Every cut on a bar or a half bar. The story is build 114's naming, which\n+reads as four plain promises: the whole list at once, your launchers found once, a game hidden from every list, and\n+one deleted completely. (No code names on screen: the user, 10/23.)\n+\n+| Bars | Section | On screen | Caption (head / body) | Why it is there |\n+|---|---|---|---|---|\n+| intro 12 s | Title | the blue hat drops beside \"Rip it \\| v1.1.4\", under it \"RIPPS2 beta / build 114\" | | the series' opening, so 114 reads as the next chapter |\n+| 0 to 2 | I. The whole list | a boot on the reel drive, chapter card over it: \"All Games, at once\" / \"before a single drive answers\" | | the problem testers reported (MMCE and USB lists slow to appear) |\n+| 2 to 4 | | All Games at the first frame: six games listed, no covers yet | The master list / Every game you have, listed before its drive answers. | the feature in one look |\n+| 4 to 6 | | the covers arrive, the cursor has not moved | The handover / Each drive's games swap in as it answers. The cursor stays put. | proves it is the real list, not a picture of one |\n+| 6 to 8 | | the same list with the drives on Manual: whole, instant | Drives on Manual / Still instant: their games are listed too. | Manual is RIPPS2's default for BDM |\n+| 8 to 10 | II. Your launchers | chapter card: \"Your launchers\" / \"found once, wherever you keep them\" | | |\n+| 10 to 12 | | STATUS opening on DRIVES: the HDD, both cards' free space, MX4SIO off | Drives first / STATUS opens on what RIPPS2 sees, with free space. | the user's ask: drives and space before the homebrew |\n+| 12 to 14 | | LAUNCHERS: five lines, not found yet; LOOK FOR THEM | Launchers / POPSTARTER, the POPS files, Ember, its BIOS, Neutrino. | |\n+| 14 to 16 | | \"Start every drive first?\" then LOOKING FOR YOUR LAUNCHERS | LOOK FOR THEM / It starts every drive, then searches them all. | |\n+| 16 to 18 | | the summary: RIPPS2 found 5 of the 5 | Found / Wherever you keep them, remembered for every launch. | |\n+| 18 to 20 | | STATUS's lines now with their paths (BACKUP/PS1, APPS/Ember, TOOLS/neutrino) | Kept / By drive kind, so a stick in another port still matches. | odd paths prove it searched |\n+| 20 to 22 | | Settings > Game Sources > Cache: Found / Not found, Look now | On the Cache page / Found or not found. No switch: it only helps. | the user's wording |\n+| 22 to 24 | III. Hide a game | chapter card: \"Hide a game\" / \"from every list, until you want it back\" | | |\n+| 24 to 26 | | Triangle options, Hide Game, the question | Hide Game / In a game's options. One question: it can be undone. | |\n+| 26 to 28 | | HIDDEN toast, the list without the game | Gone from every list / All Games, Starred Games, Recents, Random Game. | |\n+| 28 to 30 | | Settings > Game Sources > Hidden Games, SHOW IT AGAIN | Hidden Games / Kept on the game's own drive. Show it again any time. | |\n+| 30 to 32 | IV. Delete a game | chapter card: \"Delete a game\" / \"completely, and only what is its own\" | | |\n+| 32 to 34 | | the plan: Goes (the game, its settings, art, cheats) / Kept (its saves) | Everything of its / The game, its settings, its art and its cheats. | was: only the ISO went |\n+| 34 to 36 | | \"Delete its saves and RIPSHOT pictures too?\" | Saves stay / Unless you say so. | |\n+| 36 to 38 | | Last check, START, DELETED ...: ... FREED | START, and only START / Then a receipt in REMOVED.TXT. | |\n+| 38 to 40 | | the APA disk with a table cloned from a bigger drive: \"Nothing was deleted\" | Berion's check / A table written from another size of disk is refused. | the forum's own expert, credited |\n+| 40 to 42 | V. Everywhere | the About page: Neutrino beside RiptOPL, the testers and the YouTube thanks | Credits / Neutrino by rickgaiser, beside RiptOPL. | the user asked for both |\n+| 42 to 44 | | STATUS in Japanese | Every word, 33 languages / The new pages too. | |\n+| 44 to 48 | Montage | half-bar cuts: covers, the search summary, Hidden Games, the plan, STATUS, Adapt | | the energy before the outro |\n+| 60 to 62 | Outro | \"Rip it.\" over the list | | |\n+| end | End card | v1.1.4 beta, build 114; github; psx-place; testers with lukeatmynose and the YouTube thanks; built on RiptOPL, Neutrino, POPSTARTER, Ember, PS2BBL; music line | | |\n+\n+The iLink fixes from the RiptOPL merge are a line on the end card (\"i.LINK: Neutrino and POPSTARTER, from RiptOPL\"),\n+not a shot: the lab has no iLink and the reel only shows what was filmed.\n+\n+### Takes (the reel kit's `tools/takes114.ps1`, hidden lab, `rec` captures)\n+Disk `t114-reel.raw`: a copy of the reel library (the six DVD backups with covers, themes, MEDIA) with the launchers\n+added in odd places (BACKUP/PS1 POPSTARTER and the POPS files, APPS/Ember with its bios.bin, TOOLS/neutrino), so\n+nothing on `ripps2-reel108.raw` changes. `t114-apa.raw` with an HDL game for the APA shots, and `t114-apa-clone.raw`,\n+the same table on a smaller disk, for the refusal.\n+\n+| Take | Disk | Card | Walk |\n+|---|---|---|---|\n+| v_master | t114-reel | allgames.bin from a first boot | boot, rec from the first frame through the handover |\n+| v_manual | t114-reel | the same, usb_mode=1 | boot, rec, the list whole and still |\n+| v_seance | t114-reel | fresh | START, STATUS, scroll, LOOK FOR THEM, Cross, Cross, the summary, STATUS again |\n+| v_cache | t114-reel | after v_seance (KeepCard) | Settings > Game Sources > Cache, Look now |\n+| v_hide | t114-reel | fresh | Triangle on Burnout 3, Hide Game, Cross, the list, START > Settings > Game Sources > Hidden Games, SHOW IT AGAIN |\n+| v_delete | t114-reel | fresh | Triangle on a game with CFG, art and cheats, Delete, Cross, Circle, START |\n+| v_apa_no | t114-apa-clone | fresh | Triangle, Delete: the refusal |\n+| v_about | t114-reel | fresh | START, About, scrolled to the credits |\n+| v_lang_ja | t114-reel | language Japanese | START, STATUS |\n+\n+## 3. The first post's gallery: a tour, not a changelog\n+\n+Recaptured on build 114 in one sitting (same disk, same cards), cropped to the PS2 frame, 1284x962 JPEG, two to a row\n+in the spoiler. Order is what a newcomer should see, the newest feature last:\n+\n+| # | Frame | Theme | Why here |\n+|---|---|---|---|\n+| 01 | Storage, All Games, covers and the info panel | RIPPS2 (the towers) | the first look: a front end full of games |\n+| 02 | the game's info page: badges, details, a RIPSHOT screenshot | RIPPS2 | how deep one game goes |\n+| 03 | Adapt with its frosted bar | Adapt | the house theme, a different mood |\n+| 04 | RIPGRID's cover wall | RIPGRID | the library at a glance |\n+| 05 | RIPFLOW's carousel | RIPFLOW | a third way to browse |\n+| 06 | Launch Disc with RIPDISC's panel | RIPPS2 | the disc pillar |\n+| 07 | Memory Files: the orbits and a save's 3D icon | RIPPS2 | the files pillar |\n+| 08 | the in-game menu over a game (RIPSHOT) | | it reaches into the game |\n+| 09 | STATUS: drives, launchers | RIPPS2 | what it sees, new in 114 |\n+| 10 | Settings > Game Sources with Hidden Games and Cache | RIPPS2 | the settings, plainly worded |\n+| 11 | the START menu or STATUS in Japanese | RIPPS2 | 33 languages |\n+| 12 | Delete's plan screen | RIPPS2 | the newest, last |\n+\n+Frames 01 to 07 and 09 to 12 are new grabs; 08 is a still from the v1.0.8 reel's in-game capture (the lab cannot\n+start a game for a still, and the in-game menu has not changed since 108). Files: `media/b114/tour-NN-*.jpg`.\n+\n+## 4. The bump's pictures (the v1.1.4 reply)\n+Five, in the reel's order: All Games before the drive answered (no covers yet), the launcher search, STATUS with the\n+launchers found, Hidden Games, Delete's plan. Files: `media/b114/new-NN-*.jpg`.\n+\n+## 5. Order of work\n+1. Disks (copy, launchers, APA game, the clone). 2. Takes, then contact sheets of each. 3. The tour grabs. 4.\n+`plan114.py`, stills to check every caption, the render, the mix, the thumbnail. 5. psxplace114.txt: the first post\n+with the tour, the bump with the reel slot left for the user's link."
        },
        {
          "sha": "86f09be787118eff61ce90f593d55cf5a2f2b592",
          "filename": "elf/RIPTOPL_UPSTREAM",
          "status": "modified",
          "additions": 1,
          "deletions": 1,
          "changes": 2,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2FRIPTOPL_UPSTREAM",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2FRIPTOPL_UPSTREAM",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2FRIPTOPL_UPSTREAM?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -1 +1 @@\n-3cd7f89c8239485e28f5343caad6beaa594d3478 4032\n+57c45b4fcc922ba00fed949c634caea0a2900c63 4224"
        },
        {
          "sha": "bffd76cb3ca2b1910ec6792b7fe3067b73bae4eb",
          "filename": "elf/build.sh",
          "status": "modified",
          "additions": 14,
          "deletions": 5,
          "changes": 19,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Fbuild.sh",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Fbuild.sh",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Fbuild.sh?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -49,9 +49,13 @@ cp -f \"$HERE/theme/fonts/ripps2_sleek.ttf\" \"$SRC/misc/ripps2_sleek.ttf\"\n cp -f \"$HERE/theme/fonts/ripps2_sleek_bold.ttf\" \"$SRC/misc/ripps2_sleekbold.ttf\"\n cp -f \"$HERE/theme/fonts/ripps2_sleek_case.ttf\" \"$SRC/misc/ripps2_sleekcase.ttf\"\n cp -f \"$HERE/theme/fonts/ripps2_one.ttf\" \"$SRC/misc/ripps2_one.ttf\"\n-# RIPFLOW's fonts (build 70): Roboto Condensed and Bold Condensed, Apache-2.0 (theme/fonts/ROBOTO-LICENSE.txt)\n-cp -f \"$HERE/theme/fonts/ripflow.ttf\" \"$SRC/misc/ripflow.ttf\"\n-cp -f \"$HERE/theme/fonts/ripflow_bold.ttf\" \"$SRC/misc/ripflowbold.ttf\"\n+# RIPFLOW's and Settings' font (build 114): Roboto Condensed 3.015, variable (weights 400 to 700), cut to Latin-1,\n+# SIL OFL 1.1 (theme/fonts/ROBOTO-OFL.txt); made by theme/tools/make_roboto.py\n+cp -f \"$HERE/theme/fonts/ripps2_roboto.ttf\" \"$SRC/misc/ripps2roboto.ttf\"\n+# build 114: the compiled-in fonts without what FreeType never reads (glyph names, extra name records, signatures,\n+# substitution tables); outlines, hinting and kerning stay, and a font whose outlines would change is left whole\n+python3 -c \"import fontTools\" 2>/dev/null || pip3 install --quiet --break-system-packages fonttools || true\n+python3 \"$HERE/theme/tools/strip_fonts.py\" \"$SRC\"/misc/ripps2_font.ttf \"$SRC\"/misc/ripps2_sleek*.ttf \"$SRC\"/misc/ripps2_one.ttf \"$SRC\"/misc/ripps2roboto.ttf || true\n # The Memory Files ELF, compiled in and launched from RAM (patch 0004)\n # Built-in textures: glass info panel and PS2 selector mark (patch 0006)\n cp -f \"$HERE\"/theme/gfx/*.png \"$SRC/gfx/\"\n@@ -62,9 +66,9 @@ cp -f \"$HERE/bible/MX4SIO/SYSTEM.XLF\" \"$SRC/misc/bible_mx4sio_system.xlf\"\n # BIBLE on the HDD (build 73): PS2BBL 1.2.0's HDD build, HSYSTEM.XLF from the same release, unchanged\n cp -f \"$HERE/bible/HSYSTEM.XLF\" \"$SRC/misc/bible_hdd_system.xlf\"\n # Ember and POPSTARTER are the user's own from build 113, found and launched, not carried (elf/ember, elf/popstarter)\n-# Neutrino 1.8.0 (build 77): rickgaiser's release folder as a gzip'd tar, AFL-3.0 (elf/neutrino/README.md);\n+# Neutrino (build 77; build 115: its development build v1.8.0-61-g7be8de2): the release folder as a gzip'd tar, AFL-3.0 (elf/neutrino/README.md);\n # RIPPS2 installs it where the user picks when a Neutrino launch finds none\n-cp -f \"$HERE/neutrino/neutrino-1.8.0.tar.gz\" \"$SRC/misc/neutrino.tar.gz\"\n+cp -f \"$HERE/neutrino/neutrino-1.8.0-61-g7be8de2.tar.gz\" \"$SRC/misc/neutrino.tar.gz\"\n # The build number shown in About: the number of the last patch (build N is patch N); beta from build 100\n LAST=\"$(ls \"$HERE\"/patches/0*.patch | tail -n1)\"\n NUM=\"$(basename \"$LAST\" | cut -c1-4 | sed 's/^0*//')\"\n@@ -129,6 +133,10 @@ fi\n \n cd \"$SRC\"\n sh .github/scripts/install_coherent_mmce.sh\n+# build 114: RiptOPL's own releases rebuild the SDK's exFAT/FAT driver with its fragment walker guard (#986: a corrupt\n+# chain fails the launch instead of handing the game's driver wrong sectors to write a VMC to) and the contiguous VMC\n+# reserve genvmc asks for. RIPPS2 shipped the stock driver until now (FifthFox's corrupted drives, 10/22)\n+python3 .github/scripts/install_safe_bdmfs.py\n make clean\n # DIRTY= : the tree is RiptOPL plus our patches by design, so the version never says -dirty\n # RIPPS2_MAKE_FLAGS: extra make flags, e.g. DEBUG=1 EESIO_DEBUG=1 for a build that logs over EE SIO\n@@ -162,4 +170,5 @@ python3 \"$HERE/lang/ripps2lang.py\" extract \"$SRC\" --check || echo \"::warning::el\n # fontTools cuts the big CJK font down to the letters a language uses (without it the pack carries it whole)\n python3 -c \"import fontTools\" 2>/dev/null || pip3 install --quiet --break-system-packages fonttools || true\n python3 \"$HERE/lang/ripps2lang.py\" pack \"$SRC\" \"$OUT\"\n+\n ls -la \"$OUT\""
        },
        {
          "sha": "e0b52006723e1be39e72a30870ad0e427b4da897",
          "filename": "elf/lang/Albanian.yml",
          "status": "modified",
          "additions": 208,
          "deletions": 22,
          "changes": 230,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FAlbanian.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FAlbanian.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FAlbanian.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: Albanian\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTER PRANON, ESC ANULON. \u00c7DO BUTON SHFAQ TASTET.\n   USB KEYBOARD: TASTIER\u00cb USB\n-  '      DESR DVR ISOs (experimental)': '      ISO t\u00eb DESR DVR (eksperimentale)'\n-  '      Internal HDD (exFAT)': '      HDD i brendsh\u00ebm (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (eksperimentale)'\n-  '      Protocol': '      Protokolli'\n   Access: Qasja\n   Applications Page Start Mode: M\u00ebnyra e nisjes s\u00eb faqes s\u00eb aplikacioneve\n   Art Delay: Vonesa e imazheve\n@@ -22,7 +18,6 @@ strings:\n   Fifth: I pesti\n   First: I pari\n   Fourth: I kat\u00ebrti\n-  HDD (APA) Start Mode: M\u00ebnyra e nisjes s\u00eb HDD (APA)\n   HDD Cache: Memoria e p\u00ebrkohshme e HDD\n   HDD Spindown: Ndalimi i HDD\n   MMCE Settings: Cil\u00ebsimet e MMCE\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: Kjo \u00ebsht\u00eb loj\u00eb PS2 e arnuar me ESR. Vendos ESR (esr*.elf) n\u00eb APPS, BOOT ose ESR n\u00eb nj\u00eb kart\u00eb memorieje ose USB p\u00ebr ta nisur.\n   '%s, BUILD %d': '%s, ND\u00cbRTIMI %d'\n   '... and the anonymous ...': '... dhe t\u00eb panjohurit ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': '1.8.0 i p\u00ebrfshir\u00eb pa ndryshime (AFL-3.0): github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: Nj\u00eb \u00e7montim dhe rind\u00ebrtim i RiptOPL, deg\u00ebzimit t\u00eb Open PS2 Loader,\n   ABOUT: RRETH\n   'ACHIEVEMENTS: SPIRIT OF RA': 'ARRITJET: SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): Video AVI n\u00eb Skedar\u00ebt e memories, deg\u00ebzimi i RIPPS2 (AFL 2.0)\n   Apollo Save Tool by bucanero: Apollo Save Tool nga bucanero\n   BACK: PRAPA\n+  Back (keeps both): Prapa (i mban t\u00eb dyja)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: Kjo kart\u00eb MMCE nuk mund t\u00eb riem\u00ebrtoj\u00eb nj\u00eb skedar, gj\u00eb q\u00eb ngjeshja e k\u00ebrkon n\u00eb hapin e fundit. P\u00ebrdit\u00ebsoni firmware-in e saj sd2psXtd n\u00eb 1.4.0 ose m\u00eb t\u00eb ri (sd2psxtd.github.io/download), pastaj provoni p\u00ebrs\u00ebri. Asgj\u00eb nuk u ndryshua.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): BIBLE \u00ebsht\u00eb PS2BBL 1.2.0, nd\u00ebrtimet e n\u00ebnshkruara t\u00eb El Isra (kart\u00eb dhe HDD), pa ndryshime (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: Tingujt e BIOS > Nxirr i luan si\u00e7 b\u00ebn SPU2; asnj\u00eb BIOS nuk p\u00ebrfshihet\n   BOOT LOCK AND BIBLE: BOOT LOCK DHE BIBLE\n   BOOT LOCK edits its config too: BOOT LOCK ndryshon edhe konfigurimin e tij\n   BUILD DETAILS: DETAJET E ND\u00cbRTIMIT\n-  BUILT ON RIPTOPL: ND\u00cbRTUAR MBI RIPTOPL\n   BearSSL by Thomas Pornin: BearSSL nga Thomas Pornin\n   Built with Claude (Anthropic): Nd\u00ebrtuar me Claude (Anthropic)\n   CD PLAYER: LUAJT\u00cbSI I CD\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek dhe Sleek Bold, vizatuar p\u00ebr RIPPS2\n   RetroAchievements on the console by hacan359 (yoba): RetroAchievements n\u00eb konsol\u00eb nga hacan359 (yoba)\n   RiptOPL by NathanNeurotic: RiptOPL nga NathanNeurotic\n-  Roboto Condensed by Google (Apache-2.0): Roboto Condensed nga Google (Apache-2.0)\n   SOUNDS: TINGUJT\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: Instaluesi FMCB i SP193 dhe OSDMenu i pcm720, ndjekur hap pas hapi\n   SUPPORT: MB\u00cbSHTETJA\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: wLaunchELF R3Z nga NathanNeurotic\n   wLaunchELF by the ps2homebrew team: wLaunchELF nga ekipi ps2homebrew\n   what RIPPS2's card image writes are checked against: me \u00e7far\u00eb kontrollohen imazhet e kartave q\u00eb shkruan RIPPS2\n-  with its own look, launchers, file browser and save tools.: me pamjen, l\u00ebshuesit, shfletuesin e skedar\u00ebve dhe mjetet e ruajtjeve t\u00eb veta.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: xeRAbora nga hacan359, rcheevos nga RetroAchievements\n   your console's own, ripped from its BIOS: tingujt e vet\u00eb konsol\u00ebs t\u00ebnde, nxjerr\u00eb nga BIOS-i i saj\n   ' (no DNS answer)': ' (asnj\u00eb p\u00ebrgjigje DNS)'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO: duke shkruar %.60s'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: Neutrino nuk u shkrua dot n\u00eb %s. Kontrollo hap\u00ebsir\u00ebn e lir\u00eb dhe q\u00eb t\u00eb mos jet\u00eb i mbrojtur nga shkrimi, pastaj provo p\u00ebrs\u00ebri.\n   THE GAME'S DRIVE  (%s/neutrino): NJ\u00cbSIA E LOJ\u00cbS  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : Kjo loj\u00eb niset p\u00ebrmes Neutrino, dhe nuk ka Neutrino aty ku k\u00ebrkon RIPPS2 (Cil\u00ebsimet, nj\u00ebsia e loj\u00ebs, kartat e memories). RIPPS2 mbart Neutrino 1.8.0 nga rickgaiser (AFL-3.0). Ta instaloj tani? M\u00eb pas zgjedh ku.\n   '%d / %d FILES': '%d / %d SKEDAR\u00cb'\n   '%d FOLDERS AND CONTENTS': '%d DOSJE DHE P\u00cbRMBAJTJA'\n   '%d FOLDERS, %d FOUND': '%d DOSJE, %d T\u00cb GJETURA'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d nga %d ende t\u00eb ky\u00e7ura'\n   '%s   on %s': '%s   n\u00eb %s'\n   BADGES: DISTINKTIV\u00cbT\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: Distinktiv\u00ebt punojn\u00eb vet\u00ebm me TELEMETRIN\u00cb ndezur dhe xeRAbora n\u00eb pun\u00eb n\u00eb kompjuterin t\u00ebnd; deri at\u00ebher\u00eb nuk kushtojn\u00eb asgj\u00eb.\n   CHECK THE DISC: KONTROLLO DISKUN\n   From this game's last CHECK GAME SUPPORT.: Nga KONTROLLI I FUNDIT I MB\u00cbSHTETJES S\u00cb LOJ\u00cbS p\u00ebr k\u00ebt\u00eb loj\u00eb.\n   HOW IT WORKS: SI FUNKSIONON\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: N\u00eb k\u00ebt\u00eb konsol\u00eb \u00ebsht\u00eb nj\u00eb PS2BBL me ky\u00e7in e RIPPS2.\n   AUDIO folder: dosja AUDIO\n   DRIVES: NJ\u00cbSIT\u00cb\n-  INSTALL RIPPS2'S COPY: INSTALO KOPJEN E RIPPS2\n-  'Installed: ': 'Instaluar: '\n   MANAGE: MENAXHO\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: MENAXHO tregon \u00e7far\u00eb nis \u00e7do kart\u00eb memorieje, dhe instalon ose riparon BIBLE.\n   MEMORY CARD 0  (about 600 KB): KARTA E MEMORIES 0  (rreth 600 KB)\n   MEMORY CARD 1  (about 600 KB): KARTA E MEMORIES 1  (rreth 600 KB)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO: drajveri i tij nuk u ngarkua (kodi %d).'\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO: drajveri \u00ebsht\u00eb ngarkuar, por asnj\u00eb kart\u00eb SD nuk u p\u00ebrgjigj (foleja mc1).'\n   NEUTRINO SETTINGS: CIL\u00cbSIMET E NEUTRINO\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': 'Ende asnj\u00eb: RIPPS2 nuk ka tinguj t\u00eb vet\u00ebt. Nxirr ata t\u00eb k\u00ebsaj konsole, ose b\u00ebji n\u00eb RIPPS2 Audio Studio.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: Nuk \u00ebsht\u00eb instaluar. Nj\u00eb loj\u00eb q\u00eb ka nevoj\u00eb p\u00ebr t\u00eb ofron kopjen e RIPPS2, ose instaloje k\u00ebtu.\n   POPSTARTER NETWORK: RRJETI I POPSTARTER\n   POPSTARTER and Ember: POPSTARTER dhe Ember\n   PS1 SETTINGS: CIL\u00cbSIMET E PS1\n   PS2BBL 1.2.0 for RIPPS2: PS2BBL 1.2.0 p\u00ebr RIPPS2\n   RIP THIS CONSOLE'S SOUNDS: NXIRR TINGUJT E K\u00cbSAJ KONSOLE\n-  RIPPS2's homebrew: Homebrew i RIPPS2\n   STATUS: GJENDJA\n-  'STATUS: looking for RIPPS2''s homebrew': 'GJENDJA: duke k\u00ebrkuar homebrew-n e RIPPS2'\n   a drive of unknown kind: nj\u00eb nj\u00ebsi e llojit t\u00eb panjohur\n-  a drive or a memory card: nj\u00eb nj\u00ebsi ose nj\u00eb kart\u00eb memorieje\n   boot loader, card by card: ngarkuesi i nisjes, kart\u00eb pas karte\n   from its BIOS, into AUDIO: nga BIOS-i i saj, n\u00eb AUDIO\n   internal HDD (exFAT): HDD i brendsh\u00ebm (exFAT)\n@@ -975,10 +958,193 @@ strings:\n   Sidebar: Shiriti an\u00ebsor\n   your own copy, found on your drives and memory cards: kopja jote, e gjetur n\u00eb nj\u00ebsit\u00eb dhe kartat e tua t\u00eb kujtes\u00ebs\n   'your own, found and launched: github.com/Gageformer/Ember/releases': 'e jotja, e gjetur dhe e nisur: github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': 'Nuk u gjet n\u00eb mass0: (%s). Vendos ember.elf dhe bios.bin-in t\u00ebnd n\u00eb dosjen e tij EMBER.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: Nuk u gjet. Vendos POPSTARTER.ELF-in t\u00ebnd n\u00eb dosjen POPS t\u00eb nj\u00eb nj\u00ebsie.\n   Launch Disc: Nis diskun\n   Memory Files: Skedar\u00ebt e memories\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: RIPPS2 gjeti %d nga 5. Secili mbahet mend, dhe \u00e7do nisje e lexon prej andej.\n+  not found on the started drives: nuk u gjet n\u00eb nj\u00ebsit\u00eb e nisura\n+  not looked for yet: ende nuk \u00ebsht\u00eb k\u00ebrkuar\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s nuk u p\u00ebrgjigj, ndaj %s nuk mund t\u00eb niset. A \u00ebsht\u00eb lidhur?'\n+  '%s is not on %s any more, so it has left All Games.': '%s nuk \u00ebsht\u00eb m\u00eb n\u00eb %s, ndaj doli nga T\u00eb gjitha loj\u00ebrat.'\n+  STARTING %s: PO NIS %s\n+  the SMB share: ndarja SMB\n+  the drive: nj\u00ebsia\n+  the internal HDD (APA): HDD i brendsh\u00ebm (APA)\n+  the internal HDD (exFAT): HDD i brendsh\u00ebm (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: ND\u00cbRTUAR MBI RIPTOPL, ME NEUTRINO\n+  'Special thanks to all the YouTube love as well:': 'Fal\u00ebnderime t\u00eb ve\u00e7anta edhe p\u00ebr gjith\u00eb dashurin\u00eb nga YouTube:'\n+  and its own look, launchers, file browser and save tools.: dhe me pamjen, nis\u00ebsit, shfletuesin e skedar\u00ebve dhe mjetet e ruajtjeve t\u00eb veta.\n+  glad to help however you reach me.: i lumtur t\u00eb ndihmoj, si do q\u00eb t\u00eb m\u00eb gjeni.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): b\u00ebrthama e dyt\u00eb e loj\u00ebrave, 1.8.0 e pandryshuar (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: me Neutrino t\u00eb rickgaiser brenda si b\u00ebrtham\u00eb e dyt\u00eb e loj\u00ebrave,\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : 'Kjo loj\u00eb niset p\u00ebrmes Neutrino, dhe RIPPS2 nuk gjeti asnj\u00eb: as te shtegu i Cil\u00ebsimeve, as n\u00eb nj\u00ebsin\u00eb e loj\u00ebs, as n\u00eb kartat e memories, as n\u00eb ndonj\u00eb nj\u00ebsi t\u00eb nisur. RIPPS2 mbart Neutrino 1.8.0 nga rickgaiser (AFL-3.0): instaloje nj\u00eb her\u00eb, ku t\u00eb duash, dhe \u00e7do loj\u00eb Neutrino e p\u00ebrdor prej andej.'\n+  STARTING THE DRIVES: PO NIS NJ\u00cbSIT\u00cb\n+  '%d games, on the started drives': '%d loj\u00ebra, n\u00eb nj\u00ebsit\u00eb e nisura'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s: DISA SKEDAR\u00cb NUK U FSHIN\u00cb DOT'\n+  '%u partitions, all in order': '%u ndarje, t\u00eb gjitha n\u00eb rregull'\n+  ', %d art images': ', %d imazhe kopertinash'\n+  ', and ': ', dhe '\n+  ', its cheats': ', kodet e saj'\n+  ', its settings': ', cil\u00ebsimet e saj'\n+  1 game, on the started drives: 1 loj\u00eb, n\u00eb nj\u00ebsit\u00eb e nisura\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: Nj\u00eb loj\u00eb PS1 k\u00ebtu fshihet te Skedar\u00ebt e memories, ku \u00ebsht\u00eb skedar m\u00eb vete.\n+  A network catalogue's games are deleted on the PC that serves them.: Loj\u00ebrat e nj\u00eb katalogu rrjeti fshihen n\u00eb PC-n\u00eb q\u00eb i sh\u00ebrben.\n+  DELETE FOR GOOD: FSHIJE P\u00cbRGJITHMON\u00cb\n+  DELETE IT: FSHIJE\n+  'DELETED %s: %s FREED': 'U FSHI %s: U LIRUAN %s'\n+  'Delete %s from %s? Goes: %s.': 'T\u00eb fshihet %s nga %s? Ik\u00ebn: %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': 'T\u00eb fshihet %s nga %s? Ik\u00ebn: %s. Mbetet: %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: T\u00eb fshihen edhe ruajtjet dhe fotot RIPSHOT t\u00eb saj? Jo i mban.\n+  HIDDEN GAMES: LOJ\u00cbRA T\u00cb FSHEHURA\n+  'HIDDEN: %s': 'U FSHEH: %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: T\u00eb fshehet %s? Del nga \u00e7do list\u00eb, edhe nga T\u00eb gjitha loj\u00ebrat dhe T\u00eb Preferuarat. Cil\u00ebsimet > Burimet e loj\u00ebrave > Loj\u00ebra t\u00eb fshehura e shfaq p\u00ebrs\u00ebri.\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: Kontrolli i fundit. %s fshihet p\u00ebrgjithmon\u00eb, dhe asgj\u00eb nuk e kthen dot. Shtyp START p\u00ebr ta fshir\u00eb.\n+  None. Hide Game, in a game's options, puts one here.: Asnj\u00eb. Fshih loj\u00ebn, te opsionet e nj\u00eb loje, vendos nj\u00eb k\u00ebtu.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : 'Asgj\u00eb nuk u fshi: tabela e ndarjeve e k\u00ebsaj nj\u00ebsie nuk i p\u00ebrshtatet k\u00ebtij disku (%s). Mund t\u00eb jet\u00eb shkruar nga imazhi i nj\u00eb disku me madh\u00ebsi tjet\u00ebr. Riparoje s\u00eb pari n\u00eb nj\u00eb PC.'\n+  RIPPS2 cannot delete a game from this page.: RIPPS2 nuk mund t\u00eb fshij\u00eb nj\u00eb loj\u00eb nga kjo faqe.\n+  RIPPS2 could not find this game's files on its drive.: RIPPS2 nuk i gjeti skedar\u00ebt e k\u00ebsaj loje n\u00eb nj\u00ebsin\u00eb e saj.\n+  Reading the hidden lists: Po lexoj listat e fshehura\n+  SHOW AGAIN OR DELETE: SHFAQE P\u00cbRS\u00cbRI OSE FSHIJE\n+  SHOW IT AGAIN: SHFAQE P\u00cbRS\u00cbRI\n+  That game is not on its drive any more.: Ajo loj\u00eb nuk \u00ebsht\u00eb m\u00eb n\u00eb nj\u00ebsin\u00eb e saj.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: Lista e fshehur nuk u shkrua dot n\u00eb nj\u00ebsin\u00eb e loj\u00ebs. A \u00ebsht\u00eb e mbrojtur nga shkrimi, apo Operacionet e Shkrimit jan\u00eb fikur?\n+  The partition could not be removed (%d). Nothing was deleted.: Ndarja nuk u hoq dot (%d). Asgj\u00eb nuk u fshi.\n+  This drive cannot keep a hidden list (a network catalogue).: Kjo nj\u00ebsi nuk mund t\u00eb mbaj\u00eb list\u00eb t\u00eb fshehur (katalog rrjeti).\n+  This game's drive has not answered yet. Wait for it, then try again.: Nj\u00ebsia e k\u00ebsaj loje nuk \u00ebsht\u00eb p\u00ebrgjigjur ende. Prite, pastaj provo p\u00ebrs\u00ebri.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: Ndarja e k\u00ebsaj loje \u00ebsht\u00eb nga ato q\u00eb RIPPS2 nuk i heq (ndarje sistemi, ose ajo me cil\u00ebsimet e tij).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': 'nj\u00eb ndarje \u00ebsht\u00eb m\u00eb e madhe se sa lejon ky disk: tabela u b\u00eb p\u00ebr nj\u00eb disk me madh\u00ebsi tjet\u00ebr'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': 'nj\u00eb ndarje kalon fundin e k\u00ebtij disku (sektori %u nga %u): tabela u b\u00eb p\u00ebr nj\u00eb disk m\u00eb t\u00eb madh'\n+  its RIPSHOT pictures: fotot RIPSHOT t\u00eb saj\n+  its drive: nj\u00ebsia e saj\n+  its saves: ruajtjet e saj\n+  sector %u holds no partition header: sektori %u nuk ka kok\u00eb ndarjeje\n+  the chain does not close at __mbr: zinxhiri nuk mbyllet te __mbr\n+  the chain skips from sector %u: zinxhiri k\u00ebrcen nga sektori %u\n+  the chain's links disagree at sector %u: hallkat e zinxhirit nuk p\u00ebrputhen te sektori %u\n+  the drive's size could not be read: madh\u00ebsia e nj\u00ebsis\u00eb nuk u lexua dot\n+  the game (%s): loja (%s)\n+  the game's folder and everything in it (%s): dosja e loj\u00ebs dhe gjith\u00e7ka n\u00eb t\u00eb (%s)\n+  the header at sector %u could not be read: koka te sektori %u nuk u lexua dot\n+  the header at sector %u fails its checksum: koka te sektori %u nuk e kalon shum\u00ebn e kontrollit\n+  the partition at sector %u is not where or as large as APA puts one: ndarja te sektori %u nuk \u00ebsht\u00eb aty ku, as aq e madhe sa, e vendos APA\n+  the settings and art another copy of it uses: cil\u00ebsimet dhe kopertinat q\u00eb p\u00ebrdor nj\u00eb kopje tjet\u00ebr e saj\n+  '%d FOLDERS': '%d DOSJE'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': '%s nuk u kopjua dot n\u00eb %s. A \u00ebsht\u00eb nj\u00ebsia plot apo e mbrojtur nga shkrimi?'\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s nuk \u00ebsht\u00eb aty ku k\u00ebrkon RIPPS2. Ta k\u00ebrkoj n\u00eb nj\u00ebsit\u00eb q\u00eb ka nisur RIPPS2? Vendi ku gjendet mbahet mend p\u00ebr \u00e7do nisje pas saj.'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Ember nuk \u00ebsht\u00eb n\u00eb %s, pran\u00eb loj\u00ebrave, dhe duhet t\u00eb jet\u00eb. RIPPS2 e gjeti te %s: START e kopjon atje (%d KB) dhe nis loj\u00ebn.'\n+  Ember's BIOS: BIOS-i i Ember\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : 'BIOS-i i Ember (bios.bin) nuk \u00ebsht\u00eb n\u00eb %s, pran\u00eb Ember. RIPPS2 gjeti nj\u00eb te %s: START e kopjon atje (%d KB) dhe nis loj\u00ebn.'\n+  LOOKING FOR YOUR LAUNCHERS: PO K\u00cbRKOJ NIS\u00cbSIT E TU\n+  The POPS files: Skedar\u00ebt POPS\n+  '%s  MMCE card': '%s  kart\u00eb MMCE'\n+  INSTALL RIPPS2'S NEUTRINO: INSTALO NEUTRINO-N E RIPPS2\n+  'Internal HDD (APA): %s free of %s': 'HDD i brendsh\u00ebm (APA): %s t\u00eb lira nga %s'\n+  LAUNCHERS: NIS\u00cbSIT\n+  LOOK FOR THEM: K\u00cbRKOJI\n+  'Memory card %d: %d KB free': 'Karta e memories %d: %d KB t\u00eb lira'\n+  POPS files: Skedar\u00eb POPS\n+  START EVERY DRIVE: NIS \u00c7DO NJ\u00cbSI\n+  'STATUS: looking at the drives and the homebrew': 'GJENDJA: po shoh nj\u00ebsit\u00eb dhe homebrew'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): T\u00eb nisen s\u00eb pari t\u00eb gjitha nj\u00ebsit\u00eb, q\u00eb k\u00ebrkimi t\u00eb arrij\u00eb te t\u00eb gjitha? (Jo k\u00ebrkon n\u00eb nj\u00ebsit\u00eb e nisura tashm\u00eb.)\n+  USB, HDD, MMCE and the rest: USB, HDD, MMCE dhe t\u00eb tjerat\n+  every started drive: \u00e7do nj\u00ebsi e nisur\n+  found where you keep them: u gjet\u00ebn aty ku i mban\n+  not found yet: ende nuk u gjet\n+  not on a started drive: jo n\u00eb nj\u00eb nj\u00ebsi t\u00eb nisur\n+  only if you have none: vet\u00ebm n\u00ebse nuk ke asnj\u00eb\n+  starts every drive first: nis s\u00eb pari \u00e7do nj\u00ebsi\n+  Drives, launchers and homebrew: Nj\u00ebsit\u00eb, nis\u00ebsit dhe homebrew\n+  BDM Devices: Pajisjet BDM\n+  Color Sweep: Kalimi i ngjyrave\n+  Colors and Text: Ngjyrat dhe teksti\n+  HDD POPS partitions: Ndarjet POPS t\u00eb HDD\n+  Layout: Paraqitja\n+  Missing Covers: Kopertina q\u00eb mungojn\u00eb\n+  Name: Emri\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: Ende nuk u gjet asnj\u00eb konfigurim SMB i POPSTARTER. Plot\u00ebso rreshtat m\u00eb posht\u00eb.\n+  POPSTARTER Network: Rrjeti i POPSTARTER\n+  POPSTARTER SMB Network: Rrjeti SMB i POPSTARTER\n+  POPSTARTER UDPFS Network: Rrjeti UDPFS i POPSTARTER\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: RIPPS2 shton vet\u00eb diskun, ISO-n, VMC-t\u00eb dhe m\u00ebnyr\u00ebn e videos.\n+  Screen: Ekrani\n+  Speed: Shpejt\u00ebsia\n+  Sweep Colors: Ngjyrat e kalimit\n+  Text Weight: Trash\u00ebsia e tekstit\n+  Theme Color: Ngjyra kryesore\n+  Bold: T\u00eb trasha\n+  Controllers: Kontrolluesit\n+  Lively: E gjall\u00eb\n+  Medium: Mesatare\n+  PS Emulation: Emulimi PS\n+  Regular: E rregullt\n+  Slow: E ngadalt\u00eb\n+  A MOMENT: NJ\u00cb \u00c7AST\n+  A TASK IN THE BACKGROUND: NJ\u00cb DETYR\u00cb N\u00cb SFOND\n+  ABOUT %d S: RRETH %d S\n+  ANY MOMENT: \u00c7DO \u00c7AST\n+  READING THE GAME LIST OF %s: PO LEXOHET LISTA E LOJ\u00cbRAVE E %s\n+  SAVING A LIST: PO RUHET NJ\u00cb LIST\u00cb\n+  START NOW: NIS TANI\n+  STARTING: PO NIS\n+  STARTS NEXT: NIS M\u00cb PAS\n+  STOPPING IT SAFELY: PO NDALET ME SIGURI\n+  THE LAST OF THE QUEUE: FUNDI I RADH\u00cbS\n+  WHEN IT FINISHES: KUR T\u00cb MBAROJ\u00cb\n+  What each card starts; install or repair BIBLE.: \u00c7far\u00eb nis secila kart\u00eb; instalo ose riparo BIBLE.\n+  Rainbow: Ylber\n+  Custom: E personalizuar\n+  DESR DVR ISOs (experimental): ISO t\u00eb DESR DVR (eksperimentale)\n+  PC CARD CF (experimental): PC CARD CF (eksperimentale)\n+  HDD Start Mode: M\u00ebnyra e nisjes s\u00eb HDD\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): Roboto Condensed nga The Roboto Project Authors (SIL OFL 1.1)\n+  '%s on %s': '%s n\u00eb %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: %s u b\u00eb %s (%d%%), \u00e7do pjes\u00eb u kontrollua. T\u00eb hiqet ISO dhe t\u00eb mbahet ZSO?'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: %s u b\u00eb s\u00ebrish %s, \u00e7do pjes\u00eb u kontrollua. T\u00eb hiqet ZSO dhe t\u00eb mbahet ISO?'\n+  A ZSO of this game is already beside it. Remove one of the two first.: Pran\u00eb saj ka tashm\u00eb nj\u00eb ZSO t\u00eb k\u00ebsaj loje. Hiq nj\u00ebr\u00ebn nga t\u00eb dyja m\u00eb par\u00eb.\n+  About %d min, then checked: Rreth %d min, pastaj kontrollohet\n+  An ISO of this game is already beside it. Remove one of the two first.: Pran\u00eb saj ka tashm\u00eb nj\u00eb ISO t\u00eb k\u00ebsaj loje. Hiq nj\u00ebr\u00ebn nga t\u00eb dyja m\u00eb par\u00eb.\n+  CHECKING EVERY PIECE: PO KONTROLLOHET \u00c7DO PJES\u00cb\n+  COMPRESS TO ZSO: NGJESH N\u00cb ZSO\n+  'COMPRESSED %s: %s FREED': 'U NGJESH %s: U LIRUAN %s'\n+  COMPRESSING 1 OF 2: PO NGJESHET 1 NGA 2\n+  COMPRESSING 2 OF 2: PO NGJESHET 2 NGA 2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : T\u00eb ngjeshet %s? ISO mbetet derisa ZSO t\u00eb lexohet s\u00ebrish dhe t\u00eb kontrollohet, pastaj RIPPS2 pyet para se t\u00eb heq\u00eb ISO-n. Disku ka nevoj\u00eb p\u00ebr vend p\u00ebr t\u00eb dyja gjat\u00eb pun\u00ebs; mbaje konsol\u00ebn ndezur derisa t\u00eb mbaroj\u00eb. Nj\u00eb ZSO niset vet\u00ebm me b\u00ebrtham\u00ebn OPL (jo Neutrino); ruajtjet, cil\u00ebsimet, figurat dhe mashtrimet e saj mbeten me t\u00eb, dhe mund t\u00eb b\u00ebhet s\u00ebrish ISO n\u00eb \u00e7do koh\u00eb.\n+  Compress to ZSO: Ngjesh n\u00eb ZSO\n+  Cycle: Me radh\u00eb\n+  'Experimental: OPL core only (not Neutrino)': 'Eksperimentale: vet\u00ebm b\u00ebrthama OPL (jo Neutrino)'\n+  Hold SELECT: Mbaj SELECT\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: Nuk u p\u00ebrfundua dot (%s). Ajo q\u00eb shkroi u hoq; loja \u00ebsht\u00eb si ishte.\n+  List: List\u00eb\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : T\u00eb b\u00ebhet %s s\u00ebrish ISO? ZSO mbetet derisa ISO t\u00eb lexohet s\u00ebrish dhe t\u00eb kontrollohet, pastaj RIPPS2 pyet para se t\u00eb heq\u00eb ZSO-n. Disku ka nevoj\u00eb p\u00ebr vend p\u00ebr t\u00eb dyja gjat\u00eb pun\u00ebs; mbaje konsol\u00ebn ndezur derisa t\u00eb mbaroj\u00eb. Ruajtjet, cil\u00ebsimet, figurat dhe mashtrimet e saj mbeten me t\u00eb.\n+  Stop? What it has written so far is removed; the game stays as it was.: T\u00eb ndalet? Ajo q\u00eb \u00ebsht\u00eb shkruar deri tani hiqet; loja mbetet si ishte.\n+  THEMES: TEMAT\n+  There is not enough memory for this just now.: Tani p\u00ebr tani nuk ka memorie t\u00eb mjaftueshme p\u00ebr k\u00ebt\u00eb.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: Ky imazh nuk \u00ebsht\u00eb nj\u00eb q\u00eb RIPPS2 mund ta ngjesh\u00eb ose ta zb\u00ebrthej\u00eb (madh\u00ebsia e tij nuk \u00ebsht\u00eb n\u00eb blloqe t\u00eb plota 2 KB).\n+  UNCOMPRESS TO ISO: ZB\u00cbRTHE N\u00cb ISO\n+  UNCOMPRESSED %s: U ZB\u00cbRTHYE %s\n+  UNCOMPRESSING: PO ZB\u00cbRTHEHET\n+  Uncompress to ISO: Zb\u00ebrthe n\u00eb ISO\n+  Under a minute, then checked: N\u00ebn nj\u00eb minut\u00eb, pastaj kontrollohet\n+  COPYING: PO KOPJOHET\n+  SHOW AGAIN, DELETE OR COMPRESS: SHFAQE P\u00cbRS\u00cbRI, FSHIJE OSE NGJESHE\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: SHFAQE P\u00cbRS\u00cbRI, FSHIJE OSE ZB\u00cbRTHEJE\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': '%s ka nevoj\u00eb p\u00ebr rreth %s vend t\u00eb lir\u00eb n\u00eb diskun e vet gjat\u00eb pun\u00ebs (origjinali mbetet deri n\u00eb fund), dhe disku ka %s t\u00eb lir\u00eb.'\n+  Hide and Compress: Fshih dhe ngjesh\n+  'COMPRESSED AND HIDDEN: %s': 'U NGJESH DHE U FSHEH: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': 'Nuk ka vend p\u00ebr k\u00ebt\u00eb kopjim: duhen rreth %s, dhe disku ka %s t\u00eb lir\u00eb.'\n+  OVER 4 GB FOR FAT32: MBI 4 GB P\u00cbR FAT32\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: Ky disk \u00ebsht\u00eb %s, q\u00eb nuk mban skedar\u00eb 4 GB ose m\u00eb shum\u00eb, dhe imazhi i ri do t\u00eb ishte %s.\n+  '%s, %s clusters, %s free of %s': '%s, grupime %s, %s t\u00eb lira nga %s'\n+  Delete This Sweep: Fshi k\u00ebt\u00eb kalim\n+  Delete %s? RIPPS2's own sweeps stay as they are.: T\u00eb fshihet %s? Kalimet e vet\u00eb RIPPS2 mbeten si\u00e7 jan\u00eb.\n+  Sweep: Kalim\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: Distinktiv\u00ebt sh\u00ebnojn\u00eb loj\u00ebrat me arritje n\u00eb listat e tua, me sa ke zhbllokuar. Duan TELEMETRIA t\u00eb ndezur.\n labels:\n   OK: OK\n   EXITTO: Shtegu IGR\n@@ -1224,7 +1390,7 @@ labels:\n   SAVE_ICONS: Ikonat 3D t\u00eb ruajtjeve\n   HINT_SAVE_ICONS: Shfaq ikon\u00ebn e ruajtjes s\u00eb loj\u00ebs s\u00eb zgjedhur, duke u rrotulluar, aty ku tema ka vend p\u00ebr t\u00eb. Lexon s\u00eb pari VMC-n\u00eb e vet\u00eb loj\u00ebs, pastaj kartat e memories.\n   NETBOOT_RESTART_NOW: Protokolli i rrjetit ndryshon pas nj\u00eb rinisjeje. T\u00eb ruhen cil\u00ebsimet dhe t\u00eb riniset OPL tani?\n-  HINT_NET_PROTOCOL: 'Cilin sh\u00ebrbim rrjeti p\u00ebrdor RiptOPL: nj\u00eb dosje t\u00eb ndar\u00eb SMB, UDPFS, UDPBD ose HTTP. I nj\u00ebjti cil\u00ebsim si te Burimet e loj\u00ebrave. Largimi nga nj\u00eb q\u00eb \u00ebsht\u00eb tashm\u00eb n\u00eb pun\u00eb k\u00ebrkon rinisje.'\n+  HINT_NET_PROTOCOL: 'Cilin sh\u00ebrbim rrjeti p\u00ebrdor RiptOPL: nj\u00eb dosje t\u00eb ndar\u00eb SMB, UDPFS, UDPBD ose HTTP. Largimi nga nj\u00eb q\u00eb \u00ebsht\u00eb tashm\u00eb n\u00eb pun\u00eb k\u00ebrkon rinisje.'\n   HINT_UDPFS_ACCESS: Vet\u00ebm UDPFS. Skedar\u00ebt lexon ISO t\u00eb ve\u00e7anta nga dosja e loj\u00ebrave t\u00eb serverit; IMG lexon nj\u00eb imazh disku si nj\u00ebsi. SMB dhe HTTP p\u00ebrdorin gjithmon\u00eb Skedar\u00ebt, UDPBD gjithmon\u00eb IMG.\n   HINT_SMB_VERSION: Me cilin version SMB t\u00eb flitet me dosjen e ndar\u00eb t\u00eb kompjuterit.\n   HINT_NET_ADVANCED: Zhbllokon p\u00ebr ndryshim m\u00ebnyr\u00ebn e lidhjes Ethernet dhe port\u00ebn SMB.\n@@ -1425,3 +1591,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: Si pamjet q\u00eb presin n\u00eb filmin e \u00e7do loje b\u00ebhen PNG.\n   RIPPS2_SETTINGS_HOME: Ruaj cil\u00ebsimet n\u00eb\n   HINT_RIPPS2_SETTINGS_HOME: 'Ku i mban RIPPS2 cil\u00ebsimet: n\u00eb ruajtjen e vet n\u00eb nj\u00eb kart\u00eb memorieje, n\u00eb nj\u00eb dosje RIPPS2 n\u00eb nj\u00eb disk (HDD-ja e brendshme, USB, MX4SIO, iLink ose MMCE) ose n\u00eb nj\u00eb dosje q\u00eb shkruan ti.'\n+  RIPPS2_LAUNCHERS: Nis\u00ebsit\n+  RIPPS2_LOOK_NOW: K\u00ebrko tani\n+  HINT_RIPPS2_LOOK_NOW: RIPPS2 k\u00ebrkon n\u00eb nj\u00ebsit\u00eb e nisura POPSTARTER, skedar\u00ebt POPS, Ember, BIOS-in e Ember dhe Neutrino, dhe mban mend ku \u00ebsht\u00eb secili. \u00c7do nisje i lexon prej andej.\n+  RIPPS2_HIDE_GAME: Fshih loj\u00ebn\n+  RIPPS2_HIDDEN_GAMES: Loj\u00ebra t\u00eb fshehura\n+  HINT_RIPPS2_HIDDEN_GAMES: Loj\u00ebrat e fshehura me Fshih loj\u00ebn, n\u00eb nj\u00ebsit\u00eb e nisura. Shfaq p\u00ebrs\u00ebri nj\u00ebr\u00ebn, ose fshije. \u00c7do nj\u00ebsi mban list\u00ebn e vet.\n+  HINT_RIPPS2_BDM_DEVICES: 'Disqet nga t\u00eb cilat BDM lexon loj\u00ebra: USB, MX4SIO, nj\u00eb PC Card CF, disku DVR i nj\u00eb DESR dhe iLink. HDD i brendsh\u00ebm ndjek m\u00ebnyr\u00ebn e nisjes s\u00eb HDD.'\n+  HINT_RIPPS2_SCREEN: 'Pamja e menys\u00eb: m\u00ebnyra e videos, ekrani i gjer\u00eb, vendi n\u00eb televizor (H-POS, V-POS) dhe overscan.'\n+  HINT_RIPPS2_LAYOUT: Ku q\u00ebndrojn\u00eb kategorit\u00eb dhe si hyn nj\u00eb faqe, pastaj kopertinat e RIPFLOW dhe rrjeta e RIPGRID.\n+  HINT_RIPPS2_COLORS_TEXT: Trash\u00ebsia e tekstit, ngjyra e tem\u00ebs, kalimi i ngjyrave dhe \u00e7do ngjyr\u00eb.\n+  HINT_RIPPS2_TEXT_WEIGHT: 'Sa i trash\u00eb \u00ebsht\u00eb teksti i RIPPS2: k\u00ebshillat e cil\u00ebsimeve, GJENDJA dhe Rreth. E rregullt \u00ebsht\u00eb si m\u00eb par\u00eb; Mesatare dhe T\u00eb trasha lexohen m\u00eb mir\u00eb n\u00eb nj\u00eb figur\u00eb t\u00eb but\u00eb.'\n+  HINT_RIPPS2_HDD_START: 'HDD i brendsh\u00ebm, APA ose exFAT: RIPPS2 lexon at\u00eb q\u00eb \u00ebsht\u00eb dhe e shfaq nj\u00eb her\u00eb, si Loj\u00ebra HDD. Joaktiv e l\u00eb t\u00eb qet\u00eb.'\n+  HINT_RIPPS2_THEME_COLOR: Ngjyra e vet\u00eb tem\u00ebs. Zgjidh E personalizuar si ngjyr\u00eb teme p\u00ebr ta zgjedhur vet\u00eb, si ngjyrat e tekstit.\n+  HINT_RIPPS2_SWEEP: Pes\u00eb ngjyra n\u00ebp\u00ebr t\u00eb cilat kalon tema, ngadal\u00eb dhe but\u00eb. Ylber, nj\u00eb kalim nga \u00e7do tem\u00eb ngjyre, ose i yti. Joaktiv e mban tem\u00ebn t\u00eb pal\u00ebvizur.\n+  HINT_RIPPS2_SWEEP_COLORS: Pes\u00eb ngjyrat e kalimit dhe shpejt\u00ebsia e tij. Ndrysho nj\u00eb t\u00eb RIPPS2 dhe ruhet si i yti, me emrin q\u00eb i jep.\n+  HINT_RIPPS2_SWEEP_NAME: Si quhet kalimi yt n\u00eb list\u00ebn Kalimi i ngjyrave.\n+  HINT_RIPPS2_SWEEP_COLOR: Nj\u00eb nga pes\u00eb ngjyrat n\u00ebp\u00ebr t\u00eb cilat kalon kalimi, me radh\u00eb.\n+  HINT_RIPPS2_SWEEP_SPEED: 'Sa zgjat \u00e7do ngjyr\u00eb para se t\u00eb shfaqet tjetra. Edhe E gjall\u00eb mbetet e but\u00eb: ngjyrat nuk ndezin kurr\u00eb.'\n+  RIPPS2_HIDE_COMPRESS: Fshih loj\u00ebn dhe ngjeshja\n+  HINT_RIPPS2_SELECT_HOLD: 'SELECT i mbajtur 4.2 s n\u00eb nj\u00eb list\u00eb loj\u00ebrash. List\u00eb: temat n\u00eb nj\u00eb panel, zgjidh nj\u00eb. Me radh\u00eb: tema tjet\u00ebr. Fikur: asgj\u00eb (nj\u00eb shtypje ende rifreskon).'"
        },
        {
          "sha": "c302be3f07dc4adf536a7fb876a89260261b56d1",
          "filename": "elf/lang/Arabic.yml",
          "status": "modified",
          "additions": 209,
          "deletions": 22,
          "changes": 231,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FArabic.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FArabic.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FArabic.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: Arabic\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTER \u0644\u0644\u0642\u0628\u0648\u0644\u060c ESC \u0644\u0644\u0625\u0644\u063a\u0627\u0621. \u0623\u064a \u0632\u0631 \u064a\u0639\u0631\u0636 \u0627\u0644\u0645\u0641\u0627\u062a\u064a\u062d.\n   USB KEYBOARD: \u0644\u0648\u062d\u0629 \u0645\u0641\u0627\u062a\u064a\u062d USB\n-  '      DESR DVR ISOs (experimental)': '      \u0645\u0644\u0641\u0627\u062a DESR DVR ISO (\u062a\u062c\u0631\u064a\u0628\u064a)'\n-  '      Internal HDD (exFAT)': '      \u0627\u0644\u0642\u0631\u0635 \u0627\u0644\u0635\u0644\u0628 \u0627\u0644\u062f\u0627\u062e\u0644\u064a (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (\u062a\u062c\u0631\u064a\u0628\u064a)'\n-  '      Protocol': '      \u0627\u0644\u0628\u0631\u0648\u062a\u0648\u0643\u0648\u0644'\n   Access: \u0627\u0644\u0648\u0635\u0648\u0644\n   Applications Page Start Mode: \u0648\u0636\u0639 \u062a\u0634\u063a\u064a\u0644 \u0635\u0641\u062d\u0629 \u0627\u0644\u0628\u0631\u0627\u0645\u062c\n   Art Delay: \u062a\u0623\u062e\u064a\u0631 \u0627\u0644\u0635\u0648\u0631\n@@ -22,7 +18,6 @@ strings:\n   Fifth: \u0627\u0644\u062e\u0627\u0645\u0633\n   First: \u0627\u0644\u0623\u0648\u0644\n   Fourth: \u0627\u0644\u0631\u0627\u0628\u0639\n-  HDD (APA) Start Mode: \u0648\u0636\u0639 \u062a\u0634\u063a\u064a\u0644 \u0627\u0644\u0642\u0631\u0635 \u0627\u0644\u0635\u0644\u0628 (APA)\n   HDD Cache: \u0630\u0627\u0643\u0631\u0629 \u0627\u0644\u0642\u0631\u0635 \u0627\u0644\u0635\u0644\u0628 \u0627\u0644\u0645\u0624\u0642\u062a\u0629\n   HDD Spindown: \u0625\u064a\u0642\u0627\u0641 \u062f\u0648\u0631\u0627\u0646 \u0627\u0644\u0642\u0631\u0635 \u0627\u0644\u0635\u0644\u0628\n   MMCE Settings: \u0625\u0639\u062f\u0627\u062f\u0627\u062a MMCE\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: \u0647\u0630\u0647 \u0644\u0639\u0628\u0629 PS2 \u0645\u0639\u062f\u0651\u0644\u0629 \u0628\u0640 ESR. \u0636\u0639 ESR (esr*.elf) \u0641\u064a APPS \u0623\u0648 BOOT \u0623\u0648 ESR \u0639\u0644\u0649 \u0628\u0637\u0627\u0642\u0629 \u0630\u0627\u0643\u0631\u0629 \u0623\u0648 \u0630\u0627\u0643\u0631\u0629 USB \u0644\u062a\u0634\u063a\u064a\u0644\u0647\u0627.\n   '%s, BUILD %d': '%s\u060c \u0627\u0644\u0628\u0646\u0627\u0621 %d'\n   '... and the anonymous ...': '... \u0648\u0627\u0644\u0645\u062c\u0647\u0648\u0644\u0648\u0646 ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': '\u0627\u0644\u0625\u0635\u062f\u0627\u0631 1.8.0 \u0645\u0631\u0641\u0642 \u062f\u0648\u0646 \u062a\u0639\u062f\u064a\u0644 (AFL-3.0): github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: \u062a\u0641\u0643\u064a\u0643 \u0648\u0625\u0639\u0627\u062f\u0629 \u0628\u0646\u0627\u0621 \u0644\u0640 RiptOPL\u060c \u0646\u0633\u062e\u0629 Open PS2 Loader \u0627\u0644\u0645\u062a\u0641\u0631\u0639\u0629\u060c\n   ABOUT: \u062d\u0648\u0644\n   'ACHIEVEMENTS: SPIRIT OF RA': '\u0627\u0644\u0625\u0646\u062c\u0627\u0632\u0627\u062a: SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): \u0641\u064a\u062f\u064a\u0648 AVI \u0641\u064a \u0645\u0644\u0641\u0627\u062a \u0627\u0644\u0630\u0627\u0643\u0631\u0629\u060c \u0646\u0633\u062e\u0629 RIPPS2 \u0627\u0644\u0645\u062a\u0641\u0631\u0639\u0629 (AFL 2.0)\n   Apollo Save Tool by bucanero: Apollo Save Tool \u0645\u0646 bucanero\n   BACK: \u0631\u062c\u0648\u0639\n+  Back (keeps both): \u0631\u062c\u0648\u0639 (\u064a\u062d\u062a\u0641\u0638 \u0628\u0627\u0644\u0627\u062b\u0646\u064a\u0646)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: \u0644\u0627 \u062a\u0633\u062a\u0637\u064a\u0639 \u0628\u0637\u0627\u0642\u0629 MMCE \u0647\u0630\u0647 \u0625\u0639\u0627\u062f\u0629 \u062a\u0633\u0645\u064a\u0629 \u0645\u0644\u0641\u060c \u0648\u0647\u0648 \u0645\u0627 \u064a\u062d\u062a\u0627\u062c\u0647 \u0627\u0644\u0636\u063a\u0637 \u0641\u064a \u062e\u0637\u0648\u062a\u0647 \u0627\u0644\u0623\u062e\u064a\u0631\u0629. \u062d\u062f\u0651\u062b \u0628\u0631\u0646\u0627\u0645\u062c\u0647\u0627 \u0627\u0644\u062b\u0627\u0628\u062a sd2psXtd \u0625\u0644\u0649 \u0627\u0644\u0625\u0635\u062f\u0627\u0631 1.4.0 \u0623\u0648 \u0623\u062d\u062f\u062b (sd2psxtd.github.io/download)\u060c \u062b\u0645 \u062d\u0627\u0648\u0644 \u0645\u0631\u0629 \u0623\u062e\u0631\u0649. \u0644\u0645 \u064a\u062a\u063a\u064a\u0631 \u0634\u064a\u0621.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): BIBLE \u0647\u0648 PS2BBL 1.2.0\u060c \u0625\u0635\u062f\u0627\u0631\u0627\u062a El Isra \u0627\u0644\u0645\u0648\u0642\u0651\u0639\u0629 (\u0627\u0644\u0628\u0637\u0627\u0642\u0629 \u0648\u0627\u0644\u0642\u0631\u0635 \u0627\u0644\u0635\u0644\u0628)\u060c \u062f\u0648\u0646 \u062a\u063a\u064a\u064a\u0631 (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: \u064a\u0634\u063a\u0651\u0644\u0647\u0627 \u062e\u064a\u0627\u0631 \u0623\u0635\u0648\u0627\u062a BIOS > \u0627\u0633\u062a\u062e\u0631\u0627\u062c \u0643\u0645\u0627 \u064a\u0641\u0639\u0644 SPU2\u061b \u0644\u0627 \u064a\u064f\u0631\u0641\u0642 \u0623\u064a BIOS\n   BOOT LOCK AND BIBLE: BOOT LOCK \u0648BIBLE\n   BOOT LOCK edits its config too: \u064a\u0639\u062f\u0651\u0644 BOOT LOCK \u0625\u0639\u062f\u0627\u062f\u0627\u062a\u0647 \u0623\u064a\u0636\u0627\u064b\n   BUILD DETAILS: \u062a\u0641\u0627\u0635\u064a\u0644 \u0627\u0644\u0628\u0646\u0627\u0621\n-  BUILT ON RIPTOPL: \u0645\u0628\u0646\u064a \u0639\u0644\u0649 RIPTOPL\n   BearSSL by Thomas Pornin: BearSSL \u0645\u0646 Thomas Pornin\n   Built with Claude (Anthropic): \u0628\u064f\u0646\u064a \u0628\u0645\u0633\u0627\u0639\u062f\u0629 Claude (Anthropic)\n   CD PLAYER: \u0645\u0634\u063a\u0644 \u0627\u0644\u0623\u0642\u0631\u0627\u0635 \u0627\u0644\u0645\u062f\u0645\u062c\u0629\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek \u0648Sleek Bold\u060c \u0645\u0631\u0633\u0648\u0645\u0627\u0646 \u0644\u0640 RIPPS2\n   RetroAchievements on the console by hacan359 (yoba): RetroAchievements \u0639\u0644\u0649 \u0627\u0644\u062c\u0647\u0627\u0632 \u0645\u0646 hacan359 (yoba)\n   RiptOPL by NathanNeurotic: RiptOPL \u0645\u0646 NathanNeurotic\n-  Roboto Condensed by Google (Apache-2.0): Roboto Condensed \u0645\u0646 Google (Apache-2.0)\n   SOUNDS: \u0627\u0644\u0623\u0635\u0648\u0627\u062a\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: \u0645\u062b\u0628\u0651\u062a FMCB \u0645\u0646 SP193 \u0648OSDMenu \u0645\u0646 pcm720\u060c \u0645\u064f\u062a\u0651\u0628\u0639\u0627\u0646 \u062e\u0637\u0648\u0629 \u0628\u062e\u0637\u0648\u0629\n   SUPPORT: \u0627\u0644\u062f\u0639\u0645\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: wLaunchELF R3Z \u0645\u0646 NathanNeurotic\n   wLaunchELF by the ps2homebrew team: wLaunchELF \u0645\u0646 \u0641\u0631\u064a\u0642 ps2homebrew\n   what RIPPS2's card image writes are checked against: \u0645\u0627 \u062a\u064f\u0642\u0627\u0631\u0646 \u0628\u0647 \u0635\u0648\u0631 \u0627\u0644\u0628\u0637\u0627\u0642\u0627\u062a \u0627\u0644\u062a\u064a \u064a\u0643\u062a\u0628\u0647\u0627 RIPPS2\n-  with its own look, launchers, file browser and save tools.: \u0628\u0645\u0638\u0647\u0631\u0647 \u0627\u0644\u062e\u0627\u0635 \u0648\u0645\u0634\u063a\u0651\u0644\u0627\u062a\u0647 \u0648\u0645\u062a\u0635\u0641\u062d \u0627\u0644\u0645\u0644\u0641\u0627\u062a \u0648\u0623\u062f\u0648\u0627\u062a \u0627\u0644\u062d\u0641\u0638.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: xeRAbora \u0645\u0646 hacan359\u060c \u0648rcheevos \u0645\u0646 RetroAchievements\n   your console's own, ripped from its BIOS: \u0623\u0635\u0648\u0627\u062a \u062c\u0647\u0627\u0632\u0643 \u0646\u0641\u0633\u0647\u060c \u0645\u0633\u062a\u062e\u0631\u062c\u0629 \u0645\u0646 BIOS \u0627\u0644\u062e\u0627\u0635 \u0628\u0647\n   ' (no DNS answer)': ' (\u0644\u0627 \u064a\u0648\u062c\u062f \u0631\u062f \u0645\u0646 DNS)'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO: \u062c\u0627\u0631\u064d \u0643\u062a\u0627\u0628\u0629 %.60s'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: \u062a\u0639\u0630\u0651\u0631\u062a \u0643\u062a\u0627\u0628\u0629 Neutrino \u0641\u064a %s. \u0627\u0641\u062d\u0635 \u0645\u0633\u0627\u062d\u062a\u0647 \u0627\u0644\u062d\u0631\u0629 \u0648\u062a\u0623\u0643\u062f \u0623\u0646\u0647 \u063a\u064a\u0631 \u0645\u062d\u0645\u064a \u0645\u0646 \u0627\u0644\u0643\u062a\u0627\u0628\u0629\u060c \u062b\u0645 \u062d\u0627\u0648\u0644 \u0645\u0631\u0629 \u0623\u062e\u0631\u0649.\n   THE GAME'S DRIVE  (%s/neutrino): \u0645\u062d\u0631\u0643 \u0627\u0644\u0644\u0639\u0628\u0629  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : \u062a\u0628\u062f\u0623 \u0647\u0630\u0647 \u0627\u0644\u0644\u0639\u0628\u0629 \u0639\u0628\u0631 Neutrino\u060c \u0648\u0644\u0627 \u064a\u0648\u062c\u062f Neutrino \u062d\u064a\u062b \u064a\u0628\u062d\u062b RIPPS2 (\u0627\u0644\u0625\u0639\u062f\u0627\u062f\u0627\u062a\u060c \u0648\u0645\u062d\u0631\u0643 \u0627\u0644\u0644\u0639\u0628\u0629\u060c \u0648\u0628\u0637\u0627\u0642\u0627\u062a \u0627\u0644\u0630\u0627\u0643\u0631\u0629). \u064a\u062d\u0645\u0644 RIPPS2 \u0627\u0644\u0625\u0635\u062f\u0627\u0631 Neutrino 1.8.0 \u0645\u0646 rickgaiser (AFL-3.0). \u0647\u0644 \u062a\u0631\u064a\u062f \u062a\u062b\u0628\u064a\u062a\u0647 \u0627\u0644\u0622\u0646\u061f \u0628\u0639\u062f \u0630\u0644\u0643 \u062a\u062e\u062a\u0627\u0631 \u0627\u0644\u0645\u0643\u0627\u0646.\n   '%d / %d FILES': '%d / %d \u0645\u0644\u0641'\n   '%d FOLDERS AND CONTENTS': '%d \u0645\u062c\u0644\u062f \u0648\u0645\u062d\u062a\u0648\u064a\u0627\u062a\u0647\u0627'\n   '%d FOLDERS, %d FOUND': '%d \u0645\u062c\u0644\u062f\u060c \u0648\u064f\u062c\u062f %d'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d \u0645\u0646 %d \u0644\u0627 \u064a\u0632\u0627\u0644 \u0645\u0642\u0641\u0644\u0627\u064b'\n   '%s   on %s': '%s   \u0639\u0644\u0649 %s'\n   BADGES: \u0627\u0644\u0634\u0627\u0631\u0627\u062a\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: \u0644\u0627 \u062a\u0639\u0645\u0644 \u0627\u0644\u0634\u0627\u0631\u0627\u062a \u0625\u0644\u0627 \u0645\u0639 \u062a\u0641\u0639\u064a\u0644 \u0627\u0644\u0642\u064a\u0627\u0633 \u0639\u0646 \u0628\u064f\u0639\u062f \u0648\u062a\u0634\u063a\u064a\u0644 xeRAbora \u0639\u0644\u0649 \u062d\u0627\u0633\u0648\u0628\u0643\u061b \u0648\u062d\u062a\u0649 \u0630\u0644\u0643 \u0627\u0644\u062d\u064a\u0646 \u0644\u0627 \u062a\u0643\u0644\u0651\u0641 \u0634\u064a\u0626\u0627\u064b.\n   CHECK THE DISC: \u0641\u062d\u0635 \u0627\u0644\u0642\u0631\u0635\n   From this game's last CHECK GAME SUPPORT.: \u0645\u0646 \u0622\u062e\u0631 \u0641\u062d\u0635 \u0644\u062f\u0639\u0645 \u0627\u0644\u0644\u0639\u0628\u0629 \u0644\u0647\u0630\u0647 \u0627\u0644\u0644\u0639\u0628\u0629.\n   HOW IT WORKS: \u0643\u064a\u0641 \u064a\u0639\u0645\u0644\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: \u064a\u0648\u062c\u062f \u0639\u0644\u0649 \u0647\u0630\u0627 \u0627\u0644\u062c\u0647\u0627\u0632 PS2BBL \u0645\u0639 \u0642\u0641\u0644 RIPPS2.\n   AUDIO folder: \u0645\u062c\u0644\u062f AUDIO\n   DRIVES: \u0627\u0644\u0645\u062d\u0631\u0643\u0627\u062a\n-  INSTALL RIPPS2'S COPY: \u062a\u062b\u0628\u064a\u062a \u0646\u0633\u062e\u0629 RIPPS2\n-  'Installed: ': '\u0645\u062b\u0628\u0651\u062a: '\n   MANAGE: \u0625\u062f\u0627\u0631\u0629\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: \u062a\u0639\u0631\u0636 \u0625\u062f\u0627\u0631\u0629 \u0645\u0627 \u062a\u0634\u063a\u0651\u0644\u0647 \u0643\u0644 \u0628\u0637\u0627\u0642\u0629 \u0630\u0627\u0643\u0631\u0629\u060c \u0648\u062a\u062b\u0628\u0651\u062a BIBLE \u0623\u0648 \u062a\u0635\u0644\u062d\u0647.\n   MEMORY CARD 0  (about 600 KB): \u0628\u0637\u0627\u0642\u0629 \u0627\u0644\u0630\u0627\u0643\u0631\u0629 0  (\u0646\u062d\u0648 600 \u0643\u064a\u0644\u0648\u0628\u0627\u064a\u062a)\n   MEMORY CARD 1  (about 600 KB): \u0628\u0637\u0627\u0642\u0629 \u0627\u0644\u0630\u0627\u0643\u0631\u0629 1  (\u0646\u062d\u0648 600 \u0643\u064a\u0644\u0648\u0628\u0627\u064a\u062a)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO: \u062a\u0639\u0630\u0651\u0631 \u062a\u062d\u0645\u064a\u0644 \u0628\u0631\u0646\u0627\u0645\u062c \u062a\u0634\u063a\u064a\u0644\u0647 (\u0627\u0644\u0631\u0645\u0632 %d).'\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO: \u0628\u0631\u0646\u0627\u0645\u062c \u0627\u0644\u062a\u0634\u063a\u064a\u0644 \u0645\u062d\u0645\u0651\u0644\u060c \u0644\u0643\u0646 \u0644\u0645 \u062a\u0633\u062a\u062c\u0628 \u0623\u064a \u0628\u0637\u0627\u0642\u0629 SD (\u0645\u0646\u0641\u0630 mc1).'\n   NEUTRINO SETTINGS: \u0625\u0639\u062f\u0627\u062f\u0627\u062a NEUTRINO\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': '\u0644\u0627 \u0634\u064a\u0621 \u0628\u0639\u062f: \u0644\u064a\u0633 \u0644\u0640 RIPPS2 \u0623\u0635\u0648\u0627\u062a \u062e\u0627\u0635\u0629 \u0628\u0647. \u0627\u0633\u062a\u062e\u0631\u062c \u0623\u0635\u0648\u0627\u062a \u0647\u0630\u0627 \u0627\u0644\u062c\u0647\u0627\u0632\u060c \u0623\u0648 \u0627\u0635\u0646\u0639\u0647\u0627 \u0641\u064a RIPPS2 Audio Studio.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: \u063a\u064a\u0631 \u0645\u062b\u0628\u0651\u062a. \u0627\u0644\u0644\u0639\u0628\u0629 \u0627\u0644\u062a\u064a \u062a\u062d\u062a\u0627\u062c\u0647 \u062a\u0639\u0631\u0636 \u0646\u0633\u062e\u0629 RIPPS2\u060c \u0623\u0648 \u062b\u0628\u0651\u062a\u0647 \u0647\u0646\u0627.\n   POPSTARTER NETWORK: \u0634\u0628\u0643\u0629 POPSTARTER\n   POPSTARTER and Ember: POPSTARTER \u0648Ember\n   PS1 SETTINGS: \u0625\u0639\u062f\u0627\u062f\u0627\u062a PS1\n   PS2BBL 1.2.0 for RIPPS2: PS2BBL 1.2.0 \u0644\u0640 RIPPS2\n   RIP THIS CONSOLE'S SOUNDS: \u0627\u0633\u062a\u062e\u0631\u0627\u062c \u0623\u0635\u0648\u0627\u062a \u0647\u0630\u0627 \u0627\u0644\u062c\u0647\u0627\u0632\n-  RIPPS2's homebrew: \u0628\u0631\u0627\u0645\u062c RIPPS2 \u0627\u0644\u0645\u0646\u0632\u0644\u064a\u0629\n   STATUS: \u0627\u0644\u062d\u0627\u0644\u0629\n-  'STATUS: looking for RIPPS2''s homebrew': '\u0627\u0644\u062d\u0627\u0644\u0629: \u062c\u0627\u0631\u064d \u0627\u0644\u0628\u062d\u062b \u0639\u0646 \u0628\u0631\u0627\u0645\u062c RIPPS2 \u0627\u0644\u0645\u0646\u0632\u0644\u064a\u0629'\n   a drive of unknown kind: \u0645\u062d\u0631\u0643 \u0645\u0646 \u0646\u0648\u0639 \u063a\u064a\u0631 \u0645\u0639\u0631\u0648\u0641\n-  a drive or a memory card: \u0645\u062d\u0631\u0643 \u0623\u0648 \u0628\u0637\u0627\u0642\u0629 \u0630\u0627\u0643\u0631\u0629\n   boot loader, card by card: \u0645\u062d\u0645\u0651\u0644 \u0627\u0644\u0625\u0642\u0644\u0627\u0639\u060c \u0628\u0637\u0627\u0642\u0629 \u062a\u0644\u0648 \u0627\u0644\u0623\u062e\u0631\u0649\n   from its BIOS, into AUDIO: \u0645\u0646 BIOS \u0627\u0644\u062e\u0627\u0635 \u0628\u0647\u060c \u0625\u0644\u0649 AUDIO\n   internal HDD (exFAT): \u0627\u0644\u0642\u0631\u0635 \u0627\u0644\u0635\u0644\u0628 \u0627\u0644\u062f\u0627\u062e\u0644\u064a (exFAT)\n@@ -975,10 +958,194 @@ strings:\n   Sidebar: \u0634\u0631\u064a\u0637 \u062c\u0627\u0646\u0628\u064a\n   your own copy, found on your drives and memory cards: \u0646\u0633\u062e\u062a\u0643 \u0627\u0644\u062e\u0627\u0635\u0629\u060c \u064a\u064f\u0639\u062b\u0631 \u0639\u0644\u064a\u0647\u0627 \u0641\u064a \u0645\u062d\u0631\u0643\u0627\u062a\u0643 \u0648\u0628\u0637\u0627\u0642\u0627\u062a \u0627\u0644\u0630\u0627\u0643\u0631\u0629\n   'your own, found and launched: github.com/Gageformer/Ember/releases': '\u0646\u0633\u062e\u062a\u0643\u060c \u064a\u064f\u0639\u062b\u0631 \u0639\u0644\u064a\u0647\u0627 \u0648\u062a\u064f\u0634\u063a\u064e\u0651\u0644: github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': '\u063a\u064a\u0631 \u0645\u0648\u062c\u0648\u062f \u0639\u0644\u0649 mass0: (%s). \u0636\u0639 ember.elf \u0648\u0645\u0644\u0641 bios.bin \u0627\u0644\u062e\u0627\u0635 \u0628\u0643 \u0641\u064a \u0645\u062c\u0644\u062f EMBER \u0641\u064a\u0647.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: \u063a\u064a\u0631 \u0645\u0648\u062c\u0648\u062f. \u0636\u0639 POPSTARTER.ELF \u0627\u0644\u062e\u0627\u0635 \u0628\u0643 \u0641\u064a \u0645\u062c\u0644\u062f POPS \u0639\u0644\u0649 \u0623\u062d\u062f \u0627\u0644\u0645\u062d\u0631\u0643\u0627\u062a.\n   Launch Disc: \u062a\u0634\u063a\u064a\u0644 \u0627\u0644\u0642\u0631\u0635\n   Memory Files: \u0645\u0644\u0641\u0627\u062a \u0627\u0644\u0630\u0627\u0643\u0631\u0629\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: \u0648\u062c\u062f RIPPS2 %d \u0645\u0646 5. \u064a\u062d\u0641\u0638 \u0643\u0644 \u0648\u0627\u062d\u062f \u0645\u0646\u0647\u0627\u060c \u0648\u064a\u0642\u0631\u0624\u0647 \u0643\u0644 \u062a\u0634\u063a\u064a\u0644 \u0645\u0646 \u0647\u0646\u0627\u0643.\n+  not found on the started drives: \u063a\u064a\u0631 \u0645\u0648\u062c\u0648\u062f \u0639\u0644\u0649 \u0627\u0644\u0645\u062d\u0631\u0643\u0627\u062a \u0627\u0644\u0645\u0634\u063a\u0651\u0644\u0629\n+  not looked for yet: \u0644\u0645 \u064a\u062a\u0645 \u0627\u0644\u0628\u062d\u062b \u0639\u0646\u0647 \u0628\u0639\u062f\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s \u0644\u0645 \u064a\u0633\u062a\u062c\u0628\u060c \u0644\u0630\u0627 \u0644\u0627 \u064a\u0645\u0643\u0646 \u062a\u0634\u063a\u064a\u0644 %s. \u0647\u0644 \u0647\u0648 \u0645\u0648\u0635\u0648\u0644\u061f'\n+  '%s is not on %s any more, so it has left All Games.': '%s \u0644\u0645 \u064a\u0639\u062f \u0639\u0644\u0649 %s\u060c \u0644\u0630\u0627 \u062e\u0631\u062c \u0645\u0646 \u0643\u0644 \u0627\u0644\u0623\u0644\u0639\u0627\u0628.'\n+  STARTING %s: \u062a\u0634\u063a\u064a\u0644 %s\n+  the SMB share: \u0645\u0634\u0627\u0631\u0643\u0629 SMB\n+  the drive: \u0627\u0644\u0645\u062d\u0631\u0643\n+  the internal HDD (APA): \u0627\u0644\u0642\u0631\u0635 \u0627\u0644\u0635\u0644\u0628 \u0627\u0644\u062f\u0627\u062e\u0644\u064a (APA)\n+  the internal HDD (exFAT): \u0627\u0644\u0642\u0631\u0635 \u0627\u0644\u0635\u0644\u0628 \u0627\u0644\u062f\u0627\u062e\u0644\u064a (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: \u0645\u0628\u0646\u064a \u0639\u0644\u0649 RIPTOPL\u060c \u0645\u0639 NEUTRINO\n+  'Special thanks to all the YouTube love as well:': '\u0634\u0643\u0631 \u062e\u0627\u0635 \u0623\u064a\u0636\u0627\u064b \u0644\u0643\u0644 \u0627\u0644\u062d\u0628 \u0639\u0644\u0649 YouTube:'\n+  and its own look, launchers, file browser and save tools.: \u0648\u0628\u0645\u0638\u0647\u0631\u0647 \u0648\u0645\u0634\u063a\u0651\u0644\u0627\u062a\u0647 \u0648\u0645\u062a\u0635\u0641\u062d \u0645\u0644\u0641\u0627\u062a\u0647 \u0648\u0623\u062f\u0648\u0627\u062a \u0627\u0644\u062d\u0641\u0638 \u0627\u0644\u062e\u0627\u0635\u0629 \u0628\u0647.\n+  glad to help however you reach me.: \u064a\u0633\u0639\u062f\u0646\u064a \u0623\u0646 \u0623\u0633\u0627\u0639\u062f\u060c \u0623\u064a\u0627\u064b \u0643\u0627\u0646\u062a \u0637\u0631\u064a\u0642\u0629 \u0648\u0635\u0648\u0644\u0643 \u0625\u0644\u064a.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): \u0646\u0648\u0627\u0629 \u0627\u0644\u0623\u0644\u0639\u0627\u0628 \u0627\u0644\u062b\u0627\u0646\u064a\u0629\u060c 1.8.0 \u0645\u0636\u0645\u0651\u0646\u0629 \u062f\u0648\u0646 \u062a\u0639\u062f\u064a\u0644 (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: \u0645\u0639 Neutrino \u0645\u0646 rickgaiser \u0628\u062f\u0627\u062e\u0644\u0647 \u0646\u0648\u0627\u0629 \u062b\u0627\u0646\u064a\u0629 \u0644\u0644\u0623\u0644\u0639\u0627\u0628\u060c\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : '\u0647\u0630\u0647 \u0627\u0644\u0644\u0639\u0628\u0629 \u062a\u0628\u062f\u0623 \u0639\u0628\u0631 Neutrino\u060c \u0648\u0644\u0645 \u064a\u062c\u062f RIPPS2 \u0623\u064a \u0646\u0633\u062e\u0629 \u0645\u0646\u0647: \u0644\u0627 \u0641\u064a \u0645\u0633\u0627\u0631 \u0627\u0644\u0625\u0639\u062f\u0627\u062f\u0627\u062a\u060c \u0648\u0644\u0627 \u0639\u0644\u0649 \u0645\u062d\u0631\u0643 \u0627\u0644\u0644\u0639\u0628\u0629\u060c \u0648\u0644\u0627 \u0639\u0644\u0649 \u0628\u0637\u0627\u0642\u0627\u062a \u0627\u0644\u0630\u0627\u0643\u0631\u0629\u060c \u0648\u0644\u0627 \u0639\u0644\u0649 \u0623\u064a \u0645\u062d\u0631\u0643 \u0645\u0634\u063a\u0651\u0644. \u064a\u062d\u0645\u0644 RIPPS2 \u0627\u0644\u0646\u0633\u062e\u0629 1.8.0 \u0645\u0646 Neutrino \u0645\u0646 rickgaiser (AFL-3.0): \u062b\u0628\u0651\u062a\u0647 \u0645\u0631\u0629 \u0648\u0627\u062d\u062f\u0629\u060c \u062d\u064a\u062b \u062a\u062e\u062a\u0627\u0631\u060c \u0648\u0643\u0644 \u0644\u0639\u0628\u0629 Neutrino \u062a\u0633\u062a\u062e\u062f\u0645\u0647 \u0645\u0646 \u0647\u0646\u0627\u0643.'\n+  STARTING THE DRIVES: \u062a\u0634\u063a\u064a\u0644 \u0627\u0644\u0645\u062d\u0631\u0643\u0627\u062a\n+  '%d games, on the started drives': '%d \u0644\u0639\u0628\u0629\u060c \u0639\u0644\u0649 \u0627\u0644\u0645\u062d\u0631\u0643\u0627\u062a \u0627\u0644\u0645\u0634\u063a\u0651\u0644\u0629'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s: \u062a\u0639\u0630\u0651\u0631 \u062d\u0630\u0641 \u0628\u0639\u0636 \u0627\u0644\u0645\u0644\u0641\u0627\u062a'\n+  '%u partitions, all in order': '%u \u0623\u0642\u0633\u0627\u0645\u060c \u0643\u0644\u0647\u0627 \u0633\u0644\u064a\u0645\u0629'\n+  ', %d art images': \u060c %d \u0635\u0648\u0631 \u063a\u0644\u0627\u0641\n+  ', and ': \u060c \u0648\n+  ', its cheats': \u060c \u0623\u0643\u0648\u0627\u062f \u0627\u0644\u063a\u0634 \u0627\u0644\u062e\u0627\u0635\u0629 \u0628\u0647\u0627\n+  ', its settings': \u060c \u0625\u0639\u062f\u0627\u062f\u0627\u062a\u0647\u0627\n+  1 game, on the started drives: \u0644\u0639\u0628\u0629 \u0648\u0627\u062d\u062f\u0629\u060c \u0639\u0644\u0649 \u0627\u0644\u0645\u062d\u0631\u0643\u0627\u062a \u0627\u0644\u0645\u0634\u063a\u0651\u0644\u0629\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: \u0644\u0639\u0628\u0629 PS1 \u0647\u0646\u0627 \u062a\u062d\u0630\u0641 \u0645\u0646 \u0645\u0644\u0641\u0627\u062a \u0627\u0644\u0630\u0627\u0643\u0631\u0629\u060c \u062d\u064a\u062b \u0647\u064a \u0645\u0644\u0641 \u0645\u0633\u062a\u0642\u0644.\n+  A network catalogue's games are deleted on the PC that serves them.: \u0623\u0644\u0639\u0627\u0628 \u0643\u062a\u0627\u0644\u0648\u062c \u0627\u0644\u0634\u0628\u0643\u0629 \u062a\u062d\u0630\u0641 \u0639\u0644\u0649 \u0627\u0644\u062d\u0627\u0633\u0648\u0628 \u0627\u0644\u0630\u064a \u064a\u0642\u062f\u0651\u0645\u0647\u0627.\n+  DELETE FOR GOOD: \u062d\u0630\u0641 \u0646\u0647\u0627\u0626\u064a\n+  DELETE IT: \u0627\u062d\u0630\u0641\u0647\u0627\n+  'DELETED %s: %s FREED': '\u062a\u0645 \u062d\u0630\u0641 %s: \u062a\u062d\u0631\u064a\u0631 %s'\n+  'Delete %s from %s? Goes: %s.': '\u062d\u0630\u0641 %s \u0645\u0646 %s\u061f \u064a\u0630\u0647\u0628: %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': '\u062d\u0630\u0641 %s \u0645\u0646 %s\u061f \u064a\u0630\u0647\u0628: %s. \u064a\u0628\u0642\u0649: %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: \u062d\u0630\u0641 \u062d\u0641\u0638\u0627\u062a\u0647\u0627 \u0648\u0635\u0648\u0631 RIPSHOT \u0623\u064a\u0636\u0627\u064b\u061f \u0644\u0627 \u064a\u0628\u0642\u064a\u0647\u0627.\n+  HIDDEN GAMES: \u0623\u0644\u0639\u0627\u0628 \u0645\u062e\u0641\u064a\u0629\n+  'HIDDEN: %s': '\u0645\u062e\u0641\u064a\u0629: %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: \u0625\u062e\u0641\u0627\u0621 %s\u061f \u062a\u062e\u0631\u062c \u0645\u0646 \u0643\u0644 \u0642\u0627\u0626\u0645\u0629\u060c \u0648\u0645\u0646 \u0643\u0644 \u0627\u0644\u0623\u0644\u0639\u0627\u0628 \u0648\u0627\u0644\u0645\u0641\u0636\u0644\u0629 \u0623\u064a\u0636\u0627\u064b. \u0627\u0644\u0625\u0639\u062f\u0627\u062f\u0627\u062a > \u0645\u0635\u0627\u062f\u0631 \u0627\u0644\u0623\u0644\u0639\u0627\u0628 > \u0627\u0644\u0623\u0644\u0639\u0627\u0628 \u0627\u0644\u0645\u062e\u0641\u064a\u0629 \u062a\u0638\u0647\u0631\u0647\u0627 \u0645\u062c\u062f\u062f\u0627\u064b.\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: \u0641\u062d\u0635 \u0623\u062e\u064a\u0631. %s \u0633\u064a\u062d\u0630\u0641 \u0646\u0647\u0627\u0626\u064a\u0627\u064b\u060c \u0648\u0644\u0627 \u0634\u064a\u0621 \u064a\u0645\u0643\u0646\u0647 \u0625\u0639\u0627\u062f\u062a\u0647. \u0627\u0636\u063a\u0637 START \u0644\u062d\u0630\u0641\u0647.\n+  None. Hide Game, in a game's options, puts one here.: \u0644\u0627 \u0634\u064a\u0621. \u0625\u062e\u0641\u0627\u0621 \u0627\u0644\u0644\u0639\u0628\u0629\u060c \u0641\u064a \u062e\u064a\u0627\u0631\u0627\u062a \u0623\u064a \u0644\u0639\u0628\u0629\u060c \u064a\u0636\u0639 \u0648\u0627\u062d\u062f\u0629 \u0647\u0646\u0627.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : '\u0644\u0645 \u064a\u062d\u0630\u0641 \u0634\u064a\u0621: \u062c\u062f\u0648\u0644 \u0623\u0642\u0633\u0627\u0645 \u0647\u0630\u0627 \u0627\u0644\u0645\u062d\u0631\u0643 \u0644\u0627 \u064a\u0646\u0627\u0633\u0628 \u0647\u0630\u0627 \u0627\u0644\u0642\u0631\u0635 (%s). \u0631\u0628\u0645\u0627 \u0643\u062a\u0628 \u0645\u0646 \u0635\u0648\u0631\u0629 \u0642\u0631\u0635 \u0628\u062d\u062c\u0645 \u0622\u062e\u0631. \u0623\u0635\u0644\u062d\u0647 \u0639\u0644\u0649 \u062d\u0627\u0633\u0648\u0628 \u0623\u0648\u0644\u0627\u064b.'\n+  RIPPS2 cannot delete a game from this page.: \u0644\u0627 \u064a\u0633\u062a\u0637\u064a\u0639 RIPPS2 \u062d\u0630\u0641 \u0644\u0639\u0628\u0629 \u0645\u0646 \u0647\u0630\u0647 \u0627\u0644\u0635\u0641\u062d\u0629.\n+  RIPPS2 could not find this game's files on its drive.: \u0644\u0645 \u064a\u062c\u062f RIPPS2 \u0645\u0644\u0641\u0627\u062a \u0647\u0630\u0647 \u0627\u0644\u0644\u0639\u0628\u0629 \u0639\u0644\u0649 \u0645\u062d\u0631\u0643\u0647\u0627.\n+  Reading the hidden lists: \u0642\u0631\u0627\u0621\u0629 \u0627\u0644\u0642\u0648\u0627\u0626\u0645 \u0627\u0644\u0645\u062e\u0641\u064a\u0629\n+  SHOW AGAIN OR DELETE: \u0623\u0638\u0647\u0631 \u0645\u062c\u062f\u062f\u0627\u064b \u0623\u0648 \u0627\u062d\u0630\u0641\n+  SHOW IT AGAIN: \u0623\u0638\u0647\u0631\u0647\u0627 \u0645\u062c\u062f\u062f\u0627\u064b\n+  That game is not on its drive any more.: \u062a\u0644\u0643 \u0627\u0644\u0644\u0639\u0628\u0629 \u0644\u0645 \u062a\u0639\u062f \u0639\u0644\u0649 \u0645\u062d\u0631\u0643\u0647\u0627.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: \u062a\u0639\u0630\u0651\u0631\u062a \u0643\u062a\u0627\u0628\u0629 \u0627\u0644\u0642\u0627\u0626\u0645\u0629 \u0627\u0644\u0645\u062e\u0641\u064a\u0629 \u0639\u0644\u0649 \u0645\u062d\u0631\u0643 \u0627\u0644\u0644\u0639\u0628\u0629. \u0647\u0644 \u0647\u0648 \u0645\u062d\u0645\u064a \u0645\u0646 \u0627\u0644\u0643\u062a\u0627\u0628\u0629\u060c \u0623\u0645 \u0623\u0646 \u0639\u0645\u0644\u064a\u0627\u062a \u0627\u0644\u0643\u062a\u0627\u0628\u0629 \u0645\u062a\u0648\u0642\u0641\u0629\u061f\n+  The partition could not be removed (%d). Nothing was deleted.: \u062a\u0639\u0630\u0651\u0631\u062a \u0625\u0632\u0627\u0644\u0629 \u0627\u0644\u0642\u0633\u0645 (%d). \u0644\u0645 \u064a\u062d\u0630\u0641 \u0634\u064a\u0621.\n+  This drive cannot keep a hidden list (a network catalogue).: \u0647\u0630\u0627 \u0627\u0644\u0645\u062d\u0631\u0643 \u0644\u0627 \u064a\u0645\u0643\u0646\u0647 \u062d\u0641\u0638 \u0642\u0627\u0626\u0645\u0629 \u0645\u062e\u0641\u064a\u0629 (\u0643\u062a\u0627\u0644\u0648\u062c \u0634\u0628\u0643\u0629).\n+  This game's drive has not answered yet. Wait for it, then try again.: \u0645\u062d\u0631\u0643 \u0647\u0630\u0647 \u0627\u0644\u0644\u0639\u0628\u0629 \u0644\u0645 \u064a\u0633\u062a\u062c\u0628 \u0628\u0639\u062f. \u0627\u0646\u062a\u0638\u0631\u0647\u060c \u062b\u0645 \u062d\u0627\u0648\u0644 \u0645\u0631\u0629 \u0623\u062e\u0631\u0649.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: \u0642\u0633\u0645 \u0647\u0630\u0647 \u0627\u0644\u0644\u0639\u0628\u0629 \u0645\u0646 \u0627\u0644\u0623\u0642\u0633\u0627\u0645 \u0627\u0644\u062a\u064a \u0644\u0627 \u064a\u0632\u064a\u0644\u0647\u0627 RIPPS2 (\u0642\u0633\u0645 \u0646\u0638\u0627\u0645\u060c \u0623\u0648 \u0627\u0644\u0642\u0633\u0645 \u0627\u0644\u0630\u064a \u0639\u0644\u064a\u0647 \u0625\u0639\u062f\u0627\u062f\u0627\u062a\u0647).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': '\u0623\u062d\u062f \u0627\u0644\u0623\u0642\u0633\u0627\u0645 \u0623\u0643\u0628\u0631 \u0645\u0645\u0627 \u064a\u0633\u0645\u062d \u0628\u0647 \u0647\u0630\u0627 \u0627\u0644\u0642\u0631\u0635: \u0627\u0644\u062c\u062f\u0648\u0644 \u0635\u0646\u0639 \u0644\u0642\u0631\u0635 \u0628\u062d\u062c\u0645 \u0622\u062e\u0631'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': '\u0623\u062d\u062f \u0627\u0644\u0623\u0642\u0633\u0627\u0645 \u064a\u062a\u062c\u0627\u0648\u0632 \u0646\u0647\u0627\u064a\u0629 \u0647\u0630\u0627 \u0627\u0644\u0642\u0631\u0635 (\u0627\u0644\u0642\u0637\u0627\u0639 %u \u0645\u0646 %u): \u0627\u0644\u062c\u062f\u0648\u0644 \u0635\u0646\u0639 \u0644\u0642\u0631\u0635 \u0623\u0643\u0628\u0631'\n+  its RIPSHOT pictures: \u0635\u0648\u0631 RIPSHOT \u0627\u0644\u062e\u0627\u0635\u0629 \u0628\u0647\u0627\n+  its drive: \u0645\u062d\u0631\u0643\u0647\u0627\n+  its saves: \u062d\u0641\u0638\u0627\u062a\u0647\u0627\n+  sector %u holds no partition header: \u0627\u0644\u0642\u0637\u0627\u0639 %u \u0644\u0627 \u064a\u062d\u0648\u064a \u0631\u0623\u0633 \u0642\u0633\u0645\n+  the chain does not close at __mbr: \u0627\u0644\u0633\u0644\u0633\u0644\u0629 \u0644\u0627 \u062a\u0646\u063a\u0644\u0642 \u0639\u0646\u062f __mbr\n+  the chain skips from sector %u: \u0627\u0644\u0633\u0644\u0633\u0644\u0629 \u062a\u0642\u0641\u0632 \u0645\u0646 \u0627\u0644\u0642\u0637\u0627\u0639 %u\n+  the chain's links disagree at sector %u: \u062d\u0644\u0642\u0627\u062a \u0627\u0644\u0633\u0644\u0633\u0644\u0629 \u0644\u0627 \u062a\u062a\u0641\u0642 \u0639\u0646\u062f \u0627\u0644\u0642\u0637\u0627\u0639 %u\n+  the drive's size could not be read: \u062a\u0639\u0630\u0651\u0631\u062a \u0642\u0631\u0627\u0621\u0629 \u062d\u062c\u0645 \u0627\u0644\u0645\u062d\u0631\u0643\n+  the game (%s): \u0627\u0644\u0644\u0639\u0628\u0629 (%s)\n+  the game's folder and everything in it (%s): \u0645\u062c\u0644\u062f \u0627\u0644\u0644\u0639\u0628\u0629 \u0648\u0643\u0644 \u0645\u0627 \u0641\u064a\u0647 (%s)\n+  the header at sector %u could not be read: \u062a\u0639\u0630\u0651\u0631\u062a \u0642\u0631\u0627\u0621\u0629 \u0627\u0644\u0631\u0623\u0633 \u0639\u0646\u062f \u0627\u0644\u0642\u0637\u0627\u0639 %u\n+  the header at sector %u fails its checksum: \u0627\u0644\u0631\u0623\u0633 \u0639\u0646\u062f \u0627\u0644\u0642\u0637\u0627\u0639 %u \u064a\u0641\u0634\u0644 \u0641\u064a \u0645\u062c\u0645\u0648\u0639 \u0627\u0644\u062a\u062d\u0642\u0642\n+  the partition at sector %u is not where or as large as APA puts one: \u0627\u0644\u0642\u0633\u0645 \u0639\u0646\u062f \u0627\u0644\u0642\u0637\u0627\u0639 %u \u0644\u064a\u0633 \u062d\u064a\u062b \u064a\u0636\u0639 APA \u0642\u0633\u0645\u0627\u064b \u0648\u0644\u0627 \u0628\u062d\u062c\u0645\u0647\n+  the settings and art another copy of it uses: \u0627\u0644\u0625\u0639\u062f\u0627\u062f\u0627\u062a \u0648\u0627\u0644\u0635\u0648\u0631 \u0627\u0644\u062a\u064a \u062a\u0633\u062a\u062e\u062f\u0645\u0647\u0627 \u0646\u0633\u062e\u0629 \u0623\u062e\u0631\u0649 \u0645\u0646\u0647\u0627\n+  '%d FOLDERS': '%d \u0645\u062c\u0644\u062f\u0627\u062a'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': \u062a\u0639\u0630\u0651\u0631 \u0646\u0633\u062e %s \u0625\u0644\u0649 %s. \u0647\u0644 \u0627\u0644\u0645\u062d\u0631\u0643 \u0645\u0645\u062a\u0644\u0626 \u0623\u0648 \u0645\u062d\u0645\u064a \u0645\u0646 \u0627\u0644\u0643\u062a\u0627\u0628\u0629\u061f\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s \u0644\u064a\u0633 \u062d\u064a\u062b \u064a\u0628\u062d\u062b RIPPS2. \u0647\u0644 \u062a\u0628\u062d\u062b \u0639\u0646\u0647 \u0641\u064a \u0627\u0644\u0645\u062d\u0631\u0643\u0627\u062a \u0627\u0644\u062a\u064a \u0634\u063a\u0651\u0644\u0647\u0627 RIPPS2\u061f \u0645\u0643\u0627\u0646 \u0627\u0644\u0639\u062b\u0648\u0631 \u0639\u0644\u064a\u0647 \u064a\u062d\u0641\u0638 \u0644\u0643\u0644 \u062a\u0634\u063a\u064a\u0644 \u0628\u0639\u062f\u0647.'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Ember \u0644\u064a\u0633 \u0641\u064a %s\u060c \u0628\u062c\u0627\u0646\u0628 \u0627\u0644\u0623\u0644\u0639\u0627\u0628\u060c \u0648\u064a\u062c\u0628 \u0623\u0646 \u064a\u0643\u0648\u0646 \u0647\u0646\u0627\u0643. \u0648\u062c\u062f\u0647 RIPPS2 \u0641\u064a %s: \u0632\u0631 START \u064a\u0646\u0633\u062e\u0647 \u0625\u0644\u0649 \u0647\u0646\u0627\u0643 (%d \u0643\u064a\u0644\u0648\u0628\u0627\u064a\u062a) \u0648\u064a\u0628\u062f\u0623 \u0627\u0644\u0644\u0639\u0628\u0629.'\n+  Ember's BIOS: BIOS \u0627\u0644\u062e\u0627\u0635 \u0628\u0645\u062d\u0627\u0643\u064a Ember\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : 'BIOS \u0627\u0644\u062e\u0627\u0635 \u0628\u0645\u062d\u0627\u0643\u064a Ember (bios.bin) \u0644\u064a\u0633 \u0641\u064a %s\u060c \u0628\u062c\u0627\u0646\u0628 Ember. \u0648\u062c\u062f RIPPS2 \u0648\u0627\u062d\u062f\u0627\u064b \u0641\u064a %s: \u0632\u0631 START \u064a\u0646\u0633\u062e\u0647 \u0625\u0644\u0649 \u0647\u0646\u0627\u0643 (%d \u0643\u064a\u0644\u0648\u0628\u0627\u064a\u062a) \u0648\u064a\u0628\u062f\u0623 \u0627\u0644\u0644\u0639\u0628\u0629.'\n+  LOOKING FOR YOUR LAUNCHERS: \u0627\u0644\u0628\u062d\u062b \u0639\u0646 \u0645\u0634\u063a\u0651\u0644\u0627\u062a\u0643\n+  The POPS files: \u0645\u0644\u0641\u0627\u062a POPS\n+  '%s  MMCE card': '%s  \u0628\u0637\u0627\u0642\u0629 MMCE'\n+  INSTALL RIPPS2'S NEUTRINO: \u062a\u062b\u0628\u064a\u062a \u0646\u0633\u062e\u0629 RIPPS2 \u0645\u0646 NEUTRINO\n+  'Internal HDD (APA): %s free of %s': '\u0627\u0644\u0642\u0631\u0635 \u0627\u0644\u0635\u0644\u0628 \u0627\u0644\u062f\u0627\u062e\u0644\u064a (APA): %s \u062d\u0631\u0629 \u0645\u0646 %s'\n+  LAUNCHERS: \u0627\u0644\u0645\u0634\u063a\u0651\u0644\u0627\u062a\n+  LOOK FOR THEM: \u0627\u0628\u062d\u062b \u0639\u0646\u0647\u0627\n+  'Memory card %d: %d KB free': '\u0628\u0637\u0627\u0642\u0629 \u0627\u0644\u0630\u0627\u0643\u0631\u0629 %d: %d \u0643\u064a\u0644\u0648\u0628\u0627\u064a\u062a \u062d\u0631\u0629'\n+  POPS files: \u0645\u0644\u0641\u0627\u062a POPS\n+  START EVERY DRIVE: \u0634\u063a\u0651\u0644 \u0643\u0644 \u0627\u0644\u0645\u062d\u0631\u0643\u0627\u062a\n+  'STATUS: looking at the drives and the homebrew': '\u0627\u0644\u062d\u0627\u0644\u0629: \u0641\u062d\u0635 \u0627\u0644\u0645\u062d\u0631\u0643\u0627\u062a \u0648\u0627\u0644\u0628\u0631\u0627\u0645\u062c \u0627\u0644\u0645\u0646\u0632\u0644\u064a\u0629'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): \u062a\u0634\u063a\u064a\u0644 \u0643\u0644 \u0627\u0644\u0645\u062d\u0631\u0643\u0627\u062a \u0623\u0648\u0644\u0627\u064b\u060c \u0644\u064a\u0635\u0644 \u0627\u0644\u0628\u062d\u062b \u0625\u0644\u064a\u0647\u0627 \u0643\u0644\u0647\u0627\u061f (\u0644\u0627 \u064a\u0628\u062d\u062b \u0641\u064a \u0627\u0644\u0645\u062d\u0631\u0643\u0627\u062a \u0627\u0644\u0645\u0634\u063a\u0651\u0644\u0629 \u0628\u0627\u0644\u0641\u0639\u0644.)\n+  USB, HDD, MMCE and the rest: USB\u060c HDD\u060c MMCE \u0648\u0627\u0644\u0628\u0642\u064a\u0629\n+  every started drive: \u0643\u0644 \u0645\u062d\u0631\u0643 \u0645\u0634\u063a\u0651\u0644\n+  found where you keep them: \u0648\u062c\u062f\u062a \u062d\u064a\u062b \u062a\u062d\u0641\u0638\u0647\u0627\n+  not found yet: \u0644\u0645 \u064a\u0639\u062b\u0631 \u0639\u0644\u064a\u0647 \u0628\u0639\u062f\n+  not on a started drive: \u0644\u064a\u0633 \u0639\u0644\u0649 \u0645\u062d\u0631\u0643 \u0645\u0634\u063a\u0651\u0644\n+  only if you have none: \u0641\u0642\u0637 \u0625\u0646 \u0644\u0645 \u064a\u0643\u0646 \u0644\u062f\u064a\u0643 \u0648\u0627\u062d\u062f\n+  starts every drive first: \u064a\u0634\u063a\u0651\u0644 \u0643\u0644 \u0627\u0644\u0645\u062d\u0631\u0643\u0627\u062a \u0623\u0648\u0644\u0627\u064b\n+  Drives, launchers and homebrew: \u0627\u0644\u0645\u062d\u0631\u0643\u0627\u062a \u0648\u0627\u0644\u0645\u0634\u063a\u0651\u0644\u0627\u062a \u0648\u0627\u0644\u0628\u0631\u0627\u0645\u062c \u0627\u0644\u0645\u0646\u0632\u0644\u064a\u0629\n+  BDM Devices: \u0623\u062c\u0647\u0632\u0629 BDM\n+  Color Sweep: \u062a\u062f\u0631\u062c \u0627\u0644\u0623\u0644\u0648\u0627\u0646\n+  Colors and Text: \u0627\u0644\u0623\u0644\u0648\u0627\u0646 \u0648\u0627\u0644\u0646\u0635\n+  HDD POPS partitions: \u0623\u0642\u0633\u0627\u0645 POPS \u0639\u0644\u0649 \u0627\u0644\u0642\u0631\u0635 \u0627\u0644\u0635\u0644\u0628\n+  Layout: \u0627\u0644\u062a\u062e\u0637\u064a\u0637\n+  Missing Covers: \u0627\u0644\u0623\u063a\u0644\u0641\u0629 \u0627\u0644\u0646\u0627\u0642\u0635\u0629\n+  Name: \u0627\u0644\u0627\u0633\u0645\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: \u0644\u0645 \u064a\u064f\u0639\u062b\u0631 \u0628\u0639\u062f \u0639\u0644\u0649 \u0625\u0639\u062f\u0627\u062f SMB \u0644\u0640 POPSTARTER. \u0627\u0645\u0644\u0623 \u0627\u0644\u0635\u0641\u0648\u0641 \u0623\u062f\u0646\u0627\u0647.\n+  POPSTARTER Network: \u0634\u0628\u0643\u0629 POPSTARTER\n+  POPSTARTER SMB Network: \u0634\u0628\u0643\u0629 SMB \u0644\u0640 POPSTARTER\n+  POPSTARTER UDPFS Network: \u0634\u0628\u0643\u0629 UDPFS \u0644\u0640 POPSTARTER\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: \u064a\u0636\u064a\u0641 RIPPS2 \u0628\u0646\u0641\u0633\u0647 \u0627\u0644\u0642\u0631\u0635 \u0648\u0645\u0644\u0641 ISO \u0648\u0628\u0637\u0627\u0642\u0627\u062a VMC \u0648\u0648\u0636\u0639 \u0627\u0644\u0641\u064a\u062f\u064a\u0648.\n+  Screen: \u0627\u0644\u0634\u0627\u0634\u0629\n+  Speed: \u0627\u0644\u0633\u0631\u0639\u0629\n+  Sweep Colors: \u0623\u0644\u0648\u0627\u0646 \u0627\u0644\u062a\u062f\u0631\u062c\n+  Text Weight: \u0633\u0645\u0627\u0643\u0629 \u0627\u0644\u0646\u0635\n+  Theme Color: \u0627\u0644\u0644\u0648\u0646 \u0627\u0644\u0623\u0633\u0627\u0633\u064a\n+  Audio: \u0627\u0644\u0635\u0648\u062a\n+  Bold: \u0639\u0631\u064a\u0636\n+  Controllers: \u0623\u0630\u0631\u0639 \u0627\u0644\u062a\u062d\u0643\u0645\n+  Lively: \u062d\u064a\u0648\u064a\n+  Medium: \u0645\u062a\u0648\u0633\u0637\n+  PS Emulation: \u0645\u062d\u0627\u0643\u0627\u0629 PS\n+  Regular: \u0639\u0627\u062f\u064a\n+  Slow: \u0628\u0637\u064a\u0621\n+  A MOMENT: \u0644\u062d\u0638\u0629\n+  A TASK IN THE BACKGROUND: \u0645\u0647\u0645\u0629 \u0641\u064a \u0627\u0644\u062e\u0644\u0641\u064a\u0629\n+  ABOUT %d S: \u0646\u062d\u0648 %d \u062b\n+  ANY MOMENT: \u0641\u064a \u0623\u064a \u0644\u062d\u0638\u0629\n+  READING THE GAME LIST OF %s: \u0642\u0631\u0627\u0621\u0629 \u0642\u0627\u0626\u0645\u0629 \u0623\u0644\u0639\u0627\u0628 %s\n+  SAVING A LIST: \u062d\u0641\u0638 \u0642\u0627\u0626\u0645\u0629\n+  START NOW: \u0627\u0628\u062f\u0623 \u0627\u0644\u0622\u0646\n+  STARTING: \u062c\u0627\u0631\u064d \u0627\u0644\u0628\u062f\u0621\n+  STARTS NEXT: \u064a\u0628\u062f\u0623 \u0628\u0639\u062f \u0630\u0644\u0643\n+  STOPPING IT SAFELY: \u0625\u064a\u0642\u0627\u0641 \u0622\u0645\u0646\n+  THE LAST OF THE QUEUE: \u0622\u062e\u0631 \u0627\u0644\u0637\u0627\u0628\u0648\u0631\n+  WHEN IT FINISHES: \u0639\u0646\u062f\u0645\u0627 \u064a\u0646\u062a\u0647\u064a\n+  What each card starts; install or repair BIBLE.: \u0645\u0627 \u062a\u0634\u063a\u0651\u0644\u0647 \u0643\u0644 \u0628\u0637\u0627\u0642\u0629\u061b \u062b\u0628\u0651\u062a BIBLE \u0623\u0648 \u0623\u0635\u0644\u062d\u0647.\n+  Rainbow: \u0642\u0648\u0633 \u0642\u0632\u062d\n+  Custom: \u0645\u062e\u0635\u0635\n+  DESR DVR ISOs (experimental): \u0645\u0644\u0641\u0627\u062a DESR DVR ISO (\u062a\u062c\u0631\u064a\u0628\u064a)\n+  PC CARD CF (experimental): PC CARD CF (\u062a\u062c\u0631\u064a\u0628\u064a)\n+  HDD Start Mode: \u0648\u0636\u0639 \u062a\u0634\u063a\u064a\u0644 \u0627\u0644\u0642\u0631\u0635 \u0627\u0644\u0635\u0644\u0628\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): Roboto Condensed \u0645\u0646 The Roboto Project Authors (SIL OFL 1.1)\n+  '%s on %s': '%s \u0639\u0644\u0649 %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: %s \u0623\u0635\u0628\u062d %s (%d%%)\u060c \u0648\u0641\u064f\u062d\u0635 \u0643\u0644 \u062c\u0632\u0621. \u0647\u0644 \u062a\u062d\u0630\u0641 \u0645\u0644\u0641 ISO \u0648\u062a\u0628\u0642\u064a ZSO\u061f'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: %s \u0623\u0635\u0628\u062d %s \u0645\u0646 \u062c\u062f\u064a\u062f\u060c \u0648\u0641\u064f\u062d\u0635 \u0643\u0644 \u062c\u0632\u0621. \u0647\u0644 \u062a\u062d\u0630\u0641 \u0645\u0644\u0641 ZSO \u0648\u062a\u0628\u0642\u064a ISO\u061f'\n+  A ZSO of this game is already beside it. Remove one of the two first.: \u064a\u0648\u062c\u062f \u0628\u062c\u0627\u0646\u0628\u0647\u0627 \u0645\u0644\u0641 ZSO \u0644\u0647\u0630\u0647 \u0627\u0644\u0644\u0639\u0628\u0629. \u0627\u062d\u0630\u0641 \u0623\u062d\u062f\u0647\u0645\u0627 \u0623\u0648\u0644\u064b\u0627.\n+  About %d min, then checked: \u0646\u062d\u0648 %d \u062f\u0642\u064a\u0642\u0629\u060c \u062b\u0645 \u0627\u0644\u0641\u062d\u0635\n+  An ISO of this game is already beside it. Remove one of the two first.: \u064a\u0648\u062c\u062f \u0628\u062c\u0627\u0646\u0628\u0647\u0627 \u0645\u0644\u0641 ISO \u0644\u0647\u0630\u0647 \u0627\u0644\u0644\u0639\u0628\u0629. \u0627\u062d\u0630\u0641 \u0623\u062d\u062f\u0647\u0645\u0627 \u0623\u0648\u0644\u064b\u0627.\n+  CHECKING EVERY PIECE: \u0641\u062d\u0635 \u0643\u0644 \u062c\u0632\u0621\n+  COMPRESS TO ZSO: \u0636\u063a\u0637 \u0625\u0644\u0649 ZSO\n+  'COMPRESSED %s: %s FREED': '\u0636\u064f\u063a\u0637 %s: \u062a\u062d\u0631\u0631 %s'\n+  COMPRESSING 1 OF 2: \u0627\u0644\u0636\u063a\u0637 1 \u0645\u0646 2\n+  COMPRESSING 2 OF 2: \u0627\u0644\u0636\u063a\u0637 2 \u0645\u0646 2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : \u0647\u0644 \u062a\u0636\u063a\u0637 %s\u061f \u064a\u0628\u0642\u0649 \u0645\u0644\u0641 ISO \u062d\u062a\u0649 \u064a\u064f\u0642\u0631\u0623 \u0645\u0644\u0641 ZSO \u0645\u0631\u0629 \u0623\u062e\u0631\u0649 \u0648\u064a\u064f\u0641\u062d\u0635\u060c \u062b\u0645 \u064a\u0633\u0623\u0644 RIPPS2 \u0642\u0628\u0644 \u0623\u0646 \u064a\u062d\u0630\u0641 \u0645\u0644\u0641 ISO. \u064a\u062d\u062a\u0627\u062c \u0627\u0644\u0642\u0631\u0635 \u0625\u0644\u0649 \u0645\u0633\u0627\u062d\u0629 \u0644\u0644\u0627\u062b\u0646\u064a\u0646 \u0623\u062b\u0646\u0627\u0621 \u0627\u0644\u0639\u0645\u0644\u061b \u0623\u0628\u0642\u0650 \u0627\u0644\u062c\u0647\u0627\u0632 \u064a\u0639\u0645\u0644 \u062d\u062a\u0649 \u064a\u0646\u062a\u0647\u064a. \u064a\u0639\u0645\u0644 \u0645\u0644\u0641 ZSO \u0645\u0639 \u0646\u0648\u0627\u0629 OPL \u0641\u0642\u0637 (\u0648\u0644\u064a\u0633 Neutrino)\u061b \u062a\u0628\u0642\u0649 \u0645\u0639\u0647 \u062d\u0641\u0638\u0627\u062a\u0647 \u0648\u0625\u0639\u062f\u0627\u062f\u0627\u062a\u0647 \u0648\u0635\u0648\u0631\u0647 \u0648\u063a\u0634\u0627\u062a\u0647\u060c \u0648\u064a\u0645\u0643\u0646 \u0625\u0639\u0627\u062f\u062a\u0647 ISO \u0641\u064a \u0623\u064a \u0648\u0642\u062a.\n+  Compress to ZSO: \u0636\u063a\u0637 \u0625\u0644\u0649 ZSO\n+  Cycle: \u062a\u062f\u0648\u064a\u0631\n+  'Experimental: OPL core only (not Neutrino)': '\u062a\u062c\u0631\u064a\u0628\u064a: \u0646\u0648\u0627\u0629 OPL \u0641\u0642\u0637 (\u0648\u0644\u064a\u0633 Neutrino)'\n+  Hold SELECT: \u0625\u0645\u0633\u0627\u0643 SELECT\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: \u062a\u0639\u0630\u0651\u0631 \u0625\u0643\u0645\u0627\u0644\u0647 (%s). \u062d\u064f\u0630\u0641 \u0645\u0627 \u0643\u062a\u0628\u0647\u061b \u0627\u0644\u0644\u0639\u0628\u0629 \u0643\u0645\u0627 \u0643\u0627\u0646\u062a.\n+  List: \u0642\u0627\u0626\u0645\u0629\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : \u0647\u0644 \u062a\u0639\u064a\u062f %s \u0645\u0644\u0641 ISO\u061f \u064a\u0628\u0642\u0649 \u0645\u0644\u0641 ZSO \u062d\u062a\u0649 \u064a\u064f\u0642\u0631\u0623 \u0645\u0644\u0641 ISO \u0645\u0631\u0629 \u0623\u062e\u0631\u0649 \u0648\u064a\u064f\u0641\u062d\u0635\u060c \u062b\u0645 \u064a\u0633\u0623\u0644 RIPPS2 \u0642\u0628\u0644 \u0623\u0646 \u064a\u062d\u0630\u0641 \u0645\u0644\u0641 ZSO. \u064a\u062d\u062a\u0627\u062c \u0627\u0644\u0642\u0631\u0635 \u0625\u0644\u0649 \u0645\u0633\u0627\u062d\u0629 \u0644\u0644\u0627\u062b\u0646\u064a\u0646 \u0623\u062b\u0646\u0627\u0621 \u0627\u0644\u0639\u0645\u0644\u061b \u0623\u0628\u0642\u0650 \u0627\u0644\u062c\u0647\u0627\u0632 \u064a\u0639\u0645\u0644 \u062d\u062a\u0649 \u064a\u0646\u062a\u0647\u064a. \u062a\u0628\u0642\u0649 \u0645\u0639\u0647 \u062d\u0641\u0638\u0627\u062a\u0647 \u0648\u0625\u0639\u062f\u0627\u062f\u0627\u062a\u0647 \u0648\u0635\u0648\u0631\u0647 \u0648\u063a\u0634\u0627\u062a\u0647.\n+  Stop? What it has written so far is removed; the game stays as it was.: \u0625\u064a\u0642\u0627\u0641\u061f \u064a\u064f\u062d\u0630\u0641 \u0645\u0627 \u0643\u064f\u062a\u0628 \u062d\u062a\u0649 \u0627\u0644\u0622\u0646\u061b \u062a\u0628\u0642\u0649 \u0627\u0644\u0644\u0639\u0628\u0629 \u0643\u0645\u0627 \u0643\u0627\u0646\u062a.\n+  THEMES: \u0627\u0644\u0633\u0645\u0627\u062a\n+  There is not enough memory for this just now.: \u0644\u0627 \u062a\u0648\u062c\u062f \u0630\u0627\u0643\u0631\u0629 \u0643\u0627\u0641\u064a\u0629 \u0644\u0647\u0630\u0627 \u0627\u0644\u0622\u0646.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: \u0647\u0630\u0647 \u0627\u0644\u0635\u0648\u0631\u0629 \u0644\u064a\u0633\u062a \u0645\u0645\u0627 \u064a\u0633\u062a\u0637\u064a\u0639 RIPPS2 \u0636\u063a\u0637\u0647 \u0623\u0648 \u0641\u0643 \u0636\u063a\u0637\u0647 (\u062d\u062c\u0645\u0647\u0627 \u0644\u064a\u0633 \u0643\u062a\u0644\u064b\u0627 \u0643\u0627\u0645\u0644\u0629 \u0645\u0646 2 KB).\n+  UNCOMPRESS TO ISO: \u0641\u0643 \u0627\u0644\u0636\u063a\u0637 \u0625\u0644\u0649 ISO\n+  UNCOMPRESSED %s: \u0641\u064f\u0643 \u0636\u063a\u0637 %s\n+  UNCOMPRESSING: \u0641\u0643 \u0627\u0644\u0636\u063a\u0637\n+  Uncompress to ISO: \u0641\u0643 \u0627\u0644\u0636\u063a\u0637 \u0625\u0644\u0649 ISO\n+  Under a minute, then checked: \u0623\u0642\u0644 \u0645\u0646 \u062f\u0642\u064a\u0642\u0629\u060c \u062b\u0645 \u0627\u0644\u0641\u062d\u0635\n+  COPYING: \u062c\u0627\u0631\u064d \u0627\u0644\u0646\u0633\u062e\n+  SHOW AGAIN, DELETE OR COMPRESS: \u0623\u0638\u0647\u0631 \u0645\u062c\u062f\u062f\u0627\u064b \u0623\u0648 \u0627\u062d\u0630\u0641 \u0623\u0648 \u0627\u0636\u063a\u0637\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: \u0623\u0638\u0647\u0631 \u0645\u062c\u062f\u062f\u0627\u064b \u0623\u0648 \u0627\u062d\u0630\u0641 \u0623\u0648 \u0641\u0643 \u0627\u0644\u0636\u063a\u0637\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': \u062a\u062d\u062a\u0627\u062c %s \u0625\u0644\u0649 \u0646\u062d\u0648 %s \u0645\u0646 \u0627\u0644\u0645\u0633\u0627\u062d\u0629 \u0627\u0644\u062d\u0631\u0629 \u0639\u0644\u0649 \u0642\u0631\u0635\u0647\u0627 \u0623\u062b\u0646\u0627\u0621 \u0627\u0644\u0639\u0645\u0644 (\u064a\u0628\u0642\u0649 \u0627\u0644\u0623\u0635\u0644 \u062d\u062a\u0649 \u0627\u0644\u0646\u0647\u0627\u064a\u0629)\u060c \u0648\u0641\u064a \u0627\u0644\u0642\u0631\u0635 %s \u062d\u0631\u0629.\n+  Hide and Compress: \u0625\u062e\u0641\u0627\u0621 \u0648\u0636\u063a\u0637\n+  'COMPRESSED AND HIDDEN: %s': '\u0636\u064f\u063a\u0637\u062a \u0648\u0623\u064f\u062e\u0641\u064a\u062a: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': '\u0644\u0627 \u062a\u0648\u062c\u062f \u0645\u0633\u0627\u062d\u0629 \u0644\u0647\u0630\u0627 \u0627\u0644\u0646\u0633\u062e: \u064a\u062d\u062a\u0627\u062c \u0625\u0644\u0649 \u0646\u062d\u0648 %s\u060c \u0648\u0641\u064a \u0627\u0644\u0642\u0631\u0635 %s \u062d\u0631\u0629.'\n+  OVER 4 GB FOR FAT32: \u0623\u0643\u062b\u0631 \u0645\u0646 4 GB \u0644\u0640 FAT32\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: \u0647\u0630\u0627 \u0627\u0644\u0642\u0631\u0635 %s\u060c \u0648\u0644\u0627 \u064a\u062d\u0641\u0638 \u0645\u0644\u0641\u064b\u0627 \u0628\u062d\u062c\u0645 4 GB \u0623\u0648 \u0623\u0643\u062b\u0631\u060c \u0648\u0627\u0644\u0635\u0648\u0631\u0629 \u0627\u0644\u062c\u062f\u064a\u062f\u0629 \u0633\u062a\u0643\u0648\u0646 %s.\n+  '%s, %s clusters, %s free of %s': '%s\u060c \u0643\u062a\u0644 %s\u060c %s \u062d\u0631\u0629 \u0645\u0646 %s'\n+  Delete This Sweep: \u062d\u0630\u0641 \u0647\u0630\u0627 \u0627\u0644\u062a\u062f\u0631\u062c\n+  Delete %s? RIPPS2's own sweeps stay as they are.: \u0647\u0644 \u062a\u062d\u0630\u0641 %s\u061f \u062a\u062f\u0631\u062c\u0627\u062a RIPPS2 \u0646\u0641\u0633\u0647\u0627 \u062a\u0628\u0642\u0649 \u0643\u0645\u0627 \u0647\u064a.\n+  Sweep: \u062a\u062f\u0631\u062c\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: \u062a\u0645\u064a\u0651\u0632 \u0627\u0644\u0634\u0627\u0631\u0627\u062a \u0627\u0644\u0623\u0644\u0639\u0627\u0628 \u0627\u0644\u062a\u064a \u0641\u064a\u0647\u0627 \u0625\u0646\u062c\u0627\u0632\u0627\u062a \u0641\u064a \u0642\u0648\u0627\u0626\u0645\u0643\u060c \u0645\u0639 \u0639\u062f\u062f \u0645\u0627 \u0641\u062a\u062d\u062a\u0647. \u062a\u062d\u062a\u0627\u062c \u0625\u0644\u0649 \u062a\u0634\u063a\u064a\u0644 \u0627\u0644\u0642\u064a\u0627\u0633 \u0639\u0646 \u0628\u064f\u0639\u062f.\n labels:\n   SETTINGS_SAVED: \u062d\u064f\u0641\u0638\u062a \u0627\u0644\u0625\u0639\u062f\u0627\u062f\u0627\u062a \u0641\u064a %s\n   CONFIRMATION_EXIT: \u0627\u0644\u062e\u0631\u0648\u062c \u0625\u0644\u0649 \u0627\u0644\u0645\u062a\u0635\u0641\u062d\u061f\n@@ -1257,7 +1424,7 @@ labels:\n   SAVE_ICONS: \u0623\u064a\u0642\u0648\u0646\u0627\u062a \u0627\u0644\u062d\u0641\u0638 \u062b\u0644\u0627\u062b\u064a\u0629 \u0627\u0644\u0623\u0628\u0639\u0627\u062f\n   HINT_SAVE_ICONS: \u064a\u0639\u0631\u0636 \u0623\u064a\u0642\u0648\u0646\u0629 \u062d\u0641\u0638 \u0627\u0644\u0644\u0639\u0628\u0629 \u0627\u0644\u0645\u062e\u062a\u0627\u0631\u0629 \u0648\u0647\u064a \u062a\u062f\u0648\u0631\u060c \u062d\u064a\u062b \u064a\u0643\u0648\u0646 \u0644\u0644\u0633\u0645\u0629 \u0645\u0643\u0627\u0646 \u0644\u0647\u0627. \u064a\u0642\u0631\u0623 VMC \u0627\u0644\u062e\u0627\u0635 \u0628\u0627\u0644\u0644\u0639\u0628\u0629 \u0623\u0648\u0644\u0627\u064b\u060c \u062b\u0645 \u0628\u0637\u0627\u0642\u0627\u062a \u0627\u0644\u0630\u0627\u0643\u0631\u0629.\n   NETBOOT_RESTART_NOW: \u064a\u062a\u063a\u064a\u0631 \u0628\u0631\u0648\u062a\u0648\u0643\u0648\u0644 \u0627\u0644\u0634\u0628\u0643\u0629 \u0628\u0639\u062f \u0625\u0639\u0627\u062f\u0629 \u0627\u0644\u062a\u0634\u063a\u064a\u0644. \u062d\u0641\u0638 \u0627\u0644\u0625\u0639\u062f\u0627\u062f\u0627\u062a \u0648\u0625\u0639\u0627\u062f\u0629 \u062a\u0634\u063a\u064a\u0644 OPL \u0627\u0644\u0622\u0646\u061f\n-  HINT_NET_PROTOCOL: '\u0623\u064a \u062e\u062f\u0645\u0629 \u0634\u0628\u0643\u0629 \u064a\u0633\u062a\u062e\u062f\u0645\u0647\u0627 RiptOPL: \u0645\u0634\u0627\u0631\u0643\u0629 SMB\u060c \u0623\u0648 UDPFS\u060c \u0623\u0648 UDPBD\u060c \u0623\u0648 HTTP. \u0627\u0644\u0625\u0639\u062f\u0627\u062f \u0646\u0641\u0633\u0647 \u0627\u0644\u0645\u0648\u062c\u0648\u062f \u0641\u064a \u0645\u0635\u0627\u062f\u0631 \u0627\u0644\u0623\u0644\u0639\u0627\u0628. \u0627\u0644\u0627\u0646\u062a\u0642\u0627\u0644 \u0645\u0646 \u062e\u062f\u0645\u0629 \u062a\u0639\u0645\u0644 \u0628\u0627\u0644\u0641\u0639\u0644 \u064a\u062a\u0637\u0644\u0628 \u0625\u0639\u0627\u062f\u0629 \u062a\u0634\u063a\u064a\u0644.'\n+  HINT_NET_PROTOCOL: '\u0623\u064a \u062e\u062f\u0645\u0629 \u0634\u0628\u0643\u0629 \u064a\u0633\u062a\u062e\u062f\u0645\u0647\u0627 RiptOPL: \u0645\u0634\u0627\u0631\u0643\u0629 SMB\u060c \u0623\u0648 UDPFS\u060c \u0623\u0648 UDPBD\u060c \u0623\u0648 HTTP. \u0627\u0644\u0627\u0646\u062a\u0642\u0627\u0644 \u0645\u0646 \u062e\u062f\u0645\u0629 \u062a\u0639\u0645\u0644 \u0628\u0627\u0644\u0641\u0639\u0644 \u064a\u062a\u0637\u0644\u0628 \u0625\u0639\u0627\u062f\u0629 \u062a\u0634\u063a\u064a\u0644.'\n   HINT_UDPFS_ACCESS: UDPFS \u0641\u0642\u0637. \u0627\u0644\u0645\u0644\u0641\u0627\u062a \u064a\u0642\u0631\u0623 \u0645\u0644\u0641\u0627\u062a ISO \u0627\u0644\u0645\u0646\u0641\u0631\u062f\u0629 \u0645\u0646 \u0645\u062c\u0644\u062f \u0623\u0644\u0639\u0627\u0628 \u0627\u0644\u062e\u0627\u062f\u0645\u061b \u0648IMG \u064a\u0642\u0631\u0623 \u0635\u0648\u0631\u0629 \u0642\u0631\u0635 \u0643\u0645\u062d\u0631\u0643. SMB \u0648HTTP \u064a\u0633\u062a\u062e\u062f\u0645\u0627\u0646 \u0627\u0644\u0645\u0644\u0641\u0627\u062a \u062f\u0627\u0626\u0645\u0627\u064b\u060c \u0648UDPBD \u064a\u0633\u062a\u062e\u062f\u0645 IMG \u062f\u0627\u0626\u0645\u0627\u064b.\n   HINT_SMB_VERSION: \u0623\u064a \u0625\u0635\u062f\u0627\u0631 \u0645\u0646 SMB \u064a\u064f\u0633\u062a\u062e\u062f\u0645 \u0644\u0644\u062a\u062e\u0627\u0637\u0628 \u0645\u0639 \u0645\u0634\u0627\u0631\u0643\u0629 \u0627\u0644\u062d\u0627\u0633\u0648\u0628.\n   HINT_NET_ADVANCED: \u064a\u0641\u062a\u062d \u0648\u0636\u0639 \u0627\u0631\u062a\u0628\u0627\u0637 Ethernet \u0648\u0645\u0646\u0641\u0630 SMB \u0644\u0644\u062a\u062d\u0631\u064a\u0631.\n@@ -1458,3 +1625,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: \u0643\u064a\u0641 \u062a\u0635\u0628\u062d \u0627\u0644\u0644\u0642\u0637\u0627\u062a \u0627\u0644\u0645\u0646\u062a\u0638\u0631\u0629 \u0639\u0644\u0649 \u0628\u0643\u0631\u0629 \u0643\u0644 \u0644\u0639\u0628\u0629 \u0645\u0644\u0641\u0627\u062a PNG.\n   RIPPS2_SETTINGS_HOME: \u062d\u0641\u0638 \u0627\u0644\u0625\u0639\u062f\u0627\u062f\u0627\u062a \u0641\u064a\n   HINT_RIPPS2_SETTINGS_HOME: '\u0623\u064a\u0646 \u064a\u062d\u0641\u0638 RIPPS2 \u0625\u0639\u062f\u0627\u062f\u0627\u062a\u0647: \u0641\u064a \u062d\u0641\u0638\u0647 \u0627\u0644\u062e\u0627\u0635 \u0639\u0644\u0649 \u0628\u0637\u0627\u0642\u0629 \u0630\u0627\u0643\u0631\u0629\u060c \u0623\u0648 \u0641\u064a \u0645\u062c\u0644\u062f RIPPS2 \u0639\u0644\u0649 \u0648\u062d\u062f\u0629 \u062a\u062e\u0632\u064a\u0646 (\u0627\u0644\u0642\u0631\u0635 \u0627\u0644\u0635\u0644\u0628 \u0627\u0644\u062f\u0627\u062e\u0644\u064a \u0623\u0648 USB \u0623\u0648 MX4SIO \u0623\u0648 iLink \u0623\u0648 MMCE)\u060c \u0623\u0648 \u0641\u064a \u0645\u062c\u0644\u062f \u062a\u0643\u062a\u0628\u0647.'\n+  RIPPS2_LAUNCHERS: \u0627\u0644\u0645\u0634\u063a\u0651\u0644\u0627\u062a\n+  RIPPS2_LOOK_NOW: \u0627\u0628\u062d\u062b \u0627\u0644\u0622\u0646\n+  HINT_RIPPS2_LOOK_NOW: \u064a\u0628\u062d\u062b RIPPS2 \u0641\u064a \u0627\u0644\u0645\u062d\u0631\u0643\u0627\u062a \u0627\u0644\u0645\u0634\u063a\u0651\u0644\u0629 \u0639\u0646 POPSTARTER\u060c \u0645\u0644\u0641\u0627\u062a POPS\u060c Ember\u060c BIOS \u0627\u0644\u062e\u0627\u0635 \u0628\u0645\u062d\u0627\u0643\u064a Ember\u060c \u0648 Neutrino\u060c \u0648\u064a\u062d\u0641\u0638 \u0645\u0643\u0627\u0646 \u0643\u0644 \u0645\u0646\u0647\u0627. \u0643\u0644 \u062a\u0634\u063a\u064a\u0644 \u064a\u0642\u0631\u0624\u0647\u0627 \u0645\u0646 \u0647\u0646\u0627\u0643.\n+  RIPPS2_HIDE_GAME: \u0625\u062e\u0641\u0627\u0621 \u0627\u0644\u0644\u0639\u0628\u0629\n+  RIPPS2_HIDDEN_GAMES: \u0627\u0644\u0623\u0644\u0639\u0627\u0628 \u0627\u0644\u0645\u062e\u0641\u064a\u0629\n+  HINT_RIPPS2_HIDDEN_GAMES: \u0627\u0644\u0623\u0644\u0639\u0627\u0628 \u0627\u0644\u0645\u062e\u0641\u064a\u0629 \u0628\u0623\u0645\u0631 \u0625\u062e\u0641\u0627\u0621 \u0627\u0644\u0644\u0639\u0628\u0629\u060c \u0639\u0644\u0649 \u0627\u0644\u0645\u062d\u0631\u0643\u0627\u062a \u0627\u0644\u0645\u0634\u063a\u0651\u0644\u0629. \u0623\u0638\u0647\u0631 \u0648\u0627\u062d\u062f\u0629 \u0645\u062c\u062f\u062f\u0627\u064b\u060c \u0623\u0648 \u0627\u062d\u0630\u0641\u0647\u0627. \u0643\u0644 \u0645\u062d\u0631\u0643 \u064a\u062d\u062a\u0641\u0638 \u0628\u0642\u0627\u0626\u0645\u062a\u0647 \u0627\u0644\u062e\u0627\u0635\u0629.\n+  HINT_RIPPS2_BDM_DEVICES: '\u0627\u0644\u0623\u0642\u0631\u0627\u0635 \u0627\u0644\u062a\u064a \u064a\u0642\u0631\u0623 \u0645\u0646\u0647\u0627 BDM \u0627\u0644\u0623\u0644\u0639\u0627\u0628: USB \u0648MX4SIO \u0648\u0628\u0637\u0627\u0642\u0629 PC Card CF \u0648\u0642\u0631\u0635 DVR \u0641\u064a \u062c\u0647\u0627\u0632 DESR \u0648iLink. \u064a\u062a\u0628\u0639 \u0627\u0644\u0642\u0631\u0635 \u0627\u0644\u0635\u0644\u0628 \u0627\u0644\u062f\u0627\u062e\u0644\u064a \u0648\u0636\u0639 \u062a\u0634\u063a\u064a\u0644 \u0627\u0644\u0642\u0631\u0635 \u0627\u0644\u0635\u0644\u0628.'\n+  HINT_RIPPS2_SCREEN: '\u0635\u0648\u0631\u0629 \u0627\u0644\u0642\u0627\u0626\u0645\u0629: \u0648\u0636\u0639 \u0627\u0644\u0641\u064a\u062f\u064a\u0648 \u0648\u0627\u0644\u0634\u0627\u0634\u0629 \u0627\u0644\u0639\u0631\u064a\u0636\u0629 \u0648\u0645\u0648\u0636\u0639\u0647\u0627 \u0639\u0644\u0649 \u0627\u0644\u062a\u0644\u0641\u0627\u0632 (H-POS \u0648V-POS) \u0648overscan.'\n+  HINT_RIPPS2_LAYOUT: \u0623\u064a\u0646 \u062a\u0642\u0639 \u0627\u0644\u0641\u0626\u0627\u062a \u0648\u0643\u064a\u0641 \u062a\u062f\u062e\u0644 \u0627\u0644\u0635\u0641\u062d\u0629\u060c \u062b\u0645 \u0623\u063a\u0644\u0641\u0629 RIPFLOW \u0648\u0634\u0628\u0643\u0629 RIPGRID.\n+  HINT_RIPPS2_COLORS_TEXT: \u0633\u0645\u0627\u0643\u0629 \u0627\u0644\u0646\u0635 \u0648\u0644\u0648\u0646 \u0627\u0644\u0633\u0645\u0629 \u0648\u062a\u062f\u0631\u062c \u0627\u0644\u0623\u0644\u0648\u0627\u0646 \u0648\u0643\u0644 \u0644\u0648\u0646.\n+  HINT_RIPPS2_TEXT_WEIGHT: '\u0645\u062f\u0649 \u0633\u0645\u0627\u0643\u0629 \u0646\u0635 RIPPS2 \u0646\u0641\u0633\u0647: \u062a\u0644\u0645\u064a\u062d\u0627\u062a \u0627\u0644\u0625\u0639\u062f\u0627\u062f\u0627\u062a \u0648\u0627\u0644\u062d\u0627\u0644\u0629 \u0648\u062d\u0648\u0644. \u0639\u0627\u062f\u064a \u0643\u0645\u0627 \u0643\u0627\u0646\u061b \u0645\u062a\u0648\u0633\u0637 \u0648\u0639\u0631\u064a\u0636 \u064a\u064f\u0642\u0631\u0622\u0646 \u0623\u0641\u0636\u0644 \u0639\u0644\u0649 \u0635\u0648\u0631\u0629 \u0646\u0627\u0639\u0645\u0629.'\n+  HINT_RIPPS2_HDD_START: '\u0627\u0644\u0642\u0631\u0635 \u0627\u0644\u0635\u0644\u0628 \u0627\u0644\u062f\u0627\u062e\u0644\u064a\u060c APA \u0623\u0648 exFAT: \u064a\u0642\u0631\u0623\u0647 RIPPS2 \u0623\u064a\u064b\u0651\u0627 \u0643\u0627\u0646 \u0648\u064a\u0639\u0631\u0636\u0647 \u0645\u0631\u0629 \u0648\u0627\u062d\u062f\u0629 \u0643\u0623\u0644\u0639\u0627\u0628 \u0627\u0644\u0642\u0631\u0635 \u0627\u0644\u0635\u0644\u0628. \u0625\u064a\u0642\u0627\u0641 \u064a\u062a\u0631\u0643\u0647 \u0648\u0634\u0623\u0646\u0647.'\n+  HINT_RIPPS2_THEME_COLOR: \u0644\u0648\u0646 \u0627\u0644\u0633\u0645\u0629 \u0646\u0641\u0633\u0647. \u0627\u062e\u062a\u0631 \u0645\u062e\u0635\u0635 \u0643\u0644\u0648\u0646 \u0644\u0644\u0633\u0645\u0629 \u0644\u062a\u062e\u062a\u0627\u0631\u0647 \u0628\u0646\u0641\u0633\u0643 \u0643\u0645\u0627 \u062a\u064f\u062e\u062a\u0627\u0631 \u0623\u0644\u0648\u0627\u0646 \u0627\u0644\u0646\u0635.\n+  HINT_RIPPS2_SWEEP: \u062e\u0645\u0633\u0629 \u0623\u0644\u0648\u0627\u0646 \u062a\u062a\u062f\u0631\u062c \u0639\u0628\u0631\u0647\u0627 \u0627\u0644\u0633\u0645\u0629 \u0628\u0628\u0637\u0621 \u0648\u0633\u0644\u0627\u0633\u0629. \u0642\u0648\u0633 \u0642\u0632\u062d\u060c \u0623\u0648 \u062a\u062f\u0631\u062c \u0645\u0646 \u0643\u0644 \u0644\u0648\u0646 \u0633\u0645\u0629\u060c \u0623\u0648 \u062a\u062f\u0631\u062c\u0643 \u0627\u0644\u062e\u0627\u0635. \u0625\u064a\u0642\u0627\u0641 \u064a\u0628\u0642\u064a \u0644\u0648\u0646 \u0627\u0644\u0633\u0645\u0629 \u062b\u0627\u0628\u062a\u064b\u0627.\n+  HINT_RIPPS2_SWEEP_COLORS: \u0623\u0644\u0648\u0627\u0646 \u0627\u0644\u062a\u062f\u0631\u062c \u0627\u0644\u062e\u0645\u0633\u0629 \u0648\u0633\u0631\u0639\u062a\u0647. \u063a\u064a\u0651\u0631 \u0623\u062d\u062f \u062a\u062f\u0631\u062c\u0627\u062a RIPPS2 \u0641\u064a\u064f\u062d\u0641\u0638 \u0643\u062a\u062f\u0631\u062c\u0643 \u0627\u0644\u062e\u0627\u0635 \u0628\u0627\u0644\u0627\u0633\u0645 \u0627\u0644\u0630\u064a \u062a\u0639\u0637\u064a\u0647.\n+  HINT_RIPPS2_SWEEP_NAME: \u0627\u0633\u0645 \u062a\u062f\u0631\u062c\u0643 \u0641\u064a \u0642\u0627\u0626\u0645\u0629 \u062a\u062f\u0631\u062c \u0627\u0644\u0623\u0644\u0648\u0627\u0646.\n+  HINT_RIPPS2_SWEEP_COLOR: \u0623\u062d\u062f \u0627\u0644\u0623\u0644\u0648\u0627\u0646 \u0627\u0644\u062e\u0645\u0633\u0629 \u0627\u0644\u062a\u064a \u064a\u062a\u062f\u0631\u062c \u0639\u0628\u0631\u0647\u0627\u060c \u0628\u0627\u0644\u062a\u0631\u062a\u064a\u0628.\n+  HINT_RIPPS2_SWEEP_SPEED: '\u0645\u062f\u0629 \u0628\u0642\u0627\u0621 \u0643\u0644 \u0644\u0648\u0646 \u0642\u0628\u0644 \u0623\u0646 \u064a\u0638\u0647\u0631 \u0627\u0644\u062a\u0627\u0644\u064a. \u062d\u062a\u0649 \u062d\u064a\u0648\u064a \u064a\u0628\u0642\u0649 \u0633\u0644\u0633\u064b\u0627: \u0627\u0644\u0623\u0644\u0648\u0627\u0646 \u0644\u0627 \u062a\u0648\u0645\u0636 \u0623\u0628\u062f\u064b\u0627.'\n+  RIPPS2_HIDE_COMPRESS: \u0625\u062e\u0641\u0627\u0621 \u0627\u0644\u0644\u0639\u0628\u0629 \u0648\u0627\u0644\u0636\u063a\u0637\n+  HINT_RIPPS2_SELECT_HOLD: 'SELECT \u0645\u0645\u0633\u0648\u0643 4.2 \u062b \u0641\u064a \u0642\u0627\u0626\u0645\u0629 \u0623\u0644\u0639\u0627\u0628. \u0642\u0627\u0626\u0645\u0629: \u0627\u0644\u0633\u0645\u0627\u062a \u0641\u064a \u0644\u0648\u062d\u0629\u060c \u0627\u062e\u062a\u0631 \u0648\u0627\u062d\u062f\u0629. \u062a\u062f\u0648\u064a\u0631: \u0627\u0644\u0633\u0645\u0629 \u0627\u0644\u062a\u0627\u0644\u064a\u0629. \u0625\u064a\u0642\u0627\u0641: \u0644\u0627 \u0634\u064a\u0621 (\u0627\u0644\u0636\u063a\u0637\u0629 \u0645\u0627 \u0632\u0627\u0644\u062a \u062a\u062d\u062f\u0651\u062b).'"
        },
        {
          "sha": "1f01dad312f3bb722bacb73857a6b8c04ca1ee59",
          "filename": "elf/lang/Bulgarian.yml",
          "status": "modified",
          "additions": 209,
          "deletions": 22,
          "changes": 231,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FBulgarian.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FBulgarian.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FBulgarian.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: Bulgarian\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTER \u041f\u041e\u0422\u0412\u042a\u0420\u0416\u0414\u0410\u0412\u0410, ESC \u041e\u0422\u041a\u0410\u0417\u0412\u0410. \u0412\u0421\u0415\u041a\u0418 \u0411\u0423\u0422\u041e\u041d \u041f\u041e\u041a\u0410\u0417\u0412\u0410 \u041a\u041b\u0410\u0412\u0418\u0428\u0418\u0422\u0415.\n   USB KEYBOARD: USB \u041a\u041b\u0410\u0412\u0418\u0410\u0422\u0423\u0420\u0410\n-  '      DESR DVR ISOs (experimental)': '      DESR DVR ISO \u0444\u0430\u0439\u043b\u043e\u0432\u0435 (\u0435\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0430\u043b\u043d\u043e)'\n-  '      Internal HDD (exFAT)': '      \u0412\u044a\u0442\u0440\u0435\u0448\u0435\u043d HDD (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (\u0435\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0430\u043b\u043d\u043e)'\n-  '      Protocol': '      \u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b'\n   Access: \u0414\u043e\u0441\u0442\u044a\u043f\n   Applications Page Start Mode: \u0420\u0435\u0436\u0438\u043c \u043d\u0430 \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0442\u0430 \u0441 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\n   Art Delay: \u0417\u0430\u0431\u0430\u0432\u044f\u043d\u0435 \u043d\u0430 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u0442\u0430\n@@ -22,7 +18,6 @@ strings:\n   Fifth: \u041f\u0435\u0442\u0438\n   First: \u041f\u044a\u0440\u0432\u0438\n   Fourth: \u0427\u0435\u0442\u0432\u044a\u0440\u0442\u0438\n-  HDD (APA) Start Mode: \u0420\u0435\u0436\u0438\u043c \u043d\u0430 \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 HDD (APA)\n   HDD Cache: HDD \u043a\u0435\u0448\n   HDD Spindown: \u0417\u0430\u0441\u043f\u0438\u0432\u0430\u043d\u0435 \u043d\u0430 HDD\n   MMCE Settings: \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043d\u0430 MMCE\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: \u0422\u043e\u0432\u0430 \u0435 PS2 \u0438\u0433\u0440\u0430 \u0441 ESR \u043a\u0440\u044a\u043f\u043a\u0430. \u0421\u043b\u043e\u0436\u0435\u0442\u0435 ESR (esr*.elf) \u0432 APPS, BOOT \u0438\u043b\u0438 ESR \u043d\u0430 \u043a\u0430\u0440\u0442\u0430 \u0441 \u043f\u0430\u043c\u0435\u0442 \u0438\u043b\u0438 USB \u0444\u043b\u0430\u0448\u043a\u0430, \u0437\u0430 \u0434\u0430 \u044f \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u0442\u0435.\n   '%s, BUILD %d': '%s, \u041a\u041e\u041c\u041f\u0418\u041b\u0410\u0426\u0418\u042f %d'\n   '... and the anonymous ...': '... \u0438 \u0430\u043d\u043e\u043d\u0438\u043c\u043d\u0438\u0442\u0435 ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': '1.8.0 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d \u0431\u0435\u0437 \u043f\u0440\u043e\u043c\u0435\u043d\u0438 (AFL-3.0): github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: \u0420\u0430\u0437\u0433\u043b\u043e\u0431\u044f\u0432\u0430\u043d\u0435 \u0438 \u0441\u0433\u043b\u043e\u0431\u044f\u0432\u0430\u043d\u0435 \u043d\u0430\u043d\u043e\u0432\u043e \u043d\u0430 RiptOPL, \u0440\u0430\u0437\u043a\u043b\u043e\u043d\u0435\u043d\u0438\u0435\u0442\u043e \u043d\u0430 Open PS2 Loader,\n   ABOUT: \u041e\u0422\u041d\u041e\u0421\u041d\u041e\n   'ACHIEVEMENTS: SPIRIT OF RA': '\u041f\u041e\u0421\u0422\u0418\u0416\u0415\u041d\u0418\u042f: SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): AVI \u0432\u0438\u0434\u0435\u043e \u0432\u044a\u0432 \u0424\u0430\u0439\u043b\u043e\u0432\u0435\u0442\u0435 \u0432 \u043f\u0430\u043c\u0435\u0442\u0442\u0430, \u0440\u0430\u0437\u043a\u043b\u043e\u043d\u0435\u043d\u0438\u0435 \u043d\u0430 RIPPS2 (AFL 2.0)\n   Apollo Save Tool by bucanero: Apollo Save Tool \u043e\u0442 bucanero\n   BACK: \u041d\u0410\u0417\u0410\u0414\n+  Back (keeps both): \u041d\u0430\u0437\u0430\u0434 (\u0437\u0430\u043f\u0430\u0437\u0432\u0430 \u0438 \u0434\u0432\u0430\u0442\u0430)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: \u0422\u0430\u0437\u0438 MMCE \u043a\u0430\u0440\u0442\u0430 \u043d\u0435 \u043c\u043e\u0436\u0435 \u0434\u0430 \u043f\u0440\u0435\u0438\u043c\u0435\u043d\u0443\u0432\u0430 \u0444\u0430\u0439\u043b, \u0430 \u043a\u043e\u043c\u043f\u0440\u0435\u0441\u0438\u0440\u0430\u043d\u0435\u0442\u043e \u0438\u043c\u0430 \u043d\u0443\u0436\u0434\u0430 \u043e\u0442 \u0442\u043e\u0432\u0430 \u0432 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0430\u0442\u0430 \u0441\u0438 \u0441\u0442\u044a\u043f\u043a\u0430. \u041e\u0431\u043d\u043e\u0432\u0435\u0442\u0435 \u0444\u044a\u0440\u043c\u0443\u0435\u0440\u0430 \u045d sd2psXtd \u0434\u043e 1.4.0 \u0438\u043b\u0438 \u043f\u043e-\u043d\u043e\u0432 (sd2psxtd.github.io/download) \u0438 \u043e\u043f\u0438\u0442\u0430\u0439\u0442\u0435 \u043e\u0442\u043d\u043e\u0432\u043e. \u041d\u0438\u0449\u043e \u043d\u0435 \u0435 \u043f\u0440\u043e\u043c\u0435\u043d\u0435\u043d\u043e.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): BIBLE \u0435 PS2BBL 1.2.0, \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u043d\u0438\u0442\u0435 \u043a\u043e\u043c\u043f\u0438\u043b\u0430\u0446\u0438\u0438 \u043d\u0430 El Isra (\u043a\u0430\u0440\u0442\u0430 \u0438 HDD), \u0431\u0435\u0437 \u043f\u0440\u043e\u043c\u0435\u043d\u0438 (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: BIOS \u0437\u0432\u0443\u0446\u0438 > \u0418\u0437\u0432\u043b\u0438\u0447\u0430\u043d\u0435 \u0433\u0438 \u0432\u044a\u0437\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0436\u0434\u0430 \u043a\u0430\u0442\u043e SPU2; \u043d\u0435 \u0441\u0435 \u043f\u0440\u0438\u043b\u0430\u0433\u0430 BIOS\n   BOOT LOCK AND BIBLE: BOOT LOCK \u0418 BIBLE\n   BOOT LOCK edits its config too: BOOT LOCK \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0430 \u0438 \u043d\u0435\u0433\u043e\u0432\u0430\u0442\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f\n   BUILD DETAILS: \u041f\u041e\u0414\u0420\u041e\u0411\u041d\u041e\u0421\u0422\u0418 \u0417\u0410 \u041a\u041e\u041c\u041f\u0418\u041b\u0410\u0426\u0418\u042f\u0422\u0410\n-  BUILT ON RIPTOPL: \u0418\u0417\u0413\u0420\u0410\u0414\u0415\u041d\u041e \u0412\u042a\u0420\u0425\u0423 RIPTOPL\n   BearSSL by Thomas Pornin: BearSSL \u043e\u0442 Thomas Pornin\n   Built with Claude (Anthropic): \u041d\u0430\u043f\u0440\u0430\u0432\u0435\u043d\u043e \u0441 Claude (Anthropic)\n   CD PLAYER: CD \u041f\u041b\u0415\u0419\u042a\u0420\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek \u0438 Sleek Bold, \u043d\u0430\u0440\u0438\u0441\u0443\u0432\u0430\u043d\u0438 \u0437\u0430 RIPPS2\n   RetroAchievements on the console by hacan359 (yoba): RetroAchievements \u043d\u0430 \u043a\u043e\u043d\u0437\u043e\u043b\u0430\u0442\u0430 \u043e\u0442 hacan359 (yoba)\n   RiptOPL by NathanNeurotic: RiptOPL \u043e\u0442 NathanNeurotic\n-  Roboto Condensed by Google (Apache-2.0): Roboto Condensed \u043e\u0442 Google (Apache-2.0)\n   SOUNDS: \u0417\u0412\u0423\u0426\u0418\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: \u0418\u043d\u0441\u0442\u0430\u043b\u0430\u0442\u043e\u0440\u044a\u0442 \u043d\u0430 FMCB \u043e\u0442 SP193 \u0438 OSDMenu \u043e\u0442 pcm720, \u0441\u043b\u0435\u0434\u0432\u0430\u043d\u0438 \u0441\u0442\u044a\u043f\u043a\u0430 \u043f\u043e \u0441\u0442\u044a\u043f\u043a\u0430\n   SUPPORT: \u041f\u041e\u0414\u0414\u0420\u042a\u0416\u041a\u0410\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: wLaunchELF R3Z \u043e\u0442 NathanNeurotic\n   wLaunchELF by the ps2homebrew team: wLaunchELF \u043e\u0442 \u0435\u043a\u0438\u043f\u0430 \u043d\u0430 ps2homebrew\n   what RIPPS2's card image writes are checked against: \u0441\u043f\u0440\u044f\u043c\u043e \u043a\u0430\u043a\u0432\u043e \u0441\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0432\u0430 \u0437\u0430\u043f\u0438\u0441\u044a\u0442 \u043d\u0430 \u043e\u0431\u0440\u0430\u0437\u0438 \u043d\u0430 \u043a\u0430\u0440\u0442\u0438 \u043e\u0442 RIPPS2\n-  with its own look, launchers, file browser and save tools.: \u0441\u044a\u0441 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d \u0432\u0438\u0434, \u0441\u0442\u0430\u0440\u0442\u0435\u0440\u0438, \u0444\u0430\u0439\u043b\u043e\u0432 \u0431\u0440\u0430\u0443\u0437\u044a\u0440 \u0438 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0438 \u0437\u0430 \u0437\u0430\u043f\u0438\u0441\u0438.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: xeRAbora \u043e\u0442 hacan359, rcheevos \u043e\u0442 RetroAchievements\n   your console's own, ripped from its BIOS: \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u0438\u0442\u0435 \u0437\u0432\u0443\u0446\u0438 \u043d\u0430 \u0432\u0430\u0448\u0430\u0442\u0430 \u043a\u043e\u043d\u0437\u043e\u043b\u0430, \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438 \u043e\u0442 \u043d\u0435\u0439\u043d\u0438\u044f BIOS\n   ' (no DNS answer)': ' (\u043d\u044f\u043c\u0430 DNS \u043e\u0442\u0433\u043e\u0432\u043e\u0440)'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO: \u0437\u0430\u043f\u0438\u0441\u0432\u0430\u043d\u0435 \u043d\u0430 %.60s'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: Neutrino \u043d\u0435 \u043c\u043e\u0436\u0430 \u0434\u0430 \u0441\u0435 \u0437\u0430\u043f\u0438\u0448\u0435 \u0432 %s. \u041f\u0440\u043e\u0432\u0435\u0440\u0435\u0442\u0435 \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u043e\u0442\u043e \u043c\u044f\u0441\u0442\u043e \u0438 \u0434\u0430\u043b\u0438 \u043d\u0435 \u0435 \u0437\u0430\u0449\u0438\u0442\u0435\u043d\u043e \u0441\u0440\u0435\u0449\u0443 \u0437\u0430\u043f\u0438\u0441, \u0438 \u043e\u043f\u0438\u0442\u0430\u0439\u0442\u0435 \u043e\u0442\u043d\u043e\u0432\u043e.\n   THE GAME'S DRIVE  (%s/neutrino): \u0423\u0421\u0422\u0420\u041e\u0419\u0421\u0422\u0412\u041e\u0422\u041e \u041d\u0410 \u0418\u0413\u0420\u0410\u0422\u0410  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : \u0422\u0430\u0437\u0438 \u0438\u0433\u0440\u0430 \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430 \u0447\u0440\u0435\u0437 Neutrino, \u0430 \u0442\u0430\u043c, \u043a\u044a\u0434\u0435\u0442\u043e \u0442\u044a\u0440\u0441\u0438 RIPPS2 (\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438, \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u043d\u0430 \u0438\u0433\u0440\u0430\u0442\u0430, \u043a\u0430\u0440\u0442\u0438\u0442\u0435 \u0441 \u043f\u0430\u043c\u0435\u0442), \u043d\u044f\u043c\u0430 Neutrino. RIPPS2 \u043d\u043e\u0441\u0438 Neutrino 1.8.0 \u043e\u0442 rickgaiser (AFL-3.0). \u0414\u0430 \u0441\u0435 \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430 \u043b\u0438 \u0441\u0435\u0433\u0430? \u0421\u043b\u0435\u0434 \u0442\u043e\u0432\u0430 \u0438\u0437\u0431\u0438\u0440\u0430\u0442\u0435 \u043a\u044a\u0434\u0435.\n   '%d / %d FILES': '%d / %d \u0424\u0410\u0419\u041b\u0410'\n   '%d FOLDERS AND CONTENTS': '%d \u041f\u0410\u041f\u041a\u0418 \u0418 \u0421\u042a\u0414\u042a\u0420\u0416\u0410\u041d\u0418\u0415'\n   '%d FOLDERS, %d FOUND': '%d \u041f\u0410\u041f\u041a\u0418, \u041d\u0410\u041c\u0415\u0420\u0415\u041d\u0418 %d'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d \u043e\u0442 %d \u0432\u0441\u0435 \u043e\u0449\u0435 \u0437\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438'\n   '%s   on %s': '%s   \u043d\u0430 %s'\n   BADGES: \u0417\u041d\u0410\u0427\u041a\u0418\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: \u0417\u043d\u0430\u0447\u043a\u0438\u0442\u0435 \u0440\u0430\u0431\u043e\u0442\u044f\u0442 \u0441\u0430\u043c\u043e \u0441 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0430 \u0422\u0415\u041b\u0415\u041c\u0415\u0422\u0420\u0418\u042f \u0438 \u043f\u0443\u0441\u043d\u0430\u0442 xeRAbora \u043d\u0430 \u0432\u0430\u0448\u0438\u044f \u043a\u043e\u043c\u043f\u044e\u0442\u044a\u0440; \u0434\u043e\u0442\u043e\u0433\u0430\u0432\u0430 \u043d\u0435 \u0441\u0442\u0440\u0443\u0432\u0430\u0442 \u043d\u0438\u0449\u043e.\n   CHECK THE DISC: \u041f\u0420\u041e\u0412\u0415\u0420\u041a\u0410 \u041d\u0410 \u0414\u0418\u0421\u041a\u0410\n   From this game's last CHECK GAME SUPPORT.: \u041e\u0442 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0430\u0442\u0430 \u041f\u0420\u041e\u0412\u0415\u0420\u041a\u0410 \u041d\u0410 \u041f\u041e\u0414\u0414\u0420\u042a\u0416\u041a\u0410\u0422\u0410 \u041d\u0410 \u0418\u0413\u0420\u0410\u0422\u0410 \u0437\u0430 \u0442\u0430\u0437\u0438 \u0438\u0433\u0440\u0430.\n   HOW IT WORKS: \u041a\u0410\u041a \u0420\u0410\u0411\u041e\u0422\u0418\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: \u041d\u0430 \u0442\u0430\u0437\u0438 \u043a\u043e\u043d\u0437\u043e\u043b\u0430 \u0438\u043c\u0430 PS2BBL \u0441\u044a\u0441 \u0437\u0430\u043a\u043b\u044e\u0447\u0432\u0430\u043d\u0435\u0442\u043e \u043d\u0430 RIPPS2.\n   AUDIO folder: \u043f\u0430\u043f\u043a\u0430\u0442\u0430 AUDIO\n   DRIVES: \u0423\u0421\u0422\u0420\u041e\u0419\u0421\u0422\u0412\u0410\n-  INSTALL RIPPS2'S COPY: \u0418\u041d\u0421\u0422\u0410\u041b\u0418\u0420\u0410\u041d\u0415 \u041d\u0410 \u041a\u041e\u041f\u0418\u0415\u0422\u041e \u041d\u0410 RIPPS2\n-  'Installed: ': '\u0418\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u043e: '\n   MANAGE: \u0423\u041f\u0420\u0410\u0412\u041b\u0415\u041d\u0418\u0415\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: \u0423\u041f\u0420\u0410\u0412\u041b\u0415\u041d\u0418\u0415 \u043f\u043e\u043a\u0430\u0437\u0432\u0430 \u043a\u0430\u043a\u0432\u043e \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430 \u0432\u0441\u044f\u043a\u0430 \u043a\u0430\u0440\u0442\u0430 \u0441 \u043f\u0430\u043c\u0435\u0442 \u0438 \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430 \u0438\u043b\u0438 \u043f\u043e\u043f\u0440\u0430\u0432\u044f BIBLE.\n   MEMORY CARD 0  (about 600 KB): \u041a\u0410\u0420\u0422\u0410 \u0421 \u041f\u0410\u041c\u0415\u0422 0  (\u043e\u043a\u043e\u043b\u043e 600 KB)\n   MEMORY CARD 1  (about 600 KB): \u041a\u0410\u0420\u0422\u0410 \u0421 \u041f\u0410\u041c\u0415\u0422 1  (\u043e\u043a\u043e\u043b\u043e 600 KB)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO: \u0434\u0440\u0430\u0439\u0432\u0435\u0440\u044a\u0442 \u043c\u0443 \u043d\u0435 \u0441\u0435 \u0437\u0430\u0440\u0435\u0434\u0438 (\u043a\u043e\u0434 %d).'\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO: \u0434\u0440\u0430\u0439\u0432\u0435\u0440\u044a\u0442 \u0435 \u0437\u0430\u0440\u0435\u0434\u0435\u043d, \u043d\u043e \u043d\u0438\u043a\u043e\u044f SD \u043a\u0430\u0440\u0442\u0430 \u043d\u0435 \u043e\u0442\u0433\u043e\u0432\u043e\u0440\u0438 (\u0441\u043b\u043e\u0442 mc1).'\n   NEUTRINO SETTINGS: \u041d\u0410\u0421\u0422\u0420\u041e\u0419\u041a\u0418 \u041d\u0410 NEUTRINO\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': '\u041e\u0449\u0435 \u043d\u044f\u043c\u0430: RIPPS2 \u043d\u044f\u043c\u0430 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u0438 \u0437\u0432\u0443\u0446\u0438. \u0418\u0437\u0432\u043b\u0435\u0447\u0435\u0442\u0435 \u0442\u0435\u0437\u0438 \u043d\u0430 \u043a\u043e\u043d\u0437\u043e\u043b\u0430\u0442\u0430 \u0438\u043b\u0438 \u0433\u0438 \u043d\u0430\u043f\u0440\u0430\u0432\u0435\u0442\u0435 \u0432 RIPPS2 Audio Studio.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: \u041d\u0435 \u0435 \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u043e. \u0418\u0433\u0440\u0430, \u043a\u043e\u044f\u0442\u043e \u0438\u043c\u0430 \u043d\u0443\u0436\u0434\u0430 \u043e\u0442 \u043d\u0435\u0433\u043e, \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430 \u043a\u043e\u043f\u0438\u0435\u0442\u043e \u043d\u0430 RIPPS2, \u0438\u043b\u0438 \u0433\u043e \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u0439\u0442\u0435 \u0442\u0443\u043a.\n   POPSTARTER NETWORK: \u041c\u0420\u0415\u0416\u0410 \u041d\u0410 POPSTARTER\n   POPSTARTER and Ember: POPSTARTER \u0438 Ember\n   PS1 SETTINGS: PS1 \u041d\u0410\u0421\u0422\u0420\u041e\u0419\u041a\u0418\n   PS2BBL 1.2.0 for RIPPS2: PS2BBL 1.2.0 \u0437\u0430 RIPPS2\n   RIP THIS CONSOLE'S SOUNDS: \u0418\u0417\u0412\u041b\u0418\u0427\u0410\u041d\u0415 \u041d\u0410 \u0417\u0412\u0423\u0426\u0418\u0422\u0415 \u041d\u0410 \u0422\u0410\u0417\u0418 \u041a\u041e\u041d\u0417\u041e\u041b\u0410\n-  RIPPS2's homebrew: homebrew \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u0438\u0442\u0435 \u043d\u0430 RIPPS2\n   STATUS: \u0421\u042a\u0421\u0422\u041e\u042f\u041d\u0418\u0415\n-  'STATUS: looking for RIPPS2''s homebrew': '\u0421\u042a\u0421\u0422\u041e\u042f\u041d\u0418\u0415: \u0442\u044a\u0440\u0441\u0435\u043d\u0435 \u043d\u0430 homebrew \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u0438\u0442\u0435 \u043d\u0430 RIPPS2'\n   a drive of unknown kind: \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043e\u0442 \u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u0435\u043d \u0432\u0438\u0434\n-  a drive or a memory card: \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0438\u043b\u0438 \u043a\u0430\u0440\u0442\u0430 \u0441 \u043f\u0430\u043c\u0435\u0442\n   boot loader, card by card: \u0437\u0430\u0440\u0435\u0436\u0434\u0430\u0449 \u043c\u043e\u0434\u0443\u043b, \u043a\u0430\u0440\u0442\u0430 \u043f\u043e \u043a\u0430\u0440\u0442\u0430\n   from its BIOS, into AUDIO: \u043e\u0442 \u043d\u0435\u0439\u043d\u0438\u044f BIOS, \u0432 AUDIO\n   internal HDD (exFAT): \u0432\u044a\u0442\u0440\u0435\u0448\u0435\u043d HDD (exFAT)\n@@ -975,10 +958,194 @@ strings:\n   Sidebar: \u0421\u0442\u0440\u0430\u043d\u0438\u0447\u043d\u0430 \u043b\u0435\u043d\u0442\u0430\n   your own copy, found on your drives and memory cards: \u0432\u0430\u0448\u0435\u0442\u043e \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043e \u043a\u043e\u043f\u0438\u0435, \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u043e \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u0442\u0430 \u0438 \u043a\u0430\u0440\u0442\u0438\u0442\u0435 \u043f\u0430\u043c\u0435\u0442\n   'your own, found and launched: github.com/Gageformer/Ember/releases': '\u0432\u0430\u0448\u0435\u0442\u043e \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043e, \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u043e \u0438 \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u043e: github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': '\u041d\u0435 \u0435 \u043d\u0430\u043c\u0435\u0440\u0435\u043d \u043d\u0430 mass0: (%s). \u0421\u043b\u043e\u0436\u0435\u0442\u0435 ember.elf \u0438 \u0441\u0432\u043e\u044f bios.bin \u0432 \u043f\u0430\u043f\u043a\u0430\u0442\u0430 \u043c\u0443 EMBER.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: \u041d\u0435 \u0435 \u043d\u0430\u043c\u0435\u0440\u0435\u043d. \u0421\u043b\u043e\u0436\u0435\u0442\u0435 \u0441\u0432\u043e\u044f POPSTARTER.ELF \u0432 \u043f\u0430\u043f\u043a\u0430 POPS \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e.\n   Launch Disc: \u0421\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0434\u0438\u0441\u043a\n   Memory Files: \u0424\u0430\u0439\u043b\u043e\u0432\u0435 \u0432 \u043f\u0430\u043c\u0435\u0442\u0442\u0430\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: RIPPS2 \u043d\u0430\u043c\u0435\u0440\u0438 %d \u043e\u0442 5. \u0412\u0441\u0435\u043a\u0438 \u0441\u0435 \u0437\u0430\u043f\u043e\u043c\u043d\u044f \u0438 \u0432\u0441\u044f\u043a\u043e \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u0435 \u0433\u043e \u0447\u0435\u0442\u0435 \u043e\u0442\u0442\u0430\u043c.\n+  not found on the started drives: \u043d\u0435 \u0435 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u043e \u043d\u0430 \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\n+  not looked for yet: \u043e\u0449\u0435 \u043d\u0435 \u0435 \u0442\u044a\u0440\u0441\u0435\u043d\u043e\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s \u043d\u0435 \u043e\u0442\u0433\u043e\u0432\u043e\u0440\u0438, \u0437\u0430\u0442\u043e\u0432\u0430 %s \u043d\u0435 \u043c\u043e\u0436\u0435 \u0434\u0430 \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430. \u0421\u0432\u044a\u0440\u0437\u0430\u043d \u043b\u0438 \u0435?'\n+  '%s is not on %s any more, so it has left All Games.': '%s \u0432\u0435\u0447\u0435 \u043d\u0435 \u0435 \u043d\u0430 %s, \u0437\u0430\u0442\u043e\u0432\u0430 \u043d\u0430\u043f\u0443\u0441\u043d\u0430 \u0412\u0441\u0438\u0447\u043a\u0438 \u0438\u0433\u0440\u0438.'\n+  STARTING %s: \u0421\u0422\u0410\u0420\u0422\u0418\u0420\u0410\u041d\u0415 \u041d\u0410 %s\n+  the SMB share: SMB \u0441\u043f\u043e\u0434\u0435\u043b\u044f\u043d\u0435\u0442\u043e\n+  the drive: \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e\n+  the internal HDD (APA): \u0432\u044a\u0442\u0440\u0435\u0448\u043d\u0438\u044f HDD (APA)\n+  the internal HDD (exFAT): \u0432\u044a\u0442\u0440\u0435\u0448\u043d\u0438\u044f HDD (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: \u0418\u0417\u0413\u0420\u0410\u0414\u0415\u041d \u0412\u042a\u0420\u0425\u0423 RIPTOPL, \u0421 NEUTRINO\n+  'Special thanks to all the YouTube love as well:': '\u0421\u043f\u0435\u0446\u0438\u0430\u043b\u043d\u0438 \u0431\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u043d\u043e\u0441\u0442\u0438 \u0438 \u0437\u0430 \u0446\u044f\u043b\u0430\u0442\u0430 \u043b\u044e\u0431\u043e\u0432 \u043e\u0442 YouTube:'\n+  and its own look, launchers, file browser and save tools.: \u0438 \u0441\u044a\u0441 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d \u043e\u0431\u043b\u0438\u043a, \u0441\u0442\u0430\u0440\u0442\u0435\u0440\u0438, \u0444\u0430\u0439\u043b\u043e\u0432 \u0431\u0440\u0430\u0443\u0437\u044a\u0440 \u0438 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0438 \u0437\u0430 \u0437\u0430\u043f\u0438\u0441\u0438.\n+  glad to help however you reach me.: \u0441 \u0440\u0430\u0434\u043e\u0441\u0442 \u043f\u043e\u043c\u0430\u0433\u0430\u043c, \u043a\u0430\u043a\u0442\u043e \u0438 \u0434\u0430 \u043c\u0435 \u043d\u0430\u043c\u0435\u0440\u0438\u0442\u0435.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): \u0432\u0442\u043e\u0440\u043e\u0442\u043e \u0438\u0433\u0440\u043e\u0432\u043e \u044f\u0434\u0440\u043e, 1.8.0 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u0431\u0435\u0437 \u043f\u0440\u043e\u043c\u0435\u043d\u0438 (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: \u0441 Neutrino \u043d\u0430 rickgaiser \u0432\u044a\u0442\u0440\u0435 \u043a\u0430\u0442\u043e \u0432\u0442\u043e\u0440\u043e \u0438\u0433\u0440\u043e\u0432\u043e \u044f\u0434\u0440\u043e,\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : '\u0422\u0430\u0437\u0438 \u0438\u0433\u0440\u0430 \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430 \u0447\u0440\u0435\u0437 Neutrino, \u0430 RIPPS2 \u043d\u0435 \u043d\u0430\u043c\u0435\u0440\u0438 \u0442\u0430\u043a\u044a\u0432: \u043d\u0438\u0442\u043e \u0432 \u043f\u044a\u0442\u044f \u043e\u0442 \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438, \u043d\u0438\u0442\u043e \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u043d\u0430 \u0438\u0433\u0440\u0430\u0442\u0430, \u043d\u0438\u0442\u043e \u043d\u0430 \u043a\u0430\u0440\u0442\u0438\u0442\u0435 \u0441 \u043f\u0430\u043c\u0435\u0442, \u043d\u0438\u0442\u043e \u043d\u0430 \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e. RIPPS2 \u043d\u043e\u0441\u0438 Neutrino 1.8.0 \u043e\u0442 rickgaiser (AFL-3.0): \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u0439\u0442\u0435 \u0433\u043e \u0432\u0435\u0434\u043d\u044a\u0436, \u043a\u044a\u0434\u0435\u0442\u043e \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0435, \u0438 \u0432\u0441\u044f\u043a\u0430 \u0438\u0433\u0440\u0430 \u0441 Neutrino \u0433\u043e \u043f\u043e\u043b\u0437\u0432\u0430 \u043e\u0442\u0442\u0430\u043c.'\n+  STARTING THE DRIVES: \u0421\u0422\u0410\u0420\u0422\u0418\u0420\u0410\u041d\u0415 \u041d\u0410 \u0423\u0421\u0422\u0420\u041e\u0419\u0421\u0422\u0412\u0410\u0422\u0410\n+  '%d games, on the started drives': '%d \u0438\u0433\u0440\u0438, \u043d\u0430 \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s: \u041d\u042f\u041a\u041e\u0418 \u0424\u0410\u0419\u041b\u041e\u0412\u0415 \u041d\u0415 \u041c\u041e\u0416\u0410\u0425\u0410 \u0414\u0410 \u0411\u042a\u0414\u0410\u0422 \u0418\u0417\u0422\u0420\u0418\u0422\u0418'\n+  '%u partitions, all in order': '%u \u0434\u044f\u043b\u0430, \u0432\u0441\u0438\u0447\u043a\u0438 \u0432 \u0440\u0435\u0434'\n+  ', %d art images': ', %d \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043d\u0430 \u043e\u0431\u043b\u043e\u0436\u043a\u0438'\n+  ', and ': ', \u0438 '\n+  ', its cheats': ', \u043d\u0435\u0439\u043d\u0438\u0442\u0435 \u043a\u043e\u0434\u043e\u0432\u0435'\n+  ', its settings': ', \u043d\u0435\u0439\u043d\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438'\n+  1 game, on the started drives: 1 \u0438\u0433\u0440\u0430, \u043d\u0430 \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: PS1 \u0438\u0433\u0440\u0430 \u043e\u0442\u0442\u0443\u043a \u0441\u0435 \u0438\u0437\u0442\u0440\u0438\u0432\u0430 \u0432\u044a\u0432 \u0424\u0430\u0439\u043b\u043e\u0432\u0435 \u0432 \u043f\u0430\u043c\u0435\u0442\u0442\u0430, \u043a\u044a\u0434\u0435\u0442\u043e \u0435 \u043e\u0442\u0434\u0435\u043b\u0435\u043d \u0444\u0430\u0439\u043b.\n+  A network catalogue's games are deleted on the PC that serves them.: \u0418\u0433\u0440\u0438\u0442\u0435 \u043e\u0442 \u043c\u0440\u0435\u0436\u043e\u0432 \u043a\u0430\u0442\u0430\u043b\u043e\u0433 \u0441\u0435 \u0438\u0437\u0442\u0440\u0438\u0432\u0430\u0442 \u043d\u0430 \u043a\u043e\u043c\u043f\u044e\u0442\u044a\u0440\u0430, \u043a\u043e\u0439\u0442\u043e \u0433\u0438 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u044f.\n+  DELETE FOR GOOD: \u0418\u0417\u0422\u0420\u0418\u0419 \u0417\u0410\u0412\u0418\u041d\u0410\u0413\u0418\n+  DELETE IT: \u0418\u0417\u0422\u0420\u0418\u0419 \u042f\n+  'DELETED %s: %s FREED': '%s \u0418\u0417\u0422\u0420\u0418\u0422\u0410: \u041e\u0421\u0412\u041e\u0411\u041e\u0414\u0415\u041d\u0418 %s'\n+  'Delete %s from %s? Goes: %s.': '\u0414\u0430 \u0441\u0435 \u0438\u0437\u0442\u0440\u0438\u0435 \u043b\u0438 %s \u043e\u0442 %s? \u0417\u0430\u043c\u0438\u043d\u0430\u0432\u0430: %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': '\u0414\u0430 \u0441\u0435 \u0438\u0437\u0442\u0440\u0438\u0435 \u043b\u0438 %s \u043e\u0442 %s? \u0417\u0430\u043c\u0438\u043d\u0430\u0432\u0430: %s. \u041e\u0441\u0442\u0430\u0432\u0430: %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: \u0414\u0430 \u0441\u0435 \u0438\u0437\u0442\u0440\u0438\u044f\u0442 \u043b\u0438 \u0438 \u043d\u0435\u0439\u043d\u0438\u0442\u0435 \u0437\u0430\u043f\u0438\u0441\u0438 \u0438 \u0441\u043d\u0438\u043c\u043a\u0438 RIPSHOT? \u041d\u0435 \u0433\u0438 \u0437\u0430\u043f\u0430\u0437\u0432\u0430.\n+  HIDDEN GAMES: \u0421\u041a\u0420\u0418\u0422\u0418 \u0418\u0413\u0420\u0418\n+  'HIDDEN: %s': '\u0421\u041a\u0420\u0418\u0422\u0410: %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: \u0414\u0430 \u0441\u0435 \u0441\u043a\u0440\u0438\u0435 \u043b\u0438 %s? \u0422\u044f \u043d\u0430\u043f\u0443\u0441\u043a\u0430 \u0432\u0441\u0435\u043a\u0438 \u0441\u043f\u0438\u0441\u044a\u043a, \u0441\u044a\u0449\u043e \u0412\u0441\u0438\u0447\u043a\u0438 \u0438\u0433\u0440\u0438 \u0438 \u041b\u044e\u0431\u0438\u043c\u0438. \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 > \u0418\u0437\u0442\u043e\u0447\u043d\u0438\u0446\u0438 \u043d\u0430 \u0438\u0433\u0440\u0438 > \u0421\u043a\u0440\u0438\u0442\u0438 \u0438\u0433\u0440\u0438 \u044f \u043f\u043e\u043a\u0430\u0437\u0432\u0430 \u043e\u0442\u043d\u043e\u0432\u043e.\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: \u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430. %s \u0441\u0435 \u0438\u0437\u0442\u0440\u0438\u0432\u0430 \u0437\u0430\u0432\u0438\u043d\u0430\u0433\u0438 \u0438 \u043d\u0438\u0449\u043e \u043d\u0435 \u043c\u043e\u0436\u0435 \u0434\u0430 \u044f \u0432\u044a\u0440\u043d\u0435. \u041d\u0430\u0442\u0438\u0441\u043d\u0435\u0442\u0435 START, \u0437\u0430 \u0434\u0430 \u044f \u0438\u0437\u0442\u0440\u0438\u0435\u0442\u0435.\n+  None. Hide Game, in a game's options, puts one here.: \u041d\u044f\u043c\u0430. \u0421\u043a\u0440\u0438\u0439 \u0438\u0433\u0440\u0430\u0442\u0430, \u0432 \u043e\u043f\u0446\u0438\u0438\u0442\u0435 \u043d\u0430 \u0438\u0433\u0440\u0430, \u0441\u043b\u0430\u0433\u0430 \u0435\u0434\u043d\u0430 \u0442\u0443\u043a.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : '\u041d\u0438\u0449\u043e \u043d\u0435 \u0431\u0435\u0448\u0435 \u0438\u0437\u0442\u0440\u0438\u0442\u043e: \u0442\u0430\u0431\u043b\u0438\u0446\u0430\u0442\u0430 \u043d\u0430 \u0434\u044f\u043b\u043e\u0432\u0435\u0442\u0435 \u043d\u0430 \u0442\u043e\u0432\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 \u043f\u0430\u0441\u0432\u0430 \u043d\u0430 \u0442\u043e\u0437\u0438 \u0434\u0438\u0441\u043a (%s). \u041c\u043e\u0436\u0435 \u0434\u0430 \u0435 \u0437\u0430\u043f\u0438\u0441\u0430\u043d\u0430 \u043e\u0442 \u043e\u0431\u0440\u0430\u0437 \u043d\u0430 \u0434\u0438\u0441\u043a \u0441 \u0434\u0440\u0443\u0433 \u0440\u0430\u0437\u043c\u0435\u0440. \u041f\u044a\u0440\u0432\u043e \u044f \u043f\u043e\u043f\u0440\u0430\u0432\u0435\u0442\u0435 \u043d\u0430 \u043a\u043e\u043c\u043f\u044e\u0442\u044a\u0440.'\n+  RIPPS2 cannot delete a game from this page.: RIPPS2 \u043d\u0435 \u043c\u043e\u0436\u0435 \u0434\u0430 \u0438\u0437\u0442\u0440\u0438\u0435 \u0438\u0433\u0440\u0430 \u043e\u0442 \u0442\u0430\u0437\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430.\n+  RIPPS2 could not find this game's files on its drive.: RIPPS2 \u043d\u0435 \u043d\u0430\u043c\u0435\u0440\u0438 \u0444\u0430\u0439\u043b\u043e\u0432\u0435\u0442\u0435 \u043d\u0430 \u0442\u0430\u0437\u0438 \u0438\u0433\u0440\u0430 \u043d\u0430 \u043d\u0435\u0439\u043d\u043e\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e.\n+  Reading the hidden lists: \u0427\u0435\u0442\u0435\u043d\u0435 \u043d\u0430 \u0441\u043a\u0440\u0438\u0442\u0438\u0442\u0435 \u0441\u043f\u0438\u0441\u044a\u0446\u0438\n+  SHOW AGAIN OR DELETE: \u041f\u041e\u041a\u0410\u0416\u0418 \u041e\u0422\u041d\u041e\u0412\u041e \u0418\u041b\u0418 \u0418\u0417\u0422\u0420\u0418\u0419\n+  SHOW IT AGAIN: \u041f\u041e\u041a\u0410\u0416\u0418 \u042f \u041e\u0422\u041d\u041e\u0412\u041e\n+  That game is not on its drive any more.: \u0422\u0430\u0437\u0438 \u0438\u0433\u0440\u0430 \u0432\u0435\u0447\u0435 \u043d\u0435 \u0435 \u043d\u0430 \u0441\u0432\u043e\u0435\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: \u0421\u043a\u0440\u0438\u0442\u0438\u044f\u0442 \u0441\u043f\u0438\u0441\u044a\u043a \u043d\u0435 \u043c\u043e\u0436\u0430 \u0434\u0430 \u0441\u0435 \u0437\u0430\u043f\u0438\u0448\u0435 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u043d\u0430 \u0438\u0433\u0440\u0430\u0442\u0430. \u0417\u0430\u0449\u0438\u0442\u0435\u043d\u043e \u043b\u0438 \u0435 \u043e\u0442 \u0437\u0430\u043f\u0438\u0441, \u0438\u043b\u0438 \u041e\u043f\u0435\u0440\u0430\u0446\u0438\u0438\u0442\u0435 \u0437\u0430 \u0437\u0430\u043f\u0438\u0441 \u0441\u0430 \u0438\u0437\u043a\u043b\u044e\u0447\u0435\u043d\u0438?\n+  The partition could not be removed (%d). Nothing was deleted.: \u0414\u044f\u043b\u044a\u0442 \u043d\u0435 \u043c\u043e\u0436\u0430 \u0434\u0430 \u0431\u044a\u0434\u0435 \u043f\u0440\u0435\u043c\u0430\u0445\u043d\u0430\u0442 (%d). \u041d\u0438\u0449\u043e \u043d\u0435 \u0431\u0435\u0448\u0435 \u0438\u0437\u0442\u0440\u0438\u0442\u043e.\n+  This drive cannot keep a hidden list (a network catalogue).: \u0422\u043e\u0432\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 \u043c\u043e\u0436\u0435 \u0434\u0430 \u043f\u0430\u0437\u0438 \u0441\u043a\u0440\u0438\u0442 \u0441\u043f\u0438\u0441\u044a\u043a (\u043c\u0440\u0435\u0436\u043e\u0432 \u043a\u0430\u0442\u0430\u043b\u043e\u0433).\n+  This game's drive has not answered yet. Wait for it, then try again.: \u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u043d\u0430 \u0442\u0430\u0437\u0438 \u0438\u0433\u0440\u0430 \u043e\u0449\u0435 \u043d\u0435 \u0435 \u043e\u0442\u0433\u043e\u0432\u043e\u0440\u0438\u043b\u043e. \u0418\u0437\u0447\u0430\u043a\u0430\u0439\u0442\u0435 \u0433\u043e \u0438 \u043e\u043f\u0438\u0442\u0430\u0439\u0442\u0435 \u043e\u0442\u043d\u043e\u0432\u043e.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: \u0414\u044f\u043b\u044a\u0442 \u043d\u0430 \u0442\u0430\u0437\u0438 \u0438\u0433\u0440\u0430 \u0435 \u043e\u0442 \u0442\u0435\u0437\u0438, \u043a\u043e\u0438\u0442\u043e RIPPS2 \u043d\u0435 \u043f\u0440\u0435\u043c\u0430\u0445\u0432\u0430 (\u0441\u0438\u0441\u0442\u0435\u043c\u0435\u043d \u0434\u044f\u043b \u0438\u043b\u0438 \u0442\u043e\u0437\u0438 \u0441 \u043d\u0435\u0433\u043e\u0432\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': '\u0435\u0434\u0438\u043d \u0434\u044f\u043b \u0435 \u043f\u043e-\u0433\u043e\u043b\u044f\u043c, \u043e\u0442\u043a\u043e\u043b\u043a\u043e\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0432\u0430 \u0442\u043e\u0437\u0438 \u0434\u0438\u0441\u043a: \u0442\u0430\u0431\u043b\u0438\u0446\u0430\u0442\u0430 \u0435 \u043d\u0430\u043f\u0440\u0430\u0432\u0435\u043d\u0430 \u0437\u0430 \u0434\u0438\u0441\u043a \u0441 \u0434\u0440\u0443\u0433 \u0440\u0430\u0437\u043c\u0435\u0440'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': '\u0435\u0434\u0438\u043d \u0434\u044f\u043b \u043c\u0438\u043d\u0430\u0432\u0430 \u043e\u0442\u0432\u044a\u0434 \u043a\u0440\u0430\u044f \u043d\u0430 \u0442\u043e\u0437\u0438 \u0434\u0438\u0441\u043a (\u0441\u0435\u043a\u0442\u043e\u0440 %u \u043e\u0442 %u): \u0442\u0430\u0431\u043b\u0438\u0446\u0430\u0442\u0430 \u0435 \u043d\u0430\u043f\u0440\u0430\u0432\u0435\u043d\u0430 \u0437\u0430 \u043f\u043e-\u0433\u043e\u043b\u044f\u043c \u0434\u0438\u0441\u043a'\n+  its RIPSHOT pictures: \u043d\u0435\u0439\u043d\u0438\u0442\u0435 \u0441\u043d\u0438\u043c\u043a\u0438 RIPSHOT\n+  its drive: \u043d\u0435\u0439\u043d\u043e\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\n+  its saves: \u043d\u0435\u0439\u043d\u0438\u0442\u0435 \u0437\u0430\u043f\u0438\u0441\u0438\n+  sector %u holds no partition header: \u0441\u0435\u043a\u0442\u043e\u0440 %u \u043d\u044f\u043c\u0430 \u0437\u0430\u0433\u043b\u0430\u0432\u0438\u0435 \u043d\u0430 \u0434\u044f\u043b\n+  the chain does not close at __mbr: \u0432\u0435\u0440\u0438\u0433\u0430\u0442\u0430 \u043d\u0435 \u0441\u0435 \u0437\u0430\u0442\u0432\u0430\u0440\u044f \u043f\u0440\u0438 __mbr\n+  the chain skips from sector %u: \u0432\u0435\u0440\u0438\u0433\u0430\u0442\u0430 \u043f\u0440\u0435\u0441\u043a\u0430\u0447\u0430 \u043e\u0442 \u0441\u0435\u043a\u0442\u043e\u0440 %u\n+  the chain's links disagree at sector %u: \u0437\u0432\u0435\u043d\u0430\u0442\u0430 \u043d\u0430 \u0432\u0435\u0440\u0438\u0433\u0430\u0442\u0430 \u043d\u0435 \u0441\u044a\u0432\u043f\u0430\u0434\u0430\u0442 \u043f\u0440\u0438 \u0441\u0435\u043a\u0442\u043e\u0440 %u\n+  the drive's size could not be read: \u0440\u0430\u0437\u043c\u0435\u0440\u044a\u0442 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u043d\u0435 \u043c\u043e\u0436\u0430 \u0434\u0430 \u0441\u0435 \u043f\u0440\u043e\u0447\u0435\u0442\u0435\n+  the game (%s): \u0438\u0433\u0440\u0430\u0442\u0430 (%s)\n+  the game's folder and everything in it (%s): \u043f\u0430\u043f\u043a\u0430\u0442\u0430 \u043d\u0430 \u0438\u0433\u0440\u0430\u0442\u0430 \u0438 \u0432\u0441\u0438\u0447\u043a\u043e \u0432 \u043d\u0435\u044f (%s)\n+  the header at sector %u could not be read: \u0437\u0430\u0433\u043b\u0430\u0432\u0438\u0435\u0442\u043e \u043f\u0440\u0438 \u0441\u0435\u043a\u0442\u043e\u0440 %u \u043d\u0435 \u043c\u043e\u0436\u0430 \u0434\u0430 \u0441\u0435 \u043f\u0440\u043e\u0447\u0435\u0442\u0435\n+  the header at sector %u fails its checksum: \u0437\u0430\u0433\u043b\u0430\u0432\u0438\u0435\u0442\u043e \u043f\u0440\u0438 \u0441\u0435\u043a\u0442\u043e\u0440 %u \u043d\u0435 \u043c\u0438\u043d\u0430\u0432\u0430 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043d\u0430\u0442\u0430 \u0441\u0443\u043c\u0430\n+  the partition at sector %u is not where or as large as APA puts one: \u0434\u044f\u043b\u044a\u0442 \u043f\u0440\u0438 \u0441\u0435\u043a\u0442\u043e\u0440 %u \u043d\u0435 \u0435 \u0442\u0430\u043c, \u043a\u044a\u0434\u0435\u0442\u043e, \u0438\u043b\u0438 \u043a\u043e\u043b\u043a\u043e\u0442\u043e \u0433\u043e\u043b\u044f\u043c, APA \u0441\u043b\u0430\u0433\u0430 \u0435\u0434\u0438\u043d\n+  the settings and art another copy of it uses: \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u0442\u0435 \u0438 \u043e\u0431\u043b\u043e\u0436\u043a\u0438\u0442\u0435, \u043a\u043e\u0438\u0442\u043e \u043f\u043e\u043b\u0437\u0432\u0430 \u0434\u0440\u0443\u0433\u043e \u043d\u0435\u0439\u043d\u043e \u043a\u043e\u043f\u0438\u0435\n+  '%d FOLDERS': '%d \u041f\u0410\u041f\u041a\u0418'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': '%s \u043d\u0435 \u043c\u043e\u0436\u0430 \u0434\u0430 \u0441\u0435 \u043a\u043e\u043f\u0438\u0440\u0430 \u0432 %s. \u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u043f\u044a\u043b\u043d\u043e \u043b\u0438 \u0435 \u0438\u043b\u0438 \u0437\u0430\u0449\u0438\u0442\u0435\u043d\u043e \u043e\u0442 \u0437\u0430\u043f\u0438\u0441?'\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s \u043d\u0435 \u0435 \u0442\u0430\u043c, \u043a\u044a\u0434\u0435\u0442\u043e \u0442\u044a\u0440\u0441\u0438 RIPPS2. \u0414\u0430 \u0441\u0435 \u043f\u043e\u0442\u044a\u0440\u0441\u0438 \u043b\u0438 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u0442\u0430, \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u0438 \u043e\u0442 RIPPS2? \u041c\u044f\u0441\u0442\u043e\u0442\u043e, \u043a\u044a\u0434\u0435\u0442\u043e \u0431\u044a\u0434\u0435 \u043d\u0430\u043c\u0435\u0440\u0435\u043d, \u0441\u0435 \u0437\u0430\u043f\u043e\u043c\u043d\u044f \u0437\u0430 \u0432\u0441\u044f\u043a\u043e \u0441\u043b\u0435\u0434\u0432\u0430\u0449\u043e \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u0435.'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Ember \u043d\u0435 \u0435 \u0432 %s, \u0434\u043e \u0438\u0433\u0440\u0438\u0442\u0435, \u0430 \u0442\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u0435 \u0442\u0430\u043c. RIPPS2 \u0433\u043e \u043d\u0430\u043c\u0435\u0440\u0438 \u0432 %s: START \u0433\u043e \u043a\u043e\u043f\u0438\u0440\u0430 \u0442\u0430\u043c (%d KB) \u0438 \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430 \u0438\u0433\u0440\u0430\u0442\u0430.'\n+  Ember's BIOS: BIOS \u043d\u0430 Ember\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : 'BIOS \u043d\u0430 Ember (bios.bin) \u043d\u0435 \u0435 \u0432 %s, \u0434\u043e Ember. RIPPS2 \u043d\u0430\u043c\u0435\u0440\u0438 \u0435\u0434\u0438\u043d \u0432 %s: START \u0433\u043e \u043a\u043e\u043f\u0438\u0440\u0430 \u0442\u0430\u043c (%d KB) \u0438 \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430 \u0438\u0433\u0440\u0430\u0442\u0430.'\n+  LOOKING FOR YOUR LAUNCHERS: \u0422\u042a\u0420\u0421\u0415\u041d\u0415 \u041d\u0410 \u0412\u0410\u0428\u0418\u0422\u0415 \u0421\u0422\u0410\u0420\u0422\u0415\u0420\u0418\n+  The POPS files: \u0424\u0430\u0439\u043b\u043e\u0432\u0435\u0442\u0435 \u043d\u0430 POPS\n+  '%s  MMCE card': '%s  MMCE \u043a\u0430\u0440\u0442\u0430'\n+  INSTALL RIPPS2'S NEUTRINO: \u0418\u041d\u0421\u0422\u0410\u041b\u0418\u0420\u0410\u0419 NEUTRINO \u041d\u0410 RIPPS2\n+  'Internal HDD (APA): %s free of %s': '\u0412\u044a\u0442\u0440\u0435\u0448\u0435\u043d HDD (APA): \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u0438 %s \u043e\u0442 %s'\n+  LAUNCHERS: \u0421\u0422\u0410\u0420\u0422\u0415\u0420\u0418\n+  LOOK FOR THEM: \u041f\u041e\u0422\u042a\u0420\u0421\u0418 \u0413\u0418\n+  'Memory card %d: %d KB free': '\u041a\u0430\u0440\u0442\u0430 \u0441 \u043f\u0430\u043c\u0435\u0442 %d: \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u0438 %d KB'\n+  POPS files: \u0424\u0430\u0439\u043b\u043e\u0432\u0435 \u043d\u0430 POPS\n+  START EVERY DRIVE: \u0421\u0422\u0410\u0420\u0422\u0418\u0420\u0410\u0419 \u0412\u0421\u0418\u0427\u041a\u0418 \u0423\u0421\u0422\u0420\u041e\u0419\u0421\u0422\u0412\u0410\n+  'STATUS: looking at the drives and the homebrew': '\u0421\u042a\u0421\u0422\u041e\u042f\u041d\u0418\u0415: \u043f\u0440\u0435\u0433\u043b\u0435\u0434 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u0442\u0430 \u0438 homebrew'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): \u041f\u044a\u0440\u0432\u043e \u0434\u0430 \u0441\u0435 \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u0442 \u0432\u0441\u0438\u0447\u043a\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430, \u0437\u0430 \u0434\u0430 \u0441\u0442\u0438\u0433\u043d\u0435 \u0442\u044a\u0440\u0441\u0435\u043d\u0435\u0442\u043e \u0434\u043e \u0432\u0441\u0438\u0447\u043a\u0438? (\u041d\u0435 \u0442\u044a\u0440\u0441\u0438 \u043d\u0430 \u0432\u0435\u0447\u0435 \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u0438\u0442\u0435.)\n+  USB, HDD, MMCE and the rest: USB, HDD, MMCE \u0438 \u043e\u0441\u0442\u0430\u043d\u0430\u043b\u043e\u0442\u043e\n+  every started drive: \u0432\u0441\u044f\u043a\u043e \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\n+  found where you keep them: \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 \u0442\u0430\u043c, \u043a\u044a\u0434\u0435\u0442\u043e \u0433\u0438 \u0434\u044a\u0440\u0436\u0438\u0442\u0435\n+  not found yet: \u043e\u0449\u0435 \u043d\u0435 \u0435 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u043e\n+  not on a started drive: \u043d\u0435 \u0435 \u043d\u0430 \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\n+  only if you have none: \u0441\u0430\u043c\u043e \u0430\u043a\u043e \u043d\u044f\u043c\u0430\u0442\u0435 \u0442\u0430\u043a\u044a\u0432\n+  starts every drive first: \u043f\u044a\u0440\u0432\u043e \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430 \u0432\u0441\u0438\u0447\u043a\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\n+  Drives, launchers and homebrew: \u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430, \u0441\u0442\u0430\u0440\u0442\u0435\u0440\u0438 \u0438 homebrew\n+  BDM Devices: BDM \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\n+  Color Sweep: \u0426\u0432\u0435\u0442\u043e\u0432\u0430 \u0441\u043c\u044f\u043d\u0430\n+  Colors and Text: \u0426\u0432\u0435\u0442\u043e\u0432\u0435 \u0438 \u0442\u0435\u043a\u0441\u0442\n+  HDD POPS partitions: POPS \u0434\u044f\u043b\u043e\u0432\u0435 \u043d\u0430 HDD\n+  Layout: \u041f\u043e\u0434\u0440\u0435\u0434\u0431\u0430\n+  Missing Covers: \u041b\u0438\u043f\u0441\u0432\u0430\u0449\u0438 \u043e\u0431\u043b\u043e\u0436\u043a\u0438\n+  Name: \u0418\u043c\u0435\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: \u041e\u0449\u0435 \u043d\u0435 \u0435 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0430 SMB \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043d\u0430 POPSTARTER. \u041f\u043e\u043f\u044a\u043b\u043d\u0435\u0442\u0435 \u0440\u0435\u0434\u043e\u0432\u0435\u0442\u0435 \u043f\u043e-\u0434\u043e\u043b\u0443.\n+  POPSTARTER Network: \u041c\u0440\u0435\u0436\u0430 \u043d\u0430 POPSTARTER\n+  POPSTARTER SMB Network: SMB \u043c\u0440\u0435\u0436\u0430 \u043d\u0430 POPSTARTER\n+  POPSTARTER UDPFS Network: UDPFS \u043c\u0440\u0435\u0436\u0430 \u043d\u0430 POPSTARTER\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: RIPPS2 \u0441\u0430\u043c \u0434\u043e\u0431\u0430\u0432\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e, ISO, VMC \u0438 \u0432\u0438\u0434\u0435\u043e\u0440\u0435\u0436\u0438\u043c\u0430.\n+  Screen: \u0415\u043a\u0440\u0430\u043d\n+  Speed: \u0421\u043a\u043e\u0440\u043e\u0441\u0442\n+  Sweep Colors: \u0426\u0432\u0435\u0442\u043e\u0432\u0435 \u043d\u0430 \u0441\u043c\u044f\u043d\u0430\u0442\u0430\n+  Text Weight: \u0414\u0435\u0431\u0435\u043b\u0438\u043d\u0430 \u043d\u0430 \u0442\u0435\u043a\u0441\u0442\u0430\n+  Theme Color: \u041e\u0441\u043d\u043e\u0432\u0435\u043d \u0446\u0432\u044f\u0442\n+  Audio: \u0410\u0443\u0434\u0438\u043e\n+  Bold: \u0423\u0434\u0435\u0431\u0435\u043b\u0435\u043d\n+  Controllers: \u041a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u0440\u0438\n+  Lively: \u0416\u0438\u0432\u043e\n+  Medium: \u0421\u0440\u0435\u0434\u0435\u043d\n+  PS Emulation: PS \u0435\u043c\u0443\u043b\u0430\u0446\u0438\u044f\n+  Regular: \u041e\u0431\u0438\u043a\u043d\u043e\u0432\u0435\u043d\n+  Slow: \u0411\u0430\u0432\u043d\u043e\n+  A MOMENT: \u041c\u0418\u0413\n+  A TASK IN THE BACKGROUND: \u0417\u0410\u0414\u0410\u0427\u0410 \u041d\u0410 \u0417\u0410\u0414\u0415\u041d \u041f\u041b\u0410\u041d\n+  ABOUT %d S: \u041e\u041a\u041e\u041b\u041e %d \u0421\n+  ANY MOMENT: \u0412\u0421\u0415\u041a\u0418 \u041c\u0418\u0413\n+  READING THE GAME LIST OF %s: \u0427\u0415\u0422\u0415 \u0421\u041f\u0418\u0421\u042a\u041a\u0410 \u0421 \u0418\u0413\u0420\u0418 \u041d\u0410 %s\n+  SAVING A LIST: \u0417\u0410\u041f\u0418\u0421\u0412\u0410 \u0421\u041f\u0418\u0421\u042a\u041a\n+  START NOW: \u041f\u0423\u0421\u041d\u0418 \u0421\u0415\u0413\u0410\n+  STARTING: \u0421\u0422\u0410\u0420\u0422\u0418\u0420\u0410\n+  STARTS NEXT: \u0421\u0422\u0410\u0420\u0422\u0418\u0420\u0410 \u0421\u041b\u0415\u0414 \u0422\u041e\u0412\u0410\n+  STOPPING IT SAFELY: \u0421\u041f\u0418\u0420\u0410 \u0411\u0415\u0417\u041e\u041f\u0410\u0421\u041d\u041e\n+  THE LAST OF THE QUEUE: \u041a\u0420\u0410\u042f\u0422 \u041d\u0410 \u041e\u041f\u0410\u0428\u041a\u0410\u0422\u0410\n+  WHEN IT FINISHES: \u041a\u041e\u0413\u0410\u0422\u041e \u041f\u0420\u0418\u041a\u041b\u042e\u0427\u0418\n+  What each card starts; install or repair BIBLE.: \u041a\u0430\u043a\u0432\u043e \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430 \u0432\u0441\u044f\u043a\u0430 \u043a\u0430\u0440\u0442\u0430; \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u0439\u0442\u0435 \u0438\u043b\u0438 \u043f\u043e\u043f\u0440\u0430\u0432\u0435\u0442\u0435 BIBLE.\n+  Rainbow: \u0414\u044a\u0433\u0430\n+  Custom: \u0421\u043e\u0431\u0441\u0442\u0432\u0435\u043d\n+  DESR DVR ISOs (experimental): DESR DVR ISO \u0444\u0430\u0439\u043b\u043e\u0432\u0435 (\u0435\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0430\u043b\u043d\u043e)\n+  PC CARD CF (experimental): PC CARD CF (\u0435\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0430\u043b\u043d\u043e)\n+  HDD Start Mode: \u0420\u0435\u0436\u0438\u043c \u043d\u0430 \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 HDD\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): Roboto Condensed \u043e\u0442 The Roboto Project Authors (SIL OFL 1.1)\n+  '%s on %s': '%s \u043d\u0430 %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: %s \u0441\u0442\u0430\u043d\u0430 %s (%d%%), \u0432\u0441\u044f\u043a\u0430 \u0447\u0430\u0441\u0442 \u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u0430. \u0414\u0430 \u0441\u0435 \u043f\u0440\u0435\u043c\u0430\u0445\u043d\u0435 \u043b\u0438 ISO \u0438 \u0434\u0430 \u0441\u0435 \u0437\u0430\u043f\u0430\u0437\u0438 ZSO?'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: %s \u043e\u0442\u043d\u043e\u0432\u043e \u0441\u0442\u0430\u043d\u0430 %s, \u0432\u0441\u044f\u043a\u0430 \u0447\u0430\u0441\u0442 \u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u0430. \u0414\u0430 \u0441\u0435 \u043f\u0440\u0435\u043c\u0430\u0445\u043d\u0435 \u043b\u0438 ZSO \u0438 \u0434\u0430 \u0441\u0435 \u0437\u0430\u043f\u0430\u0437\u0438 ISO?'\n+  A ZSO of this game is already beside it. Remove one of the two first.: \u0414\u043e \u043d\u0435\u044f \u0432\u0435\u0447\u0435 \u0438\u043c\u0430 ZSO \u043d\u0430 \u0442\u0430\u0437\u0438 \u0438\u0433\u0440\u0430. \u041f\u044a\u0440\u0432\u043e \u043f\u0440\u0435\u043c\u0430\u0445\u043d\u0435\u0442\u0435 \u0435\u0434\u043d\u0430\u0442\u0430 \u043e\u0442 \u0434\u0432\u0435\u0442\u0435.\n+  About %d min, then checked: \u041e\u043a\u043e\u043b\u043e %d \u043c\u0438\u043d, \u043f\u043e\u0441\u043b\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430\n+  An ISO of this game is already beside it. Remove one of the two first.: \u0414\u043e \u043d\u0435\u044f \u0432\u0435\u0447\u0435 \u0438\u043c\u0430 ISO \u043d\u0430 \u0442\u0430\u0437\u0438 \u0438\u0433\u0440\u0430. \u041f\u044a\u0440\u0432\u043e \u043f\u0440\u0435\u043c\u0430\u0445\u043d\u0435\u0442\u0435 \u0435\u0434\u043d\u0430\u0442\u0430 \u043e\u0442 \u0434\u0432\u0435\u0442\u0435.\n+  CHECKING EVERY PIECE: \u041f\u0420\u041e\u0412\u0415\u0420\u041a\u0410 \u041d\u0410 \u0412\u0421\u042f\u041a\u0410 \u0427\u0410\u0421\u0422\n+  COMPRESS TO ZSO: \u041a\u041e\u041c\u041f\u0420\u0415\u0421\u0418\u0420\u0410\u0419 \u0412 ZSO\n+  'COMPRESSED %s: %s FREED': '\u041a\u041e\u041c\u041f\u0420\u0415\u0421\u0418\u0420\u0410\u041d\u0410 %s: \u041e\u0421\u0412\u041e\u0411\u041e\u0414\u0415\u041d\u0418 %s'\n+  COMPRESSING 1 OF 2: \u041a\u041e\u041c\u041f\u0420\u0415\u0421\u0418\u0420\u0410\u041d\u0415 1 \u041e\u0422 2\n+  COMPRESSING 2 OF 2: \u041a\u041e\u041c\u041f\u0420\u0415\u0421\u0418\u0420\u0410\u041d\u0415 2 \u041e\u0422 2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : \u0414\u0430 \u0441\u0435 \u043a\u043e\u043c\u043f\u0440\u0435\u0441\u0438\u0440\u0430 \u043b\u0438 %s? ISO \u043e\u0441\u0442\u0430\u0432\u0430, \u0434\u043e\u043a\u0430\u0442\u043e ZSO \u0431\u044a\u0434\u0435 \u043f\u0440\u043e\u0447\u0435\u0442\u0435\u043d \u043e\u0431\u0440\u0430\u0442\u043d\u043e \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d, \u0441\u043b\u0435\u0434 \u0442\u043e\u0432\u0430 RIPPS2 \u043f\u0438\u0442\u0430, \u043f\u0440\u0435\u0434\u0438 \u0434\u0430 \u043f\u0440\u0435\u043c\u0430\u0445\u043d\u0435 ISO. \u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0438\u043c\u0430 \u043d\u0443\u0436\u0434\u0430 \u043e\u0442 \u043c\u044f\u0441\u0442\u043e \u0438 \u0437\u0430 \u0434\u0432\u0430\u0442\u0430 \u0444\u0430\u0439\u043b\u0430 \u043f\u043e \u0432\u0440\u0435\u043c\u0435 \u043d\u0430 \u0440\u0430\u0431\u043e\u0442\u0430; \u043e\u0441\u0442\u0430\u0432\u0435\u0442\u0435 \u043a\u043e\u043d\u0437\u043e\u043b\u0430\u0442\u0430 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0430, \u0434\u043e\u043a\u0430\u0442\u043e \u043f\u0440\u0438\u043a\u043b\u044e\u0447\u0438. ZSO \u0441\u0435 \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430 \u0441\u0430\u043c\u043e \u0441 \u044f\u0434\u0440\u043e\u0442\u043e OPL (\u043d\u0435 Neutrino); \u0437\u0430\u043f\u0438\u0441\u0438\u0442\u0435, \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u0442\u0435, \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u0442\u0430 \u0438 \u043a\u043e\u0434\u043e\u0432\u0435\u0442\u0435 \u043e\u0441\u0442\u0430\u0432\u0430\u0442 \u0441 \u043d\u0435\u044f \u0438 \u0442\u044f \u043c\u043e\u0436\u0435 \u043e\u0442\u043d\u043e\u0432\u043e \u0434\u0430 \u0441\u0442\u0430\u043d\u0435 ISO \u043f\u043e \u0432\u0441\u044f\u043a\u043e \u0432\u0440\u0435\u043c\u0435.\n+  Compress to ZSO: \u041a\u043e\u043c\u043f\u0440\u0435\u0441\u0438\u0440\u0430\u0439 \u0432 ZSO\n+  Cycle: \u0420\u0435\u0434\u0443\u0432\u0430\u043d\u0435\n+  'Experimental: OPL core only (not Neutrino)': '\u0415\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0430\u043b\u043d\u043e: \u0441\u0430\u043c\u043e \u044f\u0434\u0440\u043e\u0442\u043e OPL (\u043d\u0435 Neutrino)'\n+  Hold SELECT: \u0417\u0430\u0434\u044a\u0440\u0436\u0430\u043d SELECT\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: \u041d\u0435 \u043c\u043e\u0436\u0430 \u0434\u0430 \u0431\u044a\u0434\u0435 \u0437\u0430\u0432\u044a\u0440\u0448\u0435\u043d\u043e (%s). \u0417\u0430\u043f\u0438\u0441\u0430\u043d\u043e\u0442\u043e \u0435 \u043f\u0440\u0435\u043c\u0430\u0445\u043d\u0430\u0442\u043e; \u0438\u0433\u0440\u0430\u0442\u0430 \u0435 \u043a\u0430\u043a\u0442\u043e \u0431\u0435\u0448\u0435.\n+  List: \u0421\u043f\u0438\u0441\u044a\u043a\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : \u0414\u0430 \u0441\u0442\u0430\u043d\u0435 \u043b\u0438 %s \u043e\u0442\u043d\u043e\u0432\u043e ISO? ZSO \u043e\u0441\u0442\u0430\u0432\u0430, \u0434\u043e\u043a\u0430\u0442\u043e ISO \u0431\u044a\u0434\u0435 \u043f\u0440\u043e\u0447\u0435\u0442\u0435\u043d \u043e\u0431\u0440\u0430\u0442\u043d\u043e \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d, \u0441\u043b\u0435\u0434 \u0442\u043e\u0432\u0430 RIPPS2 \u043f\u0438\u0442\u0430, \u043f\u0440\u0435\u0434\u0438 \u0434\u0430 \u043f\u0440\u0435\u043c\u0430\u0445\u043d\u0435 ZSO. \u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0438\u043c\u0430 \u043d\u0443\u0436\u0434\u0430 \u043e\u0442 \u043c\u044f\u0441\u0442\u043e \u0438 \u0437\u0430 \u0434\u0432\u0430\u0442\u0430 \u0444\u0430\u0439\u043b\u0430 \u043f\u043e \u0432\u0440\u0435\u043c\u0435 \u043d\u0430 \u0440\u0430\u0431\u043e\u0442\u0430; \u043e\u0441\u0442\u0430\u0432\u0435\u0442\u0435 \u043a\u043e\u043d\u0437\u043e\u043b\u0430\u0442\u0430 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0430, \u0434\u043e\u043a\u0430\u0442\u043e \u043f\u0440\u0438\u043a\u043b\u044e\u0447\u0438. \u0417\u0430\u043f\u0438\u0441\u0438\u0442\u0435, \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u0442\u0435, \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u0442\u0430 \u0438 \u043a\u043e\u0434\u043e\u0432\u0435\u0442\u0435 \u043e\u0441\u0442\u0430\u0432\u0430\u0442 \u0441 \u043d\u0435\u044f.\n+  Stop? What it has written so far is removed; the game stays as it was.: \u0421\u043f\u0438\u0440\u0430\u043d\u0435? \u0417\u0430\u043f\u0438\u0441\u0430\u043d\u043e\u0442\u043e \u0434\u043e\u0441\u0435\u0433\u0430 \u0441\u0435 \u043f\u0440\u0435\u043c\u0430\u0445\u0432\u0430; \u0438\u0433\u0440\u0430\u0442\u0430 \u043e\u0441\u0442\u0430\u0432\u0430 \u043a\u0430\u043a\u0442\u043e \u0431\u0435\u0448\u0435.\n+  THEMES: \u0422\u0415\u041c\u0418\n+  There is not enough memory for this just now.: \u0412 \u043c\u043e\u043c\u0435\u043d\u0442\u0430 \u043d\u044f\u043c\u0430 \u0434\u043e\u0441\u0442\u0430\u0442\u044a\u0447\u043d\u043e \u043f\u0430\u043c\u0435\u0442 \u0437\u0430 \u0442\u043e\u0432\u0430.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: \u0422\u043e\u0437\u0438 \u043e\u0431\u0440\u0430\u0437 \u043d\u0435 \u0435 \u0442\u0430\u043a\u044a\u0432, \u043a\u043e\u0439\u0442\u043e RIPPS2 \u043c\u043e\u0436\u0435 \u0434\u0430 \u043a\u043e\u043c\u043f\u0440\u0435\u0441\u0438\u0440\u0430 \u0438\u043b\u0438 \u0434\u0435\u043a\u043e\u043c\u043f\u0440\u0435\u0441\u0438\u0440\u0430 (\u0440\u0430\u0437\u043c\u0435\u0440\u044a\u0442 \u043c\u0443 \u043d\u0435 \u0435 \u0446\u0435\u043b\u0438 \u0431\u043b\u043e\u043a\u043e\u0432\u0435 \u043e\u0442 2 KB).\n+  UNCOMPRESS TO ISO: \u0414\u0415\u041a\u041e\u041c\u041f\u0420\u0415\u0421\u0418\u0420\u0410\u0419 \u0412 ISO\n+  UNCOMPRESSED %s: \u0414\u0415\u041a\u041e\u041c\u041f\u0420\u0415\u0421\u0418\u0420\u0410\u041d\u0410 %s\n+  UNCOMPRESSING: \u0414\u0415\u041a\u041e\u041c\u041f\u0420\u0415\u0421\u0418\u0420\u0410\u041d\u0415\n+  Uncompress to ISO: \u0414\u0435\u043a\u043e\u043c\u043f\u0440\u0435\u0441\u0438\u0440\u0430\u0439 \u0432 ISO\n+  Under a minute, then checked: \u041f\u043e\u0434 \u043c\u0438\u043d\u0443\u0442\u0430, \u043f\u043e\u0441\u043b\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430\n+  COPYING: \u041a\u041e\u041f\u0418\u0420\u0410\u041d\u0415\n+  SHOW AGAIN, DELETE OR COMPRESS: \u041f\u041e\u041a\u0410\u0416\u0418 \u041e\u0422\u041d\u041e\u0412\u041e, \u0418\u0417\u0422\u0420\u0418\u0419 \u0418\u041b\u0418 \u041a\u041e\u041c\u041f\u0420\u0415\u0421\u0418\u0420\u0410\u0419\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: \u041f\u041e\u041a\u0410\u0416\u0418 \u041e\u0422\u041d\u041e\u0412\u041e, \u0418\u0417\u0422\u0420\u0418\u0419 \u0418\u041b\u0418 \u0414\u0415\u041a\u041e\u041c\u041f\u0420\u0415\u0421\u0418\u0420\u0410\u0419\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': '%s \u0441\u0435 \u043d\u0443\u0436\u0434\u0430\u0435 \u043e\u0442 \u043e\u043a\u043e\u043b\u043e %s \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u043e \u043c\u044f\u0441\u0442\u043e \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0441\u0438 \u043f\u043e \u0432\u0440\u0435\u043c\u0435 \u043d\u0430 \u0440\u0430\u0431\u043e\u0442\u0430 (\u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b\u044a\u0442 \u043e\u0441\u0442\u0430\u0432\u0430 \u0434\u043e \u043a\u0440\u0430\u044f), \u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0438\u043c\u0430 %s \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u0438.'\n+  Hide and Compress: \u0421\u043a\u0440\u0438\u0439 \u0438 \u043a\u043e\u043c\u043f\u0440\u0435\u0441\u0438\u0440\u0430\u0439\n+  'COMPRESSED AND HIDDEN: %s': '\u041a\u041e\u041c\u041f\u0420\u0415\u0421\u0418\u0420\u0410\u041d\u0410 \u0418 \u0421\u041a\u0420\u0418\u0422\u0410: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': '\u041d\u044f\u043c\u0430 \u043c\u044f\u0441\u0442\u043e \u0437\u0430 \u0442\u043e\u0432\u0430 \u043a\u043e\u043f\u0438\u0440\u0430\u043d\u0435: \u043d\u0443\u0436\u043d\u0438 \u0441\u0430 \u043e\u043a\u043e\u043b\u043e %s, \u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0438\u043c\u0430 %s \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u0438.'\n+  OVER 4 GB FOR FAT32: \u041d\u0410\u0414 4 GB \u0417\u0410 FAT32\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: \u0422\u043e\u0432\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0435 %s, \u043a\u043e\u0435\u0442\u043e \u043d\u0435 \u0434\u044a\u0440\u0436\u0438 \u0444\u0430\u0439\u043b \u043e\u0442 4 GB \u0438\u043b\u0438 \u043f\u043e\u0432\u0435\u0447\u0435, \u0430 \u043d\u043e\u0432\u0438\u044f\u0442 \u043e\u0431\u0440\u0430\u0437 \u0431\u0438 \u0431\u0438\u043b %s.\n+  '%s, %s clusters, %s free of %s': '%s, \u043a\u043b\u044a\u0441\u0442\u0435\u0440\u0438 %s, %s \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u0438 \u043e\u0442 %s'\n+  Delete This Sweep: \u0418\u0437\u0442\u0440\u0438\u0439 \u0442\u0430\u0437\u0438 \u0441\u043c\u044f\u043d\u0430\n+  Delete %s? RIPPS2's own sweeps stay as they are.: \u0414\u0430 \u0441\u0435 \u0438\u0437\u0442\u0440\u0438\u0435 \u043b\u0438 %s? \u0421\u043c\u0435\u043d\u0438\u0442\u0435 \u043d\u0430 \u0441\u0430\u043c\u0438\u044f RIPPS2 \u043e\u0441\u0442\u0430\u0432\u0430\u0442 \u043a\u0430\u043a\u0442\u043e \u0441\u0430.\n+  Sweep: \u0421\u043c\u044f\u043d\u0430\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: \u0417\u043d\u0430\u0447\u043a\u0438\u0442\u0435 \u043e\u0442\u0431\u0435\u043b\u044f\u0437\u0432\u0430\u0442 \u0438\u0433\u0440\u0438\u0442\u0435 \u0441 \u043f\u043e\u0441\u0442\u0438\u0436\u0435\u043d\u0438\u044f \u0432 \u0441\u043f\u0438\u0441\u044a\u0446\u0438\u0442\u0435 \u0432\u0438 \u0438 \u043a\u043e\u043b\u043a\u043e \u0441\u0442\u0435 \u043e\u0442\u043a\u043b\u044e\u0447\u0438\u043b\u0438. \u041d\u0443\u0436\u043d\u0430 \u0435 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0430 \u0422\u0415\u041b\u0415\u041c\u0415\u0422\u0420\u0418\u042f.\n labels:\n   BDMMODE: \u0420\u0435\u0436\u0438\u043c \u043d\u0430 \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 BDM\n   GAME_ID: ID \u043d\u0430 \u0438\u0433\u0440\u0430\u0442\u0430\n@@ -1204,7 +1371,7 @@ labels:\n   SAVE_ICONS: 3D \u0438\u043a\u043e\u043d\u0438 \u043d\u0430 \u0437\u0430\u043f\u0438\u0441\u0438\u0442\u0435\n   HINT_SAVE_ICONS: \u041f\u043e\u043a\u0430\u0437\u0432\u0430 \u0438\u043a\u043e\u043d\u0430\u0442\u0430 \u043d\u0430 \u0437\u0430\u043f\u0438\u0441\u0430 \u043d\u0430 \u0438\u0437\u0431\u0440\u0430\u043d\u0430\u0442\u0430 \u0438\u0433\u0440\u0430, \u0432\u044a\u0440\u0442\u044f\u0449\u0430 \u0441\u0435, \u0442\u0430\u043c, \u043a\u044a\u0434\u0435\u0442\u043e \u0442\u0435\u043c\u0430\u0442\u0430 \u0438\u043c\u0430 \u043c\u044f\u0441\u0442\u043e \u0437\u0430 \u043d\u0435\u044f. \u041f\u044a\u0440\u0432\u043e \u0447\u0435\u0442\u0435 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u0438\u044f VMC \u043d\u0430 \u0438\u0433\u0440\u0430\u0442\u0430, \u043f\u043e\u0441\u043b\u0435 \u043a\u0430\u0440\u0442\u0438\u0442\u0435 \u0441 \u043f\u0430\u043c\u0435\u0442.\n   NETBOOT_RESTART_NOW: \u041c\u0440\u0435\u0436\u043e\u0432\u0438\u044f\u0442 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b \u0441\u0435 \u0441\u043c\u0435\u043d\u044f \u0441\u043b\u0435\u0434 \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u0435. \u0414\u0430 \u0441\u0435 \u0437\u0430\u043f\u0430\u0437\u044f\u0442 \u043b\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u0442\u0435 \u0438 \u0434\u0430 \u0441\u0435 \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430 \u043b\u0438 OPL \u0441\u0435\u0433\u0430?\n-  HINT_NET_PROTOCOL: '\u041a\u043e\u044f \u043c\u0440\u0435\u0436\u043e\u0432\u0430 \u0443\u0441\u043b\u0443\u0433\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430 RiptOPL: SMB \u0441\u043f\u043e\u0434\u0435\u043b\u044f\u043d\u0435, UDPFS, UDPBD \u0438\u043b\u0438 HTTP. \u0421\u044a\u0449\u0430\u0442\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u0430\u0442\u043e \u0432 \u0418\u0437\u0442\u043e\u0447\u043d\u0438\u0446\u0438 \u043d\u0430 \u0438\u0433\u0440\u0438. \u0421\u043c\u044f\u043d\u0430\u0442\u0430 \u043e\u0442 \u0432\u0435\u0447\u0435 \u0440\u0430\u0431\u043e\u0442\u0435\u0449\u0430 \u0443\u0441\u043b\u0443\u0433\u0430 \u0438\u0437\u0438\u0441\u043a\u0432\u0430 \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u0435.'\n+  HINT_NET_PROTOCOL: '\u041a\u043e\u044f \u043c\u0440\u0435\u0436\u043e\u0432\u0430 \u0443\u0441\u043b\u0443\u0433\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430 RiptOPL: SMB \u0441\u043f\u043e\u0434\u0435\u043b\u044f\u043d\u0435, UDPFS, UDPBD \u0438\u043b\u0438 HTTP. \u0421\u043c\u044f\u043d\u0430\u0442\u0430 \u043e\u0442 \u0432\u0435\u0447\u0435 \u0440\u0430\u0431\u043e\u0442\u0435\u0449\u0430 \u0443\u0441\u043b\u0443\u0433\u0430 \u0438\u0437\u0438\u0441\u043a\u0432\u0430 \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u0435.'\n   HINT_UDPFS_ACCESS: \u0421\u0430\u043c\u043e UDPFS. \u0424\u0430\u0439\u043b\u043e\u0432\u0435 \u0447\u0435\u0442\u0435 \u043e\u0442\u0434\u0435\u043b\u043d\u0438 ISO \u0444\u0430\u0439\u043b\u043e\u0432\u0435 \u043e\u0442 \u043f\u0430\u043f\u043a\u0430\u0442\u0430 \u0441 \u0438\u0433\u0440\u0438 \u043d\u0430 \u0441\u044a\u0440\u0432\u044a\u0440\u0430; IMG \u0447\u0435\u0442\u0435 \u043e\u0431\u0440\u0430\u0437 \u043d\u0430 \u0434\u0438\u0441\u043a \u043a\u0430\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e. SMB \u0438 HTTP \u0432\u0438\u043d\u0430\u0433\u0438 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442 \u0424\u0430\u0439\u043b\u043e\u0432\u0435, UDPBD \u0432\u0438\u043d\u0430\u0433\u0438 IMG.\n   HINT_SMB_VERSION: \u0421 \u043a\u043e\u044f \u0432\u0435\u0440\u0441\u0438\u044f \u043d\u0430 SMB \u0434\u0430 \u0441\u0435 \u0433\u043e\u0432\u043e\u0440\u0438 \u0441\u044a\u0441 \u0441\u043f\u043e\u0434\u0435\u043b\u044f\u043d\u0435\u0442\u043e \u043d\u0430 \u043a\u043e\u043c\u043f\u044e\u0442\u044a\u0440\u0430.\n   HINT_NET_ADVANCED: \u041e\u0442\u043a\u043b\u044e\u0447\u0432\u0430 \u0437\u0430 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0430\u043d\u0435 \u0440\u0435\u0436\u0438\u043c\u0430 \u043d\u0430 Ethernet \u0432\u0440\u044a\u0437\u043a\u0430\u0442\u0430 \u0438 SMB \u043f\u043e\u0440\u0442\u0430.\n@@ -1405,3 +1572,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: \u041a\u0430\u043a \u043a\u0430\u0434\u0440\u0438\u0442\u0435, \u0447\u0430\u043a\u0430\u0449\u0438 \u043d\u0430 \u043b\u0435\u043d\u0442\u0430\u0442\u0430 \u043d\u0430 \u0432\u0441\u044f\u043a\u0430 \u0438\u0433\u0440\u0430, \u0441\u0442\u0430\u0432\u0430\u0442 PNG.\n   RIPPS2_SETTINGS_HOME: \u0417\u0430\u043f\u0438\u0441 \u043d\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u0442\u0435 \u0432\n   HINT_RIPPS2_SETTINGS_HOME: '\u041a\u044a\u0434\u0435 RIPPS2 \u043f\u0430\u0437\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u0442\u0435 \u0441\u0438: \u0432 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u0438\u044f \u0441\u0438 \u0437\u0430\u043f\u0438\u0441 \u043d\u0430 \u043a\u0430\u0440\u0442\u0430 \u0441 \u043f\u0430\u043c\u0435\u0442, \u0432 \u043f\u0430\u043f\u043a\u0430 RIPPS2 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e (\u0432\u044a\u0442\u0440\u0435\u0448\u043d\u0438\u044f HDD, USB, MX4SIO, iLink \u0438\u043b\u0438 MMCE) \u0438\u043b\u0438 \u0432 \u043f\u0430\u043f\u043a\u0430, \u043a\u043e\u044f\u0442\u043e \u0432\u044a\u0432\u0435\u0434\u0435\u0448.'\n+  RIPPS2_LAUNCHERS: \u0421\u0442\u0430\u0440\u0442\u0435\u0440\u0438\n+  RIPPS2_LOOK_NOW: \u0422\u044a\u0440\u0441\u0438 \u0441\u0435\u0433\u0430\n+  HINT_RIPPS2_LOOK_NOW: RIPPS2 \u0442\u044a\u0440\u0441\u0438 \u043d\u0430 \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 POPSTARTER, \u0444\u0430\u0439\u043b\u043e\u0432\u0435\u0442\u0435 \u043d\u0430 POPS, Ember, BIOS \u043d\u0430 Ember \u0438 Neutrino \u0438 \u0437\u0430\u043f\u043e\u043c\u043d\u044f \u043a\u044a\u0434\u0435 \u0435 \u0432\u0441\u0435\u043a\u0438. \u0412\u0441\u044f\u043a\u043e \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u0435 \u0433\u0438 \u0447\u0435\u0442\u0435 \u043e\u0442\u0442\u0430\u043c.\n+  RIPPS2_HIDE_GAME: \u0421\u043a\u0440\u0438\u0439 \u0438\u0433\u0440\u0430\u0442\u0430\n+  RIPPS2_HIDDEN_GAMES: \u0421\u043a\u0440\u0438\u0442\u0438 \u0438\u0433\u0440\u0438\n+  HINT_RIPPS2_HIDDEN_GAMES: \u0418\u0433\u0440\u0438\u0442\u0435, \u0441\u043a\u0440\u0438\u0442\u0438 \u0441\u044a\u0441 \u0421\u043a\u0440\u0438\u0439 \u0438\u0433\u0440\u0430\u0442\u0430, \u043d\u0430 \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430. \u041f\u043e\u043a\u0430\u0436\u0435\u0442\u0435 \u043d\u044f\u043a\u043e\u044f \u043e\u0442\u043d\u043e\u0432\u043e \u0438\u043b\u0438 \u044f \u0438\u0437\u0442\u0440\u0438\u0439\u0442\u0435. \u0412\u0441\u044f\u043a\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043f\u0430\u0437\u0438 \u0441\u0432\u043e\u0439 \u0441\u043f\u0438\u0441\u044a\u043a.\n+  HINT_RIPPS2_BDM_DEVICES: '\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u0442\u0430, \u043e\u0442 \u043a\u043e\u0438\u0442\u043e BDM \u0447\u0435\u0442\u0435 \u0438\u0433\u0440\u0438: USB, MX4SIO, PC Card CF, DVR \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u043d\u0430 DESR \u0438 iLink. \u0412\u0433\u0440\u0430\u0434\u0435\u043d\u0438\u044f\u0442 HDD \u0441\u043b\u0435\u0434\u0432\u0430 \u0440\u0435\u0436\u0438\u043c\u0430 \u043d\u0430 \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 HDD.'\n+  HINT_RIPPS2_SCREEN: '\u041a\u0430\u0440\u0442\u0438\u043d\u0430\u0442\u0430 \u043d\u0430 \u043c\u0435\u043d\u044e\u0442\u043e: \u0432\u0438\u0434\u0435\u043e\u0440\u0435\u0436\u0438\u043c, \u0448\u0438\u0440\u043e\u043a \u0435\u043a\u0440\u0430\u043d, \u043c\u044f\u0441\u0442\u043e\u0442\u043e \u045d \u043d\u0430 \u0442\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440\u0430 (H-POS, V-POS) \u0438 overscan.'\n+  HINT_RIPPS2_LAYOUT: \u041a\u044a\u0434\u0435 \u0441\u0442\u043e\u044f\u0442 \u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u0438\u0442\u0435 \u0438 \u043a\u0430\u043a \u0432\u043b\u0438\u0437\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0442\u0430, \u043f\u043e\u0441\u043b\u0435 \u043e\u0431\u043b\u043e\u0436\u043a\u0438\u0442\u0435 \u043d\u0430 RIPFLOW \u0438 \u043c\u0440\u0435\u0436\u0430\u0442\u0430 \u043d\u0430 RIPGRID.\n+  HINT_RIPPS2_COLORS_TEXT: \u0414\u0435\u0431\u0435\u043b\u0438\u043d\u0430 \u043d\u0430 \u0442\u0435\u043a\u0441\u0442\u0430, \u0446\u0432\u0435\u0442\u043e\u0432\u0430\u0442\u0430 \u0442\u0435\u043c\u0430, \u0446\u0432\u0435\u0442\u043e\u0432\u0430\u0442\u0430 \u0441\u043c\u044f\u043d\u0430 \u0438 \u0432\u0441\u0435\u043a\u0438 \u0446\u0432\u044f\u0442.\n+  HINT_RIPPS2_TEXT_WEIGHT: '\u041a\u043e\u043b\u043a\u043e \u043f\u043b\u044a\u0442\u0435\u043d \u0435 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u0438\u044f\u0442 \u0442\u0435\u043a\u0441\u0442 \u043d\u0430 RIPPS2: \u043f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0438\u0442\u0435 \u0432 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u0442\u0435, \u0421\u042a\u0421\u0422\u041e\u042f\u041d\u0418\u0415 \u0438 \u041e\u0442\u043d\u043e\u0441\u043d\u043e. \u041e\u0431\u0438\u043a\u043d\u043e\u0432\u0435\u043d \u0435 \u043a\u0430\u043a\u0442\u043e \u043f\u0440\u0435\u0434\u0438; \u0421\u0440\u0435\u0434\u0435\u043d \u0438 \u0423\u0434\u0435\u0431\u0435\u043b\u0435\u043d \u0441\u0435 \u0447\u0435\u0442\u0430\u0442 \u043f\u043e-\u0434\u043e\u0431\u0440\u0435 \u043d\u0430 \u043c\u0435\u043a\u0430 \u043a\u0430\u0440\u0442\u0438\u043d\u0430.'\n+  HINT_RIPPS2_HDD_START: '\u0412\u0433\u0440\u0430\u0434\u0435\u043d\u0438\u044f\u0442 HDD, APA \u0438\u043b\u0438 exFAT: RIPPS2 \u0433\u043e \u0447\u0435\u0442\u0435 \u043a\u0430\u043a\u044a\u0432\u0442\u043e \u0438 \u0434\u0430 \u0435 \u0438 \u0433\u043e \u043f\u043e\u043a\u0430\u0437\u0432\u0430 \u0432\u0435\u0434\u043d\u044a\u0436, \u043a\u0430\u0442\u043e \u0438\u0433\u0440\u0438 \u043d\u0430 HDD. \u0418\u0437\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u0433\u043e \u043e\u0441\u0442\u0430\u0432\u044f \u043d\u0430 \u043c\u0438\u0440\u0430.'\n+  HINT_RIPPS2_THEME_COLOR: \u0421\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u0438\u044f\u0442 \u0446\u0432\u044f\u0442 \u043d\u0430 \u0446\u0432\u0435\u0442\u043e\u0432\u0430\u0442\u0430 \u0442\u0435\u043c\u0430. \u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0421\u043e\u0431\u0441\u0442\u0432\u0435\u043d \u043a\u0430\u0442\u043e \u0446\u0432\u0435\u0442\u043e\u0432\u0430 \u0442\u0435\u043c\u0430, \u0437\u0430 \u0434\u0430 \u0433\u043e \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0441\u0430\u043c\u0438, \u043a\u0430\u043a\u0442\u043e \u0446\u0432\u0435\u0442\u043e\u0432\u0435\u0442\u0435 \u043d\u0430 \u0442\u0435\u043a\u0441\u0442\u0430.\n+  HINT_RIPPS2_SWEEP: \u041f\u0435\u0442 \u0446\u0432\u044f\u0442\u0430, \u043f\u0440\u0435\u0437 \u043a\u043e\u0438\u0442\u043e \u0442\u0435\u043c\u0430\u0442\u0430 \u043f\u0440\u0435\u043c\u0438\u043d\u0430\u0432\u0430 \u0431\u0430\u0432\u043d\u043e \u0438 \u043f\u043b\u0430\u0432\u043d\u043e. \u0414\u044a\u0433\u0430, \u0441\u043c\u044f\u043d\u0430 \u043e\u0442 \u0432\u0441\u044f\u043a\u0430 \u0446\u0432\u0435\u0442\u043e\u0432\u0430 \u0442\u0435\u043c\u0430 \u0438\u043b\u0438 \u0432\u0430\u0448\u0430 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u0430. \u0418\u0437\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u0434\u044a\u0440\u0436\u0438 \u0442\u0435\u043c\u0430\u0442\u0430 \u043d\u0435\u043f\u043e\u0434\u0432\u0438\u0436\u043d\u0430.\n+  HINT_RIPPS2_SWEEP_COLORS: \u041f\u0435\u0442\u0442\u0435 \u0446\u0432\u044f\u0442\u0430 \u043d\u0430 \u0441\u043c\u044f\u043d\u0430\u0442\u0430 \u0438 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0442\u0430 \u045d. \u041f\u0440\u043e\u043c\u0435\u043d\u0438\u0442\u0435 \u043b\u0438 \u0435\u0434\u043d\u0430 \u043e\u0442 \u0442\u0435\u0437\u0438 \u043d\u0430 RIPPS2, \u0442\u044f \u0441\u0435 \u043f\u0430\u0437\u0438 \u043a\u0430\u0442\u043e \u0432\u0430\u0448\u0430, \u0441 \u0438\u043c\u0435\u0442\u043e, \u043a\u043e\u0435\u0442\u043e \u045d \u0434\u0430\u0434\u0435\u0442\u0435.\n+  HINT_RIPPS2_SWEEP_NAME: \u041a\u0430\u043a \u0441\u0435 \u043a\u0430\u0437\u0432\u0430 \u0432\u0430\u0448\u0430\u0442\u0430 \u0441\u043c\u044f\u043d\u0430 \u0432 \u0441\u043f\u0438\u0441\u044a\u043a\u0430 \u0426\u0432\u0435\u0442\u043e\u0432\u0430 \u0441\u043c\u044f\u043d\u0430.\n+  HINT_RIPPS2_SWEEP_COLOR: \u0415\u0434\u0438\u043d \u043e\u0442 \u043f\u0435\u0442\u0442\u0435 \u0446\u0432\u044f\u0442\u0430, \u043f\u0440\u0435\u0437 \u043a\u043e\u0438\u0442\u043e \u043c\u0438\u043d\u0430\u0432\u0430 \u0441\u043c\u044f\u043d\u0430\u0442\u0430, \u043f\u043e \u0440\u0435\u0434.\n+  HINT_RIPPS2_SWEEP_SPEED: '\u041a\u043e\u043b\u043a\u043e \u0442\u0440\u0430\u0435 \u0432\u0441\u0435\u043a\u0438 \u0446\u0432\u044f\u0442, \u043f\u0440\u0435\u0434\u0438 \u0434\u0430 \u0441\u0435 \u043f\u043e\u044f\u0432\u0438 \u0441\u043b\u0435\u0434\u0432\u0430\u0449\u0438\u044f\u0442. \u0414\u043e\u0440\u0438 \u0416\u0438\u0432\u043e \u043e\u0441\u0442\u0430\u0432\u0430 \u043f\u043b\u0430\u0432\u043d\u043e: \u0446\u0432\u0435\u0442\u043e\u0432\u0435\u0442\u0435 \u043d\u0438\u043a\u043e\u0433\u0430 \u043d\u0435 \u043c\u0438\u0433\u0430\u0442.'\n+  RIPPS2_HIDE_COMPRESS: \u0421\u043a\u0440\u0438\u0432\u0430\u043d\u0435 \u043d\u0430 \u0438\u0433\u0440\u0430 \u0438 \u043a\u043e\u043c\u043f\u0440\u0435\u0441\u0438\u0440\u0430\u043d\u0435\n+  HINT_RIPPS2_SELECT_HOLD: 'SELECT, \u0437\u0430\u0434\u044a\u0440\u0436\u0430\u043d 4.2 s \u0432 \u0441\u043f\u0438\u0441\u044a\u043a \u0441 \u0438\u0433\u0440\u0438. \u0421\u043f\u0438\u0441\u044a\u043a: \u0442\u0435\u043c\u0438\u0442\u0435 \u0432 \u043f\u0430\u043d\u0435\u043b, \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0435\u0434\u043d\u0430. \u0420\u0435\u0434\u0443\u0432\u0430\u043d\u0435: \u0441\u043b\u0435\u0434\u0432\u0430\u0449\u0430\u0442\u0430 \u0442\u0435\u043c\u0430. \u0418\u0437\u043a\u043b.: \u043d\u0438\u0449\u043e (\u043d\u0430\u0442\u0438\u0441\u043a\u0430\u043d\u0435 \u043f\u0430\u043a \u043e\u043f\u0440\u0435\u0441\u043d\u044f\u0432\u0430).'"
        },
        {
          "sha": "747567fc41988f55cb38e83fc6b9bfe6e447bac6",
          "filename": "elf/lang/Cebuano.yml",
          "status": "modified",
          "additions": 203,
          "deletions": 22,
          "changes": 225,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FCebuano.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FCebuano.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FCebuano.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: Cebuano\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTER ALANG MODAWAT, ESC ALANG MOKANSEL. BISAN UNSANG BUTTON MOPAKITA SA MGA KEY.\n   USB KEYBOARD: USB KEYBOARD\n-  '      DESR DVR ISOs (experimental)': '      DESR DVR ISO (eksperimental)'\n-  '      Internal HDD (exFAT)': '      Internal HDD (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (eksperimental)'\n-  '      Protocol': '      Protocol'\n   Access: Access\n   Applications Page Start Mode: Start mode sa Applications page\n   Art Delay: Langan sa mga hulagway\n@@ -22,7 +18,6 @@ strings:\n   Fifth: Ikalima\n   First: Una\n   Fourth: Ikaupat\n-  HDD (APA) Start Mode: Start mode sa HDD (APA)\n   HDD Cache: HDD cache\n   HDD Spindown: Pag-spindown sa HDD\n   MMCE Settings: Mga setting sa MMCE\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: PS2 nga dula kini nga adunay ESR patch. Ibutang ang ESR (esr*.elf) sa APPS, BOOT o ESR sa memory card o USB drive aron masugdan kini.\n   '%s, BUILD %d': '%s, BUILD %d'\n   '... and the anonymous ...': '... ug ang mga wala mailhi ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': '1.8.0 nga gidala nga walay giusab (AFL-3.0): github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: Usa ka pagbungkag ug pagtukod pag-usab sa RiptOPL, ang fork sa Open PS2 Loader,\n   ABOUT: MAHITUNGOD\n   'ACHIEVEMENTS: SPIRIT OF RA': 'MGA ACHIEVEMENT: SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): AVI video sa Memory Files, fork sa RIPPS2 (AFL 2.0)\n   Apollo Save Tool by bucanero: Apollo Save Tool ni bucanero\n   BACK: BALIK\n+  Back (keeps both): Balik (tipigan ang duha)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: Kining MMCE card dili maka-rename og file, nga gikinahanglan sa compression sa katapusang lakang niini. I-update ang sd2psXtd firmware niini ngadto sa 1.4.0 o mas bag-o (sd2psxtd.github.io/download), dayon sulayi pag-usab. Walay nausab.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): Ang BIBLE mao ang PS2BBL 1.2.0, ang mga signed build ni El Isra (card ug HDD), walay giusab (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: Gipatugtog sila sa BIOS Sounds > Rip sama sa SPU2; walay BIOS nga apil\n   BOOT LOCK AND BIBLE: BOOT LOCK UG BIBLE\n   BOOT LOCK edits its config too: Giusab usab sa BOOT LOCK ang config niini\n   BUILD DETAILS: MGA DETALYE SA BUILD\n-  BUILT ON RIPTOPL: GITUKOD SA RIPTOPL\n   BearSSL by Thomas Pornin: BearSSL ni Thomas Pornin\n   Built with Claude (Anthropic): Gihimo uban ni Claude (Anthropic)\n   CD PLAYER: CD PLAYER\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek ug Sleek Bold, gidrowing alang sa RIPPS2\n   RetroAchievements on the console by hacan359 (yoba): RetroAchievements sa console ni hacan359 (yoba)\n   RiptOPL by NathanNeurotic: RiptOPL ni NathanNeurotic\n-  Roboto Condensed by Google (Apache-2.0): Roboto Condensed sa Google (Apache-2.0)\n   SOUNDS: MGA TUNOG\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: FMCB installer ni SP193 ug OSDMenu ni pcm720, gisundan sa matag lakang\n   SUPPORT: SUPORTA\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: wLaunchELF R3Z ni NathanNeurotic\n   wLaunchELF by the ps2homebrew team: wLaunchELF sa ps2homebrew team\n   what RIPPS2's card image writes are checked against: ang gipasikaran sa pagsusi sa mga card image nga gisulat sa RIPPS2\n-  with its own look, launchers, file browser and save tools.: nga adunay kaugalingong dagway, mga launcher, file browser ug mga save tool.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: xeRAbora ni hacan359, rcheevos sa RetroAchievements\n   your console's own, ripped from its BIOS: ang kaugalingong tunog sa imong console, gikuha gikan sa BIOS niini\n   ' (no DNS answer)': ' (walay tubag ang DNS)'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO: gisulat ang %.60s'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: Dili masulat ang Neutrino sa %s. Susiha ang libreng lugar niini ug nga dili kini naka-write-protect, unya sulayi pag-usab.\n   THE GAME'S DRIVE  (%s/neutrino): ANG DRIVE SA DULA  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : Pinaagi sa Neutrino mosugod kining dula, ug walay Neutrino kung asa mangita ang RIPPS2 (Settings, ang drive sa dula, ang mga memory card). Gidala sa RIPPS2 ang Neutrino 1.8.0 ni rickgaiser (AFL-3.0). I-install kini karon? Sunod, ikaw ang mopili kung asa.\n   '%d / %d FILES': '%d / %d KA FILE'\n   '%d FOLDERS AND CONTENTS': '%d KA FOLDER UG SULOD'\n   '%d FOLDERS, %d FOUND': '%d KA FOLDER, %d NAKIT-AN'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d sa %d ang naka-lock pa'\n   '%s   on %s': '%s   sa %s'\n   BADGES: MGA BADGE\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: Molihok lang ang mga badge kung naka-on ang TELEMETRY ug nagdagan ang xeRAbora sa imong PC; hangtod niana, walay gasto kini.\n   CHECK THE DISC: SUSIHA ANG DISC\n   From this game's last CHECK GAME SUPPORT.: Gikan sa katapusang CHECK GAME SUPPORT niining dula.\n   HOW IT WORKS: GIUNSA KINI PAGLIHOK\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: Adunay PS2BBL nga may lock sa RIPPS2 niining console.\n   AUDIO folder: AUDIO folder\n   DRIVES: MGA DRIVE\n-  INSTALL RIPPS2'S COPY: I-INSTALL ANG KOPYA SA RIPPS2\n-  'Installed: ': 'Naka-install: '\n   MANAGE: DUMALAHA\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: Gipakita sa DUMALAHA kung unsa ang gisugdan sa matag memory card, ug nag-install o nag-ayo sa BIBLE.\n   MEMORY CARD 0  (about 600 KB): MEMORY CARD 0  (mga 600 KB)\n   MEMORY CARD 1  (about 600 KB): MEMORY CARD 1  (mga 600 KB)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO: dili mo-load ang driver niini (code %d).'\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO: naka-load ang driver, apan walay SD card nga mitubag (ang mc1 slot).'\n   NEUTRINO SETTINGS: MGA SETTING SA NEUTRINO\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': 'Wala pa: walay kaugalingong tunog ang RIPPS2. I-rip ang sa console, o himoa sa RIPPS2 Audio Studio.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: Wala na-install. Ang dula nga nagkinahanglan niini motanyag sa kopya sa RIPPS2, o i-install kini dinhi.\n   POPSTARTER NETWORK: NETWORK SA POPSTARTER\n   POPSTARTER and Ember: POPSTARTER ug Ember\n   PS1 SETTINGS: MGA SETTING SA PS1\n   PS2BBL 1.2.0 for RIPPS2: PS2BBL 1.2.0 alang sa RIPPS2\n   RIP THIS CONSOLE'S SOUNDS: I-RIP ANG MGA TUNOG NIINING CONSOLE\n-  RIPPS2's homebrew: Homebrew sa RIPPS2\n   STATUS: STATUS\n-  'STATUS: looking for RIPPS2''s homebrew': 'STATUS: gipangita ang homebrew sa RIPPS2'\n   a drive of unknown kind: drive nga wala mailhi ang klase\n-  a drive or a memory card: drive o memory card\n   boot loader, card by card: boot loader, matag card\n   from its BIOS, into AUDIO: gikan sa BIOS niini, ngadto sa AUDIO\n   internal HDD (exFAT): internal HDD (exFAT)\n@@ -975,10 +958,188 @@ strings:\n   Sidebar: Sidebar\n   your own copy, found on your drives and memory cards: imong kaugalingong kopya, makit-an sa imong mga drive ug memory card\n   'your own, found and launched: github.com/Gageformer/Ember/releases': 'imong kaugalingon, makit-an ug ilunsad: github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': 'Wala makit-i sa mass0: (%s). Ibutang ang ember.elf ug ang imong bios.bin sa EMBER folder niini.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: Wala makit-i. Ibutang ang imong kaugalingong POPSTARTER.ELF sa POPS folder sa usa ka drive.\n   Launch Disc: Sugdi ang disc\n   Memory Files: Memory files\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: Nakakita ang RIPPS2 og %d sa 5. Ginahinumdoman ang matag usa, ug didto kini basahon sa matag launch.\n+  not found on the started drives: wala makit-i sa mga nasugdang drive\n+  not looked for yet: wala pa pangitaa\n+  '%s did not answer, so %s cannot start. Is it connected?': Wala motubag ang %s, busa dili makasugod ang %s. Nakakonektar ba kini?\n+  '%s is not on %s any more, so it has left All Games.': Wala na ang %s sa %s, busa mibiya na kini sa Tanang dula.\n+  STARTING %s: GISUGDAN ANG %s\n+  the SMB share: ang SMB share\n+  the drive: ang drive\n+  the internal HDD (APA): ang internal HDD (APA)\n+  the internal HDD (exFAT): ang internal HDD (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: GITUKOD SA RIPTOPL, UBAN ANG NEUTRINO\n+  'Special thanks to all the YouTube love as well:': 'Espesyal nga pasalamat usab sa tanang gugma gikan sa YouTube:'\n+  and its own look, launchers, file browser and save tools.: ug adunay kaugalingong dagway, launcher, file browser ug save tools.\n+  glad to help however you reach me.: malipayon nga motabang, bisan unsaon nimo ako pagkontak.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): ang ikaduhang game core, 1.8.0 nga gidala nga walay giusab (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: nga adunay Neutrino ni rickgaiser sa sulod isip ikaduhang game core,\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : 'Kini nga dula mosugod pinaagi sa Neutrino, ug walay nakit-an ang RIPPS2: wala sa path sa Settings, sa drive sa dula, sa mga memory card o sa bisan unsang nasugdang drive. Gidala sa RIPPS2 ang Neutrino 1.8.0 ni rickgaiser (AFL-3.0): i-install kini kausa, bisan asa nimo gusto, ug didto kini gamiton sa matag dula sa Neutrino.'\n+  STARTING THE DRIVES: GISUGDAN ANG MGA DRIVE\n+  '%d games, on the started drives': '%d ka dula, sa mga nasugdang drive'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s: ADUNAY MGA FILE NGA WALA MAPAPAS'\n+  '%u partitions, all in order': '%u ka partition, tanan hapsay'\n+  ', %d art images': ', %d ka art image'\n+  ', and ': ', ug '\n+  ', its cheats': ', ang mga cheat niini'\n+  ', its settings': ', ang settings niini'\n+  1 game, on the started drives: 1 ka dula, sa mga nasugdang drive\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: Ang PS1 nga dula dinhi papason sa Memory files, diin kini usa ka kaugalingong file.\n+  A network catalogue's games are deleted on the PC that serves them.: Ang mga dula sa network catalogue papason sa PC nga nag-serve niini.\n+  DELETE FOR GOOD: PAPASA SA HINGPIT\n+  DELETE IT: PAPASA KINI\n+  'DELETED %s: %s FREED': 'NAPAPAS ANG %s: %s ANG NAHAW-AN'\n+  'Delete %s from %s? Goes: %s.': 'Papason ang %s gikan sa %s? Mawala: %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': 'Papason ang %s gikan sa %s? Mawala: %s. Magpabilin: %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: Papason usab ang mga save ug RIPSHOT nga hulagway niini? Ang No magtipig niini.\n+  HIDDEN GAMES: MGA GITAGONG DULA\n+  'HIDDEN: %s': 'GITAGO: %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: Itago ang %s? Mobiya kini sa matag lista, apil ang Tanang dula ug Mga Paborito. Ang Setting > Mga Tinubdan sa Dula > Mga gitagong dula mopakita niini pag-usab.\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: Katapusang check. Mapapas sa hingpit ang %s, ug walay makabalik niini. Pindota ang START aron papason kini.\n+  None. Hide Game, in a game's options, puts one here.: Wala. Ang Itago ang dula, sa options sa usa ka dula, mobutang og usa dinhi.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : 'Walay napapas: ang partition table niining drive dili mohaum niining disk (%s). Basin gisulat kini gikan sa image sa disk nga lahi ang gidak-on. Ayoha una kini sa PC.'\n+  RIPPS2 cannot delete a game from this page.: Dili makapapas og dula ang RIPPS2 gikan niining page.\n+  RIPPS2 could not find this game's files on its drive.: Wala makit-i sa RIPPS2 ang mga file niining dula sa drive niini.\n+  Reading the hidden lists: Gibasa ang mga gitagong lista\n+  SHOW AGAIN OR DELETE: IPAKITA PAG-USAB O PAPASA\n+  SHOW IT AGAIN: IPAKITA PAG-USAB\n+  That game is not on its drive any more.: Wala na ang maong dula sa drive niini.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: Dili masulat ang gitagong lista sa drive sa dula. Write-protected ba kini, o naka-off ang Write Operations?\n+  The partition could not be removed (%d). Nothing was deleted.: Dili matangtang ang partition (%d). Walay napapas.\n+  This drive cannot keep a hidden list (a network catalogue).: Dili makahupot og gitagong lista kining drive (usa ka network catalogue).\n+  This game's drive has not answered yet. Wait for it, then try again.: Wala pa motubag ang drive niining dula. Hulata kini, unya sulayi pag-usab.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: Ang partition niining dula usa sa dili tangtangon sa RIPPS2 (usa ka system partition, o ang naay settings niini).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': 'mas dako ang usa ka partition kaysa gitugot niining disk: gihimo ang table para sa disk nga lahi ang gidak-on'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': 'milapas ang usa ka partition sa tumoy niining disk (sector %u sa %u): gihimo ang table para sa mas dakong disk'\n+  its RIPSHOT pictures: ang mga RIPSHOT nga hulagway niini\n+  its drive: ang drive niini\n+  its saves: ang mga save niini\n+  sector %u holds no partition header: walay partition header ang sector %u\n+  the chain does not close at __mbr: dili mosira ang chain sa __mbr\n+  the chain skips from sector %u: molukso ang chain gikan sa sector %u\n+  the chain's links disagree at sector %u: dili magkauyon ang mga link sa chain sa sector %u\n+  the drive's size could not be read: dili mabasa ang gidak-on sa drive\n+  the game (%s): ang dula (%s)\n+  the game's folder and everything in it (%s): ang folder sa dula ug tanan nga sulod niini (%s)\n+  the header at sector %u could not be read: dili mabasa ang header sa sector %u\n+  the header at sector %u fails its checksum: mapakyas sa checksum ang header sa sector %u\n+  the partition at sector %u is not where or as large as APA puts one: wala ang partition sa sector %u sa dapit o sa gidak-on nga ibutang sa APA\n+  the settings and art another copy of it uses: ang settings ug art nga gigamit sa laing kopya niini\n+  '%d FOLDERS': '%d KA FOLDER'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': Dili makopya ang %s ngadto sa %s. Puno ba ang drive o write-protected?\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : Wala ang %s diin nangita ang RIPPS2. Pangitaon kini sa mga drive nga gisugdan sa RIPPS2? Hinumdoman kung asa kini makit-an para sa matag sunod nga launch.\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Wala ang Ember sa %s, tupad sa mga dula, ug kinahanglan nga anaa kini didto. Nakit-an kini sa RIPPS2 sa %s: kopyahon kini sa START didto (%d KB) ug sugdan ang dula.'\n+  Ember's BIOS: BIOS sa Ember\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Wala ang BIOS sa Ember (bios.bin) sa %s, tupad sa Ember. Nakakita ang RIPPS2 og usa sa %s: kopyahon kini sa START didto (%d KB) ug sugdan ang dula.'\n+  LOOKING FOR YOUR LAUNCHERS: GIPANGITA ANG IMONG MGA LAUNCHER\n+  The POPS files: Ang mga POPS file\n+  '%s  MMCE card': '%s  MMCE card'\n+  INSTALL RIPPS2'S NEUTRINO: I-INSTALL ANG NEUTRINO SA RIPPS2\n+  'Internal HDD (APA): %s free of %s': 'Internal HDD (APA): %s ang libre sa %s'\n+  LAUNCHERS: MGA LAUNCHER\n+  LOOK FOR THEM: PANGITAA SILA\n+  'Memory card %d: %d KB free': 'Memory card %d: %d KB ang libre'\n+  POPS files: Mga POPS file\n+  START EVERY DRIVE: SUGDI ANG MATAG DRIVE\n+  'STATUS: looking at the drives and the homebrew': 'STATUS: gitan-aw ang mga drive ug ang homebrew'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): Sugdan una ang matag drive, aron maabot sa pagpangita ang tanan? (Ang No mangita sa mga nasugdan na nga drive.)\n+  USB, HDD, MMCE and the rest: USB, HDD, MMCE ug ang uban pa\n+  every started drive: matag nasugdang drive\n+  found where you keep them: nakit-an diin nimo sila gitipigan\n+  not found yet: wala pa makit-i\n+  not on a started drive: wala sa nasugdang drive\n+  only if you have none: kung wala ka lang\n+  starts every drive first: sugdan una ang matag drive\n+  Drives, launchers and homebrew: Mga drive, launcher ug homebrew\n+  BDM Devices: Mga device sa BDM\n+  Color Sweep: Pagbalhin sa kolor\n+  Colors and Text: Kolor ug teksto\n+  HDD POPS partitions: Mga POPS partition sa HDD\n+  Missing Covers: Nawala nga mga cover\n+  Name: Ngalan\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: Wala pay nakit-an nga SMB setup sa POPSTARTER. Pun-a ang mga laray sa ubos.\n+  POPSTARTER Network: Network sa POPSTARTER\n+  POPSTARTER SMB Network: SMB network sa POPSTARTER\n+  POPSTARTER UDPFS Network: UDPFS network sa POPSTARTER\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: Ang RIPPS2 mismo ang modugang sa drive, ISO, mga VMC ug video mode.\n+  Speed: Kakusog\n+  Sweep Colors: Mga kolor sa pagbalhin\n+  Text Weight: Kabug-at sa teksto\n+  Theme Color: Kolor sa tema mismo\n+  Controllers: Mga controller\n+  Lively: Buhi\n+  PS Emulation: PS emulation\n+  Slow: Hinay\n+  A MOMENT: KADIYOT\n+  A TASK IN THE BACKGROUND: USA KA BULUHATON SA LUYO\n+  ABOUT %d S: MGA %d S\n+  ANY MOMENT: BISAN KANUS-A\n+  READING THE GAME LIST OF %s: GIBASA ANG LISTAHAN SA DULA SA %s\n+  SAVING A LIST: GITIPIGAN ANG LISTAHAN\n+  START NOW: SUGDI KARON\n+  STARTING: NAGSUGOD\n+  STARTS NEXT: SUNOD NGA MOSUGOD\n+  STOPPING IT SAFELY: GIHUNONG NGA LUWAS\n+  THE LAST OF THE QUEUE: ANG KATAPUSAN SA PILA\n+  WHEN IT FINISHES: KON MAHUMAN NA\n+  What each card starts; install or repair BIBLE.: Unsa ang sugdan sa matag card; i-install o ayuha ang BIBLE.\n+  Rainbow: Balangaw\n+  Custom: Kaugalingon\n+  DESR DVR ISOs (experimental): DESR DVR ISO (eksperimental)\n+  PC CARD CF (experimental): PC CARD CF (eksperimental)\n+  HDD Start Mode: Start mode sa HDD\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): Roboto Condensed sa The Roboto Project Authors (SIL OFL 1.1)\n+  '%s on %s': '%s sa %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: ang %s nahimong %s (%d%%), gisusi ang matag piraso. Tangtangon ang ISO ug tipigan ang ZSO?'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: ang %s nahimong %s pag-usab, gisusi ang matag piraso. Tangtangon ang ZSO ug tipigan ang ISO?'\n+  A ZSO of this game is already beside it. Remove one of the two first.: Aduna nay ZSO niining dulaa tupad niini. Tangtanga una ang usa sa duha.\n+  About %d min, then checked: Mga %d min, dayon susihon\n+  An ISO of this game is already beside it. Remove one of the two first.: Aduna nay ISO niining dulaa tupad niini. Tangtanga una ang usa sa duha.\n+  CHECKING EVERY PIECE: GISUSI ANG MATAG PIRASO\n+  COMPRESS TO ZSO: I-COMPRESS NGADTO SA ZSO\n+  'COMPRESSED %s: %s FREED': 'NA-COMPRESS ANG %s: %s ANG NAHAW-AN'\n+  COMPRESSING 1 OF 2: NAG-COMPRESS 1 SA 2\n+  COMPRESSING 2 OF 2: NAG-COMPRESS 2 SA 2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : I-compress ang %s? Magpabilin ang ISO hangtod mabasa pag-usab ug masusi ang ZSO, dayon mangutana ang RIPPS2 sa dili pa niini tangtangon ang ISO. Kinahanglan sa drive ang luna para sa duha samtang nagtrabaho; ayaw palonga ang console hangtod mahuman. Ang ZSO mosugod lang sa OPL core (dili sa Neutrino); ang mga save, setting, hulagway ug cheat niini magpabilin uban niini, ug mahimo kining ISO pag-usab bisan kanus-a.\n+  Compress to ZSO: I-compress ngadto sa ZSO\n+  Cycle: Sunod-sunod\n+  'Experimental: OPL core only (not Neutrino)': 'Eksperimental: OPL core lang (dili Neutrino)'\n+  Hold SELECT: Kupti ang SELECT\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: Dili kini mahuman (%s). Gitangtang ang gisulat niini; ang dula sama sa kaniadto.\n+  List: Lista\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : Himoon ang %s nga ISO pag-usab? Magpabilin ang ZSO hangtod mabasa pag-usab ug masusi ang ISO, dayon mangutana ang RIPPS2 sa dili pa niini tangtangon ang ZSO. Kinahanglan sa drive ang luna para sa duha samtang nagtrabaho; ayaw palonga ang console hangtod mahuman. Ang mga save, setting, hulagway ug cheat niini magpabilin uban niini.\n+  Stop? What it has written so far is removed; the game stays as it was.: Hunongon? Tangtangon ang nasulat na; ang dula magpabilin sama sa kaniadto.\n+  THEMES: MGA TEMA\n+  There is not enough memory for this just now.: Kulang ang memorya para niini karon.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: Kini nga image dili mahimong i-compress o i-uncompress sa RIPPS2 (ang gidak-on niini dili tibuok nga 2 KB nga mga block).\n+  UNCOMPRESS TO ISO: I-UNCOMPRESS NGADTO SA ISO\n+  UNCOMPRESSED %s: NA-UNCOMPRESS ANG %s\n+  UNCOMPRESSING: NAG-UNCOMPRESS\n+  Uncompress to ISO: I-uncompress ngadto sa ISO\n+  Under a minute, then checked: Ubos sa usa ka minuto, dayon susihon\n+  COPYING: GIKOPYA\n+  SHOW AGAIN, DELETE OR COMPRESS: IPAKITA PAG-USAB, PAPASA O I-COMPRESS\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: IPAKITA PAG-USAB, PAPASA O I-UNCOMPRESS\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': Ang %s nagkinahanglan og mga %s nga bakanteng luna sa drive niini samtang nagtrabaho (ang orihinal magpabilin hangtod sa katapusan), ug ang drive adunay %s nga bakante.\n+  Hide and Compress: Itago ug i-compress\n+  'COMPRESSED AND HIDDEN: %s': 'NA-COMPRESS UG NATAGO: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': 'Walay luna para niini nga rip: nagkinahanglan og mga %s, ug ang drive adunay %s nga bakante.'\n+  OVER 4 GB FOR FAT32: LABAW SA 4 GB PARA SA FAT32\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: Kini nga drive kay %s, nga dili makatipig og file nga 4 GB o labaw pa, ug ang bag-ong image mahimong %s.\n+  '%s, %s clusters, %s free of %s': '%s, %s nga cluster, %s bakante sa %s'\n+  Delete This Sweep: Papasa kini nga pagbalhin\n+  Delete %s? RIPPS2's own sweeps stay as they are.: Papasa ang %s? Ang kaugalingong mga pagbalhin sa RIPPS2 magpabilin.\n+  Sweep: Pagbalhin\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: Ang mga badge nagtimaan sa mga dula nga adunay achievement sa imong mga lista, ug pila ang imong na-unlock. Kinahanglan nga on ang TELEMETRY.\n labels:\n   MEDIUM: Tunga-tunga\n   BDMMODE: BDM Start Mode\n@@ -1230,7 +1391,7 @@ labels:\n   SAVE_ICONS: Mga 3D save icon\n   HINT_SAVE_ICONS: Ipakita ang nagtuyok nga save icon sa gipiling dula, kung asa adunay lugar ang tema alang niini. Basahon una ang kaugalingong VMC sa dula, unya ang mga memory card.\n   NETBOOT_RESTART_NOW: Mausab ang network protocol human sa restart. I-save ang settings ug i-restart ang OPL karon?\n-  HINT_NET_PROTOCOL: 'Unsang network service ang gigamit sa RiptOPL: SMB share, UDPFS, UDPBD o HTTP. Pareho nga setting sa Game Sources. Kinahanglan og restart aron mobalhin gikan sa nagdagan na.'\n+  HINT_NET_PROTOCOL: 'Unsang network service ang gigamit sa RiptOPL: SMB share, UDPFS, UDPBD o HTTP. Kinahanglan og restart aron mobalhin gikan sa nagdagan na.'\n   HINT_UDPFS_ACCESS: UDPFS ra. Gibasa sa Files ang bulag nga mga ISO gikan sa games folder sa server; gibasa sa IMG ang disk image isip drive. Kanunay Files ang SMB ug HTTP, kanunay IMG ang UDPBD.\n   HINT_SMB_VERSION: Unsang bersyon sa SMB ang gamiton sa PC share.\n   HINT_NET_ADVANCED: I-unlock ang Ethernet Link Mode ug ang SMB Port aron ma-edit.\n@@ -1431,3 +1592,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: Kung giunsa nga ang mga kuha nga naghulat sa rolyo sa matag dula mahimong PNG.\n   RIPPS2_SETTINGS_HOME: I-save ang settings sa\n   HINT_RIPPS2_SETTINGS_HOME: 'Asa gitago sa RIPPS2 ang settings niini: kaugalingong save sa usa ka memory card, usa ka RIPPS2 folder sa usa ka drive (internal HDD, USB, MX4SIO, iLink o MMCE), o usa ka folder nga imong isulat.'\n+  RIPPS2_LAUNCHERS: Mga launcher\n+  RIPPS2_LOOK_NOW: Pangitaa karon\n+  HINT_RIPPS2_LOOK_NOW: Pangitaon sa RIPPS2 sa mga nasugdang drive ang POPSTARTER, ang mga POPS file, ang Ember, ang BIOS sa Ember ug ang Neutrino, ug hinumdoman kung asa ang matag usa. Didto sila basahon sa matag launch.\n+  RIPPS2_HIDE_GAME: Itago ang dula\n+  RIPPS2_HIDDEN_GAMES: Mga gitagong dula\n+  HINT_RIPPS2_HIDDEN_GAMES: Ang mga dula nga gitago gamit ang Itago ang dula, sa mga nasugdang drive. Ipakita pag-usab ang usa, o papasa kini. Ang matag drive adunay kaugalingong lista.\n+  HINT_RIPPS2_BDM_DEVICES: 'Ang mga drive nga basahan sa BDM og dula: USB, MX4SIO, PC Card CF, ang DVR drive sa DESR ug iLink. Ang internal HDD mosunod sa start mode sa HDD.'\n+  HINT_RIPPS2_SCREEN: 'Ang hulagway sa menu: video mode, widescreen, asa kini sa TV (H-POS, V-POS) ug overscan.'\n+  HINT_RIPPS2_LAYOUT: Asa ang mga kategorya ug giunsa pagsulod sa panid, dayon ang mga cover sa RIPFLOW ug ang grid sa RIPGRID.\n+  HINT_RIPPS2_COLORS_TEXT: Kabug-at sa teksto, ang kolor sa tema, ang pagbalhin sa kolor ug matag kolor.\n+  HINT_RIPPS2_TEXT_WEIGHT: 'Unsa kabug-at ang kaugalingong teksto sa RIPPS2: ang mga tip sa Settings, STATUS ug About. Ang Regular sama sa kaniadto; ang Medium ug Bold mas klaro sa humok nga hulagway.'\n+  HINT_RIPPS2_HDD_START: 'Ang internal HDD, APA o exFAT: basahon kini sa RIPPS2 bisan unsa pa kini ug ipakita kausa, isip mga dula sa HDD. Ang Off dili mohilabot niini.'\n+  HINT_RIPPS2_THEME_COLOR: Ang kaugalingong kolor sa tema. Pilia ang Kaugalingon isip kolor sa tema aron ikaw ang mopili niini, sama sa mga kolor sa teksto.\n+  HINT_RIPPS2_SWEEP: Lima ka kolor nga agian sa tema, hinay ug hamis. Balangaw, usa ka pagbalhin gikan sa matag kolor sa tema, o imong kaugalingon. Ang Off magpabilin sa tema nga dili molihok.\n+  HINT_RIPPS2_SWEEP_COLORS: Ang lima ka kolor sa pagbalhin ug ang kakusog niini. Usba ang usa sa RIPPS2 ug tipigan kini isip imo, sa ngalan nga imong ihatag.\n+  HINT_RIPPS2_SWEEP_NAME: Unsa ang ngalan sa imong pagbalhin sa listahan sa Pagbalhin sa kolor.\n+  HINT_RIPPS2_SWEEP_COLOR: Usa sa lima ka kolor nga agian sa pagbalhin, sumala sa han-ay.\n+  HINT_RIPPS2_SWEEP_SPEED: 'Unsa kadugay ang matag kolor sa dili pa mogawas ang sunod. Bisan ang Buhi nagpabiling hamis: ang mga kolor dili gyud mokidlap.'\n+  RIPPS2_HIDE_COMPRESS: Itago ang dula ug compression\n+  HINT_RIPPS2_SELECT_HOLD: 'SELECT gikuptan 4.2 s sa lista sa dula. Lista: ang mga tema sa usa ka panel, pili og usa. Sunod-sunod: ang sunod nga tema. Gipalong: walay mahitabo (ang pagpindot mo-refresh gihapon).'"
        },
        {
          "sha": "b53ed47c64b252b5023b493e8759874e98469d17",
          "filename": "elf/lang/Croatian.yml",
          "status": "modified",
          "additions": 209,
          "deletions": 22,
          "changes": 231,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FCroatian.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FCroatian.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FCroatian.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: Croatian\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTER PRIHVA\u0106A, ESC ODUSTAJE. BILO KOJA TIPKA PRIKAZUJE TIPKE.\n   USB KEYBOARD: USB TIPKOVNICA\n-  '      DESR DVR ISOs (experimental)': '      DESR DVR ISO datoteke (eksperimentalno)'\n-  '      Internal HDD (exFAT)': '      Unutarnji HDD (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (eksperimentalno)'\n-  '      Protocol': '      Protokol'\n   Access: Pristup\n   Applications Page Start Mode: Na\u010din pokretanja stranice aplikacija\n   Art Delay: Odgoda slika\n@@ -22,7 +18,6 @@ strings:\n   Fifth: Peti\n   First: Prvi\n   Fourth: \u010cetvrti\n-  HDD (APA) Start Mode: Na\u010din pokretanja HDD-a (APA)\n   HDD Cache: HDD predmemorija\n   HDD Spindown: Ga\u0161enje HDD-a u mirovanju\n   MMCE Settings: MMCE postavke\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: Ovo je PS2 igra s ESR zakrpom. Stavi ESR (esr*.elf) u APPS, BOOT ili ESR na memorijskoj kartici ili USB pogonu da je pokrene\u0161.\n   '%s, BUILD %d': '%s, IZGRADNJA %d'\n   '... and the anonymous ...': '... i oni bezimeni ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': '1.8.0 prilo\u017een bez izmjena (AFL-3.0): github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: Rastavljanje i ponovna izgradnja RiptOPL-a, ogranka Open PS2 Loadera,\n   ABOUT: O PROGRAMU\n   'ACHIEVEMENTS: SPIRIT OF RA': 'POSTIGNU\u0106A: SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): AVI video u Memorijskim datotekama, ogranak RIPPS2 (AFL 2.0)\n   Apollo Save Tool by bucanero: Apollo Save Tool, autor bucanero\n   BACK: NATRAG\n+  Back (keeps both): Natrag (zadr\u017eava oba)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: Ova MMCE kartica ne mo\u017ee preimenovati datoteku, a sa\u017eimanju to treba u zadnjem koraku. A\u017eurirajte njezin sd2psXtd firmware na 1.4.0 ili noviji (sd2psxtd.github.io/download), zatim poku\u0161ajte ponovno. Ni\u0161ta nije promijenjeno.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): BIBLE je PS2BBL 1.2.0, potpisane izgradnje El Isre (kartica i HDD), bez izmjena (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: BIOS zvukovi > Izdvoji ih reproducira kao SPU2; BIOS nije prilo\u017een\n   BOOT LOCK AND BIBLE: BOOT LOCK I BIBLE\n   BOOT LOCK edits its config too: BOOT LOCK mijenja i njegovu konfiguraciju\n   BUILD DETAILS: POJEDINOSTI IZGRADNJE\n-  BUILT ON RIPTOPL: IZGRA\u0110ENO NA RIPTOPL-U\n   BearSSL by Thomas Pornin: BearSSL, autor Thomas Pornin\n   Built with Claude (Anthropic): Izra\u0111eno uz Claude (Anthropic)\n   CD PLAYER: CD REPRODUKTOR\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek i Sleek Bold, nacrtani za RIPPS2\n   RetroAchievements on the console by hacan359 (yoba): RetroAchievements na konzoli, autor hacan359 (yoba)\n   RiptOPL by NathanNeurotic: RiptOPL, autor NathanNeurotic\n-  Roboto Condensed by Google (Apache-2.0): Roboto Condensed, autor Google (Apache-2.0)\n   SOUNDS: ZVUKOVI\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: SP193-ov FMCB instalacijski program i pcm720-ov OSDMenu, pra\u0107eni korak po korak\n   SUPPORT: PODR\u0160KA\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: wLaunchELF R3Z, autor NathanNeurotic\n   wLaunchELF by the ps2homebrew team: wLaunchELF, autor tim ps2homebrew\n   what RIPPS2's card image writes are checked against: s \u010dime se provjerava zapisivanje slika kartica u RIPPS2\n-  with its own look, launchers, file browser and save tools.: s vlastitim izgledom, pokreta\u010dima, preglednikom datoteka i alatima za spremljene igre.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: xeRAbora, autor hacan359; rcheevos, autor RetroAchievements\n   your console's own, ripped from its BIOS: vlastiti zvukovi tvoje konzole, izdvojeni iz njezina BIOS-a\n   ' (no DNS answer)': ' (nema DNS odgovora)'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO: zapisivanje %.60s'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: Neutrino se nije mogao zapisati na %s. Provjeri slobodan prostor i da nije za\u0161ti\u0107en od pisanja, pa poku\u0161aj ponovno.\n   THE GAME'S DRIVE  (%s/neutrino): POGON IGRE  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : Ova se igra pokre\u0107e preko Neutrina, a ondje gdje RIPPS2 tra\u017ei (Postavke, pogon igre, memorijske kartice) nema Neutrina. RIPPS2 nosi Neutrino 1.8.0 autora rickgaiser (AFL-3.0). Instalirati ga sada? Zatim bira\u0161 gdje.\n   '%d / %d FILES': '%d / %d DATOTEKA'\n   '%d FOLDERS AND CONTENTS': '%d MAPA I SADR\u017dAJ'\n   '%d FOLDERS, %d FOUND': '%d MAPA, PRONA\u0110ENO %d'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d od %d jo\u0161 zaklju\u010dano'\n   '%s   on %s': '%s   na %s'\n   BADGES: ZNA\u010cKE\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: Zna\u010dke rade samo s uklju\u010denom TELEMETRIJOM i pokrenutim xeRAbora na tvom ra\u010dunalu; do tada ne ko\u0161taju ni\u0161ta.\n   CHECK THE DISC: PROVJERI DISK\n   From this game's last CHECK GAME SUPPORT.: Iz posljednje PROVJERE PODR\u0160KE ZA IGRU za ovu igru.\n   HOW IT WORKS: KAKO RADI\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: Na ovoj konzoli je PS2BBL s RIPPS2 zaklju\u010davanjem.\n   AUDIO folder: mapa AUDIO\n   DRIVES: POGONI\n-  INSTALL RIPPS2'S COPY: INSTALIRAJ RIPPS2 KOPIJU\n-  'Installed: ': 'Instalirano: '\n   MANAGE: UPRAVLJAJ\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: UPRAVLJAJ prikazuje \u0161to pokre\u0107e svaka memorijska kartica te instalira ili popravlja BIBLE.\n   MEMORY CARD 0  (about 600 KB): MEMORIJSKA KARTICA 0  (oko 600 KB)\n   MEMORY CARD 1  (about 600 KB): MEMORIJSKA KARTICA 1  (oko 600 KB)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO: njegov upravlja\u010dki program nije se htio u\u010ditati (kod %d).'\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO: upravlja\u010dki program je u\u010ditan, ali nijedna SD kartica nije odgovorila (utor mc1).'\n   NEUTRINO SETTINGS: NEUTRINO POSTAVKE\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': 'Jo\u0161 nema: RIPPS2 nema vlastite zvukove. Izdvoji zvukove ove konzole ili ih izradi u RIPPS2 Audio Studiju.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: Nije instalirano. Igra kojoj je potreban nudi RIPPS2 kopiju, ili ga instaliraj ovdje.\n   POPSTARTER NETWORK: POPSTARTER MRE\u017dA\n   POPSTARTER and Ember: POPSTARTER i Ember\n   PS1 SETTINGS: PS1 POSTAVKE\n   PS2BBL 1.2.0 for RIPPS2: PS2BBL 1.2.0 za RIPPS2\n   RIP THIS CONSOLE'S SOUNDS: IZDVOJI ZVUKOVE OVE KONZOLE\n-  RIPPS2's homebrew: RIPPS2 homebrew\n   STATUS: STATUS\n-  'STATUS: looking for RIPPS2''s homebrew': 'STATUS: tra\u017eenje RIPPS2 homebrew programa'\n   a drive of unknown kind: pogon nepoznate vrste\n-  a drive or a memory card: pogon ili memorijska kartica\n   boot loader, card by card: u\u010ditava\u010d, kartica po kartica\n   from its BIOS, into AUDIO: iz njezina BIOS-a, u AUDIO\n   internal HDD (exFAT): unutarnji HDD (exFAT)\n@@ -975,10 +958,194 @@ strings:\n   Sidebar: Bo\u010dna traka\n   your own copy, found on your drives and memory cards: tvoja vlastita kopija, prona\u0111ena na tvojim pogonima i memorijskim karticama\n   'your own, found and launched: github.com/Gageformer/Ember/releases': 'tvoj vlastiti, prona\u0111en i pokrenut: github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': 'Nije prona\u0111en na mass0: (%s). Stavi ember.elf i svoj bios.bin u njegovu mapu EMBER.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: Nije prona\u0111en. Stavi svoj POPSTARTER.ELF u mapu POPS na pogonu.\n   Launch Disc: Pokretanje diska\n   Memory Files: Memorijske datoteke\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: RIPPS2 je prona\u0161ao %d od 5. Svaki se pamti, a svako pokretanje ga \u010dita odande.\n+  not found on the started drives: nije prona\u0111eno na pokrenutim pogonima\n+  not looked for yet: jo\u0161 nije tra\u017eeno\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s nije odgovorio, pa se %s ne mo\u017ee pokrenuti. Je li spojen?'\n+  '%s is not on %s any more, so it has left All Games.': '%s vi\u0161e nije na %s, pa je oti\u0161la iz Svih igara.'\n+  STARTING %s: POKRE\u0106EM %s\n+  the SMB share: SMB dijeljenje\n+  the drive: pogon\n+  the internal HDD (APA): unutarnji HDD (APA)\n+  the internal HDD (exFAT): unutarnji HDD (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: IZGRA\u0110ENO NA RIPTOPL-U, S NEUTRINOM\n+  'Special thanks to all the YouTube love as well:': 'Posebno hvala i za svu ljubav s YouTubea:'\n+  and its own look, launchers, file browser and save tools.: i s vlastitim izgledom, pokreta\u010dima, preglednikom datoteka i alatima za spremanja.\n+  glad to help however you reach me.: rado poma\u017eem, kako god me dosegnete.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): druga jezgra za igre, 1.8.0 prilo\u017eena bez izmjena (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: s rickgaiserovim Neutrinom unutra kao drugom jezgrom za igre,\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : 'Ova se igra pokre\u0107e kroz Neutrino, a RIPPS2 ga nije prona\u0161ao: ni na putanji iz Postavki, ni na pogonu igre, ni na memorijskim karticama, ni na jednom pokrenutom pogonu. RIPPS2 nosi Neutrino 1.8.0 od rickgaisera (AFL-3.0): instaliraj ga jednom, gdje \u017eeli\u0161, i svaka Neutrino igra ga koristi odande.'\n+  STARTING THE DRIVES: POKRE\u0106EM POGONE\n+  '%d games, on the started drives': '%d igara, na pokrenutim pogonima'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s: NEKE DATOTEKE NISU MOGLE BITI OBRISANE'\n+  '%u partitions, all in order': '%u particija, sve u redu'\n+  ', %d art images': ', %d slika omota'\n+  ', and ': ', i '\n+  ', its cheats': ', njezini cheatovi'\n+  ', its settings': ', njezine postavke'\n+  1 game, on the started drives: 1 igra, na pokrenutim pogonima\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: PS1 igra ovdje se bri\u0161e u Memorijskim datotekama, gdje je zasebna datoteka.\n+  A network catalogue's games are deleted on the PC that serves them.: Igre mre\u017enog kataloga bri\u0161u se na ra\u010dunalu koje ih poslu\u017euje.\n+  DELETE FOR GOOD: OBRI\u0160I ZAUVIJEK\n+  DELETE IT: OBRI\u0160I JE\n+  'DELETED %s: %s FREED': '%s OBRISANA: OSLOBO\u0110ENO %s'\n+  'Delete %s from %s? Goes: %s.': 'Obrisati %s s %s? Odlazi: %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': 'Obrisati %s s %s? Odlazi: %s. Ostaje: %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: Obrisati i njezina spremanja i RIPSHOT slike? Ne ih zadr\u017eava.\n+  HIDDEN GAMES: SKRIVENE IGRE\n+  'HIDDEN: %s': 'SKRIVENO: %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: Sakriti %s? Napu\u0161ta svaki popis, i Sve igre i Favorite. Postavke > Izvori igara > Skrivene igre je ponovno prikazuju.\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: Zadnja provjera. %s se bri\u0161e zauvijek i ni\u0161ta je ne mo\u017ee vratiti. Pritisni START da je obri\u0161e\u0161.\n+  None. Hide Game, in a game's options, puts one here.: Nijedna. Sakrij igru, u opcijama igre, stavlja jednu ovdje.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : 'Ni\u0161ta nije obrisano: tablica particija ovog pogona ne odgovara ovom disku (%s). Mo\u017eda je zapisana iz slike diska druge veli\u010dine. Najprije je popravi na ra\u010dunalu.'\n+  RIPPS2 cannot delete a game from this page.: RIPPS2 ne mo\u017ee obrisati igru s ove stranice.\n+  RIPPS2 could not find this game's files on its drive.: RIPPS2 nije prona\u0161ao datoteke ove igre na njezinom pogonu.\n+  Reading the hidden lists: \u010citam skrivene popise\n+  SHOW AGAIN OR DELETE: PONOVNO PRIKA\u017dI ILI OBRI\u0160I\n+  SHOW IT AGAIN: PONOVNO JE PRIKA\u017dI\n+  That game is not on its drive any more.: Ta igra vi\u0161e nije na svom pogonu.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: Skriveni popis nije se mogao zapisati na pogon igre. Je li za\u0161ti\u0107en od pisanja, ili su Operacije pisanja isklju\u010dene?\n+  The partition could not be removed (%d). Nothing was deleted.: Particija nije mogla biti uklonjena (%d). Ni\u0161ta nije obrisano.\n+  This drive cannot keep a hidden list (a network catalogue).: Ovaj pogon ne mo\u017ee \u010duvati skriveni popis (mre\u017eni katalog).\n+  This game's drive has not answered yet. Wait for it, then try again.: Pogon ove igre jo\u0161 nije odgovorio. Pri\u010dekaj ga, pa poku\u0161aj ponovno.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: Particija ove igre je jedna od onih koje RIPPS2 ne uklanja (sistemska particija, ili ona s njegovim postavkama).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': 'particija je ve\u0107a nego \u0161to ovaj disk dopu\u0161ta: tablica je napravljena za disk druge veli\u010dine'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': 'particija prelazi kraj ovog diska (sektor %u od %u): tablica je napravljena za ve\u0107i disk'\n+  its RIPSHOT pictures: njezine RIPSHOT slike\n+  its drive: njezin pogon\n+  its saves: njezina spremanja\n+  sector %u holds no partition header: sektor %u nema zaglavlje particije\n+  the chain does not close at __mbr: lanac se ne zatvara na __mbr\n+  the chain skips from sector %u: lanac preska\u010de od sektora %u\n+  the chain's links disagree at sector %u: karike lanca se ne sla\u017eu na sektoru %u\n+  the drive's size could not be read: veli\u010dina pogona nije se mogla pro\u010ditati\n+  the game (%s): igra (%s)\n+  the game's folder and everything in it (%s): mapa igre i sve u njoj (%s)\n+  the header at sector %u could not be read: zaglavlje na sektoru %u nije se moglo pro\u010ditati\n+  the header at sector %u fails its checksum: zaglavlje na sektoru %u ne prolazi kontrolni zbroj\n+  the partition at sector %u is not where or as large as APA puts one: particija na sektoru %u nije ondje gdje, ni velika koliko, APA stavlja jednu\n+  the settings and art another copy of it uses: postavke i slike koje koristi druga njezina kopija\n+  '%d FOLDERS': '%d MAPA'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': '%s nije se moglo kopirati na %s. Je li pogon pun ili za\u0161ti\u0107en od pisanja?'\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s nije ondje gdje RIPPS2 tra\u017ei. Potra\u017eiti ga na pogonima koje je RIPPS2 pokrenuo? Gdje se prona\u0111e, pamti se za svako sljede\u0107e pokretanje.'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Ember nije u %s, pored igara, a mora biti. RIPPS2 ga je prona\u0161ao na %s: START ga kopira onamo (%d KB) i pokre\u0107e igru.'\n+  Ember's BIOS: Emberov BIOS\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Emberov BIOS (bios.bin) nije u %s, pored Embera. RIPPS2 je prona\u0161ao jedan na %s: START ga kopira onamo (%d KB) i pokre\u0107e igru.'\n+  LOOKING FOR YOUR LAUNCHERS: TRA\u017dIM TVOJE POKRETA\u010cE\n+  The POPS files: POPS datoteke\n+  '%s  MMCE card': '%s  MMCE kartica'\n+  INSTALL RIPPS2'S NEUTRINO: INSTALIRAJ RIPPS2-OV NEUTRINO\n+  'Internal HDD (APA): %s free of %s': 'Unutarnji HDD (APA): slobodno %s od %s'\n+  LAUNCHERS: POKRETA\u010cI\n+  LOOK FOR THEM: POTRA\u017dI IH\n+  'Memory card %d: %d KB free': 'Memorijska kartica %d: slobodno %d KB'\n+  POPS files: POPS datoteke\n+  START EVERY DRIVE: POKRENI SVE POGONE\n+  'STATUS: looking at the drives and the homebrew': 'STATUS: pregledavam pogone i homebrew'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): Najprije pokrenuti sve pogone, da pretraga dosegne sve? (Ne pretra\u017euje ve\u0107 pokrenute pogone.)\n+  USB, HDD, MMCE and the rest: USB, HDD, MMCE i ostalo\n+  every started drive: svaki pokrenuti pogon\n+  found where you keep them: prona\u0111eni gdje ih dr\u017ei\u0161\n+  not found yet: jo\u0161 nije prona\u0111eno\n+  not on a started drive: nije na pokrenutom pogonu\n+  only if you have none: samo ako nema\u0161 nijedan\n+  starts every drive first: najprije pokre\u0107e sve pogone\n+  Drives, launchers and homebrew: Pogoni, pokreta\u010di i homebrew\n+  BDM Devices: BDM ure\u0111aji\n+  Color Sweep: Izmjena boja\n+  Colors and Text: Boje i tekst\n+  HDD POPS partitions: POPS particije na HDD-u\n+  Layout: Raspored\n+  Missing Covers: Omoti koji nedostaju\n+  Name: Ime\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: Jo\u0161 nije prona\u0111ena SMB postavka za POPSTARTER. Ispunite retke ispod.\n+  POPSTARTER Network: POPSTARTER mre\u017ea\n+  POPSTARTER SMB Network: POPSTARTER SMB mre\u017ea\n+  POPSTARTER UDPFS Network: POPSTARTER UDPFS mre\u017ea\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: RIPPS2 sam dodaje pogon, ISO, VMC-ove i video na\u010din.\n+  Screen: Zaslon\n+  Speed: Brzina\n+  Sweep Colors: Boje izmjene\n+  Text Weight: Debljina teksta\n+  Theme Color: Glavna boja\n+  Audio: Zvuk\n+  Bold: Podebljano\n+  Controllers: Kontroleri\n+  Lively: \u017divahno\n+  Medium: Srednje\n+  PS Emulation: PS emulacija\n+  Regular: Obi\u010dno\n+  Slow: Sporo\n+  A MOMENT: TRENUTAK\n+  A TASK IN THE BACKGROUND: ZADATAK U POZADINI\n+  ABOUT %d S: OKO %d S\n+  ANY MOMENT: SVAKI TREN\n+  READING THE GAME LIST OF %s: \u010cITA POPIS IGARA S %s\n+  SAVING A LIST: SPREMA POPIS\n+  START NOW: POKRENI SADA\n+  STARTING: POKRE\u0106E SE\n+  STARTS NEXT: POKRE\u0106E SE SLJEDE\u0106E\n+  STOPPING IT SAFELY: SIGURNO ZAUSTAVLJA\n+  THE LAST OF THE QUEUE: KRAJ REDA\n+  WHEN IT FINISHES: KAD ZAVR\u0160I\n+  What each card starts; install or repair BIBLE.: \u0160to pokre\u0107e svaka kartica; instalirajte ili popravite BIBLE.\n+  Rainbow: Duga\n+  Custom: Vlastito\n+  DESR DVR ISOs (experimental): DESR DVR ISO datoteke (eksperimentalno)\n+  PC CARD CF (experimental): PC CARD CF (eksperimentalno)\n+  HDD Start Mode: Na\u010din pokretanja HDD-a\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): Roboto Condensed, autor The Roboto Project Authors (SIL OFL 1.1)\n+  '%s on %s': '%s na %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: %s je postalo %s (%d%%), svaki dio provjeren. Ukloniti ISO i zadr\u017eati ZSO?'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: %s je ponovno postalo %s, svaki dio provjeren. Ukloniti ZSO i zadr\u017eati ISO?'\n+  A ZSO of this game is already beside it. Remove one of the two first.: Pokraj nje ve\u0107 postoji ZSO ove igre. Najprije uklonite jednu od dvije.\n+  About %d min, then checked: Oko %d min, zatim provjera\n+  An ISO of this game is already beside it. Remove one of the two first.: Pokraj nje ve\u0107 postoji ISO ove igre. Najprije uklonite jednu od dvije.\n+  CHECKING EVERY PIECE: PROVJERA SVAKOG DIJELA\n+  COMPRESS TO ZSO: SA\u017dMI U ZSO\n+  'COMPRESSED %s: %s FREED': 'SA\u017dETO %s: OSLOBO\u0110ENO %s'\n+  COMPRESSING 1 OF 2: SA\u017dIMANJE 1 OD 2\n+  COMPRESSING 2 OF 2: SA\u017dIMANJE 2 OD 2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : Sa\u017eeti %s? ISO ostaje dok se ZSO ne pro\u010dita i provjeri, zatim RIPPS2 pita prije nego ukloni ISO. Pogon treba mjesta za oboje dok radi; ostavite konzolu uklju\u010denu dok ne zavr\u0161i. ZSO se pokre\u0107e samo s OPL jezgrom (ne Neutrino); njezina spremanja, postavke, slike i varke ostaju s njom, a u bilo kojem trenutku mo\u017ee ponovno postati ISO.\n+  Compress to ZSO: Sa\u017emi u ZSO\n+  Cycle: Redom\n+  'Experimental: OPL core only (not Neutrino)': 'Eksperimentalno: samo OPL jezgra (ne Neutrino)'\n+  Hold SELECT: Dr\u017eanje SELECT\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: Nije moglo biti dovr\u0161eno (%s). Ono \u0161to je zapisano uklonjeno je; igra je kakva je bila.\n+  List: Popis\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : Ponovno napraviti %s kao ISO? ZSO ostaje dok se ISO ne pro\u010dita i provjeri, zatim RIPPS2 pita prije nego ukloni ZSO. Pogon treba mjesta za oboje dok radi; ostavite konzolu uklju\u010denu dok ne zavr\u0161i. Njezina spremanja, postavke, slike i varke ostaju s njom.\n+  Stop? What it has written so far is removed; the game stays as it was.: Zaustaviti? Ono \u0161to je dosad zapisano uklanja se; igra ostaje kakva je bila.\n+  THEMES: TEME\n+  There is not enough memory for this just now.: Trenutno nema dovoljno memorije za ovo.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: Ovu sliku diska RIPPS2 ne mo\u017ee sa\u017eeti ni raspakirati (njezina veli\u010dina nije u cijelim blokovima od 2 KB).\n+  UNCOMPRESS TO ISO: RASPAKIRAJ U ISO\n+  UNCOMPRESSED %s: RASPAKIRANO %s\n+  UNCOMPRESSING: RASPAKIRAVANJE\n+  Uncompress to ISO: Raspakiraj u ISO\n+  Under a minute, then checked: Manje od minute, zatim provjera\n+  COPYING: KOPIRANJE\n+  SHOW AGAIN, DELETE OR COMPRESS: PONOVNO PRIKA\u017dI, OBRI\u0160I ILI SA\u017dMI\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: PONOVNO PRIKA\u017dI, OBRI\u0160I ILI RASPAKIRAJ\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': '%s treba oko %s slobodnog prostora na svom pogonu dok radi (izvornik ostaje do kraja), a pogon ima %s slobodno.'\n+  Hide and Compress: Sakrij i sa\u017emi\n+  'COMPRESSED AND HIDDEN: %s': 'SA\u017dETO I SKRIVENO: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': 'Nema mjesta za ovo kopiranje: treba oko %s, a pogon ima %s slobodno.'\n+  OVER 4 GB FOR FAT32: VI\u0160E OD 4 GB ZA FAT32\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: Ovaj pogon je %s, koji ne dr\u017ei datoteke od 4 GB ili vi\u0161e, a nova slika bila bi %s.\n+  '%s, %s clusters, %s free of %s': '%s, klasteri %s, %s slobodno od %s'\n+  Delete This Sweep: Obri\u0161i ovu izmjenu\n+  Delete %s? RIPPS2's own sweeps stay as they are.: Obrisati %s? Izmjene samog RIPPS2 ostaju kakve jesu.\n+  Sweep: Izmjena\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: Zna\u010dke ozna\u010davaju igre s postignu\u0107ima na tvojim popisima i koliko si ih otklju\u010dao. Treba im uklju\u010dena TELEMETRIJA.\n labels:\n   NETCONFIG: Mre\u017ene postavke\n   BDMMODE: Na\u010din pokretanja BDM-a\n@@ -1222,7 +1389,7 @@ labels:\n   SAVE_ICONS: 3D ikone spremljenih igara\n   HINT_SAVE_ICONS: Prikazuje ikonu spremljene igre odabrane igre, kako se vrti, gdje tema ima mjesto za nju. Najprije \u010dita vlastiti VMC igre, zatim memorijske kartice.\n   NETBOOT_RESTART_NOW: Mre\u017eni protokol mijenja se nakon ponovnog pokretanja. Spremiti postavke i ponovno pokrenuti OPL sada?\n-  HINT_NET_PROTOCOL: 'Koju mre\u017enu uslugu RiptOPL koristi: SMB dijeljenje, UDPFS, UDPBD ili HTTP. Ista postavka kao u Izvorima igara. Prelazak s usluge koja ve\u0107 radi zahtijeva ponovno pokretanje.'\n+  HINT_NET_PROTOCOL: 'Koju mre\u017enu uslugu RiptOPL koristi: SMB dijeljenje, UDPFS, UDPBD ili HTTP. Prelazak s usluge koja ve\u0107 radi zahtijeva ponovno pokretanje.'\n   HINT_UDPFS_ACCESS: Samo UDPFS. Datoteke \u010dita pojedina\u010dne ISO datoteke iz mape igara na poslu\u017eitelju; IMG \u010dita sliku diska kao pogon. SMB i HTTP uvijek koriste Datoteke, UDPBD uvijek IMG.\n   HINT_SMB_VERSION: Kojom se ina\u010dicom SMB-a razgovara s dijeljenjem na ra\u010dunalu.\n   HINT_NET_ADVANCED: Otklju\u010dava na\u010din Ethernet veze i SMB priklju\u010dak za ure\u0111ivanje.\n@@ -1423,3 +1590,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: Kako snimke na \u010dekanju na filmu svake igre postaju PNG.\n   RIPPS2_SETTINGS_HOME: Spremaj postavke na\n   HINT_RIPPS2_SETTINGS_HOME: 'Gdje RIPPS2 dr\u017ei postavke: u vlastitom spremanju na memorijskoj kartici, u mapi RIPPS2 na pogonu (unutarnjem HDD-u, USB-u, MX4SIO, iLinku ili MMCE) ili u mapi koju upi\u0161e\u0161.'\n+  RIPPS2_LAUNCHERS: Pokreta\u010di\n+  RIPPS2_LOOK_NOW: Potra\u017ei sada\n+  HINT_RIPPS2_LOOK_NOW: RIPPS2 na pokrenutim pogonima tra\u017ei POPSTARTER, POPS datoteke, Ember, Emberov BIOS i Neutrino te pamti gdje je svaki. Svako pokretanje ih \u010dita odande.\n+  RIPPS2_HIDE_GAME: Sakrij igru\n+  RIPPS2_HIDDEN_GAMES: Skrivene igre\n+  HINT_RIPPS2_HIDDEN_GAMES: Igre skrivene sa Sakrij igru, na pokrenutim pogonima. Ponovno prika\u017ei jednu ili je obri\u0161i. Svaki pogon \u010duva svoj popis.\n+  HINT_RIPPS2_BDM_DEVICES: 'Pogoni s kojih BDM \u010dita igre: USB, MX4SIO, PC Card CF, DVR pogon DESR-a i iLink. Unutarnji HDD prati na\u010din pokretanja HDD-a.'\n+  HINT_RIPPS2_SCREEN: 'Slika izbornika: video na\u010din, \u0161iroki zaslon, gdje stoji na televizoru (H-POS, V-POS) i overscan.'\n+  HINT_RIPPS2_LAYOUT: Gdje stoje kategorije i kako stranica ulazi, zatim omoti RIPFLOW-a i mre\u017ea RIPGRID-a.\n+  HINT_RIPPS2_COLORS_TEXT: Debljina teksta, tema boja, izmjena boja i svaka boja.\n+  HINT_RIPPS2_TEXT_WEIGHT: 'Koliko je debeo vlastiti tekst RIPPS2-a: savjeti u postavkama, STATUS i O programu. Obi\u010dno je kao prije; Srednje i Podebljano bolje se \u010ditaju na mekoj slici.'\n+  HINT_RIPPS2_HDD_START: 'Unutarnji HDD, APA ili exFAT: RIPPS2 ga \u010dita kakav god bio i prikazuje jednom, kao HDD igre. Isklju\u010deno ga ostavlja na miru.'\n+  HINT_RIPPS2_THEME_COLOR: Vlastita boja teme. Odaberite Vlastito kao temu boja da je sami birate, kao boje teksta.\n+  HINT_RIPPS2_SWEEP: Pet boja kroz koje tema prelazi polako i glatko. Duga, izmjena iz svake teme boja ili va\u0161a vlastita. Isklju\u010deno dr\u017ei temu mirnom.\n+  HINT_RIPPS2_SWEEP_COLORS: Pet boja izmjene i njezina brzina. Promijenite jednu od RIPPS2-ovih i \u010duva se kao va\u0161a, pod imenom koje joj date.\n+  HINT_RIPPS2_SWEEP_NAME: Kako se va\u0161a izmjena zove na popisu Izmjena boja.\n+  HINT_RIPPS2_SWEEP_COLOR: Jedna od pet boja kroz koje izmjena prolazi, redom.\n+  HINT_RIPPS2_SWEEP_SPEED: 'Koliko traje svaka boja prije nego \u0161to u\u0111e sljede\u0107a. I \u017divahno ostaje glatko: boje nikad ne bljeskaju.'\n+  RIPPS2_HIDE_COMPRESS: Sakrij igru i sa\u017eimanje\n+  HINT_RIPPS2_SELECT_HOLD: 'SELECT dr\u017ean 4.2 s na popisu igara. Popis: teme u plo\u010di, odaberite jednu. Redom: sljede\u0107a tema. Isklju\u010deno: ni\u0161ta (pritisak i dalje osvje\u017eava).'"
        },
        {
          "sha": "27efa180366fe67142574d9ffc3b16ecd9c1da95",
          "filename": "elf/lang/Czech.yml",
          "status": "modified",
          "additions": 209,
          "deletions": 22,
          "changes": 231,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FCzech.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FCzech.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FCzech.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: Czech\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTER POTVRD\u00cd, ESC ZRU\u0160\u00cd. LIBOVOLN\u00c9 TLA\u010c\u00cdTKO UK\u00c1\u017dE KL\u00c1VESY.\n   USB KEYBOARD: USB KL\u00c1VESNICE\n-  '      DESR DVR ISOs (experimental)': '      ISO DESR DVR (experiment\u00e1ln\u00ed)'\n-  '      Internal HDD (exFAT)': '      Intern\u00ed HDD (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (experiment\u00e1ln\u00ed)'\n-  '      Protocol': '      Protokol'\n   Access: P\u0159\u00edstup\n   Applications Page Start Mode: Re\u017eim spu\u0161t\u011bn\u00ed str\u00e1nky aplikac\u00ed\n   Art Delay: Zpo\u017ed\u011bn\u00ed obr\u00e1zk\u016f\n@@ -22,7 +18,6 @@ strings:\n   Fifth: P\u00e1t\u00fd\n   First: Prvn\u00ed\n   Fourth: \u010ctvrt\u00fd\n-  HDD (APA) Start Mode: Re\u017eim spu\u0161t\u011bn\u00ed HDD (APA)\n   HDD Cache: Mezipam\u011b\u0165 HDD\n   HDD Spindown: Usp\u00e1n\u00ed HDD\n   MMCE Settings: Nastaven\u00ed MMCE\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: Toto je hra PS2 s opravou ESR. Ke spu\u0161t\u011bn\u00ed dejte ESR (esr*.elf) do APPS, BOOT nebo ESR na pam\u011b\u0165ov\u00e9 kart\u011b nebo USB disku.\n   '%s, BUILD %d': '%s, SESTAVEN\u00cd %d'\n   '... and the anonymous ...': '... a ti bezejmenn\u00ed ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': '1.8.0 p\u0159ibaleno beze zm\u011bn (AFL-3.0): github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: Rozebran\u00fd a znovu postaven\u00fd RiptOPL, fork Open PS2 Loaderu,\n   ABOUT: O PROGRAMU\n   'ACHIEVEMENTS: SPIRIT OF RA': '\u00daSP\u011aCHY: SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): Video AVI v Pam\u011b\u0165ov\u00fdch souborech, fork RIPPS2 (AFL 2.0)\n   Apollo Save Tool by bucanero: Apollo Save Tool od bucanera\n   BACK: ZP\u011aT\n+  Back (keeps both): Zp\u011bt (ponech\u00e1 oba)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: Tato karta MMCE neum\u00ed p\u0159ejmenovat soubor, co\u017e komprese pot\u0159ebuje v posledn\u00edm kroku. Aktualizujte jej\u00ed firmware sd2psXtd na 1.4.0 nebo nov\u011bj\u0161\u00ed (sd2psxtd.github.io/download) a zkuste to znovu. Nic nebylo zm\u011bn\u011bno.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): BIBLE je PS2BBL 1.2.0, podepsan\u00e1 sestaven\u00ed El Isry (karta a HDD), beze zm\u011bn (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: Zvuky BIOS > Vyjmout je p\u0159ehr\u00e1v\u00e1 tak jako SPU2; \u017e\u00e1dn\u00fd BIOS se nep\u0159ibaluje\n   BOOT LOCK AND BIBLE: BOOT LOCK A BIBLE\n   BOOT LOCK edits its config too: BOOT LOCK upravuje i jeho nastaven\u00ed\n   BUILD DETAILS: PODROBNOSTI SESTAVEN\u00cd\n-  BUILT ON RIPTOPL: POSTAVENO NA RIPTOPL\n   BearSSL by Thomas Pornin: BearSSL od Thomase Pornina\n   Built with Claude (Anthropic): Vytvo\u0159eno s Claude (Anthropic)\n   CD PLAYER: CD P\u0158EHR\u00c1VA\u010c\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek a Sleek Bold, nakreslen\u00e9 pro RIPPS2\n   RetroAchievements on the console by hacan359 (yoba): RetroAchievements na konzoli od hacan359 (yoba)\n   RiptOPL by NathanNeurotic: RiptOPL od NathanNeurotica\n-  Roboto Condensed by Google (Apache-2.0): Roboto Condensed od Googlu (Apache-2.0)\n   SOUNDS: ZVUKY\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: Instal\u00e1tor FMCB od SP193 a OSDMenu od pcm720, krok za krokem\n   SUPPORT: PODPORA\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: wLaunchELF R3Z od NathanNeurotica\n   wLaunchELF by the ps2homebrew team: wLaunchELF od t\u00fdmu ps2homebrew\n   what RIPPS2's card image writes are checked against: s \u010d\u00edm se porovn\u00e1vaj\u00ed obrazy karet, kter\u00e9 zapisuje RIPPS2\n-  with its own look, launchers, file browser and save tools.: s vlastn\u00edm vzhledem, spou\u0161t\u011b\u010di, prohl\u00ed\u017ee\u010dem soubor\u016f a n\u00e1stroji pro ulo\u017een\u00e9 pozice.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: xeRAbora od hacan359, rcheevos od RetroAchievements\n   your console's own, ripped from its BIOS: vlastn\u00ed zvuky va\u0161\u00ed konzole, vyjmut\u00e9 z jej\u00edho BIOSu\n   ' (no DNS answer)': ' (\u017e\u00e1dn\u00e1 odpov\u011b\u010f DNS)'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO: z\u00e1pis %.60s'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: Neutrino se nepoda\u0159ilo zapsat do %s. Zkontrolujte voln\u00e9 m\u00edsto a ochranu proti z\u00e1pisu a zkuste to znovu.\n   THE GAME'S DRIVE  (%s/neutrino): DISK HRY  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : Tato hra se spou\u0161t\u00ed p\u0159es Neutrino a tam, kde RIPPS2 hled\u00e1 (Nastaven\u00ed, disk hry, pam\u011b\u0165ov\u00e9 karty), \u017e\u00e1dn\u00e9 Neutrino nen\u00ed. RIPPS2 s sebou nese Neutrino 1.8.0 od rickgaisera (AFL-3.0). Nainstalovat ho te\u010f? Potom vyberete kam.\n   '%d / %d FILES': '%d / %d SOUBOR\u016e'\n   '%d FOLDERS AND CONTENTS': '%d SLO\u017dEK A OBSAH'\n   '%d FOLDERS, %d FOUND': '%d SLO\u017dEK, NALEZENO %d'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d z %d st\u00e1le zam\u010deno'\n   '%s   on %s': '%s   na %s'\n   BADGES: ODZNAKY\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: Odznaky funguj\u00ed jen se zapnutou TELEMETRI\u00cd a s xeRAbora spu\u0161t\u011bnou na va\u0161em PC; do t\u00e9 doby nic nestoj\u00ed.\n   CHECK THE DISC: ZKONTROLOVAT DISK\n   From this game's last CHECK GAME SUPPORT.: Z posledn\u00ed KONTROLY PODPORY HRY t\u00e9to hry.\n   HOW IT WORKS: JAK TO FUNGUJE\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: Na t\u00e9to konzoli je PS2BBL se z\u00e1mkem RIPPS2.\n   AUDIO folder: slo\u017eka AUDIO\n   DRIVES: DISKY\n-  INSTALL RIPPS2'S COPY: NAINSTALOVAT KOPII RIPPS2\n-  'Installed: ': 'Nainstalov\u00e1no: '\n   MANAGE: SPRAVOVAT\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: SPRAVOVAT ukazuje, co spou\u0161t\u00ed ka\u017ed\u00e1 pam\u011b\u0165ov\u00e1 karta, a instaluje nebo opravuje BIBLE.\n   MEMORY CARD 0  (about 600 KB): PAM\u011a\u0164OV\u00c1 KARTA 0  (asi 600 KB)\n   MEMORY CARD 1  (about 600 KB): PAM\u011a\u0164OV\u00c1 KARTA 1  (asi 600 KB)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO: ovlada\u010d se nena\u010detl (k\u00f3d %d).'\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO: ovlada\u010d je na\u010dten\u00fd, ale \u017e\u00e1dn\u00e1 SD karta neodpov\u011bd\u011bla (slot mc1).'\n   NEUTRINO SETTINGS: NASTAVEN\u00cd NEUTRINO\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': 'Zat\u00edm \u017e\u00e1dn\u00e9: RIPPS2 nem\u00e1 vlastn\u00ed zvuky. Vyjm\u011bte zvuky t\u00e9to konzole, nebo je vytvo\u0159te v RIPPS2 Audio Studio.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: Nenainstalov\u00e1no. Hra, kter\u00e1 to pot\u0159ebuje, nab\u00eddne kopii RIPPS2, nebo to nainstalujte tady.\n   POPSTARTER NETWORK: S\u00cd\u0164 POPSTARTER\n   POPSTARTER and Ember: POPSTARTER a Ember\n   PS1 SETTINGS: NASTAVEN\u00cd PS1\n   PS2BBL 1.2.0 for RIPPS2: PS2BBL 1.2.0 pro RIPPS2\n   RIP THIS CONSOLE'S SOUNDS: VYJMOUT ZVUKY T\u00c9TO KONZOLE\n-  RIPPS2's homebrew: homebrew RIPPS2\n   STATUS: STAV\n-  'STATUS: looking for RIPPS2''s homebrew': 'STAV: hled\u00e1n\u00ed homebrew RIPPS2'\n   a drive of unknown kind: disk nezn\u00e1m\u00e9ho druhu\n-  a drive or a memory card: disk nebo pam\u011b\u0165ov\u00e1 karta\n   boot loader, card by card: zavad\u011b\u010d, karta po kart\u011b\n   from its BIOS, into AUDIO: z jej\u00edho BIOSu, do AUDIO\n   internal HDD (exFAT): intern\u00ed HDD (exFAT)\n@@ -975,10 +958,194 @@ strings:\n   Sidebar: Postrann\u00ed panel\n   your own copy, found on your drives and memory cards: va\u0161e vlastn\u00ed kopie, nalezen\u00e1 na va\u0161ich disc\u00edch a pam\u011b\u0165ov\u00fdch kart\u00e1ch\n   'your own, found and launched: github.com/Gageformer/Ember/releases': 'v\u00e1\u0161 vlastn\u00ed, nalezen\u00fd a spu\u0161t\u011bn\u00fd: github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': 'Nenalezeno na mass0: (%s). Vlo\u017ete ember.elf a sv\u016fj bios.bin do jeho slo\u017eky EMBER.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: Nenalezeno. Vlo\u017ete sv\u016fj POPSTARTER.ELF do slo\u017eky POPS na disku.\n   Launch Disc: Spu\u0161t\u011bn\u00ed disku\n   Memory Files: Pam\u011b\u0165ov\u00e9 soubory\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: RIPPS2 na\u0161el %d z 5. Ka\u017ed\u00fd se zapamatuje a ka\u017ed\u00e9 spu\u0161t\u011bn\u00ed ho \u010dte odtamtud.\n+  not found on the started drives: nenalezeno na spu\u0161t\u011bn\u00fdch disc\u00edch\n+  not looked for yet: zat\u00edm nehled\u00e1no\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s neodpov\u011bd\u011bl, tak\u017ee %s se nem\u016f\u017ee spustit. Je p\u0159ipojen\u00fd?'\n+  '%s is not on %s any more, so it has left All Games.': '%s u\u017e nen\u00ed na %s, tak\u017ee zmizela ze V\u0161ech her.'\n+  STARTING %s: SPOU\u0160T\u00cdM %s\n+  the SMB share: sd\u00edlen\u00ed SMB\n+  the drive: disk\n+  the internal HDD (APA): intern\u00ed HDD (APA)\n+  the internal HDD (exFAT): intern\u00ed HDD (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: POSTAVENO NA RIPTOPL, S NEUTRINEM\n+  'Special thanks to all the YouTube love as well:': 'Zvl\u00e1\u0161tn\u00ed d\u00edky tak\u00e9 za v\u0161echnu l\u00e1sku z YouTube:'\n+  and its own look, launchers, file browser and save tools.: a s vlastn\u00edm vzhledem, spou\u0161t\u011b\u010di, prohl\u00ed\u017ee\u010dem soubor\u016f a n\u00e1stroji pro ulo\u017een\u00e9 pozice.\n+  glad to help however you reach me.: r\u00e1d pom\u016f\u017eu, a\u0165 se ke mn\u011b dostanete jakkoli.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): druh\u00e9 hern\u00ed j\u00e1dro, 1.8.0 p\u0159ilo\u017een\u00e9 beze zm\u011bn (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: s Neutrinem od rickgaisera uvnit\u0159 jako druh\u00fdm hern\u00edm j\u00e1drem,\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : 'Tato hra se spou\u0161t\u00ed p\u0159es Neutrino a RIPPS2 ho nena\u0161el: ani na cest\u011b z Nastaven\u00ed, ani na disku hry, ani na pam\u011b\u0165ov\u00fdch kart\u00e1ch, ani na \u017e\u00e1dn\u00e9m spu\u0161t\u011bn\u00e9m disku. RIPPS2 obsahuje Neutrino 1.8.0 od rickgaisera (AFL-3.0): nainstalujte ho jednou, kam chcete, a ka\u017ed\u00e1 hra s Neutrinem ho odtamtud pou\u017eije.'\n+  STARTING THE DRIVES: SPOU\u0160T\u00cdM DISKY\n+  '%d games, on the started drives': '%d her, na spu\u0161t\u011bn\u00fdch disc\u00edch'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s: N\u011aKTER\u00c9 SOUBORY NE\u0160LO SMAZAT'\n+  '%u partitions, all in order': '%u odd\u00edl\u016f, v\u0161echny v po\u0159\u00e1dku'\n+  ', %d art images': ', %d obr\u00e1zk\u016f obal\u016f'\n+  ', and ': ', a '\n+  ', its cheats': ', jej\u00ed cheaty'\n+  ', its settings': ', jej\u00ed nastaven\u00ed'\n+  1 game, on the started drives: 1 hra, na spu\u0161t\u011bn\u00fdch disc\u00edch\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: Hra PS1 se tady ma\u017ee v Pam\u011b\u0165ov\u00fdch souborech, kde je samostatn\u00fdm souborem.\n+  A network catalogue's games are deleted on the PC that serves them.: Hry ze s\u00ed\u0165ov\u00e9ho katalogu se ma\u017eou na PC, kter\u00e9 je poskytuje.\n+  DELETE FOR GOOD: SMAZAT NAV\u017dDY\n+  DELETE IT: SMAZAT JI\n+  'DELETED %s: %s FREED': '%s SMAZ\u00c1NO: UVOLN\u011aNO %s'\n+  'Delete %s from %s? Goes: %s.': 'Smazat %s z %s? Zmiz\u00ed: %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': 'Smazat %s z %s? Zmiz\u00ed: %s. Z\u016fstane: %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: Smazat tak\u00e9 jej\u00ed ulo\u017een\u00e9 pozice a sn\u00edmky RIPSHOT? Ne je ponech\u00e1.\n+  HIDDEN GAMES: SKRYT\u00c9 HRY\n+  'HIDDEN: %s': 'SKRYTO: %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: Skr\u00fdt %s? Zmiz\u00ed ze v\u0161ech seznam\u016f, i ze V\u0161ech her a Obl\u00edben\u00fdch. Nastaven\u00ed > Zdroje her > Skryt\u00e9 hry ji zase uk\u00e1\u017eou.\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: Posledn\u00ed kontrola. %s bude smaz\u00e1na nav\u017edy a nic ji nevr\u00e1t\u00ed. Stiskn\u011bte START pro smaz\u00e1n\u00ed.\n+  None. Hide Game, in a game's options, puts one here.: \u017d\u00e1dn\u00e9. Skr\u00fdt hru, v mo\u017enostech hry, sem n\u011bjakou d\u00e1.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : 'Nic nebylo smaz\u00e1no: tabulka odd\u00edl\u016f tohoto disku neodpov\u00edd\u00e1 tomuto disku (%s). Mo\u017en\u00e1 byla zaps\u00e1na z obrazu disku jin\u00e9 velikosti. Nejd\u0159\u00edv ji opravte na PC.'\n+  RIPPS2 cannot delete a game from this page.: RIPPS2 nem\u016f\u017ee z t\u00e9to str\u00e1nky smazat hru.\n+  RIPPS2 could not find this game's files on its drive.: RIPPS2 nena\u0161el soubory t\u00e9to hry na jej\u00edm disku.\n+  Reading the hidden lists: \u010ctu seznamy skryt\u00fdch\n+  SHOW AGAIN OR DELETE: ZNOVU UK\u00c1ZAT NEBO SMAZAT\n+  SHOW IT AGAIN: ZNOVU JI UK\u00c1ZAT\n+  That game is not on its drive any more.: Ta hra u\u017e na sv\u00e9m disku nen\u00ed.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: Seznam skryt\u00fdch ne\u0161lo zapsat na disk hry. Je chr\u00e1n\u011bn\u00fd proti z\u00e1pisu, nebo jsou Z\u00e1pisov\u00e9 operace vypnut\u00e9?\n+  The partition could not be removed (%d). Nothing was deleted.: Odd\u00edl ne\u0161lo odstranit (%d). Nic nebylo smaz\u00e1no.\n+  This drive cannot keep a hidden list (a network catalogue).: Tento disk nem\u016f\u017ee m\u00edt seznam skryt\u00fdch (s\u00ed\u0165ov\u00fd katalog).\n+  This game's drive has not answered yet. Wait for it, then try again.: Disk t\u00e9to hry je\u0161t\u011b neodpov\u011bd\u011bl. Po\u010dkejte na n\u011bj a zkuste to znovu.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: Odd\u00edl t\u00e9to hry je jeden z t\u011bch, kter\u00e9 RIPPS2 neodstra\u0148uje (syst\u00e9mov\u00fd odd\u00edl, nebo ten s jeho nastaven\u00edm).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': 'odd\u00edl je v\u011bt\u0161\u00ed, ne\u017e tento disk dovoluje: tabulka byla vytvo\u0159ena pro disk jin\u00e9 velikosti'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': 'odd\u00edl p\u0159esahuje konec tohoto disku (sektor %u z %u): tabulka byla vytvo\u0159ena pro v\u011bt\u0161\u00ed disk'\n+  its RIPSHOT pictures: jej\u00ed sn\u00edmky RIPSHOT\n+  its drive: jej\u00ed disk\n+  its saves: jej\u00ed ulo\u017een\u00e9 pozice\n+  sector %u holds no partition header: sektor %u neobsahuje hlavi\u010dku odd\u00edlu\n+  the chain does not close at __mbr: \u0159et\u011bzec se neuzav\u00edr\u00e1 u __mbr\n+  the chain skips from sector %u: \u0159et\u011bzec p\u0159eskakuje od sektoru %u\n+  the chain's links disagree at sector %u: \u010dl\u00e1nky \u0159et\u011bzce nesouhlas\u00ed u sektoru %u\n+  the drive's size could not be read: velikost disku ne\u0161lo p\u0159e\u010d\u00edst\n+  the game (%s): hra (%s)\n+  the game's folder and everything in it (%s): slo\u017eka hry a v\u0161e v n\u00ed (%s)\n+  the header at sector %u could not be read: hlavi\u010dku u sektoru %u ne\u0161lo p\u0159e\u010d\u00edst\n+  the header at sector %u fails its checksum: hlavi\u010dka u sektoru %u nepro\u0161la kontroln\u00edm sou\u010dtem\n+  the partition at sector %u is not where or as large as APA puts one: odd\u00edl u sektoru %u nele\u017e\u00ed tam, kde ani nen\u00ed tak velk\u00fd, jak ho APA zakl\u00e1d\u00e1\n+  the settings and art another copy of it uses: nastaven\u00ed a obr\u00e1zky, kter\u00e9 pou\u017e\u00edv\u00e1 jin\u00e1 jej\u00ed kopie\n+  '%d FOLDERS': '%d SLO\u017dEK'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': '%s ne\u0161lo zkop\u00edrovat do %s. Je disk pln\u00fd nebo chr\u00e1n\u011bn\u00fd proti z\u00e1pisu?'\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s nen\u00ed tam, kde RIPPS2 hled\u00e1. Hledat na disc\u00edch, kter\u00e9 RIPPS2 spustil? M\u00edsto n\u00e1lezu se zapamatuje pro ka\u017ed\u00e9 dal\u0161\u00ed spu\u0161t\u011bn\u00ed.'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Ember nen\u00ed v %s, vedle her, a mus\u00ed tam b\u00fdt. RIPPS2 ho na\u0161el v %s: START ho tam zkop\u00edruje (%d KB) a spust\u00ed hru.'\n+  Ember's BIOS: BIOS Emberu\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : 'BIOS Emberu (bios.bin) nen\u00ed v %s, vedle Emberu. RIPPS2 jeden na\u0161el v %s: START ho tam zkop\u00edruje (%d KB) a spust\u00ed hru.'\n+  LOOKING FOR YOUR LAUNCHERS: HLED\u00c1M VA\u0160E SPOU\u0160T\u011a\u010cE\n+  The POPS files: Soubory POPS\n+  '%s  MMCE card': '%s  karta MMCE'\n+  INSTALL RIPPS2'S NEUTRINO: NAINSTALOVAT NEUTRINO Z RIPPS2\n+  'Internal HDD (APA): %s free of %s': 'Intern\u00ed HDD (APA): volno %s z %s'\n+  LAUNCHERS: SPOU\u0160T\u011a\u010cE\n+  LOOK FOR THEM: NAJ\u00cdT JE\n+  'Memory card %d: %d KB free': 'Pam\u011b\u0165ov\u00e1 karta %d: volno %d KB'\n+  POPS files: Soubory POPS\n+  START EVERY DRIVE: SPUSTIT V\u0160ECHNY DISKY\n+  'STATUS: looking at the drives and the homebrew': 'STAV: prohl\u00ed\u017e\u00edm disky a homebrew'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): Nejd\u0159\u00edv spustit v\u0161echny disky, aby hled\u00e1n\u00ed dos\u00e1hlo na v\u0161echny? (Ne hled\u00e1 na u\u017e spu\u0161t\u011bn\u00fdch disc\u00edch.)\n+  USB, HDD, MMCE and the rest: USB, HDD, MMCE a zbytek\n+  every started drive: ka\u017ed\u00fd spu\u0161t\u011bn\u00fd disk\n+  found where you keep them: nalezeny tam, kde je m\u00e1te\n+  not found yet: zat\u00edm nenalezeno\n+  not on a started drive: nen\u00ed na spu\u0161t\u011bn\u00e9m disku\n+  only if you have none: jen pokud \u017e\u00e1dn\u00fd nem\u00e1te\n+  starts every drive first: nejd\u0159\u00edv spust\u00ed v\u0161echny disky\n+  Drives, launchers and homebrew: Disky, spou\u0161t\u011b\u010de a homebrew\n+  BDM Devices: Za\u0159\u00edzen\u00ed BDM\n+  Color Sweep: St\u0159\u00edd\u00e1n\u00ed barev\n+  Colors and Text: Barvy a text\n+  HDD POPS partitions: Odd\u00edly POPS na HDD\n+  Layout: Rozvr\u017een\u00ed\n+  Missing Covers: Chyb\u011bj\u00edc\u00ed obaly\n+  Name: N\u00e1zev\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: Zat\u00edm nebylo nalezeno nastaven\u00ed SMB pro POPSTARTER. Vypl\u0148te \u0159\u00e1dky n\u00ed\u017ee.\n+  POPSTARTER Network: S\u00ed\u0165 POPSTARTER\n+  POPSTARTER SMB Network: S\u00ed\u0165 SMB pro POPSTARTER\n+  POPSTARTER UDPFS Network: S\u00ed\u0165 UDPFS pro POPSTARTER\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: RIPPS2 s\u00e1m p\u0159id\u00e1 disk, ISO, VMC a re\u017eim obrazu.\n+  Screen: Obrazovka\n+  Speed: Rychlost\n+  Sweep Colors: Barvy st\u0159\u00edd\u00e1n\u00ed\n+  Text Weight: Tlou\u0161\u0165ka textu\n+  Theme Color: Hlavn\u00ed barva\n+  Audio: Zvuk\n+  Bold: Tu\u010dn\u00e9\n+  Controllers: Ovlada\u010de\n+  Lively: \u017div\u011b\n+  Medium: St\u0159edn\u00ed\n+  PS Emulation: Emulace PS\n+  Regular: B\u011b\u017en\u00e9\n+  Slow: Pomalu\n+  A MOMENT: OKAM\u017dIK\n+  A TASK IN THE BACKGROUND: \u00daLOHA NA POZAD\u00cd\n+  ABOUT %d S: ASI %d S\n+  ANY MOMENT: KA\u017dDOU CHV\u00cdLI\n+  READING THE GAME LIST OF %s: \u010cTE SEZNAM HER Z %s\n+  SAVING A LIST: UKL\u00c1D\u00c1 SEZNAM\n+  START NOW: SPUSTIT HNED\n+  STARTING: SPOU\u0160T\u00cd SE\n+  STARTS NEXT: SPUST\u00cd SE N\u00c1SLEDN\u011a\n+  STOPPING IT SAFELY: BEZPE\u010cN\u011a ZASTAVUJE\n+  THE LAST OF THE QUEUE: KONEC FRONTY\n+  WHEN IT FINISHES: A\u017d SKON\u010c\u00cd\n+  What each card starts; install or repair BIBLE.: Co kter\u00e1 karta spou\u0161t\u00ed; nainstalujte nebo opravte BIBLE.\n+  Rainbow: Duha\n+  Custom: Vlastn\u00ed\n+  DESR DVR ISOs (experimental): ISO DESR DVR (experiment\u00e1ln\u00ed)\n+  PC CARD CF (experimental): PC CARD CF (experiment\u00e1ln\u00ed)\n+  HDD Start Mode: Re\u017eim spu\u0161t\u011bn\u00ed HDD\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): Roboto Condensed od Googlu (SIL OFL 1.1)\n+  '%s on %s': '%s na %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: z %s je %s (%d%%), ka\u017ed\u00fd kus zkontrolov\u00e1n. Odebrat ISO a ponechat ZSO?'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: z %s je op\u011bt %s, ka\u017ed\u00fd kus zkontrolov\u00e1n. Odebrat ZSO a ponechat ISO?'\n+  A ZSO of this game is already beside it. Remove one of the two first.: Vedle n\u00ed u\u017e je ZSO t\u00e9to hry. Nejprve jednu ze dvou odeberte.\n+  About %d min, then checked: Asi %d min, pak kontrola\n+  An ISO of this game is already beside it. Remove one of the two first.: Vedle n\u00ed u\u017e je ISO t\u00e9to hry. Nejprve jednu ze dvou odeberte.\n+  CHECKING EVERY PIECE: KONTROLA KA\u017dD\u00c9HO KUSU\n+  COMPRESS TO ZSO: KOMPRIMOVAT DO ZSO\n+  'COMPRESSED %s: %s FREED': 'KOMPRIMOV\u00c1NO %s: UVOLN\u011aNO %s'\n+  COMPRESSING 1 OF 2: KOMPRIMACE 1 ZE 2\n+  COMPRESSING 2 OF 2: KOMPRIMACE 2 ZE 2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : Komprimovat %s? ISO z\u016fstane, dokud se ZSO znovu nep\u0159e\u010dte a nezkontroluje, pak se RIPPS2 zept\u00e1, ne\u017e ISO odebere. Disk pot\u0159ebuje b\u011bhem pr\u00e1ce m\u00edsto pro oba; nechte konzoli zapnutou, dokud neskon\u010d\u00ed. ZSO se spou\u0161t\u00ed jen s j\u00e1drem OPL (ne Neutrino); jej\u00ed ulo\u017een\u00e9 pozice, nastaven\u00ed, obr\u00e1zky a cheaty z\u016fst\u00e1vaj\u00ed s n\u00ed a kdykoli z n\u00ed lze znovu ud\u011blat ISO.\n+  Compress to ZSO: Komprimovat do ZSO\n+  Cycle: Dokola\n+  'Experimental: OPL core only (not Neutrino)': 'Experiment\u00e1ln\u00ed: jen j\u00e1dro OPL (ne Neutrino)'\n+  Hold SELECT: Podr\u017een\u00ed SELECT\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: Nepoda\u0159ilo se dokon\u010dit (%s). Co se zapsalo, bylo odebr\u00e1no; hra je jako p\u0159edt\u00edm.\n+  List: Seznam\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : Ud\u011blat z %s znovu ISO? ZSO z\u016fstane, dokud se ISO znovu nep\u0159e\u010dte a nezkontroluje, pak se RIPPS2 zept\u00e1, ne\u017e ZSO odebere. Disk pot\u0159ebuje b\u011bhem pr\u00e1ce m\u00edsto pro oba; nechte konzoli zapnutou, dokud neskon\u010d\u00ed. Jej\u00ed ulo\u017een\u00e9 pozice, nastaven\u00ed, obr\u00e1zky a cheaty z\u016fst\u00e1vaj\u00ed s n\u00ed.\n+  Stop? What it has written so far is removed; the game stays as it was.: Zastavit? Co je dosud zaps\u00e1no, se odebere; hra z\u016fstane jako p\u0159edt\u00edm.\n+  THEMES: MOTIVY\n+  There is not enough memory for this just now.: Na tohle te\u010f nen\u00ed dost pam\u011bti.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: Tento obraz RIPPS2 nedok\u00e1\u017ee komprimovat ani rozbalit (jeho velikost nen\u00ed v cel\u00fdch bloc\u00edch po 2 KB).\n+  UNCOMPRESS TO ISO: ROZBALIT DO ISO\n+  UNCOMPRESSED %s: ROZBALENO %s\n+  UNCOMPRESSING: ROZBALOV\u00c1N\u00cd\n+  Uncompress to ISO: Rozbalit do ISO\n+  Under a minute, then checked: M\u00e9n\u011b ne\u017e minuta, pak kontrola\n+  COPYING: KOP\u00cdROV\u00c1N\u00cd\n+  SHOW AGAIN, DELETE OR COMPRESS: ZNOVU UK\u00c1ZAT, SMAZAT NEBO KOMPRIMOVAT\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: ZNOVU UK\u00c1ZAT, SMAZAT NEBO ROZBALIT\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': '%s pot\u0159ebuje b\u011bhem pr\u00e1ce asi %s voln\u00e9ho m\u00edsta na sv\u00e9m disku (origin\u00e1l z\u016fst\u00e1v\u00e1 a\u017e do konce), a disk m\u00e1 voln\u00fdch %s.'\n+  Hide and Compress: Skr\u00fdt a komprimovat\n+  'COMPRESSED AND HIDDEN: %s': 'KOMPRIMOV\u00c1NO A SKRYTO: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': 'Na tento rip nen\u00ed m\u00edsto: pot\u0159ebuje asi %s a disk m\u00e1 voln\u00fdch %s.'\n+  OVER 4 GB FOR FAT32: P\u0158ES 4 GB PRO FAT32\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: Tento disk je %s, kter\u00fd neudr\u017e\u00ed soubor o velikosti 4 GB a v\u00edce, a nov\u00fd obraz by m\u011bl %s.\n+  '%s, %s clusters, %s free of %s': '%s, clustery %s, voln\u00fdch %s z %s'\n+  Delete This Sweep: Smazat toto st\u0159\u00edd\u00e1n\u00ed\n+  Delete %s? RIPPS2's own sweeps stay as they are.: Smazat %s? St\u0159\u00edd\u00e1n\u00ed samotn\u00e9ho RIPPS2 z\u016fstanou, jak jsou.\n+  Sweep: St\u0159\u00edd\u00e1n\u00ed\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: Odznaky ozna\u010duj\u00ed hry s \u00fasp\u011bchy ve va\u0161ich seznamech a kolik jste jich odemkli. Pot\u0159ebuj\u00ed zapnutou TELEMETRII.\n labels:\n   HINT_NEUTRINO_AUTO_ELF: P\u0159ed\u00e1 zav\u00e1d\u011bc\u00ed ELF hry Neutrinu, aby vyhledalo nastaven\u00ed pro dan\u00fd titul. Ve v\u00fdchoz\u00edm stavu zapnuto; ru\u010dn\u011b nastaven\u00e1 volba Boot ELF m\u00e1 p\u0159ednost.\n   HINT_POPSTARTER_PATH: Kde je POPSTARTER.ELF. Kdy\u017e z\u016fstane pr\u00e1zdn\u00e9, RIPPS2 dopln\u00ed ten, kter\u00fd najde na disku; p\u0159epi\u0161te, chcete-li jin\u00fd, nap\u0159. usb:/POPS/POPSTARTER.ELF\n@@ -1200,7 +1367,7 @@ labels:\n   SAVE_ICONS: 3D ikony ulo\u017een\u00fdch pozic\n   HINT_SAVE_ICONS: Uk\u00e1\u017ee ot\u00e1\u010dej\u00edc\u00ed se ikonu ulo\u017een\u00e9 pozice vybran\u00e9 hry tam, kde pro ni m\u00e1 motiv m\u00edsto. Nejd\u0159\u00edv \u010dte vlastn\u00ed VMC hry, pak pam\u011b\u0165ov\u00e9 karty.\n   NETBOOT_RESTART_NOW: S\u00ed\u0165ov\u00fd protokol se zm\u011bn\u00ed po restartu. Ulo\u017eit nastaven\u00ed a restartovat OPL te\u010f?\n-  HINT_NET_PROTOCOL: 'Kterou s\u00ed\u0165ovou slu\u017ebu RiptOPL pou\u017e\u00edv\u00e1: sd\u00edlen\u00ed SMB, UDPFS, UDPBD nebo HTTP. Stejn\u00e9 nastaven\u00ed jako ve Zdroj\u00edch her. P\u0159epnut\u00ed z ji\u017e b\u011b\u017e\u00edc\u00ed vy\u017eaduje restart.'\n+  HINT_NET_PROTOCOL: 'Kterou s\u00ed\u0165ovou slu\u017ebu RiptOPL pou\u017e\u00edv\u00e1: sd\u00edlen\u00ed SMB, UDPFS, UDPBD nebo HTTP. P\u0159epnut\u00ed z ji\u017e b\u011b\u017e\u00edc\u00ed vy\u017eaduje restart.'\n   HINT_UDPFS_ACCESS: Jen UDPFS. Soubory \u010dte jednotliv\u00e1 ISO ze slo\u017eky her na serveru; IMG \u010dte obraz disku jako disk. SMB a HTTP v\u017edy pou\u017e\u00edvaj\u00ed Soubory, UDPBD v\u017edy IMG.\n   HINT_SMB_VERSION: Kterou verz\u00ed SMB mluvit se sd\u00edlenou slo\u017ekou na PC.\n   HINT_NET_ADVANCED: Odemkne \u00fapravy re\u017eimu spojen\u00ed Ethernet a portu SMB.\n@@ -1399,3 +1566,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: Jak se sn\u00edmky \u010dekaj\u00edc\u00ed na filmu ka\u017ed\u00e9 hry m\u011bn\u00ed v PNG.\n   RIPPS2_SETTINGS_HOME: Ukl\u00e1dat nastaven\u00ed na\n   HINT_RIPPS2_SETTINGS_HOME: 'Kam RIPPS2 ukl\u00e1d\u00e1 nastaven\u00ed: do vlastn\u00edho ulo\u017een\u00ed na pam\u011b\u0165ov\u00e9 kart\u011b, do slo\u017eky RIPPS2 na disku (intern\u00edm HDD, USB, MX4SIO, iLink nebo MMCE) nebo do slo\u017eky, kterou zad\u00e1\u0161.'\n+  RIPPS2_LAUNCHERS: Spou\u0161t\u011b\u010de\n+  RIPPS2_LOOK_NOW: Hledat te\u010f\n+  HINT_RIPPS2_LOOK_NOW: RIPPS2 hled\u00e1 na spu\u0161t\u011bn\u00fdch disc\u00edch POPSTARTER, soubory POPS, Ember, BIOS Emberu a Neutrino a pamatuje si, kde ka\u017ed\u00fd je. Ka\u017ed\u00e9 spu\u0161t\u011bn\u00ed je \u010dte odtamtud.\n+  RIPPS2_HIDE_GAME: Skr\u00fdt hru\n+  RIPPS2_HIDDEN_GAMES: Skryt\u00e9 hry\n+  HINT_RIPPS2_HIDDEN_GAMES: Hry skryt\u00e9 p\u0159es Skr\u00fdt hru, na spu\u0161t\u011bn\u00fdch disc\u00edch. Uka\u017ete n\u011bkterou znovu, nebo ji sma\u017ete. Ka\u017ed\u00fd disk m\u00e1 vlastn\u00ed seznam.\n+  HINT_RIPPS2_BDM_DEVICES: 'Disky, ze kter\u00fdch BDM \u010dte hry: USB, MX4SIO, PC Card CF, disk DVR v DESR a iLink. Vnit\u0159n\u00ed HDD se \u0159\u00edd\u00ed re\u017eimem spu\u0161t\u011bn\u00ed HDD.'\n+  HINT_RIPPS2_SCREEN: 'Obraz nab\u00eddky: re\u017eim obrazu, \u0161iroko\u00fahl\u00fd obraz, jeho poloha na televizoru (H-POS, V-POS) a overscan.'\n+  HINT_RIPPS2_LAYOUT: Kde jsou kategorie a jak str\u00e1nka p\u0159ich\u00e1z\u00ed, pak obaly RIPFLOW a m\u0159\u00ed\u017eka RIPGRID.\n+  HINT_RIPPS2_COLORS_TEXT: Tlou\u0161\u0165ka textu, barevn\u00fd motiv, st\u0159\u00edd\u00e1n\u00ed barev a ka\u017ed\u00e1 barva.\n+  HINT_RIPPS2_TEXT_WEIGHT: 'Jak tu\u010dn\u00fd je vlastn\u00ed text RIPPS2: tipy v nastaven\u00ed, STAV a O aplikaci. B\u011b\u017en\u00e9 je jako d\u0159\u00edv; St\u0159edn\u00ed a Tu\u010dn\u00e9 se l\u00e9pe \u010dtou na m\u011bkk\u00e9m obraze.'\n+  HINT_RIPPS2_HDD_START: 'Vnit\u0159n\u00ed HDD, APA nebo exFAT: RIPPS2 ho p\u0159e\u010dte, a\u0165 je jak\u00fdkoli, a uk\u00e1\u017ee ho jednou jako hry na HDD. Vypnuto ho nech\u00e1 b\u00fdt.'\n+  HINT_RIPPS2_THEME_COLOR: Vlastn\u00ed barva motivu. Zvolte Vlastn\u00ed jako barevn\u00fd motiv a vyberte ji sami, jako barvy textu.\n+  HINT_RIPPS2_SWEEP: P\u011bt barev, kter\u00fdmi motiv pomalu a plynule p\u0159ech\u00e1z\u00ed. Duha, st\u0159\u00edd\u00e1n\u00ed z ka\u017ed\u00e9ho barevn\u00e9ho motivu nebo va\u0161e vlastn\u00ed. Vypnuto nech\u00e1 motiv v klidu.\n+  HINT_RIPPS2_SWEEP_COLORS: P\u011bt barev st\u0159\u00edd\u00e1n\u00ed a jeho rychlost. Zm\u011b\u0148te jedno z RIPPS2 a ulo\u017e\u00ed se jako va\u0161e, pod n\u00e1zvem, kter\u00fd mu d\u00e1te.\n+  HINT_RIPPS2_SWEEP_NAME: Jak se va\u0161e st\u0159\u00edd\u00e1n\u00ed jmenuje v seznamu St\u0159\u00edd\u00e1n\u00ed barev.\n+  HINT_RIPPS2_SWEEP_COLOR: Jedna z p\u011bti barev, kter\u00fdmi st\u0159\u00edd\u00e1n\u00ed proch\u00e1z\u00ed, popo\u0159ad\u011b.\n+  HINT_RIPPS2_SWEEP_SPEED: 'Jak dlouho ka\u017ed\u00e1 barva trv\u00e1, ne\u017e p\u0159ijde dal\u0161\u00ed. I \u017div\u011b z\u016fst\u00e1v\u00e1 plynul\u00e9: barvy nikdy neblikaj\u00ed.'\n+  RIPPS2_HIDE_COMPRESS: Skr\u00fdt hru a komprese\n+  HINT_RIPPS2_SELECT_HOLD: 'SELECT podr\u017een\u00fd 4.2 s na seznamu her. Seznam: motivy v panelu, vyberte jeden. Dokola: dal\u0161\u00ed motiv. Vypnuto: nic (stisk st\u00e1le obnovuje).'"
        },
        {
          "sha": "6a1df81fcf82a095c5ac07ee9795120e08ee30c2",
          "filename": "elf/lang/Danish.yml",
          "status": "modified",
          "additions": 208,
          "deletions": 22,
          "changes": 230,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FDanish.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FDanish.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FDanish.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: Danish\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTER BEKR\u00c6FTER, ESC ANNULLERER. ENHVER KNAP VISER TASTERNE.\n   USB KEYBOARD: USB-TASTATUR\n-  '      DESR DVR ISOs (experimental)': '      DESR DVR-ISO''er (eksperimentelt)'\n-  '      Internal HDD (exFAT)': '      Intern HDD (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (eksperimentelt)'\n-  '      Protocol': '      Protokol'\n   Access: Adgang\n   Applications Page Start Mode: Starttilstand for appsiden\n   Art Delay: Billedforsinkelse\n@@ -22,7 +18,6 @@ strings:\n   Fifth: Femte\n   First: F\u00f8rste\n   Fourth: Fjerde\n-  HDD (APA) Start Mode: Starttilstand for HDD (APA)\n   HDD Cache: HDD-cache\n   HDD Spindown: HDD-dvale\n   MMCE Settings: MMCE-indstillinger\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: Dette er et ESR-patchet PS2-spil. L\u00e6g ESR (esr*.elf) i APPS, BOOT eller ESR p\u00e5 et memorykort eller en USB-n\u00f8gle for at starte det.\n   '%s, BUILD %d': '%s, BUILD %d'\n   '... and the anonymous ...': '... og de anonyme ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': '1.8.0 medf\u00f8lger u\u00e6ndret (AFL-3.0): github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: En adskillelse og genopbygning af RiptOPL, forken af Open PS2 Loader,\n   ABOUT: OM\n   'ACHIEVEMENTS: SPIRIT OF RA': 'PR\u00c6STATIONER: SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): AVI-video i Hukommelsesfiler, RIPPS2's fork (AFL 2.0)\n   Apollo Save Tool by bucanero: Apollo Save Tool af bucanero\n   BACK: TILBAGE\n+  Back (keeps both): Tilbage (beholder begge)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: Dette MMCE-kort kan ikke omd\u00f8be en fil, hvilket komprimeringen har brug for i sit sidste trin. Opdater dets sd2psXtd-firmware til 1.4.0 eller nyere (sd2psxtd.github.io/download), og pr\u00f8v igen. Intet blev \u00e6ndret.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): BIBLE er PS2BBL 1.2.0, El Isras signerede builds (kort og HDD), u\u00e6ndrede (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: BIOS-lyde > Rip afspiller dem, som SPU2 g\u00f8r; der medf\u00f8lger intet BIOS\n   BOOT LOCK AND BIBLE: BOOT LOCK OG BIBLE\n   BOOT LOCK edits its config too: BOOT LOCK redigerer ogs\u00e5 dets konfiguration\n   BUILD DETAILS: BUILDDETALJER\n-  BUILT ON RIPTOPL: BYGGET P\u00c5 RIPTOPL\n   BearSSL by Thomas Pornin: BearSSL af Thomas Pornin\n   Built with Claude (Anthropic): Lavet med Claude (Anthropic)\n   CD PLAYER: CD-AFSPILLER\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek og Sleek Bold, tegnet til RIPPS2\n   RetroAchievements on the console by hacan359 (yoba): RetroAchievements p\u00e5 konsollen af hacan359 (yoba)\n   RiptOPL by NathanNeurotic: RiptOPL af NathanNeurotic\n-  Roboto Condensed by Google (Apache-2.0): Roboto Condensed af Google (Apache-2.0)\n   SOUNDS: LYDE\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: SP193's FMCB-installationsprogram og pcm720's OSDMenu, fulgt trin for trin\n   SUPPORT: SUPPORT\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: wLaunchELF R3Z af NathanNeurotic\n   wLaunchELF by the ps2homebrew team: wLaunchELF af ps2homebrew-holdet\n   what RIPPS2's card image writes are checked against: det, RIPPS2's skrivning af kortbilleder kontrolleres mod\n-  with its own look, launchers, file browser and save tools.: med sit eget udseende, startere, filbrowser og gemmev\u00e6rkt\u00f8jer.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: xeRAbora af hacan359, rcheevos af RetroAchievements\n   your console's own, ripped from its BIOS: din konsols egne lyde, rippet fra dens BIOS\n   ' (no DNS answer)': ' (intet DNS-svar)'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO: skriver %.60s'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: Neutrino kunne ikke skrives til %s. Tjek den ledige plads, og at den ikke er skrivebeskyttet, og pr\u00f8v igen.\n   THE GAME'S DRIVE  (%s/neutrino): SPILLETS DREV  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : Dette spil starter gennem Neutrino, og der er ingen Neutrino, hvor RIPPS2 leder (Indstillinger, spillets drev, memorykortene). RIPPS2 har Neutrino 1.8.0 af rickgaiser (AFL-3.0) med. Installer den nu? Bagefter v\u00e6lger du hvor.\n   '%d / %d FILES': '%d / %d FILER'\n   '%d FOLDERS AND CONTENTS': '%d MAPPER OG INDHOLD'\n   '%d FOLDERS, %d FOUND': '%d MAPPER, %d FUNDET'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d af %d stadig l\u00e5st'\n   '%s   on %s': '%s   p\u00e5 %s'\n   BADGES: M\u00c6RKER\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: M\u00e6rker virker kun med TELEMETRI sl\u00e5et til og xeRAbora k\u00f8rende p\u00e5 din pc; indtil da koster de ingenting.\n   CHECK THE DISC: TJEK DISKEN\n   From this game's last CHECK GAME SUPPORT.: Fra spillets seneste TJEK SPILUNDERST\u00d8TTELSE.\n   HOW IT WORKS: S\u00c5DAN VIRKER DET\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: Der er en PS2BBL med RIPPS2's l\u00e5s p\u00e5 denne konsol.\n   AUDIO folder: AUDIO-mappen\n   DRIVES: DREV\n-  INSTALL RIPPS2'S COPY: INSTALLER RIPPS2'S KOPI\n-  'Installed: ': 'Installeret: '\n   MANAGE: ADMINISTRER\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: ADMINISTRER viser, hvad hvert memorykort starter, og installerer eller reparerer BIBLE.\n   MEMORY CARD 0  (about 600 KB): MEMORYKORT 0  (cirka 600 KB)\n   MEMORY CARD 1  (about 600 KB): MEMORYKORT 1  (cirka 600 KB)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO: dens driver ville ikke indl\u00e6ses (kode %d).'\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO: driveren er indl\u00e6st, men intet SD-kort svarede (stikket mc1).'\n   NEUTRINO SETTINGS: NEUTRINO-INDSTILLINGER\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': 'Ingen endnu: RIPPS2 har ingen egne lyde. Rip denne konsols, eller lav dem i RIPPS2 Audio Studio.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: Ikke installeret. Et spil, der har brug for den, tilbyder RIPPS2's kopi, eller installer den her.\n   POPSTARTER NETWORK: POPSTARTER-NETV\u00c6RK\n   POPSTARTER and Ember: POPSTARTER og Ember\n   PS1 SETTINGS: PS1-INDSTILLINGER\n   PS2BBL 1.2.0 for RIPPS2: PS2BBL 1.2.0 til RIPPS2\n   RIP THIS CONSOLE'S SOUNDS: RIP DENNE KONSOLS LYDE\n-  RIPPS2's homebrew: RIPPS2's homebrew\n   STATUS: STATUS\n-  'STATUS: looking for RIPPS2''s homebrew': 'STATUS: leder efter RIPPS2''s homebrew'\n   a drive of unknown kind: et drev af ukendt slags\n-  a drive or a memory card: et drev eller et memorykort\n   boot loader, card by card: opstartsindl\u00e6ser, kort for kort\n   from its BIOS, into AUDIO: fra dens BIOS, til AUDIO\n   internal HDD (exFAT): intern HDD (exFAT)\n@@ -975,10 +958,193 @@ strings:\n   Sidebar: Sidebj\u00e6lke\n   your own copy, found on your drives and memory cards: din egen kopi, fundet p\u00e5 dine drev og memory cards\n   'your own, found and launched: github.com/Gageformer/Ember/releases': 'din egen, fundet og startet: github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': 'Ikke fundet p\u00e5 mass0: (%s). L\u00e6g ember.elf og din bios.bin i dens EMBER-mappe.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: Ikke fundet. L\u00e6g din egen POPSTARTER.ELF i et drevs POPS-mappe.\n   Launch Disc: Start disk\n   Memory Files: Hukommelsesfiler\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: RIPPS2 fandt %d af de 5. Hver enkelt huskes, og hver start l\u00e6ser den derfra.\n+  not found on the started drives: ikke fundet p\u00e5 de startede drev\n+  not looked for yet: ikke s\u00f8gt efter endnu\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s svarede ikke, s\u00e5 %s kan ikke starte. Er det tilsluttet?'\n+  '%s is not on %s any more, so it has left All Games.': '%s er ikke l\u00e6ngere p\u00e5 %s, s\u00e5 det har forladt Alle spil.'\n+  STARTING %s: STARTER %s\n+  the SMB share: SMB-delingen\n+  the drive: drevet\n+  the internal HDD (APA): den interne HDD (APA)\n+  the internal HDD (exFAT): den interne HDD (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: BYGGET P\u00c5 RIPTOPL, MED NEUTRINO\n+  'Special thanks to all the YouTube love as well:': 'En s\u00e6rlig tak ogs\u00e5 til al k\u00e6rligheden p\u00e5 YouTube:'\n+  and its own look, launchers, file browser and save tools.: og med sit eget udseende, startere, filbrowser og gemmev\u00e6rkt\u00f8jer.\n+  glad to help however you reach me.: glad for at hj\u00e6lpe, uanset hvordan I fanger mig.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): den anden spilkerne, 1.8.0 medbragt u\u00e6ndret (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: med rickgaisers Neutrino indeni som sin anden spilkerne,\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : 'Dette spil starter gennem Neutrino, og RIPPS2 fandt ingen: ikke ved stien i Indstillinger, ikke p\u00e5 spillets drev, ikke p\u00e5 memorykortene og ikke p\u00e5 noget startet drev. RIPPS2 har Neutrino 1.8.0 af rickgaiser med (AFL-3.0): installer den \u00e9n gang, hvor du vil, og hvert Neutrino-spil bruger den derfra.'\n+  STARTING THE DRIVES: STARTER DREVENE\n+  '%d games, on the started drives': '%d spil, p\u00e5 de startede drev'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s: NOGLE FILER KUNNE IKKE SLETTES'\n+  '%u partitions, all in order': '%u partitioner, alle i orden'\n+  ', %d art images': ', %d billeder'\n+  ', and ': ', og '\n+  ', its cheats': ', dets snydekoder'\n+  ', its settings': ', dets indstillinger'\n+  1 game, on the started drives: 1 spil, p\u00e5 de startede drev\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: Et PS1-spil her slettes i Hukommelsesfiler, hvor det er en fil for sig.\n+  A network catalogue's games are deleted on the PC that serves them.: Et netv\u00e6rkskatalogs spil slettes p\u00e5 den pc, der deler dem.\n+  DELETE FOR GOOD: SLET FOR ALTID\n+  DELETE IT: SLET DET\n+  'DELETED %s: %s FREED': '%s SLETTET: %s FRIGJORT'\n+  'Delete %s from %s? Goes: %s.': 'Slet %s fra %s? Forsvinder: %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': 'Slet %s fra %s? Forsvinder: %s. Bliver: %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: Slet ogs\u00e5 dets gemte spil og RIPSHOT-billeder? Nej beholder dem.\n+  HIDDEN GAMES: SKJULTE SPIL\n+  'HIDDEN: %s': 'SKJULT: %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: Skjul %s? Det forlader alle lister, ogs\u00e5 Alle spil og Favoritter. Indstillinger > Spilkilder > Skjulte spil viser det igen.\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: Sidste tjek. %s slettes for altid, og intet kan bringe det tilbage. Tryk p\u00e5 START for at slette det.\n+  None. Hide Game, in a game's options, puts one here.: Ingen. Skjul spil, i et spils indstillinger, l\u00e6gger et her.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : 'Intet blev slettet: dette drevs partitionstabel passer ikke til denne disk (%s). Den kan v\u00e6re skrevet fra et image af en disk i en anden st\u00f8rrelse. Reparer den f\u00f8rst p\u00e5 en pc.'\n+  RIPPS2 cannot delete a game from this page.: RIPPS2 kan ikke slette et spil fra denne side.\n+  RIPPS2 could not find this game's files on its drive.: RIPPS2 kunne ikke finde dette spils filer p\u00e5 dets drev.\n+  Reading the hidden lists: L\u00e6ser de skjulte lister\n+  SHOW AGAIN OR DELETE: VIS IGEN ELLER SLET\n+  SHOW IT AGAIN: VIS DET IGEN\n+  That game is not on its drive any more.: Det spil er ikke l\u00e6ngere p\u00e5 sit drev.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: Den skjulte liste kunne ikke skrives til spillets drev. Er det skrivebeskyttet, eller er Skriveoperationer sl\u00e5et fra?\n+  The partition could not be removed (%d). Nothing was deleted.: Partitionen kunne ikke fjernes (%d). Intet blev slettet.\n+  This drive cannot keep a hidden list (a network catalogue).: Dette drev kan ikke have en skjult liste (et netv\u00e6rkskatalog).\n+  This game's drive has not answered yet. Wait for it, then try again.: Dette spils drev har ikke svaret endnu. Vent p\u00e5 det, og pr\u00f8v igen.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: Dette spils partition er en, som RIPPS2 ikke fjerner (en systempartition eller den med dets indstillinger).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': 'en partition er st\u00f8rre, end denne disk tillader: tabellen blev lavet til en disk i en anden st\u00f8rrelse'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': 'en partition g\u00e5r ud over slutningen af denne disk (sektor %u af %u): tabellen blev lavet til en st\u00f8rre disk'\n+  its RIPSHOT pictures: dets RIPSHOT-billeder\n+  its drive: dets drev\n+  its saves: dets gemte spil\n+  sector %u holds no partition header: sektor %u har ingen partitionshoved\n+  the chain does not close at __mbr: k\u00e6den lukker ikke ved __mbr\n+  the chain skips from sector %u: k\u00e6den springer fra sektor %u\n+  the chain's links disagree at sector %u: k\u00e6dens led er uenige ved sektor %u\n+  the drive's size could not be read: drevets st\u00f8rrelse kunne ikke l\u00e6ses\n+  the game (%s): spillet (%s)\n+  the game's folder and everything in it (%s): spillets mappe og alt i den (%s)\n+  the header at sector %u could not be read: hovedet ved sektor %u kunne ikke l\u00e6ses\n+  the header at sector %u fails its checksum: hovedet ved sektor %u fejler sin kontrolsum\n+  the partition at sector %u is not where or as large as APA puts one: partitionen ved sektor %u ligger ikke hvor, eller er ikke s\u00e5 stor som, APA l\u00e6gger en\n+  the settings and art another copy of it uses: de indstillinger og billeder, som en anden kopi af det bruger\n+  '%d FOLDERS': '%d MAPPER'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': '%s kunne ikke kopieres til %s. Er drevet fuldt eller skrivebeskyttet?'\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s er ikke der, hvor RIPPS2 kigger. S\u00f8g efter det p\u00e5 de drev, RIPPS2 har startet? Hvor det findes, huskes til hver start fremover.'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Ember er ikke i %s, ved siden af spillene, og det skal det v\u00e6re. RIPPS2 fandt det p\u00e5 %s: START kopierer det derhen (%d KB) og starter spillet.'\n+  Ember's BIOS: Embers BIOS\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Embers BIOS (bios.bin) er ikke i %s, ved siden af Ember. RIPPS2 fandt en p\u00e5 %s: START kopierer den derhen (%d KB) og starter spillet.'\n+  LOOKING FOR YOUR LAUNCHERS: LEDER EFTER DINE STARTERE\n+  The POPS files: POPS-filerne\n+  '%s  MMCE card': '%s  MMCE-kort'\n+  INSTALL RIPPS2'S NEUTRINO: INSTALLER RIPPS2'S NEUTRINO\n+  'Internal HDD (APA): %s free of %s': 'Intern HDD (APA): %s ledigt af %s'\n+  LAUNCHERS: STARTERE\n+  LOOK FOR THEM: FIND DEM\n+  'Memory card %d: %d KB free': 'Memorykort %d: %d KB ledigt'\n+  POPS files: POPS-filer\n+  START EVERY DRIVE: START ALLE DREV\n+  'STATUS: looking at the drives and the homebrew': 'STATUS: kigger p\u00e5 drevene og homebrew'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): Start alle drev f\u00f8rst, s\u00e5 s\u00f8gningen n\u00e5r dem alle? (Nej s\u00f8ger p\u00e5 de drev, der allerede er startet.)\n+  USB, HDD, MMCE and the rest: USB, HDD, MMCE og resten\n+  every started drive: hvert startet drev\n+  found where you keep them: fundet, hvor du har dem\n+  not found yet: ikke fundet endnu\n+  not on a started drive: ikke p\u00e5 et startet drev\n+  only if you have none: kun hvis du ikke har nogen\n+  starts every drive first: starter alle drev f\u00f8rst\n+  Drives, launchers and homebrew: Drev, startere og homebrew\n+  BDM Devices: BDM-enheder\n+  Color Sweep: Farveskift\n+  Colors and Text: Farver og tekst\n+  HDD POPS partitions: POPS-partitioner p\u00e5 HDD\n+  Missing Covers: Manglende covers\n+  Name: Navn\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: Der er endnu ikke fundet en SMB-ops\u00e6tning til POPSTARTER. Udfyld r\u00e6kkerne nedenfor.\n+  POPSTARTER Network: POPSTARTER-netv\u00e6rk\n+  POPSTARTER SMB Network: POPSTARTER SMB-netv\u00e6rk\n+  POPSTARTER UDPFS Network: POPSTARTER UDPFS-netv\u00e6rk\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: RIPPS2 tilf\u00f8jer selv drevet, ISO, VMC'er og videotilstand.\n+  Screen: Sk\u00e6rm\n+  Speed: Hastighed\n+  Sweep Colors: Skiftets farver\n+  Text Weight: Tekstv\u00e6gt\n+  Theme Color: Hovedfarve\n+  Audio: Lyd\n+  Bold: Fed\n+  Controllers: Controllere\n+  Lively: Livlig\n+  Medium: Mellem\n+  PS Emulation: PS-emulering\n+  Regular: Normal\n+  Slow: Langsom\n+  A MOMENT: ET \u00d8JEBLIK\n+  A TASK IN THE BACKGROUND: EN OPGAVE I BAGGRUNDEN\n+  ABOUT %d S: CIRKA %d S\n+  ANY MOMENT: N\u00c5R SOM HELST\n+  READING THE GAME LIST OF %s: L\u00c6SER SPILLISTEN P\u00c5 %s\n+  SAVING A LIST: GEMMER EN LISTE\n+  START NOW: START NU\n+  STARTING: STARTER\n+  STARTS NEXT: STARTER N\u00c6STE\n+  STOPPING IT SAFELY: STOPPER SIKKERT\n+  THE LAST OF THE QUEUE: DET SIDSTE I K\u00d8EN\n+  WHEN IT FINISHES: N\u00c5R DET ER F\u00c6RDIGT\n+  What each card starts; install or repair BIBLE.: Hvad hvert kort starter; installer eller reparer BIBLE.\n+  Rainbow: Regnbue\n+  Custom: Brugerdefineret\n+  DESR DVR ISOs (experimental): DESR DVR-ISO'er (eksperimentelt)\n+  PC CARD CF (experimental): PC CARD CF (eksperimentelt)\n+  HDD Start Mode: Starttilstand for HDD\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): Roboto Condensed af The Roboto Project Authors (SIL OFL 1.1)\n+  '%s on %s': '%s p\u00e5 %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: %s blev til %s (%d%%), hvert stykke tjekket. Fjern ISO''en og behold ZSO''en?'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: %s blev til %s igen, hvert stykke tjekket. Fjern ZSO''en og behold ISO''en?'\n+  A ZSO of this game is already beside it. Remove one of the two first.: Der ligger allerede en ZSO af dette spil ved siden af. Fjern en af de to f\u00f8rst.\n+  About %d min, then checked: Ca. %d min, derefter tjekket\n+  An ISO of this game is already beside it. Remove one of the two first.: Der ligger allerede en ISO af dette spil ved siden af. Fjern en af de to f\u00f8rst.\n+  CHECKING EVERY PIECE: TJEKKER HVERT STYKKE\n+  COMPRESS TO ZSO: KOMPRIMER TIL ZSO\n+  'COMPRESSED %s: %s FREED': 'KOMPRIMERET %s: %s FRIGJORT'\n+  COMPRESSING 1 OF 2: KOMPRIMERER 1 AF 2\n+  COMPRESSING 2 OF 2: KOMPRIMERER 2 AF 2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : Komprimer %s? ISO'en bliver, indtil ZSO'en er l\u00e6st tilbage og tjekket, derefter sp\u00f8rger RIPPS2, f\u00f8r den fjerner ISO'en. Drevet skal have plads til begge, mens det arbejder; lad konsollen v\u00e6re t\u00e6ndt, til det er f\u00e6rdigt. En ZSO starter kun med OPL-kernen (ikke Neutrino); dens gemte spil, indstillinger, billeder og snydekoder bliver hos den, og den kan blive til en ISO igen n\u00e5r som helst.\n+  Compress to ZSO: Komprimer til ZSO\n+  Cycle: Skift\n+  'Experimental: OPL core only (not Neutrino)': 'Eksperimentel: kun OPL-kernen (ikke Neutrino)'\n+  Hold SELECT: Hold SELECT nede\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: Det kunne ikke g\u00f8res f\u00e6rdigt (%s). Det skrevne blev fjernet; spillet er som f\u00f8r.\n+  List: Liste\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : G\u00f8r %s til en ISO igen? ZSO'en bliver, indtil ISO'en er l\u00e6st tilbage og tjekket, derefter sp\u00f8rger RIPPS2, f\u00f8r den fjerner ZSO'en. Drevet skal have plads til begge, mens det arbejder; lad konsollen v\u00e6re t\u00e6ndt, til det er f\u00e6rdigt. Dens gemte spil, indstillinger, billeder og snydekoder bliver hos den.\n+  Stop? What it has written so far is removed; the game stays as it was.: Stop? Det, der er skrevet indtil nu, fjernes; spillet forbliver som f\u00f8r.\n+  THEMES: TEMAER\n+  There is not enough memory for this just now.: Der er ikke nok hukommelse til dette lige nu.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: Dette image kan RIPPS2 ikke komprimere eller udpakke (st\u00f8rrelsen er ikke hele blokke p\u00e5 2 KB).\n+  UNCOMPRESS TO ISO: UDPAK TIL ISO\n+  UNCOMPRESSED %s: UDPAKKET %s\n+  UNCOMPRESSING: UDPAKKER\n+  Uncompress to ISO: Udpak til ISO\n+  Under a minute, then checked: Under et minut, derefter tjekket\n+  COPYING: KOPIERER\n+  SHOW AGAIN, DELETE OR COMPRESS: VIS IGEN, SLET ELLER KOMPRIMER\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: VIS IGEN, SLET ELLER UDPAK\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': '%s skal bruge omkring %s fri plads p\u00e5 sit drev, mens det arbejder (originalen bliver til det sidste), og drevet har %s fri.'\n+  Hide and Compress: Skjul og komprimer\n+  'COMPRESSED AND HIDDEN: %s': 'KOMPRIMERET OG SKJULT: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': 'Der er ikke plads til denne rip: den skal bruge omkring %s, og drevet har %s fri.'\n+  OVER 4 GB FOR FAT32: OVER 4 GB TIL FAT32\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: Dette drev er %s, som ikke kan rumme en fil p\u00e5 4 GB eller mere, og det nye image ville v\u00e6re %s.\n+  '%s, %s clusters, %s free of %s': '%s, %s klynger, %s fri af %s'\n+  Delete This Sweep: Slet dette farveskift\n+  Delete %s? RIPPS2's own sweeps stay as they are.: Slet %s? RIPPS2's egne farveskift bliver, som de er.\n+  Sweep: Farveskift\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: M\u00e6rker viser spillene med achievements i dine lister, og hvor mange du har l\u00e5st op. De kr\u00e6ver TELEMETRI sl\u00e5et til.\n labels:\n   OK: OK\n   MEDIUM: Mellem\n@@ -1217,7 +1383,7 @@ labels:\n   SAVE_ICONS: 3D-gemmeikoner\n   HINT_SAVE_ICONS: Viser det valgte spils gemmeikon, snurrende, hvor temaet har plads til det. L\u00e6ser spillets egen VMC f\u00f8rst, derefter memorykortene.\n   NETBOOT_RESTART_NOW: Netv\u00e6rksprotokollen \u00e6ndres efter en genstart. Gem indstillingerne, og genstart OPL nu?\n-  HINT_NET_PROTOCOL: 'Hvilken netv\u00e6rkstjeneste RiptOPL bruger: en SMB-deling, UDPFS, UDPBD eller HTTP. Samme indstilling som i Spilkilder. At skifte v\u00e6k fra en, der allerede k\u00f8rer, kr\u00e6ver en genstart.'\n+  HINT_NET_PROTOCOL: 'Hvilken netv\u00e6rkstjeneste RiptOPL bruger: en SMB-deling, UDPFS, UDPBD eller HTTP. At skifte v\u00e6k fra en, der allerede k\u00f8rer, kr\u00e6ver en genstart.'\n   HINT_UDPFS_ACCESS: Kun UDPFS. Filer l\u00e6ser l\u00f8se ISO'er fra serverens spilmappe; IMG l\u00e6ser et diskbillede som et drev. SMB og HTTP bruger altid Filer, UDPBD altid IMG.\n   HINT_SMB_VERSION: Hvilken SMB-version der bruges over for pc-delingen.\n   HINT_NET_ADVANCED: L\u00e5ser Ethernet-forbindelsestilstand og SMB-porten op for redigering.\n@@ -1416,3 +1582,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: Hvordan billederne, der venter p\u00e5 hvert spils rulle, bliver til PNG'er.\n   RIPPS2_SETTINGS_HOME: Gem indstillinger p\u00e5\n   HINT_RIPPS2_SETTINGS_HOME: 'Hvor RIPPS2 gemmer sine indstillinger: sin egen save p\u00e5 et memorykort, en RIPPS2-mappe p\u00e5 et drev (den interne HDD, USB, MX4SIO, iLink eller MMCE) eller en mappe, du skriver.'\n+  RIPPS2_LAUNCHERS: Startere\n+  RIPPS2_LOOK_NOW: S\u00f8g nu\n+  HINT_RIPPS2_LOOK_NOW: RIPPS2 s\u00f8ger p\u00e5 de startede drev efter POPSTARTER, POPS-filerne, Ember, Embers BIOS og Neutrino og husker, hvor hver enkelt er. Hver start l\u00e6ser dem derfra.\n+  RIPPS2_HIDE_GAME: Skjul spil\n+  RIPPS2_HIDDEN_GAMES: Skjulte spil\n+  HINT_RIPPS2_HIDDEN_GAMES: Spillene skjult med Skjul spil, p\u00e5 de startede drev. Vis et igen, eller slet det. Hvert drev har sin egen liste.\n+  HINT_RIPPS2_BDM_DEVICES: 'De drev BDM l\u00e6ser spil fra: USB, MX4SIO, et PC Card CF, en DESR''s DVR-drev og iLink. Den interne HDD f\u00f8lger HDD-starttilstanden.'\n+  HINT_RIPPS2_SCREEN: 'Menuens billede: videotilstand, widescreen, hvor det sidder p\u00e5 tv''et (H-POS, V-POS) og overscan.'\n+  HINT_RIPPS2_LAYOUT: Hvor kategorierne sidder, og hvordan en side kommer ind, derefter RIPFLOWs covers og RIPGRIDs gitter.\n+  HINT_RIPPS2_COLORS_TEXT: Tekstv\u00e6gt, farvetemaet, farveskiftet og hver farve.\n+  HINT_RIPPS2_TEXT_WEIGHT: 'Hvor kraftig RIPPS2''s egen tekst er: tippene i Indstillinger, STATUS og Om. Normal er som f\u00f8r; Mellem og Fed l\u00e6ses bedre p\u00e5 et bl\u00f8dt billede.'\n+  HINT_RIPPS2_HDD_START: 'Den interne HDD, APA eller exFAT: RIPPS2 l\u00e6ser den, uanset hvad den er, og viser den \u00e9n gang som HDD-spil. Fra lader den v\u00e6re.'\n+  HINT_RIPPS2_THEME_COLOR: Farvetemaets egen farve. V\u00e6lg Brugerdefineret som farvetema for selv at v\u00e6lge den, ligesom tekstfarverne.\n+  HINT_RIPPS2_SWEEP: Fem farver, temaet glider igennem, langsomt og bl\u00f8dt. Regnbue, et skift lavet af hvert farvetema eller dit eget. Fra holder farvetemaet stille.\n+  HINT_RIPPS2_SWEEP_COLORS: Skiftets fem farver og dets hastighed. \u00c6ndr et af RIPPS2's, s\u00e5 gemmes det som dit eget under det navn, du giver det.\n+  HINT_RIPPS2_SWEEP_NAME: Hvad dit skift hedder i listen Farveskift.\n+  HINT_RIPPS2_SWEEP_COLOR: En af de fem farver, skiftet glider igennem, i r\u00e6kkef\u00f8lge.\n+  HINT_RIPPS2_SWEEP_SPEED: 'Hvor l\u00e6nge hver farve varer, f\u00f8r den n\u00e6ste toner frem. Selv Livlig forbliver bl\u00f8d: farverne blinker aldrig.'\n+  RIPPS2_HIDE_COMPRESS: Skjul spil og komprimering\n+  HINT_RIPPS2_SELECT_HOLD: 'SELECT holdt 4.2 s p\u00e5 en spilliste. Liste: temaerne i et panel, v\u00e6lg et. Skift: det n\u00e6ste tema. Fra: intet (et tryk opdaterer stadig).'"
        },
        {
          "sha": "9adedd3ab211b8b5f58901b790446108b379688c",
          "filename": "elf/lang/Dutch.yml",
          "status": "modified",
          "additions": 207,
          "deletions": 22,
          "changes": 229,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FDutch.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FDutch.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FDutch.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: Dutch\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTER BEVESTIGT, ESC ANNULEERT. ELKE KNOP TOONT DE TOETSEN.\n   USB KEYBOARD: USB-TOETSENBORD\n-  '      DESR DVR ISOs (experimental)': '      DESR DVR-ISO''s (experimenteel)'\n-  '      Internal HDD (exFAT)': '      Interne HDD (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (experimenteel)'\n-  '      Protocol': '      Protocol'\n   Access: Toegang\n   Applications Page Start Mode: Startmodus applicatiepagina\n   Art Delay: Afbeeldingsvertraging\n@@ -22,7 +18,6 @@ strings:\n   Fifth: Vijfde\n   First: Eerste\n   Fourth: Vierde\n-  HDD (APA) Start Mode: Startmodus HDD (APA)\n   HDD Cache: HDD-cache\n   HDD Spindown: HDD-stand-by\n   MMCE Settings: MMCE-instellingen\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: Dit is een PS2-game met ESR-patch. Zet ESR (esr*.elf) in APPS, BOOT of ESR op een geheugenkaart of USB-stick om hem te starten.\n   '%s, BUILD %d': '%s, BUILD %d'\n   '... and the anonymous ...': '... en de anonieme ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': '1.8.0 ongewijzigd meegeleverd (AFL-3.0): github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: Een uit elkaar gehaalde en opnieuw opgebouwde RiptOPL, de fork van Open PS2 Loader,\n   ABOUT: OVER\n   'ACHIEVEMENTS: SPIRIT OF RA': 'PRESTATIES: SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): AVI-video in Geheugenbestanden, de fork van RIPPS2 (AFL 2.0)\n   Apollo Save Tool by bucanero: Apollo Save Tool van bucanero\n   BACK: TERUG\n+  Back (keeps both): Terug (houdt beide)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: Deze MMCE-kaart kan geen bestand hernoemen, en dat heeft het comprimeren nodig voor de laatste stap. Werk de sd2psXtd-firmware bij naar 1.4.0 of nieuwer (sd2psxtd.github.io/download) en probeer het opnieuw. Er is niets veranderd.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): BIBLE is PS2BBL 1.2.0, de ondertekende builds van El Isra (kaart en HDD), ongewijzigd (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: BIOS-geluiden > Rippen speelt ze af zoals de SPU2 dat doet; er wordt geen BIOS meegeleverd\n   BOOT LOCK AND BIBLE: BOOT LOCK EN BIBLE\n   BOOT LOCK edits its config too: BOOT LOCK bewerkt ook de configuratie ervan\n   BUILD DETAILS: BUILDDETAILS\n-  BUILT ON RIPTOPL: GEBOUWD OP RIPTOPL\n   BearSSL by Thomas Pornin: BearSSL van Thomas Pornin\n   Built with Claude (Anthropic): Gemaakt met Claude (Anthropic)\n   CD PLAYER: CD-SPELER\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek en Sleek Bold, getekend voor RIPPS2\n   RetroAchievements on the console by hacan359 (yoba): RetroAchievements op de console door hacan359 (yoba)\n   RiptOPL by NathanNeurotic: RiptOPL van NathanNeurotic\n-  Roboto Condensed by Google (Apache-2.0): Roboto Condensed van Google (Apache-2.0)\n   SOUNDS: GELUIDEN\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: De FMCB-installer van SP193 en OSDMenu van pcm720, stap voor stap gevolgd\n   SUPPORT: ONDERSTEUNING\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: wLaunchELF R3Z van NathanNeurotic\n   wLaunchELF by the ps2homebrew team: wLaunchELF van het ps2homebrew-team\n   what RIPPS2's card image writes are checked against: waartegen de kaartimages die RIPPS2 schrijft worden gecontroleerd\n-  with its own look, launchers, file browser and save tools.: met een eigen uiterlijk, launchers, bestandsbrowser en savetools.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: xeRAbora van hacan359, rcheevos van RetroAchievements\n   your console's own, ripped from its BIOS: de eigen geluiden van je console, uit de BIOS geript\n   ' (no DNS answer)': ' (geen DNS-antwoord)'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO: %.60s schrijven'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: Neutrino kon niet naar %s worden geschreven. Controleer de vrije ruimte en of hij niet tegen schrijven beveiligd is, en probeer het opnieuw.\n   THE GAME'S DRIVE  (%s/neutrino): DE SCHIJF VAN DE GAME  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : Deze game start via Neutrino, en er staat geen Neutrino waar RIPPS2 zoekt (Instellingen, de schijf van de game, de geheugenkaarten). RIPPS2 heeft Neutrino 1.8.0 van rickgaiser (AFL-3.0) bij zich. Nu installeren? Daarna kies je waar.\n   '%d / %d FILES': '%d / %d BESTANDEN'\n   '%d FOLDERS AND CONTENTS': '%d MAPPEN EN INHOUD'\n   '%d FOLDERS, %d FOUND': '%d MAPPEN, %d GEVONDEN'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d van %d nog vergrendeld'\n   '%s   on %s': '%s   op %s'\n   BADGES: BADGES\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: Badges werken alleen als TELEMETRIE aan staat en xeRAbora op je pc draait; tot dan kosten ze niets.\n   CHECK THE DISC: DE SCHIJF CONTROLEREN\n   From this game's last CHECK GAME SUPPORT.: Uit de laatste GAMEONDERSTEUNING CONTROLEREN van deze game.\n   HOW IT WORKS: HOE HET WERKT\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: Er staat een PS2BBL met de vergrendeling van RIPPS2 op deze console.\n   AUDIO folder: map AUDIO\n   DRIVES: SCHIJVEN\n-  INSTALL RIPPS2'S COPY: DE KOPIE VAN RIPPS2 INSTALLEREN\n-  'Installed: ': 'Ge\u00efnstalleerd: '\n   MANAGE: BEHEREN\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: BEHEREN toont wat elke geheugenkaart start, en installeert of repareert BIBLE.\n   MEMORY CARD 0  (about 600 KB): GEHEUGENKAART 0  (ongeveer 600 KB)\n   MEMORY CARD 1  (about 600 KB): GEHEUGENKAART 1  (ongeveer 600 KB)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO: de driver wilde niet laden (code %d).'\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO: de driver is geladen, maar geen SD-kaart antwoordde (de sleuf mc1).'\n   NEUTRINO SETTINGS: NEUTRINO-INSTELLINGEN\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': 'Nog geen: RIPPS2 heeft geen eigen geluiden. Rip die van deze console, of maak ze in RIPPS2 Audio Studio.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: Niet ge\u00efnstalleerd. Een game die het nodig heeft, biedt de kopie van RIPPS2 aan, of installeer het hier.\n   POPSTARTER NETWORK: POPSTARTER-NETWERK\n   POPSTARTER and Ember: POPSTARTER en Ember\n   PS1 SETTINGS: PS1-INSTELLINGEN\n   PS2BBL 1.2.0 for RIPPS2: PS2BBL 1.2.0 voor RIPPS2\n   RIP THIS CONSOLE'S SOUNDS: DE GELUIDEN VAN DEZE CONSOLE RIPPEN\n-  RIPPS2's homebrew: de homebrew van RIPPS2\n   STATUS: STATUS\n-  'STATUS: looking for RIPPS2''s homebrew': 'STATUS: zoeken naar de homebrew van RIPPS2'\n   a drive of unknown kind: een schijf van onbekende soort\n-  a drive or a memory card: een schijf of een geheugenkaart\n   boot loader, card by card: opstartlader, kaart voor kaart\n   from its BIOS, into AUDIO: uit de BIOS, naar AUDIO\n   internal HDD (exFAT): interne HDD (exFAT)\n@@ -975,10 +958,192 @@ strings:\n   Sidebar: Zijbalk\n   your own copy, found on your drives and memory cards: je eigen kopie, gevonden op je schijven en geheugenkaarten\n   'your own, found and launched: github.com/Gageformer/Ember/releases': 'je eigen, gevonden en gestart: github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': 'Niet gevonden op mass0: (%s). Zet ember.elf en je bios.bin in de EMBER-map daarvan.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: Niet gevonden. Zet je eigen POPSTARTER.ELF in de POPS-map van een schijf.\n   Launch Disc: Schijf starten\n   Memory Files: Geheugenbestanden\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: RIPPS2 vond %d van de 5. Elk wordt onthouden, en elke start leest het vanaf daar.\n+  not found on the started drives: niet gevonden op de gestarte schijven\n+  not looked for yet: nog niet gezocht\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s gaf geen antwoord, dus %s kan niet starten. Is het aangesloten?'\n+  '%s is not on %s any more, so it has left All Games.': '%s staat niet meer op %s, dus het is uit Alle games verdwenen.'\n+  STARTING %s: '%s STARTEN'\n+  the SMB share: de SMB-share\n+  the drive: de schijf\n+  the internal HDD (APA): de interne HDD (APA)\n+  the internal HDD (exFAT): de interne HDD (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: GEBOUWD OP RIPTOPL, MET NEUTRINO\n+  'Special thanks to all the YouTube love as well:': 'Speciale dank ook aan alle liefde op YouTube:'\n+  and its own look, launchers, file browser and save tools.: en met een eigen uiterlijk, launchers, bestandsbrowser en save-tools.\n+  glad to help however you reach me.: blij om te helpen, hoe je me ook bereikt.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): de tweede spelkern, 1.8.0 ongewijzigd meegeleverd (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: met rickgaisers Neutrino erin als tweede spelkern,\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : 'Deze game start via Neutrino, en RIPPS2 vond er geen: niet op het pad uit Instellingen, niet op de schijf van de game, niet op de geheugenkaarten en niet op een gestarte schijf. RIPPS2 heeft Neutrino 1.8.0 van rickgaiser aan boord (AFL-3.0): installeer het \u00e9\u00e9n keer, waar je wilt, en elke Neutrino-game gebruikt het vanaf daar.'\n+  STARTING THE DRIVES: DE SCHIJVEN STARTEN\n+  '%d games, on the started drives': '%d games, op de gestarte schijven'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s: SOMMIGE BESTANDEN KONDEN NIET WORDEN VERWIJDERD'\n+  '%u partitions, all in order': '%u partities, allemaal in orde'\n+  ', %d art images': ', %d afbeeldingen'\n+  ', and ': ', en '\n+  ', its cheats': ', de cheats'\n+  ', its settings': ', de instellingen'\n+  1 game, on the started drives: 1 game, op de gestarte schijven\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: Een PS1-game hier verwijder je in Geheugenbestanden, waar het een eigen bestand is.\n+  A network catalogue's games are deleted on the PC that serves them.: De games van een netwerkcatalogus verwijder je op de pc die ze aanbiedt.\n+  DELETE FOR GOOD: DEFINITIEF VERWIJDEREN\n+  DELETE IT: VERWIJDER HET\n+  'DELETED %s: %s FREED': '%s VERWIJDERD: %s VRIJ'\n+  'Delete %s from %s? Goes: %s.': '%s van %s verwijderen? Weg: %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': '%s van %s verwijderen? Weg: %s. Blijft: %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: Ook de saves en RIPSHOT-foto's verwijderen? Nee houdt ze.\n+  HIDDEN GAMES: VERBORGEN GAMES\n+  'HIDDEN: %s': 'VERBORGEN: %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: '%s verbergen? Het verdwijnt uit elke lijst, ook uit Alle games en Favorieten. Instellingen > Spelbronnen > Verborgen games toont het weer.'\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: Laatste controle. %s wordt definitief verwijderd, en niets kan het terugbrengen. Druk op START om het te verwijderen.\n+  None. Hide Game, in a game's options, puts one here.: Geen. Game verbergen, in de opties van een game, zet er een hier.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : 'Er is niets verwijderd: de partitietabel van deze schijf past niet bij deze disk (%s). Misschien is hij geschreven vanuit een image van een disk met een andere grootte. Repareer hem eerst op een pc.'\n+  RIPPS2 cannot delete a game from this page.: RIPPS2 kan op deze pagina geen game verwijderen.\n+  RIPPS2 could not find this game's files on its drive.: RIPPS2 vond de bestanden van deze game niet op zijn schijf.\n+  Reading the hidden lists: De verborgen lijsten lezen\n+  SHOW AGAIN OR DELETE: WEER TONEN OF VERWIJDEREN\n+  SHOW IT AGAIN: WEER TONEN\n+  That game is not on its drive any more.: Die game staat niet meer op zijn schijf.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: De verborgen lijst kon niet naar de schijf van de game worden geschreven. Is die tegen schrijven beveiligd, of staan de Schrijfbewerkingen uit?\n+  The partition could not be removed (%d). Nothing was deleted.: De partitie kon niet worden verwijderd (%d). Er is niets verwijderd.\n+  This drive cannot keep a hidden list (a network catalogue).: Deze schijf kan geen verborgen lijst bijhouden (een netwerkcatalogus).\n+  This game's drive has not answered yet. Wait for it, then try again.: De schijf van deze game heeft nog niet geantwoord. Wacht erop en probeer het dan opnieuw.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: De partitie van deze game is er een die RIPPS2 niet weghaalt (een systeempartitie, of die met zijn instellingen).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': 'een partitie is groter dan deze disk toelaat: de tabel is gemaakt voor een disk met een andere grootte'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': 'een partitie loopt voorbij het einde van deze disk (sector %u van %u): de tabel is gemaakt voor een grotere disk'\n+  its RIPSHOT pictures: de RIPSHOT-foto's\n+  its drive: zijn schijf\n+  its saves: de saves\n+  sector %u holds no partition header: sector %u bevat geen partitiekop\n+  the chain does not close at __mbr: de keten sluit niet bij __mbr\n+  the chain skips from sector %u: de keten springt vanaf sector %u\n+  the chain's links disagree at sector %u: de schakels van de keten kloppen niet bij sector %u\n+  the drive's size could not be read: de grootte van de schijf kon niet worden gelezen\n+  the game (%s): de game (%s)\n+  the game's folder and everything in it (%s): de map van de game en alles erin (%s)\n+  the header at sector %u could not be read: de kop bij sector %u kon niet worden gelezen\n+  the header at sector %u fails its checksum: de kop bij sector %u faalt op zijn controlesom\n+  the partition at sector %u is not where or as large as APA puts one: de partitie bij sector %u staat niet waar, of is niet zo groot als, APA er een maakt\n+  the settings and art another copy of it uses: de instellingen en afbeeldingen die een andere kopie ervan gebruikt\n+  '%d FOLDERS': '%d MAPPEN'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': '%s kon niet naar %s worden gekopieerd. Is de schijf vol of tegen schrijven beveiligd?'\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s staat niet waar RIPPS2 zoekt. Zoeken op de schijven die RIPPS2 heeft gestart? Waar het gevonden wordt, wordt onthouden voor elke start daarna.'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Ember staat niet in %s, naast de games, en daar moet het staan. RIPPS2 vond het op %s: START kopieert het daarheen (%d KB) en start de game.'\n+  Ember's BIOS: Embers BIOS\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Embers BIOS (bios.bin) staat niet in %s, naast Ember. RIPPS2 vond er een op %s: START kopieert hem daarheen (%d KB) en start de game.'\n+  LOOKING FOR YOUR LAUNCHERS: JE LAUNCHERS ZOEKEN\n+  The POPS files: De POPS-bestanden\n+  '%s  MMCE card': '%s  MMCE-kaart'\n+  INSTALL RIPPS2'S NEUTRINO: NEUTRINO VAN RIPPS2 INSTALLEREN\n+  'Internal HDD (APA): %s free of %s': 'Interne HDD (APA): %s vrij van %s'\n+  LAUNCHERS: LAUNCHERS\n+  LOOK FOR THEM: ZOEK ZE\n+  'Memory card %d: %d KB free': 'Geheugenkaart %d: %d KB vrij'\n+  POPS files: POPS-bestanden\n+  START EVERY DRIVE: ELKE SCHIJF STARTEN\n+  'STATUS: looking at the drives and the homebrew': 'STATUS: de schijven en de homebrew bekijken'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): Eerst elke schijf starten, zodat de zoektocht ze allemaal bereikt? (Nee zoekt op de schijven die al gestart zijn.)\n+  USB, HDD, MMCE and the rest: USB, HDD, MMCE en de rest\n+  every started drive: elke gestarte schijf\n+  found where you keep them: gevonden waar je ze bewaart\n+  not found yet: nog niet gevonden\n+  not on a started drive: niet op een gestarte schijf\n+  only if you have none: alleen als je er geen hebt\n+  starts every drive first: start eerst elke schijf\n+  Drives, launchers and homebrew: Schijven, launchers en homebrew\n+  BDM Devices: BDM-apparaten\n+  Color Sweep: Kleurverloop\n+  Colors and Text: Kleuren en tekst\n+  HDD POPS partitions: POPS-partities op de HDD\n+  Layout: Indeling\n+  Missing Covers: Ontbrekende hoezen\n+  Name: Naam\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: Nog geen SMB-instelling voor POPSTARTER gevonden. Vul de rijen hieronder in.\n+  POPSTARTER Network: POPSTARTER-netwerk\n+  POPSTARTER SMB Network: POPSTARTER SMB-netwerk\n+  POPSTARTER UDPFS Network: POPSTARTER UDPFS-netwerk\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: RIPPS2 voegt zelf de schijf, ISO, VMC's en videomodus toe.\n+  Screen: Scherm\n+  Speed: Snelheid\n+  Sweep Colors: Kleuren van het verloop\n+  Text Weight: Tekstdikte\n+  Theme Color: Hoofdkleur\n+  Bold: Vet\n+  Lively: Levendig\n+  Medium: Gemiddeld\n+  PS Emulation: PS-emulatie\n+  Regular: Normaal\n+  Slow: Langzaam\n+  A MOMENT: EEN MOMENT\n+  A TASK IN THE BACKGROUND: EEN TAAK OP DE ACHTERGROND\n+  ABOUT %d S: ONGEVEER %d S\n+  ANY MOMENT: ELK MOMENT\n+  READING THE GAME LIST OF %s: LEEST DE SPELLIJST VAN %s\n+  SAVING A LIST: SLAAT EEN LIJST OP\n+  START NOW: NU STARTEN\n+  STARTING: START\n+  STARTS NEXT: START HIERNA\n+  STOPPING IT SAFELY: STOPT VEILIG\n+  THE LAST OF THE QUEUE: HET LAATSTE VAN DE WACHTRIJ\n+  WHEN IT FINISHES: ALS HET KLAAR IS\n+  What each card starts; install or repair BIBLE.: Wat elke kaart start; installeer of herstel BIBLE.\n+  Rainbow: Regenboog\n+  Custom: Eigen\n+  DESR DVR ISOs (experimental): DESR DVR-ISO's (experimenteel)\n+  PC CARD CF (experimental): PC CARD CF (experimenteel)\n+  HDD Start Mode: Startmodus HDD\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): Roboto Condensed van The Roboto Project Authors (SIL OFL 1.1)\n+  '%s on %s': '%s op %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: %s werd %s (%d%%), elk stuk gecontroleerd. De ISO verwijderen en de ZSO houden?'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: %s werd weer %s, elk stuk gecontroleerd. De ZSO verwijderen en de ISO houden?'\n+  A ZSO of this game is already beside it. Remove one of the two first.: Er staat al een ZSO van deze game naast. Verwijder eerst een van de twee.\n+  About %d min, then checked: Ongeveer %d min, daarna gecontroleerd\n+  An ISO of this game is already beside it. Remove one of the two first.: Er staat al een ISO van deze game naast. Verwijder eerst een van de twee.\n+  CHECKING EVERY PIECE: ELK STUK CONTROLEREN\n+  COMPRESS TO ZSO: COMPRIMEREN NAAR ZSO\n+  'COMPRESSED %s: %s FREED': 'GECOMPRIMEERD %s: %s VRIJ'\n+  COMPRESSING 1 OF 2: COMPRIMEREN 1 VAN 2\n+  COMPRESSING 2 OF 2: COMPRIMEREN 2 VAN 2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : '%s comprimeren? De ISO blijft tot de ZSO is teruggelezen en gecontroleerd, daarna vraagt RIPPS2 het voordat de ISO wordt verwijderd. De schijf heeft ruimte nodig voor beide terwijl het werkt; laat de console aan tot het klaar is. Een ZSO start alleen met de OPL-core (niet Neutrino); de saves, instellingen, afbeeldingen en cheats blijven erbij, en hij kan altijd weer een ISO worden.'\n+  Compress to ZSO: Comprimeren naar ZSO\n+  Cycle: Doorlopen\n+  'Experimental: OPL core only (not Neutrino)': 'Experimenteel: alleen de OPL-core (niet Neutrino)'\n+  Hold SELECT: SELECT vasthouden\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: Het kon niet worden afgemaakt (%s). Wat het schreef is verwijderd; de game is zoals hij was.\n+  List: Lijst\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : '%s weer een ISO maken? De ZSO blijft tot de ISO is teruggelezen en gecontroleerd, daarna vraagt RIPPS2 het voordat de ZSO wordt verwijderd. De schijf heeft ruimte nodig voor beide terwijl het werkt; laat de console aan tot het klaar is. De saves, instellingen, afbeeldingen en cheats blijven erbij.'\n+  Stop? What it has written so far is removed; the game stays as it was.: Stoppen? Wat tot nu toe is geschreven wordt verwijderd; de game blijft zoals hij was.\n+  THEMES: THEMA'S\n+  There is not enough memory for this just now.: Er is nu niet genoeg geheugen hiervoor.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: Deze image kan RIPPS2 niet comprimeren of uitpakken (de grootte is geen hele blokken van 2 KB).\n+  UNCOMPRESS TO ISO: UITPAKKEN NAAR ISO\n+  UNCOMPRESSED %s: UITGEPAKT %s\n+  UNCOMPRESSING: UITPAKKEN\n+  Uncompress to ISO: Uitpakken naar ISO\n+  Under a minute, then checked: Minder dan een minuut, daarna gecontroleerd\n+  COPYING: KOPI\u00cbREN\n+  SHOW AGAIN, DELETE OR COMPRESS: WEER TONEN, VERWIJDEREN OF COMPRIMEREN\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: WEER TONEN, VERWIJDEREN OF UITPAKKEN\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': '%s heeft tijdens het werk ongeveer %s vrije ruimte op de schijf nodig (het origineel blijft tot het eind), en de schijf heeft %s vrij.'\n+  Hide and Compress: Verbergen en comprimeren\n+  'COMPRESSED AND HIDDEN: %s': 'GECOMPRIMEERD EN VERBORGEN: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': 'Er is geen ruimte voor deze rip: hij heeft ongeveer %s nodig, en de schijf heeft %s vrij.'\n+  OVER 4 GB FOR FAT32: MEER DAN 4 GB VOOR FAT32\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: Deze schijf is %s, dat geen bestand van 4 GB of groter bewaart, en de nieuwe image zou %s zijn.\n+  '%s, %s clusters, %s free of %s': '%s, clusters van %s, %s vrij van %s'\n+  Delete This Sweep: Dit verloop verwijderen\n+  Delete %s? RIPPS2's own sweeps stay as they are.: '%s verwijderen? De eigen verlopen van RIPPS2 blijven zoals ze zijn.'\n+  Sweep: Verloop\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: Badges markeren de games met achievements in je lijsten, met hoeveel je er hebt ontgrendeld. Ze hebben TELEMETRIE aan nodig.\n labels:\n   SETTINGS_SAVED: Instellingen opgeslagen in %s\n   OK: OK\n@@ -1267,7 +1432,7 @@ labels:\n   SAVE_ICONS: 3D-savepictogrammen\n   HINT_SAVE_ICONS: Toont het savepictogram van de gekozen game, draaiend, waar het thema er een plek voor heeft. Leest eerst de eigen VMC van de game, daarna de geheugenkaarten.\n   NETBOOT_RESTART_NOW: Het netwerkprotocol verandert na een herstart. Instellingen opslaan en OPL nu opnieuw starten?\n-  HINT_NET_PROTOCOL: 'Welke netwerkdienst RiptOPL gebruikt: een SMB-share, UDPFS, UDPBD of HTTP. Dezelfde instelling als bij Gamebronnen. Overstappen van een dienst die al draait, vraagt een herstart.'\n+  HINT_NET_PROTOCOL: 'Welke netwerkdienst RiptOPL gebruikt: een SMB-share, UDPFS, UDPBD of HTTP. Overstappen van een dienst die al draait, vraagt een herstart.'\n   HINT_UDPFS_ACCESS: Alleen UDPFS. Bestanden leest losse ISO's uit de gamemap van de server; IMG leest een schijfimage als schijf. SMB en HTTP gebruiken altijd Bestanden, UDPBD altijd IMG.\n   HINT_SMB_VERSION: Welke SMB-versie gebruikt wordt met de share op de pc.\n   HINT_NET_ADVANCED: Ontgrendelt Ethernet-verbindingsmodus en de SMB-poort voor bewerken.\n@@ -1466,3 +1631,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: Hoe de foto's die op de rol van elke game wachten PNG's worden.\n   RIPPS2_SETTINGS_HOME: Instellingen opslaan op\n   HINT_RIPPS2_SETTINGS_HOME: 'Waar RIPPS2 zijn instellingen bewaart: zijn eigen save op een geheugenkaart, een RIPPS2-map op een schijf (de interne HDD, USB, MX4SIO, iLink of MMCE) of een map die je typt.'\n+  RIPPS2_LAUNCHERS: Launchers\n+  RIPPS2_LOOK_NOW: Nu zoeken\n+  HINT_RIPPS2_LOOK_NOW: RIPPS2 zoekt op de gestarte schijven naar POPSTARTER, de POPS-bestanden, Ember, Embers BIOS en Neutrino, en onthoudt waar elk staat. Elke start leest ze vanaf daar.\n+  RIPPS2_HIDE_GAME: Game verbergen\n+  RIPPS2_HIDDEN_GAMES: Verborgen games\n+  HINT_RIPPS2_HIDDEN_GAMES: De games die met Game verbergen zijn verborgen, op de gestarte schijven. Toon er een weer, of verwijder het. Elke schijf houdt zijn eigen lijst bij.\n+  HINT_RIPPS2_BDM_DEVICES: 'De schijven waar BDM spellen van leest: USB, MX4SIO, een PC Card CF, de DVR-schijf van een DESR en iLink. De interne HDD volgt de startmodus van de HDD.'\n+  HINT_RIPPS2_SCREEN: 'Het beeld van het menu: videomodus, breedbeeld, waar het op de tv staat (H-POS, V-POS) en overscan.'\n+  HINT_RIPPS2_LAYOUT: Waar de categorie\u00ebn staan en hoe een pagina binnenkomt, dan de hoezen van RIPFLOW en het raster van RIPGRID.\n+  HINT_RIPPS2_COLORS_TEXT: Tekstdikte, het kleurthema, het kleurverloop en elke kleur.\n+  HINT_RIPPS2_TEXT_WEIGHT: 'Hoe zwaar de eigen tekst van RIPPS2 is: de tips in Instellingen, STATUS en Over. Normaal is zoals het was; Gemiddeld en Vet lezen beter op een zacht beeld.'\n+  HINT_RIPPS2_HDD_START: 'De interne HDD, APA of exFAT: RIPPS2 leest hem wat hij ook is en toont hem \u00e9\u00e9n keer, als HDD-spellen. Uit laat hem met rust.'\n+  HINT_RIPPS2_THEME_COLOR: De eigen kleur van het kleurthema. Kies Eigen als kleurthema om hem zelf te kiezen, zoals de tekstkleuren.\n+  HINT_RIPPS2_SWEEP: Vijf kleuren waar het thema langzaam en vloeiend doorheen gaat. Regenboog, een verloop uit elk kleurthema of je eigen. Uit houdt het kleurthema stil.\n+  HINT_RIPPS2_SWEEP_COLORS: De vijf kleuren van het verloop en de snelheid. Verander er een van RIPPS2 en het wordt als je eigen bewaard, onder de naam die je het geeft.\n+  HINT_RIPPS2_SWEEP_NAME: Hoe je verloop heet in de lijst Kleurverloop.\n+  HINT_RIPPS2_SWEEP_COLOR: Een van de vijf kleuren waar het verloop doorheen gaat, op volgorde.\n+  HINT_RIPPS2_SWEEP_SPEED: 'Hoe lang elke kleur duurt voordat de volgende invloeit. Ook Levendig blijft vloeiend: de kleuren knipperen nooit.'\n+  RIPPS2_HIDE_COMPRESS: Game verbergen en compressie\n+  HINT_RIPPS2_SELECT_HOLD: 'SELECT 4.2 s vastgehouden op een gamelijst. Lijst: de thema''s in een paneel, kies er een. Doorlopen: het volgende thema. Uit: niets (een druk vernieuwt nog steeds).'"
        },
        {
          "sha": "e22d007e1154df6bc2ce3b2b456bfa2e2ce0da86",
          "filename": "elf/lang/Filipino.yml",
          "status": "modified",
          "additions": 206,
          "deletions": 22,
          "changes": 228,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FFilipino.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FFilipino.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FFilipino.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: Filipino\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTER PARA TANGGAPIN, ESC PARA KANSELAHIN. ANUMANG BUTTON AY NAGPAPAKITA NG MGA KEY.\n   USB KEYBOARD: USB KEYBOARD\n-  '      DESR DVR ISOs (experimental)': '      DESR DVR ISO (eksperimental)'\n-  '      Internal HDD (exFAT)': '      Internal HDD (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (eksperimental)'\n-  '      Protocol': '      Protocol'\n   Access: Access\n   Applications Page Start Mode: Start mode ng Applications page\n   Art Delay: Antala ng larawan\n@@ -22,7 +18,6 @@ strings:\n   Fifth: Ikalima\n   First: Una\n   Fourth: Ikaapat\n-  HDD (APA) Start Mode: Start mode ng HDD (APA)\n   HDD Cache: HDD cache\n   HDD Spindown: Pag-spindown ng HDD\n   MMCE Settings: Mga setting ng MMCE\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: PS2 na laro ito na may ESR patch. Ilagay ang ESR (esr*.elf) sa APPS, BOOT o ESR sa memory card o USB drive para masimulan ito.\n   '%s, BUILD %d': '%s, BUILD %d'\n   '... and the anonymous ...': '... at ang mga hindi kilala ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': '1.8.0 na dala nang walang binago (AFL-3.0): github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: Pagbaklas at muling pagbuo ng RiptOPL, ang fork ng Open PS2 Loader,\n   ABOUT: TUNGKOL DITO\n   'ACHIEVEMENTS: SPIRIT OF RA': 'MGA ACHIEVEMENT: SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): AVI video sa Memory Files, fork ng RIPPS2 (AFL 2.0)\n   Apollo Save Tool by bucanero: Apollo Save Tool ni bucanero\n   BACK: BUMALIK\n+  Back (keeps both): Bumalik (itatago ang dalawa)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: Hindi kayang palitan ng pangalan ng MMCE card na ito ang isang file, na kailangan ng compression sa huling hakbang nito. I-update ang sd2psXtd firmware nito sa 1.4.0 o mas bago (sd2psxtd.github.io/download), saka subukang muli. Walang nabago.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): Ang BIBLE ay PS2BBL 1.2.0, mga signed build ni El Isra (card at HDD), walang binago (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: Pinapatugtog ito ng BIOS Sounds > Rip gaya ng SPU2; walang kasamang BIOS\n   BOOT LOCK AND BIBLE: BOOT LOCK AT BIBLE\n   BOOT LOCK edits its config too: Binabago rin ng BOOT LOCK ang config nito\n   BUILD DETAILS: MGA DETALYE NG BUILD\n-  BUILT ON RIPTOPL: BINUO SA RIPTOPL\n   BearSSL by Thomas Pornin: BearSSL ni Thomas Pornin\n   Built with Claude (Anthropic): Binuo kasama si Claude (Anthropic)\n   CD PLAYER: CD PLAYER\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek at Sleek Bold, iginuhit para sa RIPPS2\n   RetroAchievements on the console by hacan359 (yoba): RetroAchievements sa console ni hacan359 (yoba)\n   RiptOPL by NathanNeurotic: RiptOPL ni NathanNeurotic\n-  Roboto Condensed by Google (Apache-2.0): Roboto Condensed ng Google (Apache-2.0)\n   SOUNDS: MGA TUNOG\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: FMCB installer ni SP193 at OSDMenu ni pcm720, sinundan nang hakbang-hakbang\n   SUPPORT: SUPORTA\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: wLaunchELF R3Z ni NathanNeurotic\n   wLaunchELF by the ps2homebrew team: wLaunchELF ng ps2homebrew team\n   what RIPPS2's card image writes are checked against: ang pinagbabatayan ng pagsusuri sa mga card image na isinusulat ng RIPPS2\n-  with its own look, launchers, file browser and save tools.: na may sariling itsura, mga launcher, file browser at mga save tool.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: xeRAbora ni hacan359, rcheevos ng RetroAchievements\n   your console's own, ripped from its BIOS: ang sariling tunog ng iyong console, kinuha mula sa BIOS nito\n   ' (no DNS answer)': ' (walang sagot ang DNS)'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO: isinusulat ang %.60s'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: Hindi maisulat ang Neutrino sa %s. Tingnan ang libreng espasyo nito at kung hindi ito naka-write-protect, saka subukan ulit.\n   THE GAME'S DRIVE  (%s/neutrino): ANG DRIVE NG LARO  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : Sa pamamagitan ng Neutrino nagsisimula ang larong ito, at walang Neutrino kung saan naghahanap ang RIPPS2 (Settings, ang drive ng laro, ang mga memory card). Dala ng RIPPS2 ang Neutrino 1.8.0 ni rickgaiser (AFL-3.0). I-install ito ngayon? Susunod, pipili ka kung saan.\n   '%d / %d FILES': '%d / %d FILE'\n   '%d FOLDERS AND CONTENTS': '%d FOLDER AT LAMAN'\n   '%d FOLDERS, %d FOUND': '%d FOLDER, %d NAHANAP'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d sa %d ang naka-lock pa'\n   '%s   on %s': '%s   sa %s'\n   BADGES: MGA BADGE\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: Gumagana lang ang mga badge kapag naka-on ang TELEMETRY at tumatakbo ang xeRAbora sa iyong PC; hanggang doon, wala itong gastos.\n   CHECK THE DISC: TINGNAN ANG DISC\n   From this game's last CHECK GAME SUPPORT.: Mula sa huling CHECK GAME SUPPORT ng larong ito.\n   HOW IT WORKS: PAANO ITO GUMAGANA\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: May PS2BBL na may lock ng RIPPS2 sa console na ito.\n   AUDIO folder: AUDIO folder\n   DRIVES: MGA DRIVE\n-  INSTALL RIPPS2'S COPY: I-INSTALL ANG KOPYA NG RIPPS2\n-  'Installed: ': 'Naka-install: '\n   MANAGE: PAMAHALAAN\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: Ipinapakita ng PAMAHALAAN kung ano ang sinisimulan ng bawat memory card, at nag-i-install o nag-aayos ng BIBLE.\n   MEMORY CARD 0  (about 600 KB): MEMORY CARD 0  (mga 600 KB)\n   MEMORY CARD 1  (about 600 KB): MEMORY CARD 1  (mga 600 KB)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO: ayaw mag-load ng driver nito (code %d).'\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO: naka-load ang driver, pero walang SD card na sumagot (ang mc1 slot).'\n   NEUTRINO SETTINGS: MGA SETTING NG NEUTRINO\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': 'Wala pa: walang sariling tunog ang RIPPS2. I-rip ang sa console na ito, o gumawa sa RIPPS2 Audio Studio.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: Hindi naka-install. Iaalok ng larong nangangailangan nito ang kopya ng RIPPS2, o i-install ito dito.\n   POPSTARTER NETWORK: NETWORK NG POPSTARTER\n   POPSTARTER and Ember: POPSTARTER at Ember\n   PS1 SETTINGS: MGA SETTING NG PS1\n   PS2BBL 1.2.0 for RIPPS2: PS2BBL 1.2.0 para sa RIPPS2\n   RIP THIS CONSOLE'S SOUNDS: I-RIP ANG MGA TUNOG NG CONSOLE NA ITO\n-  RIPPS2's homebrew: Homebrew ng RIPPS2\n   STATUS: STATUS\n-  'STATUS: looking for RIPPS2''s homebrew': 'STATUS: hinahanap ang homebrew ng RIPPS2'\n   a drive of unknown kind: drive na hindi alam ang uri\n-  a drive or a memory card: drive o memory card\n   boot loader, card by card: boot loader, bawat card\n   from its BIOS, into AUDIO: mula sa BIOS nito, papunta sa AUDIO\n   internal HDD (exFAT): internal HDD (exFAT)\n@@ -975,10 +958,191 @@ strings:\n   Sidebar: Sidebar\n   your own copy, found on your drives and memory cards: sarili mong kopya, nahanap sa iyong mga drive at memory card\n   'your own, found and launched: github.com/Gageformer/Ember/releases': 'sarili mo, nahanap at inilunsad: github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': 'Hindi nahanap sa mass0: (%s). Ilagay ang ember.elf at ang iyong bios.bin sa EMBER folder nito.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: Hindi nahanap. Ilagay ang sarili mong POPSTARTER.ELF sa POPS folder ng isang drive.\n   Launch Disc: Ilunsad ang disc\n   Memory Files: Memory files\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: Nakakita ang RIPPS2 ng %d sa 5. Tinatandaan ang bawat isa, at doon ito binabasa ng bawat launch.\n+  not found on the started drives: hindi nakita sa mga nakasimulang drive\n+  not looked for yet: hindi pa hinahanap\n+  '%s did not answer, so %s cannot start. Is it connected?': Hindi sumagot ang %s, kaya hindi makakapagsimula ang %s. Nakakonekta ba ito?\n+  '%s is not on %s any more, so it has left All Games.': Wala na ang %s sa %s, kaya umalis na ito sa Lahat ng laro.\n+  STARTING %s: SINISIMULAN ANG %s\n+  the SMB share: ang SMB share\n+  the drive: ang drive\n+  the internal HDD (APA): ang internal HDD (APA)\n+  the internal HDD (exFAT): ang internal HDD (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: BINUO SA RIPTOPL, KASAMA ANG NEUTRINO\n+  'Special thanks to all the YouTube love as well:': 'Espesyal na pasasalamat din sa lahat ng pagmamahal sa YouTube:'\n+  and its own look, launchers, file browser and save tools.: at may sarili nitong itsura, launcher, file browser at save tools.\n+  glad to help however you reach me.: masaya akong tumulong, paano mo man ako maabot.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): ang pangalawang game core, 1.8.0 na dala nang walang binago (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: na may Neutrino ni rickgaiser sa loob bilang pangalawang game core nito,\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : 'Nagsisimula ang larong ito sa Neutrino, at walang nakita ang RIPPS2: wala sa path ng Settings, sa drive ng laro, sa mga memory card o sa anumang nakasimulang drive. Dala ng RIPPS2 ang Neutrino 1.8.0 ni rickgaiser (AFL-3.0): i-install ito nang isang beses, kung saan mo gusto, at doon ito gagamitin ng bawat larong Neutrino.'\n+  STARTING THE DRIVES: SINISIMULAN ANG MGA DRIVE\n+  '%d games, on the started drives': '%d laro, sa mga nakasimulang drive'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s: MAY MGA FILE NA HINDI NABURA'\n+  '%u partitions, all in order': '%u partition, lahat maayos'\n+  ', %d art images': ', %d art image'\n+  ', and ': ', at '\n+  ', its cheats': ', ang mga cheat nito'\n+  ', its settings': ', ang settings nito'\n+  1 game, on the started drives: 1 laro, sa mga nakasimulang drive\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: Ang PS1 game dito ay binubura sa Memory Files, kung saan sarili nitong file ito.\n+  A network catalogue's games are deleted on the PC that serves them.: Ang mga laro ng network catalogue ay binubura sa PC na nagse-serve sa mga ito.\n+  DELETE FOR GOOD: BURAHIN NANG TULUYAN\n+  DELETE IT: BURAHIN ITO\n+  'DELETED %s: %s FREED': 'NABURA ANG %s: %s ANG NALUWAG'\n+  'Delete %s from %s? Goes: %s.': 'Burahin ang %s mula sa %s? Mawawala: %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': 'Burahin ang %s mula sa %s? Mawawala: %s. Maiiwan: %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: Burahin din ang mga save at RIPSHOT picture nito? Itatabi ang mga ito ng No.\n+  HIDDEN GAMES: MGA NAKATAGONG LARO\n+  'HIDDEN: %s': 'NAKATAGO: %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: Itago ang %s? Aalis ito sa bawat listahan, pati sa Lahat ng laro at Mga Paborito. Ibabalik ito ng Settings > Mga pinagkukunan ng laro > Mga nakatagong laro.\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: Huling check. Mabubura nang tuluyan ang %s, at walang makakapagbalik nito. Pindutin ang START para burahin ito.\n+  None. Hide Game, in a game's options, puts one here.: Wala. Naglalagay dito ang Itago ang laro, sa options ng isang laro.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : 'Walang nabura: hindi tugma ang partition table ng drive na ito sa disk na ito (%s). Baka isinulat ito mula sa image ng disk na iba ang laki. Ayusin muna ito sa PC.'\n+  RIPPS2 cannot delete a game from this page.: Hindi makakabura ng laro ang RIPPS2 mula sa page na ito.\n+  RIPPS2 could not find this game's files on its drive.: Hindi nakita ng RIPPS2 ang mga file ng larong ito sa drive nito.\n+  Reading the hidden lists: Binabasa ang mga nakatagong listahan\n+  SHOW AGAIN OR DELETE: IPAKITA ULIT O BURAHIN\n+  SHOW IT AGAIN: IPAKITA ULIT\n+  That game is not on its drive any more.: Wala na ang larong iyon sa drive nito.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: Hindi maisulat ang nakatagong listahan sa drive ng laro. Write-protected ba ito, o naka-off ang Write Operations?\n+  The partition could not be removed (%d). Nothing was deleted.: Hindi maalis ang partition (%d). Walang nabura.\n+  This drive cannot keep a hidden list (a network catalogue).: Hindi makakapag-ingat ng nakatagong listahan ang drive na ito (isang network catalogue).\n+  This game's drive has not answered yet. Wait for it, then try again.: Hindi pa sumasagot ang drive ng larong ito. Hintayin ito, saka subukan ulit.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: Isa ang partition ng larong ito sa mga hindi inaalis ng RIPPS2 (isang system partition, o ang may settings nito).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': 'mas malaki ang isang partition kaysa sa pinapayagan ng disk na ito: ginawa ang table para sa disk na iba ang laki'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': 'lumalampas ang isang partition sa dulo ng disk na ito (sector %u ng %u): ginawa ang table para sa mas malaking disk'\n+  its RIPSHOT pictures: ang mga RIPSHOT picture nito\n+  its drive: ang drive nito\n+  its saves: ang mga save nito\n+  sector %u holds no partition header: walang partition header ang sector %u\n+  the chain does not close at __mbr: hindi nagsasara ang chain sa __mbr\n+  the chain skips from sector %u: lumalaktaw ang chain mula sa sector %u\n+  the chain's links disagree at sector %u: hindi nagtutugma ang mga link ng chain sa sector %u\n+  the drive's size could not be read: hindi mabasa ang laki ng drive\n+  the game (%s): ang laro (%s)\n+  the game's folder and everything in it (%s): ang folder ng laro at lahat ng laman nito (%s)\n+  the header at sector %u could not be read: hindi mabasa ang header sa sector %u\n+  the header at sector %u fails its checksum: pumapalya sa checksum ang header sa sector %u\n+  the partition at sector %u is not where or as large as APA puts one: wala ang partition sa sector %u sa lugar o sa laking inilalagay ng APA\n+  the settings and art another copy of it uses: ang settings at art na ginagamit ng ibang kopya nito\n+  '%d FOLDERS': '%d FOLDER'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': Hindi makopya ang %s sa %s. Puno ba ang drive o write-protected?\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : Wala ang %s kung saan naghahanap ang RIPPS2. Hanapin ito sa mga drive na sinimulan ng RIPPS2? Tatandaan kung saan ito nakita para sa bawat susunod na launch.\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Wala ang Ember sa %s, katabi ng mga laro, at dapat nandoon ito. Nakita ito ng RIPPS2 sa %s: kinokopya ito roon ng START (%d KB) at sinisimulan ang laro.'\n+  Ember's BIOS: BIOS ng Ember\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Wala ang BIOS ng Ember (bios.bin) sa %s, katabi ng Ember. Nakakita ang RIPPS2 ng isa sa %s: kinokopya ito roon ng START (%d KB) at sinisimulan ang laro.'\n+  LOOKING FOR YOUR LAUNCHERS: HINAHANAP ANG MGA LAUNCHER MO\n+  The POPS files: Ang mga POPS file\n+  '%s  MMCE card': '%s  MMCE card'\n+  INSTALL RIPPS2'S NEUTRINO: I-INSTALL ANG NEUTRINO NG RIPPS2\n+  'Internal HDD (APA): %s free of %s': 'Internal HDD (APA): %s ang libre sa %s'\n+  LAUNCHERS: MGA LAUNCHER\n+  LOOK FOR THEM: HANAPIN SILA\n+  'Memory card %d: %d KB free': 'Memory card %d: %d KB ang libre'\n+  POPS files: Mga POPS file\n+  START EVERY DRIVE: SIMULAN ANG BAWAT DRIVE\n+  'STATUS: looking at the drives and the homebrew': 'STATUS: tinitingnan ang mga drive at ang homebrew'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): Simulan muna ang bawat drive, para maabot ng paghahanap ang lahat? (Sa mga nakasimula nang drive maghahanap ang No.)\n+  USB, HDD, MMCE and the rest: USB, HDD, MMCE at ang iba pa\n+  every started drive: bawat nakasimulang drive\n+  found where you keep them: nakita kung saan mo sila itinatago\n+  not found yet: hindi pa nakikita\n+  not on a started drive: wala sa nakasimulang drive\n+  only if you have none: kung wala ka lang\n+  starts every drive first: sinisimulan muna ang bawat drive\n+  Drives, launchers and homebrew: Mga drive, launcher at homebrew\n+  BDM Devices: Mga device ng BDM\n+  Color Sweep: Paglipat ng kulay\n+  Colors and Text: Kulay at teksto\n+  HDD POPS partitions: Mga POPS partition ng HDD\n+  Missing Covers: Mga nawawalang cover\n+  Name: Pangalan\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: Wala pang nakitang SMB setup ng POPSTARTER. Punan ang mga hilera sa ibaba.\n+  POPSTARTER Network: Network ng POPSTARTER\n+  POPSTARTER SMB Network: SMB network ng POPSTARTER\n+  POPSTARTER UDPFS Network: UDPFS network ng POPSTARTER\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: Ang RIPPS2 mismo ang nagdadagdag ng drive, ISO, mga VMC at video mode.\n+  Speed: Bilis\n+  Sweep Colors: Mga kulay ng paglipat\n+  Text Weight: Kapal ng teksto\n+  Theme Color: Kulay mismo ng theme\n+  Bold: Makapal\n+  Controllers: Mga controller\n+  Lively: Masigla\n+  Medium: Katamtaman\n+  PS Emulation: PS emulation\n+  Regular: Karaniwan\n+  Slow: Mabagal\n+  A MOMENT: SANDALI\n+  A TASK IN THE BACKGROUND: ISANG GAWAIN SA LIKOD\n+  ABOUT %d S: MGA %d S\n+  ANY MOMENT: ANUMANG SANDALI\n+  READING THE GAME LIST OF %s: BINABASA ANG LISTAHAN NG LARO NG %s\n+  SAVING A LIST: NAGSE-SAVE NG LISTAHAN\n+  START NOW: SIMULAN NGAYON\n+  STARTING: NAGSISIMULA\n+  STARTS NEXT: SUSUNOD NA MAGSISIMULA\n+  STOPPING IT SAFELY: LIGTAS NA PINAPAHINTO\n+  THE LAST OF THE QUEUE: ANG HULI SA PILA\n+  WHEN IT FINISHES: KAPAG TAPOS NA\n+  What each card starts; install or repair BIBLE.: Ano ang sinisimulan ng bawat card; i-install o ayusin ang BIBLE.\n+  Rainbow: Bahaghari\n+  Custom: Sarili\n+  DESR DVR ISOs (experimental): DESR DVR ISO (eksperimental)\n+  PC CARD CF (experimental): PC CARD CF (eksperimental)\n+  HDD Start Mode: Start mode ng HDD\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): Roboto Condensed ng The Roboto Project Authors (SIL OFL 1.1)\n+  '%s on %s': '%s sa %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: ang %s ay naging %s (%d%%), nasuri ang bawat piraso. Alisin ang ISO at itago ang ZSO?'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: ang %s ay naging %s muli, nasuri ang bawat piraso. Alisin ang ZSO at itago ang ISO?'\n+  A ZSO of this game is already beside it. Remove one of the two first.: May ZSO na ng larong ito sa tabi nito. Alisin muna ang isa sa dalawa.\n+  About %d min, then checked: Mga %d min, saka susuriin\n+  An ISO of this game is already beside it. Remove one of the two first.: May ISO na ng larong ito sa tabi nito. Alisin muna ang isa sa dalawa.\n+  CHECKING EVERY PIECE: SINUSURI ANG BAWAT PIRASO\n+  COMPRESS TO ZSO: I-COMPRESS SA ZSO\n+  'COMPRESSED %s: %s FREED': 'NA-COMPRESS ANG %s: %s ANG NABAKANTE'\n+  COMPRESSING 1 OF 2: NAGCO-COMPRESS 1 SA 2\n+  COMPRESSING 2 OF 2: NAGCO-COMPRESS 2 SA 2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : I-compress ang %s? Mananatili ang ISO hanggang mabasa muli at masuri ang ZSO, saka magtatanong ang RIPPS2 bago nito alisin ang ISO. Kailangan ng drive ng espasyo para sa dalawa habang gumagana; huwag patayin ang console hanggang matapos. Ang ZSO ay nagsisimula lamang sa OPL core (hindi Neutrino); mananatili rito ang mga save, setting, larawan at cheat nito, at maaari itong gawing ISO muli anumang oras.\n+  Compress to ZSO: I-compress sa ZSO\n+  Cycle: Paikot\n+  'Experimental: OPL core only (not Neutrino)': 'Eksperimental: OPL core lamang (hindi Neutrino)'\n+  Hold SELECT: Pindutin nang matagal ang SELECT\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: Hindi ito natapos (%s). Inalis ang naisulat nito; ang laro ay gaya ng dati.\n+  List: Listahan\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : Gawing ISO muli ang %s? Mananatili ang ZSO hanggang mabasa muli at masuri ang ISO, saka magtatanong ang RIPPS2 bago nito alisin ang ZSO. Kailangan ng drive ng espasyo para sa dalawa habang gumagana; huwag patayin ang console hanggang matapos. Mananatili rito ang mga save, setting, larawan at cheat nito.\n+  Stop? What it has written so far is removed; the game stays as it was.: Ihinto? Aalisin ang naisulat na; mananatili ang laro gaya ng dati.\n+  THEMES: MGA THEME\n+  There is not enough memory for this just now.: Kulang ang memory para dito ngayon.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: Hindi ma-compress o ma-uncompress ng RIPPS2 ang image na ito (ang laki nito ay hindi buong 2 KB na block).\n+  UNCOMPRESS TO ISO: I-UNCOMPRESS SA ISO\n+  UNCOMPRESSED %s: NA-UNCOMPRESS ANG %s\n+  UNCOMPRESSING: NAG-UUNCOMPRESS\n+  Uncompress to ISO: I-uncompress sa ISO\n+  Under a minute, then checked: Wala pang isang minuto, saka susuriin\n+  COPYING: KINOKOPYA\n+  SHOW AGAIN, DELETE OR COMPRESS: IPAKITA ULIT, BURAHIN O I-COMPRESS\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: IPAKITA ULIT, BURAHIN O I-UNCOMPRESS\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': Kailangan ng %s ng mga %s na libreng espasyo sa drive nito habang gumagana (mananatili ang orihinal hanggang sa dulo), at may %s na libre ang drive.\n+  Hide and Compress: Itago at i-compress\n+  'COMPRESSED AND HIDDEN: %s': 'NA-COMPRESS AT NAITAGO: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': 'Walang espasyo para sa rip na ito: kailangan nito ng mga %s, at may %s na libre ang drive.'\n+  OVER 4 GB FOR FAT32: LAMPAS 4 GB PARA SA FAT32\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: Ang drive na ito ay %s, na hindi nag-iingat ng file na 4 GB o higit pa, at ang bagong image ay magiging %s.\n+  '%s, %s clusters, %s free of %s': '%s, %s na cluster, %s libre sa %s'\n+  Delete This Sweep: Burahin ang paglipat na ito\n+  Delete %s? RIPPS2's own sweeps stay as they are.: Burahin ang %s? Mananatili ang sariling mga paglipat ng RIPPS2.\n+  Sweep: Paglipat\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: Minamarkahan ng mga badge ang mga larong may achievement sa iyong mga listahan, at kung ilan na ang na-unlock mo. Kailangan nilang naka-on ang TELEMETRY.\n labels:\n   SETTINGS_SAVED: Na-save ang settings sa %s\n   SETTINGS: Settings\n@@ -1240,7 +1404,7 @@ labels:\n   SAVE_ICONS: Mga 3D save icon\n   HINT_SAVE_ICONS: Ipinapakita ang umiikot na save icon ng napiling laro, kung saan may puwesto para dito ang theme. Binabasa muna ang sariling VMC ng laro, saka ang mga memory card.\n   NETBOOT_RESTART_NOW: Magbabago ang network protocol pagkatapos ng restart. I-save ang settings at i-restart ang OPL ngayon?\n-  HINT_NET_PROTOCOL: 'Aling network service ang ginagamit ng RiptOPL: SMB share, UDPFS, UDPBD o HTTP. Parehong setting gaya ng sa Game Sources. Kailangan ng restart para lumipat mula sa tumatakbo na.'\n+  HINT_NET_PROTOCOL: 'Aling network service ang ginagamit ng RiptOPL: SMB share, UDPFS, UDPBD o HTTP. Kailangan ng restart para lumipat mula sa tumatakbo na.'\n   HINT_UDPFS_ACCESS: UDPFS lang. Binabasa ng Files ang mga hiwalay na ISO mula sa games folder ng server; binabasa ng IMG ang disk image bilang drive. Laging Files ang gamit ng SMB at HTTP, laging IMG ang UDPBD.\n   HINT_SMB_VERSION: Aling bersyon ng SMB ang gagamitin sa PC share.\n   HINT_NET_ADVANCED: Ina-unlock ang Ethernet Link Mode at ang SMB Port para ma-edit.\n@@ -1439,3 +1603,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: Kung paano nagiging PNG ang mga kuhang naghihintay sa rolyo ng bawat laro.\n   RIPPS2_SETTINGS_HOME: I-save ang settings sa\n   HINT_RIPPS2_SETTINGS_HOME: 'Kung saan itinatago ng RIPPS2 ang settings nito: sariling save sa isang memory card, isang RIPPS2 folder sa isang drive (internal HDD, USB, MX4SIO, iLink o MMCE), o isang folder na isusulat mo.'\n+  RIPPS2_LAUNCHERS: Mga launcher\n+  RIPPS2_LOOK_NOW: Hanapin ngayon\n+  HINT_RIPPS2_LOOK_NOW: Hinahanap ng RIPPS2 sa mga nakasimulang drive ang POPSTARTER, ang mga POPS file, ang Ember, ang BIOS ng Ember at ang Neutrino, at tinatandaan kung nasaan ang bawat isa. Doon sila binabasa ng bawat launch.\n+  RIPPS2_HIDE_GAME: Itago ang laro\n+  RIPPS2_HIDDEN_GAMES: Mga nakatagong laro\n+  HINT_RIPPS2_HIDDEN_GAMES: Ang mga larong itinago gamit ang Itago ang laro, sa mga nakasimulang drive. Ipakita ulit ang isa, o burahin ito. May sariling listahan ang bawat drive.\n+  HINT_RIPPS2_BDM_DEVICES: 'Ang mga drive na binabasahan ng BDM ng laro: USB, MX4SIO, PC Card CF, ang DVR drive ng DESR at iLink. Sumusunod ang internal HDD sa start mode ng HDD.'\n+  HINT_RIPPS2_SCREEN: 'Ang larawan ng menu: video mode, widescreen, kung saan ito nasa TV (H-POS, V-POS) at overscan.'\n+  HINT_RIPPS2_LAYOUT: Kung nasaan ang mga kategorya at paano pumapasok ang pahina, pagkatapos ang mga cover ng RIPFLOW at ang grid ng RIPGRID.\n+  HINT_RIPPS2_COLORS_TEXT: Kapal ng teksto, ang kulay ng theme, ang paglipat ng kulay at bawat kulay.\n+  HINT_RIPPS2_TEXT_WEIGHT: 'Gaano kakapal ang sariling teksto ng RIPPS2: ang mga tip sa Settings, STATUS at About. Ang Karaniwan ay gaya ng dati; mas malinaw ang Katamtaman at Makapal sa malambot na larawan.'\n+  HINT_RIPPS2_HDD_START: 'Ang internal HDD, APA o exFAT: binabasa ito ng RIPPS2 anuman ito at ipinapakita nang isang beses, bilang mga laro sa HDD. Hindi ito ginagalaw ng Off.'\n+  HINT_RIPPS2_THEME_COLOR: Ang sariling kulay ng theme. Piliin ang Sarili bilang kulay ng theme para ikaw ang pumili nito, gaya ng mga kulay ng teksto.\n+  HINT_RIPPS2_SWEEP: Limang kulay na dinadaanan ng theme, mabagal at maayos. Bahaghari, isang paglipat mula sa bawat kulay ng theme, o sarili mo. Pinapanatili ng Off na tahimik ang theme.\n+  HINT_RIPPS2_SWEEP_COLORS: Ang limang kulay ng paglipat at ang bilis nito. Baguhin ang isa sa RIPPS2 at itatabi ito bilang iyo, sa pangalang ibibigay mo.\n+  HINT_RIPPS2_SWEEP_NAME: Ano ang tawag sa paglipat mo sa listahan ng Paglipat ng kulay.\n+  HINT_RIPPS2_SWEEP_COLOR: Isa sa limang kulay na dinadaanan ng paglipat, ayon sa pagkakasunod.\n+  HINT_RIPPS2_SWEEP_SPEED: 'Gaano katagal ang bawat kulay bago lumitaw ang susunod. Kahit ang Masigla ay nananatiling maayos: hindi kumikislap ang mga kulay.'\n+  RIPPS2_HIDE_COMPRESS: Itago ang laro at compression\n+  HINT_RIPPS2_SELECT_HOLD: 'SELECT na pinindot nang 4.2 s sa listahan ng laro. Listahan: ang mga theme sa isang panel, pumili ng isa. Paikot: ang susunod na theme. Naka-off: wala (nagre-refresh pa rin ang isang pindot).'"
        },
        {
          "sha": "e09937ba188a81400189b087183a0dd23061922b",
          "filename": "elf/lang/French.yml",
          "status": "modified",
          "additions": 208,
          "deletions": 22,
          "changes": 230,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FFrench.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FFrench.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FFrench.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: French\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTR\u00c9E VALIDE, \u00c9CHAP ANNULE. UN BOUTON AFFICHE LES TOUCHES.\n   USB KEYBOARD: CLAVIER USB\n-  '      DESR DVR ISOs (experimental)': '      ISO DESR DVR (exp\u00e9rimental)'\n-  '      Internal HDD (exFAT)': '      Disque dur interne (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (exp\u00e9rimental)'\n-  '      Protocol': '      Protocole'\n   Access: Acc\u00e8s\n   Applications Page Start Mode: D\u00e9marrage de la page Applications\n   Art Delay: D\u00e9lai des illustrations\n@@ -22,7 +18,6 @@ strings:\n   Fifth: Cinqui\u00e8me\n   First: Premier\n   Fourth: Quatri\u00e8me\n-  HDD (APA) Start Mode: D\u00e9marrage du disque dur (APA)\n   HDD Cache: Cache du disque dur\n   HDD Spindown: Mise en veille du disque dur\n   MMCE Settings: Param\u00e8tres MMCE\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: C'est un jeu PS2 patch\u00e9 pour ESR. Placez ESR (esr*.elf) dans APPS, BOOT ou ESR sur une carte m\u00e9moire ou une cl\u00e9 USB pour le lancer.\n   '%s, BUILD %d': '%s, BUILD %d'\n   '... and the anonymous ...': '... et les anonymes ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': '1.8.0 inclus sans modification (AFL-3.0) : github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: Une refonte de fond en comble de RiptOPL, le fork d'Open PS2 Loader,\n   ABOUT: \u00c0 PROPOS\n   'ACHIEVEMENTS: SPIRIT OF RA': 'SUCC\u00c8S : SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): Vid\u00e9o AVI dans Fichiers m\u00e9moire, le fork de RIPPS2 (AFL 2.0)\n   Apollo Save Tool by bucanero: Apollo Save Tool par bucanero\n   BACK: RETOUR\n+  Back (keeps both): Retour (garde les deux)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: Cette carte MMCE ne sait pas renommer un fichier, ce dont la compression a besoin \u00e0 sa derni\u00e8re \u00e9tape. Mettez son firmware sd2psXtd \u00e0 jour en 1.4.0 ou plus r\u00e9cent (sd2psxtd.github.io/download), puis r\u00e9essayez. Rien n'a \u00e9t\u00e9 modifi\u00e9.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): BIBLE est PS2BBL 1.2.0, les versions sign\u00e9es d'El Isra (carte et disque dur), sans modification (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: Sons du BIOS > Extraire les joue comme le SPU2 ; aucun BIOS n'est fourni\n   BOOT LOCK AND BIBLE: BOOT LOCK ET BIBLE\n   BOOT LOCK edits its config too: BOOT LOCK modifie aussi sa configuration\n   BUILD DETAILS: D\u00c9TAILS DU BUILD\n-  BUILT ON RIPTOPL: BAS\u00c9 SUR RIPTOPL\n   BearSSL by Thomas Pornin: BearSSL par Thomas Pornin\n   Built with Claude (Anthropic): Con\u00e7u avec Claude (Anthropic)\n   CD PLAYER: LECTEUR CD\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek et Sleek Bold, dessin\u00e9es pour RIPPS2\n   RetroAchievements on the console by hacan359 (yoba): RetroAchievements sur la console par hacan359 (yoba)\n   RiptOPL by NathanNeurotic: RiptOPL par NathanNeurotic\n-  Roboto Condensed by Google (Apache-2.0): Roboto Condensed par Google (Apache-2.0)\n   SOUNDS: SONS\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: l'installateur FMCB de SP193 et l'OSDMenu de pcm720, suivis pas \u00e0 pas\n   SUPPORT: ASSISTANCE\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: wLaunchELF R3Z par NathanNeurotic\n   wLaunchELF by the ps2homebrew team: wLaunchELF par l'\u00e9quipe ps2homebrew\n   what RIPPS2's card image writes are checked against: ce par rapport \u00e0 quoi les \u00e9critures d'images de carte de RIPPS2 sont v\u00e9rifi\u00e9es\n-  with its own look, launchers, file browser and save tools.: avec son propre style, ses lanceurs, son navigateur de fichiers et ses outils de sauvegarde.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: xeRAbora par hacan359, rcheevos par RetroAchievements\n   your console's own, ripped from its BIOS: ceux de votre console, extraits de son BIOS\n   ' (no DNS answer)': ' (pas de r\u00e9ponse DNS)'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO : \u00e9criture de %.60s'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: Neutrino n'a pas pu \u00eatre \u00e9crit dans %s. V\u00e9rifiez son espace libre et l'absence de protection en \u00e9criture, puis r\u00e9essayez.\n   THE GAME'S DRIVE  (%s/neutrino): LE P\u00c9RIPH\u00c9RIQUE DU JEU  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : Ce jeu d\u00e9marre via Neutrino, et il n'y a pas de Neutrino l\u00e0 o\u00f9 RIPPS2 regarde (Param\u00e8tres, le p\u00e9riph\u00e9rique du jeu, les cartes m\u00e9moire). RIPPS2 inclut Neutrino 1.8.0 par rickgaiser (AFL-3.0). L'installer maintenant ? Vous choisissez ensuite o\u00f9.\n   '%d / %d FILES': '%d / %d FICHIERS'\n   '%d FOLDERS AND CONTENTS': '%d DOSSIERS ET CONTENU'\n   '%d FOLDERS, %d FOUND': '%d DOSSIERS, %d TROUV\u00c9S'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d sur %d encore verrouill\u00e9s'\n   '%s   on %s': '%s   sur %s'\n   BADGES: BADGES\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: Les badges ne s'activent qu'avec la T\u00c9L\u00c9M\u00c9TRIE activ\u00e9e et xeRAbora lanc\u00e9 sur votre PC ; d'ici l\u00e0, ils ne co\u00fbtent rien.\n   CHECK THE DISC: V\u00c9RIFIER LE DISQUE\n   From this game's last CHECK GAME SUPPORT.: D'apr\u00e8s la derni\u00e8re V\u00c9RIFICATION DU SUPPORT DU JEU.\n   HOW IT WORKS: COMMENT \u00c7A MARCHE\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: Un PS2BBL avec le verrou de RIPPS2 est sur cette console.\n   AUDIO folder: dossier AUDIO\n   DRIVES: P\u00c9RIPH\u00c9RIQUES\n-  INSTALL RIPPS2'S COPY: INSTALLER LA COPIE DE RIPPS2\n-  'Installed: ': 'Install\u00e9 : '\n   MANAGE: G\u00c9RER\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: G\u00c9RER montre ce que lance chaque carte m\u00e9moire, et installe ou r\u00e9pare BIBLE.\n   MEMORY CARD 0  (about 600 KB): CARTE M\u00c9MOIRE 0  (environ 600 Ko)\n   MEMORY CARD 1  (about 600 KB): CARTE M\u00c9MOIRE 1  (environ 600 Ko)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO : son pilote n''a pas pu se charger (code %d).'\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO : le pilote est charg\u00e9, mais aucune carte SD n''a r\u00e9pondu (l''emplacement mc1).'\n   NEUTRINO SETTINGS: PARAM\u00c8TRES DE NEUTRINO\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': 'Aucun pour l''instant : RIPPS2 n''a aucun son \u00e0 lui. Extrayez ceux de cette console, ou cr\u00e9ez-les dans RIPPS2 Audio Studio.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: Non install\u00e9. Un jeu qui en a besoin propose la copie de RIPPS2, ou installez-le ici.\n   POPSTARTER NETWORK: R\u00c9SEAU POPSTARTER\n   POPSTARTER and Ember: POPSTARTER et Ember\n   PS1 SETTINGS: PARAM\u00c8TRES PS1\n   PS2BBL 1.2.0 for RIPPS2: PS2BBL 1.2.0 pour RIPPS2\n   RIP THIS CONSOLE'S SOUNDS: EXTRAIRE LES SONS DE CETTE CONSOLE\n-  RIPPS2's homebrew: Les homebrews de RIPPS2\n   STATUS: \u00c9TAT\n-  'STATUS: looking for RIPPS2''s homebrew': '\u00c9TAT : recherche des homebrews de RIPPS2'\n   a drive of unknown kind: un p\u00e9riph\u00e9rique de type inconnu\n-  a drive or a memory card: un p\u00e9riph\u00e9rique ou une carte m\u00e9moire\n   boot loader, card by card: chargeur de d\u00e9marrage, carte par carte\n   from its BIOS, into AUDIO: depuis son BIOS, dans AUDIO\n   internal HDD (exFAT): disque dur interne (exFAT)\n@@ -975,10 +958,193 @@ strings:\n   Sidebar: Barre lat\u00e9rale\n   your own copy, found on your drives and memory cards: votre propre copie, trouv\u00e9e sur vos disques et cartes m\u00e9moire\n   'your own, found and launched: github.com/Gageformer/Ember/releases': 'la v\u00f4tre, trouv\u00e9e et lanc\u00e9e : github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': 'Introuvable sur mass0: (%s). Placez ember.elf et votre bios.bin dans son dossier EMBER.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: Introuvable. Placez votre propre POPSTARTER.ELF dans le dossier POPS d'un disque.\n   Launch Disc: Lancer le disque\n   Memory Files: Fichiers m\u00e9moire\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: RIPPS2 en a trouv\u00e9 %d sur 5. Chacun est retenu, et chaque lancement le lit \u00e0 cet endroit.\n+  not found on the started drives: introuvable sur les p\u00e9riph\u00e9riques d\u00e9marr\u00e9s\n+  not looked for yet: pas encore cherch\u00e9\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s n''a pas r\u00e9pondu, donc %s ne peut pas d\u00e9marrer. Est-il branch\u00e9 ?'\n+  '%s is not on %s any more, so it has left All Games.': '%s n''est plus sur %s, il a donc quitt\u00e9 Tous les jeux.'\n+  STARTING %s: D\u00c9MARRAGE DE %s\n+  the SMB share: le partage SMB\n+  the drive: le p\u00e9riph\u00e9rique\n+  the internal HDD (APA): le disque dur interne (APA)\n+  the internal HDD (exFAT): le disque dur interne (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: B\u00c2TI SUR RIPTOPL, AVEC NEUTRINO\n+  'Special thanks to all the YouTube love as well:': 'Merci aussi \u00e0 tout l''amour de YouTube :'\n+  and its own look, launchers, file browser and save tools.: et son propre look, ses lanceurs, son explorateur et ses outils de sauvegarde.\n+  glad to help however you reach me.: heureux d'aider, quelle que soit la fa\u00e7on de me joindre.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): le second c\u0153ur de jeu, 1.8.0 int\u00e9gr\u00e9 sans modification (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: avec Neutrino de rickgaiser \u00e0 l'int\u00e9rieur comme second c\u0153ur de jeu,\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : 'Ce jeu d\u00e9marre avec Neutrino, et RIPPS2 n''en a trouv\u00e9 aucun : ni au chemin des Param\u00e8tres, ni sur le p\u00e9riph\u00e9rique du jeu, ni sur les cartes m\u00e9moire, ni sur un p\u00e9riph\u00e9rique d\u00e9marr\u00e9. RIPPS2 embarque Neutrino 1.8.0 de rickgaiser (AFL-3.0) : installez-le une fois, o\u00f9 vous voulez, et chaque jeu Neutrino l''utilise depuis l\u00e0.'\n+  STARTING THE DRIVES: D\u00c9MARRAGE DES P\u00c9RIPH\u00c9RIQUES\n+  '%d games, on the started drives': '%d jeux, sur les p\u00e9riph\u00e9riques d\u00e9marr\u00e9s'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s : CERTAINS FICHIERS N''ONT PAS PU \u00caTRE SUPPRIM\u00c9S'\n+  '%u partitions, all in order': '%u partitions, toutes en ordre'\n+  ', %d art images': ', %d illustrations'\n+  ', and ': ', et '\n+  ', its cheats': ', ses codes de triche'\n+  ', its settings': ', ses param\u00e8tres'\n+  1 game, on the started drives: 1 jeu, sur les p\u00e9riph\u00e9riques d\u00e9marr\u00e9s\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: Un jeu PS1 se supprime ici dans Fichiers m\u00e9moire, o\u00f9 c'est un fichier \u00e0 part enti\u00e8re.\n+  A network catalogue's games are deleted on the PC that serves them.: Les jeux d'un catalogue r\u00e9seau se suppriment sur le PC qui les sert.\n+  DELETE FOR GOOD: SUPPRIMER POUR DE BON\n+  DELETE IT: LE SUPPRIMER\n+  'DELETED %s: %s FREED': '%s SUPPRIM\u00c9 : %s LIB\u00c9R\u00c9S'\n+  'Delete %s from %s? Goes: %s.': 'Supprimer %s de %s ? Part : %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': 'Supprimer %s de %s ? Part : %s. Reste : %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: Supprimer aussi ses sauvegardes et ses images RIPSHOT ? Non les garde.\n+  HIDDEN GAMES: JEUX MASQU\u00c9S\n+  'HIDDEN: %s': 'MASQU\u00c9 : %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: Masquer %s ? Il quitte toutes les listes, Tous les jeux et Jeux favoris aussi. Param\u00e8tres > Sources de jeux > Jeux masqu\u00e9s le fait revenir.\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: Derni\u00e8re v\u00e9rification. %s est supprim\u00e9 pour de bon, et rien ne pourra le ramener. Appuyez sur START pour le supprimer.\n+  None. Hide Game, in a game's options, puts one here.: Aucun. Masquer le jeu, dans les options d'un jeu, en place un ici.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : 'Rien n''a \u00e9t\u00e9 supprim\u00e9 : la table des partitions de ce p\u00e9riph\u00e9rique ne correspond pas \u00e0 ce disque (%s). Elle a peut-\u00eatre \u00e9t\u00e9 \u00e9crite depuis l''image d''un disque d''une autre taille. R\u00e9parez-la d''abord sur un PC.'\n+  RIPPS2 cannot delete a game from this page.: RIPPS2 ne peut pas supprimer un jeu depuis cette page.\n+  RIPPS2 could not find this game's files on its drive.: RIPPS2 n'a pas trouv\u00e9 les fichiers de ce jeu sur son p\u00e9riph\u00e9rique.\n+  Reading the hidden lists: Lecture des listes masqu\u00e9es\n+  SHOW AGAIN OR DELETE: AFFICHER \u00c0 NOUVEAU OU SUPPRIMER\n+  SHOW IT AGAIN: L'AFFICHER \u00c0 NOUVEAU\n+  That game is not on its drive any more.: Ce jeu n'est plus sur son p\u00e9riph\u00e9rique.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: La liste masqu\u00e9e n'a pas pu \u00eatre \u00e9crite sur le p\u00e9riph\u00e9rique du jeu. Est-il prot\u00e9g\u00e9 en \u00e9criture, ou les Op\u00e9rations d'\u00e9criture sont-elles d\u00e9sactiv\u00e9es ?\n+  The partition could not be removed (%d). Nothing was deleted.: La partition n'a pas pu \u00eatre supprim\u00e9e (%d). Rien n'a \u00e9t\u00e9 supprim\u00e9.\n+  This drive cannot keep a hidden list (a network catalogue).: Ce p\u00e9riph\u00e9rique ne peut pas garder de liste masqu\u00e9e (un catalogue r\u00e9seau).\n+  This game's drive has not answered yet. Wait for it, then try again.: Le p\u00e9riph\u00e9rique de ce jeu n'a pas encore r\u00e9pondu. Attendez-le, puis r\u00e9essayez.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: La partition de ce jeu est une de celles que RIPPS2 ne supprime pas (une partition syst\u00e8me, ou celle o\u00f9 sont ses param\u00e8tres).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': 'une partition est plus grande que ce disque ne le permet : la table a \u00e9t\u00e9 faite pour un disque d''une autre taille'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': 'une partition d\u00e9passe la fin de ce disque (secteur %u sur %u) : la table a \u00e9t\u00e9 faite pour un disque plus grand'\n+  its RIPSHOT pictures: ses images RIPSHOT\n+  its drive: son p\u00e9riph\u00e9rique\n+  its saves: ses sauvegardes\n+  sector %u holds no partition header: le secteur %u ne contient aucun en-t\u00eate de partition\n+  the chain does not close at __mbr: la cha\u00eene ne se referme pas sur __mbr\n+  the chain skips from sector %u: la cha\u00eene saute depuis le secteur %u\n+  the chain's links disagree at sector %u: les liens de la cha\u00eene ne concordent pas au secteur %u\n+  the drive's size could not be read: la taille du p\u00e9riph\u00e9rique n'a pas pu \u00eatre lue\n+  the game (%s): le jeu (%s)\n+  the game's folder and everything in it (%s): le dossier du jeu et tout son contenu (%s)\n+  the header at sector %u could not be read: l'en-t\u00eate au secteur %u n'a pas pu \u00eatre lu\n+  the header at sector %u fails its checksum: l'en-t\u00eate au secteur %u \u00e9choue \u00e0 sa somme de contr\u00f4le\n+  the partition at sector %u is not where or as large as APA puts one: la partition au secteur %u n'est pas l\u00e0 o\u00f9 APA en place une, ou pas de cette taille\n+  the settings and art another copy of it uses: les param\u00e8tres et illustrations qu'une autre copie utilise\n+  '%d FOLDERS': '%d DOSSIERS'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': '%s n''a pas pu \u00eatre copi\u00e9 vers %s. Le p\u00e9riph\u00e9rique est-il plein ou prot\u00e9g\u00e9 en \u00e9criture ?'\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s n''est pas l\u00e0 o\u00f9 RIPPS2 cherche. Chercher sur les p\u00e9riph\u00e9riques d\u00e9marr\u00e9s par RIPPS2 ? L''endroit trouv\u00e9 est retenu pour chaque lancement suivant.'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Ember n''est pas dans %s, \u00e0 c\u00f4t\u00e9 des jeux, et il doit y \u00eatre. RIPPS2 l''a trouv\u00e9 \u00e0 %s : START l''y copie (%d Ko) et lance le jeu.'\n+  Ember's BIOS: BIOS d'Ember\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Le BIOS d''Ember (bios.bin) n''est pas dans %s, \u00e0 c\u00f4t\u00e9 d''Ember. RIPPS2 en a trouv\u00e9 un \u00e0 %s : START l''y copie (%d Ko) et lance le jeu.'\n+  LOOKING FOR YOUR LAUNCHERS: RECHERCHE DE VOS LANCEURS\n+  The POPS files: Les fichiers POPS\n+  '%s  MMCE card': '%s  carte MMCE'\n+  INSTALL RIPPS2'S NEUTRINO: INSTALLER LE NEUTRINO DE RIPPS2\n+  'Internal HDD (APA): %s free of %s': 'Disque dur interne (APA) : %s libres sur %s'\n+  LAUNCHERS: LANCEURS\n+  LOOK FOR THEM: LES CHERCHER\n+  'Memory card %d: %d KB free': 'Carte m\u00e9moire %d : %d Ko libres'\n+  POPS files: Fichiers POPS\n+  START EVERY DRIVE: D\u00c9MARRER TOUS LES P\u00c9RIPH\u00c9RIQUES\n+  'STATUS: looking at the drives and the homebrew': '\u00c9TAT : examen des p\u00e9riph\u00e9riques et des homebrews'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): D\u00e9marrer d'abord tous les p\u00e9riph\u00e9riques, pour que la recherche les atteigne tous ? (Non cherche sur les p\u00e9riph\u00e9riques d\u00e9j\u00e0 d\u00e9marr\u00e9s.)\n+  USB, HDD, MMCE and the rest: USB, disque dur, MMCE et le reste\n+  every started drive: chaque p\u00e9riph\u00e9rique d\u00e9marr\u00e9\n+  found where you keep them: trouv\u00e9s l\u00e0 o\u00f9 vous les gardez\n+  not found yet: pas encore trouv\u00e9\n+  not on a started drive: pas sur un p\u00e9riph\u00e9rique d\u00e9marr\u00e9\n+  only if you have none: seulement si vous n'en avez pas\n+  starts every drive first: d\u00e9marre d'abord tous les p\u00e9riph\u00e9riques\n+  Drives, launchers and homebrew: P\u00e9riph\u00e9riques, lanceurs et homebrews\n+  BDM Devices: P\u00e9riph\u00e9riques BDM\n+  Color Sweep: Fondu de couleurs\n+  Colors and Text: Couleurs et texte\n+  HDD POPS partitions: Partitions POPS du disque dur\n+  Layout: Disposition\n+  Missing Covers: Jaquettes manquantes\n+  Name: Nom\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: Aucune configuration SMB de POPSTARTER trouv\u00e9e. Remplissez les lignes ci-dessous.\n+  POPSTARTER Network: R\u00e9seau POPSTARTER\n+  POPSTARTER SMB Network: R\u00e9seau SMB de POPSTARTER\n+  POPSTARTER UDPFS Network: R\u00e9seau UDPFS de POPSTARTER\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: RIPPS2 ajoute lui-m\u00eame le p\u00e9riph\u00e9rique, l'ISO, les VMC et le mode vid\u00e9o.\n+  Screen: \u00c9cran\n+  Speed: Vitesse\n+  Sweep Colors: Couleurs du fondu\n+  Text Weight: Graisse du texte\n+  Theme Color: Couleur principale\n+  Bold: Gras\n+  Controllers: Manettes\n+  Lively: Vif\n+  Medium: Moyen\n+  PS Emulation: \u00c9mulation PS\n+  Regular: Normal\n+  Slow: Lent\n+  A MOMENT: UN INSTANT\n+  A TASK IN THE BACKGROUND: UNE T\u00c2CHE EN ARRI\u00c8RE-PLAN\n+  ABOUT %d S: ENVIRON %d S\n+  ANY MOMENT: D'UN INSTANT \u00c0 L'AUTRE\n+  READING THE GAME LIST OF %s: LECTURE DE LA LISTE DES JEUX DE %s\n+  SAVING A LIST: ENREGISTREMENT D'UNE LISTE\n+  START NOW: D\u00c9MARRER MAINTENANT\n+  STARTING: D\u00c9MARRAGE\n+  STARTS NEXT: D\u00c9MARRE ENSUITE\n+  STOPPING IT SAFELY: ARR\u00caT EN S\u00c9CURIT\u00c9\n+  THE LAST OF THE QUEUE: LA FIN DE LA FILE\n+  WHEN IT FINISHES: QUAND CE SERA FINI\n+  What each card starts; install or repair BIBLE.: Ce que lance chaque carte ; installer ou r\u00e9parer BIBLE.\n+  Rainbow: Arc-en-ciel\n+  Custom: Personnalis\u00e9e\n+  DESR DVR ISOs (experimental): ISO DESR DVR (exp\u00e9rimental)\n+  PC CARD CF (experimental): PC CARD CF (exp\u00e9rimental)\n+  HDD Start Mode: D\u00e9marrage du disque dur\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): Roboto Condensed par The Roboto Project Authors (SIL OFL 1.1)\n+  '%s on %s': '%s sur %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s : %s est devenu %s (%d%%), chaque morceau v\u00e9rifi\u00e9. Supprimer l''ISO et garder le ZSO ?'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s : %s est redevenu %s, chaque morceau v\u00e9rifi\u00e9. Supprimer le ZSO et garder l''ISO ?'\n+  A ZSO of this game is already beside it. Remove one of the two first.: Un ZSO de ce jeu est d\u00e9j\u00e0 \u00e0 c\u00f4t\u00e9. Supprimez d'abord l'un des deux.\n+  About %d min, then checked: Environ %d min, puis v\u00e9rifi\u00e9\n+  An ISO of this game is already beside it. Remove one of the two first.: Un ISO de ce jeu est d\u00e9j\u00e0 \u00e0 c\u00f4t\u00e9. Supprimez d'abord l'un des deux.\n+  CHECKING EVERY PIECE: V\u00c9RIFICATION DE CHAQUE MORCEAU\n+  COMPRESS TO ZSO: COMPRESSER EN ZSO\n+  'COMPRESSED %s: %s FREED': '%s COMPRESS\u00c9 : %s LIB\u00c9R\u00c9S'\n+  COMPRESSING 1 OF 2: COMPRESSION 1 SUR 2\n+  COMPRESSING 2 OF 2: COMPRESSION 2 SUR 2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : Compresser %s ? L'ISO reste jusqu'\u00e0 ce que le ZSO ait \u00e9t\u00e9 relu et v\u00e9rifi\u00e9, puis RIPPS2 demande avant de supprimer l'ISO. Le disque a besoin de place pour les deux pendant le travail ; laissez la console allum\u00e9e jusqu'\u00e0 la fin. Un ZSO ne d\u00e9marre qu'avec le c\u0153ur OPL (pas Neutrino) ; ses sauvegardes, r\u00e9glages, images et codes restent avec lui, et il peut redevenir un ISO \u00e0 tout moment.\n+  Compress to ZSO: Compresser en ZSO\n+  Cycle: D\u00e9filement\n+  'Experimental: OPL core only (not Neutrino)': 'Exp\u00e9rimental : c\u0153ur OPL seulement (pas Neutrino)'\n+  Hold SELECT: SELECT maintenu\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: Impossible de terminer (%s). Ce qui a \u00e9t\u00e9 \u00e9crit a \u00e9t\u00e9 supprim\u00e9 ; le jeu est comme avant.\n+  List: Liste\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : Refaire de %s un ISO ? Le ZSO reste jusqu'\u00e0 ce que l'ISO ait \u00e9t\u00e9 relu et v\u00e9rifi\u00e9, puis RIPPS2 demande avant de supprimer le ZSO. Le disque a besoin de place pour les deux pendant le travail ; laissez la console allum\u00e9e jusqu'\u00e0 la fin. Ses sauvegardes, r\u00e9glages, images et codes restent avec lui.\n+  Stop? What it has written so far is removed; the game stays as it was.: Arr\u00eater ? Ce qui a \u00e9t\u00e9 \u00e9crit jusqu'ici est supprim\u00e9 ; le jeu reste comme avant.\n+  THEMES: TH\u00c8MES\n+  There is not enough memory for this just now.: Pas assez de m\u00e9moire pour cela pour le moment.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: RIPPS2 ne peut pas compresser ni d\u00e9compresser cette image (sa taille n'est pas un nombre entier de blocs de 2 KB).\n+  UNCOMPRESS TO ISO: D\u00c9COMPRESSER EN ISO\n+  UNCOMPRESSED %s: '%s D\u00c9COMPRESS\u00c9'\n+  UNCOMPRESSING: D\u00c9COMPRESSION\n+  Uncompress to ISO: D\u00e9compresser en ISO\n+  Under a minute, then checked: Moins d'une minute, puis v\u00e9rifi\u00e9\n+  COPYING: COPIE\n+  SHOW AGAIN, DELETE OR COMPRESS: AFFICHER, SUPPRIMER OU COMPRESSER\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: AFFICHER, SUPPRIMER OU D\u00c9COMPRESSER\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': '%s a besoin d''environ %s d''espace libre sur son disque pendant le travail (l''original reste jusqu''\u00e0 la fin), et le disque a %s de libre.'\n+  Hide and Compress: Masquer et compresser\n+  'COMPRESSED AND HIDDEN: %s': 'COMPRESS\u00c9 ET MASQU\u00c9 : %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': 'Pas assez de place pour cette copie : il faut environ %s, et le disque a %s de libre.'\n+  OVER 4 GB FOR FAT32: PLUS DE 4 GB POUR FAT32\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: Ce disque est en %s, qui ne garde aucun fichier de 4 GB ou plus, et la nouvelle image ferait %s.\n+  '%s, %s clusters, %s free of %s': '%s, clusters de %s, %s libres sur %s'\n+  Delete This Sweep: Supprimer ce fondu\n+  Delete %s? RIPPS2's own sweeps stay as they are.: Supprimer %s ? Les fondus de RIPPS2 restent tels quels.\n+  Sweep: Fondu\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: Les badges marquent les jeux \u00e0 succ\u00e8s dans vos listes, avec combien vous en avez d\u00e9bloqu\u00e9. Il faut la T\u00c9L\u00c9M\u00c9TRIE activ\u00e9e.\n labels:\n   HINT_NEUTRINO_AUTO_ELF: Transmet l'ELF de d\u00e9marrage du jeu \u00e0 Neutrino pour chercher sa configuration par titre. Activ\u00e9 par d\u00e9faut ; une option Boot ELF r\u00e9gl\u00e9e \u00e0 la main a la priorit\u00e9.\n   HINT_POPSTARTER_PATH: Emplacement de POPSTARTER.ELF. Laiss\u00e9 vide, RIPPS2 en renseigne un qu'il trouve sur un p\u00e9riph\u00e9rique ; \u00e9crivez par-dessus pour en utiliser un autre, comme usb:/POPS/POPSTARTER.ELF\n@@ -1200,7 +1366,7 @@ labels:\n   SAVE_ICONS: Ic\u00f4nes de sauvegarde 3D\n   HINT_SAVE_ICONS: Affiche l'ic\u00f4ne de sauvegarde du jeu choisi, en rotation, l\u00e0 o\u00f9 le th\u00e8me lui pr\u00e9voit une place. Lit d'abord la VMC du jeu, puis les cartes m\u00e9moire.\n   NETBOOT_RESTART_NOW: Le protocole r\u00e9seau change apr\u00e8s un red\u00e9marrage. Enregistrer les param\u00e8tres et red\u00e9marrer OPL maintenant ?\n-  HINT_NET_PROTOCOL: 'Le service r\u00e9seau qu''utilise RiptOPL : un partage SMB, UDPFS, UDPBD ou HTTP. Le m\u00eame r\u00e9glage que dans Sources de jeux. Quitter un service d\u00e9j\u00e0 en marche demande un red\u00e9marrage.'\n+  HINT_NET_PROTOCOL: 'Le service r\u00e9seau qu''utilise RiptOPL : un partage SMB, UDPFS, UDPBD ou HTTP. Quitter un service d\u00e9j\u00e0 en marche demande un red\u00e9marrage.'\n   HINT_UDPFS_ACCESS: UDPFS seulement. Fichiers lit les ISO isol\u00e9es du dossier des jeux du serveur ; IMG lit une image disque comme un p\u00e9riph\u00e9rique. SMB et HTTP utilisent toujours Fichiers, UDPBD toujours IMG.\n   HINT_SMB_VERSION: La version de SMB \u00e0 utiliser avec le partage du PC.\n   HINT_NET_ADVANCED: D\u00e9bloque la modification du mode de liaison Ethernet et du port SMB.\n@@ -1399,3 +1565,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: Comment les clich\u00e9s en attente sur la pellicule de chaque jeu deviennent des PNG.\n   RIPPS2_SETTINGS_HOME: Enregistrer les param\u00e8tres sur\n   HINT_RIPPS2_SETTINGS_HOME: 'O\u00f9 RIPPS2 garde ses param\u00e8tres : sa propre sauvegarde sur une carte m\u00e9moire, un dossier RIPPS2 sur un lecteur (le disque dur interne, USB, MX4SIO, iLink ou MMCE), ou un dossier que vous saisissez.'\n+  RIPPS2_LAUNCHERS: Lanceurs\n+  RIPPS2_LOOK_NOW: Chercher maintenant\n+  HINT_RIPPS2_LOOK_NOW: RIPPS2 cherche sur les p\u00e9riph\u00e9riques d\u00e9marr\u00e9s POPSTARTER, les fichiers POPS, Ember, le BIOS d'Ember et Neutrino, et retient o\u00f9 se trouve chacun. Chaque lancement les lit \u00e0 cet endroit.\n+  RIPPS2_HIDE_GAME: Masquer le jeu\n+  RIPPS2_HIDDEN_GAMES: Jeux masqu\u00e9s\n+  HINT_RIPPS2_HIDDEN_GAMES: Les jeux masqu\u00e9s avec Masquer le jeu, sur les p\u00e9riph\u00e9riques d\u00e9marr\u00e9s. Affichez-en un \u00e0 nouveau, ou supprimez-le. Chaque p\u00e9riph\u00e9rique garde sa propre liste.\n+  HINT_RIPPS2_BDM_DEVICES: 'Les p\u00e9riph\u00e9riques o\u00f9 BDM lit les jeux : USB, MX4SIO, une carte PC Card CF, le disque DVR d''un DESR et iLink. Le disque dur interne suit le d\u00e9marrage du disque dur.'\n+  HINT_RIPPS2_SCREEN: 'L''image du menu : mode vid\u00e9o, \u00e9cran large, sa place sur le t\u00e9l\u00e9viseur (H-POS, V-POS) et overscan.'\n+  HINT_RIPPS2_LAYOUT: O\u00f9 se placent les cat\u00e9gories et comment une page arrive, puis les jaquettes de RIPFLOW et la grille de RIPGRID.\n+  HINT_RIPPS2_COLORS_TEXT: Graisse du texte, la couleur du th\u00e8me, le fondu de couleurs et chaque couleur.\n+  HINT_RIPPS2_TEXT_WEIGHT: 'L''\u00e9paisseur du texte de RIPPS2 lui-m\u00eame : les infobulles des param\u00e8tres, \u00c9TAT et \u00c0 propos. Normal est comme avant ; Moyen et Gras se lisent mieux sur une image douce.'\n+  HINT_RIPPS2_HDD_START: 'Le disque dur interne, APA ou exFAT : RIPPS2 le lit quel qu''il soit et l''affiche une seule fois, comme Jeux du disque dur. D\u00e9sactiv\u00e9 le laisse tranquille.'\n+  HINT_RIPPS2_THEME_COLOR: La couleur propre au th\u00e8me. Choisissez Personnalis\u00e9e comme couleur du th\u00e8me pour la choisir vous-m\u00eame, comme les couleurs du texte.\n+  HINT_RIPPS2_SWEEP: Cinq couleurs que le th\u00e8me traverse, lentement et en douceur. Arc-en-ciel, un fondu tir\u00e9 de chaque couleur de th\u00e8me, ou le v\u00f4tre. D\u00e9sactiv\u00e9 garde le th\u00e8me immobile.\n+  HINT_RIPPS2_SWEEP_COLORS: Les cinq couleurs du fondu et sa vitesse. Modifiez-en un de RIPPS2 et il est gard\u00e9 comme le v\u00f4tre, sous le nom que vous lui donnez.\n+  HINT_RIPPS2_SWEEP_NAME: Le nom de votre fondu dans la liste Fondu de couleurs.\n+  HINT_RIPPS2_SWEEP_COLOR: L'une des cinq couleurs que traverse le fondu, dans l'ordre.\n+  HINT_RIPPS2_SWEEP_SPEED: 'La dur\u00e9e de chaque couleur avant que la suivante n''apparaisse. M\u00eame Vif reste doux : les couleurs ne clignotent jamais.'\n+  RIPPS2_HIDE_COMPRESS: Masquer le jeu et compression\n+  HINT_RIPPS2_SELECT_HOLD: 'SELECT maintenu 4.2 s sur une liste de jeux. Liste : les th\u00e8mes dans un panneau, choisissez-en un. D\u00e9filement : le th\u00e8me suivant. D\u00e9sactiv\u00e9 : rien (un appui actualise toujours).'"
        },
        {
          "sha": "b5614850c69da2f5bce7512489aedab100c9da7f",
          "filename": "elf/lang/Galician.yml",
          "status": "modified",
          "additions": 208,
          "deletions": 22,
          "changes": 230,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FGalician.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FGalician.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FGalician.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: Galician\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTER ACEPTA, ESC CANCELA. CALQUERA BOT\u00d3N MOSTRA AS TECLAS.\n   USB KEYBOARD: TECLADO USB\n-  '      DESR DVR ISOs (experimental)': '      ISO de DESR DVR (experimental)'\n-  '      Internal HDD (exFAT)': '      HDD interno (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (experimental)'\n-  '      Protocol': '      Protocolo'\n   Access: Acceso\n   Applications Page Start Mode: Modo de inicio da p\u00e1xina de aplicaci\u00f3ns\n   Art Delay: Atraso das imaxes\n@@ -22,7 +18,6 @@ strings:\n   Fifth: Quinto\n   First: Primeiro\n   Fourth: Cuarto\n-  HDD (APA) Start Mode: Modo de inicio do HDD (APA)\n   HDD Cache: Cach\u00e9 do HDD\n   HDD Spindown: Parada do HDD\n   MMCE Settings: Configuraci\u00f3n de MMCE\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: Este \u00e9 un xogo de PS2 parcheado con ESR. Po\u00f1a ESR (esr*.elf) en APPS, BOOT ou ESR nunha tarxeta de memoria ou memoria USB para inicialo.\n   '%s, BUILD %d': '%s, COMPILACI\u00d3N %d'\n   '... and the anonymous ...': '... e os an\u00f3nimos ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': '1.8.0 inclu\u00eddo sen modificar (AFL-3.0): github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: Unha desmontaxe e reconstruci\u00f3n de RiptOPL, a bifurcaci\u00f3n de Open PS2 Loader,\n   ABOUT: SOBRE\n   'ACHIEVEMENTS: SPIRIT OF RA': 'LOGROS: SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): V\u00eddeo AVI en Ficheiros da memoria, bifurcaci\u00f3n de RIPPS2 (AFL 2.0)\n   Apollo Save Tool by bucanero: Apollo Save Tool de bucanero\n   BACK: VOLVER\n+  Back (keeps both): Volver (garda os dous)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: Esta tarxeta MMCE non pode renomear un ficheiro, algo que a compresi\u00f3n precisa no seu \u00faltimo paso. Actualiza o seu firmware sd2psXtd \u00e1 1.4.0 ou posterior (sd2psxtd.github.io/download) e t\u00e9ntao de novo. Non se cambiou nada.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): BIBLE \u00e9 PS2BBL 1.2.0, as compilaci\u00f3ns asinadas de El Isra (tarxeta e HDD), sen cambios (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: Sons da BIOS > Extraer reprod\u00faceos como fai o SPU2; non se incl\u00fae ningunha BIOS\n   BOOT LOCK AND BIBLE: BOOT LOCK E BIBLE\n   BOOT LOCK edits its config too: BOOT LOCK tam\u00e9n edita a s\u00faa configuraci\u00f3n\n   BUILD DETAILS: DETALLES DA COMPILACI\u00d3N\n-  BUILT ON RIPTOPL: CONSTRU\u00cdDO SOBRE RIPTOPL\n   BearSSL by Thomas Pornin: BearSSL de Thomas Pornin\n   Built with Claude (Anthropic): Feito con Claude (Anthropic)\n   CD PLAYER: REPRODUTOR DE CD\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek e Sleek Bold, debuxadas para RIPPS2\n   RetroAchievements on the console by hacan359 (yoba): RetroAchievements na consola de hacan359 (yoba)\n   RiptOPL by NathanNeurotic: RiptOPL de NathanNeurotic\n-  Roboto Condensed by Google (Apache-2.0): Roboto Condensed de Google (Apache-2.0)\n   SOUNDS: SONS\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: O instalador FMCB de SP193 e o OSDMenu de pcm720, seguidos paso a paso\n   SUPPORT: APOIO\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: wLaunchELF R3Z de NathanNeurotic\n   wLaunchELF by the ps2homebrew team: wLaunchELF do equipo ps2homebrew\n   what RIPPS2's card image writes are checked against: co que se comproban as imaxes de tarxeta que escribe RIPPS2\n-  with its own look, launchers, file browser and save tools.: co seu propio aspecto, lanzadores, navegador de ficheiros e ferramentas de gardados.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: xeRAbora de hacan359, rcheevos de RetroAchievements\n   your console's own, ripped from its BIOS: os propios sons da s\u00faa consola, extra\u00eddos da s\u00faa BIOS\n   ' (no DNS answer)': ' (sen resposta de DNS)'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO: escribindo %.60s'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: Non se puido escribir Neutrino en %s. Comprobe o seu espazo libre e que non estea protexido contra escritura, e int\u00e9nteo de novo.\n   THE GAME'S DRIVE  (%s/neutrino): A UNIDADE DO XOGO  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : Este xogo in\u00edciase a trav\u00e9s de Neutrino, e non hai Neutrino onde busca RIPPS2 (Configuraci\u00f3n, a unidade do xogo, as tarxetas de memoria). RIPPS2 leva Neutrino 1.8.0 de rickgaiser (AFL-3.0). Instalalo agora? Despois escolle onde.\n   '%d / %d FILES': '%d / %d FICHEIROS'\n   '%d FOLDERS AND CONTENTS': '%d CARTAFOLES E CONTIDO'\n   '%d FOLDERS, %d FOUND': '%d CARTAFOLES, %d ATOPADOS'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d de %d a\u00ednda bloqueados'\n   '%s   on %s': '%s   en %s'\n   BADGES: INSIGNIAS\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: As insignias s\u00f3 funcionan coa TELEMETR\u00cdA activada e xeRAbora execut\u00e1ndose no seu ordenador; ata ent\u00f3n non custan nada.\n   CHECK THE DISC: COMPROBAR O DISCO\n   From this game's last CHECK GAME SUPPORT.: Da \u00faltima COMPROBACI\u00d3N DE COMPATIBILIDADE deste xogo.\n   HOW IT WORKS: COMO FUNCIONA\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: Esta consola ten un PS2BBL co bloqueo de RIPPS2.\n   AUDIO folder: cartafol AUDIO\n   DRIVES: UNIDADES\n-  INSTALL RIPPS2'S COPY: INSTALAR A COPIA DE RIPPS2\n-  'Installed: ': 'Instalado: '\n   MANAGE: XESTIONAR\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: XESTIONAR mostra o que inicia cada tarxeta de memoria, e instala ou repara BIBLE.\n   MEMORY CARD 0  (about 600 KB): TARXETA DE MEMORIA 0  (uns 600 KB)\n   MEMORY CARD 1  (about 600 KB): TARXETA DE MEMORIA 1  (uns 600 KB)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO: o seu controlador non quixo cargar (c\u00f3digo %d).'\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO: o controlador est\u00e1 cargado, pero ningunha tarxeta SD respondeu (a ranura mc1).'\n   NEUTRINO SETTINGS: CONFIGURACI\u00d3N DE NEUTRINO\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': 'A\u00ednda ning\u00fan: RIPPS2 non ten sons propios. Extraia os desta consola, ou f\u00e1gaos en RIPPS2 Audio Studio.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: Non instalado. Un xogo que o precise ofrece a copia de RIPPS2, ou inst\u00e1lelo aqu\u00ed.\n   POPSTARTER NETWORK: REDE DE POPSTARTER\n   POPSTARTER and Ember: POPSTARTER e Ember\n   PS1 SETTINGS: CONFIGURACI\u00d3N DE PS1\n   PS2BBL 1.2.0 for RIPPS2: PS2BBL 1.2.0 para RIPPS2\n   RIP THIS CONSOLE'S SOUNDS: EXTRAER OS SONS DESTA CONSOLA\n-  RIPPS2's homebrew: O homebrew de RIPPS2\n   STATUS: ESTADO\n-  'STATUS: looking for RIPPS2''s homebrew': 'ESTADO: buscando o homebrew de RIPPS2'\n   a drive of unknown kind: unha unidade de tipo desco\u00f1ecido\n-  a drive or a memory card: unha unidade ou unha tarxeta de memoria\n   boot loader, card by card: cargador de arranque, tarxeta por tarxeta\n   from its BIOS, into AUDIO: da s\u00faa BIOS, a AUDIO\n   internal HDD (exFAT): HDD interno (exFAT)\n@@ -975,10 +958,193 @@ strings:\n   Sidebar: Barra lateral\n   your own copy, found on your drives and memory cards: a s\u00faa propia copia, atopada nas s\u00faas unidades e tarxetas de memoria\n   'your own, found and launched: github.com/Gageformer/Ember/releases': 'o seu, atopado e iniciado: github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': 'Non se atopou en mass0: (%s). Po\u00f1a ember.elf e o seu bios.bin no seu cartafol EMBER.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: Non se atopou. Po\u00f1a o seu propio POPSTARTER.ELF no cartafol POPS dunha unidade.\n   Launch Disc: Iniciar disco\n   Memory Files: Ficheiros da memoria\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: RIPPS2 atopou %d dos 5. Cada un queda lembrado, e cada inicio leo de a\u00ed.\n+  not found on the started drives: non est\u00e1 nas unidades iniciadas\n+  not looked for yet: a\u00ednda non se buscou\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s non respondeu, as\u00ed que %s non pode iniciarse. Est\u00e1 conectado?'\n+  '%s is not on %s any more, so it has left All Games.': '%s xa non est\u00e1 en %s, as\u00ed que sa\u00edu de Todos os xogos.'\n+  STARTING %s: INICIANDO %s\n+  the SMB share: o recurso SMB\n+  the drive: a unidade\n+  the internal HDD (APA): o HDD interno (APA)\n+  the internal HDD (exFAT): o HDD interno (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: FEITO SOBRE RIPTOPL, CON NEUTRINO\n+  'Special thanks to all the YouTube love as well:': 'Grazas especiais tam\u00e9n a todo o agarimo de YouTube:'\n+  and its own look, launchers, file browser and save tools.: e co seu propio aspecto, lanzadores, explorador de ficheiros e ferramentas de gardado.\n+  glad to help however you reach me.: encantado de axudar, me contacte como me contacte.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): o segundo n\u00facleo de xogo, 1.8.0 inclu\u00eddo sen cambios (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: co Neutrino de rickgaiser dentro como o seu segundo n\u00facleo de xogo,\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : 'Este xogo in\u00edciase con Neutrino, e RIPPS2 non atopou ning\u00fan: nin na ruta da Configuraci\u00f3n, nin na unidade do xogo, nin nas tarxetas de memoria, nin en ningunha unidade iniciada. RIPPS2 leva Neutrino 1.8.0 de rickgaiser (AFL-3.0): inst\u00e1leo unha vez, onde escolla, e cada xogo de Neutrino \u00fasao desde a\u00ed.'\n+  STARTING THE DRIVES: INICIANDO AS UNIDADES\n+  '%d games, on the started drives': '%d xogos, nas unidades iniciadas'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s: ALG\u00daNS FICHEIROS NON SE PUIDERON BORRAR'\n+  '%u partitions, all in order': '%u partici\u00f3ns, todas en orde'\n+  ', %d art images': ', %d imaxes de portada'\n+  ', and ': ', e '\n+  ', its cheats': ', os seus trucos'\n+  ', its settings': ', a s\u00faa configuraci\u00f3n'\n+  1 game, on the started drives: 1 xogo, nas unidades iniciadas\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: Un xogo de PS1 de aqu\u00ed b\u00f3rrase en Ficheiros da memoria, onde \u00e9 un ficheiro propio.\n+  A network catalogue's games are deleted on the PC that serves them.: Os xogos dun cat\u00e1logo de rede b\u00f3rranse no PC que os serve.\n+  DELETE FOR GOOD: BORRAR PARA SEMPRE\n+  DELETE IT: BORRALO\n+  'DELETED %s: %s FREED': '%s BORRADO: %s LIBERADOS'\n+  'Delete %s from %s? Goes: %s.': 'Borrar %s de %s? Vai: %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': 'Borrar %s de %s? Vai: %s. Queda: %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: Borrar tam\u00e9n as s\u00faas partidas gardadas e imaxes RIPSHOT? Non cons\u00e9rvaas.\n+  HIDDEN GAMES: XOGOS OCULTOS\n+  'HIDDEN: %s': 'OCULTO: %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: Ocultar %s? Sae de todas as listas, tam\u00e9n de Todos os xogos e Favoritos. Configuraci\u00f3n > Orixes de xogos > Xogos ocultos volve mostralo.\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: \u00daltima comprobaci\u00f3n. %s b\u00f3rrase para sempre, e nada poder\u00e1 traelo de volta. Prema START para borralo.\n+  None. Hide Game, in a game's options, puts one here.: Ning\u00fan. Ocultar xogo, nas opci\u00f3ns dun xogo, pon un aqu\u00ed.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : 'Non se borrou nada: a t\u00e1boa de partici\u00f3ns desta unidade non encaixa con este disco (%s). Pode que se escribise desde a imaxe dun disco doutro tama\u00f1o. Rep\u00e1rea primeiro nun PC.'\n+  RIPPS2 cannot delete a game from this page.: RIPPS2 non pode borrar un xogo desde esta p\u00e1xina.\n+  RIPPS2 could not find this game's files on its drive.: RIPPS2 non atopou os ficheiros deste xogo na s\u00faa unidade.\n+  Reading the hidden lists: Lendo as listas ocultas\n+  SHOW AGAIN OR DELETE: MOSTRAR DE NOVO OU BORRAR\n+  SHOW IT AGAIN: MOSTRALO DE NOVO\n+  That game is not on its drive any more.: Ese xogo xa non est\u00e1 na s\u00faa unidade.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: A lista oculta non se puido escribir na unidade do xogo. Est\u00e1 protexida contra escritura, ou est\u00e1n desactivadas as Operaci\u00f3ns de escritura?\n+  The partition could not be removed (%d). Nothing was deleted.: A partici\u00f3n non se puido quitar (%d). Non se borrou nada.\n+  This drive cannot keep a hidden list (a network catalogue).: Esta unidade non pode gardar unha lista oculta (un cat\u00e1logo de rede).\n+  This game's drive has not answered yet. Wait for it, then try again.: A unidade deste xogo a\u00ednda non respondeu. Agarde por ela e t\u00e9nteo de novo.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: A partici\u00f3n deste xogo \u00e9 unha que RIPPS2 non quita (unha partici\u00f3n do sistema, ou a que ten a s\u00faa configuraci\u00f3n).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': 'unha partici\u00f3n \u00e9 m\u00e1is grande do que permite este disco: a t\u00e1boa f\u00edxose para un disco doutro tama\u00f1o'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': 'unha partici\u00f3n pasa do final deste disco (sector %u de %u): a t\u00e1boa f\u00edxose para un disco m\u00e1is grande'\n+  its RIPSHOT pictures: as s\u00faas imaxes RIPSHOT\n+  its drive: a s\u00faa unidade\n+  its saves: as s\u00faas partidas gardadas\n+  sector %u holds no partition header: o sector %u non ten cabeceira de partici\u00f3n\n+  the chain does not close at __mbr: a cadea non se pecha en __mbr\n+  the chain skips from sector %u: a cadea salta desde o sector %u\n+  the chain's links disagree at sector %u: os elos da cadea non coinciden no sector %u\n+  the drive's size could not be read: non se puido ler o tama\u00f1o da unidade\n+  the game (%s): o xogo (%s)\n+  the game's folder and everything in it (%s): o cartafol do xogo e todo o que cont\u00e9n (%s)\n+  the header at sector %u could not be read: non se puido ler a cabeceira do sector %u\n+  the header at sector %u fails its checksum: a cabeceira do sector %u falla a s\u00faa suma de comprobaci\u00f3n\n+  the partition at sector %u is not where or as large as APA puts one: a partici\u00f3n do sector %u non est\u00e1 onde, nin do tama\u00f1o co que, APA pon unha\n+  the settings and art another copy of it uses: a configuraci\u00f3n e as portadas que usa outra copia del\n+  '%d FOLDERS': '%d CARTAFOLES'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': '%s non se puido copiar a %s. A unidade est\u00e1 chea ou protexida contra escritura?'\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s non est\u00e1 onde RIPPS2 busca. Buscalo nas unidades que RIPPS2 iniciou? O lugar onde se atope queda lembrado para cada inicio seguinte.'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Ember non est\u00e1 en %s, xunto aos xogos, e ten que estar. RIPPS2 atopouno en %s: START c\u00f3piao al\u00ed (%d KB) e inicia o xogo.'\n+  Ember's BIOS: BIOS de Ember\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : 'A BIOS de Ember (bios.bin) non est\u00e1 en %s, xunto a Ember. RIPPS2 atopou unha en %s: START c\u00f3piaa al\u00ed (%d KB) e inicia o xogo.'\n+  LOOKING FOR YOUR LAUNCHERS: BUSCANDO OS SEUS LANZADORES\n+  The POPS files: Os ficheiros POPS\n+  '%s  MMCE card': '%s  tarxeta MMCE'\n+  INSTALL RIPPS2'S NEUTRINO: INSTALAR O NEUTRINO DE RIPPS2\n+  'Internal HDD (APA): %s free of %s': 'HDD interno (APA): %s libres de %s'\n+  LAUNCHERS: LANZADORES\n+  LOOK FOR THEM: BUSCALOS\n+  'Memory card %d: %d KB free': 'Tarxeta de memoria %d: %d KB libres'\n+  POPS files: Ficheiros POPS\n+  START EVERY DRIVE: INICIAR TODAS AS UNIDADES\n+  'STATUS: looking at the drives and the homebrew': 'ESTADO: revisando as unidades e o homebrew'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): Iniciar primeiro todas as unidades, para que a busca chegue a todas? (Non busca nas unidades xa iniciadas.)\n+  USB, HDD, MMCE and the rest: USB, HDD, MMCE e o resto\n+  every started drive: cada unidade iniciada\n+  found where you keep them: atopados onde os garda\n+  not found yet: a\u00ednda non atopado\n+  not on a started drive: non est\u00e1 nunha unidade iniciada\n+  only if you have none: s\u00f3 se non ten ning\u00fan\n+  starts every drive first: inicia primeiro todas as unidades\n+  Drives, launchers and homebrew: Unidades, lanzadores e homebrew\n+  BDM Devices: Dispositivos BDM\n+  Color Sweep: Transici\u00f3n de cores\n+  Colors and Text: Cores e texto\n+  HDD POPS partitions: Partici\u00f3ns POPS do HDD\n+  Layout: Disposici\u00f3n\n+  Missing Covers: Portadas que faltan\n+  Name: Nome\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: A\u00ednda non se atopou ningunha configuraci\u00f3n SMB de POPSTARTER. Enche as filas de abaixo.\n+  POPSTARTER Network: Rede de POPSTARTER\n+  POPSTARTER SMB Network: Rede SMB de POPSTARTER\n+  POPSTARTER UDPFS Network: Rede UDPFS de POPSTARTER\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: RIPPS2 engade el mesmo a unidade, a ISO, as VMC e o modo de v\u00eddeo.\n+  Screen: Pantalla\n+  Speed: Velocidade\n+  Sweep Colors: Cores da transici\u00f3n\n+  Text Weight: Grosor do texto\n+  Theme Color: Cor principal\n+  Audio: Son\n+  Bold: Negra\n+  Controllers: Mandos\n+  Lively: Vivo\n+  Medium: Medio\n+  PS Emulation: Emulaci\u00f3n PS\n+  Regular: Normal\n+  Slow: Lento\n+  A MOMENT: UN INTRE\n+  A TASK IN THE BACKGROUND: UNHA TAREFA EN SEGUNDO PLANO\n+  ABOUT %d S: ARREDOR DE %d S\n+  ANY MOMENT: EN CALQUERA INTRE\n+  READING THE GAME LIST OF %s: LENDO A LISTA DE XOGOS DE %s\n+  SAVING A LIST: GARDANDO UNHA LISTA\n+  START NOW: INICIAR AGORA\n+  STARTING: INICIANDO\n+  STARTS NEXT: IN\u00cdCIASE A SEGUIR\n+  STOPPING IT SAFELY: DETENDO CON SEGURIDADE\n+  THE LAST OF THE QUEUE: O \u00daLTIMO DA COLA\n+  WHEN IT FINISHES: CANDO REMATE\n+  What each card starts; install or repair BIBLE.: O que inicia cada tarxeta; instala ou repara BIBLE.\n+  Rainbow: Arco da vella\n+  Custom: Personalizada\n+  DESR DVR ISOs (experimental): ISO de DESR DVR (experimental)\n+  HDD Start Mode: Modo de inicio do HDD\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): Roboto Condensed de The Roboto Project Authors (SIL OFL 1.1)\n+  '%s on %s': '%s en %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: %s pasou a %s (%d%%), cada anaco comprobado. Quitar a ISO e manter o ZSO?'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: %s volveu ser %s, cada anaco comprobado. Quitar o ZSO e manter a ISO?'\n+  A ZSO of this game is already beside it. Remove one of the two first.: Xa hai un ZSO deste xogo ao seu car\u00f3n. Quita primeiro un dos dous.\n+  About %d min, then checked: Uns %d min, despois comprobado\n+  An ISO of this game is already beside it. Remove one of the two first.: Xa hai unha ISO deste xogo ao seu car\u00f3n. Quita primeiro unha das d\u00faas.\n+  CHECKING EVERY PIECE: COMPROBANDO CADA ANACO\n+  COMPRESS TO ZSO: COMPRIMIR EN ZSO\n+  'COMPRESSED %s: %s FREED': 'COMPRIMIDO %s: %s LIBERADOS'\n+  COMPRESSING 1 OF 2: COMPRIMINDO 1 DE 2\n+  COMPRESSING 2 OF 2: COMPRIMINDO 2 DE 2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : Comprimir %s? A ISO queda ata que o ZSO se lea de novo e se comprobe; despois RIPPS2 pregunta antes de quitar a ISO. A unidade precisa espazo para os dous mentres traballa; deixa a consola acesa ata que remate. Un ZSO s\u00f3 arrinca co n\u00facleo OPL (non Neutrino); as s\u00faas partidas, axustes, imaxes e trucos quedan con el, e pode volver ser unha ISO en calquera momento.\n+  Compress to ZSO: Comprimir en ZSO\n+  Cycle: Rotaci\u00f3n\n+  'Experimental: OPL core only (not Neutrino)': 'Experimental: s\u00f3 o n\u00facleo OPL (non Neutrino)'\n+  Hold SELECT: Manter SELECT\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: Non se puido rematar (%s). O que escribiu quitouse; o xogo est\u00e1 como estaba.\n+  List: Lista\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : Facer de %s unha ISO de novo? O ZSO queda ata que a ISO se lea de novo e se comprobe; despois RIPPS2 pregunta antes de quitar o ZSO. A unidade precisa espazo para os dous mentres traballa; deixa a consola acesa ata que remate. As s\u00faas partidas, axustes, imaxes e trucos quedan con el.\n+  Stop? What it has written so far is removed; the game stays as it was.: Parar? O escrito ata agora qu\u00edtase; o xogo queda como estaba.\n+  THEMES: TEMAS\n+  There is not enough memory for this just now.: Agora mesmo non hai memoria abonda para isto.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: RIPPS2 non pode comprimir nin descomprimir esta imaxe (o seu tama\u00f1o non son bloques enteiros de 2 KB).\n+  UNCOMPRESS TO ISO: DESCOMPRIMIR EN ISO\n+  UNCOMPRESSED %s: DESCOMPRIMIDO %s\n+  UNCOMPRESSING: DESCOMPRIMINDO\n+  Uncompress to ISO: Descomprimir en ISO\n+  Under a minute, then checked: Menos dun minuto, despois comprobado\n+  COPYING: COPIANDO\n+  SHOW AGAIN, DELETE OR COMPRESS: MOSTRAR DE NOVO, BORRAR OU COMPRIMIR\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: MOSTRAR DE NOVO, BORRAR OU DESCOMPRIMIR\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': '%s precisa uns %s libres na s\u00faa unidade mentres traballa (o orixinal queda ata o final), e a unidade ten %s libres.'\n+  Hide and Compress: Ocultar e comprimir\n+  'COMPRESSED AND HIDDEN: %s': 'COMPRIMIDO E OCULTO: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': 'Non hai sitio para esta copia: precisa uns %s, e a unidade ten %s libres.'\n+  OVER 4 GB FOR FAT32: M\u00c1IS DE 4 GB PARA FAT32\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: Esta unidade \u00e9 %s, que non garda ficheiros de 4 GB ou m\u00e1is, e a nova imaxe ser\u00eda de %s.\n+  '%s, %s clusters, %s free of %s': '%s, cl\u00fasteres de %s, %s libres de %s'\n+  Delete This Sweep: Borrar esta transici\u00f3n\n+  Delete %s? RIPPS2's own sweeps stay as they are.: Borrar %s? As transici\u00f3ns propias de RIPPS2 quedan como est\u00e1n.\n+  Sweep: Transici\u00f3n\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: As insignias marcan os xogos con logros nas t\u00faas listas, con cantos desbloqueaches. Precisan a TELEMETR\u00cdA activada.\n labels:\n   HINT_OSD_SETTINGS_LNG: Forzar unha configuraci\u00f3n de idioma personalizada\n   OSD_SETTINGS_TVASPECT: Tama\u00f1o da pantalla\n@@ -1445,7 +1611,7 @@ labels:\n   SAVE_ICONS: Iconas 3D de gardados\n   HINT_SAVE_ICONS: Mostra a icona de gardado do xogo escollido, xirando, onde o tema ten sitio para ela. Le primeiro o propio VMC do xogo, e despois as tarxetas de memoria.\n   NETBOOT_RESTART_NOW: O protocolo de rede cambia tras un reinicio. Gardar a configuraci\u00f3n e reiniciar OPL agora?\n-  HINT_NET_PROTOCOL: 'Que servizo de rede usa RiptOPL: un recurso SMB, UDPFS, UDPBD ou HTTP. O mesmo axuste que en Orixes de xogos. Cambiar desde un que xa estea funcionando require reiniciar.'\n+  HINT_NET_PROTOCOL: 'Que servizo de rede usa RiptOPL: un recurso SMB, UDPFS, UDPBD ou HTTP. Cambiar desde un que xa estea funcionando require reiniciar.'\n   HINT_UDPFS_ACCESS: S\u00f3 UDPFS. Ficheiros le ISO soltos do cartafol de xogos do servidor; IMG le unha imaxe de disco como unidade. SMB e HTTP usan sempre Ficheiros, UDPBD sempre IMG.\n   HINT_SMB_VERSION: Que versi\u00f3n de SMB falar co recurso do PC.\n   HINT_NET_ADVANCED: Desbloquea para editar o modo de ligaz\u00f3n Ethernet e o porto SMB.\n@@ -1646,3 +1812,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: Como as fotos que agardan no rolo de cada xogo se fan PNG.\n   RIPPS2_SETTINGS_HOME: Gardar axustes en\n   HINT_RIPPS2_SETTINGS_HOME: 'Onde garda o RIPPS2 os seus axustes: o seu propio gardado nunha tarxeta de memoria, un cartafol RIPPS2 nunha unidade (o HDD interno, USB, MX4SIO, iLink ou MMCE) ou un cartafol que escribas.'\n+  RIPPS2_LAUNCHERS: Lanzadores\n+  RIPPS2_LOOK_NOW: Buscar agora\n+  HINT_RIPPS2_LOOK_NOW: RIPPS2 busca nas unidades iniciadas POPSTARTER, os ficheiros POPS, Ember, a BIOS de Ember e Neutrino, e lembra onde est\u00e1 cada un. Cada inicio l\u00e9eos de a\u00ed.\n+  RIPPS2_HIDE_GAME: Ocultar xogo\n+  RIPPS2_HIDDEN_GAMES: Xogos ocultos\n+  HINT_RIPPS2_HIDDEN_GAMES: Os xogos ocultos con Ocultar xogo, nas unidades iniciadas. Volva mostrar un, ou b\u00f3rreo. Cada unidade garda a s\u00faa propia lista.\n+  HINT_RIPPS2_BDM_DEVICES: 'As unidades das que BDM le xogos: USB, MX4SIO, unha PC Card CF, a unidade DVR dun DESR e iLink. O HDD interno segue o modo de inicio do HDD.'\n+  HINT_RIPPS2_SCREEN: 'A imaxe do men\u00fa: modo de v\u00eddeo, pantalla ancha, onde queda na televisi\u00f3n (H-POS, V-POS) e overscan.'\n+  HINT_RIPPS2_LAYOUT: Onde van as categor\u00edas e como entra unha p\u00e1xina, despois as portadas de RIPFLOW e a grade de RIPGRID.\n+  HINT_RIPPS2_COLORS_TEXT: Grosor do texto, a cor do tema, a transici\u00f3n de cores e cada cor.\n+  HINT_RIPPS2_TEXT_WEIGHT: 'Canto groso \u00e9 o texto propio de RIPPS2: as axudas da configuraci\u00f3n, ESTADO e Acerca de. Normal \u00e9 coma antes; Medio e Negra lense mellor nunha imaxe suave.'\n+  HINT_RIPPS2_HDD_START: 'O HDD interno, APA ou exFAT: RIPPS2 leno sexa cal sexa e am\u00f3sao unha soa vez, como xogos do HDD. Desactivado d\u00e9ixao tranquilo.'\n+  HINT_RIPPS2_THEME_COLOR: A cor propia do tema. Escolle Personalizada como cor do tema para escollela ti, como as cores do texto.\n+  HINT_RIPPS2_SWEEP: Cinco cores polas que pasa o tema, amodo e suavemente. Arco da vella, unha transici\u00f3n feita de cada cor de tema ou a t\u00faa. Desactivado deixa o tema quedo.\n+  HINT_RIPPS2_SWEEP_COLORS: As cinco cores da transici\u00f3n e a s\u00faa velocidade. Cambia unha das de RIPPS2 e g\u00e1rdase como t\u00faa, co nome que lle deas.\n+  HINT_RIPPS2_SWEEP_NAME: Como se chama a t\u00faa transici\u00f3n na lista Transici\u00f3n de cores.\n+  HINT_RIPPS2_SWEEP_COLOR: Unha das cinco cores polas que pasa a transici\u00f3n, en orde.\n+  HINT_RIPPS2_SWEEP_SPEED: 'Canto dura cada cor antes de que entre a seguinte. Mesmo Vivo segue suave: as cores nunca escintilan.'\n+  RIPPS2_HIDE_COMPRESS: Ocultar xogo e compresi\u00f3n\n+  HINT_RIPPS2_SELECT_HOLD: 'SELECT mantido 4.2 s nunha lista de xogos. Lista: os temas nun panel, escolle un. Rotaci\u00f3n: o tema seguinte. Desactivado: nada (unha pulsaci\u00f3n a\u00ednda actualiza).'"
        },
        {
          "sha": "dd9340ff87ff2e43fec771bb917e357c92b0bd5b",
          "filename": "elf/lang/German.yml",
          "status": "modified",
          "additions": 206,
          "deletions": 22,
          "changes": 228,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FGerman.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FGerman.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FGerman.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: German\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTER BEST\u00c4TIGT, ESC BRICHT AB. JEDE TASTE ZEIGT DIE TASTATUR.\n   USB KEYBOARD: USB-TASTATUR\n-  '      DESR DVR ISOs (experimental)': '      DESR-DVR-ISOs (experimentell)'\n-  '      Internal HDD (exFAT)': '      Interne Festplatte (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (experimentell)'\n-  '      Protocol': '      Protokoll'\n   Access: Zugriff\n   Applications Page Start Mode: Start der Seite Anwendungen\n   Art Delay: Verz\u00f6gerung der Bilder\n@@ -22,7 +18,6 @@ strings:\n   Fifth: F\u00fcnfter\n   First: Erster\n   Fourth: Vierter\n-  HDD (APA) Start Mode: Start der Festplatte (APA)\n   HDD Cache: Festplatten-Cache\n   HDD Spindown: Festplatten-Ruhezustand\n   MMCE Settings: MMCE-Einstellungen\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: Dies ist ein mit ESR gepatchtes PS2-Spiel. Lege ESR (esr*.elf) in APPS, BOOT oder ESR auf einer Speicherkarte oder einem USB-Stick ab, um es zu starten.\n   '%s, BUILD %d': '%s, BUILD %d'\n   '... and the anonymous ...': '... und die Unbekannten ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': '1.8.0 unver\u00e4ndert enthalten (AFL-3.0): github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: Ein Zerlegen und Neuaufbau von RiptOPL, dem Open-PS2-Loader-Fork,\n   ABOUT: \u00dcBER\n   'ACHIEVEMENTS: SPIRIT OF RA': 'ERFOLGE: SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): AVI-Video in Speicherdateien, RIPPS2s Fork (AFL 2.0)\n   Apollo Save Tool by bucanero: Apollo Save Tool von bucanero\n   BACK: ZUR\u00dcCK\n+  Back (keeps both): Zur\u00fcck (beh\u00e4lt beide)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: Diese MMCE-Karte kann keine Datei umbenennen, was die Komprimierung f\u00fcr ihren letzten Schritt braucht. Aktualisiere ihre sd2psXtd-Firmware auf 1.4.0 oder neuer (sd2psxtd.github.io/download) und versuche es erneut. Nichts wurde ge\u00e4ndert.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): BIBLE ist PS2BBL 1.2.0, El Isras signierte Builds (Karte und Festplatte), unver\u00e4ndert (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: BIOS-Sounds > Rippen spielt sie wie der SPU2; es wird kein BIOS mitgeliefert\n   BOOT LOCK AND BIBLE: BOOT LOCK UND BIBLE\n   BOOT LOCK edits its config too: BOOT LOCK bearbeitet auch seine Konfiguration\n   BUILD DETAILS: BUILD-DETAILS\n-  BUILT ON RIPTOPL: AUFGEBAUT AUF RIPTOPL\n   BearSSL by Thomas Pornin: BearSSL von Thomas Pornin\n   Built with Claude (Anthropic): Gebaut mit Claude (Anthropic)\n   CD PLAYER: CD-PLAYER\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek und Sleek Bold, gezeichnet f\u00fcr RIPPS2\n   RetroAchievements on the console by hacan359 (yoba): RetroAchievements auf der Konsole von hacan359 (yoba)\n   RiptOPL by NathanNeurotic: RiptOPL von NathanNeurotic\n-  Roboto Condensed by Google (Apache-2.0): Roboto Condensed von Google (Apache-2.0)\n   SOUNDS: SOUNDS\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: SP193s FMCB-Installer und pcm720s OSDMenu, Schritt f\u00fcr Schritt befolgt\n   SUPPORT: SUPPORT\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: wLaunchELF R3Z von NathanNeurotic\n   wLaunchELF by the ps2homebrew team: wLaunchELF vom ps2homebrew-Team\n   what RIPPS2's card image writes are checked against: womit RIPPS2s Schreibvorg\u00e4nge in Kartenabbildern gepr\u00fcft werden\n-  with its own look, launchers, file browser and save tools.: mit eigenem Aussehen, Launchern, Dateibrowser und Spielstand-Werkzeugen.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: xeRAbora von hacan359, rcheevos von RetroAchievements\n   your console's own, ripped from its BIOS: die deiner Konsole, aus ihrem BIOS gerippt\n   ' (no DNS answer)': ' (keine DNS-Antwort)'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO: schreibe %.60s'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: Neutrino konnte nicht nach %s geschrieben werden. Pr\u00fcfe den freien Platz und dass es nicht schreibgesch\u00fctzt ist, und versuche es erneut.\n   THE GAME'S DRIVE  (%s/neutrino): DAS LAUFWERK DES SPIELS  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : Dieses Spiel startet \u00fcber Neutrino, und dort, wo RIPPS2 sucht (Einstellungen, das Laufwerk des Spiels, die Speicherkarten), gibt es kein Neutrino. RIPPS2 bringt Neutrino 1.8.0 von rickgaiser mit (AFL-3.0). Jetzt installieren? Als N\u00e4chstes w\u00e4hlst du den Ort.\n   '%d / %d FILES': '%d / %d DATEIEN'\n   '%d FOLDERS AND CONTENTS': '%d ORDNER MIT INHALT'\n   '%d FOLDERS, %d FOUND': '%d ORDNER, %d GEFUNDEN'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d von %d noch gesperrt'\n   '%s   on %s': '%s   auf %s'\n   BADGES: ABZEICHEN\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: Abzeichen wirken nur mit TELEMETRIE an und xeRAbora auf deinem PC; bis dahin kosten sie nichts.\n   CHECK THE DISC: DISC PR\u00dcFEN\n   From this game's last CHECK GAME SUPPORT.: Aus der letzten SPIELUNTERST\u00dcTZUNGSPR\u00dcFUNG dieses Spiels.\n   HOW IT WORKS: SO FUNKTIONIERT ES\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: Auf dieser Konsole ist ein PS2BBL mit RIPPS2s Sperre.\n   AUDIO folder: AUDIO-Ordner\n   DRIVES: LAUFWERKE\n-  INSTALL RIPPS2'S COPY: RIPPS2S KOPIE INSTALLIEREN\n-  'Installed: ': 'Installiert: '\n   MANAGE: VERWALTEN\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: VERWALTEN zeigt, was jede Speicherkarte startet, und installiert oder repariert BIBLE.\n   MEMORY CARD 0  (about 600 KB): SPEICHERKARTE 0  (etwa 600 KB)\n   MEMORY CARD 1  (about 600 KB): SPEICHERKARTE 1  (etwa 600 KB)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO: Sein Treiber lie\u00df sich nicht laden (Code %d).'\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO: Der Treiber ist geladen, aber keine SD-Karte antwortete (der mc1-Steckplatz).'\n   NEUTRINO SETTINGS: NEUTRINO-EINSTELLUNGEN\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': 'Noch keine: RIPPS2 hat keine eigenen Sounds. Rippe die dieser Konsole oder erstelle sie im RIPPS2 Audio Studio.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: Nicht installiert. Ein Spiel, das es braucht, bietet RIPPS2s Kopie an, oder installiere es hier.\n   POPSTARTER NETWORK: POPSTARTER-NETZWERK\n   POPSTARTER and Ember: POPSTARTER und Ember\n   PS1 SETTINGS: PS1-EINSTELLUNGEN\n   PS2BBL 1.2.0 for RIPPS2: PS2BBL 1.2.0 f\u00fcr RIPPS2\n   RIP THIS CONSOLE'S SOUNDS: SOUNDS DIESER KONSOLE RIPPEN\n-  RIPPS2's homebrew: RIPPS2s Homebrew\n   STATUS: STATUS\n-  'STATUS: looking for RIPPS2''s homebrew': 'STATUS: suche RIPPS2s Homebrew'\n   a drive of unknown kind: ein Laufwerk unbekannter Art\n-  a drive or a memory card: ein Laufwerk oder eine Speicherkarte\n   boot loader, card by card: Bootloader, Karte f\u00fcr Karte\n   from its BIOS, into AUDIO: aus ihrem BIOS, nach AUDIO\n   internal HDD (exFAT): interne Festplatte (exFAT)\n@@ -975,10 +958,191 @@ strings:\n   Sidebar: Seitenleiste\n   your own copy, found on your drives and memory cards: deine eigene Kopie, gefunden auf deinen Laufwerken und Memory Cards\n   'your own, found and launched: github.com/Gageformer/Ember/releases': 'dein eigenes, gefunden und gestartet: github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': 'Nicht gefunden auf mass0: (%s). Lege ember.elf und dein bios.bin in seinen EMBER-Ordner.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: Nicht gefunden. Lege dein eigenes POPSTARTER.ELF in den POPS-Ordner eines Laufwerks.\n   Launch Disc: Disc starten\n   Memory Files: Speicherdateien\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: RIPPS2 hat %d von 5 gefunden. Jeder wird gemerkt, und jeder Start liest ihn von dort.\n+  not found on the started drives: nicht auf den gestarteten Laufwerken\n+  not looked for yet: noch nicht gesucht\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s hat nicht geantwortet, darum kann %s nicht starten. Ist es angeschlossen?'\n+  '%s is not on %s any more, so it has left All Games.': '%s ist nicht mehr auf %s und hat darum Alle Spiele verlassen.'\n+  STARTING %s: STARTE %s\n+  the SMB share: die SMB-Freigabe\n+  the drive: das Laufwerk\n+  the internal HDD (APA): die interne Festplatte (APA)\n+  the internal HDD (exFAT): die interne Festplatte (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: GEBAUT AUF RIPTOPL, MIT NEUTRINO\n+  'Special thanks to all the YouTube love as well:': 'Besonderer Dank auch an all die Liebe auf YouTube:'\n+  and its own look, launchers, file browser and save tools.: und mit eigenem Look, eigenen Startern, Dateibrowser und Speicherwerkzeugen.\n+  glad to help however you reach me.: ich helfe gern, egal wie ihr mich erreicht.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): der zweite Spielkern, 1.8.0 unver\u00e4ndert mitgeliefert (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: mit rickgaisers Neutrino als zweitem Spielkern darin,\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : 'Dieses Spiel startet \u00fcber Neutrino, und RIPPS2 hat keins gefunden: nicht am Pfad aus den Einstellungen, nicht auf dem Laufwerk des Spiels, nicht auf den Speicherkarten und auf keinem gestarteten Laufwerk. RIPPS2 bringt Neutrino 1.8.0 von rickgaiser mit (AFL-3.0): Installiere es einmal, wo du willst, und jedes Neutrino-Spiel nutzt es von dort.'\n+  STARTING THE DRIVES: STARTE DIE LAUFWERKE\n+  '%d games, on the started drives': '%d Spiele, auf den gestarteten Laufwerken'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s: EINIGE DATEIEN KONNTEN NICHT GEL\u00d6SCHT WERDEN'\n+  '%u partitions, all in order': '%u Partitionen, alle in Ordnung'\n+  ', %d art images': ', %d Artwork-Bilder'\n+  ', and ': ', und '\n+  ', its cheats': ', seine Cheats'\n+  ', its settings': ', seine Einstellungen'\n+  1 game, on the started drives: 1 Spiel, auf den gestarteten Laufwerken\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: Ein PS1-Spiel wird hier in Speicherdateien gel\u00f6scht, wo es eine eigene Datei ist.\n+  A network catalogue's games are deleted on the PC that serves them.: Die Spiele eines Netzwerkkatalogs werden auf dem PC gel\u00f6scht, der sie bereitstellt.\n+  DELETE FOR GOOD: ENDG\u00dcLTIG L\u00d6SCHEN\n+  DELETE IT: ES L\u00d6SCHEN\n+  'DELETED %s: %s FREED': '%s GEL\u00d6SCHT: %s FREI'\n+  'Delete %s from %s? Goes: %s.': '%s von %s l\u00f6schen? Weg: %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': '%s von %s l\u00f6schen? Weg: %s. Bleibt: %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: Auch seine Spielst\u00e4nde und RIPSHOT-Bilder l\u00f6schen? Nein beh\u00e4lt sie.\n+  HIDDEN GAMES: VERSTECKTE SPIELE\n+  'HIDDEN: %s': 'VERSTECKT: %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: '%s verstecken? Es verl\u00e4sst jede Liste, auch Alle Spiele und Favoriten. Einstellungen > Spielquellen > Versteckte Spiele zeigt es wieder.'\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: Letzte Pr\u00fcfung. %s wird endg\u00fcltig gel\u00f6scht, und nichts kann es zur\u00fcckholen. Dr\u00fccke START, um es zu l\u00f6schen.\n+  None. Hide Game, in a game's options, puts one here.: Keine. Spiel verstecken, in den Optionen eines Spiels, legt eins hierher.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : 'Nichts wurde gel\u00f6scht: Die Partitionstabelle dieses Laufwerks passt nicht zu dieser Platte (%s). Sie wurde vielleicht aus dem Abbild einer Platte anderer Gr\u00f6\u00dfe geschrieben. Repariere sie zuerst am PC.'\n+  RIPPS2 cannot delete a game from this page.: RIPPS2 kann auf dieser Seite kein Spiel l\u00f6schen.\n+  RIPPS2 could not find this game's files on its drive.: RIPPS2 hat die Dateien dieses Spiels auf seinem Laufwerk nicht gefunden.\n+  Reading the hidden lists: Lese die Listen versteckter Spiele\n+  SHOW AGAIN OR DELETE: WIEDER ZEIGEN ODER L\u00d6SCHEN\n+  SHOW IT AGAIN: ES WIEDER ZEIGEN\n+  That game is not on its drive any more.: Dieses Spiel ist nicht mehr auf seinem Laufwerk.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: Die Liste versteckter Spiele konnte nicht auf das Laufwerk des Spiels geschrieben werden. Ist es schreibgesch\u00fctzt, oder sind die Schreibvorg\u00e4nge ausgeschaltet?\n+  The partition could not be removed (%d). Nothing was deleted.: Die Partition konnte nicht entfernt werden (%d). Nichts wurde gel\u00f6scht.\n+  This drive cannot keep a hidden list (a network catalogue).: Dieses Laufwerk kann keine Liste versteckter Spiele f\u00fchren (ein Netzwerkkatalog).\n+  This game's drive has not answered yet. Wait for it, then try again.: Das Laufwerk dieses Spiels hat noch nicht geantwortet. Warte darauf und versuche es dann erneut.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: Die Partition dieses Spiels entfernt RIPPS2 nicht (eine Systempartition oder die mit seinen Einstellungen).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': 'eine Partition ist gr\u00f6\u00dfer, als diese Platte erlaubt: Die Tabelle wurde f\u00fcr eine Platte anderer Gr\u00f6\u00dfe gemacht'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': 'eine Partition reicht \u00fcber das Ende dieser Platte hinaus (Sektor %u von %u): Die Tabelle wurde f\u00fcr eine gr\u00f6\u00dfere Platte gemacht'\n+  its RIPSHOT pictures: seine RIPSHOT-Bilder\n+  its drive: sein Laufwerk\n+  its saves: seine Spielst\u00e4nde\n+  sector %u holds no partition header: Sektor %u enth\u00e4lt keinen Partitionskopf\n+  the chain does not close at __mbr: die Kette schlie\u00dft sich nicht bei __mbr\n+  the chain skips from sector %u: die Kette springt ab Sektor %u\n+  the chain's links disagree at sector %u: die Glieder der Kette passen bei Sektor %u nicht zusammen\n+  the drive's size could not be read: die Gr\u00f6\u00dfe des Laufwerks konnte nicht gelesen werden\n+  the game (%s): das Spiel (%s)\n+  the game's folder and everything in it (%s): der Ordner des Spiels mit allem darin (%s)\n+  the header at sector %u could not be read: der Kopf bei Sektor %u konnte nicht gelesen werden\n+  the header at sector %u fails its checksum: der Kopf bei Sektor %u f\u00e4llt durch seine Pr\u00fcfsumme\n+  the partition at sector %u is not where or as large as APA puts one: die Partition bei Sektor %u liegt nicht dort oder ist nicht so gro\u00df, wie APA sie anlegt\n+  the settings and art another copy of it uses: die Einstellungen und Bilder, die eine andere Kopie nutzt\n+  '%d FOLDERS': '%d ORDNER'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': '%s konnte nicht nach %s kopiert werden. Ist das Laufwerk voll oder schreibgesch\u00fctzt?'\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s ist nicht dort, wo RIPPS2 sucht. Auf den von RIPPS2 gestarteten Laufwerken danach suchen? Der Fundort wird f\u00fcr jeden sp\u00e4teren Start gemerkt.'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Ember ist nicht in %s, neben den Spielen, und muss dort sein. RIPPS2 hat es unter %s gefunden: START kopiert es dorthin (%d KB) und startet das Spiel.'\n+  Ember's BIOS: Embers BIOS\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Embers BIOS (bios.bin) ist nicht in %s, neben Ember. RIPPS2 hat eins unter %s gefunden: START kopiert es dorthin (%d KB) und startet das Spiel.'\n+  LOOKING FOR YOUR LAUNCHERS: SUCHE DEINE STARTER\n+  The POPS files: Die POPS-Dateien\n+  '%s  MMCE card': '%s  MMCE-Karte'\n+  INSTALL RIPPS2'S NEUTRINO: RIPPS2S NEUTRINO INSTALLIEREN\n+  'Internal HDD (APA): %s free of %s': 'Interne Festplatte (APA): %s frei von %s'\n+  LAUNCHERS: STARTER\n+  LOOK FOR THEM: DANACH SUCHEN\n+  'Memory card %d: %d KB free': 'Speicherkarte %d: %d KB frei'\n+  POPS files: POPS-Dateien\n+  START EVERY DRIVE: ALLE LAUFWERKE STARTEN\n+  'STATUS: looking at the drives and the homebrew': 'STATUS: pr\u00fcfe die Laufwerke und die Homebrew'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): Zuerst alle Laufwerke starten, damit die Suche alle erreicht? (Nein durchsucht die schon gestarteten Laufwerke.)\n+  USB, HDD, MMCE and the rest: USB, Festplatte, MMCE und der Rest\n+  every started drive: jedes gestartete Laufwerk\n+  found where you keep them: gefunden, wo du sie aufbewahrst\n+  not found yet: noch nicht gefunden\n+  not on a started drive: nicht auf einem gestarteten Laufwerk\n+  only if you have none: nur wenn du keins hast\n+  starts every drive first: startet zuerst alle Laufwerke\n+  Drives, launchers and homebrew: Laufwerke, Starter und Homebrew\n+  BDM Devices: BDM-Ger\u00e4te\n+  Color Sweep: Farbwechsel\n+  Colors and Text: Farben und Text\n+  HDD POPS partitions: POPS-Partitionen der Festplatte\n+  Missing Covers: Fehlende Cover\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: Noch keine SMB-Einrichtung f\u00fcr POPSTARTER gefunden. F\u00fclle die Zeilen unten aus.\n+  POPSTARTER Network: POPSTARTER-Netzwerk\n+  POPSTARTER SMB Network: POPSTARTER-SMB-Netzwerk\n+  POPSTARTER UDPFS Network: POPSTARTER-UDPFS-Netzwerk\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: RIPPS2 erg\u00e4nzt Laufwerk, ISO, VMCs und Videomodus selbst.\n+  Screen: Bildschirm\n+  Speed: Tempo\n+  Sweep Colors: Farben des Wechsels\n+  Text Weight: Schriftst\u00e4rke\n+  Theme Color: Hauptfarbe\n+  Bold: Fett\n+  Controllers: Controller\n+  Lively: Lebhaft\n+  Medium: Mittel\n+  PS Emulation: PS-Emulation\n+  Regular: Normal\n+  Slow: Langsam\n+  A MOMENT: EINEN MOMENT\n+  A TASK IN THE BACKGROUND: EINE AUFGABE IM HINTERGRUND\n+  ABOUT %d S: ETWA %d S\n+  ANY MOMENT: JEDEN MOMENT\n+  READING THE GAME LIST OF %s: LIEST DIE SPIELELISTE VON %s\n+  SAVING A LIST: SPEICHERT EINE LISTE\n+  START NOW: JETZT STARTEN\n+  STARTING: STARTET\n+  STARTS NEXT: STARTET ALS N\u00c4CHSTES\n+  STOPPING IT SAFELY: H\u00c4LT SICHER AN\n+  THE LAST OF THE QUEUE: DAS ENDE DER WARTESCHLANGE\n+  WHEN IT FINISHES: SOBALD ES FERTIG IST\n+  What each card starts; install or repair BIBLE.: Was jede Karte startet; BIBLE installieren oder reparieren.\n+  Rainbow: Regenbogen\n+  Custom: Eigene\n+  DESR DVR ISOs (experimental): DESR-DVR-ISOs (experimentell)\n+  PC CARD CF (experimental): PC CARD CF (experimentell)\n+  HDD Start Mode: Start der Festplatte\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): Roboto Condensed von The Roboto Project Authors (SIL OFL 1.1)\n+  '%s on %s': '%s auf %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: %s wurde zu %s (%d%%), jedes St\u00fcck gepr\u00fcft. ISO entfernen und ZSO behalten?'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: %s wurde wieder zu %s, jedes St\u00fcck gepr\u00fcft. ZSO entfernen und ISO behalten?'\n+  A ZSO of this game is already beside it. Remove one of the two first.: Daneben liegt schon ein ZSO dieses Spiels. Entferne zuerst eines der beiden.\n+  About %d min, then checked: Etwa %d Min., dann gepr\u00fcft\n+  An ISO of this game is already beside it. Remove one of the two first.: Daneben liegt schon ein ISO dieses Spiels. Entferne zuerst eines der beiden.\n+  CHECKING EVERY PIECE: JEDES ST\u00dcCK WIRD GEPR\u00dcFT\n+  COMPRESS TO ZSO: IN ZSO KOMPRIMIEREN\n+  'COMPRESSED %s: %s FREED': '%s KOMPRIMIERT: %s FREI'\n+  COMPRESSING 1 OF 2: KOMPRIMIEREN 1 VON 2\n+  COMPRESSING 2 OF 2: KOMPRIMIEREN 2 VON 2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : '%s komprimieren? Das ISO bleibt, bis das ZSO zur\u00fcckgelesen und gepr\u00fcft ist, dann fragt RIPPS2, bevor es das ISO entfernt. Das Laufwerk braucht w\u00e4hrenddessen Platz f\u00fcr beide; lass die Konsole an, bis es fertig ist. Ein ZSO startet nur mit dem OPL-Kern (nicht Neutrino); seine Spielst\u00e4nde, Einstellungen, Bilder und Cheats bleiben bei ihm, und es kann jederzeit wieder ein ISO werden.'\n+  Compress to ZSO: In ZSO komprimieren\n+  Cycle: Durchschalten\n+  'Experimental: OPL core only (not Neutrino)': 'Experimentell: nur OPL-Kern (nicht Neutrino)'\n+  Hold SELECT: SELECT halten\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: Es konnte nicht fertig werden (%s). Was es schrieb, wurde entfernt; das Spiel ist wie zuvor.\n+  List: Liste\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : '%s wieder zu einem ISO machen? Das ZSO bleibt, bis das ISO zur\u00fcckgelesen und gepr\u00fcft ist, dann fragt RIPPS2, bevor es das ZSO entfernt. Das Laufwerk braucht w\u00e4hrenddessen Platz f\u00fcr beide; lass die Konsole an, bis es fertig ist. Seine Spielst\u00e4nde, Einstellungen, Bilder und Cheats bleiben bei ihm.'\n+  Stop? What it has written so far is removed; the game stays as it was.: Anhalten? Was bisher geschrieben wurde, wird entfernt; das Spiel bleibt wie zuvor.\n+  THEMES: THEMEN\n+  There is not enough memory for this just now.: Daf\u00fcr ist gerade nicht genug Speicher frei.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: Dieses Abbild kann RIPPS2 nicht komprimieren oder entpacken (seine Gr\u00f6\u00dfe ist kein Vielfaches von 2 KB).\n+  UNCOMPRESS TO ISO: IN ISO ENTPACKEN\n+  UNCOMPRESSED %s: '%s ENTPACKT'\n+  UNCOMPRESSING: ENTPACKEN\n+  Uncompress to ISO: In ISO entpacken\n+  Under a minute, then checked: Unter einer Minute, dann gepr\u00fcft\n+  COPYING: KOPIEREN\n+  SHOW AGAIN, DELETE OR COMPRESS: WIEDER ZEIGEN, L\u00d6SCHEN ODER KOMPRIMIEREN\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: WIEDER ZEIGEN, L\u00d6SCHEN ODER ENTPACKEN\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': '%s braucht w\u00e4hrend der Arbeit etwa %s freien Platz auf seinem Laufwerk (das Original bleibt bis zum Schluss), und das Laufwerk hat %s frei.'\n+  Hide and Compress: Verstecken und komprimieren\n+  'COMPRESSED AND HIDDEN: %s': 'KOMPRIMIERT UND VERSTECKT: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': 'F\u00fcr diese Kopie ist kein Platz: Sie braucht etwa %s, und das Laufwerk hat %s frei.'\n+  OVER 4 GB FOR FAT32: \u00dcBER 4 GB F\u00dcR FAT32\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: Dieses Laufwerk ist %s und h\u00e4lt keine Datei ab 4 GB, und das neue Abbild w\u00e4re %s gro\u00df.\n+  '%s, %s clusters, %s free of %s': '%s, Cluster %s, %s frei von %s'\n+  Delete This Sweep: Diesen Farbwechsel l\u00f6schen\n+  Delete %s? RIPPS2's own sweeps stay as they are.: '%s l\u00f6schen? Die Farbwechsel von RIPPS2 selbst bleiben, wie sie sind.'\n+  Sweep: Farbwechsel\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: Abzeichen markieren die Spiele mit Erfolgen in deinen Listen, mit wie vielen du freigeschaltet hast. Sie brauchen TELEMETRIE an.\n labels:\n   CFG_NOTIFICATION: Konfiguration geladen von %s\n   THM_NOTIFICATION: Design geladen von %s\n@@ -1204,7 +1368,7 @@ labels:\n   SAVE_ICONS: 3D-Spielstandsymbole\n   HINT_SAVE_ICONS: Zeigt das Spielstandsymbol des gew\u00e4hlten Spiels, sich drehend, wo das Design einen Platz daf\u00fcr hat. Liest zuerst die VMC des Spiels, dann die Speicherkarten.\n   NETBOOT_RESTART_NOW: Das Netzwerkprotokoll \u00e4ndert sich nach einem Neustart. Einstellungen speichern und OPL jetzt neu starten?\n-  HINT_NET_PROTOCOL: 'Welchen Netzwerkdienst RiptOPL nutzt: eine SMB-Freigabe, UDPFS, UDPBD oder HTTP. Dieselbe Einstellung wie unter Spielquellen. Von einem laufenden Dienst wegzuwechseln braucht einen Neustart.'\n+  HINT_NET_PROTOCOL: 'Welchen Netzwerkdienst RiptOPL nutzt: eine SMB-Freigabe, UDPFS, UDPBD oder HTTP. Von einem laufenden Dienst wegzuwechseln braucht einen Neustart.'\n   HINT_UDPFS_ACCESS: Nur UDPFS. Dateien liest einzelne ISOs aus dem Spieleordner des Servers; IMG liest ein Festplattenabbild als Laufwerk. SMB und HTTP nutzen immer Dateien, UDPBD immer IMG.\n   HINT_SMB_VERSION: Welche SMB-Version mit der PC-Freigabe gesprochen wird.\n   HINT_NET_ADVANCED: Gibt Ethernet-Verbindungsmodus und SMB-Port zum Bearbeiten frei.\n@@ -1403,3 +1567,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: Wie die Bilder auf dem Film jedes Spiels zu PNGs werden.\n   RIPPS2_SETTINGS_HOME: Einstellungen speichern auf\n   HINT_RIPPS2_SETTINGS_HOME: 'Wo RIPPS2 seine Einstellungen ablegt: im eigenen Spielstand auf einer Speicherkarte, in einem RIPPS2-Ordner auf einem Laufwerk (interne Festplatte, USB, MX4SIO, iLink oder MMCE) oder in einem Ordner, den du eingibst.'\n+  RIPPS2_LAUNCHERS: Starter\n+  RIPPS2_LOOK_NOW: Jetzt suchen\n+  HINT_RIPPS2_LOOK_NOW: RIPPS2 durchsucht die gestarteten Laufwerke nach POPSTARTER, den POPS-Dateien, Ember, Embers BIOS und Neutrino und merkt sich, wo jedes liegt. Jeder Start liest sie von dort.\n+  RIPPS2_HIDE_GAME: Spiel verstecken\n+  RIPPS2_HIDDEN_GAMES: Versteckte Spiele\n+  HINT_RIPPS2_HIDDEN_GAMES: Die mit Spiel verstecken versteckten Spiele auf den gestarteten Laufwerken. Zeige eins wieder oder l\u00f6sche es. Jedes Laufwerk f\u00fchrt seine eigene Liste.\n+  HINT_RIPPS2_BDM_DEVICES: 'Die Laufwerke, von denen BDM Spiele liest: USB, MX4SIO, eine PC-Card-CF, das DVR-Laufwerk eines DESR und iLink. Die interne Festplatte folgt dem Start der Festplatte.'\n+  HINT_RIPPS2_SCREEN: 'Das Bild des Men\u00fcs: Videomodus, Breitbild, seine Lage auf dem Fernseher (H-POS, V-POS) und Overscan.'\n+  HINT_RIPPS2_LAYOUT: Wo die Kategorien sitzen und wie eine Seite hereinkommt, dann die Cover von RIPFLOW und das Raster von RIPGRID.\n+  HINT_RIPPS2_COLORS_TEXT: Schriftst\u00e4rke, das Farbschema, der Farbwechsel und jede Farbe.\n+  HINT_RIPPS2_TEXT_WEIGHT: 'Wie kr\u00e4ftig RIPPS2s eigener Text ist: die Tipps in den Einstellungen, STATUS und \u00dcber. Normal ist wie bisher; Mittel und Fett lesen sich auf einem weichen Bild besser.'\n+  HINT_RIPPS2_HDD_START: 'Die interne Festplatte, APA oder exFAT: RIPPS2 liest sie, was sie auch ist, und zeigt sie einmal, als Festplattenspiele. Aus l\u00e4sst sie in Ruhe.'\n+  HINT_RIPPS2_THEME_COLOR: Die eigene Farbe des Farbschemas. W\u00e4hle Eigene als Farbschema, um sie selbst zu w\u00e4hlen, wie die Textfarben.\n+  HINT_RIPPS2_SWEEP: F\u00fcnf Farben, durch die das Schema langsam und sanft wechselt. Regenbogen, ein Wechsel aus jedem Farbschema oder dein eigener. Aus h\u00e4lt das Farbschema still.\n+  HINT_RIPPS2_SWEEP_COLORS: Die f\u00fcnf Farben des Wechsels und sein Tempo. \u00c4nderst du einen von RIPPS2, wird er als deiner gespeichert, unter dem Namen, den du ihm gibst.\n+  HINT_RIPPS2_SWEEP_NAME: Wie dein Wechsel in der Liste Farbwechsel hei\u00dft.\n+  HINT_RIPPS2_SWEEP_COLOR: Eine der f\u00fcnf Farben, durch die der Wechsel der Reihe nach geht.\n+  HINT_RIPPS2_SWEEP_SPEED: 'Wie lange jede Farbe bleibt, bevor die n\u00e4chste einblendet. Auch Lebhaft bleibt sanft: die Farben blitzen nie.'\n+  RIPPS2_HIDE_COMPRESS: Spiel verstecken und Komprimierung\n+  HINT_RIPPS2_SELECT_HOLD: 'SELECT 4.2 s auf einer Spieleliste gehalten. Liste: die Themen in einem Feld, eines w\u00e4hlen. Durchschalten: das n\u00e4chste Thema. Aus: nichts (ein Druck aktualisiert weiterhin).'"
        },
        {
          "sha": "d89877751bf3e7765b0f29c10cbdcda37a3a03e8",
          "filename": "elf/lang/Greek.yml",
          "status": "modified",
          "additions": 209,
          "deletions": 22,
          "changes": 231,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FGreek.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FGreek.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FGreek.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: Greek\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: \u03a4\u039f ENTER \u0395\u03a0\u0399\u0392\u0395\u0392\u0391\u0399\u03a9\u039d\u0395\u0399, \u03a4\u039f ESC \u0391\u039a\u03a5\u03a1\u03a9\u039d\u0395\u0399. \u039f\u03a0\u039f\u0399\u039f\u0394\u0397\u03a0\u039f\u03a4\u0395 \u039a\u039f\u03a5\u039c\u03a0\u0399 \u0394\u0395\u0399\u03a7\u039d\u0395\u0399 \u03a4\u0391 \u03a0\u039b\u0397\u039a\u03a4\u03a1\u0391.\n   USB KEYBOARD: \u03a0\u039b\u0397\u039a\u03a4\u03a1\u039f\u039b\u039f\u0393\u0399\u039f USB\n-  '      DESR DVR ISOs (experimental)': '      ISO DESR DVR (\u03c0\u03b5\u03b9\u03c1\u03b1\u03bc\u03b1\u03c4\u03b9\u03ba\u03cc)'\n-  '      Internal HDD (exFAT)': '      \u0395\u03c3\u03c9\u03c4\u03b5\u03c1\u03b9\u03ba\u03cc\u03c2 HDD (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (\u03c0\u03b5\u03b9\u03c1\u03b1\u03bc\u03b1\u03c4\u03b9\u03ba\u03cc)'\n-  '      Protocol': '      \u03a0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf'\n   Access: \u03a0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\n   Applications Page Start Mode: \u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03ad\u03bd\u03b1\u03c1\u03be\u03b7\u03c2 \u03c3\u03b5\u03bb\u03af\u03b4\u03b1\u03c2 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ce\u03bd\n   Art Delay: \u039a\u03b1\u03b8\u03c5\u03c3\u03c4\u03ad\u03c1\u03b7\u03c3\u03b7 \u03b5\u03b9\u03ba\u03cc\u03bd\u03c9\u03bd\n@@ -22,7 +18,6 @@ strings:\n   Fifth: \u03a0\u03ad\u03bc\u03c0\u03c4\u03bf\n   First: \u03a0\u03c1\u03ce\u03c4\u03bf\n   Fourth: \u03a4\u03ad\u03c4\u03b1\u03c1\u03c4\u03bf\n-  HDD (APA) Start Mode: \u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03ad\u03bd\u03b1\u03c1\u03be\u03b7\u03c2 HDD (APA)\n   HDD Cache: \u039a\u03c1\u03c5\u03c6\u03ae \u03bc\u03bd\u03ae\u03bc\u03b7 HDD\n   HDD Spindown: \u0391\u03b4\u03c1\u03ac\u03bd\u03b5\u03b9\u03b1 HDD\n   MMCE Settings: \u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 MMCE\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: \u0391\u03c5\u03c4\u03cc \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9 PS2 \u03bc\u03b5 \u03b5\u03c0\u03b9\u03b4\u03b9\u03cc\u03c1\u03b8\u03c9\u03c3\u03b7 ESR. \u0392\u03ac\u03bb\u03c4\u03b5 \u03c4\u03bf ESR (esr*.elf) \u03c3\u03c4\u03bf APPS, BOOT \u03ae ESR \u03c3\u03b5 \u03ba\u03ac\u03c1\u03c4\u03b1 \u03bc\u03bd\u03ae\u03bc\u03b7\u03c2 \u03ae USB stick \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c4\u03bf \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5.\n   '%s, BUILD %d': '%s, \u0395\u039a\u0394\u039f\u03a3\u0397 %d'\n   '... and the anonymous ...': '... \u03ba\u03b1\u03b9 \u03bf\u03b9 \u03b1\u03bd\u03ce\u03bd\u03c5\u03bc\u03bf\u03b9 ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': '1.8.0 \u03c3\u03c5\u03bd\u03bf\u03b4\u03b5\u03cd\u03b5\u03b9 \u03c7\u03c9\u03c1\u03af\u03c2 \u03b1\u03bb\u03bb\u03b1\u03b3\u03ad\u03c2 (AFL-3.0): github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: \u0388\u03bd\u03b1 \u03be\u03ae\u03bb\u03c9\u03bc\u03b1 \u03ba\u03b1\u03b9 \u03be\u03b1\u03bd\u03b1\u03c7\u03c4\u03af\u03c3\u03b9\u03bc\u03bf \u03c4\u03bf\u03c5 RiptOPL, \u03c4\u03bf\u03c5 fork \u03c4\u03bf\u03c5 Open PS2 Loader,\n   ABOUT: \u03a3\u03a7\u0395\u03a4\u0399\u039a\u0391\n   'ACHIEVEMENTS: SPIRIT OF RA': '\u0395\u03a0\u0399\u03a4\u0395\u03a5\u0393\u039c\u0391\u03a4\u0391: SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): \u0392\u03af\u03bd\u03c4\u03b5\u03bf AVI \u03c3\u03c4\u03b1 \u0391\u03c1\u03c7\u03b5\u03af\u03b1 \u03bc\u03bd\u03ae\u03bc\u03b7\u03c2, fork \u03c4\u03bf\u03c5 RIPPS2 (AFL 2.0)\n   Apollo Save Tool by bucanero: Apollo Save Tool \u03b1\u03c0\u03cc \u03c4\u03bf\u03bd bucanero\n   BACK: \u03a0\u0399\u03a3\u03a9\n+  Back (keeps both): \u03a0\u03af\u03c3\u03c9 (\u03ba\u03c1\u03b1\u03c4\u03ac \u03ba\u03b1\u03b9 \u03c4\u03b1 \u03b4\u03cd\u03bf)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: \u0391\u03c5\u03c4\u03ae \u03b7 \u03ba\u03ac\u03c1\u03c4\u03b1 MMCE \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03bc\u03b5\u03c4\u03bf\u03bd\u03bf\u03bc\u03ac\u03c3\u03b5\u03b9 \u03b1\u03c1\u03c7\u03b5\u03af\u03bf, \u03ba\u03ac\u03c4\u03b9 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b5\u03b9\u03ac\u03b6\u03b5\u03c4\u03b1\u03b9 \u03b7 \u03c3\u03c5\u03bc\u03c0\u03af\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf \u03c4\u03b5\u03bb\u03b5\u03c5\u03c4\u03b1\u03af\u03bf \u03c4\u03b7\u03c2 \u03b2\u03ae\u03bc\u03b1. \u0395\u03bd\u03b7\u03bc\u03b5\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c5\u03bb\u03b9\u03ba\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03bc\u03b9\u03ba\u03cc sd2psXtd \u03c4\u03b7\u03c2 \u03c3\u03c4\u03b7\u03bd 1.4.0 \u03ae \u03bd\u03b5\u03cc\u03c4\u03b5\u03c1\u03b7 (sd2psxtd.github.io/download) \u03ba\u03b1\u03b9 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac. \u0394\u03b5\u03bd \u03ac\u03bb\u03bb\u03b1\u03be\u03b5 \u03c4\u03af\u03c0\u03bf\u03c4\u03b1.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): \u03a4\u03bf BIBLE \u03b5\u03af\u03bd\u03b1\u03b9 \u03c4\u03bf PS2BBL 1.2.0, \u03bf\u03b9 \u03c5\u03c0\u03bf\u03b3\u03b5\u03b3\u03c1\u03b1\u03bc\u03bc\u03ad\u03bd\u03b5\u03c2 \u03b5\u03ba\u03b4\u03cc\u03c3\u03b5\u03b9\u03c2 \u03c4\u03bf\u03c5 El Isra (\u03ba\u03ac\u03c1\u03c4\u03b1 \u03ba\u03b1\u03b9 HDD), \u03c7\u03c9\u03c1\u03af\u03c2 \u03b1\u03bb\u03bb\u03b1\u03b3\u03ad\u03c2 (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: \u03a4\u03bf \u0389\u03c7\u03bf\u03b9 BIOS > \u0395\u03be\u03b1\u03b3\u03c9\u03b3\u03ae \u03c4\u03bf\u03c5\u03c2 \u03c0\u03b1\u03af\u03b6\u03b5\u03b9 \u03cc\u03c0\u03c9\u03c2 \u03b7 SPU2\u00b7 \u03b4\u03b5\u03bd \u03c3\u03c5\u03bd\u03bf\u03b4\u03b5\u03cd\u03b5\u03b9 \u03ba\u03b1\u03bd\u03ad\u03bd\u03b1 BIOS\n   BOOT LOCK AND BIBLE: BOOT LOCK \u039a\u0391\u0399 BIBLE\n   BOOT LOCK edits its config too: \u03a4\u03bf BOOT LOCK \u03b5\u03c0\u03b5\u03be\u03b5\u03c1\u03b3\u03ac\u03b6\u03b5\u03c4\u03b1\u03b9 \u03ba\u03b1\u03b9 \u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c4\u03bf\u03c5\n   BUILD DETAILS: \u039b\u0395\u03a0\u03a4\u039f\u039c\u0395\u03a1\u0395\u0399\u0395\u03a3 \u0395\u039a\u0394\u039f\u03a3\u0397\u03a3\n-  BUILT ON RIPTOPL: \u03a7\u03a4\u0399\u03a3\u039c\u0395\u039d\u039f \u03a0\u0391\u039d\u03a9 \u03a3\u03a4\u039f RIPTOPL\n   BearSSL by Thomas Pornin: BearSSL \u03b1\u03c0\u03cc \u03c4\u03bf\u03bd Thomas Pornin\n   Built with Claude (Anthropic): \u03a6\u03c4\u03b9\u03b1\u03b3\u03bc\u03ad\u03bd\u03bf \u03bc\u03b5 \u03c4\u03bf Claude (Anthropic)\n   CD PLAYER: \u03a3\u03a5\u03a3\u039a\u0395\u03a5\u0397 CD\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek \u03ba\u03b1\u03b9 Sleek Bold, \u03c3\u03c7\u03b5\u03b4\u03b9\u03b1\u03c3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf RIPPS2\n   RetroAchievements on the console by hacan359 (yoba): RetroAchievements \u03c3\u03c4\u03b7\u03bd \u03ba\u03bf\u03bd\u03c3\u03cc\u03bb\u03b1 \u03b1\u03c0\u03cc \u03c4\u03bf\u03bd hacan359 (yoba)\n   RiptOPL by NathanNeurotic: RiptOPL \u03b1\u03c0\u03cc \u03c4\u03bf\u03bd NathanNeurotic\n-  Roboto Condensed by Google (Apache-2.0): Roboto Condensed \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd Google (Apache-2.0)\n   SOUNDS: \u0397\u03a7\u039f\u0399\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: \u03a4\u03bf \u03c0\u03c1\u03cc\u03b3\u03c1\u03b1\u03bc\u03bc\u03b1 \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2 FMCB \u03c4\u03bf\u03c5 SP193 \u03ba\u03b1\u03b9 \u03c4\u03bf OSDMenu \u03c4\u03bf\u03c5 pcm720, \u03b2\u03ae\u03bc\u03b1 \u03c0\u03c1\u03bf\u03c2 \u03b2\u03ae\u03bc\u03b1\n   SUPPORT: \u03a5\u03a0\u039f\u03a3\u03a4\u0397\u03a1\u0399\u039e\u0397\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: wLaunchELF R3Z \u03b1\u03c0\u03cc \u03c4\u03bf\u03bd NathanNeurotic\n   wLaunchELF by the ps2homebrew team: wLaunchELF \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd \u03bf\u03bc\u03ac\u03b4\u03b1 ps2homebrew\n   what RIPPS2's card image writes are checked against: \u03bc\u03b5 \u03c4\u03b9 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03bd\u03c4\u03b1\u03b9 \u03bf\u03b9 \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03ad\u03c2 \u03b5\u03b9\u03ba\u03cc\u03bd\u03c9\u03bd \u03ba\u03ac\u03c1\u03c4\u03b1\u03c2 \u03c4\u03bf\u03c5 RIPPS2\n-  with its own look, launchers, file browser and save tools.: \u03bc\u03b5 \u03b4\u03b9\u03ba\u03ae \u03c4\u03bf\u03c5 \u03b5\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7, \u03b5\u03ba\u03ba\u03b9\u03bd\u03b7\u03c4\u03ad\u03c2, \u03c0\u03b5\u03c1\u03b9\u03b7\u03b3\u03b7\u03c4\u03ae \u03b1\u03c1\u03c7\u03b5\u03af\u03c9\u03bd \u03ba\u03b1\u03b9 \u03b5\u03c1\u03b3\u03b1\u03bb\u03b5\u03af\u03b1 \u03b1\u03c0\u03bf\u03b8\u03b7\u03ba\u03b5\u03cd\u03c3\u03b5\u03c9\u03bd.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: xeRAbora \u03b1\u03c0\u03cc \u03c4\u03bf\u03bd hacan359, rcheevos \u03b1\u03c0\u03cc \u03c4\u03bf RetroAchievements\n   your console's own, ripped from its BIOS: \u03bf\u03b9 \u03ae\u03c7\u03bf\u03b9 \u03c4\u03b7\u03c2 \u03af\u03b4\u03b9\u03b1\u03c2 \u03c4\u03b7\u03c2 \u03ba\u03bf\u03bd\u03c3\u03cc\u03bb\u03b1\u03c2 \u03c3\u03b1\u03c2, \u03b5\u03be\u03b1\u03b3\u03bc\u03ad\u03bd\u03bf\u03b9 \u03b1\u03c0\u03cc \u03c4\u03bf BIOS \u03c4\u03b7\u03c2\n   ' (no DNS answer)': ' (\u03ba\u03b1\u03bc\u03af\u03b1 \u03b1\u03c0\u03ac\u03bd\u03c4\u03b7\u03c3\u03b7 DNS)'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO: \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03ae %.60s'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: \u03a4\u03bf Neutrino \u03b4\u03b5\u03bd \u03b3\u03c1\u03ac\u03c6\u03c4\u03b7\u03ba\u03b5 \u03c3\u03c4\u03bf %s. \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b5\u03bb\u03b5\u03cd\u03b8\u03b5\u03c1\u03bf \u03c7\u03ce\u03c1\u03bf \u03ba\u03b1\u03b9 \u03cc\u03c4\u03b9 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03c3\u03c4\u03b1\u03c4\u03b5\u03c5\u03bc\u03ad\u03bd\u03bf \u03b1\u03c0\u03cc \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03ae, \u03ba\u03b1\u03b9 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.\n   THE GAME'S DRIVE  (%s/neutrino): \u039f \u0394\u0399\u03a3\u039a\u039f\u03a3 \u03a4\u039f\u03a5 \u03a0\u0391\u0399\u03a7\u039d\u0399\u0394\u0399\u039f\u03a5  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : \u0391\u03c5\u03c4\u03cc \u03c4\u03bf \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9 \u03be\u03b5\u03ba\u03b9\u03bd\u03ac \u03bc\u03ad\u03c3\u03c9 Neutrino, \u03ba\u03b1\u03b9 \u03b4\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 Neutrino \u03b5\u03ba\u03b5\u03af \u03c0\u03bf\u03c5 \u03c8\u03ac\u03c7\u03bd\u03b5\u03b9 \u03c4\u03bf RIPPS2 (\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2, \u03bf \u03b4\u03af\u03c3\u03ba\u03bf\u03c2 \u03c4\u03bf\u03c5 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03bf\u03cd, \u03bf\u03b9 \u03ba\u03ac\u03c1\u03c4\u03b5\u03c2 \u03bc\u03bd\u03ae\u03bc\u03b7\u03c2). \u03a4\u03bf RIPPS2 \u03ad\u03c7\u03b5\u03b9 \u03bc\u03b1\u03b6\u03af \u03c4\u03bf\u03c5 \u03c4\u03bf Neutrino 1.8.0 \u03c4\u03bf\u03c5 rickgaiser (AFL-3.0). \u0395\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c4\u03ce\u03c1\u03b1; \u039c\u03b5\u03c4\u03ac \u03b4\u03b9\u03b1\u03bb\u03ad\u03b3\u03b5\u03c4\u03b5 \u03c0\u03bf\u03cd.\n   '%d / %d FILES': '%d / %d \u0391\u03a1\u03a7\u0395\u0399\u0391'\n   '%d FOLDERS AND CONTENTS': '%d \u03a6\u0391\u039a\u0395\u039b\u039f\u0399 \u039a\u0391\u0399 \u03a0\u0395\u03a1\u0399\u0395\u03a7\u039f\u039c\u0395\u039d\u0391'\n   '%d FOLDERS, %d FOUND': '%d \u03a6\u0391\u039a\u0395\u039b\u039f\u0399, %d \u0392\u03a1\u0395\u0398\u0397\u039a\u0391\u039d'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d \u03b1\u03c0\u03cc %d \u03b1\u03ba\u03cc\u03bc\u03b1 \u03ba\u03bb\u03b5\u03b9\u03b4\u03c9\u03bc\u03ad\u03bd\u03b1'\n   '%s   on %s': '%s   \u03c3\u03c4\u03bf %s'\n   BADGES: \u03a3\u0397\u039c\u0391\u03a4\u0391\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: \u03a4\u03b1 \u03c3\u03ae\u03bc\u03b1\u03c4\u03b1 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03bf\u03cd\u03bd \u03bc\u03cc\u03bd\u03bf \u03bc\u03b5 \u03c4\u03b7\u03bd \u03a4\u0397\u039b\u0395\u039c\u0395\u03a4\u03a1\u0399\u0391 \u03b5\u03bd\u03b5\u03c1\u03b3\u03ae \u03ba\u03b1\u03b9 \u03c4\u03bf xeRAbora \u03bd\u03b1 \u03c4\u03c1\u03ad\u03c7\u03b5\u03b9 \u03c3\u03c4\u03bf\u03bd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03c3\u03b1\u03c2\u00b7 \u03bc\u03ad\u03c7\u03c1\u03b9 \u03c4\u03cc\u03c4\u03b5 \u03b4\u03b5\u03bd \u03ba\u03bf\u03c3\u03c4\u03af\u03b6\u03bf\u03c5\u03bd \u03c4\u03af\u03c0\u03bf\u03c4\u03b1.\n   CHECK THE DISC: \u0395\u039b\u0395\u0393\u03a7\u039f\u03a3 \u0394\u0399\u03a3\u039a\u039f\u03a5\n   From this game's last CHECK GAME SUPPORT.: \u0391\u03c0\u03cc \u03c4\u03bf\u03bd \u03c4\u03b5\u03bb\u03b5\u03c5\u03c4\u03b1\u03af\u03bf \u0395\u039b\u0395\u0393\u03a7\u039f \u03a5\u03a0\u039f\u03a3\u03a4\u0397\u03a1\u0399\u039e\u0397\u03a3 \u03a0\u0391\u0399\u03a7\u039d\u0399\u0394\u0399\u039f\u03a5 \u03b1\u03c5\u03c4\u03bf\u03cd \u03c4\u03bf\u03c5 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03bf\u03cd.\n   HOW IT WORKS: \u03a0\u03a9\u03a3 \u039b\u0395\u0399\u03a4\u039f\u03a5\u03a1\u0393\u0395\u0399\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: \u03a3\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03ba\u03bf\u03bd\u03c3\u03cc\u03bb\u03b1 \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 PS2BBL \u03bc\u03b5 \u03c4\u03bf \u03ba\u03bb\u03b5\u03af\u03b4\u03c9\u03bc\u03b1 \u03c4\u03bf\u03c5 RIPPS2.\n   AUDIO folder: \u03c6\u03ac\u03ba\u03b5\u03bb\u03bf\u03c2 AUDIO\n   DRIVES: \u0394\u0399\u03a3\u039a\u039f\u0399\n-  INSTALL RIPPS2'S COPY: \u0395\u0393\u039a\u0391\u03a4\u0391\u03a3\u03a4\u0391\u03a3\u0397 \u03a4\u039f\u03a5 \u0391\u039d\u03a4\u0399\u0393\u03a1\u0391\u03a6\u039f\u03a5 \u03a4\u039f\u03a5 RIPPS2\n-  'Installed: ': '\u0395\u03b3\u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03ac\u03b8\u03b7\u03ba\u03b5: '\n   MANAGE: \u0394\u0399\u0391\u03a7\u0395\u0399\u03a1\u0399\u03a3\u0397\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: \u0397 \u0394\u0399\u0391\u03a7\u0395\u0399\u03a1\u0399\u03a3\u0397 \u03b4\u03b5\u03af\u03c7\u03bd\u03b5\u03b9 \u03c4\u03b9 \u03be\u03b5\u03ba\u03b9\u03bd\u03ac \u03ba\u03ac\u03b8\u03b5 \u03ba\u03ac\u03c1\u03c4\u03b1 \u03bc\u03bd\u03ae\u03bc\u03b7\u03c2, \u03ba\u03b1\u03b9 \u03b5\u03b3\u03ba\u03b1\u03b8\u03b9\u03c3\u03c4\u03ac \u03ae \u03b5\u03c0\u03b9\u03c3\u03ba\u03b5\u03c5\u03ac\u03b6\u03b5\u03b9 \u03c4\u03bf BIBLE.\n   MEMORY CARD 0  (about 600 KB): \u039a\u0391\u03a1\u03a4\u0391 \u039c\u039d\u0397\u039c\u0397\u03a3 0  (\u03c0\u03b5\u03c1\u03af\u03c0\u03bf\u03c5 600 KB)\n   MEMORY CARD 1  (about 600 KB): \u039a\u0391\u03a1\u03a4\u0391 \u039c\u039d\u0397\u039c\u0397\u03a3 1  (\u03c0\u03b5\u03c1\u03af\u03c0\u03bf\u03c5 600 KB)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO: \u03bf \u03bf\u03b4\u03b7\u03b3\u03cc\u03c2 \u03c4\u03bf\u03c5 \u03b4\u03b5\u03bd \u03c6\u03bf\u03c1\u03c4\u03ce\u03b8\u03b7\u03ba\u03b5 (\u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 %d).'\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO: \u03bf \u03bf\u03b4\u03b7\u03b3\u03cc\u03c2 \u03c6\u03bf\u03c1\u03c4\u03ce\u03b8\u03b7\u03ba\u03b5, \u03b1\u03bb\u03bb\u03ac \u03ba\u03b1\u03bc\u03af\u03b1 \u03ba\u03ac\u03c1\u03c4\u03b1 SD \u03b4\u03b5\u03bd \u03b1\u03c0\u03ac\u03bd\u03c4\u03b7\u03c3\u03b5 (\u03c5\u03c0\u03bf\u03b4\u03bf\u03c7\u03ae mc1).'\n   NEUTRINO SETTINGS: \u03a1\u03a5\u0398\u039c\u0399\u03a3\u0395\u0399\u03a3 NEUTRINO\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': '\u0391\u03ba\u03cc\u03bc\u03b1 \u03ba\u03b1\u03bd\u03ad\u03bd\u03b1\u03c2: \u03c4\u03bf RIPPS2 \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b4\u03b9\u03ba\u03bf\u03cd\u03c2 \u03c4\u03bf\u03c5 \u03ae\u03c7\u03bf\u03c5\u03c2. \u0395\u03be\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03bf\u03cd\u03c2 \u03c4\u03b7\u03c2 \u03ba\u03bf\u03bd\u03c3\u03cc\u03bb\u03b1\u03c2, \u03ae \u03c6\u03c4\u03b9\u03ac\u03be\u03c4\u03b5 \u03c4\u03bf\u03c5\u03c2 \u03c3\u03c4\u03bf RIPPS2 Audio Studio.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: \u0394\u03b5\u03bd \u03b5\u03b3\u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03ac\u03b8\u03b7\u03ba\u03b5. \u0388\u03bd\u03b1 \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9 \u03c0\u03bf\u03c5 \u03c4\u03bf \u03c7\u03c1\u03b5\u03b9\u03ac\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c0\u03c1\u03bf\u03c3\u03c6\u03ad\u03c1\u03b5\u03b9 \u03c4\u03bf \u03b1\u03bd\u03c4\u03af\u03b3\u03c1\u03b1\u03c6\u03bf \u03c4\u03bf\u03c5 RIPPS2, \u03ae \u03b5\u03b3\u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03b5\u03b4\u03ce.\n   POPSTARTER NETWORK: \u0394\u0399\u039a\u03a4\u03a5\u039f POPSTARTER\n   POPSTARTER and Ember: POPSTARTER \u03ba\u03b1\u03b9 Ember\n   PS1 SETTINGS: \u03a1\u03a5\u0398\u039c\u0399\u03a3\u0395\u0399\u03a3 PS1\n   PS2BBL 1.2.0 for RIPPS2: PS2BBL 1.2.0 \u03b3\u03b9\u03b1 \u03c4\u03bf RIPPS2\n   RIP THIS CONSOLE'S SOUNDS: \u0395\u039e\u0391\u0393\u03a9\u0393\u0397 \u03a4\u03a9\u039d \u0397\u03a7\u03a9\u039d \u0391\u03a5\u03a4\u0397\u03a3 \u03a4\u0397\u03a3 \u039a\u039f\u039d\u03a3\u039f\u039b\u0391\u03a3\n-  RIPPS2's homebrew: \u03c4\u03bf homebrew \u03c4\u03bf\u03c5 RIPPS2\n   STATUS: \u039a\u0391\u03a4\u0391\u03a3\u03a4\u0391\u03a3\u0397\n-  'STATUS: looking for RIPPS2''s homebrew': '\u039a\u0391\u03a4\u0391\u03a3\u03a4\u0391\u03a3\u0397: \u03b1\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 homebrew \u03c4\u03bf\u03c5 RIPPS2'\n   a drive of unknown kind: \u03b4\u03af\u03c3\u03ba\u03bf\u03c2 \u03ac\u03b3\u03bd\u03c9\u03c3\u03c4\u03bf\u03c5 \u03b5\u03af\u03b4\u03bf\u03c5\u03c2\n-  a drive or a memory card: \u03b4\u03af\u03c3\u03ba\u03bf\u03c2 \u03ae \u03ba\u03ac\u03c1\u03c4\u03b1 \u03bc\u03bd\u03ae\u03bc\u03b7\u03c2\n   boot loader, card by card: \u03c6\u03bf\u03c1\u03c4\u03c9\u03c4\u03ae\u03c2 \u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7\u03c2, \u03ba\u03ac\u03c1\u03c4\u03b1 \u03c0\u03c1\u03bf\u03c2 \u03ba\u03ac\u03c1\u03c4\u03b1\n   from its BIOS, into AUDIO: \u03b1\u03c0\u03cc \u03c4\u03bf BIOS \u03c4\u03b7\u03c2, \u03c3\u03c4\u03bf AUDIO\n   internal HDD (exFAT): \u03b5\u03c3\u03c9\u03c4\u03b5\u03c1\u03b9\u03ba\u03cc\u03c2 HDD (exFAT)\n@@ -975,10 +958,194 @@ strings:\n   Sidebar: \u03a0\u03bb\u03b1\u03ca\u03bd\u03ae \u03bc\u03c0\u03ac\u03c1\u03b1\n   your own copy, found on your drives and memory cards: \u03c4\u03bf \u03b4\u03b9\u03ba\u03cc \u03c3\u03b1\u03c2 \u03b1\u03bd\u03c4\u03af\u03b3\u03c1\u03b1\u03c6\u03bf, \u03c0\u03bf\u03c5 \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03c3\u03c4\u03bf\u03c5\u03c2 \u03b4\u03af\u03c3\u03ba\u03bf\u03c5\u03c2 \u03ba\u03b1\u03b9 \u03c4\u03b9\u03c2 \u03ba\u03ac\u03c1\u03c4\u03b5\u03c2 \u03bc\u03bd\u03ae\u03bc\u03b7\u03c2 \u03c3\u03b1\u03c2\n   'your own, found and launched: github.com/Gageformer/Ember/releases': '\u03c4\u03bf \u03b4\u03b9\u03ba\u03cc \u03c3\u03b1\u03c2, \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf \u03ba\u03b1\u03b9 \u03b5\u03ba\u03ba\u03b9\u03bd\u03b7\u03bc\u03ad\u03bd\u03bf: github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': '\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03c3\u03c4\u03bf mass0: (%s). \u0392\u03ac\u03bb\u03c4\u03b5 \u03c4\u03bf ember.elf \u03ba\u03b1\u03b9 \u03c4\u03bf bios.bin \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf\u03bd \u03c6\u03ac\u03ba\u03b5\u03bb\u03bf EMBER \u03c4\u03bf\u03c5.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: \u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5. \u0392\u03ac\u03bb\u03c4\u03b5 \u03c4\u03bf \u03b4\u03b9\u03ba\u03cc \u03c3\u03b1\u03c2 POPSTARTER.ELF \u03c3\u03c4\u03bf\u03bd \u03c6\u03ac\u03ba\u03b5\u03bb\u03bf POPS \u03b5\u03bd\u03cc\u03c2 \u03b4\u03af\u03c3\u03ba\u03bf\u03c5.\n   Launch Disc: \u0395\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7 \u03b4\u03af\u03c3\u03ba\u03bf\u03c5\n   Memory Files: \u0391\u03c1\u03c7\u03b5\u03af\u03b1 \u03bc\u03bd\u03ae\u03bc\u03b7\u03c2\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: \u03a4\u03bf RIPPS2 \u03b2\u03c1\u03ae\u03ba\u03b5 %d \u03b1\u03c0\u03cc \u03c4\u03b1 5. \u03a4\u03bf \u03ba\u03b1\u03b8\u03ad\u03bd\u03b1 \u03b1\u03c0\u03bf\u03bc\u03bd\u03b7\u03bc\u03bf\u03bd\u03b5\u03cd\u03b5\u03c4\u03b1\u03b9, \u03ba\u03b1\u03b9 \u03ba\u03ac\u03b8\u03b5 \u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7 \u03c4\u03bf \u03b4\u03b9\u03b1\u03b2\u03ac\u03b6\u03b5\u03b9 \u03b1\u03c0\u03cc \u03b5\u03ba\u03b5\u03af.\n+  not found on the started drives: \u03b4\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03c3\u03c4\u03bf\u03c5\u03c2 \u03be\u03b5\u03ba\u03b9\u03bd\u03b7\u03bc\u03ad\u03bd\u03bf\u03c5\u03c2 \u03b4\u03af\u03c3\u03ba\u03bf\u03c5\u03c2\n+  not looked for yet: \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b1\u03bd\u03b1\u03b6\u03b7\u03c4\u03b7\u03b8\u03b5\u03af \u03b1\u03ba\u03cc\u03bc\u03b1\n+  '%s did not answer, so %s cannot start. Is it connected?': \u03a4\u03bf %s \u03b4\u03b5\u03bd \u03b1\u03c0\u03ac\u03bd\u03c4\u03b7\u03c3\u03b5, \u03bf\u03c0\u03cc\u03c4\u03b5 \u03c4\u03bf %s \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03b9. \u0395\u03af\u03bd\u03b1\u03b9 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b4\u03b5\u03bc\u03ad\u03bd\u03bf;\n+  '%s is not on %s any more, so it has left All Games.': \u03a4\u03bf %s \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03b9\u03b1 \u03c3\u03c4\u03bf %s, \u03bf\u03c0\u03cc\u03c4\u03b5 \u03ad\u03c6\u03c5\u03b3\u03b5 \u03b1\u03c0\u03cc \u03c4\u03bf \u038c\u03bb\u03b1 \u03c4\u03b1 \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9\u03b1.\n+  STARTING %s: \u0395\u039a\u039a\u0399\u039d\u0397\u03a3\u0397 %s\n+  the SMB share: \u03bf \u03ba\u03bf\u03b9\u03bd\u03cc\u03c7\u03c1\u03b7\u03c3\u03c4\u03bf\u03c2 \u03c6\u03ac\u03ba\u03b5\u03bb\u03bf\u03c2 SMB\n+  the drive: \u03bf \u03b4\u03af\u03c3\u03ba\u03bf\u03c2\n+  the internal HDD (APA): \u03bf \u03b5\u03c3\u03c9\u03c4\u03b5\u03c1\u03b9\u03ba\u03cc\u03c2 HDD (APA)\n+  the internal HDD (exFAT): \u03bf \u03b5\u03c3\u03c9\u03c4\u03b5\u03c1\u03b9\u03ba\u03cc\u03c2 HDD (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: \u03a7\u03a4\u0399\u03a3\u039c\u0395\u039d\u039f \u03a0\u0391\u039d\u03a9 \u03a3\u03a4\u039f RIPTOPL, \u039c\u0395 \u03a4\u039f NEUTRINO\n+  'Special thanks to all the YouTube love as well:': '\u0399\u03b4\u03b9\u03b1\u03af\u03c4\u03b5\u03c1\u03b5\u03c2 \u03b5\u03c5\u03c7\u03b1\u03c1\u03b9\u03c3\u03c4\u03af\u03b5\u03c2 \u03ba\u03b1\u03b9 \u03c3\u03b5 \u03cc\u03bb\u03b7 \u03c4\u03b7\u03bd \u03b1\u03b3\u03ac\u03c0\u03b7 \u03b1\u03c0\u03cc \u03c4\u03bf YouTube:'\n+  and its own look, launchers, file browser and save tools.: \u03ba\u03b1\u03b9 \u03bc\u03b5 \u03b4\u03b9\u03ba\u03ae \u03c4\u03bf\u03c5 \u03b5\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7, \u03b5\u03ba\u03ba\u03b9\u03bd\u03b7\u03c4\u03ad\u03c2, \u03c0\u03b5\u03c1\u03b9\u03b7\u03b3\u03b7\u03c4\u03ae \u03b1\u03c1\u03c7\u03b5\u03af\u03c9\u03bd \u03ba\u03b1\u03b9 \u03b5\u03c1\u03b3\u03b1\u03bb\u03b5\u03af\u03b1 \u03b1\u03c0\u03bf\u03b8\u03b7\u03ba\u03b5\u03cd\u03c3\u03b5\u03c9\u03bd.\n+  glad to help however you reach me.: \u03c7\u03b1\u03c1\u03ac \u03bc\u03bf\u03c5 \u03bd\u03b1 \u03b2\u03bf\u03b7\u03b8\u03ae\u03c3\u03c9, \u03cc\u03c0\u03c9\u03c2 \u03ba\u03b9 \u03b1\u03bd \u03bc\u03b5 \u03b2\u03c1\u03b5\u03af\u03c4\u03b5.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): \u03bf \u03b4\u03b5\u03cd\u03c4\u03b5\u03c1\u03bf\u03c2 \u03c0\u03c5\u03c1\u03ae\u03bd\u03b1\u03c2 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03ce\u03bd, 1.8.0 \u03b1\u03c5\u03c4\u03bf\u03cd\u03c3\u03b9\u03bf\u03c2 (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: \u03bc\u03b5 \u03c4\u03bf Neutrino \u03c4\u03bf\u03c5 rickgaiser \u03bc\u03ad\u03c3\u03b1 \u03c9\u03c2 \u03b4\u03b5\u03cd\u03c4\u03b5\u03c1\u03bf \u03c0\u03c5\u03c1\u03ae\u03bd\u03b1 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03ce\u03bd,\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : '\u0391\u03c5\u03c4\u03cc \u03c4\u03bf \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9 \u03be\u03b5\u03ba\u03b9\u03bd\u03ac \u03bc\u03ad\u03c3\u03c9 Neutrino, \u03ba\u03b1\u03b9 \u03c4\u03bf RIPPS2 \u03b4\u03b5\u03bd \u03b2\u03c1\u03ae\u03ba\u03b5 \u03ba\u03b1\u03bd\u03ad\u03bd\u03b1: \u03bf\u03cd\u03c4\u03b5 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c4\u03c9\u03bd \u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c9\u03bd, \u03bf\u03cd\u03c4\u03b5 \u03c3\u03c4\u03bf\u03bd \u03b4\u03af\u03c3\u03ba\u03bf \u03c4\u03bf\u03c5 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03bf\u03cd, \u03bf\u03cd\u03c4\u03b5 \u03c3\u03c4\u03b9\u03c2 \u03ba\u03ac\u03c1\u03c4\u03b5\u03c2 \u03bc\u03bd\u03ae\u03bc\u03b7\u03c2, \u03bf\u03cd\u03c4\u03b5 \u03c3\u03b5 \u03be\u03b5\u03ba\u03b9\u03bd\u03b7\u03bc\u03ad\u03bd\u03bf \u03b4\u03af\u03c3\u03ba\u03bf. \u03a4\u03bf RIPPS2 \u03ad\u03c7\u03b5\u03b9 \u03c4\u03bf Neutrino 1.8.0 \u03c4\u03bf\u03c5 rickgaiser (AFL-3.0): \u03b5\u03b3\u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03bc\u03af\u03b1 \u03c6\u03bf\u03c1\u03ac, \u03cc\u03c0\u03bf\u03c5 \u03b4\u03b9\u03b1\u03bb\u03ad\u03be\u03b5\u03c4\u03b5, \u03ba\u03b1\u03b9 \u03ba\u03ac\u03b8\u03b5 \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9 Neutrino \u03c4\u03bf \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03b1\u03c0\u03cc \u03b5\u03ba\u03b5\u03af.'\n+  STARTING THE DRIVES: \u0395\u039a\u039a\u0399\u039d\u0397\u03a3\u0397 \u03a4\u03a9\u039d \u0394\u0399\u03a3\u039a\u03a9\u039d\n+  '%d games, on the started drives': '%d \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9\u03b1, \u03c3\u03c4\u03bf\u03c5\u03c2 \u03be\u03b5\u03ba\u03b9\u03bd\u03b7\u03bc\u03ad\u03bd\u03bf\u03c5\u03c2 \u03b4\u03af\u03c3\u03ba\u03bf\u03c5\u03c2'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s: \u039a\u0391\u03a0\u039f\u0399\u0391 \u0391\u03a1\u03a7\u0395\u0399\u0391 \u0394\u0395\u039d \u0394\u0399\u0391\u0393\u03a1\u0391\u03a6\u0397\u039a\u0391\u039d'\n+  '%u partitions, all in order': '%u \u03b4\u03b9\u03b1\u03bc\u03b5\u03c1\u03af\u03c3\u03bc\u03b1\u03c4\u03b1, \u03cc\u03bb\u03b1 \u03c3\u03b5 \u03c4\u03ac\u03be\u03b7'\n+  ', %d art images': ', %d \u03b5\u03b9\u03ba\u03cc\u03bd\u03b5\u03c2 \u03b5\u03be\u03c9\u03c6\u03cd\u03bb\u03bb\u03c9\u03bd'\n+  ', and ': ', \u03ba\u03b1\u03b9 '\n+  ', its cheats': ', \u03c4\u03b1 cheats \u03c4\u03bf\u03c5'\n+  ', its settings': ', \u03bf\u03b9 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c4\u03bf\u03c5'\n+  1 game, on the started drives: 1 \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9, \u03c3\u03c4\u03bf\u03c5\u03c2 \u03be\u03b5\u03ba\u03b9\u03bd\u03b7\u03bc\u03ad\u03bd\u03bf\u03c5\u03c2 \u03b4\u03af\u03c3\u03ba\u03bf\u03c5\u03c2\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: \u0388\u03bd\u03b1 \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9 PS1 \u03b5\u03b4\u03ce \u03b4\u03b9\u03b1\u03b3\u03c1\u03ac\u03c6\u03b5\u03c4\u03b1\u03b9 \u03c3\u03c4\u03b1 \u0391\u03c1\u03c7\u03b5\u03af\u03b1 \u03bc\u03bd\u03ae\u03bc\u03b7\u03c2, \u03cc\u03c0\u03bf\u03c5 \u03b5\u03af\u03bd\u03b1\u03b9 \u03be\u03b5\u03c7\u03c9\u03c1\u03b9\u03c3\u03c4\u03cc \u03b1\u03c1\u03c7\u03b5\u03af\u03bf.\n+  A network catalogue's games are deleted on the PC that serves them.: \u03a4\u03b1 \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9\u03b1 \u03b5\u03bd\u03cc\u03c2 \u03b4\u03b9\u03ba\u03c4\u03c5\u03b1\u03ba\u03bf\u03cd \u03ba\u03b1\u03c4\u03b1\u03bb\u03cc\u03b3\u03bf\u03c5 \u03b4\u03b9\u03b1\u03b3\u03c1\u03ac\u03c6\u03bf\u03bd\u03c4\u03b1\u03b9 \u03c3\u03c4\u03bf\u03bd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03c0\u03bf\u03c5 \u03c4\u03b1 \u03c3\u03b5\u03c1\u03b2\u03af\u03c1\u03b5\u03b9.\n+  DELETE FOR GOOD: \u0394\u0399\u0391\u0393\u03a1\u0391\u03a6\u0397 \u0393\u0399\u0391 \u03a0\u0391\u039d\u03a4\u0391\n+  DELETE IT: \u0394\u0399\u0391\u0393\u03a1\u0391\u03a8\u0395 \u03a4\u039f\n+  'DELETED %s: %s FREED': '%s \u0394\u0399\u0391\u0393\u03a1\u0391\u03a6\u0397\u039a\u0395: \u0395\u039b\u0395\u03a5\u0398\u0395\u03a1\u03a9\u0398\u0397\u039a\u0391\u039d %s'\n+  'Delete %s from %s? Goes: %s.': '\u0394\u03b9\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae \u03c4\u03bf\u03c5 %s \u03b1\u03c0\u03cc \u03c4\u03bf %s; \u03a6\u03b5\u03cd\u03b3\u03b5\u03b9: %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': '\u0394\u03b9\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae \u03c4\u03bf\u03c5 %s \u03b1\u03c0\u03cc \u03c4\u03bf %s; \u03a6\u03b5\u03cd\u03b3\u03b5\u03b9: %s. \u039c\u03ad\u03bd\u03b5\u03b9: %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: \u0394\u03b9\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae \u03ba\u03b1\u03b9 \u03c4\u03c9\u03bd \u03b1\u03c0\u03bf\u03b8\u03b7\u03ba\u03b5\u03cd\u03c3\u03b5\u03c9\u03bd \u03ba\u03b1\u03b9 \u03c4\u03c9\u03bd \u03b5\u03b9\u03ba\u03cc\u03bd\u03c9\u03bd RIPSHOT \u03c4\u03bf\u03c5; \u03a4\u03bf \u038c\u03c7\u03b9 \u03c4\u03b1 \u03ba\u03c1\u03b1\u03c4\u03ac.\n+  HIDDEN GAMES: \u039a\u03a1\u03a5\u039c\u039c\u0395\u039d\u0391 \u03a0\u0391\u0399\u03a7\u039d\u0399\u0394\u0399\u0391\n+  'HIDDEN: %s': '\u039a\u03a1\u03a5\u03a6\u03a4\u0397\u039a\u0395: %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: \u0391\u03c0\u03cc\u03ba\u03c1\u03c5\u03c8\u03b7 \u03c4\u03bf\u03c5 %s; \u03a6\u03b5\u03cd\u03b3\u03b5\u03b9 \u03b1\u03c0\u03cc \u03ba\u03ac\u03b8\u03b5 \u03bb\u03af\u03c3\u03c4\u03b1, \u03ba\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03bf \u038c\u03bb\u03b1 \u03c4\u03b1 \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9\u03b1 \u03ba\u03b1\u03b9 \u03c4\u03b1 \u0391\u03b3\u03b1\u03c0\u03b7\u03bc\u03ad\u03bd\u03b1. \u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 > \u03a0\u03b7\u03b3\u03ad\u03c2 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03ce\u03bd > \u039a\u03c1\u03c5\u03bc\u03bc\u03ad\u03bd\u03b1 \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9\u03b1 \u03c4\u03bf \u03b4\u03b5\u03af\u03c7\u03bd\u03b5\u03b9 \u03be\u03b1\u03bd\u03ac.\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: \u03a4\u03b5\u03bb\u03b5\u03c5\u03c4\u03b1\u03af\u03bf\u03c2 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2. \u03a4\u03bf %s \u03b4\u03b9\u03b1\u03b3\u03c1\u03ac\u03c6\u03b5\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03c0\u03ac\u03bd\u03c4\u03b1, \u03ba\u03b1\u03b9 \u03c4\u03af\u03c0\u03bf\u03c4\u03b1 \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c4\u03bf \u03c6\u03ad\u03c1\u03b5\u03b9 \u03c0\u03af\u03c3\u03c9. \u03a0\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 START \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c4\u03bf \u03b4\u03b9\u03b1\u03b3\u03c1\u03ac\u03c8\u03b5\u03c4\u03b5.\n+  None. Hide Game, in a game's options, puts one here.: \u039a\u03b1\u03bd\u03ad\u03bd\u03b1. \u0397 \u0391\u03c0\u03cc\u03ba\u03c1\u03c5\u03c8\u03b7 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03bf\u03cd, \u03c3\u03c4\u03b9\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b5\u03bd\u03cc\u03c2 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03bf\u03cd, \u03b2\u03ac\u03b6\u03b5\u03b9 \u03ad\u03bd\u03b1 \u03b5\u03b4\u03ce.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : '\u0394\u03b5\u03bd \u03b4\u03b9\u03b1\u03b3\u03c1\u03ac\u03c6\u03b7\u03ba\u03b5 \u03c4\u03af\u03c0\u03bf\u03c4\u03b1: \u03bf \u03c0\u03af\u03bd\u03b1\u03ba\u03b1\u03c2 \u03b4\u03b9\u03b1\u03bc\u03b5\u03c1\u03b9\u03c3\u03bc\u03ac\u03c4\u03c9\u03bd \u03b1\u03c5\u03c4\u03bf\u03cd \u03c4\u03bf\u03c5 \u03b4\u03af\u03c3\u03ba\u03bf\u03c5 \u03b4\u03b5\u03bd \u03c4\u03b1\u03b9\u03c1\u03b9\u03ac\u03b6\u03b5\u03b9 \u03c3\u03b5 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03b4\u03af\u03c3\u03ba\u03bf (%s). \u039c\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b3\u03c1\u03ac\u03c6\u03c4\u03b7\u03ba\u03b5 \u03b1\u03c0\u03cc \u03b5\u03af\u03b4\u03c9\u03bb\u03bf \u03b4\u03af\u03c3\u03ba\u03bf\u03c5 \u03ac\u03bb\u03bb\u03bf\u03c5 \u03bc\u03b5\u03b3\u03ad\u03b8\u03bf\u03c5\u03c2. \u0394\u03b9\u03bf\u03c1\u03b8\u03ce\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c0\u03c1\u03ce\u03c4\u03b1 \u03c3\u03b5 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae.'\n+  RIPPS2 cannot delete a game from this page.: \u03a4\u03bf RIPPS2 \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b4\u03b9\u03b1\u03b3\u03c1\u03ac\u03c8\u03b5\u03b9 \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9 \u03b1\u03c0\u03cc \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7 \u03c3\u03b5\u03bb\u03af\u03b4\u03b1.\n+  RIPPS2 could not find this game's files on its drive.: \u03a4\u03bf RIPPS2 \u03b4\u03b5\u03bd \u03b2\u03c1\u03ae\u03ba\u03b5 \u03c4\u03b1 \u03b1\u03c1\u03c7\u03b5\u03af\u03b1 \u03b1\u03c5\u03c4\u03bf\u03cd \u03c4\u03bf\u03c5 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03bf\u03cd \u03c3\u03c4\u03bf\u03bd \u03b4\u03af\u03c3\u03ba\u03bf \u03c4\u03bf\u03c5.\n+  Reading the hidden lists: \u0391\u03bd\u03ac\u03b3\u03bd\u03c9\u03c3\u03b7 \u03c4\u03c9\u03bd \u03ba\u03c1\u03c5\u03c6\u03ce\u03bd \u03bb\u03b9\u03c3\u03c4\u03ce\u03bd\n+  SHOW AGAIN OR DELETE: \u0395\u039c\u03a6\u0391\u039d\u0399\u03a3\u0397 \u039e\u0391\u039d\u0391 \u0389 \u0394\u0399\u0391\u0393\u03a1\u0391\u03a6\u0397\n+  SHOW IT AGAIN: \u0394\u0395\u0399\u039e\u0395 \u03a4\u039f \u039e\u0391\u039d\u0391\n+  That game is not on its drive any more.: \u0391\u03c5\u03c4\u03cc \u03c4\u03bf \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03b9\u03b1 \u03c3\u03c4\u03bf\u03bd \u03b4\u03af\u03c3\u03ba\u03bf \u03c4\u03bf\u03c5.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: \u0397 \u03ba\u03c1\u03c5\u03c6\u03ae \u03bb\u03af\u03c3\u03c4\u03b1 \u03b4\u03b5\u03bd \u03b3\u03c1\u03ac\u03c6\u03c4\u03b7\u03ba\u03b5 \u03c3\u03c4\u03bf\u03bd \u03b4\u03af\u03c3\u03ba\u03bf \u03c4\u03bf\u03c5 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03bf\u03cd. \u0395\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03bf\u03c3\u03c4\u03b1\u03c4\u03b5\u03c5\u03bc\u03ad\u03bd\u03bf\u03c2 \u03b1\u03c0\u03cc \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03ae, \u03ae \u03b5\u03af\u03bd\u03b1\u03b9 \u03ba\u03bb\u03b5\u03b9\u03c3\u03c4\u03ad\u03c2 \u03bf\u03b9 \u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b5\u03c2 \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2;\n+  The partition could not be removed (%d). Nothing was deleted.: \u03a4\u03bf \u03b4\u03b9\u03b1\u03bc\u03ad\u03c1\u03b9\u03c3\u03bc\u03b1 \u03b4\u03b5\u03bd \u03b1\u03c6\u03b1\u03b9\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 (%d). \u0394\u03b5\u03bd \u03b4\u03b9\u03b1\u03b3\u03c1\u03ac\u03c6\u03b7\u03ba\u03b5 \u03c4\u03af\u03c0\u03bf\u03c4\u03b1.\n+  This drive cannot keep a hidden list (a network catalogue).: \u0391\u03c5\u03c4\u03cc\u03c2 \u03bf \u03b4\u03af\u03c3\u03ba\u03bf\u03c2 \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03ba\u03c1\u03b1\u03c4\u03ac \u03ba\u03c1\u03c5\u03c6\u03ae \u03bb\u03af\u03c3\u03c4\u03b1 (\u03b4\u03b9\u03ba\u03c4\u03c5\u03b1\u03ba\u03cc\u03c2 \u03ba\u03b1\u03c4\u03ac\u03bb\u03bf\u03b3\u03bf\u03c2).\n+  This game's drive has not answered yet. Wait for it, then try again.: \u039f \u03b4\u03af\u03c3\u03ba\u03bf\u03c2 \u03b1\u03c5\u03c4\u03bf\u03cd \u03c4\u03bf\u03c5 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03bf\u03cd \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03b1\u03c0\u03b1\u03bd\u03c4\u03ae\u03c3\u03b5\u03b9 \u03b1\u03ba\u03cc\u03bc\u03b1. \u03a0\u03b5\u03c1\u03b9\u03bc\u03ad\u03bd\u03b5\u03c4\u03ad \u03c4\u03bf\u03bd \u03ba\u03b1\u03b9 \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: \u03a4\u03bf \u03b4\u03b9\u03b1\u03bc\u03ad\u03c1\u03b9\u03c3\u03bc\u03b1 \u03b1\u03c5\u03c4\u03bf\u03cd \u03c4\u03bf\u03c5 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03bf\u03cd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c0\u03cc \u03b1\u03c5\u03c4\u03ac \u03c0\u03bf\u03c5 \u03c4\u03bf RIPPS2 \u03b4\u03b5\u03bd \u03b1\u03c6\u03b1\u03b9\u03c1\u03b5\u03af (\u03b4\u03b9\u03b1\u03bc\u03ad\u03c1\u03b9\u03c3\u03bc\u03b1 \u03c3\u03c5\u03c3\u03c4\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2, \u03ae \u03b1\u03c5\u03c4\u03cc \u03bc\u03b5 \u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c4\u03bf\u03c5).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': '\u03ad\u03bd\u03b1 \u03b4\u03b9\u03b1\u03bc\u03ad\u03c1\u03b9\u03c3\u03bc\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03bc\u03b5\u03b3\u03b1\u03bb\u03cd\u03c4\u03b5\u03c1\u03bf \u03b1\u03c0'' \u03cc\u03c3\u03bf \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03b5\u03b9 \u03b1\u03c5\u03c4\u03cc\u03c2 \u03bf \u03b4\u03af\u03c3\u03ba\u03bf\u03c2: \u03bf \u03c0\u03af\u03bd\u03b1\u03ba\u03b1\u03c2 \u03c6\u03c4\u03b9\u03ac\u03c7\u03c4\u03b7\u03ba\u03b5 \u03b3\u03b9\u03b1 \u03b4\u03af\u03c3\u03ba\u03bf \u03ac\u03bb\u03bb\u03bf\u03c5 \u03bc\u03b5\u03b3\u03ad\u03b8\u03bf\u03c5\u03c2'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': '\u03ad\u03bd\u03b1 \u03b4\u03b9\u03b1\u03bc\u03ad\u03c1\u03b9\u03c3\u03bc\u03b1 \u03c0\u03b5\u03c1\u03bd\u03ac \u03c4\u03bf \u03c4\u03ad\u03bb\u03bf\u03c2 \u03b1\u03c5\u03c4\u03bf\u03cd \u03c4\u03bf\u03c5 \u03b4\u03af\u03c3\u03ba\u03bf\u03c5 (\u03c4\u03bf\u03bc\u03ad\u03b1\u03c2 %u \u03b1\u03c0\u03cc %u): \u03bf \u03c0\u03af\u03bd\u03b1\u03ba\u03b1\u03c2 \u03c6\u03c4\u03b9\u03ac\u03c7\u03c4\u03b7\u03ba\u03b5 \u03b3\u03b9\u03b1 \u03bc\u03b5\u03b3\u03b1\u03bb\u03cd\u03c4\u03b5\u03c1\u03bf \u03b4\u03af\u03c3\u03ba\u03bf'\n+  its RIPSHOT pictures: \u03bf\u03b9 \u03b5\u03b9\u03ba\u03cc\u03bd\u03b5\u03c2 RIPSHOT \u03c4\u03bf\u03c5\n+  its drive: \u03bf \u03b4\u03af\u03c3\u03ba\u03bf\u03c2 \u03c4\u03bf\u03c5\n+  its saves: \u03bf\u03b9 \u03b1\u03c0\u03bf\u03b8\u03b7\u03ba\u03b5\u03cd\u03c3\u03b5\u03b9\u03c2 \u03c4\u03bf\u03c5\n+  sector %u holds no partition header: \u03bf \u03c4\u03bf\u03bc\u03ad\u03b1\u03c2 %u \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03b9 \u03ba\u03b5\u03c6\u03b1\u03bb\u03af\u03b4\u03b1 \u03b4\u03b9\u03b1\u03bc\u03b5\u03c1\u03af\u03c3\u03bc\u03b1\u03c4\u03bf\u03c2\n+  the chain does not close at __mbr: \u03b7 \u03b1\u03bb\u03c5\u03c3\u03af\u03b4\u03b1 \u03b4\u03b5\u03bd \u03ba\u03bb\u03b5\u03af\u03bd\u03b5\u03b9 \u03c3\u03c4\u03bf __mbr\n+  the chain skips from sector %u: \u03b7 \u03b1\u03bb\u03c5\u03c3\u03af\u03b4\u03b1 \u03c0\u03b7\u03b4\u03ac \u03b1\u03c0\u03cc \u03c4\u03bf\u03bd \u03c4\u03bf\u03bc\u03ad\u03b1 %u\n+  the chain's links disagree at sector %u: \u03bf\u03b9 \u03ba\u03c1\u03af\u03ba\u03bf\u03b9 \u03c4\u03b7\u03c2 \u03b1\u03bb\u03c5\u03c3\u03af\u03b4\u03b1\u03c2 \u03b4\u03b9\u03b1\u03c6\u03c9\u03bd\u03bf\u03cd\u03bd \u03c3\u03c4\u03bf\u03bd \u03c4\u03bf\u03bc\u03ad\u03b1 %u\n+  the drive's size could not be read: \u03c4\u03bf \u03bc\u03ad\u03b3\u03b5\u03b8\u03bf\u03c2 \u03c4\u03bf\u03c5 \u03b4\u03af\u03c3\u03ba\u03bf\u03c5 \u03b4\u03b5\u03bd \u03b4\u03b9\u03b1\u03b2\u03ac\u03c3\u03c4\u03b7\u03ba\u03b5\n+  the game (%s): \u03c4\u03bf \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9 (%s)\n+  the game's folder and everything in it (%s): \u03bf \u03c6\u03ac\u03ba\u03b5\u03bb\u03bf\u03c2 \u03c4\u03bf\u03c5 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03bf\u03cd \u03ba\u03b1\u03b9 \u03cc,\u03c4\u03b9 \u03ad\u03c7\u03b5\u03b9 \u03bc\u03ad\u03c3\u03b1 (%s)\n+  the header at sector %u could not be read: \u03b7 \u03ba\u03b5\u03c6\u03b1\u03bb\u03af\u03b4\u03b1 \u03c3\u03c4\u03bf\u03bd \u03c4\u03bf\u03bc\u03ad\u03b1 %u \u03b4\u03b5\u03bd \u03b4\u03b9\u03b1\u03b2\u03ac\u03c3\u03c4\u03b7\u03ba\u03b5\n+  the header at sector %u fails its checksum: \u03b7 \u03ba\u03b5\u03c6\u03b1\u03bb\u03af\u03b4\u03b1 \u03c3\u03c4\u03bf\u03bd \u03c4\u03bf\u03bc\u03ad\u03b1 %u \u03b1\u03c0\u03bf\u03c4\u03c5\u03b3\u03c7\u03ac\u03bd\u03b5\u03b9 \u03c3\u03c4\u03bf \u03ac\u03b8\u03c1\u03bf\u03b9\u03c3\u03bc\u03b1 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5\n+  the partition at sector %u is not where or as large as APA puts one: \u03c4\u03bf \u03b4\u03b9\u03b1\u03bc\u03ad\u03c1\u03b9\u03c3\u03bc\u03b1 \u03c3\u03c4\u03bf\u03bd \u03c4\u03bf\u03bc\u03ad\u03b1 %u \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03ba\u03b5\u03af \u03c0\u03bf\u03c5, \u03bf\u03cd\u03c4\u03b5 \u03c4\u03cc\u03c3\u03bf \u03bc\u03b5\u03b3\u03ac\u03bb\u03bf \u03cc\u03c3\u03bf, \u03c4\u03bf \u03b2\u03ac\u03b6\u03b5\u03b9 \u03c4\u03bf APA\n+  the settings and art another copy of it uses: \u03bf\u03b9 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03ba\u03b1\u03b9 \u03c4\u03b1 \u03b5\u03be\u03ce\u03c6\u03c5\u03bb\u03bb\u03b1 \u03c0\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03ac\u03bb\u03bb\u03bf \u03b1\u03bd\u03c4\u03af\u03b3\u03c1\u03b1\u03c6\u03cc \u03c4\u03bf\u03c5\n+  '%d FOLDERS': '%d \u03a6\u0391\u039a\u0395\u039b\u039f\u0399'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': \u03a4\u03bf %s \u03b4\u03b5\u03bd \u03b1\u03bd\u03c4\u03b9\u03b3\u03c1\u03ac\u03c6\u03b7\u03ba\u03b5 \u03c3\u03c4\u03bf %s. \u0395\u03af\u03bd\u03b1\u03b9 \u03bf \u03b4\u03af\u03c3\u03ba\u03bf\u03c2 \u03b3\u03b5\u03bc\u03ac\u03c4\u03bf\u03c2 \u03ae \u03c0\u03c1\u03bf\u03c3\u03c4\u03b1\u03c4\u03b5\u03c5\u03bc\u03ad\u03bd\u03bf\u03c2 \u03b1\u03c0\u03cc \u03b5\u03b3\u03b3\u03c1\u03b1\u03c6\u03ae;\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : \u03a4\u03bf %s \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03ba\u03b5\u03af \u03c0\u03bf\u03c5 \u03c8\u03ac\u03c7\u03bd\u03b5\u03b9 \u03c4\u03bf RIPPS2. \u0391\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7 \u03c3\u03c4\u03bf\u03c5\u03c2 \u03b4\u03af\u03c3\u03ba\u03bf\u03c5\u03c2 \u03c0\u03bf\u03c5 \u03be\u03b5\u03ba\u03af\u03bd\u03b7\u03c3\u03b5 \u03c4\u03bf RIPPS2; \u038c\u03c0\u03bf\u03c5 \u03b2\u03c1\u03b5\u03b8\u03b5\u03af \u03b1\u03c0\u03bf\u03bc\u03bd\u03b7\u03bc\u03bf\u03bd\u03b5\u03cd\u03b5\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03ba\u03ac\u03b8\u03b5 \u03b5\u03c0\u03cc\u03bc\u03b5\u03bd\u03b7 \u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7.\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : '\u03a4\u03bf Ember \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c4\u03bf %s, \u03b4\u03af\u03c0\u03bb\u03b1 \u03c3\u03c4\u03b1 \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9\u03b1, \u03ba\u03b1\u03b9 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9. \u03a4\u03bf RIPPS2 \u03c4\u03bf \u03b2\u03c1\u03ae\u03ba\u03b5 \u03c3\u03c4\u03bf %s: \u03c4\u03bf START \u03c4\u03bf \u03b1\u03bd\u03c4\u03b9\u03b3\u03c1\u03ac\u03c6\u03b5\u03b9 \u03b5\u03ba\u03b5\u03af (%d KB) \u03ba\u03b1\u03b9 \u03be\u03b5\u03ba\u03b9\u03bd\u03ac \u03c4\u03bf \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9.'\n+  Ember's BIOS: \u03a4\u03bf BIOS \u03c4\u03bf\u03c5 Ember\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : '\u03a4\u03bf BIOS \u03c4\u03bf\u03c5 Ember (bios.bin) \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c4\u03bf %s, \u03b4\u03af\u03c0\u03bb\u03b1 \u03c3\u03c4\u03bf Ember. \u03a4\u03bf RIPPS2 \u03b2\u03c1\u03ae\u03ba\u03b5 \u03ad\u03bd\u03b1 \u03c3\u03c4\u03bf %s: \u03c4\u03bf START \u03c4\u03bf \u03b1\u03bd\u03c4\u03b9\u03b3\u03c1\u03ac\u03c6\u03b5\u03b9 \u03b5\u03ba\u03b5\u03af (%d KB) \u03ba\u03b1\u03b9 \u03be\u03b5\u03ba\u03b9\u03bd\u03ac \u03c4\u03bf \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9.'\n+  LOOKING FOR YOUR LAUNCHERS: \u0391\u039d\u0391\u0396\u0397\u03a4\u0397\u03a3\u0397 \u03a4\u03a9\u039d \u0395\u039a\u039a\u0399\u039d\u0397\u03a4\u03a9\u039d \u03a3\u0391\u03a3\n+  The POPS files: \u03a4\u03b1 \u03b1\u03c1\u03c7\u03b5\u03af\u03b1 POPS\n+  '%s  MMCE card': '%s  \u03ba\u03ac\u03c1\u03c4\u03b1 MMCE'\n+  INSTALL RIPPS2'S NEUTRINO: \u0395\u0393\u039a\u0391\u03a4\u0391\u03a3\u03a4\u0391\u03a3\u0397 \u03a4\u039f\u03a5 NEUTRINO \u03a4\u039f\u03a5 RIPPS2\n+  'Internal HDD (APA): %s free of %s': '\u0395\u03c3\u03c9\u03c4\u03b5\u03c1\u03b9\u03ba\u03cc\u03c2 HDD (APA): %s \u03b5\u03bb\u03b5\u03cd\u03b8\u03b5\u03c1\u03b1 \u03b1\u03c0\u03cc %s'\n+  LAUNCHERS: \u0395\u039a\u039a\u0399\u039d\u0397\u03a4\u0395\u03a3\n+  LOOK FOR THEM: \u03a8\u0391\u039e\u0395 \u03a4\u039f\u03a5\u03a3\n+  'Memory card %d: %d KB free': '\u039a\u03ac\u03c1\u03c4\u03b1 \u03bc\u03bd\u03ae\u03bc\u03b7\u03c2 %d: %d KB \u03b5\u03bb\u03b5\u03cd\u03b8\u03b5\u03c1\u03b1'\n+  POPS files: \u0391\u03c1\u03c7\u03b5\u03af\u03b1 POPS\n+  START EVERY DRIVE: \u0395\u039a\u039a\u0399\u039d\u0397\u03a3\u0397 \u039f\u039b\u03a9\u039d \u03a4\u03a9\u039d \u0394\u0399\u03a3\u039a\u03a9\u039d\n+  'STATUS: looking at the drives and the homebrew': '\u039a\u0391\u03a4\u0391\u03a3\u03a4\u0391\u03a3\u0397: \u03b5\u03be\u03ad\u03c4\u03b1\u03c3\u03b7 \u03c4\u03c9\u03bd \u03b4\u03af\u03c3\u03ba\u03c9\u03bd \u03ba\u03b1\u03b9 \u03c4\u03bf\u03c5 homebrew'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): \u03a0\u03c1\u03ce\u03c4\u03b1 \u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7 \u03cc\u03bb\u03c9\u03bd \u03c4\u03c9\u03bd \u03b4\u03af\u03c3\u03ba\u03c9\u03bd, \u03ce\u03c3\u03c4\u03b5 \u03b7 \u03b1\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7 \u03bd\u03b1 \u03c6\u03c4\u03ac\u03c3\u03b5\u03b9 \u03c3\u03b5 \u03cc\u03bb\u03bf\u03c5\u03c2; (\u03a4\u03bf \u038c\u03c7\u03b9 \u03c8\u03ac\u03c7\u03bd\u03b5\u03b9 \u03c3\u03c4\u03bf\u03c5\u03c2 \u03ae\u03b4\u03b7 \u03be\u03b5\u03ba\u03b9\u03bd\u03b7\u03bc\u03ad\u03bd\u03bf\u03c5\u03c2 \u03b4\u03af\u03c3\u03ba\u03bf\u03c5\u03c2.)\n+  USB, HDD, MMCE and the rest: USB, HDD, MMCE \u03ba\u03b1\u03b9 \u03c4\u03b1 \u03c5\u03c0\u03cc\u03bb\u03bf\u03b9\u03c0\u03b1\n+  every started drive: \u03ba\u03ac\u03b8\u03b5 \u03be\u03b5\u03ba\u03b9\u03bd\u03b7\u03bc\u03ad\u03bd\u03bf\u03c2 \u03b4\u03af\u03c3\u03ba\u03bf\u03c2\n+  found where you keep them: \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03b5\u03ba\u03b5\u03af \u03c0\u03bf\u03c5 \u03c4\u03b1 \u03ba\u03c1\u03b1\u03c4\u03ac\u03c4\u03b5\n+  not found yet: \u03b4\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03b1\u03ba\u03cc\u03bc\u03b1\n+  not on a started drive: \u03cc\u03c7\u03b9 \u03c3\u03b5 \u03be\u03b5\u03ba\u03b9\u03bd\u03b7\u03bc\u03ad\u03bd\u03bf \u03b4\u03af\u03c3\u03ba\u03bf\n+  only if you have none: \u03bc\u03cc\u03bd\u03bf \u03b1\u03bd \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03ba\u03b1\u03bd\u03ad\u03bd\u03b1\n+  starts every drive first: \u03be\u03b5\u03ba\u03b9\u03bd\u03ac \u03c0\u03c1\u03ce\u03c4\u03b1 \u03cc\u03bb\u03bf\u03c5\u03c2 \u03c4\u03bf\u03c5\u03c2 \u03b4\u03af\u03c3\u03ba\u03bf\u03c5\u03c2\n+  Drives, launchers and homebrew: \u0394\u03af\u03c3\u03ba\u03bf\u03b9, \u03b5\u03ba\u03ba\u03b9\u03bd\u03b7\u03c4\u03ad\u03c2 \u03ba\u03b1\u03b9 homebrew\n+  BDM Devices: \u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 BDM\n+  Color Sweep: \u0395\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae \u03c7\u03c1\u03c9\u03bc\u03ac\u03c4\u03c9\u03bd\n+  Colors and Text: \u03a7\u03c1\u03ce\u03bc\u03b1\u03c4\u03b1 \u03ba\u03b1\u03b9 \u03ba\u03b5\u03af\u03bc\u03b5\u03bd\u03bf\n+  HDD POPS partitions: \u0394\u03b9\u03b1\u03bc\u03b5\u03c1\u03af\u03c3\u03bc\u03b1\u03c4\u03b1 POPS \u03c4\u03bf\u03c5 HDD\n+  Layout: \u0394\u03b9\u03ac\u03c4\u03b1\u03be\u03b7\n+  Missing Covers: \u0395\u03be\u03ce\u03c6\u03c5\u03bb\u03bb\u03b1 \u03c0\u03bf\u03c5 \u03bb\u03b5\u03af\u03c0\u03bf\u03c5\u03bd\n+  Name: \u038c\u03bd\u03bf\u03bc\u03b1\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: \u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5 \u03b1\u03ba\u03cc\u03bc\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 SMB \u03b3\u03b9\u03b1 \u03c4\u03bf POPSTARTER. \u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b3\u03c1\u03b1\u03bc\u03bc\u03ad\u03c2 \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9.\n+  POPSTARTER Network: \u0394\u03af\u03ba\u03c4\u03c5\u03bf POPSTARTER\n+  POPSTARTER SMB Network: \u0394\u03af\u03ba\u03c4\u03c5\u03bf SMB \u03c4\u03bf\u03c5 POPSTARTER\n+  POPSTARTER UDPFS Network: \u0394\u03af\u03ba\u03c4\u03c5\u03bf UDPFS \u03c4\u03bf\u03c5 POPSTARTER\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: \u03a4\u03bf RIPPS2 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c4\u03b5\u03b9 \u03bc\u03cc\u03bd\u03bf \u03c4\u03bf\u03c5 \u03c4\u03b7 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1, \u03c4\u03bf ISO, \u03c4\u03b9\u03c2 VMC \u03ba\u03b1\u03b9 \u03c4\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b2\u03af\u03bd\u03c4\u03b5\u03bf.\n+  Screen: \u039f\u03b8\u03cc\u03bd\u03b7\n+  Speed: \u03a4\u03b1\u03c7\u03cd\u03c4\u03b7\u03c4\u03b1\n+  Sweep Colors: \u03a7\u03c1\u03ce\u03bc\u03b1\u03c4\u03b1 \u03b5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2\n+  Text Weight: \u03a0\u03ac\u03c7\u03bf\u03c2 \u03ba\u03b5\u03b9\u03bc\u03ad\u03bd\u03bf\u03c5\n+  Theme Color: \u039a\u03cd\u03c1\u03b9\u03bf \u03c7\u03c1\u03ce\u03bc\u03b1\n+  Audio: \u0389\u03c7\u03bf\u03c2\n+  Bold: \u0388\u03bd\u03c4\u03bf\u03bd\u03b1\n+  Controllers: \u03a7\u03b5\u03b9\u03c1\u03b9\u03c3\u03c4\u03ae\u03c1\u03b9\u03b1\n+  Lively: \u0396\u03c9\u03b7\u03c1\u03ac\n+  Medium: \u039c\u03b5\u03c3\u03b1\u03af\u03b1\n+  PS Emulation: \u0395\u03be\u03bf\u03bc\u03bf\u03af\u03c9\u03c3\u03b7 PS\n+  Regular: \u039a\u03b1\u03bd\u03bf\u03bd\u03b9\u03ba\u03ac\n+  Slow: \u0391\u03c1\u03b3\u03ac\n+  A MOMENT: \u039c\u0399\u0391 \u03a3\u03a4\u0399\u0393\u039c\u0397\n+  A TASK IN THE BACKGROUND: \u039c\u0399\u0391 \u0395\u03a1\u0393\u0391\u03a3\u0399\u0391 \u03a3\u03a4\u039f \u03a0\u0391\u03a1\u0391\u03a3\u039a\u0397\u039d\u0399\u039f\n+  ABOUT %d S: \u03a0\u0395\u03a1\u0399\u03a0\u039f\u03a5 %d S\n+  ANY MOMENT: \u0391\u03a0\u039f \u03a3\u03a4\u0399\u0393\u039c\u0397 \u03a3\u0395 \u03a3\u03a4\u0399\u0393\u039c\u0397\n+  READING THE GAME LIST OF %s: \u0394\u0399\u0391\u0392\u0391\u0396\u0395\u0399 \u03a4\u0397 \u039b\u0399\u03a3\u03a4\u0391 \u03a0\u0391\u0399\u03a7\u039d\u0399\u0394\u0399\u03a9\u039d \u03a4\u039f\u03a5 %s\n+  SAVING A LIST: \u0391\u03a0\u039f\u0398\u0397\u039a\u0395\u03a5\u0395\u0399 \u039c\u0399\u0391 \u039b\u0399\u03a3\u03a4\u0391\n+  START NOW: \u0395\u039d\u0391\u03a1\u039e\u0397 \u03a4\u03a9\u03a1\u0391\n+  STARTING: \u039e\u0395\u039a\u0399\u039d\u0391\n+  STARTS NEXT: \u039e\u0395\u039a\u0399\u039d\u0391 \u0391\u039c\u0395\u03a3\u03a9\u03a3 \u039c\u0395\u03a4\u0391\n+  STOPPING IT SAFELY: \u03a3\u03a4\u0391\u039c\u0391\u03a4\u0391 \u039c\u0395 \u0391\u03a3\u03a6\u0391\u039b\u0395\u0399\u0391\n+  THE LAST OF THE QUEUE: \u03a4\u039f \u03a4\u0395\u039b\u039f\u03a3 \u03a4\u0397\u03a3 \u039f\u03a5\u03a1\u0391\u03a3\n+  WHEN IT FINISHES: \u039f\u03a4\u0391\u039d \u03a4\u0395\u039b\u0395\u0399\u03a9\u03a3\u0395\u0399\n+  What each card starts; install or repair BIBLE.: \u03a4\u03b9 \u03be\u03b5\u03ba\u03b9\u03bd\u03ac \u03ba\u03ac\u03b8\u03b5 \u03ba\u03ac\u03c1\u03c4\u03b1\u00b7 \u03b5\u03b3\u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03ae\u03c3\u03c4\u03b5 \u03ae \u03b5\u03c0\u03b9\u03c3\u03ba\u03b5\u03c5\u03ac\u03c3\u03c4\u03b5 \u03c4\u03bf BIBLE.\n+  Rainbow: \u039f\u03c5\u03c1\u03ac\u03bd\u03b9\u03bf \u03c4\u03cc\u03be\u03bf\n+  Custom: \u03a0\u03c1\u03bf\u03c3\u03b1\u03c1\u03bc\u03bf\u03c3\u03bc\u03ad\u03bd\u03bf\n+  DESR DVR ISOs (experimental): ISO DESR DVR (\u03c0\u03b5\u03b9\u03c1\u03b1\u03bc\u03b1\u03c4\u03b9\u03ba\u03cc)\n+  PC CARD CF (experimental): PC CARD CF (\u03c0\u03b5\u03b9\u03c1\u03b1\u03bc\u03b1\u03c4\u03b9\u03ba\u03cc)\n+  HDD Start Mode: \u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03ad\u03bd\u03b1\u03c1\u03be\u03b7\u03c2 HDD\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): Roboto Condensed \u03b1\u03c0\u03cc \u03c4\u03b7\u03bd The Roboto Project Authors (SIL OFL 1.1)\n+  '%s on %s': '%s \u03c3\u03b5 %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: \u03c4\u03bf %s \u03ad\u03b3\u03b9\u03bd\u03b5 %s (%d%%), \u03ba\u03ac\u03b8\u03b5 \u03ba\u03bf\u03bc\u03bc\u03ac\u03c4\u03b9 \u03b5\u03bb\u03ad\u03b3\u03c7\u03b8\u03b7\u03ba\u03b5. \u0391\u03c6\u03b1\u03af\u03c1\u03b5\u03c3\u03b7 \u03c4\u03bf\u03c5 ISO \u03ba\u03b1\u03b9 \u03b4\u03b9\u03b1\u03c4\u03ae\u03c1\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 ZSO;'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: \u03c4\u03bf %s \u03ad\u03b3\u03b9\u03bd\u03b5 \u03be\u03b1\u03bd\u03ac %s, \u03ba\u03ac\u03b8\u03b5 \u03ba\u03bf\u03bc\u03bc\u03ac\u03c4\u03b9 \u03b5\u03bb\u03ad\u03b3\u03c7\u03b8\u03b7\u03ba\u03b5. \u0391\u03c6\u03b1\u03af\u03c1\u03b5\u03c3\u03b7 \u03c4\u03bf\u03c5 ZSO \u03ba\u03b1\u03b9 \u03b4\u03b9\u03b1\u03c4\u03ae\u03c1\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 ISO;'\n+  A ZSO of this game is already beside it. Remove one of the two first.: \u0394\u03af\u03c0\u03bb\u03b1 \u03c4\u03bf\u03c5 \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03ad\u03bd\u03b1 ZSO \u03b1\u03c5\u03c4\u03bf\u03cd \u03c4\u03bf\u03c5 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03bf\u03cd. \u0391\u03c6\u03b1\u03b9\u03c1\u03ad\u03c3\u03c4\u03b5 \u03c0\u03c1\u03ce\u03c4\u03b1 \u03ad\u03bd\u03b1 \u03b1\u03c0\u03cc \u03c4\u03b1 \u03b4\u03cd\u03bf.\n+  About %d min, then checked: \u03a0\u03b5\u03c1\u03af\u03c0\u03bf\u03c5 %d \u03bb\u03b5\u03c0., \u03bc\u03b5\u03c4\u03ac \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2\n+  An ISO of this game is already beside it. Remove one of the two first.: \u0394\u03af\u03c0\u03bb\u03b1 \u03c4\u03bf\u03c5 \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03ad\u03bd\u03b1 ISO \u03b1\u03c5\u03c4\u03bf\u03cd \u03c4\u03bf\u03c5 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03bf\u03cd. \u0391\u03c6\u03b1\u03b9\u03c1\u03ad\u03c3\u03c4\u03b5 \u03c0\u03c1\u03ce\u03c4\u03b1 \u03ad\u03bd\u03b1 \u03b1\u03c0\u03cc \u03c4\u03b1 \u03b4\u03cd\u03bf.\n+  CHECKING EVERY PIECE: \u0395\u039b\u0395\u0393\u03a7\u039f\u03a3 \u039a\u0391\u0398\u0395 \u039a\u039f\u039c\u039c\u0391\u03a4\u0399\u039f\u03a5\n+  COMPRESS TO ZSO: \u03a3\u03a5\u039c\u03a0\u0399\u0395\u03a3\u0397 \u03a3\u0395 ZSO\n+  'COMPRESSED %s: %s FREED': '\u03a3\u03a5\u039c\u03a0\u0399\u0395\u03a3\u03a4\u0397\u039a\u0395 \u03a4\u039f %s: \u0395\u039b\u0395\u03a5\u0398\u0395\u03a1\u03a9\u0398\u0397\u039a\u0391\u039d %s'\n+  COMPRESSING 1 OF 2: \u03a3\u03a5\u039c\u03a0\u0399\u0395\u03a3\u0397 1 \u0391\u03a0\u039f 2\n+  COMPRESSING 2 OF 2: \u03a3\u03a5\u039c\u03a0\u0399\u0395\u03a3\u0397 2 \u0391\u03a0\u039f 2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : \u03a3\u03c5\u03bc\u03c0\u03af\u03b5\u03c3\u03b7 \u03c4\u03bf\u03c5 %s; \u03a4\u03bf ISO \u03bc\u03ad\u03bd\u03b5\u03b9 \u03bc\u03ad\u03c7\u03c1\u03b9 \u03c4\u03bf ZSO \u03bd\u03b1 \u03b4\u03b9\u03b1\u03b2\u03b1\u03c3\u03c4\u03b5\u03af \u03be\u03b1\u03bd\u03ac \u03ba\u03b1\u03b9 \u03bd\u03b1 \u03b5\u03bb\u03b5\u03b3\u03c7\u03b8\u03b5\u03af, \u03ba\u03b1\u03b9 \u03bc\u03b5\u03c4\u03ac \u03c4\u03bf RIPPS2 \u03c1\u03c9\u03c4\u03ac \u03c0\u03c1\u03b9\u03bd \u03b1\u03c6\u03b1\u03b9\u03c1\u03ad\u03c3\u03b5\u03b9 \u03c4\u03bf ISO. \u0397 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03c7\u03c1\u03b5\u03b9\u03ac\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c7\u03ce\u03c1\u03bf \u03ba\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03c4\u03b1 \u03b4\u03cd\u03bf \u03cc\u03c3\u03bf \u03b4\u03bf\u03c5\u03bb\u03b5\u03cd\u03b5\u03b9\u00b7 \u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03bf\u03bd\u03c3\u03cc\u03bb\u03b1 \u03b1\u03bd\u03b1\u03bc\u03bc\u03ad\u03bd\u03b7 \u03bc\u03ad\u03c7\u03c1\u03b9 \u03bd\u03b1 \u03c4\u03b5\u03bb\u03b5\u03b9\u03ce\u03c3\u03b5\u03b9. \u0388\u03bd\u03b1 ZSO \u03be\u03b5\u03ba\u03b9\u03bd\u03ac \u03bc\u03cc\u03bd\u03bf \u03bc\u03b5 \u03c4\u03bf\u03bd \u03c0\u03c5\u03c1\u03ae\u03bd\u03b1 OPL (\u03cc\u03c7\u03b9 Neutrino)\u00b7 \u03bf\u03b9 \u03b1\u03c0\u03bf\u03b8\u03b7\u03ba\u03b5\u03cd\u03c3\u03b5\u03b9\u03c2, \u03bf\u03b9 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2, \u03bf\u03b9 \u03b5\u03b9\u03ba\u03cc\u03bd\u03b5\u03c2 \u03ba\u03b1\u03b9 \u03c4\u03b1 cheats \u03c4\u03bf\u03c5 \u03bc\u03ad\u03bd\u03bf\u03c5\u03bd \u03bc\u03b1\u03b6\u03af \u03c4\u03bf\u03c5, \u03ba\u03b1\u03b9 \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03be\u03b1\u03bd\u03b1\u03b3\u03af\u03bd\u03b5\u03b9 ISO \u03bf\u03c0\u03bf\u03b9\u03b1\u03b4\u03ae\u03c0\u03bf\u03c4\u03b5 \u03c3\u03c4\u03b9\u03b3\u03bc\u03ae.\n+  Compress to ZSO: \u03a3\u03c5\u03bc\u03c0\u03af\u03b5\u03c3\u03b7 \u03c3\u03b5 ZSO\n+  Cycle: \u0395\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\n+  'Experimental: OPL core only (not Neutrino)': '\u03a0\u03b5\u03b9\u03c1\u03b1\u03bc\u03b1\u03c4\u03b9\u03ba\u03cc: \u03bc\u03cc\u03bd\u03bf \u03bf \u03c0\u03c5\u03c1\u03ae\u03bd\u03b1\u03c2 OPL (\u03cc\u03c7\u03b9 Neutrino)'\n+  Hold SELECT: \u039a\u03c1\u03ac\u03c4\u03b7\u03bc\u03b1 SELECT\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: \u0394\u03b5\u03bd \u03ae\u03c4\u03b1\u03bd \u03b4\u03c5\u03bd\u03b1\u03c4\u03cc \u03bd\u03b1 \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03c9\u03b8\u03b5\u03af (%s). \u038c,\u03c4\u03b9 \u03b3\u03c1\u03ac\u03c6\u03c4\u03b7\u03ba\u03b5 \u03b1\u03c6\u03b1\u03b9\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5\u00b7 \u03c4\u03bf \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9 \u03b5\u03af\u03bd\u03b1\u03b9 \u03cc\u03c0\u03c9\u03c2 \u03ae\u03c4\u03b1\u03bd.\n+  List: \u039b\u03af\u03c3\u03c4\u03b1\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : \u039d\u03b1 \u03be\u03b1\u03bd\u03b1\u03b3\u03af\u03bd\u03b5\u03b9 \u03c4\u03bf %s ISO; \u03a4\u03bf ZSO \u03bc\u03ad\u03bd\u03b5\u03b9 \u03bc\u03ad\u03c7\u03c1\u03b9 \u03c4\u03bf ISO \u03bd\u03b1 \u03b4\u03b9\u03b1\u03b2\u03b1\u03c3\u03c4\u03b5\u03af \u03be\u03b1\u03bd\u03ac \u03ba\u03b1\u03b9 \u03bd\u03b1 \u03b5\u03bb\u03b5\u03b3\u03c7\u03b8\u03b5\u03af, \u03ba\u03b1\u03b9 \u03bc\u03b5\u03c4\u03ac \u03c4\u03bf RIPPS2 \u03c1\u03c9\u03c4\u03ac \u03c0\u03c1\u03b9\u03bd \u03b1\u03c6\u03b1\u03b9\u03c1\u03ad\u03c3\u03b5\u03b9 \u03c4\u03bf ZSO. \u0397 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03c7\u03c1\u03b5\u03b9\u03ac\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c7\u03ce\u03c1\u03bf \u03ba\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03c4\u03b1 \u03b4\u03cd\u03bf \u03cc\u03c3\u03bf \u03b4\u03bf\u03c5\u03bb\u03b5\u03cd\u03b5\u03b9\u00b7 \u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03ba\u03bf\u03bd\u03c3\u03cc\u03bb\u03b1 \u03b1\u03bd\u03b1\u03bc\u03bc\u03ad\u03bd\u03b7 \u03bc\u03ad\u03c7\u03c1\u03b9 \u03bd\u03b1 \u03c4\u03b5\u03bb\u03b5\u03b9\u03ce\u03c3\u03b5\u03b9. \u039f\u03b9 \u03b1\u03c0\u03bf\u03b8\u03b7\u03ba\u03b5\u03cd\u03c3\u03b5\u03b9\u03c2, \u03bf\u03b9 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2, \u03bf\u03b9 \u03b5\u03b9\u03ba\u03cc\u03bd\u03b5\u03c2 \u03ba\u03b1\u03b9 \u03c4\u03b1 cheats \u03c4\u03bf\u03c5 \u03bc\u03ad\u03bd\u03bf\u03c5\u03bd \u03bc\u03b1\u03b6\u03af \u03c4\u03bf\u03c5.\n+  Stop? What it has written so far is removed; the game stays as it was.: \u0394\u03b9\u03b1\u03ba\u03bf\u03c0\u03ae; \u038c,\u03c4\u03b9 \u03ad\u03c7\u03b5\u03b9 \u03b3\u03c1\u03b1\u03c6\u03c4\u03b5\u03af \u03c9\u03c2 \u03c4\u03ce\u03c1\u03b1 \u03b1\u03c6\u03b1\u03b9\u03c1\u03b5\u03af\u03c4\u03b1\u03b9\u00b7 \u03c4\u03bf \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9 \u03bc\u03ad\u03bd\u03b5\u03b9 \u03cc\u03c0\u03c9\u03c2 \u03ae\u03c4\u03b1\u03bd.\n+  THEMES: \u0398\u0395\u039c\u0391\u03a4\u0391\n+  There is not enough memory for this just now.: \u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03b1\u03c1\u03ba\u03b5\u03c4\u03ae \u03bc\u03bd\u03ae\u03bc\u03b7 \u03b3\u03b9' \u03b1\u03c5\u03c4\u03cc \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7 \u03c3\u03c4\u03b9\u03b3\u03bc\u03ae.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: \u0391\u03c5\u03c4\u03cc \u03c4\u03bf \u03b5\u03af\u03b4\u03c9\u03bb\u03bf \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c4\u03bf \u03c3\u03c5\u03bc\u03c0\u03b9\u03ad\u03c3\u03b5\u03b9 \u03ae \u03bd\u03b1 \u03c4\u03bf \u03b1\u03c0\u03bf\u03c3\u03c5\u03bc\u03c0\u03b9\u03ad\u03c3\u03b5\u03b9 \u03c4\u03bf RIPPS2 (\u03c4\u03bf \u03bc\u03ad\u03b3\u03b5\u03b8\u03cc\u03c2 \u03c4\u03bf\u03c5 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03ba\u03ad\u03c1\u03b1\u03b9\u03b1 \u03bc\u03c0\u03bb\u03bf\u03ba \u03c4\u03c9\u03bd 2 KB).\n+  UNCOMPRESS TO ISO: \u0391\u03a0\u039f\u03a3\u03a5\u039c\u03a0\u0399\u0395\u03a3\u0397 \u03a3\u0395 ISO\n+  UNCOMPRESSED %s: \u0391\u03a0\u039f\u03a3\u03a5\u039c\u03a0\u0399\u0395\u03a3\u03a4\u0397\u039a\u0395 \u03a4\u039f %s\n+  UNCOMPRESSING: \u0391\u03a0\u039f\u03a3\u03a5\u039c\u03a0\u0399\u0395\u03a3\u0397\n+  Uncompress to ISO: \u0391\u03c0\u03bf\u03c3\u03c5\u03bc\u03c0\u03af\u03b5\u03c3\u03b7 \u03c3\u03b5 ISO\n+  Under a minute, then checked: \u039b\u03b9\u03b3\u03cc\u03c4\u03b5\u03c1\u03bf \u03b1\u03c0\u03cc \u03ad\u03bd\u03b1 \u03bb\u03b5\u03c0\u03c4\u03cc, \u03bc\u03b5\u03c4\u03ac \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2\n+  COPYING: \u0391\u039d\u03a4\u0399\u0393\u03a1\u0391\u03a6\u0397\n+  SHOW AGAIN, DELETE OR COMPRESS: \u0395\u039c\u03a6\u0391\u039d\u0399\u03a3\u0397 \u039e\u0391\u039d\u0391, \u0394\u0399\u0391\u0393\u03a1\u0391\u03a6\u0397 \u0389 \u03a3\u03a5\u039c\u03a0\u0399\u0395\u03a3\u0397\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: \u0395\u039c\u03a6\u0391\u039d\u0399\u03a3\u0397 \u039e\u0391\u039d\u0391, \u0394\u0399\u0391\u0393\u03a1\u0391\u03a6\u0397 \u0389 \u0391\u03a0\u039f\u03a3\u03a5\u039c\u03a0\u0399\u0395\u03a3\u0397\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': \u03a4\u03bf %s \u03c7\u03c1\u03b5\u03b9\u03ac\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c0\u03b5\u03c1\u03af\u03c0\u03bf\u03c5 %s \u03b5\u03bb\u03b5\u03cd\u03b8\u03b5\u03c1\u03bf \u03c7\u03ce\u03c1\u03bf \u03c3\u03c4\u03b7 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03c4\u03bf\u03c5 \u03cc\u03c3\u03bf \u03b4\u03bf\u03c5\u03bb\u03b5\u03cd\u03b5\u03b9 (\u03c4\u03bf \u03b1\u03c1\u03c7\u03b9\u03ba\u03cc \u03bc\u03ad\u03bd\u03b5\u03b9 \u03c9\u03c2 \u03c4\u03bf \u03c4\u03ad\u03bb\u03bf\u03c2), \u03ba\u03b1\u03b9 \u03b7 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03ad\u03c7\u03b5\u03b9 %s \u03b5\u03bb\u03b5\u03cd\u03b8\u03b5\u03c1\u03b1.\n+  Hide and Compress: \u0391\u03c0\u03cc\u03ba\u03c1\u03c5\u03c8\u03b7 \u03ba\u03b1\u03b9 \u03c3\u03c5\u03bc\u03c0\u03af\u03b5\u03c3\u03b7\n+  'COMPRESSED AND HIDDEN: %s': '\u03a3\u03a5\u039c\u03a0\u0399\u0395\u03a3\u03a4\u0397\u039a\u0395 \u039a\u0391\u0399 \u039a\u03a1\u03a5\u03a6\u03a4\u0397\u039a\u0395: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': '\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03c7\u03ce\u03c1\u03bf\u03c2 \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03b1\u03bd\u03c4\u03b9\u03b3\u03c1\u03b1\u03c6\u03ae: \u03c7\u03c1\u03b5\u03b9\u03ac\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c0\u03b5\u03c1\u03af\u03c0\u03bf\u03c5 %s, \u03ba\u03b1\u03b9 \u03b7 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03ad\u03c7\u03b5\u03b9 %s \u03b5\u03bb\u03b5\u03cd\u03b8\u03b5\u03c1\u03b1.'\n+  OVER 4 GB FOR FAT32: \u03a0\u0391\u039d\u03a9 \u0391\u03a0\u039f 4 GB \u0393\u0399\u0391 FAT32\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: \u0391\u03c5\u03c4\u03ae \u03b7 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 %s, \u03c0\u03bf\u03c5 \u03b4\u03b5\u03bd \u03ba\u03c1\u03b1\u03c4\u03ac \u03b1\u03c1\u03c7\u03b5\u03af\u03bf 4 GB \u03ae \u03bc\u03b5\u03b3\u03b1\u03bb\u03cd\u03c4\u03b5\u03c1\u03bf, \u03ba\u03b1\u03b9 \u03c4\u03bf \u03bd\u03ad\u03bf \u03b5\u03af\u03b4\u03c9\u03bb\u03bf \u03b8\u03b1 \u03ae\u03c4\u03b1\u03bd %s.\n+  '%s, %s clusters, %s free of %s': '%s, \u03c3\u03c5\u03c3\u03c4\u03ac\u03b4\u03b5\u03c2 %s, %s \u03b5\u03bb\u03b5\u03cd\u03b8\u03b5\u03c1\u03b1 \u03b1\u03c0\u03cc %s'\n+  Delete This Sweep: \u0394\u03b9\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae \u03b1\u03c5\u03c4\u03ae\u03c2 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2\n+  Delete %s? RIPPS2's own sweeps stay as they are.: \u0394\u03b9\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae \u03c4\u03bf\u03c5 %s; \u039f\u03b9 \u03b5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ad\u03c2 \u03c4\u03bf\u03c5 \u03af\u03b4\u03b9\u03bf\u03c5 \u03c4\u03bf\u03c5 RIPPS2 \u03bc\u03ad\u03bd\u03bf\u03c5\u03bd \u03cc\u03c0\u03c9\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9.\n+  Sweep: \u0395\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: \u03a4\u03b1 \u03c3\u03ae\u03bc\u03b1\u03c4\u03b1 \u03b4\u03b5\u03af\u03c7\u03bd\u03bf\u03c5\u03bd \u03c3\u03c4\u03b9\u03c2 \u03bb\u03af\u03c3\u03c4\u03b5\u03c2 \u03c3\u03b1\u03c2 \u03c4\u03b1 \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9\u03b1 \u03bc\u03b5 \u03b5\u03c0\u03b9\u03c4\u03b5\u03cd\u03b3\u03bc\u03b1\u03c4\u03b1 \u03ba\u03b1\u03b9 \u03c0\u03cc\u03c3\u03b1 \u03ad\u03c7\u03b5\u03c4\u03b5 \u03be\u03b5\u03ba\u03bb\u03b5\u03b9\u03b4\u03ce\u03c3\u03b5\u03b9. \u03a7\u03c1\u03b5\u03b9\u03ac\u03b6\u03bf\u03bd\u03c4\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03ae \u03a4\u0397\u039b\u0395\u039c\u0395\u03a4\u03a1\u0399\u0391.\n labels:\n   OK: OK\n   WIDE_SCREEN: \u0395\u03c5\u03c1\u03b5\u03af\u03b1 \u03bf\u03b8\u03cc\u03bd\u03b7\n@@ -1213,7 +1380,7 @@ labels:\n   SAVE_ICONS: \u03a4\u03c1\u03b9\u03c3\u03b4\u03b9\u03ac\u03c3\u03c4\u03b1\u03c4\u03b1 \u03b5\u03b9\u03ba\u03bf\u03bd\u03af\u03b4\u03b9\u03b1 \u03b1\u03c0\u03bf\u03b8\u03b7\u03ba\u03b5\u03cd\u03c3\u03b5\u03c9\u03bd\n   HINT_SAVE_ICONS: \u0394\u03b5\u03af\u03c7\u03bd\u03b5\u03b9 \u03c4\u03bf \u03b5\u03b9\u03ba\u03bf\u03bd\u03af\u03b4\u03b9\u03bf \u03b1\u03c0\u03bf\u03b8\u03ae\u03ba\u03b5\u03c5\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 \u03b5\u03c0\u03b9\u03bb\u03b5\u03b3\u03bc\u03ad\u03bd\u03bf\u03c5 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03bf\u03cd, \u03bd\u03b1 \u03b3\u03c5\u03c1\u03af\u03b6\u03b5\u03b9, \u03cc\u03c0\u03bf\u03c5 \u03c4\u03bf \u03b8\u03ad\u03bc\u03b1 \u03ad\u03c7\u03b5\u03b9 \u03b8\u03ad\u03c3\u03b7 \u03b3\u03b9' \u03b1\u03c5\u03c4\u03cc. \u0394\u03b9\u03b1\u03b2\u03ac\u03b6\u03b5\u03b9 \u03c0\u03c1\u03ce\u03c4\u03b1 \u03c4\u03bf VMC \u03c4\u03bf\u03c5 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03bf\u03cd, \u03bc\u03b5\u03c4\u03ac \u03c4\u03b9\u03c2 \u03ba\u03ac\u03c1\u03c4\u03b5\u03c2 \u03bc\u03bd\u03ae\u03bc\u03b7\u03c2.\n   NETBOOT_RESTART_NOW: \u03a4\u03bf \u03c0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 \u03b1\u03bb\u03bb\u03ac\u03b6\u03b5\u03b9 \u03bc\u03b5\u03c4\u03ac \u03b1\u03c0\u03cc \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7. \u0391\u03c0\u03bf\u03b8\u03ae\u03ba\u03b5\u03c5\u03c3\u03b7 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c9\u03bd \u03ba\u03b1\u03b9 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7 \u03c4\u03bf\u03c5 OPL \u03c4\u03ce\u03c1\u03b1;\n-  HINT_NET_PROTOCOL: '\u03a0\u03bf\u03b9\u03b1 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03c4\u03bf RiptOPL: \u03ba\u03bf\u03b9\u03bd\u03cc\u03c7\u03c1\u03b7\u03c3\u03c4\u03bf SMB, UDPFS, UDPBD \u03ae HTTP. \u0397 \u03af\u03b4\u03b9\u03b1 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03bc\u03b5 \u03c4\u03b9\u03c2 \u03a0\u03b7\u03b3\u03ad\u03c2 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03ce\u03bd. \u0397 \u03b1\u03bb\u03bb\u03b1\u03b3\u03ae \u03b1\u03c0\u03cc \u03bc\u03af\u03b1 \u03c0\u03bf\u03c5 \u03ae\u03b4\u03b7 \u03c4\u03c1\u03ad\u03c7\u03b5\u03b9 \u03c7\u03c1\u03b5\u03b9\u03ac\u03b6\u03b5\u03c4\u03b1\u03b9 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7.'\n+  HINT_NET_PROTOCOL: '\u03a0\u03bf\u03b9\u03b1 \u03c5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b1 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03c4\u03bf RiptOPL: \u03ba\u03bf\u03b9\u03bd\u03cc\u03c7\u03c1\u03b7\u03c3\u03c4\u03bf SMB, UDPFS, UDPBD \u03ae HTTP. \u0397 \u03b1\u03bb\u03bb\u03b1\u03b3\u03ae \u03b1\u03c0\u03cc \u03bc\u03af\u03b1 \u03c0\u03bf\u03c5 \u03ae\u03b4\u03b7 \u03c4\u03c1\u03ad\u03c7\u03b5\u03b9 \u03c7\u03c1\u03b5\u03b9\u03ac\u03b6\u03b5\u03c4\u03b1\u03b9 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7.'\n   HINT_UDPFS_ACCESS: \u039c\u03cc\u03bd\u03bf UDPFS. \u03a4\u03b1 \u0391\u03c1\u03c7\u03b5\u03af\u03b1 \u03b4\u03b9\u03b1\u03b2\u03ac\u03b6\u03bf\u03c5\u03bd \u03bc\u03b5\u03bc\u03bf\u03bd\u03c9\u03bc\u03ad\u03bd\u03b1 ISO \u03b1\u03c0\u03cc \u03c4\u03bf\u03bd \u03c6\u03ac\u03ba\u03b5\u03bb\u03bf \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03ce\u03bd \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u00b7 \u03c4\u03bf IMG \u03b4\u03b9\u03b1\u03b2\u03ac\u03b6\u03b5\u03b9 \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1 \u03b4\u03af\u03c3\u03ba\u03bf\u03c5 \u03c9\u03c2 \u03b4\u03af\u03c3\u03ba\u03bf. \u03a4\u03b1 SMB \u03ba\u03b1\u03b9 HTTP \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03bf\u03cd\u03bd \u03c0\u03ac\u03bd\u03c4\u03b1 \u0391\u03c1\u03c7\u03b5\u03af\u03b1, \u03c4\u03bf UDPBD \u03c0\u03ac\u03bd\u03c4\u03b1 IMG.\n   HINT_SMB_VERSION: \u03a0\u03bf\u03b9\u03b1 \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 SMB \u03b8\u03b1 \u03bc\u03b9\u03bb\u03ac \u03bc\u03b5 \u03c4\u03bf \u03ba\u03bf\u03b9\u03bd\u03cc\u03c7\u03c1\u03b7\u03c3\u03c4\u03bf \u03c4\u03bf\u03c5 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae.\n   HINT_NET_ADVANCED: \u039e\u03b5\u03ba\u03bb\u03b5\u03b9\u03b4\u03ce\u03bd\u03b5\u03b9 \u03b3\u03b9\u03b1 \u03b5\u03c0\u03b5\u03be\u03b5\u03c1\u03b3\u03b1\u03c3\u03af\u03b1 \u03c4\u03b7 \u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 Ethernet \u03ba\u03b1\u03b9 \u03c4\u03b7 \u0398\u03cd\u03c1\u03b1 SMB.\n@@ -1412,3 +1579,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: \u03a0\u03ce\u03c2 \u03bf\u03b9 \u03bb\u03ae\u03c8\u03b5\u03b9\u03c2 \u03c0\u03bf\u03c5 \u03c0\u03b5\u03c1\u03b9\u03bc\u03ad\u03bd\u03bf\u03c5\u03bd \u03c3\u03c4\u03bf \u03c6\u03b9\u03bb\u03bc \u03ba\u03ac\u03b8\u03b5 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03bf\u03cd \u03b3\u03af\u03bd\u03bf\u03bd\u03c4\u03b1\u03b9 PNG.\n   RIPPS2_SETTINGS_HOME: \u0391\u03c0\u03bf\u03b8\u03ae\u03ba\u03b5\u03c5\u03c3\u03b7 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c9\u03bd \u03c3\u03b5\n   HINT_RIPPS2_SETTINGS_HOME: '\u03a0\u03bf\u03cd \u03ba\u03c1\u03b1\u03c4\u03ac \u03c4\u03bf RIPPS2 \u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c4\u03bf\u03c5: \u03c3\u03c4\u03bf \u03b4\u03b9\u03ba\u03cc \u03c4\u03bf\u03c5 \u03b1\u03c0\u03bf\u03b8\u03b7\u03ba\u03b5\u03c5\u03bc\u03ad\u03bd\u03bf \u03c3\u03b5 \u03ba\u03ac\u03c1\u03c4\u03b1 \u03bc\u03bd\u03ae\u03bc\u03b7\u03c2, \u03c3\u03b5 \u03c6\u03ac\u03ba\u03b5\u03bb\u03bf RIPPS2 \u03c3\u03b5 \u03bc\u03b9\u03b1 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 (\u03c4\u03bf\u03bd \u03b5\u03c3\u03c9\u03c4\u03b5\u03c1\u03b9\u03ba\u03cc HDD, USB, MX4SIO, iLink \u03ae MMCE) \u03ae \u03c3\u03b5 \u03c6\u03ac\u03ba\u03b5\u03bb\u03bf \u03c0\u03bf\u03c5 \u03b3\u03c1\u03ac\u03c6\u03b5\u03b9\u03c2.'\n+  RIPPS2_LAUNCHERS: \u0395\u03ba\u03ba\u03b9\u03bd\u03b7\u03c4\u03ad\u03c2\n+  RIPPS2_LOOK_NOW: \u0391\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7 \u03c4\u03ce\u03c1\u03b1\n+  HINT_RIPPS2_LOOK_NOW: \u03a4\u03bf RIPPS2 \u03c8\u03ac\u03c7\u03bd\u03b5\u03b9 \u03c3\u03c4\u03bf\u03c5\u03c2 \u03be\u03b5\u03ba\u03b9\u03bd\u03b7\u03bc\u03ad\u03bd\u03bf\u03c5\u03c2 \u03b4\u03af\u03c3\u03ba\u03bf\u03c5\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03bf POPSTARTER, \u03c4\u03b1 \u03b1\u03c1\u03c7\u03b5\u03af\u03b1 POPS, \u03c4\u03bf Ember, \u03c4\u03bf BIOS \u03c4\u03bf\u03c5 Ember \u03ba\u03b1\u03b9 \u03c4\u03bf Neutrino, \u03ba\u03b1\u03b9 \u03b8\u03c5\u03bc\u03ac\u03c4\u03b1\u03b9 \u03c0\u03bf\u03cd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c4\u03bf \u03ba\u03b1\u03b8\u03ad\u03bd\u03b1. \u039a\u03ac\u03b8\u03b5 \u03b5\u03ba\u03ba\u03af\u03bd\u03b7\u03c3\u03b7 \u03c4\u03b1 \u03b4\u03b9\u03b1\u03b2\u03ac\u03b6\u03b5\u03b9 \u03b1\u03c0\u03cc \u03b5\u03ba\u03b5\u03af.\n+  RIPPS2_HIDE_GAME: \u0391\u03c0\u03cc\u03ba\u03c1\u03c5\u03c8\u03b7 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03bf\u03cd\n+  RIPPS2_HIDDEN_GAMES: \u039a\u03c1\u03c5\u03bc\u03bc\u03ad\u03bd\u03b1 \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9\u03b1\n+  HINT_RIPPS2_HIDDEN_GAMES: \u03a4\u03b1 \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9\u03b1 \u03c0\u03bf\u03c5 \u03ba\u03c1\u03cd\u03c6\u03c4\u03b7\u03ba\u03b1\u03bd \u03bc\u03b5 \u03c4\u03b7\u03bd \u0391\u03c0\u03cc\u03ba\u03c1\u03c5\u03c8\u03b7 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03bf\u03cd, \u03c3\u03c4\u03bf\u03c5\u03c2 \u03be\u03b5\u03ba\u03b9\u03bd\u03b7\u03bc\u03ad\u03bd\u03bf\u03c5\u03c2 \u03b4\u03af\u03c3\u03ba\u03bf\u03c5\u03c2. \u0394\u03b5\u03af\u03be\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03be\u03b1\u03bd\u03ac, \u03ae \u03b4\u03b9\u03b1\u03b3\u03c1\u03ac\u03c8\u03c4\u03b5 \u03c4\u03bf. \u039a\u03ac\u03b8\u03b5 \u03b4\u03af\u03c3\u03ba\u03bf\u03c2 \u03ba\u03c1\u03b1\u03c4\u03ac \u03c4\u03b7 \u03b4\u03b9\u03ba\u03ae \u03c4\u03bf\u03c5 \u03bb\u03af\u03c3\u03c4\u03b1.\n+  HINT_RIPPS2_BDM_DEVICES: '\u039f\u03b9 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b5\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b9\u03c2 \u03bf\u03c0\u03bf\u03af\u03b5\u03c2 \u03c4\u03bf BDM \u03b4\u03b9\u03b1\u03b2\u03ac\u03b6\u03b5\u03b9 \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9\u03b1: USB, MX4SIO, PC Card CF, \u03b7 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 DVR \u03b5\u03bd\u03cc\u03c2 DESR \u03ba\u03b1\u03b9 iLink. \u039f \u03b5\u03c3\u03c9\u03c4\u03b5\u03c1\u03b9\u03ba\u03cc\u03c2 HDD \u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03b5\u03af \u03c4\u03b7 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03ad\u03bd\u03b1\u03c1\u03be\u03b7\u03c2 HDD.'\n+  HINT_RIPPS2_SCREEN: '\u0397 \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1 \u03c4\u03bf\u03c5 \u03bc\u03b5\u03bd\u03bf\u03cd: \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b2\u03af\u03bd\u03c4\u03b5\u03bf, \u03b5\u03c5\u03c1\u03b5\u03af\u03b1 \u03bf\u03b8\u03cc\u03bd\u03b7, \u03b7 \u03b8\u03ad\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c4\u03b7\u03bd \u03c4\u03b7\u03bb\u03b5\u03cc\u03c1\u03b1\u03c3\u03b7 (H-POS, V-POS) \u03ba\u03b1\u03b9 overscan.'\n+  HINT_RIPPS2_LAYOUT: \u03a0\u03bf\u03cd \u03b2\u03c1\u03af\u03c3\u03ba\u03bf\u03bd\u03c4\u03b1\u03b9 \u03bf\u03b9 \u03ba\u03b1\u03c4\u03b7\u03b3\u03bf\u03c1\u03af\u03b5\u03c2 \u03ba\u03b1\u03b9 \u03c0\u03ce\u03c2 \u03bc\u03c0\u03b1\u03af\u03bd\u03b5\u03b9 \u03bc\u03b9\u03b1 \u03c3\u03b5\u03bb\u03af\u03b4\u03b1, \u03bc\u03b5\u03c4\u03ac \u03c4\u03b1 \u03b5\u03be\u03ce\u03c6\u03c5\u03bb\u03bb\u03b1 \u03c4\u03bf\u03c5 RIPFLOW \u03ba\u03b1\u03b9 \u03c4\u03bf \u03c0\u03bb\u03ad\u03b3\u03bc\u03b1 \u03c4\u03bf\u03c5 RIPGRID.\n+  HINT_RIPPS2_COLORS_TEXT: \u03a0\u03ac\u03c7\u03bf\u03c2 \u03ba\u03b5\u03b9\u03bc\u03ad\u03bd\u03bf\u03c5, \u03c4\u03bf \u03c7\u03c1\u03c9\u03bc\u03b1\u03c4\u03b9\u03ba\u03cc \u03b8\u03ad\u03bc\u03b1, \u03b7 \u03b5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae \u03c7\u03c1\u03c9\u03bc\u03ac\u03c4\u03c9\u03bd \u03ba\u03b1\u03b9 \u03ba\u03ac\u03b8\u03b5 \u03c7\u03c1\u03ce\u03bc\u03b1.\n+  HINT_RIPPS2_TEXT_WEIGHT: '\u03a0\u03cc\u03c3\u03bf \u03ad\u03bd\u03c4\u03bf\u03bd\u03bf \u03b5\u03af\u03bd\u03b1\u03b9 \u03c4\u03bf \u03ba\u03b5\u03af\u03bc\u03b5\u03bd\u03bf \u03c4\u03bf\u03c5 \u03af\u03b4\u03b9\u03bf\u03c5 \u03c4\u03bf\u03c5 RIPPS2: \u03bf\u03b9 \u03c3\u03c5\u03bc\u03b2\u03bf\u03c5\u03bb\u03ad\u03c2 \u03c4\u03c9\u03bd \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c9\u03bd, \u039a\u0391\u03a4\u0391\u03a3\u03a4\u0391\u03a3\u0397 \u03ba\u03b1\u03b9 \u03a3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac. \u039a\u03b1\u03bd\u03bf\u03bd\u03b9\u03ba\u03ac \u03b5\u03af\u03bd\u03b1\u03b9 \u03cc\u03c0\u03c9\u03c2 \u03c0\u03c1\u03b9\u03bd\u00b7 \u039c\u03b5\u03c3\u03b1\u03af\u03b1 \u03ba\u03b1\u03b9 \u0388\u03bd\u03c4\u03bf\u03bd\u03b1 \u03b4\u03b9\u03b1\u03b2\u03ac\u03b6\u03bf\u03bd\u03c4\u03b1\u03b9 \u03ba\u03b1\u03bb\u03cd\u03c4\u03b5\u03c1\u03b1 \u03c3\u03b5 \u03bc\u03b1\u03bb\u03b1\u03ba\u03ae \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1.'\n+  HINT_RIPPS2_HDD_START: '\u039f \u03b5\u03c3\u03c9\u03c4\u03b5\u03c1\u03b9\u03ba\u03cc\u03c2 HDD, APA \u03ae exFAT: \u03c4\u03bf RIPPS2 \u03c4\u03bf\u03bd \u03b4\u03b9\u03b1\u03b2\u03ac\u03b6\u03b5\u03b9 \u03cc,\u03c4\u03b9 \u03ba\u03b9 \u03b1\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03b4\u03b5\u03af\u03c7\u03bd\u03b5\u03b9 \u03bc\u03af\u03b1 \u03c6\u03bf\u03c1\u03ac, \u03c9\u03c2 \u03c0\u03b1\u03b9\u03c7\u03bd\u03af\u03b4\u03b9\u03b1 HDD. \u03a4\u03bf \u0391\u03bd\u03b5\u03bd\u03b5\u03c1\u03b3\u03cc \u03c4\u03bf\u03bd \u03b1\u03c6\u03ae\u03bd\u03b5\u03b9 \u03ae\u03c3\u03c5\u03c7\u03bf.'\n+  HINT_RIPPS2_THEME_COLOR: \u03a4\u03bf \u03b4\u03b9\u03ba\u03cc \u03c7\u03c1\u03ce\u03bc\u03b1 \u03c4\u03bf\u03c5 \u03b8\u03ad\u03bc\u03b1\u03c4\u03bf\u03c2. \u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03a0\u03c1\u03bf\u03c3\u03b1\u03c1\u03bc\u03bf\u03c3\u03bc\u03ad\u03bd\u03bf \u03c9\u03c2 \u03c7\u03c1\u03c9\u03bc\u03b1\u03c4\u03b9\u03ba\u03cc \u03b8\u03ad\u03bc\u03b1 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c4\u03bf \u03b4\u03b9\u03b1\u03bb\u03ad\u03be\u03b5\u03c4\u03b5 \u03bc\u03cc\u03bd\u03bf\u03b9 \u03c3\u03b1\u03c2, \u03cc\u03c0\u03c9\u03c2 \u03c4\u03b1 \u03c7\u03c1\u03ce\u03bc\u03b1\u03c4\u03b1 \u03c4\u03bf\u03c5 \u03ba\u03b5\u03b9\u03bc\u03ad\u03bd\u03bf\u03c5.\n+  HINT_RIPPS2_SWEEP: \u03a0\u03ad\u03bd\u03c4\u03b5 \u03c7\u03c1\u03ce\u03bc\u03b1\u03c4\u03b1 \u03b1\u03c0\u03cc \u03c4\u03b1 \u03bf\u03c0\u03bf\u03af\u03b1 \u03c0\u03b5\u03c1\u03bd\u03ac \u03c4\u03bf \u03b8\u03ad\u03bc\u03b1, \u03b1\u03c1\u03b3\u03ac \u03ba\u03b1\u03b9 \u03bf\u03bc\u03b1\u03bb\u03ac. \u039f\u03c5\u03c1\u03ac\u03bd\u03b9\u03bf \u03c4\u03cc\u03be\u03bf, \u03bc\u03b9\u03b1 \u03b5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae \u03b1\u03c0\u03cc \u03ba\u03ac\u03b8\u03b5 \u03c7\u03c1\u03c9\u03bc\u03b1\u03c4\u03b9\u03ba\u03cc \u03b8\u03ad\u03bc\u03b1 \u03ae \u03b7 \u03b4\u03b9\u03ba\u03ae \u03c3\u03b1\u03c2. \u03a4\u03bf \u0391\u03bd\u03b5\u03bd\u03b5\u03c1\u03b3\u03cc \u03ba\u03c1\u03b1\u03c4\u03ac \u03c4\u03bf \u03b8\u03ad\u03bc\u03b1 \u03b1\u03ba\u03af\u03bd\u03b7\u03c4\u03bf.\n+  HINT_RIPPS2_SWEEP_COLORS: \u03a4\u03b1 \u03c0\u03ad\u03bd\u03c4\u03b5 \u03c7\u03c1\u03ce\u03bc\u03b1\u03c4\u03b1 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2 \u03ba\u03b1\u03b9 \u03b7 \u03c4\u03b1\u03c7\u03cd\u03c4\u03b7\u03c4\u03ac \u03c4\u03b7\u03c2. \u0391\u03bb\u03bb\u03ac\u03be\u03c4\u03b5 \u03bc\u03af\u03b1 \u03c4\u03bf\u03c5 RIPPS2 \u03ba\u03b1\u03b9 \u03ba\u03c1\u03b1\u03c4\u03b9\u03ad\u03c4\u03b1\u03b9 \u03c9\u03c2 \u03b4\u03b9\u03ba\u03ae \u03c3\u03b1\u03c2, \u03bc\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c0\u03bf\u03c5 \u03c4\u03b7\u03c2 \u03b4\u03af\u03bd\u03b5\u03c4\u03b5.\n+  HINT_RIPPS2_SWEEP_NAME: \u03a0\u03ce\u03c2 \u03bb\u03ad\u03b3\u03b5\u03c4\u03b1\u03b9 \u03b7 \u03b5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae \u03c3\u03b1\u03c2 \u03c3\u03c4\u03b7 \u03bb\u03af\u03c3\u03c4\u03b1 \u0395\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae \u03c7\u03c1\u03c9\u03bc\u03ac\u03c4\u03c9\u03bd.\n+  HINT_RIPPS2_SWEEP_COLOR: \u0388\u03bd\u03b1 \u03b1\u03c0\u03cc \u03c4\u03b1 \u03c0\u03ad\u03bd\u03c4\u03b5 \u03c7\u03c1\u03ce\u03bc\u03b1\u03c4\u03b1 \u03b1\u03c0\u03cc \u03c4\u03b1 \u03bf\u03c0\u03bf\u03af\u03b1 \u03c0\u03b5\u03c1\u03bd\u03ac \u03b7 \u03b5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae, \u03bc\u03b5 \u03c4\u03b7 \u03c3\u03b5\u03b9\u03c1\u03ac.\n+  HINT_RIPPS2_SWEEP_SPEED: '\u03a0\u03cc\u03c3\u03bf \u03b4\u03b9\u03b1\u03c1\u03ba\u03b5\u03af \u03ba\u03ac\u03b8\u03b5 \u03c7\u03c1\u03ce\u03bc\u03b1 \u03c0\u03c1\u03b9\u03bd \u03b5\u03bc\u03c6\u03b1\u03bd\u03b9\u03c3\u03c4\u03b5\u03af \u03c4\u03bf \u03b5\u03c0\u03cc\u03bc\u03b5\u03bd\u03bf. \u0391\u03ba\u03cc\u03bc\u03b7 \u03ba\u03b1\u03b9 \u03c4\u03b1 \u0396\u03c9\u03b7\u03c1\u03ac \u03bc\u03ad\u03bd\u03bf\u03c5\u03bd \u03bf\u03bc\u03b1\u03bb\u03ac: \u03c4\u03b1 \u03c7\u03c1\u03ce\u03bc\u03b1\u03c4\u03b1 \u03b4\u03b5\u03bd \u03b1\u03bd\u03b1\u03b2\u03bf\u03c3\u03b2\u03ae\u03bd\u03bf\u03c5\u03bd \u03c0\u03bf\u03c4\u03ad.'\n+  RIPPS2_HIDE_COMPRESS: \u0391\u03c0\u03cc\u03ba\u03c1\u03c5\u03c8\u03b7 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03bf\u03cd \u03ba\u03b1\u03b9 \u03c3\u03c5\u03bc\u03c0\u03af\u03b5\u03c3\u03b7\n+  HINT_RIPPS2_SELECT_HOLD: 'SELECT \u03c0\u03b1\u03c4\u03b7\u03bc\u03ad\u03bd\u03bf 4.2 s \u03c3\u03b5 \u03bb\u03af\u03c3\u03c4\u03b1 \u03c0\u03b1\u03b9\u03c7\u03bd\u03b9\u03b4\u03b9\u03ce\u03bd. \u039b\u03af\u03c3\u03c4\u03b1: \u03c4\u03b1 \u03b8\u03ad\u03bc\u03b1\u03c4\u03b1 \u03c3\u03b5 \u03ad\u03bd\u03b1 \u03c0\u03bb\u03b1\u03af\u03c3\u03b9\u03bf, \u03b4\u03b9\u03b1\u03bb\u03ad\u03be\u03c4\u03b5 \u03ad\u03bd\u03b1. \u0395\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae: \u03c4\u03bf \u03b5\u03c0\u03cc\u03bc\u03b5\u03bd\u03bf \u03b8\u03ad\u03bc\u03b1. \u0391\u03bd\u03b5\u03bd\u03b5\u03c1\u03b3\u03cc: \u03c4\u03af\u03c0\u03bf\u03c4\u03b1 (\u03ad\u03bd\u03b1 \u03c0\u03ac\u03c4\u03b7\u03bc\u03b1 \u03b1\u03bd\u03b1\u03bd\u03b5\u03ce\u03bd\u03b5\u03b9 \u03b1\u03ba\u03cc\u03bc\u03b1).'"
        },
        {
          "sha": "06f503a03e349502a9d14c4e63ead3a18eff2645",
          "filename": "elf/lang/Hungarian.yml",
          "status": "modified",
          "additions": 209,
          "deletions": 22,
          "changes": 231,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FHungarian.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FHungarian.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FHungarian.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: Hungarian\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: AZ ENTER ELFOGAD, AZ ESC M\u00c9GSE. B\u00c1RMELYIK GOMB MEGMUTATJA A BILLENTY\u0170KET.\n   USB KEYBOARD: USB BILLENTY\u0170ZET\n-  '      DESR DVR ISOs (experimental)': '      DESR DVR ISO-k (k\u00eds\u00e9rleti)'\n-  '      Internal HDD (exFAT)': '      Bels\u0151 HDD (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (k\u00eds\u00e9rleti)'\n-  '      Protocol': '      Protokoll'\n   Access: Hozz\u00e1f\u00e9r\u00e9s\n   Applications Page Start Mode: Alkalmaz\u00e1sok oldal ind\u00edt\u00e1si m\u00f3dja\n   Art Delay: K\u00e9pek k\u00e9sleltet\u00e9se\n@@ -22,7 +18,6 @@ strings:\n   Fifth: \u00d6t\u00f6dik\n   First: Els\u0151\n   Fourth: Negyedik\n-  HDD (APA) Start Mode: HDD (APA) ind\u00edt\u00e1si m\u00f3d\n   HDD Cache: HDD gyors\u00edt\u00f3t\u00e1r\n   HDD Spindown: HDD le\u00e1ll\u00edt\u00e1s\n   MMCE Settings: MMCE be\u00e1ll\u00edt\u00e1sok\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: Ez egy ESR-rel jav\u00edtott PS2 j\u00e1t\u00e9k. Az ind\u00edt\u00e1s\u00e1hoz tedd az ESR-t (esr*.elf) mem\u00f3riak\u00e1rty\u00e1n vagy USB meghajt\u00f3n az APPS, BOOT vagy ESR mapp\u00e1ba.\n   '%s, BUILD %d': '%s, %d. BUILD'\n   '... and the anonymous ...': '... \u00e9s a n\u00e9vtelenek ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': 'Az 1.8.0 v\u00e1ltozatlanul mell\u00e9kelve (AFL-3.0): github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: A RiptOPL, az Open PS2 Loader forkj\u00e1nak sz\u00e9tszed\u00e9se \u00e9s \u00fajra\u00e9p\u00edt\u00e9se,\n   ABOUT: N\u00c9VJEGY\n   'ACHIEVEMENTS: SPIRIT OF RA': 'TELJES\u00cdTM\u00c9NYEK: SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): AVI vide\u00f3 a Mem\u00f3riaf\u00e1jlokban, a RIPPS2 forkja (AFL 2.0)\n   Apollo Save Tool by bucanero: Apollo Save Tool, bucanero munk\u00e1ja\n   BACK: VISSZA\n+  Back (keeps both): Vissza (mindkett\u0151 marad)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: Ez az MMCE k\u00e1rtya nem tud f\u00e1jlt \u00e1tnevezni, pedig a t\u00f6m\u00f6r\u00edt\u00e9s utols\u00f3 l\u00e9p\u00e9s\u00e9hez erre van sz\u00fcks\u00e9g. Friss\u00edtse az sd2psXtd firmware-\u00e9t 1.4.0-s vagy \u00fajabb verzi\u00f3ra (sd2psxtd.github.io/download), majd pr\u00f3b\u00e1lja \u00fajra. Semmi sem v\u00e1ltozott.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): A BIBLE a PS2BBL 1.2.0, El Isra al\u00e1\u00edrt buildjei (k\u00e1rtya \u00e9s HDD), v\u00e1ltozatlanul (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: A BIOS hangok > Kinyer\u00e9s \u00fagy j\u00e1tssza le \u0151ket, ahogy az SPU2; BIOS nincs mell\u00e9kelve\n   BOOT LOCK AND BIBLE: BOOT LOCK \u00c9S BIBLE\n   BOOT LOCK edits its config too: A BOOT LOCK a be\u00e1ll\u00edt\u00e1sait is szerkeszti\n   BUILD DETAILS: BUILD R\u00c9SZLETEI\n-  BUILT ON RIPTOPL: A RIPTOPL ALAPJ\u00c1N\n   BearSSL by Thomas Pornin: BearSSL, Thomas Pornin munk\u00e1ja\n   Built with Claude (Anthropic): Claude (Anthropic) seg\u00edts\u00e9g\u00e9vel k\u00e9sz\u00fclt\n   CD PLAYER: CD LEJ\u00c1TSZ\u00d3\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek \u00e9s Sleek Bold, a RIPPS2-hoz rajzolva\n   RetroAchievements on the console by hacan359 (yoba): RetroAchievements a konzolon, hacan359 (yoba) munk\u00e1ja\n   RiptOPL by NathanNeurotic: RiptOPL, NathanNeurotic munk\u00e1ja\n-  Roboto Condensed by Google (Apache-2.0): Roboto Condensed, a Google munk\u00e1ja (Apache-2.0)\n   SOUNDS: HANGOK\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: SP193 FMCB telep\u00edt\u0151je \u00e9s pcm720 OSDMenu-je, l\u00e9p\u00e9sr\u0151l l\u00e9p\u00e9sre k\u00f6vetve\n   SUPPORT: T\u00c1MOGAT\u00c1S\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: wLaunchELF R3Z, NathanNeurotic munk\u00e1ja\n   wLaunchELF by the ps2homebrew team: wLaunchELF, a ps2homebrew csapat munk\u00e1ja\n   what RIPPS2's card image writes are checked against: amihez a RIPPS2 k\u00e1rtyak\u00e9p-\u00edr\u00e1sait ellen\u0151rizz\u00fck\n-  with its own look, launchers, file browser and save tools.: saj\u00e1t megjelen\u00e9ssel, ind\u00edt\u00f3kkal, f\u00e1jlb\u00f6ng\u00e9sz\u0151vel \u00e9s ment\u00e9skezel\u0151 eszk\u00f6z\u00f6kkel.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: xeRAbora, hacan359 munk\u00e1ja; rcheevos, a RetroAchievements munk\u00e1ja\n   your console's own, ripped from its BIOS: a konzolod saj\u00e1t hangjai, a BIOS-\u00e1b\u00f3l kinyerve\n   ' (no DNS answer)': ' (nincs DNS-v\u00e1lasz)'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO: \u00edr\u00e1s: %.60s'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: 'A Neutrin\u00f3t nem siker\u00fclt ide \u00edrni: %s. Ellen\u0151rizd a szabad helyet \u00e9s az \u00edr\u00e1sv\u00e9delmet, azt\u00e1n pr\u00f3b\u00e1ld \u00fajra.'\n   THE GAME'S DRIVE  (%s/neutrino): A J\u00c1T\u00c9K MEGHAJT\u00d3JA  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : Ez a j\u00e1t\u00e9k a Neutrin\u00f3n kereszt\u00fcl indul, \u00e9s ott, ahol a RIPPS2 keres (Be\u00e1ll\u00edt\u00e1sok, a j\u00e1t\u00e9k meghajt\u00f3ja, a mem\u00f3riak\u00e1rty\u00e1k), nincs Neutrino. A RIPPS2 mag\u00e1val hozza rickgaiser Neutrino 1.8.0-j\u00e1t (AFL-3.0). Telep\u00edted most? Ezut\u00e1n kiv\u00e1lasztod a hely\u00e9t.\n   '%d / %d FILES': '%d / %d F\u00c1JL'\n   '%d FOLDERS AND CONTENTS': '%d MAPPA \u00c9S TARTALMA'\n   '%d FOLDERS, %d FOUND': '%d MAPPA, %d TAL\u00c1LAT'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d / %d m\u00e9g z\u00e1rolva'\n   '%s   on %s': '%s   itt: %s'\n   BADGES: JELV\u00c9NYEK\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: A jelv\u00e9nyek csak bekapcsolt TELEMETRIA \u00e9s a PC-den fut\u00f3 xeRAbora mellett m\u0171k\u00f6dnek; addig semmibe sem ker\u00fclnek.\n   CHECK THE DISC: LEMEZ ELLEN\u0150RZ\u00c9SE\n   From this game's last CHECK GAME SUPPORT.: A j\u00e1t\u00e9k legut\u00f3bbi J\u00c1T\u00c9KT\u00c1MOGAT\u00c1S ELLEN\u0150RZ\u00c9SE alapj\u00e1n.\n   HOW IT WORKS: HOGYAN M\u0170K\u00d6DIK\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: Ezen a konzolon van egy RIPPS2-z\u00e1rral ell\u00e1tott PS2BBL.\n   AUDIO folder: AUDIO mappa\n   DRIVES: MEGHAJT\u00d3K\n-  INSTALL RIPPS2'S COPY: A RIPPS2 P\u00c9LD\u00c1NY\u00c1NAK TELEP\u00cdT\u00c9SE\n-  'Installed: ': 'Telep\u00edtve: '\n   MANAGE: KEZEL\u00c9S\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: A KEZEL\u00c9S megmutatja, melyik mem\u00f3riak\u00e1rtya mit ind\u00edt, \u00e9s telep\u00edti vagy jav\u00edtja a BIBLE-t.\n   MEMORY CARD 0  (about 600 KB): 0. MEM\u00d3RIAK\u00c1RTYA  (kb. 600 KB)\n   MEMORY CARD 1  (about 600 KB): 1. MEM\u00d3RIAK\u00c1RTYA  (kb. 600 KB)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO: az illeszt\u0151programja nem t\u00f6lt\u0151d\u00f6tt be (k\u00f3d: %d).'\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO: az illeszt\u0151program bet\u00f6ltve, de egyetlen SD k\u00e1rtya sem v\u00e1laszolt (mc1 foglalat).'\n   NEUTRINO SETTINGS: NEUTRINO BE\u00c1LL\u00cdT\u00c1SAI\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': 'M\u00e9g nincs: a RIPPS2-nek nincsenek saj\u00e1t hangjai. Nyerd ki a konzol hangjait, vagy k\u00e9sz\u00edtsd el \u0151ket a RIPPS2 Audio Studi\u00f3ban.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: Nincs telep\u00edtve. Egy j\u00e1t\u00e9k, amelynek sz\u00fcks\u00e9ge van r\u00e1, felaj\u00e1nlja a RIPPS2 p\u00e9ld\u00e1ny\u00e1t, vagy telep\u00edtsd itt.\n   POPSTARTER NETWORK: POPSTARTER H\u00c1L\u00d3ZAT\n   POPSTARTER and Ember: POPSTARTER \u00e9s Ember\n   PS1 SETTINGS: PS1 BE\u00c1LL\u00cdT\u00c1SOK\n   PS2BBL 1.2.0 for RIPPS2: PS2BBL 1.2.0 a RIPPS2-h\u00f6z\n   RIP THIS CONSOLE'S SOUNDS: A KONZOL HANGJAINAK KINYER\u00c9SE\n-  RIPPS2's homebrew: a RIPPS2 homebrew programjai\n   STATUS: \u00c1LLAPOT\n-  'STATUS: looking for RIPPS2''s homebrew': '\u00c1LLAPOT: a RIPPS2 homebrew programjainak keres\u00e9se'\n   a drive of unknown kind: ismeretlen fajt\u00e1j\u00fa meghajt\u00f3\n-  a drive or a memory card: meghajt\u00f3 vagy mem\u00f3riak\u00e1rtya\n   boot loader, card by card: rendszerbet\u00f6lt\u0151, k\u00e1rty\u00e1nk\u00e9nt\n   from its BIOS, into AUDIO: a BIOS-\u00e1b\u00f3l, az AUDIO mapp\u00e1ba\n   internal HDD (exFAT): bels\u0151 HDD (exFAT)\n@@ -975,10 +958,194 @@ strings:\n   Sidebar: Oldals\u00e1v\n   your own copy, found on your drives and memory cards: a saj\u00e1t p\u00e9ld\u00e1nyod, a meghajt\u00f3idon \u00e9s mem\u00f3riak\u00e1rty\u00e1idon megtal\u00e1lva\n   'your own, found and launched: github.com/Gageformer/Ember/releases': 'a saj\u00e1tod, megtal\u00e1lva \u00e9s elind\u00edtva: github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': 'Nem tal\u00e1lhat\u00f3 a mass0: meghajt\u00f3n (%s). Tedd az ember.elf f\u00e1jlt \u00e9s a saj\u00e1t bios.bin f\u00e1jlodat az EMBER mapp\u00e1j\u00e1ba.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: Nem tal\u00e1lhat\u00f3. Tedd a saj\u00e1t POPSTARTER.ELF f\u00e1jlodat egy meghajt\u00f3 POPS mapp\u00e1j\u00e1ba.\n   Launch Disc: Lemez ind\u00edt\u00e1sa\n   Memory Files: Mem\u00f3riaf\u00e1jlok\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: A RIPPS2 %d darabot tal\u00e1lt az 5-b\u0151l. Mindegyiket megjegyzi, \u00e9s minden ind\u00edt\u00e1s onnan olvassa.\n+  not found on the started drives: nem tal\u00e1lhat\u00f3 az elind\u00edtott meghajt\u00f3kon\n+  not looked for yet: m\u00e9g nem kereste\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s nem v\u00e1laszolt, \u00edgy %s nem tud elindulni. Csatlakoztatva van?'\n+  '%s is not on %s any more, so it has left All Games.': '%s m\u00e1r nincs ezen: %s, ez\u00e9rt kiker\u00fclt a Minden j\u00e1t\u00e9k list\u00e1b\u00f3l.'\n+  STARTING %s: '%s IND\u00cdT\u00c1SA'\n+  the SMB share: az SMB-megoszt\u00e1s\n+  the drive: a meghajt\u00f3\n+  the internal HDD (APA): a bels\u0151 HDD (APA)\n+  the internal HDD (exFAT): a bels\u0151 HDD (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: A RIPTOPL-RA \u00c9P\u00dcL, NEUTRIN\u00d3VAL\n+  'Special thanks to all the YouTube love as well:': 'K\u00fcl\u00f6n k\u00f6sz\u00f6net a YouTube-on kapott szeretet\u00e9rt is:'\n+  and its own look, launchers, file browser and save tools.: \u00e9s saj\u00e1t kin\u00e9zettel, ind\u00edt\u00f3kkal, f\u00e1jlb\u00f6ng\u00e9sz\u0151vel \u00e9s ment\u00e9skezel\u0151 eszk\u00f6z\u00f6kkel.\n+  glad to help however you reach me.: sz\u00edvesen seg\u00edtek, b\u00e1rhogy is \u00e9rtek el.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): a m\u00e1sodik j\u00e1t\u00e9kmag, 1.8.0 v\u00e1ltozatlanul mell\u00e9kelve (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: benne rickgaiser Neutrin\u00f3j\u00e1val mint m\u00e1sodik j\u00e1t\u00e9kmaggal,\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : 'Ez a j\u00e1t\u00e9k a Neutrin\u00f3n kereszt\u00fcl indul, \u00e9s a RIPPS2 egyet sem tal\u00e1lt: sem a Be\u00e1ll\u00edt\u00e1sok \u00fatvonal\u00e1n, sem a j\u00e1t\u00e9k meghajt\u00f3j\u00e1n, sem a mem\u00f3riak\u00e1rty\u00e1kon, sem elind\u00edtott meghajt\u00f3n. A RIPPS2 tartalmazza rickgaiser Neutrino 1.8.0-j\u00e1t (AFL-3.0): telep\u00edtsd egyszer, ahov\u00e1 szeretn\u00e9d, \u00e9s minden Neutrino-j\u00e1t\u00e9k onnan haszn\u00e1lja.'\n+  STARTING THE DRIVES: A MEGHAJT\u00d3K IND\u00cdT\u00c1SA\n+  '%d games, on the started drives': '%d j\u00e1t\u00e9k, az elind\u00edtott meghajt\u00f3kon'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s: N\u00c9H\u00c1NY F\u00c1JLT NEM SIKER\u00dcLT T\u00d6R\u00d6LNI'\n+  '%u partitions, all in order': '%u part\u00edci\u00f3, mind rendben'\n+  ', %d art images': ', %d bor\u00edt\u00f3k\u00e9p'\n+  ', and ': ', \u00e9s '\n+  ', its cheats': ', a csal\u00e1sai'\n+  ', its settings': ', a be\u00e1ll\u00edt\u00e1sai'\n+  1 game, on the started drives: 1 j\u00e1t\u00e9k, az elind\u00edtott meghajt\u00f3kon\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: Egy itteni PS1-j\u00e1t\u00e9kot a Mem\u00f3riaf\u00e1jlokban lehet t\u00f6r\u00f6lni, ahol k\u00fcl\u00f6n f\u00e1jl.\n+  A network catalogue's games are deleted on the PC that serves them.: Egy h\u00e1l\u00f3zati katal\u00f3gus j\u00e1t\u00e9kait azon a PC-n kell t\u00f6r\u00f6lni, amely kiszolg\u00e1lja \u0151ket.\n+  DELETE FOR GOOD: T\u00d6RL\u00c9S V\u00c9GLEG\n+  DELETE IT: T\u00d6R\u00d6LD\n+  'DELETED %s: %s FREED': '%s T\u00d6R\u00d6LVE: %s FELSZABADULT'\n+  'Delete %s from %s? Goes: %s.': 'T\u00f6rl\u00f6d ezt: %s, innen: %s? Megy: %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': 'T\u00f6rl\u00f6d ezt: %s, innen: %s? Megy: %s. Marad: %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: A ment\u00e9seit \u00e9s RIPSHOT-k\u00e9peit is t\u00f6rl\u00f6d? A Nem megtartja \u0151ket.\n+  HIDDEN GAMES: REJTETT J\u00c1T\u00c9KOK\n+  'HIDDEN: %s': 'ELREJTVE: %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: 'Elrejted ezt: %s? Minden list\u00e1b\u00f3l kiker\u00fcl, a Minden j\u00e1t\u00e9k \u00e9s a Kedvencek list\u00e1b\u00f3l is. Be\u00e1ll\u00edt\u00e1sok > J\u00e1t\u00e9kforr\u00e1sok > Rejtett j\u00e1t\u00e9kok \u00fajra megmutatja.'\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: Utols\u00f3 ellen\u0151rz\u00e9s. %s v\u00e9gleg t\u00f6rl\u0151dik, \u00e9s semmi sem hozhatja vissza. Nyomd meg a START gombot a t\u00f6rl\u00e9shez.\n+  None. Hide Game, in a game's options, puts one here.: Nincs. A J\u00e1t\u00e9k elrejt\u00e9se egy j\u00e1t\u00e9k be\u00e1ll\u00edt\u00e1saiban ide tesz egyet.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : 'Semmi sem t\u00f6rl\u0151d\u00f6tt: ennek a meghajt\u00f3nak a part\u00edci\u00f3s t\u00e1bl\u00e1ja nem illik ehhez a lemezhez (%s). Lehet, hogy egy m\u00e1s m\u00e9ret\u0171 lemez k\u00e9p\u00e9b\u0151l \u00edrt\u00e1k. El\u0151bb jav\u00edtsd ki egy PC-n.'\n+  RIPPS2 cannot delete a game from this page.: A RIPPS2 nem tud j\u00e1t\u00e9kot t\u00f6r\u00f6lni err\u0151l az oldalr\u00f3l.\n+  RIPPS2 could not find this game's files on its drive.: A RIPPS2 nem tal\u00e1lta a j\u00e1t\u00e9k f\u00e1jljait a meghajt\u00f3j\u00e1n.\n+  Reading the hidden lists: A rejtett list\u00e1k olvas\u00e1sa\n+  SHOW AGAIN OR DELETE: \u00daJRA MUTAT VAGY T\u00d6R\u00d6L\n+  SHOW IT AGAIN: MUTASD \u00daJRA\n+  That game is not on its drive any more.: Az a j\u00e1t\u00e9k m\u00e1r nincs a meghajt\u00f3j\u00e1n.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: A rejtett list\u00e1t nem siker\u00fclt a j\u00e1t\u00e9k meghajt\u00f3j\u00e1ra \u00edrni. \u00cdr\u00e1sv\u00e9dett, vagy ki vannak kapcsolva az \u00cdr\u00e1sm\u0171veletek?\n+  The partition could not be removed (%d). Nothing was deleted.: A part\u00edci\u00f3t nem siker\u00fclt elt\u00e1vol\u00edtani (%d). Semmi sem t\u00f6rl\u0151d\u00f6tt.\n+  This drive cannot keep a hidden list (a network catalogue).: Ez a meghajt\u00f3 nem t\u00e1rolhat rejtett list\u00e1t (h\u00e1l\u00f3zati katal\u00f3gus).\n+  This game's drive has not answered yet. Wait for it, then try again.: A j\u00e1t\u00e9k meghajt\u00f3ja m\u00e9g nem v\u00e1laszolt. V\u00e1rd meg, azt\u00e1n pr\u00f3b\u00e1ld \u00fajra.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: A j\u00e1t\u00e9k part\u00edci\u00f3ja azok k\u00f6z\u00e9 tartozik, amelyeket a RIPPS2 nem t\u00e1vol\u00edt el (rendszerpart\u00edci\u00f3, vagy az, amelyiken a be\u00e1ll\u00edt\u00e1sai vannak).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': 'egy part\u00edci\u00f3 nagyobb, mint amit ez a lemez megenged: a t\u00e1bla egy m\u00e1s m\u00e9ret\u0171 lemezhez k\u00e9sz\u00fclt'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': 'egy part\u00edci\u00f3 t\u00falny\u00falik a lemez v\u00e9g\u00e9n (%u. szektor ennyib\u0151l: %u): a t\u00e1bla egy nagyobb lemezhez k\u00e9sz\u00fclt'\n+  its RIPSHOT pictures: a RIPSHOT-k\u00e9pei\n+  its drive: a meghajt\u00f3ja\n+  its saves: a ment\u00e9sei\n+  sector %u holds no partition header: a(z) %u. szektorban nincs part\u00edci\u00f3fejl\u00e9c\n+  the chain does not close at __mbr: a l\u00e1nc nem z\u00e1rul a __mbr-n\u00e9l\n+  the chain skips from sector %u: a l\u00e1nc ugrik a(z) %u. szektort\u00f3l\n+  the chain's links disagree at sector %u: a l\u00e1nc szemei nem egyeznek a(z) %u. szektorn\u00e1l\n+  the drive's size could not be read: a meghajt\u00f3 m\u00e9ret\u00e9t nem siker\u00fclt beolvasni\n+  the game (%s): a j\u00e1t\u00e9k (%s)\n+  the game's folder and everything in it (%s): a j\u00e1t\u00e9k mapp\u00e1ja \u00e9s minden benne (%s)\n+  the header at sector %u could not be read: a(z) %u. szektor fejl\u00e9c\u00e9t nem siker\u00fclt beolvasni\n+  the header at sector %u fails its checksum: a(z) %u. szektor fejl\u00e9ce elbukik az ellen\u0151rz\u0151\u00f6sszegen\n+  the partition at sector %u is not where or as large as APA puts one: a(z) %u. szektor part\u00edci\u00f3ja nem ott van, vagy nem akkora, ahol \u00e9s amekkor\u00e1t az APA l\u00e9trehoz\n+  the settings and art another copy of it uses: a be\u00e1ll\u00edt\u00e1sok \u00e9s k\u00e9pek, amelyeket egy m\u00e1sik p\u00e9ld\u00e1nya haszn\u00e1l\n+  '%d FOLDERS': '%d MAPPA'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': '%s nem m\u00e1solhat\u00f3 ide: %s. Megtelt vagy \u00edr\u00e1sv\u00e9dett a meghajt\u00f3?'\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s nincs ott, ahol a RIPPS2 keresi. Keressem a RIPPS2 \u00e1ltal elind\u00edtott meghajt\u00f3kon? A megtal\u00e1lt helyet minden k\u00e9s\u0151bbi ind\u00edt\u00e1shoz megjegyzi.'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Az Ember nincs itt: %s, a j\u00e1t\u00e9kok mellett, pedig ott kell lennie. A RIPPS2 megtal\u00e1lta itt: %s; a START odam\u00e1solja (%d KB) \u00e9s elind\u00edtja a j\u00e1t\u00e9kot.'\n+  Ember's BIOS: Az Ember BIOS-a\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Az Ember BIOS-a (bios.bin) nincs itt: %s, az Ember mellett. A RIPPS2 tal\u00e1lt egyet itt: %s; a START odam\u00e1solja (%d KB) \u00e9s elind\u00edtja a j\u00e1t\u00e9kot.'\n+  LOOKING FOR YOUR LAUNCHERS: AZ IND\u00cdT\u00d3ID KERES\u00c9SE\n+  The POPS files: A POPS-f\u00e1jlok\n+  '%s  MMCE card': '%s  MMCE-k\u00e1rtya'\n+  INSTALL RIPPS2'S NEUTRINO: A RIPPS2 NEUTRIN\u00d3J\u00c1NAK TELEP\u00cdT\u00c9SE\n+  'Internal HDD (APA): %s free of %s': 'Bels\u0151 HDD (APA): %s szabad ennyib\u0151l: %s'\n+  LAUNCHERS: IND\u00cdT\u00d3K\n+  LOOK FOR THEM: KERESD MEG \u0150KET\n+  'Memory card %d: %d KB free': '%d. mem\u00f3riak\u00e1rtya: %d KB szabad'\n+  POPS files: POPS-f\u00e1jlok\n+  START EVERY DRIVE: MINDEN MEGHAJT\u00d3 IND\u00cdT\u00c1SA\n+  'STATUS: looking at the drives and the homebrew': '\u00c1LLAPOT: a meghajt\u00f3k \u00e9s a homebrew \u00e1ttekint\u00e9se'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): El\u0151bb elind\u00edtod az \u00f6sszes meghajt\u00f3t, hogy a keres\u00e9s mindegyiket el\u00e9rje? (A Nem a m\u00e1r elind\u00edtott meghajt\u00f3kon keres.)\n+  USB, HDD, MMCE and the rest: USB, HDD, MMCE \u00e9s a t\u00f6bbi\n+  every started drive: minden elind\u00edtott meghajt\u00f3\n+  found where you keep them: megtal\u00e1lva ott, ahol tartod \u0151ket\n+  not found yet: m\u00e9g nincs meg\n+  not on a started drive: nincs elind\u00edtott meghajt\u00f3n\n+  only if you have none: csak ha nincs egy sem\n+  starts every drive first: el\u0151bb minden meghajt\u00f3t elind\u00edt\n+  Drives, launchers and homebrew: Meghajt\u00f3k, ind\u00edt\u00f3k \u00e9s homebrew programok\n+  BDM Devices: BDM-eszk\u00f6z\u00f6k\n+  Color Sweep: Sz\u00ednv\u00e1lt\u00e1s\n+  Colors and Text: Sz\u00ednek \u00e9s sz\u00f6veg\n+  HDD POPS partitions: A HDD POPS-part\u00edci\u00f3i\n+  Layout: Elrendez\u00e9s\n+  Missing Covers: Hi\u00e1nyz\u00f3 bor\u00edt\u00f3k\n+  Name: N\u00e9v\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: M\u00e9g nincs POPSTARTER SMB-be\u00e1ll\u00edt\u00e1s. T\u00f6ltsd ki az al\u00e1bbi sorokat.\n+  POPSTARTER Network: POPSTARTER h\u00e1l\u00f3zat\n+  POPSTARTER SMB Network: POPSTARTER SMB-h\u00e1l\u00f3zat\n+  POPSTARTER UDPFS Network: POPSTARTER UDPFS-h\u00e1l\u00f3zat\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: A RIPPS2 maga adja hozz\u00e1 a meghajt\u00f3t, az ISO-t, a VMC-ket \u00e9s a vide\u00f3m\u00f3dot.\n+  Screen: K\u00e9perny\u0151\n+  Speed: Sebess\u00e9g\n+  Sweep Colors: A v\u00e1lt\u00e1s sz\u00ednei\n+  Text Weight: Sz\u00f6vegvastags\u00e1g\n+  Theme Color: F\u0151 sz\u00edn\n+  Audio: Hang\n+  Bold: F\u00e9lk\u00f6v\u00e9r\n+  Controllers: Kontrollerek\n+  Lively: \u00c9l\u00e9nk\n+  Medium: K\u00f6zepes\n+  PS Emulation: PS-emul\u00e1ci\u00f3\n+  Regular: Norm\u00e1l\n+  Slow: Lass\u00fa\n+  A MOMENT: EGY PILLANAT\n+  A TASK IN THE BACKGROUND: EGY H\u00c1TT\u00c9RFELADAT\n+  ABOUT %d S: KB. %d MP\n+  ANY MOMENT: B\u00c1RMELYIK PILLANATBAN\n+  READING THE GAME LIST OF %s: OLVASSA A(Z) %s J\u00c1T\u00c9KLIST\u00c1J\u00c1T\n+  SAVING A LIST: LIST\u00c1T MENT\n+  START NOW: IND\u00cdT\u00c1S MOST\n+  STARTING: INDUL\n+  STARTS NEXT: K\u00d6VETKEZ\u0150K\u00c9NT INDUL\n+  STOPPING IT SAFELY: BIZTONS\u00c1GOSAN LE\u00c1LL\u00cdT\n+  THE LAST OF THE QUEUE: A SOR V\u00c9GE\n+  WHEN IT FINISHES: HA V\u00c9GZETT\n+  What each card starts; install or repair BIBLE.: Mit ind\u00edt az egyes k\u00e1rty\u00e1k; a BIBLE telep\u00edt\u00e9se vagy jav\u00edt\u00e1sa.\n+  Rainbow: Sziv\u00e1rv\u00e1ny\n+  Custom: Egy\u00e9ni\n+  DESR DVR ISOs (experimental): DESR DVR ISO-k (k\u00eds\u00e9rleti)\n+  PC CARD CF (experimental): PC CARD CF (k\u00eds\u00e9rleti)\n+  HDD Start Mode: HDD ind\u00edt\u00e1si m\u00f3d\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): Roboto Condensed, a The Roboto Project Authors munk\u00e1ja (SIL OFL 1.1)\n+  '%s on %s': '%s itt: %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: %s helyett %s (%d%%), minden darab ellen\u0151rizve. Elt\u00e1vol\u00edtod az ISO-t \u00e9s megtartod a ZSO-t?'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: %s helyett ism\u00e9t %s, minden darab ellen\u0151rizve. Elt\u00e1vol\u00edtod a ZSO-t \u00e9s megtartod az ISO-t?'\n+  A ZSO of this game is already beside it. Remove one of the two first.: Mellette m\u00e1r van egy ZSO ebb\u0151l a j\u00e1t\u00e9kb\u00f3l. El\u0151bb t\u00e1vol\u00edtsd el a kett\u0151 egyik\u00e9t.\n+  About %d min, then checked: Kb. %d perc, azt\u00e1n ellen\u0151rz\u00e9s\n+  An ISO of this game is already beside it. Remove one of the two first.: Mellette m\u00e1r van egy ISO ebb\u0151l a j\u00e1t\u00e9kb\u00f3l. El\u0151bb t\u00e1vol\u00edtsd el a kett\u0151 egyik\u00e9t.\n+  CHECKING EVERY PIECE: MINDEN DARAB ELLEN\u0150RZ\u00c9SE\n+  COMPRESS TO ZSO: T\u00d6M\u00d6R\u00cdT\u00c9S ZSO-BA\n+  'COMPRESSED %s: %s FREED': '%s T\u00d6M\u00d6R\u00cdTVE: %s FELSZABADULT'\n+  COMPRESSING 1 OF 2: T\u00d6M\u00d6R\u00cdT\u00c9S 1/2\n+  COMPRESSING 2 OF 2: T\u00d6M\u00d6R\u00cdT\u00c9S 2/2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : 'T\u00f6m\u00f6r\u00edted: %s? Az ISO megmarad, am\u00edg a ZSO-t vissza nem olvassa \u00e9s ellen\u0151rzi, azt\u00e1n a RIPPS2 r\u00e1k\u00e9rdez, miel\u0151tt elt\u00e1vol\u00edtja az ISO-t. A meghajt\u00f3nak munka k\u00f6zben mindkett\u0151h\u00f6z kell hely; hagyd bekapcsolva a konzolt, am\u00edg v\u00e9gez. A ZSO csak az OPL maggal indul (Neutrin\u00f3val nem); ment\u00e9sei, be\u00e1ll\u00edt\u00e1sai, k\u00e9pei \u00e9s csal\u00e1sai vele maradnak, \u00e9s b\u00e1rmikor \u00fajra ISO lehet bel\u0151le.'\n+  Compress to ZSO: T\u00f6m\u00f6r\u00edt\u00e9s ZSO-ba\n+  Cycle: L\u00e9ptet\u00e9s\n+  'Experimental: OPL core only (not Neutrino)': 'K\u00eds\u00e9rleti: csak OPL mag (nem Neutrino)'\n+  Hold SELECT: SELECT nyomva tart\u00e1sa\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: Nem siker\u00fclt befejezni (%s). Amit \u00edrt, t\u00f6r\u00f6lve lett; a j\u00e1t\u00e9k olyan, mint volt.\n+  List: Lista\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : '\u00dajra ISO legyen ebb\u0151l: %s? A ZSO megmarad, am\u00edg az ISO-t vissza nem olvassa \u00e9s ellen\u0151rzi, azt\u00e1n a RIPPS2 r\u00e1k\u00e9rdez, miel\u0151tt elt\u00e1vol\u00edtja a ZSO-t. A meghajt\u00f3nak munka k\u00f6zben mindkett\u0151h\u00f6z kell hely; hagyd bekapcsolva a konzolt, am\u00edg v\u00e9gez. Ment\u00e9sei, be\u00e1ll\u00edt\u00e1sai, k\u00e9pei \u00e9s csal\u00e1sai vele maradnak.'\n+  Stop? What it has written so far is removed; the game stays as it was.: Le\u00e1ll\u00edtod? Az eddig meg\u00edrt r\u00e9sz t\u00f6rl\u0151dik; a j\u00e1t\u00e9k olyan marad, mint volt.\n+  THEMES: T\u00c9M\u00c1K\n+  There is not enough memory for this just now.: Most nincs ehhez el\u00e9g mem\u00f3ria.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: Ezt a k\u00e9pf\u00e1jlt a RIPPS2 nem tudja t\u00f6m\u00f6r\u00edteni vagy kibontani (a m\u00e9rete nem eg\u00e9sz 2 KB-os blokkokb\u00f3l \u00e1ll).\n+  UNCOMPRESS TO ISO: KIBONT\u00c1S ISO-BA\n+  UNCOMPRESSED %s: '%s KIBONTVA'\n+  UNCOMPRESSING: KIBONT\u00c1S\n+  Uncompress to ISO: Kibont\u00e1s ISO-ba\n+  Under a minute, then checked: Kevesebb mint egy perc, azt\u00e1n ellen\u0151rz\u00e9s\n+  COPYING: M\u00c1SOL\u00c1S\n+  SHOW AGAIN, DELETE OR COMPRESS: \u00daJRA MUTAT, T\u00d6R\u00d6L VAGY T\u00d6M\u00d6R\u00cdT\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: \u00daJRA MUTAT, T\u00d6R\u00d6L VAGY KIBONT\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': '%s munka k\u00f6zben kb. %s szabad helyet ig\u00e9nyel a meghajt\u00f3j\u00e1n (az eredeti a v\u00e9g\u00e9ig megmarad), a meghajt\u00f3n pedig %s szabad.'\n+  Hide and Compress: Elrejt\u00e9s \u00e9s t\u00f6m\u00f6r\u00edt\u00e9s\n+  'COMPRESSED AND HIDDEN: %s': 'T\u00d6M\u00d6R\u00cdTVE \u00c9S ELREJTVE: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': 'Nincs hely ehhez a ment\u00e9shez: kb. %s kell, a meghajt\u00f3n pedig %s szabad.'\n+  OVER 4 GB FOR FAT32: 4 GB FELETT FAT32-H\u00d6Z\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: Ez a meghajt\u00f3 %s, amely nem t\u00e1rol 4 GB-os vagy nagyobb f\u00e1jlt, az \u00faj k\u00e9pf\u00e1jl pedig %s lenne.\n+  '%s, %s clusters, %s free of %s': '%s, %s f\u00fcrt\u00f6k, %s szabad ebb\u0151l: %s'\n+  Delete This Sweep: Ennek a sz\u00ednv\u00e1lt\u00e1snak a t\u00f6rl\u00e9se\n+  Delete %s? RIPPS2's own sweeps stay as they are.: 'T\u00f6rl\u00f6d ezt: %s? A RIPPS2 saj\u00e1t sz\u00ednv\u00e1lt\u00e1sai maradnak.'\n+  Sweep: Sz\u00ednv\u00e1lt\u00e1s\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: A jelv\u00e9nyek megjel\u00f6lik a list\u00e1idban a teljes\u00edtm\u00e9nyes j\u00e1t\u00e9kokat, \u00e9s hogy mennyit oldott\u00e1l fel. Bekapcsolt TELEMETRIA kell hozz\u00e1juk.\n labels:\n   OK: OK\n   BACKSPACE: Visszat\u00f6rl\u00e9s\n@@ -1211,7 +1378,7 @@ labels:\n   SAVE_ICONS: 3D ment\u00e9sikonok\n   HINT_SAVE_ICONS: A kiv\u00e1lasztott j\u00e1t\u00e9k ment\u00e9sikonj\u00e1t forogva mutatja ott, ahol a t\u00e9ma helyet ad neki. El\u0151bb a j\u00e1t\u00e9k saj\u00e1t VMC-j\u00e9t olvassa, azt\u00e1n a mem\u00f3riak\u00e1rty\u00e1kat.\n   NETBOOT_RESTART_NOW: A h\u00e1l\u00f3zati protokoll \u00fajraind\u00edt\u00e1s ut\u00e1n v\u00e1ltozik. Mented a be\u00e1ll\u00edt\u00e1sokat, \u00e9s most \u00fajraind\u00edtod az OPL-t?\n-  HINT_NET_PROTOCOL: 'Melyik h\u00e1l\u00f3zati szolg\u00e1ltat\u00e1st haszn\u00e1lja a RiptOPL: SMB megoszt\u00e1s, UDPFS, UDPBD vagy HTTP. Ugyanaz a be\u00e1ll\u00edt\u00e1s, mint a J\u00e1t\u00e9kforr\u00e1sokban. Egy m\u00e1r fut\u00f3r\u00f3l v\u00e1lt\u00e1shoz \u00fajraind\u00edt\u00e1s kell.'\n+  HINT_NET_PROTOCOL: 'Melyik h\u00e1l\u00f3zati szolg\u00e1ltat\u00e1st haszn\u00e1lja a RiptOPL: SMB megoszt\u00e1s, UDPFS, UDPBD vagy HTTP. Egy m\u00e1r fut\u00f3r\u00f3l v\u00e1lt\u00e1shoz \u00fajraind\u00edt\u00e1s kell.'\n   HINT_UDPFS_ACCESS: Csak UDPFS. A F\u00e1jlok a szerver j\u00e1t\u00e9kmapp\u00e1j\u00e1b\u00f3l olvas k\u00fcl\u00f6n\u00e1ll\u00f3 ISO-kat; az IMG egy lemezk\u00e9pet meghajt\u00f3k\u00e9nt olvas. Az SMB \u00e9s a HTTP mindig F\u00e1jlokat, az UDPBD mindig IMG-t haszn\u00e1l.\n   HINT_SMB_VERSION: Melyik SMB verzi\u00f3val besz\u00e9ljen a PC megoszt\u00e1s\u00e1val.\n   HINT_NET_ADVANCED: Szerkeszthet\u0151v\u00e9 teszi az Ethernet kapcsolati m\u00f3dot \u00e9s az SMB portot.\n@@ -1412,3 +1579,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: Hogyan lesznek PNG-k a minden j\u00e1t\u00e9k tekercs\u00e9n v\u00e1rakoz\u00f3 k\u00e9pekb\u0151l.\n   RIPPS2_SETTINGS_HOME: Be\u00e1ll\u00edt\u00e1sok ment\u00e9se ide\n   HINT_RIPPS2_SETTINGS_HOME: 'Hol tartja a RIPPS2 a be\u00e1ll\u00edt\u00e1sait: saj\u00e1t ment\u00e9s\u00e9ben egy mem\u00f3riak\u00e1rty\u00e1n, egy RIPPS2 mapp\u00e1ban egy meghajt\u00f3n (a bels\u0151 HDD-n, USB-n, MX4SIO-n, iLinken vagy MMCE-n), vagy egy \u00e1ltalad be\u00edrt mapp\u00e1ban.'\n+  RIPPS2_LAUNCHERS: Ind\u00edt\u00f3k\n+  RIPPS2_LOOK_NOW: Keres\u00e9s most\n+  HINT_RIPPS2_LOOK_NOW: A RIPPS2 az elind\u00edtott meghajt\u00f3kon megkeresi a POPSTARTER-t, a POPS-f\u00e1jlokat, az Embert, az Ember BIOS-\u00e1t \u00e9s a Neutrin\u00f3t, \u00e9s megjegyzi, hol van mindegyik. Minden ind\u00edt\u00e1s onnan olvassa \u0151ket.\n+  RIPPS2_HIDE_GAME: J\u00e1t\u00e9k elrejt\u00e9se\n+  RIPPS2_HIDDEN_GAMES: Rejtett j\u00e1t\u00e9kok\n+  HINT_RIPPS2_HIDDEN_GAMES: A J\u00e1t\u00e9k elrejt\u00e9s\u00e9vel elrejtett j\u00e1t\u00e9kok az elind\u00edtott meghajt\u00f3kon. Mutass meg egyet \u00fajra, vagy t\u00f6r\u00f6ld. Minden meghajt\u00f3 saj\u00e1t list\u00e1t vezet.\n+  HINT_RIPPS2_BDM_DEVICES: 'A meghajt\u00f3k, amelyekr\u0151l a BDM j\u00e1t\u00e9kokat olvas: USB, MX4SIO, PC Card CF, egy DESR DVR-meghajt\u00f3ja \u00e9s iLink. A bels\u0151 HDD a HDD ind\u00edt\u00e1si m\u00f3dj\u00e1t k\u00f6veti.'\n+  HINT_RIPPS2_SCREEN: 'A men\u00fc k\u00e9pe: vide\u00f3m\u00f3d, sz\u00e9lesv\u00e1szon, a helye a t\u00e9v\u00e9n (H-POS, V-POS) \u00e9s overscan.'\n+  HINT_RIPPS2_LAYOUT: Hol vannak a kateg\u00f3ri\u00e1k \u00e9s hogyan j\u00f6n be egy oldal, azt\u00e1n a RIPFLOW bor\u00edt\u00f3i \u00e9s a RIPGRID r\u00e1csa.\n+  HINT_RIPPS2_COLORS_TEXT: Sz\u00f6vegvastags\u00e1g, a sz\u00ednt\u00e9ma, a sz\u00ednv\u00e1lt\u00e1s \u00e9s minden sz\u00edn.\n+  HINT_RIPPS2_TEXT_WEIGHT: 'Milyen vastag a RIPPS2 saj\u00e1t sz\u00f6vege: a be\u00e1ll\u00edt\u00e1sok tippjei, az \u00c1LLAPOT \u00e9s a N\u00e9vjegy. A Norm\u00e1l olyan, mint kor\u00e1bban; a K\u00f6zepes \u00e9s a F\u00e9lk\u00f6v\u00e9r l\u00e1gy k\u00e9pen jobban olvashat\u00f3.'\n+  HINT_RIPPS2_HDD_START: 'A bels\u0151 HDD, APA vagy exFAT: a RIPPS2 beolvassa, ak\u00e1rmilyen is, \u00e9s egyszer mutatja, HDD-j\u00e1t\u00e9kokk\u00e9nt. A Ki b\u00e9k\u00e9n hagyja.'\n+  HINT_RIPPS2_THEME_COLOR: A sz\u00ednt\u00e9ma saj\u00e1t sz\u00edne. V\u00e1laszd az Egy\u00e9ni sz\u00ednt\u00e9m\u00e1t, hogy magad v\u00e1laszd meg, ahogy a sz\u00f6veg sz\u00edneit.\n+  HINT_RIPPS2_SWEEP: \u00d6t sz\u00edn, amelyeken a t\u00e9ma lassan \u00e9s sim\u00e1n v\u00e9gigmegy. Sziv\u00e1rv\u00e1ny, minden sz\u00ednt\u00e9m\u00e1b\u00f3l egy v\u00e1lt\u00e1s, vagy a saj\u00e1tod. A Ki mozdulatlanul hagyja a t\u00e9m\u00e1t.\n+  HINT_RIPPS2_SWEEP_COLORS: A v\u00e1lt\u00e1s \u00f6t sz\u00edne \u00e9s sebess\u00e9ge. V\u00e1ltoztass meg egyet a RIPPS2-\u00e9ib\u0151l, \u00e9s saj\u00e1todk\u00e9nt marad meg, a neked tetsz\u0151 n\u00e9ven.\n+  HINT_RIPPS2_SWEEP_NAME: Mi a v\u00e1lt\u00e1sod neve a Sz\u00ednv\u00e1lt\u00e1s list\u00e1ban.\n+  HINT_RIPPS2_SWEEP_COLOR: Az \u00f6t sz\u00edn egyike, amelyeken a v\u00e1lt\u00e1s sorban v\u00e9gigmegy.\n+  HINT_RIPPS2_SWEEP_SPEED: 'Meddig tart minden sz\u00edn, miel\u0151tt a k\u00f6vetkez\u0151 be\u00faszik. M\u00e9g az \u00c9l\u00e9nk is sima marad: a sz\u00ednek sosem villognak.'\n+  RIPPS2_HIDE_COMPRESS: J\u00e1t\u00e9k elrejt\u00e9se \u00e9s t\u00f6m\u00f6r\u00edt\u00e9s\n+  HINT_RIPPS2_SELECT_HOLD: 'SELECT 4.2 mp-ig nyomva egy j\u00e1t\u00e9klist\u00e1n. Lista: a t\u00e9m\u00e1k egy panelen, v\u00e1lassz egyet. L\u00e9ptet\u00e9s: a k\u00f6vetkez\u0151 t\u00e9ma. Ki: semmi (egy nyom\u00e1s tov\u00e1bbra is friss\u00edt).'"
        },
        {
          "sha": "ccfa4c56b8b7720037fbfac925dd35ae1177d7cf",
          "filename": "elf/lang/Indonesian.yml",
          "status": "modified",
          "additions": 208,
          "deletions": 22,
          "changes": 230,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FIndonesian.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FIndonesian.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FIndonesian.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: Indonesian\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTER MENERIMA, ESC MEMBATALKAN. TOMBOL APA SAJA MENAMPILKAN TUTS.\n   USB KEYBOARD: KEYBOARD USB\n-  '      DESR DVR ISOs (experimental)': '      ISO DESR DVR (eksperimental)'\n-  '      Internal HDD (exFAT)': '      HDD internal (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (eksperimental)'\n-  '      Protocol': '      Protokol'\n   Access: Akses\n   Applications Page Start Mode: Mode Mulai Halaman Aplikasi\n   Art Delay: Jeda Gambar\n@@ -22,7 +18,6 @@ strings:\n   Fifth: Kelima\n   First: Pertama\n   Fourth: Keempat\n-  HDD (APA) Start Mode: Mode Mulai HDD (APA)\n   HDD Cache: Cache HDD\n   HDD Spindown: HDD Tidur\n   MMCE Settings: Pengaturan MMCE\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: Ini game PS2 yang ditambal ESR. Taruh ESR (esr*.elf) di APPS, BOOT, atau ESR pada kartu memori atau flashdisk USB untuk menjalankannya.\n   '%s, BUILD %d': '%s, BUILD %d'\n   '... and the anonymous ...': '... dan mereka yang tak bernama ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': '1.8.0 dibawa tanpa perubahan (AFL-3.0): github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: Bongkar ulang dan rakit ulang RiptOPL, fork dari Open PS2 Loader,\n   ABOUT: TENTANG\n   'ACHIEVEMENTS: SPIRIT OF RA': 'PENCAPAIAN: SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): Video AVI di File Memori, fork RIPPS2 (AFL 2.0)\n   Apollo Save Tool by bucanero: Apollo Save Tool oleh bucanero\n   BACK: KEMBALI\n+  Back (keeps both): Kembali (simpan keduanya)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: Kartu MMCE ini tidak bisa mengganti nama file, padahal kompresi memerlukannya di langkah terakhir. Perbarui firmware sd2psXtd-nya ke 1.4.0 atau yang lebih baru (sd2psxtd.github.io/download), lalu coba lagi. Tidak ada yang diubah.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): BIBLE adalah PS2BBL 1.2.0, build bertanda tangan El Isra (kartu dan HDD), tanpa perubahan (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: Suara BIOS > Rip memutarnya seperti SPU2; tidak ada BIOS yang dibawa\n   BOOT LOCK AND BIBLE: BOOT LOCK DAN BIBLE\n   BOOT LOCK edits its config too: BOOT LOCK juga mengedit konfigurasinya\n   BUILD DETAILS: DETAIL BUILD\n-  BUILT ON RIPTOPL: DIBANGUN DI ATAS RIPTOPL\n   BearSSL by Thomas Pornin: BearSSL oleh Thomas Pornin\n   Built with Claude (Anthropic): Dibuat bersama Claude (Anthropic)\n   CD PLAYER: PEMUTAR CD\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek dan Sleek Bold, digambar untuk RIPPS2\n   RetroAchievements on the console by hacan359 (yoba): RetroAchievements di konsol oleh hacan359 (yoba)\n   RiptOPL by NathanNeurotic: RiptOPL oleh NathanNeurotic\n-  Roboto Condensed by Google (Apache-2.0): Roboto Condensed oleh Google (Apache-2.0)\n   SOUNDS: SUARA\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: Penginstal FMCB milik SP193 dan OSDMenu milik pcm720, diikuti langkah demi langkah\n   SUPPORT: DUKUNGAN\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: wLaunchELF R3Z oleh NathanNeurotic\n   wLaunchELF by the ps2homebrew team: wLaunchELF oleh tim ps2homebrew\n   what RIPPS2's card image writes are checked against: pembanding untuk penulisan image kartu oleh RIPPS2\n-  with its own look, launchers, file browser and save tools.: dengan tampilan, peluncur, peramban file, dan alat simpanannya sendiri.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: xeRAbora oleh hacan359, rcheevos oleh RetroAchievements\n   your console's own, ripped from its BIOS: suara milik konsolmu sendiri, di-rip dari BIOS-nya\n   ' (no DNS answer)': ' (tidak ada jawaban DNS)'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO: menulis %.60s'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: Neutrino tidak bisa ditulis ke %s. Periksa ruang kosongnya dan pastikan tidak dilindungi dari penulisan, lalu coba lagi.\n   THE GAME'S DRIVE  (%s/neutrino): DRIVE GAME  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : Game ini berjalan melalui Neutrino, dan tidak ada Neutrino di tempat yang dicari RIPPS2 (Pengaturan, drive game, kartu memori). RIPPS2 membawa Neutrino 1.8.0 oleh rickgaiser (AFL-3.0). Instal sekarang? Berikutnya kamu memilih tempatnya.\n   '%d / %d FILES': '%d / %d FILE'\n   '%d FOLDERS AND CONTENTS': '%d FOLDER DAN ISINYA'\n   '%d FOLDERS, %d FOUND': '%d FOLDER, %d DITEMUKAN'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d dari %d masih terkunci'\n   '%s   on %s': '%s   di %s'\n   BADGES: LENCANA\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: Lencana hanya bekerja saat TELEMETRI menyala dan xeRAbora berjalan di PC-mu; sampai saat itu lencana tidak membebani apa pun.\n   CHECK THE DISC: PERIKSA DISK\n   From this game's last CHECK GAME SUPPORT.: Dari PERIKSA DUKUNGAN GAME terakhir untuk game ini.\n   HOW IT WORKS: CARA KERJANYA\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: Ada PS2BBL dengan kunci RIPPS2 di konsol ini.\n   AUDIO folder: folder AUDIO\n   DRIVES: DRIVE\n-  INSTALL RIPPS2'S COPY: INSTAL SALINAN RIPPS2\n-  'Installed: ': 'Terinstal: '\n   MANAGE: KELOLA\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: KELOLA menunjukkan apa yang dijalankan tiap kartu memori, dan menginstal atau memperbaiki BIBLE.\n   MEMORY CARD 0  (about 600 KB): KARTU MEMORI 0  (sekitar 600 KB)\n   MEMORY CARD 1  (about 600 KB): KARTU MEMORI 1  (sekitar 600 KB)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO: driver-nya tidak mau dimuat (kode %d).'\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO: driver sudah ada, tetapi tidak ada kartu SD yang menjawab (slot mc1).'\n   NEUTRINO SETTINGS: PENGATURAN NEUTRINO\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': 'Belum ada: RIPPS2 tidak punya suara sendiri. Rip suara konsol ini, atau buat di RIPPS2 Audio Studio.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: Belum terinstal. Game yang membutuhkannya menawarkan salinan RIPPS2, atau instal di sini.\n   POPSTARTER NETWORK: JARINGAN POPSTARTER\n   POPSTARTER and Ember: POPSTARTER dan Ember\n   PS1 SETTINGS: PENGATURAN PS1\n   PS2BBL 1.2.0 for RIPPS2: PS2BBL 1.2.0 untuk RIPPS2\n   RIP THIS CONSOLE'S SOUNDS: RIP SUARA KONSOL INI\n-  RIPPS2's homebrew: homebrew RIPPS2\n   STATUS: STATUS\n-  'STATUS: looking for RIPPS2''s homebrew': 'STATUS: mencari homebrew RIPPS2'\n   a drive of unknown kind: drive jenis tak dikenal\n-  a drive or a memory card: drive atau kartu memori\n   boot loader, card by card: boot loader, kartu demi kartu\n   from its BIOS, into AUDIO: dari BIOS-nya, ke AUDIO\n   internal HDD (exFAT): HDD internal (exFAT)\n@@ -975,10 +958,193 @@ strings:\n   Sidebar: Bilah Samping\n   your own copy, found on your drives and memory cards: salinan milikmu sendiri, ditemukan di drive dan kartu memorimu\n   'your own, found and launched: github.com/Gageformer/Ember/releases': 'milikmu sendiri, ditemukan dan dijalankan: github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': 'Tidak ditemukan di mass0: (%s). Taruh ember.elf dan bios.bin milikmu di folder EMBER-nya.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: Tidak ditemukan. Taruh POPSTARTER.ELF milikmu sendiri di folder POPS sebuah drive.\n   Launch Disc: Jalankan Disk\n   Memory Files: File Memori\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: RIPPS2 menemukan %d dari 5. Masing-masing diingat, dan setiap peluncuran membacanya dari sana.\n+  not found on the started drives: tidak ditemukan di drive yang sudah dimulai\n+  not looked for yet: belum dicari\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s tidak menjawab, jadi %s tidak bisa dimulai. Apakah sudah tersambung?'\n+  '%s is not on %s any more, so it has left All Games.': '%s tidak ada lagi di %s, jadi sudah keluar dari Semua Game.'\n+  STARTING %s: MEMULAI %s\n+  the SMB share: share SMB\n+  the drive: drive itu\n+  the internal HDD (APA): HDD internal (APA)\n+  the internal HDD (exFAT): HDD internal (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: DIBANGUN DI ATAS RIPTOPL, DENGAN NEUTRINO\n+  'Special thanks to all the YouTube love as well:': 'Terima kasih khusus juga untuk semua cinta dari YouTube:'\n+  and its own look, launchers, file browser and save tools.: dan dengan tampilan, peluncur, penjelajah file, dan alat save miliknya sendiri.\n+  glad to help however you reach me.: senang membantu, lewat mana pun kamu menghubungiku.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): inti game kedua, 1.8.0 dibawa tanpa diubah (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: dengan Neutrino buatan rickgaiser di dalamnya sebagai inti game kedua,\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : 'Game ini dimulai lewat Neutrino, dan RIPPS2 tidak menemukannya: tidak di jalur Pengaturan, di drive game ini, di kartu memori, atau di drive mana pun yang sudah dimulai. RIPPS2 membawa Neutrino 1.8.0 buatan rickgaiser (AFL-3.0): pasang sekali, di mana pun kamu mau, dan setiap game Neutrino memakainya dari sana.'\n+  STARTING THE DRIVES: MEMULAI DRIVE\n+  '%d games, on the started drives': '%d game, di drive yang sudah dimulai'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s: BEBERAPA FILE TIDAK BISA DIHAPUS'\n+  '%u partitions, all in order': '%u partisi, semuanya rapi'\n+  ', %d art images': ', %d gambar sampul'\n+  ', and ': ', dan '\n+  ', its cheats': ', cheat-nya'\n+  ', its settings': ', pengaturannya'\n+  1 game, on the started drives: 1 game, di drive yang sudah dimulai\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: Game PS1 di sini dihapus di File Memori, tempat game itu menjadi file tersendiri.\n+  A network catalogue's games are deleted on the PC that serves them.: Game dari katalog jaringan dihapus di PC yang menyajikannya.\n+  DELETE FOR GOOD: HAPUS SELAMANYA\n+  DELETE IT: HAPUS\n+  'DELETED %s: %s FREED': '%s DIHAPUS: %s BEBAS'\n+  'Delete %s from %s? Goes: %s.': 'Hapus %s dari %s? Hilang: %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': 'Hapus %s dari %s? Hilang: %s. Tetap: %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: Hapus juga save dan gambar RIPSHOT-nya? Tidak berarti menyimpannya.\n+  HIDDEN GAMES: GAME TERSEMBUNYI\n+  'HIDDEN: %s': 'DISEMBUNYIKAN: %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: Sembunyikan %s? Game ini keluar dari setiap daftar, termasuk Semua Game dan Favorit. Pengaturan > Sumber gim > Game tersembunyi menampilkannya lagi.\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: Pemeriksaan terakhir. %s dihapus selamanya, dan tidak ada yang bisa mengembalikannya. Tekan START untuk menghapusnya.\n+  None. Hide Game, in a game's options, puts one here.: Tidak ada. Sembunyikan game, di opsi sebuah game, menaruh satu di sini.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : 'Tidak ada yang dihapus: tabel partisi drive ini tidak cocok dengan disk ini (%s). Mungkin tabel itu ditulis dari image disk berukuran lain. Perbaiki dulu di PC.'\n+  RIPPS2 cannot delete a game from this page.: RIPPS2 tidak bisa menghapus game dari halaman ini.\n+  RIPPS2 could not find this game's files on its drive.: RIPPS2 tidak menemukan file game ini di drive-nya.\n+  Reading the hidden lists: Membaca daftar tersembunyi\n+  SHOW AGAIN OR DELETE: TAMPILKAN LAGI ATAU HAPUS\n+  SHOW IT AGAIN: TAMPILKAN LAGI\n+  That game is not on its drive any more.: Game itu sudah tidak ada di drive-nya.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: Daftar tersembunyi tidak bisa ditulis ke drive game ini. Apakah drive-nya dilindungi tulis, atau Operasi Tulis dimatikan?\n+  The partition could not be removed (%d). Nothing was deleted.: Partisi tidak bisa dihapus (%d). Tidak ada yang dihapus.\n+  This drive cannot keep a hidden list (a network catalogue).: Drive ini tidak bisa menyimpan daftar tersembunyi (katalog jaringan).\n+  This game's drive has not answered yet. Wait for it, then try again.: Drive game ini belum menjawab. Tunggu sebentar, lalu coba lagi.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: Partisi game ini termasuk yang tidak dihapus RIPPS2 (partisi sistem, atau yang berisi pengaturannya).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': 'sebuah partisi lebih besar dari yang diizinkan disk ini: tabelnya dibuat untuk disk berukuran lain'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': 'sebuah partisi melewati akhir disk ini (sektor %u dari %u): tabelnya dibuat untuk disk yang lebih besar'\n+  its RIPSHOT pictures: gambar RIPSHOT-nya\n+  its drive: drive-nya\n+  its saves: save-nya\n+  sector %u holds no partition header: sektor %u tidak berisi header partisi\n+  the chain does not close at __mbr: rantainya tidak tertutup di __mbr\n+  the chain skips from sector %u: rantainya melompat dari sektor %u\n+  the chain's links disagree at sector %u: mata rantainya tidak cocok di sektor %u\n+  the drive's size could not be read: ukuran drive tidak bisa dibaca\n+  the game (%s): game ini (%s)\n+  the game's folder and everything in it (%s): folder game ini dan semua isinya (%s)\n+  the header at sector %u could not be read: header di sektor %u tidak bisa dibaca\n+  the header at sector %u fails its checksum: header di sektor %u gagal checksum\n+  the partition at sector %u is not where or as large as APA puts one: partisi di sektor %u tidak berada di tempat, atau tidak sebesar, partisi yang dibuat APA\n+  the settings and art another copy of it uses: pengaturan dan sampul yang dipakai salinan lainnya\n+  '%d FOLDERS': '%d FOLDER'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': '%s tidak bisa disalin ke %s. Apakah drive-nya penuh atau dilindungi tulis?'\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s tidak ada di tempat RIPPS2 mencari. Cari di drive yang sudah dimulai RIPPS2? Tempat ditemukannya akan diingat untuk setiap peluncuran sesudahnya.'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Ember tidak ada di %s, di samping game, padahal harus ada di sana. RIPPS2 menemukannya di %s: START menyalinnya ke sana (%d KB) dan memulai game.'\n+  Ember's BIOS: BIOS Ember\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : 'BIOS Ember (bios.bin) tidak ada di %s, di samping Ember. RIPPS2 menemukan satu di %s: START menyalinnya ke sana (%d KB) dan memulai game.'\n+  LOOKING FOR YOUR LAUNCHERS: MENCARI PELUNCURMU\n+  The POPS files: File POPS\n+  '%s  MMCE card': '%s  kartu MMCE'\n+  INSTALL RIPPS2'S NEUTRINO: PASANG NEUTRINO DARI RIPPS2\n+  'Internal HDD (APA): %s free of %s': 'HDD internal (APA): %s kosong dari %s'\n+  LAUNCHERS: PELUNCUR\n+  LOOK FOR THEM: CARI\n+  'Memory card %d: %d KB free': 'Kartu memori %d: %d KB kosong'\n+  POPS files: File POPS\n+  START EVERY DRIVE: MULAI SEMUA DRIVE\n+  'STATUS: looking at the drives and the homebrew': 'STATUS: memeriksa drive dan homebrew'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): Mulai semua drive dulu, supaya pencarian menjangkau semuanya? (Tidak mencari di drive yang sudah dimulai.)\n+  USB, HDD, MMCE and the rest: USB, HDD, MMCE, dan lainnya\n+  every started drive: setiap drive yang sudah dimulai\n+  found where you keep them: ditemukan di tempat kamu menyimpannya\n+  not found yet: belum ditemukan\n+  not on a started drive: tidak di drive yang sudah dimulai\n+  only if you have none: hanya jika kamu tidak punya\n+  starts every drive first: memulai semua drive dulu\n+  Drives, launchers and homebrew: Drive, peluncur, dan homebrew\n+  BDM Devices: Perangkat BDM\n+  Color Sweep: Peralihan Warna\n+  Colors and Text: Warna dan Teks\n+  HDD POPS partitions: Partisi POPS HDD\n+  Layout: Tata Letak\n+  Missing Covers: Sampul yang Hilang\n+  Name: Nama\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: Belum ditemukan pengaturan SMB POPSTARTER. Isi baris di bawah.\n+  POPSTARTER Network: Jaringan POPSTARTER\n+  POPSTARTER SMB Network: Jaringan SMB POPSTARTER\n+  POPSTARTER UDPFS Network: Jaringan UDPFS POPSTARTER\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: RIPPS2 sendiri menambahkan drive, ISO, VMC, dan mode video.\n+  Screen: Layar\n+  Speed: Kecepatan\n+  Sweep Colors: Warna Peralihan\n+  Text Weight: Ketebalan Teks\n+  Theme Color: Warna Utama\n+  Bold: Tebal\n+  Controllers: Pengontrol\n+  Lively: Lincah\n+  Medium: Sedang\n+  PS Emulation: Emulasi PS\n+  Regular: Biasa\n+  Slow: Lambat\n+  A MOMENT: SEBENTAR\n+  A TASK IN THE BACKGROUND: SEBUAH TUGAS DI LATAR\n+  ABOUT %d S: SEKITAR %d D\n+  ANY MOMENT: SEBENTAR LAGI\n+  READING THE GAME LIST OF %s: MEMBACA DAFTAR GAME %s\n+  SAVING A LIST: MENYIMPAN DAFTAR\n+  START NOW: MULAI SEKARANG\n+  STARTING: MEMULAI\n+  STARTS NEXT: MULAI BERIKUTNYA\n+  STOPPING IT SAFELY: BERHENTI DENGAN AMAN\n+  THE LAST OF THE QUEUE: AKHIR ANTREAN\n+  WHEN IT FINISHES: SAAT SELESAI\n+  What each card starts; install or repair BIBLE.: Apa yang dijalankan tiap kartu; pasang atau perbaiki BIBLE.\n+  Rainbow: Pelangi\n+  Custom: Kustom\n+  DESR DVR ISOs (experimental): ISO DESR DVR (eksperimental)\n+  PC CARD CF (experimental): PC CARD CF (eksperimental)\n+  HDD Start Mode: Mode Mulai HDD\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): Roboto Condensed oleh The Roboto Project Authors (SIL OFL 1.1)\n+  '%s on %s': '%s di %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: %s menjadi %s (%d%%), setiap bagian diperiksa. Hapus ISO dan simpan ZSO?'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: %s kembali menjadi %s, setiap bagian diperiksa. Hapus ZSO dan simpan ISO?'\n+  A ZSO of this game is already beside it. Remove one of the two first.: Sudah ada ZSO game ini di sebelahnya. Hapus salah satu dari keduanya dulu.\n+  About %d min, then checked: Sekitar %d menit, lalu diperiksa\n+  An ISO of this game is already beside it. Remove one of the two first.: Sudah ada ISO game ini di sebelahnya. Hapus salah satu dari keduanya dulu.\n+  CHECKING EVERY PIECE: MEMERIKSA SETIAP BAGIAN\n+  COMPRESS TO ZSO: KOMPRES KE ZSO\n+  'COMPRESSED %s: %s FREED': '%s DIKOMPRES: %s DIBEBASKAN'\n+  COMPRESSING 1 OF 2: MENGOMPRES 1 DARI 2\n+  COMPRESSING 2 OF 2: MENGOMPRES 2 DARI 2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : Kompres %s? ISO tetap ada sampai ZSO dibaca ulang dan diperiksa, lalu RIPPS2 bertanya sebelum menghapus ISO. Drive butuh ruang untuk keduanya selama bekerja; biarkan konsol menyala sampai selesai. ZSO hanya berjalan dengan inti OPL (bukan Neutrino); save, pengaturan, gambar, dan cheat-nya tetap bersamanya, dan bisa dijadikan ISO lagi kapan saja.\n+  Compress to ZSO: Kompres ke ZSO\n+  Cycle: Bergilir\n+  'Experimental: OPL core only (not Neutrino)': 'Eksperimental: hanya inti OPL (bukan Neutrino)'\n+  Hold SELECT: Tahan SELECT\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: Tidak bisa diselesaikan (%s). Yang sudah ditulis dihapus; game tetap seperti semula.\n+  List: Daftar\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : Jadikan %s ISO lagi? ZSO tetap ada sampai ISO dibaca ulang dan diperiksa, lalu RIPPS2 bertanya sebelum menghapus ZSO. Drive butuh ruang untuk keduanya selama bekerja; biarkan konsol menyala sampai selesai. Save, pengaturan, gambar, dan cheat-nya tetap bersamanya.\n+  Stop? What it has written so far is removed; the game stays as it was.: Hentikan? Yang sudah ditulis sejauh ini dihapus; game tetap seperti semula.\n+  THEMES: TEMA\n+  There is not enough memory for this just now.: Memori tidak cukup untuk ini saat ini.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: Image ini tidak bisa dikompres atau didekompres oleh RIPPS2 (ukurannya bukan blok 2 KB utuh).\n+  UNCOMPRESS TO ISO: DEKOMPRES KE ISO\n+  UNCOMPRESSED %s: '%s DIDEKOMPRES'\n+  UNCOMPRESSING: MENDEKOMPRES\n+  Uncompress to ISO: Dekompres ke ISO\n+  Under a minute, then checked: Kurang dari semenit, lalu diperiksa\n+  COPYING: MENYALIN\n+  SHOW AGAIN, DELETE OR COMPRESS: TAMPILKAN LAGI, HAPUS ATAU KOMPRES\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: TAMPILKAN LAGI, HAPUS ATAU DEKOMPRES\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': '%s butuh sekitar %s ruang kosong di drive-nya selama bekerja (aslinya tetap ada sampai akhir), dan drive punya %s kosong.'\n+  Hide and Compress: Sembunyikan dan kompres\n+  'COMPRESSED AND HIDDEN: %s': 'DIKOMPRES DAN DISEMBUNYIKAN: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': 'Tidak ada ruang untuk rip ini: butuh sekitar %s, dan drive punya %s kosong.'\n+  OVER 4 GB FOR FAT32: LEBIH DARI 4 GB UNTUK FAT32\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: Drive ini %s, yang tidak menyimpan file 4 GB atau lebih, dan image barunya akan %s.\n+  '%s, %s clusters, %s free of %s': '%s, klaster %s, %s kosong dari %s'\n+  Delete This Sweep: Hapus peralihan ini\n+  Delete %s? RIPPS2's own sweeps stay as they are.: Hapus %s? Peralihan bawaan RIPPS2 tetap seperti semula.\n+  Sweep: Peralihan\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: Lencana menandai game yang punya pencapaian di daftar Anda, dengan berapa yang sudah terbuka. Butuh TELEMETRI menyala.\n labels:\n   BDM_GAMES: Game BDM\n   HDD_GAMES: Game HDD\n@@ -1226,7 +1392,7 @@ labels:\n   SAVE_ICONS: Ikon Simpanan 3D\n   HINT_SAVE_ICONS: Menampilkan ikon simpanan game yang dipilih, berputar, di tempat yang disediakan tema. Membaca VMC milik game lebih dulu, lalu kartu memori.\n   NETBOOT_RESTART_NOW: Protokol jaringan berubah setelah mulai ulang. Simpan pengaturan dan mulai ulang OPL sekarang?\n-  HINT_NET_PROTOCOL: 'Layanan jaringan yang dipakai RiptOPL: share SMB, UDPFS, UDPBD, atau HTTP. Pengaturan yang sama dengan di Sumber Game. Beralih dari layanan yang sudah berjalan butuh mulai ulang.'\n+  HINT_NET_PROTOCOL: 'Layanan jaringan yang dipakai RiptOPL: share SMB, UDPFS, UDPBD, atau HTTP. Beralih dari layanan yang sudah berjalan butuh mulai ulang.'\n   HINT_UDPFS_ACCESS: Hanya UDPFS. File membaca ISO lepas dari folder game di server; IMG membaca image disk sebagai drive. SMB dan HTTP selalu memakai File, UDPBD selalu IMG.\n   HINT_SMB_VERSION: Versi SMB yang dipakai untuk berbicara dengan share PC.\n   HINT_NET_ADVANCED: Membuka Mode Link Ethernet dan Port SMB untuk diedit.\n@@ -1425,3 +1591,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: Cara jepretan yang menunggu di rol tiap game menjadi PNG.\n   RIPPS2_SETTINGS_HOME: Simpan pengaturan ke\n   HINT_RIPPS2_SETTINGS_HOME: 'Tempat RIPPS2 menyimpan pengaturannya: save miliknya di kartu memori, folder RIPPS2 di sebuah drive (HDD internal, USB, MX4SIO, iLink, atau MMCE), atau folder yang Anda ketik.'\n+  RIPPS2_LAUNCHERS: Peluncur\n+  RIPPS2_LOOK_NOW: Cari sekarang\n+  HINT_RIPPS2_LOOK_NOW: RIPPS2 mencari POPSTARTER, file POPS, Ember, BIOS Ember, dan Neutrino di drive yang sudah dimulai, lalu mengingat letak masing-masing. Setiap peluncuran membacanya dari sana.\n+  RIPPS2_HIDE_GAME: Sembunyikan game\n+  RIPPS2_HIDDEN_GAMES: Game tersembunyi\n+  HINT_RIPPS2_HIDDEN_GAMES: Game yang disembunyikan dengan Sembunyikan game, di drive yang sudah dimulai. Tampilkan satu lagi, atau hapus. Setiap drive menyimpan daftarnya sendiri.\n+  HINT_RIPPS2_BDM_DEVICES: 'Drive tempat BDM membaca game: USB, MX4SIO, PC Card CF, drive DVR milik DESR, dan iLink. HDD internal mengikuti Mode Mulai HDD.'\n+  HINT_RIPPS2_SCREEN: 'Gambar menu: mode video, layar lebar, letaknya di TV (H-POS, V-POS) dan overscan.'\n+  HINT_RIPPS2_LAYOUT: Letak kategori dan cara halaman masuk, lalu sampul RIPFLOW dan kisi RIPGRID.\n+  HINT_RIPPS2_COLORS_TEXT: Ketebalan teks, tema warna, peralihan warna, dan setiap warna.\n+  HINT_RIPPS2_TEXT_WEIGHT: 'Seberapa tebal teks milik RIPPS2: tip di Pengaturan, STATUS, dan Tentang. Biasa seperti sebelumnya; Sedang dan Tebal lebih mudah dibaca pada gambar yang lembut.'\n+  HINT_RIPPS2_HDD_START: 'HDD internal, APA atau exFAT: RIPPS2 membacanya apa pun formatnya dan menampilkannya sekali, sebagai Game HDD. Mati membiarkannya.'\n+  HINT_RIPPS2_THEME_COLOR: Warna milik tema itu sendiri. Pilih Kustom sebagai tema warna untuk memilihnya sendiri, seperti warna teks.\n+  HINT_RIPPS2_SWEEP: Lima warna yang dilalui tema, perlahan dan halus. Pelangi, peralihan dari tiap tema warna, atau milikmu. Mati menjaga tema tetap diam.\n+  HINT_RIPPS2_SWEEP_COLORS: Lima warna peralihan dan kecepatannya. Ubah salah satu milik RIPPS2 dan itu disimpan sebagai milikmu, dengan nama yang kamu berikan.\n+  HINT_RIPPS2_SWEEP_NAME: Nama peralihanmu di daftar Peralihan Warna.\n+  HINT_RIPPS2_SWEEP_COLOR: Salah satu dari lima warna yang dilalui peralihan, berurutan.\n+  HINT_RIPPS2_SWEEP_SPEED: 'Berapa lama tiap warna bertahan sebelum warna berikutnya muncul. Bahkan Lincah tetap halus: warnanya tidak pernah berkedip.'\n+  RIPPS2_HIDE_COMPRESS: Sembunyikan game dan kompresi\n+  HINT_RIPPS2_SELECT_HOLD: 'SELECT ditahan 4.2 dtk di daftar game. Daftar: tema dalam panel, pilih satu. Bergilir: tema berikutnya. Mati: tidak ada (tekan tetap menyegarkan).'"
        },
        {
          "sha": "19692ddc5c2aa0402b972a88168e6f09d43e1ad9",
          "filename": "elf/lang/Italian.yml",
          "status": "modified",
          "additions": 208,
          "deletions": 22,
          "changes": 230,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FItalian.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FItalian.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FItalian.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: Italian\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: INVIO CONFERMA, ESC ANNULLA. QUALSIASI TASTO MOSTRA I TASTI.\n   USB KEYBOARD: TASTIERA USB\n-  '      DESR DVR ISOs (experimental)': '      ISO DESR DVR (sperimentale)'\n-  '      Internal HDD (exFAT)': '      Hard disk interno (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (sperimentale)'\n-  '      Protocol': '      Protocollo'\n   Access: Accesso\n   Applications Page Start Mode: Avvio della pagina Applicazioni\n   Art Delay: Ritardo delle immagini\n@@ -22,7 +18,6 @@ strings:\n   Fifth: Quinto\n   First: Primo\n   Fourth: Quarto\n-  HDD (APA) Start Mode: Avvio dell'hard disk (APA)\n   HDD Cache: Cache dell'hard disk\n   HDD Spindown: Sospensione dell'hard disk\n   MMCE Settings: Impostazioni MMCE\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: Questo \u00e8 un gioco PS2 con patch ESR. Metti ESR (esr*.elf) in APPS, BOOT o ESR su una memory card o una chiavetta USB per avviarlo.\n   '%s, BUILD %d': '%s, BUILD %d'\n   '... and the anonymous ...': '... e gli anonimi ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': '1.8.0 incluso senza modifiche (AFL-3.0): github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: Uno smontaggio e una ricostruzione di RiptOPL, il fork di Open PS2 Loader,\n   ABOUT: INFORMAZIONI\n   'ACHIEVEMENTS: SPIRIT OF RA': 'OBIETTIVI: SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): Video AVI in File di memoria, il fork di RIPPS2 (AFL 2.0)\n   Apollo Save Tool by bucanero: Apollo Save Tool di bucanero\n   BACK: INDIETRO\n+  Back (keeps both): Indietro (li tiene entrambi)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: Questa scheda MMCE non sa rinominare un file, cosa che la compressione richiede nel suo ultimo passaggio. Aggiorna il suo firmware sd2psXtd alla 1.4.0 o successiva (sd2psxtd.github.io/download), poi riprova. Non \u00e8 stato cambiato nulla.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): BIBLE \u00e8 PS2BBL 1.2.0, le build firmate di El Isra (memory card e hard disk), senza modifiche (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: Suoni del BIOS > Estrai li suona come fa l'SPU2; nessun BIOS \u00e8 incluso\n   BOOT LOCK AND BIBLE: BOOT LOCK E BIBLE\n   BOOT LOCK edits its config too: BOOT LOCK modifica anche la sua configurazione\n   BUILD DETAILS: DETTAGLI DELLA BUILD\n-  BUILT ON RIPTOPL: BASATO SU RIPTOPL\n   BearSSL by Thomas Pornin: BearSSL di Thomas Pornin\n   Built with Claude (Anthropic): Realizzato con Claude (Anthropic)\n   CD PLAYER: LETTORE CD\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek e Sleek Bold, disegnati per RIPPS2\n   RetroAchievements on the console by hacan359 (yoba): RetroAchievements sulla console, di hacan359 (yoba)\n   RiptOPL by NathanNeurotic: RiptOPL di NathanNeurotic\n-  Roboto Condensed by Google (Apache-2.0): Roboto Condensed di Google (Apache-2.0)\n   SOUNDS: SUONI\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: l'installer di FMCB di SP193 e l'OSDMenu di pcm720, seguiti passo per passo\n   SUPPORT: SUPPORTO\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: wLaunchELF R3Z di NathanNeurotic\n   wLaunchELF by the ps2homebrew team: wLaunchELF del team ps2homebrew\n   what RIPPS2's card image writes are checked against: ci\u00f2 con cui vengono verificate le scritture di immagini di memory card di RIPPS2\n-  with its own look, launchers, file browser and save tools.: con un aspetto proprio, launcher, browser di file e strumenti per i salvataggi.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: xeRAbora di hacan359, rcheevos di RetroAchievements\n   your console's own, ripped from its BIOS: quelli della tua console, estratti dal suo BIOS\n   ' (no DNS answer)': ' (nessuna risposta DNS)'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO: scrittura di %.60s'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: Impossibile scrivere Neutrino in %s. Controlla lo spazio libero e che non sia protetta da scrittura, poi riprova.\n   THE GAME'S DRIVE  (%s/neutrino): L'UNIT\u00c0 DEL GIOCO  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : Questo gioco si avvia tramite Neutrino, e non c'\u00e8 Neutrino dove RIPPS2 cerca (Impostazioni, l'unit\u00e0 del gioco, le memory card). RIPPS2 include Neutrino 1.8.0 di rickgaiser (AFL-3.0). Installarlo ora? Poi scegli dove.\n   '%d / %d FILES': '%d / %d FILE'\n   '%d FOLDERS AND CONTENTS': '%d CARTELLE CON CONTENUTO'\n   '%d FOLDERS, %d FOUND': '%d CARTELLE, %d TROVATI'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d di %d ancora bloccati'\n   '%s   on %s': '%s   su %s'\n   BADGES: BADGE\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: I badge funzionano solo con la TELEMETRIA attiva e xeRAbora in esecuzione sul tuo PC; fino ad allora non costano niente.\n   CHECK THE DISC: CONTROLLA IL DISCO\n   From this game's last CHECK GAME SUPPORT.: Dall'ultimo CONTROLLO DEL SUPPORTO di questo gioco.\n   HOW IT WORKS: COME FUNZIONA\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: Su questa console c'\u00e8 un PS2BBL con il blocco di RIPPS2.\n   AUDIO folder: cartella AUDIO\n   DRIVES: UNIT\u00c0\n-  INSTALL RIPPS2'S COPY: INSTALLA LA COPIA DI RIPPS2\n-  'Installed: ': 'Installato: '\n   MANAGE: GESTISCI\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: GESTISCI mostra cosa avvia ogni memory card, e installa o ripara BIBLE.\n   MEMORY CARD 0  (about 600 KB): MEMORY CARD 0  (circa 600 KB)\n   MEMORY CARD 1  (about 600 KB): MEMORY CARD 1  (circa 600 KB)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO: il suo driver non si \u00e8 caricato (codice %d).'\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO: il driver \u00e8 caricato, ma nessuna scheda SD ha risposto (lo slot mc1).'\n   NEUTRINO SETTINGS: IMPOSTAZIONI DI NEUTRINO\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': 'Ancora nessuno: RIPPS2 non ha suoni propri. Estrai quelli di questa console, o creali in RIPPS2 Audio Studio.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: Non installato. Un gioco che ne ha bisogno propone la copia di RIPPS2, oppure installalo qui.\n   POPSTARTER NETWORK: RETE DI POPSTARTER\n   POPSTARTER and Ember: POPSTARTER ed Ember\n   PS1 SETTINGS: IMPOSTAZIONI PS1\n   PS2BBL 1.2.0 for RIPPS2: PS2BBL 1.2.0 per RIPPS2\n   RIP THIS CONSOLE'S SOUNDS: ESTRAI I SUONI DI QUESTA CONSOLE\n-  RIPPS2's homebrew: L'homebrew di RIPPS2\n   STATUS: STATO\n-  'STATUS: looking for RIPPS2''s homebrew': 'STATO: ricerca dell''homebrew di RIPPS2'\n   a drive of unknown kind: un'unit\u00e0 di tipo sconosciuto\n-  a drive or a memory card: un'unit\u00e0 o una memory card\n   boot loader, card by card: loader di avvio, memory card per memory card\n   from its BIOS, into AUDIO: dal suo BIOS, in AUDIO\n   internal HDD (exFAT): hard disk interno (exFAT)\n@@ -975,10 +958,193 @@ strings:\n   Sidebar: Barra laterale\n   your own copy, found on your drives and memory cards: la tua copia, trovata sulle tue unit\u00e0 e memory card\n   'your own, found and launched: github.com/Gageformer/Ember/releases': 'il tuo, trovato e avviato: github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': 'Non trovato su mass0: (%s). Metti ember.elf e il tuo bios.bin nella sua cartella EMBER.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: Non trovato. Metti il tuo POPSTARTER.ELF nella cartella POPS di un'unit\u00e0.\n   Launch Disc: Avvia disco\n   Memory Files: File di memoria\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: RIPPS2 ne ha trovati %d su 5. Ognuno viene ricordato, e ogni avvio lo legge da l\u00ec.\n+  not found on the started drives: non \u00e8 sulle unit\u00e0 avviate\n+  not looked for yet: non ancora cercato\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s non ha risposto, quindi %s non pu\u00f2 avviarsi. \u00c8 collegato?'\n+  '%s is not on %s any more, so it has left All Games.': '%s non \u00e8 pi\u00f9 su %s, quindi ha lasciato Tutti i giochi.'\n+  STARTING %s: AVVIO DI %s\n+  the SMB share: la condivisione SMB\n+  the drive: l'unit\u00e0\n+  the internal HDD (APA): l'hard disk interno (APA)\n+  the internal HDD (exFAT): l'hard disk interno (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: COSTRUITO SU RIPTOPL, CON NEUTRINO\n+  'Special thanks to all the YouTube love as well:': 'Un grazie speciale anche a tutto l''affetto da YouTube:'\n+  and its own look, launchers, file browser and save tools.: e con un aspetto, launcher, esplora file e strumenti di salvataggio tutti suoi.\n+  glad to help however you reach me.: felice di aiutare, in qualunque modo mi contattiate.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): il secondo core di gioco, 1.8.0 incluso senza modifiche (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: con il Neutrino di rickgaiser dentro come secondo core di gioco,\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : 'Questo gioco si avvia con Neutrino, e RIPPS2 non ne ha trovato nessuno: n\u00e9 al percorso delle Impostazioni, n\u00e9 sull''unit\u00e0 del gioco, n\u00e9 sulle memory card, n\u00e9 su un''unit\u00e0 avviata. RIPPS2 include Neutrino 1.8.0 di rickgaiser (AFL-3.0): installalo una volta, dove scegli, e ogni gioco Neutrino lo usa da l\u00ec.'\n+  STARTING THE DRIVES: AVVIO DELLE UNIT\u00c0\n+  '%d games, on the started drives': '%d giochi, sulle unit\u00e0 avviate'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s: ALCUNI FILE NON SONO STATI ELIMINATI'\n+  '%u partitions, all in order': '%u partizioni, tutte in ordine'\n+  ', %d art images': ', %d immagini'\n+  ', and ': ', e '\n+  ', its cheats': ', i suoi trucchi'\n+  ', its settings': ', le sue impostazioni'\n+  1 game, on the started drives: 1 gioco, sulle unit\u00e0 avviate\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: Un gioco PS1 qui si elimina in File di memoria, dove \u00e8 un file a s\u00e9.\n+  A network catalogue's games are deleted on the PC that serves them.: I giochi di un catalogo di rete si eliminano sul PC che li fornisce.\n+  DELETE FOR GOOD: ELIMINA PER SEMPRE\n+  DELETE IT: ELIMINALO\n+  'DELETED %s: %s FREED': '%s ELIMINATO: %s LIBERATI'\n+  'Delete %s from %s? Goes: %s.': 'Eliminare %s da %s? Va via: %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': 'Eliminare %s da %s? Va via: %s. Resta: %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: Eliminare anche i suoi salvataggi e le immagini RIPSHOT? No li tiene.\n+  HIDDEN GAMES: GIOCHI NASCOSTI\n+  'HIDDEN: %s': 'NASCOSTO: %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: Nascondere %s? Esce da ogni elenco, anche da Tutti i giochi e Preferiti. Impostazioni > Sorgenti dei giochi > Giochi nascosti lo mostra di nuovo.\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: Ultimo controllo. %s viene eliminato per sempre, e niente potr\u00e0 riportarlo indietro. Premi START per eliminarlo.\n+  None. Hide Game, in a game's options, puts one here.: Nessuno. Nascondi gioco, nelle opzioni di un gioco, ne mette uno qui.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : 'Non \u00e8 stato eliminato nulla: la tabella delle partizioni di questa unit\u00e0 non corrisponde a questo disco (%s). Potrebbe essere stata scritta dall''immagine di un disco di altre dimensioni. Riparala prima su un PC.'\n+  RIPPS2 cannot delete a game from this page.: RIPPS2 non pu\u00f2 eliminare un gioco da questa pagina.\n+  RIPPS2 could not find this game's files on its drive.: RIPPS2 non ha trovato i file di questo gioco sulla sua unit\u00e0.\n+  Reading the hidden lists: Lettura degli elenchi nascosti\n+  SHOW AGAIN OR DELETE: MOSTRA DI NUOVO O ELIMINA\n+  SHOW IT AGAIN: MOSTRALO DI NUOVO\n+  That game is not on its drive any more.: Quel gioco non \u00e8 pi\u00f9 sulla sua unit\u00e0.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: L'elenco nascosto non \u00e8 stato scritto sull'unit\u00e0 del gioco. \u00c8 protetta da scrittura, o le Operazioni di scrittura sono disattivate?\n+  The partition could not be removed (%d). Nothing was deleted.: La partizione non \u00e8 stata rimossa (%d). Non \u00e8 stato eliminato nulla.\n+  This drive cannot keep a hidden list (a network catalogue).: Questa unit\u00e0 non pu\u00f2 tenere un elenco nascosto (un catalogo di rete).\n+  This game's drive has not answered yet. Wait for it, then try again.: L'unit\u00e0 di questo gioco non ha ancora risposto. Aspettala, poi riprova.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: La partizione di questo gioco \u00e8 una che RIPPS2 non rimuove (una partizione di sistema, o quella con le sue impostazioni).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': 'una partizione \u00e8 pi\u00f9 grande di quanto questo disco permetta: la tabella \u00e8 stata fatta per un disco di altre dimensioni'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': 'una partizione va oltre la fine di questo disco (settore %u di %u): la tabella \u00e8 stata fatta per un disco pi\u00f9 grande'\n+  its RIPSHOT pictures: le sue immagini RIPSHOT\n+  its drive: la sua unit\u00e0\n+  its saves: i suoi salvataggi\n+  sector %u holds no partition header: il settore %u non contiene un'intestazione di partizione\n+  the chain does not close at __mbr: la catena non si chiude su __mbr\n+  the chain skips from sector %u: la catena salta dal settore %u\n+  the chain's links disagree at sector %u: i collegamenti della catena non coincidono al settore %u\n+  the drive's size could not be read: non \u00e8 stato possibile leggere la dimensione dell'unit\u00e0\n+  the game (%s): il gioco (%s)\n+  the game's folder and everything in it (%s): la cartella del gioco e tutto il suo contenuto (%s)\n+  the header at sector %u could not be read: non \u00e8 stato possibile leggere l'intestazione al settore %u\n+  the header at sector %u fails its checksum: l'intestazione al settore %u non supera il checksum\n+  the partition at sector %u is not where or as large as APA puts one: la partizione al settore %u non \u00e8 dove o grande quanto APA ne crea una\n+  the settings and art another copy of it uses: le impostazioni e le immagini usate da un'altra copia\n+  '%d FOLDERS': '%d CARTELLE'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': '%s non \u00e8 stato copiato in %s. L''unit\u00e0 \u00e8 piena o protetta da scrittura?'\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s non \u00e8 dove RIPPS2 cerca. Cercarlo sulle unit\u00e0 avviate da RIPPS2? Dove viene trovato resta ricordato per ogni avvio successivo.'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Ember non \u00e8 in %s, accanto ai giochi, e deve esserci. RIPPS2 l''ha trovato in %s: START lo copia l\u00ec (%d KB) e avvia il gioco.'\n+  Ember's BIOS: BIOS di Ember\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Il BIOS di Ember (bios.bin) non \u00e8 in %s, accanto a Ember. RIPPS2 ne ha trovato uno in %s: START lo copia l\u00ec (%d KB) e avvia il gioco.'\n+  LOOKING FOR YOUR LAUNCHERS: RICERCA DEI TUOI LAUNCHER\n+  The POPS files: I file POPS\n+  '%s  MMCE card': '%s  scheda MMCE'\n+  INSTALL RIPPS2'S NEUTRINO: INSTALLA IL NEUTRINO DI RIPPS2\n+  'Internal HDD (APA): %s free of %s': 'Hard disk interno (APA): %s liberi su %s'\n+  LAUNCHERS: LAUNCHER\n+  LOOK FOR THEM: CERCALI\n+  'Memory card %d: %d KB free': 'Memory card %d: %d KB liberi'\n+  POPS files: File POPS\n+  START EVERY DRIVE: AVVIA TUTTE LE UNIT\u00c0\n+  'STATUS: looking at the drives and the homebrew': 'STATO: controllo delle unit\u00e0 e degli homebrew'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): Avviare prima tutte le unit\u00e0, cos\u00ec la ricerca le raggiunge tutte? (No cerca sulle unit\u00e0 gi\u00e0 avviate.)\n+  USB, HDD, MMCE and the rest: USB, hard disk, MMCE e il resto\n+  every started drive: ogni unit\u00e0 avviata\n+  found where you keep them: trovati dove li tieni\n+  not found yet: non ancora trovato\n+  not on a started drive: non su un'unit\u00e0 avviata\n+  only if you have none: solo se non ne hai\n+  starts every drive first: avvia prima tutte le unit\u00e0\n+  Drives, launchers and homebrew: Unit\u00e0, launcher e homebrew\n+  BDM Devices: Dispositivi BDM\n+  Color Sweep: Sfumatura di colori\n+  Colors and Text: Colori e testo\n+  HDD POPS partitions: Partizioni POPS dell'hard disk\n+  Layout: Disposizione\n+  Missing Covers: Copertine mancanti\n+  Name: Nome\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: Nessuna configurazione SMB di POPSTARTER trovata. Compila le righe qui sotto.\n+  POPSTARTER Network: Rete POPSTARTER\n+  POPSTARTER SMB Network: Rete SMB di POPSTARTER\n+  POPSTARTER UDPFS Network: Rete UDPFS di POPSTARTER\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: RIPPS2 aggiunge da s\u00e9 l'unit\u00e0, l'ISO, le VMC e la modalit\u00e0 video.\n+  Screen: Schermo\n+  Speed: Velocit\u00e0\n+  Sweep Colors: Colori della sfumatura\n+  Text Weight: Spessore del testo\n+  Theme Color: Colore principale\n+  Bold: Grassetto\n+  Controllers: Controller\n+  Lively: Vivace\n+  Medium: Medio\n+  PS Emulation: Emulazione PS\n+  Regular: Normale\n+  Slow: Lento\n+  A MOMENT: UN ATTIMO\n+  A TASK IN THE BACKGROUND: UN'ATTIVIT\u00c0 IN BACKGROUND\n+  ABOUT %d S: CIRCA %d S\n+  ANY MOMENT: DA UN MOMENTO ALL'ALTRO\n+  READING THE GAME LIST OF %s: LETTURA DELLA LISTA GIOCHI DI %s\n+  SAVING A LIST: SALVATAGGIO DI UNA LISTA\n+  START NOW: AVVIA ORA\n+  STARTING: AVVIO\n+  STARTS NEXT: SI AVVIA SUBITO DOPO\n+  STOPPING IT SAFELY: ARRESTO IN SICUREZZA\n+  THE LAST OF THE QUEUE: LA FINE DELLA CODA\n+  WHEN IT FINISHES: QUANDO HA FINITO\n+  What each card starts; install or repair BIBLE.: Cosa avvia ogni scheda; installa o ripara BIBLE.\n+  Rainbow: Arcobaleno\n+  Custom: Personalizzato\n+  DESR DVR ISOs (experimental): ISO DESR DVR (sperimentale)\n+  PC CARD CF (experimental): PC CARD CF (sperimentale)\n+  HDD Start Mode: Avvio dell'hard disk\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): Roboto Condensed di The Roboto Project Authors (SIL OFL 1.1)\n+  '%s on %s': '%s su %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: %s \u00e8 diventato %s (%d%%), ogni pezzo controllato. Rimuovere l''ISO e tenere lo ZSO?'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: %s \u00e8 tornato %s, ogni pezzo controllato. Rimuovere lo ZSO e tenere l''ISO?'\n+  A ZSO of this game is already beside it. Remove one of the two first.: Accanto c'\u00e8 gi\u00e0 uno ZSO di questo gioco. Rimuovi prima uno dei due.\n+  About %d min, then checked: Circa %d min, poi controllato\n+  An ISO of this game is already beside it. Remove one of the two first.: Accanto c'\u00e8 gi\u00e0 un'ISO di questo gioco. Rimuovi prima uno dei due.\n+  CHECKING EVERY PIECE: CONTROLLO DI OGNI PEZZO\n+  COMPRESS TO ZSO: COMPRIMI IN ZSO\n+  'COMPRESSED %s: %s FREED': '%s COMPRESSO: %s LIBERATI'\n+  COMPRESSING 1 OF 2: COMPRESSIONE 1 DI 2\n+  COMPRESSING 2 OF 2: COMPRESSIONE 2 DI 2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : Comprimere %s? L'ISO resta finch\u00e9 lo ZSO non \u00e8 stato riletto e controllato, poi RIPPS2 chiede prima di rimuovere l'ISO. L'unit\u00e0 ha bisogno di spazio per entrambi durante il lavoro; lascia la console accesa finch\u00e9 non finisce. Uno ZSO si avvia solo con il core OPL (non Neutrino); i suoi salvataggi, impostazioni, immagini e trucchi restano con lui, e pu\u00f2 tornare ISO in qualsiasi momento.\n+  Compress to ZSO: Comprimi in ZSO\n+  Cycle: A rotazione\n+  'Experimental: OPL core only (not Neutrino)': 'Sperimentale: solo core OPL (non Neutrino)'\n+  Hold SELECT: Tieni premuto SELECT\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: Non \u00e8 stato possibile finire (%s). Ci\u00f2 che aveva scritto \u00e8 stato rimosso; il gioco \u00e8 com'era.\n+  List: Elenco\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : Rendere di nuovo %s un'ISO? Lo ZSO resta finch\u00e9 l'ISO non \u00e8 stata riletta e controllata, poi RIPPS2 chiede prima di rimuovere lo ZSO. L'unit\u00e0 ha bisogno di spazio per entrambi durante il lavoro; lascia la console accesa finch\u00e9 non finisce. I suoi salvataggi, impostazioni, immagini e trucchi restano con lui.\n+  Stop? What it has written so far is removed; the game stays as it was.: Fermare? Ci\u00f2 che \u00e8 stato scritto finora viene rimosso; il gioco resta com'era.\n+  THEMES: TEMI\n+  There is not enough memory for this just now.: Al momento non c'\u00e8 abbastanza memoria per questo.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: RIPPS2 non pu\u00f2 comprimere n\u00e9 decomprimere questa immagine (la sua dimensione non \u00e8 fatta di blocchi interi da 2 KB).\n+  UNCOMPRESS TO ISO: DECOMPRIMI IN ISO\n+  UNCOMPRESSED %s: '%s DECOMPRESSO'\n+  UNCOMPRESSING: DECOMPRESSIONE\n+  Uncompress to ISO: Decomprimi in ISO\n+  Under a minute, then checked: Meno di un minuto, poi controllato\n+  COPYING: COPIA\n+  SHOW AGAIN, DELETE OR COMPRESS: MOSTRA DI NUOVO, ELIMINA O COMPRIMI\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: MOSTRA DI NUOVO, ELIMINA O DECOMPRIMI\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': '%s ha bisogno di circa %s liberi sulla sua unit\u00e0 mentre lavora (l''originale resta fino alla fine), e l''unit\u00e0 ha %s liberi.'\n+  Hide and Compress: Nascondi e comprimi\n+  'COMPRESSED AND HIDDEN: %s': 'COMPRESSO E NASCOSTO: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': 'Non c''\u00e8 spazio per questa copia: servono circa %s, e l''unit\u00e0 ha %s liberi.'\n+  OVER 4 GB FOR FAT32: OLTRE 4 GB PER FAT32\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: Questa unit\u00e0 \u00e8 %s, che non tiene file da 4 GB o pi\u00f9, e la nuova immagine sarebbe di %s.\n+  '%s, %s clusters, %s free of %s': '%s, cluster da %s, %s liberi su %s'\n+  Delete This Sweep: Elimina questa sfumatura\n+  Delete %s? RIPPS2's own sweeps stay as they are.: Eliminare %s? Le sfumature di RIPPS2 restano come sono.\n+  Sweep: Sfumatura\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: I badge segnano i giochi con obiettivi nei tuoi elenchi, con quanti ne hai sbloccati. Serve la TELEMETRIA attiva.\n labels:\n   'ON': Attivo\n   'OFF': Disattivo\n@@ -1205,7 +1371,7 @@ labels:\n   SAVE_ICONS: Icone di salvataggio 3D\n   HINT_SAVE_ICONS: Mostra l'icona di salvataggio del gioco scelto, che ruota, dove il tema prevede un posto per essa. Legge prima la VMC del gioco, poi le memory card.\n   NETBOOT_RESTART_NOW: Il protocollo di rete cambia dopo un riavvio. Salvare le impostazioni e riavviare OPL ora?\n-  HINT_NET_PROTOCOL: 'Quale servizio di rete usa RiptOPL: una condivisione SMB, UDPFS, UDPBD o HTTP. La stessa impostazione di Sorgenti dei giochi. Lasciarne uno gi\u00e0 in esecuzione richiede un riavvio.'\n+  HINT_NET_PROTOCOL: 'Quale servizio di rete usa RiptOPL: una condivisione SMB, UDPFS, UDPBD o HTTP. Lasciarne uno gi\u00e0 in esecuzione richiede un riavvio.'\n   HINT_UDPFS_ACCESS: Solo UDPFS. File legge ISO sparse dalla cartella dei giochi del server; IMG legge un'immagine disco come un'unit\u00e0. SMB e HTTP usano sempre File, UDPBD sempre IMG.\n   HINT_SMB_VERSION: Quale versione di SMB usare con la condivisione del PC.\n   HINT_NET_ADVANCED: Sblocca la modifica della modalit\u00e0 di collegamento Ethernet e della porta SMB.\n@@ -1404,3 +1570,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: Come gli scatti in attesa sul rullino di ogni gioco diventano PNG.\n   RIPPS2_SETTINGS_HOME: Salva impostazioni su\n   HINT_RIPPS2_SETTINGS_HOME: 'Dove RIPPS2 tiene le impostazioni: il suo salvataggio su una memory card, una cartella RIPPS2 su un''unit\u00e0 (l''hard disk interno, USB, MX4SIO, iLink o MMCE) o una cartella che scrivi tu.'\n+  RIPPS2_LAUNCHERS: Launcher\n+  RIPPS2_LOOK_NOW: Cerca ora\n+  HINT_RIPPS2_LOOK_NOW: RIPPS2 cerca sulle unit\u00e0 avviate POPSTARTER, i file POPS, Ember, il BIOS di Ember e Neutrino, e ricorda dove si trova ognuno. Ogni avvio li legge da l\u00ec.\n+  RIPPS2_HIDE_GAME: Nascondi gioco\n+  RIPPS2_HIDDEN_GAMES: Giochi nascosti\n+  HINT_RIPPS2_HIDDEN_GAMES: I giochi nascosti con Nascondi gioco, sulle unit\u00e0 avviate. Mostrane uno di nuovo, o eliminalo. Ogni unit\u00e0 tiene il proprio elenco.\n+  HINT_RIPPS2_BDM_DEVICES: 'Le unit\u00e0 da cui BDM legge i giochi: USB, MX4SIO, una PC Card CF, l''unit\u00e0 DVR di un DESR e iLink. L''hard disk interno segue l''avvio dell''hard disk.'\n+  HINT_RIPPS2_SCREEN: 'L''immagine del menu: modalit\u00e0 video, schermo largo, la sua posizione sul televisore (H-POS, V-POS) e overscan.'\n+  HINT_RIPPS2_LAYOUT: Dove stanno le categorie e come entra una pagina, poi le copertine di RIPFLOW e la griglia di RIPGRID.\n+  HINT_RIPPS2_COLORS_TEXT: Spessore del testo, il colore del tema, la sfumatura di colori e ogni colore.\n+  HINT_RIPPS2_TEXT_WEIGHT: 'Quanto \u00e8 spesso il testo di RIPPS2: i suggerimenti delle impostazioni, STATO e Informazioni. Normale \u00e8 come prima; Medio e Grassetto si leggono meglio su un''immagine morbida.'\n+  HINT_RIPPS2_HDD_START: 'L''hard disk interno, APA o exFAT: RIPPS2 lo legge qualunque sia e lo mostra una volta, come giochi dell''hard disk. Disattivo lo lascia stare.'\n+  HINT_RIPPS2_THEME_COLOR: Il colore proprio del tema. Scegli Personalizzato come colore del tema per sceglierlo tu, come i colori del testo.\n+  HINT_RIPPS2_SWEEP: Cinque colori che il tema attraversa, piano e in modo fluido. Arcobaleno, una sfumatura da ogni colore del tema o la tua. Disattivo tiene fermo il tema.\n+  HINT_RIPPS2_SWEEP_COLORS: I cinque colori della sfumatura e la sua velocit\u00e0. Cambiane una di RIPPS2 e viene tenuta come tua, con il nome che le dai.\n+  HINT_RIPPS2_SWEEP_NAME: Il nome della tua sfumatura nella lista Sfumatura di colori.\n+  HINT_RIPPS2_SWEEP_COLOR: Uno dei cinque colori che la sfumatura attraversa, in ordine.\n+  HINT_RIPPS2_SWEEP_SPEED: 'Quanto dura ogni colore prima che entri il successivo. Anche Vivace resta fluido: i colori non lampeggiano mai.'\n+  RIPPS2_HIDE_COMPRESS: Nascondi gioco e compressione\n+  HINT_RIPPS2_SELECT_HOLD: 'SELECT tenuto 4.2 s su un elenco di giochi. Elenco: i temi in un pannello, scegline uno. A rotazione: il tema successivo. Disattivo: niente (una pressione aggiorna comunque).'"
        },
        {
          "sha": "4289b50075d7946a25b811b25edffdedf79040be",
          "filename": "elf/lang/Japanese.yml",
          "status": "modified",
          "additions": 209,
          "deletions": 22,
          "changes": 231,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FJapanese.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FJapanese.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FJapanese.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: Japanese\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTER \u3067\u78ba\u5b9a\u3001ESC \u3067\u53d6\u6d88\u3002\u3044\u305a\u308c\u304b\u306e\u30dc\u30bf\u30f3\u3067\u30ad\u30fc\u3092\u8868\u793a\u3002\n   USB KEYBOARD: USB \u30ad\u30fc\u30dc\u30fc\u30c9\n-  '      DESR DVR ISOs (experimental)': '      DESR DVR ISO\uff08\u5b9f\u9a13\u7684\uff09'\n-  '      Internal HDD (exFAT)': '      \u5185\u8535 HDD\uff08exFAT\uff09'\n-  '      PC CARD CF (experimental)': '      PC CARD CF\uff08\u5b9f\u9a13\u7684\uff09'\n-  '      Protocol': '      \u30d7\u30ed\u30c8\u30b3\u30eb'\n   Access: \u30a2\u30af\u30bb\u30b9\u65b9\u5f0f\n   Applications Page Start Mode: \u30a2\u30d7\u30ea\u30da\u30fc\u30b8\u306e\u8d77\u52d5\u30e2\u30fc\u30c9\n   Art Delay: \u30a2\u30fc\u30c8\u306e\u9045\u5ef6\n@@ -22,7 +18,6 @@ strings:\n   Fifth: 5 \u756a\u76ee\n   First: 1 \u756a\u76ee\n   Fourth: 4 \u756a\u76ee\n-  HDD (APA) Start Mode: HDD\uff08APA\uff09\u306e\u8d77\u52d5\u30e2\u30fc\u30c9\n   HDD Cache: HDD \u30ad\u30e3\u30c3\u30b7\u30e5\n   HDD Spindown: HDD \u30b9\u30d4\u30f3\u30c0\u30a6\u30f3\n   MMCE Settings: MMCE \u8a2d\u5b9a\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: \u3053\u308c\u306f ESR \u30d1\u30c3\u30c1\u6e08\u307f\u306e PS2 \u30b2\u30fc\u30e0\u3067\u3059\u3002\u8d77\u52d5\u3059\u308b\u306b\u306f\u3001\u30e1\u30e2\u30ea\u30fc\u30ab\u30fc\u30c9\u304b USB \u30e1\u30e2\u30ea\u306e APPS\u3001BOOT \u307e\u305f\u306f ESR \u306b ESR\uff08esr*.elf\uff09\u3092\u7f6e\u3044\u3066\u304f\u3060\u3055\u3044\u3002\n   '%s, BUILD %d': '%s\u3001\u30d3\u30eb\u30c9 %d'\n   '... and the anonymous ...': \u2026\u2026\u305d\u3057\u3066\u540d\u3082\u306a\u304d\u7686\u3055\u3093\u2026\u2026\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': 1.8.0 \u3092\u7121\u6539\u5909\u3067\u540c\u68b1\uff08AFL-3.0\uff09\uff1agithub.com/rickgaiser/neutrino\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: Open PS2 Loader \u306e\u30d5\u30a9\u30fc\u30af RiptOPL \u3092\u5206\u89e3\u3057\u3066\u4f5c\u308a\u76f4\u3057\u305f\u3082\u306e\u3002\n   ABOUT: \u60c5\u5831\n   'ACHIEVEMENTS: SPIRIT OF RA': \u5b9f\u7e3e\uff1aSPIRIT OF RA\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): \u30e1\u30e2\u30ea\u30fc\u30d5\u30a1\u30a4\u30eb\u306e AVI \u52d5\u753b\u3001RIPPS2 \u306e\u30d5\u30a9\u30fc\u30af\uff08AFL 2.0\uff09\n   Apollo Save Tool by bucanero: bucanero \u306b\u3088\u308b Apollo Save Tool\n   BACK: \u623b\u308b\n+  Back (keeps both): \u623b\u308b\uff08\u4e21\u65b9\u6b8b\u3059\uff09\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: \u3053\u306eMMCE\u30ab\u30fc\u30c9\u306f\u30d5\u30a1\u30a4\u30eb\u540d\u3092\u5909\u66f4\u3067\u304d\u307e\u305b\u3093\u3002\u5727\u7e2e\u306e\u6700\u5f8c\u306e\u624b\u9806\u306b\u5fc5\u8981\u3067\u3059\u3002sd2psXtd\u30d5\u30a1\u30fc\u30e0\u30a6\u30a7\u30a2\u30921.4.0\u4ee5\u964d\u306b\u66f4\u65b0\u3057\u3066\u304b\u3089\uff08sd2psxtd.github.io/download\uff09\u3001\u3082\u3046\u4e00\u5ea6\u304a\u8a66\u3057\u304f\u3060\u3055\u3044\u3002\u4f55\u3082\u5909\u66f4\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): BIBLE \u306f PS2BBL 1.2.0\u3001El Isra \u306e\u7f72\u540d\u6e08\u307f\u30d3\u30eb\u30c9\uff08\u30e1\u30e2\u30ea\u30fc\u30ab\u30fc\u30c9\u3068 HDD\uff09\u3092\u7121\u6539\u5909\u3067\u4f7f\u7528\uff08GPL-3\uff09\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: BIOS \u30b5\u30a6\u30f3\u30c9 > \u62bd\u51fa \u306f SPU2 \u3068\u540c\u3058\u3088\u3046\u306b\u518d\u751f\u3057\u307e\u3059\u3002BIOS \u306f\u540c\u68b1\u3057\u307e\u305b\u3093\n   BOOT LOCK AND BIBLE: BOOT LOCK \u3068 BIBLE\n   BOOT LOCK edits its config too: BOOT LOCK \u306f\u305d\u306e\u8a2d\u5b9a\u3082\u7de8\u96c6\u3057\u307e\u3059\n   BUILD DETAILS: \u30d3\u30eb\u30c9\u306e\u8a73\u7d30\n-  BUILT ON RIPTOPL: RIPTOPL \u30d9\u30fc\u30b9\n   BearSSL by Thomas Pornin: Thomas Pornin \u306b\u3088\u308b BearSSL\n   Built with Claude (Anthropic): Claude\uff08Anthropic\uff09\u3068\u5171\u306b\u5236\u4f5c\n   CD PLAYER: CD \u30d7\u30ec\u30fc\u30e4\u30fc\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek \u3068 Sleek Bold\u3001RIPPS2 \u306e\u305f\u3081\u306b\u63cf\u3044\u305f\u3082\u306e\n   RetroAchievements on the console by hacan359 (yoba): hacan359\uff08yoba\uff09\u306b\u3088\u308b\u672c\u4f53\u4e0a\u306e RetroAchievements\n   RiptOPL by NathanNeurotic: NathanNeurotic \u306b\u3088\u308b RiptOPL\n-  Roboto Condensed by Google (Apache-2.0): Google \u306b\u3088\u308b Roboto Condensed\uff08Apache-2.0\uff09\n   SOUNDS: \u30b5\u30a6\u30f3\u30c9\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: SP193 \u306e FMCB \u30a4\u30f3\u30b9\u30c8\u30fc\u30e9\u30fc\u3068 pcm720 \u306e OSDMenu \u306b\u4e00\u6b69\u305a\u3064\u5f93\u3063\u305f\u3082\u306e\n   SUPPORT: \u30b5\u30dd\u30fc\u30c8\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: NathanNeurotic \u306b\u3088\u308b wLaunchELF R3Z\n   wLaunchELF by the ps2homebrew team: ps2homebrew \u30c1\u30fc\u30e0\u306b\u3088\u308b wLaunchELF\n   what RIPPS2's card image writes are checked against: RIPPS2 \u306e\u30ab\u30fc\u30c9\u30a4\u30e1\u30fc\u30b8\u66f8\u304d\u8fbc\u307f\u3092\u7167\u5408\u3059\u308b\u76f8\u624b\n-  with its own look, launchers, file browser and save tools.: \u72ec\u81ea\u306e\u898b\u305f\u76ee\u3001\u30e9\u30f3\u30c1\u30e3\u30fc\u3001\u30d5\u30a1\u30a4\u30eb\u30d6\u30e9\u30a6\u30b6\u30fc\u3001\u30bb\u30fc\u30d6\u30c4\u30fc\u30eb\u3092\u5099\u3048\u3066\u3044\u307e\u3059\u3002\n   xeRAbora by hacan359, rcheevos by RetroAchievements: hacan359 \u306b\u3088\u308b xeRAbora\u3001RetroAchievements \u306b\u3088\u308b rcheevos\n   your console's own, ripped from its BIOS: \u672c\u4f53\u81ea\u8eab\u306e\u30b5\u30a6\u30f3\u30c9\u3001BIOS \u304b\u3089\u62bd\u51fa\n   ' (no DNS answer)': ' \uff08DNS \u306e\u5fdc\u7b54\u306a\u3057\uff09'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': NEUTRINO\uff1a%.60s \u3092\u66f8\u304d\u8fbc\u307f\u4e2d\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: Neutrino \u3092 %s \u306b\u66f8\u304d\u8fbc\u3081\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u7a7a\u304d\u5bb9\u91cf\u3068\u66f8\u304d\u8fbc\u307f\u7981\u6b62\u306b\u306a\u3063\u3066\u3044\u306a\u3044\u304b\u3092\u78ba\u8a8d\u3057\u3066\u3001\u3082\u3046\u4e00\u5ea6\u304a\u8a66\u3057\u304f\u3060\u3055\u3044\u3002\n   THE GAME'S DRIVE  (%s/neutrino): \u30b2\u30fc\u30e0\u306e\u30c9\u30e9\u30a4\u30d6  \uff08%s/neutrino\uff09\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : \u3053\u306e\u30b2\u30fc\u30e0\u306f Neutrino \u3067\u8d77\u52d5\u3057\u307e\u3059\u304c\u3001RIPPS2 \u304c\u63a2\u3059\u5834\u6240\uff08\u8a2d\u5b9a\u3001\u30b2\u30fc\u30e0\u306e\u30c9\u30e9\u30a4\u30d6\u3001\u30e1\u30e2\u30ea\u30fc\u30ab\u30fc\u30c9\uff09\u306b Neutrino \u304c\u3042\u308a\u307e\u305b\u3093\u3002RIPPS2 \u306f rickgaiser \u306e Neutrino 1.8.0\uff08AFL-3.0\uff09\u3092\u540c\u68b1\u3057\u3066\u3044\u307e\u3059\u3002\u4eca\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3057\u307e\u3059\u304b\uff1f\u6b21\u306b\u5834\u6240\u3092\u9078\u3073\u307e\u3059\u3002\n   '%d / %d FILES': '%d / %d \u30d5\u30a1\u30a4\u30eb'\n   '%d FOLDERS AND CONTENTS': '%d \u30d5\u30a9\u30eb\u30c0\u3068\u4e2d\u8eab'\n   '%d FOLDERS, %d FOUND': '%d \u30d5\u30a9\u30eb\u30c0\u30fc\u3001%d \u4ef6\u898b\u3064\u304b\u308a\u307e\u3057\u305f'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d / %d \u304c\u307e\u3060\u30ed\u30c3\u30af\u4e2d'\n   '%s   on %s': '%s   %s \u3067'\n   BADGES: \u30d0\u30c3\u30b8\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: \u30d0\u30c3\u30b8\u306f\u30c6\u30ec\u30e1\u30c8\u30ea\u30fc\u304c\u30aa\u30f3\u3067\u3001PC \u3067 xeRAbora \u304c\u52d5\u3044\u3066\u3044\u308b\u3068\u304d\u306b\u3060\u3051\u50cd\u304d\u307e\u3059\u3002\u305d\u308c\u307e\u3067\u306f\u4f55\u306e\u8ca0\u62c5\u3082\u3042\u308a\u307e\u305b\u3093\u3002\n   CHECK THE DISC: \u30c7\u30a3\u30b9\u30af\u3092\u78ba\u8a8d\n   From this game's last CHECK GAME SUPPORT.: \u3053\u306e\u30b2\u30fc\u30e0\u306e\u6700\u5f8c\u306e\u300c\u30b2\u30fc\u30e0\u306e\u5bfe\u5fdc\u3092\u78ba\u8a8d\u300d\u304b\u3089\u3002\n   HOW IT WORKS: \u3057\u304f\u307f\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: \u3053\u306e\u672c\u4f53\u306b\u306f RIPPS2 \u306e\u30ed\u30c3\u30af\u4ed8\u304d\u306e PS2BBL \u304c\u3042\u308a\u307e\u3059\u3002\n   AUDIO folder: AUDIO \u30d5\u30a9\u30eb\u30c0\n   DRIVES: \u30c9\u30e9\u30a4\u30d6\n-  INSTALL RIPPS2'S COPY: RIPPS2 \u306e\u30b3\u30d4\u30fc\u3092\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\n-  'Installed: ': '\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u6e08\u307f\uff1a '\n   MANAGE: \u7ba1\u7406\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: \u300c\u7ba1\u7406\u300d\u3067\u306f\u5404\u30e1\u30e2\u30ea\u30fc\u30ab\u30fc\u30c9\u304c\u4f55\u3092\u8d77\u52d5\u3059\u308b\u304b\u3092\u8868\u793a\u3057\u3001BIBLE \u3092\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u307e\u305f\u306f\u4fee\u5fa9\u3057\u307e\u3059\u3002\n   MEMORY CARD 0  (about 600 KB): \u30e1\u30e2\u30ea\u30fc\u30ab\u30fc\u30c9 0  \uff08\u7d04 600 KB\uff09\n   MEMORY CARD 1  (about 600 KB): \u30e1\u30e2\u30ea\u30fc\u30ab\u30fc\u30c9 1  \uff08\u7d04 600 KB\uff09\n   'MX4SIO: its driver would not load (code %d).': MX4SIO\uff1a\u30c9\u30e9\u30a4\u30d0\u30fc\u3092\u8aad\u307f\u8fbc\u3081\u307e\u305b\u3093\u3067\u3057\u305f\uff08\u30b3\u30fc\u30c9 %d\uff09\u3002\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': MX4SIO\uff1a\u30c9\u30e9\u30a4\u30d0\u30fc\u306f\u5165\u3063\u3066\u3044\u307e\u3059\u304c\u3001SD \u30ab\u30fc\u30c9\u304c\u5fdc\u7b54\u3057\u307e\u305b\u3093\uff08mc1 \u30b9\u30ed\u30c3\u30c8\uff09\u3002\n   NEUTRINO SETTINGS: NEUTRINO \u8a2d\u5b9a\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': \u307e\u3060\u3042\u308a\u307e\u305b\u3093\uff1aRIPPS2 \u306b\u306f\u72ec\u81ea\u306e\u30b5\u30a6\u30f3\u30c9\u304c\u3042\u308a\u307e\u305b\u3093\u3002\u3053\u306e\u672c\u4f53\u306e\u3082\u306e\u3092\u62bd\u51fa\u3059\u308b\u304b\u3001RIPPS2 Audio Studio \u3067\u4f5c\u3063\u3066\u304f\u3060\u3055\u3044\u3002\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: \u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u5fc5\u8981\u306a\u30b2\u30fc\u30e0\u304c RIPPS2 \u306e\u30b3\u30d4\u30fc\u3092\u63d0\u6848\u3057\u307e\u3059\u3002\u3053\u3053\u3067\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3059\u308b\u3053\u3068\u3082\u3067\u304d\u307e\u3059\u3002\n   POPSTARTER NETWORK: POPSTARTER \u30cd\u30c3\u30c8\u30ef\u30fc\u30af\n   POPSTARTER and Ember: POPSTARTER \u3068 Ember\n   PS1 SETTINGS: PS1 \u8a2d\u5b9a\n   PS2BBL 1.2.0 for RIPPS2: RIPPS2 \u7528\u306e PS2BBL 1.2.0\n   RIP THIS CONSOLE'S SOUNDS: \u3053\u306e\u672c\u4f53\u306e\u30b5\u30a6\u30f3\u30c9\u3092\u62bd\u51fa\n-  RIPPS2's homebrew: RIPPS2 \u306e\u81ea\u4f5c\u30bd\u30d5\u30c8\n   STATUS: \u30b9\u30c6\u30fc\u30bf\u30b9\n-  'STATUS: looking for RIPPS2''s homebrew': \u30b9\u30c6\u30fc\u30bf\u30b9\uff1aRIPPS2 \u306e\u81ea\u4f5c\u30bd\u30d5\u30c8\u3092\u63a2\u3057\u3066\u3044\u307e\u3059\n   a drive of unknown kind: \u7a2e\u985e\u4e0d\u660e\u306e\u30c9\u30e9\u30a4\u30d6\n-  a drive or a memory card: \u30c9\u30e9\u30a4\u30d6\u307e\u305f\u306f\u30e1\u30e2\u30ea\u30fc\u30ab\u30fc\u30c9\n   boot loader, card by card: \u30d6\u30fc\u30c8\u30ed\u30fc\u30c0\u30fc\u3001\u30ab\u30fc\u30c9\u3054\u3068\n   from its BIOS, into AUDIO: BIOS \u304b\u3089 AUDIO \u3078\n   internal HDD (exFAT): \u5185\u8535 HDD\uff08exFAT\uff09\n@@ -975,10 +958,194 @@ strings:\n   Sidebar: \u30b5\u30a4\u30c9\u30d0\u30fc\n   your own copy, found on your drives and memory cards: \u3042\u306a\u305f\u81ea\u8eab\u306e\u30b3\u30d4\u30fc\uff08\u30c9\u30e9\u30a4\u30d6\u3068\u30e1\u30e2\u30ea\u30fc\u30ab\u30fc\u30c9\u304b\u3089\u898b\u3064\u3051\u307e\u3059\uff09\n   'your own, found and launched: github.com/Gageformer/Ember/releases': \u3042\u306a\u305f\u81ea\u8eab\u306e\u3082\u306e\u3092\u898b\u3064\u3051\u3066\u8d77\u52d5\uff1agithub.com/Gageformer/Ember/releases\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': mass0:\uff08%s\uff09\u306b\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002ember.elf \u3068\u304a\u6301\u3061\u306e bios.bin \u3092\u305d\u306e EMBER \u30d5\u30a9\u30eb\u30c0\u30fc\u306b\u5165\u308c\u3066\u304f\u3060\u3055\u3044\u3002\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: \u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002\u304a\u6301\u3061\u306e POPSTARTER.ELF \u3092\u30c9\u30e9\u30a4\u30d6\u306e POPS \u30d5\u30a9\u30eb\u30c0\u30fc\u306b\u5165\u308c\u3066\u304f\u3060\u3055\u3044\u3002\n   Launch Disc: \u30c7\u30a3\u30b9\u30af\u8d77\u52d5\n   Memory Files: \u30e1\u30e2\u30ea\u30fc\u30d5\u30a1\u30a4\u30eb\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: RIPPS2 \u306f 5 \u3064\u306e\u3046\u3061 %d \u3064\u3092\u898b\u3064\u3051\u307e\u3057\u305f\u3002\u305d\u308c\u305e\u308c\u3092\u8a18\u61b6\u3057\u3001\u8d77\u52d5\u306e\u305f\u3073\u306b\u305d\u3053\u304b\u3089\u8aad\u307f\u8fbc\u307f\u307e\u3059\u3002\n+  not found on the started drives: \u958b\u59cb\u6e08\u307f\u306e\u30c9\u30e9\u30a4\u30d6\u306b\u898b\u3064\u304b\u308a\u307e\u305b\u3093\n+  not looked for yet: \u307e\u3060\u63a2\u3057\u3066\u3044\u307e\u305b\u3093\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s \u304c\u5fdc\u7b54\u3057\u306a\u3044\u305f\u3081\u3001%s \u3092\u958b\u59cb\u3067\u304d\u307e\u305b\u3093\u3002\u63a5\u7d9a\u3055\u308c\u3066\u3044\u307e\u3059\u304b\uff1f'\n+  '%s is not on %s any more, so it has left All Games.': '%s \u306f %s \u306b\u3082\u3046\u306a\u3044\u305f\u3081\u3001\u3059\u3079\u3066\u306e\u30b2\u30fc\u30e0\u304b\u3089\u5916\u308c\u307e\u3057\u305f\u3002'\n+  STARTING %s: '%s \u3092\u958b\u59cb\u4e2d'\n+  the SMB share: SMB \u5171\u6709\n+  the drive: \u30c9\u30e9\u30a4\u30d6\n+  the internal HDD (APA): \u5185\u8535 HDD\uff08APA\uff09\n+  the internal HDD (exFAT): \u5185\u8535 HDD\uff08exFAT\uff09\n+  BUILT ON RIPTOPL, WITH NEUTRINO: RIPTOPL \u3092\u571f\u53f0\u306b\u3001NEUTRINO \u3068\u3068\u3082\u306b\n+  'Special thanks to all the YouTube love as well:': YouTube \u3067\u306e\u305f\u304f\u3055\u3093\u306e\u5fdc\u63f4\u306b\u3082\u7279\u5225\u306a\u611f\u8b1d\u3092\uff1a\n+  and its own look, launchers, file browser and save tools.: \u305d\u3057\u3066\u72ec\u81ea\u306e\u898b\u305f\u76ee\u3001\u30e9\u30f3\u30c1\u30e3\u30fc\u3001\u30d5\u30a1\u30a4\u30eb\u30d6\u30e9\u30a6\u30b6\u30fc\u3001\u30bb\u30fc\u30d6\u30c4\u30fc\u30eb\u3092\u5099\u3048\u3066\u3044\u307e\u3059\u3002\n+  glad to help however you reach me.: \u3069\u3053\u304b\u3089\u9023\u7d61\u3092\u304f\u308c\u3066\u3082\u3001\u559c\u3093\u3067\u624b\u4f1d\u3044\u307e\u3059\u3002\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): 2 \u3064\u76ee\u306e\u30b2\u30fc\u30e0\u30b3\u30a2\u30011.8.0 \u3092\u6539\u5909\u305b\u305a\u306b\u540c\u68b1\uff08AFL-3.0\uff09\n+  with rickgaiser's Neutrino inside as its second game core,: rickgaiser \u306e Neutrino \u3092 2 \u3064\u76ee\u306e\u30b2\u30fc\u30e0\u30b3\u30a2\u3068\u3057\u3066\u5185\u8535\u3057\u3001\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : \u3053\u306e\u30b2\u30fc\u30e0\u306f Neutrino \u3067\u8d77\u52d5\u3057\u307e\u3059\u304c\u3001RIPPS2 \u306f\u898b\u3064\u3051\u3089\u308c\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u8a2d\u5b9a\u306e\u30d1\u30b9\u306b\u3082\u3001\u30b2\u30fc\u30e0\u306e\u30c9\u30e9\u30a4\u30d6\u306b\u3082\u3001\u30e1\u30e2\u30ea\u30fc\u30ab\u30fc\u30c9\u306b\u3082\u3001\u958b\u59cb\u6e08\u307f\u306e\u30c9\u30e9\u30a4\u30d6\u306b\u3082\u3042\u308a\u307e\u305b\u3093\u3002RIPPS2 \u306b\u306f rickgaiser \u306e Neutrino 1.8.0\uff08AFL-3.0\uff09\u304c\u5165\u3063\u3066\u3044\u307e\u3059\u3002\u597d\u304d\u306a\u5834\u6240\u306b\u4e00\u5ea6\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3059\u308c\u3070\u3001Neutrino \u306e\u30b2\u30fc\u30e0\u306f\u3059\u3079\u3066\u305d\u3053\u304b\u3089\u4f7f\u3044\u307e\u3059\u3002\n+  STARTING THE DRIVES: \u30c9\u30e9\u30a4\u30d6\u3092\u958b\u59cb\u4e2d\n+  '%d games, on the started drives': '%d \u672c\u306e\u30b2\u30fc\u30e0\uff08\u958b\u59cb\u6e08\u307f\u306e\u30c9\u30e9\u30a4\u30d6\uff09'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s\uff1a\u4e00\u90e8\u306e\u30d5\u30a1\u30a4\u30eb\u3092\u524a\u9664\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f'\n+  '%u partitions, all in order': '%u \u500b\u306e\u30d1\u30fc\u30c6\u30a3\u30b7\u30e7\u30f3\u3001\u3059\u3079\u3066\u6b63\u5e38'\n+  ', %d art images': \u3001\u30a2\u30fc\u30c8\u753b\u50cf %d \u679a\n+  ', and ': \u3001\n+  ', its cheats': \u3001\u30c1\u30fc\u30c8\n+  ', its settings': \u3001\u8a2d\u5b9a\n+  1 game, on the started drives: 1 \u672c\u306e\u30b2\u30fc\u30e0\uff08\u958b\u59cb\u6e08\u307f\u306e\u30c9\u30e9\u30a4\u30d6\uff09\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: \u3053\u3053\u306e PS1 \u30b2\u30fc\u30e0\u306f\u3001\u72ec\u7acb\u3057\u305f\u30d5\u30a1\u30a4\u30eb\u306b\u306a\u3063\u3066\u3044\u308b\u30e1\u30e2\u30ea\u30fc\u30d5\u30a1\u30a4\u30eb\u3067\u524a\u9664\u3057\u307e\u3059\u3002\n+  A network catalogue's games are deleted on the PC that serves them.: \u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u30ab\u30bf\u30ed\u30b0\u306e\u30b2\u30fc\u30e0\u306f\u3001\u305d\u308c\u3092\u914d\u4fe1\u3057\u3066\u3044\u308b PC \u3067\u524a\u9664\u3057\u307e\u3059\u3002\n+  DELETE FOR GOOD: \u5b8c\u5168\u306b\u524a\u9664\n+  DELETE IT: \u524a\u9664\u3059\u308b\n+  'DELETED %s: %s FREED': '%s \u3092\u524a\u9664\uff1a%s \u89e3\u653e'\n+  'Delete %s from %s? Goes: %s.': '%s \u3092 %s \u304b\u3089\u524a\u9664\u3057\u307e\u3059\u304b\uff1f\u6d88\u3048\u308b\u3082\u306e\uff1a%s\u3002'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': '%s \u3092 %s \u304b\u3089\u524a\u9664\u3057\u307e\u3059\u304b\uff1f\u6d88\u3048\u308b\u3082\u306e\uff1a%s\u3002\u6b8b\u308b\u3082\u306e\uff1a%s\u3002'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: \u30bb\u30fc\u30d6\u3068 RIPSHOT \u306e\u5199\u771f\u3082\u524a\u9664\u3057\u307e\u3059\u304b\uff1f\u3044\u3044\u3048\u3067\u6b8b\u3057\u307e\u3059\u3002\n+  HIDDEN GAMES: \u975e\u8868\u793a\u306e\u30b2\u30fc\u30e0\n+  'HIDDEN: %s': \u975e\u8868\u793a\uff1a%s\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: '%s \u3092\u975e\u8868\u793a\u306b\u3057\u307e\u3059\u304b\uff1f\u3059\u3079\u3066\u306e\u30ea\u30b9\u30c8\u304b\u3089\u3001\u3059\u3079\u3066\u306e\u30b2\u30fc\u30e0\u3068\u304a\u6c17\u306b\u5165\u308a\u304b\u3089\u3082\u5916\u308c\u307e\u3059\u3002\u8a2d\u5b9a > \u30b2\u30fc\u30e0\u306e\u8aad\u307f\u8fbc\u307f\u5143 > \u975e\u8868\u793a\u306e\u30b2\u30fc\u30e0 \u3067\u518d\u8868\u793a\u3067\u304d\u307e\u3059\u3002'\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: \u6700\u7d42\u78ba\u8a8d\u3002%s \u306f\u5b8c\u5168\u306b\u524a\u9664\u3055\u308c\u3001\u5143\u306b\u623b\u305b\u307e\u305b\u3093\u3002\u524a\u9664\u3059\u308b\u306b\u306f START \u3092\u62bc\u3057\u3066\u304f\u3060\u3055\u3044\u3002\n+  None. Hide Game, in a game's options, puts one here.: \u3042\u308a\u307e\u305b\u3093\u3002\u30b2\u30fc\u30e0\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u306e\u300c\u30b2\u30fc\u30e0\u3092\u975e\u8868\u793a\u300d\u3067\u3053\u3053\u306b\u5165\u308a\u307e\u3059\u3002\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : \u4f55\u3082\u524a\u9664\u3057\u3066\u3044\u307e\u305b\u3093\uff1a\u3053\u306e\u30c9\u30e9\u30a4\u30d6\u306e\u30d1\u30fc\u30c6\u30a3\u30b7\u30e7\u30f3\u30c6\u30fc\u30d6\u30eb\u304c\u3053\u306e\u30c7\u30a3\u30b9\u30af\u306b\u5408\u3044\u307e\u305b\u3093\uff08%s\uff09\u3002\u5225\u306e\u5bb9\u91cf\u306e\u30c7\u30a3\u30b9\u30af\u306e\u30a4\u30e1\u30fc\u30b8\u304b\u3089\u66f8\u304d\u8fbc\u307e\u308c\u305f\u53ef\u80fd\u6027\u304c\u3042\u308a\u307e\u3059\u3002\u5148\u306b PC \u3067\u4fee\u5fa9\u3057\u3066\u304f\u3060\u3055\u3044\u3002\n+  RIPPS2 cannot delete a game from this page.: RIPPS2 \u306f\u3053\u306e\u30da\u30fc\u30b8\u304b\u3089\u30b2\u30fc\u30e0\u3092\u524a\u9664\u3067\u304d\u307e\u305b\u3093\u3002\n+  RIPPS2 could not find this game's files on its drive.: RIPPS2 \u306f\u3053\u306e\u30b2\u30fc\u30e0\u306e\u30d5\u30a1\u30a4\u30eb\u3092\u305d\u306e\u30c9\u30e9\u30a4\u30d6\u3067\u898b\u3064\u3051\u3089\u308c\u307e\u305b\u3093\u3067\u3057\u305f\u3002\n+  Reading the hidden lists: \u975e\u8868\u793a\u30ea\u30b9\u30c8\u3092\u8aad\u307f\u8fbc\u307f\u4e2d\n+  SHOW AGAIN OR DELETE: \u518d\u8868\u793a\u307e\u305f\u306f\u524a\u9664\n+  SHOW IT AGAIN: \u518d\u8868\u793a\u3059\u308b\n+  That game is not on its drive any more.: \u305d\u306e\u30b2\u30fc\u30e0\u306f\u3082\u3046\u30c9\u30e9\u30a4\u30d6\u306b\u3042\u308a\u307e\u305b\u3093\u3002\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: \u975e\u8868\u793a\u30ea\u30b9\u30c8\u3092\u30b2\u30fc\u30e0\u306e\u30c9\u30e9\u30a4\u30d6\u306b\u66f8\u304d\u8fbc\u3081\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u66f8\u304d\u8fbc\u307f\u7981\u6b62\u306b\u306a\u3063\u3066\u3044\u308b\u304b\u3001\u66f8\u304d\u8fbc\u307f\u64cd\u4f5c\u304c\u30aa\u30d5\u306b\u306a\u3063\u3066\u3044\u307e\u305b\u3093\u304b\uff1f\n+  The partition could not be removed (%d). Nothing was deleted.: \u30d1\u30fc\u30c6\u30a3\u30b7\u30e7\u30f3\u3092\u524a\u9664\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\uff08%d\uff09\u3002\u4f55\u3082\u524a\u9664\u3057\u3066\u3044\u307e\u305b\u3093\u3002\n+  This drive cannot keep a hidden list (a network catalogue).: \u3053\u306e\u30c9\u30e9\u30a4\u30d6\u306f\u975e\u8868\u793a\u30ea\u30b9\u30c8\u3092\u4fdd\u5b58\u3067\u304d\u307e\u305b\u3093\uff08\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u30ab\u30bf\u30ed\u30b0\uff09\u3002\n+  This game's drive has not answered yet. Wait for it, then try again.: \u3053\u306e\u30b2\u30fc\u30e0\u306e\u30c9\u30e9\u30a4\u30d6\u304c\u307e\u3060\u5fdc\u7b54\u3057\u3066\u3044\u307e\u305b\u3093\u3002\u5f85\u3063\u3066\u304b\u3089\u3001\u3082\u3046\u4e00\u5ea6\u304a\u8a66\u3057\u304f\u3060\u3055\u3044\u3002\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: \u3053\u306e\u30b2\u30fc\u30e0\u306e\u30d1\u30fc\u30c6\u30a3\u30b7\u30e7\u30f3\u306f RIPPS2 \u304c\u524a\u9664\u3057\u306a\u3044\u3082\u306e\uff08\u30b7\u30b9\u30c6\u30e0\u30d1\u30fc\u30c6\u30a3\u30b7\u30e7\u30f3\u3001\u307e\u305f\u306f\u8a2d\u5b9a\u306e\u3042\u308b\u30d1\u30fc\u30c6\u30a3\u30b7\u30e7\u30f3\uff09\u3067\u3059\u3002\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': \u30d1\u30fc\u30c6\u30a3\u30b7\u30e7\u30f3\u304c\u3053\u306e\u30c7\u30a3\u30b9\u30af\u306e\u8a31\u3059\u5927\u304d\u3055\u3092\u8d85\u3048\u3066\u3044\u307e\u3059\uff1a\u30c6\u30fc\u30d6\u30eb\u306f\u5225\u306e\u5bb9\u91cf\u306e\u30c7\u30a3\u30b9\u30af\u7528\u306b\u4f5c\u3089\u308c\u3066\u3044\u307e\u3059\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': \u30d1\u30fc\u30c6\u30a3\u30b7\u30e7\u30f3\u304c\u3053\u306e\u30c7\u30a3\u30b9\u30af\u306e\u7d42\u7aef\u3092\u8d8a\u3048\u3066\u3044\u307e\u3059\uff08\u30bb\u30af\u30bf\u30fc %u / %u\uff09\uff1a\u30c6\u30fc\u30d6\u30eb\u306f\u3082\u3063\u3068\u5927\u304d\u306a\u30c7\u30a3\u30b9\u30af\u7528\u306b\u4f5c\u3089\u308c\u3066\u3044\u307e\u3059\n+  its RIPSHOT pictures: RIPSHOT \u306e\u5199\u771f\n+  its drive: \u305d\u306e\u30c9\u30e9\u30a4\u30d6\n+  its saves: \u30bb\u30fc\u30d6\n+  sector %u holds no partition header: \u30bb\u30af\u30bf\u30fc %u \u306b\u30d1\u30fc\u30c6\u30a3\u30b7\u30e7\u30f3\u30d8\u30c3\u30c0\u30fc\u304c\u3042\u308a\u307e\u305b\u3093\n+  the chain does not close at __mbr: \u30c1\u30a7\u30fc\u30f3\u304c __mbr \u3067\u9589\u3058\u3066\u3044\u307e\u305b\u3093\n+  the chain skips from sector %u: \u30c1\u30a7\u30fc\u30f3\u304c\u30bb\u30af\u30bf\u30fc %u \u304b\u3089\u98db\u3093\u3067\u3044\u307e\u3059\n+  the chain's links disagree at sector %u: \u30c1\u30a7\u30fc\u30f3\u306e\u30ea\u30f3\u30af\u304c\u30bb\u30af\u30bf\u30fc %u \u3067\u98df\u3044\u9055\u3063\u3066\u3044\u307e\u3059\n+  the drive's size could not be read: \u30c9\u30e9\u30a4\u30d6\u306e\u5bb9\u91cf\u3092\u8aad\u307f\u53d6\u308c\u307e\u305b\u3093\u3067\u3057\u305f\n+  the game (%s): \u30b2\u30fc\u30e0\u672c\u4f53\uff08%s\uff09\n+  the game's folder and everything in it (%s): \u30b2\u30fc\u30e0\u306e\u30d5\u30a9\u30eb\u30c0\u30fc\u3068\u305d\u306e\u4e2d\u8eab\u3059\u3079\u3066\uff08%s\uff09\n+  the header at sector %u could not be read: \u30bb\u30af\u30bf\u30fc %u \u306e\u30d8\u30c3\u30c0\u30fc\u3092\u8aad\u307f\u53d6\u308c\u307e\u305b\u3093\u3067\u3057\u305f\n+  the header at sector %u fails its checksum: \u30bb\u30af\u30bf\u30fc %u \u306e\u30d8\u30c3\u30c0\u30fc\u304c\u30c1\u30a7\u30c3\u30af\u30b5\u30e0\u306b\u5408\u3044\u307e\u305b\u3093\n+  the partition at sector %u is not where or as large as APA puts one: \u30bb\u30af\u30bf\u30fc %u \u306e\u30d1\u30fc\u30c6\u30a3\u30b7\u30e7\u30f3\u304c\u3001APA \u304c\u7f6e\u304f\u4f4d\u7f6e\u3084\u5927\u304d\u3055\u3068\u9055\u3044\u307e\u3059\n+  the settings and art another copy of it uses: \u5225\u306e\u30b3\u30d4\u30fc\u304c\u4f7f\u3063\u3066\u3044\u308b\u8a2d\u5b9a\u3068\u30a2\u30fc\u30c8\n+  '%d FOLDERS': '%d \u500b\u306e\u30d5\u30a9\u30eb\u30c0\u30fc'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': '%s \u3092 %s \u306b\u30b3\u30d4\u30fc\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u30c9\u30e9\u30a4\u30d6\u304c\u3044\u3063\u3071\u3044\u304b\u3001\u66f8\u304d\u8fbc\u307f\u7981\u6b62\u306b\u306a\u3063\u3066\u3044\u307e\u305b\u3093\u304b\uff1f'\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s \u304c RIPPS2 \u306e\u63a2\u3059\u5834\u6240\u306b\u3042\u308a\u307e\u305b\u3093\u3002RIPPS2 \u304c\u958b\u59cb\u3057\u305f\u30c9\u30e9\u30a4\u30d6\u3067\u63a2\u3057\u307e\u3059\u304b\uff1f\u898b\u3064\u304b\u3063\u305f\u5834\u6240\u306f\u4ee5\u5f8c\u306e\u8d77\u52d5\u306e\u305f\u3073\u306b\u4f7f\u308f\u308c\u307e\u3059\u3002'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : Ember \u304c\u30b2\u30fc\u30e0\u306e\u96a3\u306e %s \u306b\u3042\u308a\u307e\u305b\u3093\u304c\u3001\u305d\u3053\u306b\u5fc5\u8981\u3067\u3059\u3002RIPPS2 \u304c %s \u3067\u898b\u3064\u3051\u307e\u3057\u305f\u3002START \u3067\u305d\u3053\u3078\u30b3\u30d4\u30fc\u3057\uff08%d KB\uff09\u3001\u30b2\u30fc\u30e0\u3092\u8d77\u52d5\u3057\u307e\u3059\u3002\n+  Ember's BIOS: Ember \u306e BIOS\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : Ember \u306e BIOS\uff08bios.bin\uff09\u304c Ember \u306e\u96a3\u306e %s \u306b\u3042\u308a\u307e\u305b\u3093\u3002RIPPS2 \u304c %s \u3067\u898b\u3064\u3051\u307e\u3057\u305f\u3002START \u3067\u305d\u3053\u3078\u30b3\u30d4\u30fc\u3057\uff08%d KB\uff09\u3001\u30b2\u30fc\u30e0\u3092\u8d77\u52d5\u3057\u307e\u3059\u3002\n+  LOOKING FOR YOUR LAUNCHERS: \u30e9\u30f3\u30c1\u30e3\u30fc\u3092\u63a2\u3057\u3066\u3044\u307e\u3059\n+  The POPS files: POPS \u30d5\u30a1\u30a4\u30eb\n+  '%s  MMCE card': '%s  MMCE \u30ab\u30fc\u30c9'\n+  INSTALL RIPPS2'S NEUTRINO: RIPPS2 \u306e NEUTRINO \u3092\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\n+  'Internal HDD (APA): %s free of %s': \u5185\u8535 HDD\uff08APA\uff09\uff1a\u7a7a\u304d %s / %s\n+  LAUNCHERS: \u30e9\u30f3\u30c1\u30e3\u30fc\n+  LOOK FOR THEM: \u63a2\u3059\n+  'Memory card %d: %d KB free': \u30e1\u30e2\u30ea\u30fc\u30ab\u30fc\u30c9 %d\uff1a\u7a7a\u304d %d KB\n+  POPS files: POPS \u30d5\u30a1\u30a4\u30eb\n+  START EVERY DRIVE: \u3059\u3079\u3066\u306e\u30c9\u30e9\u30a4\u30d6\u3092\u958b\u59cb\n+  'STATUS: looking at the drives and the homebrew': \u30b9\u30c6\u30fc\u30bf\u30b9\uff1a\u30c9\u30e9\u30a4\u30d6\u3068 homebrew \u3092\u78ba\u8a8d\u4e2d\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): \u691c\u7d22\u304c\u3059\u3079\u3066\u306b\u5c4a\u304f\u3088\u3046\u3001\u5148\u306b\u3059\u3079\u3066\u306e\u30c9\u30e9\u30a4\u30d6\u3092\u958b\u59cb\u3057\u307e\u3059\u304b\uff1f\uff08\u3044\u3044\u3048\u3067\u958b\u59cb\u6e08\u307f\u306e\u30c9\u30e9\u30a4\u30d6\u3092\u691c\u7d22\u3057\u307e\u3059\u3002\uff09\n+  USB, HDD, MMCE and the rest: USB\u3001HDD\u3001MMCE \u307b\u304b\n+  every started drive: \u958b\u59cb\u6e08\u307f\u306e\u3059\u3079\u3066\u306e\u30c9\u30e9\u30a4\u30d6\n+  found where you keep them: \u7f6e\u3044\u3066\u3042\u308b\u5834\u6240\u3067\u898b\u3064\u304b\u308a\u307e\u3057\u305f\n+  not found yet: \u307e\u3060\u898b\u3064\u304b\u3063\u3066\u3044\u307e\u305b\u3093\n+  not on a started drive: \u958b\u59cb\u6e08\u307f\u306e\u30c9\u30e9\u30a4\u30d6\u306b\u3042\u308a\u307e\u305b\u3093\n+  only if you have none: \u6301\u3063\u3066\u3044\u306a\u3044\u5834\u5408\u306e\u307f\n+  starts every drive first: \u5148\u306b\u3059\u3079\u3066\u306e\u30c9\u30e9\u30a4\u30d6\u3092\u958b\u59cb\u3057\u307e\u3059\n+  Drives, launchers and homebrew: \u30c9\u30e9\u30a4\u30d6\u3001\u30e9\u30f3\u30c1\u30e3\u30fc\u3001\u81ea\u4f5c\u30bd\u30d5\u30c8\n+  BDM Devices: BDM \u30c7\u30d0\u30a4\u30b9\n+  Color Sweep: \u30ab\u30e9\u30fc\u30b9\u30a4\u30fc\u30d7\n+  Colors and Text: \u8272\u3068\u30c6\u30ad\u30b9\u30c8\n+  HDD POPS partitions: HDD \u306e POPS \u30d1\u30fc\u30c6\u30a3\u30b7\u30e7\u30f3\n+  Layout: \u30ec\u30a4\u30a2\u30a6\u30c8\n+  Missing Covers: \u8db3\u308a\u306a\u3044\u30ab\u30d0\u30fc\n+  Name: \u540d\u524d\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: POPSTARTER \u306e SMB \u8a2d\u5b9a\u306f\u307e\u3060\u3042\u308a\u307e\u305b\u3093\u3002\u4e0b\u306e\u884c\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002\n+  POPSTARTER Network: POPSTARTER \u30cd\u30c3\u30c8\u30ef\u30fc\u30af\n+  POPSTARTER SMB Network: POPSTARTER SMB \u30cd\u30c3\u30c8\u30ef\u30fc\u30af\n+  POPSTARTER UDPFS Network: POPSTARTER UDPFS \u30cd\u30c3\u30c8\u30ef\u30fc\u30af\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: \u30c9\u30e9\u30a4\u30d6\u3001ISO\u3001VMC\u3001\u30d3\u30c7\u30aa\u30e2\u30fc\u30c9\u306f RIPPS2 \u304c\u81ea\u5206\u3067\u52a0\u3048\u307e\u3059\u3002\n+  Screen: \u753b\u9762\n+  Speed: \u901f\u3055\n+  Sweep Colors: \u30b9\u30a4\u30fc\u30d7\u306e\u8272\n+  Text Weight: \u6587\u5b57\u306e\u592a\u3055\n+  Theme Color: \u30c6\u30fc\u30de\u306e\u8272\n+  Audio: \u30aa\u30fc\u30c7\u30a3\u30aa\n+  Bold: \u592a\u5b57\n+  Controllers: \u30b3\u30f3\u30c8\u30ed\u30fc\u30e9\u30fc\n+  Lively: \u8efd\u5feb\n+  Medium: \u4e2d\u304f\u3089\u3044\n+  PS Emulation: PS \u30a8\u30df\u30e5\u30ec\u30fc\u30b7\u30e7\u30f3\n+  Regular: \u6a19\u6e96\n+  Slow: \u3086\u3063\u304f\u308a\n+  A MOMENT: \u5c11\u3005\u304a\u5f85\u3061\u304f\u3060\u3055\u3044\n+  A TASK IN THE BACKGROUND: \u30d0\u30c3\u30af\u30b0\u30e9\u30a6\u30f3\u30c9\u306e\u4f5c\u696d\n+  ABOUT %d S: \u3042\u3068\u7d04 %d \u79d2\n+  ANY MOMENT: \u307e\u3082\u306a\u304f\n+  READING THE GAME LIST OF %s: '%s \u306e\u30b2\u30fc\u30e0\u30ea\u30b9\u30c8\u3092\u8aad\u307f\u8fbc\u307f\u4e2d'\n+  SAVING A LIST: \u30ea\u30b9\u30c8\u3092\u4fdd\u5b58\u4e2d\n+  START NOW: \u4eca\u3059\u3050\u958b\u59cb\n+  STARTING: \u958b\u59cb\u4e2d\n+  STARTS NEXT: \u6b21\u306b\u958b\u59cb\n+  STOPPING IT SAFELY: \u5b89\u5168\u306b\u505c\u6b62\u4e2d\n+  THE LAST OF THE QUEUE: \u30ad\u30e5\u30fc\u306e\u6700\u5f8c\n+  WHEN IT FINISHES: \u7d42\u308f\u308a\u3057\u3060\u3044\n+  What each card starts; install or repair BIBLE.: \u5404\u30ab\u30fc\u30c9\u304c\u8d77\u52d5\u3059\u308b\u3082\u306e\u3002BIBLE \u3092\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u307e\u305f\u306f\u4fee\u5fa9\u3002\n+  Rainbow: \u30ec\u30a4\u30f3\u30dc\u30fc\n+  Custom: \u30ab\u30b9\u30bf\u30e0\n+  DESR DVR ISOs (experimental): DESR DVR ISO\uff08\u5b9f\u9a13\u7684\uff09\n+  PC CARD CF (experimental): PC CARD CF\uff08\u5b9f\u9a13\u7684\uff09\n+  HDD Start Mode: HDD \u306e\u8d77\u52d5\u30e2\u30fc\u30c9\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): The Roboto Project Authors \u306b\u3088\u308b Roboto Condensed\uff08SIL OFL 1.1\uff09\n+  '%s on %s': '%s\u3001%s\u4e0a'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: %s \u304c %s \u306b\u306a\u308a\u307e\u3057\u305f (%d%%)\u3002\u3059\u3079\u3066\u306e\u65ad\u7247\u3092\u78ba\u8a8d\u6e08\u307f\u3002ISO \u3092\u524a\u9664\u3057\u3066 ZSO \u3092\u6b8b\u3057\u307e\u3059\u304b\uff1f'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: %s \u304c\u518d\u3073 %s \u306b\u306a\u308a\u307e\u3057\u305f\u3002\u3059\u3079\u3066\u306e\u65ad\u7247\u3092\u78ba\u8a8d\u6e08\u307f\u3002ZSO \u3092\u524a\u9664\u3057\u3066 ISO \u3092\u6b8b\u3057\u307e\u3059\u304b\uff1f'\n+  A ZSO of this game is already beside it. Remove one of the two first.: \u3053\u306e\u30b2\u30fc\u30e0\u306e ZSO \u304c\u3059\u3067\u306b\u96a3\u306b\u3042\u308a\u307e\u3059\u3002\u5148\u306b\u3069\u3061\u3089\u304b\u3092\u524a\u9664\u3057\u3066\u304f\u3060\u3055\u3044\u3002\n+  About %d min, then checked: \u7d04 %d \u5206\u3001\u305d\u306e\u5f8c\u78ba\u8a8d\n+  An ISO of this game is already beside it. Remove one of the two first.: \u3053\u306e\u30b2\u30fc\u30e0\u306e ISO \u304c\u3059\u3067\u306b\u96a3\u306b\u3042\u308a\u307e\u3059\u3002\u5148\u306b\u3069\u3061\u3089\u304b\u3092\u524a\u9664\u3057\u3066\u304f\u3060\u3055\u3044\u3002\n+  CHECKING EVERY PIECE: \u3059\u3079\u3066\u306e\u65ad\u7247\u3092\u78ba\u8a8d\u4e2d\n+  COMPRESS TO ZSO: ZSO \u306b\u5727\u7e2e\n+  'COMPRESSED %s: %s FREED': '%s \u3092\u5727\u7e2e: %s \u7a7a\u304d\u307e\u3057\u305f'\n+  COMPRESSING 1 OF 2: \u5727\u7e2e\u4e2d 1/2\n+  COMPRESSING 2 OF 2: \u5727\u7e2e\u4e2d 2/2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : '%s \u3092\u5727\u7e2e\u3057\u307e\u3059\u304b\uff1f ZSO \u3092\u8aad\u307f\u623b\u3057\u3066\u78ba\u8a8d\u3059\u308b\u307e\u3067 ISO \u306f\u6b8b\u308a\u3001\u305d\u306e\u5f8c RIPPS2 \u304c ISO \u3092\u524a\u9664\u3059\u308b\u524d\u306b\u78ba\u8a8d\u3057\u307e\u3059\u3002\u4f5c\u696d\u4e2d\u306f\u30c9\u30e9\u30a4\u30d6\u306b\u4e21\u65b9\u306e\u7a7a\u304d\u304c\u5fc5\u8981\u3067\u3059\u3002\u7d42\u308f\u308b\u307e\u3067\u672c\u4f53\u306e\u96fb\u6e90\u3092\u5207\u3089\u306a\u3044\u3067\u304f\u3060\u3055\u3044\u3002ZSO \u306f OPL \u30b3\u30a2\u3067\u306e\u307f\u8d77\u52d5\u3057\u307e\u3059 (Neutrino \u306f\u4e0d\u53ef)\u3002\u30bb\u30fc\u30d6\u3001\u8a2d\u5b9a\u3001\u753b\u50cf\u3001\u30c1\u30fc\u30c8\u306f\u305d\u306e\u307e\u307e\u4f7f\u3048\u3001\u3044\u3064\u3067\u3082 ISO \u306b\u623b\u305b\u307e\u3059\u3002'\n+  Compress to ZSO: ZSO \u306b\u5727\u7e2e\n+  Cycle: \u9806\u9001\u308a\n+  'Experimental: OPL core only (not Neutrino)': '\u5b9f\u9a13\u7684: OPL \u30b3\u30a2\u306e\u307f (Neutrino \u306f\u4e0d\u53ef)'\n+  Hold SELECT: SELECT \u9577\u62bc\u3057\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: \u5b8c\u4e86\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f (%s)\u3002\u66f8\u304d\u8fbc\u3093\u3060\u3082\u306e\u306f\u524a\u9664\u3055\u308c\u3001\u30b2\u30fc\u30e0\u306f\u5143\u306e\u307e\u307e\u3067\u3059\u3002\n+  List: \u30ea\u30b9\u30c8\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : '%s \u3092 ISO \u306b\u623b\u3057\u307e\u3059\u304b\uff1f ISO \u3092\u8aad\u307f\u623b\u3057\u3066\u78ba\u8a8d\u3059\u308b\u307e\u3067 ZSO \u306f\u6b8b\u308a\u3001\u305d\u306e\u5f8c RIPPS2 \u304c ZSO \u3092\u524a\u9664\u3059\u308b\u524d\u306b\u78ba\u8a8d\u3057\u307e\u3059\u3002\u4f5c\u696d\u4e2d\u306f\u30c9\u30e9\u30a4\u30d6\u306b\u4e21\u65b9\u306e\u7a7a\u304d\u304c\u5fc5\u8981\u3067\u3059\u3002\u7d42\u308f\u308b\u307e\u3067\u672c\u4f53\u306e\u96fb\u6e90\u3092\u5207\u3089\u306a\u3044\u3067\u304f\u3060\u3055\u3044\u3002\u30bb\u30fc\u30d6\u3001\u8a2d\u5b9a\u3001\u753b\u50cf\u3001\u30c1\u30fc\u30c8\u306f\u305d\u306e\u307e\u307e\u4f7f\u3048\u307e\u3059\u3002'\n+  Stop? What it has written so far is removed; the game stays as it was.: \u4e2d\u6b62\u3057\u307e\u3059\u304b\uff1f \u3053\u308c\u307e\u3067\u306b\u66f8\u304d\u8fbc\u3093\u3060\u3082\u306e\u306f\u524a\u9664\u3055\u308c\u3001\u30b2\u30fc\u30e0\u306f\u5143\u306e\u307e\u307e\u3067\u3059\u3002\n+  THEMES: \u30c6\u30fc\u30de\n+  There is not enough memory for this just now.: \u4eca\u306f\u3053\u308c\u306b\u4f7f\u3048\u308b\u30e1\u30e2\u30ea\u304c\u8db3\u308a\u307e\u305b\u3093\u3002\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: \u3053\u306e\u30a4\u30e1\u30fc\u30b8\u306f RIPPS2 \u3067\u5727\u7e2e\u3082\u5c55\u958b\u3082\u3067\u304d\u307e\u305b\u3093 (\u30b5\u30a4\u30ba\u304c 2 KB \u30d6\u30ed\u30c3\u30af\u306e\u6574\u6570\u500d\u3067\u306f\u3042\u308a\u307e\u305b\u3093)\u3002\n+  UNCOMPRESS TO ISO: ISO \u306b\u5c55\u958b\n+  UNCOMPRESSED %s: '%s \u3092\u5c55\u958b\u3057\u307e\u3057\u305f'\n+  UNCOMPRESSING: \u5c55\u958b\u4e2d\n+  Uncompress to ISO: ISO \u306b\u5c55\u958b\n+  Under a minute, then checked: 1 \u5206\u672a\u6e80\u3001\u305d\u306e\u5f8c\u78ba\u8a8d\n+  COPYING: \u30b3\u30d4\u30fc\u4e2d\n+  SHOW AGAIN, DELETE OR COMPRESS: \u518d\u8868\u793a\u3001\u524a\u9664\u3001\u5727\u7e2e\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: \u518d\u8868\u793a\u3001\u524a\u9664\u3001\u5c55\u958b\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': '%s \u306e\u4f5c\u696d\u4e2d\u306f\u30c9\u30e9\u30a4\u30d6\u306b\u7d04 %s \u306e\u7a7a\u304d\u304c\u5fc5\u8981\u3067\u3059 (\u5143\u306e\u30d5\u30a1\u30a4\u30eb\u306f\u6700\u5f8c\u307e\u3067\u6b8b\u308a\u307e\u3059)\u3002\u30c9\u30e9\u30a4\u30d6\u306e\u7a7a\u304d\u306f %s \u3067\u3059\u3002'\n+  Hide and Compress: \u975e\u8868\u793a\u306b\u3057\u3066\u5727\u7e2e\n+  'COMPRESSED AND HIDDEN: %s': '\u5727\u7e2e\u3057\u3066\u975e\u8868\u793a: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': '\u3053\u306e\u30ea\u30c3\u30d4\u30f3\u30b0\u306b\u5fc5\u8981\u306a\u7a7a\u304d\u304c\u3042\u308a\u307e\u305b\u3093: \u7d04 %s \u5fc5\u8981\u3067\u3001\u30c9\u30e9\u30a4\u30d6\u306e\u7a7a\u304d\u306f %s \u3067\u3059\u3002'\n+  OVER 4 GB FOR FAT32: FAT32 \u3067\u306f 4 GB \u8d85\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: \u3053\u306e\u30c9\u30e9\u30a4\u30d6\u306f %s \u3067\u30014 GB \u4ee5\u4e0a\u306e\u30d5\u30a1\u30a4\u30eb\u3092\u4fdd\u5b58\u3067\u304d\u307e\u305b\u3093\u3002\u65b0\u3057\u3044\u30a4\u30e1\u30fc\u30b8\u306f %s \u306b\u306a\u308a\u307e\u3059\u3002\n+  '%s, %s clusters, %s free of %s': '%s\u3001\u30af\u30e9\u30b9\u30bf %s\u3001\u7a7a\u304d %s / %s'\n+  Delete This Sweep: \u3053\u306e\u30b9\u30a4\u30fc\u30d7\u3092\u524a\u9664\n+  Delete %s? RIPPS2's own sweeps stay as they are.: '%s \u3092\u524a\u9664\u3057\u307e\u3059\u304b\uff1f RIPPS2 \u81ea\u8eab\u306e\u30b9\u30a4\u30fc\u30d7\u306f\u305d\u306e\u307e\u307e\u6b8b\u308a\u307e\u3059\u3002'\n+  Sweep: \u30b9\u30a4\u30fc\u30d7\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: \u30d0\u30c3\u30b8\u306f\u30ea\u30b9\u30c8\u5185\u306e\u5b9f\u7e3e\u304c\u3042\u308b\u30b2\u30fc\u30e0\u3068\u3001\u89e3\u9664\u3057\u305f\u6570\u3092\u8868\u793a\u3057\u307e\u3059\u3002\u30c6\u30ec\u30e1\u30c8\u30ea\u30fc\u3092\u30aa\u30f3\u306b\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002\n labels:\n   IP_ADDRESS_TYPE_STATIC: \u56fa\u5b9a\n   HINT_NEUTRINO_AUTO_ELF: \u30b2\u30fc\u30e0\u306e\u8d77\u52d5 ELF \u3092 Neutrino \u306b\u6e21\u3057\u3001\u30bf\u30a4\u30c8\u30eb\u3054\u3068\u306e\u8a2d\u5b9a\u3092\u63a2\u3055\u305b\u307e\u3059\u3002\u65e2\u5b9a\u3067\u30aa\u30f3\u3002\u624b\u52d5\u3067\u8a2d\u5b9a\u3057\u305f Boot ELF \u30aa\u30d7\u30b7\u30e7\u30f3\u304c\u512a\u5148\u3055\u308c\u307e\u3059\u3002\n@@ -1201,7 +1368,7 @@ labels:\n   SAVE_ICONS: 3D \u30bb\u30fc\u30d6\u30a2\u30a4\u30b3\u30f3\n   HINT_SAVE_ICONS: \u30c6\u30fc\u30de\u306b\u5834\u6240\u304c\u3042\u308c\u3070\u3001\u9078\u3093\u3060\u30b2\u30fc\u30e0\u306e\u30bb\u30fc\u30d6\u30a2\u30a4\u30b3\u30f3\u3092\u56de\u8ee2\u3055\u305b\u3066\u8868\u793a\u3057\u307e\u3059\u3002\u307e\u305a\u30b2\u30fc\u30e0\u81ea\u8eab\u306e VMC \u3092\u3001\u6b21\u306b\u30e1\u30e2\u30ea\u30fc\u30ab\u30fc\u30c9\u3092\u8aad\u307f\u307e\u3059\u3002\n   NETBOOT_RESTART_NOW: \u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u30d7\u30ed\u30c8\u30b3\u30eb\u306f\u518d\u8d77\u52d5\u5f8c\u306b\u5909\u308f\u308a\u307e\u3059\u3002\u4eca\u8a2d\u5b9a\u3092\u4fdd\u5b58\u3057\u3066 OPL \u3092\u518d\u8d77\u52d5\u3057\u307e\u3059\u304b\uff1f\n-  HINT_NET_PROTOCOL: RiptOPL \u304c\u4f7f\u3046\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u30b5\u30fc\u30d3\u30b9\uff1aSMB \u5171\u6709\u3001UDPFS\u3001UDPBD\u3001HTTP\u3002\u30b2\u30fc\u30e0\u30bd\u30fc\u30b9\u306e\u8a2d\u5b9a\u3068\u540c\u3058\u3067\u3059\u3002\u52d5\u4f5c\u4e2d\u306e\u3082\u306e\u304b\u3089\u5207\u308a\u66ff\u3048\u308b\u306b\u306f\u518d\u8d77\u52d5\u304c\u5fc5\u8981\u3067\u3059\u3002\n+  HINT_NET_PROTOCOL: RiptOPL \u304c\u4f7f\u3046\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u30b5\u30fc\u30d3\u30b9\uff1aSMB \u5171\u6709\u3001UDPFS\u3001UDPBD\u3001HTTP\u3002\u52d5\u4f5c\u4e2d\u306e\u3082\u306e\u304b\u3089\u5207\u308a\u66ff\u3048\u308b\u306b\u306f\u518d\u8d77\u52d5\u304c\u5fc5\u8981\u3067\u3059\u3002\n   HINT_UDPFS_ACCESS: UDPFS \u306e\u307f\u3002\u300c\u30d5\u30a1\u30a4\u30eb\u300d\u306f\u30b5\u30fc\u30d0\u30fc\u306e\u30b2\u30fc\u30e0\u30d5\u30a9\u30eb\u30c0\u304b\u3089\u500b\u5225\u306e ISO \u3092\u8aad\u307f\u3001IMG \u306f\u30c7\u30a3\u30b9\u30af\u30a4\u30e1\u30fc\u30b8\u3092\u30c9\u30e9\u30a4\u30d6\u3068\u3057\u3066\u8aad\u307f\u307e\u3059\u3002SMB \u3068 HTTP \u306f\u5e38\u306b\u300c\u30d5\u30a1\u30a4\u30eb\u300d\u3001UDPBD \u306f\u5e38\u306b IMG \u3067\u3059\u3002\n   HINT_SMB_VERSION: PC \u306e\u5171\u6709\u3068\u8a71\u3059 SMB \u306e\u30d0\u30fc\u30b8\u30e7\u30f3\u3002\n   HINT_NET_ADVANCED: \u30a4\u30fc\u30b5\u30cd\u30c3\u30c8\u306e\u30ea\u30f3\u30af\u30e2\u30fc\u30c9\u3068 SMB \u30dd\u30fc\u30c8\u3092\u7de8\u96c6\u3067\u304d\u308b\u3088\u3046\u306b\u3057\u307e\u3059\u3002\n@@ -1400,3 +1567,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: \u5404\u30b2\u30fc\u30e0\u306e\u30d5\u30a3\u30eb\u30e0\u3067\u5f85\u3063\u3066\u3044\u308b\u5199\u771f\u3092 PNG \u306b\u3059\u308b\u65b9\u6cd5\u3002\n   RIPPS2_SETTINGS_HOME: \u8a2d\u5b9a\u306e\u4fdd\u5b58\u5148\n   HINT_RIPPS2_SETTINGS_HOME: RIPPS2 \u304c\u8a2d\u5b9a\u3092\u7f6e\u304f\u5834\u6240\uff1a\u30e1\u30e2\u30ea\u30fc\u30ab\u30fc\u30c9\u4e0a\u306e\u81ea\u8eab\u306e\u30bb\u30fc\u30d6\u3001\u30c9\u30e9\u30a4\u30d6\uff08\u5185\u8535 HDD\u3001USB\u3001MX4SIO\u3001iLink\u3001MMCE\uff09\u306e RIPPS2 \u30d5\u30a9\u30eb\u30c0\u3001\u307e\u305f\u306f\u5165\u529b\u3057\u305f\u30d5\u30a9\u30eb\u30c0\u3002\n+  RIPPS2_LAUNCHERS: \u30e9\u30f3\u30c1\u30e3\u30fc\n+  RIPPS2_LOOK_NOW: \u4eca\u3059\u3050\u63a2\u3059\n+  HINT_RIPPS2_LOOK_NOW: RIPPS2 \u306f\u958b\u59cb\u6e08\u307f\u306e\u30c9\u30e9\u30a4\u30d6\u3067 POPSTARTER\u3001POPS \u30d5\u30a1\u30a4\u30eb\u3001Ember\u3001Ember \u306e BIOS\u3001Neutrino \u3092\u63a2\u3057\u3001\u305d\u308c\u305e\u308c\u306e\u5834\u6240\u3092\u8a18\u61b6\u3057\u307e\u3059\u3002\u8d77\u52d5\u306e\u305f\u3073\u306b\u305d\u3053\u304b\u3089\u8aad\u307f\u8fbc\u307f\u307e\u3059\u3002\n+  RIPPS2_HIDE_GAME: \u30b2\u30fc\u30e0\u3092\u975e\u8868\u793a\n+  RIPPS2_HIDDEN_GAMES: \u975e\u8868\u793a\u306e\u30b2\u30fc\u30e0\n+  HINT_RIPPS2_HIDDEN_GAMES: \u300c\u30b2\u30fc\u30e0\u3092\u975e\u8868\u793a\u300d\u3067\u96a0\u3057\u305f\u3001\u958b\u59cb\u6e08\u307f\u306e\u30c9\u30e9\u30a4\u30d6\u306e\u30b2\u30fc\u30e0\u3067\u3059\u3002\u518d\u8868\u793a\u3059\u308b\u304b\u3001\u524a\u9664\u3067\u304d\u307e\u3059\u3002\u30c9\u30e9\u30a4\u30d6\u3054\u3068\u306b\u81ea\u5206\u306e\u30ea\u30b9\u30c8\u3092\u6301\u3063\u3066\u3044\u307e\u3059\u3002\n+  HINT_RIPPS2_BDM_DEVICES: 'BDM \u304c\u30b2\u30fc\u30e0\u3092\u8aad\u3080\u30c9\u30e9\u30a4\u30d6: USB\u3001MX4SIO\u3001PC Card CF\u3001DESR \u306e DVR \u30c9\u30e9\u30a4\u30d6\u3001iLink\u3002\u5185\u8535 HDD \u306f HDD \u306e\u8d77\u52d5\u30e2\u30fc\u30c9\u306b\u5f93\u3044\u307e\u3059\u3002'\n+  HINT_RIPPS2_SCREEN: '\u30e1\u30cb\u30e5\u30fc\u306e\u6620\u50cf: \u30d3\u30c7\u30aa\u30e2\u30fc\u30c9\u3001\u30ef\u30a4\u30c9\u753b\u9762\u3001\u30c6\u30ec\u30d3\u4e0a\u306e\u4f4d\u7f6e\uff08H-POS\u3001V-POS\uff09\u3068\u30aa\u30fc\u30d0\u30fc\u30b9\u30ad\u30e3\u30f3\u3002'\n+  HINT_RIPPS2_LAYOUT: \u30ab\u30c6\u30b4\u30ea\u30fc\u306e\u4f4d\u7f6e\u3068\u30da\u30fc\u30b8\u306e\u5165\u308a\u65b9\u3001\u305d\u3057\u3066 RIPFLOW \u306e\u30ab\u30d0\u30fc\u3068 RIPGRID \u306e\u30b0\u30ea\u30c3\u30c9\u3002\n+  HINT_RIPPS2_COLORS_TEXT: \u6587\u5b57\u306e\u592a\u3055\u3001\u30ab\u30e9\u30fc\u30c6\u30fc\u30de\u3001\u30ab\u30e9\u30fc\u30b9\u30a4\u30fc\u30d7\u3001\u305d\u3057\u3066\u3059\u3079\u3066\u306e\u8272\u3002\n+  HINT_RIPPS2_TEXT_WEIGHT: 'RIPPS2 \u81ea\u8eab\u306e\u6587\u5b57\u306e\u592a\u3055: \u8a2d\u5b9a\u306e\u30d2\u30f3\u30c8\u3001\u30b9\u30c6\u30fc\u30bf\u30b9\u3001\u60c5\u5831\u3002\u6a19\u6e96\u306f\u3053\u308c\u307e\u3067\u3069\u304a\u308a\u3002\u4e2d\u304f\u3089\u3044\u3068\u592a\u5b57\u306f\u3084\u308f\u3089\u304b\u3044\u6620\u50cf\u3067\u8aad\u307f\u3084\u3059\u304f\u306a\u308a\u307e\u3059\u3002'\n+  HINT_RIPPS2_HDD_START: \u5185\u8535 HDD \u306f APA \u3067\u3082 exFAT \u3067\u3082\u3001RIPPS2 \u304c\u3069\u3061\u3089\u304b\u8aad\u307f\u53d6\u308a\u3001HDD \u30b2\u30fc\u30e0\u3068\u3057\u3066\u4e00\u5ea6\u3060\u3051\u8868\u793a\u3057\u307e\u3059\u3002\u30aa\u30d5\u306b\u3059\u308b\u3068\u89e6\u308c\u307e\u305b\u3093\u3002\n+  HINT_RIPPS2_THEME_COLOR: \u30ab\u30e9\u30fc\u30c6\u30fc\u30de\u305d\u306e\u3082\u306e\u306e\u8272\u3002\u30ab\u30e9\u30fc\u30c6\u30fc\u30de\u3067\u30ab\u30b9\u30bf\u30e0\u3092\u9078\u3076\u3068\u3001\u6587\u5b57\u306e\u8272\u3068\u540c\u3058\u3088\u3046\u306b\u81ea\u5206\u3067\u9078\u3079\u307e\u3059\u3002\n+  HINT_RIPPS2_SWEEP: \u30c6\u30fc\u30de\u304c\u3086\u3063\u304f\u308a\u306a\u3081\u3089\u304b\u306b\u79fb\u308a\u5909\u308f\u308b 5 \u8272\u3002\u30ec\u30a4\u30f3\u30dc\u30fc\u3001\u5404\u30ab\u30e9\u30fc\u30c6\u30fc\u30de\u304b\u3089\u4f5c\u3063\u305f\u30b9\u30a4\u30fc\u30d7\u3001\u307e\u305f\u306f\u81ea\u5206\u306e\u3082\u306e\u3002\u30aa\u30d5\u306a\u3089\u30ab\u30e9\u30fc\u30c6\u30fc\u30de\u306f\u52d5\u304d\u307e\u305b\u3093\u3002\n+  HINT_RIPPS2_SWEEP_COLORS: \u30b9\u30a4\u30fc\u30d7\u306e 5 \u8272\u3068\u901f\u3055\u3002RIPPS2 \u306e\u3082\u306e\u3092\u5909\u3048\u308b\u3068\u3001\u4ed8\u3051\u305f\u540d\u524d\u3067\u81ea\u5206\u306e\u3082\u306e\u3068\u3057\u3066\u6b8b\u308a\u307e\u3059\u3002\n+  HINT_RIPPS2_SWEEP_NAME: \u30ab\u30e9\u30fc\u30b9\u30a4\u30fc\u30d7\u306e\u4e00\u89a7\u3067\u306e\u30b9\u30a4\u30fc\u30d7\u306e\u540d\u524d\u3002\n+  HINT_RIPPS2_SWEEP_COLOR: \u30b9\u30a4\u30fc\u30d7\u304c\u9806\u306b\u79fb\u308a\u5909\u308f\u308b 5 \u8272\u306e\u3072\u3068\u3064\u3002\n+  HINT_RIPPS2_SWEEP_SPEED: \u6b21\u306e\u8272\u306b\u79fb\u308b\u307e\u3067\u5404\u8272\u304c\u7d9a\u304f\u9577\u3055\u3002\u8efd\u5feb\u3067\u3082\u306a\u3081\u3089\u304b\u3067\u3001\u8272\u304c\u70b9\u6ec5\u3059\u308b\u3053\u3068\u306f\u3042\u308a\u307e\u305b\u3093\u3002\n+  RIPPS2_HIDE_COMPRESS: \u30b2\u30fc\u30e0\u3092\u975e\u8868\u793a\u3068\u5727\u7e2e\n+  HINT_RIPPS2_SELECT_HOLD: '\u30b2\u30fc\u30e0\u30ea\u30b9\u30c8\u3067 SELECT \u3092 4.2 \u79d2\u9577\u62bc\u3057\u3002\u30ea\u30b9\u30c8: \u30d1\u30cd\u30eb\u306b\u30c6\u30fc\u30de\u3092\u4e26\u3079\u3066\u9078\u3076\u3002\u9806\u9001\u308a: \u6b21\u306e\u30c6\u30fc\u30de\u3002\u30aa\u30d5: \u4f55\u3082\u3057\u306a\u3044 (\u62bc\u3059\u3068\u66f4\u65b0\u306f\u3057\u307e\u3059)\u3002'"
        },
        {
          "sha": "aeac581581df410a38f63fb0b8b31f747dfe46f2",
          "filename": "elf/lang/Korean.yml",
          "status": "modified",
          "additions": 209,
          "deletions": 22,
          "changes": 231,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FKorean.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FKorean.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FKorean.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: Korean\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTER\ub294 \ud655\uc778, ESC\ub294 \ucde8\uc18c. \uc544\ubb34 \ubc84\ud2bc\uc774\ub098 \ub204\ub974\uba74 \ud0a4\uac00 \ud45c\uc2dc\ub429\ub2c8\ub2e4.\n   USB KEYBOARD: USB \ud0a4\ubcf4\ub4dc\n-  '      DESR DVR ISOs (experimental)': '      DESR DVR ISO (\uc2e4\ud5d8\uc801)'\n-  '      Internal HDD (exFAT)': '      \ub0b4\uc7a5 HDD (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (\uc2e4\ud5d8\uc801)'\n-  '      Protocol': '      \ud504\ub85c\ud1a0\ucf5c'\n   Access: \uc811\uadfc \ubc29\uc2dd\n   Applications Page Start Mode: \uc571 \ud398\uc774\uc9c0 \uc2dc\uc791 \ubaa8\ub4dc\n   Art Delay: \uc544\ud2b8 \uc9c0\uc5f0\n@@ -22,7 +18,6 @@ strings:\n   Fifth: \ub2e4\uc12f \ubc88\uc9f8\n   First: \uccab \ubc88\uc9f8\n   Fourth: \ub124 \ubc88\uc9f8\n-  HDD (APA) Start Mode: HDD (APA) \uc2dc\uc791 \ubaa8\ub4dc\n   HDD Cache: HDD \uce90\uc2dc\n   HDD Spindown: HDD \uc2a4\ud540\ub2e4\uc6b4\n   MMCE Settings: MMCE \uc124\uc815\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: ESR \ud328\uce58\uac00 \uc801\uc6a9\ub41c PS2 \uac8c\uc784\uc785\ub2c8\ub2e4. \uc2dc\uc791\ud558\ub824\uba74 \uba54\ubaa8\ub9ac \uce74\ub4dc\ub098 USB \ub4dc\ub77c\uc774\ube0c\uc758 APPS, BOOT \ub610\ub294 ESR\uc5d0 ESR(esr*.elf)\uc744 \ub123\uc73c\uc138\uc694.\n   '%s, BUILD %d': '%s, \ube4c\ub4dc %d'\n   '... and the anonymous ...': '... \uadf8\ub9ac\uace0 \uc774\ub984 \uc5c6\ub294 \ubd84\ub4e4 ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': '1.8.0\uc744 \uc218\uc815 \uc5c6\uc774 \ud3ec\ud568 (AFL-3.0): github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: Open PS2 Loader\uc758 \ud3ec\ud06c\uc778 RiptOPL\uc744 \ub72f\uc5b4\uc11c \ub2e4\uc2dc \ub9cc\ub4e0 \uac83\uc73c\ub85c,\n   ABOUT: \uc815\ubcf4\n   'ACHIEVEMENTS: SPIRIT OF RA': '\uc5c5\uc801: SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): \uba54\ubaa8\ub9ac \ud30c\uc77c\uc758 AVI \ube44\ub514\uc624, RIPPS2\uc758 \ud3ec\ud06c (AFL 2.0)\n   Apollo Save Tool by bucanero: bucanero\uc758 Apollo Save Tool\n   BACK: \ub4a4\ub85c\n+  Back (keeps both): \ub4a4\ub85c (\ub458 \ub2e4 \uc720\uc9c0)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: \uc774 MMCE \uce74\ub4dc\ub294 \ud30c\uc77c \uc774\ub984\uc744 \ubc14\uafc0 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uc555\ucd95\uc758 \ub9c8\uc9c0\ub9c9 \ub2e8\uacc4\uc5d0 \ud544\uc694\ud569\ub2c8\ub2e4. sd2psXtd \ud38c\uc6e8\uc5b4\ub97c 1.4.0 \uc774\uc0c1\uc73c\ub85c \uc5c5\ub370\uc774\ud2b8\ud55c \ub4a4 (sd2psxtd.github.io/download) \ub2e4\uc2dc \uc2dc\ub3c4\ud558\uc138\uc694. \uc544\ubb34\uac83\ub3c4 \ubc14\ub00c\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): BIBLE\uc740 PS2BBL 1.2.0, El Isra\uc758 \uc11c\uba85\ub41c \ube4c\ub4dc(\uba54\ubaa8\ub9ac \uce74\ub4dc\uc640 HDD)\ub97c \uadf8\ub300\ub85c \uc0ac\uc6a9 (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: BIOS \uc0ac\uc6b4\ub4dc > \ucd94\ucd9c\uc740 SPU2\uc640 \uac19\uc740 \ubc29\uc2dd\uc73c\ub85c \uc7ac\uc0dd\ud569\ub2c8\ub2e4. BIOS\ub294 \ud3ec\ud568\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4\n   BOOT LOCK AND BIBLE: BOOT LOCK\uacfc BIBLE\n   BOOT LOCK edits its config too: BOOT LOCK\uc740 \uadf8 \uc124\uc815\ub3c4 \ud3b8\uc9d1\ud569\ub2c8\ub2e4\n   BUILD DETAILS: \ube4c\ub4dc \uc815\ubcf4\n-  BUILT ON RIPTOPL: RIPTOPL \uae30\ubc18\n   BearSSL by Thomas Pornin: Thomas Pornin\uc758 BearSSL\n   Built with Claude (Anthropic): Claude(Anthropic)\uc640 \ud568\uaed8 \uc81c\uc791\n   CD PLAYER: CD \ud50c\ub808\uc774\uc5b4\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek\uc640 Sleek Bold, RIPPS2\ub97c \uc704\ud574 \uadf8\ub9bc\n   RetroAchievements on the console by hacan359 (yoba): hacan359(yoba)\uc758 \ubcf8\uccb4\uc6a9 RetroAchievements\n   RiptOPL by NathanNeurotic: NathanNeurotic\uc758 RiptOPL\n-  Roboto Condensed by Google (Apache-2.0): Google\uc758 Roboto Condensed (Apache-2.0)\n   SOUNDS: \uc0ac\uc6b4\ub4dc\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: SP193\uc758 FMCB \uc124\uce58\uae30\uc640 pcm720\uc758 OSDMenu\ub97c \ub2e8\uacc4\ubcc4\ub85c \ub530\ub984\n   SUPPORT: \uc9c0\uc6d0\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: NathanNeurotic\uc758 wLaunchELF R3Z\n   wLaunchELF by the ps2homebrew team: ps2homebrew \ud300\uc758 wLaunchELF\n   what RIPPS2's card image writes are checked against: RIPPS2\uc758 \uce74\ub4dc \uc774\ubbf8\uc9c0 \uc4f0\uae30\ub97c \ub300\uc870\ud558\ub294 \uae30\uc900\n-  with its own look, launchers, file browser and save tools.: \uace0\uc720\ud55c \ubaa8\uc591, \ub7f0\ucc98, \ud30c\uc77c \ube0c\ub77c\uc6b0\uc800, \uc138\uc774\ube0c \ub3c4\uad6c\ub97c \uac16\ucd94\uc5c8\uc2b5\ub2c8\ub2e4.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: hacan359\uc758 xeRAbora, RetroAchievements\uc758 rcheevos\n   your console's own, ripped from its BIOS: \ubcf8\uccb4 \uc790\uccb4\uc758 \uc0ac\uc6b4\ub4dc, BIOS\uc5d0\uc11c \ucd94\ucd9c\n   ' (no DNS answer)': ' (DNS \uc751\ub2f5 \uc5c6\uc74c)'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO: %.60s \uc4f0\ub294 \uc911'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: Neutrino\ub97c %s\uc5d0 \uc4f8 \uc218 \uc5c6\uc5c8\uc2b5\ub2c8\ub2e4. \ub0a8\uc740 \uacf5\uac04\uacfc \uc4f0\uae30 \uae08\uc9c0 \uc5ec\ubd80\ub97c \ud655\uc778\ud55c \ub4a4 \ub2e4\uc2dc \uc2dc\ub3c4\ud558\uc138\uc694.\n   THE GAME'S DRIVE  (%s/neutrino): \uac8c\uc784\uc758 \ub4dc\ub77c\uc774\ube0c  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : \uc774 \uac8c\uc784\uc740 Neutrino\ub85c \uc2dc\uc791\ud558\ub294\ub370, RIPPS2\uac00 \ucc3e\ub294 \uacf3(\uc124\uc815, \uac8c\uc784\uc758 \ub4dc\ub77c\uc774\ube0c, \uba54\ubaa8\ub9ac \uce74\ub4dc)\uc5d0 Neutrino\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. RIPPS2\uc5d0\ub294 rickgaiser\uc758 Neutrino 1.8.0(AFL-3.0)\uc774 \ub4e4\uc5b4 \uc788\uc2b5\ub2c8\ub2e4. \uc9c0\uae08 \uc124\uce58\ud560\uae4c\uc694? \ub2e4\uc74c\uc5d0 \uc704\uce58\ub97c \uace0\ub985\ub2c8\ub2e4.\n   '%d / %d FILES': '%d / %d \ud30c\uc77c'\n   '%d FOLDERS AND CONTENTS': \ud3f4\ub354 %d\uac1c\uc640 \ub0b4\uc6a9\n   '%d FOLDERS, %d FOUND': \ud3f4\ub354 %d\uac1c, %d\uac1c \ucc3e\uc74c\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d / %d\uac1c \uc544\uc9c1 \uc7a0\uae40'\n   '%s   on %s': '%s   %s\uc5d0\uc11c'\n   BADGES: \ubc30\uc9c0\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: \ubc30\uc9c0\ub294 \ud154\ub808\uba54\ud2b8\ub9ac\uac00 \ucf1c\uc838 \uc788\uace0 PC\uc5d0\uc11c xeRAbora\uac00 \uc2e4\ud589 \uc911\uc77c \ub54c\ub9cc \uc791\ub3d9\ud569\ub2c8\ub2e4. \uadf8\uc804\uc5d0\ub294 \uc544\ubb34 \ubd80\ub2f4\uc774 \uc5c6\uc2b5\ub2c8\ub2e4.\n   CHECK THE DISC: \ub514\uc2a4\ud06c \ud655\uc778\n   From this game's last CHECK GAME SUPPORT.: \uc774 \uac8c\uc784\uc758 \ub9c8\uc9c0\ub9c9 \uac8c\uc784 \uc9c0\uc6d0 \ud655\uc778\uc5d0\uc11c.\n   HOW IT WORKS: \uc791\ub3d9 \ubc29\uc2dd\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: \uc774 \ubcf8\uccb4\uc5d0\ub294 RIPPS2 \uc7a0\uae08\uc774 \uc788\ub294 PS2BBL\uc774 \uc788\uc2b5\ub2c8\ub2e4.\n   AUDIO folder: AUDIO \ud3f4\ub354\n   DRIVES: \ub4dc\ub77c\uc774\ube0c\n-  INSTALL RIPPS2'S COPY: RIPPS2\uc758 \uc0ac\ubcf8 \uc124\uce58\n-  'Installed: ': '\uc124\uce58\ub428: '\n   MANAGE: \uad00\ub9ac\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: \uad00\ub9ac\ub294 \uac01 \uba54\ubaa8\ub9ac \uce74\ub4dc\uac00 \ubb34\uc5c7\uc744 \uc2dc\uc791\ud558\ub294\uc9c0 \ubcf4\uc5ec \uc8fc\uace0, BIBLE\uc744 \uc124\uce58\ud558\uac70\ub098 \ubcf5\uad6c\ud569\ub2c8\ub2e4.\n   MEMORY CARD 0  (about 600 KB): \uba54\ubaa8\ub9ac \uce74\ub4dc 0  (\uc57d 600 KB)\n   MEMORY CARD 1  (about 600 KB): \uba54\ubaa8\ub9ac \uce74\ub4dc 1  (\uc57d 600 KB)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO: \ub4dc\ub77c\uc774\ubc84\ub97c \ubd88\ub7ec\uc62c \uc218 \uc5c6\uc5c8\uc2b5\ub2c8\ub2e4 (\ucf54\ub4dc %d).'\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO: \ub4dc\ub77c\uc774\ubc84\ub294 \uc788\uc9c0\ub9cc SD \uce74\ub4dc\uac00 \uc751\ub2f5\ud558\uc9c0 \uc54a\uc74c (mc1 \uc2ac\ub86f).'\n   NEUTRINO SETTINGS: NEUTRINO \uc124\uc815\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': '\uc544\uc9c1 \uc5c6\uc74c: RIPPS2\uc5d0\ub294 \uc790\uccb4 \uc0ac\uc6b4\ub4dc\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \ubcf8\uccb4\uc758 \uc0ac\uc6b4\ub4dc\ub97c \ucd94\ucd9c\ud558\uac70\ub098 RIPPS2 Audio Studio\uc5d0\uc11c \ub9cc\ub4dc\uc138\uc694.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: \uc124\uce58\ub418\uc9c0 \uc54a\uc74c. \ud544\uc694\ud55c \uac8c\uc784\uc774 RIPPS2\uc758 \uc0ac\ubcf8\uc744 \uc81c\uc548\ud558\uba70, \uc5ec\uae30\uc11c \uc124\uce58\ud560 \uc218\ub3c4 \uc788\uc2b5\ub2c8\ub2e4.\n   POPSTARTER NETWORK: POPSTARTER \ub124\ud2b8\uc6cc\ud06c\n   POPSTARTER and Ember: POPSTARTER\uc640 Ember\n   PS1 SETTINGS: PS1 \uc124\uc815\n   PS2BBL 1.2.0 for RIPPS2: RIPPS2\uc6a9 PS2BBL 1.2.0\n   RIP THIS CONSOLE'S SOUNDS: \uc774 \ubcf8\uccb4\uc758 \uc0ac\uc6b4\ub4dc \ucd94\ucd9c\n-  RIPPS2's homebrew: RIPPS2\uc758 \ud648\ube0c\ub8e8\n   STATUS: \uc0c1\ud0dc\n-  'STATUS: looking for RIPPS2''s homebrew': '\uc0c1\ud0dc: RIPPS2\uc758 \ud648\ube0c\ub8e8\ub97c \ucc3e\ub294 \uc911'\n   a drive of unknown kind: \uc885\ub958\ub97c \uc54c \uc218 \uc5c6\ub294 \ub4dc\ub77c\uc774\ube0c\n-  a drive or a memory card: \ub4dc\ub77c\uc774\ube0c \ub610\ub294 \uba54\ubaa8\ub9ac \uce74\ub4dc\n   boot loader, card by card: \ubd80\ud2b8 \ub85c\ub354, \uce74\ub4dc\ubcc4\ub85c\n   from its BIOS, into AUDIO: BIOS\uc5d0\uc11c AUDIO\ub85c\n   internal HDD (exFAT): \ub0b4\uc7a5 HDD (exFAT)\n@@ -975,10 +958,194 @@ strings:\n   Sidebar: \uc0ac\uc774\ub4dc\ubc14\n   your own copy, found on your drives and memory cards: \ub0b4 \ub4dc\ub77c\uc774\ube0c\uc640 \uba54\ubaa8\ub9ac \uce74\ub4dc\uc5d0\uc11c \ucc3e\uc740 \ubcf8\uc778\uc758 \uc0ac\ubcf8\n   'your own, found and launched: github.com/Gageformer/Ember/releases': '\ubcf8\uc778\uc758 \uac83\uc744 \ucc3e\uc544 \uc2e4\ud589: github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': 'mass0: (%s)\uc5d0\uc11c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. ember.elf\uc640 \ubcf8\uc778\uc758 bios.bin\uc744 \uadf8 EMBER \ud3f4\ub354\uc5d0 \ub123\uc73c\uc138\uc694.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ubcf8\uc778\uc758 POPSTARTER.ELF\ub97c \ub4dc\ub77c\uc774\ube0c\uc758 POPS \ud3f4\ub354\uc5d0 \ub123\uc73c\uc138\uc694.\n   Launch Disc: \ub514\uc2a4\ud06c \uc2e4\ud589\n   Memory Files: \uba54\ubaa8\ub9ac \ud30c\uc77c\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: RIPPS2\uac00 5\uac1c \uc911 %d\uac1c\ub97c \ucc3e\uc558\uc2b5\ub2c8\ub2e4. \uac01\uac01 \uae30\uc5b5\ub418\uba70, \uc2e4\ud589\ud560 \ub54c\ub9c8\ub2e4 \uac70\uae30\uc11c \uc77d\uc2b5\ub2c8\ub2e4.\n+  not found on the started drives: \uc2dc\uc791\ub41c \ub4dc\ub77c\uc774\ube0c\uc5d0\uc11c \ucc3e\uc9c0 \ubabb\ud568\n+  not looked for yet: \uc544\uc9c1 \ucc3e\uc9c0 \uc54a\uc74c\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s\uc774(\uac00) \uc751\ub2f5\ud558\uc9c0 \uc54a\uc544 %s\uc744(\ub97c) \uc2dc\uc791\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uc5f0\uacb0\ub418\uc5b4 \uc788\ub098\uc694?'\n+  '%s is not on %s any more, so it has left All Games.': '%s\uc774(\uac00) \ub354 \uc774\uc0c1 %s\uc5d0 \uc5c6\uc5b4\uc11c \ubaa8\ub4e0 \uac8c\uc784\uc5d0\uc11c \ube60\uc84c\uc2b5\ub2c8\ub2e4.'\n+  STARTING %s: '%s \uc2dc\uc791 \uc911'\n+  the SMB share: SMB \uacf5\uc720\n+  the drive: \ub4dc\ub77c\uc774\ube0c\n+  the internal HDD (APA): \ub0b4\uc7a5 HDD (APA)\n+  the internal HDD (exFAT): \ub0b4\uc7a5 HDD (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: RIPTOPL \uc704\uc5d0, NEUTRINO\uc640 \ud568\uaed8 \ub9cc\ub4e6\n+  'Special thanks to all the YouTube love as well:': 'YouTube\uc5d0\uc11c \ubcf4\ub0b4 \uc8fc\uc2e0 \ubaa8\ub4e0 \uc0ac\ub791\uc5d0\ub3c4 \ud2b9\ubcc4\ud788 \uac10\uc0ac\ub4dc\ub9bd\ub2c8\ub2e4:'\n+  and its own look, launchers, file browser and save tools.: \uadf8\ub9ac\uace0 \uace0\uc720\ud55c \ubaa8\uc2b5, \ub7f0\ucc98, \ud30c\uc77c \ube0c\ub77c\uc6b0\uc800, \uc138\uc774\ube0c \ub3c4\uad6c\ub97c \uac16\ucdc4\uc2b5\ub2c8\ub2e4.\n+  glad to help however you reach me.: \uc5b4\ub5a4 \ubc29\ubc95\uc73c\ub85c \uc5f0\ub77d\ud558\ub4e0 \uae30\uaebc\uc774 \ub3d5\uaca0\uc2b5\ub2c8\ub2e4.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): \ub450 \ubc88\uc9f8 \uac8c\uc784 \ucf54\uc5b4, 1.8.0\uc744 \uc218\uc815 \uc5c6\uc774 \ud3ec\ud568 (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: rickgaiser\uc758 Neutrino\ub97c \ub450 \ubc88\uc9f8 \uac8c\uc784 \ucf54\uc5b4\ub85c \ub0b4\uc7a5\ud558\uace0,\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : '\uc774 \uac8c\uc784\uc740 Neutrino\ub85c \uc2dc\uc791\ud558\ub294\ub370, RIPPS2\uac00 \ucc3e\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4: \uc124\uc815\uc758 \uacbd\ub85c\uc5d0\ub3c4, \uac8c\uc784\uc758 \ub4dc\ub77c\uc774\ube0c\uc5d0\ub3c4, \uba54\ubaa8\ub9ac \uce74\ub4dc\uc5d0\ub3c4, \uc2dc\uc791\ub41c \ub4dc\ub77c\uc774\ube0c\uc5d0\ub3c4 \uc5c6\uc2b5\ub2c8\ub2e4. RIPPS2\uc5d0\ub294 rickgaiser\uc758 Neutrino 1.8.0(AFL-3.0)\uc774 \ub4e4\uc5b4 \uc788\uc2b5\ub2c8\ub2e4: \uc6d0\ud558\ub294 \uacf3\uc5d0 \ud55c \ubc88 \uc124\uce58\ud558\uba74 \ubaa8\ub4e0 Neutrino \uac8c\uc784\uc774 \uac70\uae30\uc11c \uc0ac\uc6a9\ud569\ub2c8\ub2e4.'\n+  STARTING THE DRIVES: \ub4dc\ub77c\uc774\ube0c \uc2dc\uc791 \uc911\n+  '%d games, on the started drives': \uac8c\uc784 %d\uac1c, \uc2dc\uc791\ub41c \ub4dc\ub77c\uc774\ube0c\uc5d0\uc11c\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s: \uc77c\ubd80 \ud30c\uc77c\uc744 \uc0ad\uc81c\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4'\n+  '%u partitions, all in order': \ud30c\ud2f0\uc158 %u\uac1c, \ubaa8\ub450 \uc815\uc0c1\n+  ', %d art images': ', \uc544\ud2b8 \uc774\ubbf8\uc9c0 %d\uac1c'\n+  ', and ': ', \uadf8\ub9ac\uace0 '\n+  ', its cheats': ', \uce58\ud2b8'\n+  ', its settings': ', \uc124\uc815'\n+  1 game, on the started drives: \uac8c\uc784 1\uac1c, \uc2dc\uc791\ub41c \ub4dc\ub77c\uc774\ube0c\uc5d0\uc11c\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: \uc5ec\uae30\uc758 PS1 \uac8c\uc784\uc740 \ubcc4\ub3c4 \ud30c\uc77c\ub85c \uc788\ub294 \uba54\ubaa8\ub9ac \ud30c\uc77c\uc5d0\uc11c \uc0ad\uc81c\ud569\ub2c8\ub2e4.\n+  A network catalogue's games are deleted on the PC that serves them.: \ub124\ud2b8\uc6cc\ud06c \uce74\ud0c8\ub85c\uadf8\uc758 \uac8c\uc784\uc740 \uadf8\uac83\uc744 \uc81c\uacf5\ud558\ub294 PC\uc5d0\uc11c \uc0ad\uc81c\ud569\ub2c8\ub2e4.\n+  DELETE FOR GOOD: \uc644\uc804\ud788 \uc0ad\uc81c\n+  DELETE IT: \uc0ad\uc81c\n+  'DELETED %s: %s FREED': '%s \uc0ad\uc81c\ub428: %s \ud655\ubcf4'\n+  'Delete %s from %s? Goes: %s.': '%s\uc744(\ub97c) %s\uc5d0\uc11c \uc0ad\uc81c\ud560\uae4c\uc694? \uc0ac\ub77c\uc9d0: %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': '%s\uc744(\ub97c) %s\uc5d0\uc11c \uc0ad\uc81c\ud560\uae4c\uc694? \uc0ac\ub77c\uc9d0: %s. \ub0a8\uc74c: %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: \uc138\uc774\ube0c\uc640 RIPSHOT \uc0ac\uc9c4\ub3c4 \uc0ad\uc81c\ud560\uae4c\uc694? \uc544\ub2c8\uc694\ub294 \ub0a8\uae41\ub2c8\ub2e4.\n+  HIDDEN GAMES: \uc228\uae34 \uac8c\uc784\n+  'HIDDEN: %s': '\uc228\uae40: %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: '%s\uc744(\ub97c) \uc228\uae38\uae4c\uc694? \ubaa8\ub4e0 \ubaa9\ub85d\uc5d0\uc11c, \ubaa8\ub4e0 \uac8c\uc784\uacfc \uc990\uaca8\ucc3e\uae30\uc5d0\uc11c\ub3c4 \ube60\uc9d1\ub2c8\ub2e4. \uc124\uc815 > \uac8c\uc784 \uc18c\uc2a4 > \uc228\uae34 \uac8c\uc784\uc5d0\uc11c \ub2e4\uc2dc \ubcf4\uc774\uac8c \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.'\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: \ub9c8\uc9c0\ub9c9 \ud655\uc778. %s\uc740(\ub294) \uc644\uc804\ud788 \uc0ad\uc81c\ub418\uba70 \ub418\ub3cc\ub9b4 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uc0ad\uc81c\ud558\ub824\uba74 START\ub97c \ub204\ub974\uc138\uc694.\n+  None. Hide Game, in a game's options, puts one here.: \uc5c6\uc74c. \uac8c\uc784 \uc635\uc158\uc758 \uac8c\uc784 \uc228\uae30\uae30\ub85c \uc5ec\uae30\uc5d0 \ub123\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : '\uc544\ubb34\uac83\ub3c4 \uc0ad\uc81c\ud558\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4: \uc774 \ub4dc\ub77c\uc774\ube0c\uc758 \ud30c\ud2f0\uc158 \ud14c\uc774\ube14\uc774 \uc774 \ub514\uc2a4\ud06c\uc640 \ub9de\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4(%s). \ub2e4\ub978 \ud06c\uae30\uc758 \ub514\uc2a4\ud06c \uc774\ubbf8\uc9c0\ub85c \uae30\ub85d\ub418\uc5c8\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uba3c\uc800 PC\uc5d0\uc11c \uace0\uce58\uc138\uc694.'\n+  RIPPS2 cannot delete a game from this page.: RIPPS2\ub294 \uc774 \ud398\uc774\uc9c0\uc5d0\uc11c \uac8c\uc784\uc744 \uc0ad\uc81c\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.\n+  RIPPS2 could not find this game's files on its drive.: RIPPS2\uac00 \uc774 \uac8c\uc784\uc758 \ud30c\uc77c\uc744 \ud574\ub2f9 \ub4dc\ub77c\uc774\ube0c\uc5d0\uc11c \ucc3e\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.\n+  Reading the hidden lists: \uc228\uae34 \ubaa9\ub85d \uc77d\ub294 \uc911\n+  SHOW AGAIN OR DELETE: \ub2e4\uc2dc \ubcf4\uc774\uae30 \ub610\ub294 \uc0ad\uc81c\n+  SHOW IT AGAIN: \ub2e4\uc2dc \ubcf4\uc774\uae30\n+  That game is not on its drive any more.: \uadf8 \uac8c\uc784\uc740 \ub354 \uc774\uc0c1 \ud574\ub2f9 \ub4dc\ub77c\uc774\ube0c\uc5d0 \uc5c6\uc2b5\ub2c8\ub2e4.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: \uc228\uae34 \ubaa9\ub85d\uc744 \uac8c\uc784\uc758 \ub4dc\ub77c\uc774\ube0c\uc5d0 \uc4f0\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \uc4f0\uae30 \ubc29\uc9c0\ub418\uc5b4 \uc788\uac70\ub098 \uc4f0\uae30 \uc791\uc5c5\uc774 \uaebc\uc838 \uc788\ub098\uc694?\n+  The partition could not be removed (%d). Nothing was deleted.: \ud30c\ud2f0\uc158\uc744 \uc81c\uac70\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4(%d). \uc544\ubb34\uac83\ub3c4 \uc0ad\uc81c\ud558\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.\n+  This drive cannot keep a hidden list (a network catalogue).: \uc774 \ub4dc\ub77c\uc774\ube0c\ub294 \uc228\uae34 \ubaa9\ub85d\uc744 \ubcf4\uad00\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4(\ub124\ud2b8\uc6cc\ud06c \uce74\ud0c8\ub85c\uadf8).\n+  This game's drive has not answered yet. Wait for it, then try again.: \uc774 \uac8c\uc784\uc758 \ub4dc\ub77c\uc774\ube0c\uac00 \uc544\uc9c1 \uc751\ub2f5\ud558\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uae30\ub2e4\ub9b0 \ub4a4 \ub2e4\uc2dc \uc2dc\ub3c4\ud558\uc138\uc694.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: \uc774 \uac8c\uc784\uc758 \ud30c\ud2f0\uc158\uc740 RIPPS2\uac00 \uc81c\uac70\ud558\uc9c0 \uc54a\ub294 \uac83\uc785\ub2c8\ub2e4(\uc2dc\uc2a4\ud15c \ud30c\ud2f0\uc158 \ub610\ub294 \uc124\uc815\uc774 \uc788\ub294 \ud30c\ud2f0\uc158).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': '\ud30c\ud2f0\uc158\uc774 \uc774 \ub514\uc2a4\ud06c\uac00 \ud5c8\uc6a9\ud558\ub294 \uac83\ubcf4\ub2e4 \ud07d\ub2c8\ub2e4: \ud14c\uc774\ube14\uc774 \ub2e4\ub978 \ud06c\uae30\uc758 \ub514\uc2a4\ud06c\uc6a9\uc73c\ub85c \ub9cc\ub4e4\uc5b4\uc84c\uc2b5\ub2c8\ub2e4'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': '\ud30c\ud2f0\uc158\uc774 \uc774 \ub514\uc2a4\ud06c\uc758 \ub05d\uc744 \ub118\uc5b4\uac11\ub2c8\ub2e4(\uc139\ud130 %u / %u): \ud14c\uc774\ube14\uc774 \ub354 \ud070 \ub514\uc2a4\ud06c\uc6a9\uc73c\ub85c \ub9cc\ub4e4\uc5b4\uc84c\uc2b5\ub2c8\ub2e4'\n+  its RIPSHOT pictures: RIPSHOT \uc0ac\uc9c4\n+  its drive: \ud574\ub2f9 \ub4dc\ub77c\uc774\ube0c\n+  its saves: \uc138\uc774\ube0c\n+  sector %u holds no partition header: \uc139\ud130 %u\uc5d0 \ud30c\ud2f0\uc158 \ud5e4\ub354\uac00 \uc5c6\uc2b5\ub2c8\ub2e4\n+  the chain does not close at __mbr: \uccb4\uc778\uc774 __mbr\uc5d0\uc11c \ub2eb\ud788\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4\n+  the chain skips from sector %u: \uccb4\uc778\uc774 \uc139\ud130 %u\uc5d0\uc11c \uac74\ub108\ub701\ub2c8\ub2e4\n+  the chain's links disagree at sector %u: \uccb4\uc778\uc758 \ub9c1\ud06c\uac00 \uc139\ud130 %u\uc5d0\uc11c \ub9de\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4\n+  the drive's size could not be read: \ub4dc\ub77c\uc774\ube0c \ud06c\uae30\ub97c \uc77d\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4\n+  the game (%s): \uac8c\uc784 (%s)\n+  the game's folder and everything in it (%s): \uac8c\uc784 \ud3f4\ub354\uc640 \uadf8 \uc548\uc758 \ubaa8\ub4e0 \uac83 (%s)\n+  the header at sector %u could not be read: \uc139\ud130 %u\uc758 \ud5e4\ub354\ub97c \uc77d\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4\n+  the header at sector %u fails its checksum: \uc139\ud130 %u\uc758 \ud5e4\ub354\uac00 \uccb4\ud06c\uc12c\uc744 \ud1b5\uacfc\ud558\uc9c0 \ubabb\ud569\ub2c8\ub2e4\n+  the partition at sector %u is not where or as large as APA puts one: \uc139\ud130 %u\uc758 \ud30c\ud2f0\uc158\uc774 APA\uac00 \ub450\ub294 \uc704\uce58\ub098 \ud06c\uae30\uc640 \ub2e4\ub985\ub2c8\ub2e4\n+  the settings and art another copy of it uses: \ub2e4\ub978 \uc0ac\ubcf8\uc774 \uc4f0\ub294 \uc124\uc815\uacfc \uc544\ud2b8\n+  '%d FOLDERS': \ud3f4\ub354 %d\uac1c\n+  '%s could not be copied to %s. Is the drive full or write-protected?': '%s\uc744(\ub97c) %s\uc5d0 \ubcf5\uc0ac\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub4dc\ub77c\uc774\ube0c\uac00 \uac00\ub4dd \ucc3c\uac70\ub098 \uc4f0\uae30 \ubc29\uc9c0\ub418\uc5b4 \uc788\ub098\uc694?'\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s\uc774(\uac00) RIPPS2\uac00 \ucc3e\ub294 \uacf3\uc5d0 \uc5c6\uc2b5\ub2c8\ub2e4. RIPPS2\uac00 \uc2dc\uc791\ud55c \ub4dc\ub77c\uc774\ube0c\uc5d0\uc11c \ucc3e\uc744\uae4c\uc694? \ucc3e\uc740 \uc704\uce58\ub294 \uc774\ud6c4 \uc2e4\ud589\ud560 \ub54c\ub9c8\ub2e4 \uae30\uc5b5\ub429\ub2c8\ub2e4.'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Ember\uac00 \uac8c\uc784 \uc606\uc778 %s\uc5d0 \uc5c6\ub294\ub370, \uac70\uae30 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4. RIPPS2\uac00 %s\uc5d0\uc11c \ucc3e\uc558\uc2b5\ub2c8\ub2e4: START\ub97c \ub204\ub974\uba74 \uadf8\uacf3\uc73c\ub85c \ubcf5\uc0ac\ud558\uace0(%d KB) \uac8c\uc784\uc744 \uc2dc\uc791\ud569\ub2c8\ub2e4.'\n+  Ember's BIOS: Ember\uc758 BIOS\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Ember\uc758 BIOS(bios.bin)\uac00 Ember \uc606\uc778 %s\uc5d0 \uc5c6\uc2b5\ub2c8\ub2e4. RIPPS2\uac00 %s\uc5d0\uc11c \ud558\ub098 \ucc3e\uc558\uc2b5\ub2c8\ub2e4: START\ub97c \ub204\ub974\uba74 \uadf8\uacf3\uc73c\ub85c \ubcf5\uc0ac\ud558\uace0(%d KB) \uac8c\uc784\uc744 \uc2dc\uc791\ud569\ub2c8\ub2e4.'\n+  LOOKING FOR YOUR LAUNCHERS: \ub7f0\ucc98 \ucc3e\ub294 \uc911\n+  The POPS files: POPS \ud30c\uc77c\n+  '%s  MMCE card': '%s  MMCE \uce74\ub4dc'\n+  INSTALL RIPPS2'S NEUTRINO: RIPPS2\uc758 NEUTRINO \uc124\uce58\n+  'Internal HDD (APA): %s free of %s': '\ub0b4\uc7a5 HDD (APA): %s \ub0a8\uc74c / \uc804\uccb4 %s'\n+  LAUNCHERS: \ub7f0\ucc98\n+  LOOK FOR THEM: \ucc3e\uae30\n+  'Memory card %d: %d KB free': '\uba54\ubaa8\ub9ac \uce74\ub4dc %d: %d KB \ub0a8\uc74c'\n+  POPS files: POPS \ud30c\uc77c\n+  START EVERY DRIVE: \ubaa8\ub4e0 \ub4dc\ub77c\uc774\ube0c \uc2dc\uc791\n+  'STATUS: looking at the drives and the homebrew': '\uc0c1\ud0dc: \ub4dc\ub77c\uc774\ube0c\uc640 \ud648\ube0c\ub8e8 \ud655\uc778 \uc911'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): \uac80\uc0c9\uc774 \ubaa8\ub450\uc5d0 \ub2ff\ub3c4\ub85d \uba3c\uc800 \ubaa8\ub4e0 \ub4dc\ub77c\uc774\ube0c\ub97c \uc2dc\uc791\ud560\uae4c\uc694? (\uc544\ub2c8\uc694\ub294 \uc774\ubbf8 \uc2dc\uc791\ub41c \ub4dc\ub77c\uc774\ube0c\uc5d0\uc11c \ucc3e\uc2b5\ub2c8\ub2e4.)\n+  USB, HDD, MMCE and the rest: USB, HDD, MMCE \ub4f1\n+  every started drive: \uc2dc\uc791\ub41c \ubaa8\ub4e0 \ub4dc\ub77c\uc774\ube0c\n+  found where you keep them: \ubcf4\uad00\ud55c \uacf3\uc5d0\uc11c \ucc3e\uc74c\n+  not found yet: \uc544\uc9c1 \ucc3e\uc9c0 \ubabb\ud568\n+  not on a started drive: \uc2dc\uc791\ub41c \ub4dc\ub77c\uc774\ube0c\uc5d0 \uc5c6\uc74c\n+  only if you have none: \uc5c6\uc744 \ub54c\ub9cc\n+  starts every drive first: \uba3c\uc800 \ubaa8\ub4e0 \ub4dc\ub77c\uc774\ube0c\ub97c \uc2dc\uc791\ud568\n+  Drives, launchers and homebrew: \ub4dc\ub77c\uc774\ube0c, \ub7f0\ucc98, \ud648\ube0c\ub8e8\n+  BDM Devices: BDM \uc7a5\uce58\n+  Color Sweep: \uc0c9 \uc2a4\uc715\n+  Colors and Text: \uc0c9\uacfc \uae00\uc790\n+  HDD POPS partitions: HDD\uc758 POPS \ud30c\ud2f0\uc158\n+  Layout: \ub808\uc774\uc544\uc6c3\n+  Missing Covers: \ube60\uc9c4 \ucee4\ubc84\n+  Name: \uc774\ub984\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: \uc544\uc9c1 POPSTARTER SMB \uc124\uc815\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc544\ub798 \uc904\uc744 \ucc44\uc6b0\uc138\uc694.\n+  POPSTARTER Network: POPSTARTER \ub124\ud2b8\uc6cc\ud06c\n+  POPSTARTER SMB Network: POPSTARTER SMB \ub124\ud2b8\uc6cc\ud06c\n+  POPSTARTER UDPFS Network: POPSTARTER UDPFS \ub124\ud2b8\uc6cc\ud06c\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: \ub4dc\ub77c\uc774\ube0c, ISO, VMC, \ube44\ub514\uc624 \ubaa8\ub4dc\ub294 RIPPS2\uac00 \uc9c1\uc811 \ubd99\uc785\ub2c8\ub2e4.\n+  Screen: \ud654\uba74\n+  Speed: \uc18d\ub3c4\n+  Sweep Colors: \uc2a4\uc715 \uc0c9\n+  Text Weight: \uae00\uc790 \uad75\uae30\n+  Theme Color: \ud14c\ub9c8 \uc0c9\n+  Audio: \uc624\ub514\uc624\n+  Bold: \uad75\uac8c\n+  Controllers: \ucee8\ud2b8\ub864\ub7ec\n+  Lively: \uacbd\ucf8c\ud558\uac8c\n+  Medium: \uc911\uac04\n+  PS Emulation: PS \uc5d0\ubbac\ub808\uc774\uc158\n+  Regular: \ubcf4\ud1b5\n+  Slow: \ucc9c\ucc9c\ud788\n+  A MOMENT: \uc7a0\uc2dc\ub9cc\uc694\n+  A TASK IN THE BACKGROUND: \ubc31\uadf8\ub77c\uc6b4\ub4dc \uc791\uc5c5\n+  ABOUT %d S: \uc57d %d\ucd08\n+  ANY MOMENT: \uace7\n+  READING THE GAME LIST OF %s: '%s\uc758 \uac8c\uc784 \ubaa9\ub85d\uc744 \uc77d\ub294 \uc911'\n+  SAVING A LIST: \ubaa9\ub85d \uc800\uc7a5 \uc911\n+  START NOW: \uc9c0\uae08 \uc2dc\uc791\n+  STARTING: \uc2dc\uc791\ud558\ub294 \uc911\n+  STARTS NEXT: \ub2e4\uc74c\uc5d0 \uc2dc\uc791\n+  STOPPING IT SAFELY: \uc548\uc804\ud558\uac8c \uba48\ucd94\ub294 \uc911\n+  THE LAST OF THE QUEUE: \ub300\uae30\uc5f4\uc758 \ub05d\n+  WHEN IT FINISHES: \ub05d\ub098\uba74\n+  What each card starts; install or repair BIBLE.: \uac01 \uce74\ub4dc\uac00 \uc2dc\uc791\ud558\ub294 \uac83; BIBLE\uc744 \uc124\uce58\ud558\uac70\ub098 \uace0\uce69\ub2c8\ub2e4.\n+  Rainbow: \ubb34\uc9c0\uac1c\n+  Custom: \uc0ac\uc6a9\uc790 \uc9c0\uc815\n+  DESR DVR ISOs (experimental): DESR DVR ISO (\uc2e4\ud5d8\uc801)\n+  PC CARD CF (experimental): PC CARD CF (\uc2e4\ud5d8\uc801)\n+  HDD Start Mode: HDD \uc2dc\uc791 \ubaa8\ub4dc\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): The Roboto Project Authors\uc758 Roboto Condensed (SIL OFL 1.1)\n+  '%s on %s': '%s, %s\uc5d0 \uc788\uc74c'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: %s\uc5d0\uc11c %s\ub85c (%d%%), \ubaa8\ub4e0 \uc870\uac01\uc744 \ud655\uc778\ud588\uc2b5\ub2c8\ub2e4. ISO\ub97c \uc9c0\uc6b0\uace0 ZSO\ub97c \ub0a8\uae38\uae4c\uc694?'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: %s\uc5d0\uc11c \ub2e4\uc2dc %s\ub85c, \ubaa8\ub4e0 \uc870\uac01\uc744 \ud655\uc778\ud588\uc2b5\ub2c8\ub2e4. ZSO\ub97c \uc9c0\uc6b0\uace0 ISO\ub97c \ub0a8\uae38\uae4c\uc694?'\n+  A ZSO of this game is already beside it. Remove one of the two first.: \uc774 \uac8c\uc784\uc758 ZSO\uac00 \uc774\ubbf8 \uc606\uc5d0 \uc788\uc2b5\ub2c8\ub2e4. \ub458 \uc911 \ud558\ub098\ub97c \uba3c\uc800 \uc9c0\uc6b0\uc138\uc694.\n+  About %d min, then checked: \uc57d %d\ubd84, \uadf8\ub2e4\uc74c \ud655\uc778\n+  An ISO of this game is already beside it. Remove one of the two first.: \uc774 \uac8c\uc784\uc758 ISO\uac00 \uc774\ubbf8 \uc606\uc5d0 \uc788\uc2b5\ub2c8\ub2e4. \ub458 \uc911 \ud558\ub098\ub97c \uba3c\uc800 \uc9c0\uc6b0\uc138\uc694.\n+  CHECKING EVERY PIECE: \ubaa8\ub4e0 \uc870\uac01 \ud655\uc778 \uc911\n+  COMPRESS TO ZSO: ZSO\ub85c \uc555\ucd95\n+  'COMPRESSED %s: %s FREED': '%s \uc555\ucd95\ub428: %s \ud655\ubcf4'\n+  COMPRESSING 1 OF 2: \uc555\ucd95 \uc911 1/2\n+  COMPRESSING 2 OF 2: \uc555\ucd95 \uc911 2/2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : '%s\uc744(\ub97c) \uc555\ucd95\ud560\uae4c\uc694? ZSO\ub97c \ub2e4\uc2dc \uc77d\uc5b4 \ud655\uc778\ud560 \ub54c\uae4c\uc9c0 ISO\ub294 \ub0a8\uc544 \uc788\uace0, \uadf8\ub2e4\uc74c RIPPS2\uac00 ISO\ub97c \uc9c0\uc6b0\uae30 \uc804\uc5d0 \ubb3b\uc2b5\ub2c8\ub2e4. \uc791\uc5c5\ud558\ub294 \ub3d9\uc548 \ub4dc\ub77c\uc774\ube0c\uc5d0 \ub458 \ub2e4 \ub4e4\uc5b4\uac08 \uacf5\uac04\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. \ub05d\ub0a0 \ub54c\uae4c\uc9c0 \ubcf8\uccb4\ub97c \ucf1c \ub450\uc138\uc694. ZSO\ub294 OPL \ucf54\uc5b4\ub85c\ub9cc \uc2dc\uc791\ud569\ub2c8\ub2e4 (Neutrino \ubd88\uac00). \uc138\uc774\ube0c, \uc124\uc815, \uc774\ubbf8\uc9c0, \uce58\ud2b8\ub294 \uadf8\ub300\ub85c \ub530\ub77c\uac00\uba70 \uc5b8\uc81c\ub4e0 \ub2e4\uc2dc ISO\ub85c \ub9cc\ub4e4 \uc218 \uc788\uc2b5\ub2c8\ub2e4.'\n+  Compress to ZSO: ZSO\ub85c \uc555\ucd95\n+  Cycle: \uc21c\ud658\n+  'Experimental: OPL core only (not Neutrino)': '\uc2e4\ud5d8\uc801: OPL \ucf54\uc5b4\ub9cc (Neutrino \ubd88\uac00)'\n+  Hold SELECT: SELECT \uae38\uac8c \ub204\ub974\uae30\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: \ub05d\ub0b4\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4 (%s). \uc4f4 \uac83\uc740 \uc9c0\uc6e0\uc73c\uba70 \uac8c\uc784\uc740 \uadf8\ub300\ub85c\uc785\ub2c8\ub2e4.\n+  List: \ubaa9\ub85d\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : '%s\uc744(\ub97c) \ub2e4\uc2dc ISO\ub85c \ub9cc\ub4e4\uae4c\uc694? ISO\ub97c \ub2e4\uc2dc \uc77d\uc5b4 \ud655\uc778\ud560 \ub54c\uae4c\uc9c0 ZSO\ub294 \ub0a8\uc544 \uc788\uace0, \uadf8\ub2e4\uc74c RIPPS2\uac00 ZSO\ub97c \uc9c0\uc6b0\uae30 \uc804\uc5d0 \ubb3b\uc2b5\ub2c8\ub2e4. \uc791\uc5c5\ud558\ub294 \ub3d9\uc548 \ub4dc\ub77c\uc774\ube0c\uc5d0 \ub458 \ub2e4 \ub4e4\uc5b4\uac08 \uacf5\uac04\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. \ub05d\ub0a0 \ub54c\uae4c\uc9c0 \ubcf8\uccb4\ub97c \ucf1c \ub450\uc138\uc694. \uc138\uc774\ube0c, \uc124\uc815, \uc774\ubbf8\uc9c0, \uce58\ud2b8\ub294 \uadf8\ub300\ub85c \ub530\ub77c\uac11\ub2c8\ub2e4.'\n+  Stop? What it has written so far is removed; the game stays as it was.: \uc911\uc9c0\ud560\uae4c\uc694? \uc9c0\uae08\uae4c\uc9c0 \uc4f4 \uac83\uc740 \uc9c0\uc6cc\uc9c0\uace0 \uac8c\uc784\uc740 \uadf8\ub300\ub85c \ub0a8\uc2b5\ub2c8\ub2e4.\n+  THEMES: \ud14c\ub9c8\n+  There is not enough memory for this just now.: \uc9c0\uae08\uc740 \uc774 \uc791\uc5c5\uc5d0 \ud544\uc694\ud55c \uba54\ubaa8\ub9ac\uac00 \ubd80\uc871\ud569\ub2c8\ub2e4.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: \uc774 \uc774\ubbf8\uc9c0\ub294 RIPPS2\uac00 \uc555\ucd95\ud558\uac70\ub098 \ud480 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4 (\ud06c\uae30\uac00 2 KB \ube14\ub85d\uc758 \uc815\uc218\ubc30\uac00 \uc544\ub2d9\ub2c8\ub2e4).\n+  UNCOMPRESS TO ISO: ISO\ub85c \uc555\ucd95 \ud574\uc81c\n+  UNCOMPRESSED %s: '%s \uc555\ucd95 \ud574\uc81c\ub428'\n+  UNCOMPRESSING: \uc555\ucd95 \ud574\uc81c \uc911\n+  Uncompress to ISO: ISO\ub85c \uc555\ucd95 \ud574\uc81c\n+  Under a minute, then checked: 1\ubd84 \ubbf8\ub9cc, \uadf8\ub2e4\uc74c \ud655\uc778\n+  COPYING: \ubcf5\uc0ac \uc911\n+  SHOW AGAIN, DELETE OR COMPRESS: \ub2e4\uc2dc \ubcf4\uc774\uae30, \uc0ad\uc81c \ub610\ub294 \uc555\ucd95\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: \ub2e4\uc2dc \ubcf4\uc774\uae30, \uc0ad\uc81c \ub610\ub294 \uc555\ucd95 \ud574\uc81c\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': '%s \uc791\uc5c5 \uc911\uc5d0\ub294 \ub4dc\ub77c\uc774\ube0c\uc5d0 \uc57d %s\uc758 \uc5ec\uc720 \uacf5\uac04\uc774 \ud544\uc694\ud569\ub2c8\ub2e4 (\uc6d0\ubcf8\uc740 \ub05d\uae4c\uc9c0 \ub0a8\uc2b5\ub2c8\ub2e4). \ub4dc\ub77c\uc774\ube0c\uc758 \uc5ec\uc720 \uacf5\uac04\uc740 %s\uc785\ub2c8\ub2e4.'\n+  Hide and Compress: \uc228\uae30\uace0 \uc555\ucd95\n+  'COMPRESSED AND HIDDEN: %s': '\uc555\ucd95 \ud6c4 \uc228\uae40: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': '\uc774 \ub9bd\uc744 \uc704\ud55c \uacf5\uac04\uc774 \uc5c6\uc2b5\ub2c8\ub2e4: \uc57d %s\uc774(\uac00) \ud544\uc694\ud558\uace0, \ub4dc\ub77c\uc774\ube0c\uc758 \uc5ec\uc720 \uacf5\uac04\uc740 %s\uc785\ub2c8\ub2e4.'\n+  OVER 4 GB FOR FAT32: FAT32\uc5d0\ub294 4 GB \ucd08\uacfc\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: \uc774 \ub4dc\ub77c\uc774\ube0c\ub294 %s\ub77c\uc11c 4 GB \uc774\uc0c1\uc758 \ud30c\uc77c\uc744 \ub2f4\uc744 \uc218 \uc5c6\uc73c\uba70, \uc0c8 \uc774\ubbf8\uc9c0\ub294 %s\uac00 \ub429\ub2c8\ub2e4.\n+  '%s, %s clusters, %s free of %s': '%s, \ud074\ub7ec\uc2a4\ud130 %s, \uc5ec\uc720 %s / %s'\n+  Delete This Sweep: \uc774 \uc2a4\uc715 \uc0ad\uc81c\n+  Delete %s? RIPPS2's own sweeps stay as they are.: '%s\uc744(\ub97c) \uc0ad\uc81c\ud560\uae4c\uc694? RIPPS2 \uc790\uccb4 \uc2a4\uc715\uc740 \uadf8\ub300\ub85c \ub0a8\uc2b5\ub2c8\ub2e4.'\n+  Sweep: \uc2a4\uc715\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: \ubc30\uc9c0\ub294 \ubaa9\ub85d\uc5d0\uc11c \uc5c5\uc801\uc774 \uc788\ub294 \uac8c\uc784\uacfc \uc7a0\uae08 \ud574\uc81c\ud55c \uc218\ub97c \ud45c\uc2dc\ud569\ub2c8\ub2e4. \ud154\ub808\uba54\ud2b8\ub9ac\ub97c \ucf1c\uc57c \ud569\ub2c8\ub2e4.\n labels:\n   IP_ADDRESS_TYPE_STATIC: \uace0\uc815\n   HINT_NEUTRINO_AUTO_ELF: \uac8c\uc784\uc758 \ubd80\ud2b8 ELF\ub97c Neutrino\uc5d0 \ub118\uaca8 \ud0c0\uc774\ud2c0\ubcc4 \uc124\uc815\uc744 \ucc3e\uac8c \ud569\ub2c8\ub2e4. \uae30\ubcf8\uc73c\ub85c \ucf1c\uc838 \uc788\uc73c\uba70, \uc9c1\uc811 \uc124\uc815\ud55c Boot ELF \uc635\uc158\uc774 \uc6b0\uc120\ud569\ub2c8\ub2e4.\n@@ -1201,7 +1368,7 @@ labels:\n   SAVE_ICONS: 3D \uc138\uc774\ube0c \uc544\uc774\ucf58\n   HINT_SAVE_ICONS: \ud14c\ub9c8\uc5d0 \uc790\ub9ac\uac00 \uc788\uc73c\uba74 \uace0\ub978 \uac8c\uc784\uc758 \uc138\uc774\ube0c \uc544\uc774\ucf58\uc744 \ud68c\uc804\uc2dc\ucf1c \ubcf4\uc5ec \uc90d\ub2c8\ub2e4. \uba3c\uc800 \uac8c\uc784 \uc790\uccb4\uc758 VMC\ub97c, \ub2e4\uc74c\uc5d0 \uba54\ubaa8\ub9ac \uce74\ub4dc\ub97c \uc77d\uc2b5\ub2c8\ub2e4.\n   NETBOOT_RESTART_NOW: \ub124\ud2b8\uc6cc\ud06c \ud504\ub85c\ud1a0\ucf5c\uc740 \uc7ac\uc2dc\uc791 \ud6c4\uc5d0 \ubc14\ub01d\ub2c8\ub2e4. \uc9c0\uae08 \uc124\uc815\uc744 \uc800\uc7a5\ud558\uace0 OPL\uc744 \uc7ac\uc2dc\uc791\ud560\uae4c\uc694?\n-  HINT_NET_PROTOCOL: 'RiptOPL\uc774 \uc4f0\ub294 \ub124\ud2b8\uc6cc\ud06c \uc11c\ube44\uc2a4: SMB \uacf5\uc720, UDPFS, UDPBD, HTTP. \uac8c\uc784 \uc18c\uc2a4\uc758 \uc124\uc815\uacfc \uac19\uc2b5\ub2c8\ub2e4. \uc774\ubbf8 \uc2e4\ud589 \uc911\uc778 \uac83\uc5d0\uc11c \ubc14\uafb8\ub824\uba74 \uc7ac\uc2dc\uc791\ud574\uc57c \ud569\ub2c8\ub2e4.'\n+  HINT_NET_PROTOCOL: 'RiptOPL\uc774 \uc4f0\ub294 \ub124\ud2b8\uc6cc\ud06c \uc11c\ube44\uc2a4: SMB \uacf5\uc720, UDPFS, UDPBD, HTTP. \uc774\ubbf8 \uc2e4\ud589 \uc911\uc778 \uac83\uc5d0\uc11c \ubc14\uafb8\ub824\uba74 \uc7ac\uc2dc\uc791\ud574\uc57c \ud569\ub2c8\ub2e4.'\n   HINT_UDPFS_ACCESS: UDPFS \uc804\uc6a9. \ud30c\uc77c\uc740 \uc11c\ubc84\uc758 \uac8c\uc784 \ud3f4\ub354\uc5d0\uc11c \ub0b1\uac1c ISO\ub97c \uc77d\uace0, IMG\ub294 \ub514\uc2a4\ud06c \uc774\ubbf8\uc9c0\ub97c \ub4dc\ub77c\uc774\ube0c\ub85c \uc77d\uc2b5\ub2c8\ub2e4. SMB\uc640 HTTP\ub294 \ud56d\uc0c1 \ud30c\uc77c, UDPBD\ub294 \ud56d\uc0c1 IMG\ub97c \uc501\ub2c8\ub2e4.\n   HINT_SMB_VERSION: PC \uacf5\uc720\uc640 \ud1b5\uc2e0\ud560 SMB \ubc84\uc804.\n   HINT_NET_ADVANCED: \uc774\ub354\ub137 \uc5f0\uacb0 \ubaa8\ub4dc\uc640 SMB \ud3ec\ud2b8\ub97c \ud3b8\uc9d1\ud560 \uc218 \uc788\uac8c \ud569\ub2c8\ub2e4.\n@@ -1400,3 +1567,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: \uac01 \uac8c\uc784 \ud544\ub984\uc5d0\uc11c \uae30\ub2e4\ub9ac\ub294 \uc0ac\uc9c4\uc774 PNG\uac00 \ub418\ub294 \ubc29\ubc95.\n   RIPPS2_SETTINGS_HOME: \uc124\uc815 \uc800\uc7a5 \uc704\uce58\n   HINT_RIPPS2_SETTINGS_HOME: 'RIPPS2\uac00 \uc124\uc815\uc744 \ub450\ub294 \uacf3: \uba54\ubaa8\ub9ac \uce74\ub4dc\uc758 \uc790\uccb4 \uc800\uc7a5 \ub370\uc774\ud130, \ub4dc\ub77c\uc774\ube0c(\ub0b4\uc7a5 HDD, USB, MX4SIO, iLink \ub610\ub294 MMCE)\uc758 RIPPS2 \ud3f4\ub354, \ub610\ub294 \uc9c1\uc811 \uc785\ub825\ud55c \ud3f4\ub354.'\n+  RIPPS2_LAUNCHERS: \ub7f0\ucc98\n+  RIPPS2_LOOK_NOW: \uc9c0\uae08 \ucc3e\uae30\n+  HINT_RIPPS2_LOOK_NOW: RIPPS2\ub294 \uc2dc\uc791\ub41c \ub4dc\ub77c\uc774\ube0c\uc5d0\uc11c POPSTARTER, POPS \ud30c\uc77c, Ember, Ember\uc758 BIOS, Neutrino\ub97c \ucc3e\uace0 \uac01\uac01\uc758 \uc704\uce58\ub97c \uae30\uc5b5\ud569\ub2c8\ub2e4. \uc2e4\ud589\ud560 \ub54c\ub9c8\ub2e4 \uac70\uae30\uc11c \uc77d\uc2b5\ub2c8\ub2e4.\n+  RIPPS2_HIDE_GAME: \uac8c\uc784 \uc228\uae30\uae30\n+  RIPPS2_HIDDEN_GAMES: \uc228\uae34 \uac8c\uc784\n+  HINT_RIPPS2_HIDDEN_GAMES: \uac8c\uc784 \uc228\uae30\uae30\ub85c \uc228\uae34, \uc2dc\uc791\ub41c \ub4dc\ub77c\uc774\ube0c\uc758 \uac8c\uc784\uc785\ub2c8\ub2e4. \ud558\ub098\ub97c \ub2e4\uc2dc \ubcf4\uc774\uac8c \ud558\uac70\ub098 \uc0ad\uc81c\ud558\uc138\uc694. \ub4dc\ub77c\uc774\ube0c\ub9c8\ub2e4 \uc790\uae30 \ubaa9\ub85d\uc744 \uac00\uc9d1\ub2c8\ub2e4.\n+  HINT_RIPPS2_BDM_DEVICES: 'BDM\uc774 \uac8c\uc784\uc744 \uc77d\ub294 \ub4dc\ub77c\uc774\ube0c: USB, MX4SIO, PC Card CF, DESR\uc758 DVR \ub4dc\ub77c\uc774\ube0c, iLink. \ub0b4\uc7a5 HDD\ub294 HDD \uc2dc\uc791 \ubaa8\ub4dc\ub97c \ub530\ub985\ub2c8\ub2e4.'\n+  HINT_RIPPS2_SCREEN: '\uba54\ub274\uc758 \ud654\uba74: \ube44\ub514\uc624 \ubaa8\ub4dc, \uc640\uc774\ub4dc\uc2a4\ud06c\ub9b0, TV\uc5d0\uc11c\uc758 \uc704\uce58(H-POS, V-POS)\uc640 \uc624\ubc84\uc2a4\uce94.'\n+  HINT_RIPPS2_LAYOUT: \uce74\ud14c\uace0\ub9ac\uc758 \uc704\uce58\uc640 \ud398\uc774\uc9c0\uac00 \ub4e4\uc5b4\uc624\ub294 \ubc29\uc2dd, \uadf8\ub9ac\uace0 RIPFLOW\uc758 \ucee4\ubc84\uc640 RIPGRID\uc758 \uaca9\uc790.\n+  HINT_RIPPS2_COLORS_TEXT: \uae00\uc790 \uad75\uae30, \uc0c9 \ud14c\ub9c8, \uc0c9 \uc2a4\uc715\uacfc \ubaa8\ub4e0 \uc0c9.\n+  HINT_RIPPS2_TEXT_WEIGHT: 'RIPPS2 \uc790\uccb4 \uae00\uc790\uc758 \uad75\uae30: \uc124\uc815\uc758 \ub3c4\uc6c0\ub9d0, \uc0c1\ud0dc, \uc815\ubcf4. \ubcf4\ud1b5\uc740 \uc608\uc804 \uadf8\ub300\ub85c\uc774\uace0, \uc911\uac04\uacfc \uad75\uac8c\ub294 \ubd80\ub4dc\ub7ec\uc6b4 \ud654\uba74\uc5d0\uc11c \ub354 \uc798 \uc77d\ud799\ub2c8\ub2e4.'\n+  HINT_RIPPS2_HDD_START: \ub0b4\uc7a5 HDD\ub294 APA\ub4e0 exFAT\ub4e0 RIPPS2\uac00 \ubb34\uc5c7\uc774\ub4e0 \uc77d\uc5b4 HDD \uac8c\uc784\uc73c\ub85c \ud55c \ubc88\ub9cc \ubcf4\uc5ec \uc90d\ub2c8\ub2e4. \ub044\uae30\ub294 \uadf8\ub300\ub85c \ub461\ub2c8\ub2e4.\n+  HINT_RIPPS2_THEME_COLOR: \uc0c9 \ud14c\ub9c8 \uc790\uccb4\uc758 \uc0c9. \uc0c9 \ud14c\ub9c8\uc5d0\uc11c \uc0ac\uc6a9\uc790 \uc9c0\uc815\uc744 \uace0\ub974\uba74 \uae00\uc790 \uc0c9\ucc98\ub7fc \uc9c1\uc811 \uace0\ub97c \uc218 \uc788\uc2b5\ub2c8\ub2e4.\n+  HINT_RIPPS2_SWEEP: \ud14c\ub9c8\uac00 \ucc9c\ucc9c\ud788 \ubd80\ub4dc\ub7fd\uac8c \uc9c0\ub098\uac00\ub294 \ub2e4\uc12f \uac00\uc9c0 \uc0c9. \ubb34\uc9c0\uac1c, \uac01 \uc0c9 \ud14c\ub9c8\ub85c \ub9cc\ub4e0 \uc2a4\uc715, \ub610\ub294 \uc9c1\uc811 \ub9cc\ub4e0 \uac83. \ub044\uae30\ub294 \uc0c9 \ud14c\ub9c8\ub97c \uadf8\ub300\ub85c \ub461\ub2c8\ub2e4.\n+  HINT_RIPPS2_SWEEP_COLORS: \uc2a4\uc715\uc758 \ub2e4\uc12f \uc0c9\uacfc \uc18d\ub3c4. RIPPS2\uc758 \uac83\uc744 \ubc14\uafb8\uba74 \ubd99\uc778 \uc774\ub984\uc73c\ub85c \ub0b4 \uac83\uc73c\ub85c \ub0a8\uc2b5\ub2c8\ub2e4.\n+  HINT_RIPPS2_SWEEP_NAME: \uc0c9 \uc2a4\uc715 \ubaa9\ub85d\uc5d0\uc11c \ub0b4 \uc2a4\uc715\uc758 \uc774\ub984.\n+  HINT_RIPPS2_SWEEP_COLOR: \uc2a4\uc715\uc774 \ucc28\ub840\ub85c \uc9c0\ub098\uac00\ub294 \ub2e4\uc12f \uc0c9 \uc911 \ud558\ub098.\n+  HINT_RIPPS2_SWEEP_SPEED: \ub2e4\uc74c \uc0c9\uc774 \ub4e4\uc5b4\uc624\uae30 \uc804\uae4c\uc9c0 \uac01 \uc0c9\uc774 \uba38\ubb34\ub294 \uc2dc\uac04. \uacbd\ucf8c\ud558\uac8c\ub3c4 \ubd80\ub4dc\ub7fd\uace0, \uc0c9\uc774 \uae5c\ubc15\uc774\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.\n+  RIPPS2_HIDE_COMPRESS: \uac8c\uc784 \uc228\uae30\uae30 \ubc0f \uc555\ucd95\n+  HINT_RIPPS2_SELECT_HOLD: '\uac8c\uc784 \ubaa9\ub85d\uc5d0\uc11c SELECT\ub97c 4.2\ucd08 \ub204\ub984. \ubaa9\ub85d: \ud328\ub110\uc5d0 \ud14c\ub9c8\ub97c \ub744\uc6cc \ud558\ub098 \uace0\ub974\uae30. \uc21c\ud658: \ub2e4\uc74c \ud14c\ub9c8. \ub044\uae30: \uc544\ubb34\uac83\ub3c4 \uc548 \ud568 (\ub204\ub974\uba74 \uc0c8\ub85c\uace0\uce68\uc740 \ub428).'"
        },
        {
          "sha": "22f5856ce026ada835fb09a26d2be6b7c4732336",
          "filename": "elf/lang/Laotian.yml",
          "status": "modified",
          "additions": 209,
          "deletions": 22,
          "changes": 231,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FLaotian.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FLaotian.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FLaotian.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: Laotian\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTER \u0ec0\u0e9e\u0eb7\u0ec8\u0ead\u0e8d\u0ead\u0ea1\u0eae\u0eb1\u0e9a, ESC \u0ec0\u0e9e\u0eb7\u0ec8\u0ead\u0e8d\u0ebb\u0e81\u0ec0\u0ea5\u0eb5\u0e81. \u0e9b\u0eb8\u0ec8\u0ea1\u0ec3\u0e94\u0e81\u0ecd\u0ec4\u0e94\u0ec9 \u0eaa\u0eb0\u0ec1\u0e94\u0e87\u0e9b\u0eb8\u0ec8\u0ea1\u0e95\u0ec8\u0eb2\u0e87\u0ec6.\n   USB KEYBOARD: \u0ec1\u0e9b\u0ec9\u0e99\u0e9e\u0eb4\u0ea1 USB\n-  '      DESR DVR ISOs (experimental)': '      \u0ec4\u0e9f\u0ea5\u0ecc ISO \u0e82\u0ead\u0e87 DESR DVR (\u0e97\u0ebb\u0e94\u0ea5\u0ead\u0e87)'\n-  '      Internal HDD (exFAT)': '      HDD \u0e9e\u0eb2\u0e8d\u0ec3\u0e99 (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (\u0e97\u0ebb\u0e94\u0ea5\u0ead\u0e87)'\n-  '      Protocol': '      \u0ec2\u0e9b\u0ec2\u0e95\u0e84\u0ead\u0e99'\n   Access: \u0e81\u0eb2\u0e99\u0ec0\u0e82\u0ebb\u0ec9\u0eb2\u0ec0\u0e96\u0eb4\u0e87\n   Applications Page Start Mode: \u0ec2\u0edd\u0e94\u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0e95\u0ebb\u0ec9\u0e99 \u0edc\u0ec9\u0eb2\u0ec2\u0e9b\u0ea3\u0ec1\u0e81\u0ea3\u0ea1\n   Art Delay: \u0e84\u0ea7\u0eb2\u0ea1\u0e8a\u0ec9\u0eb2 \u0e82\u0ead\u0e87\u0eae\u0eb9\u0e9a\u0e9e\u0eb2\u0e9a\n@@ -22,7 +18,6 @@ strings:\n   Fifth: \u0e97\u0eb5\u0eab\u0ec9\u0eb2\n   First: \u0e97\u0eb5\u0edc\u0eb6\u0ec8\u0e87\n   Fourth: \u0e97\u0eb5\u0eaa\u0eb5\u0ec8\n-  HDD (APA) Start Mode: \u0ec2\u0edd\u0e94\u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0e95\u0ebb\u0ec9\u0e99 HDD (APA)\n   HDD Cache: \u0ec1\u0e84\u0e8a HDD\n   HDD Spindown: \u0e81\u0eb2\u0e99\u0ea2\u0eb8\u0e94\u0edd\u0eb9\u0e99 HDD\n   MMCE Settings: \u0e81\u0eb2\u0e99\u0e95\u0eb1\u0ec9\u0e87\u0e84\u0ec8\u0eb2 MMCE\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: \u0e99\u0eb5\u0ec9\u0ec1\u0ea1\u0ec8\u0e99\u0ec0\u0e81\u0ea1 PS2 \u0e97\u0eb5\u0ec8\u0ec1\u0e9e\u0eb1\u0e94\u0e94\u0ec9\u0ea7\u0e8d ESR. \u0ea7\u0eb2\u0e87 ESR (esr*.elf) \u0ec3\u0e99 APPS, BOOT \u0eab\u0ebc\u0eb7 ESR \u0ec0\u0e97\u0eb4\u0e87\u0ec0\u0ea1\u0ea1\u0ec2\u0ea1\u0ea3\u0eb5\u0e81\u0eb2\u0e94 \u0eab\u0ebc\u0eb7 USB \u0ec0\u0e9e\u0eb7\u0ec8\u0ead\u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0ea1\u0eb1\u0e99.\n   '%s, BUILD %d': '%s, \u0e9a\u0eb4\u0ea7 %d'\n   '... and the anonymous ...': '... \u0ec1\u0ea5\u0eb0 \u0e9c\u0eb9\u0ec9\u0e97\u0eb5\u0ec8\u0e9a\u0ecd\u0ec8\u0ead\u0ead\u0e81\u0e99\u0eb2\u0ea1 ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': '1.8.0 \u0ea1\u0eb2\u0e9e\u0ec9\u0ead\u0ea1 \u0ec2\u0e94\u0e8d\u0e9a\u0ecd\u0ec8\u0ec4\u0e94\u0ec9\u0e94\u0eb1\u0e94\u0ec1\u0e9b\u0e87 (AFL-3.0): github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: \u0e81\u0eb2\u0e99\u0ec1\u0e8d\u0e81 \u0ec1\u0ea5\u0eb0 \u0eaa\u0ec9\u0eb2\u0e87 RiptOPL \u0e84\u0eb7\u0e99\u0ec3\u0edd\u0ec8, \u0eaa\u0eb2\u0e82\u0eb2\u0e82\u0ead\u0e87 Open PS2 Loader,\n   ABOUT: \u0e81\u0ec8\u0ebd\u0ea7\u0e81\u0eb1\u0e9a\n   'ACHIEVEMENTS: SPIRIT OF RA': '\u0e84\u0ea7\u0eb2\u0ea1\u0eaa\u0eb3\u0ec0\u0ea5\u0eb1\u0e94: SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): \u0ea7\u0eb4\u0e94\u0eb5\u0ec2\u0ead AVI \u0ec3\u0e99 \u0ec4\u0e9f\u0ea5\u0ecc\u0ec3\u0e99\u0edc\u0ec8\u0ea7\u0e8d\u0e84\u0ea7\u0eb2\u0ea1\u0e88\u0eb3, \u0eaa\u0eb2\u0e82\u0eb2\u0e82\u0ead\u0e87 RIPPS2 (AFL 2.0)\n   Apollo Save Tool by bucanero: Apollo Save Tool \u0ec2\u0e94\u0e8d bucanero\n   BACK: \u0e81\u0eb1\u0e9a\u0e84\u0eb7\u0e99\n+  Back (keeps both): \u0e81\u0eb1\u0e9a\u0e84\u0eb7\u0e99 (\u0ec0\u0e81\u0eb1\u0e9a\u0ec4\u0ea7\u0ec9\u0e97\u0eb1\u0e87\u0eaa\u0ead\u0e87)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: \u0e9a\u0eb1\u0e94 MMCE \u0e99\u0eb5\u0ec9\u0e9b\u0ec8\u0ebd\u0e99\u0e8a\u0eb7\u0ec8\u0ec4\u0e9f\u0ea5\u0ecc\u0e9a\u0ecd\u0ec8\u0ec4\u0e94\u0ec9, \u0ec0\u0e8a\u0eb4\u0ec8\u0e87\u0e81\u0eb2\u0e99\u0e9a\u0eb5\u0e9a\u0ead\u0eb1\u0e94\u0e95\u0ec9\u0ead\u0e87\u0ec3\u0e8a\u0ec9\u0ec3\u0e99\u0e82\u0eb1\u0ec9\u0e99\u0e95\u0ead\u0e99\u0eaa\u0eb8\u0e94\u0e97\u0ec9\u0eb2\u0e8d. \u0ead\u0eb1\u0e9a\u0ec0\u0e94\u0e94\u0ec0\u0e9f\u0eb5\u0ea1\u0ec1\u0ea7 sd2psXtd \u0e82\u0ead\u0e87\u0ea1\u0eb1\u0e99\u0ec0\u0e9b\u0eb1\u0e99 1.4.0 \u0eab\u0ebc\u0eb7 \u0ec3\u0edd\u0ec8\u0e81\u0ea7\u0ec8\u0eb2 (sd2psxtd.github.io/download), \u0ec1\u0ea5\u0ec9\u0ea7\u0ea5\u0ead\u0e87\u0ec3\u0edd\u0ec8. \u0e9a\u0ecd\u0ec8\u0ea1\u0eb5\u0eab\u0e8d\u0eb1\u0e87\u0e96\u0eb7\u0e81\u0e9b\u0ec8\u0ebd\u0e99.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): BIBLE \u0ec1\u0ea1\u0ec8\u0e99 PS2BBL 1.2.0, \u0e9a\u0eb4\u0ea7\u0e97\u0eb5\u0ec8\u0ec0\u0e8a\u0eb1\u0e99\u0ec1\u0ea5\u0ec9\u0ea7 \u0e82\u0ead\u0e87 El Isra (\u0e81\u0eb2\u0e94 \u0ec1\u0ea5\u0eb0 HDD), \u0e9a\u0ecd\u0ec8\u0ec4\u0e94\u0ec9\u0e9b\u0ec8\u0ebd\u0e99\u0ec1\u0e9b\u0e87 (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: \u0eaa\u0ebd\u0e87 BIOS > \u0e96\u0ead\u0e94 \u0eab\u0ebc\u0eb4\u0ec9\u0e99\u0e9e\u0ea7\u0e81\u0ea1\u0eb1\u0e99\u0e84\u0eb7\u0e81\u0eb1\u0e9a SPU2; \u0e9a\u0ecd\u0ec8\u0ea1\u0eb5 BIOS \u0ea1\u0eb2\u0e9e\u0ec9\u0ead\u0ea1\n   BOOT LOCK AND BIBLE: BOOT LOCK \u0ec1\u0ea5\u0eb0 BIBLE\n   BOOT LOCK edits its config too: BOOT LOCK \u0ec1\u0e81\u0ec9\u0ec4\u0e82 \u0e81\u0eb2\u0e99\u0e95\u0eb1\u0ec9\u0e87\u0e84\u0ec8\u0eb2\u0e82\u0ead\u0e87\u0ea1\u0eb1\u0e99\u0e94\u0ec9\u0ea7\u0e8d\n   BUILD DETAILS: \u0ea5\u0eb2\u0e8d\u0ea5\u0eb0\u0ead\u0ebd\u0e94 \u0e9a\u0eb4\u0ea7\n-  BUILT ON RIPTOPL: \u0eaa\u0ec9\u0eb2\u0e87\u0ec0\u0e97\u0eb4\u0e87 RIPTOPL\n   BearSSL by Thomas Pornin: BearSSL \u0ec2\u0e94\u0e8d Thomas Pornin\n   Built with Claude (Anthropic): \u0eaa\u0ec9\u0eb2\u0e87\u0e94\u0ec9\u0ea7\u0e8d Claude (Anthropic)\n   CD PLAYER: \u0ec0\u0e84\u0eb7\u0ec8\u0ead\u0e87\u0eab\u0ebc\u0eb4\u0ec9\u0e99 CD\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek \u0ec1\u0ea5\u0eb0 Sleek Bold, \u0ec1\u0e95\u0ec9\u0ea1\u0eaa\u0eb3\u0ea5\u0eb1\u0e9a RIPPS2\n   RetroAchievements on the console by hacan359 (yoba): RetroAchievements \u0ec0\u0e97\u0eb4\u0e87\u0ec0\u0e84\u0eb7\u0ec8\u0ead\u0e87 \u0ec2\u0e94\u0e8d hacan359 (yoba)\n   RiptOPL by NathanNeurotic: RiptOPL \u0ec2\u0e94\u0e8d NathanNeurotic\n-  Roboto Condensed by Google (Apache-2.0): Roboto Condensed \u0ec2\u0e94\u0e8d Google (Apache-2.0)\n   SOUNDS: \u0eaa\u0ebd\u0e87\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: \u0ec2\u0e95\u0e95\u0eb4\u0e94\u0e95\u0eb1\u0ec9\u0e87 FMCB \u0e82\u0ead\u0e87 SP193 \u0ec1\u0ea5\u0eb0 OSDMenu \u0e82\u0ead\u0e87 pcm720, \u0ec0\u0eae\u0eb1\u0e94\u0e95\u0eb2\u0ea1 \u0ec0\u0e97\u0eb7\u0ec8\u0ead\u0ea5\u0eb0\u0e82\u0eb1\u0ec9\u0e99\n   SUPPORT: \u0e81\u0eb2\u0e99\u0e8a\u0ec8\u0ea7\u0e8d\u0ec0\u0eab\u0ebc\u0eb7\u0ead\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: wLaunchELF R3Z \u0ec2\u0e94\u0e8d NathanNeurotic\n   wLaunchELF by the ps2homebrew team: wLaunchELF \u0ec2\u0e94\u0e8d \u0e97\u0eb5\u0ea1 ps2homebrew\n   what RIPPS2's card image writes are checked against: \u0eaa\u0eb4\u0ec8\u0e87\u0e97\u0eb5\u0ec8\u0ec3\u0e8a\u0ec9\u0e81\u0ea7\u0e94 \u0eae\u0eb9\u0e9a\u0e81\u0eb2\u0e94 \u0e97\u0eb5\u0ec8 RIPPS2 \u0e82\u0ebd\u0e99\n-  with its own look, launchers, file browser and save tools.: \u0e9e\u0ec9\u0ead\u0ea1 \u0edc\u0ec9\u0eb2\u0e95\u0eb2, \u0ec2\u0e95\u0ec0\u0e9b\u0eb5\u0e94, \u0ec2\u0e95\u0e97\u0ec8\u0ead\u0e87\u0ec4\u0e9f\u0ea5\u0ecc \u0ec1\u0ea5\u0eb0 \u0ec0\u0e84\u0eb7\u0ec8\u0ead\u0e87\u0ea1\u0eb7\u0ec0\u0e8a\u0e9a \u0e82\u0ead\u0e87\u0ea1\u0eb1\u0e99\u0ec0\u0ead\u0e87.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: xeRAbora \u0ec2\u0e94\u0e8d hacan359, rcheevos \u0ec2\u0e94\u0e8d RetroAchievements\n   your console's own, ripped from its BIOS: \u0eaa\u0ebd\u0e87\u0e82\u0ead\u0e87\u0ec0\u0e84\u0eb7\u0ec8\u0ead\u0e87\u0e97\u0ec8\u0eb2\u0e99\u0ec0\u0ead\u0e87, \u0e96\u0ead\u0e94\u0e88\u0eb2\u0e81 BIOS \u0e82\u0ead\u0e87\u0ea1\u0eb1\u0e99\n   ' (no DNS answer)': ' (DNS \u0e9a\u0ecd\u0ec8\u0e95\u0ead\u0e9a)'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO: \u0e81\u0eb3\u0ea5\u0eb1\u0e87\u0e82\u0ebd\u0e99 %.60s'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: \u0e82\u0ebd\u0e99 Neutrino \u0ec3\u0eaa\u0ec8 %s \u0e9a\u0ecd\u0ec8\u0ec4\u0e94\u0ec9. \u0e81\u0ea7\u0e94\u0e9a\u0ec8\u0ead\u0e99\u0ea7\u0ec8\u0eb2\u0e87 \u0ec1\u0ea5\u0eb0 \u0ea7\u0ec8\u0eb2\u0ea1\u0eb1\u0e99\u0e9a\u0ecd\u0ec8\u0ec4\u0e94\u0ec9\u0e96\u0eb7\u0e81\u0e9b\u0ec9\u0ead\u0e87\u0e81\u0eb1\u0e99\u0e81\u0eb2\u0e99\u0e82\u0ebd\u0e99, \u0ec1\u0ea5\u0ec9\u0ea7\u0ea5\u0ead\u0e87\u0ec3\u0edd\u0ec8.\n   THE GAME'S DRIVE  (%s/neutrino): \u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0e82\u0ead\u0e87\u0ec0\u0e81\u0ea1  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : \u0ec0\u0e81\u0ea1\u0e99\u0eb5\u0ec9\u0ec0\u0ea5\u0eb5\u0ec8\u0ea1 \u0e9c\u0ec8\u0eb2\u0e99 Neutrino, \u0ec1\u0ea5\u0eb0 \u0e9a\u0ecd\u0ec8\u0ea1\u0eb5 Neutrino \u0ec3\u0e99\u0e9a\u0ec8\u0ead\u0e99\u0e97\u0eb5\u0ec8 RIPPS2 \u0e8a\u0ead\u0e81 (\u0e81\u0eb2\u0e99\u0e95\u0eb1\u0ec9\u0e87\u0e84\u0ec8\u0eb2, \u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0e82\u0ead\u0e87\u0ec0\u0e81\u0ea1, \u0ec0\u0ea1\u0ea1\u0ec2\u0ea1\u0ea3\u0eb5\u0e81\u0eb2\u0e94). RIPPS2 \u0ea1\u0eb5 Neutrino 1.8.0 \u0ec2\u0e94\u0e8d rickgaiser (AFL-3.0). \u0e95\u0eb4\u0e94\u0e95\u0eb1\u0ec9\u0e87\u0ea1\u0eb1\u0e99 \u0e94\u0ebd\u0ea7\u0e99\u0eb5\u0ec9 \u0e9a\u0ecd? \u0e95\u0ecd\u0ec8\u0ec4\u0e9b \u0e97\u0ec8\u0eb2\u0e99\u0ec0\u0ea5\u0eb7\u0ead\u0e81\u0e9a\u0ec8\u0ead\u0e99.\n   '%d / %d FILES': '%d / %d \u0ec4\u0e9f\u0ea5\u0ecc'\n   '%d FOLDERS AND CONTENTS': '%d \u0ec2\u0e9f\u0e99\u0ec0\u0e94\u0eb5 \u0ec1\u0ea5\u0eb0 \u0ec0\u0e99\u0eb7\u0ec9\u0ead\u0ec3\u0e99'\n   '%d FOLDERS, %d FOUND': '%d \u0ec2\u0e9f\u0e99\u0ec0\u0e94\u0eb5, \u0e9e\u0ebb\u0e9a %d'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d \u0e88\u0eb2\u0e81 %d \u0e8d\u0eb1\u0e87\u0ea5\u0eb1\u0ead\u0e81\u0ea2\u0eb9\u0ec8'\n   '%s   on %s': '%s   \u0ec0\u0e97\u0eb4\u0e87 %s'\n   BADGES: \u0e9b\u0ec9\u0eb2\u0e8d\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: \u0e9b\u0ec9\u0eb2\u0e8d \u0ec0\u0eae\u0eb1\u0e94\u0ea7\u0ebd\u0e81 \u0eaa\u0eb0\u0ec0\u0e9e\u0eb2\u0eb0\u0ec0\u0ea1\u0eb7\u0ec8\u0ead \u0ec0\u0e97\u0ec0\u0ea5\u0ec0\u0ea1\u0e97\u0ea3\u0eb5 \u0ec0\u0e9b\u0eb5\u0e94 \u0ec1\u0ea5\u0eb0 xeRAbora \u0ec0\u0eae\u0eb1\u0e94\u0ea7\u0ebd\u0e81 \u0ec0\u0e97\u0eb4\u0e87 PC \u0e82\u0ead\u0e87\u0e97\u0ec8\u0eb2\u0e99; \u0e81\u0ec8\u0ead\u0e99\u0e99\u0eb1\u0ec9\u0e99 \u0e9a\u0ecd\u0ec8\u0ec0\u0eaa\u0e8d\u0eab\u0e8d\u0eb1\u0e87.\n   CHECK THE DISC: \u0e81\u0ea7\u0e94\u0ec1\u0e9c\u0ec8\u0e99\n   From this game's last CHECK GAME SUPPORT.: \u0e88\u0eb2\u0e81 \u0e81\u0eb2\u0e99\u0e81\u0ea7\u0e94\u0e81\u0eb2\u0e99\u0eae\u0ead\u0e87\u0eae\u0eb1\u0e9a\u0ec0\u0e81\u0ea1 \u0e84\u0eb1\u0ec9\u0e87\u0ea5\u0ec8\u0eb2\u0eaa\u0eb8\u0e94 \u0e82\u0ead\u0e87\u0ec0\u0e81\u0ea1\u0e99\u0eb5\u0ec9.\n   HOW IT WORKS: \u0ea1\u0eb1\u0e99\u0ec0\u0eae\u0eb1\u0e94\u0ea7\u0ebd\u0e81\u0ec1\u0e99\u0ea7\u0ec3\u0e94\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: \u0ec0\u0e84\u0eb7\u0ec8\u0ead\u0e87\u0e99\u0eb5\u0ec9 \u0ea1\u0eb5 PS2BBL \u0e9e\u0ec9\u0ead\u0ea1\u0e81\u0eb2\u0e99\u0ea5\u0eb1\u0ead\u0e81\u0e82\u0ead\u0e87 RIPPS2.\n   AUDIO folder: \u0ec2\u0e9f\u0e99\u0ec0\u0e94\u0eb5 AUDIO\n   DRIVES: \u0ec4\u0e94\u0ea3\u0e9f\u0ecc\n-  INSTALL RIPPS2'S COPY: \u0e95\u0eb4\u0e94\u0e95\u0eb1\u0ec9\u0e87 \u0eaa\u0eb3\u0ec0\u0e99\u0ebb\u0eb2 RIPPS2\n-  'Installed: ': '\u0e95\u0eb4\u0e94\u0e95\u0eb1\u0ec9\u0e87\u0ec1\u0ea5\u0ec9\u0ea7: '\n   MANAGE: \u0e88\u0eb1\u0e94\u0e81\u0eb2\u0e99\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: \u0e88\u0eb1\u0e94\u0e81\u0eb2\u0e99 \u0eaa\u0eb0\u0ec1\u0e94\u0e87\u0ea7\u0ec8\u0eb2 \u0ec1\u0e95\u0ec8\u0ea5\u0eb0\u0ec0\u0ea1\u0ea1\u0ec2\u0ea1\u0ea3\u0eb5\u0e81\u0eb2\u0e94 \u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0eab\u0e8d\u0eb1\u0e87, \u0ec1\u0ea5\u0eb0 \u0e95\u0eb4\u0e94\u0e95\u0eb1\u0ec9\u0e87 \u0eab\u0ebc\u0eb7 \u0eaa\u0ec9\u0ead\u0ea1\u0ec1\u0e9b\u0e87 BIBLE.\n   MEMORY CARD 0  (about 600 KB): \u0ec0\u0ea1\u0ea1\u0ec2\u0ea1\u0ea3\u0eb5\u0e81\u0eb2\u0e94 0  (\u0e9b\u0eb0\u0ea1\u0eb2\u0e99 600 KB)\n   MEMORY CARD 1  (about 600 KB): \u0ec0\u0ea1\u0ea1\u0ec2\u0ea1\u0ea3\u0eb5\u0e81\u0eb2\u0e94 1  (\u0e9b\u0eb0\u0ea1\u0eb2\u0e99 600 KB)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO: \u0ec4\u0e94\u0ea3\u0ec0\u0ea7\u0eb5\u0e82\u0ead\u0e87\u0ea1\u0eb1\u0e99 \u0e9a\u0ecd\u0ec8\u0e8d\u0ead\u0ea1\u0ec2\u0eab\u0ebc\u0e94 (\u0ea5\u0eb0\u0eab\u0eb1\u0e94 %d).'\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO: \u0ec4\u0e94\u0ea3\u0ec0\u0ea7\u0eb5\u0ec2\u0eab\u0ebc\u0e94\u0ec1\u0ea5\u0ec9\u0ea7, \u0ec1\u0e95\u0ec8\u0e9a\u0ecd\u0ec8\u0ea1\u0eb5\u0e81\u0eb2\u0e94 SD \u0ec3\u0e94\u0e95\u0ead\u0e9a (\u0e8a\u0ec8\u0ead\u0e87 mc1).'\n   NEUTRINO SETTINGS: \u0e81\u0eb2\u0e99\u0e95\u0eb1\u0ec9\u0e87\u0e84\u0ec8\u0eb2 NEUTRINO\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': '\u0e8d\u0eb1\u0e87\u0e9a\u0ecd\u0ec8\u0ea1\u0eb5: RIPPS2 \u0e9a\u0ecd\u0ec8\u0ea1\u0eb5\u0eaa\u0ebd\u0e87 \u0e82\u0ead\u0e87\u0ea1\u0eb1\u0e99\u0ec0\u0ead\u0e87. \u0e96\u0ead\u0e94\u0eaa\u0ebd\u0e87\u0e82\u0ead\u0e87\u0ec0\u0e84\u0eb7\u0ec8\u0ead\u0e87\u0e99\u0eb5\u0ec9, \u0eab\u0ebc\u0eb7 \u0eaa\u0ec9\u0eb2\u0e87\u0e9e\u0ea7\u0e81\u0ea1\u0eb1\u0e99 \u0ec3\u0e99 RIPPS2 Audio Studio.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: \u0e9a\u0ecd\u0ec8\u0ec4\u0e94\u0ec9\u0e95\u0eb4\u0e94\u0e95\u0eb1\u0ec9\u0e87. \u0ec0\u0e81\u0ea1\u0e97\u0eb5\u0ec8\u0e95\u0ec9\u0ead\u0e87\u0e81\u0eb2\u0e99\u0ea1\u0eb1\u0e99 \u0e88\u0eb0\u0eaa\u0eb0\u0ec0\u0edc\u0eb5\u0eaa\u0eb3\u0ec0\u0e99\u0ebb\u0eb2 RIPPS2, \u0eab\u0ebc\u0eb7 \u0e95\u0eb4\u0e94\u0e95\u0eb1\u0ec9\u0e87\u0ea1\u0eb1\u0e99 \u0ea2\u0eb9\u0ec8\u0e99\u0eb5\u0ec9.\n   POPSTARTER NETWORK: \u0ec0\u0e84\u0eb7\u0ead\u0e82\u0ec8\u0eb2\u0e8d POPSTARTER\n   POPSTARTER and Ember: POPSTARTER \u0ec1\u0ea5\u0eb0 Ember\n   PS1 SETTINGS: \u0e81\u0eb2\u0e99\u0e95\u0eb1\u0ec9\u0e87\u0e84\u0ec8\u0eb2 PS1\n   PS2BBL 1.2.0 for RIPPS2: PS2BBL 1.2.0 \u0eaa\u0eb3\u0ea5\u0eb1\u0e9a RIPPS2\n   RIP THIS CONSOLE'S SOUNDS: \u0e96\u0ead\u0e94\u0eaa\u0ebd\u0e87 \u0e82\u0ead\u0e87\u0ec0\u0e84\u0eb7\u0ec8\u0ead\u0e87\u0e99\u0eb5\u0ec9\n-  RIPPS2's homebrew: homebrew \u0e82\u0ead\u0e87 RIPPS2\n   STATUS: \u0eaa\u0eb0\u0e96\u0eb2\u0e99\u0eb0\n-  'STATUS: looking for RIPPS2''s homebrew': '\u0eaa\u0eb0\u0e96\u0eb2\u0e99\u0eb0: \u0e81\u0eb3\u0ea5\u0eb1\u0e87\u0e8a\u0ead\u0e81 homebrew \u0e82\u0ead\u0e87 RIPPS2'\n   a drive of unknown kind: \u0ec4\u0e94\u0ea3\u0e9f\u0ecc \u0e9b\u0eb0\u0ec0\u0e9e\u0e94\u0e97\u0eb5\u0ec8\u0e9a\u0ecd\u0ec8\u0eae\u0eb9\u0ec9\u0e88\u0eb1\u0e81\n-  a drive or a memory card: \u0ec4\u0e94\u0ea3\u0e9f\u0ecc \u0eab\u0ebc\u0eb7 \u0ec0\u0ea1\u0ea1\u0ec2\u0ea1\u0ea3\u0eb5\u0e81\u0eb2\u0e94\n   boot loader, card by card: \u0ec2\u0e95\u0ec2\u0eab\u0ebc\u0e94\u0e9a\u0eb9\u0e94, \u0e97\u0eb5\u0ea5\u0eb0\u0e81\u0eb2\u0e94\n   from its BIOS, into AUDIO: \u0e88\u0eb2\u0e81 BIOS \u0e82\u0ead\u0e87\u0ea1\u0eb1\u0e99, \u0ec4\u0e9b AUDIO\n   internal HDD (exFAT): HDD \u0e9e\u0eb2\u0e8d\u0ec3\u0e99 (exFAT)\n@@ -975,10 +958,194 @@ strings:\n   Sidebar: \u0ec1\u0e96\u0e9a\u0e82\u0ec9\u0eb2\u0e87\n   your own copy, found on your drives and memory cards: \u0eaa\u0eb3\u0ec0\u0e99\u0ebb\u0eb2\u0e82\u0ead\u0e87\u0e97\u0ec8\u0eb2\u0e99\u0ec0\u0ead\u0e87, \u0e9e\u0ebb\u0e9a\u0ec3\u0e99\u0ec4\u0e94\u0ea3\u0e9f\u0ecc \u0ec1\u0ea5\u0eb0 \u0e9a\u0eb1\u0e94\u0e84\u0ea7\u0eb2\u0ea1\u0e88\u0eb3\u0e82\u0ead\u0e87\u0e97\u0ec8\u0eb2\u0e99\n   'your own, found and launched: github.com/Gageformer/Ember/releases': '\u0e82\u0ead\u0e87\u0e97\u0ec8\u0eb2\u0e99\u0ec0\u0ead\u0e87, \u0e9e\u0ebb\u0e9a \u0ec1\u0ea5\u0eb0 \u0ec0\u0e9b\u0eb5\u0e94: github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': '\u0e9a\u0ecd\u0ec8\u0e9e\u0ebb\u0e9a\u0ec3\u0e99 mass0: (%s). \u0ec3\u0eaa\u0ec8 ember.elf \u0ec1\u0ea5\u0eb0 bios.bin \u0e82\u0ead\u0e87\u0e97\u0ec8\u0eb2\u0e99 \u0ec3\u0e99\u0ec2\u0e9f\u0e99\u0ec0\u0e94\u0eb5 EMBER \u0e82\u0ead\u0e87\u0ea1\u0eb1\u0e99.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: \u0e9a\u0ecd\u0ec8\u0e9e\u0ebb\u0e9a. \u0ec3\u0eaa\u0ec8 POPSTARTER.ELF \u0e82\u0ead\u0e87\u0e97\u0ec8\u0eb2\u0e99\u0ec0\u0ead\u0e87 \u0ec3\u0e99\u0ec2\u0e9f\u0e99\u0ec0\u0e94\u0eb5 POPS \u0e82\u0ead\u0e87\u0ec4\u0e94\u0ea3\u0e9f\u0ecc.\n   Launch Disc: \u0ec0\u0e9b\u0eb5\u0e94\u0ec1\u0e9c\u0ec8\u0e99\n   Memory Files: \u0ec4\u0e9f\u0ea5\u0ecc\u0ec3\u0e99\u0edc\u0ec8\u0ea7\u0e8d\u0e84\u0ea7\u0eb2\u0ea1\u0e88\u0eb3\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: RIPPS2 \u0e9e\u0ebb\u0e9a %d \u0e88\u0eb2\u0e81 5. \u0ec1\u0e95\u0ec8\u0ea5\u0eb0\u0ead\u0eb1\u0e99\u0e96\u0eb7\u0e81\u0e88\u0eb7\u0ec8\u0ec4\u0ea7\u0ec9, \u0ec1\u0ea5\u0eb0 \u0e97\u0eb8\u0e81\u0e81\u0eb2\u0e99\u0ec0\u0e9b\u0eb5\u0e94\u0ec3\u0e8a\u0ec9\u0e88\u0eb0\u0ead\u0ec8\u0eb2\u0e99\u0ea1\u0eb1\u0e99\u0e88\u0eb2\u0e81\u0e9a\u0ec8\u0ead\u0e99\u0e99\u0eb1\u0ec9\u0e99.\n+  not found on the started drives: \u0e9a\u0ecd\u0ec8\u0e9e\u0ebb\u0e9a\u0ec3\u0e99\u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0e97\u0eb5\u0ec8\u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0ec1\u0ea5\u0ec9\u0ea7\n+  not looked for yet: \u0e8d\u0eb1\u0e87\u0e9a\u0ecd\u0ec8\u0ec4\u0e94\u0ec9\u0e8a\u0ead\u0e81\u0eab\u0eb2\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s \u0e9a\u0ecd\u0ec8\u0e95\u0ead\u0e9a, \u0eaa\u0eb0\u0e99\u0eb1\u0ec9\u0e99 %s \u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0e9a\u0ecd\u0ec8\u0ec4\u0e94\u0ec9. \u0ea1\u0eb1\u0e99\u0ec0\u0e8a\u0eb7\u0ec8\u0ead\u0ea1\u0e95\u0ecd\u0ec8\u0ea2\u0eb9\u0ec8\u0e9a\u0ecd?'\n+  '%s is not on %s any more, so it has left All Games.': '%s \u0e9a\u0ecd\u0ec8\u0ea2\u0eb9\u0ec8\u0ec3\u0e99 %s \u0ead\u0eb5\u0e81\u0ec1\u0ea5\u0ec9\u0ea7, \u0eaa\u0eb0\u0e99\u0eb1\u0ec9\u0e99\u0ea1\u0eb1\u0e99\u0e88\u0eb6\u0ec8\u0e87\u0ead\u0ead\u0e81\u0e88\u0eb2\u0e81 \u0e97\u0eb8\u0e81\u0ec0\u0e81\u0ea1.'\n+  STARTING %s: \u0e81\u0eb3\u0ea5\u0eb1\u0e87\u0ec0\u0ea5\u0eb5\u0ec8\u0ea1 %s\n+  the SMB share: \u0e9a\u0ec8\u0ead\u0e99\u0ec1\u0e9a\u0ec8\u0e87\u0e9b\u0eb1\u0e99 SMB\n+  the drive: \u0ec4\u0e94\u0ea3\u0e9f\u0ecc\n+  the internal HDD (APA): HDD \u0e9e\u0eb2\u0e8d\u0ec3\u0e99 (APA)\n+  the internal HDD (exFAT): HDD \u0e9e\u0eb2\u0e8d\u0ec3\u0e99 (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: \u0eaa\u0ec9\u0eb2\u0e87\u0ec0\u0e97\u0eb4\u0e87 RIPTOPL, \u0e9e\u0ec9\u0ead\u0ea1 NEUTRINO\n+  'Special thanks to all the YouTube love as well:': '\u0e82\u0ead\u0e9a\u0ec3\u0e88\u0ec0\u0e9b\u0eb1\u0e99\u0e9e\u0eb4\u0ec0\u0eaa\u0e94\u0e95\u0ecd\u0ec8\u0e84\u0ea7\u0eb2\u0ea1\u0eae\u0eb1\u0e81\u0e97\u0eb1\u0e87\u0edd\u0ebb\u0e94\u0e88\u0eb2\u0e81 YouTube \u0e99\u0eb3:'\n+  and its own look, launchers, file browser and save tools.: \u0ec1\u0ea5\u0eb0 \u0ea1\u0eb5\u0edc\u0ec9\u0eb2\u0e95\u0eb2, \u0e95\u0ebb\u0ea7\u0ec0\u0e9b\u0eb5\u0e94, \u0e95\u0ebb\u0ea7\u0e97\u0ec8\u0ead\u0e87\u0ec4\u0e9f\u0ea5\u0ecc \u0ec1\u0ea5\u0eb0 \u0ec0\u0e84\u0eb7\u0ec8\u0ead\u0e87\u0ea1\u0eb7\u0e9a\u0eb1\u0e99\u0e97\u0eb6\u0e81\u0ec0\u0e9b\u0eb1\u0e99\u0e82\u0ead\u0e87\u0e95\u0ebb\u0e99\u0ec0\u0ead\u0e87.\n+  glad to help however you reach me.: \u0e8d\u0eb4\u0e99\u0e94\u0eb5\u0e8a\u0ec8\u0ea7\u0e8d, \u0e9a\u0ecd\u0ec8\u0ea7\u0ec8\u0eb2\u0e97\u0ec8\u0eb2\u0e99\u0e88\u0eb0\u0e95\u0eb4\u0e94\u0e95\u0ecd\u0ec8\u0e82\u0ec9\u0ead\u0e8d\u0e97\u0eb2\u0e87\u0ec3\u0e94.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): \u0ec1\u0e81\u0e99\u0ec0\u0e81\u0ea1\u0e97\u0eb5\u0eaa\u0ead\u0e87, 1.8.0 \u0ea1\u0eb2\u0e9e\u0ec9\u0ead\u0ea1\u0ec2\u0e94\u0e8d\u0e9a\u0ecd\u0ec8\u0e94\u0eb1\u0e94\u0ec1\u0e9b\u0e87 (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: \u0ea1\u0eb5 Neutrino \u0e82\u0ead\u0e87 rickgaiser \u0ea2\u0eb9\u0ec8\u0e9e\u0eb2\u0e8d\u0ec3\u0e99\u0ec0\u0e9b\u0eb1\u0e99\u0ec1\u0e81\u0e99\u0ec0\u0e81\u0ea1\u0e97\u0eb5\u0eaa\u0ead\u0e87,\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : '\u0ec0\u0e81\u0ea1\u0e99\u0eb5\u0ec9\u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0e9c\u0ec8\u0eb2\u0e99 Neutrino, \u0ec1\u0ea5\u0eb0 RIPPS2 \u0e9a\u0ecd\u0ec8\u0e9e\u0ebb\u0e9a\u0ea1\u0eb1\u0e99: \u0e9a\u0ecd\u0ec8\u0ea2\u0eb9\u0ec8\u0e97\u0eb5\u0ec8\u0ec0\u0eaa\u0eb1\u0ec9\u0e99\u0e97\u0eb2\u0e87\u0ec3\u0e99 \u0e81\u0eb2\u0e99\u0e95\u0eb1\u0ec9\u0e87\u0e84\u0ec8\u0eb2, \u0e9a\u0ecd\u0ec8\u0ea2\u0eb9\u0ec8\u0ec3\u0e99\u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0e82\u0ead\u0e87\u0ec0\u0e81\u0ea1, \u0e9a\u0ecd\u0ec8\u0ea2\u0eb9\u0ec8\u0ec3\u0e99\u0ec0\u0ea1\u0ea1\u0ec2\u0ea1\u0ea3\u0eb5\u0e81\u0eb2\u0e94 \u0ec1\u0ea5\u0eb0 \u0e9a\u0ecd\u0ec8\u0ea2\u0eb9\u0ec8\u0ec3\u0e99\u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0e97\u0eb5\u0ec8\u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0ec1\u0ea5\u0ec9\u0ea7\u0ec3\u0e94\u0ec0\u0ea5\u0eb5\u0e8d. RIPPS2 \u0ea1\u0eb5 Neutrino 1.8.0 \u0e82\u0ead\u0e87 rickgaiser \u0ea1\u0eb2\u0e99\u0eb3 (AFL-3.0): \u0e95\u0eb4\u0e94\u0e95\u0eb1\u0ec9\u0e87\u0ea1\u0eb1\u0e99\u0e84\u0eb1\u0ec9\u0e87\u0edc\u0eb6\u0ec8\u0e87, \u0e9a\u0ec8\u0ead\u0e99\u0e97\u0eb5\u0ec8\u0e97\u0ec8\u0eb2\u0e99\u0ec0\u0ea5\u0eb7\u0ead\u0e81, \u0ec1\u0ea5\u0eb0 \u0e97\u0eb8\u0e81\u0ec0\u0e81\u0ea1 Neutrino \u0e88\u0eb0\u0ec3\u0e8a\u0ec9\u0ea1\u0eb1\u0e99\u0e88\u0eb2\u0e81\u0e9a\u0ec8\u0ead\u0e99\u0e99\u0eb1\u0ec9\u0e99.'\n+  STARTING THE DRIVES: \u0e81\u0eb3\u0ea5\u0eb1\u0e87\u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0ec4\u0e94\u0ea3\u0e9f\u0ecc\n+  '%d games, on the started drives': '%d \u0ec0\u0e81\u0ea1, \u0ec3\u0e99\u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0e97\u0eb5\u0ec8\u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0ec1\u0ea5\u0ec9\u0ea7'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s: \u0e9a\u0eb2\u0e87\u0ec4\u0e9f\u0ea5\u0ecc\u0ea5\u0ebb\u0e9a\u0e9a\u0ecd\u0ec8\u0ec4\u0e94\u0ec9'\n+  '%u partitions, all in order': '%u \u0e9e\u0eb2\u0e97\u0eb4\u0e8a\u0eb1\u0e99, \u0e97\u0eb1\u0e87\u0edd\u0ebb\u0e94\u0e96\u0eb7\u0e81\u0e95\u0ec9\u0ead\u0e87'\n+  ', %d art images': ', \u0eae\u0eb9\u0e9a\u0e9b\u0ebb\u0e81 %d \u0eae\u0eb9\u0e9a'\n+  ', and ': ', \u0ec1\u0ea5\u0eb0 '\n+  ', its cheats': ', \u0ec2\u0e84\u0e94\u0ec2\u0e81\u0e87\u0e82\u0ead\u0e87\u0ea1\u0eb1\u0e99'\n+  ', its settings': ', \u0e81\u0eb2\u0e99\u0e95\u0eb1\u0ec9\u0e87\u0e84\u0ec8\u0eb2\u0e82\u0ead\u0e87\u0ea1\u0eb1\u0e99'\n+  1 game, on the started drives: 1 \u0ec0\u0e81\u0ea1, \u0ec3\u0e99\u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0e97\u0eb5\u0ec8\u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0ec1\u0ea5\u0ec9\u0ea7\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: \u0ec0\u0e81\u0ea1 PS1 \u0ea2\u0eb9\u0ec8\u0e99\u0eb5\u0ec9\u0ea5\u0ebb\u0e9a\u0ec4\u0e94\u0ec9\u0ec3\u0e99 \u0ec4\u0e9f\u0ea5\u0ecc\u0ec3\u0e99\u0edc\u0ec8\u0ea7\u0e8d\u0e84\u0ea7\u0eb2\u0ea1\u0e88\u0eb3, \u0e9a\u0ec8\u0ead\u0e99\u0e97\u0eb5\u0ec8\u0ea1\u0eb1\u0e99\u0ec0\u0e9b\u0eb1\u0e99\u0ec4\u0e9f\u0ea5\u0ecc\u0e82\u0ead\u0e87\u0e95\u0ebb\u0e99\u0ec0\u0ead\u0e87.\n+  A network catalogue's games are deleted on the PC that serves them.: \u0ec0\u0e81\u0ea1\u0e82\u0ead\u0e87\u0ec1\u0e84\u0eb1\u0e94\u0e95\u0eb2\u0ea5\u0eb1\u0ead\u0e81\u0ec0\u0e84\u0eb7\u0ead\u0e82\u0ec8\u0eb2\u0e8d\u0ea5\u0ebb\u0e9a\u0ec4\u0e94\u0ec9\u0ea2\u0eb9\u0ec8 PC \u0e97\u0eb5\u0ec8\u0ec3\u0eab\u0ec9\u0e9a\u0ecd\u0ea5\u0eb4\u0e81\u0eb2\u0e99\u0e9e\u0ea7\u0e81\u0ea1\u0eb1\u0e99.\n+  DELETE FOR GOOD: \u0ea5\u0ebb\u0e9a\u0e96\u0eb2\u0ea7\u0ead\u0e99\n+  DELETE IT: \u0ea5\u0ebb\u0e9a\u0ea1\u0eb1\u0e99\n+  'DELETED %s: %s FREED': '\u0ea5\u0ebb\u0e9a %s \u0ec1\u0ea5\u0ec9\u0ea7: \u0eab\u0ea7\u0ec8\u0eb2\u0e87 %s'\n+  'Delete %s from %s? Goes: %s.': '\u0ea5\u0ebb\u0e9a %s \u0e88\u0eb2\u0e81 %s? \u0e88\u0eb0\u0ec4\u0e9b: %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': '\u0ea5\u0ebb\u0e9a %s \u0e88\u0eb2\u0e81 %s? \u0e88\u0eb0\u0ec4\u0e9b: %s. \u0e8d\u0eb1\u0e87\u0ea2\u0eb9\u0ec8: %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: \u0ea5\u0ebb\u0e9a\u0e9a\u0eb1\u0e99\u0e97\u0eb6\u0e81 \u0ec1\u0ea5\u0eb0 \u0eae\u0eb9\u0e9a RIPSHOT \u0e82\u0ead\u0e87\u0ea1\u0eb1\u0e99\u0e99\u0eb3\u0e9a\u0ecd? \u0e9a\u0ecd\u0ec8 \u0e88\u0eb0\u0ec0\u0e81\u0eb1\u0e9a\u0e9e\u0ea7\u0e81\u0ea1\u0eb1\u0e99\u0ec4\u0ea7\u0ec9.\n+  HIDDEN GAMES: \u0ec0\u0e81\u0ea1\u0e97\u0eb5\u0ec8\u0ec0\u0e8a\u0eb7\u0ec8\u0ead\u0e87\u0ec4\u0ea7\u0ec9\n+  'HIDDEN: %s': '\u0ec0\u0e8a\u0eb7\u0ec8\u0ead\u0e87\u0ec1\u0ea5\u0ec9\u0ea7: %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: \u0ec0\u0e8a\u0eb7\u0ec8\u0ead\u0e87 %s? \u0ea1\u0eb1\u0e99\u0e88\u0eb0\u0ead\u0ead\u0e81\u0e88\u0eb2\u0e81\u0e97\u0eb8\u0e81\u0ea5\u0eb2\u0e8d\u0e81\u0eb2\u0e99, \u0ea5\u0ea7\u0ea1\u0e97\u0eb1\u0e87 \u0e97\u0eb8\u0e81\u0ec0\u0e81\u0ea1 \u0ec1\u0ea5\u0eb0 \u0ea5\u0eb2\u0e8d\u0e81\u0eb2\u0e99\u0ec2\u0e9b\u0e94. \u0e81\u0eb2\u0e99\u0e95\u0eb1\u0ec9\u0e87\u0e84\u0ec8\u0eb2 > \u0ec1\u0eab\u0ebc\u0ec8\u0e87\u0ec0\u0e81\u0ea1 > \u0ec0\u0e81\u0ea1\u0e97\u0eb5\u0ec8\u0ec0\u0e8a\u0eb7\u0ec8\u0ead\u0e87\u0ec4\u0ea7\u0ec9 \u0e88\u0eb0\u0eaa\u0eb0\u0ec1\u0e94\u0e87\u0ea1\u0eb1\u0e99\u0ead\u0eb5\u0e81.\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: \u0e81\u0ea7\u0e94\u0e84\u0eb1\u0ec9\u0e87\u0eaa\u0eb8\u0e94\u0e97\u0ec9\u0eb2\u0e8d. %s \u0e88\u0eb0\u0e96\u0eb7\u0e81\u0ea5\u0ebb\u0e9a\u0e96\u0eb2\u0ea7\u0ead\u0e99, \u0ec1\u0ea5\u0eb0 \u0e9a\u0ecd\u0ec8\u0ea1\u0eb5\u0eab\u0e8d\u0eb1\u0e87\u0ec0\u0ead\u0ebb\u0eb2\u0ea1\u0eb1\u0e99\u0e84\u0eb7\u0e99\u0ea1\u0eb2\u0ec4\u0e94\u0ec9. \u0e81\u0ebb\u0e94 START \u0ec0\u0e9e\u0eb7\u0ec8\u0ead\u0ea5\u0ebb\u0e9a\u0ea1\u0eb1\u0e99.\n+  None. Hide Game, in a game's options, puts one here.: \u0e9a\u0ecd\u0ec8\u0ea1\u0eb5. \u0ec0\u0e8a\u0eb7\u0ec8\u0ead\u0e87\u0ec0\u0e81\u0ea1, \u0ec3\u0e99\u0e95\u0ebb\u0ea7\u0ec0\u0ea5\u0eb7\u0ead\u0e81\u0e82\u0ead\u0e87\u0ec0\u0e81\u0ea1, \u0e88\u0eb0\u0ea7\u0eb2\u0e87\u0ead\u0eb1\u0e99\u0edc\u0eb6\u0ec8\u0e87\u0ec4\u0ea7\u0ec9\u0e99\u0eb5\u0ec9.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : '\u0e9a\u0ecd\u0ec8\u0ea1\u0eb5\u0eab\u0e8d\u0eb1\u0e87\u0e96\u0eb7\u0e81\u0ea5\u0ebb\u0e9a: \u0e95\u0eb2\u0e95\u0eb0\u0ea5\u0eb2\u0e87\u0e9e\u0eb2\u0e97\u0eb4\u0e8a\u0eb1\u0e99\u0e82\u0ead\u0e87\u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0e99\u0eb5\u0ec9\u0e9a\u0ecd\u0ec8\u0ec0\u0e82\u0ebb\u0ec9\u0eb2\u0e81\u0eb1\u0e9a\u0e94\u0eb4\u0eaa\u0e99\u0eb5\u0ec9 (%s). \u0ea1\u0eb1\u0e99\u0ead\u0eb2\u0e94\u0e96\u0eb7\u0e81\u0e82\u0ebd\u0e99\u0e88\u0eb2\u0e81\u0ead\u0eb4\u0ea1\u0ec0\u0ea1\u0e94\u0e82\u0ead\u0e87\u0e94\u0eb4\u0eaa\u0e97\u0eb5\u0ec8\u0ea1\u0eb5\u0e82\u0eb0\u0edc\u0eb2\u0e94\u0ead\u0eb7\u0ec8\u0e99. \u0eaa\u0ec9\u0ead\u0ea1\u0ea1\u0eb1\u0e99\u0ec3\u0e99 PC \u0e81\u0ec8\u0ead\u0e99.'\n+  RIPPS2 cannot delete a game from this page.: RIPPS2 \u0ea5\u0ebb\u0e9a\u0ec0\u0e81\u0ea1\u0e88\u0eb2\u0e81\u0edc\u0ec9\u0eb2\u0e99\u0eb5\u0ec9\u0e9a\u0ecd\u0ec8\u0ec4\u0e94\u0ec9.\n+  RIPPS2 could not find this game's files on its drive.: RIPPS2 \u0e9a\u0ecd\u0ec8\u0e9e\u0ebb\u0e9a\u0ec4\u0e9f\u0ea5\u0ecc\u0e82\u0ead\u0e87\u0ec0\u0e81\u0ea1\u0e99\u0eb5\u0ec9\u0ec3\u0e99\u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0e82\u0ead\u0e87\u0ea1\u0eb1\u0e99.\n+  Reading the hidden lists: \u0e81\u0eb3\u0ea5\u0eb1\u0e87\u0ead\u0ec8\u0eb2\u0e99\u0ea5\u0eb2\u0e8d\u0e81\u0eb2\u0e99\u0e97\u0eb5\u0ec8\u0ec0\u0e8a\u0eb7\u0ec8\u0ead\u0e87\u0ec4\u0ea7\u0ec9\n+  SHOW AGAIN OR DELETE: \u0eaa\u0eb0\u0ec1\u0e94\u0e87\u0ead\u0eb5\u0e81 \u0eab\u0ebc\u0eb7 \u0ea5\u0ebb\u0e9a\n+  SHOW IT AGAIN: \u0eaa\u0eb0\u0ec1\u0e94\u0e87\u0ea1\u0eb1\u0e99\u0ead\u0eb5\u0e81\n+  That game is not on its drive any more.: \u0ec0\u0e81\u0ea1\u0e99\u0eb1\u0ec9\u0e99\u0e9a\u0ecd\u0ec8\u0ea2\u0eb9\u0ec8\u0ec3\u0e99\u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0e82\u0ead\u0e87\u0ea1\u0eb1\u0e99\u0ead\u0eb5\u0e81\u0ec1\u0ea5\u0ec9\u0ea7.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: \u0e82\u0ebd\u0e99\u0ea5\u0eb2\u0e8d\u0e81\u0eb2\u0e99\u0e97\u0eb5\u0ec8\u0ec0\u0e8a\u0eb7\u0ec8\u0ead\u0e87\u0ec4\u0ea7\u0ec9\u0ea5\u0ebb\u0e87\u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0e82\u0ead\u0e87\u0ec0\u0e81\u0ea1\u0e9a\u0ecd\u0ec8\u0ec4\u0e94\u0ec9. \u0ea1\u0eb1\u0e99\u0e96\u0eb7\u0e81\u0e9b\u0ec9\u0ead\u0e87\u0e81\u0eb1\u0e99\u0e81\u0eb2\u0e99\u0e82\u0ebd\u0e99, \u0eab\u0ebc\u0eb7 Write Operations \u0e9b\u0eb4\u0e94\u0ea2\u0eb9\u0ec8\u0e9a\u0ecd?\n+  The partition could not be removed (%d). Nothing was deleted.: \u0ec0\u0ead\u0ebb\u0eb2\u0e9e\u0eb2\u0e97\u0eb4\u0e8a\u0eb1\u0e99\u0ead\u0ead\u0e81\u0e9a\u0ecd\u0ec8\u0ec4\u0e94\u0ec9 (%d). \u0e9a\u0ecd\u0ec8\u0ea1\u0eb5\u0eab\u0e8d\u0eb1\u0e87\u0e96\u0eb7\u0e81\u0ea5\u0ebb\u0e9a.\n+  This drive cannot keep a hidden list (a network catalogue).: \u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0e99\u0eb5\u0ec9\u0ec0\u0e81\u0eb1\u0e9a\u0ea5\u0eb2\u0e8d\u0e81\u0eb2\u0e99\u0e97\u0eb5\u0ec8\u0ec0\u0e8a\u0eb7\u0ec8\u0ead\u0e87\u0ec4\u0ea7\u0ec9\u0e9a\u0ecd\u0ec8\u0ec4\u0e94\u0ec9 (\u0ec1\u0e84\u0eb1\u0e94\u0e95\u0eb2\u0ea5\u0eb1\u0ead\u0e81\u0ec0\u0e84\u0eb7\u0ead\u0e82\u0ec8\u0eb2\u0e8d).\n+  This game's drive has not answered yet. Wait for it, then try again.: \u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0e82\u0ead\u0e87\u0ec0\u0e81\u0ea1\u0e99\u0eb5\u0ec9\u0e8d\u0eb1\u0e87\u0e9a\u0ecd\u0ec8\u0e95\u0ead\u0e9a. \u0ea5\u0ecd\u0e96\u0ec9\u0eb2\u0ea1\u0eb1\u0e99, \u0ec1\u0ea5\u0ec9\u0ea7\u0ea5\u0ead\u0e87\u0ec3\u0edd\u0ec8.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: \u0e9e\u0eb2\u0e97\u0eb4\u0e8a\u0eb1\u0e99\u0e82\u0ead\u0e87\u0ec0\u0e81\u0ea1\u0e99\u0eb5\u0ec9\u0ec0\u0e9b\u0eb1\u0e99\u0ead\u0eb1\u0e99\u0e97\u0eb5\u0ec8 RIPPS2 \u0e9a\u0ecd\u0ec8\u0ec0\u0ead\u0ebb\u0eb2\u0ead\u0ead\u0e81 (\u0e9e\u0eb2\u0e97\u0eb4\u0e8a\u0eb1\u0e99\u0ea5\u0eb0\u0e9a\u0ebb\u0e9a, \u0eab\u0ebc\u0eb7 \u0ead\u0eb1\u0e99\u0e97\u0eb5\u0ec8\u0ea1\u0eb5\u0e81\u0eb2\u0e99\u0e95\u0eb1\u0ec9\u0e87\u0e84\u0ec8\u0eb2\u0e82\u0ead\u0e87\u0ea1\u0eb1\u0e99).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': '\u0e9e\u0eb2\u0e97\u0eb4\u0e8a\u0eb1\u0e99\u0edc\u0eb6\u0ec8\u0e87\u0ec3\u0eab\u0e8d\u0ec8\u0e81\u0ea7\u0ec8\u0eb2\u0e97\u0eb5\u0ec8\u0e94\u0eb4\u0eaa\u0e99\u0eb5\u0ec9\u0ead\u0eb0\u0e99\u0eb8\u0e8d\u0eb2\u0e94: \u0e95\u0eb2\u0e95\u0eb0\u0ea5\u0eb2\u0e87\u0e96\u0eb7\u0e81\u0ec0\u0eae\u0eb1\u0e94\u0eaa\u0eb3\u0ea5\u0eb1\u0e9a\u0e94\u0eb4\u0eaa\u0e82\u0eb0\u0edc\u0eb2\u0e94\u0ead\u0eb7\u0ec8\u0e99'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': '\u0e9e\u0eb2\u0e97\u0eb4\u0e8a\u0eb1\u0e99\u0edc\u0eb6\u0ec8\u0e87\u0ec0\u0e81\u0eb5\u0e99\u0e97\u0ec9\u0eb2\u0e8d\u0e94\u0eb4\u0eaa\u0e99\u0eb5\u0ec9 (\u0ec0\u0e8a\u0eb1\u0e81\u0ec0\u0e95\u0eb5 %u \u0e88\u0eb2\u0e81 %u): \u0e95\u0eb2\u0e95\u0eb0\u0ea5\u0eb2\u0e87\u0e96\u0eb7\u0e81\u0ec0\u0eae\u0eb1\u0e94\u0eaa\u0eb3\u0ea5\u0eb1\u0e9a\u0e94\u0eb4\u0eaa\u0e97\u0eb5\u0ec8\u0ec3\u0eab\u0e8d\u0ec8\u0e81\u0ea7\u0ec8\u0eb2'\n+  its RIPSHOT pictures: \u0eae\u0eb9\u0e9a RIPSHOT \u0e82\u0ead\u0e87\u0ea1\u0eb1\u0e99\n+  its drive: \u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0e82\u0ead\u0e87\u0ea1\u0eb1\u0e99\n+  its saves: \u0e9a\u0eb1\u0e99\u0e97\u0eb6\u0e81\u0e82\u0ead\u0e87\u0ea1\u0eb1\u0e99\n+  sector %u holds no partition header: \u0ec0\u0e8a\u0eb1\u0e81\u0ec0\u0e95\u0eb5 %u \u0e9a\u0ecd\u0ec8\u0ea1\u0eb5\u0eab\u0ebb\u0ea7\u0e9e\u0eb2\u0e97\u0eb4\u0e8a\u0eb1\u0e99\n+  the chain does not close at __mbr: \u0e95\u0ec8\u0ead\u0e87\u0ec2\u0eaa\u0ec9\u0e9a\u0ecd\u0ec8\u0e9b\u0eb4\u0e94\u0e97\u0eb5\u0ec8 __mbr\n+  the chain skips from sector %u: \u0e95\u0ec8\u0ead\u0e87\u0ec2\u0eaa\u0ec9\u0e82\u0ec9\u0eb2\u0ea1\u0e88\u0eb2\u0e81\u0ec0\u0e8a\u0eb1\u0e81\u0ec0\u0e95\u0eb5 %u\n+  the chain's links disagree at sector %u: \u0e82\u0ecd\u0ec9\u0e95\u0ecd\u0ec8\u0e82\u0ead\u0e87\u0e95\u0ec8\u0ead\u0e87\u0ec2\u0eaa\u0ec9\u0e9a\u0ecd\u0ec8\u0e81\u0ebb\u0e87\u0e81\u0eb1\u0e99\u0e97\u0eb5\u0ec8\u0ec0\u0e8a\u0eb1\u0e81\u0ec0\u0e95\u0eb5 %u\n+  the drive's size could not be read: \u0ead\u0ec8\u0eb2\u0e99\u0e82\u0eb0\u0edc\u0eb2\u0e94\u0e82\u0ead\u0e87\u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0e9a\u0ecd\u0ec8\u0ec4\u0e94\u0ec9\n+  the game (%s): \u0ec0\u0e81\u0ea1 (%s)\n+  the game's folder and everything in it (%s): \u0ec2\u0e9f\u0e99\u0ec0\u0e94\u0eb5\u0e82\u0ead\u0e87\u0ec0\u0e81\u0ea1 \u0ec1\u0ea5\u0eb0 \u0e97\u0eb8\u0e81\u0ea2\u0ec8\u0eb2\u0e87\u0ec3\u0e99\u0e99\u0eb1\u0ec9\u0e99 (%s)\n+  the header at sector %u could not be read: \u0ead\u0ec8\u0eb2\u0e99\u0eab\u0ebb\u0ea7\u0e97\u0eb5\u0ec8\u0ec0\u0e8a\u0eb1\u0e81\u0ec0\u0e95\u0eb5 %u \u0e9a\u0ecd\u0ec8\u0ec4\u0e94\u0ec9\n+  the header at sector %u fails its checksum: \u0eab\u0ebb\u0ea7\u0e97\u0eb5\u0ec8\u0ec0\u0e8a\u0eb1\u0e81\u0ec0\u0e95\u0eb5 %u \u0e9a\u0ecd\u0ec8\u0e9c\u0ec8\u0eb2\u0e99 checksum\n+  the partition at sector %u is not where or as large as APA puts one: \u0e9e\u0eb2\u0e97\u0eb4\u0e8a\u0eb1\u0e99\u0e97\u0eb5\u0ec8\u0ec0\u0e8a\u0eb1\u0e81\u0ec0\u0e95\u0eb5 %u \u0e9a\u0ecd\u0ec8\u0ea2\u0eb9\u0ec8\u0e9a\u0ec8\u0ead\u0e99 \u0eab\u0ebc\u0eb7 \u0e9a\u0ecd\u0ec8\u0ea1\u0eb5\u0e82\u0eb0\u0edc\u0eb2\u0e94\u0e97\u0eb5\u0ec8 APA \u0ea7\u0eb2\u0e87\n+  the settings and art another copy of it uses: \u0e81\u0eb2\u0e99\u0e95\u0eb1\u0ec9\u0e87\u0e84\u0ec8\u0eb2 \u0ec1\u0ea5\u0eb0 \u0eae\u0eb9\u0e9a\u0e97\u0eb5\u0ec8\u0eaa\u0eb3\u0ec0\u0e99\u0ebb\u0eb2\u0ead\u0eb7\u0ec8\u0e99\u0e82\u0ead\u0e87\u0ea1\u0eb1\u0e99\u0ec3\u0e8a\u0ec9\n+  '%d FOLDERS': '%d \u0ec2\u0e9f\u0e99\u0ec0\u0e94\u0eb5'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': \u0eaa\u0eb3\u0ec0\u0e99\u0ebb\u0eb2 %s \u0ec4\u0e9b %s \u0e9a\u0ecd\u0ec8\u0ec4\u0e94\u0ec9. \u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0ec0\u0e95\u0eb1\u0ea1 \u0eab\u0ebc\u0eb7 \u0e96\u0eb7\u0e81\u0e9b\u0ec9\u0ead\u0e87\u0e81\u0eb1\u0e99\u0e81\u0eb2\u0e99\u0e82\u0ebd\u0e99\u0e9a\u0ecd?\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s \u0e9a\u0ecd\u0ec8\u0ea2\u0eb9\u0ec8\u0e9a\u0ec8\u0ead\u0e99\u0e97\u0eb5\u0ec8 RIPPS2 \u0e8a\u0ead\u0e81. \u0e8a\u0ead\u0e81\u0ea1\u0eb1\u0e99\u0ec3\u0e99\u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0e97\u0eb5\u0ec8 RIPPS2 \u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0ec1\u0ea5\u0ec9\u0ea7\u0e9a\u0ecd? \u0e9a\u0ec8\u0ead\u0e99\u0e97\u0eb5\u0ec8\u0e9e\u0ebb\u0e9a\u0e88\u0eb0\u0e96\u0eb7\u0e81\u0e88\u0eb7\u0ec8\u0ec4\u0ea7\u0ec9\u0eaa\u0eb3\u0ea5\u0eb1\u0e9a\u0e97\u0eb8\u0e81\u0e81\u0eb2\u0e99\u0ec0\u0e9b\u0eb5\u0e94\u0ec3\u0e8a\u0ec9\u0eab\u0ebc\u0eb1\u0e87\u0e88\u0eb2\u0e81\u0e99\u0eb1\u0ec9\u0e99.'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Ember \u0e9a\u0ecd\u0ec8\u0ea2\u0eb9\u0ec8\u0ec3\u0e99 %s, \u0e82\u0ec9\u0eb2\u0e87\u0ec0\u0e81\u0ea1, \u0ec1\u0ea5\u0eb0 \u0ea1\u0eb1\u0e99\u0e95\u0ec9\u0ead\u0e87\u0ea2\u0eb9\u0ec8\u0e9a\u0ec8\u0ead\u0e99\u0e99\u0eb1\u0ec9\u0e99. RIPPS2 \u0e9e\u0ebb\u0e9a\u0ea1\u0eb1\u0e99\u0e97\u0eb5\u0ec8 %s: START \u0eaa\u0eb3\u0ec0\u0e99\u0ebb\u0eb2\u0ea1\u0eb1\u0e99\u0ec4\u0e9b\u0e97\u0eb5\u0ec8\u0e99\u0eb1\u0ec9\u0e99 (%d KB) \u0ec1\u0ea5\u0eb0 \u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0ec0\u0e81\u0ea1.'\n+  Ember's BIOS: BIOS \u0e82\u0ead\u0e87 Ember\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : 'BIOS \u0e82\u0ead\u0e87 Ember (bios.bin) \u0e9a\u0ecd\u0ec8\u0ea2\u0eb9\u0ec8\u0ec3\u0e99 %s, \u0e82\u0ec9\u0eb2\u0e87 Ember. RIPPS2 \u0e9e\u0ebb\u0e9a\u0ead\u0eb1\u0e99\u0edc\u0eb6\u0ec8\u0e87\u0e97\u0eb5\u0ec8 %s: START \u0eaa\u0eb3\u0ec0\u0e99\u0ebb\u0eb2\u0ea1\u0eb1\u0e99\u0ec4\u0e9b\u0e97\u0eb5\u0ec8\u0e99\u0eb1\u0ec9\u0e99 (%d KB) \u0ec1\u0ea5\u0eb0 \u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0ec0\u0e81\u0ea1.'\n+  LOOKING FOR YOUR LAUNCHERS: \u0e81\u0eb3\u0ea5\u0eb1\u0e87\u0e8a\u0ead\u0e81\u0eab\u0eb2\u0e95\u0ebb\u0ea7\u0ec0\u0e9b\u0eb5\u0e94\u0e82\u0ead\u0e87\u0e97\u0ec8\u0eb2\u0e99\n+  The POPS files: \u0ec4\u0e9f\u0ea5\u0ecc POPS\n+  '%s  MMCE card': '%s  \u0e81\u0eb2\u0e94 MMCE'\n+  INSTALL RIPPS2'S NEUTRINO: \u0e95\u0eb4\u0e94\u0e95\u0eb1\u0ec9\u0e87 NEUTRINO \u0e82\u0ead\u0e87 RIPPS2\n+  'Internal HDD (APA): %s free of %s': 'HDD \u0e9e\u0eb2\u0e8d\u0ec3\u0e99 (APA): \u0eab\u0ea7\u0ec8\u0eb2\u0e87 %s \u0e88\u0eb2\u0e81 %s'\n+  LAUNCHERS: \u0e95\u0ebb\u0ea7\u0ec0\u0e9b\u0eb5\u0e94\n+  LOOK FOR THEM: \u0e8a\u0ead\u0e81\u0eab\u0eb2\u0e9e\u0ea7\u0e81\u0ea1\u0eb1\u0e99\n+  'Memory card %d: %d KB free': '\u0ec0\u0ea1\u0ea1\u0ec2\u0ea1\u0ea3\u0eb5\u0e81\u0eb2\u0e94 %d: \u0eab\u0ea7\u0ec8\u0eb2\u0e87 %d KB'\n+  POPS files: \u0ec4\u0e9f\u0ea5\u0ecc POPS\n+  START EVERY DRIVE: \u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0e97\u0eb8\u0e81\u0ec4\u0e94\u0ea3\u0e9f\u0ecc\n+  'STATUS: looking at the drives and the homebrew': '\u0eaa\u0eb0\u0e96\u0eb2\u0e99\u0eb0: \u0e81\u0eb3\u0ea5\u0eb1\u0e87\u0ec0\u0e9a\u0eb4\u0ec8\u0e87\u0ec4\u0e94\u0ea3\u0e9f\u0ecc \u0ec1\u0ea5\u0eb0 homebrew'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): \u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0e97\u0eb8\u0e81\u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0e81\u0ec8\u0ead\u0e99, \u0ec0\u0e9e\u0eb7\u0ec8\u0ead\u0ec3\u0eab\u0ec9\u0e81\u0eb2\u0e99\u0e8a\u0ead\u0e81\u0eab\u0eb2\u0ec4\u0e9b\u0ec0\u0e96\u0eb4\u0e87\u0e97\u0eb1\u0e87\u0edd\u0ebb\u0e94\u0e9a\u0ecd? (\u0e9a\u0ecd\u0ec8 \u0e88\u0eb0\u0e8a\u0ead\u0e81\u0ec3\u0e99\u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0e97\u0eb5\u0ec8\u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0ec1\u0ea5\u0ec9\u0ea7.)\n+  USB, HDD, MMCE and the rest: USB, HDD, MMCE \u0ec1\u0ea5\u0eb0 \u0ead\u0eb7\u0ec8\u0e99\u0ec6\n+  every started drive: \u0e97\u0eb8\u0e81\u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0e97\u0eb5\u0ec8\u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0ec1\u0ea5\u0ec9\u0ea7\n+  found where you keep them: \u0e9e\u0ebb\u0e9a\u0e9a\u0ec8\u0ead\u0e99\u0e97\u0eb5\u0ec8\u0e97\u0ec8\u0eb2\u0e99\u0ec0\u0e81\u0eb1\u0e9a\u0ec4\u0ea7\u0ec9\n+  not found yet: \u0e8d\u0eb1\u0e87\u0e9a\u0ecd\u0ec8\u0e9e\u0ebb\u0e9a\n+  not on a started drive: \u0e9a\u0ecd\u0ec8\u0ea2\u0eb9\u0ec8\u0ec3\u0e99\u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0e97\u0eb5\u0ec8\u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0ec1\u0ea5\u0ec9\u0ea7\n+  only if you have none: \u0eaa\u0eb0\u0ec0\u0e9e\u0eb2\u0eb0\u0e96\u0ec9\u0eb2\u0e97\u0ec8\u0eb2\u0e99\u0e9a\u0ecd\u0ec8\u0ea1\u0eb5\n+  starts every drive first: \u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0e97\u0eb8\u0e81\u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0e81\u0ec8\u0ead\u0e99\n+  Drives, launchers and homebrew: \u0ec4\u0e94\u0ea3\u0e9f\u0ecc, \u0e95\u0ebb\u0ea7\u0ec0\u0e9b\u0eb5\u0e94 \u0ec1\u0ea5\u0eb0 homebrew\n+  BDM Devices: \u0ead\u0eb8\u0e9b\u0eb0\u0e81\u0ead\u0e99 BDM\n+  Color Sweep: \u0e81\u0eb2\u0e99\u0e9b\u0ec8\u0ebd\u0e99\u0eaa\u0eb5\n+  Colors and Text: \u0eaa\u0eb5 \u0ec1\u0ea5\u0eb0 \u0e82\u0ecd\u0ec9\u0e84\u0ea7\u0eb2\u0ea1\n+  HDD POPS partitions: \u0e9e\u0eb2\u0e97\u0eb4\u0e8a\u0eb1\u0e99 POPS \u0e82\u0ead\u0e87 HDD\n+  Layout: \u0e81\u0eb2\u0e99\u0e88\u0eb1\u0e94\u0ea7\u0eb2\u0e87\n+  Missing Covers: \u0edc\u0ec9\u0eb2\u0e9b\u0ebb\u0e81\u0e97\u0eb5\u0ec8\u0e82\u0eb2\u0e94\n+  Name: \u0e8a\u0eb7\u0ec8\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: \u0e8d\u0eb1\u0e87\u0e9a\u0ecd\u0ec8\u0e9e\u0ebb\u0e9a\u0e81\u0eb2\u0e99\u0e95\u0eb1\u0ec9\u0e87\u0e84\u0ec8\u0eb2 SMB \u0e82\u0ead\u0e87 POPSTARTER. \u0ec3\u0eaa\u0ec8\u0ec1\u0e96\u0ea7\u0ea5\u0eb8\u0ec8\u0ea1\u0e99\u0eb5\u0ec9.\n+  POPSTARTER Network: \u0ec0\u0e84\u0eb7\u0ead\u0e82\u0ec8\u0eb2\u0e8d POPSTARTER\n+  POPSTARTER SMB Network: \u0ec0\u0e84\u0eb7\u0ead\u0e82\u0ec8\u0eb2\u0e8d SMB \u0e82\u0ead\u0e87 POPSTARTER\n+  POPSTARTER UDPFS Network: \u0ec0\u0e84\u0eb7\u0ead\u0e82\u0ec8\u0eb2\u0e8d UDPFS \u0e82\u0ead\u0e87 POPSTARTER\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: RIPPS2 \u0ec0\u0e9e\u0eb5\u0ec8\u0ea1\u0ec4\u0e94\u0ea3, ISO, VMC \u0ec1\u0ea5\u0eb0 \u0ec2\u0edd\u0e94\u0ea7\u0eb4\u0e94\u0eb5\u0ec2\u0ead\u0ec0\u0ead\u0e87.\n+  Screen: \u0edc\u0ec9\u0eb2\u0e88\u0ecd\n+  Speed: \u0e84\u0ea7\u0eb2\u0ea1\u0ec4\u0ea7\n+  Sweep Colors: \u0eaa\u0eb5\u0e82\u0ead\u0e87\u0e81\u0eb2\u0e99\u0e9b\u0ec8\u0ebd\u0e99\n+  Text Weight: \u0e84\u0ea7\u0eb2\u0ea1\u0edc\u0eb2\u0e82\u0ead\u0e87\u0e95\u0ebb\u0ea7\u0ead\u0eb1\u0e81\u0eaa\u0ead\u0e99\n+  Theme Color: \u0eaa\u0eb5\u0eab\u0ebc\u0eb1\u0e81\n+  Audio: \u0eaa\u0ebd\u0e87\n+  Bold: \u0edc\u0eb2\n+  Controllers: \u0e95\u0ebb\u0ea7\u0e84\u0ea7\u0e9a\u0e84\u0eb8\u0ea1\n+  Lively: \u0ea1\u0eb5\u0e8a\u0eb5\u0ea7\u0eb4\u0e94\u0e8a\u0eb5\u0ea7\u0eb2\n+  Medium: \u0e81\u0eb2\u0e87\n+  PS Emulation: \u0e81\u0eb2\u0e99\u0e88\u0eb3\u0ea5\u0ead\u0e87 PS\n+  Regular: \u0e9b\u0ebb\u0e81\u0e81\u0eb0\u0e95\u0eb4\n+  Slow: \u0e8a\u0ec9\u0eb2\n+  A MOMENT: \u0e88\u0eb1\u0e81\u0edc\u0ec9\u0ead\u0e8d\n+  A TASK IN THE BACKGROUND: \u0ea7\u0ebd\u0e81\u0ec3\u0e99\u0e9e\u0eb7\u0ec9\u0e99\u0eab\u0ebc\u0eb1\u0e87\n+  ABOUT %d S: \u0e9b\u0eb0\u0ea1\u0eb2\u0e99 %d \u0ea7\u0eb4\n+  ANY MOMENT: \u0ead\u0eb5\u0e81\u0e9a\u0ecd\u0ec8\u0e94\u0ebb\u0e99\n+  READING THE GAME LIST OF %s: \u0e81\u0eb3\u0ea5\u0eb1\u0e87\u0ead\u0ec8\u0eb2\u0e99\u0ea5\u0eb2\u0e8d\u0e8a\u0eb7\u0ec8\u0ec0\u0e81\u0ea1\u0e82\u0ead\u0e87 %s\n+  SAVING A LIST: \u0e81\u0eb3\u0ea5\u0eb1\u0e87\u0e9a\u0eb1\u0e99\u0e97\u0eb6\u0e81\u0ea5\u0eb2\u0e8d\u0e8a\u0eb7\u0ec8\n+  START NOW: \u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0e94\u0ebd\u0ea7\u0e99\u0eb5\u0ec9\n+  STARTING: \u0e81\u0eb3\u0ea5\u0eb1\u0e87\u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\n+  STARTS NEXT: \u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0e95\u0ecd\u0ec8\u0ec4\u0e9b\n+  STOPPING IT SAFELY: \u0e81\u0eb3\u0ea5\u0eb1\u0e87\u0ea2\u0eb8\u0e94\u0ea2\u0ec8\u0eb2\u0e87\u0e9b\u0ead\u0e94\u0ec4\u0e9e\n+  THE LAST OF THE QUEUE: \u0e97\u0ec9\u0eb2\u0e8d\u0e84\u0eb4\u0ea7\n+  WHEN IT FINISHES: \u0ec0\u0ea1\u0eb7\u0ec8\u0ead\u0ea1\u0eb1\u0e99\u0eaa\u0eb3\u0ec0\u0ea5\u0eb1\u0e94\n+  What each card starts; install or repair BIBLE.: \u0ec1\u0e95\u0ec8\u0ea5\u0eb0\u0e9a\u0eb1\u0e94\u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0eab\u0e8d\u0eb1\u0e87; \u0e95\u0eb4\u0e94\u0e95\u0eb1\u0ec9\u0e87 \u0eab\u0ebc\u0eb7 \u0eaa\u0ec9\u0ead\u0ea1\u0ec1\u0e9b\u0e87 BIBLE.\n+  Rainbow: \u0eae\u0eb8\u0ec9\u0e87\n+  Custom: \u0e81\u0eb3\u0e99\u0ebb\u0e94\u0ec0\u0ead\u0e87\n+  DESR DVR ISOs (experimental): \u0ec4\u0e9f\u0ea5\u0ecc ISO \u0e82\u0ead\u0e87 DESR DVR (\u0e97\u0ebb\u0e94\u0ea5\u0ead\u0e87)\n+  PC CARD CF (experimental): PC CARD CF (\u0e97\u0ebb\u0e94\u0ea5\u0ead\u0e87)\n+  HDD Start Mode: \u0ec2\u0edd\u0e94\u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0e95\u0ebb\u0ec9\u0e99 HDD\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): Roboto Condensed \u0ec2\u0e94\u0e8d The Roboto Project Authors (SIL OFL 1.1)\n+  '%s on %s': '%s \u0ec3\u0e99 %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: %s \u0e81\u0eb2\u0e8d\u0ec0\u0e9b\u0eb1\u0e99 %s (%d%%), \u0e81\u0ea7\u0e94\u0e97\u0eb8\u0e81\u0eaa\u0ec8\u0ea7\u0e99\u0ec1\u0ea5\u0ec9\u0ea7. \u0ea5\u0eb6\u0e9a ISO \u0ec1\u0ea5\u0eb0 \u0ec0\u0e81\u0eb1\u0e9a ZSO \u0ec4\u0ea7\u0ec9\u0e9a\u0ecd?'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: %s \u0e81\u0eb1\u0e9a\u0ea1\u0eb2\u0ec0\u0e9b\u0eb1\u0e99 %s, \u0e81\u0ea7\u0e94\u0e97\u0eb8\u0e81\u0eaa\u0ec8\u0ea7\u0e99\u0ec1\u0ea5\u0ec9\u0ea7. \u0ea5\u0eb6\u0e9a ZSO \u0ec1\u0ea5\u0eb0 \u0ec0\u0e81\u0eb1\u0e9a ISO \u0ec4\u0ea7\u0ec9\u0e9a\u0ecd?'\n+  A ZSO of this game is already beside it. Remove one of the two first.: \u0ea1\u0eb5 ZSO \u0e82\u0ead\u0e87\u0ec0\u0e81\u0ea1\u0e99\u0eb5\u0ec9\u0ea2\u0eb9\u0ec8\u0e82\u0ec9\u0eb2\u0e87\u0ea1\u0eb1\u0e99\u0ec1\u0ea5\u0ec9\u0ea7. \u0ea5\u0eb6\u0e9a\u0ead\u0eb1\u0e99\u0ec3\u0e94\u0ead\u0eb1\u0e99\u0edc\u0eb6\u0ec8\u0e87\u0ead\u0ead\u0e81\u0e81\u0ec8\u0ead\u0e99.\n+  About %d min, then checked: \u0e9b\u0eb0\u0ea1\u0eb2\u0e99 %d \u0e99\u0eb2\u0e97\u0eb5, \u0ec1\u0ea5\u0ec9\u0ea7\u0e81\u0ea7\u0e94\n+  An ISO of this game is already beside it. Remove one of the two first.: \u0ea1\u0eb5 ISO \u0e82\u0ead\u0e87\u0ec0\u0e81\u0ea1\u0e99\u0eb5\u0ec9\u0ea2\u0eb9\u0ec8\u0e82\u0ec9\u0eb2\u0e87\u0ea1\u0eb1\u0e99\u0ec1\u0ea5\u0ec9\u0ea7. \u0ea5\u0eb6\u0e9a\u0ead\u0eb1\u0e99\u0ec3\u0e94\u0ead\u0eb1\u0e99\u0edc\u0eb6\u0ec8\u0e87\u0ead\u0ead\u0e81\u0e81\u0ec8\u0ead\u0e99.\n+  CHECKING EVERY PIECE: \u0e81\u0eb3\u0ea5\u0eb1\u0e87\u0e81\u0ea7\u0e94\u0e97\u0eb8\u0e81\u0eaa\u0ec8\u0ea7\u0e99\n+  COMPRESS TO ZSO: \u0e9a\u0eb5\u0e9a\u0ead\u0eb1\u0e94\u0ec0\u0e9b\u0eb1\u0e99 ZSO\n+  'COMPRESSED %s: %s FREED': '\u0e9a\u0eb5\u0e9a\u0ead\u0eb1\u0e94 %s \u0ec1\u0ea5\u0ec9\u0ea7: \u0ec4\u0e94\u0ec9\u0e9e\u0eb7\u0ec9\u0e99\u0e97\u0eb5\u0ec8\u0e84\u0eb7\u0e99 %s'\n+  COMPRESSING 1 OF 2: \u0e81\u0eb3\u0ea5\u0eb1\u0e87\u0e9a\u0eb5\u0e9a\u0ead\u0eb1\u0e94 1 \u0e88\u0eb2\u0e81 2\n+  COMPRESSING 2 OF 2: \u0e81\u0eb3\u0ea5\u0eb1\u0e87\u0e9a\u0eb5\u0e9a\u0ead\u0eb1\u0e94 2 \u0e88\u0eb2\u0e81 2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : \u0e9a\u0eb5\u0e9a\u0ead\u0eb1\u0e94 %s \u0e9a\u0ecd? ISO \u0e88\u0eb0\u0ea2\u0eb9\u0ec8\u0e88\u0ebb\u0e99\u0e81\u0ea7\u0ec8\u0eb2 ZSO \u0e96\u0eb7\u0e81\u0ead\u0ec8\u0eb2\u0e99\u0e84\u0eb7\u0e99 \u0ec1\u0ea5\u0eb0 \u0e81\u0ea7\u0e94\u0ec1\u0ea5\u0ec9\u0ea7, \u0e88\u0eb2\u0e81\u0e99\u0eb1\u0ec9\u0e99 RIPPS2 \u0e88\u0eb0\u0e96\u0eb2\u0ea1\u0e81\u0ec8\u0ead\u0e99\u0ea5\u0eb6\u0e9a ISO. \u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0e95\u0ec9\u0ead\u0e87\u0ea1\u0eb5\u0e9a\u0ec8\u0ead\u0e99\u0e9e\u0ecd\u0eaa\u0eb3\u0ea5\u0eb1\u0e9a\u0e97\u0eb1\u0e87\u0eaa\u0ead\u0e87\u0ec3\u0e99\u0e82\u0eb0\u0e99\u0eb0\u0ec0\u0eae\u0eb1\u0e94\u0ea7\u0ebd\u0e81; \u0ec0\u0e9b\u0eb5\u0e94\u0ec0\u0e84\u0eb7\u0ec8\u0ead\u0e87\u0ec4\u0ea7\u0ec9\u0e88\u0ebb\u0e99\u0e81\u0ea7\u0ec8\u0eb2\u0e88\u0eb0\u0eaa\u0eb3\u0ec0\u0ea5\u0eb1\u0e94. ZSO \u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0ec4\u0e94\u0ec9\u0e81\u0eb1\u0e9a\u0ec1\u0e81\u0e99 OPL \u0ec0\u0e97\u0ebb\u0ec8\u0eb2\u0e99\u0eb1\u0ec9\u0e99 (\u0e9a\u0ecd\u0ec8\u0ec1\u0ea1\u0ec8\u0e99 Neutrino); \u0e9a\u0eb1\u0e99\u0e97\u0eb6\u0e81, \u0e81\u0eb2\u0e99\u0e95\u0eb1\u0ec9\u0e87\u0e84\u0ec8\u0eb2, \u0eae\u0eb9\u0e9a \u0ec1\u0ea5\u0eb0 \u0ec2\u0e81\u0e87 \u0e82\u0ead\u0e87\u0ea1\u0eb1\u0e99\u0ea2\u0eb9\u0ec8\u0e81\u0eb1\u0e9a\u0ea1\u0eb1\u0e99, \u0ec1\u0ea5\u0eb0 \u0eaa\u0eb2\u0ea1\u0eb2\u0e94\u0ec0\u0eae\u0eb1\u0e94\u0ec0\u0e9b\u0eb1\u0e99 ISO \u0e84\u0eb7\u0e99\u0ec4\u0e94\u0ec9\u0e97\u0eb8\u0e81\u0ec0\u0ea7\u0ea5\u0eb2.\n+  Compress to ZSO: \u0e9a\u0eb5\u0e9a\u0ead\u0eb1\u0e94\u0ec0\u0e9b\u0eb1\u0e99 ZSO\n+  Cycle: \u0edd\u0eb8\u0e99\u0ea7\u0ebd\u0e99\n+  'Experimental: OPL core only (not Neutrino)': '\u0e97\u0ebb\u0e94\u0ea5\u0ead\u0e87: \u0ec1\u0e81\u0e99 OPL \u0ec0\u0e97\u0ebb\u0ec8\u0eb2\u0e99\u0eb1\u0ec9\u0e99 (\u0e9a\u0ecd\u0ec8\u0ec1\u0ea1\u0ec8\u0e99 Neutrino)'\n+  Hold SELECT: \u0e81\u0ebb\u0e94 SELECT \u0e84\u0ec9\u0eb2\u0e87\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: \u0ec0\u0eae\u0eb1\u0e94\u0e9a\u0ecd\u0ec8\u0eaa\u0eb3\u0ec0\u0ea5\u0eb1\u0e94 (%s). \u0eaa\u0eb4\u0ec8\u0e87\u0e97\u0eb5\u0ec8\u0e82\u0ebd\u0e99\u0ec4\u0e9b\u0e96\u0eb7\u0e81\u0ea5\u0eb6\u0e9a\u0ec1\u0ea5\u0ec9\u0ea7; \u0ec0\u0e81\u0ea1\u0e8d\u0eb1\u0e87\u0e84\u0eb7\u0ec0\u0e81\u0ebb\u0ec8\u0eb2.\n+  List: \u0ea5\u0eb2\u0e8d\u0e81\u0eb2\u0e99\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : \u0ec0\u0eae\u0eb1\u0e94 %s \u0ec0\u0e9b\u0eb1\u0e99 ISO \u0e84\u0eb7\u0e99\u0e9a\u0ecd? ZSO \u0e88\u0eb0\u0ea2\u0eb9\u0ec8\u0e88\u0ebb\u0e99\u0e81\u0ea7\u0ec8\u0eb2 ISO \u0e96\u0eb7\u0e81\u0ead\u0ec8\u0eb2\u0e99\u0e84\u0eb7\u0e99 \u0ec1\u0ea5\u0eb0 \u0e81\u0ea7\u0e94\u0ec1\u0ea5\u0ec9\u0ea7, \u0e88\u0eb2\u0e81\u0e99\u0eb1\u0ec9\u0e99 RIPPS2 \u0e88\u0eb0\u0e96\u0eb2\u0ea1\u0e81\u0ec8\u0ead\u0e99\u0ea5\u0eb6\u0e9a ZSO. \u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0e95\u0ec9\u0ead\u0e87\u0ea1\u0eb5\u0e9a\u0ec8\u0ead\u0e99\u0e9e\u0ecd\u0eaa\u0eb3\u0ea5\u0eb1\u0e9a\u0e97\u0eb1\u0e87\u0eaa\u0ead\u0e87\u0ec3\u0e99\u0e82\u0eb0\u0e99\u0eb0\u0ec0\u0eae\u0eb1\u0e94\u0ea7\u0ebd\u0e81; \u0ec0\u0e9b\u0eb5\u0e94\u0ec0\u0e84\u0eb7\u0ec8\u0ead\u0e87\u0ec4\u0ea7\u0ec9\u0e88\u0ebb\u0e99\u0e81\u0ea7\u0ec8\u0eb2\u0e88\u0eb0\u0eaa\u0eb3\u0ec0\u0ea5\u0eb1\u0e94. \u0e9a\u0eb1\u0e99\u0e97\u0eb6\u0e81, \u0e81\u0eb2\u0e99\u0e95\u0eb1\u0ec9\u0e87\u0e84\u0ec8\u0eb2, \u0eae\u0eb9\u0e9a \u0ec1\u0ea5\u0eb0 \u0ec2\u0e81\u0e87 \u0e82\u0ead\u0e87\u0ea1\u0eb1\u0e99\u0ea2\u0eb9\u0ec8\u0e81\u0eb1\u0e9a\u0ea1\u0eb1\u0e99.\n+  Stop? What it has written so far is removed; the game stays as it was.: \u0ea2\u0eb8\u0e94\u0e9a\u0ecd? \u0eaa\u0eb4\u0ec8\u0e87\u0e97\u0eb5\u0ec8\u0e82\u0ebd\u0e99\u0ec4\u0e9b\u0ec1\u0ea5\u0ec9\u0ea7\u0e88\u0eb0\u0e96\u0eb7\u0e81\u0ea5\u0eb6\u0e9a; \u0ec0\u0e81\u0ea1\u0e8d\u0eb1\u0e87\u0e84\u0eb7\u0ec0\u0e81\u0ebb\u0ec8\u0eb2.\n+  THEMES: \u0e97\u0eb5\u0ea1\n+  There is not enough memory for this just now.: \u0e95\u0ead\u0e99\u0e99\u0eb5\u0ec9\u0edc\u0ec8\u0ea7\u0e8d\u0e84\u0ea7\u0eb2\u0ea1\u0e88\u0eb3\u0e9a\u0ecd\u0ec8\u0e9e\u0ecd\u0eaa\u0eb3\u0ea5\u0eb1\u0e9a\u0eaa\u0eb4\u0ec8\u0e87\u0e99\u0eb5\u0ec9.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: RIPPS2 \u0e9a\u0eb5\u0e9a\u0ead\u0eb1\u0e94 \u0eab\u0ebc\u0eb7 \u0e82\u0eb0\u0eab\u0e8d\u0eb2\u0e8d\u0ead\u0eb4\u0ea1\u0ec0\u0ea1\u0eb1\u0e94\u0e99\u0eb5\u0ec9\u0e9a\u0ecd\u0ec8\u0ec4\u0e94\u0ec9 (\u0e82\u0eb0\u0edc\u0eb2\u0e94\u0e82\u0ead\u0e87\u0ea1\u0eb1\u0e99\u0e9a\u0ecd\u0ec8\u0ec1\u0ea1\u0ec8\u0e99\u0e9a\u0ea5\u0eb1\u0ead\u0e81 2 KB \u0ec0\u0e95\u0eb1\u0ea1).\n+  UNCOMPRESS TO ISO: \u0e82\u0eb0\u0eab\u0e8d\u0eb2\u0e8d\u0ec0\u0e9b\u0eb1\u0e99 ISO\n+  UNCOMPRESSED %s: \u0e82\u0eb0\u0eab\u0e8d\u0eb2\u0e8d %s \u0ec1\u0ea5\u0ec9\u0ea7\n+  UNCOMPRESSING: \u0e81\u0eb3\u0ea5\u0eb1\u0e87\u0e82\u0eb0\u0eab\u0e8d\u0eb2\u0e8d\n+  Uncompress to ISO: \u0e82\u0eb0\u0eab\u0e8d\u0eb2\u0e8d\u0ec0\u0e9b\u0eb1\u0e99 ISO\n+  Under a minute, then checked: \u0e9a\u0ecd\u0ec8\u0eae\u0ead\u0e94\u0edc\u0eb6\u0ec8\u0e87\u0e99\u0eb2\u0e97\u0eb5, \u0ec1\u0ea5\u0ec9\u0ea7\u0e81\u0ea7\u0e94\n+  COPYING: \u0e81\u0eb3\u0ea5\u0eb1\u0e87\u0eaa\u0eb3\u0ec0\u0e99\u0ebb\u0eb2\n+  SHOW AGAIN, DELETE OR COMPRESS: \u0eaa\u0eb0\u0ec1\u0e94\u0e87\u0ead\u0eb5\u0e81, \u0ea5\u0ebb\u0e9a \u0eab\u0ebc\u0eb7 \u0e9a\u0eb5\u0e9a\u0ead\u0eb1\u0e94\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: \u0eaa\u0eb0\u0ec1\u0e94\u0e87\u0ead\u0eb5\u0e81, \u0ea5\u0ebb\u0e9a \u0eab\u0ebc\u0eb7 \u0e82\u0eb0\u0eab\u0e8d\u0eb2\u0e8d\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': '%s \u0e95\u0ec9\u0ead\u0e87\u0e81\u0eb2\u0e99\u0e9e\u0eb7\u0ec9\u0e99\u0e97\u0eb5\u0ec8\u0ea7\u0ec8\u0eb2\u0e87\u0e9b\u0eb0\u0ea1\u0eb2\u0e99 %s \u0ec3\u0e99\u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0e82\u0ead\u0e87\u0ea1\u0eb1\u0e99\u0ec3\u0e99\u0e82\u0eb0\u0e99\u0eb0\u0ec0\u0eae\u0eb1\u0e94\u0ea7\u0ebd\u0e81 (\u0e95\u0ebb\u0ec9\u0e99\u0eaa\u0eb0\u0e9a\u0eb1\u0e9a\u0ea2\u0eb9\u0ec8\u0e88\u0ebb\u0e99\u0ec0\u0e96\u0eb4\u0e87\u0e97\u0ec9\u0eb2\u0e8d), \u0ec1\u0ea5\u0eb0 \u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0ea1\u0eb5 %s \u0ea7\u0ec8\u0eb2\u0e87.'\n+  Hide and Compress: \u0ec0\u0e8a\u0eb7\u0ec8\u0ead\u0e87 \u0ec1\u0ea5\u0eb0 \u0e9a\u0eb5\u0e9a\u0ead\u0eb1\u0e94\n+  'COMPRESSED AND HIDDEN: %s': '\u0e9a\u0eb5\u0e9a\u0ead\u0eb1\u0e94 \u0ec1\u0ea5\u0eb0 \u0ec0\u0e8a\u0eb7\u0ec8\u0ead\u0e87\u0ec1\u0ea5\u0ec9\u0ea7: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': '\u0e9a\u0ecd\u0ec8\u0ea1\u0eb5\u0e9a\u0ec8\u0ead\u0e99\u0e9e\u0ecd\u0eaa\u0eb3\u0ea5\u0eb1\u0e9a\u0e81\u0eb2\u0e99\u0eaa\u0eb3\u0ec0\u0e99\u0ebb\u0eb2\u0e99\u0eb5\u0ec9: \u0e95\u0ec9\u0ead\u0e87\u0e81\u0eb2\u0e99\u0e9b\u0eb0\u0ea1\u0eb2\u0e99 %s, \u0ec1\u0ea5\u0eb0 \u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0ea1\u0eb5 %s \u0ea7\u0ec8\u0eb2\u0e87.'\n+  OVER 4 GB FOR FAT32: \u0ec0\u0e81\u0eb5\u0e99 4 GB \u0eaa\u0eb3\u0ea5\u0eb1\u0e9a FAT32\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: \u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0e99\u0eb5\u0ec9\u0ec0\u0e9b\u0eb1\u0e99 %s, \u0e97\u0eb5\u0ec8\u0e9a\u0ecd\u0ec8\u0ec0\u0e81\u0eb1\u0e9a\u0ec4\u0e9f\u0ea5\u0ecc 4 GB \u0e82\u0eb6\u0ec9\u0e99\u0ec4\u0e9b, \u0ec1\u0ea5\u0eb0 \u0ead\u0eb4\u0ea1\u0ec0\u0ea1\u0eb1\u0e94\u0ec3\u0edd\u0ec8\u0e88\u0eb0\u0ec0\u0e9b\u0eb1\u0e99 %s.\n+  '%s, %s clusters, %s free of %s': '%s, \u0e84\u0ea5\u0eb1\u0eaa\u0ec0\u0e95\u0eb5 %s, \u0ea7\u0ec8\u0eb2\u0e87 %s \u0e88\u0eb2\u0e81 %s'\n+  Delete This Sweep: \u0ea5\u0ebb\u0e9a\u0e81\u0eb2\u0e99\u0e9b\u0ec8\u0ebd\u0e99\u0eaa\u0eb5\u0e99\u0eb5\u0ec9\n+  Delete %s? RIPPS2's own sweeps stay as they are.: \u0ea5\u0ebb\u0e9a %s \u0e9a\u0ecd? \u0e81\u0eb2\u0e99\u0e9b\u0ec8\u0ebd\u0e99\u0eaa\u0eb5\u0e82\u0ead\u0e87 RIPPS2 \u0ec0\u0ead\u0e87\u0e8d\u0eb1\u0e87\u0e84\u0eb7\u0ec0\u0e81\u0ebb\u0ec8\u0eb2.\n+  Sweep: \u0e81\u0eb2\u0e99\u0e9b\u0ec8\u0ebd\u0e99\u0eaa\u0eb5\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: \u0e9b\u0ec9\u0eb2\u0e8d\u0edd\u0eb2\u0e8d\u0ec0\u0e81\u0ea1\u0e97\u0eb5\u0ec8\u0ea1\u0eb5\u0e84\u0ea7\u0eb2\u0ea1\u0eaa\u0eb3\u0ec0\u0ea5\u0eb1\u0e94\u0ec3\u0e99\u0ea5\u0eb2\u0e8d\u0e81\u0eb2\u0e99\u0e82\u0ead\u0e87\u0e97\u0ec8\u0eb2\u0e99 \u0e9e\u0ec9\u0ead\u0ea1\u0e88\u0eb3\u0e99\u0ea7\u0e99\u0e97\u0eb5\u0ec8\u0e9b\u0ebb\u0e94\u0ea5\u0eb1\u0ead\u0e81\u0ec1\u0ea5\u0ec9\u0ea7. \u0e95\u0ec9\u0ead\u0e87\u0ec0\u0e9b\u0eb5\u0e94 \u0ec0\u0e97\u0ec0\u0ea5\u0ec0\u0ea1\u0e97\u0ea3\u0eb5.\n labels:\n   SETTINGS_SAVED: \u0e9a\u0eb1\u0e99\u0e97\u0eb6\u0e81\u0e81\u0eb2\u0e99\u0e95\u0eb1\u0ec9\u0e87\u0e84\u0ec8\u0eb2 \u0ec4\u0ea7\u0ec9\u0ec3\u0e99 %s \u0ec1\u0ea5\u0ec9\u0ea7\n   REMOVE_ALL_SETTINGS: \u0ea5\u0ebb\u0e9a\u0e81\u0eb2\u0e99\u0e95\u0eb1\u0ec9\u0e87\u0e84\u0ec8\u0eb2\n@@ -1282,7 +1449,7 @@ labels:\n   SAVE_ICONS: \u0ec4\u0ead\u0e84\u0ead\u0e99\u0ec0\u0e8a\u0e9a 3D\n   HINT_SAVE_ICONS: \u0eaa\u0eb0\u0ec1\u0e94\u0e87\u0ec4\u0ead\u0e84\u0ead\u0e99\u0ec0\u0e8a\u0e9a \u0e82\u0ead\u0e87\u0ec0\u0e81\u0ea1\u0e97\u0eb5\u0ec8\u0ec0\u0ea5\u0eb7\u0ead\u0e81, \u0edd\u0eb9\u0e99\u0ea2\u0eb9\u0ec8, \u0e9a\u0ec8\u0ead\u0e99\u0e97\u0eb5\u0ec8\u0e97\u0eb5\u0ea1\u0ea1\u0eb5\u0e9a\u0ec8\u0ead\u0e99\u0ec3\u0eab\u0ec9\u0ea1\u0eb1\u0e99. \u0ead\u0ec8\u0eb2\u0e99 VMC \u0e82\u0ead\u0e87\u0ec0\u0e81\u0ea1\u0ec0\u0ead\u0e87 \u0e81\u0ec8\u0ead\u0e99, \u0ec1\u0ea5\u0ec9\u0ea7 \u0ec0\u0ea1\u0ea1\u0ec2\u0ea1\u0ea3\u0eb5\u0e81\u0eb2\u0e94.\n   NETBOOT_RESTART_NOW: \u0ec2\u0e9b\u0ec2\u0e95\u0e84\u0ead\u0e99\u0ec0\u0e84\u0eb7\u0ead\u0e82\u0ec8\u0eb2\u0e8d \u0e9b\u0ec8\u0ebd\u0e99 \u0eab\u0ebc\u0eb1\u0e87\u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0ec3\u0edd\u0ec8. \u0e9a\u0eb1\u0e99\u0e97\u0eb6\u0e81\u0e81\u0eb2\u0e99\u0e95\u0eb1\u0ec9\u0e87\u0e84\u0ec8\u0eb2 \u0ec1\u0ea5\u0eb0 \u0ec0\u0ea5\u0eb5\u0ec8\u0ea1 OPL \u0ec3\u0edd\u0ec8 \u0e94\u0ebd\u0ea7\u0e99\u0eb5\u0ec9 \u0e9a\u0ecd?\n-  HINT_NET_PROTOCOL: '\u0e9a\u0ecd\u0ea5\u0eb4\u0e81\u0eb2\u0e99\u0ec0\u0e84\u0eb7\u0ead\u0e82\u0ec8\u0eb2\u0e8d \u0e97\u0eb5\u0ec8 RiptOPL \u0ec3\u0e8a\u0ec9: \u0ec1\u0e8a SMB, UDPFS, UDPBD \u0eab\u0ebc\u0eb7 HTTP. \u0e81\u0eb2\u0e99\u0e95\u0eb1\u0ec9\u0e87\u0e84\u0ec8\u0eb2\u0e94\u0ebd\u0ea7\u0e81\u0eb1\u0e99 \u0e81\u0eb1\u0e9a\u0ec3\u0e99 \u0ec1\u0eab\u0ebc\u0ec8\u0e87\u0ec0\u0e81\u0ea1. \u0e81\u0eb2\u0e99\u0e9b\u0ec8\u0ebd\u0e99\u0e88\u0eb2\u0e81\u0ead\u0eb1\u0e99\u0e97\u0eb5\u0ec8\u0e81\u0eb3\u0ea5\u0eb1\u0e87\u0ec0\u0eae\u0eb1\u0e94\u0ea7\u0ebd\u0e81 \u0e95\u0ec9\u0ead\u0e87\u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0ec3\u0edd\u0ec8.'\n+  HINT_NET_PROTOCOL: '\u0e9a\u0ecd\u0ea5\u0eb4\u0e81\u0eb2\u0e99\u0ec0\u0e84\u0eb7\u0ead\u0e82\u0ec8\u0eb2\u0e8d \u0e97\u0eb5\u0ec8 RiptOPL \u0ec3\u0e8a\u0ec9: \u0ec1\u0e8a SMB, UDPFS, UDPBD \u0eab\u0ebc\u0eb7 HTTP. \u0e81\u0eb2\u0e99\u0e9b\u0ec8\u0ebd\u0e99\u0e88\u0eb2\u0e81\u0ead\u0eb1\u0e99\u0e97\u0eb5\u0ec8\u0e81\u0eb3\u0ea5\u0eb1\u0e87\u0ec0\u0eae\u0eb1\u0e94\u0ea7\u0ebd\u0e81 \u0e95\u0ec9\u0ead\u0e87\u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0ec3\u0edd\u0ec8.'\n   HINT_UDPFS_ACCESS: UDPFS \u0ec0\u0e97\u0ebb\u0ec8\u0eb2\u0e99\u0eb1\u0ec9\u0e99. \u0ec4\u0e9f\u0ea5\u0ecc \u0ead\u0ec8\u0eb2\u0e99 ISO \u0ec1\u0e8d\u0e81 \u0e88\u0eb2\u0e81\u0ec2\u0e9f\u0e99\u0ec0\u0e94\u0eb5\u0ec0\u0e81\u0ea1 \u0e82\u0ead\u0e87\u0ec0\u0e8a\u0eb5\u0e9a\u0ec0\u0ea7\u0eb5; IMG \u0ead\u0ec8\u0eb2\u0e99\u0eae\u0eb9\u0e9a\u0e94\u0eb4\u0eaa \u0ec0\u0e9b\u0eb1\u0e99\u0ec4\u0e94\u0ea3\u0e9f\u0ecc. SMB \u0ec1\u0ea5\u0eb0 HTTP \u0ec3\u0e8a\u0ec9 \u0ec4\u0e9f\u0ea5\u0ecc \u0eaa\u0eb0\u0ec0\u0edd\u0eb5, UDPBD \u0ec3\u0e8a\u0ec9 IMG \u0eaa\u0eb0\u0ec0\u0edd\u0eb5.\n   HINT_SMB_VERSION: \u0ec0\u0ea7\u0eb5\u0e8a\u0eb1\u0e99 SMB \u0e97\u0eb5\u0ec8\u0ec3\u0e8a\u0ec9 \u0eaa\u0eb7\u0ec8\u0eaa\u0eb2\u0e99\u0e81\u0eb1\u0e9a\u0ec1\u0e8a \u0e82\u0ead\u0e87 PC.\n   HINT_NET_ADVANCED: \u0e9b\u0ebb\u0e94\u0ea5\u0eb1\u0ead\u0e81 \u0ec2\u0edd\u0e94\u0ea5\u0eb4\u0ec9\u0e87 Ethernet \u0ec1\u0ea5\u0eb0 \u0e9e\u0ead\u0e94 SMB \u0ec0\u0e9e\u0eb7\u0ec8\u0ead\u0ec1\u0e81\u0ec9\u0ec4\u0e82.\n@@ -1483,3 +1650,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: \u0ea7\u0eb4\u0e97\u0eb5\u0e97\u0eb5\u0ec8\u0e9e\u0eb2\u0e9a\u0ea5\u0ecd\u0e96\u0ec9\u0eb2\u0ec3\u0e99\u0ea1\u0ec9\u0ea7\u0e99\u0e82\u0ead\u0e87\u0ec1\u0e95\u0ec8\u0ea5\u0eb0\u0ec0\u0e81\u0ea1\u0e81\u0eb2\u0e8d\u0ec0\u0e9b\u0eb1\u0e99 PNG.\n   RIPPS2_SETTINGS_HOME: \u0e9a\u0eb1\u0e99\u0e97\u0eb6\u0e81\u0e81\u0eb2\u0e99\u0e95\u0eb1\u0ec9\u0e87\u0e84\u0ec8\u0eb2\u0ec3\u0eaa\u0ec8\n   HINT_RIPPS2_SETTINGS_HOME: '\u0e9a\u0ec8\u0ead\u0e99\u0e97\u0eb5\u0ec8 RIPPS2 \u0ec0\u0e81\u0eb1\u0e9a\u0e81\u0eb2\u0e99\u0e95\u0eb1\u0ec9\u0e87\u0e84\u0ec8\u0eb2: \u0e9a\u0ec8\u0ead\u0e99\u0e9a\u0eb1\u0e99\u0e97\u0eb6\u0e81\u0e82\u0ead\u0e87\u0ea1\u0eb1\u0e99\u0ec0\u0e97\u0eb4\u0e87\u0ec0\u0ea1\u0ea1\u0ec2\u0ea1\u0ea3\u0eb5\u0e81\u0eb2\u0e94, \u0ec2\u0e9f\u0e99\u0ec0\u0e94\u0eb5 RIPPS2 \u0ec0\u0e97\u0eb4\u0e87\u0ec4\u0e94\u0ea3\u0e9f\u0ecc (HDD \u0e9e\u0eb2\u0e8d\u0ec3\u0e99, USB, MX4SIO, iLink \u0eab\u0ebc\u0eb7 MMCE), \u0eab\u0ebc\u0eb7 \u0ec2\u0e9f\u0e99\u0ec0\u0e94\u0eb5\u0e97\u0eb5\u0ec8\u0e97\u0ec8\u0eb2\u0e99\u0e9e\u0eb4\u0ea1.'\n+  RIPPS2_LAUNCHERS: \u0e95\u0ebb\u0ea7\u0ec0\u0e9b\u0eb5\u0e94\n+  RIPPS2_LOOK_NOW: \u0e8a\u0ead\u0e81\u0eab\u0eb2\u0e94\u0ebd\u0ea7\u0e99\u0eb5\u0ec9\n+  HINT_RIPPS2_LOOK_NOW: RIPPS2 \u0e8a\u0ead\u0e81\u0eab\u0eb2 POPSTARTER, \u0ec4\u0e9f\u0ea5\u0ecc POPS, Ember, BIOS \u0e82\u0ead\u0e87 Ember \u0ec1\u0ea5\u0eb0 Neutrino \u0ec3\u0e99\u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0e97\u0eb5\u0ec8\u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0ec1\u0ea5\u0ec9\u0ea7, \u0ec1\u0ea5\u0eb0 \u0e88\u0eb7\u0ec8\u0e9a\u0ec8\u0ead\u0e99\u0e82\u0ead\u0e87\u0ec1\u0e95\u0ec8\u0ea5\u0eb0\u0ead\u0eb1\u0e99. \u0e97\u0eb8\u0e81\u0e81\u0eb2\u0e99\u0ec0\u0e9b\u0eb5\u0e94\u0ec3\u0e8a\u0ec9\u0ead\u0ec8\u0eb2\u0e99\u0e9e\u0ea7\u0e81\u0ea1\u0eb1\u0e99\u0e88\u0eb2\u0e81\u0e9a\u0ec8\u0ead\u0e99\u0e99\u0eb1\u0ec9\u0e99.\n+  RIPPS2_HIDE_GAME: \u0ec0\u0e8a\u0eb7\u0ec8\u0ead\u0e87\u0ec0\u0e81\u0ea1\n+  RIPPS2_HIDDEN_GAMES: \u0ec0\u0e81\u0ea1\u0e97\u0eb5\u0ec8\u0ec0\u0e8a\u0eb7\u0ec8\u0ead\u0e87\u0ec4\u0ea7\u0ec9\n+  HINT_RIPPS2_HIDDEN_GAMES: \u0ec0\u0e81\u0ea1\u0e97\u0eb5\u0ec8\u0ec0\u0e8a\u0eb7\u0ec8\u0ead\u0e87\u0e94\u0ec9\u0ea7\u0e8d \u0ec0\u0e8a\u0eb7\u0ec8\u0ead\u0e87\u0ec0\u0e81\u0ea1, \u0ec3\u0e99\u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0e97\u0eb5\u0ec8\u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0ec1\u0ea5\u0ec9\u0ea7. \u0eaa\u0eb0\u0ec1\u0e94\u0e87\u0ead\u0eb1\u0e99\u0edc\u0eb6\u0ec8\u0e87\u0ead\u0eb5\u0e81, \u0eab\u0ebc\u0eb7 \u0ea5\u0ebb\u0e9a\u0ea1\u0eb1\u0e99. \u0ec1\u0e95\u0ec8\u0ea5\u0eb0\u0ec4\u0e94\u0ea3\u0e9f\u0ecc\u0ec0\u0e81\u0eb1\u0e9a\u0ea5\u0eb2\u0e8d\u0e81\u0eb2\u0e99\u0e82\u0ead\u0e87\u0e95\u0ebb\u0e99\u0ec0\u0ead\u0e87.\n+  HINT_RIPPS2_BDM_DEVICES: '\u0ec4\u0e94\u0ea3\u0e97\u0eb5\u0ec8 BDM \u0ead\u0ec8\u0eb2\u0e99\u0ec0\u0e81\u0ea1: USB, MX4SIO, PC Card CF, \u0ec4\u0e94\u0ea3 DVR \u0e82\u0ead\u0e87 DESR \u0ec1\u0ea5\u0eb0 iLink. HDD \u0e9e\u0eb2\u0e8d\u0ec3\u0e99\u0ec0\u0eae\u0eb1\u0e94\u0e95\u0eb2\u0ea1\u0ec2\u0edd\u0e94\u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0e95\u0ebb\u0ec9\u0e99 HDD.'\n+  HINT_RIPPS2_SCREEN: '\u0e9e\u0eb2\u0e9a\u0e82\u0ead\u0e87\u0ec0\u0ea1\u0e99\u0eb9: \u0ec2\u0edd\u0e94\u0ea7\u0eb4\u0e94\u0eb5\u0ec2\u0ead, \u0e88\u0ecd\u0e81\u0ea7\u0ec9\u0eb2\u0e87, \u0e9a\u0ec8\u0ead\u0e99\u0ea1\u0eb1\u0e99\u0ea2\u0eb9\u0ec8\u0ec0\u0e97\u0eb4\u0e87\u0ec2\u0e97\u0ea5\u0eb0\u0e97\u0eb1\u0e94 (H-POS, V-POS) \u0ec1\u0ea5\u0eb0 overscan.'\n+  HINT_RIPPS2_LAYOUT: \u0e9a\u0ec8\u0ead\u0e99\u0e97\u0eb5\u0ec8\u0edd\u0ea7\u0e94\u0edd\u0eb9\u0ec8\u0ea2\u0eb9\u0ec8 \u0ec1\u0ea5\u0eb0 \u0edc\u0ec9\u0eb2\u0ec0\u0e82\u0ebb\u0ec9\u0eb2\u0ea1\u0eb2\u0ec1\u0e99\u0ea7\u0ec3\u0e94, \u0ec1\u0ea5\u0ec9\u0ea7\u0edc\u0ec9\u0eb2\u0e9b\u0ebb\u0e81\u0e82\u0ead\u0e87 RIPFLOW \u0ec1\u0ea5\u0eb0 \u0e95\u0eb2\u0e82\u0ec8\u0eb2\u0e8d\u0e82\u0ead\u0e87 RIPGRID.\n+  HINT_RIPPS2_COLORS_TEXT: \u0e84\u0ea7\u0eb2\u0ea1\u0edc\u0eb2\u0e82\u0ead\u0e87\u0e95\u0ebb\u0ea7\u0ead\u0eb1\u0e81\u0eaa\u0ead\u0e99, \u0e97\u0eb5\u0ea1\u0eaa\u0eb5, \u0e81\u0eb2\u0e99\u0e9b\u0ec8\u0ebd\u0e99\u0eaa\u0eb5 \u0ec1\u0ea5\u0eb0 \u0e97\u0eb8\u0e81\u0eaa\u0eb5.\n+  HINT_RIPPS2_TEXT_WEIGHT: '\u0e82\u0ecd\u0ec9\u0e84\u0ea7\u0eb2\u0ea1\u0e82\u0ead\u0e87 RIPPS2 \u0ec0\u0ead\u0e87\u0edc\u0eb2\u0ec0\u0e97\u0ebb\u0ec8\u0eb2\u0ec3\u0e94: \u0e84\u0eb3\u0ec1\u0e99\u0eb0\u0e99\u0eb3\u0ec3\u0e99\u0e81\u0eb2\u0e99\u0e95\u0eb1\u0ec9\u0e87\u0e84\u0ec8\u0eb2, \u0eaa\u0eb0\u0e96\u0eb2\u0e99\u0eb0 \u0ec1\u0ea5\u0eb0 \u0e81\u0ec8\u0ebd\u0ea7\u0e81\u0eb1\u0e9a. \u0e9b\u0ebb\u0e81\u0e81\u0eb0\u0e95\u0eb4\u0e84\u0eb7\u0ec0\u0e81\u0ebb\u0ec8\u0eb2; \u0e81\u0eb2\u0e87 \u0ec1\u0ea5\u0eb0 \u0edc\u0eb2 \u0ead\u0ec8\u0eb2\u0e99\u0e87\u0ec8\u0eb2\u0e8d\u0e81\u0ea7\u0ec8\u0eb2\u0ec3\u0e99\u0e9e\u0eb2\u0e9a\u0e97\u0eb5\u0ec8\u0ead\u0ec8\u0ead\u0e99.'\n+  HINT_RIPPS2_HDD_START: 'HDD \u0e9e\u0eb2\u0e8d\u0ec3\u0e99, APA \u0eab\u0ebc\u0eb7 exFAT: RIPPS2 \u0ead\u0ec8\u0eb2\u0e99\u0ea1\u0eb1\u0e99\u0e9a\u0ecd\u0ec8\u0ea7\u0ec8\u0eb2\u0e88\u0eb0\u0ec0\u0e9b\u0eb1\u0e99\u0ec1\u0e9a\u0e9a\u0ec3\u0e94 \u0ec1\u0ea5\u0eb0 \u0eaa\u0eb0\u0ec1\u0e94\u0e87\u0e84\u0eb1\u0ec9\u0e87\u0e94\u0ebd\u0ea7 \u0ec0\u0e9b\u0eb1\u0e99\u0ec0\u0e81\u0ea1 HDD. \u0e9b\u0eb4\u0e94 \u0e88\u0eb0\u0e9a\u0ecd\u0ec8\u0ec1\u0e95\u0eb0\u0ea1\u0eb1\u0e99.'\n+  HINT_RIPPS2_THEME_COLOR: \u0eaa\u0eb5\u0e82\u0ead\u0e87\u0e97\u0eb5\u0ea1\u0ec0\u0ead\u0e87. \u0ec0\u0ea5\u0eb7\u0ead\u0e81 \u0e81\u0eb3\u0e99\u0ebb\u0e94\u0ec0\u0ead\u0e87 \u0ec0\u0e9b\u0eb1\u0e99\u0e97\u0eb5\u0ea1\u0eaa\u0eb5 \u0ec0\u0e9e\u0eb7\u0ec8\u0ead\u0ec0\u0ea5\u0eb7\u0ead\u0e81\u0ec0\u0ead\u0e87 \u0e84\u0eb7\u0e81\u0eb1\u0e9a\u0eaa\u0eb5\u0e82\u0ecd\u0ec9\u0e84\u0ea7\u0eb2\u0ea1.\n+  HINT_RIPPS2_SWEEP: \u0eab\u0ec9\u0eb2\u0eaa\u0eb5\u0e97\u0eb5\u0ec8\u0e97\u0eb5\u0ea1\u0e9c\u0ec8\u0eb2\u0e99\u0ec4\u0e9b\u0e8a\u0ec9\u0eb2\u0ec6 \u0ec1\u0ea5\u0eb0 \u0ea5\u0ebd\u0e9a. \u0eae\u0eb8\u0ec9\u0e87, \u0e81\u0eb2\u0e99\u0e9b\u0ec8\u0ebd\u0e99\u0e88\u0eb2\u0e81\u0ec1\u0e95\u0ec8\u0ea5\u0eb0\u0e97\u0eb5\u0ea1\u0eaa\u0eb5, \u0eab\u0ebc\u0eb7 \u0e82\u0ead\u0e87\u0ec0\u0e88\u0ebb\u0ec9\u0eb2\u0ec0\u0ead\u0e87. \u0e9b\u0eb4\u0e94 \u0e88\u0eb0\u0ec3\u0eab\u0ec9\u0e97\u0eb5\u0ea1\u0eaa\u0eb5\u0ea2\u0eb9\u0ec8\u0e99\u0eb4\u0ec9\u0e87.\n+  HINT_RIPPS2_SWEEP_COLORS: \u0eab\u0ec9\u0eb2\u0eaa\u0eb5\u0e82\u0ead\u0e87\u0e81\u0eb2\u0e99\u0e9b\u0ec8\u0ebd\u0e99 \u0ec1\u0ea5\u0eb0 \u0e84\u0ea7\u0eb2\u0ea1\u0ec4\u0ea7\u0e82\u0ead\u0e87\u0ea1\u0eb1\u0e99. \u0e9b\u0ec8\u0ebd\u0e99\u0ead\u0eb1\u0e99\u0edc\u0eb6\u0ec8\u0e87\u0e82\u0ead\u0e87 RIPPS2 \u0ec1\u0ea5\u0ec9\u0ea7\u0ea1\u0eb1\u0e99\u0e88\u0eb0\u0e96\u0eb7\u0e81\u0ec0\u0e81\u0eb1\u0e9a\u0ec0\u0e9b\u0eb1\u0e99\u0e82\u0ead\u0e87\u0ec0\u0e88\u0ebb\u0ec9\u0eb2 \u0e94\u0ec9\u0ea7\u0e8d\u0e8a\u0eb7\u0ec8\u0e97\u0eb5\u0ec8\u0ec0\u0e88\u0ebb\u0ec9\u0eb2\u0e95\u0eb1\u0ec9\u0e87.\n+  HINT_RIPPS2_SWEEP_NAME: \u0e8a\u0eb7\u0ec8\u0e82\u0ead\u0e87\u0e81\u0eb2\u0e99\u0e9b\u0ec8\u0ebd\u0e99\u0e82\u0ead\u0e87\u0ec0\u0e88\u0ebb\u0ec9\u0eb2\u0ec3\u0e99\u0ea5\u0eb2\u0e8d\u0e8a\u0eb7\u0ec8 \u0e81\u0eb2\u0e99\u0e9b\u0ec8\u0ebd\u0e99\u0eaa\u0eb5.\n+  HINT_RIPPS2_SWEEP_COLOR: \u0edc\u0eb6\u0ec8\u0e87\u0ec3\u0e99\u0eab\u0ec9\u0eb2\u0eaa\u0eb5\u0e97\u0eb5\u0ec8\u0e81\u0eb2\u0e99\u0e9b\u0ec8\u0ebd\u0e99\u0e9c\u0ec8\u0eb2\u0e99, \u0e95\u0eb2\u0ea1\u0ea5\u0eb3\u0e94\u0eb1\u0e9a.\n+  HINT_RIPPS2_SWEEP_SPEED: '\u0ec1\u0e95\u0ec8\u0ea5\u0eb0\u0eaa\u0eb5\u0ea2\u0eb9\u0ec8\u0e94\u0ebb\u0e99\u0ec0\u0e97\u0ebb\u0ec8\u0eb2\u0ec3\u0e94\u0e81\u0ec8\u0ead\u0e99\u0eaa\u0eb5\u0e95\u0ecd\u0ec8\u0ec4\u0e9b\u0e88\u0eb0\u0ec0\u0e82\u0ebb\u0ec9\u0eb2\u0ea1\u0eb2. \u0ec0\u0e96\u0eb4\u0e87 \u0ea1\u0eb5\u0e8a\u0eb5\u0ea7\u0eb4\u0e94\u0e8a\u0eb5\u0ea7\u0eb2 \u0e81\u0ecd\u0e8d\u0eb1\u0e87\u0ea5\u0ebd\u0e9a: \u0eaa\u0eb5\u0e9a\u0ecd\u0ec8\u0ec0\u0e84\u0eb5\u0e8d\u0e81\u0eb0\u0e9e\u0eb4\u0e9a.'\n+  RIPPS2_HIDE_COMPRESS: \u0ec0\u0e8a\u0eb7\u0ec8\u0ead\u0e87\u0ec0\u0e81\u0ea1 \u0ec1\u0ea5\u0eb0 \u0e9a\u0eb5\u0e9a\u0ead\u0eb1\u0e94\n+  HINT_RIPPS2_SELECT_HOLD: '\u0e81\u0ebb\u0e94 SELECT \u0e84\u0ec9\u0eb2\u0e87 4.2 \u0ea7\u0eb4 \u0ec3\u0e99\u0ea5\u0eb2\u0e8d\u0e81\u0eb2\u0e99\u0ec0\u0e81\u0ea1. \u0ea5\u0eb2\u0e8d\u0e81\u0eb2\u0e99: \u0e97\u0eb5\u0ea1\u0ec3\u0e99\u0ec1\u0e9c\u0e87, \u0ec0\u0ea5\u0eb7\u0ead\u0e81\u0ead\u0eb1\u0e99\u0edc\u0eb6\u0ec8\u0e87. \u0edd\u0eb8\u0e99\u0ea7\u0ebd\u0e99: \u0e97\u0eb5\u0ea1\u0e96\u0eb1\u0e94\u0ec4\u0e9b. \u0e9b\u0eb4\u0e94: \u0e9a\u0ecd\u0ec8\u0ea1\u0eb5\u0eab\u0e8d\u0eb1\u0e87 (\u0e81\u0ebb\u0e94\u0e8d\u0eb1\u0e87\u0ec2\u0eab\u0ebc\u0e94\u0e84\u0eb7\u0e99).'"
        },
        {
          "sha": "f69287324a6a59039b818a6d77733953f2772e6e",
          "filename": "elf/lang/Persian.yml",
          "status": "modified",
          "additions": 209,
          "deletions": 22,
          "changes": 231,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FPersian.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FPersian.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FPersian.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: Persian\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTER \u0628\u0631\u0627\u06cc \u067e\u0630\u06cc\u0631\u0641\u062a\u0646\u060c ESC \u0628\u0631\u0627\u06cc \u0644\u063a\u0648. \u0647\u0631 \u062f\u06a9\u0645\u0647\u200c\u0627\u06cc \u06a9\u0644\u06cc\u062f\u0647\u0627 \u0631\u0627 \u0646\u0634\u0627\u0646 \u0645\u06cc\u200c\u062f\u0647\u062f.\n   USB KEYBOARD: \u0635\u0641\u062d\u0647\u200c\u06a9\u0644\u06cc\u062f USB\n-  '      DESR DVR ISOs (experimental)': '      \u0641\u0627\u06cc\u0644\u200c\u0647\u0627\u06cc DESR DVR ISO (\u0622\u0632\u0645\u0627\u06cc\u0634\u06cc)'\n-  '      Internal HDD (exFAT)': '      \u0647\u0627\u0631\u062f \u062f\u0627\u062e\u0644\u06cc (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (\u0622\u0632\u0645\u0627\u06cc\u0634\u06cc)'\n-  '      Protocol': '      \u067e\u0631\u0648\u062a\u06a9\u0644'\n   Access: \u062f\u0633\u062a\u0631\u0633\u06cc\n   Applications Page Start Mode: \u062d\u0627\u0644\u062a \u0634\u0631\u0648\u0639 \u0635\u0641\u062d\u0647 \u0628\u0631\u0646\u0627\u0645\u0647\u200c\u0647\u0627\n   Art Delay: \u062a\u0623\u062e\u06cc\u0631 \u062a\u0635\u0648\u06cc\u0631\u0647\u0627\n@@ -22,7 +18,6 @@ strings:\n   Fifth: \u067e\u0646\u062c\u0645\n   First: \u06cc\u06a9\u0645\n   Fourth: \u0686\u0647\u0627\u0631\u0645\n-  HDD (APA) Start Mode: \u062d\u0627\u0644\u062a \u0634\u0631\u0648\u0639 \u0647\u0627\u0631\u062f (APA)\n   HDD Cache: \u062d\u0627\u0641\u0638\u0647 \u0646\u0647\u0627\u0646 \u0647\u0627\u0631\u062f\n   HDD Spindown: \u062a\u0648\u0642\u0641 \u0686\u0631\u062e\u0634 \u0647\u0627\u0631\u062f\n   MMCE Settings: \u062a\u0646\u0638\u06cc\u0645\u0627\u062a MMCE\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: \u0627\u06cc\u0646 \u06cc\u06a9 \u0628\u0627\u0632\u06cc PS2 \u0628\u0627 \u0648\u0635\u0644\u0647 ESR \u0627\u0633\u062a. \u0628\u0631\u0627\u06cc \u0627\u062c\u0631\u0627\u06cc \u0622\u0646 ESR (esr*.elf) \u0631\u0627 \u062f\u0631 APPS\u060c BOOT \u06cc\u0627 ESR \u0631\u0648\u06cc \u06a9\u0627\u0631\u062a \u062d\u0627\u0641\u0638\u0647 \u06cc\u0627 \u0641\u0644\u0634 USB \u0628\u06af\u0630\u0627\u0631\u06cc\u062f.\n   '%s, BUILD %d': '%s\u060c \u0628\u06cc\u0644\u062f %d'\n   '... and the anonymous ...': '... \u0648 \u06af\u0645\u0646\u0627\u0645\u0627\u0646 ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': '\u0646\u0633\u062e\u0647 1.8.0 \u0628\u062f\u0648\u0646 \u062a\u063a\u06cc\u06cc\u0631 \u0647\u0645\u0631\u0627\u0647 \u0627\u0633\u062a (AFL-3.0): github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: \u0628\u0627\u0632\u0633\u0627\u0632\u06cc \u06a9\u0627\u0645\u0644 RiptOPL\u060c \u0627\u0646\u0634\u0639\u0627\u0628 Open PS2 Loader\u060c\n   ABOUT: \u062f\u0631\u0628\u0627\u0631\u0647\n   'ACHIEVEMENTS: SPIRIT OF RA': '\u062f\u0633\u062a\u0627\u0648\u0631\u062f\u0647\u0627: SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): \u0648\u06cc\u062f\u06cc\u0648\u06cc AVI \u062f\u0631 \u0641\u0627\u06cc\u0644\u200c\u0647\u0627\u06cc \u062d\u0627\u0641\u0638\u0647\u060c \u0627\u0646\u0634\u0639\u0627\u0628 RIPPS2 (AFL 2.0)\n   Apollo Save Tool by bucanero: Apollo Save Tool \u0627\u0632 bucanero\n   BACK: \u0628\u0627\u0632\u06af\u0634\u062a\n+  Back (keeps both): \u0628\u0627\u0632\u06af\u0634\u062a (\u0647\u0631 \u062f\u0648 \u0645\u06cc\u200c\u0645\u0627\u0646\u0646\u062f)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: \u0627\u06cc\u0646 \u06a9\u0627\u0631\u062a MMCE \u0646\u0645\u06cc\u200c\u062a\u0648\u0627\u0646\u062f \u0646\u0627\u0645 \u0641\u0627\u06cc\u0644 \u0631\u0627 \u062a\u063a\u06cc\u06cc\u0631 \u062f\u0647\u062f\u060c \u06a9\u0627\u0631\u06cc \u06a9\u0647 \u0641\u0634\u0631\u062f\u0647\u200c\u0633\u0627\u0632\u06cc \u062f\u0631 \u06af\u0627\u0645 \u0622\u062e\u0631\u0634 \u0628\u0647 \u0622\u0646 \u0646\u06cc\u0627\u0632 \u062f\u0627\u0631\u062f. \u0641\u0631\u06cc\u0645\u200c\u0648\u0631 sd2psXtd \u0622\u0646 \u0631\u0627 \u0628\u0647 1.4.0 \u06cc\u0627 \u062c\u062f\u06cc\u062f\u062a\u0631 \u0628\u0647\u200c\u0631\u0648\u0632 \u06a9\u0646\u06cc\u062f (sd2psxtd.github.io/download) \u0648 \u062f\u0648\u0628\u0627\u0631\u0647 \u0627\u0645\u062a\u062d\u0627\u0646 \u06a9\u0646\u06cc\u062f. \u0686\u06cc\u0632\u06cc \u062a\u063a\u06cc\u06cc\u0631 \u0646\u06a9\u0631\u062f.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): BIBLE \u0647\u0645\u0627\u0646 PS2BBL 1.2.0 \u0627\u0633\u062a\u060c \u0628\u06cc\u0644\u062f\u0647\u0627\u06cc \u0627\u0645\u0636\u0627\u0634\u062f\u0647 El Isra (\u06a9\u0627\u0631\u062a \u0648 \u0647\u0627\u0631\u062f)\u060c \u0628\u062f\u0648\u0646 \u062a\u063a\u06cc\u06cc\u0631 (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: \u06af\u0632\u06cc\u0646\u0647 \u0635\u062f\u0627\u0647\u0627\u06cc BIOS > \u0627\u0633\u062a\u062e\u0631\u0627\u062c \u0622\u0646\u200c\u0647\u0627 \u0631\u0627 \u0645\u0627\u0646\u0646\u062f SPU2 \u067e\u062e\u0634 \u0645\u06cc\u200c\u06a9\u0646\u062f\u061b \u0647\u06cc\u0686 BIOS\u06cc \u0647\u0645\u0631\u0627\u0647 \u0646\u06cc\u0633\u062a\n   BOOT LOCK AND BIBLE: BOOT LOCK \u0648 BIBLE\n   BOOT LOCK edits its config too: BOOT LOCK \u067e\u06cc\u06a9\u0631\u0628\u0646\u062f\u06cc \u0622\u0646 \u0631\u0627 \u0647\u0645 \u0648\u06cc\u0631\u0627\u06cc\u0634 \u0645\u06cc\u200c\u06a9\u0646\u062f\n   BUILD DETAILS: \u062c\u0632\u0626\u06cc\u0627\u062a \u0628\u06cc\u0644\u062f\n-  BUILT ON RIPTOPL: \u0633\u0627\u062e\u062a\u0647\u200c\u0634\u062f\u0647 \u0631\u0648\u06cc RIPTOPL\n   BearSSL by Thomas Pornin: BearSSL \u0627\u0632 Thomas Pornin\n   Built with Claude (Anthropic): \u0633\u0627\u062e\u062a\u0647\u200c\u0634\u062f\u0647 \u0628\u0627 Claude (Anthropic)\n   CD PLAYER: \u067e\u062e\u0634\u200c\u06a9\u0646\u0646\u062f\u0647 CD\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek \u0648 Sleek Bold\u060c \u0637\u0631\u0627\u062d\u06cc\u200c\u0634\u062f\u0647 \u0628\u0631\u0627\u06cc RIPPS2\n   RetroAchievements on the console by hacan359 (yoba): RetroAchievements \u0631\u0648\u06cc \u06a9\u0646\u0633\u0648\u0644 \u0627\u0632 hacan359 (yoba)\n   RiptOPL by NathanNeurotic: RiptOPL \u0627\u0632 NathanNeurotic\n-  Roboto Condensed by Google (Apache-2.0): Roboto Condensed \u0627\u0632 Google (Apache-2.0)\n   SOUNDS: \u0635\u062f\u0627\u0647\u0627\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: \u0646\u0635\u0628\u200c\u06a9\u0646\u0646\u062f\u0647 FMCB \u0627\u0632 SP193 \u0648 OSDMenu \u0627\u0632 pcm720\u060c \u06af\u0627\u0645\u200c\u0628\u0647\u200c\u06af\u0627\u0645 \u062f\u0646\u0628\u0627\u0644\u200c\u0634\u062f\u0647\n   SUPPORT: \u067e\u0634\u062a\u06cc\u0628\u0627\u0646\u06cc\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: wLaunchELF R3Z \u0627\u0632 NathanNeurotic\n   wLaunchELF by the ps2homebrew team: wLaunchELF \u0627\u0632 \u062a\u06cc\u0645 ps2homebrew\n   what RIPPS2's card image writes are checked against: \u0686\u06cc\u0632\u06cc \u06a9\u0647 \u062a\u0635\u0648\u06cc\u0631\u0647\u0627\u06cc \u06a9\u0627\u0631\u062a\u0650 \u0646\u0648\u0634\u062a\u0647\u200c\u0634\u062f\u0647 \u062a\u0648\u0633\u0637 RIPPS2 \u0628\u0627 \u0622\u0646 \u0633\u0646\u062c\u06cc\u062f\u0647 \u0645\u06cc\u200c\u0634\u0648\u0646\u062f\n-  with its own look, launchers, file browser and save tools.: \u0628\u0627 \u0638\u0627\u0647\u0631\u060c \u0644\u0627\u0646\u0686\u0631\u0647\u0627\u060c \u0645\u0631\u0648\u0631\u06af\u0631 \u0641\u0627\u06cc\u0644 \u0648 \u0627\u0628\u0632\u0627\u0631\u0647\u0627\u06cc \u0630\u062e\u06cc\u0631\u0647 \u062e\u0648\u062f\u0634.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: xeRAbora \u0627\u0632 hacan359\u060c rcheevos \u0627\u0632 RetroAchievements\n   your console's own, ripped from its BIOS: \u0635\u062f\u0627\u0647\u0627\u06cc \u062e\u0648\u062f \u06a9\u0646\u0633\u0648\u0644 \u0634\u0645\u0627\u060c \u0627\u0633\u062a\u062e\u0631\u0627\u062c\u200c\u0634\u062f\u0647 \u0627\u0632 BIOS \u0622\u0646\n   ' (no DNS answer)': ' (\u067e\u0627\u0633\u062e\u06cc \u0627\u0632 DNS \u0646\u06cc\u0627\u0645\u062f)'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO: \u062f\u0631 \u062d\u0627\u0644 \u0646\u0648\u0634\u062a\u0646 %.60s'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: \u0646\u0648\u0634\u062a\u0646 Neutrino \u062f\u0631 %s \u0645\u0645\u06a9\u0646 \u0646\u0634\u062f. \u0641\u0636\u0627\u06cc \u062e\u0627\u0644\u06cc \u0622\u0646 \u0631\u0627 \u0628\u0631\u0631\u0633\u06cc \u06a9\u0646\u06cc\u062f \u0648 \u0627\u06cc\u0646\u06a9\u0647 \u062f\u0631 \u0628\u0631\u0627\u0628\u0631 \u0646\u0648\u0634\u062a\u0646 \u0645\u062d\u0627\u0641\u0638\u062a \u0646\u0634\u062f\u0647 \u0628\u0627\u0634\u062f\u060c \u0633\u067e\u0633 \u062f\u0648\u0628\u0627\u0631\u0647 \u062a\u0644\u0627\u0634 \u06a9\u0646\u06cc\u062f.\n   THE GAME'S DRIVE  (%s/neutrino): \u062f\u0631\u0627\u06cc\u0648 \u0628\u0627\u0632\u06cc  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : \u0627\u06cc\u0646 \u0628\u0627\u0632\u06cc \u0627\u0632 \u0637\u0631\u06cc\u0642 Neutrino \u0627\u062c\u0631\u0627 \u0645\u06cc\u200c\u0634\u0648\u062f \u0648 \u062c\u0627\u06cc\u06cc \u06a9\u0647 RIPPS2 \u062c\u0633\u062a\u200c\u0648\u062c\u0648 \u0645\u06cc\u200c\u06a9\u0646\u062f (\u062a\u0646\u0638\u06cc\u0645\u0627\u062a\u060c \u062f\u0631\u0627\u06cc\u0648 \u0628\u0627\u0632\u06cc\u060c \u06a9\u0627\u0631\u062a\u200c\u0647\u0627\u06cc \u062d\u0627\u0641\u0638\u0647) Neutrino \u0646\u06cc\u0633\u062a. RIPPS2 \u0646\u0633\u062e\u0647 Neutrino 1.8.0 \u0627\u0632 rickgaiser (AFL-3.0) \u0631\u0627 \u0647\u0645\u0631\u0627\u0647 \u062f\u0627\u0631\u062f. \u0627\u06a9\u0646\u0648\u0646 \u0646\u0635\u0628 \u0634\u0648\u062f\u061f \u0633\u067e\u0633 \u062c\u0627\u06cc \u0622\u0646 \u0631\u0627 \u0627\u0646\u062a\u062e\u0627\u0628 \u0645\u06cc\u200c\u06a9\u0646\u06cc\u062f.\n   '%d / %d FILES': '%d / %d \u0641\u0627\u06cc\u0644'\n   '%d FOLDERS AND CONTENTS': '%d \u067e\u0648\u0634\u0647 \u0648 \u0645\u062d\u062a\u0648\u0627'\n   '%d FOLDERS, %d FOUND': '%d \u067e\u0648\u0634\u0647\u060c %d \u0645\u0648\u0631\u062f \u067e\u06cc\u062f\u0627 \u0634\u062f'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d \u0627\u0632 %d \u0647\u0646\u0648\u0632 \u0642\u0641\u0644 \u0627\u0633\u062a'\n   '%s   on %s': '%s   \u0631\u0648\u06cc %s'\n   BADGES: \u0646\u0634\u0627\u0646\u200c\u0647\u0627\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: \u0646\u0634\u0627\u0646\u200c\u0647\u0627 \u0641\u0642\u0637 \u0628\u0627 \u0631\u0648\u0634\u0646 \u0628\u0648\u062f\u0646 \u062a\u0644\u0647\u200c\u0645\u062a\u0631\u06cc \u0648 \u0627\u062c\u0631\u0627\u06cc xeRAbora \u0631\u0648\u06cc \u0631\u0627\u06cc\u0627\u0646\u0647 \u0634\u0645\u0627 \u0641\u0639\u0627\u0644 \u0645\u06cc\u200c\u0634\u0648\u0646\u062f\u061b \u062a\u0627 \u0622\u0646 \u0632\u0645\u0627\u0646 \u0647\u0632\u06cc\u0646\u0647\u200c\u0627\u06cc \u0646\u062f\u0627\u0631\u0646\u062f.\n   CHECK THE DISC: \u0628\u0631\u0631\u0633\u06cc \u062f\u06cc\u0633\u06a9\n   From this game's last CHECK GAME SUPPORT.: \u0627\u0632 \u0622\u062e\u0631\u06cc\u0646 \u0628\u0631\u0631\u0633\u06cc \u067e\u0634\u062a\u06cc\u0628\u0627\u0646\u06cc \u0628\u0627\u0632\u06cc \u0628\u0631\u0627\u06cc \u0627\u06cc\u0646 \u0628\u0627\u0632\u06cc.\n   HOW IT WORKS: \u0686\u06af\u0648\u0646\u0647 \u06a9\u0627\u0631 \u0645\u06cc\u200c\u06a9\u0646\u062f\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: \u0631\u0648\u06cc \u0627\u06cc\u0646 \u06a9\u0646\u0633\u0648\u0644 \u06cc\u06a9 PS2BBL \u0628\u0627 \u0642\u0641\u0644 RIPPS2 \u0647\u0633\u062a.\n   AUDIO folder: \u067e\u0648\u0634\u0647 AUDIO\n   DRIVES: \u062f\u0631\u0627\u06cc\u0648\u0647\u0627\n-  INSTALL RIPPS2'S COPY: \u0646\u0635\u0628 \u0646\u0633\u062e\u0647 RIPPS2\n-  'Installed: ': '\u0646\u0635\u0628\u200c\u0634\u062f\u0647: '\n   MANAGE: \u0645\u062f\u06cc\u0631\u06cc\u062a\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: \u0645\u062f\u06cc\u0631\u06cc\u062a \u0646\u0634\u0627\u0646 \u0645\u06cc\u200c\u062f\u0647\u062f \u0647\u0631 \u06a9\u0627\u0631\u062a \u062d\u0627\u0641\u0638\u0647 \u0686\u0647 \u0686\u06cc\u0632\u06cc \u0631\u0627 \u0627\u062c\u0631\u0627 \u0645\u06cc\u200c\u06a9\u0646\u062f\u060c \u0648 BIBLE \u0631\u0627 \u0646\u0635\u0628 \u06cc\u0627 \u062a\u0639\u0645\u06cc\u0631 \u0645\u06cc\u200c\u06a9\u0646\u062f.\n   MEMORY CARD 0  (about 600 KB): \u06a9\u0627\u0631\u062a \u062d\u0627\u0641\u0638\u0647 0  (\u062d\u062f\u0648\u062f 600 \u06a9\u06cc\u0644\u0648\u0628\u0627\u06cc\u062a)\n   MEMORY CARD 1  (about 600 KB): \u06a9\u0627\u0631\u062a \u062d\u0627\u0641\u0638\u0647 1  (\u062d\u062f\u0648\u062f 600 \u06a9\u06cc\u0644\u0648\u0628\u0627\u06cc\u062a)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO: \u062f\u0631\u0627\u06cc\u0648\u0631 \u0622\u0646 \u0628\u0627\u0631\u06af\u0630\u0627\u0631\u06cc \u0646\u0634\u062f (\u06a9\u062f %d).'\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO: \u062f\u0631\u0627\u06cc\u0648\u0631 \u0628\u0627\u0631\u06af\u0630\u0627\u0631\u06cc \u0634\u062f\u0647\u060c \u0627\u0645\u0627 \u0647\u06cc\u0686 \u06a9\u0627\u0631\u062a SD\u06cc \u067e\u0627\u0633\u062e \u0646\u062f\u0627\u062f (\u0634\u06cc\u0627\u0631 mc1).'\n   NEUTRINO SETTINGS: \u062a\u0646\u0638\u06cc\u0645\u0627\u062a NEUTRINO\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': '\u0647\u0646\u0648\u0632 \u0647\u06cc\u0686: RIPPS2 \u0635\u062f\u0627\u06cc \u062e\u0648\u062f\u0634 \u0631\u0627 \u0646\u062f\u0627\u0631\u062f. \u0635\u062f\u0627\u0647\u0627\u06cc \u0627\u06cc\u0646 \u06a9\u0646\u0633\u0648\u0644 \u0631\u0627 \u0627\u0633\u062a\u062e\u0631\u0627\u062c \u06a9\u0646\u06cc\u062f\u060c \u06cc\u0627 \u0622\u0646\u200c\u0647\u0627 \u0631\u0627 \u062f\u0631 RIPPS2 Audio Studio \u0628\u0633\u0627\u0632\u06cc\u062f.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: \u0646\u0635\u0628 \u0646\u0634\u062f\u0647. \u0628\u0627\u0632\u06cc\u200c\u0627\u06cc \u06a9\u0647 \u0628\u0647 \u0622\u0646 \u0646\u06cc\u0627\u0632 \u062f\u0627\u0631\u062f \u0646\u0633\u062e\u0647 RIPPS2 \u0631\u0627 \u067e\u06cc\u0634\u0646\u0647\u0627\u062f \u0645\u06cc\u200c\u062f\u0647\u062f\u060c \u06cc\u0627 \u0622\u0646 \u0631\u0627 \u0627\u06cc\u0646\u062c\u0627 \u0646\u0635\u0628 \u06a9\u0646\u06cc\u062f.\n   POPSTARTER NETWORK: \u0634\u0628\u06a9\u0647 POPSTARTER\n   POPSTARTER and Ember: POPSTARTER \u0648 Ember\n   PS1 SETTINGS: \u062a\u0646\u0638\u06cc\u0645\u0627\u062a PS1\n   PS2BBL 1.2.0 for RIPPS2: PS2BBL 1.2.0 \u0628\u0631\u0627\u06cc RIPPS2\n   RIP THIS CONSOLE'S SOUNDS: \u0627\u0633\u062a\u062e\u0631\u0627\u062c \u0635\u062f\u0627\u0647\u0627\u06cc \u0627\u06cc\u0646 \u06a9\u0646\u0633\u0648\u0644\n-  RIPPS2's homebrew: \u0628\u0631\u0646\u0627\u0645\u0647\u200c\u0647\u0627\u06cc \u062e\u0627\u0646\u06af\u06cc RIPPS2\n   STATUS: \u0648\u0636\u0639\u06cc\u062a\n-  'STATUS: looking for RIPPS2''s homebrew': '\u0648\u0636\u0639\u06cc\u062a: \u062f\u0631 \u062d\u0627\u0644 \u062c\u0633\u062a\u200c\u0648\u062c\u0648\u06cc \u0628\u0631\u0646\u0627\u0645\u0647\u200c\u0647\u0627\u06cc \u062e\u0627\u0646\u06af\u06cc RIPPS2'\n   a drive of unknown kind: \u062f\u0631\u0627\u06cc\u0648\u06cc \u0627\u0632 \u0646\u0648\u0639 \u0646\u0627\u0634\u0646\u0627\u062e\u062a\u0647\n-  a drive or a memory card: \u06cc\u06a9 \u062f\u0631\u0627\u06cc\u0648 \u06cc\u0627 \u06a9\u0627\u0631\u062a \u062d\u0627\u0641\u0638\u0647\n   boot loader, card by card: \u0628\u0648\u062a\u200c\u0644\u0648\u062f\u0631\u060c \u06a9\u0627\u0631\u062a \u0628\u0647 \u06a9\u0627\u0631\u062a\n   from its BIOS, into AUDIO: \u0627\u0632 BIOS \u0622\u0646\u060c \u0628\u0647 AUDIO\n   internal HDD (exFAT): \u0647\u0627\u0631\u062f \u062f\u0627\u062e\u0644\u06cc (exFAT)\n@@ -975,10 +958,194 @@ strings:\n   Sidebar: \u0646\u0648\u0627\u0631 \u06a9\u0646\u0627\u0631\u06cc\n   your own copy, found on your drives and memory cards: \u0646\u0633\u062e\u0647 \u062e\u0648\u062f\u062a\u0627\u0646\u060c \u067e\u06cc\u062f\u0627\u0634\u062f\u0647 \u0631\u0648\u06cc \u062f\u0631\u0627\u06cc\u0648\u0647\u0627 \u0648 \u06a9\u0627\u0631\u062a\u200c\u0647\u0627\u06cc \u062d\u0627\u0641\u0638\u0647\u200c\u062a\u0627\u0646\n   'your own, found and launched: github.com/Gageformer/Ember/releases': '\u0645\u0627\u0644 \u062e\u0648\u062f\u062a\u0627\u0646\u060c \u067e\u06cc\u062f\u0627 \u0648 \u0627\u062c\u0631\u0627 \u0645\u06cc\u200c\u0634\u0648\u062f: github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': '\u0631\u0648\u06cc mass0: (%s) \u067e\u06cc\u062f\u0627 \u0646\u0634\u062f. ember.elf \u0648 bios.bin \u062e\u0648\u062f\u062a\u0627\u0646 \u0631\u0627 \u062f\u0631 \u067e\u0648\u0634\u0647 EMBER \u0622\u0646 \u0628\u06af\u0630\u0627\u0631\u06cc\u062f.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: \u067e\u06cc\u062f\u0627 \u0646\u0634\u062f. POPSTARTER.ELF \u062e\u0648\u062f\u062a\u0627\u0646 \u0631\u0627 \u062f\u0631 \u067e\u0648\u0634\u0647 POPS \u06cc\u06a9 \u062f\u0631\u0627\u06cc\u0648 \u0628\u06af\u0630\u0627\u0631\u06cc\u062f.\n   Launch Disc: \u0627\u062c\u0631\u0627\u06cc \u062f\u06cc\u0633\u06a9\n   Memory Files: \u0641\u0627\u06cc\u0644\u200c\u0647\u0627\u06cc \u062d\u0627\u0641\u0638\u0647\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: RIPPS2 %d \u0645\u0648\u0631\u062f \u0627\u0632 5 \u0631\u0627 \u067e\u06cc\u062f\u0627 \u06a9\u0631\u062f. \u0647\u0631 \u06a9\u062f\u0627\u0645 \u0628\u0647 \u062e\u0627\u0637\u0631 \u0633\u067e\u0631\u062f\u0647 \u0645\u06cc\u200c\u0634\u0648\u062f \u0648 \u0647\u0631 \u0627\u062c\u0631\u0627 \u0622\u0646 \u0631\u0627 \u0627\u0632 \u0647\u0645\u0627\u0646\u200c\u062c\u0627 \u0645\u06cc\u200c\u062e\u0648\u0627\u0646\u062f.\n+  not found on the started drives: \u062f\u0631 \u062f\u0631\u0627\u06cc\u0648\u0647\u0627\u06cc \u0634\u0631\u0648\u0639\u200c\u0634\u062f\u0647 \u067e\u06cc\u062f\u0627 \u0646\u0634\u062f\n+  not looked for yet: \u0647\u0646\u0648\u0632 \u062c\u0633\u062a\u062c\u0648 \u0646\u0634\u062f\u0647\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s \u067e\u0627\u0633\u062e \u0646\u062f\u0627\u062f\u060c \u067e\u0633 %s \u0646\u0645\u06cc\u200c\u062a\u0648\u0627\u0646\u062f \u0634\u0631\u0648\u0639 \u0634\u0648\u062f. \u0622\u06cc\u0627 \u0648\u0635\u0644 \u0627\u0633\u062a\u061f'\n+  '%s is not on %s any more, so it has left All Games.': '%s \u062f\u06cc\u06af\u0631 \u0631\u0648\u06cc %s \u0646\u06cc\u0633\u062a\u060c \u067e\u0633 \u0627\u0632 \u0647\u0645\u0647 \u0628\u0627\u0632\u06cc\u200c\u0647\u0627 \u0628\u06cc\u0631\u0648\u0646 \u0631\u0641\u062a.'\n+  STARTING %s: \u062f\u0631 \u062d\u0627\u0644 \u0634\u0631\u0648\u0639 %s\n+  the SMB share: \u0627\u0634\u062a\u0631\u0627\u06a9 SMB\n+  the drive: \u062f\u0631\u0627\u06cc\u0648\n+  the internal HDD (APA): \u0647\u0627\u0631\u062f \u062f\u0627\u062e\u0644\u06cc (APA)\n+  the internal HDD (exFAT): \u0647\u0627\u0631\u062f \u062f\u0627\u062e\u0644\u06cc (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: \u0633\u0627\u062e\u062a\u0647\u200c\u0634\u062f\u0647 \u0628\u0631 \u067e\u0627\u06cc\u0647 RIPTOPL\u060c \u0647\u0645\u0631\u0627\u0647 \u0628\u0627 NEUTRINO\n+  'Special thanks to all the YouTube love as well:': '\u062a\u0634\u06a9\u0631 \u0648\u06cc\u0698\u0647 \u0647\u0645 \u0627\u0632 \u0647\u0645\u0647 \u0645\u062d\u0628\u062a\u200c\u0647\u0627 \u062f\u0631 YouTube:'\n+  and its own look, launchers, file browser and save tools.: \u0648 \u0628\u0627 \u0638\u0627\u0647\u0631\u060c \u0627\u062c\u0631\u0627\u06a9\u0646\u0646\u062f\u0647\u200c\u0647\u0627\u060c \u0645\u0631\u0648\u0631\u06af\u0631 \u0641\u0627\u06cc\u0644 \u0648 \u0627\u0628\u0632\u0627\u0631\u0647\u0627\u06cc \u0630\u062e\u06cc\u0631\u0647 \u0645\u062e\u0635\u0648\u0635 \u062e\u0648\u062f\u0634.\n+  glad to help however you reach me.: \u062e\u0648\u0634\u062d\u0627\u0644\u0645 \u06a9\u0647 \u06a9\u0645\u06a9 \u06a9\u0646\u0645\u060c \u0627\u0632 \u0647\u0631 \u0631\u0627\u0647\u06cc \u06a9\u0647 \u0628\u0647 \u0645\u0646 \u0628\u0631\u0633\u06cc\u062f.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): \u0647\u0633\u062a\u0647 \u062f\u0648\u0645 \u0628\u0627\u0632\u06cc\u060c 1.8.0 \u0628\u062f\u0648\u0646 \u062a\u063a\u06cc\u06cc\u0631 \u0647\u0645\u0631\u0627\u0647 \u0627\u0633\u062a (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: \u0628\u0627 Neutrino \u0633\u0627\u062e\u062a\u0647 rickgaiser \u062f\u0631 \u062f\u0631\u0648\u0646\u0634 \u0628\u0647 \u0639\u0646\u0648\u0627\u0646 \u0647\u0633\u062a\u0647 \u062f\u0648\u0645 \u0628\u0627\u0632\u06cc\u060c\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : '\u0627\u06cc\u0646 \u0628\u0627\u0632\u06cc \u0627\u0632 \u0637\u0631\u06cc\u0642 Neutrino \u0634\u0631\u0648\u0639 \u0645\u06cc\u200c\u0634\u0648\u062f \u0648 RIPPS2 \u0647\u06cc\u0686 \u0646\u0633\u062e\u0647\u200c\u0627\u06cc \u067e\u06cc\u062f\u0627 \u0646\u06a9\u0631\u062f: \u0646\u0647 \u062f\u0631 \u0645\u0633\u06cc\u0631 \u062a\u0646\u0638\u06cc\u0645\u0627\u062a\u060c \u0646\u0647 \u0631\u0648\u06cc \u062f\u0631\u0627\u06cc\u0648 \u0628\u0627\u0632\u06cc\u060c \u0646\u0647 \u0631\u0648\u06cc \u06a9\u0627\u0631\u062a\u200c\u0647\u0627\u06cc \u062d\u0627\u0641\u0638\u0647 \u0648 \u0646\u0647 \u0631\u0648\u06cc \u0647\u06cc\u0686 \u062f\u0631\u0627\u06cc\u0648 \u0634\u0631\u0648\u0639\u200c\u0634\u062f\u0647\u200c\u0627\u06cc. RIPPS2 \u0647\u0645\u0631\u0627\u0647 \u062e\u0648\u062f Neutrino 1.8.0 \u0633\u0627\u062e\u062a\u0647 rickgaiser \u0631\u0627 \u062f\u0627\u0631\u062f (AFL-3.0): \u0622\u0646 \u0631\u0627 \u06cc\u06a9 \u0628\u0627\u0631\u060c \u0647\u0631 \u062c\u0627 \u06a9\u0647 \u062e\u0648\u0627\u0633\u062a\u06cc\u062f\u060c \u0646\u0635\u0628 \u06a9\u0646\u06cc\u062f \u0648 \u0647\u0631 \u0628\u0627\u0632\u06cc Neutrino \u0627\u0632 \u0647\u0645\u0627\u0646\u200c\u062c\u0627 \u0627\u0632 \u0622\u0646 \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u0645\u06cc\u200c\u06a9\u0646\u062f.'\n+  STARTING THE DRIVES: \u062f\u0631 \u062d\u0627\u0644 \u0634\u0631\u0648\u0639 \u062f\u0631\u0627\u06cc\u0648\u0647\u0627\n+  '%d games, on the started drives': '%d \u0628\u0627\u0632\u06cc\u060c \u0631\u0648\u06cc \u062f\u0631\u0627\u06cc\u0648\u0647\u0627\u06cc \u0634\u0631\u0648\u0639\u200c\u0634\u062f\u0647'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s: \u0628\u0631\u062e\u06cc \u0641\u0627\u06cc\u0644\u200c\u0647\u0627 \u067e\u0627\u06a9 \u0646\u0634\u062f\u0646\u062f'\n+  '%u partitions, all in order': '%u \u067e\u0627\u0631\u062a\u06cc\u0634\u0646\u060c \u0647\u0645\u0647 \u0645\u0631\u062a\u0628'\n+  ', %d art images': \u060c %d \u062a\u0635\u0648\u06cc\u0631 \u062c\u0644\u062f\n+  ', and ': '\u060c \u0648 '\n+  ', its cheats': \u060c \u062a\u0642\u0644\u0628\u200c\u0647\u0627\u06cc\u0634\n+  ', its settings': \u060c \u062a\u0646\u0638\u06cc\u0645\u0627\u062a\u0634\n+  1 game, on the started drives: 1 \u0628\u0627\u0632\u06cc\u060c \u0631\u0648\u06cc \u062f\u0631\u0627\u06cc\u0648\u0647\u0627\u06cc \u0634\u0631\u0648\u0639\u200c\u0634\u062f\u0647\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: \u0628\u0627\u0632\u06cc PS1 \u0627\u06cc\u0646\u062c\u0627 \u062f\u0631 \u0641\u0627\u06cc\u0644\u200c\u0647\u0627\u06cc \u062d\u0627\u0641\u0638\u0647 \u067e\u0627\u06a9 \u0645\u06cc\u200c\u0634\u0648\u062f\u060c \u062c\u0627\u06cc\u06cc \u06a9\u0647 \u062e\u0648\u062f\u0634 \u06cc\u06a9 \u0641\u0627\u06cc\u0644 \u062c\u062f\u0627\u0633\u062a.\n+  A network catalogue's games are deleted on the PC that serves them.: \u0628\u0627\u0632\u06cc\u200c\u0647\u0627\u06cc \u06cc\u06a9 \u06a9\u0627\u062a\u0627\u0644\u0648\u06af \u0634\u0628\u06a9\u0647 \u0631\u0648\u06cc \u0631\u0627\u06cc\u0627\u0646\u0647\u200c\u0627\u06cc \u067e\u0627\u06a9 \u0645\u06cc\u200c\u0634\u0648\u0646\u062f \u06a9\u0647 \u0622\u0646\u200c\u0647\u0627 \u0631\u0627 \u0627\u0631\u0627\u0626\u0647 \u0645\u06cc\u200c\u06a9\u0646\u062f.\n+  DELETE FOR GOOD: \u062d\u0630\u0641 \u0628\u0631\u0627\u06cc \u0647\u0645\u06cc\u0634\u0647\n+  DELETE IT: \u062d\u0630\u0641\u0634 \u06a9\u0646\n+  'DELETED %s: %s FREED': '%s \u062d\u0630\u0641 \u0634\u062f: %s \u0622\u0632\u0627\u062f \u0634\u062f'\n+  'Delete %s from %s? Goes: %s.': '%s \u0627\u0632 %s \u062d\u0630\u0641 \u0634\u0648\u062f\u061f \u0645\u06cc\u200c\u0631\u0648\u062f: %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': '%s \u0627\u0632 %s \u062d\u0630\u0641 \u0634\u0648\u062f\u061f \u0645\u06cc\u200c\u0631\u0648\u062f: %s. \u0645\u06cc\u200c\u0645\u0627\u0646\u062f: %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: \u0630\u062e\u06cc\u0631\u0647\u200c\u0647\u0627 \u0648 \u0639\u06a9\u0633\u200c\u0647\u0627\u06cc RIPSHOT \u0622\u0646 \u0647\u0645 \u062d\u0630\u0641 \u0634\u0648\u0646\u062f\u061f \u062e\u06cc\u0631 \u0622\u0646\u200c\u0647\u0627 \u0631\u0627 \u0646\u06af\u0647 \u0645\u06cc\u200c\u062f\u0627\u0631\u062f.\n+  HIDDEN GAMES: \u0628\u0627\u0632\u06cc\u200c\u0647\u0627\u06cc \u067e\u0646\u0647\u0627\u0646\n+  'HIDDEN: %s': '\u067e\u0646\u0647\u0627\u0646 \u0634\u062f: %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: '%s \u067e\u0646\u0647\u0627\u0646 \u0634\u0648\u062f\u061f \u0627\u0632 \u0647\u0645\u0647 \u0641\u0647\u0631\u0633\u062a\u200c\u0647\u0627 \u0628\u06cc\u0631\u0648\u0646 \u0645\u06cc\u200c\u0631\u0648\u062f\u060c \u0627\u0632 \u0647\u0645\u0647 \u0628\u0627\u0632\u06cc\u200c\u0647\u0627 \u0648 \u0639\u0644\u0627\u0642\u0647\u200c\u0645\u0646\u062f\u06cc\u200c\u0647\u0627 \u0647\u0645. \u062a\u0646\u0638\u06cc\u0645\u0627\u062a > \u0645\u0646\u0627\u0628\u0639 \u0628\u0627\u0632\u06cc > \u0628\u0627\u0632\u06cc\u200c\u0647\u0627\u06cc \u067e\u0646\u0647\u0627\u0646 \u062f\u0648\u0628\u0627\u0631\u0647 \u0646\u0634\u0627\u0646\u0634 \u0645\u06cc\u200c\u062f\u0647\u062f.'\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: \u0628\u0631\u0631\u0633\u06cc \u0622\u062e\u0631. %s \u0628\u0631\u0627\u06cc \u0647\u0645\u06cc\u0634\u0647 \u062d\u0630\u0641 \u0645\u06cc\u200c\u0634\u0648\u062f \u0648 \u0647\u06cc\u0686 \u0686\u06cc\u0632 \u0646\u0645\u06cc\u200c\u062a\u0648\u0627\u0646\u062f \u0628\u0631\u0634 \u06af\u0631\u062f\u0627\u0646\u062f. \u0628\u0631\u0627\u06cc \u062d\u0630\u0641 START \u0631\u0627 \u0628\u0632\u0646\u06cc\u062f.\n+  None. Hide Game, in a game's options, puts one here.: \u0647\u06cc\u0686. \u067e\u0646\u0647\u0627\u0646 \u06a9\u0631\u062f\u0646 \u0628\u0627\u0632\u06cc\u060c \u062f\u0631 \u06af\u0632\u06cc\u0646\u0647\u200c\u0647\u0627\u06cc \u06cc\u06a9 \u0628\u0627\u0632\u06cc\u060c \u06cc\u06a9\u06cc \u0631\u0627 \u0627\u06cc\u0646\u062c\u0627 \u0645\u06cc\u200c\u06af\u0630\u0627\u0631\u062f.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : '\u0686\u06cc\u0632\u06cc \u062d\u0630\u0641 \u0646\u0634\u062f: \u062c\u062f\u0648\u0644 \u067e\u0627\u0631\u062a\u06cc\u0634\u0646 \u0627\u06cc\u0646 \u062f\u0631\u0627\u06cc\u0648 \u0628\u0627 \u0627\u06cc\u0646 \u062f\u06cc\u0633\u06a9 \u062c\u0648\u0631 \u0646\u06cc\u0633\u062a (%s). \u0634\u0627\u06cc\u062f \u0627\u0632 \u0627\u06cc\u0645\u06cc\u062c \u062f\u06cc\u0633\u06a9\u06cc \u0628\u0627 \u0627\u0646\u062f\u0627\u0632\u0647 \u062f\u06cc\u06af\u0631 \u0646\u0648\u0634\u062a\u0647 \u0634\u062f\u0647 \u0628\u0627\u0634\u062f. \u0627\u0648\u0644 \u0622\u0646 \u0631\u0627 \u0631\u0648\u06cc \u06cc\u06a9 \u0631\u0627\u06cc\u0627\u0646\u0647 \u062f\u0631\u0633\u062a \u06a9\u0646\u06cc\u062f.'\n+  RIPPS2 cannot delete a game from this page.: RIPPS2 \u0646\u0645\u06cc\u200c\u062a\u0648\u0627\u0646\u062f \u0627\u0632 \u0627\u06cc\u0646 \u0635\u0641\u062d\u0647 \u0628\u0627\u0632\u06cc \u0631\u0627 \u062d\u0630\u0641 \u06a9\u0646\u062f.\n+  RIPPS2 could not find this game's files on its drive.: RIPPS2 \u0641\u0627\u06cc\u0644\u200c\u0647\u0627\u06cc \u0627\u06cc\u0646 \u0628\u0627\u0632\u06cc \u0631\u0627 \u0631\u0648\u06cc \u062f\u0631\u0627\u06cc\u0648\u0634 \u067e\u06cc\u062f\u0627 \u0646\u06a9\u0631\u062f.\n+  Reading the hidden lists: \u062f\u0631 \u062d\u0627\u0644 \u062e\u0648\u0627\u0646\u062f\u0646 \u0641\u0647\u0631\u0633\u062a\u200c\u0647\u0627\u06cc \u067e\u0646\u0647\u0627\u0646\n+  SHOW AGAIN OR DELETE: \u0646\u0645\u0627\u06cc\u0634 \u062f\u0648\u0628\u0627\u0631\u0647 \u06cc\u0627 \u062d\u0630\u0641\n+  SHOW IT AGAIN: \u062f\u0648\u0628\u0627\u0631\u0647 \u0646\u0634\u0627\u0646\u0634 \u0628\u062f\u0647\n+  That game is not on its drive any more.: \u0622\u0646 \u0628\u0627\u0632\u06cc \u062f\u06cc\u06af\u0631 \u0631\u0648\u06cc \u062f\u0631\u0627\u06cc\u0648\u0634 \u0646\u06cc\u0633\u062a.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: \u0641\u0647\u0631\u0633\u062a \u067e\u0646\u0647\u0627\u0646 \u0631\u0648\u06cc \u062f\u0631\u0627\u06cc\u0648 \u0628\u0627\u0632\u06cc \u0646\u0648\u0634\u062a\u0647 \u0646\u0634\u062f. \u0622\u06cc\u0627 \u062f\u0631 \u0628\u0631\u0627\u0628\u0631 \u0646\u0648\u0634\u062a\u0646 \u0645\u062d\u0627\u0641\u0638\u062a \u0634\u062f\u0647\u060c \u06cc\u0627 \u0639\u0645\u0644\u06cc\u0627\u062a \u0646\u0648\u0634\u062a\u0646 \u062e\u0627\u0645\u0648\u0634 \u0627\u0633\u062a\u061f\n+  The partition could not be removed (%d). Nothing was deleted.: \u067e\u0627\u0631\u062a\u06cc\u0634\u0646 \u0628\u0631\u062f\u0627\u0634\u062a\u0647 \u0646\u0634\u062f (%d). \u0686\u06cc\u0632\u06cc \u062d\u0630\u0641 \u0646\u0634\u062f.\n+  This drive cannot keep a hidden list (a network catalogue).: \u0627\u06cc\u0646 \u062f\u0631\u0627\u06cc\u0648 \u0646\u0645\u06cc\u200c\u062a\u0648\u0627\u0646\u062f \u0641\u0647\u0631\u0633\u062a \u067e\u0646\u0647\u0627\u0646 \u0646\u06af\u0647 \u062f\u0627\u0631\u062f (\u06cc\u06a9 \u06a9\u0627\u062a\u0627\u0644\u0648\u06af \u0634\u0628\u06a9\u0647).\n+  This game's drive has not answered yet. Wait for it, then try again.: \u062f\u0631\u0627\u06cc\u0648 \u0627\u06cc\u0646 \u0628\u0627\u0632\u06cc \u0647\u0646\u0648\u0632 \u067e\u0627\u0633\u062e \u0646\u062f\u0627\u062f\u0647 \u0627\u0633\u062a. \u0645\u0646\u062a\u0638\u0631\u0634 \u0628\u0645\u0627\u0646\u06cc\u062f\u060c \u0628\u0639\u062f \u062f\u0648\u0628\u0627\u0631\u0647 \u062a\u0644\u0627\u0634 \u06a9\u0646\u06cc\u062f.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: \u067e\u0627\u0631\u062a\u06cc\u0634\u0646 \u0627\u06cc\u0646 \u0628\u0627\u0632\u06cc \u0627\u0632 \u0622\u0646\u200c\u0647\u0627\u06cc\u06cc \u0627\u0633\u062a \u06a9\u0647 RIPPS2 \u0628\u0631\u0646\u0645\u06cc\u200c\u062f\u0627\u0631\u062f (\u06cc\u06a9 \u067e\u0627\u0631\u062a\u06cc\u0634\u0646 \u0633\u06cc\u0633\u062a\u0645\u06cc\u060c \u06cc\u0627 \u0647\u0645\u0627\u0646\u06cc \u06a9\u0647 \u062a\u0646\u0638\u06cc\u0645\u0627\u062a\u0634 \u0631\u0648\u06cc \u0622\u0646 \u0627\u0633\u062a).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': '\u06cc\u06a9 \u067e\u0627\u0631\u062a\u06cc\u0634\u0646 \u0628\u0632\u0631\u06af\u200c\u062a\u0631 \u0627\u0632 \u0686\u06cc\u0632\u06cc \u0627\u0633\u062a \u06a9\u0647 \u0627\u06cc\u0646 \u062f\u06cc\u0633\u06a9 \u0627\u062c\u0627\u0632\u0647 \u0645\u06cc\u200c\u062f\u0647\u062f: \u062c\u062f\u0648\u0644 \u0628\u0631\u0627\u06cc \u062f\u06cc\u0633\u06a9\u06cc \u0628\u0627 \u0627\u0646\u062f\u0627\u0632\u0647 \u062f\u06cc\u06af\u0631 \u0633\u0627\u062e\u062a\u0647 \u0634\u062f\u0647'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': '\u06cc\u06a9 \u067e\u0627\u0631\u062a\u06cc\u0634\u0646 \u0627\u0632 \u0627\u0646\u062a\u0647\u0627\u06cc \u0627\u06cc\u0646 \u062f\u06cc\u0633\u06a9 \u0645\u06cc\u200c\u06af\u0630\u0631\u062f (\u0633\u06a9\u062a\u0648\u0631 %u \u0627\u0632 %u): \u062c\u062f\u0648\u0644 \u0628\u0631\u0627\u06cc \u062f\u06cc\u0633\u06a9\u06cc \u0628\u0632\u0631\u06af\u200c\u062a\u0631 \u0633\u0627\u062e\u062a\u0647 \u0634\u062f\u0647'\n+  its RIPSHOT pictures: \u0639\u06a9\u0633\u200c\u0647\u0627\u06cc RIPSHOT \u0622\u0646\n+  its drive: \u062f\u0631\u0627\u06cc\u0648\u0634\n+  its saves: \u0630\u062e\u06cc\u0631\u0647\u200c\u0647\u0627\u06cc\u0634\n+  sector %u holds no partition header: \u0633\u06a9\u062a\u0648\u0631 %u \u0633\u0631\u0628\u0631\u06af \u067e\u0627\u0631\u062a\u06cc\u0634\u0646 \u0646\u062f\u0627\u0631\u062f\n+  the chain does not close at __mbr: \u0632\u0646\u062c\u06cc\u0631\u0647 \u062f\u0631 __mbr \u0628\u0633\u062a\u0647 \u0646\u0645\u06cc\u200c\u0634\u0648\u062f\n+  the chain skips from sector %u: \u0632\u0646\u062c\u06cc\u0631\u0647 \u0627\u0632 \u0633\u06a9\u062a\u0648\u0631 %u \u0645\u06cc\u200c\u067e\u0631\u062f\n+  the chain's links disagree at sector %u: \u062d\u0644\u0642\u0647\u200c\u0647\u0627\u06cc \u0632\u0646\u062c\u06cc\u0631\u0647 \u062f\u0631 \u0633\u06a9\u062a\u0648\u0631 %u \u0628\u0627 \u0647\u0645 \u0646\u0645\u06cc\u200c\u062e\u0648\u0627\u0646\u0646\u062f\n+  the drive's size could not be read: \u0627\u0646\u062f\u0627\u0632\u0647 \u062f\u0631\u0627\u06cc\u0648 \u062e\u0648\u0627\u0646\u062f\u0647 \u0646\u0634\u062f\n+  the game (%s): \u0628\u0627\u0632\u06cc (%s)\n+  the game's folder and everything in it (%s): \u067e\u0648\u0634\u0647 \u0628\u0627\u0632\u06cc \u0648 \u0647\u0631 \u0686\u0647 \u062f\u0631 \u0622\u0646 \u0627\u0633\u062a (%s)\n+  the header at sector %u could not be read: \u0633\u0631\u0628\u0631\u06af \u062f\u0631 \u0633\u06a9\u062a\u0648\u0631 %u \u062e\u0648\u0627\u0646\u062f\u0647 \u0646\u0634\u062f\n+  the header at sector %u fails its checksum: \u0633\u0631\u0628\u0631\u06af \u062f\u0631 \u0633\u06a9\u062a\u0648\u0631 %u \u0627\u0632 \u062c\u0645\u0639 \u06a9\u0646\u062a\u0631\u0644\u06cc \u0631\u062f \u0645\u06cc\u200c\u0634\u0648\u062f\n+  the partition at sector %u is not where or as large as APA puts one: \u067e\u0627\u0631\u062a\u06cc\u0634\u0646 \u062f\u0631 \u0633\u06a9\u062a\u0648\u0631 %u \u062c\u0627\u06cc\u06cc \u06cc\u0627 \u0628\u0647 \u0627\u0646\u062f\u0627\u0632\u0647\u200c\u0627\u06cc \u06a9\u0647 APA \u0645\u06cc\u200c\u06af\u0630\u0627\u0631\u062f \u0646\u06cc\u0633\u062a\n+  the settings and art another copy of it uses: \u062a\u0646\u0638\u06cc\u0645\u0627\u062a \u0648 \u062a\u0635\u0627\u0648\u06cc\u0631\u06cc \u06a9\u0647 \u0646\u0633\u062e\u0647 \u062f\u06cc\u06af\u0631\u06cc \u0627\u0632 \u0622\u0646 \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u0645\u06cc\u200c\u06a9\u0646\u062f\n+  '%d FOLDERS': '%d \u067e\u0648\u0634\u0647'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': '%s \u0628\u0647 %s \u06a9\u067e\u06cc \u0646\u0634\u062f. \u0622\u06cc\u0627 \u062f\u0631\u0627\u06cc\u0648 \u067e\u0631 \u0627\u0633\u062a \u06cc\u0627 \u062f\u0631 \u0628\u0631\u0627\u0628\u0631 \u0646\u0648\u0634\u062a\u0646 \u0645\u062d\u0627\u0641\u0638\u062a \u0634\u062f\u0647\u061f'\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s \u062c\u0627\u06cc\u06cc \u06a9\u0647 RIPPS2 \u0645\u06cc\u200c\u06af\u0631\u062f\u062f \u0646\u06cc\u0633\u062a. \u062f\u0631 \u062f\u0631\u0627\u06cc\u0648\u0647\u0627\u06cc\u06cc \u06a9\u0647 RIPPS2 \u0634\u0631\u0648\u0639 \u06a9\u0631\u062f\u0647 \u062f\u0646\u0628\u0627\u0644\u0634 \u0628\u06af\u0631\u062f\u062f\u061f \u062c\u0627\u06cc\u06cc \u06a9\u0647 \u067e\u06cc\u062f\u0627 \u0634\u0648\u062f \u0628\u0631\u0627\u06cc \u0647\u0631 \u0627\u062c\u0631\u0627\u06cc \u0628\u0639\u062f\u06cc \u0628\u0647 \u062e\u0627\u0637\u0631 \u0633\u067e\u0631\u062f\u0647 \u0645\u06cc\u200c\u0634\u0648\u062f.'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Ember \u062f\u0631 %s\u060c \u06a9\u0646\u0627\u0631 \u0628\u0627\u0632\u06cc\u200c\u0647\u0627\u060c \u0646\u06cc\u0633\u062a \u0648 \u0628\u0627\u06cc\u062f \u0622\u0646\u062c\u0627 \u0628\u0627\u0634\u062f. RIPPS2 \u0622\u0646 \u0631\u0627 \u062f\u0631 %s \u067e\u06cc\u062f\u0627 \u06a9\u0631\u062f: START \u0622\u0646 \u0631\u0627 \u0628\u0647 \u0622\u0646\u062c\u0627 \u06a9\u067e\u06cc \u0645\u06cc\u200c\u06a9\u0646\u062f (%d \u06a9\u06cc\u0644\u0648\u0628\u0627\u06cc\u062a) \u0648 \u0628\u0627\u0632\u06cc \u0631\u0627 \u0634\u0631\u0648\u0639 \u0645\u06cc\u200c\u06a9\u0646\u062f.'\n+  Ember's BIOS: BIOS \u0645\u062e\u0635\u0648\u0635 Ember\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : 'BIOS \u0645\u062e\u0635\u0648\u0635 Ember (bios.bin) \u062f\u0631 %s\u060c \u06a9\u0646\u0627\u0631 Ember\u060c \u0646\u06cc\u0633\u062a. RIPPS2 \u06cc\u06a9\u06cc \u062f\u0631 %s \u067e\u06cc\u062f\u0627 \u06a9\u0631\u062f: START \u0622\u0646 \u0631\u0627 \u0628\u0647 \u0622\u0646\u062c\u0627 \u06a9\u067e\u06cc \u0645\u06cc\u200c\u06a9\u0646\u062f (%d \u06a9\u06cc\u0644\u0648\u0628\u0627\u06cc\u062a) \u0648 \u0628\u0627\u0632\u06cc \u0631\u0627 \u0634\u0631\u0648\u0639 \u0645\u06cc\u200c\u06a9\u0646\u062f.'\n+  LOOKING FOR YOUR LAUNCHERS: \u062f\u0631 \u062d\u0627\u0644 \u062c\u0633\u062a\u062c\u0648\u06cc \u0627\u062c\u0631\u0627\u06a9\u0646\u0646\u062f\u0647\u200c\u0647\u0627\u06cc \u0634\u0645\u0627\n+  The POPS files: \u0641\u0627\u06cc\u0644\u200c\u0647\u0627\u06cc POPS\n+  '%s  MMCE card': '%s  \u06a9\u0627\u0631\u062a MMCE'\n+  INSTALL RIPPS2'S NEUTRINO: \u0646\u0635\u0628 NEUTRINO \u0647\u0645\u0631\u0627\u0647 RIPPS2\n+  'Internal HDD (APA): %s free of %s': '\u0647\u0627\u0631\u062f \u062f\u0627\u062e\u0644\u06cc (APA): %s \u0622\u0632\u0627\u062f \u0627\u0632 %s'\n+  LAUNCHERS: \u0627\u062c\u0631\u0627\u06a9\u0646\u0646\u062f\u0647\u200c\u0647\u0627\n+  LOOK FOR THEM: \u067e\u06cc\u062f\u0627\u06cc\u0634\u0627\u0646 \u06a9\u0646\n+  'Memory card %d: %d KB free': '\u06a9\u0627\u0631\u062a \u062d\u0627\u0641\u0638\u0647 %d: %d \u06a9\u06cc\u0644\u0648\u0628\u0627\u06cc\u062a \u0622\u0632\u0627\u062f'\n+  POPS files: \u0641\u0627\u06cc\u0644\u200c\u0647\u0627\u06cc POPS\n+  START EVERY DRIVE: \u0634\u0631\u0648\u0639 \u0647\u0645\u0647 \u062f\u0631\u0627\u06cc\u0648\u0647\u0627\n+  'STATUS: looking at the drives and the homebrew': '\u0648\u0636\u0639\u06cc\u062a: \u0628\u0631\u0631\u0633\u06cc \u062f\u0631\u0627\u06cc\u0648\u0647\u0627 \u0648 \u0647\u0648\u0645\u200c\u0628\u0631\u0648'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): \u0627\u0648\u0644 \u0647\u0645\u0647 \u062f\u0631\u0627\u06cc\u0648\u0647\u0627 \u0634\u0631\u0648\u0639 \u0634\u0648\u0646\u062f \u062a\u0627 \u062c\u0633\u062a\u062c\u0648 \u0628\u0647 \u0647\u0645\u0647 \u0628\u0631\u0633\u062f\u061f (\u062e\u06cc\u0631 \u062f\u0631\u0627\u06cc\u0648\u0647\u0627\u06cc \u0634\u0631\u0648\u0639\u200c\u0634\u062f\u0647 \u0631\u0627 \u062c\u0633\u062a\u062c\u0648 \u0645\u06cc\u200c\u06a9\u0646\u062f.)\n+  USB, HDD, MMCE and the rest: USB\u060c HDD\u060c MMCE \u0648 \u0628\u0642\u06cc\u0647\n+  every started drive: \u0647\u0631 \u062f\u0631\u0627\u06cc\u0648 \u0634\u0631\u0648\u0639\u200c\u0634\u062f\u0647\n+  found where you keep them: \u0647\u0645\u0627\u0646\u200c\u062c\u0627 \u06a9\u0647 \u0646\u06af\u0647\u0634\u0627\u0646 \u0645\u06cc\u200c\u062f\u0627\u0631\u06cc\u062f \u067e\u06cc\u062f\u0627 \u0634\u062f\u0646\u062f\n+  not found yet: \u0647\u0646\u0648\u0632 \u067e\u06cc\u062f\u0627 \u0646\u0634\u062f\u0647\n+  not on a started drive: \u0631\u0648\u06cc \u062f\u0631\u0627\u06cc\u0648 \u0634\u0631\u0648\u0639\u200c\u0634\u062f\u0647 \u0646\u06cc\u0633\u062a\n+  only if you have none: \u0641\u0642\u0637 \u0627\u06af\u0631 \u0647\u06cc\u0686\u200c\u06a9\u062f\u0627\u0645 \u0631\u0627 \u0646\u062f\u0627\u0631\u06cc\u062f\n+  starts every drive first: \u0627\u0648\u0644 \u0647\u0645\u0647 \u062f\u0631\u0627\u06cc\u0648\u0647\u0627 \u0631\u0627 \u0634\u0631\u0648\u0639 \u0645\u06cc\u200c\u06a9\u0646\u062f\n+  Drives, launchers and homebrew: \u062f\u0631\u0627\u06cc\u0648\u0647\u0627\u060c \u0627\u062c\u0631\u0627\u06a9\u0646\u0646\u062f\u0647\u200c\u0647\u0627 \u0648 \u0628\u0631\u0646\u0627\u0645\u0647\u200c\u0647\u0627\u06cc \u062e\u0627\u0646\u06af\u06cc\n+  BDM Devices: \u062f\u0633\u062a\u06af\u0627\u0647\u200c\u0647\u0627\u06cc BDM\n+  Color Sweep: \u06af\u0630\u0631 \u0631\u0646\u06af\n+  Colors and Text: \u0631\u0646\u06af \u0648 \u0645\u062a\u0646\n+  HDD POPS partitions: \u067e\u0627\u0631\u062a\u06cc\u0634\u0646\u200c\u0647\u0627\u06cc POPS \u0647\u0627\u0631\u062f\n+  Layout: \u0686\u06cc\u062f\u0645\u0627\u0646\n+  Missing Covers: \u062c\u0644\u062f\u0647\u0627\u06cc \u062c\u0627\u0627\u0641\u062a\u0627\u062f\u0647\n+  Name: \u0646\u0627\u0645\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: \u0647\u0646\u0648\u0632 \u062a\u0646\u0638\u06cc\u0645 SMB \u0628\u0631\u0627\u06cc POPSTARTER \u067e\u06cc\u062f\u0627 \u0646\u0634\u062f\u0647. \u0631\u062f\u06cc\u0641\u200c\u0647\u0627\u06cc \u0632\u06cc\u0631 \u0631\u0627 \u067e\u0631 \u06a9\u0646\u06cc\u062f.\n+  POPSTARTER Network: \u0634\u0628\u06a9\u0647\u0654 POPSTARTER\n+  POPSTARTER SMB Network: \u0634\u0628\u06a9\u0647\u0654 SMB \u0628\u0631\u0627\u06cc POPSTARTER\n+  POPSTARTER UDPFS Network: \u0634\u0628\u06a9\u0647\u0654 UDPFS \u0628\u0631\u0627\u06cc POPSTARTER\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: RIPPS2 \u062e\u0648\u062f\u0634 \u062f\u0631\u0627\u06cc\u0648\u060c ISO\u060c VMC\u0647\u0627 \u0648 \u062d\u0627\u0644\u062a \u0648\u06cc\u062f\u06cc\u0648 \u0631\u0627 \u0627\u0636\u0627\u0641\u0647 \u0645\u06cc\u200c\u06a9\u0646\u062f.\n+  Screen: \u0635\u0641\u062d\u0647\n+  Speed: \u0633\u0631\u0639\u062a\n+  Sweep Colors: \u0631\u0646\u06af\u200c\u0647\u0627\u06cc \u06af\u0630\u0631\n+  Text Weight: \u0636\u062e\u0627\u0645\u062a \u0645\u062a\u0646\n+  Theme Color: \u0631\u0646\u06af \u0627\u0635\u0644\u06cc\n+  Audio: \u0635\u062f\u0627\n+  Bold: \u067e\u0631\u0631\u0646\u06af\n+  Controllers: \u062f\u0633\u062a\u0647\u200c\u0647\u0627\n+  Lively: \u0633\u0631\u0632\u0646\u062f\u0647\n+  Medium: \u0645\u062a\u0648\u0633\u0637\n+  PS Emulation: \u0634\u0628\u06cc\u0647\u200c\u0633\u0627\u0632\u06cc PS\n+  Regular: \u0645\u0639\u0645\u0648\u0644\u06cc\n+  Slow: \u0622\u0631\u0627\u0645\n+  A MOMENT: \u06cc\u06a9 \u0644\u062d\u0638\u0647\n+  A TASK IN THE BACKGROUND: \u06a9\u0627\u0631\u06cc \u062f\u0631 \u067e\u0633\u200c\u0632\u0645\u06cc\u0646\u0647\n+  ABOUT %d S: \u062d\u062f\u0648\u062f %d \u062b\n+  ANY MOMENT: \u0647\u0631 \u0644\u062d\u0638\u0647\n+  READING THE GAME LIST OF %s: \u062e\u0648\u0627\u0646\u062f\u0646 \u0641\u0647\u0631\u0633\u062a \u0628\u0627\u0632\u06cc\u200c\u0647\u0627\u06cc %s\n+  SAVING A LIST: \u0630\u062e\u06cc\u0631\u0647\u0654 \u06cc\u06a9 \u0641\u0647\u0631\u0633\u062a\n+  START NOW: \u0647\u0645\u06cc\u0646 \u062d\u0627\u0644\u0627 \u0634\u0631\u0648\u0639 \u06a9\u0646\n+  STARTING: \u062f\u0631 \u062d\u0627\u0644 \u0634\u0631\u0648\u0639\n+  STARTS NEXT: \u0628\u0639\u062f\u06cc \u0634\u0631\u0648\u0639 \u0645\u06cc\u200c\u0634\u0648\u062f\n+  STOPPING IT SAFELY: \u062a\u0648\u0642\u0641 \u0627\u06cc\u0645\u0646\n+  THE LAST OF THE QUEUE: \u0627\u0646\u062a\u0647\u0627\u06cc \u0635\u0641\n+  WHEN IT FINISHES: \u0648\u0642\u062a\u06cc \u062a\u0645\u0627\u0645 \u0634\u062f\n+  What each card starts; install or repair BIBLE.: \u0647\u0631 \u06a9\u0627\u0631\u062a \u0686\u0647 \u0686\u06cc\u0632\u06cc \u0631\u0627 \u0627\u062c\u0631\u0627 \u0645\u06cc\u200c\u06a9\u0646\u062f\u061b BIBLE \u0631\u0627 \u0646\u0635\u0628 \u06cc\u0627 \u062a\u0639\u0645\u06cc\u0631 \u06a9\u0646\u06cc\u062f.\n+  Rainbow: \u0631\u0646\u06af\u06cc\u0646\u200c\u06a9\u0645\u0627\u0646\n+  Custom: \u0633\u0641\u0627\u0631\u0634\u06cc\n+  DESR DVR ISOs (experimental): \u0641\u0627\u06cc\u0644\u200c\u0647\u0627\u06cc DESR DVR ISO (\u0622\u0632\u0645\u0627\u06cc\u0634\u06cc)\n+  PC CARD CF (experimental): PC CARD CF (\u0622\u0632\u0645\u0627\u06cc\u0634\u06cc)\n+  HDD Start Mode: \u062d\u0627\u0644\u062a \u0634\u0631\u0648\u0639 \u0647\u0627\u0631\u062f\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): Roboto Condensed \u0627\u0632 The Roboto Project Authors (SIL OFL 1.1)\n+  '%s on %s': '%s \u0631\u0648\u06cc %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: %s \u0634\u062f %s (%d%%)\u060c \u0647\u0631 \u062a\u06a9\u0647 \u0628\u0631\u0631\u0633\u06cc \u0634\u062f. ISO \u067e\u0627\u06a9 \u0634\u0648\u062f \u0648 ZSO \u0628\u0645\u0627\u0646\u062f\u061f'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: %s \u062f\u0648\u0628\u0627\u0631\u0647 \u0634\u062f %s\u060c \u0647\u0631 \u062a\u06a9\u0647 \u0628\u0631\u0631\u0633\u06cc \u0634\u062f. ZSO \u067e\u0627\u06a9 \u0634\u0648\u062f \u0648 ISO \u0628\u0645\u0627\u0646\u062f\u061f'\n+  A ZSO of this game is already beside it. Remove one of the two first.: \u06cc\u06a9 ZSO \u0627\u0632 \u0627\u06cc\u0646 \u0628\u0627\u0632\u06cc \u06a9\u0646\u0627\u0631\u0634 \u0647\u0633\u062a. \u0627\u0648\u0644 \u06cc\u06a9\u06cc \u0627\u0632 \u0627\u06cc\u0646 \u062f\u0648 \u0631\u0627 \u067e\u0627\u06a9 \u06a9\u0646\u06cc\u062f.\n+  About %d min, then checked: \u062d\u062f\u0648\u062f %d \u062f\u0642\u06cc\u0642\u0647\u060c \u0628\u0639\u062f \u0628\u0631\u0631\u0633\u06cc\n+  An ISO of this game is already beside it. Remove one of the two first.: \u06cc\u06a9 ISO \u0627\u0632 \u0627\u06cc\u0646 \u0628\u0627\u0632\u06cc \u06a9\u0646\u0627\u0631\u0634 \u0647\u0633\u062a. \u0627\u0648\u0644 \u06cc\u06a9\u06cc \u0627\u0632 \u0627\u06cc\u0646 \u062f\u0648 \u0631\u0627 \u067e\u0627\u06a9 \u06a9\u0646\u06cc\u062f.\n+  CHECKING EVERY PIECE: \u0628\u0631\u0631\u0633\u06cc \u0647\u0631 \u062a\u06a9\u0647\n+  COMPRESS TO ZSO: \u0641\u0634\u0631\u062f\u0647\u200c\u0633\u0627\u0632\u06cc \u0628\u0647 ZSO\n+  'COMPRESSED %s: %s FREED': '%s \u0641\u0634\u0631\u062f\u0647 \u0634\u062f: %s \u0622\u0632\u0627\u062f \u0634\u062f'\n+  COMPRESSING 1 OF 2: \u0641\u0634\u0631\u062f\u0647\u200c\u0633\u0627\u0632\u06cc 1 \u0627\u0632 2\n+  COMPRESSING 2 OF 2: \u0641\u0634\u0631\u062f\u0647\u200c\u0633\u0627\u0632\u06cc 2 \u0627\u0632 2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : '%s \u0641\u0634\u0631\u062f\u0647 \u0634\u0648\u062f\u061f ISO \u0645\u06cc\u200c\u0645\u0627\u0646\u062f \u062a\u0627 ZSO \u062f\u0648\u0628\u0627\u0631\u0647 \u062e\u0648\u0627\u0646\u062f\u0647 \u0648 \u0628\u0631\u0631\u0633\u06cc \u0634\u0648\u062f\u060c \u0633\u067e\u0633 RIPPS2 \u067e\u06cc\u0634 \u0627\u0632 \u067e\u0627\u06a9 \u06a9\u0631\u062f\u0646 ISO \u0645\u06cc\u200c\u067e\u0631\u0633\u062f. \u062f\u0631\u0627\u06cc\u0648 \u0647\u0646\u06af\u0627\u0645 \u06a9\u0627\u0631 \u0628\u0631\u0627\u06cc \u0647\u0631 \u062f\u0648 \u062c\u0627 \u0644\u0627\u0632\u0645 \u062f\u0627\u0631\u062f\u061b \u06a9\u0646\u0633\u0648\u0644 \u0631\u0627 \u062a\u0627 \u067e\u0627\u06cc\u0627\u0646 \u06a9\u0627\u0631 \u0631\u0648\u0634\u0646 \u0628\u06af\u0630\u0627\u0631\u06cc\u062f. ZSO \u0641\u0642\u0637 \u0628\u0627 \u0647\u0633\u062a\u0647 OPL \u0627\u062c\u0631\u0627 \u0645\u06cc\u200c\u0634\u0648\u062f (\u0646\u0647 Neutrino)\u061b \u0630\u062e\u06cc\u0631\u0647\u200c\u0647\u0627\u060c \u062a\u0646\u0638\u06cc\u0645\u0627\u062a\u060c \u062a\u0635\u0627\u0648\u06cc\u0631 \u0648 \u062a\u0642\u0644\u0628\u200c\u0647\u0627\u06cc \u0622\u0646 \u0628\u0627 \u0622\u0646 \u0645\u06cc\u200c\u0645\u0627\u0646\u0646\u062f \u0648 \u0647\u0631 \u0648\u0642\u062a \u0628\u062e\u0648\u0627\u0647\u06cc\u062f \u0645\u06cc\u200c\u062a\u0648\u0627\u0646\u062f \u062f\u0648\u0628\u0627\u0631\u0647 ISO \u0634\u0648\u062f.'\n+  Compress to ZSO: \u0641\u0634\u0631\u062f\u0647\u200c\u0633\u0627\u0632\u06cc \u0628\u0647 ZSO\n+  Cycle: \u0686\u0631\u062e\u0634\u06cc\n+  'Experimental: OPL core only (not Neutrino)': '\u0622\u0632\u0645\u0627\u06cc\u0634\u06cc: \u0641\u0642\u0637 \u0647\u0633\u062a\u0647 OPL (\u0646\u0647 Neutrino)'\n+  Hold SELECT: \u0646\u06af\u0647 \u062f\u0627\u0634\u062a\u0646 SELECT\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: \u062a\u0645\u0627\u0645 \u0646\u0634\u062f (%s). \u0622\u0646\u0686\u0647 \u0646\u0648\u0634\u062a\u0647 \u0628\u0648\u062f \u067e\u0627\u06a9 \u0634\u062f\u061b \u0628\u0627\u0632\u06cc \u0645\u062b\u0644 \u0642\u0628\u0644 \u0627\u0633\u062a.\n+  List: \u0641\u0647\u0631\u0633\u062a\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : '%s \u062f\u0648\u0628\u0627\u0631\u0647 ISO \u0634\u0648\u062f\u061f ZSO \u0645\u06cc\u200c\u0645\u0627\u0646\u062f \u062a\u0627 ISO \u062f\u0648\u0628\u0627\u0631\u0647 \u062e\u0648\u0627\u0646\u062f\u0647 \u0648 \u0628\u0631\u0631\u0633\u06cc \u0634\u0648\u062f\u060c \u0633\u067e\u0633 RIPPS2 \u067e\u06cc\u0634 \u0627\u0632 \u067e\u0627\u06a9 \u06a9\u0631\u062f\u0646 ZSO \u0645\u06cc\u200c\u067e\u0631\u0633\u062f. \u062f\u0631\u0627\u06cc\u0648 \u0647\u0646\u06af\u0627\u0645 \u06a9\u0627\u0631 \u0628\u0631\u0627\u06cc \u0647\u0631 \u062f\u0648 \u062c\u0627 \u0644\u0627\u0632\u0645 \u062f\u0627\u0631\u062f\u061b \u06a9\u0646\u0633\u0648\u0644 \u0631\u0627 \u062a\u0627 \u067e\u0627\u06cc\u0627\u0646 \u06a9\u0627\u0631 \u0631\u0648\u0634\u0646 \u0628\u06af\u0630\u0627\u0631\u06cc\u062f. \u0630\u062e\u06cc\u0631\u0647\u200c\u0647\u0627\u060c \u062a\u0646\u0638\u06cc\u0645\u0627\u062a\u060c \u062a\u0635\u0627\u0648\u06cc\u0631 \u0648 \u062a\u0642\u0644\u0628\u200c\u0647\u0627\u06cc \u0622\u0646 \u0628\u0627 \u0622\u0646 \u0645\u06cc\u200c\u0645\u0627\u0646\u0646\u062f.'\n+  Stop? What it has written so far is removed; the game stays as it was.: \u062a\u0648\u0642\u0641\u061f \u0622\u0646\u0686\u0647 \u062a\u0627 \u062d\u0627\u0644\u0627 \u0646\u0648\u0634\u062a\u0647 \u0634\u062f\u0647 \u067e\u0627\u06a9 \u0645\u06cc\u200c\u0634\u0648\u062f\u061b \u0628\u0627\u0632\u06cc \u0645\u062b\u0644 \u0642\u0628\u0644 \u0645\u06cc\u200c\u0645\u0627\u0646\u062f.\n+  THEMES: \u067e\u0648\u0633\u062a\u0647\u200c\u0647\u0627\n+  There is not enough memory for this just now.: \u0627\u0644\u0627\u0646 \u062d\u0627\u0641\u0638\u0647 \u06a9\u0627\u0641\u06cc \u0628\u0631\u0627\u06cc \u0627\u06cc\u0646 \u06a9\u0627\u0631 \u0646\u06cc\u0633\u062a.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: RIPPS2 \u0646\u0645\u06cc\u200c\u062a\u0648\u0627\u0646\u062f \u0627\u06cc\u0646 \u0627\u06cc\u0645\u06cc\u062c \u0631\u0627 \u0641\u0634\u0631\u062f\u0647 \u06cc\u0627 \u0628\u0627\u0632 \u06a9\u0646\u062f (\u0627\u0646\u062f\u0627\u0632\u0647\u200c\u0627\u0634 \u0628\u0644\u0648\u06a9\u200c\u0647\u0627\u06cc \u06a9\u0627\u0645\u0644 2 KB \u0646\u06cc\u0633\u062a).\n+  UNCOMPRESS TO ISO: \u0628\u0627\u0632 \u06a9\u0631\u062f\u0646 \u0628\u0647 ISO\n+  UNCOMPRESSED %s: '%s \u0628\u0627\u0632 \u0634\u062f'\n+  UNCOMPRESSING: \u062f\u0631 \u062d\u0627\u0644 \u0628\u0627\u0632 \u06a9\u0631\u062f\u0646\n+  Uncompress to ISO: \u0628\u0627\u0632 \u06a9\u0631\u062f\u0646 \u0628\u0647 ISO\n+  Under a minute, then checked: \u06a9\u0645\u062a\u0631 \u0627\u0632 \u06cc\u06a9 \u062f\u0642\u06cc\u0642\u0647\u060c \u0628\u0639\u062f \u0628\u0631\u0631\u0633\u06cc\n+  COPYING: \u062f\u0631 \u062d\u0627\u0644 \u06a9\u067e\u06cc\n+  SHOW AGAIN, DELETE OR COMPRESS: \u0646\u0645\u0627\u06cc\u0634 \u062f\u0648\u0628\u0627\u0631\u0647\u060c \u062d\u0630\u0641 \u06cc\u0627 \u0641\u0634\u0631\u062f\u0647\u200c\u0633\u0627\u0632\u06cc\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: \u0646\u0645\u0627\u06cc\u0634 \u062f\u0648\u0628\u0627\u0631\u0647\u060c \u062d\u0630\u0641 \u06cc\u0627 \u0628\u0627\u0632 \u06a9\u0631\u062f\u0646\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': '%s \u0647\u0646\u06af\u0627\u0645 \u06a9\u0627\u0631 \u062d\u062f\u0648\u062f %s \u0641\u0636\u0627\u06cc \u062e\u0627\u0644\u06cc \u0631\u0648\u06cc \u062f\u0631\u0627\u06cc\u0648 \u062e\u0648\u062f \u0644\u0627\u0632\u0645 \u062f\u0627\u0631\u062f (\u0627\u0635\u0644 \u0622\u0646 \u062a\u0627 \u067e\u0627\u06cc\u0627\u0646 \u0645\u06cc\u200c\u0645\u0627\u0646\u062f)\u060c \u0648 \u062f\u0631\u0627\u06cc\u0648 %s \u0641\u0636\u0627\u06cc \u062e\u0627\u0644\u06cc \u062f\u0627\u0631\u062f.'\n+  Hide and Compress: \u067e\u0646\u0647\u0627\u0646 \u06a9\u0631\u062f\u0646 \u0648 \u0641\u0634\u0631\u062f\u0647\u200c\u0633\u0627\u0632\u06cc\n+  'COMPRESSED AND HIDDEN: %s': '\u0641\u0634\u0631\u062f\u0647 \u0648 \u067e\u0646\u0647\u0627\u0646 \u0634\u062f: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': '\u0628\u0631\u0627\u06cc \u0627\u06cc\u0646 \u06a9\u067e\u06cc \u062c\u0627 \u0646\u06cc\u0633\u062a: \u062d\u062f\u0648\u062f %s \u0644\u0627\u0632\u0645 \u062f\u0627\u0631\u062f \u0648 \u062f\u0631\u0627\u06cc\u0648 %s \u0641\u0636\u0627\u06cc \u062e\u0627\u0644\u06cc \u062f\u0627\u0631\u062f.'\n+  OVER 4 GB FOR FAT32: \u0628\u06cc\u0634 \u0627\u0632 4 GB \u0628\u0631\u0627\u06cc FAT32\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: \u0627\u06cc\u0646 \u062f\u0631\u0627\u06cc\u0648 %s \u0627\u0633\u062a \u06a9\u0647 \u0641\u0627\u06cc\u0644 4 GB \u06cc\u0627 \u0628\u06cc\u0634\u062a\u0631 \u0646\u06af\u0647 \u0646\u0645\u06cc\u200c\u062f\u0627\u0631\u062f\u060c \u0648 \u0627\u06cc\u0645\u06cc\u062c \u062c\u062f\u06cc\u062f %s \u0645\u06cc\u200c\u0634\u062f.\n+  '%s, %s clusters, %s free of %s': '%s\u060c \u062e\u0648\u0634\u0647\u200c\u0647\u0627\u06cc %s\u060c %s \u062e\u0627\u0644\u06cc \u0627\u0632 %s'\n+  Delete This Sweep: \u062d\u0630\u0641 \u0627\u06cc\u0646 \u06af\u0630\u0631\n+  Delete %s? RIPPS2's own sweeps stay as they are.: '%s \u062d\u0630\u0641 \u0634\u0648\u062f\u061f \u06af\u0630\u0631\u0647\u0627\u06cc \u062e\u0648\u062f RIPPS2 \u0647\u0645\u0627\u0646\u200c\u0637\u0648\u0631 \u0645\u06cc\u200c\u0645\u0627\u0646\u0646\u062f.'\n+  Sweep: \u06af\u0630\u0631\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: \u0646\u0634\u0627\u0646\u200c\u0647\u0627 \u0628\u0627\u0632\u06cc\u200c\u0647\u0627\u06cc \u062f\u0627\u0631\u0627\u06cc \u062f\u0633\u062a\u0627\u0648\u0631\u062f \u0631\u0627 \u062f\u0631 \u0641\u0647\u0631\u0633\u062a\u200c\u0647\u0627\u06cc \u0634\u0645\u0627 \u0628\u0627 \u062a\u0639\u062f\u0627\u062f \u0628\u0627\u0632\u0634\u062f\u0647\u200c\u0647\u0627 \u0646\u0634\u0627\u0646 \u0645\u06cc\u200c\u062f\u0647\u0646\u062f. \u062a\u0644\u0647\u200c\u0645\u062a\u0631\u06cc \u0628\u0627\u06cc\u062f \u0631\u0648\u0634\u0646 \u0628\u0627\u0634\u062f.\n labels:\n   SAVE_CHANGES: \u0630\u062e\u06cc\u0631\u0647 \u062a\u063a\u06cc\u06cc\u0631\u0627\u062a\n   BACK: \u0628\u0627\u0632\u06af\u0634\u062a\n@@ -1458,7 +1625,7 @@ labels:\n   SAVE_ICONS: \u0622\u06cc\u06a9\u0648\u0646\u200c\u0647\u0627\u06cc \u0633\u0647\u200c\u0628\u0639\u062f\u06cc \u0630\u062e\u06cc\u0631\u0647\n   HINT_SAVE_ICONS: \u0622\u06cc\u06a9\u0648\u0646 \u0630\u062e\u06cc\u0631\u0647 \u0628\u0627\u0632\u06cc \u0627\u0646\u062a\u062e\u0627\u0628\u200c\u0634\u062f\u0647 \u0631\u0627\u060c \u062f\u0631 \u062d\u0627\u0644 \u0686\u0631\u062e\u0634\u060c \u062c\u0627\u06cc\u06cc \u06a9\u0647 \u067e\u0648\u0633\u062a\u0647 \u0628\u0631\u0627\u06cc \u0622\u0646 \u062c\u0627 \u062f\u0627\u0631\u062f \u0646\u0634\u0627\u0646 \u0645\u06cc\u200c\u062f\u0647\u062f. \u0627\u0648\u0644 VMC \u062e\u0648\u062f \u0628\u0627\u0632\u06cc \u0648 \u0633\u067e\u0633 \u06a9\u0627\u0631\u062a\u200c\u0647\u0627\u06cc \u062d\u0627\u0641\u0638\u0647 \u0631\u0627 \u0645\u06cc\u200c\u062e\u0648\u0627\u0646\u062f.\n   NETBOOT_RESTART_NOW: \u067e\u0631\u0648\u062a\u06a9\u0644 \u0634\u0628\u06a9\u0647 \u067e\u0633 \u0627\u0632 \u0631\u0627\u0647\u200c\u0627\u0646\u062f\u0627\u0632\u06cc \u062f\u0648\u0628\u0627\u0631\u0647 \u062a\u063a\u06cc\u06cc\u0631 \u0645\u06cc\u200c\u06a9\u0646\u062f. \u062a\u0646\u0638\u06cc\u0645\u0627\u062a \u0630\u062e\u06cc\u0631\u0647 \u0648 OPL \u0627\u06a9\u0646\u0648\u0646 \u062f\u0648\u0628\u0627\u0631\u0647 \u0631\u0627\u0647\u200c\u0627\u0646\u062f\u0627\u0632\u06cc \u0634\u0648\u062f\u061f\n-  HINT_NET_PROTOCOL: 'RiptOPL \u0627\u0632 \u06a9\u062f\u0627\u0645 \u0633\u0631\u0648\u06cc\u0633 \u0634\u0628\u06a9\u0647 \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u06a9\u0646\u062f: \u0627\u0634\u062a\u0631\u0627\u06a9 SMB\u060c UDPFS\u060c UDPBD \u06cc\u0627 HTTP. \u0647\u0645\u0627\u0646 \u062a\u0646\u0638\u06cc\u0645 \u0645\u0646\u0627\u0628\u0639 \u0628\u0627\u0632\u06cc \u0627\u0633\u062a. \u0631\u0641\u062a\u0646 \u0627\u0632 \u0633\u0631\u0648\u06cc\u0633\u06cc \u06a9\u0647 \u062f\u0631 \u062d\u0627\u0644 \u0627\u062c\u0631\u0627\u0633\u062a \u0628\u0647 \u0631\u0627\u0647\u200c\u0627\u0646\u062f\u0627\u0632\u06cc \u062f\u0648\u0628\u0627\u0631\u0647 \u0646\u06cc\u0627\u0632 \u062f\u0627\u0631\u062f.'\n+  HINT_NET_PROTOCOL: 'RiptOPL \u0627\u0632 \u06a9\u062f\u0627\u0645 \u0633\u0631\u0648\u06cc\u0633 \u0634\u0628\u06a9\u0647 \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u06a9\u0646\u062f: \u0627\u0634\u062a\u0631\u0627\u06a9 SMB\u060c UDPFS\u060c UDPBD \u06cc\u0627 HTTP. \u0631\u0641\u062a\u0646 \u0627\u0632 \u0633\u0631\u0648\u06cc\u0633\u06cc \u06a9\u0647 \u062f\u0631 \u062d\u0627\u0644 \u0627\u062c\u0631\u0627\u0633\u062a \u0628\u0647 \u0631\u0627\u0647\u200c\u0627\u0646\u062f\u0627\u0632\u06cc \u062f\u0648\u0628\u0627\u0631\u0647 \u0646\u06cc\u0627\u0632 \u062f\u0627\u0631\u062f.'\n   HINT_UDPFS_ACCESS: \u0641\u0642\u0637 UDPFS. \u0641\u0627\u06cc\u0644\u200c\u0647\u0627\u060c ISO\u0647\u0627\u06cc \u062c\u062f\u0627\u06af\u0627\u0646\u0647 \u0631\u0627 \u0627\u0632 \u067e\u0648\u0634\u0647 \u0628\u0627\u0632\u06cc\u200c\u0647\u0627\u06cc \u0633\u0631\u0648\u0631 \u0645\u06cc\u200c\u062e\u0648\u0627\u0646\u062f\u061b IMG \u06cc\u06a9 \u062a\u0635\u0648\u06cc\u0631 \u062f\u06cc\u0633\u06a9 \u0631\u0627 \u0628\u0647\u200c\u0639\u0646\u0648\u0627\u0646 \u062f\u0631\u0627\u06cc\u0648 \u0645\u06cc\u200c\u062e\u0648\u0627\u0646\u062f. SMB \u0648 HTTP \u0647\u0645\u06cc\u0634\u0647 \u0627\u0632 \u0641\u0627\u06cc\u0644\u200c\u0647\u0627\u060c \u0648 UDPBD \u0647\u0645\u06cc\u0634\u0647 \u0627\u0632 IMG \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u0645\u06cc\u200c\u06a9\u0646\u0646\u062f.\n   HINT_SMB_VERSION: \u0628\u0627 \u06a9\u062f\u0627\u0645 \u0646\u0633\u062e\u0647 SMB \u0628\u0627 \u0627\u0634\u062a\u0631\u0627\u06a9 \u0631\u0627\u06cc\u0627\u0646\u0647 \u06af\u0641\u062a\u200c\u0648\u06af\u0648 \u0634\u0648\u062f.\n   HINT_NET_ADVANCED: \u062d\u0627\u0644\u062a \u067e\u06cc\u0648\u0646\u062f \u0627\u062a\u0631\u0646\u062a \u0648 \u062f\u0631\u06af\u0627\u0647 SMB \u0631\u0627 \u0628\u0631\u0627\u06cc \u0648\u06cc\u0631\u0627\u06cc\u0634 \u0628\u0627\u0632 \u0645\u06cc\u200c\u06a9\u0646\u062f.\n@@ -1659,3 +1826,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: \u0686\u06af\u0648\u0646\u0647 \u0639\u06a9\u0633\u200c\u0647\u0627\u06cc \u0645\u0646\u062a\u0638\u0631 \u0631\u0648\u06cc \u062d\u0644\u0642\u0647\u0654 \u0647\u0631 \u0628\u0627\u0632\u06cc PNG \u0645\u06cc\u200c\u0634\u0648\u0646\u062f.\n   RIPPS2_SETTINGS_HOME: \u0630\u062e\u06cc\u0631\u0647\u0654 \u062a\u0646\u0638\u06cc\u0645\u0627\u062a \u062f\u0631\n   HINT_RIPPS2_SETTINGS_HOME: '\u062c\u0627\u06cc\u06cc \u06a9\u0647 RIPPS2 \u062a\u0646\u0638\u06cc\u0645\u0627\u062a\u0634 \u0631\u0627 \u0646\u06af\u0647 \u0645\u06cc\u200c\u062f\u0627\u0631\u062f: \u0630\u062e\u06cc\u0631\u0647\u0654 \u062e\u0648\u062f\u0634 \u0631\u0648\u06cc \u06a9\u0627\u0631\u062a \u062d\u0627\u0641\u0638\u0647\u060c \u067e\u0648\u0634\u0647\u0654 RIPPS2 \u0631\u0648\u06cc \u06cc\u06a9 \u062f\u0631\u0627\u06cc\u0648 (\u0647\u0627\u0631\u062f \u062f\u0627\u062e\u0644\u06cc\u060c USB\u060c MX4SIO\u060c iLink \u06cc\u0627 MMCE)\u060c \u06cc\u0627 \u067e\u0648\u0634\u0647\u200c\u0627\u06cc \u06a9\u0647 \u0645\u06cc\u200c\u0646\u0648\u06cc\u0633\u06cc\u062f.'\n+  RIPPS2_LAUNCHERS: \u0627\u062c\u0631\u0627\u06a9\u0646\u0646\u062f\u0647\u200c\u0647\u0627\n+  RIPPS2_LOOK_NOW: \u0627\u0644\u0627\u0646 \u062c\u0633\u062a\u062c\u0648 \u06a9\u0646\n+  HINT_RIPPS2_LOOK_NOW: RIPPS2 \u062f\u0631 \u062f\u0631\u0627\u06cc\u0648\u0647\u0627\u06cc \u0634\u0631\u0648\u0639\u200c\u0634\u062f\u0647 \u062f\u0646\u0628\u0627\u0644 POPSTARTER\u060c \u0641\u0627\u06cc\u0644\u200c\u0647\u0627\u06cc POPS\u060c Ember\u060c BIOS \u0645\u062e\u0635\u0648\u0635 Ember \u0648 Neutrino \u0645\u06cc\u200c\u06af\u0631\u062f\u062f \u0648 \u062c\u0627\u06cc \u0647\u0631 \u06a9\u062f\u0627\u0645 \u0631\u0627 \u0628\u0647 \u062e\u0627\u0637\u0631 \u0645\u06cc\u200c\u0633\u067e\u0627\u0631\u062f. \u0647\u0631 \u0627\u062c\u0631\u0627 \u0622\u0646\u200c\u0647\u0627 \u0631\u0627 \u0627\u0632 \u0647\u0645\u0627\u0646\u200c\u062c\u0627 \u0645\u06cc\u200c\u062e\u0648\u0627\u0646\u062f.\n+  RIPPS2_HIDE_GAME: \u067e\u0646\u0647\u0627\u0646 \u06a9\u0631\u062f\u0646 \u0628\u0627\u0632\u06cc\n+  RIPPS2_HIDDEN_GAMES: \u0628\u0627\u0632\u06cc\u200c\u0647\u0627\u06cc \u067e\u0646\u0647\u0627\u0646\n+  HINT_RIPPS2_HIDDEN_GAMES: \u0628\u0627\u0632\u06cc\u200c\u0647\u0627\u06cc\u06cc \u06a9\u0647 \u0628\u0627 \u067e\u0646\u0647\u0627\u0646 \u06a9\u0631\u062f\u0646 \u0628\u0627\u0632\u06cc \u067e\u0646\u0647\u0627\u0646 \u0634\u062f\u0647\u200c\u0627\u0646\u062f\u060c \u0631\u0648\u06cc \u062f\u0631\u0627\u06cc\u0648\u0647\u0627\u06cc \u0634\u0631\u0648\u0639\u200c\u0634\u062f\u0647. \u06cc\u06a9\u06cc \u0631\u0627 \u062f\u0648\u0628\u0627\u0631\u0647 \u0646\u0634\u0627\u0646 \u062f\u0647\u06cc\u062f \u06cc\u0627 \u062d\u0630\u0641\u0634 \u06a9\u0646\u06cc\u062f. \u0647\u0631 \u062f\u0631\u0627\u06cc\u0648 \u0641\u0647\u0631\u0633\u062a \u062e\u0648\u062f\u0634 \u0631\u0627 \u0646\u06af\u0647 \u0645\u06cc\u200c\u062f\u0627\u0631\u062f.\n+  HINT_RIPPS2_BDM_DEVICES: '\u062f\u0631\u0627\u06cc\u0648\u0647\u0627\u06cc\u06cc \u06a9\u0647 BDM \u0627\u0632 \u0622\u0646\u200c\u0647\u0627 \u0628\u0627\u0632\u06cc \u0645\u06cc\u200c\u062e\u0648\u0627\u0646\u062f: USB\u060c MX4SIO\u060c \u06cc\u06a9 PC Card CF\u060c \u062f\u0631\u0627\u06cc\u0648 DVR \u062f\u0633\u062a\u06af\u0627\u0647 DESR \u0648 iLink. \u0647\u0627\u0631\u062f \u062f\u0627\u062e\u0644\u06cc \u0627\u0632 \u062d\u0627\u0644\u062a \u0634\u0631\u0648\u0639 \u0647\u0627\u0631\u062f \u067e\u06cc\u0631\u0648\u06cc \u0645\u06cc\u200c\u06a9\u0646\u062f.'\n+  HINT_RIPPS2_SCREEN: '\u062a\u0635\u0648\u06cc\u0631 \u0645\u0646\u0648: \u062d\u0627\u0644\u062a \u0648\u06cc\u062f\u06cc\u0648\u060c \u0635\u0641\u062d\u0647\u0654 \u0639\u0631\u06cc\u0636\u060c \u062c\u0627\u06cc\u0634 \u0631\u0648\u06cc \u062a\u0644\u0648\u06cc\u0632\u06cc\u0648\u0646 (H-POS\u060c V-POS) \u0648 overscan.'\n+  HINT_RIPPS2_LAYOUT: \u062c\u0627\u06cc \u062f\u0633\u062a\u0647\u200c\u0647\u0627 \u0648 \u0686\u06af\u0648\u0646\u06af\u06cc \u0622\u0645\u062f\u0646 \u0635\u0641\u062d\u0647\u060c \u0633\u067e\u0633 \u062c\u0644\u062f\u0647\u0627\u06cc RIPFLOW \u0648 \u0634\u0628\u06a9\u0647\u0654 RIPGRID.\n+  HINT_RIPPS2_COLORS_TEXT: \u0636\u062e\u0627\u0645\u062a \u0645\u062a\u0646\u060c \u0631\u0646\u06af \u067e\u0648\u0633\u062a\u0647\u060c \u06af\u0630\u0631 \u0631\u0646\u06af \u0648 \u0647\u0645\u0647\u0654 \u0631\u0646\u06af\u200c\u0647\u0627.\n+  HINT_RIPPS2_TEXT_WEIGHT: '\u0636\u062e\u0627\u0645\u062a \u0645\u062a\u0646 \u062e\u0648\u062f RIPPS2: \u0631\u0627\u0647\u0646\u0645\u0627\u0647\u0627\u06cc \u062a\u0646\u0638\u06cc\u0645\u0627\u062a\u060c \u0648\u0636\u0639\u06cc\u062a \u0648 \u062f\u0631\u0628\u0627\u0631\u0647. \u0645\u0639\u0645\u0648\u0644\u06cc \u0645\u062b\u0644 \u0642\u0628\u0644 \u0627\u0633\u062a\u061b \u0645\u062a\u0648\u0633\u0637 \u0648 \u067e\u0631\u0631\u0646\u06af \u0631\u0648\u06cc \u062a\u0635\u0648\u06cc\u0631 \u0646\u0631\u0645 \u0628\u0647\u062a\u0631 \u062e\u0648\u0627\u0646\u062f\u0647 \u0645\u06cc\u200c\u0634\u0648\u0646\u062f.'\n+  HINT_RIPPS2_HDD_START: '\u0647\u0627\u0631\u062f \u062f\u0627\u062e\u0644\u06cc\u060c APA \u06cc\u0627 exFAT: RIPPS2 \u0647\u0631\u0686\u0647 \u0628\u0627\u0634\u062f \u0622\u0646 \u0631\u0627 \u0645\u06cc\u200c\u062e\u0648\u0627\u0646\u062f \u0648 \u06cc\u06a9 \u0628\u0627\u0631\u060c \u0628\u0647\u200c\u0639\u0646\u0648\u0627\u0646 \u0628\u0627\u0632\u06cc\u200c\u0647\u0627\u06cc HDD\u060c \u0646\u0634\u0627\u0646 \u0645\u06cc\u200c\u062f\u0647\u062f. \u062e\u0627\u0645\u0648\u0634 \u0628\u0647 \u0622\u0646 \u062f\u0633\u062a \u0646\u0645\u06cc\u200c\u0632\u0646\u062f.'\n+  HINT_RIPPS2_THEME_COLOR: \u0631\u0646\u06af \u062e\u0648\u062f \u067e\u0648\u0633\u062a\u0647. \u0633\u0641\u0627\u0631\u0634\u06cc \u0631\u0627 \u0628\u0647\u200c\u0639\u0646\u0648\u0627\u0646 \u0631\u0646\u06af \u067e\u0648\u0633\u062a\u0647 \u0628\u0631\u06af\u0632\u06cc\u0646\u06cc\u062f \u062a\u0627 \u062e\u0648\u062f\u062a\u0627\u0646 \u0622\u0646 \u0631\u0627 \u0627\u0646\u062a\u062e\u0627\u0628 \u06a9\u0646\u06cc\u062f\u060c \u0645\u062b\u0644 \u0631\u0646\u06af\u200c\u0647\u0627\u06cc \u0645\u062a\u0646.\n+  HINT_RIPPS2_SWEEP: \u067e\u0646\u062c \u0631\u0646\u06af\u06cc \u06a9\u0647 \u067e\u0648\u0633\u062a\u0647 \u0622\u0631\u0627\u0645 \u0648 \u0646\u0631\u0645 \u0627\u0632 \u0622\u0646\u200c\u0647\u0627 \u0645\u06cc\u200c\u06af\u0630\u0631\u062f. \u0631\u0646\u06af\u06cc\u0646\u200c\u06a9\u0645\u0627\u0646\u060c \u06af\u0630\u0631\u06cc \u0633\u0627\u062e\u062a\u0647 \u0627\u0632 \u0647\u0631 \u0631\u0646\u06af \u067e\u0648\u0633\u062a\u0647\u060c \u06cc\u0627 \u06af\u0630\u0631 \u062e\u0648\u062f\u062a\u0627\u0646. \u062e\u0627\u0645\u0648\u0634 \u0631\u0646\u06af \u067e\u0648\u0633\u062a\u0647 \u0631\u0627 \u062b\u0627\u0628\u062a \u0646\u06af\u0647 \u0645\u06cc\u200c\u062f\u0627\u0631\u062f.\n+  HINT_RIPPS2_SWEEP_COLORS: \u067e\u0646\u062c \u0631\u0646\u06af \u06af\u0630\u0631 \u0648 \u0633\u0631\u0639\u062a\u0634. \u06cc\u06a9\u06cc \u0627\u0632 \u06af\u0630\u0631\u0647\u0627\u06cc RIPPS2 \u0631\u0627 \u062a\u063a\u06cc\u06cc\u0631 \u062f\u0647\u06cc\u062f \u062a\u0627 \u0628\u0627 \u0646\u0627\u0645\u06cc \u06a9\u0647 \u0645\u06cc\u200c\u062f\u0647\u06cc\u062f \u0628\u0647\u200c\u0639\u0646\u0648\u0627\u0646 \u06af\u0630\u0631 \u062e\u0648\u062f\u062a\u0627\u0646 \u0646\u06af\u0647 \u062f\u0627\u0634\u062a\u0647 \u0634\u0648\u062f.\n+  HINT_RIPPS2_SWEEP_NAME: \u0646\u0627\u0645 \u06af\u0630\u0631 \u0634\u0645\u0627 \u062f\u0631 \u0641\u0647\u0631\u0633\u062a \u06af\u0630\u0631 \u0631\u0646\u06af.\n+  HINT_RIPPS2_SWEEP_COLOR: \u06cc\u06a9\u06cc \u0627\u0632 \u067e\u0646\u062c \u0631\u0646\u06af\u06cc \u06a9\u0647 \u06af\u0630\u0631 \u0628\u0647 \u062a\u0631\u062a\u06cc\u0628 \u0627\u0632 \u0622\u0646\u200c\u0647\u0627 \u0639\u0628\u0648\u0631 \u0645\u06cc\u200c\u06a9\u0646\u062f.\n+  HINT_RIPPS2_SWEEP_SPEED: '\u0647\u0631 \u0631\u0646\u06af \u0686\u0642\u062f\u0631 \u0645\u06cc\u200c\u0645\u0627\u0646\u062f \u062a\u0627 \u0631\u0646\u06af \u0628\u0639\u062f\u06cc \u0628\u06cc\u0627\u06cc\u062f. \u062d\u062a\u06cc \u0633\u0631\u0632\u0646\u062f\u0647 \u0647\u0645 \u0646\u0631\u0645 \u0645\u06cc\u200c\u0645\u0627\u0646\u062f: \u0631\u0646\u06af\u200c\u0647\u0627 \u0647\u0631\u06af\u0632 \u0686\u0634\u0645\u06a9 \u0646\u0645\u06cc\u200c\u0632\u0646\u0646\u062f.'\n+  RIPPS2_HIDE_COMPRESS: \u067e\u0646\u0647\u0627\u0646 \u06a9\u0631\u062f\u0646 \u0628\u0627\u0632\u06cc \u0648 \u0641\u0634\u0631\u062f\u0647\u200c\u0633\u0627\u0632\u06cc\n+  HINT_RIPPS2_SELECT_HOLD: 'SELECT \u0628\u0647 \u0645\u062f\u062a 4.2 \u062b\u0627\u0646\u06cc\u0647 \u0631\u0648\u06cc \u0641\u0647\u0631\u0633\u062a \u0628\u0627\u0632\u06cc \u0646\u06af\u0647 \u062f\u0627\u0634\u062a\u0647 \u0634\u0648\u062f. \u0641\u0647\u0631\u0633\u062a: \u067e\u0648\u0633\u062a\u0647\u200c\u0647\u0627 \u062f\u0631 \u06cc\u06a9 \u067e\u0646\u0644\u060c \u06cc\u06a9\u06cc \u0631\u0627 \u0627\u0646\u062a\u062e\u0627\u0628 \u06a9\u0646\u06cc\u062f. \u0686\u0631\u062e\u0634\u06cc: \u067e\u0648\u0633\u062a\u0647 \u0628\u0639\u062f\u06cc. \u062e\u0627\u0645\u0648\u0634: \u0647\u06cc\u0686 (\u0641\u0634\u0627\u0631 \u062f\u0627\u062f\u0646 \u0647\u0645\u0686\u0646\u0627\u0646 \u062a\u0627\u0632\u0647 \u0645\u06cc\u200c\u06a9\u0646\u062f).'"
        },
        {
          "sha": "b6f2e1224a24f13cf918fc28d160be101dfff0a9",
          "filename": "elf/lang/Polish.yml",
          "status": "modified",
          "additions": 209,
          "deletions": 22,
          "changes": 231,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FPolish.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FPolish.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FPolish.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: Polish\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTER ZATWIERDZA, ESC ANULUJE. DOWOLNY PRZYCISK POKAZUJE KLAWISZE.\n   USB KEYBOARD: KLAWIATURA USB\n-  '      DESR DVR ISOs (experimental)': '      ISO DESR DVR (eksperymentalne)'\n-  '      Internal HDD (exFAT)': '      Wewn\u0119trzny HDD (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (eksperymentalne)'\n-  '      Protocol': '      Protok\u00f3\u0142'\n   Access: Dost\u0119p\n   Applications Page Start Mode: Tryb startu strony aplikacji\n   Art Delay: Op\u00f3\u017anienie grafik\n@@ -22,7 +18,6 @@ strings:\n   Fifth: Pi\u0105ty\n   First: Pierwszy\n   Fourth: Czwarty\n-  HDD (APA) Start Mode: Tryb startu HDD (APA)\n   HDD Cache: Pami\u0119\u0107 podr\u0119czna HDD\n   HDD Spindown: U\u015bpienie HDD\n   MMCE Settings: Ustawienia MMCE\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: To gra PS2 z \u0142atk\u0105 ESR. Aby j\u0105 uruchomi\u0107, umie\u015b\u0107 ESR (esr*.elf) w APPS, BOOT lub ESR na karcie pami\u0119ci albo pendrivie USB.\n   '%s, BUILD %d': '%s, KOMPILACJA %d'\n   '... and the anonymous ...': '... oraz anonimowi ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': '1.8.0 do\u0142\u0105czone bez zmian (AFL-3.0): github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: Rozebrany i z\u0142o\u017cony na nowo RiptOPL, fork Open PS2 Loadera,\n   ABOUT: O PROGRAMIE\n   'ACHIEVEMENTS: SPIRIT OF RA': 'OSI\u0104GNI\u0118CIA: SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): Wideo AVI w Plikach pami\u0119ci, fork RIPPS2 (AFL 2.0)\n   Apollo Save Tool by bucanero: Apollo Save Tool autorstwa bucanero\n   BACK: WSTECZ\n+  Back (keeps both): Wstecz (zachowuje oba)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: Ta karta MMCE nie potrafi zmieni\u0107 nazwy pliku, czego kompresja potrzebuje w ostatnim kroku. Zaktualizuj jej firmware sd2psXtd do 1.4.0 lub nowszego (sd2psxtd.github.io/download) i spr\u00f3buj ponownie. Nic nie zosta\u0142o zmienione.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): BIBLE to PS2BBL 1.2.0, podpisane wydania El Isry (karta i HDD), bez zmian (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: D\u017awi\u0119ki BIOS > Zgraj odtwarza je tak jak SPU2; \u017caden BIOS nie jest do\u0142\u0105czony\n   BOOT LOCK AND BIBLE: BOOT LOCK I BIBLE\n   BOOT LOCK edits its config too: BOOT LOCK edytuje te\u017c jego konfiguracj\u0119\n   BUILD DETAILS: SZCZEG\u00d3\u0141Y KOMPILACJI\n-  BUILT ON RIPTOPL: ZBUDOWANE NA RIPTOPL\n   BearSSL by Thomas Pornin: BearSSL autorstwa Thomasa Pornina\n   Built with Claude (Anthropic): Zrobione z Claude (Anthropic)\n   CD PLAYER: ODTWARZACZ CD\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek i Sleek Bold, narysowane dla RIPPS2\n   RetroAchievements on the console by hacan359 (yoba): RetroAchievements na konsoli autorstwa hacan359 (yoba)\n   RiptOPL by NathanNeurotic: RiptOPL autorstwa NathanNeurotica\n-  Roboto Condensed by Google (Apache-2.0): Roboto Condensed autorstwa Google (Apache-2.0)\n   SOUNDS: D\u0179WI\u0118KI\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: Instalator FMCB od SP193 i OSDMenu od pcm720, krok po kroku\n   SUPPORT: WSPARCIE\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: wLaunchELF R3Z autorstwa NathanNeurotica\n   wLaunchELF by the ps2homebrew team: wLaunchELF autorstwa zespo\u0142u ps2homebrew\n   what RIPPS2's card image writes are checked against: z czym por\u00f3wnywane s\u0105 zapisy obraz\u00f3w kart RIPPS2\n-  with its own look, launchers, file browser and save tools.: z w\u0142asnym wygl\u0105dem, launcherami, przegl\u0105dark\u0105 plik\u00f3w i narz\u0119dziami zapis\u00f3w.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: xeRAbora autorstwa hacan359, rcheevos autorstwa RetroAchievements\n   your console's own, ripped from its BIOS: w\u0142asne d\u017awi\u0119ki twojej konsoli, zgrane z jej BIOS-u\n   ' (no DNS answer)': ' (brak odpowiedzi DNS)'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO: zapisywanie %.60s'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: Nie uda\u0142o si\u0119 zapisa\u0107 Neutrino w %s. Sprawd\u017a wolne miejsce i czy nie jest zabezpieczony przed zapisem, a potem spr\u00f3buj ponownie.\n   THE GAME'S DRIVE  (%s/neutrino): DYSK GRY  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : Ta gra startuje przez Neutrino, a tam, gdzie szuka RIPPS2 (Ustawienia, dysk gry, karty pami\u0119ci), nie ma Neutrino. RIPPS2 ma ze sob\u0105 Neutrino 1.8.0 autorstwa rickgaisera (AFL-3.0). Zainstalowa\u0107 je teraz? Nast\u0119pnie wybierzesz miejsce.\n   '%d / %d FILES': '%d / %d PLIK\u00d3W'\n   '%d FOLDERS AND CONTENTS': '%d FOLDER\u00d3W I ZAWARTO\u015a\u0106'\n   '%d FOLDERS, %d FOUND': '%d FOLDER\u00d3W, ZNALEZIONO %d'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d z %d wci\u0105\u017c zablokowanych'\n   '%s   on %s': '%s   na %s'\n   BADGES: ODZNAKI\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: Odznaki dzia\u0142aj\u0105 tylko przy w\u0142\u0105czonej TELEMETRII i uruchomionym na PC xeRAbora; do tego czasu nic nie kosztuj\u0105.\n   CHECK THE DISC: SPRAWD\u0179 P\u0141YT\u0118\n   From this game's last CHECK GAME SUPPORT.: Z ostatniego SPRAWD\u0179 OBS\u0141UG\u0118 GRY dla tej gry.\n   HOW IT WORKS: JAK TO DZIA\u0141A\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: Na tej konsoli jest PS2BBL z blokad\u0105 RIPPS2.\n   AUDIO folder: Folder AUDIO\n   DRIVES: DYSKI\n-  INSTALL RIPPS2'S COPY: ZAINSTALUJ KOPI\u0118 RIPPS2\n-  'Installed: ': 'Zainstalowano: '\n   MANAGE: ZARZ\u0104DZAJ\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: ZARZ\u0104DZAJ pokazuje, co uruchamia ka\u017cda karta pami\u0119ci, i instaluje lub naprawia BIBLE.\n   MEMORY CARD 0  (about 600 KB): KARTA PAMI\u0118CI 0  (oko\u0142o 600 KB)\n   MEMORY CARD 1  (about 600 KB): KARTA PAMI\u0118CI 1  (oko\u0142o 600 KB)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO: nie uda\u0142o si\u0119 wczyta\u0107 sterownika (kod %d).'\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO: sterownik jest wczytany, ale \u017cadna karta SD nie odpowiedzia\u0142a (gniazdo mc1).'\n   NEUTRINO SETTINGS: USTAWIENIA NEUTRINO\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': 'Jeszcze brak: RIPPS2 nie ma w\u0142asnych d\u017awi\u0119k\u00f3w. Zgraj te z tej konsoli albo zr\u00f3b je w RIPPS2 Audio Studio.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: Nie zainstalowano. Gra, kt\u00f3ra tego potrzebuje, zaproponuje kopi\u0119 RIPPS2, albo zainstaluj tutaj.\n   POPSTARTER NETWORK: SIE\u0106 POPSTARTER\n   POPSTARTER and Ember: POPSTARTER i Ember\n   PS1 SETTINGS: USTAWIENIA PS1\n   PS2BBL 1.2.0 for RIPPS2: PS2BBL 1.2.0 dla RIPPS2\n   RIP THIS CONSOLE'S SOUNDS: ZGRAJ D\u0179WI\u0118KI TEJ KONSOLI\n-  RIPPS2's homebrew: homebrew RIPPS2\n   STATUS: STAN\n-  'STATUS: looking for RIPPS2''s homebrew': 'STAN: szukanie homebrew RIPPS2'\n   a drive of unknown kind: dysk nieznanego rodzaju\n-  a drive or a memory card: dysk lub karta pami\u0119ci\n   boot loader, card by card: program rozruchowy, karta po karcie\n   from its BIOS, into AUDIO: z jej BIOS-u, do AUDIO\n   internal HDD (exFAT): wewn\u0119trzny HDD (exFAT)\n@@ -975,10 +958,194 @@ strings:\n   Sidebar: Pasek boczny\n   your own copy, found on your drives and memory cards: twoja w\u0142asna kopia, znaleziona na twoich dyskach i kartach pami\u0119ci\n   'your own, found and launched: github.com/Gageformer/Ember/releases': 'tw\u00f3j w\u0142asny, znaleziony i uruchomiony: github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': 'Nie znaleziono na mass0: (%s). Umie\u015b\u0107 ember.elf i sw\u00f3j bios.bin w jego folderze EMBER.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: Nie znaleziono. Umie\u015b\u0107 w\u0142asny POPSTARTER.ELF w folderze POPS na dysku.\n   Launch Disc: Uruchom p\u0142yt\u0119\n   Memory Files: Pliki pami\u0119ci\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: RIPPS2 znalaz\u0142 %d z 5. Ka\u017cdy jest zapami\u0119tany, a ka\u017cde uruchomienie odczytuje go stamt\u0105d.\n+  not found on the started drives: nie znaleziono na uruchomionych dyskach\n+  not looked for yet: jeszcze nie szukano\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s nie odpowiedzia\u0142, wi\u0119c %s nie mo\u017ce si\u0119 uruchomi\u0107. Czy jest pod\u0142\u0105czony?'\n+  '%s is not on %s any more, so it has left All Games.': '%s nie ma ju\u017c na %s, wi\u0119c znikn\u0119\u0142a z Wszystkich gier.'\n+  STARTING %s: URUCHAMIANIE %s\n+  the SMB share: udzia\u0142 SMB\n+  the drive: dysk\n+  the internal HDD (APA): wewn\u0119trzny HDD (APA)\n+  the internal HDD (exFAT): wewn\u0119trzny HDD (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: ZBUDOWANY NA RIPTOPL, Z NEUTRINO\n+  'Special thanks to all the YouTube love as well:': 'Specjalne podzi\u0119kowania tak\u017ce za ca\u0142\u0105 mi\u0142o\u015b\u0107 z YouTube:'\n+  and its own look, launchers, file browser and save tools.: oraz z w\u0142asnym wygl\u0105dem, programami startowymi, przegl\u0105dark\u0105 plik\u00f3w i narz\u0119dziami do zapis\u00f3w.\n+  glad to help however you reach me.: ch\u0119tnie pomog\u0119, jakkolwiek si\u0119 ze mn\u0105 skontaktujecie.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): drugi rdze\u0144 gier, 1.8.0 do\u0142\u0105czony bez zmian (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: z Neutrino od rickgaisera w \u015brodku jako drugim rdzeniem gier,\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : 'Ta gra uruchamia si\u0119 przez Neutrino, a RIPPS2 go nie znalaz\u0142: ani pod \u015bcie\u017ck\u0105 z Ustawie\u0144, ani na dysku gry, ani na kartach pami\u0119ci, ani na \u017cadnym uruchomionym dysku. RIPPS2 zawiera Neutrino 1.8.0 od rickgaisera (AFL-3.0): zainstaluj go raz, gdzie chcesz, a ka\u017cda gra z Neutrino b\u0119dzie go stamt\u0105d u\u017cywa\u0107.'\n+  STARTING THE DRIVES: URUCHAMIANIE DYSK\u00d3W\n+  '%d games, on the started drives': '%d gier, na uruchomionych dyskach'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s: NIEKT\u00d3RYCH PLIK\u00d3W NIE DA\u0141O SI\u0118 USUN\u0104\u0106'\n+  '%u partitions, all in order': '%u partycji, wszystkie w porz\u0105dku'\n+  ', %d art images': ', %d obraz\u00f3w ok\u0142adek'\n+  ', and ': ', oraz '\n+  ', its cheats': ', jej kody'\n+  ', its settings': ', jej ustawienia'\n+  1 game, on the started drives: 1 gra, na uruchomionych dyskach\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: Gr\u0119 PS1 st\u0105d usuwa si\u0119 w Plikach pami\u0119ci, gdzie jest osobnym plikiem.\n+  A network catalogue's games are deleted on the PC that serves them.: Gry z katalogu sieciowego usuwa si\u0119 na komputerze, kt\u00f3ry je udost\u0119pnia.\n+  DELETE FOR GOOD: USU\u0143 NA ZAWSZE\n+  DELETE IT: USU\u0143 J\u0104\n+  'DELETED %s: %s FREED': '%s USUNI\u0118TO: ZWOLNIONO %s'\n+  'Delete %s from %s? Goes: %s.': 'Usun\u0105\u0107 %s z %s? Znika: %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': 'Usun\u0105\u0107 %s z %s? Znika: %s. Zostaje: %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: Usun\u0105\u0107 te\u017c jej zapisy i zdj\u0119cia RIPSHOT? Nie je zachowuje.\n+  HIDDEN GAMES: UKRYTE GRY\n+  'HIDDEN: %s': 'UKRYTO: %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: Ukry\u0107 %s? Zniknie z ka\u017cdej listy, tak\u017ce z Wszystkich gier i Ulubionych. Ustawienia > \u0179r\u00f3d\u0142a gier > Ukryte gry poka\u017c\u0105 j\u0105 znowu.\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: Ostatnie sprawdzenie. %s zostanie usuni\u0119ta na zawsze i nic jej nie przywr\u00f3ci. Naci\u015bnij START, aby j\u0105 usun\u0105\u0107.\n+  None. Hide Game, in a game's options, puts one here.: Brak. Ukryj gr\u0119, w opcjach gry, umieszcza j\u0105 tutaj.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : 'Nic nie usuni\u0119to: tablica partycji tego dysku nie pasuje do tego dysku (%s). Mog\u0142a zosta\u0107 zapisana z obrazu dysku o innym rozmiarze. Najpierw napraw j\u0105 na komputerze.'\n+  RIPPS2 cannot delete a game from this page.: RIPPS2 nie mo\u017ce usun\u0105\u0107 gry z tej strony.\n+  RIPPS2 could not find this game's files on its drive.: RIPPS2 nie znalaz\u0142 plik\u00f3w tej gry na jej dysku.\n+  Reading the hidden lists: Odczyt list ukrytych\n+  SHOW AGAIN OR DELETE: POKA\u017b ZNOWU LUB USU\u0143\n+  SHOW IT AGAIN: POKA\u017b J\u0104 ZNOWU\n+  That game is not on its drive any more.: Tej gry nie ma ju\u017c na jej dysku.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: Nie uda\u0142o si\u0119 zapisa\u0107 listy ukrytych na dysku gry. Czy jest zabezpieczony przed zapisem, albo czy Operacja Zapisu jest wy\u0142\u0105czona?\n+  The partition could not be removed (%d). Nothing was deleted.: Nie uda\u0142o si\u0119 usun\u0105\u0107 partycji (%d). Nic nie usuni\u0119to.\n+  This drive cannot keep a hidden list (a network catalogue).: Ten dysk nie mo\u017ce przechowywa\u0107 listy ukrytych (katalog sieciowy).\n+  This game's drive has not answered yet. Wait for it, then try again.: Dysk tej gry jeszcze nie odpowiedzia\u0142. Poczekaj na niego, a potem spr\u00f3buj ponownie.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: Partycja tej gry nale\u017cy do tych, kt\u00f3rych RIPPS2 nie usuwa (partycja systemowa albo ta z jego ustawieniami).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': 'partycja jest wi\u0119ksza, ni\u017c pozwala ten dysk: tablic\u0119 zrobiono dla dysku o innym rozmiarze'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': 'partycja wychodzi poza koniec tego dysku (sektor %u z %u): tablic\u0119 zrobiono dla wi\u0119kszego dysku'\n+  its RIPSHOT pictures: jej zdj\u0119cia RIPSHOT\n+  its drive: jej dysk\n+  its saves: jej zapisy\n+  sector %u holds no partition header: sektor %u nie zawiera nag\u0142\u00f3wka partycji\n+  the chain does not close at __mbr: \u0142a\u0144cuch nie zamyka si\u0119 na __mbr\n+  the chain skips from sector %u: \u0142a\u0144cuch przeskakuje od sektora %u\n+  the chain's links disagree at sector %u: ogniwa \u0142a\u0144cucha nie zgadzaj\u0105 si\u0119 w sektorze %u\n+  the drive's size could not be read: nie uda\u0142o si\u0119 odczyta\u0107 rozmiaru dysku\n+  the game (%s): gra (%s)\n+  the game's folder and everything in it (%s): folder gry i wszystko w nim (%s)\n+  the header at sector %u could not be read: nie uda\u0142o si\u0119 odczyta\u0107 nag\u0142\u00f3wka w sektorze %u\n+  the header at sector %u fails its checksum: nag\u0142\u00f3wek w sektorze %u nie przechodzi sumy kontrolnej\n+  the partition at sector %u is not where or as large as APA puts one: partycja w sektorze %u nie le\u017cy tam, gdzie ani nie ma rozmiaru, jaki nadaje jej APA\n+  the settings and art another copy of it uses: ustawienia i ok\u0142adki, kt\u00f3rych u\u017cywa inna jej kopia\n+  '%d FOLDERS': '%d FOLDER\u00d3W'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': Nie uda\u0142o si\u0119 skopiowa\u0107 %s do %s. Czy dysk jest pe\u0142ny lub zabezpieczony przed zapisem?\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s nie ma tam, gdzie szuka RIPPS2. Poszuka\u0107 na dyskach uruchomionych przez RIPPS2? Miejsce znalezienia zostanie zapami\u0119tane na ka\u017cde kolejne uruchomienie.'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Ember nie ma w %s, obok gier, a musi tam by\u0107. RIPPS2 znalaz\u0142 go w %s: START kopiuje go tam (%d KB) i uruchamia gr\u0119.'\n+  Ember's BIOS: BIOS Embera\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : 'BIOS Embera (bios.bin) nie ma w %s, obok Embera. RIPPS2 znalaz\u0142 jeden w %s: START kopiuje go tam (%d KB) i uruchamia gr\u0119.'\n+  LOOKING FOR YOUR LAUNCHERS: SZUKANIE TWOICH PROGRAM\u00d3W STARTOWYCH\n+  The POPS files: Pliki POPS\n+  '%s  MMCE card': '%s  karta MMCE'\n+  INSTALL RIPPS2'S NEUTRINO: ZAINSTALUJ NEUTRINO Z RIPPS2\n+  'Internal HDD (APA): %s free of %s': 'Wewn\u0119trzny HDD (APA): wolne %s z %s'\n+  LAUNCHERS: PROGRAMY STARTOWE\n+  LOOK FOR THEM: POSZUKAJ ICH\n+  'Memory card %d: %d KB free': 'Karta pami\u0119ci %d: wolne %d KB'\n+  POPS files: Pliki POPS\n+  START EVERY DRIVE: URUCHOM WSZYSTKIE DYSKI\n+  'STATUS: looking at the drives and the homebrew': 'STAN: przegl\u0105d dysk\u00f3w i homebrew'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): Najpierw uruchomi\u0107 wszystkie dyski, \u017ceby wyszukiwanie dotar\u0142o do ka\u017cdego? (Nie szuka na ju\u017c uruchomionych dyskach.)\n+  USB, HDD, MMCE and the rest: USB, HDD, MMCE i reszta\n+  every started drive: ka\u017cdy uruchomiony dysk\n+  found where you keep them: znalezione tam, gdzie je trzymasz\n+  not found yet: jeszcze nie znaleziono\n+  not on a started drive: nie na uruchomionym dysku\n+  only if you have none: tylko je\u015bli nie masz \u017cadnego\n+  starts every drive first: najpierw uruchamia wszystkie dyski\n+  Drives, launchers and homebrew: Dyski, programy startowe i homebrew\n+  BDM Devices: Urz\u0105dzenia BDM\n+  Color Sweep: Przej\u015bcie kolor\u00f3w\n+  Colors and Text: Kolory i tekst\n+  HDD POPS partitions: Partycje POPS na HDD\n+  Layout: Uk\u0142ad\n+  Missing Covers: Brakuj\u0105ce ok\u0142adki\n+  Name: Nazwa\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: Nie znaleziono jeszcze ustawie\u0144 SMB dla POPSTARTER. Wype\u0142nij wiersze poni\u017cej.\n+  POPSTARTER Network: Sie\u0107 POPSTARTER\n+  POPSTARTER SMB Network: Sie\u0107 SMB dla POPSTARTER\n+  POPSTARTER UDPFS Network: Sie\u0107 UDPFS dla POPSTARTER\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: RIPPS2 sam dodaje nap\u0119d, ISO, karty VMC i tryb wideo.\n+  Screen: Ekran\n+  Speed: Szybko\u015b\u0107\n+  Sweep Colors: Kolory przej\u015bcia\n+  Text Weight: Grubo\u015b\u0107 tekstu\n+  Theme Color: Kolor g\u0142\u00f3wny\n+  Audio: D\u017awi\u0119k\n+  Bold: Pogrubiony\n+  Controllers: Kontrolery\n+  Lively: \u017bywo\n+  Medium: \u015aredni\n+  PS Emulation: Emulacja PS\n+  Regular: Zwyk\u0142y\n+  Slow: Wolno\n+  A MOMENT: CHWILK\u0118\n+  A TASK IN THE BACKGROUND: ZADANIE W TLE\n+  ABOUT %d S: OKO\u0141O %d S\n+  ANY MOMENT: LADA CHWILA\n+  READING THE GAME LIST OF %s: CZYTA LIST\u0118 GIER Z %s\n+  SAVING A LIST: ZAPISUJE LIST\u0118\n+  START NOW: URUCHOM TERAZ\n+  STARTING: URUCHAMIA\n+  STARTS NEXT: URUCHOMI SI\u0118 NAST\u0118PNIE\n+  STOPPING IT SAFELY: BEZPIECZNIE ZATRZYMUJE\n+  THE LAST OF THE QUEUE: KONIEC KOLEJKI\n+  WHEN IT FINISHES: GDY SKO\u0143CZY\n+  What each card starts; install or repair BIBLE.: Co uruchamia ka\u017cda karta; zainstaluj lub napraw BIBLE.\n+  Rainbow: T\u0119cza\n+  Custom: W\u0142asny\n+  DESR DVR ISOs (experimental): ISO DESR DVR (eksperymentalne)\n+  PC CARD CF (experimental): PC CARD CF (eksperymentalne)\n+  HDD Start Mode: Tryb startu HDD\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): Roboto Condensed autorstwa The Roboto Project Authors (SIL OFL 1.1)\n+  '%s on %s': '%s na %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: %s zmieni\u0142o si\u0119 w %s (%d%%), ka\u017cdy kawa\u0142ek sprawdzony. Usun\u0105\u0107 ISO i zachowa\u0107 ZSO?'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: %s zn\u00f3w zmieni\u0142o si\u0119 w %s, ka\u017cdy kawa\u0142ek sprawdzony. Usun\u0105\u0107 ZSO i zachowa\u0107 ISO?'\n+  A ZSO of this game is already beside it. Remove one of the two first.: Obok jest ju\u017c ZSO tej gry. Najpierw usu\u0144 jedno z dw\u00f3ch.\n+  About %d min, then checked: Oko\u0142o %d min, potem sprawdzenie\n+  An ISO of this game is already beside it. Remove one of the two first.: Obok jest ju\u017c ISO tej gry. Najpierw usu\u0144 jedno z dw\u00f3ch.\n+  CHECKING EVERY PIECE: SPRAWDZANIE KA\u017bDEGO KAWA\u0141KA\n+  COMPRESS TO ZSO: KOMPRESUJ DO ZSO\n+  'COMPRESSED %s: %s FREED': 'SKOMPRESOWANO %s: ZWOLNIONO %s'\n+  COMPRESSING 1 OF 2: KOMPRESJA 1 Z 2\n+  COMPRESSING 2 OF 2: KOMPRESJA 2 Z 2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : Skompresowa\u0107 %s? ISO zostaje, dop\u00f3ki ZSO nie zostanie odczytane i sprawdzone, potem RIPPS2 pyta, zanim usunie ISO. Dysk potrzebuje w trakcie pracy miejsca na oba; nie wy\u0142\u0105czaj konsoli, dop\u00f3ki nie sko\u0144czy. ZSO uruchamia si\u0119 tylko z rdzeniem OPL (nie Neutrino); jej zapisy, ustawienia, obrazy i kody zostaj\u0105 z ni\u0105 i w ka\u017cdej chwili mo\u017cna z niej zn\u00f3w zrobi\u0107 ISO.\n+  Compress to ZSO: Kompresuj do ZSO\n+  Cycle: Po kolei\n+  'Experimental: OPL core only (not Neutrino)': 'Eksperymentalne: tylko rdze\u0144 OPL (nie Neutrino)'\n+  Hold SELECT: Przytrzymanie SELECT\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: Nie uda\u0142o si\u0119 doko\u0144czy\u0107 (%s). To, co zapisano, usuni\u0119to; gra jest taka jak by\u0142a.\n+  List: Lista\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : Zrobi\u0107 z %s zn\u00f3w ISO? ZSO zostaje, dop\u00f3ki ISO nie zostanie odczytane i sprawdzone, potem RIPPS2 pyta, zanim usunie ZSO. Dysk potrzebuje w trakcie pracy miejsca na oba; nie wy\u0142\u0105czaj konsoli, dop\u00f3ki nie sko\u0144czy. Jej zapisy, ustawienia, obrazy i kody zostaj\u0105 z ni\u0105.\n+  Stop? What it has written so far is removed; the game stays as it was.: Zatrzyma\u0107? To, co dot\u0105d zapisano, zostanie usuni\u0119te; gra zostanie taka, jak by\u0142a.\n+  THEMES: MOTYWY\n+  There is not enough memory for this just now.: Teraz brakuje na to pami\u0119ci.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: Tego obrazu RIPPS2 nie umie skompresowa\u0107 ani rozpakowa\u0107 (jego rozmiar to nie pe\u0142ne bloki po 2 KB).\n+  UNCOMPRESS TO ISO: ROZPAKUJ DO ISO\n+  UNCOMPRESSED %s: ROZPAKOWANO %s\n+  UNCOMPRESSING: ROZPAKOWYWANIE\n+  Uncompress to ISO: Rozpakuj do ISO\n+  Under a minute, then checked: Poni\u017cej minuty, potem sprawdzenie\n+  COPYING: KOPIOWANIE\n+  SHOW AGAIN, DELETE OR COMPRESS: POKA\u017b ZNOWU, USU\u0143 LUB KOMPRESUJ\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: POKA\u017b ZNOWU, USU\u0143 LUB ROZPAKUJ\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': '%s potrzebuje w trakcie pracy oko\u0142o %s wolnego miejsca na swoim dysku (orygina\u0142 zostaje do ko\u0144ca), a dysk ma %s wolnego.'\n+  Hide and Compress: Ukryj i kompresuj\n+  'COMPRESSED AND HIDDEN: %s': 'SKOMPRESOWANO I UKRYTO: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': 'Brak miejsca na t\u0119 zgrywk\u0119: potrzeba oko\u0142o %s, a dysk ma %s wolnego.'\n+  OVER 4 GB FOR FAT32: PONAD 4 GB DLA FAT32\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: Ten dysk to %s, kt\u00f3ry nie mie\u015bci pliku 4 GB lub wi\u0119kszego, a nowy obraz mia\u0142by %s.\n+  '%s, %s clusters, %s free of %s': '%s, klastry %s, %s wolnego z %s'\n+  Delete This Sweep: Usu\u0144 to przej\u015bcie\n+  Delete %s? RIPPS2's own sweeps stay as they are.: Usun\u0105\u0107 %s? Przej\u015bcia samego RIPPS2 zostaj\u0105 bez zmian.\n+  Sweep: Przej\u015bcie\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: Odznaki zaznaczaj\u0105 gry z osi\u0105gni\u0119ciami na twoich listach i ile odblokowa\u0142e\u015b. Wymagaj\u0105 w\u0142\u0105czonej TELEMETRII.\n labels:\n   OK: OK\n   START: Uruchom\n@@ -1208,7 +1375,7 @@ labels:\n   SAVE_ICONS: Ikony zapis\u00f3w 3D\n   HINT_SAVE_ICONS: Pokazuje obracaj\u0105c\u0105 si\u0119 ikon\u0119 zapisu wybranej gry tam, gdzie motyw ma na ni\u0105 miejsce. Najpierw czyta w\u0142asny VMC gry, potem karty pami\u0119ci.\n   NETBOOT_RESTART_NOW: Protok\u00f3\u0142 sieciowy zmieni si\u0119 po ponownym uruchomieniu. Zapisa\u0107 ustawienia i uruchomi\u0107 teraz ponownie OPL?\n-  HINT_NET_PROTOCOL: 'Kt\u00f3rej us\u0142ugi sieciowej u\u017cywa RiptOPL: udzia\u0142u SMB, UDPFS, UDPBD lub HTTP. To samo ustawienie co w \u0179r\u00f3d\u0142ach gier. Zmiana z ju\u017c dzia\u0142aj\u0105cej wymaga ponownego uruchomienia.'\n+  HINT_NET_PROTOCOL: 'Kt\u00f3rej us\u0142ugi sieciowej u\u017cywa RiptOPL: udzia\u0142u SMB, UDPFS, UDPBD lub HTTP. Zmiana z ju\u017c dzia\u0142aj\u0105cej wymaga ponownego uruchomienia.'\n   HINT_UDPFS_ACCESS: Tylko UDPFS. Pliki czyta pojedyncze ISO z folderu gier na serwerze; IMG czyta obraz dysku jako dysk. SMB i HTTP zawsze u\u017cywaj\u0105 Plik\u00f3w, UDPBD zawsze IMG.\n   HINT_SMB_VERSION: Kt\u00f3r\u0105 wersj\u0105 SMB rozmawia\u0107 z udzia\u0142em na PC.\n   HINT_NET_ADVANCED: Odblokowuje edycj\u0119 trybu \u0142\u0105cza Ethernet i portu SMB.\n@@ -1407,3 +1574,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: Jak zdj\u0119cia czekaj\u0105ce na rolce ka\u017cdej gry staj\u0105 si\u0119 plikami PNG.\n   RIPPS2_SETTINGS_HOME: Zapisuj ustawienia na\n   HINT_RIPPS2_SETTINGS_HOME: 'Gdzie RIPPS2 trzyma ustawienia: we w\u0142asnym zapisie na karcie pami\u0119ci, w folderze RIPPS2 na dysku (wewn\u0119trznym HDD, USB, MX4SIO, iLink lub MMCE) lub w folderze, kt\u00f3ry wpiszesz.'\n+  RIPPS2_LAUNCHERS: Programy startowe\n+  RIPPS2_LOOK_NOW: Szukaj teraz\n+  HINT_RIPPS2_LOOK_NOW: RIPPS2 szuka na uruchomionych dyskach POPSTARTER, plik\u00f3w POPS, Embera, BIOS-u Embera i Neutrino, i zapami\u0119tuje, gdzie jest ka\u017cdy z nich. Ka\u017cde uruchomienie odczytuje je stamt\u0105d.\n+  RIPPS2_HIDE_GAME: Ukryj gr\u0119\n+  RIPPS2_HIDDEN_GAMES: Ukryte gry\n+  HINT_RIPPS2_HIDDEN_GAMES: Gry ukryte opcj\u0105 Ukryj gr\u0119, na uruchomionych dyskach. Poka\u017c kt\u00f3r\u0105\u015b znowu albo j\u0105 usu\u0144. Ka\u017cdy dysk ma w\u0142asn\u0105 list\u0119.\n+  HINT_RIPPS2_BDM_DEVICES: 'Nap\u0119dy, z kt\u00f3rych BDM czyta gry: USB, MX4SIO, PC Card CF, nap\u0119d DVR w DESR i iLink. Wewn\u0119trzny HDD pod\u0105\u017ca za trybem startu HDD.'\n+  HINT_RIPPS2_SCREEN: 'Obraz menu: tryb wideo, panorama, jego po\u0142o\u017cenie na telewizorze (H-POS, V-POS) i overscan.'\n+  HINT_RIPPS2_LAYOUT: Gdzie s\u0105 kategorie i jak wchodzi strona, potem ok\u0142adki RIPFLOW i siatka RIPGRID.\n+  HINT_RIPPS2_COLORS_TEXT: Grubo\u015b\u0107 tekstu, motyw kolor\u00f3w, przej\u015bcie kolor\u00f3w i ka\u017cdy kolor.\n+  HINT_RIPPS2_TEXT_WEIGHT: 'Jak gruby jest w\u0142asny tekst RIPPS2: podpowiedzi ustawie\u0144, STAN i Informacje. Zwyk\u0142y jest jak dot\u0105d; \u015aredni i Pogrubiony lepiej czyta si\u0119 na mi\u0119kkim obrazie.'\n+  HINT_RIPPS2_HDD_START: 'Wewn\u0119trzny HDD, APA lub exFAT: RIPPS2 czyta go, jakikolwiek jest, i pokazuje raz, jako gry z HDD. Wy\u0142\u0105czone zostawia go w spokoju.'\n+  HINT_RIPPS2_THEME_COLOR: W\u0142asny kolor motywu. Wybierz W\u0142asny jako motyw kolor\u00f3w, aby wybra\u0107 go samemu, jak kolory tekstu.\n+  HINT_RIPPS2_SWEEP: Pi\u0119\u0107 kolor\u00f3w, przez kt\u00f3re motyw przechodzi powoli i p\u0142ynnie. T\u0119cza, przej\u015bcie z ka\u017cdego motywu kolor\u00f3w lub twoje w\u0142asne. Wy\u0142\u0105czone trzyma motyw w miejscu.\n+  HINT_RIPPS2_SWEEP_COLORS: Pi\u0119\u0107 kolor\u00f3w przej\u015bcia i jego szybko\u015b\u0107. Zmie\u0144 jedno z RIPPS2, a zostanie zapisane jako twoje, pod nadan\u0105 nazw\u0105.\n+  HINT_RIPPS2_SWEEP_NAME: Jak twoje przej\u015bcie nazywa si\u0119 na li\u015bcie Przej\u015bcie kolor\u00f3w.\n+  HINT_RIPPS2_SWEEP_COLOR: Jeden z pi\u0119ciu kolor\u00f3w, przez kt\u00f3re kolejno przechodzi przej\u015bcie.\n+  HINT_RIPPS2_SWEEP_SPEED: 'Jak d\u0142ugo trwa ka\u017cdy kolor, zanim wejdzie nast\u0119pny. Nawet \u017bywo pozostaje p\u0142ynne: kolory nigdy nie migaj\u0105.'\n+  RIPPS2_HIDE_COMPRESS: Ukryj gr\u0119 i kompresja\n+  HINT_RIPPS2_SELECT_HOLD: 'SELECT przytrzymany 4.2 s na li\u015bcie gier. Lista: motywy w panelu, wybierz jeden. Po kolei: nast\u0119pny motyw. Wy\u0142.: nic (naci\u015bni\u0119cie nadal od\u015bwie\u017ca).'"
        },
        {
          "sha": "02fdf23810d05a8509917930ea35f9fa76b84598",
          "filename": "elf/lang/Portuguese.yml",
          "status": "modified",
          "additions": 208,
          "deletions": 22,
          "changes": 230,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FPortuguese.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FPortuguese.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FPortuguese.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: Portuguese\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTER ACEITA, ESC CANCELA. QUALQUER BOT\u00c3O MOSTRA AS TECLAS.\n   USB KEYBOARD: TECLADO USB\n-  '      DESR DVR ISOs (experimental)': '      ISOs DESR DVR (experimental)'\n-  '      Internal HDD (exFAT)': '      disco r\u00edgido interno (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (experimental)'\n-  '      Protocol': '      Protocolo'\n   Access: Acesso\n   Applications Page Start Mode: In\u00edcio da p\u00e1gina Aplica\u00e7\u00f5es\n   Art Delay: Atraso das imagens\n@@ -22,7 +18,6 @@ strings:\n   Fifth: Quinto\n   First: Primeiro\n   Fourth: Quarto\n-  HDD (APA) Start Mode: In\u00edcio do disco r\u00edgido (APA)\n   HDD Cache: Cache do disco r\u00edgido\n   HDD Spindown: Repouso do disco r\u00edgido\n   MMCE Settings: Defini\u00e7\u00f5es do MMCE\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: Este \u00e9 um jogo de PS2 com patch ESR. Coloque o ESR (esr*.elf) em APPS, BOOT ou ESR num cart\u00e3o de mem\u00f3ria ou pen USB para inici\u00e1-lo.\n   '%s, BUILD %d': '%s, BUILD %d'\n   '... and the anonymous ...': '... e os an\u00f4nimos ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': '1.8.0 inclu\u00eddo sem modifica\u00e7\u00e3o (AFL-3.0): github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: Uma desmontagem e reconstru\u00e7\u00e3o do RiptOPL, o fork do Open PS2 Loader,\n   ABOUT: SOBRE\n   'ACHIEVEMENTS: SPIRIT OF RA': 'CONQUISTAS: SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): V\u00eddeo AVI em Ficheiros de mem\u00f3ria, o fork do RIPPS2 (AFL 2.0)\n   Apollo Save Tool by bucanero: Apollo Save Tool de bucanero\n   BACK: VOLTAR\n+  Back (keeps both): Voltar (mant\u00e9m os dois)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: Este cart\u00e3o MMCE n\u00e3o consegue mudar o nome de um ficheiro, algo de que a compress\u00e3o precisa no \u00faltimo passo. Atualize o firmware sd2psXtd para 1.4.0 ou mais recente (sd2psxtd.github.io/download) e tente de novo. Nada foi alterado.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): BIBLE \u00e9 o PS2BBL 1.2.0, as vers\u00f5es assinadas de El Isra (cart\u00e3o e disco r\u00edgido), sem modifica\u00e7\u00e3o (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: Sons da BIOS > Extrair reprodu-los como o SPU2; nenhuma BIOS acompanha\n   BOOT LOCK AND BIBLE: BOOT LOCK E BIBLE\n   BOOT LOCK edits its config too: O BOOT LOCK tamb\u00e9m edita a configura\u00e7\u00e3o dele\n   BUILD DETAILS: DETALHES DO BUILD\n-  BUILT ON RIPTOPL: CONSTRU\u00cdDO SOBRE O RIPTOPL\n   BearSSL by Thomas Pornin: BearSSL de Thomas Pornin\n   Built with Claude (Anthropic): Feito com o Claude (Anthropic)\n   CD PLAYER: PLAYER DE CD\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek e Sleek Bold, desenhadas para o RIPPS2\n   RetroAchievements on the console by hacan359 (yoba): RetroAchievements na consola, de hacan359 (yoba)\n   RiptOPL by NathanNeurotic: RiptOPL de NathanNeurotic\n-  Roboto Condensed by Google (Apache-2.0): Roboto Condensed do Google (Apache-2.0)\n   SOUNDS: SONS\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: o instalador do FMCB de SP193 e o OSDMenu de pcm720, seguidos passo a passo\n   SUPPORT: SUPORTE\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: wLaunchELF R3Z de NathanNeurotic\n   wLaunchELF by the ps2homebrew team: wLaunchELF da equipa ps2homebrew\n   what RIPPS2's card image writes are checked against: com o que as grava\u00e7\u00f5es de imagens de cart\u00e3o do RIPPS2 s\u00e3o verificadas\n-  with its own look, launchers, file browser and save tools.: com visual pr\u00f3prio, launchers, navegador de ficheiros e ferramentas de saves.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: xeRAbora de hacan359, rcheevos de RetroAchievements\n   your console's own, ripped from its BIOS: os da sua pr\u00f3pria consola, extra\u00eddos da BIOS dele\n   ' (no DNS answer)': ' (sem resposta de DNS)'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO: a gravar %.60s'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: O Neutrino n\u00e3o p\u00f4de ser gravado em %s. Verifique o espa\u00e7o livre e se n\u00e3o est\u00e1 protegido contra grava\u00e7\u00e3o, e tente de novo.\n   THE GAME'S DRIVE  (%s/neutrino): A UNIDADE DO JOGO  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : Este jogo inicia pelo Neutrino, e n\u00e3o h\u00e1 Neutrino onde o RIPPS2 procura (Defini\u00e7\u00f5es, a unidade do jogo, os cart\u00f5es de mem\u00f3ria). O RIPPS2 traz o Neutrino 1.8.0 de rickgaiser (AFL-3.0). Instalar agora? Depois voc\u00ea escolhe onde.\n   '%d / %d FILES': '%d / %d FICHEIROS'\n   '%d FOLDERS AND CONTENTS': '%d PASTAS E CONTE\u00daDO'\n   '%d FOLDERS, %d FOUND': '%d PASTAS, %d ENCONTRADOS'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d de %d ainda bloqueadas'\n   '%s   on %s': '%s   em %s'\n   BADGES: EMBLEMAS\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: Os emblemas s\u00f3 funcionam com a TELEMETRIA ligada e o xeRAbora a correr no seu PC; at\u00e9 l\u00e1, n\u00e3o custam nada.\n   CHECK THE DISC: VERIFICAR O DISCO\n   From this game's last CHECK GAME SUPPORT.: Da \u00faltima VERIFICA\u00c7\u00c3O DE SUPORTE deste jogo.\n   HOW IT WORKS: COMO FUNCIONA\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: H\u00e1 um PS2BBL com o bloqueio do RIPPS2 nesta consola.\n   AUDIO folder: pasta AUDIO\n   DRIVES: UNIDADES\n-  INSTALL RIPPS2'S COPY: INSTALAR A C\u00d3PIA DO RIPPS2\n-  'Installed: ': 'Instalado: '\n   MANAGE: GERIR\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: GERIR mostra o que cada cart\u00e3o de mem\u00f3ria inicia, e instala ou repara o BIBLE.\n   MEMORY CARD 0  (about 600 KB): CART\u00c3O DE MEM\u00d3RIA 0  (uns 600 KB)\n   MEMORY CARD 1  (about 600 KB): CART\u00c3O DE MEM\u00d3RIA 1  (uns 600 KB)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO: o driver n\u00e3o carregou (c\u00f3digo %d).'\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO: o driver est\u00e1 carregado, mas nenhum cart\u00e3o SD respondeu (o slot mc1).'\n   NEUTRINO SETTINGS: DEFINI\u00c7\u00d5ES DO NEUTRINO\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': 'Nenhum ainda: o RIPPS2 n\u00e3o tem sons pr\u00f3prios. Extraia os desta consola, ou crie-os no RIPPS2 Audio Studio.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: N\u00e3o instalado. Um jogo que precise dele oferece a c\u00f3pia do RIPPS2, ou instale aqui.\n   POPSTARTER NETWORK: REDE DO POPSTARTER\n   POPSTARTER and Ember: POPSTARTER e Ember\n   PS1 SETTINGS: DEFINI\u00c7\u00d5ES DE PS1\n   PS2BBL 1.2.0 for RIPPS2: PS2BBL 1.2.0 para o RIPPS2\n   RIP THIS CONSOLE'S SOUNDS: EXTRAIR OS SONS DESTA CONSOLA\n-  RIPPS2's homebrew: O homebrew do RIPPS2\n   STATUS: STATUS\n-  'STATUS: looking for RIPPS2''s homebrew': 'STATUS: a procurar o homebrew do RIPPS2'\n   a drive of unknown kind: uma unidade de tipo desconhecido\n-  a drive or a memory card: uma unidade ou um cart\u00e3o de mem\u00f3ria\n   boot loader, card by card: carregador de arranque, cart\u00e3o por cart\u00e3o\n   from its BIOS, into AUDIO: da BIOS dele, para AUDIO\n   internal HDD (exFAT): disco r\u00edgido interno (exFAT)\n@@ -975,10 +958,193 @@ strings:\n   Sidebar: Barra lateral\n   your own copy, found on your drives and memory cards: a sua pr\u00f3pria c\u00f3pia, encontrada nas suas unidades e cart\u00f5es de mem\u00f3ria\n   'your own, found and launched: github.com/Gageformer/Ember/releases': 'o seu, encontrado e iniciado: github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': 'N\u00e3o encontrado em mass0: (%s). Coloque ember.elf e o seu bios.bin na pasta EMBER dela.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: N\u00e3o encontrado. Coloque o seu pr\u00f3prio POPSTARTER.ELF na pasta POPS de uma unidade.\n   Launch Disc: Iniciar disco\n   Memory Files: Ficheiros de mem\u00f3ria\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: O RIPPS2 encontrou %d dos 5. Cada um fica memorizado, e cada lan\u00e7amento l\u00ea-o a partir da\u00ed.\n+  not found on the started drives: n\u00e3o encontrado nas unidades iniciadas\n+  not looked for yet: ainda n\u00e3o procurado\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s n\u00e3o respondeu, por isso %s n\u00e3o pode arrancar. Est\u00e1 ligado?'\n+  '%s is not on %s any more, so it has left All Games.': '%s j\u00e1 n\u00e3o est\u00e1 em %s, por isso saiu de Todos os jogos.'\n+  STARTING %s: A INICIAR %s\n+  the SMB share: a partilha SMB\n+  the drive: a unidade\n+  the internal HDD (APA): o disco r\u00edgido interno (APA)\n+  the internal HDD (exFAT): o disco r\u00edgido interno (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: CONSTRU\u00cdDO SOBRE O RIPTOPL, COM O NEUTRINO\n+  'Special thanks to all the YouTube love as well:': 'Um agradecimento especial tamb\u00e9m a todo o carinho do YouTube:'\n+  and its own look, launchers, file browser and save tools.: e com aspeto, lan\u00e7adores, explorador de ficheiros e ferramentas de grava\u00e7\u00e3o pr\u00f3prios.\n+  glad to help however you reach me.: feliz por ajudar, seja como for que me contactem.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): o segundo n\u00facleo de jogo, 1.8.0 inclu\u00eddo sem altera\u00e7\u00f5es (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: com o Neutrino de rickgaiser l\u00e1 dentro como segundo n\u00facleo de jogo,\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : 'Este jogo arranca atrav\u00e9s do Neutrino, e o RIPPS2 n\u00e3o encontrou nenhum: nem no caminho das Defini\u00e7\u00f5es, nem na unidade do jogo, nem nos cart\u00f5es de mem\u00f3ria, nem em nenhuma unidade iniciada. O RIPPS2 traz o Neutrino 1.8.0 de rickgaiser (AFL-3.0): instale-o uma vez, onde escolher, e cada jogo Neutrino usa-o a partir da\u00ed.'\n+  STARTING THE DRIVES: A INICIAR AS UNIDADES\n+  '%d games, on the started drives': '%d jogos, nas unidades iniciadas'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s: ALGUNS FICHEIROS N\u00c3O PUDERAM SER APAGADOS'\n+  '%u partitions, all in order': '%u parti\u00e7\u00f5es, todas em ordem'\n+  ', %d art images': ', %d imagens de capa'\n+  ', and ': ', e '\n+  ', its cheats': ', os seus c\u00f3digos de batota'\n+  ', its settings': ', as suas defini\u00e7\u00f5es'\n+  1 game, on the started drives: 1 jogo, nas unidades iniciadas\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: Um jogo de PS1 aqui apaga-se em Ficheiros de mem\u00f3ria, onde \u00e9 um ficheiro pr\u00f3prio.\n+  A network catalogue's games are deleted on the PC that serves them.: Os jogos de um cat\u00e1logo de rede apagam-se no PC que os serve.\n+  DELETE FOR GOOD: APAGAR DE VEZ\n+  DELETE IT: APAG\u00c1-LO\n+  'DELETED %s: %s FREED': '%s APAGADO: %s LIBERTADOS'\n+  'Delete %s from %s? Goes: %s.': 'Apagar %s de %s? Sai: %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': 'Apagar %s de %s? Sai: %s. Fica: %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: Apagar tamb\u00e9m as suas grava\u00e7\u00f5es e imagens RIPSHOT? N\u00e3o mant\u00e9m-nas.\n+  HIDDEN GAMES: JOGOS OCULTOS\n+  'HIDDEN: %s': 'OCULTO: %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: Ocultar %s? Sai de todas as listas, tamb\u00e9m de Todos os jogos e Favoritos. Defini\u00e7\u00f5es > Fontes de jogos > Jogos ocultos volta a mostr\u00e1-lo.\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: \u00daltima verifica\u00e7\u00e3o. %s \u00e9 apagado de vez, e nada o pode trazer de volta. Prima START para o apagar.\n+  None. Hide Game, in a game's options, puts one here.: Nenhum. Ocultar jogo, nas op\u00e7\u00f5es de um jogo, coloca um aqui.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : 'Nada foi apagado: a tabela de parti\u00e7\u00f5es desta unidade n\u00e3o corresponde a este disco (%s). Pode ter sido escrita a partir da imagem de um disco de outro tamanho. Repare-a primeiro num PC.'\n+  RIPPS2 cannot delete a game from this page.: O RIPPS2 n\u00e3o pode apagar um jogo a partir desta p\u00e1gina.\n+  RIPPS2 could not find this game's files on its drive.: O RIPPS2 n\u00e3o encontrou os ficheiros deste jogo na sua unidade.\n+  Reading the hidden lists: A ler as listas ocultas\n+  SHOW AGAIN OR DELETE: MOSTRAR DE NOVO OU APAGAR\n+  SHOW IT AGAIN: MOSTR\u00c1-LO DE NOVO\n+  That game is not on its drive any more.: Esse jogo j\u00e1 n\u00e3o est\u00e1 na sua unidade.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: A lista oculta n\u00e3o p\u00f4de ser escrita na unidade do jogo. Est\u00e1 protegida contra escrita, ou as Op\u00e7\u00f5es de Escritas est\u00e3o desligadas?\n+  The partition could not be removed (%d). Nothing was deleted.: A parti\u00e7\u00e3o n\u00e3o p\u00f4de ser removida (%d). Nada foi apagado.\n+  This drive cannot keep a hidden list (a network catalogue).: Esta unidade n\u00e3o pode guardar uma lista oculta (um cat\u00e1logo de rede).\n+  This game's drive has not answered yet. Wait for it, then try again.: A unidade deste jogo ainda n\u00e3o respondeu. Espere por ela e tente de novo.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: A parti\u00e7\u00e3o deste jogo \u00e9 uma que o RIPPS2 n\u00e3o remove (uma parti\u00e7\u00e3o do sistema, ou aquela onde est\u00e3o as suas defini\u00e7\u00f5es).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': 'uma parti\u00e7\u00e3o \u00e9 maior do que este disco permite: a tabela foi feita para um disco de outro tamanho'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': 'uma parti\u00e7\u00e3o passa do fim deste disco (setor %u de %u): a tabela foi feita para um disco maior'\n+  its RIPSHOT pictures: as suas imagens RIPSHOT\n+  its drive: a sua unidade\n+  its saves: as suas grava\u00e7\u00f5es\n+  sector %u holds no partition header: o setor %u n\u00e3o tem cabe\u00e7alho de parti\u00e7\u00e3o\n+  the chain does not close at __mbr: a cadeia n\u00e3o fecha em __mbr\n+  the chain skips from sector %u: a cadeia salta a partir do setor %u\n+  the chain's links disagree at sector %u: as liga\u00e7\u00f5es da cadeia n\u00e3o coincidem no setor %u\n+  the drive's size could not be read: n\u00e3o foi poss\u00edvel ler o tamanho da unidade\n+  the game (%s): o jogo (%s)\n+  the game's folder and everything in it (%s): a pasta do jogo e tudo o que cont\u00e9m (%s)\n+  the header at sector %u could not be read: n\u00e3o foi poss\u00edvel ler o cabe\u00e7alho no setor %u\n+  the header at sector %u fails its checksum: o cabe\u00e7alho no setor %u falha a soma de verifica\u00e7\u00e3o\n+  the partition at sector %u is not where or as large as APA puts one: a parti\u00e7\u00e3o no setor %u n\u00e3o est\u00e1 onde, ou com o tamanho com que, o APA cria uma\n+  the settings and art another copy of it uses: as defini\u00e7\u00f5es e capas que outra c\u00f3pia dele usa\n+  '%d FOLDERS': '%d PASTAS'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': '%s n\u00e3o p\u00f4de ser copiado para %s. A unidade est\u00e1 cheia ou protegida contra escrita?'\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s n\u00e3o est\u00e1 onde o RIPPS2 procura. Procur\u00e1-lo nas unidades que o RIPPS2 iniciou? O s\u00edtio onde for encontrado fica memorizado para cada lan\u00e7amento seguinte.'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : 'O Ember n\u00e3o est\u00e1 em %s, junto aos jogos, e tem de estar. O RIPPS2 encontrou-o em %s: START copia-o para l\u00e1 (%d KB) e inicia o jogo.'\n+  Ember's BIOS: BIOS do Ember\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : 'A BIOS do Ember (bios.bin) n\u00e3o est\u00e1 em %s, junto ao Ember. O RIPPS2 encontrou uma em %s: START copia-a para l\u00e1 (%d KB) e inicia o jogo.'\n+  LOOKING FOR YOUR LAUNCHERS: \u00c0 PROCURA DOS SEUS LAN\u00c7ADORES\n+  The POPS files: Os ficheiros POPS\n+  '%s  MMCE card': '%s  cart\u00e3o MMCE'\n+  INSTALL RIPPS2'S NEUTRINO: INSTALAR O NEUTRINO DO RIPPS2\n+  'Internal HDD (APA): %s free of %s': 'Disco r\u00edgido interno (APA): %s livres de %s'\n+  LAUNCHERS: LAN\u00c7ADORES\n+  LOOK FOR THEM: PROCUR\u00c1-LOS\n+  'Memory card %d: %d KB free': 'Cart\u00e3o de mem\u00f3ria %d: %d KB livres'\n+  POPS files: Ficheiros POPS\n+  START EVERY DRIVE: INICIAR TODAS AS UNIDADES\n+  'STATUS: looking at the drives and the homebrew': 'STATUS: a ver as unidades e o homebrew'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): Iniciar primeiro todas as unidades, para a procura chegar a todas? (N\u00e3o procura nas unidades j\u00e1 iniciadas.)\n+  USB, HDD, MMCE and the rest: USB, disco r\u00edgido, MMCE e o resto\n+  every started drive: cada unidade iniciada\n+  found where you keep them: encontrados onde os guarda\n+  not found yet: ainda n\u00e3o encontrado\n+  not on a started drive: n\u00e3o est\u00e1 numa unidade iniciada\n+  only if you have none: s\u00f3 se n\u00e3o tiver nenhum\n+  starts every drive first: inicia primeiro todas as unidades\n+  Drives, launchers and homebrew: Unidades, lan\u00e7adores e homebrew\n+  BDM Devices: Dispositivos BDM\n+  Color Sweep: Transi\u00e7\u00e3o de cores\n+  Colors and Text: Cores e texto\n+  HDD POPS partitions: Parti\u00e7\u00f5es POPS do disco r\u00edgido\n+  Layout: Disposi\u00e7\u00e3o\n+  Missing Covers: Capas em falta\n+  Name: Nome\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: Ainda n\u00e3o foi encontrada uma configura\u00e7\u00e3o SMB do POPSTARTER. Preencha as linhas abaixo.\n+  POPSTARTER Network: Rede do POPSTARTER\n+  POPSTARTER SMB Network: Rede SMB do POPSTARTER\n+  POPSTARTER UDPFS Network: Rede UDPFS do POPSTARTER\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: O RIPPS2 junta ele pr\u00f3prio a unidade, a ISO, os VMC e o modo de v\u00eddeo.\n+  Screen: Ecr\u00e3\n+  Speed: Velocidade\n+  Sweep Colors: Cores da transi\u00e7\u00e3o\n+  Text Weight: Espessura do texto\n+  Theme Color: Cor principal\n+  Audio: \u00c1udio\n+  Bold: Negrito\n+  Controllers: Comandos\n+  Lively: Vivo\n+  Medium: M\u00e9dio\n+  PS Emulation: Emula\u00e7\u00e3o PS\n+  Regular: Normal\n+  Slow: Lento\n+  A MOMENT: UM MOMENTO\n+  A TASK IN THE BACKGROUND: UMA TAREFA EM SEGUNDO PLANO\n+  ABOUT %d S: CERCA DE %d S\n+  ANY MOMENT: A QUALQUER MOMENTO\n+  READING THE GAME LIST OF %s: A LER A LISTA DE JOGOS DE %s\n+  SAVING A LIST: A GUARDAR UMA LISTA\n+  START NOW: INICIAR AGORA\n+  STARTING: A INICIAR\n+  STARTS NEXT: INICIA A SEGUIR\n+  STOPPING IT SAFELY: A PARAR EM SEGURAN\u00c7A\n+  THE LAST OF THE QUEUE: O FIM DA FILA\n+  WHEN IT FINISHES: QUANDO TERMINAR\n+  What each card starts; install or repair BIBLE.: O que cada cart\u00e3o inicia; instale ou repare o BIBLE.\n+  Rainbow: Arco-\u00edris\n+  Custom: Personalizado\n+  DESR DVR ISOs (experimental): ISOs DESR DVR (experimental)\n+  HDD Start Mode: In\u00edcio do disco r\u00edgido\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): Roboto Condensed do The Roboto Project Authors (SIL OFL 1.1)\n+  '%s on %s': '%s em %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: %s passou a %s (%d%%), cada peda\u00e7o verificado. Remover o ISO e manter o ZSO?'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: %s voltou a ser %s, cada peda\u00e7o verificado. Remover o ZSO e manter o ISO?'\n+  A ZSO of this game is already beside it. Remove one of the two first.: J\u00e1 existe um ZSO deste jogo ao lado. Remova primeiro um dos dois.\n+  About %d min, then checked: Cerca de %d min, depois verificado\n+  An ISO of this game is already beside it. Remove one of the two first.: J\u00e1 existe um ISO deste jogo ao lado. Remova primeiro um dos dois.\n+  CHECKING EVERY PIECE: A VERIFICAR CADA PEDA\u00c7O\n+  COMPRESS TO ZSO: COMPRIMIR PARA ZSO\n+  'COMPRESSED %s: %s FREED': '%s COMPRIMIDO: %s LIBERTADOS'\n+  COMPRESSING 1 OF 2: A COMPRIMIR 1 DE 2\n+  COMPRESSING 2 OF 2: A COMPRIMIR 2 DE 2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : Comprimir %s? O ISO fica at\u00e9 o ZSO ser lido de novo e verificado; depois o RIPPS2 pergunta antes de remover o ISO. A unidade precisa de espa\u00e7o para ambos enquanto trabalha; deixe a consola ligada at\u00e9 terminar. Um ZSO s\u00f3 arranca com o n\u00facleo OPL (n\u00e3o Neutrino); os seus saves, defini\u00e7\u00f5es, imagens e cheats ficam com ele, e pode voltar a ser ISO a qualquer momento.\n+  Compress to ZSO: Comprimir para ZSO\n+  Cycle: Rota\u00e7\u00e3o\n+  'Experimental: OPL core only (not Neutrino)': 'Experimental: s\u00f3 o n\u00facleo OPL (n\u00e3o Neutrino)'\n+  Hold SELECT: Manter SELECT\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: N\u00e3o foi poss\u00edvel terminar (%s). O que escreveu foi removido; o jogo est\u00e1 como estava.\n+  List: Lista\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : Tornar %s um ISO de novo? O ZSO fica at\u00e9 o ISO ser lido de novo e verificado; depois o RIPPS2 pergunta antes de remover o ZSO. A unidade precisa de espa\u00e7o para ambos enquanto trabalha; deixe a consola ligada at\u00e9 terminar. Os seus saves, defini\u00e7\u00f5es, imagens e cheats ficam com ele.\n+  Stop? What it has written so far is removed; the game stays as it was.: Parar? O que foi escrito at\u00e9 agora \u00e9 removido; o jogo fica como estava.\n+  THEMES: TEMAS\n+  There is not enough memory for this just now.: N\u00e3o h\u00e1 mem\u00f3ria suficiente para isto agora.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: O RIPPS2 n\u00e3o consegue comprimir nem descomprimir esta imagem (o tamanho n\u00e3o \u00e9 em blocos inteiros de 2 KB).\n+  UNCOMPRESS TO ISO: DESCOMPRIMIR PARA ISO\n+  UNCOMPRESSED %s: '%s DESCOMPRIMIDO'\n+  UNCOMPRESSING: A DESCOMPRIMIR\n+  Uncompress to ISO: Descomprimir para ISO\n+  Under a minute, then checked: Menos de um minuto, depois verificado\n+  COPYING: A COPIAR\n+  SHOW AGAIN, DELETE OR COMPRESS: MOSTRAR DE NOVO, APAGAR OU COMPRIMIR\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: MOSTRAR DE NOVO, APAGAR OU DESCOMPRIMIR\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': '%s precisa de cerca de %s livres na sua unidade enquanto trabalha (o original fica at\u00e9 ao fim), e a unidade tem %s livres.'\n+  Hide and Compress: Ocultar e comprimir\n+  'COMPRESSED AND HIDDEN: %s': 'COMPRIMIDO E OCULTO: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': 'N\u00e3o h\u00e1 espa\u00e7o para esta c\u00f3pia: precisa de cerca de %s, e a unidade tem %s livres.'\n+  OVER 4 GB FOR FAT32: MAIS DE 4 GB PARA FAT32\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: Esta unidade \u00e9 %s, que n\u00e3o guarda ficheiros de 4 GB ou mais, e a nova imagem teria %s.\n+  '%s, %s clusters, %s free of %s': '%s, clusters de %s, %s livres de %s'\n+  Delete This Sweep: Apagar esta transi\u00e7\u00e3o\n+  Delete %s? RIPPS2's own sweeps stay as they are.: Apagar %s? As transi\u00e7\u00f5es do pr\u00f3prio RIPPS2 ficam como est\u00e3o.\n+  Sweep: Transi\u00e7\u00e3o\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: Os emblemas marcam os jogos com conquistas nas suas listas, com quantas j\u00e1 desbloqueou. Precisam da TELEMETRIA ligada.\n labels:\n   MANUAL: Manual\n   ENTER: Entrar\n@@ -1204,7 +1370,7 @@ labels:\n   SAVE_ICONS: \u00cdcones de save em 3D\n   HINT_SAVE_ICONS: Mostra o \u00edcone de save do jogo escolhido, girando, onde o tema tem um lugar para ele. L\u00ea primeiro a VMC do jogo, depois os cart\u00f5es de mem\u00f3ria.\n   NETBOOT_RESTART_NOW: O protocolo de rede muda depois de reiniciar. Guardar as defini\u00e7\u00f5es e reiniciar o OPL agora?\n-  HINT_NET_PROTOCOL: 'Qual servi\u00e7o de rede o RiptOPL usa: um compartilhamento SMB, UDPFS, UDPBD ou HTTP. O mesmo ajuste de Fontes de jogos. Sair de um que j\u00e1 est\u00e1 a correr precisa reiniciar.'\n+  HINT_NET_PROTOCOL: 'Qual servi\u00e7o de rede o RiptOPL usa: um compartilhamento SMB, UDPFS, UDPBD ou HTTP. Sair de um que j\u00e1 est\u00e1 a correr precisa reiniciar.'\n   HINT_UDPFS_ACCESS: S\u00f3 UDPFS. Ficheiros l\u00ea ISOs soltas da pasta de jogos do servidor; IMG l\u00ea uma imagem de disco como unidade. SMB e HTTP sempre usam Ficheiros, UDPBD sempre IMG.\n   HINT_SMB_VERSION: Qual vers\u00e3o do SMB usar com o compartilhamento do PC.\n   HINT_NET_ADVANCED: Libera a edi\u00e7\u00e3o do modo de link Ethernet e da porta SMB.\n@@ -1403,3 +1569,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: Como as fotos \u00e0 espera no rolo de cada jogo se tornam PNG.\n   RIPPS2_SETTINGS_HOME: Guardar defini\u00e7\u00f5es em\n   HINT_RIPPS2_SETTINGS_HOME: 'Onde o RIPPS2 guarda as defini\u00e7\u00f5es: o seu save num cart\u00e3o de mem\u00f3ria, uma pasta RIPPS2 numa unidade (o disco r\u00edgido interno, USB, MX4SIO, iLink ou MMCE) ou uma pasta que escreves.'\n+  RIPPS2_LAUNCHERS: Lan\u00e7adores\n+  RIPPS2_LOOK_NOW: Procurar agora\n+  HINT_RIPPS2_LOOK_NOW: O RIPPS2 procura nas unidades iniciadas o POPSTARTER, os ficheiros POPS, o Ember, a BIOS do Ember e o Neutrino, e memoriza onde est\u00e1 cada um. Cada lan\u00e7amento l\u00ea-os a partir da\u00ed.\n+  RIPPS2_HIDE_GAME: Ocultar jogo\n+  RIPPS2_HIDDEN_GAMES: Jogos ocultos\n+  HINT_RIPPS2_HIDDEN_GAMES: Os jogos ocultos com Ocultar jogo, nas unidades iniciadas. Volte a mostrar um, ou apague-o. Cada unidade guarda a sua pr\u00f3pria lista.\n+  HINT_RIPPS2_BDM_DEVICES: 'As unidades de onde o BDM l\u00ea jogos: USB, MX4SIO, um PC Card CF, a unidade DVR de um DESR e iLink. O disco r\u00edgido interno segue o in\u00edcio do disco r\u00edgido.'\n+  HINT_RIPPS2_SCREEN: 'A imagem do menu: modo de v\u00eddeo, ecr\u00e3 panor\u00e2mico, o seu lugar na televis\u00e3o (H-POS, V-POS) e overscan.'\n+  HINT_RIPPS2_LAYOUT: Onde ficam as categorias e como uma p\u00e1gina entra, depois as capas do RIPFLOW e a grelha do RIPGRID.\n+  HINT_RIPPS2_COLORS_TEXT: Espessura do texto, a cor do tema, a transi\u00e7\u00e3o de cores e cada cor.\n+  HINT_RIPPS2_TEXT_WEIGHT: 'Qu\u00e3o espesso \u00e9 o texto do pr\u00f3prio RIPPS2: as dicas das defini\u00e7\u00f5es, STATUS e Acerca. Normal \u00e9 como antes; M\u00e9dio e Negrito leem-se melhor numa imagem suave.'\n+  HINT_RIPPS2_HDD_START: 'O disco r\u00edgido interno, APA ou exFAT: o RIPPS2 l\u00ea-o, seja qual for, e mostra-o uma vez, como jogos do disco r\u00edgido. Desligado deixa-o em paz.'\n+  HINT_RIPPS2_THEME_COLOR: A cor do pr\u00f3prio tema. Escolha Personalizado como cor do tema para a escolher voc\u00ea, como as cores do texto.\n+  HINT_RIPPS2_SWEEP: Cinco cores por que o tema passa, devagar e suavemente. Arco-\u00edris, uma transi\u00e7\u00e3o feita de cada cor de tema, ou a sua. Desligado mant\u00e9m o tema parado.\n+  HINT_RIPPS2_SWEEP_COLORS: As cinco cores da transi\u00e7\u00e3o e a sua velocidade. Altere uma das do RIPPS2 e fica guardada como sua, com o nome que lhe der.\n+  HINT_RIPPS2_SWEEP_NAME: Como a sua transi\u00e7\u00e3o se chama na lista Transi\u00e7\u00e3o de cores.\n+  HINT_RIPPS2_SWEEP_COLOR: Uma das cinco cores por que a transi\u00e7\u00e3o passa, por ordem.\n+  HINT_RIPPS2_SWEEP_SPEED: 'Quanto dura cada cor antes de a seguinte entrar. Mesmo Vivo continua suave: as cores nunca piscam.'\n+  RIPPS2_HIDE_COMPRESS: Ocultar jogo e compress\u00e3o\n+  HINT_RIPPS2_SELECT_HOLD: 'SELECT mantido 4.2 s numa lista de jogos. Lista: os temas num painel, escolha um. Rota\u00e7\u00e3o: o tema seguinte. Desligado: nada (premir continua a atualizar).'"
        },
        {
          "sha": "430d79c051c7b2c78867dda56b5d8a212b4a69eb",
          "filename": "elf/lang/Portuguese_BR.yml",
          "status": "modified",
          "additions": 207,
          "deletions": 22,
          "changes": 229,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FPortuguese_BR.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FPortuguese_BR.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FPortuguese_BR.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: Portuguese_BR\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTER ACEITA, ESC CANCELA. QUALQUER BOT\u00c3O MOSTRA AS TECLAS.\n   USB KEYBOARD: TECLADO USB\n-  '      DESR DVR ISOs (experimental)': '      ISOs DESR DVR (experimental)'\n-  '      Internal HDD (exFAT)': '      HD interno (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (experimental)'\n-  '      Protocol': '      Protocolo'\n   Access: Acesso\n   Applications Page Start Mode: In\u00edcio da p\u00e1gina Aplicativos\n   Art Delay: Atraso das imagens\n@@ -22,7 +18,6 @@ strings:\n   Fifth: Quinto\n   First: Primeiro\n   Fourth: Quarto\n-  HDD (APA) Start Mode: In\u00edcio do HD (APA)\n   HDD Cache: Cache do HD\n   HDD Spindown: Repouso do HD\n   MMCE Settings: Configura\u00e7\u00f5es do MMCE\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: Este \u00e9 um jogo de PS2 com patch ESR. Coloque o ESR (esr*.elf) em APPS, BOOT ou ESR num cart\u00e3o de mem\u00f3ria ou pendrive para inici\u00e1-lo.\n   '%s, BUILD %d': '%s, BUILD %d'\n   '... and the anonymous ...': '... e os an\u00f4nimos ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': '1.8.0 inclu\u00eddo sem modifica\u00e7\u00e3o (AFL-3.0): github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: Uma desmontagem e reconstru\u00e7\u00e3o do RiptOPL, o fork do Open PS2 Loader,\n   ABOUT: SOBRE\n   'ACHIEVEMENTS: SPIRIT OF RA': 'CONQUISTAS: SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): V\u00eddeo AVI em Arquivos de mem\u00f3ria, o fork do RIPPS2 (AFL 2.0)\n   Apollo Save Tool by bucanero: Apollo Save Tool de bucanero\n   BACK: VOLTAR\n+  Back (keeps both): Voltar (mant\u00e9m os dois)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: Este cart\u00e3o MMCE n\u00e3o consegue renomear um arquivo, algo de que a compress\u00e3o precisa na \u00faltima etapa. Atualize o firmware sd2psXtd para 1.4.0 ou mais recente (sd2psxtd.github.io/download) e tente de novo. Nada foi alterado.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): BIBLE \u00e9 o PS2BBL 1.2.0, as vers\u00f5es assinadas de El Isra (cart\u00e3o e HD), sem modifica\u00e7\u00e3o (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: Sons da BIOS > Extrair os toca como o SPU2; nenhuma BIOS acompanha\n   BOOT LOCK AND BIBLE: BOOT LOCK E BIBLE\n   BOOT LOCK edits its config too: O BOOT LOCK tamb\u00e9m edita a configura\u00e7\u00e3o dele\n   BUILD DETAILS: DETALHES DO BUILD\n-  BUILT ON RIPTOPL: CONSTRU\u00cdDO SOBRE O RIPTOPL\n   BearSSL by Thomas Pornin: BearSSL de Thomas Pornin\n   Built with Claude (Anthropic): Feito com o Claude (Anthropic)\n   CD PLAYER: PLAYER DE CD\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek e Sleek Bold, desenhadas para o RIPPS2\n   RetroAchievements on the console by hacan359 (yoba): RetroAchievements no console, de hacan359 (yoba)\n   RiptOPL by NathanNeurotic: RiptOPL de NathanNeurotic\n-  Roboto Condensed by Google (Apache-2.0): Roboto Condensed do Google (Apache-2.0)\n   SOUNDS: SONS\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: o instalador do FMCB de SP193 e o OSDMenu de pcm720, seguidos passo a passo\n   SUPPORT: SUPORTE\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: wLaunchELF R3Z de NathanNeurotic\n   wLaunchELF by the ps2homebrew team: wLaunchELF da equipe ps2homebrew\n   what RIPPS2's card image writes are checked against: com o que as grava\u00e7\u00f5es de imagens de cart\u00e3o do RIPPS2 s\u00e3o conferidas\n-  with its own look, launchers, file browser and save tools.: com visual pr\u00f3prio, launchers, navegador de arquivos e ferramentas de saves.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: xeRAbora de hacan359, rcheevos de RetroAchievements\n   your console's own, ripped from its BIOS: os do seu pr\u00f3prio console, extra\u00eddos da BIOS dele\n   ' (no DNS answer)': ' (sem resposta de DNS)'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO: gravando %.60s'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: O Neutrino n\u00e3o p\u00f4de ser gravado em %s. Confira o espa\u00e7o livre e se n\u00e3o est\u00e1 protegido contra grava\u00e7\u00e3o, e tente de novo.\n   THE GAME'S DRIVE  (%s/neutrino): A UNIDADE DO JOGO  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : Este jogo inicia pelo Neutrino, e n\u00e3o h\u00e1 Neutrino onde o RIPPS2 procura (Configura\u00e7\u00f5es, a unidade do jogo, os cart\u00f5es de mem\u00f3ria). O RIPPS2 traz o Neutrino 1.8.0 de rickgaiser (AFL-3.0). Instalar agora? Depois voc\u00ea escolhe onde.\n   '%d / %d FILES': '%d / %d ARQUIVOS'\n   '%d FOLDERS AND CONTENTS': '%d PASTAS E CONTE\u00daDO'\n   '%d FOLDERS, %d FOUND': '%d PASTAS, %d ENCONTRADOS'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d de %d ainda bloqueadas'\n   '%s   on %s': '%s   em %s'\n   BADGES: EMBLEMAS\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: Os emblemas s\u00f3 funcionam com a TELEMETRIA ligada e o xeRAbora rodando no seu PC; at\u00e9 l\u00e1, n\u00e3o custam nada.\n   CHECK THE DISC: VERIFICAR O DISCO\n   From this game's last CHECK GAME SUPPORT.: Da \u00faltima VERIFICA\u00c7\u00c3O DE SUPORTE deste jogo.\n   HOW IT WORKS: COMO FUNCIONA\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: H\u00e1 um PS2BBL com a trava do RIPPS2 neste console.\n   AUDIO folder: pasta AUDIO\n   DRIVES: UNIDADES\n-  INSTALL RIPPS2'S COPY: INSTALAR A C\u00d3PIA DO RIPPS2\n-  'Installed: ': 'Instalado: '\n   MANAGE: GERENCIAR\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: GERENCIAR mostra o que cada cart\u00e3o de mem\u00f3ria inicia, e instala ou repara o BIBLE.\n   MEMORY CARD 0  (about 600 KB): CART\u00c3O DE MEM\u00d3RIA 0  (uns 600 KB)\n   MEMORY CARD 1  (about 600 KB): CART\u00c3O DE MEM\u00d3RIA 1  (uns 600 KB)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO: o driver n\u00e3o carregou (c\u00f3digo %d).'\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO: o driver est\u00e1 carregado, mas nenhum cart\u00e3o SD respondeu (o slot mc1).'\n   NEUTRINO SETTINGS: CONFIGURA\u00c7\u00d5ES DO NEUTRINO\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': 'Nenhum ainda: o RIPPS2 n\u00e3o tem sons pr\u00f3prios. Extraia os deste console, ou crie-os no RIPPS2 Audio Studio.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: N\u00e3o instalado. Um jogo que precise dele oferece a c\u00f3pia do RIPPS2, ou instale aqui.\n   POPSTARTER NETWORK: REDE DO POPSTARTER\n   POPSTARTER and Ember: POPSTARTER e Ember\n   PS1 SETTINGS: CONFIGURA\u00c7\u00d5ES DE PS1\n   PS2BBL 1.2.0 for RIPPS2: PS2BBL 1.2.0 para o RIPPS2\n   RIP THIS CONSOLE'S SOUNDS: EXTRAIR OS SONS DESTE CONSOLE\n-  RIPPS2's homebrew: O homebrew do RIPPS2\n   STATUS: STATUS\n-  'STATUS: looking for RIPPS2''s homebrew': 'STATUS: procurando o homebrew do RIPPS2'\n   a drive of unknown kind: uma unidade de tipo desconhecido\n-  a drive or a memory card: uma unidade ou um cart\u00e3o de mem\u00f3ria\n   boot loader, card by card: carregador de boot, cart\u00e3o por cart\u00e3o\n   from its BIOS, into AUDIO: da BIOS dele, para AUDIO\n   internal HDD (exFAT): HD interno (exFAT)\n@@ -975,10 +958,192 @@ strings:\n   Sidebar: Barra lateral\n   your own copy, found on your drives and memory cards: sua pr\u00f3pria c\u00f3pia, encontrada nos seus drives e memory cards\n   'your own, found and launched: github.com/Gageformer/Ember/releases': 'o seu, encontrado e iniciado: github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': 'N\u00e3o encontrado em mass0: (%s). Coloque ember.elf e seu bios.bin na pasta EMBER dele.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: N\u00e3o encontrado. Coloque seu pr\u00f3prio POPSTARTER.ELF na pasta POPS de um drive.\n   Launch Disc: Iniciar disco\n   Memory Files: Arquivos de mem\u00f3ria\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: O RIPPS2 encontrou %d dos 5. Cada um fica memorizado, e cada inicializa\u00e7\u00e3o o l\u00ea de l\u00e1.\n+  not found on the started drives: n\u00e3o encontrado nas unidades iniciadas\n+  not looked for yet: ainda n\u00e3o procurado\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s n\u00e3o respondeu, ent\u00e3o %s n\u00e3o pode iniciar. Est\u00e1 conectado?'\n+  '%s is not on %s any more, so it has left All Games.': '%s n\u00e3o est\u00e1 mais em %s, ent\u00e3o saiu de Todos os jogos.'\n+  STARTING %s: INICIANDO %s\n+  the SMB share: o compartilhamento SMB\n+  the drive: a unidade\n+  the internal HDD (APA): o HD interno (APA)\n+  the internal HDD (exFAT): o HD interno (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: FEITO SOBRE O RIPTOPL, COM O NEUTRINO\n+  'Special thanks to all the YouTube love as well:': 'Agradecimento especial tamb\u00e9m a todo o carinho do YouTube:'\n+  and its own look, launchers, file browser and save tools.: e com visual, inicializadores, navegador de arquivos e ferramentas de save pr\u00f3prios.\n+  glad to help however you reach me.: feliz em ajudar, n\u00e3o importa como voc\u00ea me encontre.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): o segundo n\u00facleo de jogo, 1.8.0 inclu\u00eddo sem modifica\u00e7\u00f5es (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: com o Neutrino de rickgaiser dentro como seu segundo n\u00facleo de jogo,\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : 'Este jogo inicia pelo Neutrino, e o RIPPS2 n\u00e3o encontrou nenhum: nem no caminho das Configura\u00e7\u00f5es, nem na unidade do jogo, nem nos cart\u00f5es de mem\u00f3ria, nem em nenhuma unidade iniciada. O RIPPS2 traz o Neutrino 1.8.0 de rickgaiser (AFL-3.0): instale-o uma vez, onde quiser, e todo jogo do Neutrino o usa de l\u00e1.'\n+  STARTING THE DRIVES: INICIANDO AS UNIDADES\n+  '%d games, on the started drives': '%d jogos, nas unidades iniciadas'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s: ALGUNS ARQUIVOS N\u00c3O PUDERAM SER APAGADOS'\n+  '%u partitions, all in order': '%u parti\u00e7\u00f5es, todas em ordem'\n+  ', %d art images': ', %d imagens de capa'\n+  ', and ': ', e '\n+  ', its cheats': ', seus cheats'\n+  ', its settings': ', suas configura\u00e7\u00f5es'\n+  1 game, on the started drives: 1 jogo, nas unidades iniciadas\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: Um jogo de PS1 aqui \u00e9 apagado em Arquivos de mem\u00f3ria, onde ele \u00e9 um arquivo pr\u00f3prio.\n+  A network catalogue's games are deleted on the PC that serves them.: Os jogos de um cat\u00e1logo de rede s\u00e3o apagados no PC que os serve.\n+  DELETE FOR GOOD: APAGAR DE VEZ\n+  DELETE IT: APAGAR\n+  'DELETED %s: %s FREED': '%s APAGADO: %s LIBERADOS'\n+  'Delete %s from %s? Goes: %s.': 'Apagar %s de %s? Sai: %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': 'Apagar %s de %s? Sai: %s. Fica: %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: Apagar tamb\u00e9m os saves e as imagens RIPSHOT dele? N\u00e3o os mant\u00e9m.\n+  HIDDEN GAMES: JOGOS OCULTOS\n+  'HIDDEN: %s': 'OCULTO: %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: Ocultar %s? Ele sai de todas as listas, tamb\u00e9m de Todos os jogos e Favoritos. Configura\u00e7\u00f5es > Fontes de jogos > Jogos ocultos mostra ele de novo.\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: \u00daltima verifica\u00e7\u00e3o. %s ser\u00e1 apagado de vez, e nada poder\u00e1 traz\u00ea-lo de volta. Aperte START para apag\u00e1-lo.\n+  None. Hide Game, in a game's options, puts one here.: Nenhum. Ocultar jogo, nas op\u00e7\u00f5es de um jogo, coloca um aqui.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : 'Nada foi apagado: a tabela de parti\u00e7\u00f5es desta unidade n\u00e3o bate com este disco (%s). Ela pode ter sido gravada a partir da imagem de um disco de outro tamanho. Corrija-a primeiro num PC.'\n+  RIPPS2 cannot delete a game from this page.: O RIPPS2 n\u00e3o pode apagar um jogo a partir desta p\u00e1gina.\n+  RIPPS2 could not find this game's files on its drive.: O RIPPS2 n\u00e3o encontrou os arquivos deste jogo na unidade dele.\n+  Reading the hidden lists: Lendo as listas ocultas\n+  SHOW AGAIN OR DELETE: MOSTRAR DE NOVO OU APAGAR\n+  SHOW IT AGAIN: MOSTRAR DE NOVO\n+  That game is not on its drive any more.: Esse jogo n\u00e3o est\u00e1 mais na unidade dele.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: A lista oculta n\u00e3o p\u00f4de ser gravada na unidade do jogo. Ela est\u00e1 protegida contra grava\u00e7\u00e3o, ou as Op\u00e7\u00f5es de Escritas est\u00e3o desligadas?\n+  The partition could not be removed (%d). Nothing was deleted.: A parti\u00e7\u00e3o n\u00e3o p\u00f4de ser removida (%d). Nada foi apagado.\n+  This drive cannot keep a hidden list (a network catalogue).: Esta unidade n\u00e3o pode guardar uma lista oculta (um cat\u00e1logo de rede).\n+  This game's drive has not answered yet. Wait for it, then try again.: A unidade deste jogo ainda n\u00e3o respondeu. Espere por ela e tente de novo.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: A parti\u00e7\u00e3o deste jogo \u00e9 uma que o RIPPS2 n\u00e3o remove (uma parti\u00e7\u00e3o do sistema, ou a que tem as configura\u00e7\u00f5es dele).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': 'uma parti\u00e7\u00e3o \u00e9 maior do que este disco permite: a tabela foi feita para um disco de outro tamanho'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': 'uma parti\u00e7\u00e3o passa do fim deste disco (setor %u de %u): a tabela foi feita para um disco maior'\n+  its RIPSHOT pictures: as imagens RIPSHOT dele\n+  its drive: a unidade dele\n+  its saves: os saves dele\n+  sector %u holds no partition header: o setor %u n\u00e3o tem cabe\u00e7alho de parti\u00e7\u00e3o\n+  the chain does not close at __mbr: a cadeia n\u00e3o fecha em __mbr\n+  the chain skips from sector %u: a cadeia pula a partir do setor %u\n+  the chain's links disagree at sector %u: os elos da cadeia n\u00e3o batem no setor %u\n+  the drive's size could not be read: n\u00e3o foi poss\u00edvel ler o tamanho da unidade\n+  the game (%s): o jogo (%s)\n+  the game's folder and everything in it (%s): a pasta do jogo e tudo o que tem nela (%s)\n+  the header at sector %u could not be read: n\u00e3o foi poss\u00edvel ler o cabe\u00e7alho no setor %u\n+  the header at sector %u fails its checksum: o cabe\u00e7alho no setor %u falha na soma de verifica\u00e7\u00e3o\n+  the partition at sector %u is not where or as large as APA puts one: a parti\u00e7\u00e3o no setor %u n\u00e3o est\u00e1 onde, ou do tamanho que, o APA cria uma\n+  the settings and art another copy of it uses: as configura\u00e7\u00f5es e capas que outra c\u00f3pia dele usa\n+  '%d FOLDERS': '%d PASTAS'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': '%s n\u00e3o p\u00f4de ser copiado para %s. A unidade est\u00e1 cheia ou protegida contra grava\u00e7\u00e3o?'\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s n\u00e3o est\u00e1 onde o RIPPS2 procura. Procurar nas unidades que o RIPPS2 iniciou? O lugar onde for encontrado fica memorizado para todas as inicializa\u00e7\u00f5es seguintes.'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : 'O Ember n\u00e3o est\u00e1 em %s, junto dos jogos, e precisa estar. O RIPPS2 o encontrou em %s: START copia ele para l\u00e1 (%d KB) e inicia o jogo.'\n+  Ember's BIOS: BIOS do Ember\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : 'A BIOS do Ember (bios.bin) n\u00e3o est\u00e1 em %s, junto do Ember. O RIPPS2 encontrou uma em %s: START copia ela para l\u00e1 (%d KB) e inicia o jogo.'\n+  LOOKING FOR YOUR LAUNCHERS: PROCURANDO SEUS INICIALIZADORES\n+  The POPS files: Os arquivos POPS\n+  '%s  MMCE card': '%s  cart\u00e3o MMCE'\n+  INSTALL RIPPS2'S NEUTRINO: INSTALAR O NEUTRINO DO RIPPS2\n+  'Internal HDD (APA): %s free of %s': 'HD interno (APA): %s livres de %s'\n+  LAUNCHERS: INICIALIZADORES\n+  LOOK FOR THEM: PROCURAR\n+  'Memory card %d: %d KB free': 'Cart\u00e3o de mem\u00f3ria %d: %d KB livres'\n+  POPS files: Arquivos POPS\n+  START EVERY DRIVE: INICIAR TODAS AS UNIDADES\n+  'STATUS: looking at the drives and the homebrew': 'STATUS: olhando as unidades e o homebrew'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): Iniciar todas as unidades primeiro, para a busca chegar a todas? (N\u00e3o busca nas unidades j\u00e1 iniciadas.)\n+  USB, HDD, MMCE and the rest: USB, HD, MMCE e o resto\n+  every started drive: toda unidade iniciada\n+  found where you keep them: encontrados onde voc\u00ea os guarda\n+  not found yet: ainda n\u00e3o encontrado\n+  not on a started drive: n\u00e3o est\u00e1 numa unidade iniciada\n+  only if you have none: s\u00f3 se voc\u00ea n\u00e3o tiver nenhum\n+  starts every drive first: inicia todas as unidades primeiro\n+  Drives, launchers and homebrew: Unidades, inicializadores e homebrew\n+  BDM Devices: Dispositivos BDM\n+  Color Sweep: Transi\u00e7\u00e3o de cores\n+  Colors and Text: Cores e texto\n+  HDD POPS partitions: Parti\u00e7\u00f5es POPS do HD\n+  Missing Covers: Capas faltando\n+  Name: Nome\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: Nenhuma configura\u00e7\u00e3o SMB do POPSTARTER encontrada ainda. Preencha as linhas abaixo.\n+  POPSTARTER Network: Rede do POPSTARTER\n+  POPSTARTER SMB Network: Rede SMB do POPSTARTER\n+  POPSTARTER UDPFS Network: Rede UDPFS do POPSTARTER\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: O RIPPS2 adiciona sozinho a unidade, a ISO, os VMCs e o modo de v\u00eddeo.\n+  Screen: Tela\n+  Speed: Velocidade\n+  Sweep Colors: Cores da transi\u00e7\u00e3o\n+  Text Weight: Espessura do texto\n+  Theme Color: Cor principal\n+  Audio: \u00c1udio\n+  Bold: Negrito\n+  Controllers: Controles\n+  Lively: Vivo\n+  Medium: M\u00e9dio\n+  PS Emulation: Emula\u00e7\u00e3o PS\n+  Regular: Normal\n+  Slow: Lento\n+  A MOMENT: UM MOMENTO\n+  A TASK IN THE BACKGROUND: UMA TAREFA EM SEGUNDO PLANO\n+  ABOUT %d S: CERCA DE %d S\n+  ANY MOMENT: A QUALQUER MOMENTO\n+  READING THE GAME LIST OF %s: LENDO A LISTA DE JOGOS DE %s\n+  SAVING A LIST: SALVANDO UMA LISTA\n+  START NOW: INICIAR AGORA\n+  STARTING: INICIANDO\n+  STARTS NEXT: INICIA EM SEGUIDA\n+  STOPPING IT SAFELY: PARANDO COM SEGURAN\u00c7A\n+  THE LAST OF THE QUEUE: O FIM DA FILA\n+  WHEN IT FINISHES: QUANDO TERMINAR\n+  What each card starts; install or repair BIBLE.: O que cada cart\u00e3o inicia; instale ou conserte o BIBLE.\n+  Rainbow: Arco-\u00edris\n+  Custom: Personalizado\n+  DESR DVR ISOs (experimental): ISOs DESR DVR (experimental)\n+  HDD Start Mode: In\u00edcio do HD\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): Roboto Condensed do The Roboto Project Authors (SIL OFL 1.1)\n+  '%s on %s': '%s em %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: %s virou %s (%d%%), cada peda\u00e7o verificado. Remover o ISO e manter o ZSO?'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: %s virou %s de novo, cada peda\u00e7o verificado. Remover o ZSO e manter o ISO?'\n+  A ZSO of this game is already beside it. Remove one of the two first.: J\u00e1 existe um ZSO deste jogo ao lado. Remova um dos dois primeiro.\n+  About %d min, then checked: Cerca de %d min, depois verificado\n+  An ISO of this game is already beside it. Remove one of the two first.: J\u00e1 existe um ISO deste jogo ao lado. Remova um dos dois primeiro.\n+  CHECKING EVERY PIECE: VERIFICANDO CADA PEDA\u00c7O\n+  COMPRESS TO ZSO: COMPACTAR PARA ZSO\n+  'COMPRESSED %s: %s FREED': '%s COMPACTADO: %s LIBERADOS'\n+  COMPRESSING 1 OF 2: COMPACTANDO 1 DE 2\n+  COMPRESSING 2 OF 2: COMPACTANDO 2 DE 2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : Compactar %s? O ISO fica at\u00e9 o ZSO ser lido de volta e verificado; depois o RIPPS2 pergunta antes de remover o ISO. O drive precisa de espa\u00e7o para os dois enquanto trabalha; deixe o console ligado at\u00e9 terminar. Um ZSO s\u00f3 inicia com o n\u00facleo OPL (n\u00e3o Neutrino); seus saves, configura\u00e7\u00f5es, imagens e cheats ficam com ele, e ele pode virar ISO de novo a qualquer momento.\n+  Compress to ZSO: Compactar para ZSO\n+  Cycle: Alternar\n+  'Experimental: OPL core only (not Neutrino)': 'Experimental: s\u00f3 o n\u00facleo OPL (n\u00e3o Neutrino)'\n+  Hold SELECT: Segurar SELECT\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: N\u00e3o foi poss\u00edvel terminar (%s). O que foi gravado foi removido; o jogo est\u00e1 como estava.\n+  List: Lista\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : Transformar %s em ISO de novo? O ZSO fica at\u00e9 o ISO ser lido de volta e verificado; depois o RIPPS2 pergunta antes de remover o ZSO. O drive precisa de espa\u00e7o para os dois enquanto trabalha; deixe o console ligado at\u00e9 terminar. Seus saves, configura\u00e7\u00f5es, imagens e cheats ficam com ele.\n+  Stop? What it has written so far is removed; the game stays as it was.: Parar? O que foi gravado at\u00e9 agora \u00e9 removido; o jogo fica como estava.\n+  THEMES: TEMAS\n+  There is not enough memory for this just now.: N\u00e3o h\u00e1 mem\u00f3ria suficiente para isso agora.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: O RIPPS2 n\u00e3o consegue compactar nem descompactar esta imagem (o tamanho n\u00e3o \u00e9 em blocos inteiros de 2 KB).\n+  UNCOMPRESS TO ISO: DESCOMPACTAR PARA ISO\n+  UNCOMPRESSED %s: '%s DESCOMPACTADO'\n+  UNCOMPRESSING: DESCOMPACTANDO\n+  Uncompress to ISO: Descompactar para ISO\n+  Under a minute, then checked: Menos de um minuto, depois verificado\n+  COPYING: COPIANDO\n+  SHOW AGAIN, DELETE OR COMPRESS: MOSTRAR DE NOVO, APAGAR OU COMPACTAR\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: MOSTRAR DE NOVO, APAGAR OU DESCOMPACTAR\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': '%s precisa de cerca de %s livres no seu drive enquanto trabalha (o original fica at\u00e9 o fim), e o drive tem %s livres.'\n+  Hide and Compress: Ocultar e compactar\n+  'COMPRESSED AND HIDDEN: %s': 'COMPACTADO E OCULTO: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': 'N\u00e3o h\u00e1 espa\u00e7o para esta c\u00f3pia: precisa de cerca de %s, e o drive tem %s livres.'\n+  OVER 4 GB FOR FAT32: MAIS DE 4 GB PARA FAT32\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: Este drive \u00e9 %s, que n\u00e3o guarda arquivos de 4 GB ou mais, e a nova imagem teria %s.\n+  '%s, %s clusters, %s free of %s': '%s, clusters de %s, %s livres de %s'\n+  Delete This Sweep: Apagar esta transi\u00e7\u00e3o\n+  Delete %s? RIPPS2's own sweeps stay as they are.: Apagar %s? As transi\u00e7\u00f5es do pr\u00f3prio RIPPS2 ficam como est\u00e3o.\n+  Sweep: Transi\u00e7\u00e3o\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: Os emblemas marcam os jogos com conquistas nas suas listas, com quantas voc\u00ea desbloqueou. Precisam da TELEMETRIA ligada.\n labels:\n   MANUAL: Manual\n   ENTER: Entrar\n@@ -1204,7 +1369,7 @@ labels:\n   SAVE_ICONS: \u00cdcones de save em 3D\n   HINT_SAVE_ICONS: Mostra o \u00edcone de save do jogo escolhido, girando, onde o tema tem um lugar para ele. L\u00ea primeiro a VMC do jogo, depois os cart\u00f5es de mem\u00f3ria.\n   NETBOOT_RESTART_NOW: O protocolo de rede muda depois de reiniciar. Salvar as configura\u00e7\u00f5es e reiniciar o OPL agora?\n-  HINT_NET_PROTOCOL: 'Qual servi\u00e7o de rede o RiptOPL usa: um compartilhamento SMB, UDPFS, UDPBD ou HTTP. O mesmo ajuste de Fontes de jogos. Sair de um que j\u00e1 est\u00e1 rodando precisa reiniciar.'\n+  HINT_NET_PROTOCOL: 'Qual servi\u00e7o de rede o RiptOPL usa: um compartilhamento SMB, UDPFS, UDPBD ou HTTP. Sair de um que j\u00e1 est\u00e1 rodando precisa reiniciar.'\n   HINT_UDPFS_ACCESS: S\u00f3 UDPFS. Arquivos l\u00ea ISOs soltas da pasta de jogos do servidor; IMG l\u00ea uma imagem de disco como unidade. SMB e HTTP sempre usam Arquivos, UDPBD sempre IMG.\n   HINT_SMB_VERSION: Qual vers\u00e3o do SMB usar com o compartilhamento do PC.\n   HINT_NET_ADVANCED: Libera a edi\u00e7\u00e3o do modo de link Ethernet e da porta SMB.\n@@ -1403,3 +1568,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: Como as fotos esperando no rolo de cada jogo viram PNG.\n   RIPPS2_SETTINGS_HOME: Salvar configura\u00e7\u00f5es em\n   HINT_RIPPS2_SETTINGS_HOME: 'Onde o RIPPS2 guarda as configura\u00e7\u00f5es: o save dele em um cart\u00e3o de mem\u00f3ria, uma pasta RIPPS2 em uma unidade (o HD interno, USB, MX4SIO, iLink ou MMCE) ou uma pasta que voc\u00ea digita.'\n+  RIPPS2_LAUNCHERS: Inicializadores\n+  RIPPS2_LOOK_NOW: Procurar agora\n+  HINT_RIPPS2_LOOK_NOW: O RIPPS2 procura nas unidades iniciadas o POPSTARTER, os arquivos POPS, o Ember, a BIOS do Ember e o Neutrino, e memoriza onde cada um est\u00e1. Toda inicializa\u00e7\u00e3o os l\u00ea de l\u00e1.\n+  RIPPS2_HIDE_GAME: Ocultar jogo\n+  RIPPS2_HIDDEN_GAMES: Jogos ocultos\n+  HINT_RIPPS2_HIDDEN_GAMES: Os jogos ocultos com Ocultar jogo, nas unidades iniciadas. Mostre um de novo, ou apague-o. Cada unidade guarda sua pr\u00f3pria lista.\n+  HINT_RIPPS2_BDM_DEVICES: 'As unidades de onde o BDM l\u00ea jogos: USB, MX4SIO, um PC Card CF, a unidade DVR de um DESR e iLink. O HD interno segue o in\u00edcio do HD.'\n+  HINT_RIPPS2_SCREEN: 'A imagem do menu: modo de v\u00eddeo, widescreen, onde ela fica na TV (H-POS, V-POS) e overscan.'\n+  HINT_RIPPS2_LAYOUT: Onde ficam as categorias e como uma p\u00e1gina entra, depois as capas do RIPFLOW e a grade do RIPGRID.\n+  HINT_RIPPS2_COLORS_TEXT: Espessura do texto, a cor do tema, a transi\u00e7\u00e3o de cores e cada cor.\n+  HINT_RIPPS2_TEXT_WEIGHT: 'Qu\u00e3o grosso \u00e9 o texto do pr\u00f3prio RIPPS2: as dicas das configura\u00e7\u00f5es, STATUS e Sobre. Normal \u00e9 como antes; M\u00e9dio e Negrito ficam mais leg\u00edveis numa imagem suave.'\n+  HINT_RIPPS2_HDD_START: 'O HD interno, APA ou exFAT: o RIPPS2 l\u00ea o que ele for e mostra uma vez, como jogos do HD. Desligado deixa ele em paz.'\n+  HINT_RIPPS2_THEME_COLOR: A cor do pr\u00f3prio tema. Escolha Personalizado como cor do tema para escolher voc\u00ea mesmo, como as cores do texto.\n+  HINT_RIPPS2_SWEEP: Cinco cores por onde o tema passa, devagar e suave. Arco-\u00edris, uma transi\u00e7\u00e3o feita de cada cor de tema, ou a sua. Desligado mant\u00e9m o tema parado.\n+  HINT_RIPPS2_SWEEP_COLORS: As cinco cores da transi\u00e7\u00e3o e a velocidade dela. Mude uma das do RIPPS2 e ela fica salva como sua, com o nome que voc\u00ea der.\n+  HINT_RIPPS2_SWEEP_NAME: Como a sua transi\u00e7\u00e3o se chama na lista Transi\u00e7\u00e3o de cores.\n+  HINT_RIPPS2_SWEEP_COLOR: Uma das cinco cores por onde a transi\u00e7\u00e3o passa, em ordem.\n+  HINT_RIPPS2_SWEEP_SPEED: 'Quanto tempo cada cor dura antes da pr\u00f3xima entrar. At\u00e9 Vivo continua suave: as cores nunca piscam.'\n+  RIPPS2_HIDE_COMPRESS: Ocultar jogo e compacta\u00e7\u00e3o\n+  HINT_RIPPS2_SELECT_HOLD: 'SELECT segurado 4.2 s numa lista de jogos. Lista: os temas em um painel, escolha um. Alternar: o pr\u00f3ximo tema. Desligado: nada (apertar ainda atualiza).'"
        },
        {
          "sha": "e3ffb83921cf4f4bd40f817fc85d394ee8722b0c",
          "filename": "elf/lang/Romana.yml",
          "status": "modified",
          "additions": 207,
          "deletions": 22,
          "changes": 229,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FRomana.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FRomana.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FRomana.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: Romana\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTER CONFIRM\u0102, ESC ANULEAZ\u0102. ORICE BUTON ARAT\u0102 TASTELE.\n   USB KEYBOARD: TASTATUR\u0102 USB\n-  '      DESR DVR ISOs (experimental)': '      ISO-uri DESR DVR (experimental)'\n-  '      Internal HDD (exFAT)': '      HDD intern (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (experimental)'\n-  '      Protocol': '      Protocol'\n   Access: Acces\n   Applications Page Start Mode: Mod de pornire pagin\u0103 aplica\u021bii\n   Art Delay: \u00cent\u00e2rziere imagini\n@@ -22,7 +18,6 @@ strings:\n   Fifth: Al cincilea\n   First: Primul\n   Fourth: Al patrulea\n-  HDD (APA) Start Mode: Mod de pornire HDD (APA)\n   HDD Cache: Cache HDD\n   HDD Spindown: Oprire HDD\n   MMCE Settings: Set\u0103ri MMCE\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: Acesta este un joc PS2 cu patch ESR. Pune ESR (esr*.elf) \u00een APPS, BOOT sau ESR pe un card de memorie sau un stick USB ca s\u0103 \u00eel porne\u0219ti.\n   '%s, BUILD %d': '%s, VERSIUNEA %d'\n   '... and the anonymous ...': '... \u0219i cei anonimi ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': '1.8.0 inclus nemodificat (AFL-3.0): github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: O demontare \u0219i reconstruire a RiptOPL, fork-ul Open PS2 Loader,\n   ABOUT: DESPRE\n   'ACHIEVEMENTS: SPIRIT OF RA': 'REALIZ\u0102RI: SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): Video AVI \u00een Fi\u0219ierele de memorie, fork-ul RIPPS2 (AFL 2.0)\n   Apollo Save Tool by bucanero: Apollo Save Tool de bucanero\n   BACK: \u00ceNAPOI\n+  Back (keeps both): \u00cenapoi (le p\u0103streaz\u0103 pe am\u00e2ndou\u0103)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: Acest card MMCE nu poate redenumi un fi\u0219ier, lucru de care compresia are nevoie la ultimul pas. Actualiza\u021bi firmware-ul sd2psXtd la 1.4.0 sau mai nou (sd2psxtd.github.io/download), apoi \u00eencerca\u021bi din nou. Nu s-a schimbat nimic.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): BIBLE este PS2BBL 1.2.0, versiunile semnate de El Isra (card \u0219i HDD), nemodificat (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: Sunete BIOS > Extrage le red\u0103 a\u0219a cum o face SPU2; nu se include niciun BIOS\n   BOOT LOCK AND BIBLE: BOOT LOCK \u0218I BIBLE\n   BOOT LOCK edits its config too: BOOT LOCK \u00eei editeaz\u0103 \u0219i configura\u021bia\n   BUILD DETAILS: DETALII VERSIUNE\n-  BUILT ON RIPTOPL: CONSTRUIT PE RIPTOPL\n   BearSSL by Thomas Pornin: BearSSL de Thomas Pornin\n   Built with Claude (Anthropic): F\u0103cut cu Claude (Anthropic)\n   CD PLAYER: PLAYER CD\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek \u0219i Sleek Bold, desenate pentru RIPPS2\n   RetroAchievements on the console by hacan359 (yoba): RetroAchievements pe consol\u0103 de hacan359 (yoba)\n   RiptOPL by NathanNeurotic: RiptOPL de NathanNeurotic\n-  Roboto Condensed by Google (Apache-2.0): Roboto Condensed de Google (Apache-2.0)\n   SOUNDS: SUNETE\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: Instalatorul FMCB al lui SP193 \u0219i OSDMenu al lui pcm720, urmate pas cu pas\n   SUPPORT: SUPORT\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: wLaunchELF R3Z de NathanNeurotic\n   wLaunchELF by the ps2homebrew team: wLaunchELF de echipa ps2homebrew\n   what RIPPS2's card image writes are checked against: cu ce sunt verificate scrierile de imagini de card ale RIPPS2\n-  with its own look, launchers, file browser and save tools.: cu propriul aspect, lansatoare, browser de fi\u0219iere \u0219i unelte de salv\u0103ri.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: xeRAbora de hacan359, rcheevos de RetroAchievements\n   your console's own, ripped from its BIOS: sunetele proprii ale consolei tale, extrase din BIOS-ul ei\n   ' (no DNS answer)': ' (niciun r\u0103spuns DNS)'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO: se scrie %.60s'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: Neutrino nu a putut fi scris \u00een %s. Verific\u0103 spa\u021biul liber \u0219i s\u0103 nu fie protejat la scriere, apoi \u00eencearc\u0103 din nou.\n   THE GAME'S DRIVE  (%s/neutrino): UNITATEA JOCULUI  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : Acest joc porne\u0219te prin Neutrino, iar acolo unde caut\u0103 RIPPS2 (Set\u0103ri, unitatea jocului, cardurile de memorie) nu exist\u0103 Neutrino. RIPPS2 are la el Neutrino 1.8.0 de rickgaiser (AFL-3.0). \u00cel instalezi acum? Apoi alegi unde.\n   '%d / %d FILES': '%d / %d FI\u0218IERE'\n   '%d FOLDERS AND CONTENTS': '%d FOLDERE \u0218I CON\u021aINUT'\n   '%d FOLDERS, %d FOUND': '%d DOSARE, %d G\u0102SITE'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d din %d \u00eenc\u0103 blocate'\n   '%s   on %s': '%s   pe %s'\n   BADGES: INSIGNE\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: Insignele func\u021bioneaz\u0103 doar cu TELEMETRIA pornit\u0103 \u0219i xeRAbora rul\u00e2nd pe PC-ul t\u0103u; p\u00e2n\u0103 atunci nu cost\u0103 nimic.\n   CHECK THE DISC: VERIFIC\u0102 DISCUL\n   From this game's last CHECK GAME SUPPORT.: Din ultima VERIFICARE A SUPORTULUI JOCULUI pentru acest joc.\n   HOW IT WORKS: CUM FUNC\u021aIONEAZ\u0102\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: Pe aceast\u0103 consol\u0103 exist\u0103 un PS2BBL cu blocarea RIPPS2.\n   AUDIO folder: folderul AUDIO\n   DRIVES: UNIT\u0102\u021aI\n-  INSTALL RIPPS2'S COPY: INSTALEAZ\u0102 COPIA RIPPS2\n-  'Installed: ': 'Instalat: '\n   MANAGE: GESTIONEAZ\u0102\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: GESTIONEAZ\u0102 arat\u0103 ce porne\u0219te fiecare card de memorie \u0219i instaleaz\u0103 sau repar\u0103 BIBLE.\n   MEMORY CARD 0  (about 600 KB): CARDUL DE MEMORIE 0  (aproximativ 600 KB)\n   MEMORY CARD 1  (about 600 KB): CARDUL DE MEMORIE 1  (aproximativ 600 KB)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO: driverul nu s-a putut \u00eenc\u0103rca (cod %d).'\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO: driverul este \u00eenc\u0103rcat, dar niciun card SD nu a r\u0103spuns (slotul mc1).'\n   NEUTRINO SETTINGS: SET\u0102RI NEUTRINO\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': '\u00cenc\u0103 niciunul: RIPPS2 nu are sunete proprii. Extrage-le pe ale acestei console sau f\u0103-le \u00een RIPPS2 Audio Studio.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: Neinstalat. Un joc care are nevoie de el ofer\u0103 copia RIPPS2, sau instaleaz\u0103-l aici.\n   POPSTARTER NETWORK: RE\u021aEA POPSTARTER\n   POPSTARTER and Ember: POPSTARTER \u0219i Ember\n   PS1 SETTINGS: SET\u0102RI PS1\n   PS2BBL 1.2.0 for RIPPS2: PS2BBL 1.2.0 pentru RIPPS2\n   RIP THIS CONSOLE'S SOUNDS: EXTRAGE SUNETELE ACESTEI CONSOLE\n-  RIPPS2's homebrew: homebrew-ul RIPPS2\n   STATUS: STARE\n-  'STATUS: looking for RIPPS2''s homebrew': 'STARE: se caut\u0103 homebrew-ul RIPPS2'\n   a drive of unknown kind: o unitate de tip necunoscut\n-  a drive or a memory card: o unitate sau un card de memorie\n   boot loader, card by card: \u00eenc\u0103rc\u0103tor de pornire, card cu card\n   from its BIOS, into AUDIO: din BIOS-ul ei, \u00een AUDIO\n   internal HDD (exFAT): HDD intern (exFAT)\n@@ -975,10 +958,192 @@ strings:\n   Sidebar: Bar\u0103 lateral\u0103\n   your own copy, found on your drives and memory cards: copia ta proprie, g\u0103sit\u0103 pe unit\u0103\u021bile \u0219i cardurile tale de memorie\n   'your own, found and launched: github.com/Gageformer/Ember/releases': 'al t\u0103u, g\u0103sit \u0219i pornit: github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': 'Nu a fost g\u0103sit pe mass0: (%s). Pune ember.elf \u0219i bios.bin-ul t\u0103u \u00een folderul s\u0103u EMBER.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: Nu a fost g\u0103sit. Pune propriul POPSTARTER.ELF \u00een folderul POPS al unei unit\u0103\u021bi.\n   Launch Disc: Lansare disc\n   Memory Files: Fi\u0219iere de memorie\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: RIPPS2 a g\u0103sit %d din 5. Fiecare e \u021binut minte, \u0219i fiecare lansare \u00eel cite\u0219te de acolo.\n+  not found on the started drives: neg\u0103sit pe unit\u0103\u021bile pornite\n+  not looked for yet: \u00eenc\u0103 nec\u0103utat\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s nu a r\u0103spuns, deci %s nu poate porni. E conectat?'\n+  '%s is not on %s any more, so it has left All Games.': '%s nu mai este pe %s, a\u0219a c\u0103 a ie\u0219it din Toate jocurile.'\n+  STARTING %s: PORNESC %s\n+  the SMB share: partajarea SMB\n+  the drive: unitatea\n+  the internal HDD (APA): HDD-ul intern (APA)\n+  the internal HDD (exFAT): HDD-ul intern (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: CONSTRUIT PE RIPTOPL, CU NEUTRINO\n+  'Special thanks to all the YouTube love as well:': 'Mul\u021bumiri speciale \u0219i pentru toat\u0103 dragostea de pe YouTube:'\n+  and its own look, launchers, file browser and save tools.: \u0219i cu propriul aspect, lansatoare, explorator de fi\u0219iere \u0219i unelte pentru salv\u0103ri.\n+  glad to help however you reach me.: bucuros s\u0103 ajut, oricum m-a\u021bi g\u0103si.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): al doilea nucleu de joc, 1.8.0 inclus nemodificat (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: cu Neutrino de la rickgaiser \u00een\u0103untru ca al doilea nucleu de joc,\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : 'Acest joc porne\u0219te prin Neutrino, iar RIPPS2 nu a g\u0103sit niciunul: nici la calea din Set\u0103ri, nici pe unitatea jocului, nici pe cardurile de memorie, nici pe vreo unitate pornit\u0103. RIPPS2 include Neutrino 1.8.0 de la rickgaiser (AFL-3.0): instaleaz\u0103-l o dat\u0103, unde alegi, \u0219i fiecare joc Neutrino \u00eel folose\u0219te de acolo.'\n+  STARTING THE DRIVES: PORNESC UNIT\u0102\u021aILE\n+  '%d games, on the started drives': '%d jocuri, pe unit\u0103\u021bile pornite'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s: UNELE FI\u0218IERE NU AU PUTUT FI \u0218TERSE'\n+  '%u partitions, all in order': '%u parti\u021bii, toate \u00een ordine'\n+  ', %d art images': ', %d imagini de copert\u0103'\n+  ', and ': ', \u0219i '\n+  ', its cheats': ', codurile lui'\n+  ', its settings': ', set\u0103rile lui'\n+  1 game, on the started drives: 1 joc, pe unit\u0103\u021bile pornite\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: Un joc PS1 de aici se \u0219terge \u00een Fi\u0219ierele de memorie, unde e un fi\u0219ier separat.\n+  A network catalogue's games are deleted on the PC that serves them.: Jocurile unui catalog de re\u021bea se \u0219terg pe PC-ul care le serve\u0219te.\n+  DELETE FOR GOOD: \u0218TERGE DEFINITIV\n+  DELETE IT: \u0218TERGE-L\n+  'DELETED %s: %s FREED': '%s \u0218TERS: %s ELIBERA\u021aI'\n+  'Delete %s from %s? Goes: %s.': '\u0218tergi %s de pe %s? Pleac\u0103: %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': '\u0218tergi %s de pe %s? Pleac\u0103: %s. R\u0103m\u00e2ne: %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: \u0218tergi \u0219i salv\u0103rile \u0219i pozele RIPSHOT ale lui? Nu le p\u0103streaz\u0103.\n+  HIDDEN GAMES: JOCURI ASCUNSE\n+  'HIDDEN: %s': 'ASCUNS: %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: Ascunzi %s? Iese din toate listele, \u0219i din Toate jocurile \u0219i Favorite. Set\u0103ri > Surse de jocuri > Jocuri ascunse \u00eel arat\u0103 din nou.\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: Ultima verificare. %s se \u0219terge definitiv, \u0219i nimic nu-l mai poate aduce \u00eenapoi. Apas\u0103 START ca s\u0103-l \u0219tergi.\n+  None. Hide Game, in a game's options, puts one here.: Niciunul. Ascunde jocul, \u00een op\u021biunile unui joc, pune unul aici.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : 'Nu s-a \u0219ters nimic: tabela de parti\u021bii a acestei unit\u0103\u021bi nu se potrive\u0219te cu acest disc (%s). Poate a fost scris\u0103 dintr-o imagine a unui disc de alt\u0103 m\u0103rime. Repar-o \u00eent\u00e2i pe un PC.'\n+  RIPPS2 cannot delete a game from this page.: RIPPS2 nu poate \u0219terge un joc din aceast\u0103 pagin\u0103.\n+  RIPPS2 could not find this game's files on its drive.: RIPPS2 nu a g\u0103sit fi\u0219ierele acestui joc pe unitatea lui.\n+  Reading the hidden lists: Citesc listele ascunse\n+  SHOW AGAIN OR DELETE: ARAT\u0102 DIN NOU SAU \u0218TERGE\n+  SHOW IT AGAIN: ARAT\u0102-L DIN NOU\n+  That game is not on its drive any more.: Jocul acela nu mai este pe unitatea lui.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: Lista ascuns\u0103 nu a putut fi scris\u0103 pe unitatea jocului. E protejat\u0103 la scriere, sau Opera\u021biile de scriere sunt oprite?\n+  The partition could not be removed (%d). Nothing was deleted.: Parti\u021bia nu a putut fi eliminat\u0103 (%d). Nu s-a \u0219ters nimic.\n+  This drive cannot keep a hidden list (a network catalogue).: Aceast\u0103 unitate nu poate \u021bine o list\u0103 ascuns\u0103 (un catalog de re\u021bea).\n+  This game's drive has not answered yet. Wait for it, then try again.: Unitatea acestui joc nu a r\u0103spuns \u00eenc\u0103. A\u0219teapt\u0103-o, apoi \u00eencearc\u0103 din nou.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: Parti\u021bia acestui joc e una pe care RIPPS2 nu o elimin\u0103 (o parti\u021bie de sistem, sau cea cu set\u0103rile lui).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': 'o parti\u021bie e mai mare dec\u00e2t permite acest disc: tabela a fost f\u0103cut\u0103 pentru un disc de alt\u0103 m\u0103rime'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': 'o parti\u021bie trece de cap\u0103tul acestui disc (sectorul %u din %u): tabela a fost f\u0103cut\u0103 pentru un disc mai mare'\n+  its RIPSHOT pictures: pozele RIPSHOT ale lui\n+  its drive: unitatea lui\n+  its saves: salv\u0103rile lui\n+  sector %u holds no partition header: sectorul %u nu are antet de parti\u021bie\n+  the chain does not close at __mbr: lan\u021bul nu se \u00eenchide la __mbr\n+  the chain skips from sector %u: lan\u021bul sare de la sectorul %u\n+  the chain's links disagree at sector %u: verigile lan\u021bului nu se potrivesc la sectorul %u\n+  the drive's size could not be read: m\u0103rimea unit\u0103\u021bii nu a putut fi citit\u0103\n+  the game (%s): jocul (%s)\n+  the game's folder and everything in it (%s): dosarul jocului \u0219i tot ce e \u00een el (%s)\n+  the header at sector %u could not be read: antetul de la sectorul %u nu a putut fi citit\n+  the header at sector %u fails its checksum: antetul de la sectorul %u nu trece suma de control\n+  the partition at sector %u is not where or as large as APA puts one: parti\u021bia de la sectorul %u nu e unde, sau c\u00e2t de mare, o pune APA\n+  the settings and art another copy of it uses: set\u0103rile \u0219i coper\u021bile pe care le folose\u0219te alt\u0103 copie a lui\n+  '%d FOLDERS': '%d DOSARE'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': '%s nu a putut fi copiat \u00een %s. Unitatea e plin\u0103 sau protejat\u0103 la scriere?'\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s nu e unde caut\u0103 RIPPS2. \u00cel caut pe unit\u0103\u021bile pornite de RIPPS2? Locul unde e g\u0103sit e \u021binut minte pentru fiecare lansare de dup\u0103.'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Ember nu e \u00een %s, l\u00e2ng\u0103 jocuri, \u0219i trebuie s\u0103 fie. RIPPS2 l-a g\u0103sit la %s: START \u00eel copiaz\u0103 acolo (%d KB) \u0219i porne\u0219te jocul.'\n+  Ember's BIOS: BIOS-ul lui Ember\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : 'BIOS-ul lui Ember (bios.bin) nu e \u00een %s, l\u00e2ng\u0103 Ember. RIPPS2 a g\u0103sit unul la %s: START \u00eel copiaz\u0103 acolo (%d KB) \u0219i porne\u0219te jocul.'\n+  LOOKING FOR YOUR LAUNCHERS: CAUT LANSATOARELE TALE\n+  The POPS files: Fi\u0219ierele POPS\n+  '%s  MMCE card': '%s  card MMCE'\n+  INSTALL RIPPS2'S NEUTRINO: INSTALEAZ\u0102 NEUTRINO DIN RIPPS2\n+  'Internal HDD (APA): %s free of %s': 'HDD intern (APA): %s liberi din %s'\n+  LAUNCHERS: LANSATOARE\n+  LOOK FOR THEM: CAUT\u0102-LE\n+  'Memory card %d: %d KB free': 'Card de memorie %d: %d KB liberi'\n+  POPS files: Fi\u0219iere POPS\n+  START EVERY DRIVE: PORNE\u0218TE TOATE UNIT\u0102\u021aILE\n+  'STATUS: looking at the drives and the homebrew': 'STARE: m\u0103 uit la unit\u0103\u021bi \u0219i la homebrew'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): Porne\u0219ti \u00eent\u00e2i toate unit\u0103\u021bile, ca s\u0103 ajung\u0103 c\u0103utarea la toate? (Nu caut\u0103 pe unit\u0103\u021bile deja pornite.)\n+  USB, HDD, MMCE and the rest: USB, HDD, MMCE \u0219i restul\n+  every started drive: fiecare unitate pornit\u0103\n+  found where you keep them: g\u0103site unde le \u021bii\n+  not found yet: \u00eenc\u0103 neg\u0103sit\n+  not on a started drive: nu e pe o unitate pornit\u0103\n+  only if you have none: doar dac\u0103 nu ai niciunul\n+  starts every drive first: porne\u0219te \u00eent\u00e2i toate unit\u0103\u021bile\n+  Drives, launchers and homebrew: Unit\u0103\u021bi, lansatoare \u0219i homebrew\n+  BDM Devices: Dispozitive BDM\n+  Color Sweep: Trecere de culori\n+  Colors and Text: Culori \u0219i text\n+  HDD POPS partitions: Parti\u021bii POPS pe HDD\n+  Layout: Aranjare\n+  Missing Covers: Coper\u021bi lips\u0103\n+  Name: Nume\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: Nu s-a g\u0103sit \u00eenc\u0103 o configurare SMB pentru POPSTARTER. Completa\u021bi r\u00e2ndurile de mai jos.\n+  POPSTARTER Network: Re\u021beaua POPSTARTER\n+  POPSTARTER SMB Network: Re\u021beaua SMB a POPSTARTER\n+  POPSTARTER UDPFS Network: Re\u021beaua UDPFS a POPSTARTER\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: RIPPS2 adaug\u0103 singur unitatea, ISO-ul, VMC-urile \u0219i modul video.\n+  Screen: Ecran\n+  Speed: Vitez\u0103\n+  Sweep Colors: Culorile trecerii\n+  Text Weight: Grosimea textului\n+  Theme Color: Culoare principal\u0103\n+  Bold: Aldin\n+  Controllers: Controlere\n+  Lively: Vioi\n+  Medium: Mediu\n+  PS Emulation: Emulare PS\n+  Regular: Obi\u0219nuit\n+  Slow: Lent\n+  A MOMENT: O CLIP\u0102\n+  A TASK IN THE BACKGROUND: O SARCIN\u0102 \u00ceN FUNDAL\n+  ABOUT %d S: CAM %d S\n+  ANY MOMENT: DIN CLIP\u0102 \u00ceN CLIP\u0102\n+  READING THE GAME LIST OF %s: SE CITE\u0218TE LISTA DE JOCURI DE PE %s\n+  SAVING A LIST: SE SALVEAZ\u0102 O LIST\u0102\n+  START NOW: PORNE\u0218TE ACUM\n+  STARTING: PORNE\u0218TE\n+  STARTS NEXT: PORNE\u0218TE \u00ceN CONTINUARE\n+  STOPPING IT SAFELY: SE OPRE\u0218TE \u00ceN SIGURAN\u021a\u0102\n+  THE LAST OF THE QUEUE: CAP\u0102TUL COZII\n+  WHEN IT FINISHES: C\u00c2ND TERMIN\u0102\n+  What each card starts; install or repair BIBLE.: Ce porne\u0219te fiecare card; instala\u021bi sau repara\u021bi BIBLE.\n+  Rainbow: Curcubeu\n+  Custom: Personalizat\n+  DESR DVR ISOs (experimental): ISO-uri DESR DVR (experimental)\n+  HDD Start Mode: Mod de pornire HDD\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): Roboto Condensed de The Roboto Project Authors (SIL OFL 1.1)\n+  '%s on %s': '%s pe %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: %s a devenit %s (%d%%), fiecare bucat\u0103 verificat\u0103. \u0218tergi ISO \u0219i p\u0103strezi ZSO?'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: %s a redevenit %s, fiecare bucat\u0103 verificat\u0103. \u0218tergi ZSO \u0219i p\u0103strezi ISO?'\n+  A ZSO of this game is already beside it. Remove one of the two first.: L\u00e2ng\u0103 el exist\u0103 deja un ZSO al acestui joc. \u0218terge mai \u00eent\u00e2i unul dintre cele dou\u0103.\n+  About %d min, then checked: Circa %d min, apoi verificat\n+  An ISO of this game is already beside it. Remove one of the two first.: L\u00e2ng\u0103 el exist\u0103 deja un ISO al acestui joc. \u0218terge mai \u00eent\u00e2i unul dintre cele dou\u0103.\n+  CHECKING EVERY PIECE: SE VERIFIC\u0102 FIECARE BUCAT\u0102\n+  COMPRESS TO ZSO: COMPRIM\u0102 \u00ceN ZSO\n+  'COMPRESSED %s: %s FREED': '%s COMPRIMAT: %s ELIBERA\u021aI'\n+  COMPRESSING 1 OF 2: SE COMPRIM\u0102 1 DIN 2\n+  COMPRESSING 2 OF 2: SE COMPRIM\u0102 2 DIN 2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : Comprimi %s? ISO r\u0103m\u00e2ne p\u00e2n\u0103 c\u00e2nd ZSO este recitit \u0219i verificat, apoi RIPPS2 \u00eentreab\u0103 \u00eenainte s\u0103 \u0219tearg\u0103 ISO. Unitatea are nevoie de loc pentru am\u00e2ndou\u0103 c\u00e2t lucreaz\u0103; las\u0103 consola pornit\u0103 p\u00e2n\u0103 termin\u0103. Un ZSO porne\u0219te doar cu nucleul OPL (nu Neutrino); salv\u0103rile, set\u0103rile, imaginile \u0219i codurile lui r\u0103m\u00e2n cu el \u0219i poate redeveni ISO oric\u00e2nd.\n+  Compress to ZSO: Comprim\u0103 \u00een ZSO\n+  Cycle: Pe r\u00e2nd\n+  'Experimental: OPL core only (not Neutrino)': 'Experimental: doar nucleul OPL (nu Neutrino)'\n+  Hold SELECT: \u021aine SELECT\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: Nu s-a putut termina (%s). Ce a scris a fost \u0219ters; jocul e cum era.\n+  List: List\u0103\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : Faci din %s din nou un ISO? ZSO r\u0103m\u00e2ne p\u00e2n\u0103 c\u00e2nd ISO este recitit \u0219i verificat, apoi RIPPS2 \u00eentreab\u0103 \u00eenainte s\u0103 \u0219tearg\u0103 ZSO. Unitatea are nevoie de loc pentru am\u00e2ndou\u0103 c\u00e2t lucreaz\u0103; las\u0103 consola pornit\u0103 p\u00e2n\u0103 termin\u0103. Salv\u0103rile, set\u0103rile, imaginile \u0219i codurile lui r\u0103m\u00e2n cu el.\n+  Stop? What it has written so far is removed; the game stays as it was.: Opre\u0219ti? Ce s-a scris p\u00e2n\u0103 acum se \u0219terge; jocul r\u0103m\u00e2ne cum era.\n+  THEMES: TEME\n+  There is not enough memory for this just now.: Acum nu e destul\u0103 memorie pentru asta.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: RIPPS2 nu poate comprima sau decomprima aceast\u0103 imagine (m\u0103rimea ei nu e \u00een blocuri \u00eentregi de 2 KB).\n+  UNCOMPRESS TO ISO: DECOMPRIM\u0102 \u00ceN ISO\n+  UNCOMPRESSED %s: '%s DECOMPRIMAT'\n+  UNCOMPRESSING: SE DECOMPRIM\u0102\n+  Uncompress to ISO: Decomprim\u0103 \u00een ISO\n+  Under a minute, then checked: Sub un minut, apoi verificat\n+  COPYING: SE COPIAZ\u0102\n+  SHOW AGAIN, DELETE OR COMPRESS: ARAT\u0102 DIN NOU, \u0218TERGE SAU COMPRIM\u0102\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: ARAT\u0102 DIN NOU, \u0218TERGE SAU DECOMPRIM\u0102\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': '%s are nevoie de circa %s liberi pe unitatea sa c\u00e2t lucreaz\u0103 (originalul r\u0103m\u00e2ne p\u00e2n\u0103 la sf\u00e2r\u0219it), iar unitatea are %s liberi.'\n+  Hide and Compress: Ascunde \u0219i comprim\u0103\n+  'COMPRESSED AND HIDDEN: %s': 'COMPRIMAT \u0218I ASCUNS: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': 'Nu este loc pentru aceast\u0103 copiere: are nevoie de circa %s, iar unitatea are %s liberi.'\n+  OVER 4 GB FOR FAT32: PESTE 4 GB PENTRU FAT32\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: Aceast\u0103 unitate este %s, care nu \u021bine fi\u0219iere de 4 GB sau mai mari, iar noua imagine ar avea %s.\n+  '%s, %s clusters, %s free of %s': '%s, clustere de %s, %s liberi din %s'\n+  Delete This Sweep: \u0218terge aceast\u0103 trecere\n+  Delete %s? RIPPS2's own sweeps stay as they are.: \u0218tergi %s? Trecerile proprii ale RIPPS2 r\u0103m\u00e2n cum sunt.\n+  Sweep: Trecere\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: Insignele marcheaz\u0103 jocurile cu realiz\u0103ri din listele tale \u0219i c\u00e2te ai deblocat. Au nevoie de TELEMETRIE pornit\u0103.\n labels:\n   SETTINGS_SAVED: Set\u0103ri salvate \u00een %s\n   REMOVE_ALL_SETTINGS: Elimin\u0103 set\u0103rile\n@@ -1258,7 +1423,7 @@ labels:\n   SAVE_ICONS: Pictograme de salvare 3D\n   HINT_SAVE_ICONS: Arat\u0103 pictograma de salvare a jocului ales, rotindu-se, acolo unde tema are loc pentru ea. Cite\u0219te \u00eent\u00e2i VMC-ul jocului, apoi cardurile de memorie.\n   NETBOOT_RESTART_NOW: Protocolul de re\u021bea se schimb\u0103 dup\u0103 repornire. Salvezi set\u0103rile \u0219i reporne\u0219ti OPL acum?\n-  HINT_NET_PROTOCOL: 'Ce serviciu de re\u021bea folose\u0219te RiptOPL: o partajare SMB, UDPFS, UDPBD sau HTTP. Aceea\u0219i setare ca \u00een Surse de jocuri. Trecerea de la unul care ruleaz\u0103 deja cere repornire.'\n+  HINT_NET_PROTOCOL: 'Ce serviciu de re\u021bea folose\u0219te RiptOPL: o partajare SMB, UDPFS, UDPBD sau HTTP. Trecerea de la unul care ruleaz\u0103 deja cere repornire.'\n   HINT_UDPFS_ACCESS: Doar UDPFS. Fi\u0219iere cite\u0219te ISO-uri separate din folderul de jocuri al serverului; IMG cite\u0219te o imagine de disc ca unitate. SMB \u0219i HTTP folosesc mereu Fi\u0219iere, UDPBD mereu IMG.\n   HINT_SMB_VERSION: Ce versiune SMB s\u0103 foloseasc\u0103 pentru partajarea de pe PC.\n   HINT_NET_ADVANCED: Deblocheaz\u0103 editarea modului de leg\u0103tur\u0103 Ethernet \u0219i a portului SMB.\n@@ -1459,3 +1624,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: Cum cadrele \u00een a\u0219teptare pe rola fiec\u0103rui joc devin PNG.\n   RIPPS2_SETTINGS_HOME: Salveaz\u0103 set\u0103rile pe\n   HINT_RIPPS2_SETTINGS_HOME: 'Unde \u00ee\u0219i \u021bine RIPPS2 set\u0103rile: \u00een salvarea proprie pe un card de memorie, \u00eentr-un dosar RIPPS2 pe o unitate (HDD-ul intern, USB, MX4SIO, iLink sau MMCE) sau \u00eentr-un dosar pe care \u00eel scrii.'\n+  RIPPS2_LAUNCHERS: Lansatoare\n+  RIPPS2_LOOK_NOW: Caut\u0103 acum\n+  HINT_RIPPS2_LOOK_NOW: RIPPS2 caut\u0103 pe unit\u0103\u021bile pornite POPSTARTER, fi\u0219ierele POPS, Ember, BIOS-ul lui Ember \u0219i Neutrino, \u0219i \u021bine minte unde e fiecare. Fiecare lansare le cite\u0219te de acolo.\n+  RIPPS2_HIDE_GAME: Ascunde jocul\n+  RIPPS2_HIDDEN_GAMES: Jocuri ascunse\n+  HINT_RIPPS2_HIDDEN_GAMES: Jocurile ascunse cu Ascunde jocul, pe unit\u0103\u021bile pornite. Arat\u0103 unul din nou, sau \u0219terge-l. Fiecare unitate \u00ee\u0219i \u021bine propria list\u0103.\n+  HINT_RIPPS2_BDM_DEVICES: 'Unit\u0103\u021bile de pe care BDM cite\u0219te jocuri: USB, MX4SIO, un PC Card CF, unitatea DVR a unui DESR \u0219i iLink. HDD-ul intern urmeaz\u0103 modul de pornire HDD.'\n+  HINT_RIPPS2_SCREEN: 'Imaginea meniului: modul video, ecranul lat, locul ei pe televizor (H-POS, V-POS) \u0219i overscan.'\n+  HINT_RIPPS2_LAYOUT: Unde stau categoriile \u0219i cum intr\u0103 o pagin\u0103, apoi coper\u021bile RIPFLOW \u0219i grila RIPGRID.\n+  HINT_RIPPS2_COLORS_TEXT: Grosimea textului, tema de culoare, trecerea de culori \u0219i fiecare culoare.\n+  HINT_RIPPS2_TEXT_WEIGHT: 'C\u00e2t de gros este textul propriu al RIPPS2: sfaturile din set\u0103ri, STARE \u0219i Despre. Obi\u0219nuit este ca \u00eenainte; Mediu \u0219i Aldin se citesc mai bine pe o imagine moale.'\n+  HINT_RIPPS2_HDD_START: 'HDD-ul intern, APA sau exFAT: RIPPS2 \u00eel cite\u0219te oricum ar fi \u0219i \u00eel arat\u0103 o dat\u0103, ca jocuri HDD. Oprit \u00eel las\u0103 \u00een pace.'\n+  HINT_RIPPS2_THEME_COLOR: Culoarea proprie a temei. Alege\u021bi Personalizat ca tem\u0103 de culoare ca s-o alege\u021bi singur, ca pe culorile textului.\n+  HINT_RIPPS2_SWEEP: Cinci culori prin care trece tema, \u00eencet \u0219i lin. Curcubeu, o trecere f\u0103cut\u0103 din fiecare tem\u0103 de culoare sau a dumneavoastr\u0103. Oprit \u021bine tema nemi\u0219cat\u0103.\n+  HINT_RIPPS2_SWEEP_COLORS: Cele cinci culori ale trecerii \u0219i viteza ei. Schimba\u021bi una de-a RIPPS2 \u0219i se p\u0103streaz\u0103 ca a dumneavoastr\u0103, cu numele dat.\n+  HINT_RIPPS2_SWEEP_NAME: Cum se nume\u0219te trecerea dumneavoastr\u0103 \u00een lista Trecere de culori.\n+  HINT_RIPPS2_SWEEP_COLOR: Una dintre cele cinci culori prin care trece trecerea, \u00een ordine.\n+  HINT_RIPPS2_SWEEP_SPEED: 'C\u00e2t dureaz\u0103 fiecare culoare p\u00e2n\u0103 intr\u0103 urm\u0103toarea. Chiar \u0219i Vioi r\u0103m\u00e2ne lin: culorile nu clipesc niciodat\u0103.'\n+  RIPPS2_HIDE_COMPRESS: Ascunde jocul \u0219i compresie\n+  HINT_RIPPS2_SELECT_HOLD: 'SELECT \u021binut 4.2 s pe o list\u0103 de jocuri. List\u0103: temele \u00eentr-un panou, alege una. Pe r\u00e2nd: tema urm\u0103toare. Oprit: nimic (o ap\u0103sare tot re\u00eemprosp\u0103teaz\u0103).'"
        },
        {
          "sha": "1dc2c60c79dcb09f77c5446d6f2dd5fa0d570289",
          "filename": "elf/lang/Russian.yml",
          "status": "modified",
          "additions": 209,
          "deletions": 22,
          "changes": 231,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FRussian.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FRussian.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FRussian.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: Russian\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTER \u041f\u0420\u0418\u041d\u0418\u041c\u0410\u0415\u0422, ESC \u041e\u0422\u041c\u0415\u041d\u042f\u0415\u0422. \u041b\u042e\u0411\u0410\u042f \u041a\u041d\u041e\u041f\u041a\u0410 \u041f\u041e\u041a\u0410\u0417\u042b\u0412\u0410\u0415\u0422 \u041a\u041b\u0410\u0412\u0418\u0428\u0418.\n   USB KEYBOARD: USB-\u041a\u041b\u0410\u0412\u0418\u0410\u0422\u0423\u0420\u0410\n-  '      DESR DVR ISOs (experimental)': '      ISO DESR DVR (\u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u043e)'\n-  '      Internal HDD (exFAT)': '      \u0412\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 HDD (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (\u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u043e)'\n-  '      Protocol': '      \u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b'\n   Access: \u0414\u043e\u0441\u0442\u0443\u043f\n   Applications Page Start Mode: \u0417\u0430\u043f\u0443\u0441\u043a \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439\n   Art Delay: \u0417\u0430\u0434\u0435\u0440\u0436\u043a\u0430 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439\n@@ -22,7 +18,6 @@ strings:\n   Fifth: \u041f\u044f\u0442\u044b\u0439\n   First: \u041f\u0435\u0440\u0432\u044b\u0439\n   Fourth: \u0427\u0435\u0442\u0432\u0451\u0440\u0442\u044b\u0439\n-  HDD (APA) Start Mode: \u0417\u0430\u043f\u0443\u0441\u043a HDD (APA)\n   HDD Cache: \u041a\u044d\u0448 HDD\n   HDD Spindown: \u0417\u0430\u0441\u044b\u043f\u0430\u043d\u0438\u0435 HDD\n   MMCE Settings: \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 MMCE\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: \u042d\u0442\u043e \u0438\u0433\u0440\u0430 PS2 \u0441 \u043f\u0430\u0442\u0447\u0435\u043c ESR. \u041f\u043e\u043b\u043e\u0436\u0438\u0442\u0435 ESR (esr*.elf) \u0432 APPS, BOOT \u0438\u043b\u0438 ESR \u043d\u0430 \u043a\u0430\u0440\u0442\u0435 \u043f\u0430\u043c\u044f\u0442\u0438 \u0438\u043b\u0438 USB-\u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u0435, \u0447\u0442\u043e\u0431\u044b \u0435\u0451 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c.\n   '%s, BUILD %d': '%s, \u0421\u0411\u041e\u0420\u041a\u0410 %d'\n   '... and the anonymous ...': '... \u0438 \u0431\u0435\u0437\u044b\u043c\u044f\u043d\u043d\u044b\u0435 ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': '1.8.0 \u0431\u0435\u0437 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 (AFL-3.0): github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: \u0420\u0430\u0437\u0431\u043e\u0440\u043a\u0430 \u0438 \u043f\u0435\u0440\u0435\u0441\u0431\u043e\u0440\u043a\u0430 RiptOPL, \u0444\u043e\u0440\u043a\u0430 Open PS2 Loader,\n   ABOUT: \u041e \u041f\u0420\u041e\u0413\u0420\u0410\u041c\u041c\u0415\n   'ACHIEVEMENTS: SPIRIT OF RA': '\u0414\u041e\u0421\u0422\u0418\u0416\u0415\u041d\u0418\u042f: SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): \u0412\u0438\u0434\u0435\u043e AVI \u0432 \u00ab\u0424\u0430\u0439\u043b\u0430\u0445 \u043f\u0430\u043c\u044f\u0442\u0438\u00bb, \u0444\u043e\u0440\u043a RIPPS2 (AFL 2.0)\n   Apollo Save Tool by bucanero: Apollo Save Tool \u043e\u0442 bucanero\n   BACK: \u041d\u0410\u0417\u0410\u0414\n+  Back (keeps both): \u041d\u0430\u0437\u0430\u0434 (\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043e\u0431\u0430)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: \u042d\u0442\u0430 \u043a\u0430\u0440\u0442\u0430 MMCE \u043d\u0435 \u0443\u043c\u0435\u0435\u0442 \u043f\u0435\u0440\u0435\u0438\u043c\u0435\u043d\u043e\u0432\u044b\u0432\u0430\u0442\u044c \u0444\u0430\u0439\u043b\u044b, \u0430 \u0441\u0436\u0430\u0442\u0438\u044e \u044d\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u043d\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u043c \u0448\u0430\u0433\u0435. \u041e\u0431\u043d\u043e\u0432\u0438\u0442\u0435 \u0435\u0451 \u043f\u0440\u043e\u0448\u0438\u0432\u043a\u0443 sd2psXtd \u0434\u043e 1.4.0 \u0438\u043b\u0438 \u043d\u043e\u0432\u0435\u0435 (sd2psxtd.github.io/download) \u0438 \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0441\u043d\u043e\u0432\u0430. \u041d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u043e.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): BIBLE \u044d\u0442\u043e PS2BBL 1.2.0, \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0435 \u0441\u0431\u043e\u0440\u043a\u0438 El Isra (\u043a\u0430\u0440\u0442\u0430 \u0438 HDD), \u0431\u0435\u0437 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: \u0417\u0432\u0443\u043a\u0438 BIOS > \u0418\u0437\u0432\u043b\u0435\u0447\u044c \u0438\u0433\u0440\u0430\u0435\u0442 \u0438\u0445 \u0442\u0430\u043a, \u043a\u0430\u043a SPU2; BIOS \u043d\u0435 \u043f\u0440\u0438\u043b\u0430\u0433\u0430\u0435\u0442\u0441\u044f\n   BOOT LOCK AND BIBLE: BOOT LOCK \u0418 BIBLE\n   BOOT LOCK edits its config too: BOOT LOCK \u043f\u0440\u0430\u0432\u0438\u0442 \u0438 \u0435\u0433\u043e \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e\n   BUILD DETAILS: \u041f\u041e\u0414\u0420\u041e\u0411\u041d\u041e\u0421\u0422\u0418 \u0421\u0411\u041e\u0420\u041a\u0418\n-  BUILT ON RIPTOPL: \u041f\u041e\u0421\u0422\u0420\u041e\u0415\u041d\u041e \u041d\u0410 RIPTOPL\n   BearSSL by Thomas Pornin: BearSSL \u043e\u0442 Thomas Pornin\n   Built with Claude (Anthropic): \u0421\u0434\u0435\u043b\u0430\u043d\u043e \u0441 Claude (Anthropic)\n   CD PLAYER: CD-\u041f\u041b\u0415\u0415\u0420\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek \u0438 Sleek Bold, \u043d\u0430\u0440\u0438\u0441\u043e\u0432\u0430\u043d\u044b \u0434\u043b\u044f RIPPS2\n   RetroAchievements on the console by hacan359 (yoba): RetroAchievements \u043d\u0430 \u043a\u043e\u043d\u0441\u043e\u043b\u0438 \u043e\u0442 hacan359 (yoba)\n   RiptOPL by NathanNeurotic: RiptOPL \u043e\u0442 NathanNeurotic\n-  Roboto Condensed by Google (Apache-2.0): Roboto Condensed \u043e\u0442 Google (Apache-2.0)\n   SOUNDS: \u0417\u0412\u0423\u041a\u0418\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0449\u0438\u043a FMCB \u043e\u0442 SP193 \u0438 OSDMenu \u043e\u0442 pcm720, \u0448\u0430\u0433 \u0437\u0430 \u0448\u0430\u0433\u043e\u043c\n   SUPPORT: \u041f\u041e\u0414\u0414\u0415\u0420\u0416\u041a\u0410\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: wLaunchELF R3Z \u043e\u0442 NathanNeurotic\n   wLaunchELF by the ps2homebrew team: wLaunchELF \u043e\u0442 \u043a\u043e\u043c\u0430\u043d\u0434\u044b ps2homebrew\n   what RIPPS2's card image writes are checked against: \u0441 \u0447\u0435\u043c \u0441\u0432\u0435\u0440\u044f\u044e\u0442\u0441\u044f \u0437\u0430\u043f\u0438\u0441\u0438 \u043e\u0431\u0440\u0430\u0437\u043e\u0432 \u043a\u0430\u0440\u0442 RIPPS2\n-  with its own look, launchers, file browser and save tools.: \u0441\u043e \u0441\u0432\u043e\u0438\u043c \u0432\u0438\u0434\u043e\u043c, \u043b\u0430\u0443\u043d\u0447\u0435\u0440\u0430\u043c\u0438, \u0444\u0430\u0439\u043b\u043e\u0432\u044b\u043c \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043e\u043c \u0438 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0430\u043c\u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0439.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: xeRAbora \u043e\u0442 hacan359, rcheevos \u043e\u0442 RetroAchievements\n   your console's own, ripped from its BIOS: \u0437\u0432\u0443\u043a\u0438 \u0432\u0430\u0448\u0435\u0439 \u043a\u043e\u043d\u0441\u043e\u043b\u0438, \u0438\u0437\u0432\u043b\u0435\u0447\u0451\u043d\u043d\u044b\u0435 \u0438\u0437 \u0435\u0451 BIOS\n   ' (no DNS answer)': ' (\u043d\u0435\u0442 \u043e\u0442\u0432\u0435\u0442\u0430 DNS)'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO: \u0437\u0430\u043f\u0438\u0441\u044c %.60s'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: \u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044c Neutrino \u0432 %s. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u043e\u0435 \u043c\u0435\u0441\u0442\u043e \u0438 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u0437\u0430\u0449\u0438\u0442\u044b \u043e\u0442 \u0437\u0430\u043f\u0438\u0441\u0438 \u0438 \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0441\u043d\u043e\u0432\u0430.\n   THE GAME'S DRIVE  (%s/neutrino): \u041d\u0410\u041a\u041e\u041f\u0418\u0422\u0415\u041b\u042c \u0418\u0413\u0420\u042b  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : \u042d\u0442\u0430 \u0438\u0433\u0440\u0430 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 Neutrino, \u0430 \u0442\u0430\u043c, \u0433\u0434\u0435 \u0438\u0449\u0435\u0442 RIPPS2 (\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438, \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u044c \u0438\u0433\u0440\u044b, \u043a\u0430\u0440\u0442\u044b \u043f\u0430\u043c\u044f\u0442\u0438), Neutrino \u043d\u0435\u0442. RIPPS2 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 Neutrino 1.8.0 \u043e\u0442 rickgaiser (AFL-3.0). \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u0435\u0439\u0447\u0430\u0441? \u0417\u0430\u0442\u0435\u043c \u0432\u044b \u0432\u044b\u0431\u0435\u0440\u0435\u0442\u0435, \u043a\u0443\u0434\u0430.\n   '%d / %d FILES': '%d / %d \u0424\u0410\u0419\u041b\u041e\u0412'\n   '%d FOLDERS AND CONTENTS': '%d \u041f\u0410\u041f\u041e\u041a \u0421 \u0421\u041e\u0414\u0415\u0420\u0416\u0418\u041c\u042b\u041c'\n   '%d FOLDERS, %d FOUND': '%d \u041f\u0410\u041f\u041e\u041a, \u041d\u0410\u0419\u0414\u0415\u041d\u041e %d'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   \u0437\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043e %d \u0438\u0437 %d'\n   '%s   on %s': '%s   \u043d\u0430 %s'\n   BADGES: \u0417\u041d\u0410\u0427\u041a\u0418\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: \u0417\u043d\u0430\u0447\u043a\u0438 \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0451\u043d\u043d\u043e\u0439 \u0422\u0415\u041b\u0415\u041c\u0415\u0422\u0420\u0418\u0418 \u0438 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u043e\u043c \u043d\u0430 \u0432\u0430\u0448\u0435\u043c \u041f\u041a xeRAbora; \u0434\u043e \u0442\u0435\u0445 \u043f\u043e\u0440 \u043e\u043d\u0438 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0441\u0442\u043e\u044f\u0442.\n   CHECK THE DISC: \u041f\u0420\u041e\u0412\u0415\u0420\u0418\u0422\u042c \u0414\u0418\u0421\u041a\n   From this game's last CHECK GAME SUPPORT.: \u041f\u043e \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0439 \u041f\u0420\u041e\u0412\u0415\u0420\u041a\u0415 \u041f\u041e\u0414\u0414\u0415\u0420\u0416\u041a\u0418 \u044d\u0442\u043e\u0439 \u0438\u0433\u0440\u044b.\n   HOW IT WORKS: \u041a\u0410\u041a \u042d\u0422\u041e \u0420\u0410\u0411\u041e\u0422\u0410\u0415\u0422\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: \u041d\u0430 \u044d\u0442\u043e\u0439 \u043a\u043e\u043d\u0441\u043e\u043b\u0438 \u0435\u0441\u0442\u044c PS2BBL \u0441 \u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043a\u043e\u0439 RIPPS2.\n   AUDIO folder: \u043f\u0430\u043f\u043a\u0430 AUDIO\n   DRIVES: \u041d\u0410\u041a\u041e\u041f\u0418\u0422\u0415\u041b\u0418\n-  INSTALL RIPPS2'S COPY: \u0423\u0421\u0422\u0410\u041d\u041e\u0412\u0418\u0422\u042c \u041a\u041e\u041f\u0418\u042e RIPPS2\n-  'Installed: ': '\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e: '\n   MANAGE: \u0423\u041f\u0420\u0410\u0412\u041b\u0415\u041d\u0418\u0415\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: \u0423\u041f\u0420\u0410\u0412\u041b\u0415\u041d\u0418\u0415 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442, \u0447\u0442\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u043a\u0430\u0436\u0434\u0430\u044f \u043a\u0430\u0440\u0442\u0430 \u043f\u0430\u043c\u044f\u0442\u0438, \u0438 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u0438\u043b\u0438 \u0438\u0441\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 BIBLE.\n   MEMORY CARD 0  (about 600 KB): \u041a\u0410\u0420\u0422\u0410 \u041f\u0410\u041c\u042f\u0422\u0418 0  (\u043e\u043a\u043e\u043b\u043e 600 \u041a\u0411)\n   MEMORY CARD 1  (about 600 KB): \u041a\u0410\u0420\u0422\u0410 \u041f\u0410\u041c\u042f\u0422\u0418 1  (\u043e\u043a\u043e\u043b\u043e 600 \u041a\u0411)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO: \u0435\u0433\u043e \u0434\u0440\u0430\u0439\u0432\u0435\u0440 \u043d\u0435 \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u043b\u0441\u044f (\u043a\u043e\u0434 %d).'\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO: \u0434\u0440\u0430\u0439\u0432\u0435\u0440 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d, \u043d\u043e \u043d\u0438 \u043e\u0434\u043d\u0430 SD-\u043a\u0430\u0440\u0442\u0430 \u043d\u0435 \u043e\u0442\u0432\u0435\u0442\u0438\u043b\u0430 (\u0441\u043b\u043e\u0442 mc1).'\n   NEUTRINO SETTINGS: \u041d\u0410\u0421\u0422\u0420\u041e\u0419\u041a\u0418 NEUTRINO\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': '\u041f\u043e\u043a\u0430 \u043d\u0435\u0442: \u0443 RIPPS2 \u043d\u0435\u0442 \u0441\u0432\u043e\u0438\u0445 \u0437\u0432\u0443\u043a\u043e\u0432. \u0418\u0437\u0432\u043b\u0435\u043a\u0438\u0442\u0435 \u0437\u0432\u0443\u043a\u0438 \u044d\u0442\u043e\u0439 \u043a\u043e\u043d\u0441\u043e\u043b\u0438 \u0438\u043b\u0438 \u0441\u0434\u0435\u043b\u0430\u0439\u0442\u0435 \u0438\u0445 \u0432 RIPPS2 Audio Studio.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: \u041d\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d. \u0418\u0433\u0440\u0430, \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043e\u043d \u043d\u0443\u0436\u0435\u043d, \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0438\u0442 \u043a\u043e\u043f\u0438\u044e RIPPS2, \u0438\u043b\u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0435 \u0435\u0433\u043e \u0437\u0434\u0435\u0441\u044c.\n   POPSTARTER NETWORK: \u0421\u0415\u0422\u042c POPSTARTER\n   POPSTARTER and Ember: POPSTARTER \u0438 Ember\n   PS1 SETTINGS: \u041d\u0410\u0421\u0422\u0420\u041e\u0419\u041a\u0418 PS1\n   PS2BBL 1.2.0 for RIPPS2: PS2BBL 1.2.0 \u0434\u043b\u044f RIPPS2\n   RIP THIS CONSOLE'S SOUNDS: \u0418\u0417\u0412\u041b\u0415\u0427\u042c \u0417\u0412\u0423\u041a\u0418 \u042d\u0422\u041e\u0419 \u041a\u041e\u041d\u0421\u041e\u041b\u0418\n-  RIPPS2's homebrew: Homebrew RIPPS2\n   STATUS: \u0421\u041e\u0421\u0422\u041e\u042f\u041d\u0418\u0415\n-  'STATUS: looking for RIPPS2''s homebrew': '\u0421\u041e\u0421\u0422\u041e\u042f\u041d\u0418\u0415: \u043f\u043e\u0438\u0441\u043a homebrew RIPPS2'\n   a drive of unknown kind: \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u044c \u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u043e\u0433\u043e \u0442\u0438\u043f\u0430\n-  a drive or a memory card: \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u044c \u0438\u043b\u0438 \u043a\u0430\u0440\u0442\u0430 \u043f\u0430\u043c\u044f\u0442\u0438\n   boot loader, card by card: \u0437\u0430\u0433\u0440\u0443\u0437\u0447\u0438\u043a, \u043a\u0430\u0440\u0442\u0430 \u0437\u0430 \u043a\u0430\u0440\u0442\u043e\u0439\n   from its BIOS, into AUDIO: \u0438\u0437 \u0435\u0451 BIOS, \u0432 AUDIO\n   internal HDD (exFAT): \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 HDD (exFAT)\n@@ -975,10 +958,194 @@ strings:\n   Sidebar: \u0411\u043e\u043a\u043e\u0432\u0430\u044f \u043f\u0430\u043d\u0435\u043b\u044c\n   your own copy, found on your drives and memory cards: \u0432\u0430\u0448\u0430 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u0430\u044f \u043a\u043e\u043f\u0438\u044f, \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u0430\u044f \u043d\u0430 \u0432\u0430\u0448\u0438\u0445 \u0434\u0438\u0441\u043a\u0430\u0445 \u0438 \u043a\u0430\u0440\u0442\u0430\u0445 \u043f\u0430\u043c\u044f\u0442\u0438\n   'your own, found and launched: github.com/Gageformer/Ember/releases': '\u0432\u0430\u0448 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439, \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u0439 \u0438 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0439: github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': '\u041d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d \u043d\u0430 mass0: (%s). \u041f\u043e\u043b\u043e\u0436\u0438\u0442\u0435 ember.elf \u0438 \u0441\u0432\u043e\u0439 bios.bin \u0432 \u0435\u0433\u043e \u043f\u0430\u043f\u043a\u0443 EMBER.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: \u041d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d. \u041f\u043e\u043b\u043e\u0436\u0438\u0442\u0435 \u0441\u0432\u043e\u0439 POPSTARTER.ELF \u0432 \u043f\u0430\u043f\u043a\u0443 POPS \u043d\u0430 \u0434\u0438\u0441\u043a\u0435.\n   Launch Disc: \u0417\u0430\u043f\u0443\u0441\u043a \u0434\u0438\u0441\u043a\u0430\n   Memory Files: \u0424\u0430\u0439\u043b\u044b \u043f\u0430\u043c\u044f\u0442\u0438\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: RIPPS2 \u043d\u0430\u0448\u0451\u043b %d \u0438\u0437 5. \u041a\u0430\u0436\u0434\u044b\u0439 \u0437\u0430\u043f\u043e\u043c\u0438\u043d\u0430\u0435\u0442\u0441\u044f, \u0438 \u043a\u0430\u0436\u0434\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a \u0431\u0435\u0440\u0451\u0442 \u0435\u0433\u043e \u043e\u0442\u0442\u0443\u0434\u0430.\n+  not found on the started drives: \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e \u043d\u0430 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0445 \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u044f\u0445\n+  not looked for yet: \u0435\u0449\u0451 \u043d\u0435 \u0438\u0441\u043a\u0430\u043b\u0438\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s \u043d\u0435 \u043e\u0442\u0432\u0435\u0442\u0438\u043b, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 %s \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c\u0441\u044f. \u041e\u043d \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0451\u043d?'\n+  '%s is not on %s any more, so it has left All Games.': '%s \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435\u0442 \u043d\u0430 %s, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0438\u0433\u0440\u0430 \u0443\u0448\u043b\u0430 \u0438\u0437 \u00ab\u0412\u0441\u0435\u0445 \u0438\u0433\u0440\u00bb.'\n+  STARTING %s: \u0417\u0410\u041f\u0423\u0421\u041a %s\n+  the SMB share: \u043e\u0431\u0449\u0430\u044f \u043f\u0430\u043f\u043a\u0430 SMB\n+  the drive: \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u044c\n+  the internal HDD (APA): \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 HDD (APA)\n+  the internal HDD (exFAT): \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 HDD (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: \u041f\u041e\u0421\u0422\u0420\u041e\u0415\u041d \u041d\u0410 RIPTOPL, \u0421 NEUTRINO\n+  'Special thanks to all the YouTube love as well:': '\u041e\u0441\u043e\u0431\u0430\u044f \u0431\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u043d\u043e\u0441\u0442\u044c \u0438 \u0432\u0441\u0435\u0439 \u043b\u044e\u0431\u0432\u0438 \u0441 YouTube:'\n+  and its own look, launchers, file browser and save tools.: \u0438 \u0441\u043e \u0441\u0432\u043e\u0438\u043c \u0432\u0438\u0434\u043e\u043c, \u0437\u0430\u0433\u0440\u0443\u0437\u0447\u0438\u043a\u0430\u043c\u0438, \u0444\u0430\u0439\u043b\u043e\u0432\u044b\u043c \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043e\u043c \u0438 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0430\u043c\u0438 \u0434\u043b\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0439.\n+  glad to help however you reach me.: \u0440\u0430\u0434 \u043f\u043e\u043c\u043e\u0447\u044c, \u043a\u0430\u043a \u0431\u044b \u0432\u044b \u0441\u043e \u043c\u043d\u043e\u0439 \u043d\u0438 \u0441\u0432\u044f\u0437\u0430\u043b\u0438\u0441\u044c.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): \u0432\u0442\u043e\u0440\u043e\u0435 \u0438\u0433\u0440\u043e\u0432\u043e\u0435 \u044f\u0434\u0440\u043e, 1.8.0 \u0432 \u043d\u0435\u0438\u0437\u043c\u0435\u043d\u043d\u043e\u043c \u0432\u0438\u0434\u0435 (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: \u0441 Neutrino \u043e\u0442 rickgaiser \u0432\u043d\u0443\u0442\u0440\u0438 \u043a\u0430\u043a \u0432\u0442\u043e\u0440\u044b\u043c \u0438\u0433\u0440\u043e\u0432\u044b\u043c \u044f\u0434\u0440\u043e\u043c,\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : '\u042d\u0442\u0430 \u0438\u0433\u0440\u0430 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 Neutrino, \u0430 RIPPS2 \u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0448\u0451\u043b: \u043d\u0438 \u043f\u043e \u043f\u0443\u0442\u0438 \u0438\u0437 \u041d\u0430\u0441\u0442\u0440\u043e\u0435\u043a, \u043d\u0438 \u043d\u0430 \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u0435 \u0438\u0433\u0440\u044b, \u043d\u0438 \u043d\u0430 \u043a\u0430\u0440\u0442\u0430\u0445 \u043f\u0430\u043c\u044f\u0442\u0438, \u043d\u0438 \u043d\u0430 \u043e\u0434\u043d\u043e\u043c \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u043e\u043c \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u0435. \u0412 RIPPS2 \u0435\u0441\u0442\u044c Neutrino 1.8.0 \u043e\u0442 rickgaiser (AFL-3.0): \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0435 \u0435\u0433\u043e \u043e\u0434\u0438\u043d \u0440\u0430\u0437, \u043a\u0443\u0434\u0430 \u0437\u0430\u0445\u043e\u0442\u0438\u0442\u0435, \u0438 \u043a\u0430\u0436\u0434\u0430\u044f \u0438\u0433\u0440\u0430 \u0441 Neutrino \u0431\u0443\u0434\u0435\u0442 \u0431\u0440\u0430\u0442\u044c \u0435\u0433\u043e \u043e\u0442\u0442\u0443\u0434\u0430.'\n+  STARTING THE DRIVES: \u0417\u0410\u041f\u0423\u0421\u041a \u041d\u0410\u041a\u041e\u041f\u0418\u0422\u0415\u041b\u0415\u0419\n+  '%d games, on the started drives': '%d \u0438\u0433\u0440, \u043d\u0430 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0445 \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u044f\u0445'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s: \u041d\u0415\u041a\u041e\u0422\u041e\u0420\u042b\u0415 \u0424\u0410\u0419\u041b\u042b \u041d\u0415 \u0423\u0414\u0410\u041b\u041e\u0421\u042c \u0423\u0414\u0410\u041b\u0418\u0422\u042c'\n+  '%u partitions, all in order': '%u \u0440\u0430\u0437\u0434\u0435\u043b\u043e\u0432, \u0432\u0441\u0435 \u0432 \u043f\u043e\u0440\u044f\u0434\u043a\u0435'\n+  ', %d art images': ', %d \u043e\u0431\u043b\u043e\u0436\u0435\u043a'\n+  ', and ': ', \u0438 '\n+  ', its cheats': ', \u0435\u0451 \u0447\u0438\u0442\u044b'\n+  ', its settings': ', \u0435\u0451 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438'\n+  1 game, on the started drives: 1 \u0438\u0433\u0440\u0430, \u043d\u0430 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0445 \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u044f\u0445\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: \u0418\u0433\u0440\u0430 PS1 \u043e\u0442\u0441\u044e\u0434\u0430 \u0443\u0434\u0430\u043b\u044f\u0435\u0442\u0441\u044f \u0432 \u00ab\u0424\u0430\u0439\u043b\u0430\u0445 \u043f\u0430\u043c\u044f\u0442\u0438\u00bb, \u0433\u0434\u0435 \u043e\u043d\u0430 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0444\u0430\u0439\u043b.\n+  A network catalogue's games are deleted on the PC that serves them.: \u0418\u0433\u0440\u044b \u0441\u0435\u0442\u0435\u0432\u043e\u0433\u043e \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0430 \u0443\u0434\u0430\u043b\u044f\u044e\u0442\u0441\u044f \u043d\u0430 \u041f\u041a, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0438\u0445 \u0440\u0430\u0437\u0434\u0430\u0451\u0442.\n+  DELETE FOR GOOD: \u0423\u0414\u0410\u041b\u0418\u0422\u042c \u041d\u0410\u0412\u0421\u0415\u0413\u0414\u0410\n+  DELETE IT: \u0423\u0414\u0410\u041b\u0418\u0422\u042c \u0415\u0401\n+  'DELETED %s: %s FREED': '%s \u0423\u0414\u0410\u041b\u0415\u041d\u0410: \u041e\u0421\u0412\u041e\u0411\u041e\u0416\u0414\u0415\u041d\u041e %s'\n+  'Delete %s from %s? Goes: %s.': '\u0423\u0434\u0430\u043b\u0438\u0442\u044c %s \u0441 %s? \u0423\u0445\u043e\u0434\u0438\u0442: %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': '\u0423\u0434\u0430\u043b\u0438\u0442\u044c %s \u0441 %s? \u0423\u0445\u043e\u0434\u0438\u0442: %s. \u041e\u0441\u0442\u0430\u0451\u0442\u0441\u044f: %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: \u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0442\u0430\u043a\u0436\u0435 \u0435\u0451 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0438 \u0441\u043d\u0438\u043c\u043a\u0438 RIPSHOT? \u00ab\u041d\u0435\u0442\u00bb \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0438\u0445.\n+  HIDDEN GAMES: \u0421\u041a\u0420\u042b\u0422\u042b\u0415 \u0418\u0413\u0420\u042b\n+  'HIDDEN: %s': '\u0421\u041a\u0420\u042b\u0422\u041e: %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: \u0421\u043a\u0440\u044b\u0442\u044c %s? \u0418\u0433\u0440\u0430 \u0443\u0439\u0434\u0451\u0442 \u0438\u0437 \u0432\u0441\u0435\u0445 \u0441\u043f\u0438\u0441\u043a\u043e\u0432, \u0438\u0437 \u00ab\u0412\u0441\u0435\u0445 \u0438\u0433\u0440\u00bb \u0438 \u00ab\u0418\u0437\u0431\u0440\u0430\u043d\u043d\u043e\u0433\u043e\u00bb \u0442\u043e\u0436\u0435. \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 > \u0418\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0438 \u0438\u0433\u0440 > \u0421\u043a\u0440\u044b\u0442\u044b\u0435 \u0438\u0433\u0440\u044b \u043f\u043e\u043a\u0430\u0436\u0443\u0442 \u0435\u0451 \u0441\u043d\u043e\u0432\u0430.\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: \u041f\u043e\u0441\u043b\u0435\u0434\u043d\u044f\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430. %s \u0443\u0434\u0430\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u0430\u0432\u0441\u0435\u0433\u0434\u0430, \u0438 \u043d\u0438\u0447\u0442\u043e \u0435\u0451 \u043d\u0435 \u0432\u0435\u0440\u043d\u0451\u0442. \u041d\u0430\u0436\u043c\u0438\u0442\u0435 START, \u0447\u0442\u043e\u0431\u044b \u0443\u0434\u0430\u043b\u0438\u0442\u044c.\n+  None. Hide Game, in a game's options, puts one here.: \u041d\u0435\u0442. \u00ab\u0421\u043a\u0440\u044b\u0442\u044c \u0438\u0433\u0440\u0443\u00bb \u0432 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u0445 \u0438\u0433\u0440\u044b \u043f\u043e\u043c\u0435\u0449\u0430\u0435\u0442 \u0435\u0451 \u0441\u044e\u0434\u0430.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : '\u041d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0443\u0434\u0430\u043b\u0435\u043d\u043e: \u0442\u0430\u0431\u043b\u0438\u0446\u0430 \u0440\u0430\u0437\u0434\u0435\u043b\u043e\u0432 \u044d\u0442\u043e\u0433\u043e \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u044f \u043d\u0435 \u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0442 \u044d\u0442\u043e\u043c\u0443 \u0434\u0438\u0441\u043a\u0443 (%s). \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e, \u043e\u043d\u0430 \u0437\u0430\u043f\u0438\u0441\u0430\u043d\u0430 \u0438\u0437 \u043e\u0431\u0440\u0430\u0437\u0430 \u0434\u0438\u0441\u043a\u0430 \u0434\u0440\u0443\u0433\u043e\u0433\u043e \u0440\u0430\u0437\u043c\u0435\u0440\u0430. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u0438\u0441\u043f\u0440\u0430\u0432\u044c\u0442\u0435 \u0435\u0451 \u043d\u0430 \u041f\u041a.'\n+  RIPPS2 cannot delete a game from this page.: RIPPS2 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0438\u0433\u0440\u0443 \u0441 \u044d\u0442\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b.\n+  RIPPS2 could not find this game's files on its drive.: RIPPS2 \u043d\u0435 \u043d\u0430\u0448\u0451\u043b \u0444\u0430\u0439\u043b\u044b \u044d\u0442\u043e\u0439 \u0438\u0433\u0440\u044b \u043d\u0430 \u0435\u0451 \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u0435.\n+  Reading the hidden lists: \u0427\u0442\u0435\u043d\u0438\u0435 \u0441\u043f\u0438\u0441\u043a\u043e\u0432 \u0441\u043a\u0440\u044b\u0442\u044b\u0445\n+  SHOW AGAIN OR DELETE: \u041f\u041e\u041a\u0410\u0417\u0410\u0422\u042c \u0421\u041d\u041e\u0412\u0410 \u0418\u041b\u0418 \u0423\u0414\u0410\u041b\u0418\u0422\u042c\n+  SHOW IT AGAIN: \u041f\u041e\u041a\u0410\u0417\u0410\u0422\u042c \u0421\u041d\u041e\u0412\u0410\n+  That game is not on its drive any more.: \u042d\u0442\u043e\u0439 \u0438\u0433\u0440\u044b \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435\u0442 \u043d\u0430 \u0435\u0451 \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u0435.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: \u0421\u043f\u0438\u0441\u043e\u043a \u0441\u043a\u0440\u044b\u0442\u044b\u0445 \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0430 \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u044c \u0438\u0433\u0440\u044b. \u041e\u043d \u0437\u0430\u0449\u0438\u0449\u0451\u043d \u043e\u0442 \u0437\u0430\u043f\u0438\u0441\u0438, \u0438\u043b\u0438 \u041e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u0437\u0430\u043f\u0438\u0441\u0438 \u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u044b?\n+  The partition could not be removed (%d). Nothing was deleted.: \u0420\u0430\u0437\u0434\u0435\u043b \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0443\u0431\u0440\u0430\u0442\u044c (%d). \u041d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0443\u0434\u0430\u043b\u0435\u043d\u043e.\n+  This drive cannot keep a hidden list (a network catalogue).: \u042d\u0442\u043e\u0442 \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u044c \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u0441\u043a\u0440\u044b\u0442\u044b\u0445 (\u0441\u0435\u0442\u0435\u0432\u043e\u0439 \u043a\u0430\u0442\u0430\u043b\u043e\u0433).\n+  This game's drive has not answered yet. Wait for it, then try again.: \u041d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u044c \u044d\u0442\u043e\u0439 \u0438\u0433\u0440\u044b \u0435\u0449\u0451 \u043d\u0435 \u043e\u0442\u0432\u0435\u0442\u0438\u043b. \u0414\u043e\u0436\u0434\u0438\u0442\u0435\u0441\u044c \u0435\u0433\u043e \u0438 \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0441\u043d\u043e\u0432\u0430.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: \u0420\u0430\u0437\u0434\u0435\u043b \u044d\u0442\u043e\u0439 \u0438\u0433\u0440\u044b \u0438\u0437 \u0442\u0435\u0445, \u0447\u0442\u043e RIPPS2 \u043d\u0435 \u0443\u0431\u0438\u0440\u0430\u0435\u0442 (\u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0439 \u0440\u0430\u0437\u0434\u0435\u043b \u0438\u043b\u0438 \u0442\u043e\u0442, \u0433\u0434\u0435 \u0435\u0433\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': '\u0440\u0430\u0437\u0434\u0435\u043b \u0431\u043e\u043b\u044c\u0448\u0435, \u0447\u0435\u043c \u0434\u043e\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u044d\u0442\u043e\u0442 \u0434\u0438\u0441\u043a: \u0442\u0430\u0431\u043b\u0438\u0446\u0430 \u0441\u0434\u0435\u043b\u0430\u043d\u0430 \u0434\u043b\u044f \u0434\u0438\u0441\u043a\u0430 \u0434\u0440\u0443\u0433\u043e\u0433\u043e \u0440\u0430\u0437\u043c\u0435\u0440\u0430'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': '\u0440\u0430\u0437\u0434\u0435\u043b \u0432\u044b\u0445\u043e\u0434\u0438\u0442 \u0437\u0430 \u043a\u043e\u043d\u0435\u0446 \u044d\u0442\u043e\u0433\u043e \u0434\u0438\u0441\u043a\u0430 (\u0441\u0435\u043a\u0442\u043e\u0440 %u \u0438\u0437 %u): \u0442\u0430\u0431\u043b\u0438\u0446\u0430 \u0441\u0434\u0435\u043b\u0430\u043d\u0430 \u0434\u043b\u044f \u0434\u0438\u0441\u043a\u0430 \u043f\u043e\u0431\u043e\u043b\u044c\u0448\u0435'\n+  its RIPSHOT pictures: \u0435\u0451 \u0441\u043d\u0438\u043c\u043a\u0438 RIPSHOT\n+  its drive: \u0435\u0451 \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u044c\n+  its saves: \u0435\u0451 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f\n+  sector %u holds no partition header: \u0432 \u0441\u0435\u043a\u0442\u043e\u0440\u0435 %u \u043d\u0435\u0442 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430 \u0440\u0430\u0437\u0434\u0435\u043b\u0430\n+  the chain does not close at __mbr: \u0446\u0435\u043f\u043e\u0447\u043a\u0430 \u043d\u0435 \u0437\u0430\u043c\u044b\u043a\u0430\u0435\u0442\u0441\u044f \u043d\u0430 __mbr\n+  the chain skips from sector %u: \u0446\u0435\u043f\u043e\u0447\u043a\u0430 \u043f\u0435\u0440\u0435\u0441\u043a\u0430\u043a\u0438\u0432\u0430\u0435\u0442 \u0441 \u0441\u0435\u043a\u0442\u043e\u0440\u0430 %u\n+  the chain's links disagree at sector %u: \u0437\u0432\u0435\u043d\u044c\u044f \u0446\u0435\u043f\u043e\u0447\u043a\u0438 \u043d\u0435 \u0441\u0445\u043e\u0434\u044f\u0442\u0441\u044f \u0432 \u0441\u0435\u043a\u0442\u043e\u0440\u0435 %u\n+  the drive's size could not be read: \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0440\u0430\u0437\u043c\u0435\u0440 \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u044f\n+  the game (%s): \u0438\u0433\u0440\u0430 (%s)\n+  the game's folder and everything in it (%s): \u043f\u0430\u043f\u043a\u0430 \u0438\u0433\u0440\u044b \u0438 \u0432\u0441\u0451 \u0432 \u043d\u0435\u0439 (%s)\n+  the header at sector %u could not be read: \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u0432 \u0441\u0435\u043a\u0442\u043e\u0440\u0435 %u\n+  the header at sector %u fails its checksum: \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u0432 \u0441\u0435\u043a\u0442\u043e\u0440\u0435 %u \u043d\u0435 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044c\u043d\u0443\u044e \u0441\u0443\u043c\u043c\u0443\n+  the partition at sector %u is not where or as large as APA puts one: \u0440\u0430\u0437\u0434\u0435\u043b \u0432 \u0441\u0435\u043a\u0442\u043e\u0440\u0435 %u \u043b\u0435\u0436\u0438\u0442 \u043d\u0435 \u0442\u0430\u043c \u0438\u043b\u0438 \u043d\u0435 \u0442\u0430\u043a\u043e\u0433\u043e \u0440\u0430\u0437\u043c\u0435\u0440\u0430, \u043a\u0430\u043a \u0435\u0433\u043e \u0441\u043e\u0437\u0434\u0430\u0451\u0442 APA\n+  the settings and art another copy of it uses: \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438 \u043e\u0431\u043b\u043e\u0436\u043a\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0434\u0440\u0443\u0433\u0430\u044f \u0435\u0451 \u043a\u043e\u043f\u0438\u044f\n+  '%d FOLDERS': '%d \u041f\u0410\u041f\u041e\u041a'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': '%s \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432 %s. \u041d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u044c \u043f\u043e\u043b\u043e\u043d \u0438\u043b\u0438 \u0437\u0430\u0449\u0438\u0449\u0451\u043d \u043e\u0442 \u0437\u0430\u043f\u0438\u0441\u0438?'\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s \u043d\u0435\u0442 \u0442\u0430\u043c, \u0433\u0434\u0435 \u0438\u0449\u0435\u0442 RIPPS2. \u041f\u043e\u0438\u0441\u043a\u0430\u0442\u044c \u043d\u0430 \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u044f\u0445, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u043b RIPPS2? \u041d\u0430\u0439\u0434\u0435\u043d\u043d\u043e\u0435 \u043c\u0435\u0441\u0442\u043e \u0437\u0430\u043f\u043e\u043c\u043d\u0438\u0442\u0441\u044f \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430.'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Ember \u043d\u0435\u0442 \u0432 %s, \u0440\u044f\u0434\u043e\u043c \u0441 \u0438\u0433\u0440\u0430\u043c\u0438, \u0430 \u043e\u043d \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0442\u0430\u043c. RIPPS2 \u043d\u0430\u0448\u0451\u043b \u0435\u0433\u043e \u0432 %s: START \u043a\u043e\u043f\u0438\u0440\u0443\u0435\u0442 \u0435\u0433\u043e \u0442\u0443\u0434\u0430 (%d \u041a\u0411) \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u0438\u0433\u0440\u0443.'\n+  Ember's BIOS: BIOS \u0434\u043b\u044f Ember\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : 'BIOS \u0434\u043b\u044f Ember (bios.bin) \u043d\u0435\u0442 \u0432 %s, \u0440\u044f\u0434\u043e\u043c \u0441 Ember. RIPPS2 \u043d\u0430\u0448\u0451\u043b \u0435\u0433\u043e \u0432 %s: START \u043a\u043e\u043f\u0438\u0440\u0443\u0435\u0442 \u0435\u0433\u043e \u0442\u0443\u0434\u0430 (%d \u041a\u0411) \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u0438\u0433\u0440\u0443.'\n+  LOOKING FOR YOUR LAUNCHERS: \u0418\u0429\u0423 \u0412\u0410\u0428\u0418 \u0417\u0410\u0413\u0420\u0423\u0417\u0427\u0418\u041a\u0418\n+  The POPS files: \u0424\u0430\u0439\u043b\u044b POPS\n+  '%s  MMCE card': '%s  \u043a\u0430\u0440\u0442\u0430 MMCE'\n+  INSTALL RIPPS2'S NEUTRINO: \u0423\u0421\u0422\u0410\u041d\u041e\u0412\u0418\u0422\u042c NEUTRINO \u0418\u0417 RIPPS2\n+  'Internal HDD (APA): %s free of %s': '\u0412\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 HDD (APA): \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u043e %s \u0438\u0437 %s'\n+  LAUNCHERS: \u0417\u0410\u0413\u0420\u0423\u0417\u0427\u0418\u041a\u0418\n+  LOOK FOR THEM: \u041d\u0410\u0419\u0422\u0418 \u0418\u0425\n+  'Memory card %d: %d KB free': '\u041a\u0430\u0440\u0442\u0430 \u043f\u0430\u043c\u044f\u0442\u0438 %d: \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u043e %d \u041a\u0411'\n+  POPS files: \u0424\u0430\u0439\u043b\u044b POPS\n+  START EVERY DRIVE: \u0417\u0410\u041f\u0423\u0421\u0422\u0418\u0422\u042c \u0412\u0421\u0415 \u041d\u0410\u041a\u041e\u041f\u0418\u0422\u0415\u041b\u0418\n+  'STATUS: looking at the drives and the homebrew': '\u0421\u041e\u0421\u0422\u041e\u042f\u041d\u0418\u0415: \u0441\u043c\u043e\u0442\u0440\u044e \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u0438 \u0438 homebrew'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0432\u0441\u0435 \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u0438, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0438\u0441\u043a \u0434\u043e\u0448\u0451\u043b \u0434\u043e \u043a\u0430\u0436\u0434\u043e\u0433\u043e? (\u00ab\u041d\u0435\u0442\u00bb \u0438\u0449\u0435\u0442 \u043d\u0430 \u0443\u0436\u0435 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0445.)\n+  USB, HDD, MMCE and the rest: USB, HDD, MMCE \u0438 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0435\n+  every started drive: \u043a\u0430\u0436\u0434\u044b\u0439 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0439 \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u044c\n+  found where you keep them: \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0442\u0430\u043c, \u0433\u0434\u0435 \u0432\u044b \u0438\u0445 \u0434\u0435\u0440\u0436\u0438\u0442\u0435\n+  not found yet: \u043f\u043e\u043a\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e\n+  not on a started drive: \u043d\u0435 \u043d\u0430 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u043e\u043c \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u0435\n+  only if you have none: \u0442\u043e\u043b\u044c\u043a\u043e \u0435\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0435\u0433\u043e \u043d\u0435\u0442\n+  starts every drive first: \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u0432\u0441\u0435 \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u0438\n+  Drives, launchers and homebrew: \u041d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u0438, \u0437\u0430\u0433\u0440\u0443\u0437\u0447\u0438\u043a\u0438 \u0438 homebrew\n+  BDM Devices: \u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 BDM\n+  Color Sweep: \u0421\u043c\u0435\u043d\u0430 \u0446\u0432\u0435\u0442\u043e\u0432\n+  Colors and Text: \u0426\u0432\u0435\u0442\u0430 \u0438 \u0442\u0435\u043a\u0441\u0442\n+  HDD POPS partitions: \u0420\u0430\u0437\u0434\u0435\u043b\u044b POPS \u043d\u0430 HDD\n+  Layout: \u0420\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\n+  Missing Covers: \u041d\u0435\u0434\u043e\u0441\u0442\u0430\u044e\u0449\u0438\u0435 \u043e\u0431\u043b\u043e\u0436\u043a\u0438\n+  Name: \u0418\u043c\u044f\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 SMB \u0434\u043b\u044f POPSTARTER \u0435\u0449\u0451 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430. \u0417\u0430\u043f\u043e\u043b\u043d\u0438\u0442\u0435 \u0441\u0442\u0440\u043e\u043a\u0438 \u043d\u0438\u0436\u0435.\n+  POPSTARTER Network: \u0421\u0435\u0442\u044c POPSTARTER\n+  POPSTARTER SMB Network: \u0421\u0435\u0442\u044c SMB \u0434\u043b\u044f POPSTARTER\n+  POPSTARTER UDPFS Network: \u0421\u0435\u0442\u044c UDPFS \u0434\u043b\u044f POPSTARTER\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: RIPPS2 \u0441\u0430\u043c \u0434\u043e\u0431\u0430\u0432\u0438\u0442 \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u044c, ISO, VMC \u0438 \u0432\u0438\u0434\u0435\u043e\u0440\u0435\u0436\u0438\u043c.\n+  Screen: \u042d\u043a\u0440\u0430\u043d\n+  Speed: \u0421\u043a\u043e\u0440\u043e\u0441\u0442\u044c\n+  Sweep Colors: \u0426\u0432\u0435\u0442\u0430 \u0441\u043c\u0435\u043d\u044b\n+  Text Weight: \u0422\u043e\u043b\u0449\u0438\u043d\u0430 \u0442\u0435\u043a\u0441\u0442\u0430\n+  Theme Color: \u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0446\u0432\u0435\u0442\n+  Audio: \u0417\u0432\u0443\u043a\n+  Bold: \u0416\u0438\u0440\u043d\u044b\u0439\n+  Controllers: \u041a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u044b\n+  Lively: \u0416\u0438\u0432\u043e\n+  Medium: \u0421\u0440\u0435\u0434\u043d\u0438\u0439\n+  PS Emulation: \u042d\u043c\u0443\u043b\u044f\u0446\u0438\u044f PS\n+  Regular: \u041e\u0431\u044b\u0447\u043d\u044b\u0439\n+  Slow: \u041c\u0435\u0434\u043b\u0435\u043d\u043d\u043e\n+  A MOMENT: \u041c\u0418\u041d\u0423\u0422\u041a\u0423\n+  A TASK IN THE BACKGROUND: \u0417\u0410\u0414\u0410\u0427\u0410 \u0412 \u0424\u041e\u041d\u0415\n+  ABOUT %d S: \u041e\u041a\u041e\u041b\u041e %d \u0421\n+  ANY MOMENT: \u0412\u041e\u0422-\u0412\u041e\u0422\n+  READING THE GAME LIST OF %s: \u0427\u0418\u0422\u0410\u0415\u0422 \u0421\u041f\u0418\u0421\u041e\u041a \u0418\u0413\u0420 \u0421 %s\n+  SAVING A LIST: \u0421\u041e\u0425\u0420\u0410\u041d\u042f\u0415\u0422 \u0421\u041f\u0418\u0421\u041e\u041a\n+  START NOW: \u0417\u0410\u041f\u0423\u0421\u0422\u0418\u0422\u042c \u0421\u0415\u0419\u0427\u0410\u0421\n+  STARTING: \u0417\u0410\u041f\u0423\u0421\u041a\n+  STARTS NEXT: \u0417\u0410\u041f\u0423\u0421\u0422\u0418\u0422\u0421\u042f \u0421\u041b\u0415\u0414\u041e\u041c\n+  STOPPING IT SAFELY: \u041e\u0421\u0422\u0410\u041d\u0410\u0412\u041b\u0418\u0412\u0410\u0415\u0422 \u0411\u0415\u0417\u041e\u041f\u0410\u0421\u041d\u041e\n+  THE LAST OF THE QUEUE: \u041a\u041e\u041d\u0415\u0426 \u041e\u0427\u0415\u0420\u0415\u0414\u0418\n+  WHEN IT FINISHES: \u041a\u041e\u0413\u0414\u0410 \u0417\u0410\u041a\u041e\u041d\u0427\u0418\u0422\n+  What each card starts; install or repair BIBLE.: \u0427\u0442\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u043a\u0430\u0436\u0434\u0430\u044f \u043a\u0430\u0440\u0442\u0430; \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0438\u043b\u0438 \u043f\u043e\u0447\u0438\u043d\u0438\u0442\u044c BIBLE.\n+  Rainbow: \u0420\u0430\u0434\u0443\u0433\u0430\n+  Custom: \u0421\u0432\u043e\u0439\n+  DESR DVR ISOs (experimental): ISO DESR DVR (\u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u043e)\n+  PC CARD CF (experimental): PC CARD CF (\u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u043e)\n+  HDD Start Mode: \u0417\u0430\u043f\u0443\u0441\u043a HDD\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): Roboto Condensed \u043e\u0442 The Roboto Project Authors (SIL OFL 1.1)\n+  '%s on %s': '%s \u043d\u0430 %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: %s \u0441\u0442\u0430\u043b\u043e %s (%d%%), \u043a\u0430\u0436\u0434\u044b\u0439 \u043a\u0443\u0441\u043e\u043a \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d. \u0423\u0434\u0430\u043b\u0438\u0442\u044c ISO \u0438 \u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c ZSO?'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: %s \u0441\u043d\u043e\u0432\u0430 \u0441\u0442\u0430\u043b\u043e %s, \u043a\u0430\u0436\u0434\u044b\u0439 \u043a\u0443\u0441\u043e\u043a \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d. \u0423\u0434\u0430\u043b\u0438\u0442\u044c ZSO \u0438 \u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c ISO?'\n+  A ZSO of this game is already beside it. Remove one of the two first.: \u0420\u044f\u0434\u043e\u043c \u0443\u0436\u0435 \u0435\u0441\u0442\u044c ZSO \u044d\u0442\u043e\u0439 \u0438\u0433\u0440\u044b. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u0443\u0434\u0430\u043b\u0438\u0442\u0435 \u043e\u0434\u0438\u043d \u0438\u0437 \u0434\u0432\u0443\u0445.\n+  About %d min, then checked: \u041e\u043a\u043e\u043b\u043e %d \u043c\u0438\u043d, \u0437\u0430\u0442\u0435\u043c \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430\n+  An ISO of this game is already beside it. Remove one of the two first.: \u0420\u044f\u0434\u043e\u043c \u0443\u0436\u0435 \u0435\u0441\u0442\u044c ISO \u044d\u0442\u043e\u0439 \u0438\u0433\u0440\u044b. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u0443\u0434\u0430\u043b\u0438\u0442\u0435 \u043e\u0434\u0438\u043d \u0438\u0437 \u0434\u0432\u0443\u0445.\n+  CHECKING EVERY PIECE: \u041f\u0420\u041e\u0412\u0415\u0420\u041a\u0410 \u041a\u0410\u0416\u0414\u041e\u0413\u041e \u041a\u0423\u0421\u041a\u0410\n+  COMPRESS TO ZSO: \u0421\u0416\u0410\u0422\u042c \u0412 ZSO\n+  'COMPRESSED %s: %s FREED': '%s \u0421\u0416\u0410\u0422\u0410: \u041e\u0421\u0412\u041e\u0411\u041e\u0416\u0414\u0415\u041d\u041e %s'\n+  COMPRESSING 1 OF 2: \u0421\u0416\u0410\u0422\u0418\u0415 1 \u0418\u0417 2\n+  COMPRESSING 2 OF 2: \u0421\u0416\u0410\u0422\u0418\u0415 2 \u0418\u0417 2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : \u0421\u0436\u0430\u0442\u044c %s? ISO \u043e\u0441\u0442\u0430\u0451\u0442\u0441\u044f, \u043f\u043e\u043a\u0430 ZSO \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043d \u043e\u0431\u0440\u0430\u0442\u043d\u043e \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d, \u0437\u0430\u0442\u0435\u043c RIPPS2 \u0441\u043f\u0440\u043e\u0441\u0438\u0442, \u043f\u0440\u0435\u0436\u0434\u0435 \u0447\u0435\u043c \u0443\u0434\u0430\u043b\u0438\u0442\u044c ISO. \u041d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u044e \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u043d\u0443\u0436\u043d\u043e \u043c\u0435\u0441\u0442\u043e \u0434\u043b\u044f \u043e\u0431\u043e\u0438\u0445; \u043d\u0435 \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0439\u0442\u0435 \u043a\u043e\u043d\u0441\u043e\u043b\u044c, \u043f\u043e\u043a\u0430 \u043d\u0435 \u0437\u0430\u043a\u043e\u043d\u0447\u0438\u0442\u0441\u044f. ZSO \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u044f\u0434\u0440\u043e\u043c OPL (\u043d\u0435 Neutrino); \u0435\u0451 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f, \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438, \u043e\u0431\u043b\u043e\u0436\u043a\u0438 \u0438 \u0447\u0438\u0442\u044b \u043e\u0441\u0442\u0430\u044e\u0442\u0441\u044f \u0441 \u043d\u0435\u0439, \u0438 \u0435\u0451 \u0432 \u043b\u044e\u0431\u043e\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u043c\u043e\u0436\u043d\u043e \u0441\u043d\u043e\u0432\u0430 \u0441\u0434\u0435\u043b\u0430\u0442\u044c ISO.\n+  Compress to ZSO: \u0421\u0436\u0430\u0442\u044c \u0432 ZSO\n+  Cycle: \u041f\u043e \u043a\u0440\u0443\u0433\u0443\n+  'Experimental: OPL core only (not Neutrino)': '\u042d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u043e: \u0442\u043e\u043b\u044c\u043a\u043e \u044f\u0434\u0440\u043e OPL (\u043d\u0435 Neutrino)'\n+  Hold SELECT: \u0423\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 SELECT\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: \u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c (%s). \u0417\u0430\u043f\u0438\u0441\u0430\u043d\u043d\u043e\u0435 \u0443\u0434\u0430\u043b\u0435\u043d\u043e; \u0438\u0433\u0440\u0430 \u0442\u0430\u043a\u0430\u044f \u0436\u0435, \u043a\u0430\u043a \u0431\u044b\u043b\u0430.\n+  List: \u0421\u043f\u0438\u0441\u043e\u043a\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : \u0421\u043d\u043e\u0432\u0430 \u0441\u0434\u0435\u043b\u0430\u0442\u044c %s ISO? ZSO \u043e\u0441\u0442\u0430\u0451\u0442\u0441\u044f, \u043f\u043e\u043a\u0430 ISO \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043d \u043e\u0431\u0440\u0430\u0442\u043d\u043e \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d, \u0437\u0430\u0442\u0435\u043c RIPPS2 \u0441\u043f\u0440\u043e\u0441\u0438\u0442, \u043f\u0440\u0435\u0436\u0434\u0435 \u0447\u0435\u043c \u0443\u0434\u0430\u043b\u0438\u0442\u044c ZSO. \u041d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u044e \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u043d\u0443\u0436\u043d\u043e \u043c\u0435\u0441\u0442\u043e \u0434\u043b\u044f \u043e\u0431\u043e\u0438\u0445; \u043d\u0435 \u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0439\u0442\u0435 \u043a\u043e\u043d\u0441\u043e\u043b\u044c, \u043f\u043e\u043a\u0430 \u043d\u0435 \u0437\u0430\u043a\u043e\u043d\u0447\u0438\u0442\u0441\u044f. \u0415\u0451 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f, \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438, \u043e\u0431\u043b\u043e\u0436\u043a\u0438 \u0438 \u0447\u0438\u0442\u044b \u043e\u0441\u0442\u0430\u044e\u0442\u0441\u044f \u0441 \u043d\u0435\u0439.\n+  Stop? What it has written so far is removed; the game stays as it was.: \u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c? \u0417\u0430\u043f\u0438\u0441\u0430\u043d\u043d\u043e\u0435 \u0434\u043e \u0441\u0438\u0445 \u043f\u043e\u0440 \u0443\u0434\u0430\u043b\u044f\u0435\u0442\u0441\u044f; \u0438\u0433\u0440\u0430 \u043e\u0441\u0442\u0430\u0451\u0442\u0441\u044f \u0442\u0430\u043a\u043e\u0439, \u043a\u0430\u043a \u0431\u044b\u043b\u0430.\n+  THEMES: \u0422\u0415\u041c\u042b\n+  There is not enough memory for this just now.: \u0421\u0435\u0439\u0447\u0430\u0441 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043d\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442 \u043f\u0430\u043c\u044f\u0442\u0438.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: \u042d\u0442\u043e\u0442 \u043e\u0431\u0440\u0430\u0437 RIPPS2 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0441\u0436\u0430\u0442\u044c \u0438\u043b\u0438 \u0440\u0430\u0441\u043f\u0430\u043a\u043e\u0432\u0430\u0442\u044c (\u0435\u0433\u043e \u0440\u0430\u0437\u043c\u0435\u0440 \u043d\u0435 \u043a\u0440\u0430\u0442\u0435\u043d \u0431\u043b\u043e\u043a\u0430\u043c \u043f\u043e 2 KB).\n+  UNCOMPRESS TO ISO: \u0420\u0410\u0421\u041f\u0410\u041a\u041e\u0412\u0410\u0422\u042c \u0412 ISO\n+  UNCOMPRESSED %s: '%s \u0420\u0410\u0421\u041f\u0410\u041a\u041e\u0412\u0410\u041d\u0410'\n+  UNCOMPRESSING: \u0420\u0410\u0421\u041f\u0410\u041a\u041e\u0412\u041a\u0410\n+  Uncompress to ISO: \u0420\u0430\u0441\u043f\u0430\u043a\u043e\u0432\u0430\u0442\u044c \u0432 ISO\n+  Under a minute, then checked: \u041c\u0435\u043d\u044c\u0448\u0435 \u043c\u0438\u043d\u0443\u0442\u044b, \u0437\u0430\u0442\u0435\u043c \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430\n+  COPYING: \u041a\u041e\u041f\u0418\u0420\u041e\u0412\u0410\u041d\u0418\u0415\n+  SHOW AGAIN, DELETE OR COMPRESS: \u041f\u041e\u041a\u0410\u0417\u0410\u0422\u042c \u0421\u041d\u041e\u0412\u0410, \u0423\u0414\u0410\u041b\u0418\u0422\u042c \u0418\u041b\u0418 \u0421\u0416\u0410\u0422\u042c\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: \u041f\u041e\u041a\u0410\u0417\u0410\u0422\u042c \u0421\u041d\u041e\u0412\u0410, \u0423\u0414\u0410\u041b\u0418\u0422\u042c \u0418\u041b\u0418 \u0420\u0410\u0421\u041f\u0410\u041a\u041e\u0412\u0410\u0422\u042c\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': '%s \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u043d\u0443\u0436\u043d\u043e \u043e\u043a\u043e\u043b\u043e %s \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u043e\u0433\u043e \u043c\u0435\u0441\u0442\u0430 \u043d\u0430 \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u0435 (\u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u043e\u0441\u0442\u0430\u0451\u0442\u0441\u044f \u0434\u043e \u043a\u043e\u043d\u0446\u0430), \u0430 \u043d\u0430 \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u0435 \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u043e %s.'\n+  Hide and Compress: \u0421\u043a\u0440\u044b\u0442\u044c \u0438 \u0441\u0436\u0430\u0442\u044c\n+  'COMPRESSED AND HIDDEN: %s': '\u0421\u0416\u0410\u0422\u0410 \u0418 \u0421\u041a\u0420\u042b\u0422\u0410: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': '\u041d\u0435\u0442 \u043c\u0435\u0441\u0442\u0430 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0440\u0438\u043f\u0430: \u043d\u0443\u0436\u043d\u043e \u043e\u043a\u043e\u043b\u043e %s, \u0430 \u043d\u0430 \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u0435 \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u043e %s.'\n+  OVER 4 GB FOR FAT32: \u0411\u041e\u041b\u042c\u0428\u0415 4 GB \u0414\u041b\u042f FAT32\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: \u042d\u0442\u043e\u0442 \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u044c \u0432 %s, \u0433\u0434\u0435 \u043d\u0435\u0442 \u0444\u0430\u0439\u043b\u043e\u0432 \u043e\u0442 4 GB, \u0430 \u043d\u043e\u0432\u044b\u0439 \u043e\u0431\u0440\u0430\u0437 \u0437\u0430\u043d\u044f\u043b \u0431\u044b %s.\n+  '%s, %s clusters, %s free of %s': '%s, \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u044b %s, \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u043e %s \u0438\u0437 %s'\n+  Delete This Sweep: \u0423\u0434\u0430\u043b\u0438\u0442\u044c \u044d\u0442\u0443 \u0441\u043c\u0435\u043d\u0443\n+  Delete %s? RIPPS2's own sweeps stay as they are.: \u0423\u0434\u0430\u043b\u0438\u0442\u044c %s? \u0421\u043c\u0435\u043d\u044b \u0446\u0432\u0435\u0442\u043e\u0432 \u0441\u0430\u043c\u043e\u0433\u043e RIPPS2 \u043e\u0441\u0442\u0430\u043d\u0443\u0442\u0441\u044f \u043a\u0430\u043a \u0435\u0441\u0442\u044c.\n+  Sweep: \u0421\u043c\u0435\u043d\u0430\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: \u0417\u043d\u0430\u0447\u043a\u0438 \u043e\u0442\u043c\u0435\u0447\u0430\u044e\u0442 \u0432 \u0432\u0430\u0448\u0438\u0445 \u0441\u043f\u0438\u0441\u043a\u0430\u0445 \u0438\u0433\u0440\u044b \u0441 \u0434\u043e\u0441\u0442\u0438\u0436\u0435\u043d\u0438\u044f\u043c\u0438 \u0438 \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0432\u044b \u043e\u0442\u043a\u0440\u044b\u043b\u0438. \u041d\u0443\u0436\u043d\u0430 \u0432\u043a\u043b\u044e\u0447\u0451\u043d\u043d\u0430\u044f \u0422\u0415\u041b\u0415\u041c\u0415\u0422\u0420\u0418\u042f.\n labels:\n   DS34BT_MODE: DualShock3/4 \u043f\u043e Bluetooth\n   HINT_NEUTRINO_AUTO_ELF: \u041f\u0435\u0440\u0435\u0434\u0430\u0451\u0442 \u0437\u0430\u0433\u0440\u0443\u0437\u043e\u0447\u043d\u044b\u0439 ELF \u0438\u0433\u0440\u044b \u0432 Neutrino, \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0439\u0442\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0434\u043b\u044f \u044d\u0442\u043e\u0439 \u0438\u0433\u0440\u044b. \u0412\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e; \u0437\u0430\u0434\u0430\u043d\u043d\u044b\u0439 \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 Boot ELF \u0432\u0430\u0436\u043d\u0435\u0435.\n@@ -1201,7 +1368,7 @@ labels:\n   SAVE_ICONS: 3D-\u0437\u043d\u0430\u0447\u043a\u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0439\n   HINT_SAVE_ICONS: \u041f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u0432\u0440\u0430\u0449\u0430\u044e\u0449\u0438\u0439\u0441\u044f \u0437\u043d\u0430\u0447\u043e\u043a \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0439 \u0438\u0433\u0440\u044b \u0442\u0430\u043c, \u0433\u0434\u0435 \u0443 \u0442\u0435\u043c\u044b \u0435\u0441\u0442\u044c \u0434\u043b\u044f \u043d\u0435\u0433\u043e \u043c\u0435\u0441\u0442\u043e. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u0447\u0438\u0442\u0430\u0435\u0442 VMC \u0438\u0433\u0440\u044b, \u0437\u0430\u0442\u0435\u043c \u043a\u0430\u0440\u0442\u044b \u043f\u0430\u043c\u044f\u0442\u0438.\n   NETBOOT_RESTART_NOW: \u0421\u0435\u0442\u0435\u0432\u043e\u0439 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b \u0441\u043c\u0435\u043d\u0438\u0442\u0441\u044f \u043f\u043e\u0441\u043b\u0435 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0430. \u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c OPL \u0441\u0435\u0439\u0447\u0430\u0441?\n-  HINT_NET_PROTOCOL: '\u041a\u0430\u043a\u043e\u0439 \u0441\u0435\u0442\u0435\u0432\u043e\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 RiptOPL: \u043e\u0431\u0449\u0438\u0439 \u0440\u0435\u0441\u0443\u0440\u0441 SMB, UDPFS, UDPBD \u0438\u043b\u0438 HTTP. \u0422\u0430 \u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430, \u0447\u0442\u043e \u0432 \u00ab\u0418\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430\u0445 \u0438\u0433\u0440\u00bb. \u0423\u0445\u043e\u0434 \u0441 \u0443\u0436\u0435 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u043e\u0433\u043e \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0430.'\n+  HINT_NET_PROTOCOL: '\u041a\u0430\u043a\u043e\u0439 \u0441\u0435\u0442\u0435\u0432\u043e\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 RiptOPL: \u043e\u0431\u0449\u0438\u0439 \u0440\u0435\u0441\u0443\u0440\u0441 SMB, UDPFS, UDPBD \u0438\u043b\u0438 HTTP. \u0423\u0445\u043e\u0434 \u0441 \u0443\u0436\u0435 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u043e\u0433\u043e \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0430.'\n   HINT_UDPFS_ACCESS: \u0422\u043e\u043b\u044c\u043a\u043e UDPFS. \u00ab\u0424\u0430\u0439\u043b\u044b\u00bb \u0447\u0438\u0442\u0430\u0435\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 ISO \u0438\u0437 \u043f\u0430\u043f\u043a\u0438 \u0438\u0433\u0440 \u0441\u0435\u0440\u0432\u0435\u0440\u0430; IMG \u0447\u0438\u0442\u0430\u0435\u0442 \u043e\u0431\u0440\u0430\u0437 \u0434\u0438\u0441\u043a\u0430 \u043a\u0430\u043a \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u044c. SMB \u0438 HTTP \u0432\u0441\u0435\u0433\u0434\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442 \u00ab\u0424\u0430\u0439\u043b\u044b\u00bb, UDPBD \u0432\u0441\u0435\u0433\u0434\u0430 IMG.\n   HINT_SMB_VERSION: \u041a\u0430\u043a\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e SMB \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441 \u043e\u0431\u0449\u0435\u0439 \u043f\u0430\u043f\u043a\u043e\u0439 \u041f\u041a.\n   HINT_NET_ADVANCED: \u041e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0434\u043b\u044f \u043f\u0440\u0430\u0432\u043a\u0438 \u0440\u0435\u0436\u0438\u043c \u0441\u0432\u044f\u0437\u0438 Ethernet \u0438 \u043f\u043e\u0440\u0442 SMB.\n@@ -1400,3 +1567,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: \u041a\u0430\u043a \u043a\u0430\u0434\u0440\u044b, \u0436\u0434\u0443\u0449\u0438\u0435 \u043d\u0430 \u043f\u043b\u0451\u043d\u043a\u0435 \u043a\u0430\u0436\u0434\u043e\u0439 \u0438\u0433\u0440\u044b, \u0441\u0442\u0430\u043d\u043e\u0432\u044f\u0442\u0441\u044f PNG.\n   RIPPS2_SETTINGS_HOME: \u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043d\u0430\n   HINT_RIPPS2_SETTINGS_HOME: '\u0413\u0434\u0435 RIPPS2 \u0445\u0440\u0430\u043d\u0438\u0442 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438: \u0432 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u043c \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0438 \u043d\u0430 \u043a\u0430\u0440\u0442\u0435 \u043f\u0430\u043c\u044f\u0442\u0438, \u0432 \u043f\u0430\u043f\u043a\u0435 RIPPS2 \u043d\u0430 \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u0435 (\u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0435\u043c HDD, USB, MX4SIO, iLink \u0438\u043b\u0438 MMCE) \u0438\u043b\u0438 \u0432 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u0439 \u0432\u0430\u043c\u0438 \u043f\u0430\u043f\u043a\u0435.'\n+  RIPPS2_LAUNCHERS: \u0417\u0430\u0433\u0440\u0443\u0437\u0447\u0438\u043a\u0438\n+  RIPPS2_LOOK_NOW: \u0418\u0441\u043a\u0430\u0442\u044c \u0441\u0435\u0439\u0447\u0430\u0441\n+  HINT_RIPPS2_LOOK_NOW: RIPPS2 \u0438\u0449\u0435\u0442 \u043d\u0430 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0445 \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u044f\u0445 POPSTARTER, \u0444\u0430\u0439\u043b\u044b POPS, Ember, BIOS \u0434\u043b\u044f Ember \u0438 Neutrino \u0438 \u0437\u0430\u043f\u043e\u043c\u0438\u043d\u0430\u0435\u0442, \u0433\u0434\u0435 \u043b\u0435\u0436\u0438\u0442 \u043a\u0430\u0436\u0434\u044b\u0439. \u041a\u0430\u0436\u0434\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a \u0431\u0435\u0440\u0451\u0442 \u0438\u0445 \u043e\u0442\u0442\u0443\u0434\u0430.\n+  RIPPS2_HIDE_GAME: \u0421\u043a\u0440\u044b\u0442\u044c \u0438\u0433\u0440\u0443\n+  RIPPS2_HIDDEN_GAMES: \u0421\u043a\u0440\u044b\u0442\u044b\u0435 \u0438\u0433\u0440\u044b\n+  HINT_RIPPS2_HIDDEN_GAMES: \u0418\u0433\u0440\u044b, \u0441\u043a\u0440\u044b\u0442\u044b\u0435 \u0447\u0435\u0440\u0435\u0437 \u00ab\u0421\u043a\u0440\u044b\u0442\u044c \u0438\u0433\u0440\u0443\u00bb, \u043d\u0430 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0445 \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u044f\u0445. \u041f\u043e\u043a\u0430\u0436\u0438\u0442\u0435 \u043e\u0434\u043d\u0443 \u0441\u043d\u043e\u0432\u0430 \u0438\u043b\u0438 \u0443\u0434\u0430\u043b\u0438\u0442\u0435 \u0435\u0451. \u0423 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u044f \u0441\u0432\u043e\u0439 \u0441\u043f\u0438\u0441\u043e\u043a.\n+  HINT_RIPPS2_BDM_DEVICES: '\u041d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u0438, \u0441 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 BDM \u0447\u0438\u0442\u0430\u0435\u0442 \u0438\u0433\u0440\u044b: USB, MX4SIO, PC Card CF, DVR-\u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u044c DESR \u0438 iLink. \u0412\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0439 HDD \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u0437\u0430\u043f\u0443\u0441\u043a\u0443 HDD.'\n+  HINT_RIPPS2_SCREEN: '\u041a\u0430\u0440\u0442\u0438\u043d\u043a\u0430 \u043c\u0435\u043d\u044e: \u0432\u0438\u0434\u0435\u043e\u0440\u0435\u0436\u0438\u043c, \u0448\u0438\u0440\u043e\u043a\u0438\u0439 \u044d\u043a\u0440\u0430\u043d, \u0435\u0451 \u043c\u0435\u0441\u0442\u043e \u043d\u0430 \u0442\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440\u0435 (H-POS, V-POS) \u0438 overscan.'\n+  HINT_RIPPS2_LAYOUT: \u0413\u0434\u0435 \u0441\u0442\u043e\u044f\u0442 \u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u0438 \u0438 \u043a\u0430\u043a \u0432\u0445\u043e\u0434\u0438\u0442 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430, \u0437\u0430\u0442\u0435\u043c \u043e\u0431\u043b\u043e\u0436\u043a\u0438 RIPFLOW \u0438 \u0441\u0435\u0442\u043a\u0430 RIPGRID.\n+  HINT_RIPPS2_COLORS_TEXT: \u0422\u043e\u043b\u0449\u0438\u043d\u0430 \u0442\u0435\u043a\u0441\u0442\u0430, \u0446\u0432\u0435\u0442\u043e\u0432\u0430\u044f \u0442\u0435\u043c\u0430, \u0441\u043c\u0435\u043d\u0430 \u0446\u0432\u0435\u0442\u043e\u0432 \u0438 \u043a\u0430\u0436\u0434\u044b\u0439 \u0446\u0432\u0435\u0442.\n+  HINT_RIPPS2_TEXT_WEIGHT: '\u041d\u0430\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043f\u043b\u043e\u0442\u043d\u044b\u0439 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0442\u0435\u043a\u0441\u0442 RIPPS2: \u043f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a, \u0421\u041e\u0421\u0422\u041e\u042f\u041d\u0418\u0415 \u0438 \u041e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0435. \u041e\u0431\u044b\u0447\u043d\u044b\u0439 \u043a\u0430\u043a \u0440\u0430\u043d\u044c\u0448\u0435; \u0421\u0440\u0435\u0434\u043d\u0438\u0439 \u0438 \u0416\u0438\u0440\u043d\u044b\u0439 \u043b\u0443\u0447\u0448\u0435 \u0447\u0438\u0442\u0430\u044e\u0442\u0441\u044f \u043d\u0430 \u043c\u044f\u0433\u043a\u043e\u0439 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0435.'\n+  HINT_RIPPS2_HDD_START: '\u0412\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0439 HDD, APA \u0438\u043b\u0438 exFAT: RIPPS2 \u0447\u0438\u0442\u0430\u0435\u0442 \u0435\u0433\u043e, \u043a\u0430\u043a\u0438\u043c \u0431\u044b \u043e\u043d \u043d\u0438 \u0431\u044b\u043b, \u0438 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043e\u0434\u0438\u043d \u0440\u0430\u0437, \u043a\u0430\u043a \u0438\u0433\u0440\u044b HDD. \u0412\u044b\u043a\u043b. \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0435\u0433\u043e \u0432 \u043f\u043e\u043a\u043e\u0435.'\n+  HINT_RIPPS2_THEME_COLOR: \u0421\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0446\u0432\u0435\u0442 \u0442\u0435\u043c\u044b. \u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0421\u0432\u043e\u0439 \u043a\u0430\u043a \u0446\u0432\u0435\u0442\u043e\u0432\u0443\u044e \u0442\u0435\u043c\u0443, \u0447\u0442\u043e\u0431\u044b \u0432\u044b\u0431\u0440\u0430\u0442\u044c \u0435\u0433\u043e \u0441\u0430\u043c\u0438\u043c, \u043a\u0430\u043a \u0446\u0432\u0435\u0442\u0430 \u0442\u0435\u043a\u0441\u0442\u0430.\n+  HINT_RIPPS2_SWEEP: \u041f\u044f\u0442\u044c \u0446\u0432\u0435\u0442\u043e\u0432, \u0447\u0435\u0440\u0435\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0442\u0435\u043c\u0430 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u0442 \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u043e \u0438 \u043f\u043b\u0430\u0432\u043d\u043e. \u0420\u0430\u0434\u0443\u0433\u0430, \u0441\u043c\u0435\u043d\u0430 \u0438\u0437 \u043a\u0430\u0436\u0434\u043e\u0439 \u0446\u0432\u0435\u0442\u043e\u0432\u043e\u0439 \u0442\u0435\u043c\u044b \u0438\u043b\u0438 \u0432\u0430\u0448\u0430. \u0412\u044b\u043a\u043b. \u0434\u0435\u0440\u0436\u0438\u0442 \u0442\u0435\u043c\u0443 \u043d\u0435\u043f\u043e\u0434\u0432\u0438\u0436\u043d\u043e\u0439.\n+  HINT_RIPPS2_SWEEP_COLORS: \u041f\u044f\u0442\u044c \u0446\u0432\u0435\u0442\u043e\u0432 \u0441\u043c\u0435\u043d\u044b \u0438 \u0435\u0451 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c. \u0418\u0437\u043c\u0435\u043d\u0438\u0442\u0435 \u043e\u0434\u043d\u0443 \u0438\u0437 RIPPS2, \u0438 \u043e\u043d\u0430 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u043a\u0430\u043a \u0432\u0430\u0448\u0430, \u043f\u043e\u0434 \u0434\u0430\u043d\u043d\u044b\u043c \u0432\u0430\u043c\u0438 \u0438\u043c\u0435\u043d\u0435\u043c.\n+  HINT_RIPPS2_SWEEP_NAME: \u041a\u0430\u043a \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432\u0430\u0448\u0430 \u0441\u043c\u0435\u043d\u0430 \u0432 \u0441\u043f\u0438\u0441\u043a\u0435 \u0421\u043c\u0435\u043d\u0430 \u0446\u0432\u0435\u0442\u043e\u0432.\n+  HINT_RIPPS2_SWEEP_COLOR: \u041e\u0434\u0438\u043d \u0438\u0437 \u043f\u044f\u0442\u0438 \u0446\u0432\u0435\u0442\u043e\u0432, \u0447\u0435\u0440\u0435\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u043c\u0435\u043d\u0430 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 \u043f\u043e \u043f\u043e\u0440\u044f\u0434\u043a\u0443.\n+  HINT_RIPPS2_SWEEP_SPEED: '\u0421\u043a\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u0438\u0442\u0441\u044f \u043a\u0430\u0436\u0434\u044b\u0439 \u0446\u0432\u0435\u0442, \u043f\u043e\u043a\u0430 \u043d\u0435 \u0432\u043e\u0439\u0434\u0451\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439. \u0414\u0430\u0436\u0435 \u0416\u0438\u0432\u043e \u043e\u0441\u0442\u0430\u0451\u0442\u0441\u044f \u043f\u043b\u0430\u0432\u043d\u044b\u043c: \u0446\u0432\u0435\u0442\u0430 \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u043d\u0435 \u043c\u0438\u0433\u0430\u044e\u0442.'\n+  RIPPS2_HIDE_COMPRESS: \u0421\u043a\u0440\u044b\u0442\u044c \u0438\u0433\u0440\u0443 \u0438 \u0441\u0436\u0430\u0442\u0438\u0435\n+  HINT_RIPPS2_SELECT_HOLD: 'SELECT \u0443\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f 4.2 \u0441 \u043d\u0430 \u0441\u043f\u0438\u0441\u043a\u0435 \u0438\u0433\u0440. \u0421\u043f\u0438\u0441\u043e\u043a: \u0442\u0435\u043c\u044b \u043d\u0430 \u043f\u0430\u043d\u0435\u043b\u0438, \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0434\u043d\u0443. \u041f\u043e \u043a\u0440\u0443\u0433\u0443: \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f \u0442\u0435\u043c\u0430. \u0412\u044b\u043a\u043b.: \u043d\u0438\u0447\u0435\u0433\u043e (\u043d\u0430\u0436\u0430\u0442\u0438\u0435 \u043f\u043e-\u043f\u0440\u0435\u0436\u043d\u0435\u043c\u0443 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442).'"
        },
        {
          "sha": "f934fcc2b4cbd86747a1b718802d1cefa1435c9e",
          "filename": "elf/lang/SChinese.yml",
          "status": "modified",
          "additions": 209,
          "deletions": 22,
          "changes": 231,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FSChinese.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FSChinese.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FSChinese.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: SChinese\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTER \u786e\u8ba4\uff0cESC \u53d6\u6d88\u3002\u6309\u4efb\u610f\u6309\u94ae\u663e\u793a\u6309\u952e\u3002\n   USB KEYBOARD: USB \u952e\u76d8\n-  '      DESR DVR ISOs (experimental)': '      DESR DVR ISO\uff08\u5b9e\u9a8c\u6027\uff09'\n-  '      Internal HDD (exFAT)': '      \u5185\u7f6e\u786c\u76d8\uff08exFAT\uff09'\n-  '      PC CARD CF (experimental)': '      PC CARD CF\uff08\u5b9e\u9a8c\u6027\uff09'\n-  '      Protocol': '      \u534f\u8bae'\n   Access: \u8bbf\u95ee\u65b9\u5f0f\n   Applications Page Start Mode: \u5e94\u7528\u7a0b\u5e8f\u9875\u9762\u542f\u52a8\u65b9\u5f0f\n   Art Delay: \u56fe\u7247\u5ef6\u8fdf\n@@ -22,7 +18,6 @@ strings:\n   Fifth: \u7b2c\u4e94\n   First: \u7b2c\u4e00\n   Fourth: \u7b2c\u56db\n-  HDD (APA) Start Mode: \u786c\u76d8\uff08APA\uff09\u542f\u52a8\u65b9\u5f0f\n   HDD Cache: \u786c\u76d8\u7f13\u5b58\n   HDD Spindown: \u786c\u76d8\u4f11\u7720\n   MMCE Settings: MMCE \u8bbe\u7f6e\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: \u8fd9\u662f\u6253\u8fc7 ESR \u8865\u4e01\u7684 PS2 \u6e38\u620f\u3002\u8bf7\u628a ESR\uff08esr*.elf\uff09\u653e\u5230\u8bb0\u5fc6\u5361\u6216 U \u76d8\u4e0a\u7684 APPS\u3001BOOT \u6216 ESR \u6587\u4ef6\u5939\u4e2d\u6765\u542f\u52a8\u5b83\u3002\n   '%s, BUILD %d': '%s\uff0c\u6784\u5efa %d'\n   '... and the anonymous ...': \u2026\u2026\u4ee5\u53ca\u533f\u540d\u7684\u5404\u4f4d\u2026\u2026\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': 1.8.0 \u539f\u6837\u9644\u5e26\uff08AFL-3.0\uff09\uff1agithub.com/rickgaiser/neutrino\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: \u5bf9 RiptOPL\uff08Open PS2 Loader \u7684\u5206\u652f\uff09\u7684\u62c6\u89e3\u4e0e\u91cd\u5efa\uff0c\n   ABOUT: \u5173\u4e8e\n   'ACHIEVEMENTS: SPIRIT OF RA': \u6210\u5c31\uff1aSPIRIT OF RA\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): \u8bb0\u5fc6\u6587\u4ef6\u4e2d\u7684 AVI \u89c6\u9891\uff0cRIPPS2 \u7684\u5206\u652f\uff08AFL 2.0\uff09\n   Apollo Save Tool by bucanero: bucanero \u7684 Apollo Save Tool\n   BACK: \u8fd4\u56de\n+  Back (keeps both): \u8fd4\u56de\uff08\u4e24\u4e2a\u90fd\u4fdd\u7559\uff09\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: \u8fd9\u5f20 MMCE \u5361\u65e0\u6cd5\u91cd\u547d\u540d\u6587\u4ef6\uff0c\u800c\u538b\u7f29\u7684\u6700\u540e\u4e00\u6b65\u9700\u8981\u8fd9\u6837\u505a\u3002\u8bf7\u5c06\u5176 sd2psXtd \u56fa\u4ef6\u66f4\u65b0\u5230 1.4.0 \u6216\u66f4\u9ad8\u7248\u672c\uff08sd2psxtd.github.io/download\uff09\uff0c\u7136\u540e\u91cd\u8bd5\u3002\u672a\u505a\u4efb\u4f55\u66f4\u6539\u3002\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): BIBLE \u662f PS2BBL 1.2.0\uff0cEl Isra \u7b7e\u540d\u7684\u7248\u672c\uff08\u8bb0\u5fc6\u5361\u548c\u786c\u76d8\uff09\uff0c\u672a\u4fee\u6539\uff08GPL-3\uff09\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: BIOS \u97f3\u6548 > \u63d0\u53d6\u4f1a\u50cf SPU2 \u4e00\u6837\u64ad\u653e\u5b83\u4eec\uff1b\u4e0d\u9644\u5e26\u4efb\u4f55 BIOS\n   BOOT LOCK AND BIBLE: BOOT LOCK \u4e0e BIBLE\n   BOOT LOCK edits its config too: BOOT LOCK \u4e5f\u4f1a\u7f16\u8f91\u5b83\u7684\u914d\u7f6e\n   BUILD DETAILS: \u6784\u5efa\u8be6\u60c5\n-  BUILT ON RIPTOPL: \u57fa\u4e8e RIPTOPL\n   BearSSL by Thomas Pornin: Thomas Pornin \u7684 BearSSL\n   Built with Claude (Anthropic): \u501f\u52a9 Claude\uff08Anthropic\uff09\u5236\u4f5c\n   CD PLAYER: CD \u64ad\u653e\u5668\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek \u4e0e Sleek Bold\uff0c\u4e3a RIPPS2 \u7ed8\u5236\n   RetroAchievements on the console by hacan359 (yoba): hacan359\uff08yoba\uff09\u7684\u4e3b\u673a\u7aef RetroAchievements\n   RiptOPL by NathanNeurotic: NathanNeurotic \u7684 RiptOPL\n-  Roboto Condensed by Google (Apache-2.0): Google \u7684 Roboto Condensed\uff08Apache-2.0\uff09\n   SOUNDS: \u97f3\u6548\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: SP193 \u7684 FMCB \u5b89\u88c5\u5668\u548c pcm720 \u7684 OSDMenu\uff0c\u9010\u6b65\u9075\u5faa\n   SUPPORT: \u652f\u6301\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: NathanNeurotic \u7684 wLaunchELF R3Z\n   wLaunchELF by the ps2homebrew team: ps2homebrew \u56e2\u961f\u7684 wLaunchELF\n   what RIPPS2's card image writes are checked against: \u7528\u6765\u6838\u5bf9 RIPPS2 \u5199\u5165\u8bb0\u5fc6\u5361\u955c\u50cf\u7684\u5de5\u5177\n-  with its own look, launchers, file browser and save tools.: \u62e5\u6709\u81ea\u5df1\u7684\u5916\u89c2\u3001\u542f\u52a8\u5668\u3001\u6587\u4ef6\u6d4f\u89c8\u5668\u548c\u5b58\u6863\u5de5\u5177\u3002\n   xeRAbora by hacan359, rcheevos by RetroAchievements: hacan359 \u7684 xeRAbora\uff0cRetroAchievements \u7684 rcheevos\n   your console's own, ripped from its BIOS: \u4f60\u4e3b\u673a\u81ea\u5df1\u7684\u97f3\u6548\uff0c\u4ece\u5b83\u7684 BIOS \u4e2d\u63d0\u53d6\n   ' (no DNS answer)': ' \uff08DNS \u65e0\u5e94\u7b54\uff09'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': NEUTRINO\uff1a\u6b63\u5728\u5199\u5165 %.60s\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: \u65e0\u6cd5\u628a Neutrino \u5199\u5165 %s\u3002\u8bf7\u68c0\u67e5\u5269\u4f59\u7a7a\u95f4\u4ee5\u53ca\u662f\u5426\u5199\u4fdd\u62a4\uff0c\u7136\u540e\u91cd\u8bd5\u3002\n   THE GAME'S DRIVE  (%s/neutrino): \u6e38\u620f\u6240\u5728\u9a71\u52a8\u5668  \uff08%s/neutrino\uff09\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : \u8fd9\u4e2a\u6e38\u620f\u901a\u8fc7 Neutrino \u542f\u52a8\uff0c\u800c RIPPS2 \u67e5\u627e\u7684\u4f4d\u7f6e\uff08\u8bbe\u7f6e\u3001\u6e38\u620f\u6240\u5728\u9a71\u52a8\u5668\u3001\u8bb0\u5fc6\u5361\uff09\u90fd\u6ca1\u6709 Neutrino\u3002RIPPS2 \u9644\u5e26 rickgaiser \u7684 Neutrino 1.8.0\uff08AFL-3.0\uff09\u3002\u73b0\u5728\u5b89\u88c5\u5417\uff1f\u63a5\u4e0b\u6765\u9009\u62e9\u4f4d\u7f6e\u3002\n   '%d / %d FILES': '%d / %d \u4e2a\u6587\u4ef6'\n   '%d FOLDERS AND CONTENTS': '%d \u4e2a\u6587\u4ef6\u5939\u53ca\u5185\u5bb9'\n   '%d FOLDERS, %d FOUND': '%d \u4e2a\u6587\u4ef6\u5939\uff0c\u627e\u5230 %d \u4e2a'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d / %d \u4ecd\u9501\u5b9a'\n   '%s   on %s': '%s   \u4e8e %s'\n   BADGES: \u5fbd\u7ae0\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: \u53ea\u6709\u5728\u5f00\u542f\u9065\u6d4b\u4e14\u4f60\u7684\u7535\u8111\u4e0a\u8fd0\u884c\u7740 xeRAbora \u65f6\u5fbd\u7ae0\u624d\u4f1a\u751f\u6548\uff1b\u5728\u6b64\u4e4b\u524d\u5b83\u4eec\u4e0d\u5360\u7528\u4efb\u4f55\u8d44\u6e90\u3002\n   CHECK THE DISC: \u68c0\u67e5\u5149\u76d8\n   From this game's last CHECK GAME SUPPORT.: \u6765\u81ea\u8fd9\u4e2a\u6e38\u620f\u4e0a\u4e00\u6b21\u7684\u201c\u68c0\u67e5\u6e38\u620f\u652f\u6301\u201d\u3002\n   HOW IT WORKS: \u4f7f\u7528\u8bf4\u660e\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: \u8fd9\u53f0\u4e3b\u673a\u4e0a\u6709\u5e26 RIPPS2 \u9501\u5b9a\u7684 PS2BBL\u3002\n   AUDIO folder: AUDIO \u6587\u4ef6\u5939\n   DRIVES: \u9a71\u52a8\u5668\n-  INSTALL RIPPS2'S COPY: \u5b89\u88c5 RIPPS2 \u7684\u526f\u672c\n-  'Installed: ': '\u5df2\u5b89\u88c5\uff1a '\n   MANAGE: \u7ba1\u7406\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: \u201c\u7ba1\u7406\u201d\u4f1a\u663e\u793a\u6bcf\u5f20\u8bb0\u5fc6\u5361\u542f\u52a8\u7684\u5185\u5bb9\uff0c\u5e76\u5b89\u88c5\u6216\u4fee\u590d BIBLE\u3002\n   MEMORY CARD 0  (about 600 KB): \u8bb0\u5fc6\u5361 0  \uff08\u7ea6 600 KB\uff09\n   MEMORY CARD 1  (about 600 KB): \u8bb0\u5fc6\u5361 1  \uff08\u7ea6 600 KB\uff09\n   'MX4SIO: its driver would not load (code %d).': MX4SIO\uff1a\u9a71\u52a8\u65e0\u6cd5\u52a0\u8f7d\uff08\u4ee3\u7801 %d\uff09\u3002\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': MX4SIO\uff1a\u9a71\u52a8\u5df2\u52a0\u8f7d\uff0c\u4f46\u6ca1\u6709 SD \u5361\u54cd\u5e94\uff08mc1 \u63d2\u69fd\uff09\u3002\n   NEUTRINO SETTINGS: NEUTRINO \u8bbe\u7f6e\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': \u8fd8\u6ca1\u6709\uff1aRIPPS2 \u6ca1\u6709\u81ea\u5e26\u97f3\u6548\u3002\u8bf7\u63d0\u53d6\u8fd9\u53f0\u4e3b\u673a\u7684\u97f3\u6548\uff0c\u6216\u5728 RIPPS2 Audio Studio \u4e2d\u5236\u4f5c\u3002\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: \u672a\u5b89\u88c5\u3002\u9700\u8981\u5b83\u7684\u6e38\u620f\u4f1a\u63d0\u4f9b RIPPS2 \u7684\u526f\u672c\uff0c\u6216\u5728\u8fd9\u91cc\u5b89\u88c5\u3002\n   POPSTARTER NETWORK: POPSTARTER \u7f51\u7edc\n   POPSTARTER and Ember: POPSTARTER \u4e0e Ember\n   PS1 SETTINGS: PS1 \u8bbe\u7f6e\n   PS2BBL 1.2.0 for RIPPS2: \u4f9b RIPPS2 \u4f7f\u7528\u7684 PS2BBL 1.2.0\n   RIP THIS CONSOLE'S SOUNDS: \u63d0\u53d6\u8fd9\u53f0\u4e3b\u673a\u7684\u97f3\u6548\n-  RIPPS2's homebrew: RIPPS2 \u7684\u81ea\u5236\u7a0b\u5e8f\n   STATUS: \u72b6\u6001\n-  'STATUS: looking for RIPPS2''s homebrew': \u72b6\u6001\uff1a\u6b63\u5728\u67e5\u627e RIPPS2 \u7684\u81ea\u5236\u7a0b\u5e8f\n   a drive of unknown kind: \u672a\u77e5\u7c7b\u578b\u7684\u9a71\u52a8\u5668\n-  a drive or a memory card: \u9a71\u52a8\u5668\u6216\u8bb0\u5fc6\u5361\n   boot loader, card by card: \u5f15\u5bfc\u7a0b\u5e8f\uff0c\u9010\u5361\u67e5\u770b\n   from its BIOS, into AUDIO: \u6765\u81ea\u5176 BIOS\uff0c\u5b58\u5165 AUDIO\n   internal HDD (exFAT): \u5185\u7f6e\u786c\u76d8\uff08exFAT\uff09\n@@ -975,10 +958,194 @@ strings:\n   Sidebar: \u4fa7\u8fb9\u680f\n   your own copy, found on your drives and memory cards: \u4f60\u81ea\u5df1\u7684\u526f\u672c\uff0c\u5728\u4f60\u7684\u9a71\u52a8\u5668\u548c\u8bb0\u5fc6\u5361\u4e0a\u627e\u5230\n   'your own, found and launched: github.com/Gageformer/Ember/releases': \u4f60\u81ea\u5df1\u7684\uff0c\u627e\u5230\u5e76\u542f\u52a8\uff1agithub.com/Gageformer/Ember/releases\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': \u5728 mass0:\uff08%s\uff09\u4e0a\u672a\u627e\u5230\u3002\u8bf7\u628a ember.elf \u548c\u4f60\u81ea\u5df1\u7684 bios.bin \u653e\u8fdb\u5b83\u7684 EMBER \u6587\u4ef6\u5939\u3002\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: \u672a\u627e\u5230\u3002\u8bf7\u628a\u4f60\u81ea\u5df1\u7684 POPSTARTER.ELF \u653e\u8fdb\u67d0\u4e2a\u9a71\u52a8\u5668\u7684 POPS \u6587\u4ef6\u5939\u3002\n   Launch Disc: \u542f\u52a8\u5149\u76d8\n   Memory Files: \u8bb0\u5fc6\u6587\u4ef6\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: RIPPS2 \u627e\u5230\u4e86 5 \u4e2a\u4e2d\u7684 %d \u4e2a\u3002\u6bcf\u4e00\u4e2a\u90fd\u4f1a\u88ab\u8bb0\u4f4f\uff0c\u6bcf\u6b21\u542f\u52a8\u90fd\u4ece\u90a3\u91cc\u8bfb\u53d6\u3002\n+  not found on the started drives: \u5728\u5df2\u542f\u52a8\u7684\u9a71\u52a8\u5668\u4e0a\u672a\u627e\u5230\n+  not looked for yet: \u5c1a\u672a\u67e5\u627e\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s \u6ca1\u6709\u54cd\u5e94\uff0c\u6240\u4ee5 %s \u65e0\u6cd5\u542f\u52a8\u3002\u5b83\u8fde\u63a5\u597d\u4e86\u5417\uff1f'\n+  '%s is not on %s any more, so it has left All Games.': '%s \u5df2\u4e0d\u5728 %s \u4e0a\uff0c\u6240\u4ee5\u5df2\u79bb\u5f00\u5168\u90e8\u6e38\u620f\u3002'\n+  STARTING %s: \u6b63\u5728\u542f\u52a8 %s\n+  the SMB share: SMB \u5171\u4eab\n+  the drive: \u9a71\u52a8\u5668\n+  the internal HDD (APA): \u5185\u7f6e\u786c\u76d8\uff08APA\uff09\n+  the internal HDD (exFAT): \u5185\u7f6e\u786c\u76d8\uff08exFAT\uff09\n+  BUILT ON RIPTOPL, WITH NEUTRINO: \u57fa\u4e8e RIPTOPL\uff0c\u642d\u8f7d NEUTRINO\n+  'Special thanks to all the YouTube love as well:': \u4e5f\u7279\u522b\u611f\u8c22\u6765\u81ea YouTube \u7684\u6240\u6709\u559c\u7231\uff1a\n+  and its own look, launchers, file browser and save tools.: \u5e76\u6709\u81ea\u5df1\u7684\u5916\u89c2\u3001\u542f\u52a8\u5668\u3001\u6587\u4ef6\u6d4f\u89c8\u5668\u548c\u5b58\u6863\u5de5\u5177\u3002\n+  glad to help however you reach me.: \u65e0\u8bba\u4f60\u600e\u6837\u8054\u7cfb\u6211\uff0c\u90fd\u4e50\u610f\u5e2e\u5fd9\u3002\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): \u7b2c\u4e8c\u4e2a\u6e38\u620f\u6838\u5fc3\uff0c1.8.0 \u539f\u6837\u9644\u5e26\uff08AFL-3.0\uff09\n+  with rickgaiser's Neutrino inside as its second game core,: \u5185\u542b rickgaiser \u7684 Neutrino \u4f5c\u4e3a\u7b2c\u4e8c\u4e2a\u6e38\u620f\u6838\u5fc3\uff0c\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : \u8fd9\u4e2a\u6e38\u620f\u901a\u8fc7 Neutrino \u542f\u52a8\uff0c\u800c RIPPS2 \u6ca1\u6709\u627e\u5230\uff1a\u8bbe\u7f6e\u91cc\u7684\u8def\u5f84\u3001\u6e38\u620f\u6240\u5728\u7684\u9a71\u52a8\u5668\u3001\u8bb0\u5fc6\u5361\u548c\u4efb\u4f55\u5df2\u542f\u52a8\u7684\u9a71\u52a8\u5668\u4e0a\u90fd\u6ca1\u6709\u3002RIPPS2 \u5e26\u6709 rickgaiser \u7684 Neutrino 1.8.0\uff08AFL-3.0\uff09\uff1a\u5728\u4f60\u9009\u7684\u5730\u65b9\u5b89\u88c5\u4e00\u6b21\uff0c\u4e4b\u540e\u6bcf\u4e2a Neutrino \u6e38\u620f\u90fd\u4ece\u90a3\u91cc\u4f7f\u7528\u5b83\u3002\n+  STARTING THE DRIVES: \u6b63\u5728\u542f\u52a8\u9a71\u52a8\u5668\n+  '%d games, on the started drives': '%d \u4e2a\u6e38\u620f\uff0c\u5728\u5df2\u542f\u52a8\u7684\u9a71\u52a8\u5668\u4e0a'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s\uff1a\u6709\u4e9b\u6587\u4ef6\u65e0\u6cd5\u5220\u9664'\n+  '%u partitions, all in order': '%u \u4e2a\u5206\u533a\uff0c\u5168\u90e8\u6b63\u5e38'\n+  ', %d art images': \u3001%d \u5f20\u5c01\u9762\u56fe\u7247\n+  ', and ': \uff0c\u4ee5\u53ca\n+  ', its cheats': \u3001\u5b83\u7684\u91d1\u624b\u6307\n+  ', its settings': \u3001\u5b83\u7684\u8bbe\u7f6e\n+  1 game, on the started drives: 1 \u4e2a\u6e38\u620f\uff0c\u5728\u5df2\u542f\u52a8\u7684\u9a71\u52a8\u5668\u4e0a\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: \u8fd9\u91cc\u7684 PS1 \u6e38\u620f\u8981\u5728\u8bb0\u5fc6\u6587\u4ef6\u4e2d\u5220\u9664\uff0c\u5728\u90a3\u91cc\u5b83\u662f\u5355\u72ec\u7684\u6587\u4ef6\u3002\n+  A network catalogue's games are deleted on the PC that serves them.: \u7f51\u7edc\u76ee\u5f55\u7684\u6e38\u620f\u8981\u5728\u63d0\u4f9b\u5b83\u4eec\u7684\u7535\u8111\u4e0a\u5220\u9664\u3002\n+  DELETE FOR GOOD: \u6c38\u4e45\u5220\u9664\n+  DELETE IT: \u5220\u9664\u5b83\n+  'DELETED %s: %s FREED': \u5df2\u5220\u9664 %s\uff1a\u91ca\u653e %s\n+  'Delete %s from %s? Goes: %s.': \u5220\u9664 %s\uff08\u5728 %s \u4e0a\uff09\uff1f\u5220\u9664\uff1a%s\u3002\n+  'Delete %s from %s? Goes: %s. Kept: %s.': \u5220\u9664 %s\uff08\u5728 %s \u4e0a\uff09\uff1f\u5220\u9664\uff1a%s\u3002\u4fdd\u7559\uff1a%s\u3002\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: \u4e5f\u5220\u9664\u5b83\u7684\u5b58\u6863\u548c RIPSHOT \u7167\u7247\u5417\uff1f\u9009\u201c\u5426\u201d\u5219\u4fdd\u7559\u3002\n+  HIDDEN GAMES: \u9690\u85cf\u7684\u6e38\u620f\n+  'HIDDEN: %s': \u5df2\u9690\u85cf\uff1a%s\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: \u9690\u85cf %s\uff1f\u5b83\u4f1a\u79bb\u5f00\u6240\u6709\u5217\u8868\uff0c\u5305\u62ec\u5168\u90e8\u6e38\u620f\u548c\u6536\u85cf\u5939\u3002\u8bbe\u7f6e > \u6e38\u620f\u6765\u6e90 > \u9690\u85cf\u7684\u6e38\u620f \u53ef\u4ee5\u518d\u663e\u793a\u5b83\u3002\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: \u6700\u540e\u786e\u8ba4\u3002%s \u5c06\u88ab\u6c38\u4e45\u5220\u9664\uff0c\u4efb\u4f55\u65b9\u6cd5\u90fd\u65e0\u6cd5\u6062\u590d\u3002\u6309 START \u5220\u9664\u3002\n+  None. Hide Game, in a game's options, puts one here.: \u6ca1\u6709\u3002\u5728\u6e38\u620f\u9009\u9879\u4e2d\u9009\u62e9\u201c\u9690\u85cf\u6e38\u620f\u201d\u5373\u53ef\u653e\u5230\u8fd9\u91cc\u3002\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : \u672a\u5220\u9664\u4efb\u4f55\u5185\u5bb9\uff1a\u8fd9\u4e2a\u9a71\u52a8\u5668\u7684\u5206\u533a\u8868\u4e0e\u8fd9\u5757\u786c\u76d8\u4e0d\u7b26\uff08%s\uff09\u3002\u5b83\u53ef\u80fd\u662f\u4ece\u5176\u4ed6\u5bb9\u91cf\u786c\u76d8\u7684\u955c\u50cf\u5199\u5165\u7684\u3002\u8bf7\u5148\u5728\u7535\u8111\u4e0a\u4fee\u590d\u3002\n+  RIPPS2 cannot delete a game from this page.: RIPPS2 \u65e0\u6cd5\u5728\u6b64\u9875\u9762\u5220\u9664\u6e38\u620f\u3002\n+  RIPPS2 could not find this game's files on its drive.: RIPPS2 \u5728\u6e38\u620f\u6240\u5728\u7684\u9a71\u52a8\u5668\u4e0a\u627e\u4e0d\u5230\u5b83\u7684\u6587\u4ef6\u3002\n+  Reading the hidden lists: \u6b63\u5728\u8bfb\u53d6\u9690\u85cf\u5217\u8868\n+  SHOW AGAIN OR DELETE: \u91cd\u65b0\u663e\u793a\u6216\u5220\u9664\n+  SHOW IT AGAIN: \u91cd\u65b0\u663e\u793a\n+  That game is not on its drive any more.: \u8be5\u6e38\u620f\u5df2\u4e0d\u5728\u5b83\u7684\u9a71\u52a8\u5668\u4e0a\u3002\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: \u65e0\u6cd5\u628a\u9690\u85cf\u5217\u8868\u5199\u5165\u6e38\u620f\u6240\u5728\u7684\u9a71\u52a8\u5668\u3002\u5b83\u662f\u5426\u5199\u4fdd\u62a4\uff0c\u6216\u8005\u5199\u5165\u529f\u80fd\u5df2\u5173\u95ed\uff1f\n+  The partition could not be removed (%d). Nothing was deleted.: \u65e0\u6cd5\u79fb\u9664\u5206\u533a\uff08%d\uff09\u3002\u672a\u5220\u9664\u4efb\u4f55\u5185\u5bb9\u3002\n+  This drive cannot keep a hidden list (a network catalogue).: \u8fd9\u4e2a\u9a71\u52a8\u5668\u65e0\u6cd5\u4fdd\u5b58\u9690\u85cf\u5217\u8868\uff08\u7f51\u7edc\u76ee\u5f55\uff09\u3002\n+  This game's drive has not answered yet. Wait for it, then try again.: \u8fd9\u4e2a\u6e38\u620f\u7684\u9a71\u52a8\u5668\u8fd8\u6ca1\u6709\u54cd\u5e94\u3002\u8bf7\u7b49\u5b83\u5c31\u7eea\u540e\u518d\u8bd5\u3002\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: \u8fd9\u4e2a\u6e38\u620f\u7684\u5206\u533a\u5c5e\u4e8e RIPPS2 \u4e0d\u4f1a\u79fb\u9664\u7684\u5206\u533a\uff08\u7cfb\u7edf\u5206\u533a\uff0c\u6216\u5b58\u653e\u5176\u8bbe\u7f6e\u7684\u5206\u533a\uff09\u3002\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': \u6709\u4e2a\u5206\u533a\u6bd4\u8fd9\u5757\u786c\u76d8\u5141\u8bb8\u7684\u66f4\u5927\uff1a\u5206\u533a\u8868\u662f\u4e3a\u53e6\u4e00\u5bb9\u91cf\u7684\u786c\u76d8\u5236\u4f5c\u7684\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': \u6709\u4e2a\u5206\u533a\u8d85\u51fa\u4e86\u8fd9\u5757\u786c\u76d8\u7684\u672b\u5c3e\uff08\u6247\u533a %u / %u\uff09\uff1a\u5206\u533a\u8868\u662f\u4e3a\u66f4\u5927\u7684\u786c\u76d8\u5236\u4f5c\u7684\n+  its RIPSHOT pictures: \u5b83\u7684 RIPSHOT \u7167\u7247\n+  its drive: \u5b83\u7684\u9a71\u52a8\u5668\n+  its saves: \u5b83\u7684\u5b58\u6863\n+  sector %u holds no partition header: \u6247\u533a %u \u6ca1\u6709\u5206\u533a\u5934\n+  the chain does not close at __mbr: \u94fe\u6ca1\u6709\u5728 __mbr \u5904\u95ed\u5408\n+  the chain skips from sector %u: \u94fe\u4ece\u6247\u533a %u \u5904\u8df3\u8dc3\n+  the chain's links disagree at sector %u: \u94fe\u7684\u73af\u8282\u5728\u6247\u533a %u \u5904\u4e0d\u4e00\u81f4\n+  the drive's size could not be read: \u65e0\u6cd5\u8bfb\u53d6\u9a71\u52a8\u5668\u7684\u5bb9\u91cf\n+  the game (%s): \u6e38\u620f\u672c\u4f53\uff08%s\uff09\n+  the game's folder and everything in it (%s): \u6e38\u620f\u6587\u4ef6\u5939\u53ca\u5176\u4e2d\u7684\u4e00\u5207\uff08%s\uff09\n+  the header at sector %u could not be read: \u65e0\u6cd5\u8bfb\u53d6\u6247\u533a %u \u7684\u5206\u533a\u5934\n+  the header at sector %u fails its checksum: \u6247\u533a %u \u7684\u5206\u533a\u5934\u6821\u9a8c\u548c\u4e0d\u901a\u8fc7\n+  the partition at sector %u is not where or as large as APA puts one: \u6247\u533a %u \u7684\u5206\u533a\u4e0d\u5728 APA \u653e\u7f6e\u5206\u533a\u7684\u4f4d\u7f6e\uff0c\u6216\u5927\u5c0f\u4e0d\u7b26\n+  the settings and art another copy of it uses: \u5b83\u7684\u53e6\u4e00\u4efd\u526f\u672c\u4f7f\u7528\u7684\u8bbe\u7f6e\u548c\u5c01\u9762\n+  '%d FOLDERS': '%d \u4e2a\u6587\u4ef6\u5939'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': \u65e0\u6cd5\u628a %s \u590d\u5236\u5230 %s\u3002\u9a71\u52a8\u5668\u662f\u5426\u5df2\u6ee1\u6216\u5199\u4fdd\u62a4\uff1f\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s \u4e0d\u5728 RIPPS2 \u67e5\u627e\u7684\u4f4d\u7f6e\u3002\u8981\u5728 RIPPS2 \u5df2\u542f\u52a8\u7684\u9a71\u52a8\u5668\u4e0a\u641c\u7d22\u5b83\u5417\uff1f\u627e\u5230\u7684\u4f4d\u7f6e\u4f1a\u5728\u4e4b\u540e\u6bcf\u6b21\u542f\u52a8\u65f6\u8bb0\u4f4f\u3002'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : Ember \u4e0d\u5728\u6e38\u620f\u65c1\u8fb9\u7684 %s \u4e2d\uff0c\u4f46\u5b83\u5fc5\u987b\u5728\u90a3\u91cc\u3002RIPPS2 \u5728 %s \u627e\u5230\u4e86\u5b83\uff1aSTART \u4f1a\u628a\u5b83\u590d\u5236\u8fc7\u53bb\uff08%d KB\uff09\u5e76\u542f\u52a8\u6e38\u620f\u3002\n+  Ember's BIOS: Ember \u7684 BIOS\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : Ember \u7684 BIOS\uff08bios.bin\uff09\u4e0d\u5728 Ember \u65c1\u8fb9\u7684 %s \u4e2d\u3002RIPPS2 \u5728 %s \u627e\u5230\u4e86\u4e00\u4e2a\uff1aSTART \u4f1a\u628a\u5b83\u590d\u5236\u8fc7\u53bb\uff08%d KB\uff09\u5e76\u542f\u52a8\u6e38\u620f\u3002\n+  LOOKING FOR YOUR LAUNCHERS: \u6b63\u5728\u67e5\u627e\u4f60\u7684\u542f\u52a8\u5668\n+  The POPS files: POPS \u6587\u4ef6\n+  '%s  MMCE card': '%s  MMCE \u5361'\n+  INSTALL RIPPS2'S NEUTRINO: \u5b89\u88c5 RIPPS2 \u7684 NEUTRINO\n+  'Internal HDD (APA): %s free of %s': \u5185\u7f6e\u786c\u76d8\uff08APA\uff09\uff1a\u53ef\u7528 %s / \u5171 %s\n+  LAUNCHERS: \u542f\u52a8\u5668\n+  LOOK FOR THEM: \u67e5\u627e\u5b83\u4eec\n+  'Memory card %d: %d KB free': \u8bb0\u5fc6\u5361 %d\uff1a\u53ef\u7528 %d KB\n+  POPS files: POPS \u6587\u4ef6\n+  START EVERY DRIVE: \u542f\u52a8\u6240\u6709\u9a71\u52a8\u5668\n+  'STATUS: looking at the drives and the homebrew': \u72b6\u6001\uff1a\u6b63\u5728\u67e5\u770b\u9a71\u52a8\u5668\u548c\u81ea\u5236\u7a0b\u5e8f\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): \u5148\u542f\u52a8\u6240\u6709\u9a71\u52a8\u5668\uff0c\u8ba9\u641c\u7d22\u8986\u76d6\u5168\u90e8\u5417\uff1f\uff08\u9009\u201c\u5426\u201d\u5219\u53ea\u641c\u7d22\u5df2\u542f\u52a8\u7684\u9a71\u52a8\u5668\u3002\uff09\n+  USB, HDD, MMCE and the rest: USB\u3001\u786c\u76d8\u3001MMCE \u7b49\n+  every started drive: \u6bcf\u4e2a\u5df2\u542f\u52a8\u7684\u9a71\u52a8\u5668\n+  found where you keep them: \u5728\u4f60\u5b58\u653e\u7684\u4f4d\u7f6e\u627e\u5230\n+  not found yet: \u5c1a\u672a\u627e\u5230\n+  not on a started drive: \u4e0d\u5728\u5df2\u542f\u52a8\u7684\u9a71\u52a8\u5668\u4e0a\n+  only if you have none: \u4ec5\u5728\u4f60\u6ca1\u6709\u65f6\n+  starts every drive first: \u5148\u542f\u52a8\u6240\u6709\u9a71\u52a8\u5668\n+  Drives, launchers and homebrew: \u9a71\u52a8\u5668\u3001\u542f\u52a8\u5668\u548c\u81ea\u5236\u7a0b\u5e8f\n+  BDM Devices: BDM \u8bbe\u5907\n+  Color Sweep: \u989c\u8272\u6e10\u53d8\n+  Colors and Text: \u989c\u8272\u4e0e\u6587\u5b57\n+  HDD POPS partitions: \u786c\u76d8\u7684 POPS \u5206\u533a\n+  Layout: \u5e03\u5c40\n+  Missing Covers: \u7f3a\u5c11\u7684\u5c01\u9762\n+  Name: \u540d\u79f0\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: \u8fd8\u6ca1\u6709\u627e\u5230 POPSTARTER \u7684 SMB \u8bbe\u7f6e\u3002\u8bf7\u586b\u5199\u4e0b\u9762\u51e0\u884c\u3002\n+  POPSTARTER Network: POPSTARTER \u7f51\u7edc\n+  POPSTARTER SMB Network: POPSTARTER SMB \u7f51\u7edc\n+  POPSTARTER UDPFS Network: POPSTARTER UDPFS \u7f51\u7edc\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: \u9a71\u52a8\u5668\u3001ISO\u3001VMC \u548c\u89c6\u9891\u6a21\u5f0f\u7531 RIPPS2 \u81ea\u5df1\u52a0\u4e0a\u3002\n+  Screen: \u753b\u9762\n+  Speed: \u901f\u5ea6\n+  Sweep Colors: \u6e10\u53d8\u989c\u8272\n+  Text Weight: \u6587\u5b57\u7c97\u7ec6\n+  Theme Color: \u4e3b\u8272\n+  Audio: \u97f3\u9891\n+  Bold: \u7c97\n+  Controllers: \u624b\u67c4\n+  Lively: \u6d3b\u6cfc\n+  Medium: \u4e2d\u7b49\n+  PS Emulation: PS \u6a21\u62df\n+  Regular: \u5e38\u89c4\n+  Slow: \u7f13\u6162\n+  A MOMENT: \u7a0d\u7b49\n+  A TASK IN THE BACKGROUND: \u540e\u53f0\u4efb\u52a1\n+  ABOUT %d S: \u7ea6 %d \u79d2\n+  ANY MOMENT: \u9a6c\u4e0a\n+  READING THE GAME LIST OF %s: \u6b63\u5728\u8bfb\u53d6 %s \u7684\u6e38\u620f\u5217\u8868\n+  SAVING A LIST: \u6b63\u5728\u4fdd\u5b58\u5217\u8868\n+  START NOW: \u7acb\u5373\u5f00\u59cb\n+  STARTING: \u6b63\u5728\u5f00\u59cb\n+  STARTS NEXT: \u63a5\u4e0b\u6765\u5f00\u59cb\n+  STOPPING IT SAFELY: \u6b63\u5728\u5b89\u5168\u505c\u6b62\n+  THE LAST OF THE QUEUE: \u961f\u5217\u672b\u5c3e\n+  WHEN IT FINISHES: \u5b8c\u6210\u4e4b\u540e\n+  What each card starts; install or repair BIBLE.: \u6bcf\u5f20\u5361\u542f\u52a8\u4ec0\u4e48\uff1b\u5b89\u88c5\u6216\u4fee\u590d BIBLE\u3002\n+  Rainbow: \u5f69\u8679\n+  Custom: \u81ea\u5b9a\u4e49\n+  DESR DVR ISOs (experimental): DESR DVR ISO\uff08\u5b9e\u9a8c\u6027\uff09\n+  PC CARD CF (experimental): PC CARD CF\uff08\u5b9e\u9a8c\u6027\uff09\n+  HDD Start Mode: \u786c\u76d8 \u542f\u52a8\u65b9\u5f0f\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): The Roboto Project Authors \u7684 Roboto Condensed\uff08SIL OFL 1.1\uff09\n+  '%s on %s': '%s\uff0c\u4f4d\u4e8e %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s\uff1a%s \u53d8\u4e3a %s (%d%%)\uff0c\u6bcf\u4e00\u5757\u5747\u5df2\u6821\u9a8c\u3002\u5220\u9664 ISO \u5e76\u4fdd\u7559 ZSO\uff1f'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s\uff1a%s \u53c8\u53d8\u56de %s\uff0c\u6bcf\u4e00\u5757\u5747\u5df2\u6821\u9a8c\u3002\u5220\u9664 ZSO \u5e76\u4fdd\u7559 ISO\uff1f'\n+  A ZSO of this game is already beside it. Remove one of the two first.: \u65c1\u8fb9\u5df2\u6709\u6b64\u6e38\u620f\u7684 ZSO\u3002\u8bf7\u5148\u5220\u9664\u5176\u4e2d\u4e00\u4e2a\u3002\n+  About %d min, then checked: \u7ea6 %d \u5206\u949f\uff0c\u7136\u540e\u6821\u9a8c\n+  An ISO of this game is already beside it. Remove one of the two first.: \u65c1\u8fb9\u5df2\u6709\u6b64\u6e38\u620f\u7684 ISO\u3002\u8bf7\u5148\u5220\u9664\u5176\u4e2d\u4e00\u4e2a\u3002\n+  CHECKING EVERY PIECE: \u6b63\u5728\u6821\u9a8c\u6bcf\u4e00\u5757\n+  COMPRESS TO ZSO: \u538b\u7f29\u4e3a ZSO\n+  'COMPRESSED %s: %s FREED': \u5df2\u538b\u7f29 %s\uff1a\u91ca\u653e %s\n+  COMPRESSING 1 OF 2: \u538b\u7f29\u4e2d 1/2\n+  COMPRESSING 2 OF 2: \u538b\u7f29\u4e2d 2/2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : \u538b\u7f29 %s\uff1f\u5728 ZSO \u88ab\u8bfb\u56de\u5e76\u6821\u9a8c\u4e4b\u524d ISO \u4f1a\u4fdd\u7559\uff0c\u4e4b\u540e RIPPS2 \u4f1a\u5728\u5220\u9664 ISO \u524d\u8be2\u95ee\u3002\u5de5\u4f5c\u671f\u95f4\u9a71\u52a8\u5668\u9700\u8981\u540c\u65f6\u5bb9\u7eb3\u4e24\u8005\u7684\u7a7a\u95f4\uff1b\u5b8c\u6210\u524d\u8bf7\u4e0d\u8981\u5173\u95ed\u4e3b\u673a\u3002ZSO \u53ea\u80fd\u7528 OPL \u6838\u5fc3\u542f\u52a8\uff08\u4e0d\u652f\u6301 Neutrino\uff09\uff1b\u5b83\u7684\u5b58\u6863\u3001\u8bbe\u7f6e\u3001\u56fe\u7247\u548c\u91d1\u624b\u6307\u90fd\u968f\u5b83\u4fdd\u7559\uff0c\u5e76\u53ef\u968f\u65f6\u8f6c\u56de ISO\u3002\n+  Compress to ZSO: \u538b\u7f29\u4e3a ZSO\n+  Cycle: \u8f6e\u6362\n+  'Experimental: OPL core only (not Neutrino)': \u5b9e\u9a8c\u6027\uff1a\u4ec5 OPL \u6838\u5fc3\uff08\u4e0d\u652f\u6301 Neutrino\uff09\n+  Hold SELECT: \u6309\u4f4f SELECT\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: \u65e0\u6cd5\u5b8c\u6210\uff08%s\uff09\u3002\u5df2\u5199\u5165\u7684\u5185\u5bb9\u5df2\u5220\u9664\uff1b\u6e38\u620f\u4fdd\u6301\u539f\u6837\u3002\n+  List: \u5217\u8868\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : \u5c06 %s \u8f6c\u56de ISO\uff1f\u5728 ISO \u88ab\u8bfb\u56de\u5e76\u6821\u9a8c\u4e4b\u524d ZSO \u4f1a\u4fdd\u7559\uff0c\u4e4b\u540e RIPPS2 \u4f1a\u5728\u5220\u9664 ZSO \u524d\u8be2\u95ee\u3002\u5de5\u4f5c\u671f\u95f4\u9a71\u52a8\u5668\u9700\u8981\u540c\u65f6\u5bb9\u7eb3\u4e24\u8005\u7684\u7a7a\u95f4\uff1b\u5b8c\u6210\u524d\u8bf7\u4e0d\u8981\u5173\u95ed\u4e3b\u673a\u3002\u5b83\u7684\u5b58\u6863\u3001\u8bbe\u7f6e\u3001\u56fe\u7247\u548c\u91d1\u624b\u6307\u90fd\u968f\u5b83\u4fdd\u7559\u3002\n+  Stop? What it has written so far is removed; the game stays as it was.: \u505c\u6b62\uff1f\u76ee\u524d\u5df2\u5199\u5165\u7684\u5185\u5bb9\u4f1a\u88ab\u5220\u9664\uff1b\u6e38\u620f\u4fdd\u6301\u539f\u6837\u3002\n+  THEMES: \u4e3b\u9898\n+  There is not enough memory for this just now.: \u76ee\u524d\u5185\u5b58\u4e0d\u8db3\uff0c\u65e0\u6cd5\u6267\u884c\u6b64\u64cd\u4f5c\u3002\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: RIPPS2 \u65e0\u6cd5\u538b\u7f29\u6216\u89e3\u538b\u6b64\u955c\u50cf\uff08\u5176\u5927\u5c0f\u4e0d\u662f\u5b8c\u6574\u7684 2 KB \u5757\uff09\u3002\n+  UNCOMPRESS TO ISO: \u89e3\u538b\u4e3a ISO\n+  UNCOMPRESSED %s: \u5df2\u89e3\u538b %s\n+  UNCOMPRESSING: \u89e3\u538b\u4e2d\n+  Uncompress to ISO: \u89e3\u538b\u4e3a ISO\n+  Under a minute, then checked: \u4e0d\u5230\u4e00\u5206\u949f\uff0c\u7136\u540e\u6821\u9a8c\n+  COPYING: \u6b63\u5728\u590d\u5236\n+  SHOW AGAIN, DELETE OR COMPRESS: \u91cd\u65b0\u663e\u793a\u3001\u5220\u9664\u6216\u538b\u7f29\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: \u91cd\u65b0\u663e\u793a\u3001\u5220\u9664\u6216\u89e3\u538b\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': '%s \u5904\u7406\u671f\u95f4\u9700\u8981\u5176\u6240\u5728\u9a71\u52a8\u5668\u7ea6 %s \u7684\u53ef\u7528\u7a7a\u95f4\uff08\u539f\u6587\u4ef6\u4f1a\u4fdd\u7559\u5230\u6700\u540e\uff09\uff0c\u800c\u9a71\u52a8\u5668\u53ea\u6709 %s \u53ef\u7528\u3002'\n+  Hide and Compress: \u9690\u85cf\u5e76\u538b\u7f29\n+  'COMPRESSED AND HIDDEN: %s': \u5df2\u538b\u7f29\u5e76\u9690\u85cf\uff1a%s\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': \u6ca1\u6709\u8db3\u591f\u7a7a\u95f4\u8fdb\u884c\u6b64\u6b21\u63d0\u53d6\uff1a\u5927\u7ea6\u9700\u8981 %s\uff0c\u9a71\u52a8\u5668\u5269\u4f59 %s\u3002\n+  OVER 4 GB FOR FAT32: FAT32 \u8d85\u8fc7 4 GB\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: \u6b64\u9a71\u52a8\u5668\u4e3a %s\uff0c\u65e0\u6cd5\u4fdd\u5b58 4 GB \u6216\u66f4\u5927\u7684\u6587\u4ef6\uff0c\u800c\u65b0\u955c\u50cf\u5c06\u4e3a %s\u3002\n+  '%s, %s clusters, %s free of %s': '%s\uff0c\u7c07 %s\uff0c\u53ef\u7528 %s / %s'\n+  Delete This Sweep: \u5220\u9664\u6b64\u6e10\u53d8\n+  Delete %s? RIPPS2's own sweeps stay as they are.: \u5220\u9664 %s\uff1fRIPPS2 \u81ea\u5e26\u7684\u6e10\u53d8\u4fdd\u6301\u4e0d\u53d8\u3002\n+  Sweep: \u6e10\u53d8\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: \u5fbd\u7ae0\u4f1a\u5728\u5217\u8868\u4e2d\u6807\u51fa\u6709\u6210\u5c31\u7684\u6e38\u620f\uff0c\u4ee5\u53ca\u4f60\u5df2\u89e3\u9501\u7684\u6570\u91cf\u3002\u9700\u8981\u5f00\u542f\u9065\u6d4b\u3002\n labels:\n   MTAP_ENABLE: \u591a\u5206\u63d2\u6a21\u62df\n   HINT_PADEMU_WORKAROUND: \u90e8\u5206\u4eff\u5236 DS3 \u9700\u8981\u53d8\u901a\u5904\u7406\uff0c\u6b64\u9009\u9879\u4f1a\u542f\u7528\u5b83\u3002\n@@ -1202,7 +1369,7 @@ labels:\n   SAVE_ICONS: 3D \u5b58\u6863\u56fe\u6807\n   HINT_SAVE_ICONS: \u5728\u4e3b\u9898\u7559\u6709\u4f4d\u7f6e\u7684\u5730\u65b9\u663e\u793a\u6240\u9009\u6e38\u620f\u7684\u65cb\u8f6c\u5b58\u6863\u56fe\u6807\u3002\u5148\u8bfb\u53d6\u6e38\u620f\u7684 VMC\uff0c\u518d\u8bfb\u53d6\u8bb0\u5fc6\u5361\u3002\n   NETBOOT_RESTART_NOW: \u7f51\u7edc\u534f\u8bae\u4f1a\u5728\u91cd\u542f\u540e\u66f4\u6539\u3002\u73b0\u5728\u4fdd\u5b58\u8bbe\u7f6e\u5e76\u91cd\u542f OPL \u5417\uff1f\n-  HINT_NET_PROTOCOL: RiptOPL \u4f7f\u7528\u54ea\u79cd\u7f51\u7edc\u670d\u52a1\uff1aSMB \u5171\u4eab\u3001UDPFS\u3001UDPBD \u6216 HTTP\u3002\u4e0e\u201c\u6e38\u620f\u6765\u6e90\u201d\u4e2d\u7684\u8bbe\u7f6e\u76f8\u540c\u3002\u4ece\u5df2\u5728\u8fd0\u884c\u7684\u670d\u52a1\u5207\u6362\u8d70\u9700\u8981\u91cd\u542f\u3002\n+  HINT_NET_PROTOCOL: RiptOPL \u4f7f\u7528\u54ea\u79cd\u7f51\u7edc\u670d\u52a1\uff1aSMB \u5171\u4eab\u3001UDPFS\u3001UDPBD \u6216 HTTP\u3002\u4ece\u5df2\u5728\u8fd0\u884c\u7684\u670d\u52a1\u5207\u6362\u8d70\u9700\u8981\u91cd\u542f\u3002\n   HINT_UDPFS_ACCESS: \u4ec5 UDPFS\u3002\u201c\u6587\u4ef6\u201d\u4ece\u670d\u52a1\u5668\u7684\u6e38\u620f\u6587\u4ef6\u5939\u8bfb\u53d6\u5355\u72ec\u7684 ISO\uff1bIMG \u628a\u78c1\u76d8\u955c\u50cf\u5f53\u4f5c\u9a71\u52a8\u5668\u8bfb\u53d6\u3002SMB \u548c HTTP \u59cb\u7ec8\u4f7f\u7528\u201c\u6587\u4ef6\u201d\uff0cUDPBD \u59cb\u7ec8\u4f7f\u7528 IMG\u3002\n   HINT_SMB_VERSION: \u4e0e\u7535\u8111\u5171\u4eab\u901a\u4fe1\u65f6\u4f7f\u7528\u7684 SMB \u7248\u672c\u3002\n   HINT_NET_ADVANCED: \u89e3\u9501\u4ee5\u592a\u7f51\u8fde\u63a5\u6a21\u5f0f\u548c SMB \u7aef\u53e3\u7684\u7f16\u8f91\u3002\n@@ -1401,3 +1568,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: \u5404\u6e38\u620f\u80f6\u5377\u4e0a\u7b49\u5f85\u7684\u7167\u7247\u5982\u4f55\u53d8\u6210 PNG\u3002\n   RIPPS2_SETTINGS_HOME: \u8bbe\u7f6e\u4fdd\u5b58\u5230\n   HINT_RIPPS2_SETTINGS_HOME: RIPPS2 \u5b58\u653e\u8bbe\u7f6e\u7684\u4f4d\u7f6e\uff1a\u8bb0\u5fc6\u5361\u4e0a\u5b83\u81ea\u5df1\u7684\u5b58\u6863\u3001\u9a71\u52a8\u5668\uff08\u5185\u7f6e\u786c\u76d8\u3001USB\u3001MX4SIO\u3001iLink \u6216 MMCE\uff09\u4e0a\u7684 RIPPS2 \u6587\u4ef6\u5939\uff0c\u6216\u4f60\u8f93\u5165\u7684\u6587\u4ef6\u5939\u3002\n+  RIPPS2_LAUNCHERS: \u542f\u52a8\u5668\n+  RIPPS2_LOOK_NOW: \u7acb\u5373\u67e5\u627e\n+  HINT_RIPPS2_LOOK_NOW: RIPPS2 \u4f1a\u5728\u5df2\u542f\u52a8\u7684\u9a71\u52a8\u5668\u4e0a\u67e5\u627e POPSTARTER\u3001POPS \u6587\u4ef6\u3001Ember\u3001Ember \u7684 BIOS \u548c Neutrino\uff0c\u5e76\u8bb0\u4f4f\u6bcf\u4e00\u4e2a\u7684\u4f4d\u7f6e\u3002\u6bcf\u6b21\u542f\u52a8\u90fd\u4ece\u90a3\u91cc\u8bfb\u53d6\u3002\n+  RIPPS2_HIDE_GAME: \u9690\u85cf\u6e38\u620f\n+  RIPPS2_HIDDEN_GAMES: \u9690\u85cf\u7684\u6e38\u620f\n+  HINT_RIPPS2_HIDDEN_GAMES: \u7528\u201c\u9690\u85cf\u6e38\u620f\u201d\u9690\u85cf\u7684\u6e38\u620f\uff0c\u5728\u5df2\u542f\u52a8\u7684\u9a71\u52a8\u5668\u4e0a\u3002\u53ef\u4ee5\u91cd\u65b0\u663e\u793a\u5176\u4e2d\u4e00\u4e2a\uff0c\u6216\u5220\u9664\u5b83\u3002\u6bcf\u4e2a\u9a71\u52a8\u5668\u5404\u6709\u81ea\u5df1\u7684\u5217\u8868\u3002\n+  HINT_RIPPS2_BDM_DEVICES: BDM \u8bfb\u53d6\u6e38\u620f\u7684\u9a71\u52a8\u5668\uff1aUSB\u3001MX4SIO\u3001PC Card CF\u3001DESR \u7684 DVR \u9a71\u52a8\u5668\u548c iLink\u3002\u5185\u7f6e\u786c\u76d8\u8ddf\u968f\u786c\u76d8\u542f\u52a8\u65b9\u5f0f\u3002\n+  HINT_RIPPS2_SCREEN: \u83dc\u5355\u7684\u753b\u9762\uff1a\u89c6\u9891\u6a21\u5f0f\u3001\u5bbd\u5c4f\u3001\u5728\u7535\u89c6\u4e0a\u7684\u4f4d\u7f6e\uff08H-POS\u3001V-POS\uff09\u548c\u8fc7\u626b\u63cf\u3002\n+  HINT_RIPPS2_LAYOUT: \u5206\u7c7b\u6240\u5728\u7684\u4f4d\u7f6e\u548c\u9875\u9762\u8fdb\u5165\u7684\u65b9\u5f0f\uff0c\u7136\u540e\u662f RIPFLOW \u7684\u5c01\u9762\u548c RIPGRID \u7684\u7f51\u683c\u3002\n+  HINT_RIPPS2_COLORS_TEXT: \u6587\u5b57\u7c97\u7ec6\u3001\u914d\u8272\u3001\u989c\u8272\u6e10\u53d8\u548c\u6bcf\u79cd\u989c\u8272\u3002\n+  HINT_RIPPS2_TEXT_WEIGHT: RIPPS2 \u81ea\u5df1\u6587\u5b57\u7684\u7c97\u7ec6\uff1a\u8bbe\u7f6e\u7684\u63d0\u793a\u3001\u72b6\u6001\u548c\u5173\u4e8e\u3002\u5e38\u89c4\u548c\u4ee5\u524d\u4e00\u6837\uff1b\u4e2d\u7b49\u548c\u7c97\u5728\u67d4\u548c\u7684\u753b\u9762\u4e0a\u66f4\u6613\u8bfb\u3002\n+  HINT_RIPPS2_HDD_START: \u5185\u7f6e\u786c\u76d8\uff0c\u65e0\u8bba APA \u8fd8\u662f exFAT\uff0cRIPPS2 \u90fd\u4f1a\u8bfb\u53d6\u5e76\u53ea\u663e\u793a\u4e00\u6b21\uff0c\u4f5c\u4e3a\u786c\u76d8\u6e38\u620f\u3002\u5173\u95ed\u5219\u4e0d\u78b0\u5b83\u3002\n+  HINT_RIPPS2_THEME_COLOR: \u914d\u8272\u672c\u8eab\u7684\u989c\u8272\u3002\u628a\u914d\u8272\u9009\u4e3a\u81ea\u5b9a\u4e49\uff0c\u5c31\u80fd\u50cf\u6587\u5b57\u989c\u8272\u4e00\u6837\u81ea\u5df1\u6311\u9009\u3002\n+  HINT_RIPPS2_SWEEP: \u4e3b\u9898\u7f13\u6162\u5e73\u6ed1\u5730\u7ecf\u8fc7\u7684\u4e94\u79cd\u989c\u8272\u3002\u5f69\u8679\u3001\u7531\u6bcf\u79cd\u914d\u8272\u505a\u6210\u7684\u6e10\u53d8\uff0c\u6216\u4f60\u81ea\u5df1\u7684\u3002\u5173\u95ed\u8ba9\u914d\u8272\u4fdd\u6301\u4e0d\u52a8\u3002\n+  HINT_RIPPS2_SWEEP_COLORS: \u6e10\u53d8\u7684\u4e94\u79cd\u989c\u8272\u548c\u901f\u5ea6\u3002\u6539\u52a8 RIPPS2 \u7684\u67d0\u4e2a\u6e10\u53d8\uff0c\u5b83\u4f1a\u4ee5\u4f60\u8d77\u7684\u540d\u5b57\u4f5c\u4e3a\u4f60\u81ea\u5df1\u7684\u4fdd\u5b58\u4e0b\u6765\u3002\n+  HINT_RIPPS2_SWEEP_NAME: \u4f60\u7684\u6e10\u53d8\u5728\u989c\u8272\u6e10\u53d8\u5217\u8868\u4e2d\u7684\u540d\u5b57\u3002\n+  HINT_RIPPS2_SWEEP_COLOR: \u6e10\u53d8\u4f9d\u6b21\u7ecf\u8fc7\u7684\u4e94\u79cd\u989c\u8272\u4e4b\u4e00\u3002\n+  HINT_RIPPS2_SWEEP_SPEED: \u6bcf\u79cd\u989c\u8272\u505c\u7559\u591a\u4e45\u540e\u4e0b\u4e00\u79cd\u6de1\u5165\u3002\u5373\u4f7f\u6d3b\u6cfc\u4e5f\u5f88\u5e73\u6ed1\uff1a\u989c\u8272\u4ece\u4e0d\u95ea\u70c1\u3002\n+  RIPPS2_HIDE_COMPRESS: \u9690\u85cf\u6e38\u620f\u548c\u538b\u7f29\n+  HINT_RIPPS2_SELECT_HOLD: \u5728\u6e38\u620f\u5217\u8868\u4e0a\u6309\u4f4f SELECT 4.2 \u79d2\u3002\u5217\u8868\uff1a\u5728\u9762\u677f\u4e2d\u5217\u51fa\u4e3b\u9898\uff0c\u9009\u4e00\u4e2a\u3002\u8f6e\u6362\uff1a\u4e0b\u4e00\u4e2a\u4e3b\u9898\u3002\u5173\uff1a\u4e0d\u505a\u4efb\u4f55\u4e8b\uff08\u6309\u4e00\u4e0b\u4ecd\u4f1a\u5237\u65b0\uff09\u3002"
        },
        {
          "sha": "8064ccdf919f335a0a3d00dc373920ca562f6364",
          "filename": "elf/lang/Serbian.yml",
          "status": "modified",
          "additions": 209,
          "deletions": 22,
          "changes": 231,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FSerbian.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FSerbian.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FSerbian.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: Serbian\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTER POTVR\u0110UJE, ESC OTKAZUJE. BILO KOJE DUGME PRIKAZUJE TASTERE.\n   USB KEYBOARD: USB TASTATURA\n-  '      DESR DVR ISOs (experimental)': '      DESR DVR ISO datoteke (eksperimentalno)'\n-  '      Internal HDD (exFAT)': '      Unutra\u0161nji HDD (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (eksperimentalno)'\n-  '      Protocol': '      Protokol'\n   Access: Pristup\n   Applications Page Start Mode: Re\u017eim pokretanja stranice aplikacija\n   Art Delay: Ka\u0161njenje slika\n@@ -22,7 +18,6 @@ strings:\n   Fifth: Peti\n   First: Prvi\n   Fourth: \u010cetvrti\n-  HDD (APA) Start Mode: Re\u017eim pokretanja HDD-a (APA)\n   HDD Cache: HDD ke\u0161\n   HDD Spindown: Uspavljivanje HDD-a\n   MMCE Settings: MMCE pode\u0161avanja\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: Ovo je PS2 igra sa ESR zakrpom. Stavite ESR (esr*.elf) u APPS, BOOT ili ESR na memorijskoj kartici ili USB fle\u0161u da biste je pokrenuli.\n   '%s, BUILD %d': '%s, VERZIJA %d'\n   '... and the anonymous ...': '... i oni bezimeni ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': '1.8.0 prilo\u017een bez izmena (AFL-3.0): github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: Rastavljanje i ponovna izgradnja RiptOPL-a, ogranka Open PS2 Loader-a,\n   ABOUT: O PROGRAMU\n   'ACHIEVEMENTS: SPIRIT OF RA': 'DOSTIGNU\u0106A: SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): AVI video u Memorijskim datotekama, ogranak RIPPS2 (AFL 2.0)\n   Apollo Save Tool by bucanero: Apollo Save Tool, autor bucanero\n   BACK: NAZAD\n+  Back (keeps both): Nazad (zadr\u017eava oba)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: Ova MMCE kartica ne mo\u017ee da preimenuje datoteku, a kompresiji je to potrebno u poslednjem koraku. A\u017eurirajte njen sd2psXtd firmver na 1.4.0 ili noviji (sd2psxtd.github.io/download), pa poku\u0161ajte ponovo. Ni\u0161ta nije promenjeno.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): BIBLE je PS2BBL 1.2.0, potpisana izdanja El Isre (kartica i HDD), bez izmena (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: BIOS zvuci > Izdvoji ih reprodukuje kao SPU2; BIOS nije prilo\u017een\n   BOOT LOCK AND BIBLE: BOOT LOCK I BIBLE\n   BOOT LOCK edits its config too: BOOT LOCK menja i njegovu konfiguraciju\n   BUILD DETAILS: DETALJI VERZIJE\n-  BUILT ON RIPTOPL: IZGRA\u0110ENO NA RIPTOPL-U\n   BearSSL by Thomas Pornin: BearSSL, autor Thomas Pornin\n   Built with Claude (Anthropic): Napravljeno uz Claude (Anthropic)\n   CD PLAYER: CD PLEJER\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek i Sleek Bold, nacrtani za RIPPS2\n   RetroAchievements on the console by hacan359 (yoba): RetroAchievements na konzoli, autor hacan359 (yoba)\n   RiptOPL by NathanNeurotic: RiptOPL, autor NathanNeurotic\n-  Roboto Condensed by Google (Apache-2.0): Roboto Condensed, autor Google (Apache-2.0)\n   SOUNDS: ZVUCI\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: SP193-ov FMCB instalater i pcm720-ov OSDMenu, pra\u0107eni korak po korak\n   SUPPORT: PODR\u0160KA\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: wLaunchELF R3Z, autor NathanNeurotic\n   wLaunchELF by the ps2homebrew team: wLaunchELF, autor tim ps2homebrew\n   what RIPPS2's card image writes are checked against: sa \u010dime se proverava upisivanje slika kartica u RIPPS2\n-  with its own look, launchers, file browser and save tools.: sa sopstvenim izgledom, pokreta\u010dima, pregleda\u010dem datoteka i alatima za sa\u010duvane igre.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: xeRAbora, autor hacan359; rcheevos, autor RetroAchievements\n   your console's own, ripped from its BIOS: sopstveni zvuci va\u0161e konzole, izdvojeni iz njenog BIOS-a\n   ' (no DNS answer)': ' (nema DNS odgovora)'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO: upisivanje %.60s'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: Neutrino nije mogao da se upi\u0161e u %s. Proverite slobodan prostor i da nije za\u0161ti\u0107en od upisa, pa poku\u0161ajte ponovo.\n   THE GAME'S DRIVE  (%s/neutrino): URE\u0110AJ IGRE  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : Ova igra se pokre\u0107e preko Neutrina, a tamo gde RIPPS2 tra\u017ei (Pode\u0161avanja, ure\u0111aj igre, memorijske kartice) nema Neutrina. RIPPS2 nosi Neutrino 1.8.0 autora rickgaiser (AFL-3.0). Instalirati ga sada? Zatim birate gde.\n   '%d / %d FILES': '%d / %d DATOTEKA'\n   '%d FOLDERS AND CONTENTS': '%d FASCIKLI I SADR\u017dAJ'\n   '%d FOLDERS, %d FOUND': '%d FASCIKLI, PRONA\u0110ENO %d'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d od %d jo\u0161 zaklju\u010dano'\n   '%s   on %s': '%s   na %s'\n   BADGES: ZNA\u010cKE\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: Zna\u010dke rade samo sa uklju\u010denom TELEMETRIJOM i pokrenutim xeRAbora na va\u0161em ra\u010dunaru; do tada ne ko\u0161taju ni\u0161ta.\n   CHECK THE DISC: PROVERI DISK\n   From this game's last CHECK GAME SUPPORT.: Iz poslednje PROVERE PODR\u0160KE ZA IGRU za ovu igru.\n   HOW IT WORKS: KAKO RADI\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: Na ovoj konzoli je PS2BBL sa RIPPS2 zaklju\u010davanjem.\n   AUDIO folder: fascikla AUDIO\n   DRIVES: URE\u0110AJI\n-  INSTALL RIPPS2'S COPY: INSTALIRAJ RIPPS2 KOPIJU\n-  'Installed: ': 'Instalirano: '\n   MANAGE: UPRAVLJAJ\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: UPRAVLJAJ pokazuje \u0161ta pokre\u0107e svaka memorijska kartica i instalira ili popravlja BIBLE.\n   MEMORY CARD 0  (about 600 KB): MEMORIJSKA KARTICA 0  (oko 600 KB)\n   MEMORY CARD 1  (about 600 KB): MEMORIJSKA KARTICA 1  (oko 600 KB)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO: njegov drajver nije hteo da se u\u010dita (kod %d).'\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO: drajver je u\u010ditan, ali nijedna SD kartica nije odgovorila (slot mc1).'\n   NEUTRINO SETTINGS: NEUTRINO PODE\u0160AVANJA\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': 'Jo\u0161 nema: RIPPS2 nema sopstvene zvuke. Izdvojite zvuke ove konzole ili ih napravite u RIPPS2 Audio Studio.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: Nije instalirano. Igra kojoj je potrebno nudi RIPPS2 kopiju, ili instalirajte ovde.\n   POPSTARTER NETWORK: POPSTARTER MRE\u017dA\n   POPSTARTER and Ember: POPSTARTER i Ember\n   PS1 SETTINGS: PS1 PODE\u0160AVANJA\n   PS2BBL 1.2.0 for RIPPS2: PS2BBL 1.2.0 za RIPPS2\n   RIP THIS CONSOLE'S SOUNDS: IZDVOJI ZVUKE OVE KONZOLE\n-  RIPPS2's homebrew: RIPPS2 homebrew\n   STATUS: STATUS\n-  'STATUS: looking for RIPPS2''s homebrew': 'STATUS: tra\u017eenje RIPPS2 homebrew programa'\n   a drive of unknown kind: ure\u0111aj nepoznate vrste\n-  a drive or a memory card: ure\u0111aj ili memorijska kartica\n   boot loader, card by card: u\u010ditava\u010d, kartica po kartica\n   from its BIOS, into AUDIO: iz njenog BIOS-a, u AUDIO\n   internal HDD (exFAT): unutra\u0161nji HDD (exFAT)\n@@ -975,10 +958,194 @@ strings:\n   Sidebar: Bo\u010dna traka\n   your own copy, found on your drives and memory cards: va\u0161a sopstvena kopija, prona\u0111ena na va\u0161im diskovima i memorijskim karticama\n   'your own, found and launched: github.com/Gageformer/Ember/releases': 'va\u0161 sopstveni, prona\u0111en i pokrenut: github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': 'Nije prona\u0111en na mass0: (%s). Stavite ember.elf i svoj bios.bin u njegov folder EMBER.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: Nije prona\u0111en. Stavite svoj POPSTARTER.ELF u folder POPS na disku.\n   Launch Disc: Pokretanje diska\n   Memory Files: Memorijske datoteke\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: RIPPS2 je prona\u0161ao %d od 5. Svaki se pamti, a svako pokretanje ga \u010dita odatle.\n+  not found on the started drives: nije prona\u0111eno na pokrenutim ure\u0111ajima\n+  not looked for yet: jo\u0161 nije tra\u017eeno\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s nije odgovorio, pa %s ne mo\u017ee da se pokrene. Da li je povezan?'\n+  '%s is not on %s any more, so it has left All Games.': '%s vi\u0161e nije na %s, pa je napustila Sve igre.'\n+  STARTING %s: POKRE\u0106EM %s\n+  the SMB share: SMB deljenje\n+  the drive: ure\u0111aj\n+  the internal HDD (APA): unutra\u0161nji HDD (APA)\n+  the internal HDD (exFAT): unutra\u0161nji HDD (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: IZGRA\u0110ENO NA RIPTOPL-U, SA NEUTRINOM\n+  'Special thanks to all the YouTube love as well:': 'Posebna zahvalnost i za svu ljubav sa YouTube-a:'\n+  and its own look, launchers, file browser and save tools.: i sa sopstvenim izgledom, pokreta\u010dima, pregleda\u010dem datoteka i alatima za sa\u010duvane pozicije.\n+  glad to help however you reach me.: rado poma\u017eem, kako god da me na\u0111ete.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): drugo jezgro za igre, 1.8.0 prilo\u017eeno bez izmena (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: sa rickgaiserovim Neutrinom unutra kao drugim jezgrom za igre,\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : 'Ova igra se pokre\u0107e kroz Neutrino, a RIPPS2 ga nije prona\u0161ao: ni na putanji iz Pode\u0161avanja, ni na ure\u0111aju igre, ni na memorijskim karticama, ni na jednom pokrenutom ure\u0111aju. RIPPS2 nosi Neutrino 1.8.0 od rickgaisera (AFL-3.0): instalirajte ga jednom, gde izaberete, i svaka Neutrino igra ga koristi odatle.'\n+  STARTING THE DRIVES: POKRE\u0106EM URE\u0110AJE\n+  '%d games, on the started drives': '%d igara, na pokrenutim ure\u0111ajima'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s: NEKE DATOTEKE NISU MOGLE DA SE OBRI\u0160U'\n+  '%u partitions, all in order': '%u particija, sve u redu'\n+  ', %d art images': ', %d slika omota'\n+  ', and ': ', i '\n+  ', its cheats': ', njeni kodovi'\n+  ', its settings': ', njena pode\u0161avanja'\n+  1 game, on the started drives: 1 igra, na pokrenutim ure\u0111ajima\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: PS1 igra ovde se bri\u0161e u Memorijskim datotekama, gde je zasebna datoteka.\n+  A network catalogue's games are deleted on the PC that serves them.: Igre mre\u017enog kataloga bri\u0161u se na ra\u010dunaru koji ih slu\u017ei.\n+  DELETE FOR GOOD: OBRI\u0160I ZAUVEK\n+  DELETE IT: OBRI\u0160I JE\n+  'DELETED %s: %s FREED': '%s OBRISANA: OSLOBO\u0110ENO %s'\n+  'Delete %s from %s? Goes: %s.': 'Obrisati %s sa %s? Odlazi: %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': 'Obrisati %s sa %s? Odlazi: %s. Ostaje: %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: Obrisati i njene sa\u010duvane pozicije i RIPSHOT slike? Ne ih zadr\u017eava.\n+  HIDDEN GAMES: SKRIVENE IGRE\n+  'HIDDEN: %s': 'SKRIVENO: %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: Sakriti %s? Napu\u0161ta svaku listu, i Sve igre i Omiljene igre. Pode\u0161avanja > Izvori igara > Skrivene igre je ponovo prikazuju.\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: Poslednja provera. %s se bri\u0161e zauvek i ni\u0161ta ne mo\u017ee da je vrati. Pritisnite START da je obri\u0161ete.\n+  None. Hide Game, in a game's options, puts one here.: Nijedna. Sakrij igru, u opcijama igre, stavlja jednu ovde.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : 'Ni\u0161ta nije obrisano: tabela particija ovog ure\u0111aja ne odgovara ovom disku (%s). Mo\u017eda je upisana iz slike diska druge veli\u010dine. Prvo je popravite na ra\u010dunaru.'\n+  RIPPS2 cannot delete a game from this page.: RIPPS2 ne mo\u017ee da obri\u0161e igru sa ove stranice.\n+  RIPPS2 could not find this game's files on its drive.: RIPPS2 nije prona\u0161ao datoteke ove igre na njenom ure\u0111aju.\n+  Reading the hidden lists: \u010citam skrivene liste\n+  SHOW AGAIN OR DELETE: PONOVO PRIKA\u017dI ILI OBRI\u0160I\n+  SHOW IT AGAIN: PONOVO JE PRIKA\u017dI\n+  That game is not on its drive any more.: Ta igra vi\u0161e nije na svom ure\u0111aju.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: Skrivena lista nije mogla da se upi\u0161e na ure\u0111aj igre. Da li je za\u0161ti\u0107en od upisa, ili su Operacije Pisanja isklju\u010dene?\n+  The partition could not be removed (%d). Nothing was deleted.: Particija nije mogla da se ukloni (%d). Ni\u0161ta nije obrisano.\n+  This drive cannot keep a hidden list (a network catalogue).: Ovaj ure\u0111aj ne mo\u017ee da \u010duva skrivenu listu (mre\u017eni katalog).\n+  This game's drive has not answered yet. Wait for it, then try again.: Ure\u0111aj ove igre jo\u0161 nije odgovorio. Sa\u010dekajte ga, pa poku\u0161ajte ponovo.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: Particija ove igre je jedna od onih koje RIPPS2 ne uklanja (sistemska particija, ili ona sa njegovim pode\u0161avanjima).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': 'particija je ve\u0107a nego \u0161to ovaj disk dozvoljava: tabela je napravljena za disk druge veli\u010dine'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': 'particija prelazi kraj ovog diska (sektor %u od %u): tabela je napravljena za ve\u0107i disk'\n+  its RIPSHOT pictures: njene RIPSHOT slike\n+  its drive: njen ure\u0111aj\n+  its saves: njene sa\u010duvane pozicije\n+  sector %u holds no partition header: sektor %u nema zaglavlje particije\n+  the chain does not close at __mbr: lanac se ne zatvara na __mbr\n+  the chain skips from sector %u: lanac preska\u010de od sektora %u\n+  the chain's links disagree at sector %u: karike lanca se ne sla\u017eu na sektoru %u\n+  the drive's size could not be read: veli\u010dina ure\u0111aja nije mogla da se pro\u010dita\n+  the game (%s): igra (%s)\n+  the game's folder and everything in it (%s): fascikla igre i sve u njoj (%s)\n+  the header at sector %u could not be read: zaglavlje na sektoru %u nije moglo da se pro\u010dita\n+  the header at sector %u fails its checksum: zaglavlje na sektoru %u ne prolazi kontrolni zbir\n+  the partition at sector %u is not where or as large as APA puts one: particija na sektoru %u nije tamo gde, ni velika koliko, APA stavlja jednu\n+  the settings and art another copy of it uses: pode\u0161avanja i slike koje koristi druga njena kopija\n+  '%d FOLDERS': '%d FASCIKLI'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': '%s nije moglo da se kopira na %s. Da li je ure\u0111aj pun ili za\u0161ti\u0107en od upisa?'\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s nije tamo gde RIPPS2 tra\u017ei. Potra\u017eiti ga na ure\u0111ajima koje je RIPPS2 pokrenuo? Gde se prona\u0111e, pamti se za svako slede\u0107e pokretanje.'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Ember nije u %s, pored igara, a mora da bude. RIPPS2 ga je prona\u0161ao na %s: START ga kopira tamo (%d KB) i pokre\u0107e igru.'\n+  Ember's BIOS: Emberov BIOS\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Emberov BIOS (bios.bin) nije u %s, pored Embera. RIPPS2 je prona\u0161ao jedan na %s: START ga kopira tamo (%d KB) i pokre\u0107e igru.'\n+  LOOKING FOR YOUR LAUNCHERS: TRA\u017dIM VA\u0160E POKRETA\u010cE\n+  The POPS files: POPS datoteke\n+  '%s  MMCE card': '%s  MMCE kartica'\n+  INSTALL RIPPS2'S NEUTRINO: INSTALIRAJ RIPPS2-OV NEUTRINO\n+  'Internal HDD (APA): %s free of %s': 'Unutra\u0161nji HDD (APA): slobodno %s od %s'\n+  LAUNCHERS: POKRETA\u010cI\n+  LOOK FOR THEM: POTRA\u017dI IH\n+  'Memory card %d: %d KB free': 'Memorijska kartica %d: slobodno %d KB'\n+  POPS files: POPS datoteke\n+  START EVERY DRIVE: POKRENI SVE URE\u0110AJE\n+  'STATUS: looking at the drives and the homebrew': 'STATUS: pregledam ure\u0111aje i homebrew'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): Prvo pokrenuti sve ure\u0111aje, da pretraga stigne do svih? (Ne pretra\u017euje ve\u0107 pokrenute ure\u0111aje.)\n+  USB, HDD, MMCE and the rest: USB, HDD, MMCE i ostalo\n+  every started drive: svaki pokrenuti ure\u0111aj\n+  found where you keep them: prona\u0111eni gde ih dr\u017eite\n+  not found yet: jo\u0161 nije prona\u0111eno\n+  not on a started drive: nije na pokrenutom ure\u0111aju\n+  only if you have none: samo ako nemate nijedan\n+  starts every drive first: prvo pokre\u0107e sve ure\u0111aje\n+  Drives, launchers and homebrew: Ure\u0111aji, pokreta\u010di i homebrew\n+  BDM Devices: BDM ure\u0111aji\n+  Color Sweep: Smena boja\n+  Colors and Text: Boje i tekst\n+  HDD POPS partitions: POPS particije na HDD-u\n+  Layout: Raspored\n+  Missing Covers: Omoti koji nedostaju\n+  Name: Ime\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: Jo\u0161 nije prona\u0111eno SMB pode\u0161avanje za POPSTARTER. Popunite redove ispod.\n+  POPSTARTER Network: POPSTARTER mre\u017ea\n+  POPSTARTER SMB Network: POPSTARTER SMB mre\u017ea\n+  POPSTARTER UDPFS Network: POPSTARTER UDPFS mre\u017ea\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: RIPPS2 sam dodaje ure\u0111aj, ISO, VMC-ove i video re\u017eim.\n+  Screen: Ekran\n+  Speed: Brzina\n+  Sweep Colors: Boje smene\n+  Text Weight: Debljina teksta\n+  Theme Color: Glavna boja\n+  Audio: Zvuk\n+  Bold: Podebljano\n+  Controllers: Kontroleri\n+  Lively: \u017divahno\n+  Medium: Srednje\n+  PS Emulation: PS emulacija\n+  Regular: Obi\u010dno\n+  Slow: Sporo\n+  A MOMENT: TRENUTAK\n+  A TASK IN THE BACKGROUND: ZADATAK U POZADINI\n+  ABOUT %d S: OKO %d S\n+  ANY MOMENT: SVAKOG \u010cASA\n+  READING THE GAME LIST OF %s: \u010cITA SPISAK IGARA SA %s\n+  SAVING A LIST: \u010cUVA SPISAK\n+  START NOW: POKRENI SADA\n+  STARTING: POKRE\u0106E SE\n+  STARTS NEXT: POKRE\u0106E SE SLEDE\u0106E\n+  STOPPING IT SAFELY: BEZBEDNO ZAUSTAVLJA\n+  THE LAST OF THE QUEUE: KRAJ REDA\n+  WHEN IT FINISHES: KAD ZAVR\u0160I\n+  What each card starts; install or repair BIBLE.: \u0160ta pokre\u0107e svaka kartica; instalirajte ili popravite BIBLE.\n+  Rainbow: Duga\n+  Custom: Sopstveno\n+  DESR DVR ISOs (experimental): DESR DVR ISO datoteke (eksperimentalno)\n+  PC CARD CF (experimental): PC CARD CF (eksperimentalno)\n+  HDD Start Mode: Re\u017eim pokretanja HDD-a\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): Roboto Condensed, autor The Roboto Project Authors (SIL OFL 1.1)\n+  '%s on %s': '%s na %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: %s je postalo %s (%d%%), svaki deo proveren. Ukloniti ISO i zadr\u017eati ZSO?'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: %s je ponovo postalo %s, svaki deo proveren. Ukloniti ZSO i zadr\u017eati ISO?'\n+  A ZSO of this game is already beside it. Remove one of the two first.: Pored nje ve\u0107 postoji ZSO ove igre. Prvo uklonite jednu od dve.\n+  About %d min, then checked: Oko %d min, zatim provera\n+  An ISO of this game is already beside it. Remove one of the two first.: Pored nje ve\u0107 postoji ISO ove igre. Prvo uklonite jednu od dve.\n+  CHECKING EVERY PIECE: PROVERA SVAKOG DELA\n+  COMPRESS TO ZSO: KOMPRESUJ U ZSO\n+  'COMPRESSED %s: %s FREED': 'KOMPRESOVANO %s: OSLOBO\u0110ENO %s'\n+  COMPRESSING 1 OF 2: KOMPRESIJA 1 OD 2\n+  COMPRESSING 2 OF 2: KOMPRESIJA 2 OD 2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : Kompresovati %s? ISO ostaje dok se ZSO ponovo ne pro\u010dita i proveri, zatim RIPPS2 pita pre nego \u0161to ukloni ISO. Ure\u0111aju je tokom rada potrebno mesta za oba; ostavite konzolu uklju\u010denu dok ne zavr\u0161i. ZSO se pokre\u0107e samo sa OPL jezgrom (ne Neutrino); njena snimanja, pode\u0161avanja, slike i varke ostaju uz nju, a u svakom trenutku mo\u017ee ponovo postati ISO.\n+  Compress to ZSO: Kompresuj u ZSO\n+  Cycle: Redom\n+  'Experimental: OPL core only (not Neutrino)': 'Eksperimentalno: samo OPL jezgro (ne Neutrino)'\n+  Hold SELECT: Dr\u017eanje SELECT\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: Nije moglo da se zavr\u0161i (%s). Ono \u0161to je upisano je uklonjeno; igra je kakva je bila.\n+  List: Lista\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : Ponovo napraviti %s kao ISO? ZSO ostaje dok se ISO ponovo ne pro\u010dita i proveri, zatim RIPPS2 pita pre nego \u0161to ukloni ZSO. Ure\u0111aju je tokom rada potrebno mesta za oba; ostavite konzolu uklju\u010denu dok ne zavr\u0161i. Njena snimanja, pode\u0161avanja, slike i varke ostaju uz nju.\n+  Stop? What it has written so far is removed; the game stays as it was.: Zaustaviti? Ono \u0161to je dosad upisano se uklanja; igra ostaje kakva je bila.\n+  THEMES: TEME\n+  There is not enough memory for this just now.: Trenutno nema dovoljno memorije za ovo.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: RIPPS2 ne mo\u017ee da kompresuje ni raspakuje ovu sliku diska (njena veli\u010dina nije u celim blokovima od 2 KB).\n+  UNCOMPRESS TO ISO: RASPAKUJ U ISO\n+  UNCOMPRESSED %s: RASPAKOVANO %s\n+  UNCOMPRESSING: RASPAKIVANJE\n+  Uncompress to ISO: Raspakuj u ISO\n+  Under a minute, then checked: Manje od minuta, zatim provera\n+  COPYING: KOPIRANJE\n+  SHOW AGAIN, DELETE OR COMPRESS: PONOVO PRIKA\u017dI, OBRI\u0160I ILI KOMPRESUJ\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: PONOVO PRIKA\u017dI, OBRI\u0160I ILI RASPAKUJ\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': '%s tokom rada treba oko %s slobodnog prostora na svom ure\u0111aju (original ostaje do kraja), a ure\u0111aj ima %s slobodno.'\n+  Hide and Compress: Sakrij i kompresuj\n+  'COMPRESSED AND HIDDEN: %s': 'KOMPRESOVANO I SKRIVENO: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': 'Nema mesta za ovo kopiranje: potrebno je oko %s, a ure\u0111aj ima %s slobodno.'\n+  OVER 4 GB FOR FAT32: PREKO 4 GB ZA FAT32\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: Ovaj ure\u0111aj je %s, koji ne \u010duva datoteke od 4 GB ili ve\u0107e, a nova slika bi bila %s.\n+  '%s, %s clusters, %s free of %s': '%s, klasteri %s, %s slobodno od %s'\n+  Delete This Sweep: Obri\u0161i ovu smenu\n+  Delete %s? RIPPS2's own sweeps stay as they are.: Obrisati %s? Smene samog RIPPS2 ostaju kakve jesu.\n+  Sweep: Smena\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: Zna\u010dke obele\u017eavaju igre sa dostignu\u0107ima na tvojim listama i koliko si ih otklju\u010dao. Treba im uklju\u010dena TELEMETRIJA.\n labels:\n   ERROR_SAVING_SETTINGS_TO: Pode\u0161avanja nisu mogla da se upi\u0161u u %s (gre\u0161ka %d)\n   LAUNCH_PS2_DISC: Pokreni PS2 disk\n@@ -1433,7 +1600,7 @@ labels:\n   SAVE_ICONS: 3D ikone sa\u010duvanih igara\n   HINT_SAVE_ICONS: Prikazuje ikonu sa\u010duvane igre izabrane igre, kako se okre\u0107e, gde tema ima mesto za nju. Prvo \u010dita sopstveni VMC igre, pa memorijske kartice.\n   NETBOOT_RESTART_NOW: Mre\u017eni protokol se menja posle ponovnog pokretanja. Sa\u010duvati pode\u0161avanja i ponovo pokrenuti OPL sada?\n-  HINT_NET_PROTOCOL: 'Koju mre\u017enu uslugu RiptOPL koristi: SMB deljenje, UDPFS, UDPBD ili HTTP. Isto pode\u0161avanje kao u Izvorima igara. Prelazak sa usluge koja ve\u0107 radi zahteva ponovno pokretanje.'\n+  HINT_NET_PROTOCOL: 'Koju mre\u017enu uslugu RiptOPL koristi: SMB deljenje, UDPFS, UDPBD ili HTTP. Prelazak sa usluge koja ve\u0107 radi zahteva ponovno pokretanje.'\n   HINT_UDPFS_ACCESS: Samo UDPFS. Datoteke \u010dita pojedina\u010dne ISO datoteke iz fascikle igara na serveru; IMG \u010dita sliku diska kao ure\u0111aj. SMB i HTTP uvek koriste Datoteke, UDPBD uvek IMG.\n   HINT_SMB_VERSION: Kojom verzijom SMB-a se razgovara sa deljenjem na ra\u010dunaru.\n   HINT_NET_ADVANCED: Otklju\u010dava re\u017eim Ethernet veze i SMB port za ure\u0111ivanje.\n@@ -1634,3 +1801,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: Kako snimci na \u010dekanju na filmu svake igre postaju PNG.\n   RIPPS2_SETTINGS_HOME: \u010cuvaj pode\u0161avanja na\n   HINT_RIPPS2_SETTINGS_HOME: 'Gde RIPPS2 dr\u017ei pode\u0161avanja: u sopstvenom \u010duvanju na memorijskoj kartici, u fascikli RIPPS2 na disku (unutra\u0161njem HDD-u, USB-u, MX4SIO, iLinku ili MMCE) ili u fascikli koju upi\u0161e\u0161.'\n+  RIPPS2_LAUNCHERS: Pokreta\u010di\n+  RIPPS2_LOOK_NOW: Potra\u017ei sada\n+  HINT_RIPPS2_LOOK_NOW: RIPPS2 na pokrenutim ure\u0111ajima tra\u017ei POPSTARTER, POPS datoteke, Ember, Emberov BIOS i Neutrino i pamti gde je svaki. Svako pokretanje ih \u010dita odatle.\n+  RIPPS2_HIDE_GAME: Sakrij igru\n+  RIPPS2_HIDDEN_GAMES: Skrivene igre\n+  HINT_RIPPS2_HIDDEN_GAMES: Igre skrivene sa Sakrij igru, na pokrenutim ure\u0111ajima. Ponovo prika\u017eite jednu, ili je obri\u0161ite. Svaki ure\u0111aj \u010duva svoju listu.\n+  HINT_RIPPS2_BDM_DEVICES: 'Ure\u0111aji sa kojih BDM \u010dita igre: USB, MX4SIO, PC Card CF, DVR ure\u0111aj DESR-a i iLink. Unutra\u0161nji HDD prati re\u017eim pokretanja HDD-a.'\n+  HINT_RIPPS2_SCREEN: 'Slika menija: video re\u017eim, \u0161iroki ekran, gde stoji na televizoru (H-POS, V-POS) i overscan.'\n+  HINT_RIPPS2_LAYOUT: Gde stoje kategorije i kako stranica ulazi, zatim omoti RIPFLOW-a i mre\u017ea RIPGRID-a.\n+  HINT_RIPPS2_COLORS_TEXT: Debljina teksta, tema boja, smena boja i svaka boja.\n+  HINT_RIPPS2_TEXT_WEIGHT: 'Koliko je debeo sopstveni tekst RIPPS2-a: saveti u pode\u0161avanjima, STATUS i O programu. Obi\u010dno je kao pre; Srednje i Podebljano bolje se \u010ditaju na mekoj slici.'\n+  HINT_RIPPS2_HDD_START: 'Unutra\u0161nji HDD, APA ili exFAT: RIPPS2 ga \u010dita kakav god bio i prikazuje jednom, kao HDD igre. Isklju\u010deno ga ostavlja na miru.'\n+  HINT_RIPPS2_THEME_COLOR: Sopstvena boja teme. Izaberite Sopstveno kao temu boja da je sami birate, kao boje teksta.\n+  HINT_RIPPS2_SWEEP: Pet boja kroz koje tema prelazi polako i glatko. Duga, smena iz svake teme boja ili va\u0161a sopstvena. Isklju\u010deno dr\u017ei temu mirnom.\n+  HINT_RIPPS2_SWEEP_COLORS: Pet boja smene i njena brzina. Promenite jednu od RIPPS2-ovih i \u010duva se kao va\u0161a, pod imenom koje joj date.\n+  HINT_RIPPS2_SWEEP_NAME: Kako se va\u0161a smena zove na spisku Smena boja.\n+  HINT_RIPPS2_SWEEP_COLOR: Jedna od pet boja kroz koje smena prolazi, redom.\n+  HINT_RIPPS2_SWEEP_SPEED: 'Koliko traje svaka boja pre nego \u0161to u\u0111e slede\u0107a. I \u017divahno ostaje glatko: boje nikad ne trepere.'\n+  RIPPS2_HIDE_COMPRESS: Sakrij igru i kompresija\n+  HINT_RIPPS2_SELECT_HOLD: 'SELECT dr\u017ean 4.2 s na listi igara. Lista: teme u panelu, izaberite jednu. Redom: slede\u0107a tema. Isklju\u010deno: ni\u0161ta (pritisak i dalje osve\u017eava).'"
        },
        {
          "sha": "5a6d20b5c0982bf4dd24fd2697b3a8940abcae6d",
          "filename": "elf/lang/Spanish.yml",
          "status": "modified",
          "additions": 207,
          "deletions": 22,
          "changes": 229,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FSpanish.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FSpanish.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FSpanish.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: Spanish\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTER ACEPTA, ESC CANCELA. CUALQUIER BOT\u00d3N MUESTRA LAS TECLAS.\n   USB KEYBOARD: TECLADO USB\n-  '      DESR DVR ISOs (experimental)': '      ISO DESR DVR (experimental)'\n-  '      Internal HDD (exFAT)': '      Disco duro interno (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (experimental)'\n-  '      Protocol': '      Protocolo'\n   Access: Acceso\n   Applications Page Start Mode: Inicio de la p\u00e1gina Aplicaciones\n   Art Delay: Retraso de las im\u00e1genes\n@@ -23,7 +19,6 @@ strings:\n   Fifth: Quinto\n   First: Primero\n   Fourth: Cuarto\n-  HDD (APA) Start Mode: Inicio del disco duro (APA)\n   HDD Cache: Cach\u00e9 del disco duro\n   HDD Spindown: Reposo del disco duro\n   MMCE Settings: Ajustes de MMCE\n@@ -92,19 +87,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: Este es un juego de PS2 parcheado con ESR. Pon ESR (esr*.elf) en APPS, BOOT o ESR en una tarjeta de memoria o una memoria USB para iniciarlo.\n   '%s, BUILD %d': '%s, BUILD %d'\n   '... and the anonymous ...': '... y los an\u00f3nimos ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': '1.8.0 incluido sin modificar (AFL-3.0): github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: Un desmontaje y reconstrucci\u00f3n de RiptOPL, el fork de Open PS2 Loader,\n   ABOUT: ACERCA DE\n   'ACHIEVEMENTS: SPIRIT OF RA': 'LOGROS: SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): V\u00eddeo AVI en Archivos de memoria, el fork de RIPPS2 (AFL 2.0)\n   Apollo Save Tool by bucanero: Apollo Save Tool de bucanero\n   BACK: ATR\u00c1S\n+  Back (keeps both): Atr\u00e1s (conserva los dos)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: Esta tarjeta MMCE no puede cambiar el nombre de un archivo, algo que la compresi\u00f3n necesita en su \u00faltimo paso. Actualiza su firmware sd2psXtd a la 1.4.0 o posterior (sd2psxtd.github.io/download) y vuelve a intentarlo. No se cambi\u00f3 nada.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): BIBLE es PS2BBL 1.2.0, las versiones firmadas de El Isra (tarjeta y disco duro), sin modificar (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: Sonidos de la BIOS > Extraer los reproduce como el SPU2; no se incluye ninguna BIOS\n   BOOT LOCK AND BIBLE: BOOT LOCK Y BIBLE\n   BOOT LOCK edits its config too: BOOT LOCK tambi\u00e9n edita su configuraci\u00f3n\n   BUILD DETAILS: DETALLES DEL BUILD\n-  BUILT ON RIPTOPL: BASADO EN RIPTOPL\n   BearSSL by Thomas Pornin: BearSSL de Thomas Pornin\n   Built with Claude (Anthropic): Hecho con Claude (Anthropic)\n   CD PLAYER: REPRODUCTOR DE CD\n@@ -137,7 +132,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek y Sleek Bold, dibujadas para RIPPS2\n   RetroAchievements on the console by hacan359 (yoba): RetroAchievements en la consola, de hacan359 (yoba)\n   RiptOPL by NathanNeurotic: RiptOPL de NathanNeurotic\n-  Roboto Condensed by Google (Apache-2.0): Roboto Condensed de Google (Apache-2.0)\n   SOUNDS: SONIDOS\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: el instalador de FMCB de SP193 y el OSDMenu de pcm720, seguidos paso a paso\n   SUPPORT: SOPORTE\n@@ -173,7 +167,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: wLaunchELF R3Z de NathanNeurotic\n   wLaunchELF by the ps2homebrew team: wLaunchELF del equipo ps2homebrew\n   what RIPPS2's card image writes are checked against: con lo que se comprueban las escrituras de im\u00e1genes de tarjeta de RIPPS2\n-  with its own look, launchers, file browser and save tools.: con su propio aspecto, lanzadores, navegador de archivos y herramientas de partidas.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: xeRAbora de hacan359, rcheevos de RetroAchievements\n   your console's own, ripped from its BIOS: los de tu propia consola, extra\u00eddos de su BIOS\n   ' (no DNS answer)': ' (sin respuesta DNS)'\n@@ -559,8 +552,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO: escribiendo %.60s'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: Neutrino no se pudo escribir en %s. Revisa su espacio libre y que no est\u00e9 protegida contra escritura, e int\u00e9ntalo de nuevo.\n   THE GAME'S DRIVE  (%s/neutrino): LA UNIDAD DEL JUEGO  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : Este juego se inicia a trav\u00e9s de Neutrino, y no hay Neutrino donde busca RIPPS2 (Ajustes, la unidad del juego, las tarjetas de memoria). RIPPS2 incluye Neutrino 1.8.0 de rickgaiser (AFL-3.0). \u00bfInstalarlo ahora? Despu\u00e9s eliges d\u00f3nde.\n   '%d / %d FILES': '%d / %d ARCHIVOS'\n   '%d FOLDERS AND CONTENTS': '%d CARPETAS Y SU CONTENIDO'\n   '%d FOLDERS, %d FOUND': '%d CARPETAS, %d ENCONTRADOS'\n@@ -781,7 +772,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d de %d a\u00fan bloqueados'\n   '%s   on %s': '%s   en %s'\n   BADGES: INSIGNIAS\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: Las insignias solo se activan con la TELEMETR\u00cdA activada y xeRAbora en marcha en tu PC; hasta entonces no cuestan nada.\n   CHECK THE DISC: COMPROBAR EL DISCO\n   From this game's last CHECK GAME SUPPORT.: De la \u00faltima COMPROBACI\u00d3N DE COMPATIBILIDAD de este juego.\n   HOW IT WORKS: C\u00d3MO FUNCIONA\n@@ -848,10 +838,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: En esta consola hay un PS2BBL con el bloqueo de RIPPS2.\n   AUDIO folder: carpeta AUDIO\n   DRIVES: UNIDADES\n-  INSTALL RIPPS2'S COPY: INSTALAR LA COPIA DE RIPPS2\n-  'Installed: ': 'Instalado: '\n   MANAGE: GESTIONAR\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: GESTIONAR muestra lo que inicia cada tarjeta de memoria, e instala o repara BIBLE.\n   MEMORY CARD 0  (about 600 KB): TARJETA DE MEMORIA 0  (unos 600 KB)\n   MEMORY CARD 1  (about 600 KB): TARJETA DE MEMORIA 1  (unos 600 KB)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO: su controlador no se pudo cargar (c\u00f3digo %d).'\n@@ -861,17 +848,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO: el controlador est\u00e1 cargado, pero ninguna tarjeta SD respondi\u00f3 (la ranura mc1).'\n   NEUTRINO SETTINGS: AJUSTES DE NEUTRINO\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': 'A\u00fan ninguno: RIPPS2 no tiene sonidos propios. Extrae los de esta consola, o cr\u00e9alos en RIPPS2 Audio Studio.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: No instalado. Un juego que lo necesite ofrece la copia de RIPPS2, o inst\u00e1lalo aqu\u00ed.\n   POPSTARTER NETWORK: RED DE POPSTARTER\n   POPSTARTER and Ember: POPSTARTER y Ember\n   PS1 SETTINGS: AJUSTES DE PS1\n   PS2BBL 1.2.0 for RIPPS2: PS2BBL 1.2.0 para RIPPS2\n   RIP THIS CONSOLE'S SOUNDS: EXTRAER LOS SONIDOS DE ESTA CONSOLA\n-  RIPPS2's homebrew: El homebrew de RIPPS2\n   STATUS: ESTADO\n-  'STATUS: looking for RIPPS2''s homebrew': 'ESTADO: buscando el homebrew de RIPPS2'\n   a drive of unknown kind: una unidad de tipo desconocido\n-  a drive or a memory card: una unidad o una tarjeta de memoria\n   boot loader, card by card: cargador de arranque, tarjeta por tarjeta\n   from its BIOS, into AUDIO: desde su BIOS, a AUDIO\n   internal HDD (exFAT): disco duro interno (exFAT)\n@@ -976,10 +959,192 @@ strings:\n   Sidebar: Barra lateral\n   your own copy, found on your drives and memory cards: tu propia copia, encontrada en tus unidades y tarjetas de memoria\n   'your own, found and launched: github.com/Gageformer/Ember/releases': 'el tuyo, encontrado e iniciado: github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': 'No encontrado en mass0: (%s). Pon ember.elf y tu bios.bin en su carpeta EMBER.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: No encontrado. Pon tu propio POPSTARTER.ELF en la carpeta POPS de una unidad.\n   Launch Disc: Iniciar disco\n   Memory Files: Archivos de memoria\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: RIPPS2 encontr\u00f3 %d de los 5. Cada uno queda recordado, y cada inicio lo lee de ah\u00ed.\n+  not found on the started drives: no est\u00e1 en las unidades iniciadas\n+  not looked for yet: a\u00fan no se ha buscado\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s no respondi\u00f3, as\u00ed que %s no puede iniciarse. \u00bfEst\u00e1 conectado?'\n+  '%s is not on %s any more, so it has left All Games.': '%s ya no est\u00e1 en %s, as\u00ed que ha salido de Todos los juegos.'\n+  STARTING %s: INICIANDO %s\n+  the SMB share: el recurso SMB\n+  the drive: la unidad\n+  the internal HDD (APA): el disco duro interno (APA)\n+  the internal HDD (exFAT): el disco duro interno (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: HECHO SOBRE RIPTOPL, CON NEUTRINO\n+  'Special thanks to all the YouTube love as well:': 'Gracias especiales tambi\u00e9n a todo el cari\u00f1o de YouTube:'\n+  and its own look, launchers, file browser and save tools.: y su propio aspecto, lanzadores, explorador de archivos y herramientas de guardado.\n+  glad to help however you reach me.: encantado de ayudar, me contactes como me contactes.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): el segundo n\u00facleo de juego, 1.8.0 incluido sin cambios (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: con el Neutrino de rickgaiser dentro como su segundo n\u00facleo de juego,\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : 'Este juego se inicia con Neutrino, y RIPPS2 no encontr\u00f3 ninguno: ni en la ruta de Ajustes, ni en la unidad del juego, ni en las tarjetas de memoria, ni en ninguna unidad iniciada. RIPPS2 lleva Neutrino 1.8.0 de rickgaiser (AFL-3.0): inst\u00e1lalo una vez, donde elijas, y cada juego de Neutrino lo usa desde ah\u00ed.'\n+  STARTING THE DRIVES: INICIANDO LAS UNIDADES\n+  '%d games, on the started drives': '%d juegos, en las unidades iniciadas'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s: ALGUNOS ARCHIVOS NO SE PUDIERON BORRAR'\n+  '%u partitions, all in order': '%u particiones, todas en orden'\n+  ', %d art images': ', %d im\u00e1genes de car\u00e1tula'\n+  ', and ': ', y '\n+  ', its cheats': ', sus trucos'\n+  ', its settings': ', sus ajustes'\n+  1 game, on the started drives: 1 juego, en las unidades iniciadas\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: Un juego de PS1 de aqu\u00ed se borra en Archivos de memoria, donde es un archivo propio.\n+  A network catalogue's games are deleted on the PC that serves them.: Los juegos de un cat\u00e1logo de red se borran en el PC que los sirve.\n+  DELETE FOR GOOD: BORRAR PARA SIEMPRE\n+  DELETE IT: BORRARLO\n+  'DELETED %s: %s FREED': '%s BORRADO: %s LIBERADOS'\n+  'Delete %s from %s? Goes: %s.': '\u00bfBorrar %s de %s? Se va: %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': '\u00bfBorrar %s de %s? Se va: %s. Se queda: %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: \u00bfBorrar tambi\u00e9n sus partidas guardadas e im\u00e1genes RIPSHOT? No las conserva.\n+  HIDDEN GAMES: JUEGOS OCULTOS\n+  'HIDDEN: %s': 'OCULTO: %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: \u00bfOcultar %s? Sale de todas las listas, tambi\u00e9n de Todos los juegos y Favoritos. Ajustes > Fuentes de juegos > Juegos ocultos lo vuelve a mostrar.\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: \u00daltima comprobaci\u00f3n. %s se borra para siempre, y nada podr\u00e1 traerlo de vuelta. Pulsa START para borrarlo.\n+  None. Hide Game, in a game's options, puts one here.: Ninguno. Ocultar juego, en las opciones de un juego, pone uno aqu\u00ed.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : 'No se borr\u00f3 nada: la tabla de particiones de esta unidad no encaja con este disco (%s). Puede que se escribiera desde la imagen de un disco de otro tama\u00f1o. Rep\u00e1rala primero en un PC.'\n+  RIPPS2 cannot delete a game from this page.: RIPPS2 no puede borrar un juego desde esta p\u00e1gina.\n+  RIPPS2 could not find this game's files on its drive.: RIPPS2 no encontr\u00f3 los archivos de este juego en su unidad.\n+  Reading the hidden lists: Leyendo las listas ocultas\n+  SHOW AGAIN OR DELETE: MOSTRAR DE NUEVO O BORRAR\n+  SHOW IT AGAIN: MOSTRARLO DE NUEVO\n+  That game is not on its drive any more.: Ese juego ya no est\u00e1 en su unidad.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: La lista oculta no se pudo escribir en la unidad del juego. \u00bfEst\u00e1 protegida contra escritura, o est\u00e1n desactivadas las Operaciones de escritura?\n+  The partition could not be removed (%d). Nothing was deleted.: La partici\u00f3n no se pudo quitar (%d). No se borr\u00f3 nada.\n+  This drive cannot keep a hidden list (a network catalogue).: Esta unidad no puede guardar una lista oculta (un cat\u00e1logo de red).\n+  This game's drive has not answered yet. Wait for it, then try again.: La unidad de este juego a\u00fan no ha respondido. Esp\u00e9rala y vuelve a intentarlo.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: La partici\u00f3n de este juego es una que RIPPS2 no quita (una partici\u00f3n del sistema, o la que tiene sus ajustes).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': 'una partici\u00f3n es m\u00e1s grande de lo que permite este disco: la tabla se hizo para un disco de otro tama\u00f1o'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': 'una partici\u00f3n pasa del final de este disco (sector %u de %u): la tabla se hizo para un disco m\u00e1s grande'\n+  its RIPSHOT pictures: sus im\u00e1genes RIPSHOT\n+  its drive: su unidad\n+  its saves: sus partidas guardadas\n+  sector %u holds no partition header: el sector %u no tiene cabecera de partici\u00f3n\n+  the chain does not close at __mbr: la cadena no se cierra en __mbr\n+  the chain skips from sector %u: la cadena salta desde el sector %u\n+  the chain's links disagree at sector %u: los enlaces de la cadena no coinciden en el sector %u\n+  the drive's size could not be read: no se pudo leer el tama\u00f1o de la unidad\n+  the game (%s): el juego (%s)\n+  the game's folder and everything in it (%s): la carpeta del juego y todo lo que contiene (%s)\n+  the header at sector %u could not be read: no se pudo leer la cabecera del sector %u\n+  the header at sector %u fails its checksum: la cabecera del sector %u falla su suma de comprobaci\u00f3n\n+  the partition at sector %u is not where or as large as APA puts one: la partici\u00f3n del sector %u no est\u00e1 donde APA pone una, o no es de ese tama\u00f1o\n+  the settings and art another copy of it uses: los ajustes y car\u00e1tulas que usa otra copia\n+  '%d FOLDERS': '%d CARPETAS'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': '%s no se pudo copiar a %s. \u00bfEst\u00e1 la unidad llena o protegida contra escritura?'\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s no est\u00e1 donde RIPPS2 busca. \u00bfBuscarlo en las unidades que RIPPS2 ha iniciado? Donde se encuentre queda recordado para cada inicio siguiente.'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Ember no est\u00e1 en %s, junto a los juegos, y tiene que estar. RIPPS2 lo encontr\u00f3 en %s: START lo copia all\u00ed (%d KB) e inicia el juego.'\n+  Ember's BIOS: BIOS de Ember\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : 'El BIOS de Ember (bios.bin) no est\u00e1 en %s, junto a Ember. RIPPS2 encontr\u00f3 uno en %s: START lo copia all\u00ed (%d KB) e inicia el juego.'\n+  LOOKING FOR YOUR LAUNCHERS: BUSCANDO TUS LANZADORES\n+  The POPS files: Los archivos POPS\n+  '%s  MMCE card': '%s  tarjeta MMCE'\n+  INSTALL RIPPS2'S NEUTRINO: INSTALAR EL NEUTRINO DE RIPPS2\n+  'Internal HDD (APA): %s free of %s': 'Disco duro interno (APA): %s libres de %s'\n+  LAUNCHERS: LANZADORES\n+  LOOK FOR THEM: BUSCARLOS\n+  'Memory card %d: %d KB free': 'Tarjeta de memoria %d: %d KB libres'\n+  POPS files: Archivos POPS\n+  START EVERY DRIVE: INICIAR TODAS LAS UNIDADES\n+  'STATUS: looking at the drives and the homebrew': 'ESTADO: revisando las unidades y el homebrew'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): \u00bfIniciar primero todas las unidades, para que la b\u00fasqueda llegue a todas? (No busca en las unidades ya iniciadas.)\n+  USB, HDD, MMCE and the rest: USB, disco duro, MMCE y el resto\n+  every started drive: cada unidad iniciada\n+  found where you keep them: encontrados donde los guardas\n+  not found yet: a\u00fan no encontrado\n+  not on a started drive: no est\u00e1 en una unidad iniciada\n+  only if you have none: solo si no tienes ninguno\n+  starts every drive first: inicia primero todas las unidades\n+  Drives, launchers and homebrew: Unidades, lanzadores y homebrew\n+  BDM Devices: Dispositivos BDM\n+  Color Sweep: Transici\u00f3n de colores\n+  Colors and Text: Colores y texto\n+  HDD POPS partitions: Particiones POPS del disco duro\n+  Layout: Disposici\u00f3n\n+  Missing Covers: Portadas que faltan\n+  Name: Nombre\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: A\u00fan no se encontr\u00f3 una configuraci\u00f3n SMB de POPSTARTER. Rellena las filas de abajo.\n+  POPSTARTER Network: Red de POPSTARTER\n+  POPSTARTER SMB Network: Red SMB de POPSTARTER\n+  POPSTARTER UDPFS Network: Red UDPFS de POPSTARTER\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: RIPPS2 a\u00f1ade por s\u00ed mismo la unidad, la ISO, las VMC y el modo de v\u00eddeo.\n+  Screen: Pantalla\n+  Speed: Velocidad\n+  Sweep Colors: Colores de la transici\u00f3n\n+  Text Weight: Grosor del texto\n+  Theme Color: Color principal\n+  Bold: Negrita\n+  Controllers: Mandos\n+  Lively: Vivo\n+  Medium: Medio\n+  PS Emulation: Emulaci\u00f3n PS\n+  Regular: Normal\n+  Slow: Lento\n+  A MOMENT: UN MOMENTO\n+  A TASK IN THE BACKGROUND: UNA TAREA EN SEGUNDO PLANO\n+  ABOUT %d S: UNOS %d S\n+  ANY MOMENT: EN CUALQUIER MOMENTO\n+  READING THE GAME LIST OF %s: LEYENDO LA LISTA DE JUEGOS DE %s\n+  SAVING A LIST: GUARDANDO UNA LISTA\n+  START NOW: INICIAR YA\n+  STARTING: INICIANDO\n+  STARTS NEXT: SE INICIA A CONTINUACI\u00d3N\n+  STOPPING IT SAFELY: DETENIENDO CON SEGURIDAD\n+  THE LAST OF THE QUEUE: EL FINAL DE LA COLA\n+  WHEN IT FINISHES: CUANDO TERMINE\n+  What each card starts; install or repair BIBLE.: Lo que inicia cada tarjeta; instala o repara BIBLE.\n+  Rainbow: Arco\u00edris\n+  Custom: Personalizado\n+  DESR DVR ISOs (experimental): ISO DESR DVR (experimental)\n+  HDD Start Mode: Inicio del disco duro\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): Roboto Condensed de The Roboto Project Authors (SIL OFL 1.1)\n+  '%s on %s': '%s en %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: %s pas\u00f3 a %s (%d%%), cada trozo comprobado. \u00bfQuitar el ISO y conservar el ZSO?'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: %s volvi\u00f3 a ser %s, cada trozo comprobado. \u00bfQuitar el ZSO y conservar el ISO?'\n+  A ZSO of this game is already beside it. Remove one of the two first.: Ya hay un ZSO de este juego a su lado. Quita primero uno de los dos.\n+  About %d min, then checked: Unos %d min, luego comprobado\n+  An ISO of this game is already beside it. Remove one of the two first.: Ya hay un ISO de este juego a su lado. Quita primero uno de los dos.\n+  CHECKING EVERY PIECE: COMPROBANDO CADA TROZO\n+  COMPRESS TO ZSO: COMPRIMIR A ZSO\n+  'COMPRESSED %s: %s FREED': '%s COMPRIMIDO: %s LIBERADOS'\n+  COMPRESSING 1 OF 2: COMPRIMIENDO 1 DE 2\n+  COMPRESSING 2 OF 2: COMPRIMIENDO 2 DE 2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : \u00bfComprimir %s? El ISO se queda hasta que el ZSO se haya vuelto a leer y comprobado; luego RIPPS2 pregunta antes de quitar el ISO. La unidad necesita espacio para ambos mientras trabaja; deja la consola encendida hasta que termine. Un ZSO solo arranca con el n\u00facleo OPL (no Neutrino); sus partidas, ajustes, im\u00e1genes y trucos se quedan con \u00e9l, y puede volver a ser un ISO en cualquier momento.\n+  Compress to ZSO: Comprimir a ZSO\n+  Cycle: Rotaci\u00f3n\n+  'Experimental: OPL core only (not Neutrino)': 'Experimental: solo el n\u00facleo OPL (no Neutrino)'\n+  Hold SELECT: Mantener SELECT\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: No se pudo terminar (%s). Lo que escribi\u00f3 se quit\u00f3; el juego est\u00e1 como estaba.\n+  List: Lista\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : \u00bfVolver a hacer de %s un ISO? El ZSO se queda hasta que el ISO se haya vuelto a leer y comprobado; luego RIPPS2 pregunta antes de quitar el ZSO. La unidad necesita espacio para ambos mientras trabaja; deja la consola encendida hasta que termine. Sus partidas, ajustes, im\u00e1genes y trucos se quedan con \u00e9l.\n+  Stop? What it has written so far is removed; the game stays as it was.: \u00bfDetener? Lo escrito hasta ahora se quita; el juego queda como estaba.\n+  THEMES: TEMAS\n+  There is not enough memory for this just now.: Ahora mismo no hay memoria suficiente para esto.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: RIPPS2 no puede comprimir ni descomprimir esta imagen (su tama\u00f1o no son bloques enteros de 2 KB).\n+  UNCOMPRESS TO ISO: DESCOMPRIMIR A ISO\n+  UNCOMPRESSED %s: '%s DESCOMPRIMIDO'\n+  UNCOMPRESSING: DESCOMPRIMIENDO\n+  Uncompress to ISO: Descomprimir a ISO\n+  Under a minute, then checked: Menos de un minuto, luego comprobado\n+  COPYING: COPIANDO\n+  SHOW AGAIN, DELETE OR COMPRESS: MOSTRAR DE NUEVO, BORRAR O COMPRIMIR\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: MOSTRAR DE NUEVO, BORRAR O DESCOMPRIMIR\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': '%s necesita unos %s libres en su unidad mientras trabaja (el original se queda hasta el final), y la unidad tiene %s libres.'\n+  Hide and Compress: Ocultar y comprimir\n+  'COMPRESSED AND HIDDEN: %s': 'COMPRIMIDO Y OCULTO: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': 'No hay sitio para esta copia: necesita unos %s, y la unidad tiene %s libres.'\n+  OVER 4 GB FOR FAT32: M\u00c1S DE 4 GB PARA FAT32\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: Esta unidad es %s, que no guarda archivos de 4 GB o m\u00e1s, y la nueva imagen ocupar\u00eda %s.\n+  '%s, %s clusters, %s free of %s': '%s, cl\u00fasteres de %s, %s libres de %s'\n+  Delete This Sweep: Borrar esta transici\u00f3n\n+  Delete %s? RIPPS2's own sweeps stay as they are.: \u00bfBorrar %s? Las transiciones propias de RIPPS2 se quedan como est\u00e1n.\n+  Sweep: Transici\u00f3n\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: Las insignias marcan los juegos con logros en tus listas, con cu\u00e1ntos has desbloqueado. Necesitan la TELEMETR\u00cdA activada.\n labels:\n   HINT_NEUTRINO_AUTO_ELF: Pasa el ELF de arranque del juego a Neutrino para buscar su configuraci\u00f3n por t\u00edtulo. Activado por defecto; una opci\u00f3n Boot ELF puesta a mano tiene prioridad.\n   HINT_POPSTARTER_PATH: D\u00f3nde est\u00e1 POPSTARTER.ELF. Si lo dejas vac\u00edo, RIPPS2 pone uno que encuentre en una unidad; escribe encima para usar otro, como usb:/POPS/POPSTARTER.ELF\n@@ -1201,7 +1366,7 @@ labels:\n   SAVE_ICONS: Iconos de partida en 3D\n   HINT_SAVE_ICONS: Muestra el icono de partida del juego elegido, girando, donde el tema tiene un sitio para \u00e9l. Lee primero la VMC del juego y luego las tarjetas de memoria.\n   NETBOOT_RESTART_NOW: El protocolo de red cambia tras reiniciar. \u00bfGuardar los ajustes y reiniciar OPL ahora?\n-  HINT_NET_PROTOCOL: 'Qu\u00e9 servicio de red usa RiptOPL: un recurso compartido SMB, UDPFS, UDPBD o HTTP. El mismo ajuste que en Or\u00edgenes de juegos. Dejar uno que ya est\u00e1 en marcha requiere reiniciar.'\n+  HINT_NET_PROTOCOL: 'Qu\u00e9 servicio de red usa RiptOPL: un recurso compartido SMB, UDPFS, UDPBD o HTTP. Dejar uno que ya est\u00e1 en marcha requiere reiniciar.'\n   HINT_UDPFS_ACCESS: Solo UDPFS. Archivos lee ISO sueltas de la carpeta de juegos del servidor; IMG lee una imagen de disco como una unidad. SMB y HTTP siempre usan Archivos, UDPBD siempre IMG.\n   HINT_SMB_VERSION: Qu\u00e9 versi\u00f3n de SMB usar con el recurso compartido del PC.\n   HINT_NET_ADVANCED: Desbloquea la edici\u00f3n del modo de enlace Ethernet y del puerto SMB.\n@@ -1400,3 +1565,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: C\u00f3mo las fotos que esperan en el carrete de cada juego se convierten en PNG.\n   RIPPS2_SETTINGS_HOME: Guardar ajustes en\n   HINT_RIPPS2_SETTINGS_HOME: 'D\u00f3nde guarda RIPPS2 sus ajustes: su propia partida en una tarjeta de memoria, una carpeta RIPPS2 en una unidad (el disco duro interno, USB, MX4SIO, iLink o MMCE) o una carpeta que escribas.'\n+  RIPPS2_LAUNCHERS: Lanzadores\n+  RIPPS2_LOOK_NOW: Buscar ahora\n+  HINT_RIPPS2_LOOK_NOW: RIPPS2 busca en las unidades iniciadas POPSTARTER, los archivos POPS, Ember, el BIOS de Ember y Neutrino, y recuerda d\u00f3nde est\u00e1 cada uno. Cada inicio los lee de ah\u00ed.\n+  RIPPS2_HIDE_GAME: Ocultar juego\n+  RIPPS2_HIDDEN_GAMES: Juegos ocultos\n+  HINT_RIPPS2_HIDDEN_GAMES: Los juegos ocultos con Ocultar juego, en las unidades iniciadas. Vuelve a mostrar uno, o b\u00f3rralo. Cada unidad guarda su propia lista.\n+  HINT_RIPPS2_BDM_DEVICES: 'Las unidades de las que BDM lee juegos: USB, MX4SIO, una PC Card CF, la unidad DVR de un DESR e iLink. El disco duro interno sigue el inicio del disco duro.'\n+  HINT_RIPPS2_SCREEN: 'La imagen del men\u00fa: modo de v\u00eddeo, pantalla ancha, d\u00f3nde queda en el televisor (H-POS, V-POS) y overscan.'\n+  HINT_RIPPS2_LAYOUT: D\u00f3nde van las categor\u00edas y c\u00f3mo entra una p\u00e1gina, luego las portadas de RIPFLOW y la cuadr\u00edcula de RIPGRID.\n+  HINT_RIPPS2_COLORS_TEXT: Grosor del texto, el color del tema, la transici\u00f3n de colores y cada color.\n+  HINT_RIPPS2_TEXT_WEIGHT: 'Lo grueso que es el texto propio de RIPPS2: las ayudas de los ajustes, ESTADO y Acerca de. Normal es como antes; Medio y Negrita se leen mejor en una imagen suave.'\n+  HINT_RIPPS2_HDD_START: 'El disco duro interno, APA o exFAT: RIPPS2 lo lee sea cual sea y lo muestra una sola vez, como juegos del disco duro. Desactivado lo deja en paz.'\n+  HINT_RIPPS2_THEME_COLOR: El color propio del tema. Elige Personalizado como color del tema para elegirlo t\u00fa, como los colores del texto.\n+  HINT_RIPPS2_SWEEP: Cinco colores por los que pasa el tema, despacio y con suavidad. Arco\u00edris, una transici\u00f3n hecha de cada color de tema o la tuya. Desactivado deja el tema quieto.\n+  HINT_RIPPS2_SWEEP_COLORS: Los cinco colores de la transici\u00f3n y su velocidad. Cambia una de RIPPS2 y se guarda como tuya, con el nombre que le pongas.\n+  HINT_RIPPS2_SWEEP_NAME: C\u00f3mo se llama tu transici\u00f3n en la lista Transici\u00f3n de colores.\n+  HINT_RIPPS2_SWEEP_COLOR: Uno de los cinco colores por los que pasa la transici\u00f3n, en orden.\n+  HINT_RIPPS2_SWEEP_SPEED: 'Cu\u00e1nto dura cada color antes de que entre el siguiente. Incluso Vivo sigue suave: los colores nunca parpadean.'\n+  RIPPS2_HIDE_COMPRESS: Ocultar juego y compresi\u00f3n\n+  HINT_RIPPS2_SELECT_HOLD: 'SELECT mantenido 4.2 s en una lista de juegos. Lista: los temas en un panel, elige uno. Rotaci\u00f3n: el tema siguiente. Desactivado: nada (una pulsaci\u00f3n sigue actualizando).'"
        },
        {
          "sha": "c60b6f4ccc0f456c947d99255f9ecd8b6477f4b5",
          "filename": "elf/lang/Swedish.yml",
          "status": "modified",
          "additions": 208,
          "deletions": 22,
          "changes": 230,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FSwedish.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FSwedish.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FSwedish.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: Swedish\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTER BEKR\u00c4FTAR, ESC AVBRYTER. VALFRI KNAPP VISAR TANGENTERNA.\n   USB KEYBOARD: USB-TANGENTBORD\n-  '      DESR DVR ISOs (experimental)': '      DESR DVR-ISO:er (experimentellt)'\n-  '      Internal HDD (exFAT)': '      Intern HDD (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (experimentellt)'\n-  '      Protocol': '      Protokoll'\n   Access: \u00c5tkomst\n   Applications Page Start Mode: Startl\u00e4ge f\u00f6r appsidan\n   Art Delay: Bildf\u00f6rdr\u00f6jning\n@@ -22,7 +18,6 @@ strings:\n   Fifth: Femte\n   First: F\u00f6rsta\n   Fourth: Fj\u00e4rde\n-  HDD (APA) Start Mode: Startl\u00e4ge f\u00f6r HDD (APA)\n   HDD Cache: HDD-cache\n   HDD Spindown: HDD-vilol\u00e4ge\n   MMCE Settings: MMCE-inst\u00e4llningar\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: Det h\u00e4r \u00e4r ett ESR-patchat PS2-spel. L\u00e4gg ESR (esr*.elf) i APPS, BOOT eller ESR p\u00e5 ett minneskort eller USB-minne f\u00f6r att starta det.\n   '%s, BUILD %d': '%s, BYGGE %d'\n   '... and the anonymous ...': '... och de anonyma ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': '1.8.0 medf\u00f6ljer of\u00f6r\u00e4ndrad (AFL-3.0): github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: En nedmontering och ombyggnad av RiptOPL, forken av Open PS2 Loader,\n   ABOUT: OM\n   'ACHIEVEMENTS: SPIRIT OF RA': 'PRESTATIONER: SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): AVI-video i Minnesfiler, RIPPS2:s fork (AFL 2.0)\n   Apollo Save Tool by bucanero: Apollo Save Tool av bucanero\n   BACK: TILLBAKA\n+  Back (keeps both): Tillbaka (beh\u00e5ller b\u00e5da)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: Det h\u00e4r MMCE-kortet kan inte byta namn p\u00e5 en fil, vilket komprimeringen beh\u00f6ver i sitt sista steg. Uppdatera dess sd2psXtd-firmware till 1.4.0 eller senare (sd2psxtd.github.io/download) och f\u00f6rs\u00f6k igen. Inget \u00e4ndrades.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): BIBLE \u00e4r PS2BBL 1.2.0, El Isras signerade byggen (kort och HDD), of\u00f6r\u00e4ndrade (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: BIOS-ljud > Rippa spelar dem som SPU2 g\u00f6r; inget BIOS medf\u00f6ljer\n   BOOT LOCK AND BIBLE: BOOT LOCK OCH BIBLE\n   BOOT LOCK edits its config too: BOOT LOCK redigerar \u00e4ven dess konfiguration\n   BUILD DETAILS: BYGGDETALJER\n-  BUILT ON RIPTOPL: BYGGT P\u00c5 RIPTOPL\n   BearSSL by Thomas Pornin: BearSSL av Thomas Pornin\n   Built with Claude (Anthropic): Gjort med Claude (Anthropic)\n   CD PLAYER: CD-SPELARE\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek och Sleek Bold, ritade f\u00f6r RIPPS2\n   RetroAchievements on the console by hacan359 (yoba): RetroAchievements p\u00e5 konsolen av hacan359 (yoba)\n   RiptOPL by NathanNeurotic: RiptOPL av NathanNeurotic\n-  Roboto Condensed by Google (Apache-2.0): Roboto Condensed av Google (Apache-2.0)\n   SOUNDS: LJUD\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: SP193:s FMCB-installerare och pcm720:s OSDMenu, f\u00f6ljda steg f\u00f6r steg\n   SUPPORT: SUPPORT\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: wLaunchELF R3Z av NathanNeurotic\n   wLaunchELF by the ps2homebrew team: wLaunchELF av ps2homebrew-teamet\n   what RIPPS2's card image writes are checked against: det som RIPPS2:s skrivningar av kortavbilder kontrolleras mot\n-  with its own look, launchers, file browser and save tools.: med eget utseende, startprogram, filbl\u00e4ddrare och sparverktyg.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: xeRAbora av hacan359, rcheevos av RetroAchievements\n   your console's own, ripped from its BIOS: din konsols egna ljud, rippade fr\u00e5n dess BIOS\n   ' (no DNS answer)': ' (inget DNS-svar)'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO: skriver %.60s'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: Neutrino kunde inte skrivas till %s. Kontrollera det lediga utrymmet och att den inte \u00e4r skrivskyddad, och f\u00f6rs\u00f6k igen.\n   THE GAME'S DRIVE  (%s/neutrino): SPELETS ENHET  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : Det h\u00e4r spelet startar via Neutrino, och det finns ingen Neutrino d\u00e4r RIPPS2 letar (Inst\u00e4llningar, spelets enhet, minneskorten). RIPPS2 har med sig Neutrino 1.8.0 av rickgaiser (AFL-3.0). Installera den nu? Sedan v\u00e4ljer du var.\n   '%d / %d FILES': '%d / %d FILER'\n   '%d FOLDERS AND CONTENTS': '%d MAPPAR OCH INNEH\u00c5LL'\n   '%d FOLDERS, %d FOUND': '%d MAPPAR, %d HITTADE'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d av %d fortfarande l\u00e5sta'\n   '%s   on %s': '%s   p\u00e5 %s'\n   BADGES: M\u00c4RKEN\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: M\u00e4rken fungerar bara med TELEMETRI p\u00e5 och xeRAbora ig\u00e5ng p\u00e5 din PC; tills dess kostar de ingenting.\n   CHECK THE DISC: KONTROLLERA SKIVAN\n   From this game's last CHECK GAME SUPPORT.: Fr\u00e5n spelets senaste KONTROLLERA SPELST\u00d6D.\n   HOW IT WORKS: S\u00c5 FUNGERAR DET\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: Det finns en PS2BBL med RIPPS2:s l\u00e5s p\u00e5 den h\u00e4r konsolen.\n   AUDIO folder: AUDIO-mappen\n   DRIVES: ENHETER\n-  INSTALL RIPPS2'S COPY: INSTALLERA RIPPS2:S KOPIA\n-  'Installed: ': 'Installerat: '\n   MANAGE: HANTERA\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: HANTERA visar vad varje minneskort startar, och installerar eller reparerar BIBLE.\n   MEMORY CARD 0  (about 600 KB): MINNESKORT 0  (cirka 600 KB)\n   MEMORY CARD 1  (about 600 KB): MINNESKORT 1  (cirka 600 KB)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO: dess drivrutin ville inte laddas (kod %d).'\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO: drivrutinen \u00e4r laddad, men inget SD-kort svarade (platsen mc1).'\n   NEUTRINO SETTINGS: NEUTRINO-INST\u00c4LLNINGAR\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': 'Inga \u00e4nnu: RIPPS2 har inga egna ljud. Rippa den h\u00e4r konsolens, eller g\u00f6r dem i RIPPS2 Audio Studio.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: Inte installerat. Ett spel som beh\u00f6ver det erbjuder RIPPS2:s kopia, eller installera det h\u00e4r.\n   POPSTARTER NETWORK: POPSTARTER-N\u00c4TVERK\n   POPSTARTER and Ember: POPSTARTER och Ember\n   PS1 SETTINGS: PS1-INST\u00c4LLNINGAR\n   PS2BBL 1.2.0 for RIPPS2: PS2BBL 1.2.0 f\u00f6r RIPPS2\n   RIP THIS CONSOLE'S SOUNDS: RIPPA DEN H\u00c4R KONSOLENS LJUD\n-  RIPPS2's homebrew: RIPPS2:s homebrew\n   STATUS: STATUS\n-  'STATUS: looking for RIPPS2''s homebrew': 'STATUS: letar efter RIPPS2:s homebrew'\n   a drive of unknown kind: en enhet av ok\u00e4nd sort\n-  a drive or a memory card: en enhet eller ett minneskort\n   boot loader, card by card: startladdare, kort f\u00f6r kort\n   from its BIOS, into AUDIO: fr\u00e5n dess BIOS, till AUDIO\n   internal HDD (exFAT): intern HDD (exFAT)\n@@ -975,10 +958,193 @@ strings:\n   Sidebar: Sidof\u00e4lt\n   your own copy, found on your drives and memory cards: din egen kopia, hittad p\u00e5 dina enheter och minneskort\n   'your own, found and launched: github.com/Gageformer/Ember/releases': 'din egen, hittad och startad: github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': 'Hittades inte p\u00e5 mass0: (%s). L\u00e4gg ember.elf och din bios.bin i dess EMBER-mapp.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: Hittades inte. L\u00e4gg din egen POPSTARTER.ELF i en enhets POPS-mapp.\n   Launch Disc: Starta skiva\n   Memory Files: Minnesfiler\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: RIPPS2 hittade %d av de 5. Var och en sparas, och varje start l\u00e4ser den d\u00e4rifr\u00e5n.\n+  not found on the started drives: hittades inte p\u00e5 de startade enheterna\n+  not looked for yet: inte s\u00f6kt efter \u00e4n\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s svarade inte, s\u00e5 %s kan inte starta. \u00c4r den ansluten?'\n+  '%s is not on %s any more, so it has left All Games.': '%s finns inte l\u00e4ngre p\u00e5 %s, s\u00e5 det har l\u00e4mnat Alla spel.'\n+  STARTING %s: STARTAR %s\n+  the SMB share: SMB-utdelningen\n+  the drive: enheten\n+  the internal HDD (APA): den interna HDD:n (APA)\n+  the internal HDD (exFAT): den interna HDD:n (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: BYGGD P\u00c5 RIPTOPL, MED NEUTRINO\n+  'Special thanks to all the YouTube love as well:': 'Ett s\u00e4rskilt tack ocks\u00e5 till all k\u00e4rlek p\u00e5 YouTube:'\n+  and its own look, launchers, file browser and save tools.: och med eget utseende, startare, filbl\u00e4ddrare och sparverktyg.\n+  glad to help however you reach me.: glad att hj\u00e4lpa till, hur ni \u00e4n n\u00e5r mig.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): den andra spelk\u00e4rnan, 1.8.0 medf\u00f6ljer of\u00f6r\u00e4ndrad (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: med rickgaisers Neutrino inuti som sin andra spelk\u00e4rna,\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : 'Det h\u00e4r spelet startar genom Neutrino, och RIPPS2 hittade ingen: inte p\u00e5 s\u00f6kv\u00e4gen i Inst\u00e4llningar, inte p\u00e5 spelets enhet, inte p\u00e5 minneskorten och inte p\u00e5 n\u00e5gon startad enhet. RIPPS2 har med Neutrino 1.8.0 av rickgaiser (AFL-3.0): installera den en g\u00e5ng, d\u00e4r du vill, s\u00e5 anv\u00e4nder varje Neutrino-spel den d\u00e4rifr\u00e5n.'\n+  STARTING THE DRIVES: STARTAR ENHETERNA\n+  '%d games, on the started drives': '%d spel, p\u00e5 de startade enheterna'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s: VISSA FILER KUNDE INTE RADERAS'\n+  '%u partitions, all in order': '%u partitioner, alla i ordning'\n+  ', %d art images': ', %d bilder'\n+  ', and ': ', och '\n+  ', its cheats': ', dess fusk'\n+  ', its settings': ', dess inst\u00e4llningar'\n+  1 game, on the started drives: 1 spel, p\u00e5 de startade enheterna\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: Ett PS1-spel h\u00e4r raderas i Minnesfiler, d\u00e4r det \u00e4r en egen fil.\n+  A network catalogue's games are deleted on the PC that serves them.: Ett n\u00e4tverkskatalogs spel raderas p\u00e5 datorn som delar ut dem.\n+  DELETE FOR GOOD: RADERA F\u00d6R GOTT\n+  DELETE IT: RADERA DET\n+  'DELETED %s: %s FREED': '%s RADERAT: %s FRIGJORT'\n+  'Delete %s from %s? Goes: %s.': 'Radera %s fr\u00e5n %s? F\u00f6rsvinner: %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': 'Radera %s fr\u00e5n %s? F\u00f6rsvinner: %s. Blir kvar: %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: Radera \u00e4ven dess sparfiler och RIPSHOT-bilder? Nej beh\u00e5ller dem.\n+  HIDDEN GAMES: DOLDA SPEL\n+  'HIDDEN: %s': 'DOLT: %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: D\u00f6lj %s? Det l\u00e4mnar varje lista, \u00e4ven Alla spel och Favoriter. Inst\u00e4llningar > Spelk\u00e4llor > Dolda spel visar det igen.\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: Sista kontrollen. %s raderas f\u00f6r gott, och inget kan f\u00e5 tillbaka det. Tryck p\u00e5 START f\u00f6r att radera det.\n+  None. Hide Game, in a game's options, puts one here.: Inga. D\u00f6lj spel, i ett spels alternativ, l\u00e4gger ett h\u00e4r.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : 'Inget raderades: den h\u00e4r enhetens partitionstabell passar inte den h\u00e4r disken (%s). Den kan ha skrivits fr\u00e5n en avbild av en disk av en annan storlek. Laga den p\u00e5 en dator f\u00f6rst.'\n+  RIPPS2 cannot delete a game from this page.: RIPPS2 kan inte radera ett spel fr\u00e5n den h\u00e4r sidan.\n+  RIPPS2 could not find this game's files on its drive.: RIPPS2 hittade inte det h\u00e4r spelets filer p\u00e5 dess enhet.\n+  Reading the hidden lists: L\u00e4ser de dolda listorna\n+  SHOW AGAIN OR DELETE: VISA IGEN ELLER RADERA\n+  SHOW IT AGAIN: VISA DET IGEN\n+  That game is not on its drive any more.: Det spelet finns inte l\u00e4ngre p\u00e5 sin enhet.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: Den dolda listan kunde inte skrivas till spelets enhet. \u00c4r den skrivskyddad, eller \u00e4r Skriv\u00e5tg\u00e4rder avst\u00e4ngda?\n+  The partition could not be removed (%d). Nothing was deleted.: Partitionen kunde inte tas bort (%d). Inget raderades.\n+  This drive cannot keep a hidden list (a network catalogue).: Den h\u00e4r enheten kan inte ha en dold lista (en n\u00e4tverkskatalog).\n+  This game's drive has not answered yet. Wait for it, then try again.: Det h\u00e4r spelets enhet har inte svarat \u00e4n. V\u00e4nta p\u00e5 den och f\u00f6rs\u00f6k sedan igen.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: Det h\u00e4r spelets partition \u00e4r en som RIPPS2 inte tar bort (en systempartition, eller den med dess inst\u00e4llningar).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': 'en partition \u00e4r st\u00f6rre \u00e4n den h\u00e4r disken till\u00e5ter: tabellen gjordes f\u00f6r en disk av en annan storlek'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': 'en partition g\u00e5r f\u00f6rbi slutet av den h\u00e4r disken (sektor %u av %u): tabellen gjordes f\u00f6r en st\u00f6rre disk'\n+  its RIPSHOT pictures: dess RIPSHOT-bilder\n+  its drive: dess enhet\n+  its saves: dess sparfiler\n+  sector %u holds no partition header: sektor %u har inget partitionshuvud\n+  the chain does not close at __mbr: kedjan sluter sig inte vid __mbr\n+  the chain skips from sector %u: kedjan hoppar fr\u00e5n sektor %u\n+  the chain's links disagree at sector %u: kedjans l\u00e4nkar st\u00e4mmer inte vid sektor %u\n+  the drive's size could not be read: enhetens storlek kunde inte l\u00e4sas\n+  the game (%s): spelet (%s)\n+  the game's folder and everything in it (%s): spelets mapp och allt i den (%s)\n+  the header at sector %u could not be read: huvudet vid sektor %u kunde inte l\u00e4sas\n+  the header at sector %u fails its checksum: huvudet vid sektor %u klarar inte sin kontrollsumma\n+  the partition at sector %u is not where or as large as APA puts one: partitionen vid sektor %u ligger inte d\u00e4r, eller \u00e4r inte s\u00e5 stor som, APA l\u00e4gger en\n+  the settings and art another copy of it uses: inst\u00e4llningarna och bilderna som en annan kopia av det anv\u00e4nder\n+  '%d FOLDERS': '%d MAPPAR'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': '%s kunde inte kopieras till %s. \u00c4r enheten full eller skrivskyddad?'\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s finns inte d\u00e4r RIPPS2 letar. S\u00f6k efter det p\u00e5 enheterna som RIPPS2 har startat? Platsen d\u00e4r det hittas sparas f\u00f6r varje start efter\u00e5t.'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Ember finns inte i %s, bredvid spelen, och d\u00e4r m\u00e5ste det finnas. RIPPS2 hittade det p\u00e5 %s: START kopierar det dit (%d KB) och startar spelet.'\n+  Ember's BIOS: Embers BIOS\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Embers BIOS (bios.bin) finns inte i %s, bredvid Ember. RIPPS2 hittade en p\u00e5 %s: START kopierar den dit (%d KB) och startar spelet.'\n+  LOOKING FOR YOUR LAUNCHERS: LETAR EFTER DINA STARTARE\n+  The POPS files: POPS-filerna\n+  '%s  MMCE card': '%s  MMCE-kort'\n+  INSTALL RIPPS2'S NEUTRINO: INSTALLERA RIPPS2:S NEUTRINO\n+  'Internal HDD (APA): %s free of %s': 'Intern HDD (APA): %s ledigt av %s'\n+  LAUNCHERS: STARTARE\n+  LOOK FOR THEM: LETA EFTER DEM\n+  'Memory card %d: %d KB free': 'Minneskort %d: %d KB ledigt'\n+  POPS files: POPS-filer\n+  START EVERY DRIVE: STARTA VARJE ENHET\n+  'STATUS: looking at the drives and the homebrew': 'STATUS: tittar p\u00e5 enheterna och homebrew'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): Starta varje enhet f\u00f6rst, s\u00e5 att s\u00f6kningen n\u00e5r alla? (Nej s\u00f6ker p\u00e5 enheterna som redan \u00e4r startade.)\n+  USB, HDD, MMCE and the rest: USB, HDD, MMCE och resten\n+  every started drive: varje startad enhet\n+  found where you keep them: hittade d\u00e4r du har dem\n+  not found yet: inte hittad \u00e4n\n+  not on a started drive: inte p\u00e5 en startad enhet\n+  only if you have none: bara om du inte har n\u00e5gon\n+  starts every drive first: startar varje enhet f\u00f6rst\n+  Drives, launchers and homebrew: Enheter, startare och homebrew\n+  BDM Devices: BDM-enheter\n+  Color Sweep: F\u00e4rgskifte\n+  Colors and Text: F\u00e4rger och text\n+  HDD POPS partitions: POPS-partitioner p\u00e5 HDD\n+  Missing Covers: Saknade omslag\n+  Name: Namn\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: Ingen SMB-inst\u00e4llning f\u00f6r POPSTARTER hittades \u00e4n. Fyll i raderna nedan.\n+  POPSTARTER Network: POPSTARTER-n\u00e4tverk\n+  POPSTARTER SMB Network: POPSTARTER SMB-n\u00e4tverk\n+  POPSTARTER UDPFS Network: POPSTARTER UDPFS-n\u00e4tverk\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: RIPPS2 l\u00e4gger sj\u00e4lv till enheten, ISO, VMC:er och videol\u00e4get.\n+  Screen: Sk\u00e4rm\n+  Speed: Hastighet\n+  Sweep Colors: Skiftets f\u00e4rger\n+  Text Weight: Textvikt\n+  Theme Color: Huvudf\u00e4rg\n+  Audio: Ljud\n+  Bold: Fet\n+  Controllers: Handkontroller\n+  Lively: Livlig\n+  Medium: Mellan\n+  PS Emulation: PS-emulering\n+  Regular: Normal\n+  Slow: L\u00e5ngsam\n+  A MOMENT: ETT \u00d6GONBLICK\n+  A TASK IN THE BACKGROUND: EN UPPGIFT I BAKGRUNDEN\n+  ABOUT %d S: CIRKA %d S\n+  ANY MOMENT: N\u00c4R SOM HELST\n+  READING THE GAME LIST OF %s: L\u00c4SER SPELLISTAN P\u00c5 %s\n+  SAVING A LIST: SPARAR EN LISTA\n+  START NOW: STARTA NU\n+  STARTING: STARTAR\n+  STARTS NEXT: STARTAR H\u00c4RN\u00c4ST\n+  STOPPING IT SAFELY: STOPPAR S\u00c4KERT\n+  THE LAST OF THE QUEUE: SLUTET P\u00c5 K\u00d6N\n+  WHEN IT FINISHES: N\u00c4R DET \u00c4R KLART\n+  What each card starts; install or repair BIBLE.: Vad varje kort startar; installera eller reparera BIBLE.\n+  Rainbow: Regnb\u00e5ge\n+  Custom: Egen\n+  DESR DVR ISOs (experimental): DESR DVR-ISO:er (experimentellt)\n+  PC CARD CF (experimental): PC CARD CF (experimentellt)\n+  HDD Start Mode: Startl\u00e4ge f\u00f6r HDD\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): Roboto Condensed av The Roboto Project Authors (SIL OFL 1.1)\n+  '%s on %s': '%s p\u00e5 %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: %s blev %s (%d%%), varje bit kontrollerad. Ta bort ISO:n och beh\u00e5ll ZSO:n?'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: %s blev %s igen, varje bit kontrollerad. Ta bort ZSO:n och beh\u00e5ll ISO:n?'\n+  A ZSO of this game is already beside it. Remove one of the two first.: Det finns redan en ZSO av det h\u00e4r spelet bredvid. Ta bort en av de tv\u00e5 f\u00f6rst.\n+  About %d min, then checked: Cirka %d min, sedan kontrollerad\n+  An ISO of this game is already beside it. Remove one of the two first.: Det finns redan en ISO av det h\u00e4r spelet bredvid. Ta bort en av de tv\u00e5 f\u00f6rst.\n+  CHECKING EVERY PIECE: KONTROLLERAR VARJE BIT\n+  COMPRESS TO ZSO: KOMPRIMERA TILL ZSO\n+  'COMPRESSED %s: %s FREED': '%s KOMPRIMERAT: %s FRIGJORT'\n+  COMPRESSING 1 OF 2: KOMPRIMERAR 1 AV 2\n+  COMPRESSING 2 OF 2: KOMPRIMERAR 2 AV 2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : Komprimera %s? ISO:n ligger kvar tills ZSO:n har l\u00e4sts tillbaka och kontrollerats, sedan fr\u00e5gar RIPPS2 innan den tar bort ISO:n. Enheten beh\u00f6ver plats f\u00f6r b\u00e5da medan den arbetar; l\u00e5t konsolen vara p\u00e5 tills den \u00e4r klar. En ZSO startar bara med OPL-k\u00e4rnan (inte Neutrino); dess sparningar, inst\u00e4llningar, bilder och fusk f\u00f6ljer med, och den kan bli en ISO igen n\u00e4r som helst.\n+  Compress to ZSO: Komprimera till ZSO\n+  Cycle: V\u00e4xla\n+  'Experimental: OPL core only (not Neutrino)': 'Experimentell: bara OPL-k\u00e4rnan (inte Neutrino)'\n+  Hold SELECT: H\u00e5ll SELECT\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: Det gick inte att slutf\u00f6ra (%s). Det som skrevs togs bort; spelet \u00e4r som f\u00f6rut.\n+  List: Lista\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : G\u00f6ra %s till en ISO igen? ZSO:n ligger kvar tills ISO:n har l\u00e4sts tillbaka och kontrollerats, sedan fr\u00e5gar RIPPS2 innan den tar bort ZSO:n. Enheten beh\u00f6ver plats f\u00f6r b\u00e5da medan den arbetar; l\u00e5t konsolen vara p\u00e5 tills den \u00e4r klar. Dess sparningar, inst\u00e4llningar, bilder och fusk f\u00f6ljer med.\n+  Stop? What it has written so far is removed; the game stays as it was.: Stoppa? Det som skrivits hittills tas bort; spelet f\u00f6rblir som f\u00f6rut.\n+  THEMES: TEMAN\n+  There is not enough memory for this just now.: Det finns inte tillr\u00e4ckligt med minne f\u00f6r detta just nu.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: Den h\u00e4r avbildningen kan RIPPS2 inte komprimera eller packa upp (storleken \u00e4r inte hela block om 2 KB).\n+  UNCOMPRESS TO ISO: PACKA UPP TILL ISO\n+  UNCOMPRESSED %s: '%s UPPACKAT'\n+  UNCOMPRESSING: PACKAR UPP\n+  Uncompress to ISO: Packa upp till ISO\n+  Under a minute, then checked: Under en minut, sedan kontrollerad\n+  COPYING: KOPIERAR\n+  SHOW AGAIN, DELETE OR COMPRESS: VISA IGEN, RADERA ELLER KOMPRIMERA\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: VISA IGEN, RADERA ELLER PACKA UPP\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': '%s beh\u00f6ver ungef\u00e4r %s ledigt p\u00e5 sin enhet medan den arbetar (originalet ligger kvar till slutet), och enheten har %s ledigt.'\n+  Hide and Compress: D\u00f6lj och komprimera\n+  'COMPRESSED AND HIDDEN: %s': 'KOMPRIMERAT OCH DOLT: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': 'Det finns inte plats f\u00f6r den h\u00e4r rippen: den beh\u00f6ver ungef\u00e4r %s, och enheten har %s ledigt.'\n+  OVER 4 GB FOR FAT32: \u00d6VER 4 GB F\u00d6R FAT32\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: Den h\u00e4r enheten \u00e4r %s, som inte rymmer filer p\u00e5 4 GB eller mer, och den nya avbildningen skulle bli %s.\n+  '%s, %s clusters, %s free of %s': '%s, kluster p\u00e5 %s, %s ledigt av %s'\n+  Delete This Sweep: Radera det h\u00e4r f\u00e4rgskiftet\n+  Delete %s? RIPPS2's own sweeps stay as they are.: Radera %s? RIPPS2:s egna f\u00e4rgskiften blir kvar som de \u00e4r.\n+  Sweep: F\u00e4rgskifte\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: M\u00e4rken visar spelen med prestationer i dina listor och hur m\u00e5nga du har l\u00e5st upp. De kr\u00e4ver att TELEMETRI \u00e4r p\u00e5.\n labels:\n   OK: OK\n   BDMMODE: Startl\u00e4ge f\u00f6r BDM\n@@ -1222,7 +1388,7 @@ labels:\n   SAVE_ICONS: 3D-sparikoner\n   HINT_SAVE_ICONS: Visar det valda spelets sparikon, snurrande, d\u00e4r temat har plats f\u00f6r den. L\u00e4ser spelets egen VMC f\u00f6rst, sedan minneskorten.\n   NETBOOT_RESTART_NOW: N\u00e4tverksprotokollet \u00e4ndras efter en omstart. Spara inst\u00e4llningarna och starta om OPL nu?\n-  HINT_NET_PROTOCOL: 'Vilken n\u00e4tverkstj\u00e4nst RiptOPL anv\u00e4nder: en SMB-resurs, UDPFS, UDPBD eller HTTP. Samma inst\u00e4llning som i Spelk\u00e4llor. Att byta fr\u00e5n en som redan k\u00f6rs kr\u00e4ver en omstart.'\n+  HINT_NET_PROTOCOL: 'Vilken n\u00e4tverkstj\u00e4nst RiptOPL anv\u00e4nder: en SMB-resurs, UDPFS, UDPBD eller HTTP. Att byta fr\u00e5n en som redan k\u00f6rs kr\u00e4ver en omstart.'\n   HINT_UDPFS_ACCESS: Bara UDPFS. Filer l\u00e4ser l\u00f6sa ISO:er fr\u00e5n serverns spelmapp; IMG l\u00e4ser en diskavbild som en enhet. SMB och HTTP anv\u00e4nder alltid Filer, UDPBD alltid IMG.\n   HINT_SMB_VERSION: Vilken SMB-version som anv\u00e4nds mot PC-resursen.\n   HINT_NET_ADVANCED: L\u00e5ser upp Ethernet-l\u00e4nkl\u00e4ge och SMB-port f\u00f6r redigering.\n@@ -1421,3 +1587,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: Hur bilderna som v\u00e4ntar p\u00e5 varje spels rulle blir PNG.\n   RIPPS2_SETTINGS_HOME: Spara inst\u00e4llningar p\u00e5\n   HINT_RIPPS2_SETTINGS_HOME: 'Var RIPPS2 sparar sina inst\u00e4llningar: sin egen sparfil p\u00e5 ett minneskort, en RIPPS2-mapp p\u00e5 en enhet (den interna HDD:n, USB, MX4SIO, iLink eller MMCE) eller en mapp du skriver.'\n+  RIPPS2_LAUNCHERS: Startare\n+  RIPPS2_LOOK_NOW: S\u00f6k nu\n+  HINT_RIPPS2_LOOK_NOW: RIPPS2 s\u00f6ker p\u00e5 de startade enheterna efter POPSTARTER, POPS-filerna, Ember, Embers BIOS och Neutrino och kommer ih\u00e5g var var och en finns. Varje start l\u00e4ser dem d\u00e4rifr\u00e5n.\n+  RIPPS2_HIDE_GAME: D\u00f6lj spel\n+  RIPPS2_HIDDEN_GAMES: Dolda spel\n+  HINT_RIPPS2_HIDDEN_GAMES: Spelen som dolts med D\u00f6lj spel, p\u00e5 de startade enheterna. Visa ett igen, eller radera det. Varje enhet har sin egen lista.\n+  HINT_RIPPS2_BDM_DEVICES: 'Enheterna BDM l\u00e4ser spel fr\u00e5n: USB, MX4SIO, ett PC Card CF, en DESR:s DVR-enhet och iLink. Den inbyggda HDD:n f\u00f6ljer startl\u00e4get f\u00f6r HDD.'\n+  HINT_RIPPS2_SCREEN: 'Menyns bild: videol\u00e4ge, bredbild, var den sitter p\u00e5 tv:n (H-POS, V-POS) och overscan.'\n+  HINT_RIPPS2_LAYOUT: Var kategorierna sitter och hur en sida kommer in, sedan RIPFLOWs omslag och RIPGRIDs rutn\u00e4t.\n+  HINT_RIPPS2_COLORS_TEXT: Textvikt, f\u00e4rgtemat, f\u00e4rgskiftet och varje f\u00e4rg.\n+  HINT_RIPPS2_TEXT_WEIGHT: 'Hur kraftig RIPPS2:s egen text \u00e4r: tipsen i inst\u00e4llningarna, STATUS och Om. Normal \u00e4r som f\u00f6rut; Mellan och Fet l\u00e4ses b\u00e4ttre p\u00e5 en mjuk bild.'\n+  HINT_RIPPS2_HDD_START: 'Den inbyggda HDD:n, APA eller exFAT: RIPPS2 l\u00e4ser den vad den \u00e4n \u00e4r och visar den en g\u00e5ng, som HDD-spel. Av l\u00e5ter den vara.'\n+  HINT_RIPPS2_THEME_COLOR: F\u00e4rgtemats egen f\u00e4rg. V\u00e4lj Egen som f\u00e4rgtema f\u00f6r att v\u00e4lja den sj\u00e4lv, som textf\u00e4rgerna.\n+  HINT_RIPPS2_SWEEP: Fem f\u00e4rger som temat glider genom, l\u00e5ngsamt och mjukt. Regnb\u00e5ge, ett skifte gjort av varje f\u00e4rgtema eller ditt eget. Av h\u00e5ller f\u00e4rgtemat stilla.\n+  HINT_RIPPS2_SWEEP_COLORS: Skiftets fem f\u00e4rger och dess hastighet. \u00c4ndra ett av RIPPS2:s s\u00e5 sparas det som ditt eget, under namnet du ger det.\n+  HINT_RIPPS2_SWEEP_NAME: Vad ditt skifte heter i listan F\u00e4rgskifte.\n+  HINT_RIPPS2_SWEEP_COLOR: En av de fem f\u00e4rgerna skiftet glider genom, i tur och ordning.\n+  HINT_RIPPS2_SWEEP_SPEED: 'Hur l\u00e4nge varje f\u00e4rg varar innan n\u00e4sta tonar in. \u00c4ven Livlig f\u00f6rblir mjuk: f\u00e4rgerna blinkar aldrig.'\n+  RIPPS2_HIDE_COMPRESS: D\u00f6lj spel och komprimering\n+  HINT_RIPPS2_SELECT_HOLD: 'SELECT h\u00e5llen 4.2 s p\u00e5 en spellista. Lista: temana i en panel, v\u00e4lj ett. V\u00e4xla: n\u00e4sta tema. Av: inget (ett tryck uppdaterar \u00e4nd\u00e5).'"
        },
        {
          "sha": "3191383894a975a78925ecdb35cf5be933a01f74",
          "filename": "elf/lang/TChinese.yml",
          "status": "modified",
          "additions": 209,
          "deletions": 22,
          "changes": 231,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FTChinese.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FTChinese.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FTChinese.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: TChinese\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTER \u78ba\u8a8d\uff0cESC \u53d6\u6d88\u3002\u6309\u4efb\u610f\u6309\u9215\u986f\u793a\u6309\u9375\u3002\n   USB KEYBOARD: USB \u9375\u76e4\n-  '      DESR DVR ISOs (experimental)': '      DESR DVR ISO\uff08\u5be6\u9a57\u6027\uff09'\n-  '      Internal HDD (exFAT)': '      \u5167\u5efa\u786c\u789f\uff08exFAT\uff09'\n-  '      PC CARD CF (experimental)': '      PC CARD CF\uff08\u5be6\u9a57\u6027\uff09'\n-  '      Protocol': '      \u5354\u5b9a'\n   Access: \u5b58\u53d6\u65b9\u5f0f\n   Applications Page Start Mode: \u61c9\u7528\u7a0b\u5f0f\u9801\u9762\u555f\u52d5\u6a21\u5f0f\n   Art Delay: \u5716\u7247\u5ef6\u9072\n@@ -22,7 +18,6 @@ strings:\n   Fifth: \u7b2c\u4e94\n   First: \u7b2c\u4e00\n   Fourth: \u7b2c\u56db\n-  HDD (APA) Start Mode: \u786c\u789f\uff08APA\uff09\u555f\u52d5\u6a21\u5f0f\n   HDD Cache: \u786c\u789f\u5feb\u53d6\n   HDD Spindown: \u786c\u789f\u4f11\u7720\n   MMCE Settings: MMCE \u8a2d\u5b9a\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: \u9019\u662f\u6253\u904e ESR \u4fee\u88dc\u7684 PS2 \u904a\u6232\u3002\u8acb\u628a ESR\uff08esr*.elf\uff09\u653e\u5230\u8a18\u61b6\u5361\u6216\u96a8\u8eab\u789f\u4e0a\u7684 APPS\u3001BOOT \u6216 ESR \u8cc7\u6599\u593e\u4f86\u555f\u52d5\u5b83\u3002\n   '%s, BUILD %d': '%s\uff0c\u7d44\u5efa %d'\n   '... and the anonymous ...': \u2026\u2026\u4ee5\u53ca\u533f\u540d\u7684\u5404\u4f4d\u2026\u2026\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': 1.8.0 \u539f\u6a23\u9644\u5e36\uff08AFL-3.0\uff09\uff1agithub.com/rickgaiser/neutrino\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: RiptOPL\uff08Open PS2 Loader \u7684\u5206\u652f\uff09\u7684\u62c6\u89e3\u8207\u91cd\u5efa\uff0c\n   ABOUT: \u95dc\u65bc\n   'ACHIEVEMENTS: SPIRIT OF RA': \u6210\u5c31\uff1aSPIRIT OF RA\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): \u8a18\u61b6\u6a94\u6848\u4e2d\u7684 AVI \u5f71\u7247\uff0cRIPPS2 \u7684\u5206\u652f\uff08AFL 2.0\uff09\n   Apollo Save Tool by bucanero: bucanero \u7684 Apollo Save Tool\n   BACK: \u8fd4\u56de\n+  Back (keeps both): \u8fd4\u56de\uff08\u5169\u500b\u90fd\u4fdd\u7559\uff09\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: \u9019\u5f35 MMCE \u5361\u7121\u6cd5\u91cd\u65b0\u547d\u540d\u6a94\u6848\uff0c\u800c\u58d3\u7e2e\u7684\u6700\u5f8c\u4e00\u6b65\u9700\u8981\u9019\u6a23\u505a\u3002\u8acb\u5c07\u5176 sd2psXtd \u97cc\u9ad4\u66f4\u65b0\u5230 1.4.0 \u6216\u66f4\u65b0\u7248\u672c\uff08sd2psxtd.github.io/download\uff09\uff0c\u7136\u5f8c\u91cd\u8a66\u3002\u672a\u505a\u4efb\u4f55\u8b8a\u66f4\u3002\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): BIBLE \u662f PS2BBL 1.2.0\uff0cEl Isra \u7c3d\u7f72\u7684\u7248\u672c\uff08\u8a18\u61b6\u5361\u8207\u786c\u789f\uff09\uff0c\u672a\u7d93\u4fee\u6539\uff08GPL-3\uff09\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: BIOS \u97f3\u6548 > \u64f7\u53d6\u6703\u50cf SPU2 \u4e00\u6a23\u64ad\u653e\u5b83\u5011\uff1b\u4e0d\u9644\u5e36\u4efb\u4f55 BIOS\n   BOOT LOCK AND BIBLE: BOOT LOCK \u8207 BIBLE\n   BOOT LOCK edits its config too: BOOT LOCK \u4e5f\u6703\u7de8\u8f2f\u5b83\u7684\u8a2d\u5b9a\n   BUILD DETAILS: \u7d44\u5efa\u8a73\u60c5\n-  BUILT ON RIPTOPL: \u57fa\u65bc RIPTOPL\n   BearSSL by Thomas Pornin: Thomas Pornin \u7684 BearSSL\n   Built with Claude (Anthropic): \u501f\u52a9 Claude\uff08Anthropic\uff09\u88fd\u4f5c\n   CD PLAYER: CD \u64ad\u653e\u5668\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek \u8207 Sleek Bold\uff0c\u70ba RIPPS2 \u7e6a\u88fd\n   RetroAchievements on the console by hacan359 (yoba): hacan359\uff08yoba\uff09\u7684\u4e3b\u6a5f\u7aef RetroAchievements\n   RiptOPL by NathanNeurotic: NathanNeurotic \u7684 RiptOPL\n-  Roboto Condensed by Google (Apache-2.0): Google \u7684 Roboto Condensed\uff08Apache-2.0\uff09\n   SOUNDS: \u97f3\u6548\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: SP193 \u7684 FMCB \u5b89\u88dd\u7a0b\u5f0f\u8207 pcm720 \u7684 OSDMenu\uff0c\u9010\u6b65\u9075\u5faa\n   SUPPORT: \u652f\u63f4\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: NathanNeurotic \u7684 wLaunchELF R3Z\n   wLaunchELF by the ps2homebrew team: ps2homebrew \u5718\u968a\u7684 wLaunchELF\n   what RIPPS2's card image writes are checked against: \u7528\u4f86\u6838\u5c0d RIPPS2 \u5beb\u5165\u8a18\u61b6\u5361\u6620\u50cf\u7684\u5de5\u5177\n-  with its own look, launchers, file browser and save tools.: \u64c1\u6709\u81ea\u5df1\u7684\u5916\u89c0\u3001\u555f\u52d5\u5668\u3001\u6a94\u6848\u700f\u89bd\u5668\u8207\u5b58\u6a94\u5de5\u5177\u3002\n   xeRAbora by hacan359, rcheevos by RetroAchievements: hacan359 \u7684 xeRAbora\uff0cRetroAchievements \u7684 rcheevos\n   your console's own, ripped from its BIOS: \u4f60\u4e3b\u6a5f\u81ea\u5df1\u7684\u97f3\u6548\uff0c\u5f9e\u5b83\u7684 BIOS \u64f7\u53d6\n   ' (no DNS answer)': ' \uff08DNS \u7121\u56de\u61c9\uff09'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': NEUTRINO\uff1a\u6b63\u5728\u5beb\u5165 %.60s\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: \u7121\u6cd5\u628a Neutrino \u5beb\u5165 %s\u3002\u8acb\u6aa2\u67e5\u53ef\u7528\u7a7a\u9593\u4ee5\u53ca\u662f\u5426\u9632\u5beb\uff0c\u7136\u5f8c\u91cd\u8a66\u3002\n   THE GAME'S DRIVE  (%s/neutrino): \u904a\u6232\u6240\u5728\u78c1\u789f\u6a5f  \uff08%s/neutrino\uff09\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : \u9019\u500b\u904a\u6232\u900f\u904e Neutrino \u555f\u52d5\uff0c\u800c RIPPS2 \u5c0b\u627e\u7684\u4f4d\u7f6e\uff08\u8a2d\u5b9a\u3001\u904a\u6232\u6240\u5728\u78c1\u789f\u6a5f\u3001\u8a18\u61b6\u5361\uff09\u90fd\u6c92\u6709 Neutrino\u3002RIPPS2 \u9644\u5e36 rickgaiser \u7684 Neutrino 1.8.0\uff08AFL-3.0\uff09\u3002\u73fe\u5728\u5b89\u88dd\u55ce\uff1f\u63a5\u4e0b\u4f86\u9078\u64c7\u4f4d\u7f6e\u3002\n   '%d / %d FILES': '%d / %d \u500b\u6a94\u6848'\n   '%d FOLDERS AND CONTENTS': '%d \u500b\u8cc7\u6599\u593e\u53ca\u5167\u5bb9'\n   '%d FOLDERS, %d FOUND': '%d \u500b\u8cc7\u6599\u593e\uff0c\u627e\u5230 %d \u500b'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d / %d \u4ecd\u9396\u5b9a'\n   '%s   on %s': '%s   \u65bc %s'\n   BADGES: \u5fbd\u7ae0\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: \u53ea\u6709\u5728\u958b\u555f\u9059\u6e2c\u4e14\u4f60\u7684\u96fb\u8166\u4e0a\u57f7\u884c\u8457 xeRAbora \u6642\u5fbd\u7ae0\u624d\u6703\u751f\u6548\uff1b\u5728\u6b64\u4e4b\u524d\u5b83\u5011\u4e0d\u4f54\u7528\u4efb\u4f55\u8cc7\u6e90\u3002\n   CHECK THE DISC: \u6aa2\u67e5\u5149\u789f\n   From this game's last CHECK GAME SUPPORT.: \u4f86\u81ea\u9019\u500b\u904a\u6232\u4e0a\u4e00\u6b21\u7684\u300c\u6aa2\u67e5\u904a\u6232\u652f\u63f4\u300d\u3002\n   HOW IT WORKS: \u4f7f\u7528\u8aaa\u660e\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: \u9019\u53f0\u4e3b\u6a5f\u4e0a\u6709\u5e36 RIPPS2 \u9396\u5b9a\u7684 PS2BBL\u3002\n   AUDIO folder: AUDIO \u8cc7\u6599\u593e\n   DRIVES: \u78c1\u789f\u6a5f\n-  INSTALL RIPPS2'S COPY: \u5b89\u88dd RIPPS2 \u7684\u526f\u672c\n-  'Installed: ': '\u5df2\u5b89\u88dd\uff1a '\n   MANAGE: \u7ba1\u7406\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: \u300c\u7ba1\u7406\u300d\u6703\u986f\u793a\u6bcf\u5f35\u8a18\u61b6\u5361\u555f\u52d5\u7684\u5167\u5bb9\uff0c\u4e26\u5b89\u88dd\u6216\u4fee\u5fa9 BIBLE\u3002\n   MEMORY CARD 0  (about 600 KB): \u8a18\u61b6\u5361 0  \uff08\u7d04 600 KB\uff09\n   MEMORY CARD 1  (about 600 KB): \u8a18\u61b6\u5361 1  \uff08\u7d04 600 KB\uff09\n   'MX4SIO: its driver would not load (code %d).': MX4SIO\uff1a\u9a45\u52d5\u7a0b\u5f0f\u7121\u6cd5\u8f09\u5165\uff08\u4ee3\u78bc %d\uff09\u3002\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': MX4SIO\uff1a\u9a45\u52d5\u7a0b\u5f0f\u5df2\u8f09\u5165\uff0c\u4f46\u6c92\u6709 SD \u5361\u56de\u61c9\uff08mc1 \u63d2\u69fd\uff09\u3002\n   NEUTRINO SETTINGS: NEUTRINO \u8a2d\u5b9a\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': \u9084\u6c92\u6709\uff1aRIPPS2 \u6c92\u6709\u5167\u5efa\u97f3\u6548\u3002\u8acb\u64f7\u53d6\u9019\u53f0\u4e3b\u6a5f\u7684\u97f3\u6548\uff0c\u6216\u5728 RIPPS2 Audio Studio \u4e2d\u88fd\u4f5c\u3002\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: \u672a\u5b89\u88dd\u3002\u9700\u8981\u5b83\u7684\u904a\u6232\u6703\u63d0\u4f9b RIPPS2 \u7684\u526f\u672c\uff0c\u6216\u5728\u9019\u88e1\u5b89\u88dd\u3002\n   POPSTARTER NETWORK: POPSTARTER \u7db2\u8def\n   POPSTARTER and Ember: POPSTARTER \u8207 Ember\n   PS1 SETTINGS: PS1 \u8a2d\u5b9a\n   PS2BBL 1.2.0 for RIPPS2: \u4f9b RIPPS2 \u4f7f\u7528\u7684 PS2BBL 1.2.0\n   RIP THIS CONSOLE'S SOUNDS: \u64f7\u53d6\u9019\u53f0\u4e3b\u6a5f\u7684\u97f3\u6548\n-  RIPPS2's homebrew: RIPPS2 \u7684\u81ea\u88fd\u7a0b\u5f0f\n   STATUS: \u72c0\u614b\n-  'STATUS: looking for RIPPS2''s homebrew': \u72c0\u614b\uff1a\u6b63\u5728\u5c0b\u627e RIPPS2 \u7684\u81ea\u88fd\u7a0b\u5f0f\n   a drive of unknown kind: \u672a\u77e5\u985e\u578b\u7684\u78c1\u789f\u6a5f\n-  a drive or a memory card: \u78c1\u789f\u6a5f\u6216\u8a18\u61b6\u5361\n   boot loader, card by card: \u958b\u6a5f\u7a0b\u5f0f\uff0c\u9010\u5361\u67e5\u770b\n   from its BIOS, into AUDIO: \u4f86\u81ea\u5176 BIOS\uff0c\u5b58\u5165 AUDIO\n   internal HDD (exFAT): \u5167\u5efa\u786c\u789f\uff08exFAT\uff09\n@@ -975,10 +958,194 @@ strings:\n   Sidebar: \u5074\u908a\u6b04\n   your own copy, found on your drives and memory cards: \u4f60\u81ea\u5df1\u7684\u526f\u672c\uff0c\u5728\u4f60\u7684\u78c1\u789f\u6a5f\u548c\u8a18\u61b6\u5361\u4e0a\u627e\u5230\n   'your own, found and launched: github.com/Gageformer/Ember/releases': \u4f60\u81ea\u5df1\u7684\uff0c\u627e\u5230\u4e26\u555f\u52d5\uff1agithub.com/Gageformer/Ember/releases\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': \u5728 mass0:\uff08%s\uff09\u4e0a\u627e\u4e0d\u5230\u3002\u8acb\u628a ember.elf \u548c\u4f60\u81ea\u5df1\u7684 bios.bin \u653e\u9032\u5b83\u7684 EMBER \u8cc7\u6599\u593e\u3002\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: \u627e\u4e0d\u5230\u3002\u8acb\u628a\u4f60\u81ea\u5df1\u7684 POPSTARTER.ELF \u653e\u9032\u67d0\u500b\u78c1\u789f\u6a5f\u7684 POPS \u8cc7\u6599\u593e\u3002\n   Launch Disc: \u555f\u52d5\u5149\u789f\n   Memory Files: \u8a18\u61b6\u6a94\u6848\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: RIPPS2 \u627e\u5230\u4e86 5 \u500b\u4e2d\u7684 %d \u500b\u3002\u6bcf\u4e00\u500b\u90fd\u6703\u88ab\u8a18\u4f4f\uff0c\u6bcf\u6b21\u555f\u52d5\u90fd\u5f9e\u90a3\u88e1\u8b80\u53d6\u3002\n+  not found on the started drives: \u5728\u5df2\u555f\u52d5\u7684\u78c1\u789f\u6a5f\u4e0a\u627e\u4e0d\u5230\n+  not looked for yet: \u5c1a\u672a\u5c0b\u627e\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s \u6c92\u6709\u56de\u61c9\uff0c\u6240\u4ee5 %s \u7121\u6cd5\u555f\u52d5\u3002\u5b83\u6709\u9023\u63a5\u55ce\uff1f'\n+  '%s is not on %s any more, so it has left All Games.': '%s \u5df2\u4e0d\u5728 %s \u4e0a\uff0c\u6240\u4ee5\u5df2\u96e2\u958b\u5168\u90e8\u904a\u6232\u3002'\n+  STARTING %s: \u6b63\u5728\u555f\u52d5 %s\n+  the SMB share: SMB \u5171\u7528\u8cc7\u6599\u593e\n+  the drive: \u78c1\u789f\u6a5f\n+  the internal HDD (APA): \u5167\u5efa\u786c\u789f\uff08APA\uff09\n+  the internal HDD (exFAT): \u5167\u5efa\u786c\u789f\uff08exFAT\uff09\n+  BUILT ON RIPTOPL, WITH NEUTRINO: \u5efa\u7acb\u5728 RIPTOPL \u4e4b\u4e0a\uff0c\u642d\u8f09 NEUTRINO\n+  'Special thanks to all the YouTube love as well:': \u4e5f\u7279\u5225\u611f\u8b1d\u4f86\u81ea YouTube \u7684\u6240\u6709\u559c\u611b\uff1a\n+  and its own look, launchers, file browser and save tools.: \u4e26\u6709\u81ea\u5df1\u7684\u5916\u89c0\u3001\u555f\u52d5\u5668\u3001\u6a94\u6848\u700f\u89bd\u5668\u548c\u5b58\u6a94\u5de5\u5177\u3002\n+  glad to help however you reach me.: \u4e0d\u8ad6\u4f60\u600e\u9ebc\u806f\u7d61\u6211\uff0c\u90fd\u6a02\u610f\u5e6b\u5fd9\u3002\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): \u7b2c\u4e8c\u500b\u904a\u6232\u6838\u5fc3\uff0c1.8.0 \u539f\u6a23\u9644\u5e36\uff08AFL-3.0\uff09\n+  with rickgaiser's Neutrino inside as its second game core,: \u5167\u542b rickgaiser \u7684 Neutrino \u4f5c\u70ba\u7b2c\u4e8c\u500b\u904a\u6232\u6838\u5fc3\uff0c\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : \u9019\u500b\u904a\u6232\u900f\u904e Neutrino \u555f\u52d5\uff0c\u800c RIPPS2 \u6c92\u6709\u627e\u5230\uff1a\u8a2d\u5b9a\u88e1\u7684\u8def\u5f91\u3001\u904a\u6232\u6240\u5728\u7684\u78c1\u789f\u6a5f\u3001\u8a18\u61b6\u5361\u548c\u4efb\u4f55\u5df2\u555f\u52d5\u7684\u78c1\u789f\u6a5f\u4e0a\u90fd\u6c92\u6709\u3002RIPPS2 \u9644\u5e36 rickgaiser \u7684 Neutrino 1.8.0\uff08AFL-3.0\uff09\uff1a\u5728\u4f60\u9078\u7684\u5730\u65b9\u5b89\u88dd\u4e00\u6b21\uff0c\u4e4b\u5f8c\u6bcf\u500b Neutrino \u904a\u6232\u90fd\u5f9e\u90a3\u88e1\u4f7f\u7528\u5b83\u3002\n+  STARTING THE DRIVES: \u6b63\u5728\u555f\u52d5\u78c1\u789f\u6a5f\n+  '%d games, on the started drives': '%d \u500b\u904a\u6232\uff0c\u5728\u5df2\u555f\u52d5\u7684\u78c1\u789f\u6a5f\u4e0a'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s\uff1a\u6709\u4e9b\u6a94\u6848\u7121\u6cd5\u522a\u9664'\n+  '%u partitions, all in order': '%u \u500b\u5206\u5272\u5340\uff0c\u5168\u90e8\u6b63\u5e38'\n+  ', %d art images': \u3001%d \u5f35\u5c01\u9762\u5716\u7247\n+  ', and ': \uff0c\u4ee5\u53ca\n+  ', its cheats': \u3001\u5b83\u7684\u91d1\u624b\u6307\n+  ', its settings': \u3001\u5b83\u7684\u8a2d\u5b9a\n+  1 game, on the started drives: 1 \u500b\u904a\u6232\uff0c\u5728\u5df2\u555f\u52d5\u7684\u78c1\u789f\u6a5f\u4e0a\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: \u9019\u88e1\u7684 PS1 \u904a\u6232\u8981\u5728\u8a18\u61b6\u6a94\u6848\u4e2d\u522a\u9664\uff0c\u5728\u90a3\u88e1\u5b83\u662f\u7368\u7acb\u7684\u6a94\u6848\u3002\n+  A network catalogue's games are deleted on the PC that serves them.: \u7db2\u8def\u76ee\u9304\u7684\u904a\u6232\u8981\u5728\u63d0\u4f9b\u5b83\u5011\u7684\u96fb\u8166\u4e0a\u522a\u9664\u3002\n+  DELETE FOR GOOD: \u6c38\u4e45\u522a\u9664\n+  DELETE IT: \u522a\u9664\u5b83\n+  'DELETED %s: %s FREED': \u5df2\u522a\u9664 %s\uff1a\u91cb\u653e %s\n+  'Delete %s from %s? Goes: %s.': \u522a\u9664 %s\uff08\u5728 %s \u4e0a\uff09\uff1f\u522a\u9664\uff1a%s\u3002\n+  'Delete %s from %s? Goes: %s. Kept: %s.': \u522a\u9664 %s\uff08\u5728 %s \u4e0a\uff09\uff1f\u522a\u9664\uff1a%s\u3002\u4fdd\u7559\uff1a%s\u3002\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: \u4e5f\u522a\u9664\u5b83\u7684\u5b58\u6a94\u548c RIPSHOT \u7167\u7247\u55ce\uff1f\u9078\u300c\u5426\u300d\u5247\u4fdd\u7559\u3002\n+  HIDDEN GAMES: \u96b1\u85cf\u7684\u904a\u6232\n+  'HIDDEN: %s': \u5df2\u96b1\u85cf\uff1a%s\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: \u96b1\u85cf %s\uff1f\u5b83\u6703\u96e2\u958b\u6240\u6709\u6e05\u55ae\uff0c\u5305\u62ec\u5168\u90e8\u904a\u6232\u548c\u6700\u611b\u3002\u8a2d\u5b9a > \u904a\u6232\u4f86\u6e90 > \u96b1\u85cf\u7684\u904a\u6232 \u53ef\u4ee5\u518d\u986f\u793a\u5b83\u3002\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: \u6700\u5f8c\u78ba\u8a8d\u3002%s \u5c07\u88ab\u6c38\u4e45\u522a\u9664\uff0c\u4efb\u4f55\u65b9\u6cd5\u90fd\u7121\u6cd5\u5fa9\u539f\u3002\u6309 START \u522a\u9664\u3002\n+  None. Hide Game, in a game's options, puts one here.: \u6c92\u6709\u3002\u5728\u904a\u6232\u9078\u9805\u4e2d\u9078\u64c7\u300c\u96b1\u85cf\u904a\u6232\u300d\u5373\u53ef\u653e\u5230\u9019\u88e1\u3002\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : \u672a\u522a\u9664\u4efb\u4f55\u5167\u5bb9\uff1a\u9019\u500b\u78c1\u789f\u6a5f\u7684\u5206\u5272\u8868\u8207\u9019\u9846\u786c\u789f\u4e0d\u7b26\uff08%s\uff09\u3002\u5b83\u53ef\u80fd\u662f\u5f9e\u5176\u4ed6\u5bb9\u91cf\u786c\u789f\u7684\u6620\u50cf\u5beb\u5165\u7684\u3002\u8acb\u5148\u5728\u96fb\u8166\u4e0a\u4fee\u5fa9\u3002\n+  RIPPS2 cannot delete a game from this page.: RIPPS2 \u7121\u6cd5\u5728\u6b64\u9801\u9762\u522a\u9664\u904a\u6232\u3002\n+  RIPPS2 could not find this game's files on its drive.: RIPPS2 \u5728\u904a\u6232\u6240\u5728\u7684\u78c1\u789f\u6a5f\u4e0a\u627e\u4e0d\u5230\u5b83\u7684\u6a94\u6848\u3002\n+  Reading the hidden lists: \u6b63\u5728\u8b80\u53d6\u96b1\u85cf\u6e05\u55ae\n+  SHOW AGAIN OR DELETE: \u91cd\u65b0\u986f\u793a\u6216\u522a\u9664\n+  SHOW IT AGAIN: \u91cd\u65b0\u986f\u793a\n+  That game is not on its drive any more.: \u8a72\u904a\u6232\u5df2\u4e0d\u5728\u5b83\u7684\u78c1\u789f\u6a5f\u4e0a\u3002\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: \u7121\u6cd5\u628a\u96b1\u85cf\u6e05\u55ae\u5beb\u5165\u904a\u6232\u6240\u5728\u7684\u78c1\u789f\u6a5f\u3002\u5b83\u662f\u5426\u6709\u5beb\u5165\u4fdd\u8b77\uff0c\u6216\u5beb\u5165\u64cd\u4f5c\u5df2\u95dc\u9589\uff1f\n+  The partition could not be removed (%d). Nothing was deleted.: \u7121\u6cd5\u79fb\u9664\u5206\u5272\u5340\uff08%d\uff09\u3002\u672a\u522a\u9664\u4efb\u4f55\u5167\u5bb9\u3002\n+  This drive cannot keep a hidden list (a network catalogue).: \u9019\u500b\u78c1\u789f\u6a5f\u7121\u6cd5\u4fdd\u5b58\u96b1\u85cf\u6e05\u55ae\uff08\u7db2\u8def\u76ee\u9304\uff09\u3002\n+  This game's drive has not answered yet. Wait for it, then try again.: \u9019\u500b\u904a\u6232\u7684\u78c1\u789f\u6a5f\u9084\u6c92\u6709\u56de\u61c9\u3002\u8acb\u7b49\u5b83\u5c31\u7dd2\u5f8c\u518d\u8a66\u3002\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: \u9019\u500b\u904a\u6232\u7684\u5206\u5272\u5340\u5c6c\u65bc RIPPS2 \u4e0d\u6703\u79fb\u9664\u7684\u5206\u5272\u5340\uff08\u7cfb\u7d71\u5206\u5272\u5340\uff0c\u6216\u5b58\u653e\u5176\u8a2d\u5b9a\u7684\u5206\u5272\u5340\uff09\u3002\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': \u6709\u500b\u5206\u5272\u5340\u6bd4\u9019\u9846\u786c\u789f\u5141\u8a31\u7684\u66f4\u5927\uff1a\u5206\u5272\u8868\u662f\u70ba\u53e6\u4e00\u5bb9\u91cf\u7684\u786c\u789f\u88fd\u4f5c\u7684\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': \u6709\u500b\u5206\u5272\u5340\u8d85\u51fa\u4e86\u9019\u9846\u786c\u789f\u7684\u7d50\u5c3e\uff08\u78c1\u5340 %u / %u\uff09\uff1a\u5206\u5272\u8868\u662f\u70ba\u66f4\u5927\u7684\u786c\u789f\u88fd\u4f5c\u7684\n+  its RIPSHOT pictures: \u5b83\u7684 RIPSHOT \u7167\u7247\n+  its drive: \u5b83\u7684\u78c1\u789f\u6a5f\n+  its saves: \u5b83\u7684\u5b58\u6a94\n+  sector %u holds no partition header: \u78c1\u5340 %u \u6c92\u6709\u5206\u5272\u5340\u6a19\u982d\n+  the chain does not close at __mbr: \u93c8\u6c92\u6709\u5728 __mbr \u8655\u9589\u5408\n+  the chain skips from sector %u: \u93c8\u5f9e\u78c1\u5340 %u \u8655\u8df3\u8e8d\n+  the chain's links disagree at sector %u: \u93c8\u7684\u74b0\u7bc0\u5728\u78c1\u5340 %u \u8655\u4e0d\u4e00\u81f4\n+  the drive's size could not be read: \u7121\u6cd5\u8b80\u53d6\u78c1\u789f\u6a5f\u7684\u5bb9\u91cf\n+  the game (%s): \u904a\u6232\u672c\u9ad4\uff08%s\uff09\n+  the game's folder and everything in it (%s): \u904a\u6232\u8cc7\u6599\u593e\u53ca\u5176\u4e2d\u7684\u4e00\u5207\uff08%s\uff09\n+  the header at sector %u could not be read: \u7121\u6cd5\u8b80\u53d6\u78c1\u5340 %u \u7684\u6a19\u982d\n+  the header at sector %u fails its checksum: \u78c1\u5340 %u \u7684\u6a19\u982d\u6aa2\u67e5\u78bc\u4e0d\u901a\u904e\n+  the partition at sector %u is not where or as large as APA puts one: \u78c1\u5340 %u \u7684\u5206\u5272\u5340\u4e0d\u5728 APA \u653e\u7f6e\u5206\u5272\u5340\u7684\u4f4d\u7f6e\uff0c\u6216\u5927\u5c0f\u4e0d\u7b26\n+  the settings and art another copy of it uses: \u5b83\u7684\u53e6\u4e00\u4efd\u526f\u672c\u4f7f\u7528\u7684\u8a2d\u5b9a\u548c\u5c01\u9762\n+  '%d FOLDERS': '%d \u500b\u8cc7\u6599\u593e'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': \u7121\u6cd5\u628a %s \u8907\u88fd\u5230 %s\u3002\u78c1\u789f\u6a5f\u662f\u5426\u5df2\u6eff\u6216\u6709\u5beb\u5165\u4fdd\u8b77\uff1f\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s \u4e0d\u5728 RIPPS2 \u5c0b\u627e\u7684\u4f4d\u7f6e\u3002\u8981\u5728 RIPPS2 \u5df2\u555f\u52d5\u7684\u78c1\u789f\u6a5f\u4e0a\u641c\u5c0b\u5b83\u55ce\uff1f\u627e\u5230\u7684\u4f4d\u7f6e\u6703\u5728\u4e4b\u5f8c\u6bcf\u6b21\u555f\u52d5\u6642\u8a18\u4f4f\u3002'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : Ember \u4e0d\u5728\u904a\u6232\u65c1\u908a\u7684 %s \u4e2d\uff0c\u4f46\u5b83\u5fc5\u9808\u5728\u90a3\u88e1\u3002RIPPS2 \u5728 %s \u627e\u5230\u4e86\u5b83\uff1aSTART \u6703\u628a\u5b83\u8907\u88fd\u904e\u53bb\uff08%d KB\uff09\u4e26\u555f\u52d5\u904a\u6232\u3002\n+  Ember's BIOS: Ember \u7684 BIOS\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : Ember \u7684 BIOS\uff08bios.bin\uff09\u4e0d\u5728 Ember \u65c1\u908a\u7684 %s \u4e2d\u3002RIPPS2 \u5728 %s \u627e\u5230\u4e86\u4e00\u500b\uff1aSTART \u6703\u628a\u5b83\u8907\u88fd\u904e\u53bb\uff08%d KB\uff09\u4e26\u555f\u52d5\u904a\u6232\u3002\n+  LOOKING FOR YOUR LAUNCHERS: \u6b63\u5728\u5c0b\u627e\u4f60\u7684\u555f\u52d5\u5668\n+  The POPS files: POPS \u6a94\u6848\n+  '%s  MMCE card': '%s  MMCE \u5361'\n+  INSTALL RIPPS2'S NEUTRINO: \u5b89\u88dd RIPPS2 \u7684 NEUTRINO\n+  'Internal HDD (APA): %s free of %s': \u5167\u5efa\u786c\u789f\uff08APA\uff09\uff1a\u53ef\u7528 %s / \u5171 %s\n+  LAUNCHERS: \u555f\u52d5\u5668\n+  LOOK FOR THEM: \u5c0b\u627e\u5b83\u5011\n+  'Memory card %d: %d KB free': \u8a18\u61b6\u5361 %d\uff1a\u53ef\u7528 %d KB\n+  POPS files: POPS \u6a94\u6848\n+  START EVERY DRIVE: \u555f\u52d5\u6240\u6709\u78c1\u789f\u6a5f\n+  'STATUS: looking at the drives and the homebrew': \u72c0\u614b\uff1a\u6b63\u5728\u67e5\u770b\u78c1\u789f\u6a5f\u548c\u81ea\u88fd\u7a0b\u5f0f\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): \u5148\u555f\u52d5\u6240\u6709\u78c1\u789f\u6a5f\uff0c\u8b93\u641c\u5c0b\u6db5\u84cb\u5168\u90e8\u55ce\uff1f\uff08\u9078\u300c\u5426\u300d\u5247\u53ea\u641c\u5c0b\u5df2\u555f\u52d5\u7684\u78c1\u789f\u6a5f\u3002\uff09\n+  USB, HDD, MMCE and the rest: USB\u3001\u786c\u789f\u3001MMCE \u7b49\n+  every started drive: \u6bcf\u500b\u5df2\u555f\u52d5\u7684\u78c1\u789f\u6a5f\n+  found where you keep them: \u5728\u4f60\u5b58\u653e\u7684\u4f4d\u7f6e\u627e\u5230\n+  not found yet: \u5c1a\u672a\u627e\u5230\n+  not on a started drive: \u4e0d\u5728\u5df2\u555f\u52d5\u7684\u78c1\u789f\u6a5f\u4e0a\n+  only if you have none: \u50c5\u5728\u4f60\u6c92\u6709\u6642\n+  starts every drive first: \u5148\u555f\u52d5\u6240\u6709\u78c1\u789f\u6a5f\n+  Drives, launchers and homebrew: \u78c1\u789f\u6a5f\u3001\u555f\u52d5\u5668\u548c\u81ea\u88fd\u7a0b\u5f0f\n+  BDM Devices: BDM \u88dd\u7f6e\n+  Color Sweep: \u984f\u8272\u6f38\u8b8a\n+  Colors and Text: \u984f\u8272\u8207\u6587\u5b57\n+  HDD POPS partitions: \u786c\u789f\u7684 POPS \u5206\u5272\u5340\n+  Layout: \u7248\u9762\n+  Missing Covers: \u7f3a\u5c11\u7684\u5c01\u9762\n+  Name: \u540d\u7a31\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: \u5c1a\u672a\u627e\u5230 POPSTARTER \u7684 SMB \u8a2d\u5b9a\u3002\u8acb\u586b\u5beb\u4e0b\u65b9\u5404\u5217\u3002\n+  POPSTARTER Network: POPSTARTER \u7db2\u8def\n+  POPSTARTER SMB Network: POPSTARTER SMB \u7db2\u8def\n+  POPSTARTER UDPFS Network: POPSTARTER UDPFS \u7db2\u8def\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: \u78c1\u789f\u6a5f\u3001ISO\u3001VMC \u8207\u5f71\u50cf\u6a21\u5f0f\u7531 RIPPS2 \u81ea\u884c\u52a0\u4e0a\u3002\n+  Screen: \u756b\u9762\n+  Speed: \u901f\u5ea6\n+  Sweep Colors: \u6f38\u8b8a\u984f\u8272\n+  Text Weight: \u6587\u5b57\u7c97\u7d30\n+  Theme Color: \u4e3b\u8272\n+  Audio: \u97f3\u8a0a\n+  Bold: \u7c97\u9ad4\n+  Controllers: \u63a7\u5236\u5668\n+  Lively: \u6d3b\u6f51\n+  Medium: \u4e2d\u7b49\n+  PS Emulation: PS \u6a21\u64ec\n+  Regular: \u4e00\u822c\n+  Slow: \u7de9\u6162\n+  A MOMENT: \u7a0d\u7b49\n+  A TASK IN THE BACKGROUND: \u80cc\u666f\u5de5\u4f5c\n+  ABOUT %d S: \u7d04 %d \u79d2\n+  ANY MOMENT: \u99ac\u4e0a\n+  READING THE GAME LIST OF %s: \u6b63\u5728\u8b80\u53d6 %s \u7684\u904a\u6232\u6e05\u55ae\n+  SAVING A LIST: \u6b63\u5728\u5132\u5b58\u6e05\u55ae\n+  START NOW: \u7acb\u5373\u958b\u59cb\n+  STARTING: \u6b63\u5728\u958b\u59cb\n+  STARTS NEXT: \u63a5\u8457\u958b\u59cb\n+  STOPPING IT SAFELY: \u6b63\u5728\u5b89\u5168\u505c\u6b62\n+  THE LAST OF THE QUEUE: \u4f47\u5217\u672b\u5c3e\n+  WHEN IT FINISHES: \u5b8c\u6210\u4e4b\u5f8c\n+  What each card starts; install or repair BIBLE.: \u6bcf\u5f35\u5361\u555f\u52d5\u4ec0\u9ebc\uff1b\u5b89\u88dd\u6216\u4fee\u5fa9 BIBLE\u3002\n+  Rainbow: \u5f69\u8679\n+  Custom: \u81ea\u8a02\n+  DESR DVR ISOs (experimental): DESR DVR ISO\uff08\u5be6\u9a57\u6027\uff09\n+  PC CARD CF (experimental): PC CARD CF\uff08\u5be6\u9a57\u6027\uff09\n+  HDD Start Mode: \u786c\u789f \u555f\u52d5\u6a21\u5f0f\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): The Roboto Project Authors \u7684 Roboto Condensed\uff08SIL OFL 1.1\uff09\n+  '%s on %s': '%s\uff0c\u4f4d\u65bc %s'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s\uff1a%s \u8b8a\u70ba %s (%d%%)\uff0c\u6bcf\u4e00\u584a\u7686\u5df2\u6aa2\u67e5\u3002\u522a\u9664 ISO \u4e26\u4fdd\u7559 ZSO\uff1f'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s\uff1a%s \u53c8\u8b8a\u56de %s\uff0c\u6bcf\u4e00\u584a\u7686\u5df2\u6aa2\u67e5\u3002\u522a\u9664 ZSO \u4e26\u4fdd\u7559 ISO\uff1f'\n+  A ZSO of this game is already beside it. Remove one of the two first.: \u65c1\u908a\u5df2\u6709\u6b64\u904a\u6232\u7684 ZSO\u3002\u8acb\u5148\u522a\u9664\u5176\u4e2d\u4e00\u500b\u3002\n+  About %d min, then checked: \u7d04 %d \u5206\u9418\uff0c\u7136\u5f8c\u6aa2\u67e5\n+  An ISO of this game is already beside it. Remove one of the two first.: \u65c1\u908a\u5df2\u6709\u6b64\u904a\u6232\u7684 ISO\u3002\u8acb\u5148\u522a\u9664\u5176\u4e2d\u4e00\u500b\u3002\n+  CHECKING EVERY PIECE: \u6b63\u5728\u6aa2\u67e5\u6bcf\u4e00\u584a\n+  COMPRESS TO ZSO: \u58d3\u7e2e\u70ba ZSO\n+  'COMPRESSED %s: %s FREED': \u5df2\u58d3\u7e2e %s\uff1a\u91cb\u653e %s\n+  COMPRESSING 1 OF 2: \u58d3\u7e2e\u4e2d 1/2\n+  COMPRESSING 2 OF 2: \u58d3\u7e2e\u4e2d 2/2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : \u58d3\u7e2e %s\uff1f\u5728 ZSO \u8b80\u56de\u4e26\u6aa2\u67e5\u4e4b\u524d ISO \u6703\u4fdd\u7559\uff0c\u4e4b\u5f8c RIPPS2 \u6703\u5728\u522a\u9664 ISO \u524d\u8a62\u554f\u3002\u4f5c\u696d\u671f\u9593\u78c1\u789f\u9700\u8981\u540c\u6642\u5bb9\u7d0d\u5169\u8005\u7684\u7a7a\u9593\uff1b\u5b8c\u6210\u524d\u8acb\u52ff\u95dc\u9589\u4e3b\u6a5f\u3002ZSO \u53ea\u80fd\u7528 OPL \u6838\u5fc3\u555f\u52d5\uff08\u4e0d\u652f\u63f4 Neutrino\uff09\uff1b\u5b83\u7684\u5b58\u6a94\u3001\u8a2d\u5b9a\u3001\u5716\u7247\u8207\u91d1\u624b\u6307\u90fd\u6703\u8ddf\u8457\u5b83\uff0c\u4e26\u53ef\u96a8\u6642\u8f49\u56de ISO\u3002\n+  Compress to ZSO: \u58d3\u7e2e\u70ba ZSO\n+  Cycle: \u8f2a\u66ff\n+  'Experimental: OPL core only (not Neutrino)': \u5be6\u9a57\u6027\uff1a\u50c5 OPL \u6838\u5fc3\uff08\u4e0d\u652f\u63f4 Neutrino\uff09\n+  Hold SELECT: \u6309\u4f4f SELECT\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: \u7121\u6cd5\u5b8c\u6210\uff08%s\uff09\u3002\u5df2\u5beb\u5165\u7684\u5167\u5bb9\u5df2\u522a\u9664\uff1b\u904a\u6232\u7dad\u6301\u539f\u6a23\u3002\n+  List: \u6e05\u55ae\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : \u5c07 %s \u8f49\u56de ISO\uff1f\u5728 ISO \u8b80\u56de\u4e26\u6aa2\u67e5\u4e4b\u524d ZSO \u6703\u4fdd\u7559\uff0c\u4e4b\u5f8c RIPPS2 \u6703\u5728\u522a\u9664 ZSO \u524d\u8a62\u554f\u3002\u4f5c\u696d\u671f\u9593\u78c1\u789f\u9700\u8981\u540c\u6642\u5bb9\u7d0d\u5169\u8005\u7684\u7a7a\u9593\uff1b\u5b8c\u6210\u524d\u8acb\u52ff\u95dc\u9589\u4e3b\u6a5f\u3002\u5b83\u7684\u5b58\u6a94\u3001\u8a2d\u5b9a\u3001\u5716\u7247\u8207\u91d1\u624b\u6307\u90fd\u6703\u8ddf\u8457\u5b83\u3002\n+  Stop? What it has written so far is removed; the game stays as it was.: \u505c\u6b62\uff1f\u76ee\u524d\u5df2\u5beb\u5165\u7684\u5167\u5bb9\u6703\u88ab\u522a\u9664\uff1b\u904a\u6232\u7dad\u6301\u539f\u6a23\u3002\n+  THEMES: \u4e3b\u984c\n+  There is not enough memory for this just now.: \u76ee\u524d\u8a18\u61b6\u9ad4\u4e0d\u8db3\uff0c\u7121\u6cd5\u57f7\u884c\u6b64\u64cd\u4f5c\u3002\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: RIPPS2 \u7121\u6cd5\u58d3\u7e2e\u6216\u89e3\u58d3\u6b64\u6620\u50cf\u6a94\uff08\u5176\u5927\u5c0f\u4e0d\u662f\u5b8c\u6574\u7684 2 KB \u5340\u584a\uff09\u3002\n+  UNCOMPRESS TO ISO: \u89e3\u58d3\u70ba ISO\n+  UNCOMPRESSED %s: \u5df2\u89e3\u58d3 %s\n+  UNCOMPRESSING: \u89e3\u58d3\u4e2d\n+  Uncompress to ISO: \u89e3\u58d3\u70ba ISO\n+  Under a minute, then checked: \u4e0d\u5230\u4e00\u5206\u9418\uff0c\u7136\u5f8c\u6aa2\u67e5\n+  COPYING: \u6b63\u5728\u8907\u88fd\n+  SHOW AGAIN, DELETE OR COMPRESS: \u91cd\u65b0\u986f\u793a\u3001\u522a\u9664\u6216\u58d3\u7e2e\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: \u91cd\u65b0\u986f\u793a\u3001\u522a\u9664\u6216\u89e3\u58d3\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': '%s \u8655\u7406\u671f\u9593\u9700\u8981\u5176\u6240\u5728\u78c1\u789f\u7d04 %s \u7684\u53ef\u7528\u7a7a\u9593\uff08\u539f\u6a94\u6848\u6703\u4fdd\u7559\u5230\u6700\u5f8c\uff09\uff0c\u800c\u78c1\u789f\u53ea\u6709 %s \u53ef\u7528\u3002'\n+  Hide and Compress: \u96b1\u85cf\u4e26\u58d3\u7e2e\n+  'COMPRESSED AND HIDDEN: %s': \u5df2\u58d3\u7e2e\u4e26\u96b1\u85cf\uff1a%s\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': \u6c92\u6709\u8db3\u5920\u7a7a\u9593\u9032\u884c\u6b64\u6b21\u64f7\u53d6\uff1a\u5927\u7d04\u9700\u8981 %s\uff0c\u78c1\u789f\u5269\u9918 %s\u3002\n+  OVER 4 GB FOR FAT32: FAT32 \u8d85\u904e 4 GB\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: \u6b64\u78c1\u789f\u70ba %s\uff0c\u7121\u6cd5\u5132\u5b58 4 GB \u6216\u66f4\u5927\u7684\u6a94\u6848\uff0c\u800c\u65b0\u6620\u50cf\u6a94\u5c07\u70ba %s\u3002\n+  '%s, %s clusters, %s free of %s': '%s\uff0c\u53e2\u96c6 %s\uff0c\u53ef\u7528 %s / %s'\n+  Delete This Sweep: \u522a\u9664\u6b64\u6f38\u8b8a\n+  Delete %s? RIPPS2's own sweeps stay as they are.: \u522a\u9664 %s\uff1fRIPPS2 \u5167\u5efa\u7684\u6f38\u8b8a\u4fdd\u6301\u4e0d\u8b8a\u3002\n+  Sweep: \u6f38\u8b8a\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: \u5fbd\u7ae0\u6703\u5728\u6e05\u55ae\u4e2d\u6a19\u51fa\u6709\u6210\u5c31\u7684\u904a\u6232\uff0c\u4ee5\u53ca\u4f60\u5df2\u89e3\u9396\u7684\u6578\u91cf\u3002\u9700\u8981\u958b\u555f\u9059\u6e2c\u3002\n labels:\n   BDMMODE: BDM \u88dd\u7f6e\u555f\u52d5\u6a21\u5f0f\n   RESETCOLOR: \u91cd\u8a2d\u984f\u8272\n@@ -1211,7 +1378,7 @@ labels:\n   SAVE_ICONS: 3D \u5b58\u6a94\u5716\u793a\n   HINT_SAVE_ICONS: \u5728\u4e3b\u984c\u7559\u6709\u4f4d\u7f6e\u7684\u5730\u65b9\u986f\u793a\u6240\u9078\u904a\u6232\u7684\u65cb\u8f49\u5b58\u6a94\u5716\u793a\u3002\u5148\u8b80\u53d6\u904a\u6232\u81ea\u5df1\u7684 VMC\uff0c\u518d\u8b80\u53d6\u8a18\u61b6\u5361\u3002\n   NETBOOT_RESTART_NOW: \u7db2\u8def\u5354\u5b9a\u6703\u5728\u91cd\u65b0\u555f\u52d5\u5f8c\u8b8a\u66f4\u3002\u73fe\u5728\u5132\u5b58\u8a2d\u5b9a\u4e26\u91cd\u65b0\u555f\u52d5 OPL \u55ce\uff1f\n-  HINT_NET_PROTOCOL: RiptOPL \u4f7f\u7528\u54ea\u7a2e\u7db2\u8def\u670d\u52d9\uff1aSMB \u5171\u7528\u3001UDPFS\u3001UDPBD \u6216 HTTP\u3002\u8207\u300c\u904a\u6232\u4f86\u6e90\u300d\u4e2d\u7684\u8a2d\u5b9a\u76f8\u540c\u3002\u5f9e\u5df2\u5728\u57f7\u884c\u7684\u670d\u52d9\u5207\u63db\u8d70\u9700\u8981\u91cd\u65b0\u555f\u52d5\u3002\n+  HINT_NET_PROTOCOL: RiptOPL \u4f7f\u7528\u54ea\u7a2e\u7db2\u8def\u670d\u52d9\uff1aSMB \u5171\u7528\u3001UDPFS\u3001UDPBD \u6216 HTTP\u3002\u5f9e\u5df2\u5728\u57f7\u884c\u7684\u670d\u52d9\u5207\u63db\u8d70\u9700\u8981\u91cd\u65b0\u555f\u52d5\u3002\n   HINT_UDPFS_ACCESS: \u50c5 UDPFS\u3002\u300c\u6a94\u6848\u300d\u5f9e\u4f3a\u670d\u5668\u7684\u904a\u6232\u8cc7\u6599\u593e\u8b80\u53d6\u500b\u5225\u7684 ISO\uff1bIMG \u628a\u78c1\u789f\u6620\u50cf\u7576\u4f5c\u78c1\u789f\u6a5f\u8b80\u53d6\u3002SMB \u8207 HTTP \u4e00\u5f8b\u4f7f\u7528\u300c\u6a94\u6848\u300d\uff0cUDPBD \u4e00\u5f8b\u4f7f\u7528 IMG\u3002\n   HINT_SMB_VERSION: \u8207\u96fb\u8166\u5171\u7528\u8cc7\u6599\u593e\u901a\u8a0a\u6642\u4f7f\u7528\u7684 SMB \u7248\u672c\u3002\n   HINT_NET_ADVANCED: \u89e3\u9396\u4e59\u592a\u7db2\u8def\u9023\u7dda\u6a21\u5f0f\u8207 SMB \u9023\u63a5\u57e0\u7684\u7de8\u8f2f\u3002\n@@ -1410,3 +1577,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: \u5404\u904a\u6232\u5e95\u7247\u4e0a\u7b49\u5f85\u4e2d\u7684\u7167\u7247\u5982\u4f55\u8b8a\u6210 PNG\u3002\n   RIPPS2_SETTINGS_HOME: \u8a2d\u5b9a\u5132\u5b58\u81f3\n   HINT_RIPPS2_SETTINGS_HOME: RIPPS2 \u5b58\u653e\u8a2d\u5b9a\u7684\u4f4d\u7f6e\uff1a\u8a18\u61b6\u5361\u4e0a\u5b83\u81ea\u5df1\u7684\u5b58\u6a94\u3001\u78c1\u789f\u6a5f\uff08\u5167\u5efa\u786c\u789f\u3001USB\u3001MX4SIO\u3001iLink \u6216 MMCE\uff09\u4e0a\u7684 RIPPS2 \u8cc7\u6599\u593e\uff0c\u6216\u4f60\u8f38\u5165\u7684\u8cc7\u6599\u593e\u3002\n+  RIPPS2_LAUNCHERS: \u555f\u52d5\u5668\n+  RIPPS2_LOOK_NOW: \u7acb\u5373\u5c0b\u627e\n+  HINT_RIPPS2_LOOK_NOW: RIPPS2 \u6703\u5728\u5df2\u555f\u52d5\u7684\u78c1\u789f\u6a5f\u4e0a\u5c0b\u627e POPSTARTER\u3001POPS \u6a94\u6848\u3001Ember\u3001Ember \u7684 BIOS \u548c Neutrino\uff0c\u4e26\u8a18\u4f4f\u6bcf\u4e00\u500b\u7684\u4f4d\u7f6e\u3002\u6bcf\u6b21\u555f\u52d5\u90fd\u5f9e\u90a3\u88e1\u8b80\u53d6\u3002\n+  RIPPS2_HIDE_GAME: \u96b1\u85cf\u904a\u6232\n+  RIPPS2_HIDDEN_GAMES: \u96b1\u85cf\u7684\u904a\u6232\n+  HINT_RIPPS2_HIDDEN_GAMES: \u7528\u300c\u96b1\u85cf\u904a\u6232\u300d\u96b1\u85cf\u7684\u904a\u6232\uff0c\u5728\u5df2\u555f\u52d5\u7684\u78c1\u789f\u6a5f\u4e0a\u3002\u53ef\u4ee5\u91cd\u65b0\u986f\u793a\u5176\u4e2d\u4e00\u500b\uff0c\u6216\u522a\u9664\u5b83\u3002\u6bcf\u500b\u78c1\u789f\u6a5f\u5404\u6709\u81ea\u5df1\u7684\u6e05\u55ae\u3002\n+  HINT_RIPPS2_BDM_DEVICES: BDM \u8b80\u53d6\u904a\u6232\u7684\u78c1\u789f\u6a5f\uff1aUSB\u3001MX4SIO\u3001PC Card CF\u3001DESR \u7684 DVR \u78c1\u789f\u6a5f\u8207 iLink\u3002\u5167\u5efa\u786c\u789f\u8ddf\u96a8\u786c\u789f\u555f\u52d5\u6a21\u5f0f\u3002\n+  HINT_RIPPS2_SCREEN: \u9078\u55ae\u7684\u756b\u9762\uff1a\u5f71\u50cf\u6a21\u5f0f\u3001\u5bec\u87a2\u5e55\u3001\u5728\u96fb\u8996\u4e0a\u7684\u4f4d\u7f6e\uff08H-POS\u3001V-POS\uff09\u8207\u904e\u6383\u63cf\u3002\n+  HINT_RIPPS2_LAYOUT: \u5206\u985e\u6240\u5728\u7684\u4f4d\u7f6e\u8207\u9801\u9762\u9032\u5165\u7684\u65b9\u5f0f\uff0c\u63a5\u8457\u662f RIPFLOW \u7684\u5c01\u9762\u8207 RIPGRID \u7684\u683c\u7dda\u3002\n+  HINT_RIPPS2_COLORS_TEXT: \u6587\u5b57\u7c97\u7d30\u3001\u914d\u8272\u3001\u984f\u8272\u6f38\u8b8a\u8207\u6bcf\u7a2e\u984f\u8272\u3002\n+  HINT_RIPPS2_TEXT_WEIGHT: RIPPS2 \u81ea\u5df1\u6587\u5b57\u7684\u7c97\u7d30\uff1a\u8a2d\u5b9a\u7684\u63d0\u793a\u3001\u72c0\u614b\u8207\u95dc\u65bc\u3002\u4e00\u822c\u548c\u4ee5\u524d\u4e00\u6a23\uff1b\u4e2d\u7b49\u8207\u7c97\u9ad4\u5728\u67d4\u548c\u7684\u756b\u9762\u4e0a\u66f4\u597d\u8b80\u3002\n+  HINT_RIPPS2_HDD_START: \u5167\u5efa\u786c\u789f\uff0c\u7121\u8ad6 APA \u6216 exFAT\uff0cRIPPS2 \u90fd\u6703\u8b80\u53d6\u4e26\u53ea\u986f\u793a\u4e00\u6b21\uff0c\u4f5c\u70ba\u786c\u789f\u904a\u6232\u3002\u95dc\u9589\u5247\u4e0d\u78b0\u5b83\u3002\n+  HINT_RIPPS2_THEME_COLOR: \u914d\u8272\u672c\u8eab\u7684\u984f\u8272\u3002\u628a\u914d\u8272\u9078\u70ba\u81ea\u8a02\uff0c\u5c31\u80fd\u50cf\u6587\u5b57\u984f\u8272\u4e00\u6a23\u81ea\u5df1\u6311\u9078\u3002\n+  HINT_RIPPS2_SWEEP: \u4e3b\u984c\u7de9\u6162\u5e73\u9806\u5730\u7d93\u904e\u7684\u4e94\u7a2e\u984f\u8272\u3002\u5f69\u8679\u3001\u7531\u6bcf\u7a2e\u914d\u8272\u505a\u6210\u7684\u6f38\u8b8a\uff0c\u6216\u4f60\u81ea\u5df1\u7684\u3002\u95dc\u9589\u8b93\u914d\u8272\u4fdd\u6301\u4e0d\u52d5\u3002\n+  HINT_RIPPS2_SWEEP_COLORS: \u6f38\u8b8a\u7684\u4e94\u7a2e\u984f\u8272\u8207\u901f\u5ea6\u3002\u6539\u52d5 RIPPS2 \u7684\u67d0\u500b\u6f38\u8b8a\uff0c\u5b83\u6703\u4ee5\u4f60\u53d6\u7684\u540d\u5b57\u4f5c\u70ba\u4f60\u81ea\u5df1\u7684\u4fdd\u5b58\u4e0b\u4f86\u3002\n+  HINT_RIPPS2_SWEEP_NAME: \u4f60\u7684\u6f38\u8b8a\u5728\u984f\u8272\u6f38\u8b8a\u6e05\u55ae\u4e2d\u7684\u540d\u5b57\u3002\n+  HINT_RIPPS2_SWEEP_COLOR: \u6f38\u8b8a\u4f9d\u5e8f\u7d93\u904e\u7684\u4e94\u7a2e\u984f\u8272\u4e4b\u4e00\u3002\n+  HINT_RIPPS2_SWEEP_SPEED: \u6bcf\u7a2e\u984f\u8272\u505c\u7559\u591a\u4e45\u5f8c\u4e0b\u4e00\u7a2e\u6de1\u5165\u3002\u5373\u4f7f\u6d3b\u6f51\u4e5f\u5f88\u5e73\u9806\uff1a\u984f\u8272\u5f9e\u4e0d\u9583\u720d\u3002\n+  RIPPS2_HIDE_COMPRESS: \u96b1\u85cf\u904a\u6232\u8207\u58d3\u7e2e\n+  HINT_RIPPS2_SELECT_HOLD: \u5728\u904a\u6232\u6e05\u55ae\u4e0a\u6309\u4f4f SELECT 4.2 \u79d2\u3002\u6e05\u55ae\uff1a\u5728\u9762\u677f\u4e2d\u5217\u51fa\u4e3b\u984c\uff0c\u9078\u4e00\u500b\u3002\u8f2a\u66ff\uff1a\u4e0b\u4e00\u500b\u4e3b\u984c\u3002\u95dc\u9589\uff1a\u4e0d\u505a\u4efb\u4f55\u4e8b\uff08\u6309\u4e00\u4e0b\u4ecd\u6703\u91cd\u65b0\u6574\u7406\uff09\u3002"
        },
        {
          "sha": "e1b8b04f3e71fd1d4680cdee2d75e9769032be67",
          "filename": "elf/lang/Turkish.yml",
          "status": "modified",
          "additions": 209,
          "deletions": 22,
          "changes": 231,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FTurkish.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FTurkish.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FTurkish.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9",
          "patch": "@@ -5,10 +5,6 @@ language: Turkish\n strings:\n   ENTER ACCEPTS, ESC CANCELS. ANY BUTTON SHOWS THE KEYS.: ENTER ONAYLAR, ESC \u0130PTAL EDER. HERHANG\u0130 B\u0130R TU\u015e TU\u015eLARI G\u00d6STER\u0130R.\n   USB KEYBOARD: USB KLAVYE\n-  '      DESR DVR ISOs (experimental)': '      DESR DVR ISO''lar\u0131 (deneysel)'\n-  '      Internal HDD (exFAT)': '      Dahili HDD (exFAT)'\n-  '      PC CARD CF (experimental)': '      PC CARD CF (deneysel)'\n-  '      Protocol': '      Protokol'\n   Access: Eri\u015fim\n   Applications Page Start Mode: Uygulamalar Sayfas\u0131 Ba\u015flatma Modu\n   Art Delay: Resim Gecikmesi\n@@ -22,7 +18,6 @@ strings:\n   Fifth: Be\u015finci\n   First: Birinci\n   Fourth: D\u00f6rd\u00fcnc\u00fc\n-  HDD (APA) Start Mode: HDD (APA) Ba\u015flatma Modu\n   HDD Cache: HDD \u00d6nbelle\u011fi\n   HDD Spindown: HDD Bekleme\n   MMCE Settings: MMCE Ayarlar\u0131\n@@ -91,19 +86,19 @@ strings:\n   This is an ESR-patched PS2 game. Put ESR (esr*.elf) in APPS, BOOT or ESR on a memory card or USB drive to start it.: Bu, ESR yamal\u0131 bir PS2 oyunu. Ba\u015flatmak i\u00e7in ESR'yi (esr*.elf) bir haf\u0131za kart\u0131nda veya USB bellekte APPS, BOOT ya da ESR klas\u00f6r\u00fcne koyun.\n   '%s, BUILD %d': '%s, DERLEME %d'\n   '... and the anonymous ...': '... ve ad\u0131 bilinmeyenler ...'\n-  '1.8.0 carried unmodified (AFL-3.0): github.com/rickgaiser/neutrino': '1.8.0 de\u011fi\u015ftirilmeden ta\u015f\u0131n\u0131yor (AFL-3.0): github.com/rickgaiser/neutrino'\n   A teardown and rebuild of RiptOPL, the Open PS2 Loader fork,: Open PS2 Loader \u00e7atal\u0131 RiptOPL'nin s\u00f6k\u00fcp yeniden kurulmu\u015f h\u00e2li,\n   ABOUT: HAKKINDA\n   'ACHIEVEMENTS: SPIRIT OF RA': 'BA\u015eARIMLAR: SPIRIT OF RA'\n   AVI video in Memory Files, RIPPS2's fork (AFL 2.0): Haf\u0131za Dosyalar\u0131'nda AVI video, RIPPS2'nin \u00e7atal\u0131 (AFL 2.0)\n   Apollo Save Tool by bucanero: bucanero'dan Apollo Save Tool\n   BACK: GER\u0130\n+  Back (keeps both): Geri (ikisi de kal\u0131r)\n+  This MMCE card cannot rename a file, which compression needs for its last step. Update its sd2psXtd firmware to 1.4.0 or newer (sd2psxtd.github.io/download), then try again. Nothing was changed.: Bu MMCE kart\u0131 bir dosyay\u0131 yeniden adland\u0131ram\u0131yor; s\u0131k\u0131\u015ft\u0131rman\u0131n son ad\u0131m\u0131 buna ihtiya\u00e7 duyar. sd2psXtd yaz\u0131l\u0131m\u0131n\u0131 1.4.0 veya daha yenisine g\u00fcncelleyin (sd2psxtd.github.io/download), sonra yeniden deneyin. Hi\u00e7bir \u015fey de\u011fi\u015ftirilmedi.\n   BIBLE is PS2BBL 1.2.0, El Isra's signed builds (card and HDD), unchanged (GPL-3): BIBLE, El Isra'n\u0131n imzal\u0131 derlemeleri (kart ve HDD) olan PS2BBL 1.2.0'd\u0131r, de\u011fi\u015ftirilmemi\u015ftir (GPL-3)\n   BIOS Sounds > Rip plays them as the SPU2 does; no BIOS ships: BIOS Sesleri > \u00c7\u0131kar onlar\u0131 SPU2'nin \u00e7ald\u0131\u011f\u0131 gibi \u00e7alar; BIOS dahil de\u011fildir\n   BOOT LOCK AND BIBLE: BOOT LOCK VE BIBLE\n   BOOT LOCK edits its config too: BOOT LOCK onun yap\u0131land\u0131rmas\u0131n\u0131 da d\u00fczenler\n   BUILD DETAILS: DERLEME AYRINTILARI\n-  BUILT ON RIPTOPL: RIPTOPL \u00dcZER\u0130NE KURULU\n   BearSSL by Thomas Pornin: Thomas Pornin'den BearSSL\n   Built with Claude (Anthropic): Claude (Anthropic) ile yap\u0131ld\u0131\n   CD PLAYER: CD \u00c7ALAR\n@@ -136,7 +131,6 @@ strings:\n   RIPPS2 Sleek and Sleek Bold, drawn for RIPPS2: RIPPS2 Sleek ve Sleek Bold, RIPPS2 i\u00e7in \u00e7izildi\n   RetroAchievements on the console by hacan359 (yoba): hacan359'dan (yoba) konsolda RetroAchievements\n   RiptOPL by NathanNeurotic: NathanNeurotic'ten RiptOPL\n-  Roboto Condensed by Google (Apache-2.0): Google'dan Roboto Condensed (Apache-2.0)\n   SOUNDS: SESLER\n   SP193's FMCB installer and pcm720's OSDMenu, followed step by step: SP193'\u00fcn FMCB y\u00fckleyicisi ve pcm720'nin OSDMenu'su, ad\u0131m ad\u0131m izlendi\n   SUPPORT: DESTEK\n@@ -172,7 +166,6 @@ strings:\n   wLaunchELF R3Z by NathanNeurotic: NathanNeurotic'ten wLaunchELF R3Z\n   wLaunchELF by the ps2homebrew team: ps2homebrew ekibinden wLaunchELF\n   what RIPPS2's card image writes are checked against: RIPPS2'nin kart g\u00f6r\u00fcnt\u00fcs\u00fc yaz\u0131mlar\u0131n\u0131n kar\u015f\u0131la\u015ft\u0131r\u0131ld\u0131\u011f\u0131 ara\u00e7\n-  with its own look, launchers, file browser and save tools.: kendi g\u00f6r\u00fcn\u00fcm\u00fc, ba\u015flat\u0131c\u0131lar\u0131, dosya taray\u0131c\u0131s\u0131 ve kay\u0131t ara\u00e7lar\u0131yla.\n   xeRAbora by hacan359, rcheevos by RetroAchievements: hacan359'dan xeRAbora, RetroAchievements'tan rcheevos\n   your console's own, ripped from its BIOS: konsolunuzun kendi sesleri, BIOS'undan \u00e7\u0131kar\u0131ld\u0131\n   ' (no DNS answer)': ' (DNS yan\u0131t\u0131 yok)'\n@@ -558,8 +551,6 @@ strings:\n   'NEUTRINO: writing %.60s': 'NEUTRINO: yaz\u0131l\u0131yor %.60s'\n   Neutrino could not be written to %s. Check its free space and that it is not write protected, then try again.: Neutrino %s konumuna yaz\u0131lamad\u0131. Bo\u015f alan\u0131n\u0131 ve yazmaya kar\u015f\u0131 korumal\u0131 olmad\u0131\u011f\u0131n\u0131 kontrol edip yeniden deneyin.\n   THE GAME'S DRIVE  (%s/neutrino): OYUNUN S\u00dcR\u00dcC\u00dcS\u00dc  (%s/neutrino)\n-  ? This game starts through Neutrino, and there is no Neutrino where RIPPS2 looks (Settings, the game's drive, the memory cards). RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0). Install it now? Next you pick where.\n-  : Bu oyun Neutrino ile ba\u015flar ve RIPPS2'nin bakt\u0131\u011f\u0131 yerlerde (Ayarlar, oyunun s\u00fcr\u00fcc\u00fcs\u00fc, haf\u0131za kartlar\u0131) Neutrino yok. RIPPS2, rickgaiser'\u0131n Neutrino 1.8.0'\u0131n\u0131 (AFL-3.0) ta\u015f\u0131r. \u015eimdi kurulsun mu? Ard\u0131ndan yerini se\u00e7ersiniz.\n   '%d / %d FILES': '%d / %d DOSYA'\n   '%d FOLDERS AND CONTENTS': '%d KLAS\u00d6R VE \u0130\u00c7ER\u0130KLER\u0130'\n   '%d FOLDERS, %d FOUND': '%d KLAS\u00d6R, %d BULUNDU'\n@@ -780,7 +771,6 @@ strings:\n   '%s   %d of %d still locked': '%s   %d / %d h\u00e2l\u00e2 kilitli'\n   '%s   on %s': '%s   %s \u00fczerinde'\n   BADGES: ROZETLER\n-  Badges only engage with TELEMETRY on and xeRAbora running on your PC; until then they cost nothing.: Rozetler yaln\u0131zca TELEMETR\u0130 a\u00e7\u0131k ve PC'nizde xeRAbora \u00e7al\u0131\u015f\u0131rken devreye girer; o zamana kadar hi\u00e7bir y\u00fck getirmezler.\n   CHECK THE DISC: D\u0130SK\u0130 KONTROL ET\n   From this game's last CHECK GAME SUPPORT.: Bu oyunun son OYUN DESTE\u011e\u0130N\u0130 KONTROL ET i\u015fleminden.\n   HOW IT WORKS: NASIL \u00c7ALI\u015eIR\n@@ -847,10 +837,7 @@ strings:\n   A PS2BBL with RIPPS2's lock is on this console.: Bu konsolda RIPPS2 kilidi olan bir PS2BBL var.\n   AUDIO folder: AUDIO klas\u00f6r\u00fc\n   DRIVES: S\u00dcR\u00dcC\u00dcLER\n-  INSTALL RIPPS2'S COPY: RIPPS2'N\u0130N KOPYASINI KUR\n-  'Installed: ': 'Kurulu: '\n   MANAGE: Y\u00d6NET\n-  MANAGE shows what each memory card starts, and installs or repairs BIBLE.: Y\u00d6NET her haf\u0131za kart\u0131n\u0131n neyi ba\u015flatt\u0131\u011f\u0131n\u0131 g\u00f6sterir ve BIBLE'\u0131 kurar ya da onar\u0131r.\n   MEMORY CARD 0  (about 600 KB): HAFIZA KARTI 0  (yakla\u015f\u0131k 600 KB)\n   MEMORY CARD 1  (about 600 KB): HAFIZA KARTI 1  (yakla\u015f\u0131k 600 KB)\n   'MX4SIO: its driver would not load (code %d).': 'MX4SIO: s\u00fcr\u00fcc\u00fcs\u00fc y\u00fcklenmedi (kod %d).'\n@@ -860,17 +847,13 @@ strings:\n   'MX4SIO: the driver is in, but no SD card answered (the mc1 slot).': 'MX4SIO: s\u00fcr\u00fcc\u00fc y\u00fckl\u00fc, ama hi\u00e7bir SD kart yan\u0131t vermedi (mc1 yuvas\u0131).'\n   NEUTRINO SETTINGS: NEUTRINO AYARLARI\n   'None yet: RIPPS2 has no sounds of its own. Rip this console''s, or make them in RIPPS2 Audio Studio.': 'Hen\u00fcz yok: RIPPS2''nin kendine ait sesi yok. Bu konsolunkileri \u00e7\u0131kar\u0131n veya RIPPS2 Audio Studio''da yap\u0131n.'\n-  Not installed. A game that needs it offers RIPPS2's copy, or install it here.: Kurulu de\u011fil. Buna ihtiya\u00e7 duyan bir oyun RIPPS2'nin kopyas\u0131n\u0131 \u00f6nerir, ya da burada kurun.\n   POPSTARTER NETWORK: POPSTARTER A\u011eI\n   POPSTARTER and Ember: POPSTARTER ve Ember\n   PS1 SETTINGS: PS1 AYARLARI\n   PS2BBL 1.2.0 for RIPPS2: RIPPS2 i\u00e7in PS2BBL 1.2.0\n   RIP THIS CONSOLE'S SOUNDS: BU KONSOLUN SESLER\u0130N\u0130 \u00c7IKAR\n-  RIPPS2's homebrew: RIPPS2'nin ev yap\u0131m\u0131 yaz\u0131l\u0131mlar\u0131\n   STATUS: DURUM\n-  'STATUS: looking for RIPPS2''s homebrew': 'DURUM: RIPPS2''nin ev yap\u0131m\u0131 yaz\u0131l\u0131mlar\u0131 aran\u0131yor'\n   a drive of unknown kind: t\u00fcr\u00fc bilinmeyen bir s\u00fcr\u00fcc\u00fc\n-  a drive or a memory card: bir s\u00fcr\u00fcc\u00fc veya haf\u0131za kart\u0131\n   boot loader, card by card: \u00f6ny\u00fckleyici, kart kart\n   from its BIOS, into AUDIO: BIOS'undan, AUDIO i\u00e7ine\n   internal HDD (exFAT): dahili HDD (exFAT)\n@@ -975,10 +958,194 @@ strings:\n   Sidebar: Kenar \u00c7ubu\u011fu\n   your own copy, found on your drives and memory cards: kendi kopyan\u0131z, s\u00fcr\u00fcc\u00fclerinizde ve haf\u0131za kartlar\u0131n\u0131zda bulunur\n   'your own, found and launched: github.com/Gageformer/Ember/releases': 'kendi kopyan\u0131z, bulunup ba\u015flat\u0131l\u0131r: github.com/Gageformer/Ember/releases'\n-  'Not found on mass0: (%s). Put ember.elf and your bios.bin in its EMBER folder.': 'mass0: (%s) \u00fczerinde bulunamad\u0131. ember.elf ve kendi bios.bin dosyan\u0131z\u0131 onun EMBER klas\u00f6r\u00fcne koyun.'\n-  Not found. Put your own POPSTARTER.ELF in a drive's POPS folder.: Bulunamad\u0131. Kendi POPSTARTER.ELF dosyan\u0131z\u0131 bir s\u00fcr\u00fcc\u00fcn\u00fcn POPS klas\u00f6r\u00fcne koyun.\n   Launch Disc: Disk ba\u015flat\n   Memory Files: Haf\u0131za dosyalar\u0131\n+  RIPPS2 found %d of the 5. Each one is remembered, and every launch reads it from there.: RIPPS2 5'ten %d tanesini buldu. Her biri hat\u0131rlan\u0131r ve her ba\u015flatma onu oradan okur.\n+  not found on the started drives: ba\u015flat\u0131lan s\u00fcr\u00fcc\u00fclerde bulunamad\u0131\n+  not looked for yet: hen\u00fcz aranmad\u0131\n+  '%s did not answer, so %s cannot start. Is it connected?': '%s yan\u0131t vermedi, bu y\u00fczden %s ba\u015flayam\u0131yor. Ba\u011fl\u0131 m\u0131?'\n+  '%s is not on %s any more, so it has left All Games.': '%s art\u0131k %s \u00fczerinde de\u011fil, bu y\u00fczden T\u00fcm Oyunlar''dan \u00e7\u0131kt\u0131.'\n+  STARTING %s: '%s BA\u015eLATILIYOR'\n+  the SMB share: SMB payla\u015f\u0131m\u0131\n+  the drive: s\u00fcr\u00fcc\u00fc\n+  the internal HDD (APA): dahili HDD (APA)\n+  the internal HDD (exFAT): dahili HDD (exFAT)\n+  BUILT ON RIPTOPL, WITH NEUTRINO: RIPTOPL \u00dcZER\u0130NE KURULU, NEUTRINO \u0130LE\n+  'Special thanks to all the YouTube love as well:': 'YouTube''daki t\u00fcm sevgiye de \u00f6zel te\u015fekk\u00fcrler:'\n+  and its own look, launchers, file browser and save tools.: ve kendi g\u00f6r\u00fcn\u00fcm\u00fc, ba\u015flat\u0131c\u0131lar\u0131, dosya taray\u0131c\u0131s\u0131 ve kay\u0131t ara\u00e7lar\u0131yla.\n+  glad to help however you reach me.: bana nas\u0131l ula\u015f\u0131rsan\u0131z ula\u015f\u0131n, yard\u0131m etmekten memnunum.\n+  the second game core, 1.8.0 carried unmodified (AFL-3.0): ikinci oyun \u00e7ekirde\u011fi, 1.8.0 de\u011fi\u015ftirilmeden ta\u015f\u0131n\u0131r (AFL-3.0)\n+  with rickgaiser's Neutrino inside as its second game core,: i\u00e7inde ikinci oyun \u00e7ekirde\u011fi olarak rickgaiser'\u0131n Neutrino'suyla,\n+  ? 'This game starts through Neutrino, and RIPPS2 found none: not at Settings'' path, on the game''s drive, on the memory cards or on any started drive. RIPPS2 carries Neutrino 1.8.0 by rickgaiser (AFL-3.0): install it once, where you pick, and every Neutrino game uses it from there.'\n+  : 'Bu oyun Neutrino \u00fczerinden ba\u015flar ve RIPPS2 bir tane bulamad\u0131: ne Ayarlar''daki yolda, ne oyunun s\u00fcr\u00fcc\u00fcs\u00fcnde, ne haf\u0131za kartlar\u0131nda, ne de ba\u015flat\u0131lm\u0131\u015f bir s\u00fcr\u00fcc\u00fcde. RIPPS2, rickgaiser''\u0131n Neutrino 1.8.0''\u0131n\u0131 ta\u015f\u0131r (AFL-3.0): onu bir kez, se\u00e7ti\u011finiz yere kurun; her Neutrino oyunu onu oradan kullan\u0131r.'\n+  STARTING THE DRIVES: S\u00dcR\u00dcC\u00dcLER BA\u015eLATILIYOR\n+  '%d games, on the started drives': '%d oyun, ba\u015flat\u0131lan s\u00fcr\u00fcc\u00fclerde'\n+  '%s: SOME FILES COULD NOT BE DELETED': '%s: BAZI DOSYALAR S\u0130L\u0130NEMED\u0130'\n+  '%u partitions, all in order': '%u b\u00f6l\u00fcm, hepsi d\u00fczg\u00fcn'\n+  ', %d art images': ', %d kapak g\u00f6rseli'\n+  ', and ': ', ve '\n+  ', its cheats': ', hileleri'\n+  ', its settings': ', ayarlar\u0131'\n+  1 game, on the started drives: 1 oyun, ba\u015flat\u0131lan s\u00fcr\u00fcc\u00fclerde\n+  A PS1 game here is deleted in Memory Files, where it is a file of its own.: Buradaki bir PS1 oyunu, kendi ba\u015f\u0131na bir dosya oldu\u011fu Haf\u0131za Dosyalar\u0131'nda silinir.\n+  A network catalogue's games are deleted on the PC that serves them.: Bir a\u011f katalo\u011funun oyunlar\u0131, onlar\u0131 sunan bilgisayarda silinir.\n+  DELETE FOR GOOD: KALICI OLARAK S\u0130L\n+  DELETE IT: S\u0130L\n+  'DELETED %s: %s FREED': '%s S\u0130L\u0130ND\u0130: %s BO\u015eALDI'\n+  'Delete %s from %s? Goes: %s.': '%s, %s \u00fczerinden silinsin mi? Gidecek: %s.'\n+  'Delete %s from %s? Goes: %s. Kept: %s.': '%s, %s \u00fczerinden silinsin mi? Gidecek: %s. Kalacak: %s.'\n+  Delete its saves and RIPSHOT pictures too? No keeps them.: Kay\u0131tlar\u0131 ve RIPSHOT resimleri de silinsin mi? Hay\u0131r onlar\u0131 tutar.\n+  HIDDEN GAMES: G\u0130ZL\u0130 OYUNLAR\n+  'HIDDEN: %s': 'G\u0130ZLEND\u0130: %s'\n+  Hide %s? It leaves every list, All Games and Starred Games too. Settings > Game Sources > Hidden Games shows it again.: '%s gizlensin mi? Her listeden, T\u00fcm Oyunlar ve Favoriler''den de \u00e7\u0131kar. Ayarlar > Oyun kaynaklar\u0131 > Gizli oyunlar onu yeniden g\u00f6sterir.'\n+  Last check. %s is deleted for good, and nothing can bring it back. Press START to delete it.: Son kontrol. %s kal\u0131c\u0131 olarak silinir ve hi\u00e7bir \u015fey onu geri getiremez. Silmek i\u00e7in START'a bas\u0131n.\n+  None. Hide Game, in a game's options, puts one here.: Yok. Bir oyunun se\u00e7eneklerindeki Oyunu gizle buraya bir tane koyar.\n+  ? 'Nothing was deleted: this drive''s partition table does not fit this disk (%s). It may have been written from an image of a disk of another size. Repair it on a PC first.'\n+  : 'Hi\u00e7bir \u015fey silinmedi: bu s\u00fcr\u00fcc\u00fcn\u00fcn b\u00f6l\u00fcm tablosu bu diske uymuyor (%s). Ba\u015fka boyutta bir diskin imaj\u0131ndan yaz\u0131lm\u0131\u015f olabilir. \u00d6nce bir bilgisayarda onar\u0131n.'\n+  RIPPS2 cannot delete a game from this page.: RIPPS2 bu sayfadan oyun silemez.\n+  RIPPS2 could not find this game's files on its drive.: RIPPS2 bu oyunun dosyalar\u0131n\u0131 s\u00fcr\u00fcc\u00fcs\u00fcnde bulamad\u0131.\n+  Reading the hidden lists: Gizli listeler okunuyor\n+  SHOW AGAIN OR DELETE: YEN\u0130DEN G\u00d6STER VEYA S\u0130L\n+  SHOW IT AGAIN: YEN\u0130DEN G\u00d6STER\n+  That game is not on its drive any more.: O oyun art\u0131k s\u00fcr\u00fcc\u00fcs\u00fcnde de\u011fil.\n+  The hidden list could not be written to the game's drive. Is it write-protected, or are Write Operations off?: Gizli liste oyunun s\u00fcr\u00fcc\u00fcs\u00fcne yaz\u0131lamad\u0131. Yazmaya kar\u015f\u0131 korumal\u0131 m\u0131, yoksa \u0130\u015flemleri Yaz kapal\u0131 m\u0131?\n+  The partition could not be removed (%d). Nothing was deleted.: B\u00f6l\u00fcm kald\u0131r\u0131lamad\u0131 (%d). Hi\u00e7bir \u015fey silinmedi.\n+  This drive cannot keep a hidden list (a network catalogue).: Bu s\u00fcr\u00fcc\u00fc gizli liste tutamaz (bir a\u011f katalo\u011fu).\n+  This game's drive has not answered yet. Wait for it, then try again.: Bu oyunun s\u00fcr\u00fcc\u00fcs\u00fc hen\u00fcz yan\u0131t vermedi. Bekleyin, sonra yeniden deneyin.\n+  This game's partition is one RIPPS2 does not remove (a system partition, or the one its settings are on).: Bu oyunun b\u00f6l\u00fcm\u00fc, RIPPS2'nin kald\u0131rmad\u0131\u011f\u0131 b\u00f6l\u00fcmlerden biri (bir sistem b\u00f6l\u00fcm\u00fc ya da ayarlar\u0131n\u0131n bulundu\u011fu b\u00f6l\u00fcm).\n+  'a partition is larger than this disk allows: the table was made for a disk of another size': 'bir b\u00f6l\u00fcm bu diskin izin verdi\u011finden b\u00fcy\u00fck: tablo ba\u015fka boyutta bir disk i\u00e7in yap\u0131lm\u0131\u015f'\n+  'a partition runs past the end of this disk (sector %u of %u): the table was made for a bigger disk': 'bir b\u00f6l\u00fcm bu diskin sonunu a\u015f\u0131yor (sekt\u00f6r %u / %u): tablo daha b\u00fcy\u00fck bir disk i\u00e7in yap\u0131lm\u0131\u015f'\n+  its RIPSHOT pictures: RIPSHOT resimleri\n+  its drive: s\u00fcr\u00fcc\u00fcs\u00fc\n+  its saves: kay\u0131tlar\u0131\n+  sector %u holds no partition header: '%u sekt\u00f6r\u00fcnde b\u00f6l\u00fcm ba\u015fl\u0131\u011f\u0131 yok'\n+  the chain does not close at __mbr: zincir __mbr'de kapanm\u0131yor\n+  the chain skips from sector %u: zincir %u sekt\u00f6r\u00fcnden atl\u0131yor\n+  the chain's links disagree at sector %u: zincirin halkalar\u0131 %u sekt\u00f6r\u00fcnde uyu\u015fmuyor\n+  the drive's size could not be read: s\u00fcr\u00fcc\u00fcn\u00fcn boyutu okunamad\u0131\n+  the game (%s): oyun (%s)\n+  the game's folder and everything in it (%s): oyunun klas\u00f6r\u00fc ve i\u00e7indeki her \u015fey (%s)\n+  the header at sector %u could not be read: '%u sekt\u00f6r\u00fcndeki ba\u015fl\u0131k okunamad\u0131'\n+  the header at sector %u fails its checksum: '%u sekt\u00f6r\u00fcndeki ba\u015fl\u0131k sa\u011flama toplam\u0131n\u0131 ge\u00e7emiyor'\n+  the partition at sector %u is not where or as large as APA puts one: '%u sekt\u00f6r\u00fcndeki b\u00f6l\u00fcm, APA''n\u0131n bir b\u00f6l\u00fcm koydu\u011fu yerde veya boyutta de\u011fil'\n+  the settings and art another copy of it uses: ba\u015fka bir kopyas\u0131n\u0131n kulland\u0131\u011f\u0131 ayarlar ve kapaklar\n+  '%d FOLDERS': '%d KLAS\u00d6R'\n+  '%s could not be copied to %s. Is the drive full or write-protected?': '%s, %s konumuna kopyalanamad\u0131. S\u00fcr\u00fcc\u00fc dolu mu, yoksa yazmaya kar\u015f\u0131 korumal\u0131 m\u0131?'\n+  ? '%s is not where RIPPS2 looks. Search the drives RIPPS2 has started for it? Where it is found is remembered for every launch after.'\n+  : '%s, RIPPS2''nin bakt\u0131\u011f\u0131 yerde de\u011fil. RIPPS2''nin ba\u015flatt\u0131\u011f\u0131 s\u00fcr\u00fcc\u00fclerde arans\u0131n m\u0131? Bulundu\u011fu yer sonraki her ba\u015flatma i\u00e7in hat\u0131rlan\u0131r.'\n+  ? 'Ember is not in %s, beside the games, and it has to be. RIPPS2 found it at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Ember, oyunlar\u0131n yan\u0131nda %s i\u00e7inde de\u011fil ve orada olmal\u0131. RIPPS2 onu %s konumunda buldu: START onu oraya kopyalar (%d KB) ve oyunu ba\u015flat\u0131r.'\n+  Ember's BIOS: Ember'in BIOS'u\n+  ? 'Ember''s BIOS (bios.bin) is not in %s, beside Ember. RIPPS2 found one at %s: START copies it there (%d KB) and starts the game.'\n+  : 'Ember''in BIOS''u (bios.bin), Ember''in yan\u0131nda %s i\u00e7inde de\u011fil. RIPPS2 %s konumunda bir tane buldu: START onu oraya kopyalar (%d KB) ve oyunu ba\u015flat\u0131r.'\n+  LOOKING FOR YOUR LAUNCHERS: BA\u015eLATICILARINIZ ARANIYOR\n+  The POPS files: POPS dosyalar\u0131\n+  '%s  MMCE card': '%s  MMCE kart\u0131'\n+  INSTALL RIPPS2'S NEUTRINO: RIPPS2'N\u0130N NEUTRINO'SUNU KUR\n+  'Internal HDD (APA): %s free of %s': 'Dahili HDD (APA): %s bo\u015f, toplam %s'\n+  LAUNCHERS: BA\u015eLATICILAR\n+  LOOK FOR THEM: ONLARI ARA\n+  'Memory card %d: %d KB free': 'Haf\u0131za kart\u0131 %d: %d KB bo\u015f'\n+  POPS files: POPS dosyalar\u0131\n+  START EVERY DRIVE: T\u00dcM S\u00dcR\u00dcC\u00dcLER\u0130 BA\u015eLAT\n+  'STATUS: looking at the drives and the homebrew': 'DURUM: s\u00fcr\u00fcc\u00fclere ve homebrew''a bak\u0131l\u0131yor'\n+  Start every drive first, so the search reaches all of them? (No searches the drives already started.): Arama hepsine ula\u015fs\u0131n diye \u00f6nce t\u00fcm s\u00fcr\u00fcc\u00fcler ba\u015flat\u0131ls\u0131n m\u0131? (Hay\u0131r, zaten ba\u015flat\u0131lm\u0131\u015f s\u00fcr\u00fcc\u00fclerde arar.)\n+  USB, HDD, MMCE and the rest: USB, HDD, MMCE ve di\u011ferleri\n+  every started drive: ba\u015flat\u0131lan her s\u00fcr\u00fcc\u00fc\n+  found where you keep them: onlar\u0131 tuttu\u011funuz yerde bulundu\n+  not found yet: hen\u00fcz bulunamad\u0131\n+  not on a started drive: ba\u015flat\u0131lm\u0131\u015f bir s\u00fcr\u00fcc\u00fcde de\u011fil\n+  only if you have none: yaln\u0131zca hi\u00e7 yoksa\n+  starts every drive first: \u00f6nce t\u00fcm s\u00fcr\u00fcc\u00fcleri ba\u015flat\u0131r\n+  Drives, launchers and homebrew: S\u00fcr\u00fcc\u00fcler, ba\u015flat\u0131c\u0131lar ve ev yap\u0131m\u0131 yaz\u0131l\u0131mlar\n+  BDM Devices: BDM Ayg\u0131tlar\u0131\n+  Color Sweep: Renk Ge\u00e7i\u015fi\n+  Colors and Text: Renkler ve Metin\n+  HDD POPS partitions: HDD'nin POPS B\u00f6l\u00fcmleri\n+  Layout: D\u00fczen\n+  Missing Covers: Eksik Kapaklar\n+  Name: Ad\n+  No POPSTARTER SMB setup found yet. Fill in the rows below.: Hen\u00fcz POPSTARTER SMB ayar\u0131 bulunamad\u0131. A\u015fa\u011f\u0131daki sat\u0131rlar\u0131 doldurun.\n+  POPSTARTER Network: POPSTARTER A\u011f\u0131\n+  POPSTARTER SMB Network: POPSTARTER SMB A\u011f\u0131\n+  POPSTARTER UDPFS Network: POPSTARTER UDPFS A\u011f\u0131\n+  RIPPS2 adds the drive, ISO, VMCs and video mode itself.: RIPPS2 s\u00fcr\u00fcc\u00fcy\u00fc, ISO'yu, VMC'leri ve video modunu kendisi ekler.\n+  Screen: Ekran\n+  Speed: H\u0131z\n+  Sweep Colors: Ge\u00e7i\u015f Renkleri\n+  Text Weight: Metin Kal\u0131nl\u0131\u011f\u0131\n+  Theme Color: Ana Renk\n+  Audio: Ses\n+  Bold: Kal\u0131n\n+  Controllers: Kumandalar\n+  Lively: Canl\u0131\n+  Medium: Orta\n+  PS Emulation: PS \u00d6yk\u00fcnmesi\n+  Regular: Normal\n+  Slow: Yava\u015f\n+  A MOMENT: B\u0130R AN\n+  A TASK IN THE BACKGROUND: ARKA PLANDA B\u0130R \u0130\u015e\n+  ABOUT %d S: YAKLA\u015eIK %d SN\n+  ANY MOMENT: HER AN\n+  READING THE GAME LIST OF %s: '%s OYUN L\u0130STES\u0130 OKUNUYOR'\n+  SAVING A LIST: B\u0130R L\u0130STE KAYDED\u0130L\u0130YOR\n+  START NOW: \u015e\u0130MD\u0130 BA\u015eLAT\n+  STARTING: BA\u015eLIYOR\n+  STARTS NEXT: SIRADA BA\u015eLAYACAK\n+  STOPPING IT SAFELY: G\u00dcVENLE DURDURULUYOR\n+  THE LAST OF THE QUEUE: SIRANIN SONU\n+  WHEN IT FINISHES: B\u0130T\u0130NCE\n+  What each card starts; install or repair BIBLE.: Her kart\u0131n ne ba\u015flatt\u0131\u011f\u0131; BIBLE'\u0131 kurun veya onar\u0131n.\n+  Rainbow: G\u00f6kku\u015fa\u011f\u0131\n+  Custom: \u00d6zel\n+  DESR DVR ISOs (experimental): DESR DVR ISO'lar\u0131 (deneysel)\n+  PC CARD CF (experimental): PC CARD CF (deneysel)\n+  HDD Start Mode: HDD Ba\u015flatma Modu\n+  Roboto Condensed by The Roboto Project Authors (SIL OFL 1.1): The Roboto Project Authors'dan Roboto Condensed (SIL OFL 1.1)\n+  '%s on %s': '%s, %s \u00fczerinde'\n+  '%s: %s made %s (%d%%), every piece checked. Remove the ISO and keep the ZSO?': '%s: %s, %s oldu (%d%%), her par\u00e7a denetlendi. ISO silinsin ve ZSO kals\u0131n m\u0131?'\n+  '%s: %s made %s again, every piece checked. Remove the ZSO and keep the ISO?': '%s: %s yeniden %s oldu, her par\u00e7a denetlendi. ZSO silinsin ve ISO kals\u0131n m\u0131?'\n+  A ZSO of this game is already beside it. Remove one of the two first.: Yan\u0131nda bu oyunun bir ZSO'su zaten var. \u00d6nce ikisinden birini kald\u0131r\u0131n.\n+  About %d min, then checked: Yakla\u015f\u0131k %d dk, sonra denetlenir\n+  An ISO of this game is already beside it. Remove one of the two first.: Yan\u0131nda bu oyunun bir ISO'su zaten var. \u00d6nce ikisinden birini kald\u0131r\u0131n.\n+  CHECKING EVERY PIECE: HER PAR\u00c7A DENETLEN\u0130YOR\n+  COMPRESS TO ZSO: ZSO'YA SIKI\u015eTIR\n+  'COMPRESSED %s: %s FREED': '%s SIKI\u015eTIRILDI: %s BO\u015eALDI'\n+  COMPRESSING 1 OF 2: SIKI\u015eTIRILIYOR 1/2\n+  COMPRESSING 2 OF 2: SIKI\u015eTIRILIYOR 2/2\n+  ? Compress %s? The ISO stays until the ZSO has been read back and checked, then RIPPS2 asks before it removes the ISO. The drive needs room for both while it works; leave the console on until it finishes. A ZSO starts with the OPL core only (not Neutrino); its saves, settings, art and cheats stay with it, and it can be made an ISO again at any time.\n+  : '%s s\u0131k\u0131\u015ft\u0131r\u0131ls\u0131n m\u0131? ZSO geri okunup denetlenene kadar ISO kal\u0131r, sonra RIPPS2 ISO''yu kald\u0131rmadan \u00f6nce sorar. S\u00fcr\u00fcc\u00fc \u00e7al\u0131\u015f\u0131rken ikisine de yer gerekir; bitene kadar konsolu a\u00e7\u0131k b\u0131rak\u0131n. ZSO yaln\u0131zca OPL \u00e7ekirde\u011fiyle ba\u015flar (Neutrino de\u011fil); kay\u0131tlar\u0131, ayarlar\u0131, resimleri ve hileleri onunla kal\u0131r ve istendi\u011finde yeniden ISO yap\u0131labilir.'\n+  Compress to ZSO: ZSO'ya s\u0131k\u0131\u015ft\u0131r\n+  Cycle: S\u0131rayla\n+  'Experimental: OPL core only (not Neutrino)': 'Deneysel: yaln\u0131zca OPL \u00e7ekirde\u011fi (Neutrino de\u011fil)'\n+  Hold SELECT: SELECT bas\u0131l\u0131 tut\n+  It could not be finished (%s). What it wrote was removed; the game is as it was.: Tamamlanamad\u0131 (%s). Yazd\u0131klar\u0131 kald\u0131r\u0131ld\u0131; oyun eskisi gibi.\n+  List: Liste\n+  ? Make %s an ISO again? The ZSO stays until the ISO has been read back and checked, then RIPPS2 asks before it removes the ZSO. The drive needs room for both while it works; leave the console on until it finishes. Its saves, settings, art and cheats stay with it.\n+  : '%s yeniden ISO yap\u0131ls\u0131n m\u0131? ISO geri okunup denetlenene kadar ZSO kal\u0131r, sonra RIPPS2 ZSO''yu kald\u0131rmadan \u00f6nce sorar. S\u00fcr\u00fcc\u00fc \u00e7al\u0131\u015f\u0131rken ikisine de yer gerekir; bitene kadar konsolu a\u00e7\u0131k b\u0131rak\u0131n. Kay\u0131tlar\u0131, ayarlar\u0131, resimleri ve hileleri onunla kal\u0131r.'\n+  Stop? What it has written so far is removed; the game stays as it was.: Durdurulsun mu? \u015eimdiye kadar yaz\u0131lan kald\u0131r\u0131l\u0131r; oyun eskisi gibi kal\u0131r.\n+  THEMES: TEMALAR\n+  There is not enough memory for this just now.: \u015eu anda bunun i\u00e7in yeterli bellek yok.\n+  This image is not one RIPPS2 can compress or uncompress (its size is not whole 2 KB blocks).: RIPPS2 bu kal\u0131b\u0131 s\u0131k\u0131\u015ft\u0131ramaz veya a\u00e7amaz (boyutu tam 2 KB bloklardan olu\u015fmuyor).\n+  UNCOMPRESS TO ISO: ISO'YA A\u00c7\n+  UNCOMPRESSED %s: '%s A\u00c7ILDI'\n+  UNCOMPRESSING: A\u00c7ILIYOR\n+  Uncompress to ISO: ISO'ya a\u00e7\n+  Under a minute, then checked: Bir dakikadan az, sonra denetlenir\n+  COPYING: KOPYALANIYOR\n+  SHOW AGAIN, DELETE OR COMPRESS: YEN\u0130DEN G\u00d6STER, S\u0130L VEYA SIKI\u015eTIR\n+  SHOW AGAIN, DELETE OR UNCOMPRESS: YEN\u0130DEN G\u00d6STER, S\u0130L VEYA A\u00c7\n+  '%s needs about %s free on its drive while it works (the original stays until the end), and the drive has %s free.': '%s \u00e7al\u0131\u015f\u0131rken s\u00fcr\u00fcc\u00fcs\u00fcnde yakla\u015f\u0131k %s bo\u015f alan ister (orijinal sonuna kadar kal\u0131r), s\u00fcr\u00fcc\u00fcde ise %s bo\u015f var.'\n+  Hide and Compress: Gizle ve s\u0131k\u0131\u015ft\u0131r\n+  'COMPRESSED AND HIDDEN: %s': 'SIKI\u015eTIRILDI VE G\u0130ZLEND\u0130: %s'\n+  'There is not room for this rip: it needs about %s, and the drive has %s free.': 'Bu kopyalama i\u00e7in yer yok: yakla\u015f\u0131k %s gerekiyor, s\u00fcr\u00fcc\u00fcde ise %s bo\u015f var.'\n+  OVER 4 GB FOR FAT32: FAT32 \u0130\u00c7\u0130N 4 GB \u00dcST\u00dc\n+  This drive is %s, which keeps no file of 4 GB or more, and the new image would be %s.: Bu s\u00fcr\u00fcc\u00fc %s; 4 GB veya daha b\u00fcy\u00fck dosya tutmaz ve yeni kal\u0131p %s olurdu.\n+  '%s, %s clusters, %s free of %s': '%s, %s k\u00fcmeler, %s bo\u015f / %s'\n+  Delete This Sweep: Bu ge\u00e7i\u015fi sil\n+  Delete %s? RIPPS2's own sweeps stay as they are.: '%s silinsin mi? RIPPS2''nin kendi ge\u00e7i\u015fleri oldu\u011fu gibi kal\u0131r.'\n+  Sweep: Ge\u00e7i\u015f\n+  Badges mark the games with achievements in your lists, with how many you have unlocked. They need TELEMETRY on.: Rozetler, listelerinizde ba\u015far\u0131m\u0131 olan oyunlar\u0131 ve ka\u00e7 tanesini a\u00e7t\u0131\u011f\u0131n\u0131z\u0131 g\u00f6sterir. TELEMETR\u0130 a\u00e7\u0131k olmal\u0131d\u0131r.\n labels:\n   ETH_100MFDX: 100Mbit Tam \u00e7ift y\u00f6nl\u00fc\n   ETH_100MHDX: 100Mbit Yar\u0131 \u00e7ift y\u00f6nl\u00fc\n@@ -1206,7 +1373,7 @@ labels:\n   SAVE_ICONS: 3D Kay\u0131t Simgeleri\n   HINT_SAVE_ICONS: Temada yeri varsa se\u00e7ili oyunun kay\u0131t simgesini d\u00f6nerek g\u00f6sterir. \u00d6nce oyunun kendi VMC'sini, sonra haf\u0131za kartlar\u0131n\u0131 okur.\n   NETBOOT_RESTART_NOW: A\u011f protokol\u00fc yeniden ba\u015flatmadan sonra de\u011fi\u015fir. Ayarlar kaydedilip OPL \u015fimdi yeniden ba\u015flat\u0131ls\u0131n m\u0131?\n-  HINT_NET_PROTOCOL: 'RiptOPL''nin kulland\u0131\u011f\u0131 a\u011f hizmeti: bir SMB payla\u015f\u0131m\u0131, UDPFS, UDPBD veya HTTP. Oyun Kaynaklar\u0131''ndakiyle ayn\u0131 ayar. Zaten \u00e7al\u0131\u015fan birinden ge\u00e7mek yeniden ba\u015flatma gerektirir.'\n+  HINT_NET_PROTOCOL: 'RiptOPL''nin kulland\u0131\u011f\u0131 a\u011f hizmeti: bir SMB payla\u015f\u0131m\u0131, UDPFS, UDPBD veya HTTP. Zaten \u00e7al\u0131\u015fan birinden ge\u00e7mek yeniden ba\u015flatma gerektirir.'\n   HINT_UDPFS_ACCESS: Yaln\u0131zca UDPFS. Dosyalar, sunucunun oyun klas\u00f6r\u00fcnden tek tek ISO'lar\u0131 okur; IMG bir disk g\u00f6r\u00fcnt\u00fcs\u00fcn\u00fc s\u00fcr\u00fcc\u00fc olarak okur. SMB ve HTTP her zaman Dosyalar'\u0131, UDPBD her zaman IMG'yi kullan\u0131r.\n   HINT_SMB_VERSION: PC payla\u015f\u0131m\u0131yla konu\u015furken kullan\u0131lacak SMB s\u00fcr\u00fcm\u00fc.\n   HINT_NET_ADVANCED: Ethernet Ba\u011flant\u0131 Modu ve SMB Ba\u011flant\u0131 Noktas\u0131 d\u00fczenlemesinin kilidini a\u00e7ar.\n@@ -1405,3 +1572,23 @@ labels:\n   HINT_RIPPS2_DEVELOP_SHOTS: Her oyunun rulosunda bekleyen karelerin nas\u0131l PNG oldu\u011fu.\n   RIPPS2_SETTINGS_HOME: Ayarlar\u0131 kaydet\n   HINT_RIPPS2_SETTINGS_HOME: 'RIPPS2 ayarlar\u0131n\u0131 nerede tutar: haf\u0131za kart\u0131ndaki kendi kayd\u0131nda, bir s\u00fcr\u00fcc\u00fcdeki RIPPS2 klas\u00f6r\u00fcnde (dahili HDD, USB, MX4SIO, iLink veya MMCE) veya senin yazd\u0131\u011f\u0131n bir klas\u00f6rde.'\n+  RIPPS2_LAUNCHERS: Ba\u015flat\u0131c\u0131lar\n+  RIPPS2_LOOK_NOW: \u015eimdi ara\n+  HINT_RIPPS2_LOOK_NOW: RIPPS2 ba\u015flat\u0131lan s\u00fcr\u00fcc\u00fclerde POPSTARTER'\u0131, POPS dosyalar\u0131n\u0131, Ember'i, Ember'in BIOS'unu ve Neutrino'yu arar ve her birinin nerede oldu\u011funu hat\u0131rlar. Her ba\u015flatma onlar\u0131 oradan okur.\n+  RIPPS2_HIDE_GAME: Oyunu gizle\n+  RIPPS2_HIDDEN_GAMES: Gizli oyunlar\n+  HINT_RIPPS2_HIDDEN_GAMES: Oyunu gizle ile gizlenen oyunlar, ba\u015flat\u0131lan s\u00fcr\u00fcc\u00fclerde. Birini yeniden g\u00f6sterin ya da silin. Her s\u00fcr\u00fcc\u00fc kendi listesini tutar.\n+  HINT_RIPPS2_BDM_DEVICES: 'BDM''nin oyun okudu\u011fu s\u00fcr\u00fcc\u00fcler: USB, MX4SIO, bir PC Card CF, bir DESR''nin DVR s\u00fcr\u00fcc\u00fcs\u00fc ve iLink. Dahili HDD, HDD Ba\u015flatma Modu''nu izler.'\n+  HINT_RIPPS2_SCREEN: 'Men\u00fcn\u00fcn g\u00f6r\u00fcnt\u00fcs\u00fc: video modu, geni\u015f ekran, televiz\u00f6rdeki yeri (H-POS, V-POS) ve overscan.'\n+  HINT_RIPPS2_LAYOUT: Kategorilerin yeri ve bir sayfan\u0131n nas\u0131l geldi\u011fi, sonra RIPFLOW'un kapaklar\u0131 ve RIPGRID'in \u0131zgaras\u0131.\n+  HINT_RIPPS2_COLORS_TEXT: Metin kal\u0131nl\u0131\u011f\u0131, renk temas\u0131, renk ge\u00e7i\u015fi ve her renk.\n+  HINT_RIPPS2_TEXT_WEIGHT: 'RIPPS2''nin kendi metninin kal\u0131nl\u0131\u011f\u0131: ayarlar\u0131n ipu\u00e7lar\u0131, DURUM ve Hakk\u0131nda. Normal eskisi gibi; Orta ve Kal\u0131n yumu\u015fak bir g\u00f6r\u00fcnt\u00fcde daha iyi okunur.'\n+  HINT_RIPPS2_HDD_START: 'Dahili HDD, APA ya da exFAT: RIPPS2 ne olursa olsun okur ve bir kez, HDD oyunlar\u0131 olarak g\u00f6sterir. Kapal\u0131 ona dokunmaz.'\n+  HINT_RIPPS2_THEME_COLOR: Renk temas\u0131n\u0131n kendi rengi. Kendiniz se\u00e7mek i\u00e7in renk temas\u0131 olarak \u00d6zel'i se\u00e7in, metin renkleri gibi.\n+  HINT_RIPPS2_SWEEP: Teman\u0131n yava\u015f\u00e7a ve yumu\u015fak\u00e7a ge\u00e7ti\u011fi be\u015f renk. G\u00f6kku\u015fa\u011f\u0131, her renk temas\u0131ndan yap\u0131lm\u0131\u015f bir ge\u00e7i\u015f ya da sizinki. Kapal\u0131 renk temas\u0131n\u0131 sabit tutar.\n+  HINT_RIPPS2_SWEEP_COLORS: Ge\u00e7i\u015fin be\u015f rengi ve h\u0131z\u0131. RIPPS2'nin birini de\u011fi\u015ftirin, verdi\u011finiz adla sizin olarak saklan\u0131r.\n+  HINT_RIPPS2_SWEEP_NAME: Ge\u00e7i\u015finizin Renk Ge\u00e7i\u015fi listesindeki ad\u0131.\n+  HINT_RIPPS2_SWEEP_COLOR: Ge\u00e7i\u015fin s\u0131rayla ge\u00e7ti\u011fi be\u015f renkten biri.\n+  HINT_RIPPS2_SWEEP_SPEED: 'Bir sonraki gelmeden \u00f6nce her rengin ne kadar s\u00fcrd\u00fc\u011f\u00fc. Canl\u0131 bile yumu\u015fak kal\u0131r: renkler asla yan\u0131p s\u00f6nmez.'\n+  RIPPS2_HIDE_COMPRESS: Oyunu gizle ve s\u0131k\u0131\u015ft\u0131rma\n+  HINT_RIPPS2_SELECT_HOLD: 'Bir oyun listesinde SELECT 4.2 sn bas\u0131l\u0131 tutulur. Liste: temalar bir panelde, birini se\u00e7in. S\u0131rayla: sonraki tema. Kapal\u0131: hi\u00e7bir \u015fey (basmak yine yeniler).'"
        },
        {
          "sha": "1354dadc701d3246a24c1a103300c683a1e2818b",
          "filename": "elf/lang/Vietnamese.yml",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FVietnamese.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2FVietnamese.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2FVietnamese.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "acd89aec7b9c5dd941af2476d2659660cac4de81",
          "filename": "elf/lang/_strings.yml",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2F_strings.yml",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2F_strings.yml",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2F_strings.yml?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "9c48e05a24fa2267e207ecf17560291b5621c197",
          "filename": "elf/lang/fonts/OFL-Roboto.txt",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2Ffonts%2FOFL-Roboto.txt",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2Ffonts%2FOFL-Roboto.txt",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2Ffonts%2FOFL-Roboto.txt?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "8df12124290b09a9fd98f302f6c92e4c66aea433",
          "filename": "elf/lang/fonts/RobotoCondensed-RIPPS2.ttf",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2Ffonts%2FRobotoCondensed-RIPPS2.ttf",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2Ffonts%2FRobotoCondensed-RIPPS2.ttf",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2Ffonts%2FRobotoCondensed-RIPPS2.ttf?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "70e106323fcc9b8d8a2b135d821685910bcd4f45",
          "filename": "elf/lang/ripps2lang.py",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2Fripps2lang.py",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Flang%2Fripps2lang.py",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Flang%2Fripps2lang.py?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "dd039adf7ec3781233a4df88b21b5f1771da0281",
          "filename": "elf/neutrino/README.md",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Fneutrino%2FREADME.md",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Fneutrino%2FREADME.md",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Fneutrino%2FREADME.md?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "0c164c695af4eb471b1d3e422253d249f63caf93",
          "filename": "elf/neutrino/neutrino-1.8.0-61-g7be8de2.tar.gz",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Fneutrino%2Fneutrino-1.8.0-61-g7be8de2.tar.gz",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Fneutrino%2Fneutrino-1.8.0-61-g7be8de2.tar.gz",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Fneutrino%2Fneutrino-1.8.0-61-g7be8de2.tar.gz?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "a88d4a81e3c255361051416142c16245740504f9",
          "filename": "elf/neutrino/neutrino-1.8.0.tar.gz",
          "status": "removed",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/28bc44d0d3a31b084d6b7e55ff91745f9a09c026/elf%2Fneutrino%2Fneutrino-1.8.0.tar.gz",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/28bc44d0d3a31b084d6b7e55ff91745f9a09c026/elf%2Fneutrino%2Fneutrino-1.8.0.tar.gz",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Fneutrino%2Fneutrino-1.8.0.tar.gz?ref=28bc44d0d3a31b084d6b7e55ff91745f9a09c026"
        },
        {
          "sha": "e1ad945a62d65ba61f116fb9618becd5a76e0162",
          "filename": "elf/patches/0114-ripps2-games.patch",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Fpatches%2F0114-ripps2-games.patch",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Fpatches%2F0114-ripps2-games.patch",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Fpatches%2F0114-ripps2-games.patch?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "e04d8385c9ef5c537822939ef4478c12c53323fd",
          "filename": "elf/patches/0115-ripps2-hardware.patch",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Fpatches%2F0115-ripps2-hardware.patch",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Fpatches%2F0115-ripps2-hardware.patch",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Fpatches%2F0115-ripps2-hardware.patch?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "9684484bfd401c669178e18a646ab540584f861e",
          "filename": "elf/tests/expected_failures.txt",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftests%2Fexpected_failures.txt",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftests%2Fexpected_failures.txt",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Ftests%2Fexpected_failures.txt?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "7afb3b0ffef4af4edf4be2c19547b670e8a5a016",
          "filename": "elf/tests/test_ripps2_build102.py",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftests%2Ftest_ripps2_build102.py",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftests%2Ftest_ripps2_build102.py",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Ftests%2Ftest_ripps2_build102.py?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "581c8fa5ad896812aed881ae84d1b4db55234286",
          "filename": "elf/tests/test_ripps2_build103.py",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftests%2Ftest_ripps2_build103.py",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftests%2Ftest_ripps2_build103.py",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Ftests%2Ftest_ripps2_build103.py?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "07c8e201ece7c01deecb84983483512a876067ec",
          "filename": "elf/tests/test_ripps2_build113.py",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftests%2Ftest_ripps2_build113.py",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftests%2Ftest_ripps2_build113.py",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Ftests%2Ftest_ripps2_build113.py?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "99eae09c92ed706410a7e6727660dc6ed5885de0",
          "filename": "elf/tests/test_ripps2_build114.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftests%2Ftest_ripps2_build114.py",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftests%2Ftest_ripps2_build114.py",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Ftests%2Ftest_ripps2_build114.py?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "dd6b1478300e22958b0c1412c1578b4f4b39bd01",
          "filename": "elf/tests/test_ripps2_build80.py",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftests%2Ftest_ripps2_build80.py",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftests%2Ftest_ripps2_build80.py",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Ftests%2Ftest_ripps2_build80.py?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "935908710c9a9e4a143177fb7845b55be700e137",
          "filename": "elf/tests/test_ripps2_build81.py",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftests%2Ftest_ripps2_build81.py",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftests%2Ftest_ripps2_build81.py",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Ftests%2Ftest_ripps2_build81.py?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "802c98a83c70fb7bc728ed45c2ad690e072a8716",
          "filename": "elf/tests/test_ripps2_build89.py",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftests%2Ftest_ripps2_build89.py",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftests%2Ftest_ripps2_build89.py",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Ftests%2Ftest_ripps2_build89.py?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "8e1c99ff0174f144ca2d0b4404838def1cb0c281",
          "filename": "elf/tests/test_ripps2_build95.py",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftests%2Ftest_ripps2_build95.py",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftests%2Ftest_ripps2_build95.py",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Ftests%2Ftest_ripps2_build95.py?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "a9ce78cc82cd95c84f22fb32b802a8c6613be5d3",
          "filename": "elf/tests/test_ripps2_build96.py",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftests%2Ftest_ripps2_build96.py",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftests%2Ftest_ripps2_build96.py",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Ftests%2Ftest_ripps2_build96.py?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "8fffbc14f00254309f0e8a73f007a300a50bd5d1",
          "filename": "elf/tests/test_ripps2_build99.py",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftests%2Ftest_ripps2_build99.py",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftests%2Ftest_ripps2_build99.py",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Ftests%2Ftest_ripps2_build99.py?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "053999b88bb30c94d93a5051146a90d929077a63",
          "filename": "elf/tests/test_ripps2_neutrino_unpack.py",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftests%2Ftest_ripps2_neutrino_unpack.py",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftests%2Ftest_ripps2_neutrino_unpack.py",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Ftests%2Ftest_ripps2_neutrino_unpack.py?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "152975f479b439a01a0a07ff75805f09481879e3",
          "filename": "elf/tests/test_ripps2_theme79.py",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftests%2Ftest_ripps2_theme79.py",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftests%2Ftest_ripps2_theme79.py",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Ftests%2Ftest_ripps2_theme79.py?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "49e7407508a1d54433de132820c49c9ab152b51f",
          "filename": "elf/theme/fonts/ROBOTO-LICENSE.txt",
          "status": "removed",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/28bc44d0d3a31b084d6b7e55ff91745f9a09c026/elf%2Ftheme%2Ffonts%2FROBOTO-LICENSE.txt",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/28bc44d0d3a31b084d6b7e55ff91745f9a09c026/elf%2Ftheme%2Ffonts%2FROBOTO-LICENSE.txt",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Ftheme%2Ffonts%2FROBOTO-LICENSE.txt?ref=28bc44d0d3a31b084d6b7e55ff91745f9a09c026"
        },
        {
          "sha": "9c48e05a24fa2267e207ecf17560291b5621c197",
          "filename": "elf/theme/fonts/ROBOTO-OFL.txt",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftheme%2Ffonts%2FROBOTO-OFL.txt",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftheme%2Ffonts%2FROBOTO-OFL.txt",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Ftheme%2Ffonts%2FROBOTO-OFL.txt?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "1371f033d1f2810ba324ef623d08f506bc91da88",
          "filename": "elf/theme/fonts/ripflow.ttf",
          "status": "removed",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/28bc44d0d3a31b084d6b7e55ff91745f9a09c026/elf%2Ftheme%2Ffonts%2Fripflow.ttf",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/28bc44d0d3a31b084d6b7e55ff91745f9a09c026/elf%2Ftheme%2Ffonts%2Fripflow.ttf",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Ftheme%2Ffonts%2Fripflow.ttf?ref=28bc44d0d3a31b084d6b7e55ff91745f9a09c026"
        },
        {
          "sha": "b0d0d368c66db96c1e26f009c18ef005a4e5c2d5",
          "filename": "elf/theme/fonts/ripflow_bold.ttf",
          "status": "removed",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/28bc44d0d3a31b084d6b7e55ff91745f9a09c026/elf%2Ftheme%2Ffonts%2Fripflow_bold.ttf",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/28bc44d0d3a31b084d6b7e55ff91745f9a09c026/elf%2Ftheme%2Ffonts%2Fripflow_bold.ttf",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Ftheme%2Ffonts%2Fripflow_bold.ttf?ref=28bc44d0d3a31b084d6b7e55ff91745f9a09c026"
        },
        {
          "sha": "cf14cf67d3eb2c341e55eb2516fd3d237b651052",
          "filename": "elf/theme/fonts/ripps2_roboto.ttf",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftheme%2Ffonts%2Fripps2_roboto.ttf",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftheme%2Ffonts%2Fripps2_roboto.ttf",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Ftheme%2Ffonts%2Fripps2_roboto.ttf?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "40861625c548e1cffe42a4564523c212e13c4b53",
          "filename": "elf/theme/tools/make_roboto.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftheme%2Ftools%2Fmake_roboto.py",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftheme%2Ftools%2Fmake_roboto.py",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Ftheme%2Ftools%2Fmake_roboto.py?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "d8096ea24186462281660445d85b5e163eb75367",
          "filename": "elf/theme/tools/strip_fonts.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftheme%2Ftools%2Fstrip_fonts.py",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/elf%2Ftheme%2Ftools%2Fstrip_fonts.py",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/elf%2Ftheme%2Ftools%2Fstrip_fonts.py?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "8d96b26e315b2ee6a8830ff793884d16e290ba72",
          "filename": "index.html",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/index.html",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/index.html",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/index.html?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "2d61bdbf2d625cb13363fb3002ee763ef8ec8934",
          "filename": "media/b114/new-01-all-games-at-once.jpg",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fnew-01-all-games-at-once.jpg",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fnew-01-all-games-at-once.jpg",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/media%2Fb114%2Fnew-01-all-games-at-once.jpg?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "0bde943f78c3d58188afd8061f2f623039c5a064",
          "filename": "media/b114/new-02-launcher-search.jpg",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fnew-02-launcher-search.jpg",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fnew-02-launcher-search.jpg",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/media%2Fb114%2Fnew-02-launcher-search.jpg?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "09e2c7052316ced5c8ae2ffb1819ce146db277f3",
          "filename": "media/b114/new-03-cache-found.jpg",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fnew-03-cache-found.jpg",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fnew-03-cache-found.jpg",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/media%2Fb114%2Fnew-03-cache-found.jpg?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "c2b2744a968c162ef851e547e6b0d3d7a6304078",
          "filename": "media/b114/new-04-delete-plan.jpg",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fnew-04-delete-plan.jpg",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fnew-04-delete-plan.jpg",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/media%2Fb114%2Fnew-04-delete-plan.jpg?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "026330fac39c6493db3b1e675a40dc2c549ad9ba",
          "filename": "media/b114/new-05-launch-guard.jpg",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fnew-05-launch-guard.jpg",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fnew-05-launch-guard.jpg",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/media%2Fb114%2Fnew-05-launch-guard.jpg?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "79a6a90105d1ac7bcd0078150591e4bca6788985",
          "filename": "media/b114/new-06-compress-hold.jpg",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fnew-06-compress-hold.jpg",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fnew-06-compress-hold.jpg",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/media%2Fb114%2Fnew-06-compress-hold.jpg?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "dbb3500593bd8a9cdb639f0c4d00a590bd9c1f3b",
          "filename": "media/b114/new-07-compress-bar.jpg",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fnew-07-compress-bar.jpg",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fnew-07-compress-bar.jpg",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/media%2Fb114%2Fnew-07-compress-bar.jpg?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "6a54cca8ab38eed73e17a47388f6b2889ddac6d9",
          "filename": "media/b114/new-08-theme-list.jpg",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fnew-08-theme-list.jpg",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fnew-08-theme-list.jpg",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/media%2Fb114%2Fnew-08-theme-list.jpg?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "0f8c6b3acc2ac4963fa14235b0b7443e3e90f798",
          "filename": "media/b114/new-09-tangerine.jpg",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fnew-09-tangerine.jpg",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fnew-09-tangerine.jpg",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/media%2Fb114%2Fnew-09-tangerine.jpg?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "3d012faaf6888b0148ca8620692f7fb9bd3a27f5",
          "filename": "media/b114/new-10-custom.jpg",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fnew-10-custom.jpg",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fnew-10-custom.jpg",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/media%2Fb114%2Fnew-10-custom.jpg?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "d82b418ac905f756a637431b328eaef47bcbb209",
          "filename": "media/b114/new-11-color-sweep.jpg",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fnew-11-color-sweep.jpg",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fnew-11-color-sweep.jpg",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/media%2Fb114%2Fnew-11-color-sweep.jpg?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "7af1f0f0720b0910493b5fd107c5a97cdd10fa58",
          "filename": "media/b114/new-12-hidden-games.jpg",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fnew-12-hidden-games.jpg",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fnew-12-hidden-games.jpg",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/media%2Fb114%2Fnew-12-hidden-games.jpg?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "f2726a7dd408bbb6e6017a281533406c94cf6c94",
          "filename": "media/b114/ripps2-boot-empty-games.gif",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fripps2-boot-empty-games.gif",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fripps2-boot-empty-games.gif",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/media%2Fb114%2Fripps2-boot-empty-games.gif?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "cb8853e7c6d8e79f992a165a9f3cac190e2474b8",
          "filename": "media/b114/ripps2-v114-wheels-short.mp4",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fripps2-v114-wheels-short.mp4",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Fripps2-v114-wheels-short.mp4",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/media%2Fb114%2Fripps2-v114-wheels-short.mp4?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "6b1bc4868dd3bd8799de064ee264ce5375b35a25",
          "filename": "media/b114/tour-01-storage.jpg",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Ftour-01-storage.jpg",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Ftour-01-storage.jpg",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/media%2Fb114%2Ftour-01-storage.jpg?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "6908930f19814d9a404c88e81c56ef0a12e43230",
          "filename": "media/b114/tour-02-info-page.jpg",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Ftour-02-info-page.jpg",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Ftour-02-info-page.jpg",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/media%2Fb114%2Ftour-02-info-page.jpg?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "c1f05d5eb04a082c1c3dad141d74ee2f342be8ac",
          "filename": "media/b114/tour-03-adapt.jpg",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Ftour-03-adapt.jpg",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Ftour-03-adapt.jpg",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/media%2Fb114%2Ftour-03-adapt.jpg?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "7536748f9fc47075b632409cfd1bdeb8b2ecfb1e",
          "filename": "media/b114/tour-04-ripgrid.jpg",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Ftour-04-ripgrid.jpg",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Ftour-04-ripgrid.jpg",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/media%2Fb114%2Ftour-04-ripgrid.jpg?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "ccb5b00f3558ad131fb6443b1cb02aa20dfc574d",
          "filename": "media/b114/tour-05-ripflow.jpg",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Ftour-05-ripflow.jpg",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Ftour-05-ripflow.jpg",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/media%2Fb114%2Ftour-05-ripflow.jpg?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "0429687f7c7f03537a1085b6d89871ff2ace05be",
          "filename": "media/b114/tour-06-launch-disc.jpg",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Ftour-06-launch-disc.jpg",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Ftour-06-launch-disc.jpg",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/media%2Fb114%2Ftour-06-launch-disc.jpg?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "92348714d079658a6a09f19be2df95c422bf1cc2",
          "filename": "media/b114/tour-07-memory-files.jpg",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Ftour-07-memory-files.jpg",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Ftour-07-memory-files.jpg",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/media%2Fb114%2Ftour-07-memory-files.jpg?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "82597286882259c7bf2acd7990ba7ab4a04c20cc",
          "filename": "media/b114/tour-08-in-game-menu.jpg",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Ftour-08-in-game-menu.jpg",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Ftour-08-in-game-menu.jpg",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/media%2Fb114%2Ftour-08-in-game-menu.jpg?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "b0ca5638c492cae4e652f119b2f3424a818c80a4",
          "filename": "media/b114/tour-09-status.jpg",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Ftour-09-status.jpg",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Ftour-09-status.jpg",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/media%2Fb114%2Ftour-09-status.jpg?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "4221b57b85a44fabcc7baf80ada8faa89f003f80",
          "filename": "media/b114/tour-10-game-sources.jpg",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Ftour-10-game-sources.jpg",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Ftour-10-game-sources.jpg",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/media%2Fb114%2Ftour-10-game-sources.jpg?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "cc8c78210ffbd0999bf1aac715a6e2aca010013d",
          "filename": "media/b114/tour-11-japanese.jpg",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Ftour-11-japanese.jpg",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fb114%2Ftour-11-japanese.jpg",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/media%2Fb114%2Ftour-11-japanese.jpg?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "2dbb957a8387245cad0a71c8c6890cffb2211f00",
          "filename": "media/ripps2-test-me.png",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fripps2-test-me.png",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/media%2Fripps2-test-me.png",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/media%2Fripps2-test-me.png?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "0c089a3e7d2c9cbfdeb74b12e67c59104d94ca93",
          "filename": "tools/dev/make_bible_cards.py",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdev%2Fmake_bible_cards.py",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdev%2Fmake_bible_cards.py",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/tools%2Fdev%2Fmake_bible_cards.py?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "466b2bdf231a847fa5528b27f7eb99bff4344bc9",
          "filename": "tools/dev/pack_neutrino.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdev%2Fpack_neutrino.py",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdev%2Fpack_neutrino.py",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/tools%2Fdev%2Fpack_neutrino.py?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "46a970059d6bae4d04252db4bfc734ad0d15221a",
          "filename": "tools/dev/ps2bg.ps1",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdev%2Fps2bg.ps1",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdev%2Fps2bg.ps1",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/tools%2Fdev%2Fps2bg.ps1?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "c85a2f4c4a7c4226cfb3d25079e8c13de172fb39",
          "filename": "tools/dev/regen_patch.sh",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdev%2Fregen_patch.sh",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdev%2Fregen_patch.sh",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/tools%2Fdev%2Fregen_patch.sh?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "d767bd97708c5e9c3dca715422113090ca57ff53",
          "filename": "tools/discord/make_boot_embed.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdiscord%2Fmake_boot_embed.py",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdiscord%2Fmake_boot_embed.py",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/tools%2Fdiscord%2Fmake_boot_embed.py?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "4a8a4f9e6918fdd02fa6515bed388617c8423b66",
          "filename": "tools/discord/make_boot_gif.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdiscord%2Fmake_boot_gif.py",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdiscord%2Fmake_boot_gif.py",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/tools%2Fdiscord%2Fmake_boot_gif.py?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "174f405e3a0cebb4ae57de13051688953ba981ba",
          "filename": "tools/discord/make_test_embed.py",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdiscord%2Fmake_test_embed.py",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdiscord%2Fmake_test_embed.py",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/tools%2Fdiscord%2Fmake_test_embed.py?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "cded14b906b16bf5b8250f3367d649f3a4151892",
          "filename": "tools/discord/notes/boot-empty-games.md",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdiscord%2Fnotes%2Fboot-empty-games.md",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdiscord%2Fnotes%2Fboot-empty-games.md",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/tools%2Fdiscord%2Fnotes%2Fboot-empty-games.md?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "482cc44e624a1de1c37b24ec3400676d43b62711",
          "filename": "tools/discord/notes/build114.md",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdiscord%2Fnotes%2Fbuild114.md",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdiscord%2Fnotes%2Fbuild114.md",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/tools%2Fdiscord%2Fnotes%2Fbuild114.md?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "53e3459c9722b4d7c58c306ab3338aed6a85a27e",
          "filename": "tools/discord/notes/psxplace114-ready.txt",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdiscord%2Fnotes%2Fpsxplace114-ready.txt",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdiscord%2Fnotes%2Fpsxplace114-ready.txt",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/tools%2Fdiscord%2Fnotes%2Fpsxplace114-ready.txt?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "8e7274621382ba36364fbd48e4a01b26387d70c3",
          "filename": "tools/discord/notes/psxplace114.txt",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdiscord%2Fnotes%2Fpsxplace114.txt",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdiscord%2Fnotes%2Fpsxplace114.txt",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/tools%2Fdiscord%2Fnotes%2Fpsxplace114.txt?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "4ffc3d19dcb05e713626d7262c5bd3130b19ec80",
          "filename": "tools/discord/notes/release114.md",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdiscord%2Fnotes%2Frelease114.md",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdiscord%2Fnotes%2Frelease114.md",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/tools%2Fdiscord%2Fnotes%2Frelease114.md?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "e8436314addba4f1668373379d2fac44df69bc26",
          "filename": "tools/discord/notes/reply114-berion.md",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdiscord%2Fnotes%2Freply114-berion.md",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdiscord%2Fnotes%2Freply114-berion.md",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/tools%2Fdiscord%2Fnotes%2Freply114-berion.md?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        },
        {
          "sha": "9749ce89e4c97c1324a0d3869fd1fdec389703be",
          "filename": "tools/discord/notes/reply114-fifthfox.md",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/akilluminati47/RIPPS2/blob/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdiscord%2Fnotes%2Freply114-fifthfox.md",
          "raw_url": "https://github.com/akilluminati47/RIPPS2/raw/e1e3504f4a8bfd6b2f88425d284eb845393cfcb9/tools%2Fdiscord%2Fnotes%2Freply114-fifthfox.md",
          "contents_url": "https://api.github.com/repos/akilluminati47/RIPPS2/contents/tools%2Fdiscord%2Fnotes%2Freply114-fifthfox.md?ref=e1e3504f4a8bfd6b2f88425d284eb845393cfcb9"
        }
      ]
    }
  },
  {
    "repo": "DKWDRV/DKWDRV",
    "url": "https://api.github.com/repos/DKWDRV/DKWDRV/compare/31fe2a85f2f3dbc65fd33de31ede4174b9da8b83...261b24914b48483423d8cb37717e7fe1d4b259a3",
    "comparison": {
      "url": "https://api.github.com/repos/DKWDRV/DKWDRV/compare/31fe2a85f2f3dbc65fd33de31ede4174b9da8b83...261b24914b48483423d8cb37717e7fe1d4b259a3",
      "html_url": "https://github.com/DKWDRV/DKWDRV/compare/31fe2a85f2f3dbc65fd33de31ede4174b9da8b83...261b24914b48483423d8cb37717e7fe1d4b259a3",
      "permalink_url": "https://github.com/DKWDRV/DKWDRV/compare/DKWDRV:31fe2a8...DKWDRV:261b249",
      "diff_url": "https://github.com/DKWDRV/DKWDRV/compare/31fe2a85f2f3dbc65fd33de31ede4174b9da8b83...261b24914b48483423d8cb37717e7fe1d4b259a3.diff",
      "patch_url": "https://github.com/DKWDRV/DKWDRV/compare/31fe2a85f2f3dbc65fd33de31ede4174b9da8b83...261b24914b48483423d8cb37717e7fe1d4b259a3.patch",
      "base_commit": {
        "sha": "31fe2a85f2f3dbc65fd33de31ede4174b9da8b83",
        "node_id": "C_kwDOLuGcfdoAKDMxZmUyYTg1ZjJmM2RiYzY1ZmQzM2RlMzFlZGU0MTc0YjlkYThiODM",
        "commit": {
          "author": {
            "name": "DKWDRV",
            "email": "166951701+DKWDRV@users.noreply.github.com",
            "date": "2026-10-23T16:23:55Z"
          },
          "committer": {
            "name": "GitHub",
            "email": "noreply@github.com",
            "date": "2026-10-23T16:23:55Z"
          },
          "message": "Update README.md add 1.7.6t changelog",
          "tree": {
            "sha": "39ce53f783cf7632f43d26a41c07e91fbb3b251b",
            "url": "https://api.github.com/repos/DKWDRV/DKWDRV/git/trees/39ce53f783cf7632f43d26a41c07e91fbb3b251b"
          },
          "url": "https://api.github.com/repos/DKWDRV/DKWDRV/git/commits/31fe2a85f2f3dbc65fd33de31ede4174b9da8b83",
          "comment_count": 0,
          "verification": {
            "verified": true,
            "reason": "valid",
            "signature": "-----BEGIN PGP SIGNATURE-----\n\nwsFcBAABCAAQBQJq24obCRC1aQ7uu5UhlAAAU/4QALCsXWKJT+kK0x/krl1cTdRb\nCgkeWxyQKXb36I63eBlIq1M9oVTRqjrg6EnDvwqIenOmMD55V5dPBeffl5KRd4/F\nj4AeRhTMBUqdl8aZKGvq/ImXwc73bknY6GbKWqgvJYnceK1/AFV0npYo57AKLb7x\nwTnXfwORcxj1RaoTFvWJvS2ziqD9F2EuDptmVdyntH5a7iiCEb4MYhpsT9NNA4cA\nHepZ9E0z8XzckjVC6jNiH7Ph49l7nzClAC+Hv+QoWtr55PDJEC1lPa6tX76kPwVT\n50GHFRj1+PufSnTTVfHGn7mWGYQ0fV2MDxBf1eGH8OmMt1dJLDdvFLI6MSOuG0CK\nKEeSy//5aE+GKEzqM4IQP8EfwPERfT2WUDR2zooVsKkdxayA/uur7OczDdmo/vms\nNbQNYPT70x9vBkZnuT03P/IJZLI1mYyPeaDpJ4WkfEcwK7Pm01CdZeGJ9VQQfPni\n3ArBtPsc9BkSjYYrj6KMXNRUNVGdWw5MgnzIY3WYqVUyc35zBG6kHdJ7EhoJSuDk\nofaAQh/wn0F7vD2GGDJ98ycRTY6RQlRGHg6mJU/GlD/vejw4msxuM/2fIe622UOK\nqH1eY8aU5jQ8kofKdr5ZYZeLYXOTJdU5sp+xJNWQtlxPOjjIaYF7SiJVRBKtikNV\nsQWW8Y69Mk6eAgaNGn3q\n=y94C\n-----END PGP SIGNATURE-----\n",
            "payload": "tree 39ce53f783cf7632f43d26a41c07e91fbb3b251b\nparent 7f41569e9d62981d2b0b35b9d2bd4ab1dbcd5746\nauthor DKWDRV <166951701+DKWDRV@users.noreply.github.com> 1792772635 +0200\ncommitter GitHub <noreply@github.com> 1792772635 +0200\n\nUpdate README.md add 1.7.6t changelog",
            "verified_at": "2026-10-23T16:23:55Z"
          }
        },
        "url": "https://api.github.com/repos/DKWDRV/DKWDRV/commits/31fe2a85f2f3dbc65fd33de31ede4174b9da8b83",
        "html_url": "https://github.com/DKWDRV/DKWDRV/commit/31fe2a85f2f3dbc65fd33de31ede4174b9da8b83",
        "comments_url": "https://api.github.com/repos/DKWDRV/DKWDRV/commits/31fe2a85f2f3dbc65fd33de31ede4174b9da8b83/comments",
        "author": {
          "login": "DKWDRV",
          "id": 166951701,
          "node_id": "U_kgDOCfN7FQ",
          "avatar_url": "https://avatars.githubusercontent.com/u/166951701?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/DKWDRV",
          "html_url": "https://github.com/DKWDRV",
          "followers_url": "https://api.github.com/users/DKWDRV/followers",
          "following_url": "https://api.github.com/users/DKWDRV/following{/other_user}",
          "gists_url": "https://api.github.com/users/DKWDRV/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/DKWDRV/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/DKWDRV/subscriptions",
          "organizations_url": "https://api.github.com/users/DKWDRV/orgs",
          "repos_url": "https://api.github.com/users/DKWDRV/repos",
          "events_url": "https://api.github.com/users/DKWDRV/events{/privacy}",
          "received_events_url": "https://api.github.com/users/DKWDRV/received_events",
          "type": "User",
          "user_view_type": "public",
          "site_admin": false
        },
        "committer": {
          "login": "web-flow",
          "id": 19864447,
          "node_id": "MDQ6VXNlcjE5ODY0NDQ3",
          "avatar_url": "https://avatars.githubusercontent.com/u/19864447?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/web-flow",
          "html_url": "https://github.com/web-flow",
          "followers_url": "https://api.github.com/users/web-flow/followers",
          "following_url": "https://api.github.com/users/web-flow/following{/other_user}",
          "gists_url": "https://api.github.com/users/web-flow/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/web-flow/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/web-flow/subscriptions",
          "organizations_url": "https://api.github.com/users/web-flow/orgs",
          "repos_url": "https://api.github.com/users/web-flow/repos",
          "events_url": "https://api.github.com/users/web-flow/events{/privacy}",
          "received_events_url": "https://api.github.com/users/web-flow/received_events",
          "type": "User",
          "user_view_type": "public",
          "site_admin": false
        },
        "parents": [
          {
            "sha": "7f41569e9d62981d2b0b35b9d2bd4ab1dbcd5746",
            "url": "https://api.github.com/repos/DKWDRV/DKWDRV/commits/7f41569e9d62981d2b0b35b9d2bd4ab1dbcd5746",
            "html_url": "https://github.com/DKWDRV/DKWDRV/commit/7f41569e9d62981d2b0b35b9d2bd4ab1dbcd5746"
          }
        ]
      },
      "merge_base_commit": {
        "sha": "31fe2a85f2f3dbc65fd33de31ede4174b9da8b83",
        "node_id": "C_kwDOLuGcfdoAKDMxZmUyYTg1ZjJmM2RiYzY1ZmQzM2RlMzFlZGU0MTc0YjlkYThiODM",
        "commit": {
          "author": {
            "name": "DKWDRV",
            "email": "166951701+DKWDRV@users.noreply.github.com",
            "date": "2026-10-23T16:23:55Z"
          },
          "committer": {
            "name": "GitHub",
            "email": "noreply@github.com",
            "date": "2026-10-23T16:23:55Z"
          },
          "message": "Update README.md add 1.7.6t changelog",
          "tree": {
            "sha": "39ce53f783cf7632f43d26a41c07e91fbb3b251b",
            "url": "https://api.github.com/repos/DKWDRV/DKWDRV/git/trees/39ce53f783cf7632f43d26a41c07e91fbb3b251b"
          },
          "url": "https://api.github.com/repos/DKWDRV/DKWDRV/git/commits/31fe2a85f2f3dbc65fd33de31ede4174b9da8b83",
          "comment_count": 0,
          "verification": {
            "verified": true,
            "reason": "valid",
            "signature": "-----BEGIN PGP SIGNATURE-----\n\nwsFcBAABCAAQBQJq24obCRC1aQ7uu5UhlAAAU/4QALCsXWKJT+kK0x/krl1cTdRb\nCgkeWxyQKXb36I63eBlIq1M9oVTRqjrg6EnDvwqIenOmMD55V5dPBeffl5KRd4/F\nj4AeRhTMBUqdl8aZKGvq/ImXwc73bknY6GbKWqgvJYnceK1/AFV0npYo57AKLb7x\nwTnXfwORcxj1RaoTFvWJvS2ziqD9F2EuDptmVdyntH5a7iiCEb4MYhpsT9NNA4cA\nHepZ9E0z8XzckjVC6jNiH7Ph49l7nzClAC+Hv+QoWtr55PDJEC1lPa6tX76kPwVT\n50GHFRj1+PufSnTTVfHGn7mWGYQ0fV2MDxBf1eGH8OmMt1dJLDdvFLI6MSOuG0CK\nKEeSy//5aE+GKEzqM4IQP8EfwPERfT2WUDR2zooVsKkdxayA/uur7OczDdmo/vms\nNbQNYPT70x9vBkZnuT03P/IJZLI1mYyPeaDpJ4WkfEcwK7Pm01CdZeGJ9VQQfPni\n3ArBtPsc9BkSjYYrj6KMXNRUNVGdWw5MgnzIY3WYqVUyc35zBG6kHdJ7EhoJSuDk\nofaAQh/wn0F7vD2GGDJ98ycRTY6RQlRGHg6mJU/GlD/vejw4msxuM/2fIe622UOK\nqH1eY8aU5jQ8kofKdr5ZYZeLYXOTJdU5sp+xJNWQtlxPOjjIaYF7SiJVRBKtikNV\nsQWW8Y69Mk6eAgaNGn3q\n=y94C\n-----END PGP SIGNATURE-----\n",
            "payload": "tree 39ce53f783cf7632f43d26a41c07e91fbb3b251b\nparent 7f41569e9d62981d2b0b35b9d2bd4ab1dbcd5746\nauthor DKWDRV <166951701+DKWDRV@users.noreply.github.com> 1792772635 +0200\ncommitter GitHub <noreply@github.com> 1792772635 +0200\n\nUpdate README.md add 1.7.6t changelog",
            "verified_at": "2026-10-23T16:23:55Z"
          }
        },
        "url": "https://api.github.com/repos/DKWDRV/DKWDRV/commits/31fe2a85f2f3dbc65fd33de31ede4174b9da8b83",
        "html_url": "https://github.com/DKWDRV/DKWDRV/commit/31fe2a85f2f3dbc65fd33de31ede4174b9da8b83",
        "comments_url": "https://api.github.com/repos/DKWDRV/DKWDRV/commits/31fe2a85f2f3dbc65fd33de31ede4174b9da8b83/comments",
        "author": {
          "login": "DKWDRV",
          "id": 166951701,
          "node_id": "U_kgDOCfN7FQ",
          "avatar_url": "https://avatars.githubusercontent.com/u/166951701?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/DKWDRV",
          "html_url": "https://github.com/DKWDRV",
          "followers_url": "https://api.github.com/users/DKWDRV/followers",
          "following_url": "https://api.github.com/users/DKWDRV/following{/other_user}",
          "gists_url": "https://api.github.com/users/DKWDRV/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/DKWDRV/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/DKWDRV/subscriptions",
          "organizations_url": "https://api.github.com/users/DKWDRV/orgs",
          "repos_url": "https://api.github.com/users/DKWDRV/repos",
          "events_url": "https://api.github.com/users/DKWDRV/events{/privacy}",
          "received_events_url": "https://api.github.com/users/DKWDRV/received_events",
          "type": "User",
          "user_view_type": "public",
          "site_admin": false
        },
        "committer": {
          "login": "web-flow",
          "id": 19864447,
          "node_id": "MDQ6VXNlcjE5ODY0NDQ3",
          "avatar_url": "https://avatars.githubusercontent.com/u/19864447?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/web-flow",
          "html_url": "https://github.com/web-flow",
          "followers_url": "https://api.github.com/users/web-flow/followers",
          "following_url": "https://api.github.com/users/web-flow/following{/other_user}",
          "gists_url": "https://api.github.com/users/web-flow/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/web-flow/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/web-flow/subscriptions",
          "organizations_url": "https://api.github.com/users/web-flow/orgs",
          "repos_url": "https://api.github.com/users/web-flow/repos",
          "events_url": "https://api.github.com/users/web-flow/events{/privacy}",
          "received_events_url": "https://api.github.com/users/web-flow/received_events",
          "type": "User",
          "user_view_type": "public",
          "site_admin": false
        },
        "parents": [
          {
            "sha": "7f41569e9d62981d2b0b35b9d2bd4ab1dbcd5746",
            "url": "https://api.github.com/repos/DKWDRV/DKWDRV/commits/7f41569e9d62981d2b0b35b9d2bd4ab1dbcd5746",
            "html_url": "https://github.com/DKWDRV/DKWDRV/commit/7f41569e9d62981d2b0b35b9d2bd4ab1dbcd5746"
          }
        ]
      },
      "status": "ahead",
      "ahead_by": 1,
      "behind_by": 0,
      "total_commits": 1,
      "commits": [
        {
          "sha": "261b24914b48483423d8cb37717e7fe1d4b259a3",
          "node_id": "C_kwDOLuGcfdoAKDI2MWIyNDkxNGI0ODQ4MzQyM2Q4Y2IzNzcxN2U3ZmUxZDRiMjU5YTM",
          "commit": {
            "author": {
              "name": "DKWDRV",
              "email": "166951701+DKWDRV@users.noreply.github.com",
              "date": "2026-10-25T22:02:49Z"
            },
            "committer": {
              "name": "GitHub",
              "email": "noreply@github.com",
              "date": "2026-10-25T22:02:49Z"
            },
            "message": "Update README.md add 1.7.6u",
            "tree": {
              "sha": "5580da1739879ad5789ac42a216e2ef1033e41b8",
              "url": "https://api.github.com/repos/DKWDRV/DKWDRV/git/trees/5580da1739879ad5789ac42a216e2ef1033e41b8"
            },
            "url": "https://api.github.com/repos/DKWDRV/DKWDRV/git/commits/261b24914b48483423d8cb37717e7fe1d4b259a3",
            "comment_count": 0,
            "verification": {
              "verified": true,
              "reason": "valid",
              "signature": "-----BEGIN PGP SIGNATURE-----\n\nwsFcBAABCAAQBQJq3nyJCRC1aQ7uu5UhlAAAHyQQAK9L3SFNKfyvHl2m2Xr4sv6M\npr5D5vhhXxZB6mxAFWs4Osn6rVIx5JyBgLCjVg5viA6rM7u8nrGR0/y3NoxeIZP5\n+W+zsK53DMErSWpHbWRethVQ3KQgtHPaC7Y4BIgCrRy2RnuPQ6bSt2cxEztI8970\nFU7DqlQrTZyAQSB5mfP9QKa3wny6KkHHvWMGcQadK0hGzBM6y3arntNpHAG+0PpR\nZCwUZtPRPj2uxndtfE56YgAhRBDfNy+2HAvY3/qiC+FHoJ+S8SDDUZaOJY6fYeUR\n9/0Jy52hI42Ank3CrLGHf3GueSY77eqoyAxUvLXsYLIOhJyXnyGe6he59firqFM9\nV/AG/rzvpAjVcdGXBtCOVzC3MBJzJdEjLgNSC5RA6o0682gdJpmU5Q4Fm3RbRl8w\nUahs/3Vs0DRmowdMgtlUMGT+x0x8NcsPd0NvzXyTd8wvMtq+DNauBTRozHW2vDEe\nWmIxMxkzFdgqCq5381eKuLcK/ZqkoWY2gSkcUNVmBrZismtFno8rzhW/Aq7z3bBW\nJARq+Tjuf6hSiuZfe4PGGfjiqX062jPNwRbdpNXPirduo/akYs6c1I38CQ3B9+pM\nWH0bQhvZlEJ+WSJ5WoT3NLuSSVt3mVWQACGAPxJxrk26BPikYFSSghp8LTV4D5bb\nRuDcawVC+qRJXZnuE863\n=5U42\n-----END PGP SIGNATURE-----\n",
              "payload": "tree 5580da1739879ad5789ac42a216e2ef1033e41b8\nparent 31fe2a85f2f3dbc65fd33de31ede4174b9da8b83\nauthor DKWDRV <166951701+DKWDRV@users.noreply.github.com> 1792965769 +0100\ncommitter GitHub <noreply@github.com> 1792965769 +0100\n\nUpdate README.md add 1.7.6u",
              "verified_at": "2026-10-25T22:02:49Z"
            }
          },
          "url": "https://api.github.com/repos/DKWDRV/DKWDRV/commits/261b24914b48483423d8cb37717e7fe1d4b259a3",
          "html_url": "https://github.com/DKWDRV/DKWDRV/commit/261b24914b48483423d8cb37717e7fe1d4b259a3",
          "comments_url": "https://api.github.com/repos/DKWDRV/DKWDRV/commits/261b24914b48483423d8cb37717e7fe1d4b259a3/comments",
          "author": {
            "login": "DKWDRV",
            "id": 166951701,
            "node_id": "U_kgDOCfN7FQ",
            "avatar_url": "https://avatars.githubusercontent.com/u/166951701?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/DKWDRV",
            "html_url": "https://github.com/DKWDRV",
            "followers_url": "https://api.github.com/users/DKWDRV/followers",
            "following_url": "https://api.github.com/users/DKWDRV/following{/other_user}",
            "gists_url": "https://api.github.com/users/DKWDRV/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/DKWDRV/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/DKWDRV/subscriptions",
            "organizations_url": "https://api.github.com/users/DKWDRV/orgs",
            "repos_url": "https://api.github.com/users/DKWDRV/repos",
            "events_url": "https://api.github.com/users/DKWDRV/events{/privacy}",
            "received_events_url": "https://api.github.com/users/DKWDRV/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "web-flow",
            "id": 19864447,
            "node_id": "MDQ6VXNlcjE5ODY0NDQ3",
            "avatar_url": "https://avatars.githubusercontent.com/u/19864447?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/web-flow",
            "html_url": "https://github.com/web-flow",
            "followers_url": "https://api.github.com/users/web-flow/followers",
            "following_url": "https://api.github.com/users/web-flow/following{/other_user}",
            "gists_url": "https://api.github.com/users/web-flow/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/web-flow/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/web-flow/subscriptions",
            "organizations_url": "https://api.github.com/users/web-flow/orgs",
            "repos_url": "https://api.github.com/users/web-flow/repos",
            "events_url": "https://api.github.com/users/web-flow/events{/privacy}",
            "received_events_url": "https://api.github.com/users/web-flow/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "31fe2a85f2f3dbc65fd33de31ede4174b9da8b83",
              "url": "https://api.github.com/repos/DKWDRV/DKWDRV/commits/31fe2a85f2f3dbc65fd33de31ede4174b9da8b83",
              "html_url": "https://github.com/DKWDRV/DKWDRV/commit/31fe2a85f2f3dbc65fd33de31ede4174b9da8b83"
            }
          ]
        }
      ],
      "files": [
        {
          "sha": "4ef6df9ce8cc30c5fbfda2410a5f085b2b81c73e",
          "filename": "README.md",
          "status": "modified",
          "additions": 14,
          "deletions": 0,
          "changes": 14,
          "blob_url": "https://github.com/DKWDRV/DKWDRV/blob/261b24914b48483423d8cb37717e7fe1d4b259a3/README.md",
          "raw_url": "https://github.com/DKWDRV/DKWDRV/raw/261b24914b48483423d8cb37717e7fe1d4b259a3/README.md",
          "contents_url": "https://api.github.com/repos/DKWDRV/DKWDRV/contents/README.md?ref=261b24914b48483423d8cb37717e7fe1d4b259a3",
          "patch": "@@ -166,6 +166,20 @@ and other SFX might be missing from the game.\n \n ## Changelog\n \n+<details>\n+  <summary>1.7.6u</summary>\n+\n+<br>\n+\n+- USB CD Emu Rewrite STEP #6:\n+- -Major sync times change, impacts everything!\n+- -Many FMVs now run in sync and the seek/read times are more accurate.\n+- -PS1 logo boots normally now, after such a long time.\n+- -Still some issues with DMA timing and usb read polling but I am releasing this as a sneak peak for the future.\n+- -I expect a lot of random crashes/hangs but it is fine till i sort the timing further\n+</details>\n+\n+\n <details>\n   <summary>1.7.6t</summary>\n "
        }
      ]
    }
  },
  {
    "repo": "johnson-cooper/PS2SP",
    "url": "https://api.github.com/repos/johnson-cooper/PS2SP/compare/2c8737b0988b7792bad448560c6af98373d52547...cb040e79dfd467aa99252575c9d6fd97684116de",
    "comparison": {
      "url": "https://api.github.com/repos/johnson-cooper/PS2SP/compare/2c8737b0988b7792bad448560c6af98373d52547...cb040e79dfd467aa99252575c9d6fd97684116de",
      "html_url": "https://github.com/johnson-cooper/PS2SP/compare/2c8737b0988b7792bad448560c6af98373d52547...cb040e79dfd467aa99252575c9d6fd97684116de",
      "permalink_url": "https://github.com/johnson-cooper/PS2SP/compare/johnson-cooper:2c8737b...johnson-cooper:cb040e7",
      "diff_url": "https://github.com/johnson-cooper/PS2SP/compare/2c8737b0988b7792bad448560c6af98373d52547...cb040e79dfd467aa99252575c9d6fd97684116de.diff",
      "patch_url": "https://github.com/johnson-cooper/PS2SP/compare/2c8737b0988b7792bad448560c6af98373d52547...cb040e79dfd467aa99252575c9d6fd97684116de.patch",
      "base_commit": {
        "sha": "2c8737b0988b7792bad448560c6af98373d52547",
        "node_id": "C_kwDOUxThN9oAKDJjODczN2IwOTg4Yjc3OTJiYWQ0NDg1NjBjNmFmOTgzNzNkNTI1NDc",
        "commit": {
          "author": {
            "name": "ps2sp-bot",
            "email": "41898282+github-actions[bot]@users.noreply.github.com",
            "date": "2026-10-24T19:03:10Z"
          },
          "committer": {
            "name": "ps2sp-bot",
            "email": "41898282+github-actions[bot]@users.noreply.github.com",
            "date": "2026-10-24T19:03:10Z"
          },
          "message": "[CF-Pages-Skip] chore(catalog): advance PS2 discovery",
          "tree": {
            "sha": "5dcfb6f183263e264abdfabdfb2f5f0e5163af59",
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/5dcfb6f183263e264abdfabdfb2f5f0e5163af59"
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/2c8737b0988b7792bad448560c6af98373d52547",
          "comment_count": 0,
          "verification": {
            "verified": false,
            "reason": "unsigned",
            "signature": null,
            "payload": null,
            "verified_at": null
          }
        },
        "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/2c8737b0988b7792bad448560c6af98373d52547",
        "html_url": "https://github.com/johnson-cooper/PS2SP/commit/2c8737b0988b7792bad448560c6af98373d52547",
        "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/2c8737b0988b7792bad448560c6af98373d52547/comments",
        "author": {
          "login": "github-actions[bot]",
          "id": 41898282,
          "node_id": "MDM6Qm90NDE4OTgyODI=",
          "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/github-actions%5Bbot%5D",
          "html_url": "https://github.com/apps/github-actions",
          "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
          "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
          "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
          "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
          "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
          "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
          "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
          "type": "Bot",
          "user_view_type": "public",
          "site_admin": false
        },
        "committer": {
          "login": "github-actions[bot]",
          "id": 41898282,
          "node_id": "MDM6Qm90NDE4OTgyODI=",
          "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/github-actions%5Bbot%5D",
          "html_url": "https://github.com/apps/github-actions",
          "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
          "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
          "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
          "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
          "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
          "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
          "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
          "type": "Bot",
          "user_view_type": "public",
          "site_admin": false
        },
        "parents": [
          {
            "sha": "ceba5903ea0070ffa5cd3038c77699db43ddbabe",
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/ceba5903ea0070ffa5cd3038c77699db43ddbabe",
            "html_url": "https://github.com/johnson-cooper/PS2SP/commit/ceba5903ea0070ffa5cd3038c77699db43ddbabe"
          }
        ]
      },
      "merge_base_commit": {
        "sha": "2c8737b0988b7792bad448560c6af98373d52547",
        "node_id": "C_kwDOUxThN9oAKDJjODczN2IwOTg4Yjc3OTJiYWQ0NDg1NjBjNmFmOTgzNzNkNTI1NDc",
        "commit": {
          "author": {
            "name": "ps2sp-bot",
            "email": "41898282+github-actions[bot]@users.noreply.github.com",
            "date": "2026-10-24T19:03:10Z"
          },
          "committer": {
            "name": "ps2sp-bot",
            "email": "41898282+github-actions[bot]@users.noreply.github.com",
            "date": "2026-10-24T19:03:10Z"
          },
          "message": "[CF-Pages-Skip] chore(catalog): advance PS2 discovery",
          "tree": {
            "sha": "5dcfb6f183263e264abdfabdfb2f5f0e5163af59",
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/5dcfb6f183263e264abdfabdfb2f5f0e5163af59"
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/2c8737b0988b7792bad448560c6af98373d52547",
          "comment_count": 0,
          "verification": {
            "verified": false,
            "reason": "unsigned",
            "signature": null,
            "payload": null,
            "verified_at": null
          }
        },
        "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/2c8737b0988b7792bad448560c6af98373d52547",
        "html_url": "https://github.com/johnson-cooper/PS2SP/commit/2c8737b0988b7792bad448560c6af98373d52547",
        "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/2c8737b0988b7792bad448560c6af98373d52547/comments",
        "author": {
          "login": "github-actions[bot]",
          "id": 41898282,
          "node_id": "MDM6Qm90NDE4OTgyODI=",
          "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/github-actions%5Bbot%5D",
          "html_url": "https://github.com/apps/github-actions",
          "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
          "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
          "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
          "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
          "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
          "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
          "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
          "type": "Bot",
          "user_view_type": "public",
          "site_admin": false
        },
        "committer": {
          "login": "github-actions[bot]",
          "id": 41898282,
          "node_id": "MDM6Qm90NDE4OTgyODI=",
          "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/github-actions%5Bbot%5D",
          "html_url": "https://github.com/apps/github-actions",
          "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
          "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
          "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
          "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
          "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
          "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
          "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
          "type": "Bot",
          "user_view_type": "public",
          "site_admin": false
        },
        "parents": [
          {
            "sha": "ceba5903ea0070ffa5cd3038c77699db43ddbabe",
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/ceba5903ea0070ffa5cd3038c77699db43ddbabe",
            "html_url": "https://github.com/johnson-cooper/PS2SP/commit/ceba5903ea0070ffa5cd3038c77699db43ddbabe"
          }
        ]
      },
      "status": "ahead",
      "ahead_by": 43,
      "behind_by": 0,
      "total_commits": 43,
      "commits": [
        {
          "sha": "c6ac3653929f69c5692683cf860140882a412654",
          "node_id": "C_kwDOUxThN9oAKGM2YWMzNjUzOTI5ZjY5YzU2OTI2ODNjZjg2MDE0MDg4MmE0MTI2NTQ",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-24T20:07:42Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-24T20:07:42Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance external Git discovery",
            "tree": {
              "sha": "878cdc01cb986f1917c3627c1b9b78dbab256e9f",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/878cdc01cb986f1917c3627c1b9b78dbab256e9f"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/c6ac3653929f69c5692683cf860140882a412654",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/c6ac3653929f69c5692683cf860140882a412654",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/c6ac3653929f69c5692683cf860140882a412654",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/c6ac3653929f69c5692683cf860140882a412654/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "2c8737b0988b7792bad448560c6af98373d52547",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/2c8737b0988b7792bad448560c6af98373d52547",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/2c8737b0988b7792bad448560c6af98373d52547"
            }
          ]
        },
        {
          "sha": "394ce3341321dfce32d8a922ffb76e5e4033a8f3",
          "node_id": "C_kwDOUxThN9oAKDM5NGNlMzM0MTMyMWRmY2UzMmQ4YTkyMmZmYjc2ZTVlNDAzM2E4ZjM",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-24T20:40:10Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-24T20:40:10Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance PS2 web discovery",
            "tree": {
              "sha": "d1bdc88d7f8b4547fae53f0248f6f5c4b8ccc508",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/d1bdc88d7f8b4547fae53f0248f6f5c4b8ccc508"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/394ce3341321dfce32d8a922ffb76e5e4033a8f3",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/394ce3341321dfce32d8a922ffb76e5e4033a8f3",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/394ce3341321dfce32d8a922ffb76e5e4033a8f3",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/394ce3341321dfce32d8a922ffb76e5e4033a8f3/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "c6ac3653929f69c5692683cf860140882a412654",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/c6ac3653929f69c5692683cf860140882a412654",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/c6ac3653929f69c5692683cf860140882a412654"
            }
          ]
        },
        {
          "sha": "f40d1576a876677a2a13340f8b7883146d1c99c0",
          "node_id": "C_kwDOUxThN9oAKGY0MGQxNTc2YTg3NjY3N2EyYTEzMzQwZjhiNzg4MzE0NmQxYzk5YzA",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-24T20:52:07Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-24T20:52:07Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): synchronize project metadata",
            "tree": {
              "sha": "4cbac75f7ed1245d34946c946a1da7056775e625",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/4cbac75f7ed1245d34946c946a1da7056775e625"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/f40d1576a876677a2a13340f8b7883146d1c99c0",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/f40d1576a876677a2a13340f8b7883146d1c99c0",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/f40d1576a876677a2a13340f8b7883146d1c99c0",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/f40d1576a876677a2a13340f8b7883146d1c99c0/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "394ce3341321dfce32d8a922ffb76e5e4033a8f3",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/394ce3341321dfce32d8a922ffb76e5e4033a8f3",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/394ce3341321dfce32d8a922ffb76e5e4033a8f3"
            }
          ]
        },
        {
          "sha": "2fa06863b9e6a61b2a0c1b566e55b57c0a460353",
          "node_id": "C_kwDOUxThN9oAKDJmYTA2ODYzYjllNmE2MWIyYTBjMWI1NjZlNTViNTdjMGE0NjAzNTM",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-24T20:59:22Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-24T20:59:22Z"
            },
            "message": "chore(deploy): publish batched PS2SP updates",
            "tree": {
              "sha": "4cbac75f7ed1245d34946c946a1da7056775e625",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/4cbac75f7ed1245d34946c946a1da7056775e625"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/2fa06863b9e6a61b2a0c1b566e55b57c0a460353",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/2fa06863b9e6a61b2a0c1b566e55b57c0a460353",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/2fa06863b9e6a61b2a0c1b566e55b57c0a460353",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/2fa06863b9e6a61b2a0c1b566e55b57c0a460353/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "f40d1576a876677a2a13340f8b7883146d1c99c0",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/f40d1576a876677a2a13340f8b7883146d1c99c0",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/f40d1576a876677a2a13340f8b7883146d1c99c0"
            }
          ]
        },
        {
          "sha": "8ffa9d577aa20e157313ca71cf05f7b2e636ab7e",
          "node_id": "C_kwDOUxThN9oAKDhmZmE5ZDU3N2FhMjBlMTU3MzEzY2E3MWNmMDVmN2IyZTYzNmFiN2U",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-24T21:09:47Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-24T21:09:47Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance GitHub depth discovery",
            "tree": {
              "sha": "125c33fed929a8e9c07483d8f040743be18dac5c",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/125c33fed929a8e9c07483d8f040743be18dac5c"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/8ffa9d577aa20e157313ca71cf05f7b2e636ab7e",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/8ffa9d577aa20e157313ca71cf05f7b2e636ab7e",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/8ffa9d577aa20e157313ca71cf05f7b2e636ab7e",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/8ffa9d577aa20e157313ca71cf05f7b2e636ab7e/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "2fa06863b9e6a61b2a0c1b566e55b57c0a460353",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/2fa06863b9e6a61b2a0c1b566e55b57c0a460353",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/2fa06863b9e6a61b2a0c1b566e55b57c0a460353"
            }
          ]
        },
        {
          "sha": "fbbcb79b049d4813d9ac78b79a2595e05471ca3d",
          "node_id": "C_kwDOUxThN9oAKGZiYmNiNzliMDQ5ZDQ4MTNkOWFjNzhiNzlhMjU5NWUwNTQ3MWNhM2Q",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-24T21:56:25Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-24T21:56:25Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance PS2 discovery",
            "tree": {
              "sha": "cbe46c9b8e57c94890625c74034167bcb3a93160",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/cbe46c9b8e57c94890625c74034167bcb3a93160"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/fbbcb79b049d4813d9ac78b79a2595e05471ca3d",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/fbbcb79b049d4813d9ac78b79a2595e05471ca3d",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/fbbcb79b049d4813d9ac78b79a2595e05471ca3d",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/fbbcb79b049d4813d9ac78b79a2595e05471ca3d/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "8ffa9d577aa20e157313ca71cf05f7b2e636ab7e",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/8ffa9d577aa20e157313ca71cf05f7b2e636ab7e",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/8ffa9d577aa20e157313ca71cf05f7b2e636ab7e"
            }
          ]
        },
        {
          "sha": "5eac9c27d6bc18e5613720fd2bd4e487a0202014",
          "node_id": "C_kwDOUxThN9oAKDVlYWM5YzI3ZDZiYzE4ZTU2MTM3MjBmZDJiZDRlNDg3YTAyMDIwMTQ",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-24T22:51:14Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-24T22:51:14Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance external Git discovery",
            "tree": {
              "sha": "c31ae3477bcb8c720463cdf3942516b7620b3ec2",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/c31ae3477bcb8c720463cdf3942516b7620b3ec2"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/5eac9c27d6bc18e5613720fd2bd4e487a0202014",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/5eac9c27d6bc18e5613720fd2bd4e487a0202014",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/5eac9c27d6bc18e5613720fd2bd4e487a0202014",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/5eac9c27d6bc18e5613720fd2bd4e487a0202014/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "fbbcb79b049d4813d9ac78b79a2595e05471ca3d",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/fbbcb79b049d4813d9ac78b79a2595e05471ca3d",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/fbbcb79b049d4813d9ac78b79a2595e05471ca3d"
            }
          ]
        },
        {
          "sha": "a530dbc164451091d717b12555245bd68bb196c9",
          "node_id": "C_kwDOUxThN9oAKGE1MzBkYmMxNjQ0NTEwOTFkNzE3YjEyNTU1MjQ1YmQ2OGJiMTk2Yzk",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-24T23:06:20Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-24T23:06:20Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance PS2 web discovery",
            "tree": {
              "sha": "e423de8e0082ac9ce928b068fe48ff4d12a2012f",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/e423de8e0082ac9ce928b068fe48ff4d12a2012f"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/a530dbc164451091d717b12555245bd68bb196c9",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/a530dbc164451091d717b12555245bd68bb196c9",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/a530dbc164451091d717b12555245bd68bb196c9",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/a530dbc164451091d717b12555245bd68bb196c9/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "5eac9c27d6bc18e5613720fd2bd4e487a0202014",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/5eac9c27d6bc18e5613720fd2bd4e487a0202014",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/5eac9c27d6bc18e5613720fd2bd4e487a0202014"
            }
          ]
        },
        {
          "sha": "2a5db910300aca33ec4e2afa0553b64478805984",
          "node_id": "C_kwDOUxThN9oAKDJhNWRiOTEwMzAwYWNhMzNlYzRlMmFmYTA1NTNiNjQ0Nzg4MDU5ODQ",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-24T23:17:28Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-24T23:17:28Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): synchronize project metadata",
            "tree": {
              "sha": "aaac235e1e5206af8b42960c0404b218bd929500",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/aaac235e1e5206af8b42960c0404b218bd929500"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/2a5db910300aca33ec4e2afa0553b64478805984",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/2a5db910300aca33ec4e2afa0553b64478805984",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/2a5db910300aca33ec4e2afa0553b64478805984",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/2a5db910300aca33ec4e2afa0553b64478805984/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "a530dbc164451091d717b12555245bd68bb196c9",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/a530dbc164451091d717b12555245bd68bb196c9",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/a530dbc164451091d717b12555245bd68bb196c9"
            }
          ]
        },
        {
          "sha": "f20cdb287a116a772ecadb09a56bd0bd68d834cb",
          "node_id": "C_kwDOUxThN9oAKGYyMGNkYjI4N2ExMTZhNzcyZWNhZGIwOWE1NmJkMGJkNjhkODM0Y2I",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-24T23:40:12Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-24T23:40:12Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance GitHub depth discovery",
            "tree": {
              "sha": "b505d08117b587cd35d22d702162fcb4441893f0",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/b505d08117b587cd35d22d702162fcb4441893f0"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/f20cdb287a116a772ecadb09a56bd0bd68d834cb",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/f20cdb287a116a772ecadb09a56bd0bd68d834cb",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/f20cdb287a116a772ecadb09a56bd0bd68d834cb",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/f20cdb287a116a772ecadb09a56bd0bd68d834cb/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "2a5db910300aca33ec4e2afa0553b64478805984",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/2a5db910300aca33ec4e2afa0553b64478805984",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/2a5db910300aca33ec4e2afa0553b64478805984"
            }
          ]
        },
        {
          "sha": "f77a70c8cc2f3adabd8cd5cfc38aff976dc5ea4d",
          "node_id": "C_kwDOUxThN9oAKGY3N2E3MGM4Y2MyZjNhZGFiZDhjZDVjZmMzOGFmZjk3NmRjNWVhNGQ",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T00:34:21Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T00:34:21Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance PS2 discovery",
            "tree": {
              "sha": "94f4d56cf481c3c2cc5a3fb4442abfd9595915ba",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/94f4d56cf481c3c2cc5a3fb4442abfd9595915ba"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/f77a70c8cc2f3adabd8cd5cfc38aff976dc5ea4d",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/f77a70c8cc2f3adabd8cd5cfc38aff976dc5ea4d",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/f77a70c8cc2f3adabd8cd5cfc38aff976dc5ea4d",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/f77a70c8cc2f3adabd8cd5cfc38aff976dc5ea4d/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "f20cdb287a116a772ecadb09a56bd0bd68d834cb",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/f20cdb287a116a772ecadb09a56bd0bd68d834cb",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/f20cdb287a116a772ecadb09a56bd0bd68d834cb"
            }
          ]
        },
        {
          "sha": "01dce2af72660e83d3612248b0c843c493282ce0",
          "node_id": "C_kwDOUxThN9oAKDAxZGNlMmFmNzI2NjBlODNkMzYxMjI0OGIwYzg0M2M0OTMyODJjZTA",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T00:46:04Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T00:46:04Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance external Git discovery",
            "tree": {
              "sha": "9c9f13b2ce41b07e582fd6c24c12d0ef2013bf55",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/9c9f13b2ce41b07e582fd6c24c12d0ef2013bf55"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/01dce2af72660e83d3612248b0c843c493282ce0",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/01dce2af72660e83d3612248b0c843c493282ce0",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/01dce2af72660e83d3612248b0c843c493282ce0",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/01dce2af72660e83d3612248b0c843c493282ce0/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "f77a70c8cc2f3adabd8cd5cfc38aff976dc5ea4d",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/f77a70c8cc2f3adabd8cd5cfc38aff976dc5ea4d",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/f77a70c8cc2f3adabd8cd5cfc38aff976dc5ea4d"
            }
          ]
        },
        {
          "sha": "0fd6efb3713b08be8beadd44eaf5c160c888ca59",
          "node_id": "C_kwDOUxThN9oAKDBmZDZlZmIzNzEzYjA4YmU4YmVhZGQ0NGVhZjVjMTYwYzg4OGNhNTk",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T01:05:02Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T01:05:02Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance PS2 web discovery",
            "tree": {
              "sha": "e7755f5262bfc35d0e3ab31d6e40aa14c3a6116d",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/e7755f5262bfc35d0e3ab31d6e40aa14c3a6116d"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/0fd6efb3713b08be8beadd44eaf5c160c888ca59",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/0fd6efb3713b08be8beadd44eaf5c160c888ca59",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/0fd6efb3713b08be8beadd44eaf5c160c888ca59",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/0fd6efb3713b08be8beadd44eaf5c160c888ca59/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "01dce2af72660e83d3612248b0c843c493282ce0",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/01dce2af72660e83d3612248b0c843c493282ce0",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/01dce2af72660e83d3612248b0c843c493282ce0"
            }
          ]
        },
        {
          "sha": "393b3997eebc16704078d637e1c0a6c4231a03e0",
          "node_id": "C_kwDOUxThN9oAKDM5M2IzOTk3ZWViYzE2NzA0MDc4ZDYzN2UxYzBhNmM0MjMxYTAzZTA",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T01:16:24Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T01:16:24Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): synchronize project metadata",
            "tree": {
              "sha": "6ddeec85ec36309b27541124176cc3d493c6d126",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/6ddeec85ec36309b27541124176cc3d493c6d126"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/393b3997eebc16704078d637e1c0a6c4231a03e0",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/393b3997eebc16704078d637e1c0a6c4231a03e0",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/393b3997eebc16704078d637e1c0a6c4231a03e0",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/393b3997eebc16704078d637e1c0a6c4231a03e0/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "0fd6efb3713b08be8beadd44eaf5c160c888ca59",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/0fd6efb3713b08be8beadd44eaf5c160c888ca59",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/0fd6efb3713b08be8beadd44eaf5c160c888ca59"
            }
          ]
        },
        {
          "sha": "a569031f1a6c57a456ade66470d8e19f945c8346",
          "node_id": "C_kwDOUxThN9oAKGE1NjkwMzFmMWE2YzU3YTQ1NmFkZTY2NDcwZDhlMTlmOTQ1YzgzNDY",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T04:57:31Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T04:57:31Z"
            },
            "message": "chore(deploy): publish batched PS2SP updates",
            "tree": {
              "sha": "6ddeec85ec36309b27541124176cc3d493c6d126",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/6ddeec85ec36309b27541124176cc3d493c6d126"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/a569031f1a6c57a456ade66470d8e19f945c8346",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/a569031f1a6c57a456ade66470d8e19f945c8346",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/a569031f1a6c57a456ade66470d8e19f945c8346",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/a569031f1a6c57a456ade66470d8e19f945c8346/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "393b3997eebc16704078d637e1c0a6c4231a03e0",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/393b3997eebc16704078d637e1c0a6c4231a03e0",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/393b3997eebc16704078d637e1c0a6c4231a03e0"
            }
          ]
        },
        {
          "sha": "1e391e9f6f493f8373e5b638b5500885825efd91",
          "node_id": "C_kwDOUxThN9oAKDFlMzkxZTlmNmY0OTNmODM3M2U1YjYzOGI1NTAwODg1ODI1ZWZkOTE",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T05:13:49Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T05:13:49Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance GitHub depth discovery",
            "tree": {
              "sha": "85d8429f618aad76bc76dec17bd2fbc217cda141",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/85d8429f618aad76bc76dec17bd2fbc217cda141"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/1e391e9f6f493f8373e5b638b5500885825efd91",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/1e391e9f6f493f8373e5b638b5500885825efd91",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/1e391e9f6f493f8373e5b638b5500885825efd91",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/1e391e9f6f493f8373e5b638b5500885825efd91/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "a569031f1a6c57a456ade66470d8e19f945c8346",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/a569031f1a6c57a456ade66470d8e19f945c8346",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/a569031f1a6c57a456ade66470d8e19f945c8346"
            }
          ]
        },
        {
          "sha": "00f0c066be9833ddb94919cf1b00319107ebf3a5",
          "node_id": "C_kwDOUxThN9oAKDAwZjBjMDY2YmU5ODMzZGRiOTQ5MTljZjFiMDAzMTkxMDdlYmYzYTU",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T05:21:55Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T05:21:55Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance PS2 discovery",
            "tree": {
              "sha": "c2c617fa72f3a92ad0e11c283a317076a9ffb2ab",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/c2c617fa72f3a92ad0e11c283a317076a9ffb2ab"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/00f0c066be9833ddb94919cf1b00319107ebf3a5",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/00f0c066be9833ddb94919cf1b00319107ebf3a5",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/00f0c066be9833ddb94919cf1b00319107ebf3a5",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/00f0c066be9833ddb94919cf1b00319107ebf3a5/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "1e391e9f6f493f8373e5b638b5500885825efd91",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/1e391e9f6f493f8373e5b638b5500885825efd91",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/1e391e9f6f493f8373e5b638b5500885825efd91"
            }
          ]
        },
        {
          "sha": "fa1f66840b578fb69c53178a1f0cc6aef8967284",
          "node_id": "C_kwDOUxThN9oAKGZhMWY2Njg0MGI1NzhmYjY5YzUzMTc4YTFmMGNjNmFlZjg5NjcyODQ",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T05:37:08Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T05:37:08Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance external Git discovery",
            "tree": {
              "sha": "56ff431d2688d85656da916bbd3a404e62950727",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/56ff431d2688d85656da916bbd3a404e62950727"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/fa1f66840b578fb69c53178a1f0cc6aef8967284",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/fa1f66840b578fb69c53178a1f0cc6aef8967284",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/fa1f66840b578fb69c53178a1f0cc6aef8967284",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/fa1f66840b578fb69c53178a1f0cc6aef8967284/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "00f0c066be9833ddb94919cf1b00319107ebf3a5",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/00f0c066be9833ddb94919cf1b00319107ebf3a5",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/00f0c066be9833ddb94919cf1b00319107ebf3a5"
            }
          ]
        },
        {
          "sha": "542b794622a9592789ff660c00f43e539fdcf11b",
          "node_id": "C_kwDOUxThN9oAKDU0MmI3OTQ2MjJhOTU5Mjc4OWZmNjYwYzAwZjQzZTUzOWZkY2YxMWI",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T05:58:41Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T05:58:41Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance PS2 web discovery",
            "tree": {
              "sha": "ff8ec89ce0238523eb9431e0ba95446d7d778246",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/ff8ec89ce0238523eb9431e0ba95446d7d778246"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/542b794622a9592789ff660c00f43e539fdcf11b",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/542b794622a9592789ff660c00f43e539fdcf11b",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/542b794622a9592789ff660c00f43e539fdcf11b",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/542b794622a9592789ff660c00f43e539fdcf11b/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "fa1f66840b578fb69c53178a1f0cc6aef8967284",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/fa1f66840b578fb69c53178a1f0cc6aef8967284",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/fa1f66840b578fb69c53178a1f0cc6aef8967284"
            }
          ]
        },
        {
          "sha": "bc0123b500a6c07e23342fedb355da69fdc9fddc",
          "node_id": "C_kwDOUxThN9oAKGJjMDEyM2I1MDBhNmMwN2UyMzM0MmZlZGIzNTVkYTY5ZmRjOWZkZGM",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T06:13:32Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T06:13:32Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): synchronize project metadata",
            "tree": {
              "sha": "a5b6c38bb07ebe042c529e30d223cb51f0604081",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/a5b6c38bb07ebe042c529e30d223cb51f0604081"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/bc0123b500a6c07e23342fedb355da69fdc9fddc",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/bc0123b500a6c07e23342fedb355da69fdc9fddc",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/bc0123b500a6c07e23342fedb355da69fdc9fddc",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/bc0123b500a6c07e23342fedb355da69fdc9fddc/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "542b794622a9592789ff660c00f43e539fdcf11b",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/542b794622a9592789ff660c00f43e539fdcf11b",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/542b794622a9592789ff660c00f43e539fdcf11b"
            }
          ]
        },
        {
          "sha": "a87d715d9b6756b490de1a6ef716b4f4ff153cb8",
          "node_id": "C_kwDOUxThN9oAKGE4N2Q3MTVkOWI2NzU2YjQ5MGRlMWE2ZWY3MTZiNGY0ZmYxNTNjYjg",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T10:06:40Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T10:06:40Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance GitHub depth discovery",
            "tree": {
              "sha": "ca20acf7aeeb0dd99e44e043dc27d154b0f11f2b",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/ca20acf7aeeb0dd99e44e043dc27d154b0f11f2b"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/a87d715d9b6756b490de1a6ef716b4f4ff153cb8",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/a87d715d9b6756b490de1a6ef716b4f4ff153cb8",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/a87d715d9b6756b490de1a6ef716b4f4ff153cb8",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/a87d715d9b6756b490de1a6ef716b4f4ff153cb8/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "bc0123b500a6c07e23342fedb355da69fdc9fddc",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/bc0123b500a6c07e23342fedb355da69fdc9fddc",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/bc0123b500a6c07e23342fedb355da69fdc9fddc"
            }
          ]
        },
        {
          "sha": "b77753f3ab8bdeca15de6ee65e800c837d6d0e3b",
          "node_id": "C_kwDOUxThN9oAKGI3Nzc1M2YzYWI4YmRlY2ExNWRlNmVlNjVlODAwYzgzN2Q2ZDBlM2I",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T10:12:04Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T10:12:04Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance PS2 discovery",
            "tree": {
              "sha": "6e5c6c25d635d5c6e9371630ebe118127943b0a2",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/6e5c6c25d635d5c6e9371630ebe118127943b0a2"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/b77753f3ab8bdeca15de6ee65e800c837d6d0e3b",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/b77753f3ab8bdeca15de6ee65e800c837d6d0e3b",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/b77753f3ab8bdeca15de6ee65e800c837d6d0e3b",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/b77753f3ab8bdeca15de6ee65e800c837d6d0e3b/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "a87d715d9b6756b490de1a6ef716b4f4ff153cb8",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/a87d715d9b6756b490de1a6ef716b4f4ff153cb8",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/a87d715d9b6756b490de1a6ef716b4f4ff153cb8"
            }
          ]
        },
        {
          "sha": "e7d6bbd47b9cf6dfd056f7e2aa228667b676ee21",
          "node_id": "C_kwDOUxThN9oAKGU3ZDZiYmQ0N2I5Y2Y2ZGZkMDU2ZjdlMmFhMjI4NjY3YjY3NmVlMjE",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T10:36:20Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T10:36:20Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance external Git discovery",
            "tree": {
              "sha": "77b4f8eec05215e7653066f8b829818fa123e1f9",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/77b4f8eec05215e7653066f8b829818fa123e1f9"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/e7d6bbd47b9cf6dfd056f7e2aa228667b676ee21",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/e7d6bbd47b9cf6dfd056f7e2aa228667b676ee21",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/e7d6bbd47b9cf6dfd056f7e2aa228667b676ee21",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/e7d6bbd47b9cf6dfd056f7e2aa228667b676ee21/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "b77753f3ab8bdeca15de6ee65e800c837d6d0e3b",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/b77753f3ab8bdeca15de6ee65e800c837d6d0e3b",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/b77753f3ab8bdeca15de6ee65e800c837d6d0e3b"
            }
          ]
        },
        {
          "sha": "5b17eccbfeb11b8580690bbf47f5f6de8fd679af",
          "node_id": "C_kwDOUxThN9oAKDViMTdlY2NiZmViMTFiODU4MDY5MGJiZjQ3ZjVmNmRlOGZkNjc5YWY",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T11:14:35Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T11:14:35Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance PS2 web discovery",
            "tree": {
              "sha": "b6ce4a7af6f394a837d75fc8e8ebd638a9bb3683",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/b6ce4a7af6f394a837d75fc8e8ebd638a9bb3683"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/5b17eccbfeb11b8580690bbf47f5f6de8fd679af",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/5b17eccbfeb11b8580690bbf47f5f6de8fd679af",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/5b17eccbfeb11b8580690bbf47f5f6de8fd679af",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/5b17eccbfeb11b8580690bbf47f5f6de8fd679af/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "e7d6bbd47b9cf6dfd056f7e2aa228667b676ee21",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/e7d6bbd47b9cf6dfd056f7e2aa228667b676ee21",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/e7d6bbd47b9cf6dfd056f7e2aa228667b676ee21"
            }
          ]
        },
        {
          "sha": "976c135fa4c309140a13cc0a84381ead76523fe1",
          "node_id": "C_kwDOUxThN9oAKDk3NmMxMzVmYTRjMzA5MTQwYTEzY2MwYTg0MzgxZWFkNzY1MjNmZTE",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T11:31:59Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T11:31:59Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): synchronize project metadata",
            "tree": {
              "sha": "ad06595a7977562b56ba97ac1b8aad5feb1d83fd",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/ad06595a7977562b56ba97ac1b8aad5feb1d83fd"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/976c135fa4c309140a13cc0a84381ead76523fe1",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/976c135fa4c309140a13cc0a84381ead76523fe1",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/976c135fa4c309140a13cc0a84381ead76523fe1",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/976c135fa4c309140a13cc0a84381ead76523fe1/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "5b17eccbfeb11b8580690bbf47f5f6de8fd679af",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/5b17eccbfeb11b8580690bbf47f5f6de8fd679af",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/5b17eccbfeb11b8580690bbf47f5f6de8fd679af"
            }
          ]
        },
        {
          "sha": "d00dbaa5753211c2148ba97f7417773cd8ab7703",
          "node_id": "C_kwDOUxThN9oAKGQwMGRiYWE1NzUzMjExYzIxNDhiYTk3Zjc0MTc3NzNjZDhhYjc3MDM",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T11:47:41Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T11:47:41Z"
            },
            "message": "chore(deploy): publish batched PS2SP updates",
            "tree": {
              "sha": "ad06595a7977562b56ba97ac1b8aad5feb1d83fd",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/ad06595a7977562b56ba97ac1b8aad5feb1d83fd"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/d00dbaa5753211c2148ba97f7417773cd8ab7703",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/d00dbaa5753211c2148ba97f7417773cd8ab7703",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/d00dbaa5753211c2148ba97f7417773cd8ab7703",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/d00dbaa5753211c2148ba97f7417773cd8ab7703/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "976c135fa4c309140a13cc0a84381ead76523fe1",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/976c135fa4c309140a13cc0a84381ead76523fe1",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/976c135fa4c309140a13cc0a84381ead76523fe1"
            }
          ]
        },
        {
          "sha": "7468b678fc4204acb94a475671875cbd5b8edce6",
          "node_id": "C_kwDOUxThN9oAKDc0NjhiNjc4ZmM0MjA0YWNiOTRhNDc1NjcxODc1Y2JkNWI4ZWRjZTY",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T14:36:39Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T14:36:39Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance PS2 discovery",
            "tree": {
              "sha": "859da8bf27b738f51d296cfc537b0b89ebdd5d45",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/859da8bf27b738f51d296cfc537b0b89ebdd5d45"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/7468b678fc4204acb94a475671875cbd5b8edce6",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/7468b678fc4204acb94a475671875cbd5b8edce6",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/7468b678fc4204acb94a475671875cbd5b8edce6",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/7468b678fc4204acb94a475671875cbd5b8edce6/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "d00dbaa5753211c2148ba97f7417773cd8ab7703",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/d00dbaa5753211c2148ba97f7417773cd8ab7703",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/d00dbaa5753211c2148ba97f7417773cd8ab7703"
            }
          ]
        },
        {
          "sha": "8916f4e1f469335da80a2cf49382b2639f4be4d5",
          "node_id": "C_kwDOUxThN9oAKDg5MTZmNGUxZjQ2OTMzNWRhODBhMmNmNDkzODJiMjYzOWY0YmU0ZDU",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T14:39:09Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T14:39:10Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance GitHub depth discovery",
            "tree": {
              "sha": "6b259134344b06ea9636a95841d8b3d3643d69ee",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/6b259134344b06ea9636a95841d8b3d3643d69ee"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/8916f4e1f469335da80a2cf49382b2639f4be4d5",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/8916f4e1f469335da80a2cf49382b2639f4be4d5",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/8916f4e1f469335da80a2cf49382b2639f4be4d5",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/8916f4e1f469335da80a2cf49382b2639f4be4d5/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "7468b678fc4204acb94a475671875cbd5b8edce6",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/7468b678fc4204acb94a475671875cbd5b8edce6",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/7468b678fc4204acb94a475671875cbd5b8edce6"
            }
          ]
        },
        {
          "sha": "3f8b7bc2ec02ec9adbf96be9a11d758adeba6e51",
          "node_id": "C_kwDOUxThN9oAKDNmOGI3YmMyZWMwMmVjOWFkYmY5NmJlOWExMWQ3NThhZGViYTZlNTE",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T14:45:12Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T14:45:12Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance external Git discovery",
            "tree": {
              "sha": "5f575338912c63f6d2110fec56cec3191c63089b",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/5f575338912c63f6d2110fec56cec3191c63089b"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/3f8b7bc2ec02ec9adbf96be9a11d758adeba6e51",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/3f8b7bc2ec02ec9adbf96be9a11d758adeba6e51",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/3f8b7bc2ec02ec9adbf96be9a11d758adeba6e51",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/3f8b7bc2ec02ec9adbf96be9a11d758adeba6e51/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "8916f4e1f469335da80a2cf49382b2639f4be4d5",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/8916f4e1f469335da80a2cf49382b2639f4be4d5",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/8916f4e1f469335da80a2cf49382b2639f4be4d5"
            }
          ]
        },
        {
          "sha": "5982b7fd0c70b6b0d43b5f00bf829fd873a3bc88",
          "node_id": "C_kwDOUxThN9oAKDU5ODJiN2ZkMGM3MGI2YjBkNDNiNWYwMGJmODI5ZmQ4NzNhM2JjODg",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T16:01:07Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T16:01:07Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance PS2 web discovery",
            "tree": {
              "sha": "f4f45b12198e31fe0555dc3268f4d41a51c18d89",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/f4f45b12198e31fe0555dc3268f4d41a51c18d89"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/5982b7fd0c70b6b0d43b5f00bf829fd873a3bc88",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/5982b7fd0c70b6b0d43b5f00bf829fd873a3bc88",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/5982b7fd0c70b6b0d43b5f00bf829fd873a3bc88",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/5982b7fd0c70b6b0d43b5f00bf829fd873a3bc88/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "3f8b7bc2ec02ec9adbf96be9a11d758adeba6e51",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/3f8b7bc2ec02ec9adbf96be9a11d758adeba6e51",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/3f8b7bc2ec02ec9adbf96be9a11d758adeba6e51"
            }
          ]
        },
        {
          "sha": "84186974e5015ff480818111c4f3ad29bb24e8fc",
          "node_id": "C_kwDOUxThN9oAKDg0MTg2OTc0ZTUwMTVmZjQ4MDgxODExMWM0ZjNhZDI5YmIyNGU4ZmM",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T16:22:09Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T16:22:09Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): synchronize project metadata",
            "tree": {
              "sha": "278d91ef283938261e5597550b55cddba557950a",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/278d91ef283938261e5597550b55cddba557950a"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/84186974e5015ff480818111c4f3ad29bb24e8fc",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/84186974e5015ff480818111c4f3ad29bb24e8fc",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/84186974e5015ff480818111c4f3ad29bb24e8fc",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/84186974e5015ff480818111c4f3ad29bb24e8fc/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "5982b7fd0c70b6b0d43b5f00bf829fd873a3bc88",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/5982b7fd0c70b6b0d43b5f00bf829fd873a3bc88",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/5982b7fd0c70b6b0d43b5f00bf829fd873a3bc88"
            }
          ]
        },
        {
          "sha": "b9a40093738588f2197658fba20efd68968b0565",
          "node_id": "C_kwDOUxThN9oAKGI5YTQwMDkzNzM4NTg4ZjIxOTc2NThmYmEyMGVmZDY4OTY4YjA1NjU",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T16:35:58Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T16:35:58Z"
            },
            "message": "chore(deploy): publish batched PS2SP updates",
            "tree": {
              "sha": "278d91ef283938261e5597550b55cddba557950a",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/278d91ef283938261e5597550b55cddba557950a"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/b9a40093738588f2197658fba20efd68968b0565",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/b9a40093738588f2197658fba20efd68968b0565",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/b9a40093738588f2197658fba20efd68968b0565",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/b9a40093738588f2197658fba20efd68968b0565/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "84186974e5015ff480818111c4f3ad29bb24e8fc",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/84186974e5015ff480818111c4f3ad29bb24e8fc",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/84186974e5015ff480818111c4f3ad29bb24e8fc"
            }
          ]
        },
        {
          "sha": "d46c342419fd70220b3f15d11bbd9201942854f0",
          "node_id": "C_kwDOUxThN9oAKGQ0NmMzNDI0MTlmZDcwMjIwYjNmMTVkMTFiYmQ5MjAxOTQyODU0ZjA",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T17:54:35Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T17:54:35Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance PS2 discovery",
            "tree": {
              "sha": "421c61feca705f2959eb42ffaf8ca73090b91726",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/421c61feca705f2959eb42ffaf8ca73090b91726"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/d46c342419fd70220b3f15d11bbd9201942854f0",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/d46c342419fd70220b3f15d11bbd9201942854f0",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/d46c342419fd70220b3f15d11bbd9201942854f0",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/d46c342419fd70220b3f15d11bbd9201942854f0/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "b9a40093738588f2197658fba20efd68968b0565",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/b9a40093738588f2197658fba20efd68968b0565",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/b9a40093738588f2197658fba20efd68968b0565"
            }
          ]
        },
        {
          "sha": "9ed772291c37c7a0fbfc1d41579a4df82767f251",
          "node_id": "C_kwDOUxThN9oAKDllZDc3MjI5MWMzN2M3YTBmYmZjMWQ0MTU3OWE0ZGY4Mjc2N2YyNTE",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T17:55:08Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T17:55:10Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance GitHub depth discovery",
            "tree": {
              "sha": "d86eb95e8e56df9c259bb95463769986c33f1ec3",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/d86eb95e8e56df9c259bb95463769986c33f1ec3"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/9ed772291c37c7a0fbfc1d41579a4df82767f251",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/9ed772291c37c7a0fbfc1d41579a4df82767f251",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/9ed772291c37c7a0fbfc1d41579a4df82767f251",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/9ed772291c37c7a0fbfc1d41579a4df82767f251/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "d46c342419fd70220b3f15d11bbd9201942854f0",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/d46c342419fd70220b3f15d11bbd9201942854f0",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/d46c342419fd70220b3f15d11bbd9201942854f0"
            }
          ]
        },
        {
          "sha": "9bb0a33c3e804cb15e6564afc7febef8026d32e6",
          "node_id": "C_kwDOUxThN9oAKDliYjBhMzNjM2U4MDRjYjE1ZTY1NjRhZmM3ZmViZWY4MDI2ZDMyZTY",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T18:04:19Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T18:04:19Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance external Git discovery",
            "tree": {
              "sha": "2cb055826a37f18bab36d8ba45856586da9ae57b",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/2cb055826a37f18bab36d8ba45856586da9ae57b"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/9bb0a33c3e804cb15e6564afc7febef8026d32e6",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/9bb0a33c3e804cb15e6564afc7febef8026d32e6",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/9bb0a33c3e804cb15e6564afc7febef8026d32e6",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/9bb0a33c3e804cb15e6564afc7febef8026d32e6/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "9ed772291c37c7a0fbfc1d41579a4df82767f251",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/9ed772291c37c7a0fbfc1d41579a4df82767f251",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/9ed772291c37c7a0fbfc1d41579a4df82767f251"
            }
          ]
        },
        {
          "sha": "5856a335682ea4de826311c00d79ea0b7339cc56",
          "node_id": "C_kwDOUxThN9oAKDU4NTZhMzM1NjgyZWE0ZGU4MjYzMTFjMDBkNzllYTBiNzMzOWNjNTY",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T18:55:27Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T18:55:27Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance PS2 web discovery",
            "tree": {
              "sha": "be2f07f38906e9114c519ec51e933a3fd0e80c71",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/be2f07f38906e9114c519ec51e933a3fd0e80c71"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/5856a335682ea4de826311c00d79ea0b7339cc56",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/5856a335682ea4de826311c00d79ea0b7339cc56",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/5856a335682ea4de826311c00d79ea0b7339cc56",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/5856a335682ea4de826311c00d79ea0b7339cc56/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "9bb0a33c3e804cb15e6564afc7febef8026d32e6",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/9bb0a33c3e804cb15e6564afc7febef8026d32e6",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/9bb0a33c3e804cb15e6564afc7febef8026d32e6"
            }
          ]
        },
        {
          "sha": "47226b33a056a0173fae54c46da1663ffb99d7a1",
          "node_id": "C_kwDOUxThN9oAKDQ3MjI2YjMzYTA1NmEwMTczZmFlNTRjNDZkYTE2NjNmZmI5OWQ3YTE",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T19:40:18Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T19:40:18Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): synchronize project metadata",
            "tree": {
              "sha": "d730006ec11ed0f29041b67fbe22264b6aa9c49f",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/d730006ec11ed0f29041b67fbe22264b6aa9c49f"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/47226b33a056a0173fae54c46da1663ffb99d7a1",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/47226b33a056a0173fae54c46da1663ffb99d7a1",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/47226b33a056a0173fae54c46da1663ffb99d7a1",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/47226b33a056a0173fae54c46da1663ffb99d7a1/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "5856a335682ea4de826311c00d79ea0b7339cc56",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/5856a335682ea4de826311c00d79ea0b7339cc56",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/5856a335682ea4de826311c00d79ea0b7339cc56"
            }
          ]
        },
        {
          "sha": "e89414fc77b476cfdfbf3b295ef82889bcf04b78",
          "node_id": "C_kwDOUxThN9oAKGU4OTQxNGZjNzdiNDc2Y2ZkZmJmM2IyOTVlZjgyODg5YmNmMDRiNzg",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T20:59:31Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T20:59:31Z"
            },
            "message": "chore(deploy): publish batched PS2SP updates",
            "tree": {
              "sha": "d730006ec11ed0f29041b67fbe22264b6aa9c49f",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/d730006ec11ed0f29041b67fbe22264b6aa9c49f"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/e89414fc77b476cfdfbf3b295ef82889bcf04b78",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/e89414fc77b476cfdfbf3b295ef82889bcf04b78",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/e89414fc77b476cfdfbf3b295ef82889bcf04b78",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/e89414fc77b476cfdfbf3b295ef82889bcf04b78/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "47226b33a056a0173fae54c46da1663ffb99d7a1",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/47226b33a056a0173fae54c46da1663ffb99d7a1",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/47226b33a056a0173fae54c46da1663ffb99d7a1"
            }
          ]
        },
        {
          "sha": "4e051f89b2aed51cc906e0f63778e4a62d5995fd",
          "node_id": "C_kwDOUxThN9oAKDRlMDUxZjg5YjJhZWQ1MWNjOTA2ZTBmNjM3NzhlNGE2MmQ1OTk1ZmQ",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T21:12:22Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T21:12:22Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance GitHub depth discovery",
            "tree": {
              "sha": "204735f42b58a22004e3a56c8dce5e7c3e4efad9",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/204735f42b58a22004e3a56c8dce5e7c3e4efad9"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/4e051f89b2aed51cc906e0f63778e4a62d5995fd",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/4e051f89b2aed51cc906e0f63778e4a62d5995fd",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/4e051f89b2aed51cc906e0f63778e4a62d5995fd",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/4e051f89b2aed51cc906e0f63778e4a62d5995fd/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "e89414fc77b476cfdfbf3b295ef82889bcf04b78",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/e89414fc77b476cfdfbf3b295ef82889bcf04b78",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/e89414fc77b476cfdfbf3b295ef82889bcf04b78"
            }
          ]
        },
        {
          "sha": "9494d88bbe28ad6248bf6df15f1fa118332383c7",
          "node_id": "C_kwDOUxThN9oAKDk0OTRkODhiYmUyOGFkNjI0OGJmNmRmMTVmMWZhMTE4MzMyMzgzYzc",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T21:17:17Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T21:17:17Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance PS2 discovery",
            "tree": {
              "sha": "ff183685d2d70ed7d1c22d645a60fb234c5d814c",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/ff183685d2d70ed7d1c22d645a60fb234c5d814c"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/9494d88bbe28ad6248bf6df15f1fa118332383c7",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/9494d88bbe28ad6248bf6df15f1fa118332383c7",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/9494d88bbe28ad6248bf6df15f1fa118332383c7",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/9494d88bbe28ad6248bf6df15f1fa118332383c7/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "4e051f89b2aed51cc906e0f63778e4a62d5995fd",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/4e051f89b2aed51cc906e0f63778e4a62d5995fd",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/4e051f89b2aed51cc906e0f63778e4a62d5995fd"
            }
          ]
        },
        {
          "sha": "9c09117f7b303efdca6e5531c66d42926e656cc4",
          "node_id": "C_kwDOUxThN9oAKDljMDkxMTdmN2IzMDNlZmRjYTZlNTUzMWM2NmQ0MjkyNmU2NTZjYzQ",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T21:23:02Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T21:23:02Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance external Git discovery",
            "tree": {
              "sha": "03c10de716d587396facdce719a37dda387cd9ae",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/03c10de716d587396facdce719a37dda387cd9ae"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/9c09117f7b303efdca6e5531c66d42926e656cc4",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/9c09117f7b303efdca6e5531c66d42926e656cc4",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/9c09117f7b303efdca6e5531c66d42926e656cc4",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/9c09117f7b303efdca6e5531c66d42926e656cc4/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "9494d88bbe28ad6248bf6df15f1fa118332383c7",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/9494d88bbe28ad6248bf6df15f1fa118332383c7",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/9494d88bbe28ad6248bf6df15f1fa118332383c7"
            }
          ]
        },
        {
          "sha": "df2eb301ff7dd271e8e457b3ea30136cee7dc400",
          "node_id": "C_kwDOUxThN9oAKGRmMmViMzAxZmY3ZGQyNzFlOGU0NTdiM2VhMzAxMzZjZWU3ZGM0MDA",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T21:41:59Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T21:41:59Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): advance PS2 web discovery",
            "tree": {
              "sha": "be9c6996a7532910b86e00a638e9204a215810da",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/be9c6996a7532910b86e00a638e9204a215810da"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/df2eb301ff7dd271e8e457b3ea30136cee7dc400",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/df2eb301ff7dd271e8e457b3ea30136cee7dc400",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/df2eb301ff7dd271e8e457b3ea30136cee7dc400",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/df2eb301ff7dd271e8e457b3ea30136cee7dc400/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "9c09117f7b303efdca6e5531c66d42926e656cc4",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/9c09117f7b303efdca6e5531c66d42926e656cc4",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/9c09117f7b303efdca6e5531c66d42926e656cc4"
            }
          ]
        },
        {
          "sha": "cb040e79dfd467aa99252575c9d6fd97684116de",
          "node_id": "C_kwDOUxThN9oAKGNiMDQwZTc5ZGZkNDY3YWE5OTI1MjU3NWM5ZDZmZDk3Njg0MTE2ZGU",
          "commit": {
            "author": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T22:36:17Z"
            },
            "committer": {
              "name": "ps2sp-bot",
              "email": "41898282+github-actions[bot]@users.noreply.github.com",
              "date": "2026-10-25T22:36:17Z"
            },
            "message": "[CF-Pages-Skip] chore(catalog): synchronize project metadata",
            "tree": {
              "sha": "250e47aeea6967679d6222ccd88521d07d98e388",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/trees/250e47aeea6967679d6222ccd88521d07d98e388"
            },
            "url": "https://api.github.com/repos/johnson-cooper/PS2SP/git/commits/cb040e79dfd467aa99252575c9d6fd97684116de",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/cb040e79dfd467aa99252575c9d6fd97684116de",
          "html_url": "https://github.com/johnson-cooper/PS2SP/commit/cb040e79dfd467aa99252575c9d6fd97684116de",
          "comments_url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/cb040e79dfd467aa99252575c9d6fd97684116de/comments",
          "author": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "github-actions[bot]",
            "id": 41898282,
            "node_id": "MDM6Qm90NDE4OTgyODI=",
            "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/github-actions%5Bbot%5D",
            "html_url": "https://github.com/apps/github-actions",
            "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
            "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
            "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
            "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
            "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
            "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
            "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
            "type": "Bot",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "df2eb301ff7dd271e8e457b3ea30136cee7dc400",
              "url": "https://api.github.com/repos/johnson-cooper/PS2SP/commits/df2eb301ff7dd271e8e457b3ea30136cee7dc400",
              "html_url": "https://github.com/johnson-cooper/PS2SP/commit/df2eb301ff7dd271e8e457b3ea30136cee7dc400"
            }
          ]
        }
      ],
      "files": [
        {
          "sha": "fb70b6a06354aca1531d8fa12fcbb8ee24e709d4",
          "filename": "content/projects/2004sp-ps2port-connordy.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2F2004sp-ps2port-connordy.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2F2004sp-ps2port-connordy.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2F2004sp-ps2port-connordy.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -36,12 +36,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:22.572Z'\n+  lastSynchronized: '2026-10-25T22:33:29.101Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"ea5014300ef0d5ab2ce6c705f583fa0f4d0d578228403a781549bdf1bc8a27f4\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"099641bb8d93af0d7ed63b73161226ed570118e204d69f9e469b5a013647ed8a\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:johnson-cooper/2004sp-ps2port'\n   confidence: 95"
        },
        {
          "sha": "d469f96983c66a125f167c7c03f27ba9c7b9fe02",
          "filename": "content/projects/3s-decomp-3sx-testing.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2F3s-decomp-3sx-testing.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2F3s-decomp-3sx-testing.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2F3s-decomp-3sx-testing.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -28,12 +28,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T06:00:32.426Z'\n+  lastSynchronized: '2026-10-25T06:10:39.603Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"27f89606734d5104b72c54b41d5f489bba1aadaa08504e699f8f4a73cec37f7b\"\n-    releasesEtag: '\"beabca011d245159687fb6a685ede4790f750a2002b686c57e5bbc672bd5ded1\"'\n+    repoEtag: W/\"b68907df1cac04b0b9a6f9b970ef19569fe9731bb76e3c7478572664eb4f64c0\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'fork-network:crowded-street/3s-decomp'\n   confidence: 100"
        },
        {
          "sha": "6bce655a4cf86ac031c3b68689b62161c4e5345e",
          "filename": "content/projects/3s-decomp-kanemaki.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2F3s-decomp-kanemaki.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2F3s-decomp-kanemaki.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2F3s-decomp-kanemaki.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -28,12 +28,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T06:00:33.028Z'\n+  lastSynchronized: '2026-10-25T06:10:40.092Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"88ab2b7a0e731b455b71790ca46a4c2d1d93530d3df2c0bb480736cb4a5836ae\"\n-    releasesEtag: '\"beabca011d245159687fb6a685ede4790f750a2002b686c57e5bbc672bd5ded1\"'\n+    repoEtag: W/\"206cc810237bf05e0d3608fc6b34e4dbbacaa49211743fbbdeeeaa173aaa6c78\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'fork-network:crowded-street/3s-decomp'\n   confidence: 100"
        },
        {
          "sha": "eaf783838ef4f199e75b2c2714c5d4046a11eddb",
          "filename": "content/projects/3s-decomp-manya3084.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2F3s-decomp-manya3084.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2F3s-decomp-manya3084.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2F3s-decomp-manya3084.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -28,12 +28,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T06:00:33.594Z'\n+  lastSynchronized: '2026-10-25T06:10:40.542Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"4e0d2779d1f07f40c9ca573c7de141caf32afd94dcb27a68bcbfd7c98668afec\"\n-    releasesEtag: '\"beabca011d245159687fb6a685ede4790f750a2002b686c57e5bbc672bd5ded1\"'\n+    repoEtag: W/\"16e5d0f62b7804ea480a96e1accc744315beaec993fd3336fe4e3604a90c2d13\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 100"
        },
        {
          "sha": "83b53ecae6f3854330d4dcd65be4a104b86d4bde",
          "filename": "content/projects/3s-decomp-octopod-daydreamer.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2F3s-decomp-octopod-daydreamer.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2F3s-decomp-octopod-daydreamer.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2F3s-decomp-octopod-daydreamer.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -28,12 +28,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T06:00:34.183Z'\n+  lastSynchronized: '2026-10-25T06:10:41.057Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"e00a789f73e91cd1a46261e44fc50917395890187041896a9916a2c571a82945\"\n-    releasesEtag: '\"beabca011d245159687fb6a685ede4790f750a2002b686c57e5bbc672bd5ded1\"'\n+    repoEtag: W/\"25770db7271ae384e45b3b4f288e8fe1fade58ed41da6cf57082c47fbb0b1570\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'fork-network:crowded-street/3s-decomp'\n   confidence: 100"
        },
        {
          "sha": "1030022ad46c24398cf77559c5f68dd872fb784d",
          "filename": "content/projects/3s-decomp-trafgals.md",
          "status": "modified",
          "additions": 9,
          "deletions": 7,
          "changes": 16,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2F3s-decomp-trafgals.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2F3s-decomp-trafgals.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2F3s-decomp-trafgals.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -21,16 +21,19 @@ repository:\n   defaultBranch: main\n   stars: 0\n   forks: 0\n-  lastCommit: '2026-10-20T07:14:38Z'\n+  lastCommit: '2026-10-22T05:51:05Z'\n latestRelease:\n-  tag: null\n-  name: null\n-  publishedAt: null\n-  url: null\n+  tag: web-data-1\n+  name: The disc image for the web build\n+  publishedAt: '2026-10-22T04:36:17Z'\n+  url: 'https://github.com/trafgals/3s-decomp/releases/tag/web-data-1'\n activity:\n-  lastSynchronized: '2026-10-20T10:25:31.189Z'\n+  lastSynchronized: '2026-10-25T06:10:41.587Z'\n automation:\n   sync: true\n+  github:\n+    repoEtag: W/\"7c8c3de56e0141c69c46da68173679359c8d63420af4157899bdd02df0bed324\"\n+    releasesEtag: W/\"0855103f9bce933afd06e3210ea3ab142be9a0cc6214dea76342a3e9dc5c8a2c\"\n discovery:\n   method: 'incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 100\n@@ -47,5 +50,4 @@ relationships:\n   forkOf: crowded-street/3s-decomp\n   source: crowded-street/3s-decomp\n ---\n-\n Automatically discovered by PS2SP. Relevance evidence is recorded in frontmatter; human curation can expand this entry without disabling automated repository metadata synchronization."
        },
        {
          "sha": "f3c51f41b1b02d64cecd57e42c746ca952d3eff7",
          "filename": "content/projects/3s-psp-icelaglace.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2F3s-psp-icelaglace.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2F3s-psp-icelaglace.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2F3s-psp-icelaglace.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-24T11:00:09.891Z'\n+  lastSynchronized: '2026-10-25T11:29:36.345Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"632b1ae03d70306f609b81727a4e00f0c078c144420f03c6042c0905483d29c8\"\n-    releasesEtag: '\"2d8126faf7d60a088ad7a14f528f5a8f3ca919b5cd485e0f97d8ebb20b5102f0\"'\n+    repoEtag: W/\"bbcb7becfdb06e9e529d54c07e8b851ed69d7550164750a6aedeb7b3f208b341\"\n+    releasesEtag: '\"9e77b662133f4d4e3007923dbf3a0cbb2fd896f67c82d38c785dfb153804c449\"'\n discovery:\n   method: 'fork-network:demmis98/3s-psp'\n   confidence: 95"
        },
        {
          "sha": "c3308fb1e4f8b6ba7cda3e72ca6458af57fa83f6",
          "filename": "content/projects/3s-psvita-gustavo3211.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2F3s-psvita-gustavo3211.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2F3s-psvita-gustavo3211.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2F3s-psvita-gustavo3211.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T20:35:08.452Z'\n+  lastSynchronized: '2026-10-24T20:49:53.715Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"0e3431878b9169faa320a7ae0d3db34ef36c33037d57651d068df7e481d6dc8e\"\n-    releasesEtag: '\"94e354af2c4e28e3962b296e8975d0c6ec1a30b51f9be8608fe23fd98f6c999a\"'\n+    repoEtag: W/\"79dfab62548844ba4c856ad64156a93d9fbcf96af1be2fd203572f126c97eca1\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: 'fork-network:demmis98/3s-psp'\n   confidence: 95"
        },
        {
          "sha": "b02d0e6b2a6f72de3a13eaf9e6799785591cb650",
          "filename": "content/projects/aethersx-budy-winchester.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Faethersx-budy-winchester.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Faethersx-budy-winchester.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Faethersx-budy-winchester.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-22T19:15:32.075Z'\n+  lastSynchronized: '2026-10-25T19:37:26.176Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"a09427b9c41b8503e4574e8cba8d57091481f4437af1839a555bf4435f86f26d\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"ef1e2a2ea5d5605c93ee69678d67dc9f0cdd9aee30f90ff7b1da64cc25e1f4a6\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'fork-network:xlenore/ps2-covers'\n   confidence: 95"
        },
        {
          "sha": "3c9599bb7e2de2863ff95b3eedcc89d28cea5284",
          "filename": "content/projects/ai-awesome-rea.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fai-awesome-rea.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fai-awesome-rea.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fai-awesome-rea.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -35,12 +35,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-23T17:27:12.070Z'\n-  lastSynchronized: '2026-10-24T11:00:10.575Z'\n+  lastSynchronized: '2026-10-25T11:29:36.988Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"7c8e534b55e8fd7e9e0aba157685bf067845d78d627edd118a93a8193c7ccfbb\"\n-    releasesEtag: '\"2d8126faf7d60a088ad7a14f528f5a8f3ca919b5cd485e0f97d8ebb20b5102f0\"'\n+    repoEtag: W/\"dfe5c38c9acc3a29b20231c266245225af470f177ea72749586f5b61fa93068e\"\n+    releasesEtag: '\"9e77b662133f4d4e3007923dbf3a0cbb2fd896f67c82d38c785dfb153804c449\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 75"
        },
        {
          "sha": "8dd64ebb1a96e0fd5a653418ea8eca91a0fcb063",
          "filename": "content/projects/apascan.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fapascan.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fapascan.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fapascan.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-17T05:49:41.310Z'\n+  lastSynchronized: '2026-10-25T16:19:40.528Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"e85b6bce93867ca6803910c73aba9a100fd52ece79d92ef7bbf5165f51b1313d\"\n-    releasesEtag: '\"840d8b22d7db312c6ea7f64c0729d54bdc2329ace0982cd7dfae9d1d30d0d261\"'\n+    repoEtag: W/\"9456cca8a17e18af4f3adaae269906bb95a012413459faf0307eb69a902146de\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'starred-curated:NathanNeurotic'\n   confidence: 100"
        },
        {
          "sha": "f5c7e9a94cdc2c82f220849f22344b0db94dabba",
          "filename": "content/projects/apollo-ps2-akuhak.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fapollo-ps2-akuhak.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fapollo-ps2-akuhak.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fapollo-ps2-akuhak.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -29,12 +29,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T01:31:39.858Z'\n+  lastSynchronized: '2026-10-25T01:14:03.586Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"34d020edd4c76e1318c75685b189048c45c36e004fc04c8bce202ec4c821f13b\"\n-    releasesEtag: '\"ff6090af9c91b61292326178ca0823b97aed61799df8cc0ca8f7a6c7d1215cb9\"'\n+    repoEtag: W/\"67053721c7781dccadf3d6fc11766bc42459b0587d27b17dac8d17dbfb075387\"\n+    releasesEtag: '\"a5c451af9d88eff769190129023bb74c9de83ceb2f12875d0ca286055e5e73e6\"'\n discovery:\n   method: 'fork-network:bucanero/apollo-ps2'\n   confidence: 100"
        },
        {
          "sha": "29ec7eaf35db5893f44e986e01e435b8ce20e4ec",
          "filename": "content/projects/apollo-ps2.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fapollo-ps2.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fapollo-ps2.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fapollo-ps2.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -28,12 +28,12 @@ latestRelease:\n   publishedAt: '2024-12-24T11:36:11Z'\n   url: 'https://github.com/bucanero/apollo-ps2/releases/tag/v1.0.2'\n activity:\n-  lastSynchronized: '2026-10-23T22:11:23.061Z'\n+  lastSynchronized: '2026-10-25T22:33:29.624Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"4fd17b32125a51516013df026e2fb2222c477b76513f9610d2935c5b1d52c48d\"\n-    releasesEtag: W/\"2116ffc2b26006d7c62766ff258f61fed162ed4feec864e53ec903717e59b5ee\"\n+    repoEtag: W/\"27fe3733598eda96522b744ba80950158130cba625589f3caa7ebc22f758aee4\"\n+    releasesEtag: W/\"a86ed78715179a6618ee88a41f4a747b4a9396f5616601984d9144574a4c1da3\"\n discovery:\n   method: 'starred:NathanNeurotic'\n   confidence: 100"
        },
        {
          "sha": "5ce43b54cec4b6e8ff7b332d9867bbdcedd5cee1",
          "filename": "content/projects/archipelago-rac3-taoshix.md",
          "status": "modified",
          "additions": 4,
          "deletions": 4,
          "changes": 8,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farchipelago-rac3-taoshix.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farchipelago-rac3-taoshix.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farchipelago-rac3-taoshix.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -22,19 +22,19 @@ repository:\n   defaultBranch: main\n   stars: 18\n   forks: 1\n-  lastCommit: '2026-10-20T12:05:48Z'\n+  lastCommit: '2026-10-24T18:56:51Z'\n latestRelease:\n   tag: v0.6.3\n   name: RaC3 APWorld v0.6.3\n   publishedAt: '2026-10-15T21:46:37Z'\n   url: 'https://github.com/Taoshix/Archipelago-RaC3/releases/tag/v0.6.3'\n activity:\n-  lastSynchronized: '2026-10-20T20:35:09.267Z'\n+  lastSynchronized: '2026-10-24T20:49:54.425Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"f20b592bd51b2c2c1381bbe4c8c31a7f3e0c98dcb76ecb8d4e56fe10068d8504\"\n-    releasesEtag: W/\"8d6ec724027e64e314baab64fc723745dc14945e1ed0e36e6637c7dada9b6b90\"\n+    repoEtag: W/\"8452d3a7f718567d70abec7821631bf87bc1bd9d6f4a3437cbc6c6f9d26d5136\"\n+    releasesEtag: W/\"391944c00049ee7a399db489a5d7df3d9eb7975b99dd6ea778c189e8fc8f64d4\"\n discovery:\n   method: 'incremental:topic:ps2'\n   confidence: 95"
        },
        {
          "sha": "1d33c8ffced6b8ec346730a523418a7a7cbda921",
          "filename": "content/projects/arduino-ps2-robot-transporter-controller.md",
          "status": "modified",
          "additions": 4,
          "deletions": 2,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farduino-ps2-robot-transporter-controller.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farduino-ps2-robot-transporter-controller.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farduino-ps2-robot-transporter-controller.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -29,9 +29,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-21T00:58:06.538Z'\n+  lastSynchronized: '2026-10-24T20:49:54.837Z'\n automation:\n   sync: true\n+  github:\n+    repoEtag: W/\"32e185ab13daa02936fc434aa03cd25fd597dc7def202367a3a398e5c860c854\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: 'incremental:ps2 in:name,description'\n   confidence: 95\n@@ -43,5 +46,4 @@ verified: false\n featured: false\n hidden: false\n ---\n-\n Automatically discovered by PS2SP. Relevance evidence is recorded in frontmatter; human curation can expand this entry without disabling automated repository metadata synchronization."
        },
        {
          "sha": "5767db582c897be6342c9ccde9ed6fa916c6f399",
          "filename": "content/projects/armsx2-armsx2.md",
          "status": "modified",
          "additions": 10,
          "deletions": 10,
          "changes": 20,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-armsx2.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-armsx2.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-armsx2.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -18,21 +18,21 @@ source:\n repository:\n   archived: false\n   defaultBranch: master\n-  stars: 2248\n-  forks: 186\n-  lastCommit: '2026-10-22T19:03:29Z'\n+  stars: 2272\n+  forks: 196\n+  lastCommit: '2026-10-23T14:10:29Z'\n latestRelease:\n-  tag: nightly-20261022\n-  name: ARMSX2 nightly 20261022 (b276f045c6)\n-  publishedAt: '2026-10-22T14:35:03Z'\n-  url: 'https://github.com/ARMSX2/ARMSX2/releases/tag/nightly-20261022'\n+  tag: nightly-20261023\n+  name: ARMSX2 nightly 20261023 (90126bdfea)\n+  publishedAt: '2026-10-23T14:10:10Z'\n+  url: 'https://github.com/ARMSX2/ARMSX2/releases/tag/nightly-20261023'\n activity:\n-  lastSynchronized: '2026-10-22T19:15:32.872Z'\n+  lastSynchronized: '2026-10-25T19:37:26.815Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"bb70a76ba8ed610f05b2aaff8ceb304b87f58472ca2af3d9340c2b0c5bf8ecb2\"\n-    releasesEtag: W/\"8e565fe1dc31a267270f606d97db768c5f5204467e79314c3991cae7d0805e92\"\n+    repoEtag: W/\"10d386d35be671d6ba26624f977e8f8b0e278411b5aa297a28621a54993a8120\"\n+    releasesEtag: W/\"0bba235408b5815b6c1a53199ad09ede951e8ab2108f6bdaef900cbb9c4227e5\"\n discovery:\n   method: 'incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 100"
        },
        {
          "sha": "2042606b95503db47745ac48c0e872b98e76f378",
          "filename": "content/projects/armsx2-beadbeed.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-beadbeed.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-beadbeed.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-beadbeed.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-17T05:49:49.192Z'\n+  lastSynchronized: '2026-10-25T16:19:41.338Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"9e7e41087992267efc6a6591452728ca207e6bc748e143d107866c658dfe8bb2\"\n-    releasesEtag: '\"840d8b22d7db312c6ea7f64c0729d54bdc2329ace0982cd7dfae9d1d30d0d261\"'\n+    repoEtag: W/\"eda019e160750e469f43a844ccd9625724ee53243724d56fce558e3f8e726f2f\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 100"
        },
        {
          "sha": "93fb1291abd7a27829e94c0bac548be78c36fb1c",
          "filename": "content/projects/armsx2-bradwindy.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-bradwindy.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-bradwindy.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-bradwindy.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-24T11:00:11.326Z'\n+  lastSynchronized: '2026-10-25T11:29:37.536Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"e836b0a12a9aaebc0cc6443f2cb0d8873a181625fb215ab40ebb2858e8297ee8\"\n-    releasesEtag: '\"2d8126faf7d60a088ad7a14f528f5a8f3ca919b5cd485e0f97d8ebb20b5102f0\"'\n+    repoEtag: W/\"80f8349df028d3c95747e59f2c088508e873a949455ecfc3bdd7886002b21730\"\n+    releasesEtag: '\"9e77b662133f4d4e3007923dbf3a0cbb2fd896f67c82d38c785dfb153804c449\"'\n discovery:\n   method: 'incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 100"
        },
        {
          "sha": "02e005f79cfc53965e41be3ff70cce3050440671",
          "filename": "content/projects/armsx2-cn-afeimod.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-cn-afeimod.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-cn-afeimod.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-cn-afeimod.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: '2026-06-26T05:03:09Z'\n   url: 'https://github.com/afeimod/ARMSX2-CN/releases/tag/Refresh-UI-CN-MOD1'\n activity:\n-  lastSynchronized: '2026-10-23T22:11:23.647Z'\n+  lastSynchronized: '2026-10-25T22:33:30.221Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"04aec5573c442eb03400c22475de85cd0e753476fd57389b92ccb7c247bcd8ef\"\n-    releasesEtag: W/\"f89d9c2405813b29843c943ec0c76fb326f520c40b4db97135849f43312923ce\"\n+    repoEtag: W/\"5ec853b29aac70093a7d4a1c37c7a5cd2353c1144af7e49c80bcc0f796a00d2d\"\n+    releasesEtag: W/\"0ab7acba930c74f0c48f847ed203eebabf1394d3d23400d6583b6a60428c6ba4\"\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100"
        },
        {
          "sha": "dcea39fd16f57d5a6e2d4693b7c9fe8342334ba2",
          "filename": "content/projects/armsx2-darktanator.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-darktanator.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-darktanator.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-darktanator.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T01:31:40.395Z'\n+  lastSynchronized: '2026-10-25T01:14:04.088Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"212abaad125c795c63dc9020bbb72b9ce2a794cc3d4f8e0dce53dff612536a47\"\n-    releasesEtag: '\"ff6090af9c91b61292326178ca0823b97aed61799df8cc0ca8f7a6c7d1215cb9\"'\n+    repoEtag: W/\"a1f1cd45b4eb5c0fdd81ab0f38cd17ad341a70b718558f38b281fc07e234c595\"\n+    releasesEtag: '\"a5c451af9d88eff769190129023bb74c9de83ceb2f12875d0ca286055e5e73e6\"'\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100"
        },
        {
          "sha": "c3cd2d019250c07846fff4474648910ff8c6eacf",
          "filename": "content/projects/armsx2-davit33.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-davit33.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-davit33.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-davit33.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-21T23:19:36.240Z'\n+  lastSynchronized: '2026-10-24T23:14:53.085Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"a71a54ac31948214c53906814321c3bea3a650b81c4ed819e57b8b70c83eeb86\"\n-    releasesEtag: '\"669f535743067c0ac9111e98f8b3d7f826f4edc7838967d02f64b6f505af2660\"'\n+    repoEtag: W/\"7ab2e83d911e0dd6a1f5f5979bc131f30ce4249df96d5339151fff3e90fadff4\"\n+    releasesEtag: '\"d958fbfec27a80deaeffdf70593bfd5e43f02f963f152d4a8d9f4917da92f5c0\"'\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100"
        },
        {
          "sha": "a53161357cdc2ba8a64108ea3467d201f0a9e0ff",
          "filename": "content/projects/armsx2-deurefdfgdf.md",
          "status": "modified",
          "additions": 5,
          "deletions": 3,
          "changes": 8,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-deurefdfgdf.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-deurefdfgdf.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-deurefdfgdf.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -20,16 +20,19 @@ repository:\n   defaultBranch: master\n   stars: 0\n   forks: 0\n-  lastCommit: '2026-10-22T10:19:30Z'\n+  lastCommit: '2026-10-23T09:45:53Z'\n latestRelease:\n   tag: null\n   name: null\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-22T10:39:06.591Z'\n+  lastSynchronized: '2026-10-25T06:10:42.146Z'\n automation:\n   sync: true\n+  github:\n+    repoEtag: W/\"379653424295dc903fb7c8b67c63e4d0510c1430b4e1e51b25c780b6e71aef9b\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 100\n@@ -46,5 +49,4 @@ relationships:\n   forkOf: ARMSX2/ARMSX2\n   source: PCSX2/pcsx2\n ---\n-\n Automatically discovered by PS2SP. Relevance evidence is recorded in frontmatter; human curation can expand this entry without disabling automated repository metadata synchronization."
        },
        {
          "sha": "dbcb2028ca0bcaa8a39f9eafebd627c66042db6c",
          "filename": "content/projects/armsx2-devilphyo022-blip.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-devilphyo022-blip.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-devilphyo022-blip.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-devilphyo022-blip.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-21T23:19:36.761Z'\n+  lastSynchronized: '2026-10-24T23:14:53.583Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"23bd663afaa22da821c7ae6c2463547e8f1baab2b490ecbf26fb317c10777e3c\"\n-    releasesEtag: '\"669f535743067c0ac9111e98f8b3d7f826f4edc7838967d02f64b6f505af2660\"'\n+    repoEtag: W/\"7a4525c7d125d57ffd0629f40b24333825724c461b53a5d02f89d38c0849050f\"\n+    releasesEtag: '\"d958fbfec27a80deaeffdf70593bfd5e43f02f963f152d4a8d9f4917da92f5c0\"'\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100"
        },
        {
          "sha": "84ea3acd6fb6d7effd729b572427b3d2e00dc116",
          "filename": "content/projects/armsx2-gutierri.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-gutierri.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-gutierri.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-gutierri.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-22T19:15:33.581Z'\n+  lastSynchronized: '2026-10-25T19:37:27.376Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"db3ea388b9200e4f96206b5412b610210a90ceaefc4bb86374a1bdd64cd264d2\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"c080ba7b0b10e16d24fa390031f8c6d624b1ddea06220a184e54282e1a979563\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100"
        },
        {
          "sha": "4a5066201fee658b784ea0a7cc9b3e838fe85811",
          "filename": "content/projects/armsx2-hamzadevz.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-hamzadevz.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-hamzadevz.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-hamzadevz.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T01:31:40.869Z'\n+  lastSynchronized: '2026-10-25T01:14:04.557Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"323381a201715f72a35efcfac96bf919fc3cf25bfe9057d3076322a10674563a\"\n-    releasesEtag: '\"ff6090af9c91b61292326178ca0823b97aed61799df8cc0ca8f7a6c7d1215cb9\"'\n+    repoEtag: W/\"71742b541a780cdab7e51cc2f14a1e437e4fe54166b5f5cd9a43aa09e6363473\"\n+    releasesEtag: '\"a5c451af9d88eff769190129023bb74c9de83ceb2f12875d0ca286055e5e73e6\"'\n discovery:\n   method: 'incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 100"
        },
        {
          "sha": "b7e009bd971e9653da3ceb6fe05dc810c6ae78af",
          "filename": "content/projects/armsx2-henyckma.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-henyckma.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-henyckma.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-henyckma.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:24.184Z'\n+  lastSynchronized: '2026-10-25T22:33:30.856Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"ec62217b51d0c4b6861e12deed4f35dd400d026c16590f49fd1a17105c00f2df\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"e7116642c7c718860e81bad9ab9c4cb1c470cddabf4f0701cd91f441c960db5b\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100"
        },
        {
          "sha": "6461f37c96d16feef6d7233af53274cdde5aafa2",
          "filename": "content/projects/armsx2-howawesome64.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-howawesome64.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-howawesome64.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-howawesome64.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T20:35:09.909Z'\n+  lastSynchronized: '2026-10-24T20:49:55.426Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"4adec967247aa0567807f237b77a37b9aeec701349d0b065078834f36f8737a7\"\n-    releasesEtag: '\"94e354af2c4e28e3962b296e8975d0c6ec1a30b51f9be8608fe23fd98f6c999a\"'\n+    repoEtag: W/\"d5823768a220f9bcbe366b73025d5dd3204a2b5bb976a42226fbfa34e28b0b50\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: 'incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 100"
        },
        {
          "sha": "2e22f1befdf400159972c9408d4908090f792d07",
          "filename": "content/projects/armsx2-humline.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-humline.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-humline.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-humline.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-24T11:00:11.933Z'\n+  lastSynchronized: '2026-10-25T11:29:38.043Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"e22346459d2c2d0085898286db6ebafad6e9ff2850e93789f6e1bd1217962fb3\"\n-    releasesEtag: '\"2d8126faf7d60a088ad7a14f528f5a8f3ca919b5cd485e0f97d8ebb20b5102f0\"'\n+    repoEtag: W/\"1bbebce43ae00978a07056d1b76ba97a34a9c67ed8f1595dc34fe97c642f725a\"\n+    releasesEtag: '\"9e77b662133f4d4e3007923dbf3a0cbb2fd896f67c82d38c785dfb153804c449\"'\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100"
        },
        {
          "sha": "5cbbe0b5233932c81dadb35cc8e319b969e3a3c6",
          "filename": "content/projects/armsx2-hy300leosquizz-ctrl.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-hy300leosquizz-ctrl.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-hy300leosquizz-ctrl.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-hy300leosquizz-ctrl.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:24.679Z'\n+  lastSynchronized: '2026-10-25T22:33:31.378Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"648e6b1f5f6c3a7adbaf1efe47d1c0f4bc8ae6f49ba47614f3a0758acc96be4e\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"ae662a46c056862aea6dfb280828cd6e1d220f10a254bcb40476b9205f8fbbce\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 100"
        },
        {
          "sha": "007000fbacee02d5debdbb09abd42262cc4a7bcf",
          "filename": "content/projects/armsx2-imcokeman.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-imcokeman.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-imcokeman.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-imcokeman.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-21T23:19:37.279Z'\n+  lastSynchronized: '2026-10-24T23:14:54.096Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"b8bc3f3de10ba652902d0d8b30bb0f716bcdfc45273bf11d84c179ac03a92511\"\n-    releasesEtag: '\"669f535743067c0ac9111e98f8b3d7f826f4edc7838967d02f64b6f505af2660\"'\n+    repoEtag: W/\"ce6b0042e2cbf4f3efe43c53ec6b74455ad37759a011e245d881c84fa4f5be75\"\n+    releasesEtag: '\"d958fbfec27a80deaeffdf70593bfd5e43f02f963f152d4a8d9f4917da92f5c0\"'\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100"
        },
        {
          "sha": "9bf8aa660d104a0ea07c9916c33b71e18873611e",
          "filename": "content/projects/armsx2-ipad16-labrsudl.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-ipad16-labrsudl.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-ipad16-labrsudl.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-ipad16-labrsudl.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-21T23:19:37.810Z'\n+  lastSynchronized: '2026-10-24T23:14:54.571Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"ff3d7b4e0eb80e3198446ba9146490339fa8197d07e43fc465b616dd9dbb04fb\"\n-    releasesEtag: '\"669f535743067c0ac9111e98f8b3d7f826f4edc7838967d02f64b6f505af2660\"'\n+    repoEtag: W/\"5d958e3beae9aa4c9225cf276c3b185a1659f4eedd47f0b63cc987db2097ad10\"\n+    releasesEtag: '\"d958fbfec27a80deaeffdf70593bfd5e43f02f963f152d4a8d9f4917da92f5c0\"'\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100"
        },
        {
          "sha": "7a6d1e6cd2ac1ed7e8d4d842bfc35c85f8d0e720",
          "filename": "content/projects/armsx2-ivanbusinessgs.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-ivanbusinessgs.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-ivanbusinessgs.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-ivanbusinessgs.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:25.316Z'\n+  lastSynchronized: '2026-10-25T22:33:31.906Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"e78cbbb8675db57cf9b31b1807f9c20ccd6b93c6c25a719c707eee86df89eb56\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"b5e812910fa38f3b2bcfc7acb9d4cf492255e1ba60a62088fb86cab057feb411\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100"
        },
        {
          "sha": "e35a2e1146c4932dcb5bb467c3cb451c54cb5a0a",
          "filename": "content/projects/armsx2-jdpt0.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-jdpt0.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-jdpt0.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-jdpt0.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-22T19:15:34.219Z'\n+  lastSynchronized: '2026-10-25T19:37:27.865Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"51d4d8272a13f2037b9130191ca17e8d41f97c42cb0e6742a12f78f5b5008816\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"b4db95601606bd68ffbd1309a51df6e1da99c63d5d459b7c3a9e55b27f86139f\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100"
        },
        {
          "sha": "dbecec5e76d706686fe11df4dc4959ce900e3880",
          "filename": "content/projects/armsx2-johnpetersa19.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-johnpetersa19.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-johnpetersa19.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-johnpetersa19.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:25.762Z'\n+  lastSynchronized: '2026-10-25T22:33:32.484Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"abe0f25a82255efb74cc82f963c5bbe1bacbf9f08f74844fd1c86af7646a88fe\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"79cd7816988102f9552607fdbceec04496bbd7917f603083c0a1cb5750b4b84f\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100"
        },
        {
          "sha": "cb374bddc6fb042366e491d8a71ef8c761488c88",
          "filename": "content/projects/armsx2-kiraponz.md",
          "status": "modified",
          "additions": 4,
          "deletions": 2,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-kiraponz.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-kiraponz.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-kiraponz.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,9 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T22:40:16.620Z'\n+  lastSynchronized: '2026-10-24T20:49:55.971Z'\n automation:\n   sync: true\n+  github:\n+    repoEtag: W/\"f162eb949bc56ee40e7d8ea4eebde2b705a2f94dd70e32f895ef1fc93b5a8aff\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100\n@@ -46,5 +49,4 @@ relationships:\n   forkOf: ARMSX2/ARMSX2\n   source: PCSX2/pcsx2\n ---\n-\n Automatically discovered by PS2SP. Relevance evidence is recorded in frontmatter; human curation can expand this entry without disabling automated repository metadata synchronization."
        },
        {
          "sha": "2c0d3227abc2c9a7d7e6fd0d00f5c6424a416fbb",
          "filename": "content/projects/armsx2-koundinyalanka1.md",
          "status": "modified",
          "additions": 4,
          "deletions": 2,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-koundinyalanka1.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-koundinyalanka1.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-koundinyalanka1.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,9 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T22:40:16.620Z'\n+  lastSynchronized: '2026-10-25T06:10:42.685Z'\n automation:\n   sync: true\n+  github:\n+    repoEtag: W/\"bc2ec53791e5e9efb53fbe3ad44d93b7eb018ad44dfc5b9bbd087127c3292a53\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100\n@@ -46,5 +49,4 @@ relationships:\n   forkOf: ARMSX2/ARMSX2\n   source: PCSX2/pcsx2\n ---\n-\n Automatically discovered by PS2SP. Relevance evidence is recorded in frontmatter; human curation can expand this entry without disabling automated repository metadata synchronization."
        },
        {
          "sha": "168cedc4b8560194d933c3ac8eeabcfab30a6a8a",
          "filename": "content/projects/armsx2-lim2222.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-lim2222.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-lim2222.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-lim2222.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: '2026-10-15T07:51:04Z'\n   url: 'https://github.com/lim2222/ARMSX2/releases/tag/TaikoV1.0'\n activity:\n-  lastSynchronized: '2026-10-21T23:19:38.422Z'\n+  lastSynchronized: '2026-10-24T23:14:55.080Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"b89dd315c475b8e67ee7286741db16d153abbd514711a512ff3884ca72bd91cc\"\n-    releasesEtag: W/\"00e60f84eea5aee2afa9c6c7a2ed3b5778f82c146400f868960df4053786f672\"\n+    repoEtag: W/\"36227103177618d13459201b9996091dd03d0330f43e2eabdd239c31c1a10881\"\n+    releasesEtag: W/\"5267743b45db2b065427ad474db200654168d48bd9799305de05640ee476129d\"\n discovery:\n   method: 'incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 100"
        },
        {
          "sha": "3cdda1d4a0925d4114a7af2b838d60458a3650de",
          "filename": "content/projects/armsx2-ljq123ok.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-ljq123ok.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-ljq123ok.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-ljq123ok.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T01:31:41.437Z'\n+  lastSynchronized: '2026-10-25T01:14:05.070Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"888eadf459d39c612d90aa6943bf46320b93c49f046653998152d74e61e035e6\"\n-    releasesEtag: '\"ff6090af9c91b61292326178ca0823b97aed61799df8cc0ca8f7a6c7d1215cb9\"'\n+    repoEtag: W/\"c5917375386c6fbc012d81da300703ef92d00ba48acd971925f1fa23672386b6\"\n+    releasesEtag: '\"a5c451af9d88eff769190129023bb74c9de83ceb2f12875d0ca286055e5e73e6\"'\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100"
        },
        {
          "sha": "552273919c6a76d66c1fbbe567e7bd7478569200",
          "filename": "content/projects/armsx2-miss1433.md",
          "status": "modified",
          "additions": 4,
          "deletions": 2,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-miss1433.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-miss1433.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-miss1433.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,9 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T22:40:16.620Z'\n+  lastSynchronized: '2026-10-24T20:49:56.529Z'\n automation:\n   sync: true\n+  github:\n+    repoEtag: W/\"2043c7a52baef3e366cad6a633a460eb4c7f6a3186ced598e06d3d3fd783ea47\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100\n@@ -46,5 +49,4 @@ relationships:\n   forkOf: ARMSX2/ARMSX2\n   source: PCSX2/pcsx2\n ---\n-\n Automatically discovered by PS2SP. Relevance evidence is recorded in frontmatter; human curation can expand this entry without disabling automated repository metadata synchronization."
        },
        {
          "sha": "dea11cc666c2547223730fffc5902932f4f9cfe2",
          "filename": "content/projects/armsx2-nx-palindromicbreadloaf.md",
          "status": "modified",
          "additions": 5,
          "deletions": 3,
          "changes": 8,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-nx-palindromicbreadloaf.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-nx-palindromicbreadloaf.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-nx-palindromicbreadloaf.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -20,16 +20,19 @@ repository:\n   defaultBranch: master\n   stars: 68\n   forks: 7\n-  lastCommit: '2026-10-12T01:36:03Z'\n+  lastCommit: '2026-10-22T02:45:23Z'\n latestRelease:\n   tag: v3.0.0\n   name: v3.0.0\n   publishedAt: '2026-08-24T18:18:41Z'\n   url: 'https://github.com/PalindromicBreadLoaf/ARMSX2-NX/releases/tag/v3.0.0'\n activity:\n-  lastSynchronized: '2026-10-20T22:40:16.620Z'\n+  lastSynchronized: '2026-10-25T16:19:41.853Z'\n automation:\n   sync: true\n+  github:\n+    repoEtag: W/\"a301e8ffcc923304e96bdce46e0af954954e31d266d54fca245b7edf5ac28e4b\"\n+    releasesEtag: W/\"2ef9afa74e43caaca5e6550c911da851f0bf95850cd60bb5314457706eb9872f\"\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 95\n@@ -46,5 +49,4 @@ relationships:\n   forkOf: ARMSX2/ARMSX2\n   source: PCSX2/pcsx2\n ---\n-\n Automatically discovered by PS2SP. Relevance evidence is recorded in frontmatter; human curation can expand this entry without disabling automated repository metadata synchronization."
        },
        {
          "sha": "faf92c95e0b9c069553d6baaf8a08c13dc720e22",
          "filename": "content/projects/armsx2-odin107.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-odin107.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-odin107.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-odin107.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-24T11:00:12.651Z'\n+  lastSynchronized: '2026-10-25T11:29:38.508Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"2b6392cf2ede4ab212200a5d768a0f9863c00b0b7c960c517f02c3e48dc720c1\"\n-    releasesEtag: '\"2d8126faf7d60a088ad7a14f528f5a8f3ca919b5cd485e0f97d8ebb20b5102f0\"'\n+    repoEtag: W/\"5009987111978e07d644514779d2cb1451bf58768404ad7ce5c8db6c6f979647\"\n+    releasesEtag: '\"9e77b662133f4d4e3007923dbf3a0cbb2fd896f67c82d38c785dfb153804c449\"'\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100"
        },
        {
          "sha": "790842d9770b7fecdd62b32ba11a3551c299c25f",
          "filename": "content/projects/armsx2-pi5-moosish.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-pi5-moosish.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-pi5-moosish.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-pi5-moosish.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: '2026-09-28T17:50:05Z'\n   url: 'https://github.com/Moosish/ARMSX2-PI5/releases/tag/pi5-builds'\n activity:\n-  lastSynchronized: '2026-10-23T22:11:26.443Z'\n+  lastSynchronized: '2026-10-25T22:33:33.061Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"0bb5c4b6ea9a36037b958ed3cceadf3dcedbd68cdcc06fc92afd76860b1362fe\"\n-    releasesEtag: W/\"14dae464fa5fc0d0e1d23b42ec187d7f9f8a45f3b14856931feadc55cb613ac2\"\n+    repoEtag: W/\"16605a8d680d53ef19d4a71f6bf9a29e9d5beffc4977649405221cfd5fc03c5a\"\n+    releasesEtag: W/\"8df3f3be291e4e522047e1757392d1c1242419f30e1925e9bc1feabc907408a0\"\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 95"
        },
        {
          "sha": "7566b2489dae31c4e65e6ceae883c0afa07a8a6d",
          "filename": "content/projects/armsx2-pietroooooooo.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-pietroooooooo.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-pietroooooooo.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-pietroooooooo.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-21T23:19:38.884Z'\n+  lastSynchronized: '2026-10-24T23:14:55.649Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"c5a6b0b56427f06ab5bba060e56c3e0278cf33f95becece8de971f16af6b23ef\"\n-    releasesEtag: '\"669f535743067c0ac9111e98f8b3d7f826f4edc7838967d02f64b6f505af2660\"'\n+    repoEtag: W/\"af7d3b9101038d358b844445cd0bddec1d70466922720ac22851c1fae8e01576\"\n+    releasesEtag: '\"d958fbfec27a80deaeffdf70593bfd5e43f02f963f152d4a8d9f4917da92f5c0\"'\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100"
        },
        {
          "sha": "e3e121919884526b6f6918aa8c6606488113c7ac",
          "filename": "content/projects/armsx2-poco-f7-matheusdourado.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-poco-f7-matheusdourado.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-poco-f7-matheusdourado.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-poco-f7-matheusdourado.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-17T05:49:52.689Z'\n+  lastSynchronized: '2026-10-25T16:19:42.268Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"6a2195abaa90abdf1958bc2a986ff0c5066d6a035542b53239e25e47b338b833\"\n-    releasesEtag: '\"840d8b22d7db312c6ea7f64c0729d54bdc2329ace0982cd7dfae9d1d30d0d261\"'\n+    repoEtag: W/\"044effdfd9c6e0e4e7a2b32a0586bd603a30fc021e7d2157b720aefa6d3da7d3\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 100"
        },
        {
          "sha": "3db9185796c42859ba32c651c05949253c50e32a",
          "filename": "content/projects/armsx2-reader.md",
          "status": "modified",
          "additions": 4,
          "deletions": 4,
          "changes": 8,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-reader.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-reader.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-reader.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -24,20 +24,20 @@ repository:\n   defaultBranch: main\n   stars: 0\n   forks: 0\n-  lastCommit: '2026-10-16T12:57:51Z'\n+  lastCommit: '2026-10-21T00:44:53Z'\n latestRelease:\n   tag: v0.2.0\n   name: ARMSX2 Reader 0.2.0\n   publishedAt: '2026-10-16T12:57:52Z'\n   url: 'https://github.com/sommepo/armsx2-reader/releases/tag/v0.2.0'\n activity:\n   lastChecked: '2026-10-14T18:54:17.306Z'\n-  lastSynchronized: '2026-10-20T06:00:34.856Z'\n+  lastSynchronized: '2026-10-25T06:10:43.126Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"10687d50bf590bbb814680df7a854f097d30256ddef7b05ae8aaeabeec15edb7\"\n-    releasesEtag: W/\"98e3e9a7706939b8685339d2d58161c473f7c8f3e224f24672d01ebf618e8cba\"\n+    repoEtag: W/\"d3288e696fc697aaa2f75f0a60e6cf888c9bc3fc347d60ef4b61536f7aa8e960\"\n+    releasesEtag: W/\"0dd629b27d4ed55edf3fb4b05e741a6e41ee86251a93a9883a6a0eb3a5e9f9ac\"\n discovery:\n   method: 'pending-promotion:incremental:ps2 in:name,description'\n   confidence: 85"
        },
        {
          "sha": "4a8ef1b061d06703d248d9acbdeec9ec2bc45be4",
          "filename": "content/projects/armsx2-resoa7767.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-resoa7767.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-resoa7767.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-resoa7767.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T01:31:41.854Z'\n+  lastSynchronized: '2026-10-25T01:14:05.691Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"1f409f0571685897e29c722e2dd35dd3b9bd85710565b5347a92d70c66f32591\"\n-    releasesEtag: '\"ff6090af9c91b61292326178ca0823b97aed61799df8cc0ca8f7a6c7d1215cb9\"'\n+    repoEtag: W/\"702a1a5b15fa73a81f5901e77e47beb0e233aafee1973da61b2556a264a73ff4\"\n+    releasesEtag: '\"a5c451af9d88eff769190129023bb74c9de83ceb2f12875d0ca286055e5e73e6\"'\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100"
        },
        {
          "sha": "390b8beea4facb1394fc00eebaf27d454307112b",
          "filename": "content/projects/armsx2-revox291.md",
          "status": "modified",
          "additions": 4,
          "deletions": 2,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-revox291.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-revox291.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-revox291.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,9 +27,12 @@ latestRelease:\n   publishedAt: '2026-08-29T08:44:02Z'\n   url: 'https://github.com/revox291/ARMSX2/releases/tag/nightly-20260829'\n activity:\n-  lastSynchronized: '2026-10-20T22:40:16.620Z'\n+  lastSynchronized: '2026-10-25T16:19:42.847Z'\n automation:\n   sync: true\n+  github:\n+    repoEtag: W/\"842411d1587e23af9def66f2c33a1da84224d46fbc27e1dda865bf8ba296d022\"\n+    releasesEtag: W/\"ef04f8dec3c3e49344ba10ffd60664b6b44847a29db40fce2d961121b64856ed\"\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100\n@@ -47,5 +50,4 @@ relationships:\n   forkOf: ARMSX2/ARMSX2\n   source: PCSX2/pcsx2\n ---\n-\n Automatically discovered by PS2SP. Relevance evidence is recorded in frontmatter; human curation can expand this entry without disabling automated repository metadata synchronization."
        },
        {
          "sha": "d1cba0288ab7d955e7e5e09f716332b8949cdd2a",
          "filename": "content/projects/armsx2-santiago046.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-santiago046.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-santiago046.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-santiago046.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T01:31:42.405Z'\n+  lastSynchronized: '2026-10-25T01:14:06.103Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"30619112451afe73915a142d8f4846bec78636ae7044732ea0f892b98172f28f\"\n-    releasesEtag: '\"ff6090af9c91b61292326178ca0823b97aed61799df8cc0ca8f7a6c7d1215cb9\"'\n+    repoEtag: W/\"6778cc97631e0dab5d7c03f00d96bd6d21956995f50c80cb87dd7cab981a81b1\"\n+    releasesEtag: '\"a5c451af9d88eff769190129023bb74c9de83ceb2f12875d0ca286055e5e73e6\"'\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100"
        },
        {
          "sha": "277c413f91a2a186a8c3156c50a51e3b60135c50",
          "filename": "content/projects/armsx2-shriyashzzz.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-shriyashzzz.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-shriyashzzz.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-shriyashzzz.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:26.976Z'\n+  lastSynchronized: '2026-10-25T22:33:33.615Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"d178cb1e14980bf0c35a3f086d0894cd7965151c0f6bea0051ed88a59769d6c8\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"d33e047cc510697d675cd40aff027ca96b5b9f83979eb28858da7d4e339dc488\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 100"
        },
        {
          "sha": "6124204bd1bd7c671364cd63831b1cbfdec16c6b",
          "filename": "content/projects/armsx2-slimpuggamer.md",
          "status": "modified",
          "additions": 4,
          "deletions": 2,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-slimpuggamer.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-slimpuggamer.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-slimpuggamer.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,9 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T22:40:16.620Z'\n+  lastSynchronized: '2026-10-25T16:19:43.312Z'\n automation:\n   sync: true\n+  github:\n+    repoEtag: W/\"2df49eecf4678455748af082009512a41f51da981d3aa8f5556cd5d28a35c3f2\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100\n@@ -46,5 +49,4 @@ relationships:\n   forkOf: ARMSX2/ARMSX2\n   source: PCSX2/pcsx2\n ---\n-\n Automatically discovered by PS2SP. Relevance evidence is recorded in frontmatter; human curation can expand this entry without disabling automated repository metadata synchronization."
        },
        {
          "sha": "837c2694f64ce6165e3acbf3981efa9f6482bb14",
          "filename": "content/projects/armsx2-stefanomarchesi.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-stefanomarchesi.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-stefanomarchesi.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-stefanomarchesi.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-24T11:00:13.298Z'\n+  lastSynchronized: '2026-10-25T11:29:39.188Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"85970d4f25b9a8a6da44180ebe0a567afd72ede1968e8040dd50cdcdc407aa02\"\n-    releasesEtag: '\"2d8126faf7d60a088ad7a14f528f5a8f3ca919b5cd485e0f97d8ebb20b5102f0\"'\n+    repoEtag: W/\"f400084553de1cb313c1091ee5e9f38cf287ea0b187615f633836ce4f6449755\"\n+    releasesEtag: '\"9e77b662133f4d4e3007923dbf3a0cbb2fd896f67c82d38c785dfb153804c449\"'\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100"
        },
        {
          "sha": "ce4a085c94d9ea52de50827c7d26928ef2d580f6",
          "filename": "content/projects/armsx2-swichroot-icchi16.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-swichroot-icchi16.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-swichroot-icchi16.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-swichroot-icchi16.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T01:31:42.891Z'\n+  lastSynchronized: '2026-10-25T01:14:06.559Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"6ed52f31457b2996a1c235d71ab5f03efad978b620753048e5e1a8557bef87ad\"\n-    releasesEtag: '\"ff6090af9c91b61292326178ca0823b97aed61799df8cc0ca8f7a6c7d1215cb9\"'\n+    repoEtag: W/\"5d2f20234378bea84a832b1b9b54aea472ef13d7409a04ed410432850dbc3496\"\n+    releasesEtag: '\"a5c451af9d88eff769190129023bb74c9de83ceb2f12875d0ca286055e5e73e6\"'\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100"
        },
        {
          "sha": "17b98c15bbe5b44606025bd72165bbecb3bc02df",
          "filename": "content/projects/armsx2-travelingbuddies.md",
          "status": "modified",
          "additions": 4,
          "deletions": 2,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-travelingbuddies.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-travelingbuddies.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-travelingbuddies.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,9 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T22:40:16.620Z'\n+  lastSynchronized: '2026-10-25T06:10:43.756Z'\n automation:\n   sync: true\n+  github:\n+    repoEtag: W/\"692fe3c4ae424a67c9b41cf1af9c19c35e7d3f3711423a3732852ad9fef15fbc\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100\n@@ -46,5 +49,4 @@ relationships:\n   forkOf: ARMSX2/ARMSX2\n   source: PCSX2/pcsx2\n ---\n-\n Automatically discovered by PS2SP. Relevance evidence is recorded in frontmatter; human curation can expand this entry without disabling automated repository metadata synchronization."
        },
        {
          "sha": "c421143e63ebcf0b8aa8b208853bac253696327d",
          "filename": "content/projects/armsx2-wooloomooloo2.md",
          "status": "modified",
          "additions": 4,
          "deletions": 2,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-wooloomooloo2.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-wooloomooloo2.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-wooloomooloo2.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,9 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T22:40:16.620Z'\n+  lastSynchronized: '2026-10-24T20:49:57.032Z'\n automation:\n   sync: true\n+  github:\n+    repoEtag: W/\"9bd849b7c2106a2b4e3493b7158ddc9c755ec2fdc0becf7c0d475879e0427483\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100\n@@ -46,5 +49,4 @@ relationships:\n   forkOf: ARMSX2/ARMSX2\n   source: PCSX2/pcsx2\n ---\n-\n Automatically discovered by PS2SP. Relevance evidence is recorded in frontmatter; human curation can expand this entry without disabling automated repository metadata synchronization."
        },
        {
          "sha": "8e76fe909832a9f2b3e868eba944acf18ec26dd8",
          "filename": "content/projects/armsx2-youngdstbeats-dev.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-youngdstbeats-dev.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2-youngdstbeats-dev.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2-youngdstbeats-dev.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:27.486Z'\n+  lastSynchronized: '2026-10-25T22:33:34.079Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"116096d285b8bea56bc546e7a17f4d044b2a0cbe25977066c91ac51c36b8cc33\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"1a44549507b945bea5f62ec04152e56088b3430c26353468be0f65b681ba5158\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100"
        },
        {
          "sha": "8a2a04c9ec8fe20f41f095e033a7a2f11fd83f4e",
          "filename": "content/projects/armsx2.md",
          "status": "modified",
          "additions": 8,
          "deletions": 8,
          "changes": 16,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -24,20 +24,20 @@ repository:\n   defaultBranch: master\n   stars: 1\n   forks: 0\n-  lastCommit: '2026-10-20T13:24:35Z'\n+  lastCommit: '2026-10-23T10:08:05Z'\n latestRelease:\n-  tag: 2.6.9-fork.1\n-  name: ARMSX2 2.6.9-fork.1\n-  publishedAt: '2026-09-13T11:24:38Z'\n-  url: 'https://github.com/ilyalissoboi/ARMSX2/releases/tag/2.6.9-fork.1'\n+  tag: 2.8.4-fork.1\n+  name: ARMSX2 2.8.4-fork.1\n+  publishedAt: '2026-10-23T11:23:52Z'\n+  url: 'https://github.com/ilyalissoboi/ARMSX2/releases/tag/2.8.4-fork.1'\n activity:\n   lastChecked: '2026-10-20T05:18:56.651Z'\n-  lastSynchronized: '2026-10-21T23:19:39.437Z'\n+  lastSynchronized: '2026-10-24T23:14:56.095Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"a397d4aff21bac434f079e781d4d265a8f86b839eb5060fa3ba2e2f2450d9e45\"\n-    releasesEtag: W/\"6703c4d37c452e646b2db0174f9d2eacae82ecfec544febafdfef80e3f86e5a2\"\n+    repoEtag: W/\"804551764c25dcaf7453c23c0d9edea628c9f89875498f11cb63d1502af52732\"\n+    releasesEtag: W/\"cadb067b2b5ae536f1cd0ac12c57495646579cbe0396feec9703d59a7dca0c36\"\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 85"
        },
        {
          "sha": "84b22f835cf9ad5d7f594e97d87acfe2b468f4ce",
          "filename": "content/projects/armsx2test-murpgy.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2test-murpgy.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Farmsx2test-murpgy.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Farmsx2test-murpgy.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:27.964Z'\n+  lastSynchronized: '2026-10-25T22:33:34.676Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"1af39c70fa844bc7b46d24353c52d4221ea71e3d76eca77bc794e35f5b136a25\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"66e8fb4a12abda4790d56140ea68f7fdcbdda38902525d4f2aa5122f0019c7f4\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:ARMSX2/ARMSX2'\n   confidence: 100"
        },
        {
          "sha": "ff54841b408abf61c461b28827c201eb3ab6bee3",
          "filename": "content/projects/astrarecompish-chatproductions.md",
          "status": "added",
          "additions": 55,
          "deletions": 0,
          "changes": 55,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fastrarecompish-chatproductions.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fastrarecompish-chatproductions.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fastrarecompish-chatproductions.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -0,0 +1,55 @@\n+---\n+name: AstraRecompish\n+slug: astrarecompish-chatproductions\n+summary: >-\n+  PS2 on PS Vita research: PC-assisted PlayStation 2 static recompilation, BIOS\n+  bring-up, and a Vita-native hybrid runtime. Experimental\u2014no playable retail\n+  games or audio output yet.\n+categories:\n+  - preservation\n+  - development\n+tags:\n+  - auto-discovered\n+  - fork\n+features: []\n+authors: []\n+license: MIT\n+homepage: null\n+source:\n+  provider: github\n+  repository: ChatProductions/AstraRecompish\n+  repositoryId: '1432382454'\n+repository:\n+  archived: false\n+  defaultBranch: main\n+  stars: 0\n+  forks: 0\n+  lastCommit: '2026-10-24T21:33:14Z'\n+latestRelease:\n+  tag: null\n+  name: null\n+  publishedAt: null\n+  url: null\n+activity:\n+  lastSynchronized: '2026-10-24T21:53:47.976Z'\n+automation:\n+  sync: true\n+discovery:\n+  method: 'incremental:ps2 in:name,description'\n+  confidence: 100\n+  evidence:\n+    - repository description explicitly identifies PS2\n+    - README explicitly mentions PlayStation 2\n+    - README identifies PS2 homebrew\n+    - GitHub fork of another repository\n+    - 'fork lineage traces to indexed PS2 project: mirou1611/AstraRecomp'\n+  maturity: active-wip\n+verified: false\n+featured: false\n+hidden: false\n+relationships:\n+  forkOf: mirou1611/AstraRecomp\n+  source: mirou1611/AstraRecomp\n+---\n+\n+Automatically discovered by PS2SP. Relevance evidence is recorded in frontmatter; human curation can expand this entry without disabling automated repository metadata synchronization."
        },
        {
          "sha": "dd512465552f294808e53bb00e63661185e83d4a",
          "filename": "content/projects/athenaenv-adaltonfernando.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-adaltonfernando.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-adaltonfernando.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fathenaenv-adaltonfernando.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -35,12 +35,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-22T19:15:34.922Z'\n+  lastSynchronized: '2026-10-25T19:37:28.422Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"0829a075092684b0a778651eb7b333727165978c5a06cb92799173cc4c56772c\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"3b108cf6742ab8807bc34f61b3853a5890278969aa8351bcd3485b4c53b532fe\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'fork-network:DanielSant0s/AthenaEnv'\n   confidence: 100"
        },
        {
          "sha": "2fd87c67a3411e80a3c6f33d63b8a90f36753b7e",
          "filename": "content/projects/athenaenv-athenaenv-ps2-games.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-athenaenv-ps2-games.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-athenaenv-ps2-games.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fathenaenv-athenaenv-ps2-games.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -35,12 +35,12 @@ latestRelease:\n   publishedAt: '2026-10-03T15:47:57Z'\n   url: 'https://github.com/AthenaENV-PS2-GAMES/AthenaEnv/releases/tag/latest'\n activity:\n-  lastSynchronized: '2026-10-22T19:15:35.713Z'\n+  lastSynchronized: '2026-10-25T19:37:28.975Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"504fdcc08c67a6ce30876caf8e2003f56b30d60dd53a0daa85fe7f18f1e6b12c\"\n-    releasesEtag: W/\"b3fe1bc669e5e372d35c0707eee4ef79796cbd1c441cafbbfc7bf1e119f226c8\"\n+    repoEtag: W/\"32bf3e202d43e8f43e0ba308a38b76ba509170ce2b38f1c20af73791cc1b3885\"\n+    releasesEtag: W/\"a48d7ed6c66679b6e433641283d13d477e82253dd6e71a2f827c6a73eacf87ee\"\n discovery:\n   method: 'fork-network:DanielSant0s/AthenaEnv'\n   confidence: 100"
        },
        {
          "sha": "abbf061de7b7d61b21828650538854bfaa09c1e2",
          "filename": "content/projects/athenaenv-bardock88.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-bardock88.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-bardock88.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fathenaenv-bardock88.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -35,12 +35,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:28.482Z'\n+  lastSynchronized: '2026-10-25T22:33:35.201Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"b9b8f89131ff8081578d7419362241192333fcc8387e2280193f704e68fad1a6\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"5ee47097ca4cd41a637b192d23d47e702b6a28b667ce3493d2897c271f9c30cb\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:DanielSant0s/AthenaEnv'\n   confidence: 100"
        },
        {
          "sha": "5f21f34e5234578b5cf4bda5188cad6826bb4f38",
          "filename": "content/projects/athenaenv-biwakosea.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-biwakosea.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-biwakosea.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fathenaenv-biwakosea.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -35,12 +35,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T20:35:10.500Z'\n+  lastSynchronized: '2026-10-24T20:49:57.542Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"c968e2ddec15dab169dd1115ff38d7780db2805706ad3cd0d2acb1a8c5dfa660\"\n-    releasesEtag: '\"94e354af2c4e28e3962b296e8975d0c6ec1a30b51f9be8608fe23fd98f6c999a\"'\n+    repoEtag: W/\"a056bf94927493210bb287f259784ce00f546eb5a055b4f9cf333912a43b52f3\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: 'fork-network:DanielSant0s/AthenaEnv'\n   confidence: 100"
        },
        {
          "sha": "eebddcf0a4e8939364c7ff8d5c2caaf434873c93",
          "filename": "content/projects/athenaenv-freduk.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-freduk.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-freduk.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fathenaenv-freduk.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -35,12 +35,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-17T05:50:06.658Z'\n+  lastSynchronized: '2026-10-25T16:19:43.793Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"779d46c2a388762d940a9531d59f171d76ee125f6c919858712bbcd128b7ddbe\"\n-    releasesEtag: '\"840d8b22d7db312c6ea7f64c0729d54bdc2329ace0982cd7dfae9d1d30d0d261\"'\n+    repoEtag: W/\"3307f1eaed33dde85159c4e251caabc48ad9ce8fced6951b9c74f4f5b5f57085\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'fork-network:DanielSant0s/AthenaEnv'\n   confidence: 100"
        },
        {
          "sha": "1a7849cd66fa82a9b19410567b07f457b13d6714",
          "filename": "content/projects/athenaenv-gustavocastro1.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-gustavocastro1.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-gustavocastro1.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fathenaenv-gustavocastro1.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -35,12 +35,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-21T23:19:39.913Z'\n+  lastSynchronized: '2026-10-24T23:14:56.566Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"ced6e08a8353c3f68e00b5a3351e305ddb02bab632c9e167fd35ae19948a5425\"\n-    releasesEtag: '\"669f535743067c0ac9111e98f8b3d7f826f4edc7838967d02f64b6f505af2660\"'\n+    repoEtag: W/\"f88d1955779f2aecae697ca0864636f36452f707053cb304a0a89066905c55ef\"\n+    releasesEtag: '\"d958fbfec27a80deaeffdf70593bfd5e43f02f963f152d4a8d9f4917da92f5c0\"'\n discovery:\n   method: 'fork-network:DanielSant0s/AthenaEnv'\n   confidence: 100"
        },
        {
          "sha": "bdda84ba280951c10c0417e6d82506d812bec763",
          "filename": "content/projects/athenaenv-gustavofurtad2.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-gustavofurtad2.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-gustavofurtad2.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fathenaenv-gustavofurtad2.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -35,12 +35,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T06:00:35.439Z'\n+  lastSynchronized: '2026-10-25T06:10:44.247Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"a8f50a453007c6abc4cecf79c66605f926e662d49fe14718254d52958eb376f5\"\n-    releasesEtag: '\"beabca011d245159687fb6a685ede4790f750a2002b686c57e5bbc672bd5ded1\"'\n+    repoEtag: W/\"f01302874c2c0ba187538c23299bee991a20a56bfe6aca85406d21cf3eff186c\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'fork-network:DanielSant0s/AthenaEnv'\n   confidence: 100"
        },
        {
          "sha": "4b380ecf6bdf9d554c7dbb925d75b2d05ab3739f",
          "filename": "content/projects/athenaenv-hardlevel.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-hardlevel.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-hardlevel.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fathenaenv-hardlevel.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -35,12 +35,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T20:35:11.019Z'\n+  lastSynchronized: '2026-10-24T20:49:58.052Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"6bfed858de0e2d8cc0cc9cb90f9357f9188a6ce98ac9068fd038a06e77f4a97a\"\n-    releasesEtag: '\"94e354af2c4e28e3962b296e8975d0c6ec1a30b51f9be8608fe23fd98f6c999a\"'\n+    repoEtag: W/\"5c0365bf50b55be0d4907933015f4849d41a965efb98307f3b782330947a3924\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: 'fork-network:DanielSant0s/AthenaEnv'\n   confidence: 100"
        },
        {
          "sha": "15085f43217965bc92a0d3344f72cd8ba01f9fd7",
          "filename": "content/projects/athenaenv-hirotex.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-hirotex.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-hirotex.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fathenaenv-hirotex.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -35,12 +35,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T20:35:11.527Z'\n+  lastSynchronized: '2026-10-24T20:49:58.511Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"619db9391a6c31b7d87a51e4cce4aaaf0550466d26886ad429af345c6e64d333\"\n-    releasesEtag: '\"94e354af2c4e28e3962b296e8975d0c6ec1a30b51f9be8608fe23fd98f6c999a\"'\n+    repoEtag: W/\"b8c5d5b8d2709a66a6cac1b3a180977bb2f15db0d7355eea1f667f7248000114\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: 'fork-network:DanielSant0s/AthenaEnv'\n   confidence: 100"
        },
        {
          "sha": "09a5bfc9b948806730ecb50dff42134543fdf0ac",
          "filename": "content/projects/athenaenv-israpps.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-israpps.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-israpps.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fathenaenv-israpps.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -35,12 +35,12 @@ latestRelease:\n   publishedAt: '2025-06-14T00:43:56Z'\n   url: 'https://github.com/israpps/AthenaEnv/releases/tag/latest'\n activity:\n-  lastSynchronized: '2026-10-24T11:00:13.981Z'\n+  lastSynchronized: '2026-10-25T11:29:39.823Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"a5eefe52c1326715e1c99a25642e167dfa1752a54b955a8ce4658860632fc63b\"\n-    releasesEtag: W/\"ca2ac5c0e60663da19a1dd84cfad609e331fb26d862adc1bcfaba59d3c523721\"\n+    repoEtag: W/\"eb037a9bdb3b11c88130feefa49aae55816e471d149f419a53a2a0a21e62c48d\"\n+    releasesEtag: W/\"aec5bdd8a53cd9a421e6b932e699e0ed92a0f23a8ac0fbe1985cbe3a8c18f511\"\n discovery:\n   method: 'fork-network:DanielSant0s/AthenaEnv'\n   confidence: 100"
        },
        {
          "sha": "c6b8381f2a55a1043ccad0212cca7a6dc1f20b14",
          "filename": "content/projects/athenaenv-lucasalberto01.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-lucasalberto01.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-lucasalberto01.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fathenaenv-lucasalberto01.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -35,12 +35,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-17T05:50:13.838Z'\n+  lastSynchronized: '2026-10-25T16:19:44.257Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"50ab038e88ed0bc047d33bed43dbec92f4a582029d2854e9bff845aedc90af2a\"\n-    releasesEtag: '\"840d8b22d7db312c6ea7f64c0729d54bdc2329ace0982cd7dfae9d1d30d0d261\"'\n+    repoEtag: W/\"669699f6b425520509883812909710306663e0c05e874777b1b9d64cbbe5efe0\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'fork-network:DanielSant0s/AthenaEnv'\n   confidence: 100"
        },
        {
          "sha": "b54fbaa348fb4decc63abd7fd1df1bca396d6a35",
          "filename": "content/projects/athenaenv-matheuslm7.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-matheuslm7.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-matheuslm7.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fathenaenv-matheuslm7.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -35,12 +35,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T01:31:43.341Z'\n+  lastSynchronized: '2026-10-25T01:14:07.021Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"534d7a8d5fa4069bc4810bbcdaa59f2f5c6107d0b7a82185ad614f9b2e6cab8e\"\n-    releasesEtag: '\"ff6090af9c91b61292326178ca0823b97aed61799df8cc0ca8f7a6c7d1215cb9\"'\n+    repoEtag: W/\"db37c8373f98767cc0a38ddccb179d813c82f337757371fd614633f28e2139d2\"\n+    releasesEtag: '\"a5c451af9d88eff769190129023bb74c9de83ceb2f12875d0ca286055e5e73e6\"'\n discovery:\n   method: 'fork-network:DanielSant0s/AthenaEnv'\n   confidence: 100"
        },
        {
          "sha": "c5a1c09a2d355921e728caeb852e1b0ce1c4696c",
          "filename": "content/projects/athenaenv-momo-aux1.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-momo-aux1.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-momo-aux1.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fathenaenv-momo-aux1.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -35,12 +35,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T01:31:43.779Z'\n+  lastSynchronized: '2026-10-25T01:14:07.428Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"4ac85946ebbd966d777b3f8781098c48786cab03a641feff1562fc75c54f233b\"\n-    releasesEtag: '\"ff6090af9c91b61292326178ca0823b97aed61799df8cc0ca8f7a6c7d1215cb9\"'\n+    repoEtag: W/\"884fa578d5092c0aeecf09cdc653dd4eab1fe75ad17e956c743026616f2e220d\"\n+    releasesEtag: '\"a5c451af9d88eff769190129023bb74c9de83ceb2f12875d0ca286055e5e73e6\"'\n discovery:\n   method: 'fork-network:DanielSant0s/AthenaEnv'\n   confidence: 100"
        },
        {
          "sha": "dc4cab6153721c036d62044b3b1f2503207f58a3",
          "filename": "content/projects/athenaenv-moth1995.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-moth1995.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-moth1995.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fathenaenv-moth1995.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -35,12 +35,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:29.059Z'\n+  lastSynchronized: '2026-10-25T22:33:35.692Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"379573c541e26d133a7ee2f98ca4c35f57597eb9b72c18b41388add8881fee5f\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"166c2dcece5291be6644094004bc3370d9f803893b5aa88d8b8017616e8381a8\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:DanielSant0s/AthenaEnv'\n   confidence: 100"
        },
        {
          "sha": "24f33521d1b6db6bf9608f83d2e6c7a345acdc59",
          "filename": "content/projects/athenaenv-revers-br.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-revers-br.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-revers-br.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fathenaenv-revers-br.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -35,12 +35,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-17T05:50:18.574Z'\n+  lastSynchronized: '2026-10-25T16:19:44.658Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"675a8c3e8de2f9110583caa4f993839ce8d3b8516f98c38ea99c9fd42b4715b1\"\n-    releasesEtag: '\"840d8b22d7db312c6ea7f64c0729d54bdc2329ace0982cd7dfae9d1d30d0d261\"'\n+    repoEtag: W/\"e97f1a041559c9c262b3f018747394f35dc52e16d6b5746471e060bce39869e3\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'fork-network:DanielSant0s/AthenaEnv'\n   confidence: 100"
        },
        {
          "sha": "752166a353973a07cfb64a8703cfbbff509d70bd",
          "filename": "content/projects/athenaenv-sovereign-research.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-sovereign-research.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-sovereign-research.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fathenaenv-sovereign-research.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -35,12 +35,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-22T19:15:36.265Z'\n+  lastSynchronized: '2026-10-25T19:37:29.442Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"0a09d44ac81deecbc06bf0f7f08822510e87c1fcb22cea9a27f394a7b3d7694f\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"b97aae390600c2e402c3d58f38b0cdfa45621aafa19ec072d49f080b7ca7cbba\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'fork-network:DanielSant0s/AthenaEnv'\n   confidence: 100"
        },
        {
          "sha": "49d10ea197461e53abbc9be65ad4f2c06a465077",
          "filename": "content/projects/athenaenv-suesaking.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-suesaking.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-suesaking.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fathenaenv-suesaking.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -35,12 +35,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T06:00:36.047Z'\n+  lastSynchronized: '2026-10-25T06:10:44.699Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"ee86fa33972f27be7d9534bfa589bb9ce601c8451068f3057fd3ed24566f4a65\"\n-    releasesEtag: '\"beabca011d245159687fb6a685ede4790f750a2002b686c57e5bbc672bd5ded1\"'\n+    repoEtag: W/\"40c408f74280c6dfe732f8b79da942ab416d67a4395b9a553ad692d65405fd07\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'fork-network:DanielSant0s/AthenaEnv'\n   confidence: 100"
        },
        {
          "sha": "5c197fba2cadce072881841f875e5ab8534a4adf",
          "filename": "content/projects/athenaenv-tpanarchist.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-tpanarchist.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-tpanarchist.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fathenaenv-tpanarchist.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -35,12 +35,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T20:35:12.044Z'\n+  lastSynchronized: '2026-10-24T20:49:58.930Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"ea931e6133c3ae26bb0dcbf7127718948d7d6e46ea62b85bac00656116b55872\"\n-    releasesEtag: '\"94e354af2c4e28e3962b296e8975d0c6ec1a30b51f9be8608fe23fd98f6c999a\"'\n+    repoEtag: W/\"24e1301dc3be8d30307a9d0965de9965e99ffe8b1ac1504ce2448300211339a4\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: 'fork-network:DanielSant0s/AthenaEnv'\n   confidence: 100"
        },
        {
          "sha": "5102b0c81a0a9993fe764dd7d8c7bfc620b32259",
          "filename": "content/projects/athenaenv-ulissesribeiro.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-ulissesribeiro.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-ulissesribeiro.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fathenaenv-ulissesribeiro.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -35,12 +35,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-22T19:15:36.815Z'\n+  lastSynchronized: '2026-10-25T19:37:29.926Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"c3aaddb8a064b284136229195bdd0e473120363ae11ee5f9d20b666bfdc80ee4\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"a53b225de6d550c04563a8e289c288c7c8a5bfd47185fa10986009f20b0bf4c2\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'fork-network:DanielSant0s/AthenaEnv'\n   confidence: 100"
        },
        {
          "sha": "9d6c787f37cc3cc3dfd00d955e4d95ef411d787b",
          "filename": "content/projects/athenaenv-usernamx42.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-usernamx42.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-usernamx42.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fathenaenv-usernamx42.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -35,12 +35,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T06:00:36.620Z'\n+  lastSynchronized: '2026-10-25T06:10:45.106Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"c3852d06093e0aff126e62d4f431f09678c7b06bb844356caf02b3254e91ca2e\"\n-    releasesEtag: '\"beabca011d245159687fb6a685ede4790f750a2002b686c57e5bbc672bd5ded1\"'\n+    repoEtag: W/\"9e3d819a3b003ed9bbb35a255a5b5618fcc8a45321aadb646348b7cd65e9a4b9\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'fork-network:DanielSant0s/AthenaEnv'\n   confidence: 100"
        },
        {
          "sha": "abe726b5251b8aa6bea876b7a82b4fdaafa6d163",
          "filename": "content/projects/athenaenv-wherkinmann.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-wherkinmann.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-wherkinmann.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fathenaenv-wherkinmann.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -35,12 +35,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-17T05:50:25.096Z'\n+  lastSynchronized: '2026-10-25T16:19:45.097Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"64fc9d2a1ba2b6b7233a6b9e21f0f6186d1da47bac3b8385b41154c481de042d\"\n-    releasesEtag: '\"840d8b22d7db312c6ea7f64c0729d54bdc2329ace0982cd7dfae9d1d30d0d261\"'\n+    repoEtag: W/\"355741854fcadbe3b515f9aac09da534dca37d4afbd33c83c86087823f5a5e55\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'fork-network:DanielSant0s/AthenaEnv'\n   confidence: 100"
        },
        {
          "sha": "b3dddbb79352900fd22298898d578283c4167d8b",
          "filename": "content/projects/athenaenv-whizboy-arnold.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-whizboy-arnold.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-whizboy-arnold.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fathenaenv-whizboy-arnold.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -35,12 +35,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:29.585Z'\n+  lastSynchronized: '2026-10-25T22:33:36.178Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"e91c2f85d8fb7d7c0f81c65eb5de169415ba5e1e98a6dedf622b1f95d7e0b19d\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"31c40e3a7bdc5fdfddd8f3af30a4aee30a0159c69e0fb6b75193861b63dc021e\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:DanielSant0s/AthenaEnv'\n   confidence: 100"
        },
        {
          "sha": "1aa194cc2cb3c7a3378f868d9eeb46d70b1050b9",
          "filename": "content/projects/athenaenv-wilkersebastian.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-wilkersebastian.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv-wilkersebastian.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fathenaenv-wilkersebastian.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -35,12 +35,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-24T11:00:14.600Z'\n+  lastSynchronized: '2026-10-25T11:29:40.343Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"561e4be5347c388281973901c8b0c5fa34a603bab9b95d0869bb947514260b0d\"\n-    releasesEtag: '\"2d8126faf7d60a088ad7a14f528f5a8f3ca919b5cd485e0f97d8ebb20b5102f0\"'\n+    repoEtag: W/\"d86285e594a00ca6266a37e2280666d3a314e47ae972e1f8ac0d490bcac68786\"\n+    releasesEtag: '\"9e77b662133f4d4e3007923dbf3a0cbb2fd896f67c82d38c785dfb153804c449\"'\n discovery:\n   method: 'fork-network:DanielSant0s/AthenaEnv'\n   confidence: 100"
        },
        {
          "sha": "575401e26072bf57ca07e939241b02357cdc18c1",
          "filename": "content/projects/athenaenv.md",
          "status": "modified",
          "additions": 5,
          "deletions": 5,
          "changes": 10,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fathenaenv.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fathenaenv.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -24,22 +24,22 @@ source:\n repository:\n   archived: false\n   defaultBranch: main\n-  stars: 698\n+  stars: 699\n   forks: 60\n-  lastCommit: '2026-10-19T10:28:30Z'\n+  lastCommit: '2026-10-24T21:07:20Z'\n latestRelease:\n   tag: latest\n   name: Development build\n   publishedAt: '2026-08-01T17:49:34Z'\n   url: 'https://github.com/DanielSant0s/AthenaEnv/releases/tag/latest'\n activity:\n   lastChecked: '2026-09-29T00:27:00.178Z'\n-  lastSynchronized: '2026-10-22T19:15:37.408Z'\n+  lastSynchronized: '2026-10-25T19:37:30.489Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"4b24e6ee236a7082dfb4e3dd1817643b58f9e71c6da3a788658244ee90a7cb3b\"\n-    releasesEtag: W/\"164c01829de30e47b11c1c61a0e49ceef6cc310613de109e79f159add31033c9\"\n+    repoEtag: W/\"b75cdbe952893ac30b6eff86f5ae426c299e6fefa52bac1ffedadf309d2c45ce\"\n+    releasesEtag: W/\"d7afe42a4ab36cfb99bc480470dc0005eccbf0d4194f87031aed63feba99a570\"\n discovery:\n   method: curated\n   confidence: 100"
        },
        {
          "sha": "6f8f31ce8cefd6d86ac2984c70f286df5267fb74",
          "filename": "content/projects/awesome-ps2.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fawesome-ps2.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fawesome-ps2.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fawesome-ps2.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-24T11:00:15.144Z'\n+  lastSynchronized: '2026-10-25T11:29:40.907Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"414276014f894dc038be8dc5030a25d674e3ceb74b4fa633eda400972a64908d\"\n-    releasesEtag: '\"2d8126faf7d60a088ad7a14f528f5a8f3ca919b5cd485e0f97d8ebb20b5102f0\"'\n+    repoEtag: W/\"c329a26e27a27b4276b1d9fe47b0a149cfd4a1fdd3ed07a7420a7f7ad01181ec\"\n+    releasesEtag: '\"9e77b662133f4d4e3007923dbf3a0cbb2fd896f67c82d38c785dfb153804c449\"'\n discovery:\n   method: curated-owner\n   confidence: 100"
        },
        {
          "sha": "3f52d6588026cde9b5a6684142e22c7e4bdb955a",
          "filename": "content/projects/bakugan-battle-brawlers-ptbr.md",
          "status": "added",
          "additions": 49,
          "deletions": 0,
          "changes": 49,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbakugan-battle-brawlers-ptbr.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbakugan-battle-brawlers-ptbr.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbakugan-battle-brawlers-ptbr.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -0,0 +1,49 @@\n+---\n+name: bakugan-battle-brawlers-ptbr\n+slug: bakugan-battle-brawlers-ptbr\n+summary: >-\n+  Tradu\u00e7\u00e3o de f\u00e3 de Bakugan Battle Brawlers (PS2, PAL) para portugu\u00eas do Brasil\n+  \u2014 patch xdelta para a sua pr\u00f3pria ISO\n+categories:\n+  - utilities\n+tags:\n+  - auto-discovered\n+features: []\n+authors: []\n+license: MIT\n+homepage: null\n+source:\n+  provider: github\n+  repository: AsaGeki/bakugan-battle-brawlers-ptbr\n+  repositoryId: '1432645815'\n+repository:\n+  archived: false\n+  defaultBranch: main\n+  stars: 0\n+  forks: 0\n+  lastCommit: '2026-10-24T22:28:52Z'\n+latestRelease:\n+  tag: v1.0\n+  name: v1.0\n+  publishedAt: '2026-10-24T22:28:53Z'\n+  url: 'https://github.com/AsaGeki/bakugan-battle-brawlers-ptbr/releases/tag/v1.0'\n+activity:\n+  lastSynchronized: '2026-10-25T16:19:45.543Z'\n+automation:\n+  sync: true\n+  github:\n+    repoEtag: W/\"700e01cf9833da6dbdf8e72b6ef3ad732986964cccd067b091fe37b8c98d0b8e\"\n+    releasesEtag: W/\"7ae1df2ce3e3043ad09a03a5ed7b1839028f376159744ed141d251ee1575806b\"\n+discovery:\n+  method: 'incremental:ps2 in:name,description'\n+  confidence: 100\n+  evidence:\n+    - PS2-specific topic\n+    - repository description explicitly identifies PS2\n+    - published GitHub release present\n+  maturity: released-active\n+verified: false\n+featured: false\n+hidden: false\n+---\n+Automatically discovered by PS2SP. Relevance evidence is recorded in frontmatter; human curation can expand this entry without disabling automated repository metadata synchronization."
        },
        {
          "sha": "6be8ff1a42808fb494a5db18d3fa31c5ac9223a9",
          "filename": "content/projects/beta9-rcovery-backup-checkpoint-nuno6573.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbeta9-rcovery-backup-checkpoint-nuno6573.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbeta9-rcovery-backup-checkpoint-nuno6573.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbeta9-rcovery-backup-checkpoint-nuno6573.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -28,12 +28,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T20:35:12.640Z'\n+  lastSynchronized: '2026-10-24T20:49:59.427Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"6beba54ebaa9981c7f4936f530a0c577ddea8d2d9d61989c29d3c131b0ec9985\"\n-    releasesEtag: '\"94e354af2c4e28e3962b296e8975d0c6ec1a30b51f9be8608fe23fd98f6c999a\"'\n+    repoEtag: W/\"cc98fb63f10487e2551d6fdf3a572f223798247ed69cd5a81bb8867e72ddcf90\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: 'fork-network:NathanNeurotic/POPSLoader'\n   confidence: 95"
        },
        {
          "sha": "b584a6a445d8facefccd6a383e4e3c275b407d0e",
          "filename": "content/projects/binmerger.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbinmerger.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbinmerger.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbinmerger.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: '2022-09-21T18:41:00Z'\n   url: 'https://github.com/israpps/BinMerger/releases/tag/Latest'\n activity:\n-  lastSynchronized: '2026-10-17T05:50:34.895Z'\n+  lastSynchronized: '2026-10-25T16:19:45.980Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"384b68b35c93960b842c8f54b60c85f4014a1ad1e7a3e6a3b28e9749bc254444\"\n-    releasesEtag: W/\"70d18214d11a2867bded63b14d1d44efcd798aa4d4c0d05cfeff72760fc78e17\"\n+    repoEtag: W/\"f0ef17a47f2a1f4d87047faad537b3d042198de5771f3ab299955030949a7cd1\"\n+    releasesEtag: W/\"491b14ab4ec38ca72a2b012b07532dcc4d41d914799f35db879650e76096fa2d\"\n discovery:\n   method: 'starred-curated:NathanNeurotic'\n   confidence: 100"
        },
        {
          "sha": "f2d2d163f9e681dbd5b8a12789bd0ae16b540a58",
          "filename": "content/projects/binutils-gdb-spicedev.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbinutils-gdb-spicedev.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbinutils-gdb-spicedev.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbinutils-gdb-spicedev.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -28,12 +28,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:30.090Z'\n+  lastSynchronized: '2026-10-25T22:33:36.756Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"6c9cb61b4f0019f7f96e1e20dffcecc7b1e63dbe5079b05cecda77c1195a0317\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"96b749ab5e5cae0a08ac2a3a7c6d78949eaf9e7df1a8a3a1b770b830a220bcb3\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:ps2dev/binutils-gdb'\n   confidence: 95"
        },
        {
          "sha": "4cdbc78cc73a6d74cd3e52a885e088627c0d0acb",
          "filename": "content/projects/blazium-toolchain.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fblazium-toolchain.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fblazium-toolchain.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fblazium-toolchain.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   url: 'https://github.com/blazium-games/blazium-toolchain/releases/tag/v0.1.16'\n activity:\n   lastChecked: '2026-09-29T00:27:46.918Z'\n-  lastSynchronized: '2026-10-24T11:00:15.740Z'\n+  lastSynchronized: '2026-10-25T11:29:41.490Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"14f585b2f7bd73d4eb2660e29d99f1e29339c304c19214cdbf2b439180d8db1f\"\n-    releasesEtag: W/\"e81557a232b2977a800ceeb0a01f13ef5c7bc03ca7b4c844578c5ccca51563d5\"\n+    repoEtag: W/\"d022ab20dc09b700396de27414b5a1c2e54998c403e64f87cb0cca54e518783d\"\n+    releasesEtag: W/\"a7b76c0d355492c7ac82c05def2e7737831818f779a727b32916de1274c70674\"\n discovery:\n   method: github-search\n   confidence: 100"
        },
        {
          "sha": "bd5a7a9d92d457201619425ed2f00da542cad4fe",
          "filename": "content/projects/bootcard-igr-israpps.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbootcard-igr-israpps.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbootcard-igr-israpps.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbootcard-igr-israpps.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -28,12 +28,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:30.600Z'\n+  lastSynchronized: '2026-10-25T22:33:37.312Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"c66b5054ebcdc09dcc6a32afec3aa2d22a792bd7d66cea089b44f534cc298049\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"753945929383ccb2851c03f156ac283d770ed978ef7a6a5b88925ae3a069a8a4\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'starred-curated:NathanNeurotic'\n   confidence: 95"
        },
        {
          "sha": "0434946d1ddce811fa54ad349f3a83f77ff41101",
          "filename": "content/projects/bully-ps2-16x10-sediaslalu-star.md",
          "status": "added",
          "additions": 52,
          "deletions": 0,
          "changes": 52,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbully-ps2-16x10-sediaslalu-star.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbully-ps2-16x10-sediaslalu-star.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbully-ps2-16x10-sediaslalu-star.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -0,0 +1,52 @@\n+---\n+name: bully-ps2-16x10\n+slug: bully-ps2-16x10-sediaslalu-star\n+summary: >-\n+  16:10 camera projection and UI scaling patch for Bully PS2 USA (SLUS-21269 /\n+  28703748), tested on ARMSX2 macOS.\n+categories:\n+  - utilities\n+tags:\n+  - auto-discovered\n+  - fork\n+features: []\n+authors: []\n+license: null\n+homepage: null\n+source:\n+  provider: github\n+  repository: sediaslalu-star/bully-ps2-16x10\n+  repositoryId: '1430712146'\n+repository:\n+  archived: false\n+  defaultBranch: main\n+  stars: 0\n+  forks: 0\n+  lastCommit: '2026-10-09T08:16:28Z'\n+latestRelease:\n+  tag: null\n+  name: null\n+  publishedAt: null\n+  url: null\n+activity:\n+  lastSynchronized: '2026-10-24T21:53:47.976Z'\n+automation:\n+  sync: true\n+discovery:\n+  method: 'fork-network:MatthewBlenk/bully-ps2-16x10'\n+  confidence: 100\n+  evidence:\n+    - repository name explicitly identifies PS2\n+    - repository description explicitly identifies PS2\n+    - GitHub fork of another repository\n+    - 'fork lineage traces to indexed PS2 project: MatthewBlenk/bully-ps2-16x10'\n+  maturity: active-unreleased\n+verified: false\n+featured: false\n+hidden: false\n+relationships:\n+  forkOf: MatthewBlenk/bully-ps2-16x10\n+  source: MatthewBlenk/bully-ps2-16x10\n+---\n+\n+Automatically discovered by PS2SP. Relevance evidence is recorded in frontmatter; human curation can expand this entry without disabling automated repository metadata synchronization."
        },
        {
          "sha": "f4687f38f199afaeb7b80c853c6ee83e7491f131",
          "filename": "content/projects/bully-ps2-16x10.md",
          "status": "modified",
          "additions": 4,
          "deletions": 4,
          "changes": 8,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbully-ps2-16x10.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbully-ps2-16x10.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbully-ps2-16x10.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -20,20 +20,20 @@ repository:\n   archived: false\n   defaultBranch: main\n   stars: 0\n-  forks: 0\n+  forks: 1\n   lastCommit: '2026-10-09T08:16:28Z'\n latestRelease:\n   tag: revision-4\n   name: 'Revision 4 \u2014 16:10 community test release'\n   publishedAt: '2026-10-09T08:11:08Z'\n   url: 'https://github.com/MatthewBlenk/bully-ps2-16x10/releases/tag/revision-4'\n activity:\n-  lastSynchronized: '2026-10-17T05:50:43.905Z'\n+  lastSynchronized: '2026-10-25T16:19:46.445Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"9130bb519079bd5fcd814bd72dc276cd798fc3ea1080216d018eaad5e225961c\"\n-    releasesEtag: W/\"c46b4fde00bc3d4ad61407585d5d54bb10bb54e1815984e8e204250124455ad4\"\n+    repoEtag: W/\"f4e8f440afe9dabe202735511cad906ffed69d33e4da018453d26850275829a0\"\n+    releasesEtag: W/\"675965b2e9a1bad74432f895286197cc64ba5571df759c18dc65598782bb84ca\"\n discovery:\n   method: 'incremental:ps2 in:name,description'\n   confidence: 100"
        },
        {
          "sha": "1c99a23a252b41496d2f8dcb178c2e06d866e39b",
          "filename": "content/projects/burnout-dominator-musickit.md",
          "status": "modified",
          "additions": 4,
          "deletions": 4,
          "changes": 8,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fburnout-dominator-musickit.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fburnout-dominator-musickit.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fburnout-dominator-musickit.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -21,19 +21,19 @@ repository:\n   defaultBranch: main\n   stars: 4\n   forks: 0\n-  lastCommit: '2026-10-14T20:52:52Z'\n+  lastCommit: '2026-10-20T22:34:16Z'\n latestRelease:\n   tag: null\n   name: null\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T06:00:37.583Z'\n+  lastSynchronized: '2026-10-25T06:10:45.838Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"cf6be6560ae4137e6fd791fe34a3b9e71ea81da0cba43f35f851026f3a945e6d\"\n-    releasesEtag: '\"beabca011d245159687fb6a685ede4790f750a2002b686c57e5bbc672bd5ded1\"'\n+    repoEtag: W/\"760c50ffc1b9cf28bbcef0ed7080754db6e079adc0898cb81b97c980e0c2395a\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'incremental:ps2 in:name,description'\n   confidence: 100"
        },
        {
          "sha": "251bc000f3f2842bbf3586efaa83a539897ed26a",
          "filename": "content/projects/burnout-revenge-musickit.md",
          "status": "modified",
          "additions": 4,
          "deletions": 4,
          "changes": 8,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fburnout-revenge-musickit.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fburnout-revenge-musickit.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fburnout-revenge-musickit.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -22,19 +22,19 @@ repository:\n   defaultBranch: main\n   stars: 9\n   forks: 0\n-  lastCommit: '2026-10-14T20:52:49Z'\n+  lastCommit: '2026-10-20T22:34:14Z'\n latestRelease:\n   tag: null\n   name: null\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-17T05:50:45.044Z'\n+  lastSynchronized: '2026-10-25T16:19:46.840Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"c9d7c62cd2651a6b0f8ee86afd79b97c79306ba0b28dcaddbb1869015997411e\"\n-    releasesEtag: '\"840d8b22d7db312c6ea7f64c0729d54bdc2329ace0982cd7dfae9d1d30d0d261\"'\n+    repoEtag: W/\"2f5e9dd2172fe3d21d8460be339bc55d48adc8de50a9305619663425a3944eac\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'incremental:ps2 in:name,description'\n   confidence: 100"
        },
        {
          "sha": "d0857ff9198f237950daf616d5a8bc30927c2082",
          "filename": "content/projects/burnoutdominatorreversed.md",
          "status": "modified",
          "additions": 6,
          "deletions": 6,
          "changes": 12,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fburnoutdominatorreversed.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fburnoutdominatorreversed.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fburnoutdominatorreversed.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -21,21 +21,21 @@ source:\n repository:\n   archived: false\n   defaultBranch: main\n-  stars: 10\n-  forks: 1\n-  lastCommit: '2026-10-23T21:43:53Z'\n+  stars: 12\n+  forks: 2\n+  lastCommit: '2026-10-25T18:42:07Z'\n latestRelease:\n   tag: null\n   name: null\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:31.091Z'\n+  lastSynchronized: '2026-10-25T22:33:37.730Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"29bba6fe1279a17ca7223635f74fe724b57d48c3e5a1f56991c4629ae3f5e8ca\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"ec89e19cddfeaef4fe60597feae275a0a4806f0ceef279c61be86f03e1c1d68d\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'incremental:ps2 in:name,description'\n   confidence: 100"
        },
        {
          "sha": "39e4da2e9fb35a4bf766f43818f38644b85cf84f",
          "filename": "content/projects/butterdos-cualquiercosa327.md",
          "status": "modified",
          "additions": 4,
          "deletions": 2,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterdos-cualquiercosa327.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterdos-cualquiercosa327.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterdos-cualquiercosa327.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,9 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-21T06:34:46.723Z'\n+  lastSynchronized: '2026-10-25T06:10:46.371Z'\n automation:\n   sync: true\n+  github:\n+    repoEtag: W/\"fb4aba845309b0e71392c2a9bfc048bebe66d9a38bd713a9f76f42e8a6ec4765\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'fork-network:wtjwtjwtjwtj/ButterDOS'\n   confidence: 95\n@@ -50,5 +53,4 @@ relationships:\n   forkOf: wtjwtjwtjwtj/ButterDOS\n   source: ButterscotchRunner/Butterscotch\n ---\n-\n Automatically discovered by PS2SP. Relevance evidence is recorded in frontmatter; human curation can expand this entry without disabling automated repository metadata synchronization."
        },
        {
          "sha": "16ce9d42ded9e7cd07c6f72874a829ef5fd668f2",
          "filename": "content/projects/butterdos-wtjwtjwtjwtj.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterdos-wtjwtjwtjwtj.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterdos-wtjwtjwtjwtj.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterdos-wtjwtjwtjwtj.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T20:35:13.191Z'\n+  lastSynchronized: '2026-10-24T20:49:59.929Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"2c7d26b44e0f6a45122c2294210dd0aecb0ddf865046a6984302f15f71f813c9\"\n-    releasesEtag: '\"94e354af2c4e28e3962b296e8975d0c6ec1a30b51f9be8608fe23fd98f6c999a\"'\n+    repoEtag: W/\"8028279c94a306a7ae55fb63edbfb9ae6ef3c83939813a809288a87bde44fa4f\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: 'fork-network:ButterscotchRunner/Butterscotch'\n   confidence: 95"
        },
        {
          "sha": "3068500fc9292cdcc4c1d484ff2f535b9d9668e8",
          "filename": "content/projects/butterfork-topmodder.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterfork-topmodder.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterfork-topmodder.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterfork-topmodder.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T20:35:13.759Z'\n+  lastSynchronized: '2026-10-24T20:50:00.576Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"5f66556cc2f010ae9bd88a2d5ccc08d9bcaa612d86f75c8b61ae414e8b6a9aa5\"\n-    releasesEtag: '\"94e354af2c4e28e3962b296e8975d0c6ec1a30b51f9be8608fe23fd98f6c999a\"'\n+    repoEtag: W/\"34c2f158a4966a50cf19907598b6814ab348cdc35739dafd898484505a486ac3\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: 'fork-network:ButterscotchRunner/Butterscotch'\n   confidence: 100"
        },
        {
          "sha": "2294e5d517690ec44265877436682dfd26b4c352",
          "filename": "content/projects/butterscotch-360-marques16-bit.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-360-marques16-bit.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-360-marques16-bit.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-360-marques16-bit.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-22T19:15:38.128Z'\n+  lastSynchronized: '2026-10-25T19:37:31.094Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"4dfa43e0c31745f071f25aac5481f348adb9496b57ae16199eaa77954f44b4ae\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"728618ee2bf0ecf3a16414d79d5d5140d5e5d2c0f2da12e7e2910f3ed38bad8c\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'fork-network:ceilingtilefan/Butterscotch-360'\n   confidence: 95"
        },
        {
          "sha": "0d125cdc1eb73fd1715a630d9bf4d245a1c1affe",
          "filename": "content/projects/butterscotch-aloxado320.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-aloxado320.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-aloxado320.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-aloxado320.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T06:00:38.180Z'\n+  lastSynchronized: '2026-10-25T06:10:46.873Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"27916ff7358cd93cdf0bec0ec29dde10386cfe20daad91780eadf6bb303a2cac\"\n-    releasesEtag: '\"beabca011d245159687fb6a685ede4790f750a2002b686c57e5bbc672bd5ded1\"'\n+    repoEtag: W/\"fd152eb55f6fbb3de417f59d81cd97a276716e28fc87d0e7b5f4bd25a4b55557\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'fork-network:ButterscotchRunner/Butterscotch'\n   confidence: 95"
        },
        {
          "sha": "261228adca9e7e9e27148dc6fd3a073632ca699e",
          "filename": "content/projects/butterscotch-ananim353.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-ananim353.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-ananim353.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-ananim353.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T06:00:38.858Z'\n+  lastSynchronized: '2026-10-25T06:10:47.332Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"ef7bd6c9baedafc773ca429b2a08c63cc4981637dd745b040534a9720633f8da\"\n-    releasesEtag: '\"beabca011d245159687fb6a685ede4790f750a2002b686c57e5bbc672bd5ded1\"'\n+    repoEtag: W/\"0ef8984663d024fbc6c530cdccab368e093b231afe9d621e3cfbee136912db55\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'fork-network:ButterscotchRunner/Butterscotch'\n   confidence: 95"
        },
        {
          "sha": "fa1e5fad4368132c165eb4630aaeef51381fab0a",
          "filename": "content/projects/butterscotch-ckckckckckckckeaaa.md",
          "status": "modified",
          "additions": 5,
          "deletions": 3,
          "changes": 8,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-ckckckckckckckeaaa.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-ckckckckckckckeaaa.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-ckckckckckckckeaaa.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -23,16 +23,19 @@ repository:\n   defaultBranch: main\n   stars: 0\n   forks: 0\n-  lastCommit: '2026-10-23T17:13:38Z'\n+  lastCommit: '2026-10-23T19:39:06Z'\n latestRelease:\n   tag: null\n   name: null\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T17:27:12.070Z'\n+  lastSynchronized: '2026-10-24T23:14:57.022Z'\n automation:\n   sync: true\n+  github:\n+    repoEtag: W/\"cd62b4d09dbaeb6bf5d44ec5dcd5d3e2152a7062c2045d92896da6a70c2e17a4\"\n+    releasesEtag: '\"d958fbfec27a80deaeffdf70593bfd5e43f02f963f152d4a8d9f4917da92f5c0\"'\n discovery:\n   method: 'incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 95\n@@ -50,5 +53,4 @@ relationships:\n   forkOf: ButterscotchRunner/Butterscotch\n   source: ButterscotchRunner/Butterscotch\n ---\n-\n Automatically discovered by PS2SP. Relevance evidence is recorded in frontmatter; human curation can expand this entry without disabling automated repository metadata synchronization."
        },
        {
          "sha": "a114c3a0f190c768b1f19b6f45eb442420d49d33",
          "filename": "content/projects/butterscotch-classic0306.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-classic0306.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-classic0306.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-classic0306.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T20:35:14.330Z'\n+  lastSynchronized: '2026-10-24T20:50:01.045Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"6880400ccab27faedca02d8d9c494ffaecb2a8631b3706fb84655fc142a8f6f7\"\n-    releasesEtag: '\"94e354af2c4e28e3962b296e8975d0c6ec1a30b51f9be8608fe23fd98f6c999a\"'\n+    repoEtag: W/\"ebad0fdfaf626bf27264ef8bc0ee52d7737b02d41df5b2a815ca90e03cf6092f\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: 'fork-network:ButterscotchRunner/Butterscotch'\n   confidence: 100"
        },
        {
          "sha": "221d0e62982445443df6814b36ed74a0a09a1792",
          "filename": "content/projects/butterscotch-cobaltgit.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-cobaltgit.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-cobaltgit.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-cobaltgit.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: '2026-08-24T09:16:38Z'\n   url: 'https://github.com/cobaltgit/Butterscotch/releases/tag/onion-v2'\n activity:\n-  lastSynchronized: '2026-10-21T23:19:40.577Z'\n+  lastSynchronized: '2026-10-24T23:14:57.511Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"dd5d42ff92e63dc1f416024af20d0e9b1b8557f2fb019c1ad227d6ead1447ffd\"\n-    releasesEtag: W/\"914b510eae2ca529af20e1e029c25fb73d9a7630de1fcdc7598f8726ddccbbe8\"\n+    repoEtag: W/\"8a59e17cb8c3bb169d9083a2d99ca3bfb8d2809eb09fbde208e8d470ec7e170e\"\n+    releasesEtag: W/\"499d7d75ac332fa9f053c7c3299fff75a465fe0599b3e825cde06317043218c5\"\n discovery:\n   method: 'fork-network:ButterscotchRunner/Butterscotch'\n   confidence: 95"
        },
        {
          "sha": "f358cff79c47febc67375d7d2d69b313f91a528d",
          "filename": "content/projects/butterscotch-d3fau4.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-d3fau4.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-d3fau4.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-d3fau4.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T06:00:39.446Z'\n+  lastSynchronized: '2026-10-25T06:10:47.831Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"fed122ae994aaf88df27cbdb411463b00bfc0c01b358c69d4b16f0bc39a8d4ea\"\n-    releasesEtag: '\"beabca011d245159687fb6a685ede4790f750a2002b686c57e5bbc672bd5ded1\"'\n+    repoEtag: W/\"41c33f25edaa5abed3d75bcb9a604b6a633d90f36e3c6dd2fc95f1e6b3984bcb\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'fork-network:ButterscotchRunner/Butterscotch'\n   confidence: 100"
        },
        {
          "sha": "b9554a5be55ae9701780fcf9e58e6f56a1ed1952",
          "filename": "content/projects/butterscotch-fancy2209.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-fancy2209.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-fancy2209.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-fancy2209.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-17T05:50:59.206Z'\n+  lastSynchronized: '2026-10-25T16:19:47.474Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"80802a503add964b4f5a6840a44b63c35b5b056a37372d22c508295e1ac10280\"\n-    releasesEtag: '\"840d8b22d7db312c6ea7f64c0729d54bdc2329ace0982cd7dfae9d1d30d0d261\"'\n+    repoEtag: W/\"befbe6ce480fbea8aefbe3a62c818d3bfc4d9fc345ebc70a87aed0bf50a3070a\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'fork-network:ButterscotchRunner/Butterscotch'\n   confidence: 100"
        },
        {
          "sha": "4587c75546983f42ca45f8645c85f0206dbf6b32",
          "filename": "content/projects/butterscotch-feiyunwill.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-feiyunwill.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-feiyunwill.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-feiyunwill.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T01:31:44.288Z'\n+  lastSynchronized: '2026-10-25T01:14:07.932Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"78ebf1e535303495f58e877ad87d0f370c03d83765147ad6f18e9e7e0ea67f4f\"\n-    releasesEtag: '\"ff6090af9c91b61292326178ca0823b97aed61799df8cc0ca8f7a6c7d1215cb9\"'\n+    repoEtag: W/\"00fae1932fbc12d66e744cba4677fa2f0ad692bbe2ddc4362f21fb19778ca6f4\"\n+    releasesEtag: '\"a5c451af9d88eff769190129023bb74c9de83ceb2f12875d0ca286055e5e73e6\"'\n discovery:\n   method: 'fork-network:ButterscotchRunner/Butterscotch'\n   confidence: 100"
        },
        {
          "sha": "836f7261e4b451041118ebec9d0c51d221d93d0e",
          "filename": "content/projects/butterscotch-frenchwizz.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-frenchwizz.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-frenchwizz.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-frenchwizz.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:31.709Z'\n+  lastSynchronized: '2026-10-25T22:33:38.291Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"105632f63891477aadfa932c76e46d5bb829161329e20b4fb70cc5273124469a\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"c1bd07a59f4346fa06e707f0aa08d2923195d49a301a8b5e168b763f394e4bf7\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:efimandreev0/Butterscotch'\n   confidence: 95"
        },
        {
          "sha": "09aaee837fbe69702dd60b0895c43540ff1be9f2",
          "filename": "content/projects/butterscotch-glitchedcode.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-glitchedcode.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-glitchedcode.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-glitchedcode.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T20:35:14.827Z'\n+  lastSynchronized: '2026-10-24T20:50:01.589Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"046575fe778b8191e2f5b2e2242a443b4f20bd64199b44e259afef475737c4bf\"\n-    releasesEtag: '\"94e354af2c4e28e3962b296e8975d0c6ec1a30b51f9be8608fe23fd98f6c999a\"'\n+    repoEtag: W/\"019f0c276a0451c01a18c4d1762f455b3c7de60159a1e91b72d1f9fda31ac6e0\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: 'fork-network:ButterscotchRunner/Butterscotch'\n   confidence: 95"
        },
        {
          "sha": "fd5560d2ecd736928b6d525630928c751f522da0",
          "filename": "content/projects/butterscotch-hatoving.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-hatoving.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-hatoving.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-hatoving.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T20:35:15.365Z'\n+  lastSynchronized: '2026-10-24T20:50:02.234Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"c543b3ff191e33f59c20e4139656d70e97e198df3586cbe66641ae68d94305e0\"\n-    releasesEtag: '\"94e354af2c4e28e3962b296e8975d0c6ec1a30b51f9be8608fe23fd98f6c999a\"'\n+    repoEtag: W/\"8988cda5496460fae851b9cd1827373c0de43aafb1c90e294180fcc51f51cc78\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: 'fork-network:ButterscotchRunner/Butterscotch'\n   confidence: 95"
        },
        {
          "sha": "c8ca92f23554ef1403dbf4f76ce431aa7607f709",
          "filename": "content/projects/butterscotch-kriscrossapple.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-kriscrossapple.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-kriscrossapple.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-kriscrossapple.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-24T11:00:16.408Z'\n+  lastSynchronized: '2026-10-25T11:29:41.931Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"576341178d1782353dadeebe79a99030a7c1513ab4edc32b2f75e689d99befc0\"\n-    releasesEtag: '\"2d8126faf7d60a088ad7a14f528f5a8f3ca919b5cd485e0f97d8ebb20b5102f0\"'\n+    repoEtag: W/\"23815ee6d9439733afbc5cdc1617b9e9dc330d573a8b40328086186186235f01\"\n+    releasesEtag: '\"9e77b662133f4d4e3007923dbf3a0cbb2fd896f67c82d38c785dfb153804c449\"'\n discovery:\n   method: 'fork-network:ButterscotchRunner/Butterscotch'\n   confidence: 95"
        },
        {
          "sha": "cdc109b00ce7fdc7cb0fdac5cafb26a3dade4ba6",
          "filename": "content/projects/butterscotch-ksp0o.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-ksp0o.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-ksp0o.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-ksp0o.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-22T19:15:38.728Z'\n+  lastSynchronized: '2026-10-25T19:37:31.659Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"7bae18b2fee83d13d4ae205ad6df7872b30110ed687ec6148fed12435c67429a\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"377bafce86601bd7e1bfdb7f18883d88f5c879e657664ba61c2426fb141dd5e2\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'fork-network:ButterscotchRunner/Butterscotch'\n   confidence: 100"
        },
        {
          "sha": "de19d0bf28ff65a8b47927d5cdf2c0afe78a2af3",
          "filename": "content/projects/butterscotch-menieldm.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-menieldm.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-menieldm.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-menieldm.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: '2026-08-06T15:45:57Z'\n   url: 'https://github.com/Menieldm/Butterscotch/releases/tag/v1.3.1'\n activity:\n-  lastSynchronized: '2026-10-21T23:19:41.085Z'\n+  lastSynchronized: '2026-10-24T23:14:58.044Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"21eab61696c75633a8ae963a395745b9130d74bb6a06298c728b70e813f8d756\"\n-    releasesEtag: W/\"0b713dbb95b92f604e52933da7a50c792a3d875961ffde4528142d5a078e794d\"\n+    repoEtag: W/\"c044f099dc71f40fa03f5410b810abf388cbd7f0b3aff4b2bf1c0b39f7f40fd3\"\n+    releasesEtag: W/\"25ffe5f87a99df4305e0e800aaafb71408c01a6fc1ede2634747a826bca41e18\"\n discovery:\n   method: 'fork-network:efimandreev0/Butterscotch'\n   confidence: 95"
        },
        {
          "sha": "0dc45051a1177dabbe7aa6bcd95ac0493f86da62",
          "filename": "content/projects/butterscotch-miyoocfw-intycat.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-miyoocfw-intycat.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-miyoocfw-intycat.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-miyoocfw-intycat.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: '2026-04-07T06:26:48Z'\n   url: 'https://github.com/intycat/Butterscotch-MiyooCFW/releases/tag/v1.3.0'\n activity:\n-  lastSynchronized: '2026-10-23T22:11:32.320Z'\n+  lastSynchronized: '2026-10-25T22:33:38.839Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"6eb24823860931d9d6bed0a478cc3383afbb8e5cf0e1cbb31f4caeab6d46d246\"\n-    releasesEtag: W/\"888e615b9cf81b6354392d1113056387005e143e81f32ed7ce1825359a7ba710\"\n+    repoEtag: W/\"35c76b9802d6a07872fb9c8b90aab33f4f684798e5f7d888fe49b5123e7e0759\"\n+    releasesEtag: W/\"c0e453e1e53239e029ec83b31bf4058063d8fae3e698515aea44bbe7a8ee3c01\"\n discovery:\n   method: 'fork-network:ButterscotchRunner/Butterscotch'\n   confidence: 95"
        },
        {
          "sha": "14a43c3338eb6b918148c8a086bff32c6881abad",
          "filename": "content/projects/butterscotch-monobhvr.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-monobhvr.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-monobhvr.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-monobhvr.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-22T19:15:39.475Z'\n+  lastSynchronized: '2026-10-25T19:37:32.190Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"229d8cf76b38c7b5a6d036cc431deaf3f0458d03d53aa48606ae84b323df99af\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"92151b238596f2af92dc16a27e71da23576baebaa7ee87537b03f824f8d0a676\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 95"
        },
        {
          "sha": "01b20baf9cd053931ee03688289f31df7ba27556",
          "filename": "content/projects/butterscotch-producdevity.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-producdevity.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-producdevity.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-producdevity.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-22T19:15:40.205Z'\n+  lastSynchronized: '2026-10-25T19:37:32.728Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"b6aeda922f3022233e7a1b43aedf4e0922603c96e365e5e2a85ece6e5b84f745\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"f644eba1800118959bfd8bd5c82255f90ab7dc96b52a8f66a171b8495fa229a5\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'fork-network:cobaltgit/Butterscotch'\n   confidence: 100"
        },
        {
          "sha": "cd319f64ab43bbfec00ccc5ba746ffefa4ff14ec",
          "filename": "content/projects/butterscotch-pugemon.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-pugemon.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-pugemon.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-pugemon.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-21T23:19:41.739Z'\n+  lastSynchronized: '2026-10-24T23:14:58.506Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"833fea11219203666346fbdf340224a93cc164b0cdf98b820555cec62ac526b4\"\n-    releasesEtag: '\"669f535743067c0ac9111e98f8b3d7f826f4edc7838967d02f64b6f505af2660\"'\n+    repoEtag: W/\"40758bc7ce888981ae00b78d4a06107145215ca1e1063cc804787bc3d9013fed\"\n+    releasesEtag: '\"d958fbfec27a80deaeffdf70593bfd5e43f02f963f152d4a8d9f4917da92f5c0\"'\n discovery:\n   method: 'fork-network:efimandreev0/Butterscotch'\n   confidence: 100"
        },
        {
          "sha": "d615ba11ba6f854a3787b215f75bbcf2535659e9",
          "filename": "content/projects/butterscotch-qum1nzhennan.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-qum1nzhennan.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-qum1nzhennan.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-qum1nzhennan.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-17T05:51:11.063Z'\n+  lastSynchronized: '2026-10-25T16:19:47.950Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"1b90c6254b11cc9861b7ed310708e5e60cfb24acfa3fa1ecd7d87f0539bde864\"\n-    releasesEtag: '\"840d8b22d7db312c6ea7f64c0729d54bdc2329ace0982cd7dfae9d1d30d0d261\"'\n+    repoEtag: W/\"092d9e14bae615dae803b1c723720dfe00ec612597ccfa5c3ca63db0575de348\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'fork-network:ButterscotchRunner/Butterscotch'\n   confidence: 95"
        },
        {
          "sha": "e8bd324e2ca678803750cece74a61abb49a7569e",
          "filename": "content/projects/butterscotch-seija-amanojaku.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-seija-amanojaku.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-seija-amanojaku.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-seija-amanojaku.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T20:35:15.956Z'\n+  lastSynchronized: '2026-10-24T20:50:02.804Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"51d5cacce3480866518807f7f42c29c3b34cb1889d2d636fc8695c89ec974ede\"\n-    releasesEtag: '\"94e354af2c4e28e3962b296e8975d0c6ec1a30b51f9be8608fe23fd98f6c999a\"'\n+    repoEtag: W/\"8e4a34b4ab7524a73ae22ec8e84d8c73e7d71239661dce214cb648fa69ee303f\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: 'fork-network:ButterscotchRunner/Butterscotch'\n   confidence: 100"
        },
        {
          "sha": "4b3a593b4d202727f04cc8f1d7be66efee909830",
          "filename": "content/projects/butterscotch-spaceyspacestar.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-spaceyspacestar.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-spaceyspacestar.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-spaceyspacestar.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T20:35:16.538Z'\n+  lastSynchronized: '2026-10-24T20:50:03.428Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"e5577395e02b943d0df32f70cb0d33d1fcabbfe4e20c2fe649c16e9681a080b5\"\n-    releasesEtag: '\"94e354af2c4e28e3962b296e8975d0c6ec1a30b51f9be8608fe23fd98f6c999a\"'\n+    repoEtag: W/\"041e776103ee4d6947084794b21786daa0d8e3c80bd42a5aca2115024ef313b1\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: 'fork-network:ButterscotchRunner/Butterscotch'\n   confidence: 100"
        },
        {
          "sha": "dcd3e659919526d3dbabd2d9400fc6fcd0cc11a5",
          "filename": "content/projects/butterscotch-test-avolud.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-test-avolud.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-test-avolud.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-test-avolud.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-17T05:51:19.728Z'\n+  lastSynchronized: '2026-10-25T16:19:48.386Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"478ad4454b4419f96bbf4ea89b0d6df21048204539d061280159e3f7c537d5f9\"\n-    releasesEtag: '\"840d8b22d7db312c6ea7f64c0729d54bdc2329ace0982cd7dfae9d1d30d0d261\"'\n+    repoEtag: W/\"9451da5a3ddd77981edc3c28e3ae8cf216f7ed91486117703c3163ef10f38e0e\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'fork-network:ButterscotchRunner/Butterscotch'\n   confidence: 95"
        },
        {
          "sha": "0957ba95e270a37f7e3a62d051f6fe295794025d",
          "filename": "content/projects/butterscotch-thesillydoggo.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-thesillydoggo.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-thesillydoggo.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-thesillydoggo.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:32.823Z'\n+  lastSynchronized: '2026-10-25T22:33:39.375Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"fbcced280c1f6dddf9218320ee34d4bec277eadeab6c38ee22cf32920c2144ac\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"75dd59d4ee09f46da4cd669652d16de3e87cb168bb6c8daf770f6407a6b26a29\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:ButterscotchRunner/Butterscotch'\n   confidence: 100"
        },
        {
          "sha": "3284e959137a655d32b778a00c0262eb60da5b2f",
          "filename": "content/projects/butterscotch-un1q32.md",
          "status": "modified",
          "additions": 4,
          "deletions": 4,
          "changes": 8,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-un1q32.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-un1q32.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-un1q32.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -23,19 +23,19 @@ repository:\n   defaultBranch: main\n   stars: 0\n   forks: 1\n-  lastCommit: '2026-10-20T21:36:32Z'\n+  lastCommit: '2026-10-24T12:14:13Z'\n latestRelease:\n   tag: null\n   name: null\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-21T23:19:42.193Z'\n+  lastSynchronized: '2026-10-24T23:14:58.891Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"88e5a2dda028a472eca63f640e3e09c50cb99f2cac541bee051c715ea79da5e4\"\n-    releasesEtag: '\"669f535743067c0ac9111e98f8b3d7f826f4edc7838967d02f64b6f505af2660\"'\n+    repoEtag: W/\"78b33bbd65cb79081980d4f47316a9092988578e708d58d6cb881d3f43b62096\"\n+    releasesEtag: '\"d958fbfec27a80deaeffdf70593bfd5e43f02f963f152d4a8d9f4917da92f5c0\"'\n discovery:\n   method: 'incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 95"
        },
        {
          "sha": "0cab3ebfee4e161b12df21ff65ab3931ea32c47e",
          "filename": "content/projects/butterscotch-wlroots.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-wlroots.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-wlroots.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-wlroots.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T01:31:45.020Z'\n+  lastSynchronized: '2026-10-25T01:14:08.670Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"3e6fcd8b0f56d7c28e93f6a253a21826264940bb1c843944f8bb9de3ff20b642\"\n-    releasesEtag: '\"ff6090af9c91b61292326178ca0823b97aed61799df8cc0ca8f7a6c7d1215cb9\"'\n+    repoEtag: W/\"92934186f683b213af04174216df93f66fd31b16fdd4fb85206875f36ae99068\"\n+    releasesEtag: '\"a5c451af9d88eff769190129023bb74c9de83ceb2f12875d0ca286055e5e73e6\"'\n discovery:\n   method: 'fork-network:ButterscotchRunner/Butterscotch'\n   confidence: 95"
        },
        {
          "sha": "40a5ad10d29784abb4e9f0cfcf636ea59d9ff8c1",
          "filename": "content/projects/butterscotch-wmjb.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-wmjb.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-wmjb.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-wmjb.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-17T05:51:25.272Z'\n+  lastSynchronized: '2026-10-25T16:19:48.880Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"ba02e1725ec415900536ba971584db858addbc446862e23b693a9db3c6c65e42\"\n-    releasesEtag: '\"840d8b22d7db312c6ea7f64c0729d54bdc2329ace0982cd7dfae9d1d30d0d261\"'\n+    repoEtag: W/\"9cc52a234e1d044bbb7475cd4ab9fb210b95b711fafbceb4e4dc7dbdfa07ed09\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'fork-network:ButterscotchRunner/Butterscotch'\n   confidence: 100"
        },
        {
          "sha": "ebafe623ebfb886467d35cd70543ea599b734208",
          "filename": "content/projects/butterscotch-yt-somedude677.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-yt-somedude677.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch-yt-somedude677.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch-yt-somedude677.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-24T11:00:16.921Z'\n+  lastSynchronized: '2026-10-25T11:29:42.401Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"afcc2fe8bc4509e12c2e8655d4e206b27d2f6192730bc635b44aa642a1b41640\"\n-    releasesEtag: '\"2d8126faf7d60a088ad7a14f528f5a8f3ca919b5cd485e0f97d8ebb20b5102f0\"'\n+    repoEtag: W/\"7f99a02907e1514153d15873907a260511a5cdb3e840b6746a9cdfc21b95fac0\"\n+    releasesEtag: '\"9e77b662133f4d4e3007923dbf3a0cbb2fd896f67c82d38c785dfb153804c449\"'\n discovery:\n   method: 'fork-network:ButterscotchRunner/Butterscotch'\n   confidence: 95"
        },
        {
          "sha": "1e36a3720fca0dfe474ebc5240356916eb3a5d8c",
          "filename": "content/projects/butterscotch.md",
          "status": "modified",
          "additions": 4,
          "deletions": 4,
          "changes": 8,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -24,20 +24,20 @@ repository:\n   defaultBranch: main\n   stars: 401\n   forks: 127\n-  lastCommit: '2026-10-24T06:47:30Z'\n+  lastCommit: '2026-10-24T20:49:06Z'\n latestRelease:\n   tag: null\n   name: null\n   publishedAt: null\n   url: null\n activity:\n   lastChecked: '2026-09-29T00:27:00.436Z'\n-  lastSynchronized: '2026-10-24T11:00:17.424Z'\n+  lastSynchronized: '2026-10-25T11:29:42.843Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"e3d37c5ae7c4d628d9bfd28d3e360441e45eedf74e97aeb69d97bf808b39ce7c\"\n-    releasesEtag: '\"2d8126faf7d60a088ad7a14f528f5a8f3ca919b5cd485e0f97d8ebb20b5102f0\"'\n+    repoEtag: W/\"918a953dd2244e9e975b629b8b42f3966d7c5a59c61e2956a214a30e85d49150\"\n+    releasesEtag: '\"9e77b662133f4d4e3007923dbf3a0cbb2fd896f67c82d38c785dfb153804c449\"'\n discovery:\n   method: github-search\n   confidence: 100"
        },
        {
          "sha": "f35a5256a53a66ca70cd4adaf9b555779a79f45a",
          "filename": "content/projects/butterscotch360-next-ral-sei.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch360-next-ral-sei.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fbutterscotch360-next-ral-sei.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fbutterscotch360-next-ral-sei.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: '2026-10-18T05:05:07Z'\n   url: 'https://github.com/Ral-sei/Butterscotch360-Next/releases/tag/Next-v0.3'\n activity:\n-  lastSynchronized: '2026-10-24T11:00:18.059Z'\n+  lastSynchronized: '2026-10-25T11:29:43.265Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"0e97017dc88091f1e43a645330d3a36d8014580622fcd42f6a7da6525bbfd1be\"\n-    releasesEtag: W/\"61810c89352d1d4d1983c90e75f51eb901636668e50ae71af7bfca2438d1560a\"\n+    repoEtag: W/\"1ce8193835135bc0fa91e592ea7af7ff8302968091ea61dd85f14bac3f66eff8\"\n+    releasesEtag: W/\"088ec4e3798ba55fed1b943efd3c463e279e3144d129b6878d595eac96d575fc\"\n discovery:\n   method: 'fork-network:ButterscotchRunner/Butterscotch'\n   confidence: 95"
        },
        {
          "sha": "beee894cb9551df2e5f2270a842918d8a40a3390",
          "filename": "content/projects/caduceus.md",
          "status": "modified",
          "additions": 4,
          "deletions": 4,
          "changes": 8,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fcaduceus.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fcaduceus.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fcaduceus.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -23,7 +23,7 @@ source:\n repository:\n   archived: false\n   defaultBranch: main\n-  stars: 3\n+  stars: 4\n   forks: 0\n   lastCommit: '2026-10-06T18:33:39Z'\n latestRelease:\n@@ -33,12 +33,12 @@ latestRelease:\n   url: 'https://github.com/Rian6/caduceus/releases/tag/v1.2.1'\n activity:\n   lastChecked: '2026-09-29T00:28:21.949Z'\n-  lastSynchronized: '2026-10-23T22:11:33.416Z'\n+  lastSynchronized: '2026-10-25T22:33:39.859Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"4a073e772e13f3935a918b52d66ef6869b5e53eb14a38bc412ee654eba64044d\"\n-    releasesEtag: W/\"f1bff46a7c6397b229c65c79dad94ad7982b0e9e55b1195180c7e563d835766d\"\n+    repoEtag: W/\"021101c468751469f754e5474139c148246d19a163686f1ea68ff3eeaa1ddf2e\"\n+    releasesEtag: W/\"6a5e6dd83bd8907256605f15f9774f6664e85ef0712bdb72da99ba84cd116287\"\n discovery:\n   method: 'pending-promotion:github-search'\n   confidence: 90"
        },
        {
          "sha": "0817f82bb52bb560411e1ff21d0fbf364ecf8ada",
          "filename": "content/projects/cenit-huevoman77.md",
          "status": "modified",
          "additions": 8,
          "deletions": 10,
          "changes": 18,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fcenit-huevoman77.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fcenit-huevoman77.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fcenit-huevoman77.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -23,21 +23,19 @@ repository:\n   defaultBranch: master\n   stars: 4\n   forks: 0\n-  lastCommit: '2026-10-16T21:55:19Z'\n+  lastCommit: '2026-10-24T03:18:34Z'\n latestRelease:\n-  tag: base-0.6.38\n-  name: >-\n-    Cenit 0.6.38 \u2014 el superbloque tiene denominador y el reporte lleva el equipo\n-    medido\n-  publishedAt: '2026-10-16T21:55:20Z'\n-  url: 'https://github.com/HUEVOMAN77/Cenit/releases/tag/base-0.6.38'\n+  tag: base-0.6.44\n+  name: Cenit 0.6.44 - la cola de cuadros deja de serializar EE y GS\n+  publishedAt: '2026-10-24T03:18:34Z'\n+  url: 'https://github.com/HUEVOMAN77/Cenit/releases/tag/base-0.6.44'\n activity:\n-  lastSynchronized: '2026-10-21T23:19:42.742Z'\n+  lastSynchronized: '2026-10-24T23:14:59.347Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"9c8c9ef1fb6850d6b197cae9d2239f3965443f77cdfc8fdc41837ae973692574\"\n-    releasesEtag: W/\"395a41ee4730a8c8dc0de3782d0b87dbe757f57b66ec353261a136a4540d4226\"\n+    repoEtag: W/\"f6e6028e7252e0961e7b26e84e9b4200d0a010359fc844fc35f56ff646c9aa96\"\n+    releasesEtag: W/\"4913eaca579e57d59ed972bbdf684712b18933b4ef0e5f5c6de3257b9d68d1c6\"\n discovery:\n   method: 'incremental:topic:playstation2'\n   confidence: 100"
        },
        {
          "sha": "f7cdd0cad8ab64d25079b55e52e322d5290ec1a1",
          "filename": "content/projects/cheatdeviceps2-happymay2003.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fcheatdeviceps2-happymay2003.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fcheatdeviceps2-happymay2003.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fcheatdeviceps2-happymay2003.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -29,12 +29,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-17T05:51:34.279Z'\n+  lastSynchronized: '2026-10-25T16:19:49.380Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"b72df9c30d335215c47b1dd22f476c045d0eeae7df66a4d11faa4f222274c915\"\n-    releasesEtag: '\"840d8b22d7db312c6ea7f64c0729d54bdc2329ace0982cd7dfae9d1d30d0d261\"'\n+    repoEtag: W/\"a26a5664c217964199980addeccc2d090c39b7bf41ce3ac38c03baf9cfafa964\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'fork-network:israpps/CheatDevicePS2'\n   confidence: 100"
        },
        {
          "sha": "e0c3b0e4a95b21e69bfd76cc2e8101aed2d7831e",
          "filename": "content/projects/chimera-core-pcsx2.md",
          "status": "modified",
          "additions": 8,
          "deletions": 8,
          "changes": 16,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fchimera-core-pcsx2.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fchimera-core-pcsx2.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fchimera-core-pcsx2.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -22,21 +22,21 @@ repository:\n   defaultBranch: main\n   stars: 0\n   forks: 1\n-  lastCommit: '2026-10-21T16:41:05Z'\n+  lastCommit: '2026-10-24T09:06:32Z'\n latestRelease:\n-  tag: nightly-2026-10-21\n-  name: 'Nightly 2026-10-21 (1ad1a184, made 2026-10-20 11:53 CEST)'\n-  publishedAt: '2026-10-21T09:24:27Z'\n+  tag: nightly-2026-10-24\n+  name: 'Nightly 2026-10-24 (6c576f00, made 2026-10-24 08:50 CEST)'\n+  publishedAt: '2026-10-24T09:06:33Z'\n   url: >-\n-    https://github.com/ToolAssisted-run/chimera-core-pcsx2/releases/tag/nightly-2026-10-21\n+    https://github.com/ToolAssisted-run/chimera-core-pcsx2/releases/tag/nightly-2026-10-24\n activity:\n   lastChecked: '2026-09-29T00:28:45.721Z'\n-  lastSynchronized: '2026-10-21T23:19:43.204Z'\n+  lastSynchronized: '2026-10-24T23:15:00.006Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"aa267ff5b59dce2d9bdd8f5c7a80bec2a44472a28644ea59560a24e82cf154cf\"\n-    releasesEtag: W/\"ff03af84744b5cbc72c1e559cc82bd9c954b23e98638d2dc42a37e7e88ad62f2\"\n+    repoEtag: W/\"1623eec442223f79fc03beff1d0c897d80ce7a807a838d5d57b3f0ded3c20ac0\"\n+    releasesEtag: W/\"2224e32f97fa64c1f32e55cb806f5b69bced5a757429ad6ec0b33c4f0715ce79\"\n discovery:\n   method: 'pending-promotion:github-search'\n   confidence: 90"
        },
        {
          "sha": "dedeb01333c9c51c020b5941655df8d07ef8b9b5",
          "filename": "content/projects/chronicle-nintendo-switch.md",
          "status": "modified",
          "additions": 4,
          "deletions": 4,
          "changes": 8,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fchronicle-nintendo-switch.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fchronicle-nintendo-switch.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fchronicle-nintendo-switch.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -26,20 +26,20 @@ repository:\n   defaultBranch: switch-port\n   stars: 0\n   forks: 0\n-  lastCommit: '2026-10-20T03:15:26Z'\n+  lastCommit: '2026-10-24T15:28:59Z'\n latestRelease:\n   tag: null\n   name: null\n   publishedAt: null\n   url: null\n activity:\n   lastChecked: '2026-10-20T05:18:56.651Z'\n-  lastSynchronized: '2026-10-21T23:19:43.701Z'\n+  lastSynchronized: '2026-10-24T23:15:00.466Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"92250d8c69c9314030a40bb2932b54c042d3b0889ecce0a2d50fad52c3dd3325\"\n-    releasesEtag: '\"669f535743067c0ac9111e98f8b3d7f826f4edc7838967d02f64b6f505af2660\"'\n+    repoEtag: W/\"a3da9dad2bbc4efe7fa5c4454b1079c534a0990921ad87de03f3f4ac44bbc2ad\"\n+    releasesEtag: '\"d958fbfec27a80deaeffdf70593bfd5e43f02f963f152d4a8d9f4917da92f5c0\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 85"
        },
        {
          "sha": "873def7ccd70f96111f1aa901a915c35d2b0a5ec",
          "filename": "content/projects/chronicle-ps4.md",
          "status": "modified",
          "additions": 4,
          "deletions": 4,
          "changes": 8,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fchronicle-ps4.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fchronicle-ps4.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fchronicle-ps4.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -24,7 +24,7 @@ source:\n repository:\n   archived: false\n   defaultBranch: ps4\n-  stars: 0\n+  stars: 1\n   forks: 0\n   lastCommit: '2026-10-17T11:17:50Z'\n latestRelease:\n@@ -34,12 +34,12 @@ latestRelease:\n   url: 'https://github.com/odiecay/chronicle-ps4/releases/tag/v0.1.1'\n activity:\n   lastChecked: '2026-10-14T14:03:13.565Z'\n-  lastSynchronized: '2026-10-23T01:31:45.581Z'\n+  lastSynchronized: '2026-10-25T01:14:09.162Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"c12deb4731690c835d9697a9b7dbd94b2a5d7f52f494a971a1dc31171b6b9726\"\n-    releasesEtag: W/\"2ace8848990f4f32f85e3760188772d4fcbc3abe26afa71483ecce1c53ce485e\"\n+    repoEtag: W/\"ede7a9da32343a7e26d1c8649c7bbf788204971308426c72760e6933e78f7f61\"\n+    releasesEtag: W/\"b26eb5a244cd8c5cfc53db72a8c5ff7b9ecee9bef667cc779b2de7657daec566\"\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 85"
        },
        {
          "sha": "cb4f336a9db8ba8bd198a7a94e4dd75a5266cb02",
          "filename": "content/projects/chronicle-tororoi.md",
          "status": "modified",
          "additions": 4,
          "deletions": 4,
          "changes": 8,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fchronicle-tororoi.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fchronicle-tororoi.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fchronicle-tororoi.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -26,20 +26,20 @@ repository:\n   defaultBranch: master\n   stars: 0\n   forks: 0\n-  lastCommit: '2026-10-21T20:36:41Z'\n+  lastCommit: '2026-10-25T22:19:00Z'\n latestRelease:\n   tag: null\n   name: null\n   publishedAt: null\n   url: null\n activity:\n   lastChecked: '2026-10-07T01:48:56.704Z'\n-  lastSynchronized: '2026-10-23T22:11:33.927Z'\n+  lastSynchronized: '2026-10-25T22:33:40.413Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"cd6fe3a2a7291fc37fe2a5758e8fd21431031892b34d54db8b266f4a3d96caea\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"d7657abf8d6a21bce47b6f11a2ddfeddc1563b71df33b1ced60832283c5a4857\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 85"
        },
        {
          "sha": "6e23a8676153f417cedadf77bc18a60db36f2c51",
          "filename": "content/projects/chronicle-windows.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fchronicle-windows.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fchronicle-windows.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fchronicle-windows.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -34,12 +34,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-04T08:48:40.209Z'\n-  lastSynchronized: '2026-10-22T19:15:40.850Z'\n+  lastSynchronized: '2026-10-25T19:37:33.287Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"3e137d665a09348ae18b857d0da7dcb2d98d90de2fb3759e03f984ec6d6db4c4\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"c0b047da9bcf22da93419cec57fc0412fa065bb253668d6f5d9002ed7c37846b\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 85"
        },
        {
          "sha": "6c9d88ac4880affb6653962d446d8f2a62c8863e",
          "filename": "content/projects/chronicle.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fchronicle.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fchronicle.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fchronicle.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -34,12 +34,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-06T21:53:56.790Z'\n-  lastSynchronized: '2026-10-17T05:51:40.383Z'\n+  lastSynchronized: '2026-10-25T16:19:49.830Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"4cfc9975e5ca301a27be30dd8c75afd2c3a9da5c1c0eadd3c2961b5dd8c5e59a\"\n-    releasesEtag: '\"840d8b22d7db312c6ea7f64c0729d54bdc2329ace0982cd7dfae9d1d30d0d261\"'\n+    repoEtag: W/\"8d15ca87f564095039495b64b15047a780ca6cff1c3f1630a991455be237c6ef\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 85"
        },
        {
          "sha": "1e4879e450e2da58024fed4e029b764122e91032",
          "filename": "content/projects/chronicletwo-carrdylan52.md",
          "status": "modified",
          "additions": 4,
          "deletions": 4,
          "changes": 8,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fchronicletwo-carrdylan52.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fchronicletwo-carrdylan52.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fchronicletwo-carrdylan52.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -26,20 +26,20 @@ repository:\n   defaultBranch: master\n   stars: 0\n   forks: 0\n-  lastCommit: '2026-10-21T01:22:51Z'\n+  lastCommit: '2026-10-24T11:44:48Z'\n latestRelease:\n   tag: null\n   name: null\n   publishedAt: null\n   url: null\n activity:\n   lastChecked: '2026-10-05T07:41:33.942Z'\n-  lastSynchronized: '2026-10-23T22:11:34.380Z'\n+  lastSynchronized: '2026-10-25T22:33:40.923Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"fb46d07609e9e0146875ff77e8b9695be291b107b8985484599acec73e715fd7\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"b1628801a0d70d7e6845084b243e42adf5d1374cfb4a1d6b4d6c6c2bfca0eacb\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 85"
        },
        {
          "sha": "9d5d1595860230c03a05a2e3687a29dbf5b0ed74",
          "filename": "content/projects/chronicletwo-demetari.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fchronicletwo-demetari.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fchronicletwo-demetari.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fchronicletwo-demetari.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -34,12 +34,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-06T03:20:11.642Z'\n-  lastSynchronized: '2026-10-17T05:51:41.331Z'\n+  lastSynchronized: '2026-10-25T16:19:50.253Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"208850686e7536d09ac113aa61684dd81dca75ab17ff5204c45b1ec9c0c5e66e\"\n-    releasesEtag: '\"840d8b22d7db312c6ea7f64c0729d54bdc2329ace0982cd7dfae9d1d30d0d261\"'\n+    repoEtag: W/\"6ca74e2269d2af1e7aed5c3aa1feff97cd53e1b1bcea61e9491c0c9ed34335ed\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 85"
        },
        {
          "sha": "26599c2763db491edfefd63464ef44886b9357e8",
          "filename": "content/projects/chronicletwo-jeffiest.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fchronicletwo-jeffiest.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fchronicletwo-jeffiest.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fchronicletwo-jeffiest.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -34,12 +34,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-11T02:11:44.268Z'\n-  lastSynchronized: '2026-10-23T22:11:34.834Z'\n+  lastSynchronized: '2026-10-25T22:33:41.356Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"4f329e3016ae678fccdbcb99dd7a740e13a1e6e5942d615b3512bb761279ee95\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"129570b4a93a0d949daeb8b6a13f06013f7a243efac5b57a51a9bd6752788942\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 85"
        },
        {
          "sha": "d0e196784da0a3d85363e884caa4d53177117f4f",
          "filename": "content/projects/chronicletwo-rheijnen.md",
          "status": "added",
          "additions": 55,
          "deletions": 0,
          "changes": 55,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fchronicletwo-rheijnen.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fchronicletwo-rheijnen.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fchronicletwo-rheijnen.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -0,0 +1,55 @@\n+---\n+name: ChronicleTwo\n+slug: chronicletwo-rheijnen\n+summary: >-\n+  Chronicle Two is a work-in-progress decompilation project for Dark Chronicle\n+  for the PlayStation 2. \n+categories:\n+  - preservation\n+  - development\n+tags:\n+  - auto-discovered\n+  - pending-promoted\n+  - active-unreleased\n+  - fork\n+features: []\n+authors: []\n+license: null\n+homepage: null\n+source:\n+  provider: github\n+  repository: RHeijnen/ChronicleTwo\n+  repositoryId: '1432614960'\n+  url: 'https://github.com/RHeijnen/ChronicleTwo'\n+repository:\n+  archived: false\n+  defaultBranch: null\n+  stars: 0\n+  forks: 0\n+  lastCommit: null\n+latestRelease:\n+  tag: null\n+  name: null\n+  publishedAt: null\n+  url: null\n+activity:\n+  lastChecked: '2026-10-24T21:53:47.976Z'\n+automation:\n+  sync: true\n+discovery:\n+  method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n+  confidence: 85\n+  evidence:\n+    - repository description explicitly identifies PS2\n+    - README explicitly mentions PlayStation 2\n+    - GitHub fork of another repository\n+  maturity: active-unreleased\n+verified: false\n+featured: false\n+hidden: false\n+relationships:\n+  forkOf: TheMoonPeople/ChronicleTwo\n+  source: TheMoonPeople/ChronicleTwo\n+---\n+\n+Automatically promoted from PS2SP's discovery review queue because it met the public catalog threshold. This entry is not yet human-verified; upstream repository and release metadata will continue to synchronize automatically."
        },
        {
          "sha": "8a1fe58afa4e37715854ba3e75916a3554cc512e",
          "filename": "content/projects/chulip-decomp.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fchulip-decomp.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fchulip-decomp.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fchulip-decomp.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-21T23:19:44.142Z'\n+  lastSynchronized: '2026-10-24T23:15:00.882Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"ed7b895d73fddbf9e594ac7c888ba6a7cb6ff3a0991c9329a77b26c65a855e96\"\n-    releasesEtag: '\"669f535743067c0ac9111e98f8b3d7f826f4edc7838967d02f64b6f505af2660\"'\n+    repoEtag: W/\"b4d1ebbab380a23e78a703fa6c58ff0af17218d4f24bbf741f003871e91b4849\"\n+    releasesEtag: '\"d958fbfec27a80deaeffdf70593bfd5e43f02f963f152d4a8d9f4917da92f5c0\"'\n discovery:\n   method: 'incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 100"
        },
        {
          "sha": "941ece9039be4f11aa88395a56f8ac069dcc21ac",
          "filename": "content/projects/cinnamon-latest-grayforz2468.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fcinnamon-latest-grayforz2468.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fcinnamon-latest-grayforz2468.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fcinnamon-latest-grayforz2468.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -32,12 +32,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-22T19:15:41.471Z'\n+  lastSynchronized: '2026-10-25T19:37:33.881Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"33ad11af95af20cb34b910fc037dd646f5d2bd794b91f1f6a1df038113413413\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"351b87b1f661c1381684dfd64a5272c8f77070dc35bc0a926001e2f19b29780d\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 95"
        },
        {
          "sha": "edb9bc0ee3398a876cc7cf67efbf3a973b5579bb",
          "filename": "content/projects/classiclient.md",
          "status": "added",
          "additions": 54,
          "deletions": 0,
          "changes": 54,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fclassiclient.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fclassiclient.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fclassiclient.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -0,0 +1,54 @@\n+---\n+name: ClassiClient\n+slug: classiclient\n+summary: >-\n+  A fully free and open source Minecraft Classic-compatible CPE client made in\n+  C, with an emphasis on community and freedom.\n+categories:\n+  - games\n+tags:\n+  - auto-discovered\n+  - pending-promoted\n+  - active-wip\n+  - fork\n+features: []\n+authors: []\n+license: null\n+homepage: null\n+source:\n+  provider: github\n+  repository: w5p926ykdh-pixel/ClassiClient\n+  repositoryId: '1432381925'\n+  url: 'https://github.com/w5p926ykdh-pixel/ClassiClient'\n+repository:\n+  archived: false\n+  defaultBranch: null\n+  stars: 0\n+  forks: 0\n+  lastCommit: null\n+latestRelease:\n+  tag: null\n+  name: null\n+  publishedAt: null\n+  url: null\n+activity:\n+  lastChecked: '2026-10-24T21:53:47.976Z'\n+automation:\n+  sync: true\n+discovery:\n+  method: 'pending-promotion:incremental:\"ps2 port\" in:name,description,readme'\n+  confidence: 75\n+  evidence:\n+    - README explicitly mentions PlayStation 2\n+    - README contains PS2 development/toolchain evidence\n+    - GitHub fork of another repository\n+  maturity: active-wip\n+verified: false\n+featured: false\n+hidden: false\n+relationships:\n+  forkOf: Hayden1204/ClassiClient\n+  source: ClassiCube/ClassiCube\n+---\n+\n+Automatically promoted from PS2SP's discovery review queue because it met the public catalog threshold. This entry is not yet human-verified; upstream repository and release metadata will continue to synchronize automatically."
        },
        {
          "sha": "ec53d47fa25e1f97f0f55152f188cf5631a7cdb3",
          "filename": "content/projects/classicube-4gwyh4y9w2-dev.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fclassicube-4gwyh4y9w2-dev.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fclassicube-4gwyh4y9w2-dev.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fclassicube-4gwyh4y9w2-dev.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -33,12 +33,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-15T20:32:48.379Z'\n-  lastSynchronized: '2026-10-23T01:31:46.141Z'\n+  lastSynchronized: '2026-10-25T01:14:09.571Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"3218fbab494ac7da650db47af335da4a1017d930be6522fe37571f6358484512\"\n-    releasesEtag: '\"ff6090af9c91b61292326178ca0823b97aed61799df8cc0ca8f7a6c7d1215cb9\"'\n+    repoEtag: W/\"ebae0466f638e603a3120c193a17e3a773829ba215bc07cd2b63a29e4c3c86f9\"\n+    releasesEtag: '\"a5c451af9d88eff769190129023bb74c9de83ceb2f12875d0ca286055e5e73e6\"'\n discovery:\n   method: 'pending-promotion:incremental:\"ps2 port\" in:name,description,readme'\n   confidence: 75"
        },
        {
          "sha": "5355c71d2d87fc23011645aaec2d766062002edf",
          "filename": "content/projects/classicube-d3nd3.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fclassicube-d3nd3.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fclassicube-d3nd3.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fclassicube-d3nd3.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -33,12 +33,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-17T05:53:23.162Z'\n-  lastSynchronized: '2026-10-23T22:11:35.323Z'\n+  lastSynchronized: '2026-10-25T22:33:41.838Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"f9f523aed614a593bd5088a82a569b2265d29b2833a269c969a6341a5ff8f589\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"fe195f280dafcf67d737d196c0fc47ae750ea85a2b4149df0c5006c60e9455bf\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'pending-promotion:incremental:\"ps2 port\" in:name,description,readme'\n   confidence: 75"
        },
        {
          "sha": "4e9786bb399be272e9aa503aad9901ec34673760",
          "filename": "content/projects/classicube-mofumofuchan.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fclassicube-mofumofuchan.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fclassicube-mofumofuchan.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fclassicube-mofumofuchan.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -33,12 +33,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-17T11:10:50.101Z'\n-  lastSynchronized: '2026-10-20T06:00:39.978Z'\n+  lastSynchronized: '2026-10-25T06:10:48.327Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"213c374baa015fc6ce01adac3634df80318145a3345884e72ff207e829b5c20a\"\n-    releasesEtag: '\"beabca011d245159687fb6a685ede4790f750a2002b686c57e5bbc672bd5ded1\"'\n+    repoEtag: W/\"60c0de4d2faff60c2f7d0c2d3f5e58c203a2ef179962c54756b56da1eb645459\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'pending-promotion:incremental:\"ps2 port\" in:name,description,readme'\n   confidence: 75"
        },
        {
          "sha": "73c8e61eed23e6146e95517ca09a00769412d171",
          "filename": "content/projects/classicube-poyguylolz.md",
          "status": "modified",
          "additions": 4,
          "deletions": 4,
          "changes": 8,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fclassicube-poyguylolz.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fclassicube-poyguylolz.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fclassicube-poyguylolz.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -25,20 +25,20 @@ repository:\n   defaultBranch: master\n   stars: 0\n   forks: 0\n-  lastCommit: '2026-10-17T14:10:30Z'\n+  lastCommit: '2026-10-24T22:11:42Z'\n latestRelease:\n   tag: null\n   name: null\n   publishedAt: null\n   url: null\n activity:\n   lastChecked: '2026-10-10T18:51:42.050Z'\n-  lastSynchronized: '2026-10-23T22:11:35.832Z'\n+  lastSynchronized: '2026-10-25T22:33:42.255Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"2b2db7b6b4b996c32c265981aeb60aa6ab1fc46aff22062f5fc66dbefc10f0e1\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"d4af0cd32f0b092eb67b2d4d9bbf422ad84e9f89452a951b97cee19a42bddf4b\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'pending-promotion:incremental:\"ps2 port\" in:name,description,readme'\n   confidence: 75"
        },
        {
          "sha": "0930a1a163961d3a88a6662c6c5ad5d680de8dd0",
          "filename": "content/projects/classicube-sungsphinx.md",
          "status": "added",
          "additions": 54,
          "deletions": 0,
          "changes": 54,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fclassicube-sungsphinx.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fclassicube-sungsphinx.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fclassicube-sungsphinx.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -0,0 +1,54 @@\n+---\n+name: ClassiCube\n+slug: classicube-sungsphinx\n+summary: >-\n+  Custom Minecraft Classic / ClassiCube client written in C  from scratch\n+  (formerly ClassicalSharp in C#)\n+categories:\n+  - games\n+tags:\n+  - auto-discovered\n+  - pending-promoted\n+  - active-wip\n+  - fork\n+features: []\n+authors: []\n+license: null\n+homepage: null\n+source:\n+  provider: github\n+  repository: sungsphinx/ClassiCube\n+  repositoryId: '752777841'\n+  url: 'https://github.com/sungsphinx/ClassiCube'\n+repository:\n+  archived: false\n+  defaultBranch: null\n+  stars: 0\n+  forks: 0\n+  lastCommit: null\n+latestRelease:\n+  tag: null\n+  name: null\n+  publishedAt: null\n+  url: null\n+activity:\n+  lastChecked: '2026-10-25T00:31:44.133Z'\n+automation:\n+  sync: true\n+discovery:\n+  method: 'pending-promotion:incremental:\"ps2 port\" in:name,description,readme'\n+  confidence: 75\n+  evidence:\n+    - README explicitly mentions PlayStation 2\n+    - README contains PS2 development/toolchain evidence\n+    - GitHub fork of another repository\n+  maturity: active-wip\n+verified: false\n+featured: false\n+hidden: false\n+relationships:\n+  forkOf: ClassiCube/ClassiCube\n+  source: ClassiCube/ClassiCube\n+---\n+\n+Automatically promoted from PS2SP's discovery review queue because it met the public catalog threshold. This entry is not yet human-verified; upstream repository and release metadata will continue to synchronize automatically."
        },
        {
          "sha": "b4d03fb70a778448921cbfe9157cfd2e7904ade9",
          "filename": "content/projects/classicube-sys.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fclassicube-sys.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fclassicube-sys.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fclassicube-sys.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -31,12 +31,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-12T06:32:41.350Z'\n-  lastSynchronized: '2026-10-24T11:00:18.519Z'\n+  lastSynchronized: '2026-10-25T11:29:43.698Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"42a23be39114b5693e8282b37e45f4c274e517c230b7e3a7a0fed8d15077607a\"\n-    releasesEtag: '\"2d8126faf7d60a088ad7a14f528f5a8f3ca919b5cd485e0f97d8ebb20b5102f0\"'\n+    repoEtag: W/\"d0b3ef399acfebb53aa8e04eb51025c5d0487232a75ac5ed98f7697b2c632995\"\n+    releasesEtag: '\"9e77b662133f4d4e3007923dbf3a0cbb2fd896f67c82d38c785dfb153804c449\"'\n discovery:\n   method: 'pending-promotion:incremental:\"ps2 port\" in:name,description,readme'\n   confidence: 75"
        },
        {
          "sha": "54f8eda553b6925b8d247272e6e620cd9bbbf8ca",
          "filename": "content/projects/classicube-venksociety.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fclassicube-venksociety.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fclassicube-venksociety.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fclassicube-venksociety.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -33,12 +33,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-12T06:32:41.350Z'\n-  lastSynchronized: '2026-10-20T06:00:40.521Z'\n+  lastSynchronized: '2026-10-25T06:10:48.724Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"7529089dacc44016ab12f22087e01da82d3dcadf376b1198192ce52489ad267e\"\n-    releasesEtag: '\"beabca011d245159687fb6a685ede4790f750a2002b686c57e5bbc672bd5ded1\"'\n+    repoEtag: W/\"48e2e9901374cff3d6ef577729e8402e8957be98d4112cd5a154b14b863282ae\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'pending-promotion:incremental:\"ps2 port\" in:name,description,readme'\n   confidence: 75"
        },
        {
          "sha": "931bb01012a5d560c01a17a70116826c3c49f1cd",
          "filename": "content/projects/classicube.md",
          "status": "modified",
          "additions": 4,
          "deletions": 4,
          "changes": 8,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fclassicube.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fclassicube.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fclassicube.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -25,20 +25,20 @@ repository:\n   defaultBranch: master\n   stars: 0\n   forks: 0\n-  lastCommit: '2026-10-16T08:32:54Z'\n+  lastCommit: '2026-10-22T08:39:21Z'\n latestRelease:\n   tag: null\n   name: null\n   publishedAt: null\n   url: null\n activity:\n   lastChecked: '2026-10-06T10:47:00.780Z'\n-  lastSynchronized: '2026-10-20T06:00:41.036Z'\n+  lastSynchronized: '2026-10-25T06:10:49.150Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"f9de3a4426f8c72b2b69278a0ae7d26788ad77bb6a9a15b969f9146154e67f8e\"\n-    releasesEtag: '\"beabca011d245159687fb6a685ede4790f750a2002b686c57e5bbc672bd5ded1\"'\n+    repoEtag: W/\"73c719bedacbc7a959f8184251fd6325cec1a3113717be9bf2d2664b25f6ffb0\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'pending-promotion:incremental:\"ps2 port\" in:name,description,readme'\n   confidence: 75"
        },
        {
          "sha": "2d8d7751ddddd25a60c5042a7bfd95631c3389d9",
          "filename": "content/projects/cod3-russian-translation.md",
          "status": "modified",
          "additions": 11,
          "deletions": 8,
          "changes": 19,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fcod3-russian-translation.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fcod3-russian-translation.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fcod3-russian-translation.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -13,7 +13,7 @@ tags:\n   - prerelease-only\n features: []\n authors: []\n-license: null\n+license: MIT\n homepage: null\n source:\n   provider: github\n@@ -22,19 +22,23 @@ source:\n   url: 'https://github.com/alekszverr-jpg/cod3-russian-translation'\n repository:\n   archived: false\n-  defaultBranch: null\n+  defaultBranch: main\n   stars: 0\n   forks: 0\n-  lastCommit: null\n+  lastCommit: '2026-10-22T18:12:08Z'\n latestRelease:\n-  tag: null\n-  name: null\n-  publishedAt: null\n-  url: null\n+  tag: v1.0\n+  name: Call of Duty 3 \u2014 \u0440\u0443\u0441\u0441\u043a\u0438\u0439 \u043f\u0435\u0440\u0435\u0432\u043e\u0434 v1.0 \u00b7 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043e\u0442 22 \u043e\u043a\u0442\u044f\u0431\u0440\u044f\n+  publishedAt: '2026-10-20T13:39:00Z'\n+  url: 'https://github.com/alekszverr-jpg/cod3-russian-translation/releases/tag/v1.0'\n activity:\n   lastChecked: '2026-10-20T15:22:35.905Z'\n+  lastSynchronized: '2026-10-25T06:10:49.607Z'\n automation:\n   sync: true\n+  github:\n+    repoEtag: W/\"63d93343b452de12f15e2142ca1d6b2d7c3ef157a4c019bd325c04496783de75\"\n+    releasesEtag: W/\"69fd1e253d4c19a8f4ee6bf5b293ae96ea17a6afe3cee77282aa4c3b08d76f2b\"\n discovery:\n   method: 'pending-promotion:incremental:ps2 in:name,description'\n   confidence: 85\n@@ -47,5 +51,4 @@ verified: false\n featured: false\n hidden: false\n ---\n-\n Automatically promoted from PS2SP's discovery review queue because it met the public catalog threshold. This entry is not yet human-verified; upstream repository and release metadata will continue to synchronize automatically."
        },
        {
          "sha": "7ce63532d1a5e7b716c431b85bcffebd4acc711a",
          "filename": "content/projects/console-skill.md",
          "status": "modified",
          "additions": 5,
          "deletions": 5,
          "changes": 10,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fconsole-skill.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fconsole-skill.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fconsole-skill.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -22,21 +22,21 @@ source:\n repository:\n   archived: false\n   defaultBranch: main\n-  stars: 64\n-  forks: 7\n+  stars: 96\n+  forks: 12\n   lastCommit: '2026-10-22T14:14:45Z'\n latestRelease:\n   tag: null\n   name: null\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-24T11:00:18.979Z'\n+  lastSynchronized: '2026-10-25T11:29:44.125Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"99601d3aa7179ebfae48ed43aa58fe91ab4ce34b4b5172b697592d82073f74b8\"\n-    releasesEtag: '\"2d8126faf7d60a088ad7a14f528f5a8f3ca919b5cd485e0f97d8ebb20b5102f0\"'\n+    repoEtag: W/\"4ab64b66cd3bccf176c9bb4be34ac4a540deaf43e9a2e308816087c810ef7fd2\"\n+    releasesEtag: '\"9e77b662133f4d4e3007923dbf3a0cbb2fd896f67c82d38c785dfb153804c449\"'\n discovery:\n   method: 'incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 100"
        },
        {
          "sha": "c57ac6e5b247016560b6a9953ca17a395086984d",
          "filename": "content/projects/crash-twinsanity-windows-auronsavant.md",
          "status": "added",
          "additions": 59,
          "deletions": 0,
          "changes": 59,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fcrash-twinsanity-windows-auronsavant.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fcrash-twinsanity-windows-auronsavant.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fcrash-twinsanity-windows-auronsavant.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -0,0 +1,59 @@\n+---\n+name: Crash-Twinsanity-Windows\n+slug: crash-twinsanity-windows-auronsavant\n+summary: >-\n+  Crash Twinsanity (PS2, PAL) running natively on Windows, played through to the\n+  credits: a Windows port of HAB3X's Mac port of Smartkin's C++ decompilation.\n+  No game data included: bring your own disc image.\n+categories:\n+  - preservation\n+  - development\n+  - ports\n+  - games\n+tags:\n+  - auto-discovered\n+  - fork\n+features: []\n+authors: []\n+license: null\n+homepage: 'https://github.com/GioPixelGH/Crash-Twinsanity-Windows/releases/latest'\n+source:\n+  provider: github\n+  repository: AuronSavant/Crash-Twinsanity-Windows\n+  repositoryId: '1427315446'\n+repository:\n+  archived: false\n+  defaultBranch: main\n+  stars: 0\n+  forks: 0\n+  lastCommit: '2026-10-23T16:43:23Z'\n+latestRelease:\n+  tag: null\n+  name: null\n+  publishedAt: null\n+  url: null\n+activity:\n+  lastSynchronized: '2026-10-25T10:09:09.591Z'\n+automation:\n+  sync: true\n+discovery:\n+  method: 'fork-network:GioPixelGH/Crash-Twinsanity-Windows'\n+  confidence: 100\n+  evidence:\n+    - repository description explicitly identifies PS2\n+    - README explicitly mentions PlayStation 2\n+    - README contains PS2 development/toolchain evidence\n+    - GitHub fork of another repository\n+    - >-\n+      fork lineage traces to indexed PS2 project:\n+      GioPixelGH/Crash-Twinsanity-Windows\n+  maturity: active-unreleased\n+verified: false\n+featured: false\n+hidden: false\n+relationships:\n+  forkOf: GioPixelGH/Crash-Twinsanity-Windows\n+  source: GioPixelGH/Crash-Twinsanity-Windows\n+---\n+\n+Automatically discovered by PS2SP. Relevance evidence is recorded in frontmatter; human curation can expand this entry without disabling automated repository metadata synchronization."
        },
        {
          "sha": "9a95986f1ec9dba8d861e54594c473015e860cb4",
          "filename": "content/projects/crashwoc-decomp-ps2-hypernova-0ring.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fcrashwoc-decomp-ps2-hypernova-0ring.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fcrashwoc-decomp-ps2-hypernova-0ring.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fcrashwoc-decomp-ps2-hypernova-0ring.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-17T05:51:52.561Z'\n+  lastSynchronized: '2026-10-25T16:19:50.915Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"220e9f25896d4b6ec11cf7ea0bc052b75170d3bdac3755976edbe6666adaa430\"\n-    releasesEtag: '\"840d8b22d7db312c6ea7f64c0729d54bdc2329ace0982cd7dfae9d1d30d0d261\"'\n+    repoEtag: W/\"30fc86591eb97a9291dd6bc8e83763292da8cc387ddd36b9a80f89070d431844\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'incremental:ps2 in:name,description'\n   confidence: 100"
        },
        {
          "sha": "d8120b4ca886c52bd61b9ffccf2f3e623516d564",
          "filename": "content/projects/crystalclock.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fcrystalclock.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fcrystalclock.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fcrystalclock.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T20:35:17.195Z'\n+  lastSynchronized: '2026-10-24T20:50:03.973Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"f7f46e6c3a637ba772e5b7f10cca94ba77047b9308d2ccdedb8f059c4175f72b\"\n-    releasesEtag: '\"94e354af2c4e28e3962b296e8975d0c6ec1a30b51f9be8608fe23fd98f6c999a\"'\n+    repoEtag: W/\"a96867c85498cb190f823c6c329af2606c77480d57c789afe782c208454494ed\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: curated-owner\n   confidence: 100"
        },
        {
          "sha": "44f1161366254d09c16124993b030743629d5480",
          "filename": "content/projects/curso-ps2sdk.md",
          "status": "added",
          "additions": 48,
          "deletions": 0,
          "changes": 48,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fcurso-ps2sdk.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fcurso-ps2sdk.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fcurso-ps2sdk.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -0,0 +1,48 @@\n+---\n+name: curso_PS2SDK\n+slug: curso-ps2sdk\n+summary: >-\n+  Aprendizaje pr\u00e1ctico de PS2SDK y desarrollo de homebrew para PlayStation 2 en\n+  C.\n+categories:\n+  - sdks\n+  - development\n+tags:\n+  - auto-discovered\n+features: []\n+authors: []\n+license: null\n+homepage: null\n+source:\n+  provider: github\n+  repository: cMancillaValle/curso_PS2SDK\n+  repositoryId: '1432488070'\n+repository:\n+  archived: false\n+  defaultBranch: main\n+  stars: 0\n+  forks: 0\n+  lastCommit: '2026-10-24T21:03:33Z'\n+latestRelease:\n+  tag: null\n+  name: null\n+  publishedAt: null\n+  url: null\n+activity:\n+  lastSynchronized: '2026-10-24T21:53:47.976Z'\n+automation:\n+  sync: true\n+discovery:\n+  method: 'incremental:ps2 in:name,description'\n+  confidence: 100\n+  evidence:\n+    - repository description explicitly identifies PS2\n+    - README explicitly mentions PlayStation 2\n+    - README contains PS2 development/toolchain evidence\n+  maturity: active-unreleased\n+verified: false\n+featured: false\n+hidden: false\n+---\n+\n+Automatically discovered by PS2SP. Relevance evidence is recorded in frontmatter; human curation can expand this entry without disabling automated repository metadata synchronization."
        },
        {
          "sha": "6aaee861c41bb9484068c9674830c911c72cd78c",
          "filename": "content/projects/daedalus-ps2.md",
          "status": "modified",
          "additions": 4,
          "deletions": 4,
          "changes": 8,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdaedalus-ps2.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdaedalus-ps2.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdaedalus-ps2.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -18,7 +18,7 @@ source:\n repository:\n   archived: false\n   defaultBranch: master\n-  stars: 18\n+  stars: 19\n   forks: 1\n   lastCommit: '2026-10-21T01:46:47Z'\n latestRelease:\n@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: '2026-10-21T01:30:35Z'\n   url: 'https://github.com/eclipsebladestudioficial/NaedalusX64/releases/tag/v1.6'\n activity:\n-  lastSynchronized: '2026-10-23T22:11:36.315Z'\n+  lastSynchronized: '2026-10-25T22:33:42.772Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"5e029dc250257225eb313e4718ea7882ccc01cf77a373a24487b6bc140c162bb\"\n-    releasesEtag: W/\"c7ce9eac678367ad22ae46c765b666b8c4cf53286753c6638083f85b0c0c7f83\"\n+    repoEtag: W/\"fbf6dcf111745aecc9fd51a906d34769800971744fae2ed8e2e0feb591dd2177\"\n+    releasesEtag: W/\"c006c04d58ebe76c73f6637e32207ec66078251e887e0bc9b17aac2d7798c282\"\n discovery:\n   method: pending-recheck\n   confidence: 95"
        },
        {
          "sha": "2e7310bceb261f559587173890cf4aa100cc3896",
          "filename": "content/projects/dcdecomp.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdcdecomp.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdcdecomp.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdcdecomp.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -34,12 +34,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-01T03:27:44.437Z'\n-  lastSynchronized: '2026-10-21T23:19:44.798Z'\n+  lastSynchronized: '2026-10-24T23:15:01.313Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"a5892cc7e2d4f3b1c8dff1463ff11f93063bdc897829c1f6d0a9ccb6779b58f9\"\n-    releasesEtag: '\"669f535743067c0ac9111e98f8b3d7f826f4edc7838967d02f64b6f505af2660\"'\n+    repoEtag: W/\"63542b1f60282fbd93c77b159c8a154b31ac6bfa54ec407753fe4d3a3a6e91db\"\n+    releasesEtag: '\"d958fbfec27a80deaeffdf70593bfd5e43f02f963f152d4a8d9f4917da92f5c0\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 85"
        },
        {
          "sha": "1630cb8347421bb3280f76b965919c6734c3a92e",
          "filename": "content/projects/dds-decomp-artemis626.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdds-decomp-artemis626.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdds-decomp-artemis626.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdds-decomp-artemis626.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -28,12 +28,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:36.802Z'\n+  lastSynchronized: '2026-10-25T22:33:43.216Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"aefdbdaa69485690e38256c1532e8cdb6f055e19b144a3263bc44ab77e4b0bee\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"f9f581ed2d6bbf89a1ee487486568db576e2c27e77b753c3ff1fd868bcabeee0\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'incremental:ps2 in:name,description'\n   confidence: 100"
        },
        {
          "sha": "6d750b0b49b8432f328d5f9d67c4934eb051810e",
          "filename": "content/projects/decomp-anything.md",
          "status": "modified",
          "additions": 7,
          "deletions": 4,
          "changes": 11,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-anything.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-anything.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecomp-anything.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -14,7 +14,7 @@ tags:\n   - fork\n features: []\n authors: []\n-license: null\n+license: MIT\n homepage: null\n source:\n   provider: github\n@@ -23,19 +23,23 @@ source:\n   url: 'https://github.com/nimbuldev/decomp.anything'\n repository:\n   archived: false\n-  defaultBranch: null\n+  defaultBranch: main\n   stars: 0\n   forks: 0\n-  lastCommit: null\n+  lastCommit: '2026-10-22T02:08:14Z'\n latestRelease:\n   tag: null\n   name: null\n   publishedAt: null\n   url: null\n activity:\n   lastChecked: '2026-10-22T05:22:42.360Z'\n+  lastSynchronized: '2026-10-25T06:10:50.069Z'\n automation:\n   sync: true\n+  github:\n+    repoEtag: W/\"851bd86e00946bba96ddbc269322df637c53b37a2355d9b93d8067b3513b09fe\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 75\n@@ -51,5 +55,4 @@ relationships:\n   forkOf: giveen/decomp.anything\n   source: giveen/decomp.anything\n ---\n-\n Automatically promoted from PS2SP's discovery review queue because it met the public catalog threshold. This entry is not yet human-verified; upstream repository and release metadata will continue to synchronize automatically."
        },
        {
          "sha": "fab2aa648f48873b1cd79cf6ffdf8ef3a28a9f0f",
          "filename": "content/projects/decomp-wots2-port-test-kattimasen-ops.md",
          "status": "modified",
          "additions": 4,
          "deletions": 4,
          "changes": 8,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wots2-port-test-kattimasen-ops.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wots2-port-test-kattimasen-ops.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecomp-wots2-port-test-kattimasen-ops.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -23,19 +23,19 @@ repository:\n   defaultBranch: master\n   stars: 0\n   forks: 0\n-  lastCommit: '2026-10-22T19:22:15Z'\n+  lastCommit: '2026-10-25T20:25:37Z'\n latestRelease:\n   tag: null\n   name: null\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:37.262Z'\n+  lastSynchronized: '2026-10-25T22:33:43.658Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"ef5496113fa8fa9a8561a094e3c21e299f1bf853b49462f97cd55417008423ba\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"4f8f0a7213285b8227ea98e76e1fae9e1661323f4d508cacc0c739f986b2f1b7\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'incremental:ps2 in:name,description'\n   confidence: 100"
        },
        {
          "sha": "f81fbfd3ef1a74a3006afa45baf733d10cf41c58",
          "filename": "content/projects/decomp-wtf-aisloppytoppy.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-aisloppytoppy.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-aisloppytoppy.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecomp-wtf-aisloppytoppy.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -34,12 +34,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-15T23:18:28.396Z'\n-  lastSynchronized: '2026-10-20T06:00:41.574Z'\n+  lastSynchronized: '2026-10-25T06:10:50.546Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"7f98ac26e8dd23332eaa6896d6df4d64e29861b550b014de8c1cfbbc055ce54c\"\n-    releasesEtag: '\"beabca011d245159687fb6a685ede4790f750a2002b686c57e5bbc672bd5ded1\"'\n+    repoEtag: W/\"01592aca9b7220db04049e8cf7ef2f799e8060c245709ee53eeaa27749aff49f\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 75"
        },
        {
          "sha": "6a74e8610f807a54b2f2a54774aa70acd45468c4",
          "filename": "content/projects/decomp-wtf-anr2me.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-anr2me.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-anr2me.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecomp-wtf-anr2me.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -34,12 +34,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-14T18:54:17.306Z'\n-  lastSynchronized: '2026-10-20T06:00:42.078Z'\n+  lastSynchronized: '2026-10-25T06:10:51.045Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"053af529d793b281f2923279bc392c033f19dae2ad2d7291b583d72ff18eeacd\"\n-    releasesEtag: '\"beabca011d245159687fb6a685ede4790f750a2002b686c57e5bbc672bd5ded1\"'\n+    repoEtag: W/\"82f3ae51badcc41443ff03463d4a94de1983552788f0c5de496d622d756d341d\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 75"
        },
        {
          "sha": "3a1acef9490889706b6573384915b0a9731bf13d",
          "filename": "content/projects/decomp-wtf-benwithjamin.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-benwithjamin.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-benwithjamin.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecomp-wtf-benwithjamin.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -34,12 +34,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-15T23:18:28.396Z'\n-  lastSynchronized: '2026-10-23T22:11:37.773Z'\n+  lastSynchronized: '2026-10-25T22:33:44.184Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"37db3cedd78e6508d3561babe5bf26c4904d8187516473d46adb43fe28e1d13c\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"f5a9547b4198c40b37eb88437d350ac3312eb0f3a0a81dfd2d9b0f3ae1a2cb43\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 75"
        },
        {
          "sha": "7de35cc2eb55aa79a250293061e7877c82463ea9",
          "filename": "content/projects/decomp-wtf-briightcore.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-briightcore.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-briightcore.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecomp-wtf-briightcore.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -34,12 +34,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-15T20:32:48.379Z'\n-  lastSynchronized: '2026-10-21T23:19:45.308Z'\n+  lastSynchronized: '2026-10-24T23:15:01.887Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"79973a6a9f053cd00b01d1bf3f2a4851d1ff6e42e662b4105af34d7014b4b99d\"\n-    releasesEtag: '\"669f535743067c0ac9111e98f8b3d7f826f4edc7838967d02f64b6f505af2660\"'\n+    repoEtag: W/\"b3006cf910ae579bd88e8885f761648f092726ce5229f8f6a8598777b53e7539\"\n+    releasesEtag: '\"d958fbfec27a80deaeffdf70593bfd5e43f02f963f152d4a8d9f4917da92f5c0\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 75"
        },
        {
          "sha": "5332d3134372712ea8e85b89f9f7967b22dd8621",
          "filename": "content/projects/decomp-wtf-deathedit.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-deathedit.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-deathedit.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecomp-wtf-deathedit.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -34,12 +34,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-15T23:18:28.396Z'\n-  lastSynchronized: '2026-10-21T23:19:45.782Z'\n+  lastSynchronized: '2026-10-24T23:15:02.341Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"adf12c0c7a6c070fe8ad6c6c7af6f5644be91b1768d63f38db853ed093c68058\"\n-    releasesEtag: '\"669f535743067c0ac9111e98f8b3d7f826f4edc7838967d02f64b6f505af2660\"'\n+    repoEtag: W/\"bdc6218671455c9e71e9656060dc0d11604cc8f411b065c170e301b532020b84\"\n+    releasesEtag: '\"d958fbfec27a80deaeffdf70593bfd5e43f02f963f152d4a8d9f4917da92f5c0\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 75"
        },
        {
          "sha": "9fe06e2145dd8eb33619be67c0e91d4ce20e0157",
          "filename": "content/projects/decomp-wtf-deckarep.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-deckarep.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-deckarep.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecomp-wtf-deckarep.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -34,12 +34,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-14T18:54:17.306Z'\n-  lastSynchronized: '2026-10-22T19:15:42.697Z'\n+  lastSynchronized: '2026-10-25T19:37:34.914Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"88cfd41e7d42219c9378ab197bdbdf3fe0657c6e226142616d61454e6750abac\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"eecb2e50304304c61eb578e468f91fd3aeacfcf2401688521a7c43d8e0fc6757\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 75"
        },
        {
          "sha": "54407d30797ec5e8ae51b7e0053bbaeca4cab4aa",
          "filename": "content/projects/decomp-wtf-frankyfurt101.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-frankyfurt101.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-frankyfurt101.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecomp-wtf-frankyfurt101.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -34,12 +34,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-14T18:54:17.306Z'\n-  lastSynchronized: '2026-10-20T20:35:17.725Z'\n+  lastSynchronized: '2026-10-24T20:50:04.526Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"5c7f927e5c312cf5c30cdac0465afcf530b4e6b26f88a0627a080445200417fa\"\n-    releasesEtag: '\"94e354af2c4e28e3962b296e8975d0c6ec1a30b51f9be8608fe23fd98f6c999a\"'\n+    repoEtag: W/\"24b90934a7b2c4d8c97caa2b58de612bb5e3797f40a1c9024dde0f2e6778e476\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 75"
        },
        {
          "sha": "f2f0732254709b8d74354586720cb010f4623695",
          "filename": "content/projects/decomp-wtf-hwl513782273.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-hwl513782273.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-hwl513782273.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecomp-wtf-hwl513782273.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -34,12 +34,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-19T14:59:02.710Z'\n-  lastSynchronized: '2026-10-20T06:00:43.184Z'\n+  lastSynchronized: '2026-10-25T06:10:51.860Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"d0e8e782390f14f0c81e843c76d6a549461d123a04f91720156291ffb1013eb0\"\n-    releasesEtag: '\"beabca011d245159687fb6a685ede4790f750a2002b686c57e5bbc672bd5ded1\"'\n+    repoEtag: W/\"4a04b3d13e8df5c71f999b6ea0c10d848ce5f428a1ebfcb3b9c8b5a3b2f6acc1\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 75"
        },
        {
          "sha": "f176980281500dc3bdf703f52591dbd054b42f89",
          "filename": "content/projects/decomp-wtf-illusivebagel.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-illusivebagel.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-illusivebagel.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecomp-wtf-illusivebagel.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -34,12 +34,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-15T23:18:28.396Z'\n-  lastSynchronized: '2026-10-20T20:35:18.286Z'\n+  lastSynchronized: '2026-10-24T20:50:05.158Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"924539de78a12579de3443814f3e0175626db7de2601e9d9fde6641c25c1434d\"\n-    releasesEtag: '\"94e354af2c4e28e3962b296e8975d0c6ec1a30b51f9be8608fe23fd98f6c999a\"'\n+    repoEtag: W/\"08d3849f1a7794e6bac47160b5e1d89aaecc2ab1bd7385f5ba119ca35f30d81b\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 75"
        },
        {
          "sha": "6de1bb5639816397773f444fa1b35ad8e94e4883",
          "filename": "content/projects/decomp-wtf-joytis.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-joytis.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-joytis.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecomp-wtf-joytis.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -34,12 +34,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-16T01:39:51.910Z'\n-  lastSynchronized: '2026-10-21T23:19:46.321Z'\n+  lastSynchronized: '2026-10-24T23:15:02.758Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"9d749030617c897236492d0c7f6ed9be9e4831dd05e58c1a382d882fc75a2255\"\n-    releasesEtag: '\"669f535743067c0ac9111e98f8b3d7f826f4edc7838967d02f64b6f505af2660\"'\n+    repoEtag: W/\"3bf197dbe8b23712317dfb086d5836e6c3af2ba43a81d12b013db57455420b48\"\n+    releasesEtag: '\"d958fbfec27a80deaeffdf70593bfd5e43f02f963f152d4a8d9f4917da92f5c0\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 75"
        },
        {
          "sha": "a00ad32fe2609172d152e859129471647c7b3d9c",
          "filename": "content/projects/decomp-wtf-kaueyago.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-kaueyago.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-kaueyago.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecomp-wtf-kaueyago.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -34,12 +34,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-15T23:18:28.396Z'\n-  lastSynchronized: '2026-10-22T19:15:43.362Z'\n+  lastSynchronized: '2026-10-25T19:37:35.601Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"1ceb4561bd4d9af23095390c443781b23de7bbbee6d22a557742feb1fa5d2015\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"8be601436fe2fb04890303ed3553ed3312fceede435b68e950369a4fddf866ac\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 75"
        },
        {
          "sha": "060c8178afed2f2ceed820fb91e4fdd70aa96599",
          "filename": "content/projects/decomp-wtf-nexhas28.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-nexhas28.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-nexhas28.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecomp-wtf-nexhas28.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -34,12 +34,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-15T23:18:28.396Z'\n-  lastSynchronized: '2026-10-20T06:00:43.651Z'\n+  lastSynchronized: '2026-10-25T06:10:52.318Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"f03894b00b9897237c2b4c4f7f70df7f80f4ab845e82b808bc70733c3e4b0a51\"\n-    releasesEtag: '\"beabca011d245159687fb6a685ede4790f750a2002b686c57e5bbc672bd5ded1\"'\n+    repoEtag: W/\"b7cfdbdb77579ce4d8d897f1b690c97fe6ae472b11664853849bf611d94fb3e4\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 75"
        },
        {
          "sha": "ed431262d88986e10c05714a14cc72f914482a45",
          "filename": "content/projects/decomp-wtf-ppmguire.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-ppmguire.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-ppmguire.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecomp-wtf-ppmguire.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -34,12 +34,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-16T01:39:51.910Z'\n-  lastSynchronized: '2026-10-20T06:00:44.161Z'\n+  lastSynchronized: '2026-10-25T06:10:52.881Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"2662d4c8fb1f28ee80d075ef34063d8231bf104974320d866d02e1fd0d16b315\"\n-    releasesEtag: '\"beabca011d245159687fb6a685ede4790f750a2002b686c57e5bbc672bd5ded1\"'\n+    repoEtag: W/\"c74ded4d62f18b656022bdade9b3c2dcf649933431f98d4b861908933552fb3b\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 75"
        },
        {
          "sha": "1574ac0670d50f04459be7c29b56fee448b87f25",
          "filename": "content/projects/decomp-wtf-pythonlimited.md",
          "status": "modified",
          "additions": 8,
          "deletions": 5,
          "changes": 13,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-pythonlimited.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-pythonlimited.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecomp-wtf-pythonlimited.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -14,28 +14,32 @@ tags:\n   - fork\n features: []\n authors: []\n-license: null\n-homepage: null\n+license: MIT\n+homepage: 'https://decomp.wtf'\n source:\n   provider: github\n   repository: PythonLimited/decomp.wtf\n   repositoryId: '1426251226'\n   url: 'https://github.com/PythonLimited/decomp.wtf'\n repository:\n   archived: false\n-  defaultBranch: null\n+  defaultBranch: main\n   stars: 0\n   forks: 0\n-  lastCommit: null\n+  lastCommit: '2026-10-24T12:51:57Z'\n latestRelease:\n   tag: null\n   name: null\n   publishedAt: null\n   url: null\n activity:\n   lastChecked: '2026-10-21T00:58:06.538Z'\n+  lastSynchronized: '2026-10-25T06:10:53.312Z'\n automation:\n   sync: true\n+  github:\n+    repoEtag: W/\"c7d2536ae57c8cb1c4672c4d138b058e899a7574625828f4441db700d1ccb2b8\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 75\n@@ -51,5 +55,4 @@ relationships:\n   forkOf: i12bp8/decomp.wtf\n   source: i12bp8/decomp.wtf\n ---\n-\n Automatically promoted from PS2SP's discovery review queue because it met the public catalog threshold. This entry is not yet human-verified; upstream repository and release metadata will continue to synchronize automatically."
        },
        {
          "sha": "e45740a77f1078d8364010b690abdb32ae28677f",
          "filename": "content/projects/decomp-wtf-talentoscope.md",
          "status": "modified",
          "additions": 4,
          "deletions": 4,
          "changes": 8,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-talentoscope.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-talentoscope.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecomp-wtf-talentoscope.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -26,20 +26,20 @@ repository:\n   defaultBranch: main\n   stars: 0\n   forks: 0\n-  lastCommit: '2026-10-20T12:41:52Z'\n+  lastCommit: '2026-10-24T19:11:29Z'\n latestRelease:\n   tag: null\n   name: null\n   publishedAt: null\n   url: null\n activity:\n   lastChecked: '2026-10-17T21:45:31.930Z'\n-  lastSynchronized: '2026-10-20T20:35:18.744Z'\n+  lastSynchronized: '2026-10-24T20:50:05.626Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"fb340ddf1bbe2a1d9bccb8248e9c64f33eefcee41a8628792dfc8fde963c1e77\"\n-    releasesEtag: '\"94e354af2c4e28e3962b296e8975d0c6ec1a30b51f9be8608fe23fd98f6c999a\"'\n+    repoEtag: W/\"cd79f13bc34009354a373a8b873d1a9fff23dd469e344abfc4863106a05b228a\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 75"
        },
        {
          "sha": "e90de0ac0eb8241550e9327cc54d21f7ba4a963a",
          "filename": "content/projects/decomp-wtf-test.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-test.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-test.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecomp-wtf-test.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -34,12 +34,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-16T01:39:51.910Z'\n-  lastSynchronized: '2026-10-24T11:00:19.541Z'\n+  lastSynchronized: '2026-10-25T11:29:44.628Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"b84f2d9675f05d3e7dded141d2a625a274fab4490f8520a09868c85797744add\"\n-    releasesEtag: '\"2d8126faf7d60a088ad7a14f528f5a8f3ca919b5cd485e0f97d8ebb20b5102f0\"'\n+    repoEtag: W/\"ea480106441bcde667ed3af2797017d18bf222c4f9aaaaf963774ec2a9cada28\"\n+    releasesEtag: '\"9e77b662133f4d4e3007923dbf3a0cbb2fd896f67c82d38c785dfb153804c449\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 75"
        },
        {
          "sha": "a173d2ab164f13bcf90049ea6b6bb064b44904f6",
          "filename": "content/projects/decomp-wtf-ufodriverr.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-ufodriverr.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-ufodriverr.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecomp-wtf-ufodriverr.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -34,12 +34,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-14T22:38:09.246Z'\n-  lastSynchronized: '2026-10-23T01:31:46.708Z'\n+  lastSynchronized: '2026-10-25T01:14:10.024Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"ee460909e631634eb604cb9f1e9d63276ed2fe3bba3737220951621677690493\"\n-    releasesEtag: '\"ff6090af9c91b61292326178ca0823b97aed61799df8cc0ca8f7a6c7d1215cb9\"'\n+    repoEtag: W/\"66dcf3645a5d4ad9d8fca2215f02eb00e50cf01c4646d9cb77a0a28b2791bae3\"\n+    releasesEtag: '\"a5c451af9d88eff769190129023bb74c9de83ceb2f12875d0ca286055e5e73e6\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 75"
        },
        {
          "sha": "bc12269f24e4aae4e8dfc1bca7934ad81451c0f7",
          "filename": "content/projects/decomp-wtf-valenvivaldi.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-valenvivaldi.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-valenvivaldi.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecomp-wtf-valenvivaldi.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -34,12 +34,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-15T23:18:28.396Z'\n-  lastSynchronized: '2026-10-22T19:15:44.035Z'\n+  lastSynchronized: '2026-10-25T19:37:36.055Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"e27b47ea38353257cd9cd87d1fc160e4a4e942212c336f4821e9743eed3a3163\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"a1a61fc3cf357700edc2e74a5a73c2a50fb3cf8aed018cec9efc329fa40c6cf0\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 75"
        },
        {
          "sha": "f4bdc2e8abeac3492cd75ad0b740e5300e3916a8",
          "filename": "content/projects/decomp-wtf-wondersd.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-wondersd.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf-wondersd.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecomp-wtf-wondersd.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -34,12 +34,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-16T01:39:51.910Z'\n-  lastSynchronized: '2026-10-21T23:19:46.768Z'\n+  lastSynchronized: '2026-10-24T23:15:03.229Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"22854f941a27ee4628906a5d2e442bee588e6c7d41cb263585b54b03cbc74e72\"\n-    releasesEtag: '\"669f535743067c0ac9111e98f8b3d7f826f4edc7838967d02f64b6f505af2660\"'\n+    repoEtag: W/\"3553a296441c6e6daf7877324a05a13224f1ab2527941a3d8633e63d8d829931\"\n+    releasesEtag: '\"d958fbfec27a80deaeffdf70593bfd5e43f02f963f152d4a8d9f4917da92f5c0\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 75"
        },
        {
          "sha": "4c8c671dca60628e4895544d6afe8616ac564d32",
          "filename": "content/projects/decomp-wtf.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecomp-wtf.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecomp-wtf.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -34,12 +34,12 @@ latestRelease:\n   url: null\n activity:\n   lastChecked: '2026-10-14T18:54:17.306Z'\n-  lastSynchronized: '2026-10-23T22:11:38.494Z'\n+  lastSynchronized: '2026-10-25T22:33:45.026Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"5e1213a0d05201baed027612d0bc376c071b640b5cf9c10a1aa252dcd59585dc\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"5676c382a1d81bb6af13f29f3b212e3d468fdb3aad37dfe978628d3a1ea1c8fd\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 75"
        },
        {
          "sha": "24e9c0fa38f4aeb1a56d53b7d85eeb0a59fa409f",
          "filename": "content/projects/decompdecomp-bluesfire.md",
          "status": "added",
          "additions": 55,
          "deletions": 0,
          "changes": 55,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecompdecomp-bluesfire.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecompdecomp-bluesfire.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecompdecomp-bluesfire.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -0,0 +1,55 @@\n+---\n+name: decompdecomp\n+slug: decompdecomp-bluesfire\n+summary: >-\n+  A desktop app where AI agents decompile the games you own into C/C++ that\n+  rebuilds them byte for byte. \n+categories:\n+  - preservation\n+  - development\n+tags:\n+  - auto-discovered\n+  - pending-promoted\n+  - historical-or-discontinued\n+  - fork\n+features: []\n+authors: []\n+license: AGPL-3.0\n+homepage: 'http://decomp.ndelic.com/'\n+source:\n+  provider: github\n+  repository: Bluesfire/decompdecomp\n+  repositoryId: '1433372268'\n+repository:\n+  archived: false\n+  defaultBranch: main\n+  stars: 0\n+  forks: 0\n+  lastCommit: '2026-10-25T16:28:49Z'\n+latestRelease:\n+  tag: null\n+  name: null\n+  publishedAt: null\n+  url: null\n+activity:\n+  lastSynchronized: '2026-10-25T17:52:06.390Z'\n+automation:\n+  sync: true\n+discovery:\n+  method: 'incremental:\"PlayStation 2\" in:name,description,readme'\n+  confidence: 100\n+  evidence:\n+    - README explicitly mentions PlayStation 2\n+    - README contains PS2 development/toolchain evidence\n+    - GitHub fork of another repository\n+    - 'fork lineage traces to indexed PS2 project: i12bp8/decomp.wtf'\n+  maturity: active-unreleased\n+verified: false\n+featured: false\n+hidden: false\n+relationships:\n+  forkOf: n-delic/decompdecomp\n+  source: i12bp8/decomp.wtf\n+---\n+\n+Automatically discovered by PS2SP. Relevance evidence is recorded in frontmatter; human curation can expand this entry without disabling automated repository metadata synchronization."
        },
        {
          "sha": "e95cb942481f154373b8dd398ee8425c26197cc2",
          "filename": "content/projects/decompdecomp-doctormajid7-ux.md",
          "status": "added",
          "additions": 55,
          "deletions": 0,
          "changes": 55,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecompdecomp-doctormajid7-ux.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecompdecomp-doctormajid7-ux.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecompdecomp-doctormajid7-ux.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -0,0 +1,55 @@\n+---\n+name: decompdecomp\n+slug: decompdecomp-doctormajid7-ux\n+summary: >-\n+  A desktop app where AI agents decompile the games you own into C/C++ that\n+  rebuilds them byte for byte. \n+categories:\n+  - preservation\n+  - development\n+tags:\n+  - auto-discovered\n+  - pending-promoted\n+  - historical-or-discontinued\n+  - fork\n+features: []\n+authors: []\n+license: AGPL-3.0\n+homepage: 'http://decomp.ndelic.com/'\n+source:\n+  provider: github\n+  repository: doctormajid7-ux/decompdecomp\n+  repositoryId: '1432491360'\n+repository:\n+  archived: false\n+  defaultBranch: main\n+  stars: 0\n+  forks: 0\n+  lastCommit: '2026-10-24T17:19:50Z'\n+latestRelease:\n+  tag: null\n+  name: null\n+  publishedAt: null\n+  url: null\n+activity:\n+  lastSynchronized: '2026-10-24T21:53:47.976Z'\n+automation:\n+  sync: true\n+discovery:\n+  method: 'incremental:\"PlayStation 2\" in:name,description,readme'\n+  confidence: 100\n+  evidence:\n+    - README explicitly mentions PlayStation 2\n+    - README contains PS2 development/toolchain evidence\n+    - GitHub fork of another repository\n+    - 'fork lineage traces to indexed PS2 project: i12bp8/decomp.wtf'\n+  maturity: active-unreleased\n+verified: false\n+featured: false\n+hidden: false\n+relationships:\n+  forkOf: n-delic/decompdecomp\n+  source: i12bp8/decomp.wtf\n+---\n+\n+Automatically discovered by PS2SP. Relevance evidence is recorded in frontmatter; human curation can expand this entry without disabling automated repository metadata synchronization."
        },
        {
          "sha": "2d93f68489fdc8f755c33f72fbb167d76a574847",
          "filename": "content/projects/decompdecomp-probrogit.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecompdecomp-probrogit.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecompdecomp-probrogit.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecompdecomp-probrogit.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -32,12 +32,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-24T11:00:20.205Z'\n+  lastSynchronized: '2026-10-25T11:29:45.144Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"2d332a77600490abc112549df0205430a624dfb40109d265adf20ca38b6a64ed\"\n-    releasesEtag: '\"2d8126faf7d60a088ad7a14f528f5a8f3ca919b5cd485e0f97d8ebb20b5102f0\"'\n+    repoEtag: W/\"e0a92087911bcc374386e6f0e6344c3f4dc67f77205eb509ee4fb101062aa10a\"\n+    releasesEtag: '\"9e77b662133f4d4e3007923dbf3a0cbb2fd896f67c82d38c785dfb153804c449\"'\n discovery:\n   method: 'incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 95"
        },
        {
          "sha": "092d602bf88283069854ff907280f0a0d2d0a534",
          "filename": "content/projects/decompdecomp-puyodead1.md",
          "status": "modified",
          "additions": 10,
          "deletions": 7,
          "changes": 17,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecompdecomp-puyodead1.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecompdecomp-puyodead1.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecompdecomp-puyodead1.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -25,16 +25,20 @@ repository:\n   defaultBranch: main\n   stars: 0\n   forks: 0\n-  lastCommit: '2026-10-19T01:42:10Z'\n+  lastCommit: '2026-10-25T15:57:00Z'\n latestRelease:\n-  tag: null\n-  name: null\n-  publishedAt: null\n-  url: null\n+  tag: v1.0.6-alpha.4+puyo.14\n+  name: v1.0.6-alpha.4+puyo.14\n+  publishedAt: '2026-10-25T06:13:37Z'\n+  url: >-\n+    https://github.com/Puyodead1/decompdecomp/releases/tag/v1.0.6-alpha.4%2Bpuyo.14\n activity:\n-  lastSynchronized: '2026-10-19T01:43:13.773Z'\n+  lastSynchronized: '2026-10-25T16:19:51.439Z'\n automation:\n   sync: true\n+  github:\n+    repoEtag: W/\"23d11defc31e61793339f1939babdb0a26ffbbf64dab879a836d60b477318b31\"\n+    releasesEtag: W/\"a1516012ec9457321b7345a301496fb59b6c954d6ebe2c29174d33358caf2e1b\"\n discovery:\n   method: 'incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 95\n@@ -50,5 +54,4 @@ relationships:\n   forkOf: n-delic/decompdecomp\n   source: i12bp8/decomp.wtf\n ---\n-\n Automatically discovered by PS2SP. Relevance evidence is recorded in frontmatter; human curation can expand this entry without disabling automated repository metadata synchronization."
        },
        {
          "sha": "e1411db241f68d23ea45d6695d7faee01358ac5e",
          "filename": "content/projects/decompdecomp-timvandijk.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecompdecomp-timvandijk.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecompdecomp-timvandijk.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecompdecomp-timvandijk.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -32,12 +32,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-22T19:15:44.793Z'\n+  lastSynchronized: '2026-10-25T19:37:36.655Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"9e14c652e505edd5109f41e971b9fd09dd55994401ac76e535b0e6a52776c54b\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"4a73adf469e108b3e2a12bb64df8958b547128874e8c5fedcd421d3e7d24c87f\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 95"
        },
        {
          "sha": "9a23c9104415caa6cbcd5654999f2a02b89dfdbe",
          "filename": "content/projects/decompr-ensapra.md",
          "status": "modified",
          "additions": 5,
          "deletions": 3,
          "changes": 8,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecompr-ensapra.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdecompr-ensapra.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdecompr-ensapra.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -25,16 +25,19 @@ repository:\n   defaultBranch: main\n   stars: 0\n   forks: 0\n-  lastCommit: '2026-10-24T11:24:31Z'\n+  lastCommit: '2026-10-24T12:25:25Z'\n latestRelease:\n   tag: null\n   name: null\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-24T12:13:58.718Z'\n+  lastSynchronized: '2026-10-25T16:19:51.912Z'\n automation:\n   sync: true\n+  github:\n+    repoEtag: W/\"b66763b4220641e8c38bf4353bca586c08858ce404efdf9d1ec2e40314e2c1e6\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 100\n@@ -51,5 +54,4 @@ relationships:\n   forkOf: n-delic/decompdecomp\n   source: i12bp8/decomp.wtf\n ---\n-\n Automatically discovered by PS2SP. Relevance evidence is recorded in frontmatter; human curation can expand this entry without disabling automated repository metadata synchronization."
        },
        {
          "sha": "dbb0447ea70d53929ce8e7257417e6a17d7f4bb6",
          "filename": "content/projects/def-jam-vendetta-recomp.md",
          "status": "modified",
          "additions": 9,
          "deletions": 9,
          "changes": 18,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdef-jam-vendetta-recomp.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdef-jam-vendetta-recomp.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdef-jam-vendetta-recomp.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -23,22 +23,22 @@ source:\n repository:\n   archived: false\n   defaultBranch: master\n-  stars: 25\n+  stars: 29\n   forks: 1\n-  lastCommit: '2026-10-22T21:06:43Z'\n+  lastCommit: '2026-10-23T09:35:03Z'\n latestRelease:\n-  tag: v0.4.2\n-  name: Def Jam Vendetta Recompiled 0.4.2\n-  publishedAt: '2026-10-22T21:07:16Z'\n-  url: 'https://github.com/jcsix694/def-jam-vendetta-recomp/releases/tag/v0.4.2'\n+  tag: v0.4.3\n+  name: Def Jam Vendetta Recompiled 0.4.3\n+  publishedAt: '2026-10-23T09:35:35Z'\n+  url: 'https://github.com/jcsix694/def-jam-vendetta-recomp/releases/tag/v0.4.3'\n activity:\n   lastChecked: '2026-10-12T16:07:57.694Z'\n-  lastSynchronized: '2026-10-23T01:31:47.303Z'\n+  lastSynchronized: '2026-10-25T01:14:10.430Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"1b7d66f312f434c4607d255d53483cb36032d9915dc3249f72a72c16d6a8ac68\"\n-    releasesEtag: W/\"86730536e05659c860b8eab93806e237dc34894511f06f7323ed1cad81fbcca2\"\n+    repoEtag: W/\"00ee1d768ea3858da846f11e3b3c3bfb8f2059bd47bb0153f3b1f8b5245a2e86\"\n+    releasesEtag: W/\"4574c508ed868f7d4ae6c20e375391ea1632aedbc01c599bc47feb01bb8ece8c\"\n discovery:\n   method: 'pending-promotion:incremental:ps2 in:name,description'\n   confidence: 85"
        },
        {
          "sha": "4aba29236a36ffd70c4e99b878cce30d7e65b194",
          "filename": "content/projects/discforge-chd.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdiscforge-chd.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdiscforge-chd.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdiscforge-chd.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -33,12 +33,12 @@ latestRelease:\n   url: 'https://github.com/Suicideboyy/DiscForge-CHD/releases/tag/v2.7.0'\n activity:\n   lastChecked: '2026-09-29T16:39:19.086Z'\n-  lastSynchronized: '2026-10-22T19:15:45.574Z'\n+  lastSynchronized: '2026-10-25T19:37:37.170Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"ab34b42512dbeb242d589ed4b937ea7b1c31db61b5074603f20116c34d053ff4\"\n-    releasesEtag: W/\"d3128ce5d2987c69dd8368cbed83ab40f9c13859825d664e1a7acb5d6dbd9336\"\n+    repoEtag: W/\"8e0b932263d80e322473b91b75d4b48f38c912734212775d4ce5a72c7c90045b\"\n+    releasesEtag: W/\"e72c718c312f99d85fda90c4757d23758bf2a135d9f789645882a54a68a021a3\"\n discovery:\n   method: 'pending-promotion:incremental:ps2 in:name,description'\n   confidence: 85"
        },
        {
          "sha": "d866e83a5cfade53fc21687c7139c772196d5a07",
          "filename": "content/projects/dkwdrv-dkwdrv.md",
          "status": "modified",
          "additions": 9,
          "deletions": 9,
          "changes": 18,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdkwdrv-dkwdrv.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdkwdrv-dkwdrv.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdkwdrv-dkwdrv.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -19,21 +19,21 @@ source:\n repository:\n   archived: false\n   defaultBranch: main\n-  stars: 409\n+  stars: 410\n   forks: 12\n-  lastCommit: '2026-10-11T22:56:07Z'\n+  lastCommit: '2026-10-23T16:23:55Z'\n latestRelease:\n-  tag: 1.7.6s\n-  name: 1.7.6s\n-  publishedAt: '2026-10-11T22:53:11Z'\n-  url: 'https://github.com/DKWDRV/DKWDRV/releases/tag/1.7.6s'\n+  tag: 1.7.6t\n+  name: 1.7.6t\n+  publishedAt: '2026-10-23T16:22:55Z'\n+  url: 'https://github.com/DKWDRV/DKWDRV/releases/tag/1.7.6t'\n activity:\n-  lastSynchronized: '2026-10-20T20:35:19.291Z'\n+  lastSynchronized: '2026-10-24T20:50:06.168Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"fef459b2b0b620d438bbf4dafc6d6b17c5ccd98945fe0cb98a225f9e620ef6c3\"\n-    releasesEtag: W/\"e9f2ff0e4ed0f4b1bff9fe37cc15468ba14ed6fef0367dc292419787c9cd92a7\"\n+    repoEtag: W/\"6b46263449b3c878fb1de5ea49c043f47cd94fc8fedd802f1c9981f47e2c90e3\"\n+    releasesEtag: W/\"35c73cf4fc60aa43d7d4f55b72c49f75bc98e60171f6d603d76086cdaaacd9d6\"\n discovery:\n   method: 'starred-curated:NathanNeurotic'\n   confidence: 100"
        },
        {
          "sha": "8c4119c70afafea9e84bc5f7ab1fda80a1bfda59",
          "filename": "content/projects/dkwdrv-higorhgon.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdkwdrv-higorhgon.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdkwdrv-higorhgon.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdkwdrv-higorhgon.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -29,12 +29,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:38.951Z'\n+  lastSynchronized: '2026-10-25T22:33:45.473Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"9ce88882e41ee4c78e814887fd8101bcf5fd4bf8920158e6ea1e09056086c969\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"b9d989c4a76942b3a48257d9c6e17abbd4b64f5e2a74e81b802bcb2a6581d7c0\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:DKWDRV/DKWDRV'\n   confidence: 100"
        },
        {
          "sha": "ba9dd5760f9308bfc2403912d7eb99a02ee7ac62",
          "filename": "content/projects/dkwdrv-icechampion.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdkwdrv-icechampion.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdkwdrv-icechampion.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdkwdrv-icechampion.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -29,12 +29,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T01:31:47.787Z'\n+  lastSynchronized: '2026-10-25T01:14:10.970Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"a5f97c56f9a0290f29d7fee9829b512c82f0f6d9736c650a24bc09c867388e02\"\n-    releasesEtag: '\"ff6090af9c91b61292326178ca0823b97aed61799df8cc0ca8f7a6c7d1215cb9\"'\n+    repoEtag: W/\"0ccbd9fd9348c24bdb3c46453e994804068a846349092335223132328fad3ed3\"\n+    releasesEtag: '\"a5c451af9d88eff769190129023bb74c9de83ceb2f12875d0ca286055e5e73e6\"'\n discovery:\n   method: 'fork-network:DKWDRV/DKWDRV'\n   confidence: 95"
        },
        {
          "sha": "7b09ead2f7e2acc5e4c79e5efcf31f61c2abdd12",
          "filename": "content/projects/dkwdrv-jonasrk.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdkwdrv-jonasrk.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdkwdrv-jonasrk.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdkwdrv-jonasrk.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -29,12 +29,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T20:35:19.930Z'\n+  lastSynchronized: '2026-10-24T20:50:06.733Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"804f8d571eda5f4d60ab47bdb8186d1478a46344b76c5719c80bfd1368b7229a\"\n-    releasesEtag: '\"94e354af2c4e28e3962b296e8975d0c6ec1a30b51f9be8608fe23fd98f6c999a\"'\n+    repoEtag: W/\"de9f4e734649a8f16073d16ecfc576d36a773eb5786f7d666013da37f1c614f3\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: 'fork-network:DKWDRV/DKWDRV'\n   confidence: 95"
        },
        {
          "sha": "fe3a29e319c1248943344eeef74d1722ffc6fdc1",
          "filename": "content/projects/dkwdrv-old.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdkwdrv-old.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdkwdrv-old.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdkwdrv-old.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T20:35:20.465Z'\n+  lastSynchronized: '2026-10-24T20:50:07.226Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"34b72373dc134531a50b3abe42d41025c9791b8c85d64050e9b8de7fd87cb27c\"\n-    releasesEtag: '\"94e354af2c4e28e3962b296e8975d0c6ec1a30b51f9be8608fe23fd98f6c999a\"'\n+    repoEtag: W/\"787b7316fd2bcbe9704f2cdd45320cef0059db23f7b0c8e24b5e3921e8d19a7a\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: curated-owner\n   confidence: 100"
        },
        {
          "sha": "a3e4697caace250827c60cc6976d04efcdd7fa9d",
          "filename": "content/projects/dkwdrv-soridon.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdkwdrv-soridon.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdkwdrv-soridon.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdkwdrv-soridon.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -29,12 +29,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T06:00:44.759Z'\n+  lastSynchronized: '2026-10-25T06:10:53.794Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"b51fd2245be5e0e185e0f9c7218cbe5fa53aad391a0f310e08d8f913285c1a15\"\n-    releasesEtag: '\"beabca011d245159687fb6a685ede4790f750a2002b686c57e5bbc672bd5ded1\"'\n+    repoEtag: W/\"ffc504f9578f689379069cf6e14465e5a1be44b1e44cf766443f2388f3de2400\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'fork-network:DKWDRV/DKWDRV'\n   confidence: 95"
        },
        {
          "sha": "71db68f4a484c0ff6f638ceb889863dd0784907b",
          "filename": "content/projects/dothack-infection-fr.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdothack-infection-fr.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdothack-infection-fr.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdothack-infection-fr.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -26,12 +26,12 @@ latestRelease:\n   publishedAt: '2026-10-08T16:18:30Z'\n   url: 'https://github.com/absassist/dothack-infection-fr/releases/tag/v1.0'\n activity:\n-  lastSynchronized: '2026-10-21T23:19:47.317Z'\n+  lastSynchronized: '2026-10-24T23:15:03.752Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"453b159dadd7777d90514b84f91dd10a2347fbf61016005bbc0becbb02495dc1\"\n-    releasesEtag: W/\"7bd4a16e81218e1e5d22409aae857ecaa0c7bb313e942c617ad4ce02472776fb\"\n+    repoEtag: W/\"f07c4b6b76daae57b882c35971bea91b9edf0be33d7aca6b61b7164584ab833f\"\n+    releasesEtag: W/\"a0bc717332a985d1d54b1b2a09db985d6047a9614775ffc5bedb6a7c95037c87\"\n discovery:\n   method: 'incremental:ps2 in:name,description'\n   confidence: 100"
        },
        {
          "sha": "51eb6b95c72c31f3649c5ee6456f27ca09a49ea9",
          "filename": "content/projects/double-unofficial-open-ps2-loader-lang.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdouble-unofficial-open-ps2-loader-lang.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdouble-unofficial-open-ps2-loader-lang.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdouble-unofficial-open-ps2-loader-lang.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -29,12 +29,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-09T16:13:17.170Z'\n+  lastSynchronized: '2026-10-25T16:19:52.267Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"10118d3cdffda70b219f43144f27fda35a6ebb964684e390b548f47652dab205\"\n-    releasesEtag: '\"03eaebc487f57cb70aac1ff9827db51e9cbd437e4da42c0952991bf1af2062f6\"'\n+    repoEtag: W/\"8a440727f99c33fac287b8b486b5aa9b2b9dc3bd74b3d2ce1f7cec6d87e04b4a\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'trusted-owner:ps2homebrew'\n   confidence: 100"
        },
        {
          "sha": "216b2fa9e0987159993c2d426040a384e4244b26",
          "filename": "content/projects/downhill-revived.md",
          "status": "modified",
          "additions": 10,
          "deletions": 10,
          "changes": 20,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdownhill-revived.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdownhill-revived.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdownhill-revived.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -21,21 +21,21 @@ source:\n repository:\n   archived: false\n   defaultBranch: main\n-  stars: 9\n-  forks: 0\n-  lastCommit: '2026-10-21T20:03:30Z'\n+  stars: 32\n+  forks: 3\n+  lastCommit: '2026-10-24T16:59:05Z'\n latestRelease:\n-  tag: v0.2.7\n-  name: Downhill Revived 0.2.7\n-  publishedAt: '2026-10-18T22:26:41Z'\n-  url: 'https://github.com/skuza-oss/downhill-revived/releases/tag/v0.2.7'\n+  tag: v0.3.1\n+  name: Downhill Revived 0.3.1\n+  publishedAt: '2026-10-24T16:59:11Z'\n+  url: 'https://github.com/skuza-oss/downhill-revived/releases/tag/v0.3.1'\n activity:\n-  lastSynchronized: '2026-10-21T23:19:47.748Z'\n+  lastSynchronized: '2026-10-24T23:15:04.227Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"ef0a774542454b86384169cd6208de682ba42e5393045a4dc4890dbac59d9a12\"\n-    releasesEtag: W/\"0f7467bf231fb83ee6f8551c5a729639244d3e809444412b67d0f1aa87b56607\"\n+    repoEtag: W/\"b579b89375c50a8850e5373f4329c2223d0e4e0e2f742088e9e9086514b6779f\"\n+    releasesEtag: W/\"10070a0d4d3ec9a55c693547a0288f153b24023b3533bec52048a7cbd5e56064\"\n discovery:\n   method: 'incremental:ps2 in:name,description'\n   confidence: 100"
        },
        {
          "sha": "fdcb1ab7f16ca0277700ec2dec70b7c1c2521162",
          "filename": "content/projects/dq8-recompiled-sinan-karakaya.md",
          "status": "modified",
          "additions": 9,
          "deletions": 9,
          "changes": 18,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdq8-recompiled-sinan-karakaya.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdq8-recompiled-sinan-karakaya.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdq8-recompiled-sinan-karakaya.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -21,22 +21,22 @@ source:\n repository:\n   archived: false\n   defaultBranch: main\n-  stars: 79\n+  stars: 84\n   forks: 2\n-  lastCommit: '2026-10-11T15:44:45Z'\n+  lastCommit: '2026-10-23T21:42:07Z'\n latestRelease:\n-  tag: 3e74da6\n-  name: DQ8Recomp 3e74da6\n-  publishedAt: '2026-10-11T15:44:45Z'\n-  url: 'https://github.com/Sinan-Karakaya/DQ8-Recompiled/releases/tag/3e74da6'\n+  tag: f6dfd96\n+  name: DQ8Recomp f6dfd96\n+  publishedAt: '2026-10-23T21:42:08Z'\n+  url: 'https://github.com/Sinan-Karakaya/DQ8-Recompiled/releases/tag/f6dfd96'\n activity:\n   lastChecked: '2026-10-08T16:57:35.240Z'\n-  lastSynchronized: '2026-10-22T19:15:46.212Z'\n+  lastSynchronized: '2026-10-25T19:37:37.893Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"d86f1446cd81e2fe60c491c12f2019b7e78fbf66d127303a6a38fdc120322c8c\"\n-    releasesEtag: W/\"f2aae1577cc5b903190a88cab6fcecbe27edd7ba2238fa01531a5ec96bbc64e0\"\n+    repoEtag: W/\"751ccac337c094a6a571e641a51063345e945ba32fab0705dbff84bd1bbfd469\"\n+    releasesEtag: W/\"cc67726eeeaf6d8f241ac653a8755bb5aedcf6a169436b239b1bcab204e01529\"\n discovery:\n   method: 'pending-promotion:pending-recheck'\n   confidence: 85"
        },
        {
          "sha": "d091678f7fef61caea787b574c94e085e898c7bd",
          "filename": "content/projects/dreamgl.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdreamgl.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdreamgl.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdreamgl.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -29,12 +29,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:39.396Z'\n+  lastSynchronized: '2026-10-25T22:33:45.938Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"3c0b52a754f244e10c41096346dc7e90c308531b6c39ae7acc76a02840498f3f\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"76a87f441651ac3a6272f8ff07eef3d4e31adea22e97f0a465bde8d8e00856ef\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'trusted-owner:ps2homebrew'\n   confidence: 100"
        },
        {
          "sha": "15fbeae40a58d6c4af5471cca87de01ed961e0b1",
          "filename": "content/projects/dsx2-2024-darcotech.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdsx2-2024-darcotech.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fdsx2-2024-darcotech.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fdsx2-2024-darcotech.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-21T23:19:48.226Z'\n+  lastSynchronized: '2026-10-24T23:15:04.659Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"8fde970ed043367e62ede9621c7f1e207dc75e92f609376028690cedc1c16216\"\n-    releasesEtag: '\"669f535743067c0ac9111e98f8b3d7f826f4edc7838967d02f64b6f505af2660\"'\n+    repoEtag: W/\"2471146eb00d0c992f0af4ae5b2c15253661d3605032ec080d4a99b037c52b47\"\n+    releasesEtag: '\"d958fbfec27a80deaeffdf70593bfd5e43f02f963f152d4a8d9f4917da92f5c0\"'\n discovery:\n   method: 'fork-network:Trixarian/NetherSX2-patch'\n   confidence: 95"
        },
        {
          "sha": "90239a0afd1d6c2756f72474dfa7a6a5e1e76a82",
          "filename": "content/projects/eegs-gmh5225.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Feegs-gmh5225.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Feegs-gmh5225.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Feegs-gmh5225.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T06:00:45.312Z'\n+  lastSynchronized: '2026-10-25T06:10:54.257Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"aefc65bcb39140ccbe94286b7e78fefbe3bbc03983b1fdbfde6a06a9cfe170e0\"\n-    releasesEtag: '\"beabca011d245159687fb6a685ede4790f750a2002b686c57e5bbc672bd5ded1\"'\n+    repoEtag: W/\"1e5a9b8425dc5f3791fea74db10c16646aa14cc6a889dee4870a610147da0747\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'fork-network:allkern/iris'\n   confidence: 95"
        },
        {
          "sha": "77d6499881e7e279e2b6b724f11511a59e8e449c",
          "filename": "content/projects/ember.md",
          "status": "modified",
          "additions": 4,
          "deletions": 4,
          "changes": 8,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fember.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fember.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fember.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -18,7 +18,7 @@ source:\n repository:\n   archived: false\n   defaultBranch: main\n-  stars: 84\n+  stars: 87\n   forks: 0\n   lastCommit: '2026-10-07T03:39:39Z'\n latestRelease:\n@@ -28,12 +28,12 @@ latestRelease:\n   url: 'https://github.com/Gageformer/Ember/releases/tag/Beta-2'\n activity:\n   lastChecked: '2026-09-29T00:28:03.014Z'\n-  lastSynchronized: '2026-10-20T06:00:45.856Z'\n+  lastSynchronized: '2026-10-25T06:10:54.747Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"45a3009af1223cdf920d72dbccd8d06a024f88c4d725655a366422b5903000dd\"\n-    releasesEtag: W/\"f909139993b5f0343d289a58237651fe0ec8e5699c8f53c5982d60adc7b2050b\"\n+    repoEtag: W/\"7896a8588d7b3bbcc7d2b4d5cb496cea348ebb21afa2e093504349b6d82bb84a\"\n+    releasesEtag: W/\"48a2222530de6608c401f3faaa6430c482dd3ae75d21d015cdc64a2ed27412af\"\n discovery:\n   method: github-search\n   confidence: 100"
        },
        {
          "sha": "ed868db1ad7e28706f7d887066c14ab27ba04e89",
          "filename": "content/projects/emucorex-coding-br.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Femucorex-coding-br.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Femucorex-coding-br.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Femucorex-coding-br.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T01:31:48.310Z'\n+  lastSynchronized: '2026-10-25T01:14:11.519Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"a56c2c79f6fa5aed3d2da99104513ea40bea9a247695838fc7369d34abd1bd5b\"\n-    releasesEtag: '\"ff6090af9c91b61292326178ca0823b97aed61799df8cc0ca8f7a6c7d1215cb9\"'\n+    repoEtag: W/\"aa49d3f765665117fd6ce0bf3bbfc15c22f6430cb83a91b19c42d6bfd1ca7d8f\"\n+    releasesEtag: '\"a5c451af9d88eff769190129023bb74c9de83ceb2f12875d0ca286055e5e73e6\"'\n discovery:\n   method: 'fork-network:sashkinbro/EmuCoreX'\n   confidence: 100"
        },
        {
          "sha": "bfaf7a68d4c58edf32fff0e421ef3cd4e698e83a",
          "filename": "content/projects/emucorex-fenix222fox-hash.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Femucorex-fenix222fox-hash.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Femucorex-fenix222fox-hash.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Femucorex-fenix222fox-hash.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-09T16:13:17.648Z'\n+  lastSynchronized: '2026-10-25T16:19:52.786Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"b86f9e53aae2ccd23ed93472bae471cc48e16d1c2a43bb700208a1cd2614fbcc\"\n-    releasesEtag: '\"03eaebc487f57cb70aac1ff9827db51e9cbd437e4da42c0952991bf1af2062f6\"'\n+    repoEtag: W/\"490a9c6250ba97c2bab550e5047a7ae1bcbd3aab123b45d1a20907139ee9ad55\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'fork-network:sashkinbro/EmuCoreX'\n   confidence: 100"
        },
        {
          "sha": "fda795d1dd6ec0f6dd4dcb0906085aaa33c0e1d4",
          "filename": "content/projects/emucorex-huanyfen.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Femucorex-huanyfen.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Femucorex-huanyfen.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Femucorex-huanyfen.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-22T19:15:46.820Z'\n+  lastSynchronized: '2026-10-25T19:37:38.384Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"ce6b3fa063ab787c0889ab2f0c7a6445afa85048307d28b5d822c27711f51920\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"b10f3523dd9d6b81d7ebdb081a59ae5a15b0e122b5f1dc500037e6f196a6b75b\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'fork-network:sashkinbro/EmuCoreX'\n   confidence: 100"
        },
        {
          "sha": "586aff96da8085147b953ae42842b1d7bac989a0",
          "filename": "content/projects/emucorex-kaynet3000.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Femucorex-kaynet3000.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Femucorex-kaynet3000.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Femucorex-kaynet3000.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-24T11:00:20.739Z'\n+  lastSynchronized: '2026-10-25T11:29:45.628Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"bfbdac6217261ce98b14a916fa6296907dff019755090f21788b7fb7b7101ccc\"\n-    releasesEtag: '\"2d8126faf7d60a088ad7a14f528f5a8f3ca919b5cd485e0f97d8ebb20b5102f0\"'\n+    repoEtag: W/\"175e90d251f5b25260c744e0be34ce6ba68cefe13254ffafde6d67ea48b78a60\"\n+    releasesEtag: '\"9e77b662133f4d4e3007923dbf3a0cbb2fd896f67c82d38c785dfb153804c449\"'\n discovery:\n   method: 'fork-network:sashkinbro/EmuCoreX'\n   confidence: 100"
        },
        {
          "sha": "f8d5fd53b23ef6bdca39ba30cd19d31975530a54",
          "filename": "content/projects/emucorex-sulac4ever.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Femucorex-sulac4ever.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Femucorex-sulac4ever.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Femucorex-sulac4ever.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-22T19:15:47.450Z'\n+  lastSynchronized: '2026-10-25T19:37:38.900Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"367550e8c75855e9e18c3e4aabf9d9e54acac8dd99fded9341caaf07b0ccaae2\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"b4635971d47cb4f87ea68f5863f522747ff8c02078018ecf72922dacf5df4669\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'fork-network:sashkinbro/EmuCoreX'\n   confidence: 100"
        },
        {
          "sha": "55b2b0c6128d097ee4a715b4105a0af6e6f6ec76",
          "filename": "content/projects/emucorex-xdaofangxix.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Femucorex-xdaofangxix.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Femucorex-xdaofangxix.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Femucorex-xdaofangxix.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   publishedAt: '2026-08-14T03:15:42Z'\n   url: 'https://github.com/XDaoFangxiX/EmuCoreX/releases/tag/v20260814-5'\n activity:\n-  lastSynchronized: '2026-10-22T19:15:48.233Z'\n+  lastSynchronized: '2026-10-25T19:37:39.573Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"9f1ba7b8d57fdad3987272b0a8e7aa197e0fff87418a9e6d1e34f07e437fb2f7\"\n-    releasesEtag: W/\"4b45542009e2f9639a77f58e4ae68225b417922e9200bb09693f8836ecbcbd20\"\n+    repoEtag: W/\"dd650bb1a551555e5087494915c715c906b8d3a1c36e3836d59d1dcd8df22044\"\n+    releasesEtag: W/\"dd5bbae5d6651154b89698dba1ccaffabf180036cdea106a82800fc55bf87a8d\"\n discovery:\n   method: 'fork-network:sashkinbro/EmuCoreX'\n   confidence: 100"
        },
        {
          "sha": "6d9875e97f63a67e91bd25340ac43e8b84f8e8c0",
          "filename": "content/projects/emumod.md",
          "status": "added",
          "additions": 48,
          "deletions": 0,
          "changes": 48,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Femumod.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Femumod.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Femumod.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -0,0 +1,48 @@\n+---\n+name: EmuMod\n+slug: emumod\n+summary: >-\n+  Juega tus juegos de GBA, 3DS, PSP, NES, N64, PS2, PS3 y Wii dentro de\n+  Minecraft 1.20.1 (Fabric). Creado por Spar Retro \u00b7 youtube.com/@sparretro\n+categories:\n+  - games\n+tags:\n+  - auto-discovered\n+features: []\n+authors: []\n+license: null\n+homepage: 'https://www.youtube.com/@sparretro'\n+source:\n+  provider: github\n+  repository: Sparretro/EmuMod\n+  repositoryId: '1433375998'\n+repository:\n+  archived: false\n+  defaultBranch: main\n+  stars: 0\n+  forks: 0\n+  lastCommit: '2026-10-25T17:45:10Z'\n+latestRelease:\n+  tag: v2.1.1\n+  name: EmuMod 2.1.1\n+  publishedAt: '2026-10-25T17:45:10Z'\n+  url: 'https://github.com/Sparretro/EmuMod/releases/tag/v2.1.1'\n+activity:\n+  lastSynchronized: '2026-10-25T17:52:06.390Z'\n+automation:\n+  sync: true\n+discovery:\n+  method: 'incremental:ps2 in:name,description'\n+  confidence: 100\n+  evidence:\n+    - PS2-specific topic\n+    - repository description explicitly identifies PS2\n+    - README explicitly mentions PlayStation 2\n+    - published GitHub release present\n+  maturity: released-active\n+verified: false\n+featured: false\n+hidden: false\n+---\n+\n+Automatically discovered by PS2SP. Relevance evidence is recorded in frontmatter; human curation can expand this entry without disabling automated repository metadata synchronization."
        },
        {
          "sha": "cd318f9a24abab35ea6a5305e048930f5c5a153d",
          "filename": "content/projects/emuvyra-ps2.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Femuvyra-ps2.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Femuvyra-ps2.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Femuvyra-ps2.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -26,12 +26,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-22T19:15:48.813Z'\n+  lastSynchronized: '2026-10-25T19:37:40.008Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"5f081045fe7d815997a5dd8caadc0d27e98c6cd819a53b0e653495bc7543eb54\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"98efc82318b31c2d7deb7570623a0c804aba7faad1d3708b15d1156c1512611a\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'incremental:ps2 in:name,description'\n   confidence: 95"
        },
        {
          "sha": "68c1eabeeecfa8323431a1cc62e81d1e2202cb43",
          "filename": "content/projects/enceladus-frok-jeevesgb.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fenceladus-frok-jeevesgb.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fenceladus-frok-jeevesgb.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fenceladus-frok-jeevesgb.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -31,12 +31,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-24T11:00:21.246Z'\n+  lastSynchronized: '2026-10-25T11:29:46.046Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"ce7116d2cc84ccddddbe719e760975384fc41c2d4f3c0b487637b98dc3238077\"\n-    releasesEtag: '\"2d8126faf7d60a088ad7a14f528f5a8f3ca919b5cd485e0f97d8ebb20b5102f0\"'\n+    repoEtag: W/\"4feaa816470ff9eb8c552ac13c276aafb2b6f9f2dbf5aab47f2a2bfab3329494\"\n+    releasesEtag: '\"9e77b662133f4d4e3007923dbf3a0cbb2fd896f67c82d38c785dfb153804c449\"'\n discovery:\n   method: 'fork-network:DanielSant0s/Enceladus'\n   confidence: 100"
        },
        {
          "sha": "d6e780d1db2b1f042bce2a57c49d803cebfb6ebf",
          "filename": "content/projects/enceladus-indraphilip.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fenceladus-indraphilip.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fenceladus-indraphilip.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fenceladus-indraphilip.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -29,12 +29,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-22T19:15:49.456Z'\n+  lastSynchronized: '2026-10-25T19:37:40.510Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"6cdaac64b095cf49f066fef9991b8f3a89b8bb4fb6a2349d125c4f0257e9af48\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"68e1e2df8d2684860ef04e538736d948b32a52be8560bef8b224c7e4d65125cb\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'fork-network:DanielSant0s/Enceladus'\n   confidence: 100"
        },
        {
          "sha": "119b758150c2782978faf6666e995da2a6f809a0",
          "filename": "content/projects/enceladus-mayankoyote.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fenceladus-mayankoyote.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fenceladus-mayankoyote.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fenceladus-mayankoyote.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -29,12 +29,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-21T23:19:48.764Z'\n+  lastSynchronized: '2026-10-24T23:15:05.132Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"109c00449fda223752cb0e34088c9f8bf8de8ff87dfa1ed2cea0c3d23a4a1fcd\"\n-    releasesEtag: '\"669f535743067c0ac9111e98f8b3d7f826f4edc7838967d02f64b6f505af2660\"'\n+    repoEtag: W/\"2114d4a0e33b109833dc5f01693ca9363105b3438df8d883ea4b762168db06d6\"\n+    releasesEtag: '\"d958fbfec27a80deaeffdf70593bfd5e43f02f963f152d4a8d9f4917da92f5c0\"'\n discovery:\n   method: 'fork-network:israpps/Enceladus'\n   confidence: 100"
        },
        {
          "sha": "2f57b547ce63900659773f447c32707c5f826d27",
          "filename": "content/projects/enceladus-o1line.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fenceladus-o1line.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fenceladus-o1line.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fenceladus-o1line.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -29,12 +29,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:39.844Z'\n+  lastSynchronized: '2026-10-25T22:33:46.464Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"69f55fc1ba0370e8f957f169dcb2cf928e22ea1807e7857c62c7657a837b0667\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"28153a9de21bc56bbce19b2682f2ab34a216a4e9a2dd39d0ad02d218c22421c3\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:DanielSant0s/Enceladus'\n   confidence: 100"
        },
        {
          "sha": "be5d620df16f6f39865e9b1b46d2e7fafcef0803",
          "filename": "content/projects/enceladus-parrado.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fenceladus-parrado.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fenceladus-parrado.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fenceladus-parrado.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -29,12 +29,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T01:31:48.775Z'\n+  lastSynchronized: '2026-10-25T01:14:12.054Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"b488c7af25150915641cdf9e98c80715e871aa50ade64fad272def7b820a3f93\"\n-    releasesEtag: '\"ff6090af9c91b61292326178ca0823b97aed61799df8cc0ca8f7a6c7d1215cb9\"'\n+    repoEtag: W/\"9e3127433ef3bf4190f9b594c94c60f4e076b571a221aab5e8487494161290c2\"\n+    releasesEtag: '\"a5c451af9d88eff769190129023bb74c9de83ceb2f12875d0ca286055e5e73e6\"'\n discovery:\n   method: 'fork-network:DanielSant0s/Enceladus'\n   confidence: 100"
        },
        {
          "sha": "aa7aaebe979dc25ea8b6a3791eb0b0841fa741ec",
          "filename": "content/projects/enceladus-paulo10998g-cloud.md",
          "status": "added",
          "additions": 53,
          "deletions": 0,
          "changes": 53,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fenceladus-paulo10998g-cloud.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fenceladus-paulo10998g-cloud.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fenceladus-paulo10998g-cloud.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -0,0 +1,53 @@\n+---\n+name: Enceladus\n+slug: enceladus-paulo10998g-cloud\n+summary: ' A complete Lua environment for creating homebrew applications and games on PlayStation 2.'\n+categories:\n+  - engines\n+  - runtimes\n+  - development\n+tags:\n+  - auto-discovered\n+  - fork\n+features: []\n+authors: []\n+license: GPL-3.0\n+homepage: null\n+source:\n+  provider: github\n+  repository: paulo10998g-cloud/Enceladus\n+  repositoryId: '1433296082'\n+repository:\n+  archived: false\n+  defaultBranch: main\n+  stars: 0\n+  forks: 0\n+  lastCommit: '2026-10-25T17:01:49Z'\n+latestRelease:\n+  tag: null\n+  name: null\n+  publishedAt: null\n+  url: null\n+activity:\n+  lastSynchronized: '2026-10-25T17:52:06.390Z'\n+automation:\n+  sync: true\n+discovery:\n+  method: 'incremental:ps2sdk in:name,description,readme'\n+  confidence: 100\n+  evidence:\n+    - repository description explicitly identifies PS2\n+    - README explicitly mentions PlayStation 2\n+    - README contains PS2 development/toolchain evidence\n+    - GitHub fork of another repository\n+    - 'fork lineage traces to indexed PS2 project: DanielSant0s/Enceladus'\n+  maturity: active-unreleased\n+verified: false\n+featured: false\n+hidden: false\n+relationships:\n+  forkOf: DanielSant0s/Enceladus\n+  source: DanielSant0s/Enceladus\n+---\n+\n+Automatically discovered by PS2SP. Relevance evidence is recorded in frontmatter; human curation can expand this entry without disabling automated repository metadata synchronization."
        },
        {
          "sha": "cc8676ee922b4bab9d02cb5d605bf99222ae5e88",
          "filename": "content/projects/enceladus-stayhye.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fenceladus-stayhye.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fenceladus-stayhye.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fenceladus-stayhye.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -29,12 +29,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-09T16:13:18.140Z'\n+  lastSynchronized: '2026-10-25T16:19:53.257Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"caa354e0ee461b90bff341bc28e76545004484ffd87c9081b62bf9f9d2cf5c65\"\n-    releasesEtag: '\"03eaebc487f57cb70aac1ff9827db51e9cbd437e4da42c0952991bf1af2062f6\"'\n+    repoEtag: W/\"8e9bace5b6365dd2d79be3584ec79f37d71835abac44b0e8ab1ca92880794ac4\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'fork-network:DanielSant0s/Enceladus'\n   confidence: 100"
        },
        {
          "sha": "ed55ef6c7eaa744c4b6fa2dd9051a3e4291cd5f3",
          "filename": "content/projects/eod-recomp.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Feod-recomp.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Feod-recomp.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Feod-recomp.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -31,12 +31,12 @@ latestRelease:\n   url: 'https://github.com/pareidolico/eod-recomp/releases/tag/v1.0.2'\n activity:\n   lastChecked: '2026-10-11T02:11:44.268Z'\n-  lastSynchronized: '2026-10-22T19:15:50.196Z'\n+  lastSynchronized: '2026-10-25T19:37:41.202Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"9e1599497d82324e177badec42e1ea625c2aee8276afc88259fd6081471b8523\"\n-    releasesEtag: W/\"68ceb56824355acccd59d55d5b8579e9ee8189e7345a5aa1b6d793729d01439c\"\n+    repoEtag: W/\"6ba817949e24c73a4ae53c547bd29e92e533284fcbe7a109575fbe8a619056fd\"\n+    releasesEtag: W/\"2e8cc87d86056369ab941952c608690e3dab829659ceab31b02d320f54dbc49c\"\n discovery:\n   method: 'pending-promotion:incremental:\"PlayStation 2\" in:name,description,readme'\n   confidence: 90"
        },
        {
          "sha": "a2c60cbc7480ae2f31cc40888d5cf78ab5016941",
          "filename": "content/projects/esr-gui-mac.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fesr-gui-mac.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Fesr-gui-mac.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Fesr-gui-mac.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -26,12 +26,12 @@ latestRelease:\n   publishedAt: '2026-10-16T16:58:11Z'\n   url: 'https://github.com/giancoppola/ESR-Patcher-for-Mac/releases/tag/v0.1.0'\n activity:\n-  lastSynchronized: '2026-10-23T01:31:49.254Z'\n+  lastSynchronized: '2026-10-25T01:14:12.506Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"42b52d8d979b7e090b62e38dd8afbc4370b2c1eddba04a06eb7d648c4d58da2d\"\n-    releasesEtag: W/\"ab19ad7a4033ad2b5b49881d75120f6b11e903de4fa0cda5dc9e9cac041f91d8\"\n+    repoEtag: W/\"4c1024b1e9328b35ed15e96589528e8972d7acd15b76b5782c359f6f747941d5\"\n+    releasesEtag: W/\"133d188dbdd199df5649309261613c1f80dd65bec624e5e56d8fb01cb122002c\"\n discovery:\n   method: 'incremental:ps2 in:name,description'\n   confidence: 100"
        },
        {
          "sha": "2e5481fed34cf76efb37c08d934651bced044fcb",
          "filename": "content/projects/eva2-ps2-eng-translation.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Feva2-ps2-eng-translation.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Feva2-ps2-eng-translation.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Feva2-ps2-eng-translation.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -26,12 +26,12 @@ latestRelease:\n   publishedAt: '2026-09-30T04:33:08Z'\n   url: 'https://github.com/basilrum/eva2_ps2_eng_translation/releases/tag/v1.0'\n activity:\n-  lastSynchronized: '2026-10-20T20:35:21.068Z'\n+  lastSynchronized: '2026-10-24T20:50:07.735Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"f090eca533575a018f24c093d4329c75eb053016b155c0dcebf7c76a94262d88\"\n-    releasesEtag: W/\"f16c8adf94c338b0e505d524b35aa1e68080faa34fe619197d752d4c310edd18\"\n+    repoEtag: W/\"c0b9ea253bef21fc7372ddad420a0538d731007cb63842cb64d3de680547c09a\"\n+    releasesEtag: W/\"96e0ece34d3c01d1a9bfe3092f71928f51d7bd1a3c4ddb116cf629154dfdfbd9\"\n discovery:\n   method: 'incremental:ps2 in:name,description'\n   confidence: 100"
        },
        {
          "sha": "392013c47dcca0d8e30d8199aa67c361291cbfb4",
          "filename": "content/projects/fallout2-ce-ps2.md",
          "status": "modified",
          "additions": 8,
          "deletions": 8,
          "changes": 16,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffallout2-ce-ps2.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffallout2-ce-ps2.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffallout2-ce-ps2.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -19,19 +19,19 @@ repository:\n   defaultBranch: ps2\n   stars: 2\n   forks: 0\n-  lastCommit: '2026-10-22T16:48:04Z'\n+  lastCommit: '2026-10-25T00:22:39Z'\n latestRelease:\n-  tag: v1.0.2-ps2\n-  name: Fallout 2 CE PS2 - v1.0.2 (Hardware Boot Hardening)\n-  publishedAt: '2026-10-22T05:59:28Z'\n-  url: 'https://github.com/Nicolasalucin/fallout2-ce-ps2/releases/tag/v1.0.2-ps2'\n+  tag: v1.0.3-ps2\n+  name: Fallout 2 CE PS2 v1.0.3\n+  publishedAt: '2026-10-25T00:23:01Z'\n+  url: 'https://github.com/Nicolasalucin/fallout2-ce-ps2/releases/tag/v1.0.3-ps2'\n activity:\n-  lastSynchronized: '2026-10-24T11:00:21.792Z'\n+  lastSynchronized: '2026-10-25T11:29:46.527Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"3cee73cdb7feae187024ec71828cb7b8df995c4a80cb5c6f42d0e97ec781a030\"\n-    releasesEtag: W/\"8d3753fb17fab966b0a36ef9f178f77a8b369078945e8b493b67ea6258adcaac\"\n+    repoEtag: W/\"4e7dca4911fc62da712ae7d227d6ff09362d8fa2d57c5953a90810cfb51b1506\"\n+    releasesEtag: W/\"ca5329e6302394ea635e9b3735c8854ee172a2dc4e9a5c1f76e8292b1e58fe48\"\n discovery:\n   method: 'incremental:\"ps2 port\" in:name,description,readme'\n   confidence: 100"
        },
        {
          "sha": "3ea39c6844276aad890621a44011a3697f636db5",
          "filename": "content/projects/fc1307-tools.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffc1307-tools.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffc1307-tools.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffc1307-tools.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -28,12 +28,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T01:31:49.745Z'\n+  lastSynchronized: '2026-10-25T01:14:13.007Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"94b807cc0a1761421414b6bf6e73961fccb19cbd7944e9396256bca1b020598c\"\n-    releasesEtag: '\"ff6090af9c91b61292326178ca0823b97aed61799df8cc0ca8f7a6c7d1215cb9\"'\n+    repoEtag: W/\"2302f48a38aafcc740c894eadd97afac583ce01ad666f8c9d0ce3533a274fe70\"\n+    releasesEtag: '\"a5c451af9d88eff769190129023bb74c9de83ceb2f12875d0ca286055e5e73e6\"'\n discovery:\n   method: curated-owner\n   confidence: 100"
        },
        {
          "sha": "cf5095e008992c9ef9c3d905201fa757d94fdafe",
          "filename": "content/projects/fceumm-ps2-1247847495.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffceumm-ps2-1247847495.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffceumm-ps2-1247847495.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffceumm-ps2-1247847495.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -32,12 +32,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T01:31:50.213Z'\n+  lastSynchronized: '2026-10-25T01:14:13.459Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"608dd805de9938546a0c2295205e25c0d10dad9e36c165ae844fffe43d1cb56a\"\n-    releasesEtag: '\"ff6090af9c91b61292326178ca0823b97aed61799df8cc0ca8f7a6c7d1215cb9\"'\n+    repoEtag: W/\"fc718ca0f78ece97b7bb8f71a6a31459c30f829753090e79490b2bc7b15c57e2\"\n+    releasesEtag: '\"a5c451af9d88eff769190129023bb74c9de83ceb2f12875d0ca286055e5e73e6\"'\n discovery:\n   method: 'fork-network:ps2homebrew/Fceumm-PS2'\n   confidence: 100"
        },
        {
          "sha": "52a7a5b4901618635173f6ecee41300a1588e148",
          "filename": "content/projects/fceumm-ps2-362053534.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffceumm-ps2-362053534.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffceumm-ps2-362053534.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffceumm-ps2-362053534.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -32,12 +32,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T06:00:46.463Z'\n+  lastSynchronized: '2026-10-25T06:10:55.251Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"860934054ec351d26bf8eb2cb58f2f4475c6f4ee6e2768574f654ae632fa842e\"\n-    releasesEtag: '\"beabca011d245159687fb6a685ede4790f750a2002b686c57e5bbc672bd5ded1\"'\n+    repoEtag: W/\"aefda845c8ec29e2a3e938f29ed714e9bd903ac45cd486b5254150ebf646f759\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'fork-network:ps2homebrew/Fceumm-PS2'\n   confidence: 100"
        },
        {
          "sha": "9164484f2a9eeab1b6321cc066810210cb4d22aa",
          "filename": "content/projects/fceumm-ps2-ayassinsayd.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffceumm-ps2-ayassinsayd.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffceumm-ps2-ayassinsayd.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffceumm-ps2-ayassinsayd.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -32,12 +32,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T01:31:50.717Z'\n+  lastSynchronized: '2026-10-25T01:14:13.899Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"8fac0230a502e24ed305b5f65632a6e7bc7b8ade7ac165dd080f9f8cbeb6bf42\"\n-    releasesEtag: '\"ff6090af9c91b61292326178ca0823b97aed61799df8cc0ca8f7a6c7d1215cb9\"'\n+    repoEtag: W/\"bc736a71129df64b66f64e6e0382277e384c1a642cc7242b461c83f136337e6b\"\n+    releasesEtag: '\"a5c451af9d88eff769190129023bb74c9de83ceb2f12875d0ca286055e5e73e6\"'\n discovery:\n   method: 'fork-network:ps2homebrew/Fceumm-PS2'\n   confidence: 100"
        },
        {
          "sha": "c6197a7561727cdf5367a3f9af79105772ecf27d",
          "filename": "content/projects/fceumm-ps2-errory2k.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffceumm-ps2-errory2k.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffceumm-ps2-errory2k.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffceumm-ps2-errory2k.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -32,12 +32,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-22T19:15:50.855Z'\n+  lastSynchronized: '2026-10-25T19:37:41.733Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"53d455e495415a3b9de3ab19b9d11ee5c2b0490ae86ab8af040364680e707df8\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"bac6a8313d5dda2ead4ef3d6ac3345e6d86eb246d534719b25ec67fe4c690324\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'fork-network:ps2homebrew/Fceumm-PS2'\n   confidence: 100"
        },
        {
          "sha": "61e5a840c0d88c8cd038dfe79842fac24dc414c4",
          "filename": "content/projects/fceumm-ps2-shgohh.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffceumm-ps2-shgohh.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffceumm-ps2-shgohh.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffceumm-ps2-shgohh.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -32,12 +32,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-21T23:19:49.203Z'\n+  lastSynchronized: '2026-10-24T23:15:05.560Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"ea3bb74a7d90736c67b40774ee13c2886f6de9fc1aa2943a3093847237a7001a\"\n-    releasesEtag: '\"669f535743067c0ac9111e98f8b3d7f826f4edc7838967d02f64b6f505af2660\"'\n+    repoEtag: W/\"b200cbde88ebbeb32d053328c57e30f580dd97b5cf754a96343eaba41a1c0bfe\"\n+    releasesEtag: '\"d958fbfec27a80deaeffdf70593bfd5e43f02f963f152d4a8d9f4917da92f5c0\"'\n discovery:\n   method: 'fork-network:ps2homebrew/Fceumm-PS2'\n   confidence: 100"
        },
        {
          "sha": "798a6b79f2f224d84f74d51fc8c7f173d4198c37",
          "filename": "content/projects/fceumm-ps2-smb-l10n37.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffceumm-ps2-smb-l10n37.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffceumm-ps2-smb-l10n37.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffceumm-ps2-smb-l10n37.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -31,12 +31,12 @@ latestRelease:\n   url: 'https://github.com/L10N37/Fceumm-PS2-SMB/releases/tag/v0.3.4-smb1'\n activity:\n   lastChecked: '2026-09-29T00:30:39.763Z'\n-  lastSynchronized: '2026-10-09T16:13:18.795Z'\n+  lastSynchronized: '2026-10-25T16:19:53.952Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"2107cc15d8a5f2e1ecf892aa01ee62b3209f42de88651a7c2f9708a2c8dec86a\"\n-    releasesEtag: W/\"999f804000dbbc0a27d4fa5e464ca6df150ce02f8220efec3adbd45da3996849\"\n+    repoEtag: W/\"ad025cd0fd957c16f2da33636d3a63b57ea655f05dc4f5f800de875d162543b0\"\n+    releasesEtag: W/\"5add6d11d702729f35f8271c16987cba2c0f66dcfe3c952f7d7f34db5787461b\"\n discovery:\n   method: github-maintained-fork\n   confidence: 100"
        },
        {
          "sha": "568bf22b74796386b8924c36c1ccd30d28d88a47",
          "filename": "content/projects/fceumm-ps2-solomlorr.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffceumm-ps2-solomlorr.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffceumm-ps2-solomlorr.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffceumm-ps2-solomlorr.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -32,12 +32,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-24T11:00:22.409Z'\n+  lastSynchronized: '2026-10-25T11:29:46.943Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"c3b2998db558f663f58879d7b805dc1c30ab781b4f4a9cc7677738d62af5ca52\"\n-    releasesEtag: '\"2d8126faf7d60a088ad7a14f528f5a8f3ca919b5cd485e0f97d8ebb20b5102f0\"'\n+    repoEtag: W/\"6bd59172ae42151dc145c2f2f0caec01db8559d8b635a07084cfca857500814a\"\n+    releasesEtag: '\"9e77b662133f4d4e3007923dbf3a0cbb2fd896f67c82d38c785dfb153804c449\"'\n discovery:\n   method: 'fork-network:ps2homebrew/Fceumm-PS2'\n   confidence: 100"
        },
        {
          "sha": "174b0c84a93f14be42276d74c7767d380e35b338",
          "filename": "content/projects/fceumm-ps2-tc17.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffceumm-ps2-tc17.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffceumm-ps2-tc17.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffceumm-ps2-tc17.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -32,12 +32,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T20:35:21.640Z'\n+  lastSynchronized: '2026-10-24T20:50:08.242Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"6047b29cffb43faf7a04fb219672dd3555d7898e212d65b3548f45571e757ff3\"\n-    releasesEtag: '\"94e354af2c4e28e3962b296e8975d0c6ec1a30b51f9be8608fe23fd98f6c999a\"'\n+    repoEtag: W/\"981e9ec06f0ecdda5935c9e65eba8ffbae5f84436b3aba28ed7f16a324ce2f5d\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: 'fork-network:ps2homebrew/Fceumm-PS2'\n   confidence: 100"
        },
        {
          "sha": "7909a5dcc8b9e4ee1b05cbb2bf8a0adcf2c58eae",
          "filename": "content/projects/fceumm-ps2coverflow-wallmasterr.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffceumm-ps2coverflow-wallmasterr.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffceumm-ps2coverflow-wallmasterr.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffceumm-ps2coverflow-wallmasterr.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -32,12 +32,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T20:35:22.136Z'\n+  lastSynchronized: '2026-10-24T20:50:08.776Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"87e391b2ce287fbf4a96f89a299eaa9794c94b75760e193e620bb9a056d2223b\"\n-    releasesEtag: '\"94e354af2c4e28e3962b296e8975d0c6ec1a30b51f9be8608fe23fd98f6c999a\"'\n+    repoEtag: W/\"385301ba72b876d8df21b86ba68b35492e984095d52f30df1119dd72c63cf385\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: 'fork-network:ps2homebrew/Fceumm-PS2'\n   confidence: 95"
        },
        {
          "sha": "968cfb9cf30cbffcae4a794529f3448f032bd164",
          "filename": "content/projects/fhdb-bootstrap-manager.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffhdb-bootstrap-manager.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffhdb-bootstrap-manager.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffhdb-bootstrap-manager.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   url: 'https://github.com/PunishedSnake/fhdb-bootstrap-manager/releases/tag/v0.5.0'\n activity:\n   lastChecked: '2026-09-29T00:27:01.624Z'\n-  lastSynchronized: '2026-10-21T23:19:49.656Z'\n+  lastSynchronized: '2026-10-24T23:15:06.155Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"fbb5d2427649274026a5a05b55494703261e78faeda773b5251ef78719aa4e86\"\n-    releasesEtag: W/\"2c9ad55685c236bee86facee292f753d9c512074231d2b224e0922fd06e593cf\"\n+    repoEtag: W/\"d59a8f6ee6768d7e515bb5f9d5ae048393126e765b1f812885f357330f1d1dc5\"\n+    releasesEtag: W/\"249a277d34fbf9069b1ecf6a8687d07072a3d3a526288757374762ff8d740d32\"\n discovery:\n   method: github-search\n   confidence: 100"
        },
        {
          "sha": "69c6747865634f3fda901e421f76c24ce61c4ef4",
          "filename": "content/projects/firmware-akuhak.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffirmware-akuhak.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffirmware-akuhak.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffirmware-akuhak.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -29,12 +29,12 @@ latestRelease:\n   publishedAt: '2025-03-03T21:03:56Z'\n   url: 'https://github.com/AKuHAK/firmware/releases/tag/nightly'\n activity:\n-  lastSynchronized: '2026-10-21T23:19:50.302Z'\n+  lastSynchronized: '2026-10-24T23:15:06.807Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"5cb6c7917d8742c9a9204fae5d3416bc223cd6f8a67299a41a9a2aad5745936f\"\n-    releasesEtag: W/\"6e51ce29e31696eaa80560ba4c4d96af7bee567aafd5afb96510a929cab98d4b\"\n+    repoEtag: W/\"6e9fb725166984cfc8b5b2b2eaa121702299f8072958cc6d1608c9f434e6a50a\"\n+    releasesEtag: W/\"dd121f2bd8818d0fb6e55e0200f4f34bc0f5a9c239d6b6dd971f6a4e7b5f4f3e\"\n discovery:\n   method: 'fork-network:sd2psXtd/firmware'\n   confidence: 95"
        },
        {
          "sha": "17982b051def5b8488e73f3ea81755d32138a267",
          "filename": "content/projects/firmware-israpps.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffirmware-israpps.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffirmware-israpps.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffirmware-israpps.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -29,12 +29,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-22T19:15:51.569Z'\n+  lastSynchronized: '2026-10-25T19:37:42.323Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"27e74e289e1682d6657e64fe1314f15cc907a081b2982e210ea00a718628b2db\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"332bb124dc39db6a321d1d95cb5395ab90bf3c1a99bf4c74112931cc52947f5a\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'fork-network:sd2psXtd/firmware'\n   confidence: 95"
        },
        {
          "sha": "cacbbe44f375fc308113eee23de5c128daf398c4",
          "filename": "content/projects/firmware-mathewbeall.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffirmware-mathewbeall.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffirmware-mathewbeall.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffirmware-mathewbeall.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -29,12 +29,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:40.384Z'\n+  lastSynchronized: '2026-10-25T22:33:46.980Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"2a9c7b94f6ddca62ec91f42b460474c264c1602b9c6df8bdb1327f7d8ff92de3\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"03586d97e430adff47a9664efc86a0cf1dd22266a756f7af92d8b75035b9ef5b\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:sd2psXtd/firmware'\n   confidence: 95"
        },
        {
          "sha": "147004369c3cea08e3cf34b58170fcb60f49db1d",
          "filename": "content/projects/first-mod-jak-kamo.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffirst-mod-jak-kamo.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffirst-mod-jak-kamo.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffirst-mod-jak-kamo.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -29,12 +29,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-21T23:19:50.786Z'\n+  lastSynchronized: '2026-10-24T23:15:07.296Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"12d43d3180956dc5e6d783108faf76c3a4362dabfdc0fb0830cd9e1465aba436\"\n-    releasesEtag: '\"669f535743067c0ac9111e98f8b3d7f826f4edc7838967d02f64b6f505af2660\"'\n+    repoEtag: W/\"3b4bf18479f9a3e6ae4b72c41cbe59fc6229fca164fb04f5820a18204521458c\"\n+    releasesEtag: '\"d958fbfec27a80deaeffdf70593bfd5e43f02f963f152d4a8d9f4917da92f5c0\"'\n discovery:\n   method: 'fork-network:open-goal/jak-project'\n   confidence: 95"
        },
        {
          "sha": "bfd26b0c5245861f2184459069309ae97c9da384",
          "filename": "content/projects/fnwf.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffnwf.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffnwf.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffnwf.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -30,12 +30,12 @@ latestRelease:\n   url: 'https://github.com/Renan2010p/fnwf/releases/tag/v2.3.0'\n activity:\n   lastChecked: '2026-09-29T00:27:45.068Z'\n-  lastSynchronized: '2026-10-20T06:00:47.079Z'\n+  lastSynchronized: '2026-10-25T06:10:55.718Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"160547acb333abb67ad571896af34187234017b69c1c1c46d4c4fdb56e27e556\"\n-    releasesEtag: W/\"5096f40276f782db0ed382cf1fd7045c1b4789fd4b43e25fb9d3dcb54c19638f\"\n+    repoEtag: W/\"463e967af191cd099d33adf95b290869ea1d610f19f2d333db20c6418378bd85\"\n+    releasesEtag: W/\"e0d8728af935cb1f8e80bcafa7ec30450b63e5352828f66b71091cb598bc5222\"\n discovery:\n   method: 'pending-promotion:github-search'\n   confidence: 75"
        },
        {
          "sha": "df47c70767133359e15d3a41b52d30e5f12f4b39",
          "filename": "content/projects/freedvdboot-5ht.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-5ht.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-5ht.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-5ht.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:40.856Z'\n+  lastSynchronized: '2026-10-25T22:33:47.428Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"f90a250e5010e8c0196ee3fdbbd3083baabf92050e53e03bf6680293852949a0\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"5f99edbe3ecaf3ce7d0637377d51a1154221ed751ad80a8830e1da4d613b55ef\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "b7f2a583a38a53f72258da6392615f9ac4dfa7ba",
          "filename": "content/projects/freedvdboot-aldobff.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-aldobff.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-aldobff.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-aldobff.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:41.306Z'\n+  lastSynchronized: '2026-10-25T22:33:47.898Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"d9ac8e4e6098d1a54327378f776069d39668fcf674c387f1146817758dc6e643\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"e3e89f15a5c681b50ed281ea122b3fdc1b53993e7f784a9e480d92e8d64eaed4\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "a0476e871aacf049377790b1f593eee0c27ea78d",
          "filename": "content/projects/freedvdboot-almamu.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-almamu.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-almamu.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-almamu.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T06:00:47.630Z'\n+  lastSynchronized: '2026-10-25T06:10:56.159Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"747cb2832ec66dc248307a4aae4c0e827e35e4b37802c703c9a8dac549668712\"\n-    releasesEtag: '\"beabca011d245159687fb6a685ede4790f750a2002b686c57e5bbc672bd5ded1\"'\n+    repoEtag: W/\"df7294de68200e9992589b05001dd64da998a2f15ebbc9ddc70b1afb600b484c\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "771f2d12753a6b84a33ca81f9f57485b3c90f888",
          "filename": "content/projects/freedvdboot-anderson20200.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-anderson20200.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-anderson20200.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-anderson20200.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-24T11:00:22.989Z'\n+  lastSynchronized: '2026-10-25T11:29:47.465Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"bb15112a90f3780fa1c519bea80dbf3085e0bea39e081399d9cbd80096b0bea5\"\n-    releasesEtag: '\"2d8126faf7d60a088ad7a14f528f5a8f3ca919b5cd485e0f97d8ebb20b5102f0\"'\n+    repoEtag: W/\"513185a362d294d33dd63b7ff09e337de23ca64162eeca810e40d196985c4435\"\n+    releasesEtag: '\"9e77b662133f4d4e3007923dbf3a0cbb2fd896f67c82d38c785dfb153804c449\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "566fb15e5125e49587ae6b1d201ba74dc6303a0c",
          "filename": "content/projects/freedvdboot-andriux26.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-andriux26.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-andriux26.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-andriux26.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-09T16:13:19.320Z'\n+  lastSynchronized: '2026-10-25T16:19:54.443Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"83bd0a808cba69920be1e20e9563ae454c07b442a08f40e15890b185fc8a5a92\"\n-    releasesEtag: '\"03eaebc487f57cb70aac1ff9827db51e9cbd437e4da42c0952991bf1af2062f6\"'\n+    repoEtag: W/\"39473b3badc9dc35e2f4ac1c0786050b63eb592d6dd79e4cab8a05956080b4c0\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "fa603c87301ec7ad28021ffc651dce2d3b2f7cde",
          "filename": "content/projects/freedvdboot-anntallex.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-anntallex.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-anntallex.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-anntallex.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:41.721Z'\n+  lastSynchronized: '2026-10-25T22:33:48.445Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"3dec443ef34715035f2ce22d0c4ba98b6f0d4b1a46235a696ec00f314238c6af\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"910ca136b67d38e755031d85a4ad9cf7730077c5897611e66c28d9b143ebc08b\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "234e5e494e6ce5d5ddbc7700c34cfc7a43245a71",
          "filename": "content/projects/freedvdboot-bigdrodo.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-bigdrodo.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-bigdrodo.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-bigdrodo.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T20:35:22.748Z'\n+  lastSynchronized: '2026-10-24T20:50:09.408Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"9ae7593524163f9d227b2b9cccd6fce0a114ebc3e6dd3c7bc252de5564f28cd4\"\n-    releasesEtag: '\"94e354af2c4e28e3962b296e8975d0c6ec1a30b51f9be8608fe23fd98f6c999a\"'\n+    repoEtag: W/\"85eb5e03612ffb1bb2ec2eb895176233a048afc8a98a55ff6bf24a8288de40f5\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "1292ea75fdf15691f9e63f55bbd100b957ea4fac",
          "filename": "content/projects/freedvdboot-bjorkor.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-bjorkor.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-bjorkor.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-bjorkor.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:42.127Z'\n+  lastSynchronized: '2026-10-25T22:33:48.974Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"94001847718126574fcc97e703915d4049d915dce54aa5d222c3b9badee1131f\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"187c1a4c01cbc393762681e319241f572afd1adc5c5bae33601007ae046ae0f8\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "7166d7ff3e6b210a7864591dfe6d06125a7785c0",
          "filename": "content/projects/freedvdboot-captainswag101.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-captainswag101.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-captainswag101.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-captainswag101.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-21T23:19:51.263Z'\n+  lastSynchronized: '2026-10-24T23:15:07.836Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"bdc7f243a518a63ff7292117bf6de492a1c9a289ec80d70f488fb2ad96fe0f28\"\n-    releasesEtag: '\"669f535743067c0ac9111e98f8b3d7f826f4edc7838967d02f64b6f505af2660\"'\n+    repoEtag: W/\"d6207f1b1dd77afe684be0a21fbc5719c99c6a588e236d9351aeff5afc86f386\"\n+    releasesEtag: '\"d958fbfec27a80deaeffdf70593bfd5e43f02f963f152d4a8d9f4917da92f5c0\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "43fd061299cd69ec2ac89a73be64f8e4f5975377",
          "filename": "content/projects/freedvdboot-cocochan55.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-cocochan55.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-cocochan55.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-cocochan55.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-24T11:00:23.493Z'\n+  lastSynchronized: '2026-10-25T11:29:47.860Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"9d667905bb4da1ec12ad77160614ac74686bf8db97be9aa63136901ce16a6a9e\"\n-    releasesEtag: '\"2d8126faf7d60a088ad7a14f528f5a8f3ca919b5cd485e0f97d8ebb20b5102f0\"'\n+    repoEtag: W/\"f224f60f5e1187c0fe15558004edffc1e87d197790220a2e8cd8c08a8db6a2b7\"\n+    releasesEtag: '\"9e77b662133f4d4e3007923dbf3a0cbb2fd896f67c82d38c785dfb153804c449\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "b3be07aaabf5a89b3708e58e93c30fa03fe58a95",
          "filename": "content/projects/freedvdboot-csekel.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-csekel.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-csekel.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-csekel.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-21T23:19:51.745Z'\n+  lastSynchronized: '2026-10-24T23:15:08.239Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"3c9892e1b33c52254517f672e8d67e12d3b90873c23d6e144d10a55066bc5e98\"\n-    releasesEtag: '\"669f535743067c0ac9111e98f8b3d7f826f4edc7838967d02f64b6f505af2660\"'\n+    repoEtag: W/\"7be87d20e58b861b1940b4cda53a70bcc0bd97ba2df8a439db0d3a8fb30c2a67\"\n+    releasesEtag: '\"d958fbfec27a80deaeffdf70593bfd5e43f02f963f152d4a8d9f4917da92f5c0\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "ed0e919f61504bdc9c269c83b893861a9df59cfe",
          "filename": "content/projects/freedvdboot-cturt.md",
          "status": "modified",
          "additions": 4,
          "deletions": 4,
          "changes": 8,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-cturt.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-cturt.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-cturt.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -17,7 +17,7 @@ source:\n repository:\n   archived: false\n   defaultBranch: master\n-  stars: 2541\n+  stars: 2543\n   forks: 159\n   lastCommit: '2022-02-02T08:15:09Z'\n latestRelease:\n@@ -26,12 +26,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:42.503Z'\n+  lastSynchronized: '2026-10-25T22:33:49.415Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"db843436849e42aabeb6e7b2121385f958d70f3722ac7881e43126b466756a6b\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"eee32b67ce2d3c52546ba8423fe07209c4ac0af60b65f3a6afc584de65653385\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'starred:NathanNeurotic'\n   confidence: 100"
        },
        {
          "sha": "4b16ca0a746382e6cb1762035ead71db9d4689a7",
          "filename": "content/projects/freedvdboot-cualquiercosa327.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-cualquiercosa327.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-cualquiercosa327.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-cualquiercosa327.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-24T11:00:23.999Z'\n+  lastSynchronized: '2026-10-25T11:29:48.297Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"48aa380de4eaa66d7b4eea928bc6703da9e5225c01bd967c2026fea02cb2fa51\"\n-    releasesEtag: '\"2d8126faf7d60a088ad7a14f528f5a8f3ca919b5cd485e0f97d8ebb20b5102f0\"'\n+    repoEtag: W/\"493b09ec53d233c45e3dbbe303a768d2b87fee82d0514b2ae97d5b721df35198\"\n+    releasesEtag: '\"9e77b662133f4d4e3007923dbf3a0cbb2fd896f67c82d38c785dfb153804c449\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "69aedc86f5786ab7814e04b15119ed99bb77c2dd",
          "filename": "content/projects/freedvdboot-cyanzys.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-cyanzys.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-cyanzys.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-cyanzys.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T01:31:51.165Z'\n+  lastSynchronized: '2026-10-25T01:14:14.398Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"ecc3499ac221db3d8f99460155bb6352458a5ffbc9a3569a970cc6794d55834f\"\n-    releasesEtag: '\"ff6090af9c91b61292326178ca0823b97aed61799df8cc0ca8f7a6c7d1215cb9\"'\n+    repoEtag: W/\"86a2630d190728cc4d2114edcb226f99109980a1179e7c9cc6971a51e68417e3\"\n+    releasesEtag: '\"a5c451af9d88eff769190129023bb74c9de83ceb2f12875d0ca286055e5e73e6\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "ec50242534a975bc91e5c2bbf77f77bcf726f13c",
          "filename": "content/projects/freedvdboot-daleang.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-daleang.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-daleang.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-daleang.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-09T16:13:19.841Z'\n+  lastSynchronized: '2026-10-25T16:19:54.839Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"3eaac28410832ac4aa8dda3811e2274e08198c888823ef852b7c522d2fb02a62\"\n-    releasesEtag: '\"03eaebc487f57cb70aac1ff9827db51e9cbd437e4da42c0952991bf1af2062f6\"'\n+    repoEtag: W/\"9761d3dc656429adcde3e39cadded95a4e13ae376be1bad893145b25e4102e67\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "8d20cb2788f7c62de78f63309332db0cd69fe50b",
          "filename": "content/projects/freedvdboot-darkguy2008.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-darkguy2008.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-darkguy2008.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-darkguy2008.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-09T16:13:20.299Z'\n+  lastSynchronized: '2026-10-25T16:19:55.292Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"fb685202ac3c9c8d2a032ed4bf10e299759d0eb27ae46ca69135fc063dea6b8e\"\n-    releasesEtag: '\"03eaebc487f57cb70aac1ff9827db51e9cbd437e4da42c0952991bf1af2062f6\"'\n+    repoEtag: W/\"47a1c0e95ae7b23f5ff414eac8c8daac4c4db1fc8e548796076aa2f42ce84954\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "168d4b50815bcba358260ee1a03a357f5bbee1b5",
          "filename": "content/projects/freedvdboot-den787.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-den787.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-den787.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-den787.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-24T11:00:24.569Z'\n+  lastSynchronized: '2026-10-25T11:29:48.726Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"0bc19d7de7598ea6a7634b51e18e54237c7017c1a6fade410eeed6d8531c9ea6\"\n-    releasesEtag: '\"2d8126faf7d60a088ad7a14f528f5a8f3ca919b5cd485e0f97d8ebb20b5102f0\"'\n+    repoEtag: W/\"f3835ffc35786be102471327ecf14f9d5a7379e43a9897fdfadd06013d27c1ed\"\n+    releasesEtag: '\"9e77b662133f4d4e3007923dbf3a0cbb2fd896f67c82d38c785dfb153804c449\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "b8fd168ff6d74e07e8ade26b2074f027da7eb8c2",
          "filename": "content/projects/freedvdboot-edenis-106.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-edenis-106.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-edenis-106.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-edenis-106.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-09T16:13:20.772Z'\n+  lastSynchronized: '2026-10-25T16:19:55.700Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"dbc5b55be7fd634a8701d5a4804b50126fd6185fae26f5d8785fba2ffaa5fbdd\"\n-    releasesEtag: '\"03eaebc487f57cb70aac1ff9827db51e9cbd437e4da42c0952991bf1af2062f6\"'\n+    repoEtag: W/\"e73986ef7078b5cdee465635729e55fbc4d7818451cf9ea1b3a3363681d35d36\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "d1314fa6c6ac7f4ff2e95152e58f1541743eadba",
          "filename": "content/projects/freedvdboot-eligrey.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-eligrey.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-eligrey.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-eligrey.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T06:00:48.140Z'\n+  lastSynchronized: '2026-10-25T06:10:56.607Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"b81886a462c252f1f1c94a2943a399fb2cffd3299862e48ae685cbd4e78dbc86\"\n-    releasesEtag: '\"beabca011d245159687fb6a685ede4790f750a2002b686c57e5bbc672bd5ded1\"'\n+    repoEtag: W/\"0c338e10a847ad1aa8b2f3c8459050127c9e6d070cde9c2f388ada531b211bfd\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "ace8be008efc007a88d02ab643b5076643eca99d",
          "filename": "content/projects/freedvdboot-ember8867.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-ember8867.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-ember8867.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-ember8867.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-09T16:13:21.333Z'\n+  lastSynchronized: '2026-10-25T16:19:56.117Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"b70887c7bf3937b93c3fb00247a926fbb5810c589d0db0db06b89f0d3da9e49a\"\n-    releasesEtag: '\"03eaebc487f57cb70aac1ff9827db51e9cbd437e4da42c0952991bf1af2062f6\"'\n+    repoEtag: W/\"c33345bac83f59fc6bcd0c80b4a30ba2f70183dbc9995c19d28f67fa1c26eeb3\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "f694d52ffece778039e1ab5fb67050da79d3255b",
          "filename": "content/projects/freedvdboot-fvogliotti.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-fvogliotti.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-fvogliotti.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-fvogliotti.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T01:31:51.698Z'\n+  lastSynchronized: '2026-10-25T01:14:14.867Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"e0e456df80111b3cd16925ad4cb594f3f008bd7f017c5fa2873d0c8d9d649013\"\n-    releasesEtag: '\"ff6090af9c91b61292326178ca0823b97aed61799df8cc0ca8f7a6c7d1215cb9\"'\n+    repoEtag: W/\"2de78b9da990629692dee885d33771c54b713002fec6355e133b58bd1bf1353c\"\n+    releasesEtag: '\"a5c451af9d88eff769190129023bb74c9de83ceb2f12875d0ca286055e5e73e6\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "2792750c91f10f3735b741c2830a9888a1b27691",
          "filename": "content/projects/freedvdboot-gatekeeper1122.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-gatekeeper1122.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-gatekeeper1122.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-gatekeeper1122.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T01:31:52.141Z'\n+  lastSynchronized: '2026-10-25T01:14:15.265Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"57177beed0bd3d0a1219f82bb9e29afffa14f18102f51c6b60af1370d6512779\"\n-    releasesEtag: '\"ff6090af9c91b61292326178ca0823b97aed61799df8cc0ca8f7a6c7d1215cb9\"'\n+    repoEtag: W/\"e5fb51632d510e30a213aadfda7996a12a782ec9114d48950efab91c31a2ee9a\"\n+    releasesEtag: '\"a5c451af9d88eff769190129023bb74c9de83ceb2f12875d0ca286055e5e73e6\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "62bf7dc1c8710973b8f575d6fee3a77b191df1df",
          "filename": "content/projects/freedvdboot-heradon.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-heradon.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-heradon.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-heradon.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-21T23:19:52.220Z'\n+  lastSynchronized: '2026-10-24T23:15:08.723Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"d499ca1b2fa027323a99ab873b2dee3170e92a3c66c563f4b295124b971678af\"\n-    releasesEtag: '\"669f535743067c0ac9111e98f8b3d7f826f4edc7838967d02f64b6f505af2660\"'\n+    repoEtag: W/\"69623c21523028850ca36c867e68954c44760202a8070ebe4aa0d77d1dad60b0\"\n+    releasesEtag: '\"d958fbfec27a80deaeffdf70593bfd5e43f02f963f152d4a8d9f4917da92f5c0\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "09fae052b60264972c2b962f3b21e85a06347f50",
          "filename": "content/projects/freedvdboot-iideethefirst.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-iideethefirst.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-iideethefirst.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-iideethefirst.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-22T19:15:52.160Z'\n+  lastSynchronized: '2026-10-25T19:37:42.855Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"08555114fd3f9af9d487ab7778a33abc378c5323b6e3e4547a1726ce2d040994\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"5119666a3d04e67fd0c12be17c9d6ede8e15d911fed1df8bab5ccd1e9cf7ec4c\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "90aeaff2eba75fa8fd667d4e0222192372ec3f54",
          "filename": "content/projects/freedvdboot-jakyote.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-jakyote.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-jakyote.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-jakyote.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:43.006Z'\n+  lastSynchronized: '2026-10-25T22:33:49.962Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"d4f19b2f990fd2be2bb934eef0545a098b294d6fd28c39a84911a4cf07619496\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"3d56cbd02a0942aacea0a9fa9d727ee11a3eab1bcc01b454e73e553f0018f588\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "e5fd14ee657be44987ead70924dc56c8864177c1",
          "filename": "content/projects/freedvdboot-jeramyowsley1989.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-jeramyowsley1989.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-jeramyowsley1989.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-jeramyowsley1989.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T01:31:52.639Z'\n+  lastSynchronized: '2026-10-25T01:14:15.762Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"9416965179fa108713187669c462017ea372e870f81d56b34324309917001d59\"\n-    releasesEtag: '\"ff6090af9c91b61292326178ca0823b97aed61799df8cc0ca8f7a6c7d1215cb9\"'\n+    repoEtag: W/\"26a7defe80a225cf4505db7cbf4c9234afb02b9440609f34c00587dc9b40846d\"\n+    releasesEtag: '\"a5c451af9d88eff769190129023bb74c9de83ceb2f12875d0ca286055e5e73e6\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "266062e4d080c56353ba958f368243ea087bf85a",
          "filename": "content/projects/freedvdboot-jmpfbmx.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-jmpfbmx.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-jmpfbmx.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-jmpfbmx.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:43.416Z'\n+  lastSynchronized: '2026-10-25T22:33:50.400Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"e1cc3e2c48c431099bf5a36c0a0dda5e97b5d396853d1fc0672e7c8db5aded0e\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"a5b7b2fbfb0e5570a81e57cdf7200b6010cb6518b38622e22abd0a87d15cac9a\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "077b3bef276fb36bd924cc7d1c6ce51ae8c4ef28",
          "filename": "content/projects/freedvdboot-jonpol01.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-jonpol01.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-jonpol01.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-jonpol01.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-24T11:00:25.052Z'\n+  lastSynchronized: '2026-10-25T11:29:49.169Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"0b1633d0d7d2055a439b3bfa8c6fd89d64e00b22adef2e2a29e7358fcfd116d5\"\n-    releasesEtag: '\"2d8126faf7d60a088ad7a14f528f5a8f3ca919b5cd485e0f97d8ebb20b5102f0\"'\n+    repoEtag: W/\"2568dc0a8f4030db7337e63b40f07e5e39a1c0b4be4217851f59e36327324e84\"\n+    releasesEtag: '\"9e77b662133f4d4e3007923dbf3a0cbb2fd896f67c82d38c785dfb153804c449\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "29b4cb5a209c059bd70728010f2d787ab9ba5dab",
          "filename": "content/projects/freedvdboot-killertamagotchi.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-killertamagotchi.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-killertamagotchi.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-killertamagotchi.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-09T16:13:21.808Z'\n+  lastSynchronized: '2026-10-25T16:19:56.632Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"3ce4ebf31b201fa60dc187a0c1446b09ddc2b33960b10838cb51f44bda4a0c0c\"\n-    releasesEtag: '\"03eaebc487f57cb70aac1ff9827db51e9cbd437e4da42c0952991bf1af2062f6\"'\n+    repoEtag: W/\"b7ab7836d81a850ad3de460b331371e3e3f4f7033fc0a1ef7014ceac04e4b48d\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "e32e07f0d8d41dda7dc37377e50c9cf64853f713",
          "filename": "content/projects/freedvdboot-l4yer.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-l4yer.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-l4yer.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-l4yer.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:43.806Z'\n+  lastSynchronized: '2026-10-25T22:33:50.861Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"98ca83f5c7cc72472332ccbf294f7575e3389174f8468c94cb4867328344cb8f\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"d54e5265b7a2f362ddfb8c76f95ca737b15a57d39bbe6dde7ae72b406ac9ebff\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "7725506949ebcc4feb7090cc8ef03703a2783bcd",
          "filename": "content/projects/freedvdboot-leofeitosa.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-leofeitosa.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-leofeitosa.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-leofeitosa.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-24T11:00:25.591Z'\n+  lastSynchronized: '2026-10-25T11:29:49.588Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"9671b803fe1c99b657e2cc2dff394dcfa164d2e52eff29cc12ab1c27fddb0624\"\n-    releasesEtag: '\"2d8126faf7d60a088ad7a14f528f5a8f3ca919b5cd485e0f97d8ebb20b5102f0\"'\n+    repoEtag: W/\"4bf3d63add11be962eb26ed436762f4ee7ba9982ab067ebcdad94809c13298ad\"\n+    releasesEtag: '\"9e77b662133f4d4e3007923dbf3a0cbb2fd896f67c82d38c785dfb153804c449\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "30e91cd20f4ab79285995de18132945d641ea823",
          "filename": "content/projects/freedvdboot-lightedday.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-lightedday.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-lightedday.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-lightedday.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-21T23:19:52.624Z'\n+  lastSynchronized: '2026-10-24T23:15:09.226Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"3d4ff83b4762fb0f300f9c3fd0ae31c9653b59cace4caac6899601eb78d9d067\"\n-    releasesEtag: '\"669f535743067c0ac9111e98f8b3d7f826f4edc7838967d02f64b6f505af2660\"'\n+    repoEtag: W/\"416986650144a4bf97fee95f2db1d63cc71a758e6dd94fd3a4eae24e560cad0f\"\n+    releasesEtag: '\"d958fbfec27a80deaeffdf70593bfd5e43f02f963f152d4a8d9f4917da92f5c0\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "640c2cf33bc1e53f3b045cc3f916ca53afa3e3a2",
          "filename": "content/projects/freedvdboot-limon93.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-limon93.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-limon93.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-limon93.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T20:35:23.300Z'\n+  lastSynchronized: '2026-10-24T20:50:10.039Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"8c0d2b9854f06a36bfe364a0b9f3ba789ec177c202239ab42e1880ade22c14f7\"\n-    releasesEtag: '\"94e354af2c4e28e3962b296e8975d0c6ec1a30b51f9be8608fe23fd98f6c999a\"'\n+    repoEtag: W/\"d6dd9396bbb94c0fad7f48bb447731ccf4081aca784877a3cde1ef1c720b9bc3\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "022e631f0f4fa3a618e5092fb8aa29f804c42afc",
          "filename": "content/projects/freedvdboot-littlebalup.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-littlebalup.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-littlebalup.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-littlebalup.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-21T23:19:53.085Z'\n+  lastSynchronized: '2026-10-24T23:15:09.638Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"ff5a7b72b29ad67ae195166801f473677715638e5945e907a9d3ad03246c5b27\"\n-    releasesEtag: '\"669f535743067c0ac9111e98f8b3d7f826f4edc7838967d02f64b6f505af2660\"'\n+    repoEtag: W/\"73143bc7f63d009fdf9645a8acb5c9f3c66535db4f813cde070f068985815253\"\n+    releasesEtag: '\"d958fbfec27a80deaeffdf70593bfd5e43f02f963f152d4a8d9f4917da92f5c0\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "2716e845b309944703db56ea1f902c69150b41e4",
          "filename": "content/projects/freedvdboot-littlefreak.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-littlefreak.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-littlefreak.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-littlefreak.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T20:35:23.835Z'\n+  lastSynchronized: '2026-10-24T20:50:10.573Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"73ddfa770820906f78bafafeacfb6bc8b61990d3ca443aa45c328d4bf98816b0\"\n-    releasesEtag: '\"94e354af2c4e28e3962b296e8975d0c6ec1a30b51f9be8608fe23fd98f6c999a\"'\n+    repoEtag: W/\"52cf8d16cd18d3cb9a1045fd65bc1d8f8b669b3df9c8e9a4e2251033eef088d0\"\n+    releasesEtag: '\"6fcd387081f9435b98bcf0721412c9c91e5080317856a568362bf5cafd0c899f\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "e3855f79bc545a6a79370058698c2dcf44c68746",
          "filename": "content/projects/freedvdboot-manolo19.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-manolo19.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-manolo19.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-manolo19.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:44.225Z'\n+  lastSynchronized: '2026-10-25T22:33:51.315Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"e90386089bb04710aa79a7591e0bcbcd22a62d8e5dcc0edd700c2b21cc332474\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"bdd9d55a620827d9af9c3dee5b424f69831c7dbac58ca0f161a78140a8be9c80\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "2d93ef7d834a7329579237f05ecd30e9d7c1e241",
          "filename": "content/projects/freedvdboot-mcvillan.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-mcvillan.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-mcvillan.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-mcvillan.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-21T23:19:53.537Z'\n+  lastSynchronized: '2026-10-24T23:15:10.152Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"7f42dde3b18311930e8410a2edb3944dae19bb0d7011ebdde7c9d5a12555471a\"\n-    releasesEtag: '\"669f535743067c0ac9111e98f8b3d7f826f4edc7838967d02f64b6f505af2660\"'\n+    repoEtag: W/\"898ba33f4c15e42a5b2dd5c738a79007838fe984e26732cff82e577a5c202c84\"\n+    releasesEtag: '\"d958fbfec27a80deaeffdf70593bfd5e43f02f963f152d4a8d9f4917da92f5c0\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "ae4382d32caf9dcea77e063d5ee4c8b309c04431",
          "filename": "content/projects/freedvdboot-mschul4.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-mschul4.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-mschul4.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-mschul4.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-09T16:13:22.333Z'\n+  lastSynchronized: '2026-10-25T16:19:57.030Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"42f07177d62bcd0786ee7464fa4196249f3bb600b236c8bfa0477973c411a8b3\"\n-    releasesEtag: '\"03eaebc487f57cb70aac1ff9827db51e9cbd437e4da42c0952991bf1af2062f6\"'\n+    repoEtag: W/\"df97f6dcada3ee7e85a6ed06464065a0aa29f1ba0d37d1c777477b28337b5a07\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "9321e2c7b445ac3198256f32a733514fad4e4fb6",
          "filename": "content/projects/freedvdboot-mxrachel.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-mxrachel.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-mxrachel.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-mxrachel.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T06:00:48.732Z'\n+  lastSynchronized: '2026-10-25T06:10:57.097Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"52a39f044d1dc2748441aff3c63076e5875cef038449cce77ef969eadd611c4e\"\n-    releasesEtag: '\"beabca011d245159687fb6a685ede4790f750a2002b686c57e5bbc672bd5ded1\"'\n+    repoEtag: W/\"6779c2f58308575eb0e93b6ec7cdf19f476c6b7e724ae8022f2b0f809712c1c0\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "53e0a5f1faf2b67e7f41cf1626e704f7da2156f9",
          "filename": "content/projects/freedvdboot-olekhy.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-olekhy.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-olekhy.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-olekhy.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-21T23:19:53.976Z'\n+  lastSynchronized: '2026-10-24T23:15:10.591Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"13ff952629430c7fac0b8ea274a584cc953a0d7cf7369c3bd85d4c2654ab6181\"\n-    releasesEtag: '\"669f535743067c0ac9111e98f8b3d7f826f4edc7838967d02f64b6f505af2660\"'\n+    repoEtag: W/\"64a1d5d8d39dab4b2b15878bbda02f83471953ca278c99822dd43b7128ee1929\"\n+    releasesEtag: '\"d958fbfec27a80deaeffdf70593bfd5e43f02f963f152d4a8d9f4917da92f5c0\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "736aed02c9ba553a382d6c9ad4003e1eb873620b",
          "filename": "content/projects/freedvdboot-prixoenvloime.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-prixoenvloime.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-prixoenvloime.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-prixoenvloime.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T01:31:53.088Z'\n+  lastSynchronized: '2026-10-25T01:14:16.181Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"b7b37dce75eb74bd8e2a70a88f975fcef27666387397904f0c96710994614126\"\n-    releasesEtag: '\"ff6090af9c91b61292326178ca0823b97aed61799df8cc0ca8f7a6c7d1215cb9\"'\n+    repoEtag: W/\"9be2f09a6220bffea4c2bbe001b03a70814a7cc1c490219b95636009339d1e40\"\n+    releasesEtag: '\"a5c451af9d88eff769190129023bb74c9de83ceb2f12875d0ca286055e5e73e6\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "0e920d5bccb744a7644f0a87b2c4dbed8d50f47a",
          "filename": "content/projects/freedvdboot-ps1337.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-ps1337.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-ps1337.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-ps1337.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-22T19:15:52.826Z'\n+  lastSynchronized: '2026-10-25T19:37:43.402Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"72f798b42bc39612e74b13d238e097e859a9589f99cafbe566b42408084ed1ab\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"c78764039a132ced45c4f0d6e7d62a8fcb38178282688f9ea191aae6952db6d9\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "19b4fed8638b21597fc2dd0399efdc6fe7f58356",
          "filename": "content/projects/freedvdboot-remailednet.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-remailednet.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-remailednet.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-remailednet.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-22T19:15:53.429Z'\n+  lastSynchronized: '2026-10-25T19:37:43.927Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"d82d45ca3d65c674f6f40be03a20149c444216fe9e27d956f278c064012bd87c\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"ff48899129ea471a8743e6395917bd1b5d924c7c7841d8de4bcd313c101650ee\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "383062c291e622600e624cd6f916562973717709",
          "filename": "content/projects/freedvdboot-resilixia.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-resilixia.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-resilixia.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-resilixia.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:44.695Z'\n+  lastSynchronized: '2026-10-25T22:33:51.780Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"5e4a0b449662ae4930606c4dcebe044f4ed1de28e1562df44f48d639328214b6\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"745822ea8b52417357df2e1770fd281bc1165c4b8fb573cb47c80d88593d3d93\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "efedf5a831da7aa69cc450d459da151cca36b66f",
          "filename": "content/projects/freedvdboot-sepppenner.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-sepppenner.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-sepppenner.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-sepppenner.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T01:31:53.598Z'\n+  lastSynchronized: '2026-10-25T01:14:16.634Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"75a94d5b79bf8105f4e0fbe5af48a331c763140ec431e765ff8e12c04f7f4130\"\n-    releasesEtag: '\"ff6090af9c91b61292326178ca0823b97aed61799df8cc0ca8f7a6c7d1215cb9\"'\n+    repoEtag: W/\"e2f70c5660c07fba5cda9bfcb0fb2fadc48aef4c5b4bee05d26cc085aa826a47\"\n+    releasesEtag: '\"a5c451af9d88eff769190129023bb74c9de83ceb2f12875d0ca286055e5e73e6\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "20febf2c3129170832bbf521a1d65c362089ecae",
          "filename": "content/projects/freedvdboot-simplyryan.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-simplyryan.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-simplyryan.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-simplyryan.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-22T19:15:54.081Z'\n+  lastSynchronized: '2026-10-25T19:37:44.409Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"8429eb84f919fcfb4adf3a60d1eb8cf95cd1497c67d81b0c63e65e7192b4aafb\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"bbaf2dea0c08d09e95157aaad6463364762b84b5ef7bde17567a7b7d7c7ea695\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "61528f45e3e7564e5b3e403a123b8f6963fe30b1",
          "filename": "content/projects/freedvdboot-soulcda.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-soulcda.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-soulcda.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-soulcda.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-09T16:13:22.775Z'\n+  lastSynchronized: '2026-10-25T16:19:57.479Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"d1901d0e49362ee95061d63961810fbe5160d9cd1ea162e9d694c13bba704c5b\"\n-    releasesEtag: '\"03eaebc487f57cb70aac1ff9827db51e9cbd437e4da42c0952991bf1af2062f6\"'\n+    repoEtag: W/\"fadf59e33981d0942a3fdcfa64160c3e44370880d003c18cf55c4f7ab63a5688\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "b3b43687997cfb574545cd124e587948927ebd4a",
          "filename": "content/projects/freedvdboot-stjordanis.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-stjordanis.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-stjordanis.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-stjordanis.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-22T19:15:54.749Z'\n+  lastSynchronized: '2026-10-25T19:37:44.949Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"f17e5000de463193b003751d8933674d8e97cfffbfe52d02567b08e5fecac020\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"ff89064f4cb5f8c6862615478b7aa66780c79af86e02317ee18c098df61b6252\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "d568e2a9de5cdaf7d14360952dab9028af915837",
          "filename": "content/projects/freedvdboot-supernome4567.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-supernome4567.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-supernome4567.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-supernome4567.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-22T19:15:55.344Z'\n+  lastSynchronized: '2026-10-25T19:37:45.443Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"8002145ffe2b013b37507d06bf1c5a083b5e8a6e6fa82f5f51841052671747ae\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"2783a4c43f09979e4e6d59dfeb9b2fb9f55005fdca10f887dca65688e5935c16\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "38475fbbc951866e0e3121f9f415f4060159f8e4",
          "filename": "content/projects/freedvdboot-superpowerp.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-superpowerp.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-superpowerp.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-superpowerp.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:45.113Z'\n+  lastSynchronized: '2026-10-25T22:33:52.260Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"f9c7158ac54a0faac16876f833dd5fc006be5d01a18e4ed63e0d4c73f0a1d82f\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"b495e12979e50c5ac4836a5ffda6bf04a2c93bd014916e2d276cd56da5cfbe07\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "560a038af714bba4749d79cc4e37559a921a5f90",
          "filename": "content/projects/freedvdboot-vigneshok10.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-vigneshok10.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-vigneshok10.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-vigneshok10.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-09T16:13:23.294Z'\n+  lastSynchronized: '2026-10-25T16:19:57.891Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"887bb37f9bf5baf3312c3db0dd4f352c2a39c10c3a2e70d15e36c59a165b6ccb\"\n-    releasesEtag: '\"03eaebc487f57cb70aac1ff9827db51e9cbd437e4da42c0952991bf1af2062f6\"'\n+    repoEtag: W/\"d786da634ba01f10afc2943cfc47333325e1e08cee4692b98b3ae6dac6ca29c7\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "1ae0647cc4283b0b2fd82a6dc3badfad258692e5",
          "filename": "content/projects/freedvdboot-w316525096.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-w316525096.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-w316525096.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-w316525096.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -29,12 +29,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:45.515Z'\n+  lastSynchronized: '2026-10-25T22:33:52.733Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"beae57398af2d49e943ed317b279a5870b33f9c86ba2a00c54e7561fe048c6d0\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"2ccf3ff35011152046d84b99e1936d5b8d374d586b32b19488d1b3f1675fd2fb\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "364fd803770714d6d75fefee9156566a11a4e3c6",
          "filename": "content/projects/freedvdboot-wildfirebill-sony-ps2.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-wildfirebill-sony-ps2.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-wildfirebill-sony-ps2.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-wildfirebill-sony-ps2.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T06:00:49.328Z'\n+  lastSynchronized: '2026-10-25T06:10:57.661Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"841d181ba17f0d78179762051e8b95c8d45dc23cb8b37dc91d0a85251ab3ab9f\"\n-    releasesEtag: '\"beabca011d245159687fb6a685ede4790f750a2002b686c57e5bbc672bd5ded1\"'\n+    repoEtag: W/\"c2f89e9e2496419119125d8f77121a3d4ff798b667cc4726d5140242f7bdc6ce\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "899886122f8b6e0f3a017deea178e58aae432fcf",
          "filename": "content/projects/freedvdboot-yoshimod-aleelyoshi.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-yoshimod-aleelyoshi.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-yoshimod-aleelyoshi.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-yoshimod-aleelyoshi.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-22T19:15:55.996Z'\n+  lastSynchronized: '2026-10-25T19:37:45.936Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"856b7032662f91f053acf86f4874826f4c4af3fe7389dfbb07f5d2719a70a92b\"\n-    releasesEtag: '\"b33679cef99543938486d165e96451f4443539d11787e6ae7f8cfd42561bd983\"'\n+    repoEtag: W/\"f6458b61adf4443bacef014277b1bbe10eddfa228a7bc9f27c37c193c33bd33d\"\n+    releasesEtag: '\"17828022ef91e6b899139ea659c43195f157e5fe9fda163fe4cfd658085b1033\"'\n discovery:\n   method: 'incremental:topic:ps2'\n   confidence: 100"
        },
        {
          "sha": "eecb709ae943c014ae6fd7a75a6a1542452b734f",
          "filename": "content/projects/freedvdboot-yourkalamity.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-yourkalamity.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-yourkalamity.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-yourkalamity.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-09T16:13:23.728Z'\n+  lastSynchronized: '2026-10-25T16:19:58.363Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"a2814099cedbcdb8880a35ab5bd488009b7ee9ffca6a8f224df29f997d4ab5d7\"\n-    releasesEtag: '\"03eaebc487f57cb70aac1ff9827db51e9cbd437e4da42c0952991bf1af2062f6\"'\n+    repoEtag: W/\"f986782ce35df66c4ff7dff00cf88257850c5ae2eb17493dec92021e3f5adcd0\"\n+    releasesEtag: '\"67519807a1434cc860a2f0d388408dbaadf9b95ce5d4b0732f16fb46ac9515f3\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "5305cec8b30969013c8e46283ca2f85f34db493a",
          "filename": "content/projects/freedvdboot-z1xto.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-z1xto.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-z1xto.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-z1xto.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-20T06:00:49.879Z'\n+  lastSynchronized: '2026-10-25T06:10:58.137Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"6b09cd3324b533a0f06af7191e7f2dac02edd30a5805b26f6f468839b1d79461\"\n-    releasesEtag: '\"beabca011d245159687fb6a685ede4790f750a2002b686c57e5bbc672bd5ded1\"'\n+    repoEtag: W/\"bec9c1f440e250bdbaddcfb01db01034f1cca725563629a0a6e19cf91c10ce93\"\n+    releasesEtag: '\"654d5e3e1a5a9ef9e796502c1441ef105af6f549038446c1b045c75e80cd1ac0\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "91855ffb9392202a00a8c807ed9eb567f9d314a3",
          "filename": "content/projects/freedvdboot-zer0nka.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-zer0nka.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreedvdboot-zer0nka.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreedvdboot-zer0nka.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T01:31:54.047Z'\n+  lastSynchronized: '2026-10-25T01:14:17.057Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"d4fc9942bbd7da1ac95d039750fd1eecf8da87c781444354a88ddafb8b40537c\"\n-    releasesEtag: '\"ff6090af9c91b61292326178ca0823b97aed61799df8cc0ca8f7a6c7d1215cb9\"'\n+    repoEtag: W/\"2b5896235b1ae68c0f70d0e2d86954a0105fce2af8d3f26b5dbb37cd5780b6f1\"\n+    releasesEtag: '\"a5c451af9d88eff769190129023bb74c9de83ceb2f12875d0ca286055e5e73e6\"'\n discovery:\n   method: 'fork-network:CTurt/FreeDVDBoot'\n   confidence: 100"
        },
        {
          "sha": "cd0b23b1bbe593e1deeb916542d8f95a4e63cc12",
          "filename": "content/projects/freemastercodefinder.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreemastercodefinder.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreemastercodefinder.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreemastercodefinder.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: '2023-08-01T16:01:43Z'\n   url: 'https://github.com/israpps/FreeMastercodeFinder/releases/tag/latest'\n activity:\n-  lastSynchronized: '2026-10-20T20:35:24.368Z'\n+  lastSynchronized: '2026-10-24T20:50:11.044Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"f7a929b0d0944a126521db709ae3fb3e47a45568b451c776314755a1753399cc\"\n-    releasesEtag: W/\"4b1b0eaa505c478c20ffb4e2b620f0c7ee080ffa6a5309933d0d47e744fa627c\"\n+    repoEtag: W/\"d8cc8be950d104cef588b7f715aaf84440f3fe3bfd8f266cf596810e25ff976c\"\n+    releasesEtag: W/\"7c5aded6e107d455083e3417bcf040488fc059a196edfb322e2a717c0ea910d4\"\n discovery:\n   method: 'starred-curated:NathanNeurotic'\n   confidence: 100"
        },
        {
          "sha": "117a917e915da8caeec2eeead97008a30b0df1dd",
          "filename": "content/projects/freemcboot-1-8-mod-clutchthrower.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreemcboot-1-8-mod-clutchthrower.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreemcboot-1-8-mod-clutchthrower.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreemcboot-1-8-mod-clutchthrower.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:46.005Z'\n+  lastSynchronized: '2026-10-25T22:33:53.224Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"0e82647e075ae4ca5bcd8f2aaed5aa5eca26dd95b15d57ec6f1d96fee94290a8\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"e69dcd3a68c1592ab7c3fb40b7d5537ef76cd6638919bb33fd0adebebfbbb540\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:projectZEUS01010101/FreeMcBoot_1.8_MOD'\n   confidence: 95"
        },
        {
          "sha": "2c5a260ec416352f023f1a2c7836536cb14bdba1",
          "filename": "content/projects/freemcboot-1-8-mod-projectzeus01010101.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreemcboot-1-8-mod-projectzeus01010101.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreemcboot-1-8-mod-projectzeus01010101.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreemcboot-1-8-mod-projectzeus01010101.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T01:31:54.588Z'\n+  lastSynchronized: '2026-10-25T01:14:17.526Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"52fd80c5f2c6d0ef0b39c820d71dfb09642b416609c2fa3ddfed642a020e85cb\"\n-    releasesEtag: '\"ff6090af9c91b61292326178ca0823b97aed61799df8cc0ca8f7a6c7d1215cb9\"'\n+    repoEtag: W/\"b7a827ef529104f09e105eba556677db2457cc53c9db97d300530fdc3e76d80e\"\n+    releasesEtag: '\"a5c451af9d88eff769190129023bb74c9de83ceb2f12875d0ca286055e5e73e6\"'\n discovery:\n   method: 'fork-network:Jay-Jay-OPL/FreeMcBoot'\n   confidence: 95"
        },
        {
          "sha": "23fd443e6e33eba928e8e77aef4c5024d9d2da06",
          "filename": "content/projects/freemcboot-362053534.md",
          "status": "modified",
          "additions": 3,
          "deletions": 3,
          "changes": 6,
          "blob_url": "https://github.com/johnson-cooper/PS2SP/blob/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreemcboot-362053534.md",
          "raw_url": "https://github.com/johnson-cooper/PS2SP/raw/cb040e79dfd467aa99252575c9d6fd97684116de/content%2Fprojects%2Ffreemcboot-362053534.md",
          "contents_url": "https://api.github.com/repos/johnson-cooper/PS2SP/contents/content%2Fprojects%2Ffreemcboot-362053534.md?ref=cb040e79dfd467aa99252575c9d6fd97684116de",
          "patch": "@@ -27,12 +27,12 @@ latestRelease:\n   publishedAt: null\n   url: null\n activity:\n-  lastSynchronized: '2026-10-23T22:11:46.479Z'\n+  lastSynchronized: '2026-10-25T22:33:53.733Z'\n automation:\n   sync: true\n   github:\n-    repoEtag: W/\"f35126ce0d801218f9023ff116cf19b12d9db9dc7b5765ba3c22aea1bd11f63b\"\n-    releasesEtag: '\"83e88907bcdf1a6e2bb4f4c45d64e426e2fcbb7e5cb331f2e184283ffd59869e\"'\n+    repoEtag: W/\"daff09edc014ae70c60400b1f715233527a34d4d2131b1aa1639e43eedd25cfd\"\n+    releasesEtag: '\"92252d522264dc4bde860f93a68ea45377a106a99241a7a274c2b7bc57034182\"'\n discovery:\n   method: 'fork-network:TnA-Plastic/FreeMcBoot'\n   confidence: 95"
        }
      ]
    }
  },
  {
    "repo": "GeekRhapsody/OdysseyLauncher",
    "url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/compare/f935f07f31d58f6767a03e31d3e27598654cbd91...bc1b85931871a65b6948764d00cd5182e2e873b8",
    "comparison": {
      "url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/compare/f935f07f31d58f6767a03e31d3e27598654cbd91...bc1b85931871a65b6948764d00cd5182e2e873b8",
      "html_url": "https://github.com/GeekRhapsody/OdysseyLauncher/compare/f935f07f31d58f6767a03e31d3e27598654cbd91...bc1b85931871a65b6948764d00cd5182e2e873b8",
      "permalink_url": "https://github.com/GeekRhapsody/OdysseyLauncher/compare/GeekRhapsody:f935f07...GeekRhapsody:bc1b859",
      "diff_url": "https://github.com/GeekRhapsody/OdysseyLauncher/compare/f935f07f31d58f6767a03e31d3e27598654cbd91...bc1b85931871a65b6948764d00cd5182e2e873b8.diff",
      "patch_url": "https://github.com/GeekRhapsody/OdysseyLauncher/compare/f935f07f31d58f6767a03e31d3e27598654cbd91...bc1b85931871a65b6948764d00cd5182e2e873b8.patch",
      "base_commit": {
        "sha": "f935f07f31d58f6767a03e31d3e27598654cbd91",
        "node_id": "C_kwDOUv3zItoAKGY5MzVmMDdmMzFkNThmNjc2N2EwM2UzMWQzZTI3NTk4NjU0Y2JkOTE",
        "commit": {
          "author": {
            "name": "Claudio Pereira",
            "email": "claudio.mpereira@gmail.com",
            "date": "2026-10-20T13:42:30Z"
          },
          "committer": {
            "name": "Claudio Pereira",
            "email": "claudio.mpereira@gmail.com",
            "date": "2026-10-20T13:42:30Z"
          },
          "message": "App icon",
          "tree": {
            "sha": "76abc8e0e435d9508fd287eeedbc2ddb0fb15070",
            "url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/git/trees/76abc8e0e435d9508fd287eeedbc2ddb0fb15070"
          },
          "url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/git/commits/f935f07f31d58f6767a03e31d3e27598654cbd91",
          "comment_count": 0,
          "verification": {
            "verified": false,
            "reason": "unsigned",
            "signature": null,
            "payload": null,
            "verified_at": null
          }
        },
        "url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/commits/f935f07f31d58f6767a03e31d3e27598654cbd91",
        "html_url": "https://github.com/GeekRhapsody/OdysseyLauncher/commit/f935f07f31d58f6767a03e31d3e27598654cbd91",
        "comments_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/commits/f935f07f31d58f6767a03e31d3e27598654cbd91/comments",
        "author": {
          "login": "GeekRhapsody",
          "id": 82876170,
          "node_id": "MDQ6VXNlcjgyODc2MTcw",
          "avatar_url": "https://avatars.githubusercontent.com/u/82876170?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/GeekRhapsody",
          "html_url": "https://github.com/GeekRhapsody",
          "followers_url": "https://api.github.com/users/GeekRhapsody/followers",
          "following_url": "https://api.github.com/users/GeekRhapsody/following{/other_user}",
          "gists_url": "https://api.github.com/users/GeekRhapsody/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/GeekRhapsody/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/GeekRhapsody/subscriptions",
          "organizations_url": "https://api.github.com/users/GeekRhapsody/orgs",
          "repos_url": "https://api.github.com/users/GeekRhapsody/repos",
          "events_url": "https://api.github.com/users/GeekRhapsody/events{/privacy}",
          "received_events_url": "https://api.github.com/users/GeekRhapsody/received_events",
          "type": "User",
          "user_view_type": "public",
          "site_admin": false
        },
        "committer": {
          "login": "GeekRhapsody",
          "id": 82876170,
          "node_id": "MDQ6VXNlcjgyODc2MTcw",
          "avatar_url": "https://avatars.githubusercontent.com/u/82876170?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/GeekRhapsody",
          "html_url": "https://github.com/GeekRhapsody",
          "followers_url": "https://api.github.com/users/GeekRhapsody/followers",
          "following_url": "https://api.github.com/users/GeekRhapsody/following{/other_user}",
          "gists_url": "https://api.github.com/users/GeekRhapsody/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/GeekRhapsody/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/GeekRhapsody/subscriptions",
          "organizations_url": "https://api.github.com/users/GeekRhapsody/orgs",
          "repos_url": "https://api.github.com/users/GeekRhapsody/repos",
          "events_url": "https://api.github.com/users/GeekRhapsody/events{/privacy}",
          "received_events_url": "https://api.github.com/users/GeekRhapsody/received_events",
          "type": "User",
          "user_view_type": "public",
          "site_admin": false
        },
        "parents": [
          {
            "sha": "b45e3c4193e24111617eb28b1c345de2690978b9",
            "url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/commits/b45e3c4193e24111617eb28b1c345de2690978b9",
            "html_url": "https://github.com/GeekRhapsody/OdysseyLauncher/commit/b45e3c4193e24111617eb28b1c345de2690978b9"
          }
        ]
      },
      "merge_base_commit": {
        "sha": "f935f07f31d58f6767a03e31d3e27598654cbd91",
        "node_id": "C_kwDOUv3zItoAKGY5MzVmMDdmMzFkNThmNjc2N2EwM2UzMWQzZTI3NTk4NjU0Y2JkOTE",
        "commit": {
          "author": {
            "name": "Claudio Pereira",
            "email": "claudio.mpereira@gmail.com",
            "date": "2026-10-20T13:42:30Z"
          },
          "committer": {
            "name": "Claudio Pereira",
            "email": "claudio.mpereira@gmail.com",
            "date": "2026-10-20T13:42:30Z"
          },
          "message": "App icon",
          "tree": {
            "sha": "76abc8e0e435d9508fd287eeedbc2ddb0fb15070",
            "url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/git/trees/76abc8e0e435d9508fd287eeedbc2ddb0fb15070"
          },
          "url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/git/commits/f935f07f31d58f6767a03e31d3e27598654cbd91",
          "comment_count": 0,
          "verification": {
            "verified": false,
            "reason": "unsigned",
            "signature": null,
            "payload": null,
            "verified_at": null
          }
        },
        "url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/commits/f935f07f31d58f6767a03e31d3e27598654cbd91",
        "html_url": "https://github.com/GeekRhapsody/OdysseyLauncher/commit/f935f07f31d58f6767a03e31d3e27598654cbd91",
        "comments_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/commits/f935f07f31d58f6767a03e31d3e27598654cbd91/comments",
        "author": {
          "login": "GeekRhapsody",
          "id": 82876170,
          "node_id": "MDQ6VXNlcjgyODc2MTcw",
          "avatar_url": "https://avatars.githubusercontent.com/u/82876170?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/GeekRhapsody",
          "html_url": "https://github.com/GeekRhapsody",
          "followers_url": "https://api.github.com/users/GeekRhapsody/followers",
          "following_url": "https://api.github.com/users/GeekRhapsody/following{/other_user}",
          "gists_url": "https://api.github.com/users/GeekRhapsody/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/GeekRhapsody/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/GeekRhapsody/subscriptions",
          "organizations_url": "https://api.github.com/users/GeekRhapsody/orgs",
          "repos_url": "https://api.github.com/users/GeekRhapsody/repos",
          "events_url": "https://api.github.com/users/GeekRhapsody/events{/privacy}",
          "received_events_url": "https://api.github.com/users/GeekRhapsody/received_events",
          "type": "User",
          "user_view_type": "public",
          "site_admin": false
        },
        "committer": {
          "login": "GeekRhapsody",
          "id": 82876170,
          "node_id": "MDQ6VXNlcjgyODc2MTcw",
          "avatar_url": "https://avatars.githubusercontent.com/u/82876170?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/GeekRhapsody",
          "html_url": "https://github.com/GeekRhapsody",
          "followers_url": "https://api.github.com/users/GeekRhapsody/followers",
          "following_url": "https://api.github.com/users/GeekRhapsody/following{/other_user}",
          "gists_url": "https://api.github.com/users/GeekRhapsody/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/GeekRhapsody/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/GeekRhapsody/subscriptions",
          "organizations_url": "https://api.github.com/users/GeekRhapsody/orgs",
          "repos_url": "https://api.github.com/users/GeekRhapsody/repos",
          "events_url": "https://api.github.com/users/GeekRhapsody/events{/privacy}",
          "received_events_url": "https://api.github.com/users/GeekRhapsody/received_events",
          "type": "User",
          "user_view_type": "public",
          "site_admin": false
        },
        "parents": [
          {
            "sha": "b45e3c4193e24111617eb28b1c345de2690978b9",
            "url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/commits/b45e3c4193e24111617eb28b1c345de2690978b9",
            "html_url": "https://github.com/GeekRhapsody/OdysseyLauncher/commit/b45e3c4193e24111617eb28b1c345de2690978b9"
          }
        ]
      },
      "status": "ahead",
      "ahead_by": 2,
      "behind_by": 0,
      "total_commits": 2,
      "commits": [
        {
          "sha": "d5908652a70f7f5f286454ad611f64e886a65ebf",
          "node_id": "C_kwDOUv3zItoAKGQ1OTA4NjUyYTcwZjdmNWYyODY0NTRhZDYxMWY2NGU4ODZhNjVlYmY",
          "commit": {
            "author": {
              "name": "Claudio Pereira",
              "email": "claudio.mpereira@gmail.com",
              "date": "2026-10-25T12:33:01Z"
            },
            "committer": {
              "name": "Claudio Pereira",
              "email": "claudio.mpereira@gmail.com",
              "date": "2026-10-25T12:33:01Z"
            },
            "message": "Console theme: models for 72 more systems, and four rebuilt\n\nEach system's console as its card, built by Blender scripts in\ntools/console-models/ (a layout, a Pillow print and the model per system),\nmeasured from photographs and published dimensions, turned to a three-quarter\nview, with an 8 s focused clip that plays a made-up game, Odyssey.\n\nNew: Amiga, ZX Spectrum, PlayStation, Super Nintendo, PlayStation 2, ZX81,\nNintendo DS, Nintendo 3DS, Satellaview, Wii, Atari 2600, MSX1, Apple II,\nMaster System, PICO-8, Atari ST, Nintendo 64, GameCube, 3DO, Famicom Disk\nSystem, PC Engine, VIC-20, Neo Geo, ColecoVision, Flash, Mega-CD, Amiga CD32,\nNAOMI, Apple IIGS, MSX2, CD-i, Wii U, Odyssey 2, Atari 5200, Atari Lynx,\nNeo Geo CD, PC Engine CD, Neo Geo Pocket Color, Neo Geo Pocket,\nPlayStation 3, Atari 7800, Watara Supervision, Atari Jaguar, Atari Jaguar CD,\nPC-98, Xbox, Game & Watch, 32X, Vectrex, Pok\u00e9mon mini, Virtual Boy,\nGX4000, PlayStation 4, Xbox 360, PC-FX, Desktop Apps, PlayStation 5,\nIntellivision, 64DD, Super Game Boy, BBC Micro, Acorn Electron, Atari 800,\nAtari XE Game System, TI-99/4A, Channel F, Macintosh, CDTV, Plus/4,\nArchimedes, Dragon 32 and Oric.\n\nRebuilt in the same style: Saturn, Dreamcast, NES (its door flips up and\nout) and Mega Drive (with its pad, and an Odyssey label in place of a real\ngame's; the 32X's and Mega-CD's cards are unchanged).\n\nShared: plugs.py (each plugged-in cable ends in its real plug), controllers'\nsides rounded outwards (the inset helpers' winding), one UV set per model.\nCLAUDE.md, the decisions log and theme.toml updated to match.\n\nSquashed from 86 commits on console-models.\n\nGenerated with Claude Code under company AI usage policy",
            "tree": {
              "sha": "60e7cefee568218aaa12a15e960673ed9c246f44",
              "url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/git/trees/60e7cefee568218aaa12a15e960673ed9c246f44"
            },
            "url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/git/commits/d5908652a70f7f5f286454ad611f64e886a65ebf",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/commits/d5908652a70f7f5f286454ad611f64e886a65ebf",
          "html_url": "https://github.com/GeekRhapsody/OdysseyLauncher/commit/d5908652a70f7f5f286454ad611f64e886a65ebf",
          "comments_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/commits/d5908652a70f7f5f286454ad611f64e886a65ebf/comments",
          "author": {
            "login": "GeekRhapsody",
            "id": 82876170,
            "node_id": "MDQ6VXNlcjgyODc2MTcw",
            "avatar_url": "https://avatars.githubusercontent.com/u/82876170?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/GeekRhapsody",
            "html_url": "https://github.com/GeekRhapsody",
            "followers_url": "https://api.github.com/users/GeekRhapsody/followers",
            "following_url": "https://api.github.com/users/GeekRhapsody/following{/other_user}",
            "gists_url": "https://api.github.com/users/GeekRhapsody/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/GeekRhapsody/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/GeekRhapsody/subscriptions",
            "organizations_url": "https://api.github.com/users/GeekRhapsody/orgs",
            "repos_url": "https://api.github.com/users/GeekRhapsody/repos",
            "events_url": "https://api.github.com/users/GeekRhapsody/events{/privacy}",
            "received_events_url": "https://api.github.com/users/GeekRhapsody/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "GeekRhapsody",
            "id": 82876170,
            "node_id": "MDQ6VXNlcjgyODc2MTcw",
            "avatar_url": "https://avatars.githubusercontent.com/u/82876170?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/GeekRhapsody",
            "html_url": "https://github.com/GeekRhapsody",
            "followers_url": "https://api.github.com/users/GeekRhapsody/followers",
            "following_url": "https://api.github.com/users/GeekRhapsody/following{/other_user}",
            "gists_url": "https://api.github.com/users/GeekRhapsody/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/GeekRhapsody/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/GeekRhapsody/subscriptions",
            "organizations_url": "https://api.github.com/users/GeekRhapsody/orgs",
            "repos_url": "https://api.github.com/users/GeekRhapsody/repos",
            "events_url": "https://api.github.com/users/GeekRhapsody/events{/privacy}",
            "received_events_url": "https://api.github.com/users/GeekRhapsody/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "f935f07f31d58f6767a03e31d3e27598654cbd91",
              "url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/commits/f935f07f31d58f6767a03e31d3e27598654cbd91",
              "html_url": "https://github.com/GeekRhapsody/OdysseyLauncher/commit/f935f07f31d58f6767a03e31d3e27598654cbd91"
            }
          ]
        },
        {
          "sha": "bc1b85931871a65b6948764d00cd5182e2e873b8",
          "node_id": "C_kwDOUv3zItoAKGJjMWI4NTkzMTg3MWE2NWI2OTQ4NzY0ZDAwY2Q1MTgyZTJlODczYjg",
          "commit": {
            "author": {
              "name": "Claudio Pereira",
              "email": "claudio.mpereira@gmail.com",
              "date": "2026-10-25T12:35:06Z"
            },
            "committer": {
              "name": "Claudio Pereira",
              "email": "claudio.mpereira@gmail.com",
              "date": "2026-10-25T12:35:06Z"
            },
            "message": "Version 0.19.0",
            "tree": {
              "sha": "a0e32990c27c5d07912ca494b3dbb55ff9b438a7",
              "url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/git/trees/a0e32990c27c5d07912ca494b3dbb55ff9b438a7"
            },
            "url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/git/commits/bc1b85931871a65b6948764d00cd5182e2e873b8",
            "comment_count": 0,
            "verification": {
              "verified": false,
              "reason": "unsigned",
              "signature": null,
              "payload": null,
              "verified_at": null
            }
          },
          "url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/commits/bc1b85931871a65b6948764d00cd5182e2e873b8",
          "html_url": "https://github.com/GeekRhapsody/OdysseyLauncher/commit/bc1b85931871a65b6948764d00cd5182e2e873b8",
          "comments_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/commits/bc1b85931871a65b6948764d00cd5182e2e873b8/comments",
          "author": {
            "login": "GeekRhapsody",
            "id": 82876170,
            "node_id": "MDQ6VXNlcjgyODc2MTcw",
            "avatar_url": "https://avatars.githubusercontent.com/u/82876170?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/GeekRhapsody",
            "html_url": "https://github.com/GeekRhapsody",
            "followers_url": "https://api.github.com/users/GeekRhapsody/followers",
            "following_url": "https://api.github.com/users/GeekRhapsody/following{/other_user}",
            "gists_url": "https://api.github.com/users/GeekRhapsody/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/GeekRhapsody/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/GeekRhapsody/subscriptions",
            "organizations_url": "https://api.github.com/users/GeekRhapsody/orgs",
            "repos_url": "https://api.github.com/users/GeekRhapsody/repos",
            "events_url": "https://api.github.com/users/GeekRhapsody/events{/privacy}",
            "received_events_url": "https://api.github.com/users/GeekRhapsody/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "committer": {
            "login": "GeekRhapsody",
            "id": 82876170,
            "node_id": "MDQ6VXNlcjgyODc2MTcw",
            "avatar_url": "https://avatars.githubusercontent.com/u/82876170?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/GeekRhapsody",
            "html_url": "https://github.com/GeekRhapsody",
            "followers_url": "https://api.github.com/users/GeekRhapsody/followers",
            "following_url": "https://api.github.com/users/GeekRhapsody/following{/other_user}",
            "gists_url": "https://api.github.com/users/GeekRhapsody/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/GeekRhapsody/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/GeekRhapsody/subscriptions",
            "organizations_url": "https://api.github.com/users/GeekRhapsody/orgs",
            "repos_url": "https://api.github.com/users/GeekRhapsody/repos",
            "events_url": "https://api.github.com/users/GeekRhapsody/events{/privacy}",
            "received_events_url": "https://api.github.com/users/GeekRhapsody/received_events",
            "type": "User",
            "user_view_type": "public",
            "site_admin": false
          },
          "parents": [
            {
              "sha": "d5908652a70f7f5f286454ad611f64e886a65ebf",
              "url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/commits/d5908652a70f7f5f286454ad611f64e886a65ebf",
              "html_url": "https://github.com/GeekRhapsody/OdysseyLauncher/commit/d5908652a70f7f5f286454ad611f64e886a65ebf"
            }
          ]
        }
      ],
      "files": [
        {
          "sha": "bcfc0af65b6539ec0ff6f3d60b3dafe2558c9cd0",
          "filename": "CLAUDE.md",
          "status": "modified",
          "additions": 2,
          "deletions": 2,
          "changes": 4,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/CLAUDE.md",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/CLAUDE.md",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/CLAUDE.md?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -33,7 +33,7 @@ A fully 3D game launcher frontend (systems grid \u2192 games grid \u2192 emulator), st\n | `godot/themes/` | The app's own themes. Godot ignores the folder (`.gdignore`: no `.import` files, nothing in the PCK); the export copies it to `themes/` beside the executable (`godot/addons/odyssey_export/`), and the app reads it from there (from `godot/themes/` in an editor run). |\n | `godot/themes/memory-card/` | The base theme, not one to choose: `theme.toml` (looks, templates and each built-in system's colour and template, and the look of the first 14) and its models. Every theme builds on it: what a theme leaves out is memory-card's, and its templates are every theme's to name (docs/ARCHITECTURE.md A6). |\n | `godot/themes/slab/` | Slab, the default theme (2026-10-08): a black to dark grey background and dark grey menus (`[ui.menu]`, 2026-10-15: docs/ARCHITECTURE.md A6, \"Menus\"), and one dark grey slab (Memory Card's generic card's shape, `models/systems/slab.glb`, from the generator) for every system, its front a `label` showing the system's image from `logos/<system id>.webp` (`[defaults.system_slots]`: docs/ARCHITECTURE.md A6, \"System cards' logos\"). 171 images: 170 from the owner's ES-DE set (`slab_images/` at the root, untracked: the ones matching a built-in id; `favourites`, `recently_played`, `all_games` renamed (`all_games` supplied on its own, 2026-10-15), cropped to their content and at most 512 px by `tools/slab-logos.py` (about ten minutes), plus `ps5.webp`, which ES-DE lacks: made by hand (2026-10-15) in the same layout from a PS5 controller and logo (untracked `ps5/` at the root), so `tools/slab-logos.py` deletes it unless its source gains a `ps5` image. |\n-| `godot/themes/console/` | Console (the default until 2026-10-08): each system's console as its card (Game Gear, Game Boy, Game Boy Color, Game Boy Advance, WonderSwan, WonderSwan Color, PSP, Mega Drive, Switch, Commodore 64 so far; the Amstrad CPC, a CPC 464 with its CTM644 monitor, whose screen plays `videos/amstradcpc.mp4` while it's focused), a gaming PC and its monitor for Windows, a beige PC and CRT of its era for DOS, Windows 3.x and Windows 9x, Super Mario 64's Power Star for Favourites and the Dreamcast menu's alarm clock for Recently played, and the Mega Drive's games on a cartridge (`megadrive_cartridge`); everything else is memory-card's. |\n+| `godot/themes/console/` | Console (the default until 2026-10-08): each system's console as its card (Game Gear, Game Boy, Game Boy Color, Game Boy Advance, Nintendo 64 (with its controller), 64DD (the Nintendo 64 standing on it), GameCube (with its controller), Famicom Disk System (a Famicom on its drive), PC Engine (with a HuCard and its pad), PC Engine CD (a PC Engine Duo with its pad), PC-FX (NEC's tower, with its pad), Neo Geo (the AES and its arcade stick), Neo Geo CD (the top-loader and its pad), ColecoVision (with its keypad controllers), Odyssey\u00b2 (a Magnavox Odyssey\u00b2 with its joysticks), Nintendo DS, Nintendo 3DS, WonderSwan, WonderSwan Color, Atari Lynx (a Lynx II), Neo Geo Pocket Color, Neo Geo Pocket, Watara Supervision (the two-piece original, its screen on a bellows neck), Game & Watch (a Multi Screen in Donkey Kong II's colours, a made-up game on its screens), Pok\u00e9mon mini (in Wooper Blue), Virtual Boy (on its stand, its controller beside it), PSP, PlayStation, PlayStation 2, PlayStation 3 (the original, with its DualShock 3), PlayStation 4 (the original, with its DualShock 4), PlayStation 5 (the first, standing on its stand, with its DualSense), Master System (with its Control Pad), 3DO (a Panasonic FZ-1 with its controller), Mega Drive (the first, with its control pad), Mega-CD (the Mega Drive on a Mega-CD), 32X (a Mega Drive 32X on the Mega Drive), Saturn (the black Model 1 with its control pad), Dreamcast (the European one, its controller with a VMU), GX4000 (with its controller), NES (the front-loader with its controller), Super Nintendo, Satellaview, Super Game Boy (in the Super Nintendo's slot), Wii, Wii U (in black, its GamePad in its cradle), Xbox (the original, with its Controller S), Xbox 360 (the first, in white, its controller on its top), Intellivision (its controllers docked in its top), Channel F (the first, walnut and black, with its two controllers), Atari 2600, Atari 800 (with a CX40 joystick), Atari XE (the XE Game System with its keyboard and a beige CX40), Atari 5200 (the four-port model with its controller), Atari 7800 (the ProSystem with its ProLine joystick), Atari Jaguar (with its controller), Atari Jaguar CD (the Jaguar with the CD on it), MSX1 (a Philips VG-8020, the generic MSX's too), MSX2 (a Philips VG-8235, its 3.5 drive in its right end), CD-i (a Philips CD-i 220 with its gamepad), Vectrex (MB's, its controller in front of it), Switch, Commodore 64 so far; the Amstrad CPC, a CPC 464 with its CTM644 monitor, whose screen plays `videos/amstradcpc.mp4` while it's focused; the Amiga, an Amiga 500 with its 1084S monitor showing Kickstart 1.3's hand; the Amiga CD32 with its pad; the Commodore CDTV with its remote controller; the Apple II, an Apple IIe with two Disk II drives and a Monitor II in green phosphor; the Apple IIGS with its AppleColor RGB monitor, 3.5 drive, keyboard and mouse; the PC-98, an NEC PC-9801VM with its colour monitor and keyboard; the BBC Micro, a Model B with a twin disk drive on it and a Microvitec Cub on the drive; the Archimedes, a BBC A3000 with its three-button mouse and an Acorn colour monitor; the Atari ST, a 520STFM with its SC1224 showing GEM's desktop and its mouse; the ZX Spectrum, a 48K in front of a portable television, the ZX81, the VIC-20, the Acorn Electron (a Plus 1 on its back), the Commodore Plus/4 (its 1531 Datassette at its right), the Dragon 32 and the Oric Atmos (a cassette recorder at its right) in front of the same television; the TI-99/4A, the silver and black one, with its two joysticks; the Macintosh, the original one, with its keyboard and mouse), a gaming PC and its monitor for Windows, a beige PC and CRT of its era for DOS, Windows 3.x and Windows 9x, PICO-8's cartridge made solid (PICO-8 has no hardware), its label a screen, a 2000s LCD monitor playing a Flash game in a browser for Adobe Flash (no hardware either), an unbranded laptop showing a made-up desktop of shortcuts for Desktop Apps (no hardware either), a Sega candy cabinet for NAOMI (an arcade board), Super Mario 64's Power Star for Favourites and the Dreamcast menu's alarm clock for Recently played, and the Mega Drive's games on a cartridge (`megadrive_cartridge`); everything else is memory-card's. |\n | `godot/themes/classic-view/` | Classic View (2026-10-19), the look of a 2D launcher: a wallpaper (`wallpapers/main.webp`, the 1080p one from `tools/wallpaper/`), `[motion]` at 0 (no sway or bob), every game Memory Card's `flat_cover` (`[defaults] game_template`), one light straight on, and one flat 16:9 dark grey card for every system (`models/systems/classic.glb`, from the generator) with the system's logo from `logos/<system id>.webp`: 161 from Batocera's Carbon theme, made by `tools/classic-view-logos.py` (about four minutes) from the untracked `system_logos_all/` (Carbon's whole set, by Batocera's names; a `-w` variant, drawn for a dark background, wins) and `system_logos/` (ES-DE's names, for the few the first lacks). The 10 built-in systems with no logo print their names (`[systems.<id>.slots] label = [\"generated\"]`). |\n | `godot/addons/odyssey_export/` | An editor addon (enabled in `project.godot`; `#if TOOLS`): when an export ends, `ThemesExportPlugin` replaces the export's `themes/` with a copy of `godot/themes/`, and `UpdaterExportPlugin` publishes `OdysseyUpdater.exe` with Native AOT (into `artifacts/updater/`) and copies it beside the executable. |\n | `godot/icons/status/` | The status indicators' icons, top right: Lucide SVGs (ISC; version and changes in its README), imported at 4\u00d7 with mipmaps. |\n@@ -46,7 +46,7 @@ A fully 3D game launcher frontend (systems grid \u2192 games grid \u2192 emulator), st\n | `tools/test-wallpapers/` | `make_test_wallpapers.py` draws slot-showcase's wallpapers into `tests/themes/slot-showcase/wallpapers/` (Pillow; seeded: the same on every run). Not in the solution. |\n | `tests/themes-list/` | A made-up theme list for the downloader (`themes.json` and screenshots of the app's own themes), for `--themes-list` captures; its repositories don't exist. `ThemeIndexReaderTests` checks it reads cleanly. |\n | `samples/themes/retro-tv/` | The sample theme for theme authors (`docs/THEMING.md`): a CRT television game template (screenshot screen, logo plate, focused and launch clips) and a console system model (idle clip). A user theme: copy it into a user folder's `themes/`. |\n-| `tools/console-models/` | Blender scripts for the console theme's own models (`godot/themes/console/`), and for memory-card's Mega Drive clamshell (`mega_drive_clamshell.py`: `models/templates/megadrive_clamshell.glb`, the case's outer hull with its rims and hanger tab, sized by ScreenScraper's scans; no inputs), Switch case (`switch_case.py`: `models/templates/switch_case.glb`, one closed solid with the art as its own faces; no inputs) and PlayStation 2 and PSP clamshells (`playstation_clamshell.py <ps2|ps2_blue|psp> <out>`: `models/templates/ps2_clamshell.glb`, black, `ps2_clamshell_blue.glb` and `psp_clamshell.glb`, grey; one hull in each case's proportions, one closed solid with its rounded edges, flat spine, seam and finger recess, its insert sized by ScreenScraper's scans): `game_gear.py` builds the Game Gear (`models/systems/gamegear.glb`, turned to a three-quarter view) with the printing `game_gear_print.py` draws (Pillow); `game_boy.py <images> <gb|gbc|gba> <out>` builds the Game Boy, Game Boy Color and Game Boy Advance (`models/systems/gb.glb`, `gbc.glb`, `gba.glb`, three-quarter views, each a node tree whose `focused` clip brings a cartridge in, its own way, and switches the screen on) from the atlases `game_boy_print.py` draws, both reading the measurements and atlas regions in `game_boy_layout.py`; `mega_drive.py` builds the Mega Drive (`models/systems/megadrive.glb`, three-quarter view, a `focused` clip that inserts a cartridge) and `mega_drive_cartridge.py` the Mega Drive games' template (`models/templates/megadrive_cartridge.glb`: the `cover` slot above the label's band), with the printing and label crops `mega_drive_print.py` makes from `mega_drive_label.png`; `windows_pc.py` builds Windows' PC and monitor (`models/systems/windows.glb`: a glass-sided tower with RGB fans, the monitor showing a desktop; no clips) from the two images `windows_pc_print.py` draws (the tower's faces in one atlas, laid out in `windows_pc_layout.py`, and the desktop); `strip_maps.py` strips the maps the launcher doesn't draw yet (normal, roughness, occlusion, emissive) from a `.glb` (it made the first `gb.glb`, since replaced, 5.4 MB from 13.2); `wonderswan.py <images> <wonderswan|wonderswancolor> <out>` builds the WonderSwan and WonderSwan Color (`models/systems/wonderswan.glb`, `wonderswancolor.glb`, three-quarter views, each a node tree whose `focused` clip brings a cartridge into the back, its own way, then turns the console on its end for a game played upright) from the atlases `wonderswan_print.py` draws, both reading the measurements and atlas regions in `wonderswan_layout.py`; `psp.py <images> <out>` builds the PSP (`models/systems/psp.glb`, a PSP-1000, three-quarter view, a node tree whose `focused` clip turns it round, opens its UMD door, rolls a UMD in and turns back to the XMB and the game) from the two atlases `psp_print.py` draws (`psp_print.png`, the front and back; `psp_media.png`, the screens, the UMD and the door), both reading the measurements and atlas regions in `psp_layout.py`; `switch.py` builds the Switch (`models/systems/switch.glb`, three-quarter view, a `focused` clip that slides the right Joy-Con up its rail and back) from the front and back printing `switch_print.py` draws, both reading the measurements in `switch_layout.py`. `commodore_64.py` makes the Commodore 64 (`models/systems/c64.glb`, three-quarter view). `amstrad_cpc.py <images> <out>` builds the Amstrad CPC (`models/systems/amstradcpc.glb`: a CPC 464 and its CTM644 monitor, a node tree whose `focused` clip loads a game from tape: the datacorder's door opens, a cassette slides in, PLAY, and eject; its glass is a `video` material) from the atlases `amstrad_cpc_print.py` draws, and `amstrad_cpc_video.py <out>` draws the screen through the clip, frame for frame, and encodes it with ffmpeg (`videos/amstradcpc.mp4`, the card's video: docs/THEMING.md section 4, \"Card videos\"); all three read the measurements and the clip's timing in `amstrad_cpc_layout.py`. `retro_pc.py <images> <set> <out>` builds DOS's, Windows 3.x's and Windows 9x's sets (`models/systems/dos.glb`, `windows3x.glb`, `windows9x.glb`: a 386 or 486 desktop with a CRT on it, a mini tower with a CRT beside it; no clips) from the printing and screens `retro_pc_print.py` draws, both reading the sets' measurements and parts in `retro_pc_layout.py`. `power_star.py` builds Favourites' Power Star (`models/systems/favourites.glb`: a plain star at rest, its `focused` clip opening its eyes) and `alarm_clock.py` Recently played's alarm clock (`models/systems/recently_played.glb`: matched to a capture of the Dreamcast menu, its `focused` clip a one-second rocking roll with hops, scissoring hands and bells pulsing in turn; drawn at 85% of a card by two invisible specks that make its depth its largest side), neither with inputs, both keying their clips through `clip.py` (easing curves and a per-frame keyer) |\n+| `tools/console-models/` | Blender scripts for the console theme's own models (`godot/themes/console/`), and for memory-card's Mega Drive clamshell (`mega_drive_clamshell.py`: `models/templates/megadrive_clamshell.glb`, the case's outer hull with its rims and hanger tab, sized by ScreenScraper's scans; no inputs), Switch case (`switch_case.py`: `models/templates/switch_case.glb`, one closed solid with the art as its own faces; no inputs) and PlayStation 2 and PSP clamshells (`playstation_clamshell.py <ps2|ps2_blue|psp> <out>`: `models/templates/ps2_clamshell.glb`, black, `ps2_clamshell_blue.glb` and `psp_clamshell.glb`, grey; one hull in each case's proportions, one closed solid with its rounded edges, flat spine, seam and finger recess, its insert sized by ScreenScraper's scans): `game_gear.py` builds the Game Gear (`models/systems/gamegear.glb`, turned to a three-quarter view) with the printing `game_gear_print.py` draws (Pillow); `game_boy.py <images> <gb|gbc|gba> <out>` builds the Game Boy, Game Boy Color and Game Boy Advance (`models/systems/gb.glb`, `gbc.glb`, `gba.glb`, three-quarter views, each a node tree whose `focused` clip brings a cartridge in, its own way, and switches the screen on) from the atlases `game_boy_print.py` draws, both reading the measurements and atlas regions in `game_boy_layout.py`; `mega_drive.py` builds the Mega Drive (`models/systems/megadrive.glb`: the first one, its `build()` the console alone, which the 32X's and Mega-CD's cards use too, and its three-button control pad (a smoothed outline with a rounded profile, its printing its own texture) with its 9-pin plug in CONTROL 1, three-quarter view, a node tree whose `focused` clip drops a cartridge (a made-up game, Odyssey, on its label) into the slot, slides POWER on and lights the POWER light, plays on the D-pad, A, B and C, presses RESET, then switches off and takes the cartridge out; the pad and the clip are measured in `mega_drive_layout.py`) and `mega_drive_cartridge.py` the Mega Drive games' template (`models/templates/megadrive_cartridge.glb`: the `cover` slot above the label's band), with the printing and label crops `mega_drive_print.py` makes from `mega_drive_label.png` (and the card's Odyssey label and its pad's printing); `windows_pc.py` builds Windows' PC and monitor (`models/systems/windows.glb`: a glass-sided tower with RGB fans, the monitor showing a desktop; no clips) from the two images `windows_pc_print.py` draws (the tower's faces in one atlas, laid out in `windows_pc_layout.py`, and the desktop); `strip_maps.py` strips the maps the launcher doesn't draw yet (normal, roughness, occlusion, emissive) from a `.glb` (it made the first `gb.glb`, since replaced, 5.4 MB from 13.2); `wonderswan.py <images> <wonderswan|wonderswancolor> <out>` builds the WonderSwan and WonderSwan Color (`models/systems/wonderswan.glb`, `wonderswancolor.glb`, three-quarter views, each a node tree whose `focused` clip brings a cartridge into the back, its own way, then turns the console on its end for a game played upright) from the atlases `wonderswan_print.py` draws, both reading the measurements and atlas regions in `wonderswan_layout.py`; `psp.py <images> <out>` builds the PSP (`models/systems/psp.glb`, a PSP-1000, three-quarter view, a node tree whose `focused` clip turns it round, opens its UMD door, rolls a UMD in and turns back to the XMB and the game) from the two atlases `psp_print.py` draws (`psp_print.png`, the front and back; `psp_media.png`, the screens, the UMD and the door), both reading the measurements and atlas regions in `psp_layout.py`; `switch.py` builds the Switch (`models/systems/switch.glb`, three-quarter view, a `focused` clip that slides the right Joy-Con up its rail and back) from the front and back printing `switch_print.py` draws, both reading the measurements in `switch_layout.py`. `commodore_64.py` makes the Commodore 64 (`models/systems/c64.glb`, three-quarter view). `amiga.py <images> <out>` builds the Amiga (`models/systems/amiga.glb`: an Amiga 500 and its 1084S monitor, a node tree whose `focused` clip slides a floppy into the drive, brings the game's title screen up, then resets with Ctrl and both Amiga keys and ejects the floppy) from the two atlases `amiga_print.py` draws (`amiga_print.png`, the computer's top and the monitor's badge; `amiga_media.png`, the screens and the floppy), both reading the measurements and atlas regions in `amiga_layout.py`. `zx_spectrum.py <images> <out>` builds the ZX Spectrum (`models/systems/zxspectrum.glb`: a 48K in front of a made-up portable television, a node tree whose `focused` clip types LOAD \"\" and loads a game from tape: the border's pilot and data stripes, the loading screen a third at a time, its colours) from the two atlases `zx_spectrum_print.py` draws (`zx_spectrum_print.png`, the computer's top and the television's front; `zx_spectrum_media.png`, the screen at rest and the paper's and border's states), both reading the measurements and atlas regions in `zx_spectrum_layout.py`; it borrows `amiga.py`'s geometry helpers. `zx81.py <images> <out>` builds the ZX81 (`models/systems/zx81.glb`: a ZX81 in front of `zx_spectrum.py`'s television, a node tree whose `focused` clip types LOAD \"\" on the membrane and loads a game through the ZX81's flickering bands) from the two atlases `zx81_print.py` draws (`zx81_print.png`, the computer's top and the television's front; `zx81_media.png`, the screens), both reading the measurements in `zx81_layout.py`. `psx.py <images> <out>` builds the PlayStation (`models/systems/psx.glb`, the grey SCPH-1002, three-quarter view, a node tree whose `focused` clip presses OPEN, springs the lid up, drops a disc in and shuts it, and later ejects it) from the atlas `psx_print.py` draws (`psx_print.png`: the top, the port recess, the disc's two sides), both reading the measurements and atlas regions in `psx_layout.py`. `snes.py <images> <kind> <out>` builds the Super Nintendo and the Satellaview (`snes`: `models/systems/snes.glb`, the PAL console; `satellaview`: `models/systems/satellaview.glb`, a Super Famicom on its satellite receiver, the BS-X cartridge carrying its memory pack; three-quarter views, each a node tree whose `focused` clip drops a cartridge in through the slot's dust flaps, slides POWER on, and later ejects it, the receiver's ACCESS light flickering) from the atlas `snes_print.py <kind>` draws (`<kind>_print.png`: the top, the ports, the cartridge's label), both reading the measurements and atlas regions in `snes_layout.py`. `ps2.py <images> <out>` builds the PlayStation 2 (`models/systems/ps2.glb`, the black launch model lying flat, three-quarter view, a node tree whose `focused` clip slides the tray out, drops a DVD on it and slides it in, the power light turning green, and later ejects it) from the two atlases `ps2_print.py` draws (`ps2_print.png`, the top and fronts; `ps2_disc.png`, the DVD's two sides), both reading the measurements and atlas regions in `ps2_layout.py`. `nintendo_ds.py <images> <kind> <out>` builds the Nintendo DS and 3DS (`nds`: `models/systems/nds.glb`, a white DS Lite open; `n3ds`: `models/systems/n3ds.glb`, the first 3DS in Aqua Blue open; three-quarter views, each a node tree whose `focused` clip slides a game card into slot-1, lights both screens and has the stylus tap TOUCH TO START) from the two atlases `nintendo_ds_print.py <kind>` draws (`<kind>_print.png`, the halves' faces; `<kind>_media.png`, the screens and the card's label), both reading the measurements and atlas regions in `nintendo_ds_layout.py`. `wii.py <images> <out>` builds the Wii (`models/systems/wii.glb`: the console standing on its stand, its remote leaning on its side, three-quarter view, a node tree whose `focused` clip slides a disc into the slot, the slot glowing and breathing blue, the power light turning green and the remote's player light coming on as it rumbles, and later ejects it) from the atlas `wii_print.py` draws, both reading the measurements and atlas regions in `wii_layout.py`. `atari_2600.py <images> <out>` builds the Atari 2600 (`models/systems/atari2600.glb`: the four-switch woody and its CX40 joystick, three-quarter view, a node tree whose `focused` clip drops a cartridge into the slot, flips POWER on, flicks GAME SELECT and GAME RESET and waggles the joystick, firing its button) from the atlas `atari_2600_print.py` draws, both reading the measurements and atlas regions in `atari_2600_layout.py`. `msx.py <images> <out>` builds MSX1's card (`models/systems/msx1.glb`, the generic MSX's too: a Philips VG-8020, three-quarter view, a node tree whose `focused` clip flips the cartridge lid up and back, drops a cartridge into the front slot, rocks the power switch on, plays the cursor keys and the space bar and toggles CAPS, and later takes the cartridge out and shuts the lid) from the atlas `msx_print.py` draws, both reading the measurements and atlas regions in `msx_layout.py`; it borrows `amiga.py`'s geometry helpers. `msx2.py <images> <out>` builds MSX2's card (`models/systems/msx2.glb`: a Philips VG-8235, its keyboard in front of the main unit with the 3.5 drive in its right end, three-quarter view, a node tree whose `focused` clip slides a disk into the drive, flickers FDD BUSY and the drive's light while it loads, plays the cursor keys and the space bar and toggles CAPS, then presses eject and takes the disk away) from the atlas `msx2_print.py` draws, both reading the measurements and atlas regions in `msx2_layout.py`. `cdi.py <images> <out>` builds the CD-i's card (`models/systems/cdimono1.glb`: a Philips CD-i 220 and its gamepad, three-quarter view, a node tree whose `focused` clip presses OPEN, slides the tray out, drops a disc onto it and takes it in, lights the display (CD-i, then the track and its time) while the gamepad's thumbpad and buttons are played, then brings the tray out again and takes the disc away) from the atlas `cdi_print.py` draws, both reading the measurements and atlas regions in `cdi_layout.py`. `wiiu.py <images> <out>` builds the Wii U (`models/systems/wiiu.glb`: the console in black, lying down, and its GamePad standing in its cradle in front of it, the Wii U Menu on its screen, three-quarter view, a node tree whose `focused` clip slides a disc into the console, chooses its tile on the menu, starts it with A and plays it on the sticks, A and B, then presses EJECT and takes the disc away) from the atlas `wiiu_print.py` draws, both reading the measurements and atlas regions in `wiiu_layout.py`. `odyssey2.py <images> <out>` builds the Odyssey\u00b2 (`models/systems/odyssey2.glb`: a Magnavox Odyssey\u00b2 and its two joysticks, three-quarter view, a node tree whose `focused` clip drops a cartridge into the slot, presses POWER, swings the first joystick's stick and presses ACTION, then presses POWER again and takes the cartridge out) from the atlas `odyssey2_print.py` draws, both reading the measurements and atlas regions in `odyssey2_layout.py`. `atari5200.py <images> <out>` builds the Atari 5200 (`models/systems/atari5200.glb`: the four-port model and its controller, three-quarter view, a node tree whose `focused` clip drops a cartridge into the slot, presses POWER ON/OFF and lights the power light, presses START and plays on the stick and a fire button, then switches off and takes the cartridge out) from the atlas `atari5200_print.py` draws, both reading the measurements and atlas regions in `atari5200_layout.py`. `lynx.py <images> <out>` builds the Atari Lynx (`models/systems/atarilynx.glb`: a Lynx II standing up, three-quarter view, a node tree whose `focused` clip brings a game card over it showing its label, turns it round and drops it into the slot at the top of its back, presses ON, shows the title and then the game, presses FLIP and hops over onto its top with the picture turned over (the D-pad now at the right, the other pair of A and B played), hops back, presses OFF and takes the card out) from the atlas `lynx_print.py` draws, both reading the measurements and atlas regions in `lynx_layout.py`. `neogeocd.py <images> <out>` builds the Neo Geo CD (`models/systems/neogeocd.glb`: the top-loader and its controller, three-quarter view, a node tree whose `focused` clip swings the lid up, drops a CD onto the spindle and shuts the lid, presses POWER and flickers the access light through the Neo Geo CD's long load, plays on the pad's stick, A and B, then lifts the lid again and takes the CD out) from the atlas `neogeocd_print.py` draws, both reading the measurements and atlas regions in `neogeocd_layout.py`. `pcenginecd.py <images> <out>` builds the PC Engine CD (`models/systems/pcenginecd.glb`: a PC Engine Duo, the PC Engine with the Super CD-ROM\u00b2 built in, and its pad, three-quarter view, a node tree whose `focused` clip presses OPEN, drops a CD onto the spindle and shuts the lid, slides POWER on and flickers BUSY, presses RUN and plays on the D-pad and II and I in turbo bursts, then switches off and takes the CD out) from the atlas `pcenginecd_print.py` draws, both reading the measurements and atlas regions in `pcenginecd_layout.py`. `neogeo_pocket.py <images> <kind> <out>` builds the Neo Geo Pocket Color (`ngpc`: `models/systems/ngpc.glb`, in Platinum Blue, standing up, three-quarter view, a node tree whose `focused` clip brings a cartridge over it showing its label, turns it round and drops it into the recess at the top of its back, presses POWER, shows the start-up logo and Odyssey's title, plays on the clicky stick, A and B, does a special move (the stick swept round a quarter and A) and hops with it, then presses POWER again and takes the cartridge out) and the Neo Geo Pocket (`ngp`: `models/systems/ngp.glb`, the monochrome one, in carbon black, the same clip on its four-grey screens) from the atlases `neogeo_pocket_print.py` draws (`ngpc_print.png`, `ngp_print.png`), both reading the measurements and atlas regions in `neogeo_pocket_layout.py`. `ps3.py <images> <out>` builds the PlayStation 3 (`models/systems/ps3.glb`: the original CECHA01 and its DualShock 3, three-quarter view, a node tree whose `focused` clip pushes a Blu-ray into the slot and lets the drive take it, turns the power light green, presses the PS button (the first player light), plays on the sticks and cross, steers by tilting the controller, then ejects the disc and takes it away) from the atlas `ps3_print.py` draws, both reading the measurements and atlas regions in `ps3_layout.py`. `atari7800.py <images> <out>` builds the Atari 7800 (`models/systems/atari7800.glb`: the US ProSystem and its ProLine joystick, three-quarter view, a node tree whose `focused` clip drops a cartridge into the slot, presses POWER and lights its light, presses RESET, swings the stick and presses a fire button, then switches off and takes the cartridge out) from the atlas `atari7800_print.py` draws, both reading the measurements and atlas regions in `atari7800_layout.py`. `supervision.py <images> <out>` builds the Watara Supervision (`models/systems/supervision.glb`: the original two-piece model standing up, three-quarter view, a node tree whose `focused` clip brings a cartridge over the screen unit showing its label, turns it round and drops it into its slot, shows the start-up logo, the title and the game in the LCD's four greens, tilts the screen unit back on its bellows neck while the D-pad, A and B are played and up again, then takes the cartridge out) from the atlas `supervision_print.py` draws, both reading the measurements and atlas regions in `supervision_layout.py`. `jaguar.py <images> <kind> <out>` builds the Atari Jaguar (`atarijaguar`: `models/systems/atarijaguar.glb`, the Jaguar and its controller, three-quarter view, a node tree whose `focused` clip drops a cartridge into the slot, presses the power button and lights its light, plays on the D-pad, A, B and C, then switches off and takes the cartridge out) and the Jaguar CD (`atarijaguarcd`: `models/systems/atarijaguarcd.glb`, the same with the Jaguar CD on it, its `focused` clip swinging the CD's lid up, dropping a CD onto the spindle and shutting the lid, then playing as the Jaguar's and taking the CD out) from the atlas `jaguar_print.py` draws, both reading the measurements and atlas regions in `jaguar_layout.py`. `pc98.py <images> <out>` builds the PC-98 (`models/systems/pc98.glb`: an NEC PC-9801VM, its colour monitor on it and its keyboard in front, three-quarter view, a node tree whose `focused` clip slides a 5.25-inch floppy into drive 1 and turns its lever down, presses POWER, flickers the drive's light through the memory check and Odyssey's title, presses RETURN and plays, then turns the lever up and takes the floppy out) from the two atlases `pc98_print.py` draws (`pc98_print.png`, the keyboard from above and the fronts; `pc98_media.png`, the screens and the floppy), both reading the measurements and atlas regions in `pc98_layout.py`. `xbox.py <images> <out>` builds the Xbox (`models/systems/xbox.glb`: the original Xbox and its Controller S, three-quarter view, a node tree whose `focused` clip presses power and lights its ring green, presses eject and slides the tray out, drops a DVD onto it and takes it in, plays on the sticks, the d-pad and A, B, X and Y, then ejects again and takes the DVD away) from the atlas `xbox_print.py` draws, both reading the measurements and atlas regions in `xbox_layout.py`. `gameandwatch.py <images> <out>` builds the Game & Watch (`models/systems/gameandwatch.glb`: a Multi Screen in Donkey Kong II's brown and cream, open on the table, a made-up game, Odyssey, on its two LCD screens, three-quarter view, a node tree whose `focused` clip presses GAME A, plays three frames of the game on both screens while the D-pad rocks and JUMP fires, then shuts the handheld with a snap and springs it open again, back to the clock) from the atlas `gameandwatch_print.py` draws, both reading the measurements and atlas regions in `gameandwatch_layout.py`. `sega32x.py <images> <out>` builds the 32X (`models/systems/sega32x.glb`: a Mega Drive 32X plugged into `mega_drive.py`'s Mega Drive, built by its `build()`, three-quarter view, a node tree whose `focused` clip hops the 32X off the Mega Drive, turns it once round in the air and drops it back into the slot with a squash, then drops a cartridge (the 32X's own shell, a made-up game, Odyssey, on its label) into its slot through the two dust flaps, and later takes it out) from the atlas `sega32x_print.py` draws and the Mega Drive's own (`mega_drive_print.py`, into the same folder), reading the measurements and atlas regions in `sega32x_layout.py`. `vectrex.py <images> <out>` builds the Vectrex (`models/systems/vectrex.glb`: Milton Bradley's, its controller on the table in front of it, its coiled cord running into the bay, three-quarter view, the power-on screen drawn in lines, a node tree whose `focused` clip slides a cartridge into its side, settles an overlay over the tube, flickers the screen on and plays a made-up game, Odyssey, in vectors through the overlay while the joystick rocks and the buttons press, then takes the overlay and the cartridge out again) from the atlas `vectrex_print.py` draws, both reading the measurements and atlas regions in `vectrex_layout.py`. `pokemini.py <images> <out>` builds the Pok\u00e9mon mini (`models/systems/pokemini.glb`: in Wooper Blue, its front half frosted clear with its insides showing faintly, its outline traced from a photograph, three-quarter view, its clock on the LCD, a node tree whose `focused` clip drops a cartridge into the top of its back, powers on to a made-up game, Odyssey, drawn in the LCD's 96 \u00d7 64 pixels and plays it on the D-pad and A, shakes the handheld and rumbles, presses C, then powers off and takes the cartridge out) from the atlas `pokemini_print.py` draws, both reading the measurements and atlas regions in `pokemini_layout.py`. `virtualboy.py <images> <out>` builds the Virtual Boy (`models/systems/virtualboy.glb`: the unit on its stand, lofted through cross-sections along its width, the red shell's lower edge rising over the middle, the rubber eyecup round its two screens at the back, its controller standing on its grips beside it with its cord to the unit, three-quarter view, a node tree whose `focused` clip turns the unit round on its stand to show its eyes, where a made-up game, Odyssey, plays in red on black (each eye's picture a little apart, for the depth) while the D-pad rocks and A fires, then turns it back) from the atlas `virtualboy_print.py` draws, both reading the measurements and atlas regions in `virtualboy_layout.py`. `gx4000.py <images> <out>` builds the Amstrad GX4000 (`models/systems/gx4000.glb`: the console lofted along its width following its curved front, its two rails with their round pads, the pink switch, the cartridge slot and its flaps, the ports printed on its front, and its controller with its cord to the left joystick port, three-quarter view, a node tree whose `focused` clip drops a cartridge (a made-up game, Odyssey) into the slot through its flaps, switches on and lights the power light, plays on the D-pad, 1 and 2, presses PAUSE twice, then switches off and takes the cartridge out) from the atlas `gx4000_print.py` draws, both reading the measurements and atlas regions in `gx4000_layout.py`. `ps4.py <images> <out>` builds the PlayStation 4 (`models/systems/ps4.glb`: the first PS4, its decks' sides parallelograms (the front set back at the top, the back overhanging), the slit between them, the glossy and matte parts with the groove where the light bar shines, and its DualShock 4 in front of it (traced from the layout drawing on Commons), three-quarter view, a node tree whose `focused` clip slides a disc (a made-up game, Odyssey) into the slit, lights the light bar blue and the controller's, plays on the sticks, the d-pad, cross and circle, then ejects the disc and the lights go off) from the atlas `ps4_print.py` draws, both reading the measurements and atlas regions in `ps4_layout.py`. `xbox360.py <images> <out>` builds the Xbox 360 (`models/systems/xbox360.glb`: the first one in white, lying flat, lofted along its width with its top and underside drawn in at the middle, the grey grilles bulging from its ends, the hard drive on the left one, the faceplate printed, and its controller (traced from the drawing of it on Commons) resting on its top, three-quarter view, a node tree whose `focused` clip presses power and lights the Ring of Light a quadrant at a time, slides the tray out, drops a disc (a made-up game, Odyssey) on it and takes it in, plays on the sticks, the d-pad, A, B, X and Y, then ejects the disc and the lights go off) from the atlas `xbox360_print.py` draws, both reading the measurements and atlas regions in `xbox360_layout.py`. `pcfx.py <images> <out>` builds the PC-FX (`models/systems/pcfx.glb`: NEC's tower, its front's strip recessed between the pillars (NEC PC-FX, the badge, the POWER and BUSY lights, the expansion door, the controller ports), the cap along its top with the CD lid, the sides' raised panels of vents, and its pad (traced from a photograph) with its cord to port 1, three-quarter view, a node tree whose `focused` clip swings the lid up on its hinge, drops a CD (a made-up game, Odyssey) onto the spindle and shuts it, lights POWER and flickers BUSY, plays on the d-pad, I and II, then takes the CD out) from the atlas `pcfx_print.py` draws, both reading the measurements and atlas regions in `pcfx_layout.py`. `desktop.py <images> <out>` builds Desktop Apps' card (`models/systems/desktop.glb`: Desktop Apps is the shortcuts on the user's desktop, so an unbranded laptop, open, its screen showing a made-up desktop (a column of shortcuts, each icon with its shortcut arrow, the first a made-up game, Odyssey; the taskbar), three-quarter view, a node tree whose `focused` clip glides the cursor to the game's shortcut and double-clicks it, zooms its window open from the icon and plays it, then clicks the window's close button) from the atlas `desktop_print.py` draws, both reading the measurements and atlas regions in `desktop_layout.py`. `ps5.py <images> <out>` builds the PlayStation 5 (`models/systems/ps5.glb`: the first one with its disc drive, standing on its stand, its glossy black core between the white panels, each lofted up the console through its side outline's width and following the front's hourglass in and out again, rising past the core at the top, and its DualSense in front of it (traced from a photograph of one from above), three-quarter view, a node tree whose `focused` clip slides a disc (a made-up game, Odyssey) upright into the slot, lights the light strips and the DualSense's light bar, plays on the sticks, the d-pad, cross and circle, then ejects the disc) from the atlas `ps5_print.py` draws, both reading the measurements and atlas regions in `ps5_layout.py`. `intellivision.py <images> <out>` builds the Intellivision (`models/systems/intellivision.glb`: Mattel's Master Component, its side profile across its width, the gold strips and the wood-grain front printed, the docks' band sunk into its top with its two controllers lying in it (keypad and gold disc each), the cartridge slot in its right end, three-quarter view, a node tree whose `focused` clip slides a cartridge (a made-up game, Odyssey) into the slot, switches on, lifts the right controller out of its dock tipping it towards the viewer while its disc rocks, puts it back, and takes the cartridge out) from the atlas `intellivision_print.py` draws, both reading the measurements and atlas regions in `intellivision_layout.py`. `n64dd.py <images> <out>` builds the 64DD (`models/systems/n64dd.glb`: `n64.py`'s console and controller, built by its functions, the console lifted onto the drive, which is the console's base's shape grown taller with its corner pods running its full height and the disk's slot and EJECT in the panel sunk into its front, three-quarter view, a node tree whose `focused` clip slides a disk (a made-up game, Odyssey) into the drive, switches on and plays on the controller, then presses EJECT and the disk slides out) from the atlas `n64dd_print.py` draws (the drive's front, the disk's label) and the N64's own (`n64_print.py`, into the same folder), reading the drive's measurements and atlas regions in `n64dd_layout.py`. `sgb.py <images> <out>` builds the Super Game Boy (`models/systems/sgb.glb`: `snes.py`'s European Super Nintendo, built by its functions, with the Super Game Boy standing in its slot, the adapter's outline with the notch in its top and the Game Boy cartridge's slot below it, three-quarter view, a node tree whose `focused` clip drops a Game Boy cartridge (a made-up game, Odyssey) into the adapter, slides POWER on, and later takes the cartridge out) from the atlas `sgb_print.py` draws (the adapter's front, the Game Boy cartridge's label) and the Super Nintendo's own (`snes_print.py snes`, into the same folder), reading the adapter's measurements and atlas regions in `sgb_layout.py`. `bbcmicro.py <images> <out>` builds the BBC Micro (`models/systems/bbcmicro.glb`: a Model B, its keyboard panel sunk into its front half with every key a frustum printed with its legend (the red function keys, the clear strip with the grille and the owl), a twin 5.25-inch drive on its back half and a Microvitec Cub monitor on the drive, three-quarter view, a node tree whose `focused` clip slides a floppy (a made-up game, Odyssey) into drive 0 and turns its latch, boots it with SHIFT and BREAK while the drive's light flickers to a teletext title in Mode 7, plays it in Mode 2 on Z, X and RETURN, then BREAKs back to the boot screen and takes the floppy out) from the atlas `bbcmicro_print.py` draws, both reading the measurements, the keys' rows and the atlas regions in `bbcmicro_layout.py`. `electron.py <images> <out>` builds the Acorn Electron (`models/systems/electron.glb`: the wedge, its 56 keys standing in the opening in its slope's frame, the dark band with acorn electron, a Plus 1 on its back with its two cartridge slots, in front of `zx_spectrum.py`'s television, three-quarter view, a node tree whose `focused` clip drops a ROM cartridge (a made-up game, Odyssey) into the Plus 1's first slot past its cover, starts it with CTRL and BREAK, plays it on Z, X and RETURN, then BREAKs back to the start-up screen and takes the cartridge out) from the atlas `electron_print.py` draws (with the television's front, `zx_spectrum_print.py`'s, where its layout puts it, and the screens drawn as `bbcmicro_print.py`'s), reading the measurements, the keys' rows and the atlas regions in `electron_layout.py`. `atari800.py <images> <out>` builds the Atari 800 (`models/systems/atari800.glb`: the shell from its side outline, its top's ends chamfered, its 57 keys standing on the sloping deck in its frame, the function keys and the power light in their column, the lid (the ATARI 800 plate on its face, vents on its top) over the metal cartridge bay and its two slots, the vents' fins behind it, the ports' block underneath, and `atari_2600.py`'s CX40 joystick, its cord in port 1, three-quarter view, a node tree whose `focused` clip lifts the lid, drops a cartridge (a made-up game, Odyssey) into the left slot and shuts the lid, presses OPTION and START and plays on the joystick, then lifts the lid again and takes the cartridge out) from the atlas `atari800_print.py` draws (with the joystick's top, `atari_2600_print.py`'s, where its layout puts it), both reading the measurements, the keys' rows and the atlas regions in `atari800_layout.py`. `xegs.py <images> <out>` builds the Atari XE Game System (`models/systems/atarixe.glb`: the shell over its inset base, the front strip with the ATARI logo in relief and its four pastel buttons, the ridges filling the triangle at its back left, the funnel and the cartridge slot cut along their long side, the power button on its raised triangle, the keyboard in front with its keys standing in its well and its cable to the console's left side, and `atari_2600.py`'s CX40 in beige at its right, its cord in the first port's notch, three-quarter view, a node tree whose `focused` clip drops a cartridge (a made-up game, Odyssey) into the slot along the ridges, presses POWER (its LED rising lit through the button) and START and plays on the joystick, then switches off and takes the cartridge out) from the atlas `xegs_print.py` draws (with the beige joystick's top where `atari_2600_layout.py` puts the 2600's), both reading the measurements, the keys' rows and the atlas regions in `xegs_layout.py`. `ti99.py <images> <out>` builds the TI-99/4A (`models/systems/ti99.glb`: the aluminium from its side outline over the black base, the keyboard surround and the module bay sunk into the sloping deck, its 48 keys standing in the surround in the deck's frame under the strip with its power light, the slot diving from the bay under the back section, and its two joysticks in front at its left, their cables to the port in the base's left side, three-quarter view, a node tree whose `focused` clip slides a command module (a made-up game, Odyssey) down into the bay and into the slot, turns the power light on, presses 2 and plays on the first joystick, then slides the module back out) from the atlas `ti99_print.py` draws, both reading the measurements, the keys' rows and the atlas regions in `ti99_layout.py`. `channelf.py <images> <out>` builds the Fairchild Channel F (`models/systems/channelf.glb`: its side outline cut to its sides sloping in to the foot, walnut on the sides and the front, black on top, the lid with its chrome strip, RESET and the keys 1 to 4, the INSERT CARTRIDGE plate and the Press to Eject bar on the front panel, the slot in the lid's front face behind its door, and its two controllers lying in front, their knobs turned as lathe profiles, their cables to its left side, three-quarter view, a node tree whose `focused` clip slides a Videocart (a made-up game, Odyssey) into the slot, pushing the door in, presses RESET and 1 and plays on a controller's knob, then presses Press to Eject and takes the Videocart out) from the atlas `channelf_print.py` draws, both reading the measurements and the atlas regions in `channelf_layout.py`. `macintosh.py <images> <out>` builds the Macintosh (`models/systems/macintosh.glb`: the original one, its case from the side outline of its housing's design patent, the screen's sunk frame, the floppy slot and its finger recess cut into the leaning front face in the face's frame, the handle and the vents cut in, the rainbow Apple a badge on the face, its keyboard with its keys on its sloping top and its coiled cord sagging to the chin, its mouse with its button plate and its cable round to the back, three-quarter view, a node tree whose `focused` clip slides a floppy (a made-up game, Odyssey) into the drive, boots from the question mark through the happy Mac and Welcome to Macintosh. to the game in its window, moves and clicks the mouse, then ejects the floppy) from the atlas `macintosh_print.py` draws (the screens drawn one bit deep at 512 \u00d7 342), both reading the measurements, the keys' rows and the atlas regions in `macintosh_layout.py`. `cdtv.py <images> <out>` builds the Commodore CDTV (`models/systems/cdtv.glb`: the black case on its four silver feet, the strip recessed under its front, the caddy slot and the eject button's hole cut into its front, the display's five faces and the two LEDs swapped by depth, its remote controller in front with its keys, D-pad, A and B, three-quarter view, a node tree whose `focused` clip slides a CD caddy (a made-up game, Odyssey, through its window) into the slot, lights the green LED and flickers the yellow one, counts track 01's time on the display and plays on the remote, then presses eject and takes the caddy out) from the atlas `cdtv_print.py` draws, both reading the measurements and the atlas regions in `cdtv_layout.py`. `plus4.py <images> <out>` builds the Commodore Plus/4 (`models/systems/plus4.glb`: the dark wedge from its side outline with its cooling fins along the back, its keys standing on the deck in the deck's frame, the function keys on the chamfer and the cursor keys' diamond, its 1531 Datassette at its right (the lid a frame round its window, hinged at its back, the piano keys at its front, its cable to the Plus/4's back), in front of `zx_spectrum.py`'s television, three-quarter view, a node tree whose `focused` clip opens the lid, slides a cassette (a made-up game, Odyssey, labelled on both sides) into it and shuts it, types LOAD, presses PLAY, goes through PRESS PLAY ON TAPE, SEARCHING and FOUND ODYSSEY to the title and the game played on the cursor keys, then STOP, EJECT and the cassette out; the cassette is the lid's child, so it rides in it) from the two atlases `plus4_print.py` draws (the print, with the television's front, `zx_spectrum_print.py`'s, where its layout puts it; and the screens, the TED's picture in its border, drawn in character cells as `vic20_print.py`'s are), both reading the measurements, the keys' rows and the atlas regions in `plus4_layout.py`. `archimedes.py <images> <out>` builds the Acorn Archimedes (`models/systems/archimedes.glb`: a BBC A3000 from its side outline, its 103 keys standing on the deck in the deck's frame (the red function keys, the cursor keys, the keypad), the Acorn badge on the back block's front face, the drive's slot in its right side, its three-button mouse at its right with its cable to the back, and an Acorn colour monitor behind it on its base, three-quarter view, a node tree whose `focused` clip slides a floppy (a made-up game, Odyssey) into the drive while its light flickers, clicks the drive's icon on RISC OS's icon bar to open its filer window, double-clicks !Odyssey and plays the game on the cursor keys, then Escape and the eject button) from the two atlases `archimedes_print.py` draws (the print; and the screens at RISC OS's 640 \u00d7 512), both reading the measurements, the keys' layout and the atlas regions in `archimedes_layout.py`. `dragon32.py <images> <out>` builds the Dragon 32 (`models/systems/dragon32.glb`: the beige wedge from its side outline, the keyboard's well sunk into its slope with its 53 black keys standing in it in the slope's frame, the DRAGON 32 badge behind them, the ribs across the back block and the cartridge's slot in its right side, in front of `zx_spectrum.py`'s television, three-quarter view, a node tree whose `focused` clip slides a cartridge (a made-up game, Odyssey) into the slot, starts it, presses SPACE and plays it on the arrow keys, then takes the cartridge out and the Dragon starts again) from the two atlases `dragon32_print.py` draws (the print, with the television's front, `zx_spectrum_print.py`'s, where its layout puts it; and the screens, the VDG's 256 \u00d7 192 in its black border), both reading the measurements, the keys' rows and the atlas regions in `dragon32_layout.py`. `oric.py <images> <out>` builds the Oric (`models/systems/oric.glb`: an Oric Atmos, its black shell from its side outline on its red base, the keyboard's well cut into its deck with its keys standing in it (the red ones at the sides), the grille's plateau and the ORIC ATMOS 48K badge on its back block, an unbranded cassette recorder at its right (the lid a frame round its window, hinged at its back, the piano keys at its front, its cable to the Atmos's back), in front of `zx_spectrum.py`'s television, three-quarter view, a node tree whose `focused` clip opens the lid, slides a cassette (a made-up game, Odyssey) into it and shuts it, types CLOAD\"\" and RETURN, presses PLAY, loads the game and plays it on the arrow keys, then STOP/EJ and the cassette out) from the two atlases `oric_print.py` draws (the print, with the television's front, `zx_spectrum_print.py`'s, where its layout puts it; and the screens, the Oric's 240 \u00d7 224 in its black border), both reading the measurements, the keys' rows and the atlas regions in `oric_layout.py`. `saturn.py <images> <out>` builds the Sega Saturn (`models/systems/saturn.glb`, replacing the front-on model the theme first had: the black Model 1 from its side outline cut round its plan's corners, the ports' band cut back under its brow, POWER, OPEN, RESET and the ACCESS light on its front band, the CD lid a dome over the well hinged at its back, the cartridge slot's flap, the vents in its right side, and its black control pad from a smoothed outline with a rounded profile, its D-pad, A B C and X Y Z, START, L and R, its cable to the first port, three-quarter view, a node tree whose `focused` clip presses OPEN, drops a CD (a made-up game, Odyssey) onto the spindle and shuts the lid, presses POWER while ACCESS flickers, plays on the pad, then takes the CD out) from the atlas `saturn_print.py` draws, both reading the measurements, the lid's outline and the atlas regions in `saturn_layout.py`. `dreamcast.py <images> <out>` builds the Dreamcast (`models/systems/dreamcast.glb`, replacing the front-on model the theme first had: the European one, its blue swirl on the round lid lying on its top and hinged at its back, its top rising a little to the back, the ports' panel sunk into its front with A B C D, POWER and OPEN in its top's front corners with the power light, the vents in its right side, its base set in under it, and its controller (from a smoothed outline with a rounded profile, its stick, D-pad, A B X Y and START, a VMU in its slot) with its cable from between its grips to port A, three-quarter view, a node tree whose `focused` clip presses OPEN, drops a GD-ROM (a made-up game, Odyssey) onto the spindle and shuts the lid, presses POWER (the light comes on and the VMU shows the game's ship), plays on the controller, then switches off and takes the GD-ROM out) from the atlas `dreamcast_print.py` draws, both reading the measurements and the atlas regions in `dreamcast_layout.py`; it borrows `saturn.py`'s outline helpers. `nes.py <images> <out>` builds the NES (`models/systems/nes.glb`, replacing the front-on model the theme first had: the front-loading NES-001, its darker base sloping in to the foot, the upper body with the door's recess and the cartridge's chamber, the band across the top black at its ends and ribbed between, POWER, RESET and the red light, the two ports, and its controller with its plug in port 1, three-quarter view, a node tree whose `focused` clip flips the door up and out on its top hinge (as the real one opens, its face ending up), slides a cartridge (a made-up game, Odyssey) in under it and presses it down, shuts the door, switches on, presses START and plays on the cross, B and A, presses RESET, switches off and takes the cartridge out) from the atlas `nes_print.py` draws, both reading the measurements and the atlas regions in `nes_layout.py`. `plugs.py` builds the plugs at the ends of the cards' cables, from photographs (the Dreamcast controller's, the Saturn's, the NES controller's, the 9-pin D of joysticks and mice, round DIN and mini-DIN, the modular plug, and a strain relief where a cable leaves a controller or a hard-wired one a console), and stands each on its port; a card that shows a cable plugged in shows its plug (the Mega Drive, Saturn, Dreamcast, NES, Atari 800, XE Game System, TI-99/4A, Channel F, Macintosh, Plus/4, Oric and Archimedes cards use it). `apple2.py <images> <out>` builds the Apple II (`models/systems/apple2.glb`: an Apple IIe, two Disk II drives on its lid and a Monitor II on them, three-quarter view, a node tree whose `focused` clip slides a floppy into drive 1 and turns its door down, lights IN USE while Odyssey's title screen arrives in hi-res's line order, then presses Control and RESET, brings the banner back and takes the floppy out) from the two atlases `apple2_print.py` draws (`apple2_print.png`, the computer's top, the drives' panels and the monitor's control strip; `apple2_media.png`, the green screens and the floppy), both reading the measurements and atlas regions in `apple2_layout.py`; it borrows `amiga.py`'s geometry helpers. `master_system.py <images> <out>` builds the Master System (`models/systems/mastersystem.glb`: the first model and its Control Pad, three-quarter view, a node tree whose `focused` clip drops a cartridge into the slot through its dust flap, presses POWER, rocks the D-pad and presses its buttons and PAUSE, and later takes the cartridge out) from the atlas `master_system_print.py` draws, both reading the measurements and atlas regions in `master_system_layout.py`. `pico8.py <images> <out>` builds PICO-8's card (`models/systems/pico8.glb`: the fantasy console has no hardware, so its .p8.png cartridge made solid, its corners cut in pixel steps, a node tree whose `focused` clip hops it round once and boots its label, a screen, through noise and PICO-8's logo into a made-up game) from the two atlases `pico8_print.py` draws in PICO-8's palette and three-by-five letters (`pico8_print.png`, the cartridge's face and back; `pico8_media.png`, the label's screens), both reading the measurements, the logo and the atlas regions in `pico8_layout.py`. `atari_st.py <images> <out>` builds the Atari ST (`models/systems/atarist.glb`: a 520STFM, its SC1224 and its mouse, three-quarter view, a node tree whose `focused` clip slides a floppy into the drive in its right end, double-clicks the mouse to open the floppy's window and again to run ODYSSEY.PRG, the screen going black while the drive light flickers before the game's title screen, then brings the desktop back and ejects the floppy) from the two atlases `atari_st_print.py` draws (`atari_st_print.png`, the computer's top, the monitor's badge and the mouse's back; `atari_st_media.png`, the GEM screens, the title screen and the floppy), both reading the measurements and atlas regions in `atari_st_layout.py`; it borrows `amiga.py`'s geometry helpers and `msx_layout.py`'s `inset()`. `n64.py <images> <out>` builds the Nintendo 64 (`models/systems/n64.glb`: the console and its controller, three-quarter view, a node tree whose `focused` clip drops a cartridge in through the slot's dust flaps, slides POWER on and lights the power light, swings the stick round and presses A, B, START and RESET, and later takes the cartridge out) from the atlas `n64_print.py` draws, both reading the measurements, the controller's traced outline and the atlas regions in `n64_layout.py`. `gamecube.py <images> <out>` builds the GameCube (`models/systems/gc.glb`: an indigo GameCube, its handle and its controller, three-quarter view, a node tree whose `focused` clip presses OPEN and springs the lid up on its hinge, drops a mini disc onto the spindle and shuts the lid, presses POWER and lights the power light, circles the stick, flicks the C stick and presses A and B, and later takes the disc out) from the two atlases `gamecube_print.py` draws (`gamecube_print.png`, the console's top, the front panel and the controller's top; `gamecube_disc.png`, the mini disc's two sides), both reading the measurements, the controller's traced outline and the atlas regions in `gamecube_layout.py`. `threedo.py <images> <out>` builds the 3DO (`models/systems/3do.glb`: a Panasonic FZ-1 and its controller, three-quarter view, a node tree whose `focused` clip presses POWER and lights READY, presses OPEN/CLOSE to slide the tray out, drops a CD into its well and takes it in while ACCESS flickers, rocks the D-pad and presses A, B and C, and later ejects the CD) from the two atlases `threedo_print.py` draws (`threedo_print.png`, the console's top and front and the controller's top; `threedo_disc.png`, the CD's two sides), both reading the measurements and atlas regions in `threedo_layout.py`. `fds.py <images> <out>` builds the Famicom Disk System (`models/systems/fds.glb`: a Famicom with its RAM adapter and both controllers docked, sitting on its Disk System drive, three-quarter view, a node tree whose `focused` clip slides POWER on, slides a disk card into the drive side A up while the operating lamp flickers, presses the eject button, turns the card over in the air and puts it back side B up, and later ejects it) from the atlas `fds_print.py` draws, both reading the measurements and atlas regions in `fds_layout.py`. `pc_engine.py <images> <out>` builds the PC Engine (`models/systems/pcengine.glb`: the white PC Engine, a HuCard and its PI-PD001 pad, three-quarter view, a node tree whose `focused` clip slides the HuCard along its channel into the slot, slides POWER on, presses RUN, rocks the D-pad and presses II and I, and later takes the HuCard out) from the atlas `pc_engine_print.py` draws, both reading the measurements and atlas regions in `pc_engine_layout.py`. `vic20.py <images> <out>` builds the VIC-20 (`models/systems/vic20.glb`: a Commodore VIC-20 in front of `zx_spectrum.py`'s television, READY. on its screen, three-quarter view, a node tree whose `focused` clip switches it off, plugs a cartridge into the expansion port in its back, switches on to the game's title, presses f1, Z, X and the space bar, then switches off, takes the cartridge out and switches on to READY.) from the two atlases `vic20_print.py` draws (`vic20_print.png`, the computer's top, the cartridge's label and the television's front; `vic20_media.png`, the screens), both reading the measurements and atlas regions in `vic20_layout.py`. `neogeo.py <images> <out>` builds the Neo Geo (`models/systems/neogeo.glb`: a Neo Geo AES and its arcade stick in front of its left end, three-quarter view, a node tree whose `focused` clip drops a cartridge into the slot in the raised racetrack, slides POWER on and presses START, rolls the lever from down to forward and presses A twice (fireballs), then a B, C and D combo, presses RESET, and later takes the cartridge out) from the two atlases `neogeo_print.py` draws (`neogeo_print.png`, the console's top and front; `neogeo_pad.png`, the stick's top and the cartridge's label), both reading the measurements and atlas regions in `neogeo_layout.py`. `colecovision.py <images> <out>` builds the ColecoVision (`models/systems/colecovision.glb`: the console, one controller in its bay and the other in front, three-quarter view, a node tree whose `focused` clip drops a cartridge into the slot behind INSERT CARTRIDGE HERE, slides the power switch ON, presses key 1 for the skill level, circles the knob and presses the fire button, presses RESET, and later takes the cartridge out) from the atlas `colecovision_print.py` draws, both reading the measurements and atlas regions in `colecovision_layout.py`. `flash.py <images> <out>` builds Flash's card (`models/systems/flash.glb`: Flash has no hardware, so a silver 2000s LCD monitor on its foot, a browser filling its screen with a Flash game in the page, and a mouse, three-quarter view, a node tree whose `focused` clip fills the preloader under the Flash Player's logo, brings up Odyssey's title, clicks PLAY, plays two frames in turn, and right-clicks the menu ending in About Adobe Flash Player) from the atlas `flash_print.py` draws, both reading the measurements, the traced Flash logo and the atlas regions in `flash_layout.py`. `mega_cd.py <images> <out>` builds the Mega-CD (`models/systems/segacd.glb`: the first Mega-CD with `mega_drive.py`'s Mega Drive, built by its `build()`, sitting on it, three-quarter view, a node tree whose `focused` clip slides the tray out, drops a CD in, takes it in while READY lights and ACCESS flickers, and later ejects it) from the atlas `mega_cd_print.py` draws and the Mega Drive's own (`mega_drive_print.py`, into the same folder), reading the measurements and atlas regions in `mega_cd_layout.py`. `cd32.py <images> <out>` builds the Amiga CD32 (`models/systems/amigacd32.glb`: the console and its pad, three-quarter view, a node tree whose `focused` clip swings the lid up on its hinge, drops a CD into the well and shuts it, flickers the CD light, rocks the D-pad and presses the coloured buttons, and later takes the CD out) from the atlas `cd32_print.py` draws, both reading the measurements, the pad's traced outline and the atlas regions in `cd32_layout.py`. `naomi.py <images> <out>` builds NAOMI's card (`models/systems/naomi.glb`: NAOMI is an arcade board, so a white Sega candy cabinet with NAOMI on its marquee, three-quarter view, a node tree whose `focused` clip drops a coin into the slot, flashes and presses 1P START, plays the game on the stick and buttons, and goes back to NAOMI's logo) from the two atlases `naomi_print.py` draws (`naomi_print.png`, the marquee, the speaker panel, the control panel and the coin doors; `naomi_media.png`, the screens), both reading the measurements and atlas regions in `naomi_layout.py`. `apple2gs.py <images> <out>` builds the Apple IIGS (`models/systems/apple2gs.glb`: the IIGS, its AppleColor RGB monitor on it, its 3.5 drive beside it, its keyboard and mouse in front, three-quarter view, a node tree whose `focused` clip slides a 3.5-inch disk into the drive, boots GS/OS to Odyssey's title, presses RETURN and plays, then presses eject and takes the disk away, back to Check startup device!) from the two atlases `apple2gs_print.py` draws (`apple2gs_print.png`, the keyboard from above and the fronts; `apple2gs_media.png`, the screens and the disk), both reading the measurements and atlas regions in `apple2gs_layout.py`. `amstrad_cpc.py <images> <out>` builds the Amstrad CPC (`models/systems/amstradcpc.glb`: a CPC 464 and its CTM644 monitor, a node tree whose `focused` clip loads a game from tape: the datacorder's door opens, a cassette slides in, PLAY, and eject; its glass is a `video` material) from the atlases `amstrad_cpc_print.py` draws, and `amstrad_cpc_video.py <out>` draws the screen through the clip, frame for frame, and encodes it with ffmpeg (`videos/amstradcpc.mp4`, the card's video: docs/THEMING.md section 4, \"Card videos\"); all three read the measurements and the clip's timing in `amstrad_cpc_layout.py`. `retro_pc.py <images> <set> <out>` builds DOS's, Windows 3.x's and Windows 9x's sets (`models/systems/dos.glb`, `windows3x.glb`, `windows9x.glb`: a 386 or 486 desktop with a CRT on it, a mini tower with a CRT beside it; no clips) from the printing and screens `retro_pc_print.py` draws, both reading the sets' measurements and parts in `retro_pc_layout.py`. `power_star.py` builds Favourites' Power Star (`models/systems/favourites.glb`: a plain star at rest, its `focused` clip opening its eyes) and `alarm_clock.py` Recently played's alarm clock (`models/systems/recently_played.glb`: matched to a capture of the Dreamcast menu, its `focused` clip a one-second rocking roll with hops, scissoring hands and bells pulsing in turn; drawn at 85% of a card by two invisible specks that make its depth its largest side), neither with inputs, both keying their clips through `clip.py` (easing curves and a per-frame keyer) |\n | `godot/src/Tools/`, `godot/scenes/tools/` | The `[Tool]` generator for the built-in models; its output is `godot/themes/memory-card/models/{templates,systems}/*.glb`, `godot/themes/slab/models/systems/slab.glb` and `godot/themes/classic-view/models/systems/classic.glb`, plus the test theme's `tests/themes/slot-showcase/models/` and the sample theme's `samples/themes/retro-tv/models/`. Excluded from exports. |\n \n ## Commands (PowerShell, repo root)"
        },
        {
          "sha": "5be8b679f4deb25f47ff09ef7096da94850c08a3",
          "filename": "Directory.Build.props",
          "status": "modified",
          "additions": 1,
          "deletions": 1,
          "changes": 2,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/Directory.Build.props",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/Directory.Build.props",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/Directory.Build.props?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -1,7 +1,7 @@\n <Project>\n   <!-- Imported by every project in the repo, including the Godot project (via Microsoft.Common.props). -->\n   <PropertyGroup>\n-    <Version>0.18.0</Version>\n+    <Version>0.19.0</Version>\n     <Nullable>enable</Nullable>\n   </PropertyGroup>\n "
        },
        {
          "sha": "5286ae76d5be06fc28ffb2d945414366a168eddb",
          "filename": "docs/ARCHITECTURE.md",
          "status": "modified",
          "additions": 79,
          "deletions": 1,
          "changes": 80,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/docs%2FARCHITECTURE.md",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/docs%2FARCHITECTURE.md",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/docs%2FARCHITECTURE.md?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "8fb76216786e8014b96691ceb1501db7f5066128",
          "filename": "godot/project.godot",
          "status": "modified",
          "additions": 1,
          "deletions": 1,
          "changes": 2,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fproject.godot",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fproject.godot",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fproject.godot?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -11,7 +11,7 @@ config_version=5\n [application]\n \n config/name=\"Odyssey Launcher\"\n-config/version=\"0.18.0\"\n+config/version=\"0.19.0\"\n run/main_scene=\"res://scenes/main.tscn\"\n config/features=PackedStringArray(\"4.7\", \"C#\", \"Mobile\")\n boot_splash/bg_color=Color(0.015686275, 0.015686275, 0.047058824, 1)"
        },
        {
          "sha": "591a92af29253ff9b9bb6bdbd98fcd57211aa5e7",
          "filename": "godot/themes/console/models/systems/3do.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2F3do.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2F3do.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2F3do.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "b33ab338977a12166b4be303bf1ffdf0561b327c",
          "filename": "godot/themes/console/models/systems/amiga.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Famiga.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Famiga.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Famiga.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "8a9b466020a9320b71ae88cf65d63a4c1fb305b5",
          "filename": "godot/themes/console/models/systems/amigacd32.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Famigacd32.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Famigacd32.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Famigacd32.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "0f81924b53f1a3a191e6b6162559b65d274d1fda",
          "filename": "godot/themes/console/models/systems/apple2.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fapple2.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fapple2.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fapple2.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "606e80629d4fc955302a8d34ef2d94d22c54fcbb",
          "filename": "godot/themes/console/models/systems/apple2gs.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fapple2gs.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fapple2gs.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fapple2gs.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "ab2256c5a1ba9db552c7ee6a5e4629979cd49c63",
          "filename": "godot/themes/console/models/systems/archimedes.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Farchimedes.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Farchimedes.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Farchimedes.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "d7ae66bec985c8af847ad79504cc28b55367a434",
          "filename": "godot/themes/console/models/systems/atari2600.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fatari2600.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fatari2600.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fatari2600.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "b7272d494d8eb1c1d9383277d5f3ded90dd0d46a",
          "filename": "godot/themes/console/models/systems/atari5200.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fatari5200.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fatari5200.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fatari5200.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "c96a350a6a1b72de2a1e71158ca8ded580471e6a",
          "filename": "godot/themes/console/models/systems/atari7800.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fatari7800.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fatari7800.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fatari7800.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "47ca6ff0962acacc738c47ebee8b55a50e359da0",
          "filename": "godot/themes/console/models/systems/atari800.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fatari800.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fatari800.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fatari800.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "18f41b4305e9c582024045c69e1df3d7f417b4ca",
          "filename": "godot/themes/console/models/systems/atarijaguar.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fatarijaguar.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fatarijaguar.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fatarijaguar.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "d36aa278de6edeced64fef7f92c620ddff3e80d0",
          "filename": "godot/themes/console/models/systems/atarijaguarcd.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fatarijaguarcd.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fatarijaguarcd.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fatarijaguarcd.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "4b336e2531626828f422e69a80b3368db3acc345",
          "filename": "godot/themes/console/models/systems/atarilynx.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fatarilynx.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fatarilynx.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fatarilynx.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "2059c78e0a4945555763ac9f90d99c23b1743474",
          "filename": "godot/themes/console/models/systems/atarist.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fatarist.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fatarist.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fatarist.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "0762820440e69fcd815c89b00b314c075e035d09",
          "filename": "godot/themes/console/models/systems/atarixe.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fatarixe.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fatarixe.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fatarixe.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "2b7ed537dc9023cf4e4fc2d895221f0cac5938a9",
          "filename": "godot/themes/console/models/systems/bbcmicro.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fbbcmicro.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fbbcmicro.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fbbcmicro.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "9715c3128295676c0c7114ef31cb7f140a80ad94",
          "filename": "godot/themes/console/models/systems/cdimono1.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fcdimono1.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fcdimono1.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fcdimono1.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "bc6cfda558590c07b82de3ed6967efa4671963fc",
          "filename": "godot/themes/console/models/systems/cdtv.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fcdtv.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fcdtv.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fcdtv.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "76af7af5ee206bb064a01a47423ef5512317803b",
          "filename": "godot/themes/console/models/systems/channelf.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fchannelf.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fchannelf.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fchannelf.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "44d286f4a7012251a89a913655f5c22edeccc8d2",
          "filename": "godot/themes/console/models/systems/colecovision.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fcolecovision.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fcolecovision.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fcolecovision.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "8e14368b1f1453a372d32441762e03c4d40bb2e2",
          "filename": "godot/themes/console/models/systems/desktop.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fdesktop.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fdesktop.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fdesktop.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "8d7c1628afde3d566b76addb8d8dab3a5e6a22fa",
          "filename": "godot/themes/console/models/systems/dragon32.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fdragon32.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fdragon32.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fdragon32.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "806a7e9555fa478e1a5a448f8f3100cbb1f4760e",
          "filename": "godot/themes/console/models/systems/dreamcast.glb",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fdreamcast.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fdreamcast.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fdreamcast.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "43801d47bd514be4fcf8fb0b3f3766d8fa6e66ff",
          "filename": "godot/themes/console/models/systems/electron.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Felectron.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Felectron.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Felectron.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "b427e0553eee40ff41d2b78e02da69b8c3ce054a",
          "filename": "godot/themes/console/models/systems/fds.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Ffds.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Ffds.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Ffds.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "3e628bf035498700658ce0a9b9c4a1695ab9b8eb",
          "filename": "godot/themes/console/models/systems/flash.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fflash.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fflash.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fflash.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "3019b549fbb7f02bf88d7e8aca4dfb47acd4d36c",
          "filename": "godot/themes/console/models/systems/gameandwatch.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fgameandwatch.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fgameandwatch.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fgameandwatch.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "ed5c6717fbf60c7ea92d5b8af97f096a1f0fb288",
          "filename": "godot/themes/console/models/systems/gc.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fgc.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fgc.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fgc.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "e7f09c041812d681b20980df3b97a4ac6e8a0298",
          "filename": "godot/themes/console/models/systems/gx4000.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fgx4000.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fgx4000.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fgx4000.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "7513549e15f27c1014c8805afd4bc90b88fe30dd",
          "filename": "godot/themes/console/models/systems/intellivision.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fintellivision.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fintellivision.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fintellivision.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "2ad52d1f9ca0b4199ccccd3f3003f038b1dc9d61",
          "filename": "godot/themes/console/models/systems/macintosh.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fmacintosh.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fmacintosh.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fmacintosh.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "c3dda11891b94ac093dc2c871173bd4aa96cce27",
          "filename": "godot/themes/console/models/systems/mastersystem.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fmastersystem.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fmastersystem.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fmastersystem.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "e071ec5206abb0f2fa81eebae4b8c7651ffa3c0e",
          "filename": "godot/themes/console/models/systems/megadrive.glb",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fmegadrive.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fmegadrive.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fmegadrive.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "ea2d9e25dddbcd6dffbf7f9576a94fde6e168372",
          "filename": "godot/themes/console/models/systems/msx1.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fmsx1.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fmsx1.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fmsx1.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "6914c973d7c081954d73a477d30cb7006f97dbbf",
          "filename": "godot/themes/console/models/systems/msx2.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fmsx2.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fmsx2.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fmsx2.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "880e67e08ca2e3d25400d038b96ca9595d41f775",
          "filename": "godot/themes/console/models/systems/n3ds.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fn3ds.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fn3ds.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fn3ds.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "54ce41938be69a51af49d2bd67ba6f8821f7b4a4",
          "filename": "godot/themes/console/models/systems/n64.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fn64.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fn64.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fn64.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "9bef99ffcbbddba63f480ed4124800c2d415fbec",
          "filename": "godot/themes/console/models/systems/n64dd.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fn64dd.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fn64dd.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fn64dd.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "9c94d2e487105feb309c193d8927243fe99e770f",
          "filename": "godot/themes/console/models/systems/naomi.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fnaomi.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fnaomi.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fnaomi.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "78638d5288b1ea89409e9f77472d3d626bfb137f",
          "filename": "godot/themes/console/models/systems/nds.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fnds.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fnds.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fnds.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "a2b2d074cbcac2c27ac78da6ec0bc17b0960ef20",
          "filename": "godot/themes/console/models/systems/neogeo.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fneogeo.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fneogeo.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fneogeo.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "c63e15a6597f4868ac46f285dde0e526891ff489",
          "filename": "godot/themes/console/models/systems/neogeocd.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fneogeocd.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fneogeocd.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fneogeocd.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "f913a54448f13cdf3fd7369b7a47cfe633d1d9f1",
          "filename": "godot/themes/console/models/systems/nes.glb",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fnes.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fnes.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fnes.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "d5c519747e32a7d0f24c61df851c19c5e6ef1dc9",
          "filename": "godot/themes/console/models/systems/ngp.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fngp.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fngp.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fngp.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "f6fec9bb2fbe733443c4ad9c0c29584d05c678c4",
          "filename": "godot/themes/console/models/systems/ngpc.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fngpc.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fngpc.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fngpc.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "33c77cf1add60ff38e2d16e76706b0ad197b340a",
          "filename": "godot/themes/console/models/systems/odyssey2.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fodyssey2.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fodyssey2.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fodyssey2.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "134a1cf8bcd44e0a374eb61b5b51ff17a401be85",
          "filename": "godot/themes/console/models/systems/oric.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Foric.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Foric.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Foric.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "b908d6ab1ed38e4d6d4b5bc028824e0cb8d8c269",
          "filename": "godot/themes/console/models/systems/pc98.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fpc98.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fpc98.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fpc98.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "cf9f81efe0cead0a2ff631832244b159866eaecb",
          "filename": "godot/themes/console/models/systems/pcengine.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fpcengine.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fpcengine.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fpcengine.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "fe43581421835cf71f1a6dd1467ba4643f471771",
          "filename": "godot/themes/console/models/systems/pcenginecd.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fpcenginecd.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fpcenginecd.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fpcenginecd.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "6e33fe9561a03a2869630595955790202e4dc569",
          "filename": "godot/themes/console/models/systems/pcfx.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fpcfx.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fpcfx.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fpcfx.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "401988c11ef5e89f6e3fae13d8a643e22eba8c93",
          "filename": "godot/themes/console/models/systems/pico8.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fpico8.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fpico8.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fpico8.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "1513fb0d290427f112b4bd0be69e15595473c318",
          "filename": "godot/themes/console/models/systems/plus4.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fplus4.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fplus4.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fplus4.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "2ea64d3c51cfed24c26a6fa4a90904657ac9b8e8",
          "filename": "godot/themes/console/models/systems/pokemini.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fpokemini.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fpokemini.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fpokemini.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "ee0ae1a7d9ddd45fffa1eb920257ab6acf54a858",
          "filename": "godot/themes/console/models/systems/ps2.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fps2.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fps2.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fps2.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "29a2f4809c5a86fcc55678ff1f806ad52d55e7e9",
          "filename": "godot/themes/console/models/systems/ps3.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fps3.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fps3.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fps3.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "e5ff6da3ceeb16533e2154ea976142cd9bbad982",
          "filename": "godot/themes/console/models/systems/ps4.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fps4.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fps4.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fps4.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "da9b76b4bcbbe54996218227f61c9b164a0b671f",
          "filename": "godot/themes/console/models/systems/ps5.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fps5.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fps5.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fps5.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "f9028a5d4db3054de284e884ef202ac7638e1b9f",
          "filename": "godot/themes/console/models/systems/psx.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fpsx.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fpsx.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fpsx.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "af7912419a743a6228484ae5a121cf0f5246c320",
          "filename": "godot/themes/console/models/systems/satellaview.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fsatellaview.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fsatellaview.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fsatellaview.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "0b2c4140dc6b062ab99f16cdff9e8625348c6fc0",
          "filename": "godot/themes/console/models/systems/saturn.glb",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fsaturn.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fsaturn.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fsaturn.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "d7690b1d6ec368e9818f01d284a60450d63bcb27",
          "filename": "godot/themes/console/models/systems/sega32x.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fsega32x.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fsega32x.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fsega32x.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "5442d96d8ac528029d4aa0d33a82fd2e88d89210",
          "filename": "godot/themes/console/models/systems/segacd.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fsegacd.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fsegacd.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fsegacd.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "79d863204a1dc4bfa47533bc1417d7b3f60dd731",
          "filename": "godot/themes/console/models/systems/sgb.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fsgb.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fsgb.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fsgb.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "22da218cbd47b03a72b81f24e547260f05318891",
          "filename": "godot/themes/console/models/systems/snes.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fsnes.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fsnes.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fsnes.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "f414a75331460a6c456d22394cb52544856dd21c",
          "filename": "godot/themes/console/models/systems/supervision.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fsupervision.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fsupervision.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fsupervision.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "1d925382a0bc90ce0f6353a4e29efb0b17ffec69",
          "filename": "godot/themes/console/models/systems/ti99.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fti99.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fti99.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fti99.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "3046bcfdaac47918809a27d56796ea3a9253484a",
          "filename": "godot/themes/console/models/systems/vectrex.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fvectrex.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fvectrex.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fvectrex.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "1e39bfb76856ca1cc4d0003effe74af2f7845a6e",
          "filename": "godot/themes/console/models/systems/vic20.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fvic20.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fvic20.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fvic20.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "85e26e0e2091e45cf67d5960cd0d930a2adebb66",
          "filename": "godot/themes/console/models/systems/virtualboy.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fvirtualboy.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fvirtualboy.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fvirtualboy.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "5e52bb472115ecfd75362009a042b3b4270e72ca",
          "filename": "godot/themes/console/models/systems/wii.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fwii.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fwii.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fwii.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "883d3c569cec06f00e1f8914065c42245f6cd6df",
          "filename": "godot/themes/console/models/systems/wiiu.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fwiiu.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fwiiu.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fwiiu.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "83bcb8d02d8ca212eb34296fbba66b44ae73f4c6",
          "filename": "godot/themes/console/models/systems/xbox.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fxbox.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fxbox.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fxbox.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "18041e2ab5867528174c5c5a667af0d46a2b31a6",
          "filename": "godot/themes/console/models/systems/xbox360.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fxbox360.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fxbox360.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fxbox360.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "9d1271e4b65ed12c123233807a5edda9b631aa89",
          "filename": "godot/themes/console/models/systems/zx81.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fzx81.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fzx81.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fzx81.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "b77703741995920184fc7b8f7664906bdd0543aa",
          "filename": "godot/themes/console/models/systems/zxspectrum.glb",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fzxspectrum.glb",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fzxspectrum.glb",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Fmodels%2Fsystems%2Fzxspectrum.glb?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "c5d612ab12e227fc2392429488823cca3f359018",
          "filename": "godot/themes/console/theme.toml",
          "status": "modified",
          "additions": 224,
          "deletions": 4,
          "changes": 228,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Ftheme.toml",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/godot%2Fthemes%2Fconsole%2Ftheme.toml",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/godot%2Fthemes%2Fconsole%2Ftheme.toml?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -57,15 +57,42 @@ model = \"models/systems/wonderswan.glb\"       # built by tools/console-models/wo\n [systems.wonderswancolor]\n model = \"models/systems/wonderswancolor.glb\"  # built by tools/console-models/wonderswan.py, turned to a three-quarter view; its focused clip swoops a cartridge in, then leaps onto its end in a full twirl\n \n+[systems.nds]\n+model = \"models/systems/nds.glb\"                # built by tools/console-models/nintendo_ds.py: a DS Lite open, turned to a three-quarter view; its focused clip slides a game card into slot-1, lights both screens, and the stylus taps TOUCH TO START\n+\n+[systems.n3ds]\n+model = \"models/systems/n3ds.glb\"               # built by tools/console-models/nintendo_ds.py: the first 3DS in Aqua Blue, open, turned to a three-quarter view; its focused clip slides a game card in, lights the HOME Menu, and the stylus taps TOUCH TO START\n+\n+[systems.virtualboy]\n+model = \"models/systems/virtualboy.glb\"        # built by tools/console-models/virtualboy.py: the Virtual Boy on its stand, its controller standing on its grips beside it; its focused clip turns the unit round on its stand to show its eyes, where a made-up game plays in red, then turns it back\n+\n+[systems.wii]\n+model = \"models/systems/wii.glb\"                # built by tools/console-models/wii.py: the Wii on its stand, its remote leaning on it; its focused clip slides a disc in, the slot glows, the remote rumbles\n+\n+[systems.pokemini]\n+model = \"models/systems/pokemini.glb\"          # built by tools/console-models/pokemini.py: the Pok\u00e9mon mini in Wooper Blue, its clock on the LCD; its focused clip drops a cartridge into the top of its back and plays a made-up game on the D-pad and A, shakes it and rumbles, then takes the cartridge out\n+\n [systems.psp]\n model = \"models/systems/psp.glb\"              # built by tools/console-models/psp.py, turned to a three-quarter view; its focused clip turns it round, rolls a UMD into its door, and turns back to the XMB and the game\n \n+[systems.mastersystem]\n+model = \"models/systems/mastersystem.glb\"      # built by tools/console-models/master_system.py: the first Master System and its Control Pad; its focused clip drops a cartridge in through the dust flap, presses POWER, plays the pad and PAUSE, and later takes the cartridge out\n+\n+[systems.segacd]\n+model = \"models/systems/segacd.glb\"            # built by tools/console-models/mega_cd.py: the first Mega-CD with the Mega Drive (mega_drive.py's) on it; its focused clip slides the tray out, drops a CD in, takes it in while ACCESS flickers, and later ejects it\n+\n [systems.megadrive]\n-model = \"models/systems/megadrive.glb\"        # built by tools/console-models/mega_drive.py, turned to a three-quarter view; its focused clip inserts a cartridge\n+model = \"models/systems/megadrive.glb\"        # built by tools/console-models/mega_drive.py: the first Mega Drive and its control pad; its focused clip drops a cartridge into the slot, slides POWER on and lights the POWER light, plays on the pad, presses RESET, then switches off and takes the cartridge out\n+\n+[systems.sega32x]\n+model = \"models/systems/sega32x.glb\"           # built by tools/console-models/sega32x.py: the Mega Drive 32X plugged into the first Mega Drive (mega_drive.py's); its focused clip hops it off and back on with a turn in the air, then drops a cartridge into its slot through the dust flaps, and later takes it out\n \n [systems.windows]\n model = \"models/systems/windows.glb\"          # built by tools/console-models/windows_pc.py: a gaming PC with a glass side and RGB fans, and its monitor\n \n+[systems.desktop]\n+model = \"models/systems/desktop.glb\"           # built by tools/console-models/desktop.py: Desktop Apps has no hardware, so an unbranded laptop showing a made-up desktop of shortcuts; its focused clip double-clicks the game's shortcut, plays it in its window, then closes it\n+\n [systems.dos]\n model = \"models/systems/dos.glb\"              # built by tools/console-models/retro_pc.py: a late-80s 386 desktop with a 14-inch VGA monitor on it\n \n@@ -75,24 +102,217 @@ model = \"models/systems/windows3x.glb\"        # built by tools/console-models/re\n [systems.windows9x]\n model = \"models/systems/windows9x.glb\"        # built by tools/console-models/retro_pc.py: a late-90s mini tower with a 15-inch monitor beside it\n \n+[systems.wiiu]\n+model = \"models/systems/wiiu.glb\"              # built by tools/console-models/wiiu.py: the Wii U in black and its GamePad in its cradle, the Wii U Menu on its screen; its focused clip slides a disc in, starts it from the menu and plays on the GamePad, then ejects it\n+\n [systems.switch]\n model = \"models/systems/switch.glb\"           # built by tools/console-models/switch.py, turned to a three-quarter view; its focused clip slides the right Joy-Con up its rail and back\n \n+[systems.3do]\n+model = \"models/systems/3do.glb\"               # built by tools/console-models/threedo.py: a Panasonic FZ-1 and its controller; its focused clip slides the tray out, drops a CD in, takes it in while ACCESS flickers, plays the pad, and later ejects the CD\n+\n+[systems.vectrex]\n+model = \"models/systems/vectrex.glb\"           # built by tools/console-models/vectrex.py: MB's Vectrex with its controller in front of it, the power-on screen drawn in lines; its focused clip slides a cartridge into its side, settles an overlay over the tube and plays a made-up vector game while the joystick rocks, then takes them out again\n+\n+[systems.atarist]\n+model = \"models/systems/atarist.glb\"           # built by tools/console-models/atari_st.py: a 520STFM, its SC1224 showing GEM's desktop, and its mouse; its focused clip slides a floppy in, double-clicks its way to the game, and ejects it\n+\n+[systems.channelf]\n+model = \"models/systems/channelf.glb\"          # built by tools/console-models/channelf.py: the first Fairchild Channel F, walnut and black, and its two controllers; its focused clip slides a Videocart into the slot past its door, presses RESET and 1 and plays on a controller's knob, then ejects the Videocart\n+\n+[systems.atari2600]\n+model = \"models/systems/atari2600.glb\"         # built by tools/console-models/atari_2600.py: the four-switch woody and its CX40 joystick; its focused clip drops a cartridge in, flips POWER, flicks SELECT and RESET, and waggles the joystick\n+\n+[systems.atari5200]\n+model = \"models/systems/atari5200.glb\"         # built by tools/console-models/atari5200.py: an Atari 5200 (four ports) and its controller; its focused clip drops a cartridge in, switches on, presses START and plays on the stick and fire button, then takes the cartridge out\n+\n+[systems.atari7800]\n+model = \"models/systems/atari7800.glb\"         # built by tools/console-models/atari7800.py: an Atari 7800 ProSystem and its ProLine joystick; its focused clip drops a cartridge in, switches on, presses RESET and plays on the joystick, then takes the cartridge out\n+\n+[systems.atari800]\n+model = \"models/systems/atari800.glb\"          # built by tools/console-models/atari800.py: an Atari 800 and its CX40 joystick; its focused clip lifts the lid, drops a cartridge into the left slot and shuts the lid, presses OPTION and START and plays on the joystick, then takes the cartridge out\n+\n+[systems.atarixe]\n+model = \"models/systems/atarixe.glb\"           # built by tools/console-models/xegs.py: the XE Game System, its keyboard and its CX40 joystick; its focused clip drops a cartridge into the slot, presses POWER (its LED lights) and START and plays on the joystick, then switches off and takes the cartridge out\n+\n+[systems.atarijaguar]\n+model = \"models/systems/atarijaguar.glb\"       # built by tools/console-models/jaguar.py: an Atari Jaguar and its controller; its focused clip drops a cartridge in, presses the power button and plays on the pad, then takes the cartridge out\n+\n+[systems.atarijaguarcd]\n+model = \"models/systems/atarijaguarcd.glb\"     # built by tools/console-models/jaguar.py: the Jaguar with the Jaguar CD on it, and its controller; its focused clip opens the lid, drops a CD in, presses the power button and plays, then takes the CD out\n+\n+[systems.atarilynx]\n+model = \"models/systems/atarilynx.glb\"         # built by tools/console-models/lynx.py: an Atari Lynx II; its focused clip drops a game card into its back, plays, then FLIPs the picture and turns over to play the other way up, and back, and takes the card out\n+\n+[systems.cdimono1]\n+model = \"models/systems/cdimono1.glb\"          # built by tools/console-models/cdi.py: a Philips CD-i 220 and its gamepad; its focused clip opens the tray, drops a disc in, reads it and plays on the gamepad, then takes the disc out\n+\n+[systems.colecovision]\n+model = \"models/systems/colecovision.glb\"      # built by tools/console-models/colecovision.py: the ColecoVision, one controller in its bay and one in front; its focused clip drops a cartridge in, switches on, picks skill 1 on the keypad and plays the knob and fire button, and later takes the cartridge out\n+\n+[systems.intellivision]\n+model = \"models/systems/intellivision.glb\"     # built by tools/console-models/intellivision.py: Mattel's Intellivision, its controllers docked in its top; its focused clip slides a cartridge into its right end, switches on and lifts the right controller out to play, then puts it back\n+\n+[systems.plus4]\n+model = \"models/systems/plus4.glb\"             # built by tools/console-models/plus4.py: a Commodore Plus/4 and its 1531 Datassette in front of the ZX Spectrum's television; its focused clip puts a cassette in the Datassette, types LOAD, presses PLAY, finds and loads the game and plays it on the cursor keys, then takes the cassette out\n+\n [systems.c64]\n model = \"models/systems/c64.glb\"              # built by tools/console-models/commodore_64.py\n \n+[systems.odyssey2]\n+model = \"models/systems/odyssey2.glb\"          # built by tools/console-models/odyssey2.py: a Magnavox Odyssey\u00b2 and its two joysticks; its focused clip drops a cartridge in, presses POWER and plays on the joystick, then takes the cartridge out\n+\n [systems.amstradcpc]\n model = \"models/systems/amstradcpc.glb\"       # built by tools/console-models/amstrad_cpc.py: a CPC 464 and its CTM644 monitor; its focused clip loads a game from tape\n video = \"videos/amstradcpc.mp4\"               # drawn by tools/console-models/amstrad_cpc_video.py: the screen through the clip, played on the glass's video material\n \n+[systems.gx4000]\n+model = \"models/systems/gx4000.glb\"            # built by tools/console-models/gx4000.py: the Amstrad GX4000 and its controller; its focused clip drops a cartridge into the slot through its flaps, switches on and plays on the controller, presses PAUSE, then switches off and takes the cartridge out\n+\n+[systems.cdtv]\n+model = \"models/systems/cdtv.glb\"              # built by tools/console-models/cdtv.py: the Commodore CDTV and its remote controller; its focused clip slides a caddy into the slot, lights the LEDs, counts the track's time on the display and plays on the remote, then ejects the caddy\n+\n+[systems.amiga]\n+model = \"models/systems/amiga.glb\"            # built by tools/console-models/amiga.py: an Amiga 500 and its 1084S monitor, Kickstart 1.3 asking for a disk; its focused clip loads a game from a floppy, then resets with Ctrl and both Amiga keys\n+\n+[systems.xbox]\n+model = \"models/systems/xbox.glb\"              # built by tools/console-models/xbox.py: the original Xbox and its Controller S; its focused clip switches on, slides the tray out, drops a DVD on it and plays on the pad, then takes the DVD out\n+\n+[systems.xbox360]\n+model = \"models/systems/xbox360.glb\"           # built by tools/console-models/xbox360.py: the first Xbox 360 in white, lying flat, its controller on its top; its focused clip lights the Ring of Light, slides the tray out, drops a disc on it and plays on the controller, then takes the disc out\n+\n+[systems.bbcmicro]\n+model = \"models/systems/bbcmicro.glb\"          # built by tools/console-models/bbcmicro.py: the BBC Micro Model B, a twin disk drive on it and a Microvitec Cub on the drive; its focused clip slides a floppy into drive 0, boots it with SHIFT and BREAK to a teletext title, and plays\n+\n+[systems.electron]\n+model = \"models/systems/electron.glb\"          # built by tools/console-models/electron.py: an Acorn Electron with a Plus 1 on its back, in front of the ZX Spectrum's television; its focused clip drops a ROM cartridge into the Plus 1, starts it with CTRL and BREAK and plays\n+\n+[systems.ti99]\n+model = \"models/systems/ti99.glb\"              # built by tools/console-models/ti99.py: the silver and black TI-99/4A and its two joysticks; its focused clip slides a command module into the slot, the power light comes on, presses 2 and plays on the joystick, then slides the module out\n+\n+[systems.macintosh]\n+model = \"models/systems/macintosh.glb\"         # built by tools/console-models/macintosh.py: the original Macintosh, its keyboard and its mouse; its focused clip slides a floppy into the drive, boots from the question mark through the happy Mac and Welcome to Macintosh. to the game, plays with the mouse, then ejects the floppy\n+\n+[systems.archimedes]\n+model = \"models/systems/archimedes.glb\"        # built by tools/console-models/archimedes.py: a BBC A3000, its three-button mouse and an Acorn colour monitor; its focused clip slides a floppy into the drive, opens its filer window and !Odyssey with the mouse and plays the game on the cursor keys, then ejects the floppy\n+\n+[systems.dragon32]\n+model = \"models/systems/dragon32.glb\"          # built by tools/console-models/dragon32.py: a Dragon 32 in front of the ZX Spectrum's television; its focused clip slides a cartridge into the slot in its right side, Odyssey starts and is played on the arrow keys, then the cartridge comes out and the Dragon starts again\n+\n+[systems.oric]\n+model = \"models/systems/oric.glb\"              # built by tools/console-models/oric.py: an Oric Atmos and a cassette recorder in front of the ZX Spectrum's television; its focused clip puts a cassette in the recorder, types CLOAD\"\" and presses PLAY, loads the game and plays it on the arrow keys, then takes the cassette out\n+\n+[systems.zxspectrum]\n+model = \"models/systems/zxspectrum.glb\"       # built by tools/console-models/zx_spectrum.py: a ZX Spectrum 48K and a portable television; its focused clip types LOAD \"\" and loads a game from tape, stripes and all\n+\n+[systems.vic20]\n+model = \"models/systems/vic20.glb\"             # built by tools/console-models/vic20.py: a Commodore VIC-20 and the ZX Spectrum's portable television, READY. on the screen; its focused clip switches off, plugs a cartridge into the expansion port, switches on to the game and plays it, then takes it out\n+\n+[systems.zx81]\n+model = \"models/systems/zx81.glb\"              # built by tools/console-models/zx81.py: a ZX81 and the ZX Spectrum's portable television; its focused clip types LOAD \"\" and loads a game through its flickering bands\n+\n+# The generic MSX shares MSX1's card.\n+[systems.msx]\n+model = \"models/systems/msx1.glb\"\n+\n+[systems.msx1]\n+model = \"models/systems/msx1.glb\"              # built by tools/console-models/msx.py: a Philips VG-8020; its focused clip flips the lid back, drops a cartridge in, switches on, and plays the cursor keys\n+\n+[systems.msx2]\n+model = \"models/systems/msx2.glb\"              # built by tools/console-models/msx2.py: a Philips VG-8235, the MSX2 with its 3.5 drive in its right end; its focused clip slides a disk into the drive, flickers FDD BUSY, plays the cursor keys, the space bar and CAPS, then ejects the disk\n+\n+[systems.flash]\n+model = \"models/systems/flash.glb\"             # built by tools/console-models/flash.py: Flash has no hardware, so a 2000s LCD monitor playing a Flash game in a browser, and its mouse; its focused clip fills the preloader, clicks PLAY, plays, and right-clicks the Flash Player's menu\n+\n+[systems.pico8]\n+model = \"models/systems/pico8.glb\"             # built by tools/console-models/pico8.py: PICO-8 has no hardware, so its cartridge made solid, its label a screen; its focused clip hops it round once and boots its label into a game\n+\n+[systems.amigacd32]\n+model = \"models/systems/amigacd32.glb\"         # built by tools/console-models/cd32.py: the Amiga CD32 and its pad; its focused clip swings the lid up, drops a CD in, plays the pad's coloured buttons, and later takes the CD out\n+\n+[systems.apple2]\n+model = \"models/systems/apple2.glb\"            # built by tools/console-models/apple2.py: an Apple IIe, two Disk II drives and a Monitor II; its focused clip boots a game from a floppy, its title screen arriving in hi-res's line order, then resets\n+\n+[systems.apple2gs]\n+model = \"models/systems/apple2gs.glb\"          # built by tools/console-models/apple2gs.py: an Apple IIGS, its AppleColor RGB monitor, 3.5 drive, keyboard and mouse; its focused clip slides a disk in, boots GS/OS to a game's title, presses RETURN and plays, then ejects the disk\n+\n+[systems.fds]\n+model = \"models/systems/fds.glb\"               # built by tools/console-models/fds.py: a Famicom with its RAM adapter on its Disk System drive; its focused clip slides a disk card in, ejects it, turns it over to side B and puts it back\n+\n+[systems.neogeo]\n+model = \"models/systems/neogeo.glb\"            # built by tools/console-models/neogeo.py: a Neo Geo AES and its arcade stick; its focused clip drops a cartridge in, switches on, throws two fireballs and a combo on the stick, and later takes the cartridge out\n+\n+[systems.neogeocd]\n+model = \"models/systems/neogeocd.glb\"          # built by tools/console-models/neogeocd.py: a Neo Geo CD top-loader and its controller; its focused clip lifts the lid, drops a CD in, flickers the access light through the long load, plays on the pad, then takes the CD out\n+\n+[systems.ngp]\n+model = \"models/systems/ngp.glb\"               # built by tools/console-models/neogeo_pocket.py: a Neo Geo Pocket (the monochrome one) in carbon black; its clip is the Color's, its screens in the Pocket's four greys\n+\n+[systems.ngpc]\n+model = \"models/systems/ngpc.glb\"              # built by tools/console-models/neogeo_pocket.py: a Neo Geo Pocket Color in Platinum Blue; its focused clip drops a cartridge into its back, boots, plays on the clicky stick, does a special move and hops, then takes the cartridge out\n+\n [systems.nes]\n-model = \"models/systems/nes.glb\"\n+model = \"models/systems/nes.glb\"               # built by tools/console-models/nes.py: the front-loading NES and its controller; its focused clip opens the door, slides a cartridge in and presses it down, shuts the door, switches on and plays on the controller, then takes the cartridge out\n+\n+[systems.pc98]\n+model = \"models/systems/pc98.glb\"              # built by tools/console-models/pc98.py: an NEC PC-9801VM, its colour monitor and its keyboard; its focused clip slides a floppy into drive 1, shuts its lever, boots to a game's title, presses RETURN and plays, then takes the floppy out\n+\n+[systems.pcengine]\n+model = \"models/systems/pcengine.glb\"          # built by tools/console-models/pc_engine.py: the white PC Engine, a HuCard and its pad; its focused clip slides the HuCard in along its channel, slides POWER on and plays the pad, and later takes the HuCard out\n+\n+[systems.pcenginecd]\n+model = \"models/systems/pcenginecd.glb\"        # built by tools/console-models/pcenginecd.py: a PC Engine Duo (the PC Engine with the Super CD-ROM\u00b2 built in) and its pad; its focused clip opens the lid, drops a CD in, switches on and plays, then takes the CD out\n+\n+[systems.pcfx]\n+model = \"models/systems/pcfx.glb\"              # built by tools/console-models/pcfx.py: NEC's PC-FX, the tower, and its pad; its focused clip swings the CD lid up, drops a disc onto the spindle and shuts it, lights POWER and BUSY and plays on the pad, then takes the disc out\n+\n+[systems.psx]\n+model = \"models/systems/psx.glb\"                # built by tools/console-models/psx.py, turned to a three-quarter view; its focused clip opens the lid, drops a disc in, and later ejects it\n+\n+[systems.snes]\n+model = \"models/systems/snes.glb\"               # built by tools/console-models/snes.py: the PAL console, turned to a three-quarter view; its focused clip drops a cartridge through the dust flaps, switches on, and later ejects it\n+\n+[systems.gameandwatch]\n+model = \"models/systems/gameandwatch.glb\"      # built by tools/console-models/gameandwatch.py: a Game & Watch Multi Screen in Donkey Kong II's colours, open, a made-up game on its screens; its focused clip plays the game, then shuts with a snap and springs open again\n+\n+[systems.sgb]\n+model = \"models/systems/sgb.glb\"               # built by tools/console-models/sgb.py: the Super Game Boy in the European Super Nintendo's slot (snes.py's); its focused clip drops a Game Boy cartridge into it and switches on, then takes the cartridge out\n+\n+[systems.gc]\n+model = \"models/systems/gc.glb\"                # built by tools/console-models/gamecube.py: an indigo GameCube, its handle and its controller; its focused clip springs the lid open, drops a mini disc in, switches on and plays the controller, and later takes the disc out\n+\n+[systems.naomi]\n+model = \"models/systems/naomi.glb\"             # built by tools/console-models/naomi.py: NAOMI is an arcade board, so a Sega candy cabinet with NAOMI on its marquee; its focused clip drops a coin in, presses START and plays, then shows NAOMI's logo again\n+\n+[systems.n64]\n+model = \"models/systems/n64.glb\"               # built by tools/console-models/n64.py: the console and its controller; its focused clip drops a cartridge in through the dust flaps, slides POWER on, swings the stick and presses the buttons, and later takes the cartridge out\n+\n+[systems.n64dd]\n+model = \"models/systems/n64dd.glb\"             # built by tools/console-models/n64dd.py: the Nintendo 64 (n64.py's) standing on its 64DD, its controller beside it; its focused clip slides a disk into the drive, switches on and plays, then ejects the disk\n+\n+[systems.supervision]\n+model = \"models/systems/supervision.glb\"       # built by tools/console-models/supervision.py: the original two-piece Watara Supervision; its focused clip drops a cartridge into its screen unit, boots and plays, and tilts the screen back on its bellows neck and up again\n+\n+[systems.ps2]\n+model = \"models/systems/ps2.glb\"                # built by tools/console-models/ps2.py: the black launch model, turned to a three-quarter view; its focused clip slides the tray out, drops a DVD on it, and later ejects it\n+\n+[systems.ps3]\n+model = \"models/systems/ps3.glb\"               # built by tools/console-models/ps3.py: the original PlayStation 3 and its DualShock 3; its focused clip slides a Blu-ray into the slot, plays on the sticks and steers by tilting the pad, then ejects the disc\n+\n+[systems.ps4]\n+model = \"models/systems/ps4.glb\"               # built by tools/console-models/ps4.py: the first PlayStation 4 and its DualShock 4; its focused clip slides a disc into the slit, lights the light bar blue and plays on the controller, then ejects the disc\n+\n+[systems.ps5]\n+model = \"models/systems/ps5.glb\"               # built by tools/console-models/ps5.py: the first PlayStation 5, standing on its stand, and its DualSense; its focused clip slides a disc into the slot upright, lights the light strips and plays on the controller, then ejects the disc\n+\n+[systems.satellaview]\n+model = \"models/systems/satellaview.glb\"       # built by tools/console-models/snes.py: a Super Famicom on its Satellaview; its focused clip drops the BS-X cartridge in, switches on, and the receiver takes a broadcast\n \n [systems.saturn]\n-model = \"models/systems/saturn.glb\"\n+model = \"models/systems/saturn.glb\"            # built by tools/console-models/saturn.py: the black Model 1 Saturn and its control pad; its focused clip opens the lid, drops a CD onto the spindle and shuts it, presses POWER while ACCESS flickers and plays on the pad, then takes the CD out\n \n [systems.dreamcast]\n-model = \"models/systems/dreamcast.glb\"\n+model = \"models/systems/dreamcast.glb\"         # built by tools/console-models/dreamcast.py: the European Dreamcast and its controller with a VMU; its focused clip opens the lid, drops a GD-ROM onto the spindle and shuts it, presses POWER (the light and the VMU come on) and plays on the controller, then takes the GD-ROM out\n \n # Favourites and Recently played have no console: a Power Star and an alarm clock.\n [systems.favourites]"
        },
        {
          "sha": "92d3da2bbc4a7c0a934096a4526e6844c9a5b5e4",
          "filename": "tools/console-models/amiga.py",
          "status": "added",
          "additions": 398,
          "deletions": 0,
          "changes": 398,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Famiga.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Famiga.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Famiga.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,398 @@\n+\"\"\"The console theme's Amiga system model (godot/themes/console/models/systems/amiga.glb): an Amiga 500 and its\n+Commodore 1084S monitor, Kickstart 1.3 asking for a disk.\n+\n+Built in Blender from the real machines' proportions (amiga_layout.py has the measurements, taken from photographs).\n+The computer is its side outline extruded across its width: the keyboard deck rising from the low front edge, the step,\n+the raised back rounding over at the back edge; nine vent grooves are cut across the raised back and the gaps of the\n+comb of fins along its back edge, the keyboard's wells into the deck, and the drive's panel and slot into its right\n+end. Its 96 keys (the UK layout) stand in the wells, tapering to their printed tops, off-white or grey as the real\n+ones. Behind it stands the monitor: its side outline extruded (the hood sloping down behind), the bezel's panel sunk\n+into its front, the screen's opening sloping in to the curved glass, its badge, the seams of the control flap under\n+it, and a grille in each side. The printing is two textures drawn by amiga_print.py: the computer's top seen from above\n+(planar-mapped onto every face facing up: the key tops and legends, the badge, the lights, the AMIGA lettering) with\n+the monitor's badge and the swatches its bezel and the lit drive light take; and the screens (the glass at rest shows\n+Kickstart 1.3's hand holding a disk), the floppy's two sides and their swatches.\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: the drive's end towards the viewer,\n+the top tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m) holding the set; the\n+set holding the floppy, the eject button, the lit drive light, two screens over the glass (black, and the title screen\n+of a made-up game, Odyssey) and the Ctrl and Amiga keys. The floppy waits in the drive shrunk to nothing, the light and\n+the screens are shrunk to nothing, so at rest the card is the set with Kickstart's hand on the screen.\n+\n+The `focused` clip (8 s, looping) loads a game: the floppy pops up beside the drive, flips over and slides in; the\n+drive light flickers while the screen goes black, then Odyssey's title screen comes up. Ctrl and both Amiga keys go\n+down together (the Amiga's reset), the hand is back, the eject button is pressed and the floppy pops out and goes.\n+\n+Seven materials (case, print, key_light, key_dark, dark, glass, disk) and two textures, within the system model\n+budget. Run from the repo root:\n+\n+    python tools/console-models/amiga_print.py artifacts/amiga\n+    blender -b --factory-startup --python tools/console-models/amiga.py -- artifacts/amiga godot/themes/console/models/systems/amiga.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bmesh\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import amiga_layout as L  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, ease_out, key, phase, smooth  # noqa: E402\n+from mega_drive_cartridge import bevel, boolean, box, export, join, material, new_object, prism_x, triangles  # noqa: E402\n+\n+TILT_YAW = -30.0     # degrees about the vertical: the right end (the drive) comes towards the viewer\n+TILT_PITCH = 25.0    # degrees about the horizontal: the top tips up towards the viewer\n+ROUGHNESS = dict(case=0.55, print=0.55, key_light=0.5, key_dark=0.5, dark=0.8, glass=0.2, disk=0.4)\n+RESET_KEYS = (\"ctrl\", \"lamiga\", \"ramiga\")\n+\n+# The deck's frame: x across, s up the slope from the deck's front edge, h off it.\n+SLOPE_FRAME = Matrix.Translation((0.0, L.DECK[0][0], L.DECK[0][1])) @ Matrix.Rotation(L.SLOPE, 4, \"X\")\n+PRESS = SLOPE_FRAME.to_3x3() @ Vector((0.0, 0.0, -1.0))   # a key's travel, in the model's frame\n+\n+\n+# ---- UVs -------------------------------------------------------------------------------------------------------------\n+\n+def atlas_uv(region, w, h):\n+    \"\"\"A face drawn w x h into region: (a, b) millimetres across it (a to the right, b down from its top) to UVs.\"\"\"\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+TOP = atlas_uv(L.PRINT_REGIONS[\"top\"], L.WIDTH, L.DEPTH)\n+\n+\n+def top_uv(co):\n+    \"\"\"The computer's top seen from above.\"\"\"\n+    return TOP(co.x + L.WIDTH / 2, L.DEPTH - co.y)\n+\n+\n+def swatch(where):\n+    x, y = where\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+def paint(obj, mat, uv_of, keep):\n+    \"\"\"Gives the faces keep(centre, normal, material name) accepts the material mat and the UVs uv_of(corner).\"\"\"\n+    mesh = obj.data\n+    names = [m.name for m in mesh.materials]\n+    if mat.name not in names:\n+        mesh.materials.append(mat)\n+        names.append(mat.name)\n+    index = names.index(mat.name)\n+    # One layer, the one exported as TEXCOORD_0: a boolean's cutter can bring a second, and joins match them by name.\n+    while len(mesh.uv_layers) > 1:\n+        mesh.uv_layers.remove(mesh.uv_layers[1])\n+    uv = mesh.uv_layers[0] if mesh.uv_layers else mesh.uv_layers.new(name=\"UVMap\")\n+    uv.name = \"UVMap\"\n+    for p in mesh.polygons:\n+        if keep(p.center, p.normal, names[p.material_index]):\n+            p.material_index = index\n+            for li in p.loop_indices:\n+                uv.data[li].uv = uv_of(mesh.vertices[mesh.loops[li].vertex_index].co)\n+\n+\n+# ---- Geometry --------------------------------------------------------------------------------------------------------\n+\n+def solid(name, rings, mat):\n+    \"\"\"A solid through rings of 3D points (each the same count, in the same order), capped at both ends.\"\"\"\n+    bm = bmesh.new()\n+    verts = [[bm.verts.new(p) for p in ring] for ring in rings]\n+    n = len(rings[0])\n+    for a, b in zip(verts, verts[1:]):\n+        for i in range(n):\n+            j = (i + 1) % n\n+            bm.faces.new([a[i], a[j], b[j], b[i]])\n+    bm.faces.new(verts[0])\n+    bm.faces.new(list(reversed(verts[-1])))\n+    bmesh.ops.recalc_face_normals(bm, faces=bm.faces)\n+    return new_object(name, bm, mat)\n+\n+\n+def on_slope(name, outline, h0, h1, mat):\n+    \"\"\"A solid from an (x, s) outline on the deck, from h0 to h1 off it.\"\"\"\n+    return solid(name, [[SLOPE_FRAME @ Vector((x, s, h)) for x, s in outline] for h in (h0, h1)], mat)\n+\n+\n+def rounded(cx, cz, w, h, r, segments=5):\n+    \"\"\"A rounded rectangle's outline in (x, z), w x h centred on (cx, cz), anticlockwise from its right side.\"\"\"\n+    points = []\n+    for qx, qz, a0 in ((1, 1, 0), (-1, 1, 90), (-1, -1, 180), (1, -1, 270)):\n+        ox, oz = cx + qx * (w / 2 - r), cz + qz * (h / 2 - r)\n+        for k in range(segments + 1):\n+            a = math.radians(a0 + 90 * k / segments)\n+            points.append((ox + r * math.cos(a), oz + r * math.sin(a)))\n+    return points\n+\n+\n+def curved(name, w, h, bulge, uv, mat, nu=16, nv=12, keep=None):\n+    \"\"\"A screen facing the viewer (-y), w x h about its origin, its middle bulging bulge towards the viewer; uv(a, b)\n+    gives the UV of the point a across from its left and b down from its top (both 0 to 1). keep(i, j), if given,\n+    says which of its nu x nv cells to make (a part of the screen).\"\"\"\n+    bm = bmesh.new()\n+    layer = bm.loops.layers.uv.new(\"UVMap\")\n+    grid = [[bm.verts.new((w * (i / nu - 0.5), -bulge * (1 - (2 * i / nu - 1) ** 2) * (1 - (2 * j / nv - 1) ** 2),\n+                           h * (0.5 - j / nv))) for i in range(nu + 1)] for j in range(nv + 1)]\n+    for j in range(nv):\n+        for i in range(nu):\n+            if keep is not None and not keep(i, j):\n+                continue\n+            corners = ((i, j), (i, j + 1), (i + 1, j + 1), (i + 1, j))       # wound to face -y\n+            face = bm.faces.new([grid[b][a] for a, b in corners])\n+            face.smooth = True\n+            for loop, (a, b) in zip(face.loops, corners):\n+                loop[layer].uv = uv(a / nu, b / nv)\n+    bmesh.ops.delete(bm, geom=[v for v in bm.verts if not v.link_faces], context=\"VERTS\")\n+    return new_object(name, bm, mat)\n+\n+\n+def computer(m):\n+    \"\"\"The Amiga 500: the body cut for its wells, grooves, fins and drive, its top printed.\"\"\"\n+    body = prism_x(\"a500\", L.profile(), -L.WIDTH / 2, L.WIDTH / 2, m[\"case\"])\n+    bevel(body, 1.6, segments=2)\n+    boolean(body, join([on_slope(\"well\", outline, -L.WELL, 12.0, m[\"dark\"]) for outline in L.wells()], \"wells\"))\n+    g = L.GROOVES\n+    boolean(body, join([box(\"groove\", g[\"x\"][0], g[\"x\"][1], y - g[\"width\"] / 2, y + g[\"width\"] / 2,\n+                            L.TOP_Z - g[\"depth\"], L.TOP_Z + 5.0, m[\"key_dark\"]) for y in g[\"y\"]], \"grooves\"))\n+    f = L.FIN_GAPS\n+    boolean(body, join([box(\"gap\", x - f[\"width\"] / 2, x + f[\"width\"] / 2, *f[\"y\"], *f[\"z\"], m[\"key_dark\"])\n+                        for x in f[\"x\"]], \"gaps\"))\n+    y0, y1, z0, z1, depth = L.DRIVE[\"recess\"]\n+    boolean(body, box(\"panel\", L.WIDTH / 2 - depth, L.WIDTH / 2 + 5.0, y0, y1, z0, z1, m[\"case\"]))\n+    y0, y1, z0, z1, depth = L.DRIVE[\"slot\"]\n+    boolean(body, box(\"slot\", L.WIDTH / 2 - depth, L.WIDTH / 2 + 5.0, y0, y1, z0, z1, m[\"dark\"]))\n+    paint(body, m[\"print\"], top_uv, lambda c, n, name: n.z > 0.9 and name == \"case\")\n+    return body\n+\n+\n+def keys(m):\n+    \"\"\"The keys, standing in their wells and tapering to their printed tops: Ctrl and the Amiga keys apart (the clip\n+    presses them).\"\"\"\n+    static, moving = [], {}\n+    h, inset = L.KEY_HEIGHT, L.KEY_INSET\n+    for k in L.KEYS:\n+        name, xu, row, wu, colour = k[:5]\n+        x0, s0, x1, s1 = L.key_rect(xu, row, wu, k[6] if len(k) > 6 else 1)\n+\n+        def rect(d, z):\n+            return [(x0 + d, s0 + d, z), (x1 - d, s0 + d, z), (x1 - d, s1 - d, z), (x0 + d, s1 - d, z)]\n+        cap = solid(name, [rect(0.0, -L.WELL - 0.5), rect(0.0, h - 4.5), rect(inset, h)], m[\"key_\" + colour])\n+        bevel(cap, 0.6, segments=1, angle=30)\n+        cap.data.transform(SLOPE_FRAME)\n+        paint(cap, m[\"print\"], top_uv, lambda c, n, _name: n.z > 0.95)\n+        if name in RESET_KEYS:\n+            moving[name] = cap\n+        else:\n+            static.append(cap)\n+    return static, moving\n+\n+\n+def monitor(m):\n+    \"\"\"The 1084S, its front's middle at the origin, facing -y: the shell, the bezel and the opening, the glass, the\n+    badge, the flap's seams, the side grilles.\"\"\"\n+    M = L.MONITOR\n+    w, b, o = M[\"width\"], M[\"bezel\"], M[\"opening\"]\n+    shell = prism_x(\"monitor\", M[\"profile\"], -w / 2, w / 2, m[\"case\"])\n+    bevel(shell, 2.5, segments=2)\n+    boolean(shell, box(\"bezel\", -b[\"x\"], b[\"x\"], -5.0, b[\"depth\"], b[\"z\"][0], b[\"z\"][1], m[\"case\"]))\n+    boolean(shell, join([box(\"seam\", -w / 2 + 7.0, w / 2 - 7.0, -5.0, 0.8, z - 0.6, z + 0.6, m[\"dark\"])\n+                         for z in M[\"seams\"]], \"seams\"))\n+    outer, inner = rounded(0.0, o[\"z\"], *o[\"outer\"]), rounded(0.0, o[\"z\"], *o[\"inner\"])\n+    boolean(shell, solid(\"opening\", [[(x, y, z) for x, z in outline] for outline, y in\n+                                     ((outer, -10.0), (outer, b[\"depth\"] - 0.01), (inner, o[\"depth\"]),\n+                                      (inner, o[\"depth\"] + 1.0))], m[\"case\"]))\n+    v = M[\"vents\"]\n+    boolean(shell, join([box(\"vent\", side * w / 2 - 2.0, side * w / 2 + 2.0, *v[\"y\"], z - v[\"height\"] / 2,\n+                             z + v[\"height\"] / 2, m[\"dark\"]) for side in (-1, 1) for z in v[\"z\"]], \"vents\"))\n+\n+    # The bezel's face and the opening's walls are the bezel's colour; the gap round the panel is dark.\n+    def bezel(c, n, name):\n+        inside = abs(c.x) < b[\"x\"] + 0.01 and b[\"z\"][0] - 0.01 < c.z < b[\"z\"][1] + 0.01 and c.y > 0.3\n+        return name == \"case\" and inside and not (abs(n.y) < 0.5 and c.y < b[\"depth\"] - 0.1)\n+    paint(shell, m[\"print\"], swatch(L.PRINT_SWATCHES[\"bezel\"]), bezel)\n+    paint(shell, m[\"dark\"], swatch(L.PRINT_SWATCHES[\"black\"]),\n+          lambda c, n, name: name == \"case\" and abs(n.y) < 0.5 and 0.3 < c.y < b[\"depth\"] - 0.1\n+          and abs(c.x) < b[\"x\"] + 0.5 and b[\"z\"][0] - 0.5 < c.z < b[\"z\"][1] + 0.5)\n+\n+    gw, gh = M[\"glass\"][\"size\"]\n+    region = atlas_uv(L.MEDIA_REGIONS[\"kickstart\"], 1.0, 1.0)\n+    glass = curved(\"glass\", gw, gh, M[\"glass\"][\"bulge\"], region, m[\"glass\"])\n+    glass.data.transform(Matrix.Translation((0.0, o[\"depth\"], o[\"z\"])))\n+\n+    bm = bmesh.new()\n+    layer = bm.loops.layers.uv.new(\"UVMap\")\n+    (x0, x1), (z0, z1) = M[\"badge\"][\"x\"], M[\"badge\"][\"z\"]\n+    corners = [(x0, z0), (x1, z0), (x1, z1), (x0, z1)]                  # wound to face -y\n+    face = bm.faces.new([bm.verts.new((x, b[\"depth\"] - 0.15, z)) for x, z in corners])\n+    badge_uv = atlas_uv(L.PRINT_REGIONS[\"badge\"], x1 - x0, z1 - z0)\n+    for loop, (x, z) in zip(face.loops, corners):\n+        loop[layer].uv = badge_uv(x - x0, z1 - z)\n+    badge = new_object(\"badge\", bm, m[\"print\"])\n+\n+    parts = [shell, glass, badge]\n+    at = Matrix.Translation((M[\"at\"][0], M[\"at\"][1], 0.0))\n+    for obj in parts:\n+        obj.data.transform(at)\n+    return parts\n+\n+\n+def screen(m, name, uv):\n+    \"\"\"A screen switched on over the glass, its middle at its origin (the clip opens and closes it about there).\"\"\"\n+    gw, gh = L.MONITOR[\"glass\"][\"size\"]\n+    return curved(name, gw, gh, L.MONITOR[\"glass\"][\"bulge\"], uv, m[\"glass\"])\n+\n+\n+def disk(m):\n+    \"\"\"The floppy, its middle at its origin, its shutter's edge towards -x (into the drive), its label up.\"\"\"\n+    l, w, t = L.DISK[\"length\"], L.DISK[\"width\"], L.DISK[\"thickness\"]\n+    shell = box(\"disk\", -l / 2, l / 2, -w / 2, w / 2, -t / 2, t / 2, m[\"disk\"])\n+    bevel(shell, 0.8, segments=2)\n+    top, bottom = atlas_uv(L.MEDIA_REGIONS[\"disk_top\"], l, w), atlas_uv(L.MEDIA_REGIONS[\"disk_bottom\"], l, w)\n+    paint(shell, m[\"disk\"], lambda co: top(co.x + l / 2, w / 2 - co.y), lambda c, n, name: n.z > 0.9)\n+    paint(shell, m[\"disk\"], lambda co: bottom(co.x + l / 2, w / 2 + co.y), lambda c, n, name: n.z < -0.9)\n+    paint(shell, m[\"disk\"], swatch(L.MEDIA_SWATCHES[\"edge\"]), lambda c, n, name: abs(n.z) <= 0.9)\n+    return shell\n+\n+\n+def drive_light(m):\n+    \"\"\"The drive light lit: a bar just over the printed one, its middle at its origin.\"\"\"\n+    x0, x1 = L.LIGHTS[\"x\"]\n+    w = L.LIGHTS[\"width\"] + 0.4\n+    s = (L.LIGHTS[\"drive\"] - L.DECK[0][0]) / math.cos(L.SLOPE)\n+    lit = on_slope(\"drive_light\", [(x0 - 0.2, s - w / 2), (x1 + 0.2, s - w / 2), (x1 + 0.2, s + w / 2),\n+                                   (x0 - 0.2, s + w / 2)], 0.05, 0.25, m[\"print\"])\n+    paint(lit, m[\"print\"], swatch(L.PRINT_SWATCHES[\"lit\"]), lambda c, n, name: True)\n+    middle = SLOPE_FRAME @ Vector(((x0 + x1) / 2, s, 0.15))\n+    lit.data.transform(Matrix.Translation(-middle))\n+    return lit, middle\n+\n+\n+def eject_button(m):\n+    y0, y1, z0, z1, out = L.DRIVE[\"eject\"]\n+    face = L.WIDTH / 2 - L.DRIVE[\"recess\"][4]\n+    button = box(\"eject\", face - 1.0, face + out, y0, y1, z0, z1, m[\"case\"])\n+    bevel(button, 0.6, segments=1)\n+    return button\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+def appear(f, f0, f1):\n+    return HIDDEN + (1 - HIDDEN) * back_out(phase(f, f0, f1)) if f >= f0 else HIDDEN\n+\n+\n+def flat(v):\n+    \"\"\"A screen squeezed to v of its height (switching on or off).\"\"\"\n+    return Pose(axes=(1.0, 1.0, max(HIDDEN, v)))\n+\n+\n+def tap(f, f0, hold=2):\n+    \"\"\"A press at f0: down in three frames, held, back up in four.\"\"\"\n+    return smooth(phase(f, f0, f0 + 3)) * (1 - smooth(phase(f, f0 + 3 + hold, f0 + 7 + hold)))\n+\n+\n+def timeline(f):\n+    out = L.WIDTH / 2 + 22.0 + L.DISK[\"length\"] / 2 - L.SEAT[0]   # the floppy's middle beside the drive: 22 mm out\n+\n+    # The floppy: pops up beside the drive, flips over coming down to the slot, slides in; later pops out and goes.\n+    if f < 14 or f >= 228:\n+        floppy = Pose(scale=HIDDEN)\n+    elif f < 24:\n+        floppy = Pose(x=out, z=50.0, scale=appear(f, 14, 22))\n+    elif f < 44:\n+        t = smooth(phase(f, 24, 44))\n+        floppy = Pose(x=out - 30.0 * t, z=50.0 * (1 - t), pitch=360.0 * t)\n+    elif f < 198:\n+        floppy = Pose(x=(out - 30.0) * (1 - smooth(phase(f, 44, 58))))\n+    elif f < 214:\n+        floppy = Pose(x=40.0 * back_out(phase(f, 198, 206)))\n+    else:\n+        t = ease_in(phase(f, 214, 228))\n+        floppy = Pose(x=40.0 + 80.0 * t, z=30.0 * t, scale=max(HIDDEN, 1 - ease_in(phase(f, 220, 228))))\n+\n+    # The drive light, while the disk is read: on, with a short blink every few frames.\n+    reading = 60 <= f < 112 or 200 <= f < 206\n+    light = Pose() if reading and (f // 5) % 5 != 3 else Pose(scale=HIDDEN)\n+\n+    # The screens: black while the game loads, its title screen opening; the reset blacks it out, and the hand's back.\n+    black = Pose() if 62 <= f < 102 or 184 <= f < 194 else Pose(scale=HIDDEN)\n+    if f < 100 or f >= 188:\n+        title = Pose(scale=HIDDEN)\n+    elif f < 106:\n+        title = flat(0.02 + 0.98 * ease_out(phase(f, 100, 106)))\n+    elif f < 182:\n+        title = Pose()\n+    else:\n+        title = flat(1 - ease_in(phase(f, 182, 188)))\n+\n+    poses = {\"floppy\": floppy, \"drive_light\": light, \"screen_black\": black, \"screen_title\": title,\n+             \"eject\": Pose(x=-1.5 * tap(f, 196))}\n+    for k, name in enumerate(RESET_KEYS):                         # Ctrl, then each Amiga key, held, let go together\n+        d = 3.0 * smooth(phase(f, 170 + 3 * k, 173 + 3 * k)) * (1 - smooth(phase(f, 186, 190)))\n+        poses[name] = Pose(x=PRESS.x * d, y=PRESS.y * d, z=PRESS.z * d)\n+    return poses\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    printed = bpy.data.images.load(os.path.join(images, \"amiga_print.png\"))\n+    media = bpy.data.images.load(os.path.join(images, \"amiga_media.png\"))\n+    colours = dict(L.COLOURS, print=\"#FFFFFF\", glass=\"#FFFFFF\", disk=\"#FFFFFF\")\n+    textures = dict(print=printed, glass=media, disk=media)\n+    m = {n: material(n, colours[n], r, textures.get(n)) for n, r in ROUGHNESS.items()}\n+\n+    static, moving = keys(m)\n+    whole = join([computer(m)] + static + monitor(m), \"amiga\")\n+    eject = eject_button(m)\n+    lit, lit_at = drive_light(m)\n+    floppy = disk(m)\n+    unlit = swatch(L.MEDIA_SWATCHES[\"black\"])(None)\n+    black = screen(m, \"screen_black\", lambda a, b: unlit)\n+    title = screen(m, \"screen_title\", atlas_uv(L.MEDIA_REGIONS[\"title\"], 1.0, 1.0))\n+    for obj in [whole, eject, floppy] + list(moving.values()):\n+        for p in obj.data.polygons:\n+            p.use_smooth = True\n+        obj.data.set_sharp_from_angle(angle=math.radians(35))\n+\n+    # The node tree: the root (the view and the fit) holds the set; the set holds the moving parts at their places.\n+    root = bpy.data.objects.new(\"amiga_root\", None)\n+    scene.collection.objects.link(root)\n+    whole.parent = root\n+    o, M = L.MONITOR[\"opening\"], L.MONITOR\n+    glass_front = Vector((M[\"at\"][0], M[\"at\"][1] + o[\"depth\"], o[\"z\"]))\n+    for part, where in ((floppy, Vector(L.SEAT)), (lit, lit_at), (black, glass_front + Vector((0.0, -0.5, 0.0))),\n+                        (title, glass_front + Vector((0.0, -1.0, 0.0)))):\n+        part.parent = whole\n+        part.location = where\n+    for part in [eject] + list(moving.values()):\n+        part.parent = whole\n+    nodes = {\"floppy\": floppy, \"drive_light\": lit, \"screen_black\": black, \"screen_title\": title, \"eject\": eject,\n+             **moving}\n+\n+    # The three-quarter view, then the spec's fit of the set at rest, as the root's transform.\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    points = [tilt @ v.co for v in whole.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [whole, eject, lit, floppy, black, title] + list(moving.values())\n+    print(f\"Amiga: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(whole.data)} the set), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "7af11b1b2bf6b76d070f5f62fa9799c570e4a902",
          "filename": "tools/console-models/amiga_layout.py",
          "status": "added",
          "additions": 192,
          "deletions": 0,
          "changes": 192,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Famiga_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Famiga_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Famiga_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,192 @@\n+\"\"\"The Amiga 500 and its Commodore 1084S monitor: the measurements shared by amiga_print.py (which draws their printing)\n+and amiga.py (which builds them). Plain Python: Blender's Python imports it too.\n+\n+Millimetres. The computer: x from its middle (left to right), y back from its front edge, z up from the table. It's\n+474 x 330 x 62 mm: a wedge whose keyboard deck rises from a low front edge to a step, behind which the raised back\n+(the AMIGA lettering, nine vent grooves) rounds over into the comb of fins along its back edge, with the floppy drive\n+in its right end. The monitor stands behind it, its front MONITOR[\"at\"]: 357 x 330 mm, its hood shortened from the\n+real 378 mm to 302 (the launcher fits a card's largest side, so the set's depth would draw it smaller than the other\n+cards). The rest was measured from photographs on Wikimedia Commons (front views of the set, a top view, views of each\n+end and of the back, and a 1084S from the side), scaled by the computer's width.\n+\n+The keyboard is the UK one. Its keys are laid out in units of U from the main block's left edge (KEYS_X0) and in rows\n+from the front (row 5, the space bar's) to the back (row 1, the digits'), with the function keys behind them as row 0.\n+The deck is a plane at SLOPE: a key stands on it in the slope's frame (x across, s up the slope from the deck's front\n+edge, h off it), and deck_point() maps a point of that frame to the model's (y, z).\n+\n+Two textures. `print` (ATLAS square, PRINT_REGIONS): `top` (the computer's top seen from above: the key tops and their\n+legends, the badge and lights, the AMIGA lettering; planar-mapped onto every face facing up) and `badge` (the\n+monitor's), and swatches for plain parts. `media` (MEDIA_REGIONS): `kickstart` (the glass at rest: Kickstart 1.3's\n+hand holding a disk) and `title` (the game's title screen), each the glass's whole width and height with the raster\n+in its middle, drawn at the Amiga's 320 x 256 pixels; `disk_top` and `disk_bottom` (the floppy's two sides, as seen\n+looking at each, its shutter to the left); and swatches. A region is (x, y, width, height) in pixels.\n+\"\"\"\n+\n+import math\n+\n+ATLAS = 1024\n+\n+# -- The computer ----------------------------------------------------------------------------------------------------\n+\n+WIDTH, DEPTH = 474.0, 330.0\n+DECK = ((6.0, 23.0), (179.0, 39.0))     # the keyboard deck's front and back edges, (y, z)\n+SLOPE = math.atan2(DECK[1][1] - DECK[0][1], DECK[1][0] - DECK[0][0])\n+TOP_Z = 61.5                            # the raised back's top\n+STEP_Y = 186.0                          # where the step up from the deck meets it\n+FINS = dict(y=300.0, z=44.0)            # the top rounds over from y to the back edge, down to z there\n+\n+\n+def profile():\n+    \"\"\"The computer's side outline, (y, z), anticlockwise seen from its right end.\"\"\"\n+    fy, fz = FINS[\"y\"], FINS[\"z\"]\n+    back = [(fy + (DEPTH - fy) * math.sin(math.radians(a)), fz + (TOP_Z - fz) * math.cos(math.radians(a)))\n+            for a in range(90, -1, -10)]\n+    front = [(6.0 - 6.0 * math.sin(math.radians(a)), 17.0 + 6.0 * math.cos(math.radians(a))) for a in range(0, 91, 15)]\n+    return ([(2.5, 0.0), (DEPTH - 2.5, 0.0), (DEPTH, 2.5)] + back + [(STEP_Y, TOP_Z), DECK[1], DECK[0]]\n+            + front[1:] + [(0.0, 2.5)])\n+\n+\n+def deck_point(s, h):\n+    \"\"\"The model's (y, z) of the point s up the deck's slope from its front edge and h off it.\"\"\"\n+    c, k = math.cos(SLOPE), math.sin(SLOPE)\n+    return DECK[0][0] + s * c - h * k, DECK[0][1] + s * k + h * c\n+\n+\n+GROOVES = dict(x=(-211.0, 181.0), y=[222.0 + 8.4 * k for k in range(9)], width=2.4, depth=2.0)\n+FIN_GAPS = dict(x=[-228.0 + 5.6 * k for k in range(82)], width=2.6, y=(297.0, 340.0), z=(41.0, 70.0))\n+DRIVE = dict(recess=(190.0, 300.0, 22.0, 48.0, 1.2),   # the drive's panel in the right end: y0, y1, z0, z1, depth\n+             slot=(197.0, 293.0, 31.5, 38.5, 22.0),\n+             eject=(276.0, 290.0, 24.5, 29.0, 2.0))    # the eject button, standing out of the panel\n+\n+# The keyboard: KEYS_X0 is the main block's left edge, U a key's pitch, GAP between two keys. A key stands from the\n+# well's floor (WELL below the deck) to its top, KEY_HEIGHT above the deck, tapering by KEY_INSET on every side.\n+U, GAP = 18.2, 0.9\n+KEYS_X0 = -220.5\n+ROW_S = {5: 20.0, 4: 38.2, 3: 56.4, 2: 74.6, 1: 92.8}  # each row's front edge, up the slope\n+FROW = (117.5, 16.0)                                   # the function keys' row: its front edge and depth\n+WELL, KEY_HEIGHT, KEY_INSET = 3.0, 9.0, 2.2\n+\n+\n+def kx(u):\n+    return KEYS_X0 + u * U\n+\n+\n+def key_rect(xu, row, wu, rows=1):\n+    \"\"\"A key's base, (x0, s0, x1, s1) in the slope's frame.\"\"\"\n+    if row == 0:\n+        s0, s1 = FROW[0], FROW[0] + FROW[1]\n+    else:\n+        s0, s1 = ROW_S[row + rows - 1], ROW_S[row] + U\n+    return kx(xu) + GAP / 2, s0 + GAP / 2, kx(xu + wu) - GAP / 2, s1 - GAP / 2\n+\n+\n+def _row(names, x0, row, colour, legends):\n+    return [(n, x0 + i, row, 1.0, colour, legend) for i, (n, legend) in enumerate(zip(names, legends))]\n+\n+\n+# (name, x in units, row, width in units, \"light\" or \"dark\", legend[, rows]): a legend's line break splits its upper and\n+# lower characters.\n+KEYS = (\n+    [(\"esc\", 0.0, 0, 1.1, \"dark\", \"Esc\")]\n+    + [(f\"f{k + 1}\", 1.9 + 1.25 * k, 0, 1.25, \"dark\", f\"F{k + 1}\") for k in range(5)]\n+    + [(f\"f{k + 6}\", 8.65 + 1.25 * k, 0, 1.25, \"dark\", f\"F{k + 6}\") for k in range(5)]\n+    + [(\"grave\", 0.0, 1, 1.5, \"dark\", \"~\\n`\")]\n+    + _row([f\"d{k}\" for k in \"1234567890\"], 1.5, 1, \"light\", [\"!\\n1\", '\"\\n2', \"\u00a3\\n3\", \"$\\n4\", \"%\\n5\", \"^\\n6\", \"&\\n7\", \"*\\n8\",\n+                                                              \"(\\n9\", \")\\n0\"])\n+    + _row([\"minus\", \"equals\", \"backslash\"], 11.5, 1, \"light\", [\"_\\n-\", \"+\\n=\", \"|\\n\\\\\"])\n+    + [(\"backspace\", 14.5, 1, 1.5, \"dark\", \"\u2190\")]\n+    + [(\"tab\", 0.0, 2, 1.5, \"dark\", \"\u21e5\")]\n+    + _row(list(\"qwertyuiop\"), 1.5, 2, \"light\", list(\"QWERTYUIOP\"))\n+    + _row([\"lbracket\", \"rbracket\"], 11.5, 2, \"light\", [\"{\\n[\", \"}\\n]\"])\n+    + [(\"return_top\", 13.6, 2, 2.4, \"dark\", \"\u21b5\")]\n+    + [(\"ctrl\", 0.0, 3, 1.0, \"dark\", \"Ctrl\"), (\"caps\", 1.0, 3, 1.0, \"dark\", \"Caps\\nLock\")]\n+    + _row(list(\"asdfghjkl\"), 2.0, 3, \"light\", list(\"ASDFGHJKL\"))\n+    + _row([\"semicolon\", \"quote\", \"hash\"], 11.0, 3, \"light\", [\":\\n;\", \"@\\n'\", \"~\\n#\"])\n+    + [(\"return_bottom\", 14.1, 3, 1.9, \"dark\", \"\")]\n+    + [(\"lshift\", 0.0, 4, 2.5, \"dark\", \"\u21e7\")]\n+    + _row(list(\"zxcvbnm\"), 2.5, 4, \"light\", list(\"ZXCVBNM\"))\n+    + _row([\"comma\", \"period\", \"slash\"], 9.5, 4, \"light\", [\"<\\n,\", \">\\n.\", \"?\\n/\"])\n+    + [(\"rshift\", 12.5, 4, 3.5, \"dark\", \"\u21e7\")]\n+    + [(\"lalt\", 0.4, 5, 1.4, \"dark\", \"Alt\"), (\"lamiga\", 1.8, 5, 1.4, \"dark\", \"A\"), (\"space\", 3.2, 5, 9.2, \"light\", \"\"),\n+       (\"ramiga\", 12.4, 5, 1.4, \"dark\", \"A\"), (\"ralt\", 13.8, 5, 1.4, \"dark\", \"Alt\")]\n+    + [(\"del\", 16.4, 1, 1.5, \"dark\", \"Del\"), (\"help\", 17.9, 1, 1.5, \"dark\", \"Help\")]\n+    + [(\"up\", 17.6, 3, 1.0, \"light\", \"\u2191\"), (\"left\", 16.6, 4, 1.0, \"light\", \"\u2190\"), (\"down\", 17.6, 4, 1.0, \"light\", \"\u2193\"),\n+       (\"right\", 18.6, 4, 1.0, \"light\", \"\u2192\")]\n+    + _row([\"np_lpar\", \"np_rpar\", \"np_slash\", \"np_star\"], 20.0, 1, \"dark\", [\"(\", \")\", \"/\", \"*\"])\n+    + _row([\"np7\", \"np8\", \"np9\"], 20.0, 2, \"light\", [\"7\\nHome\", \"8\\n\u2191\", \"9\\nPg Up\"])\n+    + [(\"np_minus\", 23.0, 2, 1.0, \"dark\", \"-\")]\n+    + _row([\"np4\", \"np5\", \"np6\"], 20.0, 3, \"light\", [\"4\\n\u2190\", \"5\", \"6\\n\u2192\"])\n+    + [(\"np_plus\", 23.0, 3, 1.0, \"dark\", \"+\")]\n+    + _row([\"np1\", \"np2\", \"np3\"], 20.0, 4, \"light\", [\"1\\nEnd\", \"2\\n\u2193\", \"3\\nPg Dn\"])\n+    + [(\"np_enter\", 23.0, 4, 1.0, \"dark\", \"Enter\", 2)]\n+    + [(\"np0\", 20.0, 5, 2.0, \"light\", \"0\\nIns\"), (\"np_dot\", 22.0, 5, 1.0, \"light\", \".\\nDel\")]\n+)\n+\n+\n+def wells():\n+    \"\"\"The wells the keys stand in, each an (x, s) outline: the main block (its bottom row ends short), the function\n+    keys in three groups, Del and Help, the cursor keys' T, and the numeric pad.\"\"\"\n+    m = 1.0\n+    s5, s4, s3, s1 = ROW_S[5] - m, ROW_S[4], ROW_S[3], ROW_S[1] + U + m\n+    f0, f1 = FROW[0] - m, FROW[0] + FROW[1] + m\n+\n+    def rect(x0, x1, a, b):\n+        return [(x0, a), (x1, a), (x1, b), (x0, b)]\n+    return [\n+        [(kx(0) - m, s5), (kx(15.2) + m, s5), (kx(15.2) + m, s4), (kx(16) + m, s4), (kx(16) + m, s1), (kx(0) - m, s1)],\n+        rect(kx(0) - m, kx(1.1) + m, f0, f1),\n+        rect(kx(1.9) - m, kx(8.15) + m, f0, f1),\n+        rect(kx(8.65) - m, kx(14.9) + m, f0, f1),\n+        rect(kx(16.4) - m, kx(19.4) + m, ROW_S[1] - m, s1),\n+        [(kx(16.6) - m, s4 - m), (kx(19.6) + m, s4 - m), (kx(19.6) + m, s3), (kx(18.6) + m, s3),\n+         (kx(18.6) + m, ROW_S[3] + U + m), (kx(17.6) - m, ROW_S[3] + U + m), (kx(17.6) - m, s3), (kx(16.6) - m, s3)],\n+        rect(kx(20) - m, kx(24) + m, s5, s1),\n+    ]\n+\n+\n+# The printing on the deck, in the top's (x, y): the Commodore badge, and the power and drive lights' bars.\n+BADGE = dict(x=185.0, y=158.0, size=13.0)\n+LIGHTS = dict(x=(181.0, 196.0), power=133.5, drive=127.5, width=1.8)  # their bars' x, each one's y, their width\n+LETTERING = dict(x=186.0, y=201.0, height=13.5)                       # AMIGA: its right edge and middle\n+\n+# -- The monitor -----------------------------------------------------------------------------------------------------\n+\n+MONITOR = dict(\n+    at=(-8.0, 334.0),                   # its front's middle, in the computer's (x, y)\n+    width=357.0,\n+    # Its side outline, (y back from its front, z): the front's full height, a step, then the hood sloping down to\n+    # the back.\n+    profile=[(3.0, 0.0), (296.0, 0.0), (302.0, 6.0), (302.0, 236.0), (206.0, 314.0), (198.0, 314.0), (192.0, 330.0),\n+             (4.0, 330.0), (0.0, 326.0), (0.0, 3.0)],\n+    bezel=dict(x=170.5, z=(58.0, 322.0), depth=2.5),          # the bezel's panel, sunk into the front\n+    seams=(56.0, 8.0),                                        # the control flap's top and bottom, below it\n+    opening=dict(z=202.0, outer=(277.0, 196.0, 14.0), inner=(262.0, 186.0, 9.0), depth=18.5),\n+    glass=dict(size=(268.0, 192.0), bulge=7.0),               # the glass, from the opening's inner edge to its middle\n+    badge=dict(x=(-160.0, -98.0), z=(79.0, 91.0)),\n+    vents=dict(y=(184.0, 284.0), z=[24.0 + 6.0 * k for k in range(14)], height=2.6),  # the grilles in its sides\n+)\n+\n+# -- The floppy disk -------------------------------------------------------------------------------------------------\n+\n+DISK = dict(length=94.0, width=90.0, thickness=3.3)   # its shutter's edge first: length along x, width along y\n+SEAT = (WIDTH / 2 - DISK[\"length\"] / 2 - 8.0, (DRIVE[\"slot\"][0] + DRIVE[\"slot\"][1]) / 2,\n+        (DRIVE[\"slot\"][2] + DRIVE[\"slot\"][3]) / 2)    # its middle, in the drive\n+\n+# -- The atlases -----------------------------------------------------------------------------------------------------\n+\n+COLOURS = dict(case=\"#D8D1BE\", key_light=\"#DEDACC\", key_dark=\"#A29D8E\", top_light=\"#ECE9DE\", top_dark=\"#B3AE9F\",\n+               bezel=\"#C6BFAD\", dark=\"#1B1A18\", disk=\"#24439C\")\n+\n+PRINT_REGIONS = dict(top=(0, 0, 1024, 713), badge=(0, 722, 400, 78))\n+PRINT_SWATCHES = dict(bezel=(16, 836), lit=(48, 836), black=(80, 836))  # each a 24 x 24 block's middle, in pixels\n+\n+SCREEN_PX = (640, 460)       # a screen's region, the glass's proportions\n+RASTER = (576, 432)          # the lit picture in its middle, 4:3; the Amiga's 320 x 256 pixels drawn into it\n+MEDIA_REGIONS = dict(kickstart=(0, 0) + SCREEN_PX, title=(0, 468) + SCREEN_PX, disk_top=(648, 0, 376, 360),\n+                     disk_bottom=(648, 368, 376, 360))\n+MEDIA_SWATCHES = dict(black=(664, 752), edge=(696, 752))\n+\n+# -- The focused clip ------------------------------------------------------------------------------------------------\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s"
        },
        {
          "sha": "c06b94d37f62382d77ba390084a882045c1491f1",
          "filename": "tools/console-models/amiga_print.py",
          "status": "added",
          "additions": 338,
          "deletions": 0,
          "changes": 338,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Famiga_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Famiga_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Famiga_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,338 @@\n+\"\"\"The Amiga 500's and its 1084S monitor's printing, for the console theme's Amiga model (tools/console-models/amiga.py).\n+\n+Draws its two atlases, their regions as amiga_layout.py places them. amiga_print.png: the computer's top seen from\n+above, in millimetres as the model's planar UVs map it (each key's top in its colour with its legend, the Commodore\n+badge, the power light lit and the drive light off, the AMIGA lettering in relief), the monitor's badge, and the\n+swatches small parts take their colour from. amiga_media.png: the two screens, each the glass's whole width and\n+height, a dark border round the lit raster, drawn at the Amiga's 320 x 256 pixels: Kickstart 1.3's hand holding a\n+disk (the glass at rest) and the title screen of a made-up game, Odyssey; and the floppy's two sides, its label\n+(Odyssey's) and its hub. Run it with the system Python (Pillow):\n+\n+    python tools/console-models/amiga_print.py <out folder>\n+\n+It writes amiga_print.png and amiga_media.png there.\n+\"\"\"\n+\n+import math\n+import os\n+import random\n+import sys\n+\n+from PIL import Image, ImageDraw, ImageFilter, ImageFont\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import amiga_layout as L  # noqa: E402\n+from psp_print import ship  # noqa: E402\n+from retro_pc_print import rgb, shade  # noqa: E402\n+from windows_pc_print import FONTS, Region, gradient, mix  # noqa: E402\n+\n+CASE = rgb(L.COLOURS[\"case\"])\n+LEGEND = (62, 61, 57)\n+\n+\n+def top_point(x, y):\n+    \"\"\"A point of the computer's top (x from its middle, y back from its front) in the top region's millimetres.\"\"\"\n+    return x + L.WIDTH / 2, L.DEPTH - y\n+\n+\n+# ---- The computer's top ----------------------------------------------------------------------------------------------\n+\n+def key_top(reg, key):\n+    \"\"\"A key's top: its colour, a slightly lighter middle, and its legend at the top left (arrows and symbols in the\n+    middle), the shifted character above the other.\"\"\"\n+    name, xu, row, wu, colour, legend = key[:6]\n+    rows = key[6] if len(key) > 6 else 1\n+    x0, s0, x1, s1 = L.key_rect(xu, row, wu, rows)\n+    inset = L.KEY_INSET + 0.5\n+    (ya, _), (yb, _) = L.deck_point(s0 + inset, L.KEY_HEIGHT), L.deck_point(s1 - inset, L.KEY_HEIGHT)\n+    (rx0, ry0), (rx1, ry1) = top_point(x0 + inset, yb), top_point(x1 - inset, ya)\n+    fill = rgb(L.COLOURS[\"top_light\" if colour == \"light\" else \"top_dark\"])\n+    reg.rect_(rx0 - 0.6, ry0 - 0.6, rx1 + 0.6, ry1 + 0.6, fill)\n+    reg.rect_(rx0 + 1.2, ry0 + 1.2, rx1 - 1.2, ry1 - 1.2, shade(fill, 0.05), r=1.5)\n+    if not legend:\n+        return\n+    if name in (\"lamiga\", \"ramiga\"):\n+        reg.text(\"A\", \"timesbi.ttf\", 5.0, rx0 + 2.2, ry0 + 3.4, LEGEND, anchor=\"lm\")\n+        return\n+    lines = legend.split(\"\\n\")\n+    symbol = len(lines) == 1 and len(legend) == 1 and not legend.isalnum()\n+    if symbol or name.startswith(\"np\") and len(lines[0]) == 1 and len(lines) == 1:\n+        font = \"seguisym.ttf\" if not legend.isascii() else \"arial.ttf\"\n+        reg.text(legend, font, 4.2 if not legend.isascii() else 3.4, (rx0 + rx1) / 2, (ry0 + ry1) / 2, LEGEND)\n+        return\n+    size = 2.4 if len(lines[0]) > 2 or len(lines) > 1 and len(lines[1]) > 2 else 3.0\n+    for k, line in enumerate(lines):\n+        font = \"seguisym.ttf\" if not line.isascii() else \"arial.ttf\"\n+        reg.text(line, font, size, rx0 + 1.9, ry0 + 2.6 + k * (size + 0.9), LEGEND, anchor=\"lm\")\n+    if name == \"caps\":\n+        reg.circle(rx1 - 2.2, ry0 + 2.2, 0.7, (40, 38, 34))\n+\n+\n+def commodore_logo(reg, cx, cy, s, fill):\n+    \"\"\"Commodore's mark, s across: the C open to the right, and its two flags in the opening.\"\"\"\n+    layer = reg.layer()\n+    d = ImageDraw.Draw(layer)\n+    r0, r1 = 0.5 * s, 0.29 * s\n+    d.ellipse(reg.box(cx - r0, cy - r0, cx + r0, cy + r0), fill=fill + (255,))\n+    d.ellipse(reg.box(cx - r1, cy - r1, cx + r1, cy + r1), fill=(0, 0, 0, 0))\n+    d.rectangle(reg.box(cx, cy - 0.23 * s, cx + r0 + 1, cy + 0.23 * s), fill=(0, 0, 0, 0))\n+    reg.over(layer)\n+    for top, bottom in ((-0.21 * s, -0.03 * s), (0.03 * s, 0.21 * s)):\n+        reg.poly([(cx + 0.06 * s, cy + top), (cx + 0.5 * s, cy + top), (cx + 0.5 * s - (bottom - top), cy + bottom),\n+                  (cx + 0.06 * s, cy + bottom)], fill)\n+\n+\n+def lettering(reg):\n+    \"\"\"AMIGA in relief on the raised back: lit along its upper left, shadowed along its lower right.\"\"\"\n+    x1, y, h = L.LETTERING[\"x\"], L.LETTERING[\"y\"], L.LETTERING[\"height\"]\n+    rx1, ry = top_point(x1, y)\n+    font = ImageFont.truetype(FONTS + \"timesbi.ttf\", max(1, round(h * 1.38 * reg.sx)))\n+    spacing = h * 0.55\n+    widths = [font.getlength(ch) / reg.sx for ch in \"AMIGA\"]\n+    x = rx1 - (sum(widths) + spacing * 4)\n+    for ch, w in zip(\"AMIGA\", widths):\n+        for (dx, dy), colour in (((-0.6, -0.6), shade(CASE, 0.5)), ((0.7, 0.7), shade(CASE, -0.38)),\n+                                 ((0.0, 0.0), shade(CASE, 0.06))):\n+            reg.draw.text(reg.p(x + dx, ry + dy), ch, font=font, fill=colour, anchor=\"lm\")\n+        x += w + spacing\n+\n+\n+def top():\n+    reg = Region(L.PRINT_REGIONS[\"top\"], L.WIDTH, L.DEPTH, CASE)\n+    for key in L.KEYS:\n+        key_top(reg, key)\n+\n+    # The badge behind the function keys, and the lights beside them.\n+    bx, by = top_point(L.BADGE[\"x\"], L.BADGE[\"y\"])\n+    s = L.BADGE[\"size\"]\n+    reg.rect_(bx - s / 2, by - s / 2, bx + s / 2, by + s / 2, (150, 151, 150), r=1.0)\n+    reg.rect_(bx - s / 2 + 0.5, by - s / 2 + 0.5, bx + s / 2 - 0.5, by + s / 2 - 0.5, (208, 209, 207), r=0.7)\n+    commodore_logo(reg, bx, by, s * 0.62, (84, 88, 96))\n+    (lx0, _), (lx1, _) = top_point(L.LIGHTS[\"x\"][0], 0), top_point(L.LIGHTS[\"x\"][1], 0)\n+    w = L.LIGHTS[\"width\"]\n+    for name, lit in ((\"power\", True), (\"drive\", False)):\n+        _, ly = top_point(0, L.LIGHTS[name])\n+        reg.rect_(lx0 - 0.4, ly - w / 2 - 0.4, lx1 + 0.4, ly + w / 2 + 0.4, shade(CASE, -0.3), r=0.4)\n+        if lit:\n+            glow = reg.layer()\n+            ImageDraw.Draw(glow).rounded_rectangle(reg.box(lx0 - 2, ly - w, lx1 + 2, ly + w), radius=2 * reg.sx,\n+                                                   fill=(255, 60, 40, 110))\n+            reg.over(glow.filter(ImageFilter.GaussianBlur(1.2 * reg.sx)))\n+            reg.rect_(lx0, ly - w / 2, lx1, ly + w / 2, (255, 70, 52), r=0.3)\n+            reg.rect_(lx0 + 0.6, ly - w / 4, lx1 - 0.6, ly, (255, 180, 160), r=0.2)\n+        else:\n+            reg.rect_(lx0, ly - w / 2, lx1, ly + w / 2, (96, 52, 46), r=0.3)\n+        reg.text(name.upper(), \"arial.ttf\", 1.6, lx0 - 1.6, ly, shade(CASE, -0.32), anchor=\"rm\")\n+    lettering(reg)\n+    return reg\n+\n+\n+def monitor_badge():\n+    reg = Region(L.PRINT_REGIONS[\"badge\"], 62.0, 12.0, (128, 129, 132))\n+    reg.rect_(0.3, 0.3, 61.7, 11.7, (150, 151, 154), r=0.8)\n+    reg.rect_(0.8, 0.8, 61.2, 11.2, (118, 119, 122), r=0.6)\n+    commodore_logo(reg, 4.6, 4.3, 4.4, (238, 238, 236))\n+    reg.text(\"Commodore 1084S\", \"arialbd.ttf\", 4.2, 7.6, 4.3, (238, 238, 236), anchor=\"lm\")\n+    reg.text(\"V I D E O   M O N I T O R\", \"arial.ttf\", 2.6, 31.0, 8.9, (230, 230, 228))\n+    return reg\n+\n+\n+def print_atlas():\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), CASE)\n+    top().paste(image)\n+    monitor_badge().paste(image)\n+    d = ImageDraw.Draw(image)\n+    for name, colour in ((\"bezel\", rgb(L.COLOURS[\"bezel\"])), (\"lit\", (255, 64, 46)), (\"black\", (8, 8, 8))):\n+        x, y = L.PRINT_SWATCHES[name]\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colour)\n+    return image\n+\n+\n+# ---- The screens -----------------------------------------------------------------------------------------------------\n+\n+WHITE, BLACK = (255, 255, 255), (0, 0, 0)\n+DISK_BLUE, METAL = (0, 85, 170), (170, 170, 170)\n+\n+\n+def pixel_font(name, size):\n+    return ImageFont.truetype(FONTS + name, size)\n+\n+\n+def kickstart():\n+    \"\"\"Kickstart 1.3's screen, 320 x 256: on white, a hand holding a disk out to be put in the drive, and V1.3.\"\"\"\n+    img = Image.new(\"RGB\", (320, 256), WHITE)\n+    d = ImageDraw.Draw(img)\n+    d.fontmode = \"1\"\n+    ox, oy = 92, 60                                   # the drawing's top left\n+\n+    def p(points):\n+        return [(ox + x, oy + y) for x, y in points]\n+\n+    # The disk, tipped a little: its shell, the shutter with its window, the label with Workbench's name.\n+    a = math.radians(-14)\n+    c, s = math.cos(a), math.sin(a)\n+\n+    def disk(points):\n+        return p([(66 + x * c - y * s, 50 + x * s + y * c) for x, y in points])\n+    d.polygon(disk([(-38, -40), (38, -40), (38, 40), (-34, 40), (-38, 36)]), fill=DISK_BLUE, outline=BLACK)\n+    d.polygon(disk([(-18, -40), (18, -40), (18, -12), (-18, -12)]), fill=METAL, outline=BLACK)\n+    d.polygon(disk([(2, -36), (10, -36), (10, -16), (2, -16)]), fill=DISK_BLUE, outline=BLACK)\n+    d.polygon(disk([(-30, 2), (30, 2), (30, 40), (-30, 40)]), fill=WHITE, outline=BLACK)\n+    label = Image.new(\"L\", (60, 24), 0)\n+    dl = ImageDraw.Draw(label)\n+    dl.fontmode = \"1\"\n+    dl.text((30, 7), \"AMIGA\", font=pixel_font(\"timesbd.ttf\", 11), fill=255, anchor=\"mm\")\n+    dl.text((30, 18), \"Workbench\", font=pixel_font(\"arial.ttf\", 9), fill=255, anchor=\"mm\")\n+    label = label.rotate(14, resample=Image.NEAREST, expand=True)\n+    lx, ly = disk([(6, 22)])[0]\n+    img.paste(BLACK, (round(lx - label.width / 2), round(ly - label.height / 2)), label)\n+    d = ImageDraw.Draw(img)\n+    d.fontmode = \"1\"\n+\n+    # The hand, from the lower left: the wrist and its cuff, the palm, the fingertips curled round the disk's left\n+    # edge, the thumb across its front.\n+    outline = dict(fill=WHITE, outline=BLACK, width=2)\n+    d.polygon(p([(4, 156), (22, 108), (46, 116), (30, 162)]), **outline)\n+    d.line(p([(16, 124), (40, 132)]), fill=BLACK, width=2)\n+    d.polygon(p([(22, 110), (16, 88), (18, 62), (30, 50), (44, 56), (52, 92), (48, 118)]), **outline)\n+    for x, y in ((26, 52), (24, 64), (24, 76), (26, 88)):\n+        d.ellipse([ox + x, oy + y - 6, ox + x + 22, oy + y + 6], **outline)\n+    d.polygon(p([(40, 114), (44, 100), (52, 88), (62, 78), (70, 76), (72, 84), (64, 96), (56, 118)]), **outline)\n+    d.line(p([(64, 80), (68, 86)]), fill=BLACK, width=2)\n+    d.text((ox + 110, oy + 122), \"V1.3\", font=pixel_font(\"arialbi.ttf\", 15), fill=BLACK, anchor=\"mm\")\n+    return img\n+\n+\n+def checker_floor(d, horizon, w, h, colours):\n+    \"\"\"A floor of tiles running to the horizon, rows narrowing with distance: the demo scene's favourite.\"\"\"\n+    for row in range(h - horizon):\n+        y = horizon + row\n+        z = 400.0 / (row + 4)                         # distance into the screen\n+        for x in range(w):\n+            u = (x - w / 2) * z / 64.0\n+            tile = (int(math.floor(u / 8.0)) + int(math.floor(z / 6.0))) % 2\n+            d.point((x, y), fill=colours[tile])\n+\n+\n+def title_screen():\n+    \"\"\"Odyssey's title screen, 320 x 256: copper bars graded to the horizon, a ringed planet, the ship over a tiled\n+    floor, the title in chrome, PRESS FIRE TO START.\"\"\"\n+    w, h, horizon = 320, 256, 176\n+    img = Image.new(\"RGB\", (w, h))\n+    d = ImageDraw.Draw(img)\n+    d.fontmode = \"1\"\n+    stops = [(0, (0, 0, 48)), (0.45, (64, 16, 112)), (0.8, (208, 64, 96)), (1.0, (255, 176, 64))]\n+    for y in range(0, horizon, 2):                    # the copper's bands: two lines each\n+        t = y / horizon\n+        for (t0, c0), (t1, c1) in zip(stops, stops[1:]):\n+            if t0 <= t <= t1:\n+                colour = mix(c0, c1, (t - t0) / (t1 - t0))\n+        d.rectangle([0, y, w, y + 1], fill=tuple(v // 16 * 16 for v in colour))\n+    rng = random.Random(500)\n+    for _ in range(90):\n+        x, y = rng.randrange(w), rng.randrange(horizon - 40)\n+        d.point((x, y), fill=(255, 255, 255) if rng.random() < 0.5 else (160, 176, 255))\n+    d.ellipse([200, 92, 300, 192], fill=(240, 112, 48))\n+    d.ellipse([206, 96, 286, 168], fill=(255, 160, 80))\n+    d.arc([168, 128, 332, 156], 160, 380, fill=(255, 240, 208), width=3)\n+    checker_floor(d, horizon, w, h, [(40, 16, 96), (112, 48, 160)])\n+    d.line([(0, horizon), (w, horizon)], fill=(255, 208, 128))\n+    ship(d, 116, 150, 2.2, (255, 255, 255), (255, 176, 48))\n+    for k in range(4):\n+        d.line([(40 - k * 10, 146 + k * 3), (84 - k * 6, 146 + k * 3)], fill=(255, 208, 128), width=2)\n+\n+    # The title: chrome letters, a dark outline, graded from sky to ground.\n+    font = pixel_font(\"ariblk.ttf\", 46)\n+    mask = Image.new(\"L\", (w, 80), 0)\n+    dm = ImageDraw.Draw(mask)\n+    dm.fontmode = \"1\"\n+    dm.text((w / 2, 40), \"ODYSSEY\", font=font, fill=255, anchor=\"mm\")\n+    edge = mask.filter(ImageFilter.MaxFilter(5))\n+    chrome = gradient((w, 80), [(0.0, (180, 240, 255)), (0.45, (255, 255, 255)), (0.5, (40, 48, 128)),\n+                                (0.75, (120, 96, 200)), (1.0, (255, 208, 120))]).convert(\"RGB\")\n+    img.paste((16, 0, 48), (0, 8), edge)\n+    img.paste(chrome.crop((0, 0, w, 80)), (0, 8), mask)\n+    d = ImageDraw.Draw(img)\n+    d.fontmode = \"1\"\n+    d.text((w / 2, 230), \"PRESS FIRE TO START\", font=pixel_font(\"arialbd.ttf\", 12), fill=(255, 240, 64), anchor=\"mm\")\n+    d.text((w / 2, 246), \"\u00a9 ODYSSEY LAUNCHER\", font=pixel_font(\"arial.ttf\", 9), fill=(208, 192, 255), anchor=\"mm\")\n+    return img\n+\n+\n+def glass_image(raster):\n+    \"\"\"A screen's region: the tube's dark border, the 320 x 256 picture in its middle (doubled, then fitted).\"\"\"\n+    image = Image.new(\"RGB\", L.SCREEN_PX, (22, 24, 23))\n+    picture = raster.resize((640, 512), Image.NEAREST).resize(L.RASTER, Image.LANCZOS)\n+    image.paste(picture, ((L.SCREEN_PX[0] - L.RASTER[0]) // 2, (L.SCREEN_PX[1] - L.RASTER[1]) // 2))\n+    return image\n+\n+\n+# ---- The floppy ------------------------------------------------------------------------------------------------------\n+\n+def disk_side(rect, label):\n+    \"\"\"One side of the floppy, seen looking at it, its shutter's edge to the left: the shell, the shutter (its window\n+    showing the disk), and either the label (the label side) or the hub (the other).\"\"\"\n+    blue = rgb(L.COLOURS[\"disk\"])\n+    reg = Region(rect, L.DISK[\"length\"], L.DISK[\"width\"], blue)\n+    reg.rect_(0.0, 0.0, 94.0, 90.0, shade(blue, 0.06), r=2.0)\n+    reg.rect_(0.0, 15.0, 36.0, 75.0, shade(blue, -0.18))           # the shutter's track\n+    reg.rect_(0.0, 19.0, 32.0, 71.0, (200, 204, 210))               # the shutter\n+    reg.rect_(0.5, 19.5, 31.5, 22.0, (225, 228, 232))\n+    reg.rect_(8.0, 37.0, 27.0, 53.0, (40, 38, 44), r=1.5)           # its window, the disk's brown showing\n+    reg.rect_(9.5, 38.5, 25.5, 51.5, (92, 64, 44), r=1.0)\n+    reg.circle(91.0 - 3.0, 4.0 + 3.0, 2.2, (16, 16, 20))           # the write-protect hole\n+    if label:\n+        art = Image.new(\"RGB\", (780, 515), (246, 244, 236))\n+        da = ImageDraw.Draw(art)\n+        band = gradient((780, 250), [(0.0, (24, 12, 72)), (1.0, (120, 40, 140))]).convert(\"RGB\")\n+        art.paste(band, (0, 0))\n+        dband = ImageDraw.Draw(art)\n+        dband.ellipse([610, 150, 730, 270], fill=(240, 112, 48))\n+        ship(dband, 190, 170, 3.2, (255, 255, 255), (255, 176, 48))\n+        da.text((390, 82), \"ODYSSEY\", font=pixel_font(\"ariblk.ttf\", 120), fill=(255, 228, 92), anchor=\"mm\",\n+                stroke_width=5, stroke_fill=(70, 20, 10))\n+        da.text((390, 318), \"AMIGA  \u00b7  DISK 1 OF 1\", font=pixel_font(\"arialbd.ttf\", 46), fill=(40, 40, 60),\n+                anchor=\"mm\")\n+        da.line([(60, 372), (720, 372)], fill=(180, 176, 168), width=4)\n+        da.line([(60, 432), (720, 432)], fill=(180, 176, 168), width=4)\n+        da.text((390, 476), \"\u00a9 ODYSSEY LAUNCHER\", font=pixel_font(\"arial.ttf\", 34), fill=(110, 108, 120), anchor=\"mm\")\n+        art = art.rotate(90, expand=True)              # it reads with the shutter at the top\n+        reg.rect_(39.0, 5.5, 92.0, 84.5, (230, 228, 220), r=1.2)\n+        x0, y0, x1, y1 = reg.box(40.0, 6.5, 91.0, 83.5)\n+        reg.img.paste(art.resize((round(x1 - x0), round(y1 - y0)), Image.LANCZOS), (round(x0), round(y0)))\n+        reg.poly([(3.0, 6.0), (8.0, 3.5), (8.0, 8.5)], shade(blue, 0.3))  # the arrow: this edge goes in first\n+    else:\n+        cx, cy = 52.0, 45.0\n+        reg.circle(cx, cy, 14.5, shade(blue, -0.25))\n+        reg.circle(cx, cy, 12.5, (176, 180, 186))\n+        reg.circle(cx, cy, 11.0, (196, 200, 206))\n+        reg.rect_(cx - 2.2, cy - 2.2, cx + 2.2, cy + 2.2, (30, 30, 34))\n+        reg.rect_(cx + 4.0, cy + 2.0, cx + 8.0, cy + 6.5, (30, 30, 34))\n+        reg.rect_(86.0, 79.0, 91.0, 86.0, (230, 230, 232), r=0.6)  # the write-protect tab\n+    return reg\n+\n+\n+def media_atlas():\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), (22, 24, 23))\n+    for name, raster in ((\"kickstart\", kickstart()), (\"title\", title_screen())):\n+        x, y, _, _ = L.MEDIA_REGIONS[name]\n+        image.paste(glass_image(raster), (x, y))\n+    disk_side(L.MEDIA_REGIONS[\"disk_top\"], True).paste(image)\n+    disk_side(L.MEDIA_REGIONS[\"disk_bottom\"], False).paste(image)\n+    d = ImageDraw.Draw(image)\n+    for name, colour in ((\"black\", (6, 6, 7)), (\"edge\", rgb(L.COLOURS[\"disk\"]))):\n+        x, y = L.MEDIA_SWATCHES[name]\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colour)\n+    return image\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    for name, image in ((\"amiga_print.png\", print_atlas()), (\"amiga_media.png\", media_atlas())):\n+        path = os.path.join(out, name)\n+        image.save(path, optimize=True)\n+        print(path)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "a1b6a650637cf7aa991ed9fcaed8a083e51de48b",
          "filename": "tools/console-models/apple2.py",
          "status": "added",
          "additions": 348,
          "deletions": 0,
          "changes": 348,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fapple2.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fapple2.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fapple2.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,348 @@\n+\"\"\"The console theme's Apple II system model (godot/themes/console/models/systems/apple2.glb): an Apple IIe, two Disk\n+II drives on its lid and a Monitor II on them, the IIe's banner on the screen while it looks for a disk.\n+\n+Built in Blender from the real machines' proportions (apple2_layout.py has the measurements, taken from photographs).\n+The computer is its side outline extruded across its width, standing on its dark base plate: the low front edge\n+undercut beneath, the top sloping up to the flat top over its back two thirds; the keyboard's well and RESET's recess\n+are cut into the slope, and the comb of vent slots into each side. Its 64 keys (the US layout) stand in the well,\n+tapering to their printed tops, and the power light stands among them, lit. The drives are boxes, each with its front\n+panel sunk into its front, the slot and the finger recess cut into it, the door's lever standing over the slot, and\n+the vents in its sides. The monitor is its side outline extruded (its back sloping in at the top), the surround sunk\n+into its front and the opening sloping in to the curved glass, a seam down its control strip. The printing is two\n+textures drawn by apple2_print.py: the computer's top seen from above (planar-mapped onto every face facing up: the\n+key tops and legends, the badge, the lid's seams), the drives' panels and the monitor's strip, with swatches; and the\n+screens in green phosphor and the floppy.\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: the right end towards the viewer, the\n+top tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m) holding the set; the set\n+holding the floppy, drive 1's door (on its hinge) and its lit IN USE light, the three screens over the glass (Odyssey's\n+title screen two and five eighths loaded, and whole), and Control and RESET. The floppy waits in drive 1 shrunk to\n+nothing, the light and the screens are shrunk to nothing, so at rest the card is the set with the banner on the\n+screen.\n+\n+The `focused` clip (8 s, looping) boots a game: the floppy slides into drive 1 and its door turns down over it; IN\n+USE lights while the title screen arrives in hi-res's order, every eighth line first. Then Control and RESET go down\n+together, the banner's back, the door turns up and the floppy comes out and goes.\n+\n+Seven materials (case, keys, bezel, dark, print, glass, disk) and two textures, within the system model budget. Run\n+from the repo root:\n+\n+    python tools/console-models/apple2_print.py artifacts/apple2\n+    blender -b --factory-startup --python tools/console-models/apple2.py -- artifacts/apple2 godot/themes/console/models/systems/apple2.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bmesh\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import apple2_layout as L  # noqa: E402\n+from amiga import curved, paint, rounded, solid  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, ease_out, key, phase, smooth  # noqa: E402\n+from mega_drive_cartridge import bevel, boolean, box, export, join, material, new_object, prism_x, triangles  # noqa: E402\n+\n+TILT_YAW = -22.0     # degrees about the vertical: the right end comes towards the viewer\n+TILT_PITCH = 20.0    # degrees about the horizontal: the top tips up towards the viewer\n+ROUGHNESS = dict(case=0.6, keys=0.55, bezel=0.6, dark=0.7, print=0.5, glass=0.15, disk=0.5)\n+RESET_KEYS = (\"control\", \"reset\")\n+TOP_DEPTH = 250.0    # the top region's depth (apple2_print.py's): the top in front of the drives\n+\n+# The top's frame: x across, s up the slope from the front edge, h off it.\n+SLOPE_FRAME = Matrix.Translation((0.0, L.TIP[0], L.TIP[1])) @ Matrix.Rotation(L.SLOPE, 4, \"X\")\n+PRESS = SLOPE_FRAME.to_3x3() @ Vector((0.0, 0.0, -1.0))   # a key's travel, in the model's frame\n+\n+\n+# ---- UVs -------------------------------------------------------------------------------------------------------------\n+\n+def atlas_uv(region, w, h):\n+    \"\"\"A face drawn w x h into region: (a, b) millimetres across it (a to the right, b down from its top) to UVs.\"\"\"\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+TOP = atlas_uv(L.PRINT_REGIONS[\"top\"], L.WIDTH, TOP_DEPTH)\n+\n+\n+def top_uv(co):\n+    \"\"\"The computer's top in front of the drives, seen from above.\"\"\"\n+    return TOP(co.x + L.WIDTH / 2, TOP_DEPTH - co.y)\n+\n+\n+def swatch(where):\n+    x, y = where\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+# ---- Geometry --------------------------------------------------------------------------------------------------------\n+\n+def on_slope(name, outline, h0, h1, mat):\n+    \"\"\"A solid from an (x, s) outline on the slope, from h0 to h1 off it.\"\"\"\n+    return solid(name, [[SLOPE_FRAME @ Vector((x, s, h)) for x, s in outline] for h in (h0, h1)], mat)\n+\n+\n+def computer(m):\n+    \"\"\"The IIe on its base plate: the body cut for the keyboard's well, RESET's recess and the side vents, its top\n+    printed.\"\"\"\n+    outline = [(y, max(z, L.PLATE)) for y, z in L.profile()]\n+    body = prism_x(\"iie\", outline, -L.WIDTH / 2, L.WIDTH / 2, m[\"case\"])\n+    bevel(body, 2.0, segments=2)\n+    boolean(body, on_slope(\"well\", L.well(), -L.WELL, 16.0, m[\"dark\"]))\n+    x0, x1, s0, s1 = L.RESET[\"well\"]\n+    boolean(body, on_slope(\"reset_well\", [(x0, s0), (x1, s0), (x1, s1), (x0, s1)], -5.0, 16.0, m[\"case\"]))\n+    v, edge = L.VENTS, L.WIDTH / 2\n+    slots = [box(\"vent\", x0, x1, y - v[\"width\"] / 2, y + v[\"width\"] / 2, *v[\"z\"], m[\"dark\"]) for y in v[\"y\"]\n+             for x0, x1 in ((edge - v[\"inset\"], edge + 5.0), (-edge - 5.0, -edge + v[\"inset\"]))]\n+    boolean(body, join(slots, \"vents\"))\n+    paint(body, m[\"print\"], top_uv, lambda c, n, name: n.z > 0.5 and name == \"case\" and c.y < TOP_DEPTH - 1.0\n+          and abs(c.x) < L.WIDTH / 2 - 0.5)\n+    plate = box(\"plate\", -L.WIDTH / 2 + 3.0, L.WIDTH / 2 - 3.0, L.UNDERCUT[0] + 2.0, L.DEPTH - 3.0, 0.0, L.PLATE + 1.0,\n+                m[\"dark\"])\n+    paint(plate, m[\"print\"], swatch(L.PRINT_SWATCHES[\"plate\"]), lambda c, n, name: True)\n+    return [body, plate]\n+\n+\n+def key_solid(name, rect, height, mat, m):\n+    \"\"\"A key: from the well's floor up its sides, tapering to its printed top, height off the slope.\"\"\"\n+    x0, s0, x1, s1 = rect\n+\n+    def ring(d, h):\n+        return [(x0 + d, s0 + d, h), (x1 - d, s0 + d, h), (x1 - d, s1 - d, h), (x0 + d, s1 - d, h)]\n+    cap = solid(name, [ring(0.0, -L.WELL - 0.5), ring(0.0, height - 4.5), ring(L.KEY_INSET, height)], mat)\n+    bevel(cap, 0.6, segments=1, angle=30)\n+    cap.data.transform(SLOPE_FRAME)\n+    paint(cap, m[\"print\"], top_uv, lambda c, n, _name: n.z > 0.9)\n+    return cap\n+\n+\n+def keys(m):\n+    \"\"\"The keys, standing in their well, the power light among them, and RESET in its recess: Control and RESET\n+    apart (the clip presses them).\"\"\"\n+    static, moving = [], {}\n+    for name, xu, row, wu, _ in L.KEYS:\n+        x0, s0, x1, s1 = L.key_rect(xu, row, wu)\n+        outline = [(x0, s0), (x1, s0), (x1, s1), (x0, s1)]\n+        if name == \"light\":\n+            window = on_slope(\"light\", outline, -L.WELL - 0.5, L.KEY_HEIGHT - 3.0, m[\"print\"])\n+            bevel(window, 0.6, segments=1)\n+            paint(window, m[\"print\"], top_uv, lambda c, n, _name: n.z > 0.9)\n+            paint(window, m[\"print\"], swatch(L.PRINT_SWATCHES[\"bezel\"]), lambda c, n, _name: n.z <= 0.9)\n+            static.append(window)\n+            continue\n+        cap = key_solid(name, (x0, s0, x1, s1), L.KEY_HEIGHT, m[\"keys\"], m)\n+        if name in RESET_KEYS:\n+            moving[name] = cap\n+        else:\n+            static.append(cap)\n+    (x0, x1), (s0, s1) = L.RESET[\"x\"], L.RESET[\"s\"]\n+    moving[\"reset\"] = key_solid(\"reset\", (x0, s0, x1, s1), 6.0, m[\"keys\"], m)\n+    return static, moving\n+\n+\n+def drive(m, n):\n+    \"\"\"Disk II number n (1 the left), standing on the lid: the box, its panel sunk into its front and printed, the slot\n+    and the finger recess cut into it, the vents in its sides; drive 2's door, open, with it.\"\"\"\n+    w, d, h = L.DRIVE[\"size\"]\n+    cx, y0, z0 = L.DRIVE[\"at\"][n - 1], L.DRIVE[\"front\"], L.HEIGHT\n+    shell = box(f\"drive{n}\", cx - w / 2, cx + w / 2, y0, y0 + d, z0, z0 + h, m[\"case\"])\n+    bevel(shell, 2.0, segments=2)\n+    pw, ph, sink = L.DRIVE[\"panel\"]\n+    pz = L.PANEL_Z\n+    boolean(shell, box(\"panel\", cx - pw / 2, cx + pw / 2, y0 - 5.0, y0 + sink, pz, pz + ph, m[\"case\"]))\n+    s = L.DRIVE[\"slot\"]\n+    boolean(shell, box(\"slot\", cx + s[\"x\"][0], cx + s[\"x\"][1], y0 - 5.0, y0 + s[\"depth\"],\n+                       pz + s[\"z\"] - s[\"height\"] / 2, pz + s[\"z\"] + s[\"height\"] / 2, m[\"dark\"]))\n+    r = L.DRIVE[\"recess\"]\n+    boolean(shell, box(\"recess\", cx + r[\"x\"][0], cx + r[\"x\"][1], y0 - 5.0, y0 + sink + r[\"depth\"], pz + r[\"z\"][0],\n+                       pz + r[\"z\"][1], m[\"dark\"]))\n+    v = L.DRIVE[\"vents\"]\n+    boolean(shell, join([box(\"vent\", cx + side * w / 2 - 3.0, cx + side * w / 2 + 3.0, y0 + y - v[\"width\"] / 2,\n+                             y0 + y + v[\"width\"] / 2, z0 + v[\"z\"][0], z0 + v[\"z\"][1], m[\"dark\"])\n+                         for side in (-1, 1) for y in v[\"y\"]], \"vents\"))\n+    panel = atlas_uv(L.PRINT_REGIONS[f\"drive{n}\"], pw, ph)\n+    paint(shell, m[\"print\"], lambda co: panel(co.x - (cx - pw / 2), pz + ph - co.z),\n+          lambda c, nn, name: nn.y < -0.9 and abs(c.y - (y0 + sink)) < 0.3 and name == \"case\")\n+    paint(shell, m[\"print\"], swatch(L.PRINT_SWATCHES[\"drive\"]),\n+          lambda c, nn, name: name == \"case\" and abs(c.x - cx) < pw / 2 + 0.1 and pz - 0.1 < c.z < pz + ph + 0.1\n+          and c.y < y0 + sink - 0.05 and abs(nn.y) < 0.5)\n+    return shell\n+\n+\n+def door(m, cx):\n+    \"\"\"A drive's door: the lever standing over the slot, open; and its hinge, along the slot's top edge.\"\"\"\n+    dd = L.DRIVE[\"door\"]\n+    y0 = L.DRIVE[\"front\"] + L.DRIVE[\"panel\"][2]\n+    pz = L.PANEL_Z\n+    lever = box(\"door\", cx + dd[\"x\"][0], cx + dd[\"x\"][1], y0 - dd[\"thickness\"], y0 + 0.5, pz + dd[\"z\"][0],\n+                pz + dd[\"z\"][1], m[\"print\"])\n+    bevel(lever, 0.8, segments=2)\n+    paint(lever, m[\"print\"], swatch(L.PRINT_SWATCHES[\"dark\"]), lambda c, n, name: True)\n+    return lever, Vector((cx, y0 - dd[\"thickness\"] / 2, pz + L.DRIVE[\"slot\"][\"z\"] + L.DRIVE[\"slot\"][\"height\"] / 2))\n+\n+\n+def in_use(m):\n+    \"\"\"Drive 1's IN USE light lit: a disc just over the printed one, its middle at its origin.\"\"\"\n+    lx, lz, r = L.DRIVE[\"light\"]\n+    y = L.DRIVE[\"front\"] + L.DRIVE[\"panel\"][2] - 0.15\n+    bm = bmesh.new()\n+    ring = [bm.verts.new((r * 1.1 * math.cos(2 * math.pi * k / 12), 0.0, r * 1.1 * math.sin(2 * math.pi * k / 12)))\n+            for k in range(12)]\n+    bm.faces.new(ring)                                     # wound to face -y\n+    lit = new_object(\"in_use\", bm, m[\"print\"])\n+    paint(lit, m[\"print\"], swatch(L.PRINT_SWATCHES[\"lit\"]), lambda c, n, name: True)\n+    return lit, Vector((L.DRIVE[\"at\"][0] + lx, y, L.PANEL_Z + lz))\n+\n+\n+def monitor(m):\n+    \"\"\"The Monitor II on the drives, its front's middle at MONITOR[\"at\"]: the shell, the surround and the opening,\n+    the glass, the control strip's seam and face.\"\"\"\n+    M = L.MONITOR\n+    w, b, o = M[\"width\"], M[\"bezel\"], M[\"opening\"]\n+    shell = prism_x(\"monitor\", M[\"profile\"], -w / 2, w / 2, m[\"case\"])\n+    bevel(shell, 3.0, segments=2)\n+    boolean(shell, box(\"surround\", b[\"x\"][0], b[\"x\"][1], -5.0, b[\"depth\"], b[\"z\"][0], b[\"z\"][1], m[\"bezel\"]))\n+    outer, inner = rounded(o[\"x\"], o[\"z\"], *o[\"outer\"]), rounded(o[\"x\"], o[\"z\"], *o[\"inner\"])\n+    boolean(shell, solid(\"opening\", [[(x, y, z) for x, z in outline] for outline, y in\n+                                     ((outer, -10.0), (outer, b[\"depth\"] - 0.01), (inner, o[\"depth\"]),\n+                                      (inner, o[\"depth\"] + 1.0))], m[\"bezel\"]))\n+    sx = w / 2 - M[\"strip\"]\n+    boolean(shell, box(\"seam\", sx - 0.6, sx + 0.6, -5.0, 1.2, -5.0, M[\"profile\"][-1][1] + 5.0, m[\"dark\"]))\n+    h = M[\"profile\"][-1][1]\n+    face = atlas_uv(L.PRINT_REGIONS[\"strip\"], M[\"strip\"], h)\n+    paint(shell, m[\"print\"], lambda co: face(co.x - sx, h - co.z),\n+          lambda c, n, name: n.y < -0.9 and c.x > sx + 0.6 and c.y < 0.5 and name == \"case\")\n+\n+    gw, gh = M[\"glass\"][\"size\"]\n+    glass = curved(\"glass\", gw, gh, M[\"glass\"][\"bulge\"], atlas_uv(L.MEDIA_REGIONS[\"banner\"], 1.0, 1.0), m[\"glass\"])\n+    glass.data.transform(Matrix.Translation((o[\"x\"], o[\"depth\"], o[\"z\"])))\n+    parts = [shell, glass]\n+    at = Matrix.Translation((M[\"at\"][0], M[\"at\"][1], L.HEIGHT + L.DRIVE[\"size\"][2]))\n+    for obj in parts:\n+        obj.data.transform(at)\n+    return parts\n+\n+\n+def screen(m, name, region):\n+    \"\"\"A screen over the glass, its middle at its origin (the clip shows and hides it).\"\"\"\n+    gw, gh = L.MONITOR[\"glass\"][\"size\"]\n+    return curved(name, gw, gh, L.MONITOR[\"glass\"][\"bulge\"], atlas_uv(L.MEDIA_REGIONS[region], 1.0, 1.0), m[\"glass\"])\n+\n+\n+def floppy(m):\n+    \"\"\"The floppy, its middle at its origin, lying flat, the edge that goes in first towards +y, its label up.\"\"\"\n+    s, t = L.DISK[\"size\"], L.DISK[\"thickness\"]\n+    jacket = box(\"floppy\", -s / 2, s / 2, -s / 2, s / 2, -t / 2, t / 2, m[\"disk\"])\n+    bevel(jacket, 0.5, segments=1)\n+    face = atlas_uv(L.MEDIA_REGIONS[\"disk\"], s, s)\n+    paint(jacket, m[\"disk\"], lambda co: face(co.x + s / 2, s / 2 - co.y), lambda c, n, name: n.z > 0.9)\n+    paint(jacket, m[\"disk\"], swatch(L.MEDIA_SWATCHES[\"jacket\"]), lambda c, n, name: n.z <= 0.9)\n+    return jacket\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+def appear(f, f0, f1):\n+    return HIDDEN + (1 - HIDDEN) * back_out(phase(f, f0, f1)) if f >= f0 else HIDDEN\n+\n+\n+OUT = 175.0          # how far in front of its seat the floppy waits: wholly out of the drive\n+\n+\n+def timeline(f):\n+    # The floppy: appears in front of drive 1, slides in; later slides back out and goes.\n+    if f < 8 or f >= 214:\n+        disk = Pose(scale=HIDDEN)\n+    elif f < 18:\n+        disk = Pose(y=-OUT, scale=appear(f, 8, 16))\n+    elif f < 40:\n+        disk = Pose(y=-OUT * (1 - smooth(phase(f, 18, 40))))\n+    elif f < 188:\n+        disk = Pose()\n+    elif f < 206:\n+        disk = Pose(y=-OUT * ease_out(phase(f, 188, 206)))\n+    else:\n+        disk = Pose(y=-OUT, scale=max(HIDDEN, 1 - ease_in(phase(f, 206, 214))))\n+\n+    # The door turns down over the slot once the floppy's in, and back up before it comes out.\n+    if f < 180:\n+        turn = 180.0 * smooth(phase(f, 42, 50))\n+    else:\n+        turn = 180.0 * (1 - smooth(phase(f, 180, 188)))\n+\n+    # IN USE while the game loads, and again after RESET while the IIe looks for a disk.\n+    light = Pose() if 50 <= f < 98 or 168 <= f < 182 else Pose(scale=HIDDEN)\n+\n+    # The title screen arrives a pass of hi-res lines at a time; RESET clears it back to the banner.\n+    def shown(f0, f1):\n+        return Pose() if f0 <= f < f1 else Pose(scale=HIDDEN)\n+    poses = {\"floppy\": disk, \"door\": Pose(pitch=turn), \"in_use\": light, \"screen_pass2\": shown(62, 74),\n+             \"screen_pass5\": shown(74, 86), \"screen_title\": shown(86, 166)}\n+    for k, name in enumerate(RESET_KEYS):                   # Control, then RESET, held, let go together\n+        d = 3.0 * smooth(phase(f, 156 + 4 * k, 159 + 4 * k)) * (1 - smooth(phase(f, 168, 172)))\n+        poses[name] = Pose(x=PRESS.x * d, y=PRESS.y * d, z=PRESS.z * d)\n+    return poses\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    printed = bpy.data.images.load(os.path.join(images, \"apple2_print.png\"))\n+    media = bpy.data.images.load(os.path.join(images, \"apple2_media.png\"))\n+    colours = dict(L.COLOURS, print=\"#FFFFFF\", glass=\"#FFFFFF\", disk=\"#FFFFFF\")\n+    textures = dict(print=printed, glass=media, disk=media)\n+    m = {n: material(n, colours[n], r, textures.get(n)) for n, r in ROUGHNESS.items()}\n+\n+    static, moving = keys(m)\n+    whole = join(computer(m) + static + [drive(m, 1), drive(m, 2), door(m, L.DRIVE[\"at\"][1])[0]] + monitor(m), \"apple2\")\n+    lever, hinge = door(m, L.DRIVE[\"at\"][0])\n+    lever.data.transform(Matrix.Translation(-hinge))\n+    lit, lit_at = in_use(m)\n+    disk = floppy(m)\n+    screens = {name: screen(m, \"screen_\" + name, name) for name in (\"pass2\", \"pass5\", \"title\")}\n+    for obj in [whole, lever, disk] + list(moving.values()):\n+        for p in obj.data.polygons:\n+            p.use_smooth = True\n+        obj.data.set_sharp_from_angle(angle=math.radians(35))\n+\n+    # The node tree: the root (the view and the fit) holds the set; the set holds the moving parts at their places.\n+    root = bpy.data.objects.new(\"apple2_root\", None)\n+    scene.collection.objects.link(root)\n+    whole.parent = root\n+    M, o = L.MONITOR, L.MONITOR[\"opening\"]\n+    glass_front = Vector((M[\"at\"][0] + o[\"x\"], M[\"at\"][1] + o[\"depth\"], L.HEIGHT + L.DRIVE[\"size\"][2] + o[\"z\"]))\n+    placed = [(disk, Vector(L.SEAT)), (lever, hinge), (lit, lit_at)]\n+    placed += [(part, glass_front + Vector((0.0, -0.4 - 0.3 * k, 0.0))) for k, part in enumerate(screens.values())]\n+    for part, where in placed:\n+        part.parent = whole\n+        part.location = where\n+    for part in moving.values():\n+        part.parent = whole\n+    nodes = {\"floppy\": disk, \"door\": lever, \"in_use\": lit, **{\"screen_\" + k: v for k, v in screens.items()}, **moving}\n+\n+    # The three-quarter view, then the spec's fit of the set at rest, as the root's transform.\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    points = [tilt @ v.co for v in whole.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [whole, lever, lit, disk] + list(screens.values()) + list(moving.values())\n+    print(f\"Apple II: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(whole.data)} the set), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "0e87146e1e9df840f9de113d833fb574a959b3d7",
          "filename": "tools/console-models/apple2_layout.py",
          "status": "added",
          "additions": 156,
          "deletions": 0,
          "changes": 156,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fapple2_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fapple2_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fapple2_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,156 @@\n+\"\"\"The Apple IIe, its two Disk II drives and its Monitor II: the measurements shared by apple2_print.py (which draws\n+their printing) and apple2.py (which builds them). Plain Python: Blender's Python imports it too.\n+\n+Millimetres: x from the computer's middle (left to right), y back from its front edge, z up from the table. The IIe is\n+392 x 448 x 108 mm: a wedge whose sloping top runs from its low front edge (undercut beneath) up to the flat top over\n+its back two thirds, the keyboard in a well in the slope, RESET in a recess of its own at its right, the lid's front\n+sloping down to the keyboard with the apple //e badge on it, and a comb of vent slots in each side. On its lid stand\n+the two Disk II drives side by side, and on them the Monitor II, its depth cut from about 330 mm to 262 (the launcher\n+fits a card's largest side, so the set's depth would draw it smaller than the other cards). Measured from photographs:\n+the Bibliotheque nationale de France's set of an IIe and its drive (from above, in front, each side, the drive from\n+above), a front view of the set on Wikimedia Commons, and close views of the US keyboard; scaled by the keys' 19.05 mm\n+pitch.\n+\n+The keyboard is the US one. Its keys are laid out in units of U from the main block's left edge (KEYS_X0), in rows\n+from the front (row 5, the space bar's) to the back (row 1, the digits'). The top slopes at SLOPE: a key stands on it\n+in the slope's frame (x across, s up the slope from the front edge, h off it), and slope_point() maps a point of that\n+frame to the model's (y, z).\n+\n+Two textures. `print` (ATLAS square, PRINT_REGIONS): `top` (the computer's top in front of the drives seen from\n+above: the key tops and legends, the power light, the badge; planar-mapped onto every face facing up), `drive1` and\n+`drive2` (the drives' front panels), `strip` (the monitor's control strip), and swatches. `media` (MEDIA_REGIONS):\n+the screens, each the glass's whole width and height, green on black (`banner`, the screen at rest: Apple //e at its\n+top; `pass2` and `pass5`, Odyssey's title screen part loaded, the hi-res lines in the order they arrive; `title`, the\n+whole of it), the floppy's label side, and swatches. A region is (x, y, width, height) in pixels.\n+\"\"\"\n+\n+import math\n+\n+ATLAS = 1024\n+\n+# -- The computer ----------------------------------------------------------------------------------------------------\n+\n+WIDTH, DEPTH, HEIGHT = 392.0, 448.0, 108.0\n+TIP = (0.0, 53.0)                     # the front edge's top\n+FLAT_Y = 247.0                        # where the sloping top meets the flat top\n+SLOPE = math.atan2(HEIGHT - TIP[1], FLAT_Y - TIP[0])\n+UNDERCUT = (52.0, 4.0)                # the undercut runs back from under the front edge to here, (y, z)\n+PLATE = 4.0                           # the dark base plate under it\n+\n+\n+def profile():\n+    \"\"\"The computer's side outline, (y, z), anticlockwise seen from its right end.\"\"\"\n+    return [(UNDERCUT[0] + 4.0, 0.0), (DEPTH - 3.0, 0.0), (DEPTH, 3.0), (DEPTH, HEIGHT - 4.0), (DEPTH - 4.0, HEIGHT),\n+            (FLAT_Y, HEIGHT), (TIP[0] + 2.0, TIP[1] + 0.45), (TIP[0], TIP[1] - 2.0), UNDERCUT]\n+\n+\n+def slope_point(s, h):\n+    \"\"\"The model's (y, z) of the point s up the top's slope from the front edge and h off it.\"\"\"\n+    c, k = math.cos(SLOPE), math.sin(SLOPE)\n+    return TIP[0] + s * c - h * k, TIP[1] + s * k + h * c\n+\n+\n+SEAMS = dict(lid_front=139.0, lid_fold=243.0, lid_x=154.3)   # the lid's front edge and fold, along y; its sides, x\n+BADGE = dict(x=(-141.5, -53.1), split=-79.8, y=(168.4, 192.0))  # apple and //e, side by side, on the lid's slope\n+VENTS = dict(y=[252.0 + 10.6 * k for k in range(18)], width=4.2, z=(66.0, HEIGHT + 5.0), inset=12.0)\n+\n+# The keyboard: KEYS_X0 is the main block's left edge, U a key's pitch, GAP between two keys. A key stands from the\n+# well's floor (WELL below the top) to its top, KEY_HEIGHT above it, tapering by KEY_INSET on every side.\n+U, GAP = 19.05, 1.0\n+KEYS_X0 = -140.0\n+ROW_S = {5: 19.5, 4: 38.55, 3: 57.6, 2: 76.65, 1: 95.7}    # each row's front edge, up the slope\n+WELL, KEY_HEIGHT, KEY_INSET = 4.0, 10.0, 2.4\n+RESET = dict(x=(141.9, 158.3), s=(93.6, 108.9), well=(138.5, 161.7, 90.5, 112.0))\n+\n+\n+def kx(u):\n+    return KEYS_X0 + u * U\n+\n+\n+def key_rect(xu, row, wu):\n+    \"\"\"A key's base, (x0, s0, x1, s1) in the slope's frame.\"\"\"\n+    s0, s1 = ROW_S[row], ROW_S[row] + U\n+    return kx(xu) + GAP / 2, s0 + GAP / 2, kx(xu + wu) - GAP / 2, s1 - GAP / 2\n+\n+\n+def _row(names, x0, row, legends):\n+    return [(n, x0 + i, row, 1.0, legend) for i, (n, legend) in enumerate(zip(names, legends))]\n+\n+\n+# (name, x in units, row, width in units, legend): a legend's line break splits its upper and lower characters. The\n+# power light stands in the bottom row like a key, lit.\n+KEYS = (\n+    [(\"esc\", 0.0, 1, 1.0, \"Esc\")]\n+    + _row([f\"d{k}\" for k in \"1234567890\"] + [\"minus\", \"equals\"], 1.0, 1,\n+           [\"!\\n1\", \"@\\n2\", \"#\\n3\", \"$\\n4\", \"%\\n5\", \"^\\n6\", \"&\\n7\", \"*\\n8\", \"(\\n9\", \")\\n0\", \"_\\n-\", \"+\\n=\"])\n+    + [(\"delete\", 13.0, 1, 1.5, \"Delete\")]\n+    + [(\"tab\", 0.0, 2, 1.5, \"Tab\")]\n+    + _row(list(\"qwertyuiop\") + [\"lbracket\", \"rbracket\", \"backslash\"], 1.5, 2,\n+           list(\"QWERTYUIOP\") + [\"{\\n[\", \"}\\n]\", \"|\\n\\\\\"])\n+    + [(\"control\", 0.0, 3, 1.75, \"Control\")]\n+    + _row(list(\"asdfghjkl\") + [\"semicolon\", \"quote\"], 1.75, 3, list(\"ASDFGHJKL\") + [\":\\n;\", '\"\\n\\''])\n+    + [(\"return\", 12.75, 3, 1.75, \"Return\")]\n+    + [(\"lshift\", 0.0, 4, 2.25, \"Shift\")]\n+    + _row(list(\"zxcvbnm\") + [\"comma\", \"period\", \"slash\"], 2.25, 4, list(\"ZXCVBNM\") + [\"<\\n,\", \">\\n.\", \"?\\n/\"])\n+    + [(\"rshift\", 12.25, 4, 2.25, \"Shift\")]\n+    + [(\"caps\", 0.0, 5, 1.25, \"Caps\\nLock\"), (\"grave\", 1.25, 5, 1.0, \"~\\n`\"), (\"light\", 2.25, 5, 1.0, \"\"),\n+       (\"open_apple\", 3.25, 5, 1.0, \"open\"), (\"space\", 4.25, 5, 5.25, \"\"), (\"solid_apple\", 9.5, 5, 1.0, \"solid\")]\n+    + _row([\"left\", \"right\", \"down\", \"up\"], 10.5, 5, [\"\u2190\", \"\u2192\", \"\u2193\", \"\u2191\"])\n+)\n+\n+\n+def well():\n+    \"\"\"The keyboard's well, an (x, s) outline.\"\"\"\n+    m = 1.6\n+    s5, s1 = ROW_S[5] - m, ROW_S[1] + U + m\n+    return [(kx(0) - m, s5), (kx(14.5) + m, s5), (kx(14.5) + m, s1), (kx(0) - m, s1)]\n+\n+\n+# -- The Disk II drives ----------------------------------------------------------------------------------------------\n+\n+DRIVE = dict(size=(155.0, 222.0, 87.0), at=(-102.5, 102.5), front=244.0,\n+             panel=(147.0, 79.0, 1.5),       # the front panel: w, h (centred), how far it's sunk into the front\n+             slot=dict(z=40.0, height=3.2, x=(-64.0, 64.0), depth=150.0),  # z from the panel's bottom\n+             door=dict(x=(-17.5, 17.5), z=(42.0, 66.0), thickness=4.5),   # the lever over the slot, open (up)\n+             recess=dict(x=(-16.5, 16.5), z=(12.0, 37.0), depth=6.0),     # the finger recess under the slot\n+             light=(-43.0, 14.5, 1.6),       # IN USE's light: its x, z from the panel's bottom, radius\n+             vents=dict(y=[150.0 + 6.5 * k for k in range(9)], width=2.6, z=(55.0, 92.0)))\n+PANEL_Z = HEIGHT + (DRIVE[\"size\"][2] - DRIVE[\"panel\"][1]) / 2   # the panels' bottom edge\n+\n+# -- The monitor -----------------------------------------------------------------------------------------------------\n+\n+MONITOR = dict(\n+    at=(0.0, 238.0),                 # its front's middle, in the computer's (x, y), standing on the drives\n+    width=396.0,\n+    profile=[(0.0, 0.0), (262.0, 0.0), (262.0, 236.0), (230.0, 292.0), (0.0, 292.0)],   # (y back, z)\n+    strip=39.0,                      # the control strip down its right side, its width\n+    bezel=dict(x=(-178.0, 144.0), z=(20.0, 256.0), depth=3.0),   # the screen's surround, sunk into the front\n+    opening=dict(x=-17.0, z=138.0, outer=(300.0, 214.0, 14.0), inner=(268.0, 196.0, 10.0), depth=24.0),\n+    glass=dict(size=(264.0, 192.0), bulge=8.0),\n+)\n+\n+# -- The floppy ------------------------------------------------------------------------------------------------------\n+\n+DISK = dict(size=133.0, thickness=1.6)                # a 5.25-inch floppy in its jacket\n+SEAT = (DRIVE[\"at\"][0], DRIVE[\"front\"] + 8.0 + DISK[\"size\"] / 2, PANEL_Z + DRIVE[\"slot\"][\"z\"])   # its middle, in\n+\n+# -- The atlases -----------------------------------------------------------------------------------------------------\n+\n+COLOURS = dict(case=\"#E3DCC4\", keys=\"#A39E8F\", bezel=\"#C5BCA6\", dark=\"#2A2826\", top_keys=\"#AEA99A\",\n+               drive=\"#29292B\", plate=\"#5B544A\")\n+\n+PRINT_REGIONS = dict(top=(0, 0, 1024, 653), drive1=(0, 661, 400, 225), drive2=(408, 661, 400, 225),\n+                     strip=(816, 661, 80, 363))\n+PRINT_SWATCHES = dict(bezel=(920, 680), dark=(952, 680), lit=(984, 680), plate=(920, 712), green=(952, 712),\n+                      drive=(984, 712))\n+\n+SCREEN_PX = (504, 380)       # a screen's region, the glass's proportions\n+RASTER = (448, 336)          # the lit picture in its middle, 4:3; the Apple's 280 x 192 drawn into it\n+MEDIA_REGIONS = dict(banner=(0, 0) + SCREEN_PX, pass2=(512, 0) + SCREEN_PX, pass5=(0, 388) + SCREEN_PX,\n+                     title=(512, 388) + SCREEN_PX, disk=(0, 776, 240, 240))\n+MEDIA_SWATCHES = dict(black=(272, 800), jacket=(304, 800))\n+\n+# -- The focused clip ------------------------------------------------------------------------------------------------\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s"
        },
        {
          "sha": "dd536b2ef390c6094b435bd55c749ebb44bfbc4e",
          "filename": "tools/console-models/apple2_print.py",
          "status": "added",
          "additions": 353,
          "deletions": 0,
          "changes": 353,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fapple2_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fapple2_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fapple2_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,353 @@\n+\"\"\"The Apple IIe's, its Disk II drives' and its Monitor II's printing, for the console theme's Apple II model\n+(tools/console-models/apple2.py).\n+\n+Draws its two atlases, their regions as apple2_layout.py places them. apple2_print.png: the computer's top in front of\n+the drives, seen from above, in millimetres as the model's planar UVs map it (each key's top with its legend, the\n+power light lit in the bottom row, the lid's seams and the apple //e badge), the two drives' front panels (DRIVE 1 and\n+DRIVE 2, the IN USE lights off, disk II and the rainbow apple), the monitor's control strip (its power light lit, the\n+switch, the badge), and the swatches small parts take their colour from. apple2_media.png: the screens, each the\n+glass's whole width and height, the Apple's 280 x 192 pixels in green phosphor round a dark border (the banner the\n+IIe shows looking for a disk; the title screen of a made-up game, Odyssey, two passes into loading, the hi-res lines\n+arriving every eighth line at a time, and whole), and the floppy's label side. Run it with the system Python (Pillow):\n+\n+    python tools/console-models/apple2_print.py <out folder>\n+\n+It writes apple2_print.png and apple2_media.png there.\n+\"\"\"\n+\n+import os\n+import sys\n+\n+from PIL import Image, ImageChops, ImageDraw, ImageFilter, ImageFont\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import apple2_layout as L  # noqa: E402\n+from psp_print import ship  # noqa: E402\n+from retro_pc_print import rgb, shade  # noqa: E402\n+from windows_pc_print import FONTS, Region  # noqa: E402\n+\n+CASE = rgb(L.COLOURS[\"case\"])\n+LEGEND = (58, 56, 52)\n+RAINBOW = [(97, 187, 70), (253, 184, 39), (245, 130, 31), (224, 58, 62), (150, 61, 151), (0, 157, 220)]\n+TOP_DEPTH = 250.0            # the top region's depth: the top in front of the drives\n+PHOSPHOR, DARK_GLASS = (88, 255, 120), (8, 20, 12)\n+\n+\n+def top_point(x, y):\n+    \"\"\"A point of the computer's top (x from its middle, y back from its front) in the top region's millimetres.\"\"\"\n+    return x + L.WIDTH / 2, TOP_DEPTH - y\n+\n+\n+def slope_top(points, h):\n+    \"\"\"An (x, s) outline on the slope, raised h off it, in the top region's millimetres.\"\"\"\n+    return [top_point(x, L.slope_point(s, h)[0]) for x, s in points]\n+\n+\n+# ---- The apple ---------------------------------------------------------------------------------------------------------\n+\n+def apple(reg, cx, cy, h, fill=None, outline=None, width=0.3, rainbow=False):\n+    \"\"\"Apple's apple, h tall, its middle at (cx, cy): the body with its bite and the leaf; striped like the rainbow\n+    logo, filled, or outlined.\"\"\"\n+    mask = Image.new(\"L\", reg.img.size, 0)\n+    d = ImageDraw.Draw(mask)\n+    w = h * 0.94\n+    d.ellipse(reg.box(cx - w / 2, cy - h * 0.3, cx + w * 0.04, cy + h / 2), fill=255)\n+    d.ellipse(reg.box(cx - w * 0.04, cy - h * 0.3, cx + w / 2, cy + h / 2), fill=255)\n+    d.ellipse(reg.box(cx - w * 0.13, cy + h * 0.41, cx + w * 0.13, cy + h * 0.56), fill=0)    # the dip at its foot\n+    d.ellipse(reg.box(cx + w * 0.3, cy - h * 0.1, cx + w * 0.68, cy + h * 0.28), fill=0)      # the bite\n+    d.ellipse(reg.box(cx - w * 0.2, cy - h * 0.38, cx + w * 0.12, cy - h * 0.2), fill=0)      # the dip at its top\n+    d.polygon([reg.p(cx + w * 0.02, cy - h * 0.34), reg.p(cx + w * 0.28, cy - h * 0.62),\n+               reg.p(cx + w * 0.06, cy - h * 0.5)], fill=255)                                  # the leaf\n+    if rainbow:\n+        bands = Image.new(\"RGBA\", reg.img.size, (0, 0, 0, 0))\n+        db = ImageDraw.Draw(bands)\n+        top, bottom = cy - h * 0.62, cy + h / 2\n+        for k, colour in enumerate(RAINBOW):\n+            y0 = top + (bottom - top) * (0.0 if k == 0 else 0.33 + (k - 1) * 0.134)\n+            y1 = top + (bottom - top) * (0.33 + k * 0.134)\n+            db.rectangle(reg.box(cx - w, y0, cx + w, y1), fill=colour + (255,))\n+        reg.img.paste(bands, (0, 0), mask)\n+    elif fill is not None:\n+        reg.img.paste(fill, (0, 0), mask)\n+    if outline is not None:\n+        edge = mask.filter(ImageFilter.FIND_EDGES).point(lambda v: 255 if v > 60 else 0)\n+        edge = edge.filter(ImageFilter.MaxFilter(max(1, round(width * reg.sx)) | 1))\n+        reg.img.paste(outline, (0, 0), edge)\n+\n+\n+# ---- The computer's top ------------------------------------------------------------------------------------------------\n+\n+def key_top(reg, name, xu, row, wu, legend):\n+    x0, s0, x1, s1 = L.key_rect(xu, row, wu)\n+    outline = [(x0, s0), (x1, s0), (x1, s1), (x0, s1)]\n+    top = slope_top([(x0 + L.KEY_INSET, s0 + L.KEY_INSET), (x1 - L.KEY_INSET, s1 - L.KEY_INSET)], L.KEY_HEIGHT)\n+    (ax, ay), (bx, by) = top\n+    fill = rgb(L.COLOURS[\"top_keys\"])\n+    if name == \"light\":                                    # a pale window, the green light glowing in it\n+        top = slope_top([(x0 + 1.0, s0 + 1.0), (x1 - 1.0, s1 - 1.0)], L.KEY_HEIGHT - 3.0)\n+        (ax, ay), (bx, by) = top\n+        reg.rect_(ax - 0.6, by - 0.6, bx + 0.6, ay + 0.6, (226, 224, 212))\n+        glow = reg.layer()\n+        cx, cy = (ax + bx) / 2, (ay + by) / 2\n+        ImageDraw.Draw(glow).rounded_rectangle(reg.box(cx - 5.5, cy - 3.5, cx + 5.5, cy + 3.5), radius=2 * reg.sx,\n+                                               fill=(110, 255, 90, 170))\n+        reg.over(glow.filter(ImageFilter.GaussianBlur(1.6 * reg.sx)))\n+        reg.rect_(cx - 3.2, cy - 1.4, cx + 3.2, cy + 1.4, (150, 255, 120), r=0.8)\n+        return\n+    reg.rect_(ax - 0.7, by - 0.7, bx + 0.7, ay + 0.7, fill)\n+    reg.rect_(ax + 1.0, by + 1.0, bx - 1.0, ay - 1.0, shade(fill, 0.05), r=1.4)\n+    if not legend:\n+        return\n+    if legend in (\"open\", \"solid\"):\n+        apple(reg, ax + 3.6, by + 3.6, 3.8, fill=LEGEND if legend == \"solid\" else None,\n+              outline=LEGEND if legend == \"open\" else None, width=0.35)\n+        return\n+    lines = legend.split(\"\\n\")\n+    if len(lines) == 1 and len(legend) == 1 and not legend.isalnum():\n+        reg.text(legend, \"seguisym.ttf\", 4.0, (ax + bx) / 2, (ay + by) / 2, LEGEND)\n+        return\n+    if len(lines) == 1 and len(legend) == 1:\n+        reg.text(legend, \"arial.ttf\", 3.4, ax + 2.6, by + 3.2, LEGEND)\n+        return\n+    size = 2.3 if max(len(line) for line in lines) > 2 else 3.0\n+    for k, line in enumerate(lines):\n+        reg.text(line, \"arial.ttf\", size, ax + 1.8, by + 2.6 + k * (size + 1.0), LEGEND, anchor=\"lm\")\n+\n+\n+def badge(reg):\n+    (x0, y0), (x1, y1) = top_point(L.BADGE[\"x\"][0], L.BADGE[\"y\"][1]), top_point(L.BADGE[\"x\"][1], L.BADGE[\"y\"][0])\n+    split = top_point(L.BADGE[\"split\"], 0.0)[0]\n+    plate = (78, 74, 70)\n+    reg.rect_(x0, y0, split - 1.2, y1, plate, r=1.4)\n+    reg.rect_(split + 1.2, y0, x1, y1, plate, r=1.4)\n+    h = y1 - y0\n+    apple(reg, x0 + 0.36 * h, (y0 + y1) / 2 + 0.4, h * 0.66, rainbow=True)\n+    layer = reg.layer()\n+    f = ImageFont.truetype(FONTS + \"arialbd.ttf\", max(1, round(h * 0.62 * reg.sx)))\n+    ImageDraw.Draw(layer).text(reg.p(x0 + 0.72 * h, (y0 + y1) / 2 + 0.6), \"apple\", font=f, fill=(226, 224, 218, 255),\n+                               anchor=\"lm\")\n+    ImageDraw.Draw(layer).text(reg.p((split + x1) / 2 + 0.3, (y0 + y1) / 2 + 0.6), \"//e\", font=f,\n+                               fill=(226, 224, 218, 255), anchor=\"mm\")\n+    reg.over(layer.transform(layer.size, Image.AFFINE, (1, 0.22, -0.22 * reg.p(0, (y0 + y1) / 2)[1], 0, 1, 0)))\n+\n+\n+def top():\n+    reg = Region(L.PRINT_REGIONS[\"top\"], L.WIDTH, TOP_DEPTH, CASE)\n+    seam = shade(CASE, -0.32)\n+    lx = L.SEAMS[\"lid_x\"]\n+    (sx0, sy), (sx1, _) = top_point(-lx, L.SEAMS[\"lid_front\"]), top_point(lx, L.SEAMS[\"lid_front\"])\n+    reg.line([(sx0, sy), (sx1, sy)], seam, 0.5)\n+    for x in (-lx, lx):\n+        a, b = top_point(x, L.SEAMS[\"lid_front\"]), top_point(x, TOP_DEPTH)\n+        reg.line([a, b], seam, 0.5)\n+    fold = top_point(0.0, L.SEAMS[\"lid_fold\"])[1]\n+    reg.line([(sx0, fold), (sx1, fold)], shade(CASE, -0.08), 0.6)\n+    badge(reg)\n+    for name, xu, row, wu, legend in L.KEYS:\n+        key_top(reg, name, xu, row, wu, legend)\n+    x0, x1 = L.RESET[\"x\"]\n+    s0, s1 = L.RESET[\"s\"]\n+    (ax, ay), (bx, by) = slope_top([(x0 + 1.6, s0 + 1.6), (x1 - 1.6, s1 - 1.6)], 6.0)\n+    fill = rgb(L.COLOURS[\"top_keys\"])\n+    reg.rect_(ax - 0.7, by - 0.7, bx + 0.7, ay + 0.7, fill)\n+    reg.rect_(ax + 0.8, by + 0.8, bx - 0.8, ay - 0.8, shade(fill, 0.05), r=1.0)\n+    reg.text(\"Reset\", \"arial.ttf\", 2.2, (ax + bx) / 2, (ay + by) / 2, LEGEND)\n+    return reg\n+\n+\n+# ---- The drives and the monitor's strip --------------------------------------------------------------------------------\n+\n+def drive_panel(n):\n+    w, h, _ = L.DRIVE[\"panel\"]\n+    base = rgb(L.COLOURS[\"drive\"])\n+    reg = Region(L.PRINT_REGIONS[f\"drive{n}\"], w, h, base)\n+    grain = reg.layer()\n+    d = ImageDraw.Draw(grain)\n+    for k in range(2600):                                   # the panel's fine texture\n+        x, y = (k * 7919) % (reg.img.size[0]), (k * 104729 // 3) % (reg.img.size[1])\n+        d.point((x, y), fill=(255, 255, 255, 10 + k % 14))\n+    reg.over(grain)\n+\n+    def at(x, z):\n+        return x + w / 2, h - z\n+    reg.text(\"DRIVE\", \"arial.ttf\", 3.0, *at(46.0, 52.5), (214, 214, 212), anchor=\"rm\")\n+    reg.text(str(n), \"arial.ttf\", 6.0, *at(52.5, 53.2), (214, 214, 212))\n+    lx, lz, r = L.DRIVE[\"light\"]\n+    reg.text(\"IN USE\", \"arial.ttf\", 2.6, *at(lx - 4.6, lz), (214, 214, 212), anchor=\"rm\")\n+    ax, ay = at(lx - 3.6, lz)\n+    reg.poly([(ax, ay - 1.2), (ax + 2.0, ay), (ax, ay + 1.2)], (214, 214, 212))\n+    reg.circle(*at(lx, lz), r + 0.5, (16, 16, 18))\n+    reg.circle(*at(lx, lz), r, (92, 22, 20))\n+    layer = reg.layer()\n+    f = ImageFont.truetype(FONTS + \"arialbd.ttf\", max(1, round(8.6 * reg.sx)))\n+    ImageDraw.Draw(layer).text(reg.p(*at(-66.0, 5.6)), \"disk\", font=f, fill=(230, 230, 228, 255), anchor=\"ls\")\n+    reg.over(layer)\n+    reg.rect_(*at(-43.5, 12.2), *at(-42.3, 5.6), (230, 230, 228))\n+    reg.rect_(*at(-41.2, 12.2), *at(-40.0, 5.6), (230, 230, 228))\n+    ax, ay = at(47.0, 14.0)\n+    apple(reg, ax, ay, 15.0, rainbow=True)\n+    return reg\n+\n+\n+def strip():\n+    w, h = L.MONITOR[\"strip\"], L.MONITOR[\"profile\"][-1][1]\n+    reg = Region(L.PRINT_REGIONS[\"strip\"], w, h, CASE)\n+\n+    def at(x, z):\n+        return x, h - z\n+    reg.rect_(*at(1.0, h - 2.0), *at(w - 1.0, 2.0), shade(CASE, 0.03), r=2.0)\n+    glow = reg.layer()\n+    gx, gy = at(w / 2 + 6.0, h - 9.0)\n+    ImageDraw.Draw(glow).ellipse(reg.box(gx - 5.0, gy - 3.0, gx + 5.0, gy + 3.0), fill=(90, 255, 110, 150))\n+    reg.over(glow.filter(ImageFilter.GaussianBlur(1.4 * reg.sx)))\n+    reg.rect_(gx - 2.6, gy - 1.2, gx + 2.6, gy + 1.2, (160, 255, 150), r=0.6)\n+    cx, cy = at(w / 2, h - 26.0)\n+    reg.circle(cx, cy, 6.5, shade(CASE, -0.12))\n+    reg.circle(cx, cy, 5.5, shade(CASE, 0.04))\n+    reg.circle(cx, cy, 2.6, outline=shade(CASE, -0.5), width=0.4)\n+    cx, cy = at(w / 2, 150.0)\n+    reg.circle(cx, cy, 6.5, shade(CASE, -0.12))\n+    reg.circle(cx, cy, 5.5, shade(CASE, 0.04))\n+    reg.rect_(cx - 2.6, cy - 0.3, cx + 2.6, cy + 0.3, shade(CASE, -0.5))\n+    bx, by = at(w / 2, 26.0)\n+    reg.rect_(bx - 8.0, by - 9.5, bx + 8.0, by + 9.5, (246, 244, 236), r=1.2, outline=shade(CASE, -0.2), width=0.3)\n+    apple(reg, bx, by + 0.6, 12.5, rainbow=True)\n+    return reg\n+\n+\n+def print_atlas(out):\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), CASE)\n+    for reg in (top(), drive_panel(1), drive_panel(2), strip()):\n+        reg.paste(image)\n+    d = ImageDraw.Draw(image)\n+    for name, colour in ((\"bezel\", rgb(L.COLOURS[\"bezel\"])), (\"dark\", rgb(L.COLOURS[\"dark\"])), (\"lit\", (255, 60, 44)),\n+                         (\"plate\", rgb(L.COLOURS[\"plate\"])), (\"green\", (120, 255, 110)),\n+                         (\"drive\", rgb(L.COLOURS[\"drive\"]))):\n+        x, y = L.PRINT_SWATCHES[name]\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colour)\n+    path = os.path.join(out, \"apple2_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+# ---- The screens -------------------------------------------------------------------------------------------------------\n+\n+FONT = ImageFont.truetype(FONTS + \"consolab.ttf\", 9)\n+\n+\n+def text_screen(lines):\n+    \"\"\"The 40-column text screen: (row, column, text) in 7 x 8 cells, lit where the characters are.\"\"\"\n+    img = Image.new(\"1\", (280, 192), 0)\n+    d = ImageDraw.Draw(img)\n+    d.fontmode = \"1\"\n+    for row, col, text in lines:\n+        for k, ch in enumerate(text):\n+            d.text((col * 7 + k * 7 + 3.5, row * 8 + 4), ch, font=FONT, fill=1, anchor=\"mm\")\n+    return img\n+\n+\n+def title_screen():\n+    \"\"\"Odyssey's title screen in hi-res, one bit a pixel: ODYSSEY large over a ringed planet, the ship, stars, and\n+    PRESS SPACE under them.\"\"\"\n+    img = Image.new(\"1\", (280, 192), 0)\n+    d = ImageDraw.Draw(img)\n+    for k in range(70):\n+        x, y = (k * 7919) % 280, (k * 104729 // 7) % 192\n+        d.point((x, y), fill=1)\n+        if k % 9 == 0:\n+            d.point([(x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)], fill=1)\n+    big = ImageFont.truetype(FONTS + \"ariblk.ttf\", 34)\n+    d.fontmode = \"1\"\n+    d.text((140, 30), \"ODYSSEY\", font=big, fill=1, anchor=\"mm\")\n+    d.rectangle([30, 52, 250, 53], fill=1)\n+    d.ellipse([168, 82, 236, 150], outline=1, width=2)\n+    for k in range(5):                                       # the planet's bands\n+        y = 96 + k * 11\n+        d.arc([170, y - 30, 234, y + 6], 20, 160, fill=1)\n+    d.arc([148, 104, 256, 128], 160, 380, fill=1, width=2)   # its ring\n+    ship(d, 92, 118, 2.2, 1, 1)\n+    d.line([(40, 118), (62, 118)], fill=1)\n+    d.line([(46, 112), (64, 112)], fill=1)\n+    d.line([(46, 124), (64, 124)], fill=1)\n+    small = ImageFont.truetype(FONTS + \"consolab.ttf\", 9)\n+    d.text((140, 172), \"PRESS SPACE TO LAUNCH\", font=small, fill=1, anchor=\"mm\")\n+    return img\n+\n+\n+def passes(img, n):\n+    \"\"\"The hi-res picture n eighths loaded: memory in order fills every eighth line first, then the next, and so on.\"\"\"\n+    out = Image.new(\"1\", img.size, 0)\n+    for y in range(img.size[1]):\n+        if y % 8 < n:\n+            out.paste(img.crop((0, y, img.size[0], y + 1)), (0, y))\n+    return out\n+\n+\n+def phosphor(bits):\n+    \"\"\"A screen region: the one-bit picture in green phosphor in the raster, glowing, with scanlines, round a dark\n+    border.\"\"\"\n+    w, h = L.SCREEN_PX\n+    rw, rh = L.RASTER\n+    lit = bits.convert(\"L\").resize((rw, rh), Image.NEAREST)\n+    raster = Image.new(\"RGB\", (rw, rh), DARK_GLASS)\n+    raster.paste(PHOSPHOR, (0, 0), lit)\n+    glow = Image.new(\"RGB\", (rw, rh), (0, 0, 0))\n+    glow.paste((30, 120, 55), (0, 0), lit.filter(ImageFilter.GaussianBlur(3)))\n+    raster = ImageChops.add(raster, glow)\n+    scan = Image.new(\"L\", (rw, rh), 255)\n+    ds = ImageDraw.Draw(scan)\n+    for y in range(1, rh, 2):\n+        ds.line([(0, y), (rw, y)], fill=200)\n+    raster = Image.composite(raster, Image.new(\"RGB\", (rw, rh), (0, 0, 0)), scan)\n+    screen = Image.new(\"RGB\", (w, h), (6, 12, 8))\n+    screen.paste(raster, ((w - rw) // 2, (h - rh) // 2))\n+    return screen\n+\n+\n+def disk():\n+    \"\"\"The floppy's label side, looking down on it, the edge that goes in first at the top: the black jacket, the\n+    hub's ring, the index hole, the head's slot, the write-protect notch, and Odyssey's label along the near edge.\"\"\"\n+    x, y, w, h = L.MEDIA_REGIONS[\"disk\"]\n+    reg = Region((x, y, w, h), L.DISK[\"size\"], L.DISK[\"size\"], (24, 24, 26))\n+    s = L.DISK[\"size\"]\n+    c = s / 2\n+    reg.circle(c, c, 19.0, (60, 60, 64))\n+    reg.circle(c, c, 14.5, (150, 120, 70))\n+    reg.circle(c, c, 14.0, (14, 14, 16))\n+    reg.circle(c + 25.0, c - 8.0, 2.4, (14, 14, 16))\n+    reg.rect_(c - 6.0, 8.0, c + 6.0, c - 22.0, (100, 70, 40), r=6.0)\n+    reg.rect_(c - 5.0, 9.0, c + 5.0, c - 23.0, (60, 40, 22), r=5.0)\n+    reg.rect_(s - 6.0, s - 46.0, s, s - 40.0, (10, 10, 12))\n+    reg.rect_(10.0, s - 34.0, s - 30.0, s - 8.0, (244, 242, 234), r=1.0)\n+    reg.rect_(10.0, s - 34.0, s - 30.0, s - 28.0, (206, 34, 40), r=1.0)\n+    reg.text(\"ODYSSEY\", \"arialbd.ttf\", 7.0, (s - 20.0) / 2, s - 17.5, (34, 34, 38))\n+    reg.text(\"DOS 3.3  DISK 1\", \"arial.ttf\", 3.2, (s - 20.0) / 2, s - 11.5, (80, 80, 84))\n+    return reg\n+\n+\n+def media_atlas(out):\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), (0, 0, 0))\n+    title = title_screen()\n+    screens = {\"banner\": text_screen([(0, 15, \"Apple //e\")]), \"pass2\": passes(title, 2), \"pass5\": passes(title, 5),\n+               \"title\": title}\n+    for name, bits in screens.items():\n+        x, y, _, _ = L.MEDIA_REGIONS[name]\n+        image.paste(phosphor(bits), (x, y))\n+    disk().paste(image)\n+    d = ImageDraw.Draw(image)\n+    for name, colour in ((\"black\", (6, 12, 8)), (\"jacket\", (24, 24, 26))):\n+        x, y = L.MEDIA_SWATCHES[name]\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colour)\n+    path = os.path.join(out, \"apple2_media.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    print_atlas(out)\n+    media_atlas(out)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "30e8648954cff2cdbb91085d44f8e9ae74526ed8",
          "filename": "tools/console-models/apple2gs.py",
          "status": "added",
          "additions": 381,
          "deletions": 0,
          "changes": 381,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fapple2gs.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fapple2gs.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fapple2gs.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,381 @@\n+\"\"\"The console theme's Apple IIGS system model (godot/themes/console/models/systems/apple2gs.glb): an Apple IIGS, its\n+AppleColor RGB monitor on it, its Apple 3.5 Drive beside it, its keyboard and mouse in front, Check startup device! on\n+the screen.\n+\n+Built in Blender from the real machines' proportions (apple2gs_layout.py has the measurements, taken from photographs).\n+The IIGS is its plinth (the lip sloping along its front) and its main box, grooved across its top and along its sides,\n+the seam between its lid and its base rising along its sides. The monitor's deep front frame stands on it, the screen's\n+opening sloping in to the curved glass, its housing behind, grooved along its sides and top, three knobs in its right\n+side. The drive is a low box, its slot cut high in its front, its top grooved. The keyboard is a wedge, its keys\n+standing on it, tapering to their printed tops, a blank column between the main block and the keypad, the raised\n+strip behind with the power key; the mouse a box domed towards its back. The printing is two textures drawn by\n+apple2gs_print.py: the keyboard's top, the IIGS's, the monitor's and the drive's fronts, the mouse's top and swatches;\n+and the screens and the disk.\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: the right side towards the viewer, the\n+top tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m) holding the set; the set\n+holding the 3.5-inch disk (waiting in the drive, shrunk to nothing), the drive's eject button and its lit light, three\n+screens over the glass (GS/OS starting, and the title screen and a moment of play of a made-up game, Odyssey), shrunk\n+to nothing, and RETURN.\n+\n+The `focused` clip (8 s, looping) boots a game: the disk slides into the drive and its light flickers; GS/OS starts\n+and Odyssey's title comes up; RETURN is pressed and the game starts; then the eject button goes in, the disk pops out\n+and goes, and Check startup device! is back.\n+\n+Six materials (case, key, dark, print, glass, disk) and two textures, within the system model budget. Run from the\n+repo root:\n+\n+    python tools/console-models/apple2gs_print.py artifacts/apple2gs\n+    blender -b --factory-startup --python tools/console-models/apple2gs.py -- artifacts/apple2gs godot/themes/console/models/systems/apple2gs.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import apple2gs_layout as L  # noqa: E402\n+from amiga import curved, paint, rounded, solid  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, key, phase, smooth  # noqa: E402\n+from mega_drive_cartridge import bevel, boolean, box, export, join, material, prism_x, triangles  # noqa: E402\n+from msx_layout import inset  # noqa: E402\n+\n+TILT_YAW = -24.0     # degrees about the vertical: the right side comes towards the viewer\n+TILT_PITCH = 22.0    # degrees about the horizontal: the top tips up towards the viewer\n+ROUGHNESS = dict(case=0.55, key=0.5, dark=0.7, print=0.5, glass=0.15, disk=0.4)\n+\n+# The keyboard's frame: x across from its middle, s up its slope from its front edge, h off it.\n+K = L.KEYBOARD\n+KB_SLOPE = math.atan2(K[\"heights\"][1] - K[\"heights\"][0], K[\"depth\"])\n+KB_FRONT = K[\"at\"][1] - K[\"depth\"] / 2\n+KB_FRAME = Matrix.Translation((K[\"at\"][0], KB_FRONT, K[\"heights\"][0])) @ Matrix.Rotation(KB_SLOPE, 4, \"X\")\n+PRESS = KB_FRAME.to_3x3() @ Vector((0.0, 0.0, -1.0))   # a key's travel, in the model's frame\n+\n+# The lip's frame: x across, s up its slope from its front edge, h off it.\n+LIP = ((2.0, 13.0), (L.PLINTH[\"lip\"], L.PLINTH[\"height\"]))\n+LIP_SLOPE = math.atan2(LIP[1][1] - LIP[0][1], LIP[1][0] - LIP[0][0])\n+LIP_FRAME = Matrix.Translation((0.0, LIP[0][0], LIP[0][1])) @ Matrix.Rotation(LIP_SLOPE, 4, \"X\")\n+\n+\n+# ---- UVs -------------------------------------------------------------------------------------------------------------\n+\n+def atlas_uv(region, w, h):\n+    \"\"\"A face drawn w x h into region: (a, b) millimetres across it (a to the right, b down from its top) to UVs.\"\"\"\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+KB_TOP = atlas_uv(L.REGIONS[\"keyboard\"], K[\"width\"], K[\"depth\"] + L.STRIP[\"depth\"])\n+\n+\n+def key_uv(co):\n+    \"\"\"The keyboard seen from above, in its slope's frame: its keys' tops, the blank column.\"\"\"\n+    local = KB_FRAME.inverted() @ co\n+    return KB_TOP(local.x + K[\"width\"] / 2, K[\"depth\"] + L.STRIP[\"depth\"] - local.y)\n+\n+\n+def flat_uv(co):\n+    \"\"\"The keyboard seen from above, flat: the strip and the power key.\"\"\"\n+    return KB_TOP(co.x - K[\"at\"][0] + K[\"width\"] / 2, K[\"depth\"] + L.STRIP[\"depth\"] - (co.y - KB_FRONT))\n+\n+\n+def swatch(name):\n+    x, y = L.SWATCHES[name]\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+def painted(obj, m, name):\n+    paint(obj, m[\"print\"], swatch(name), lambda c, n, _name: True)\n+    return obj\n+\n+\n+def grooves_y(name, x0, x1, count, y0, y1, z_top, width, depth, mat):\n+    \"\"\"Grooves across a top, running from front to back: count of them between x0 and x1.\"\"\"\n+    step = (x1 - x0) / count\n+    return join([box(name, x0 + (k + 0.5) * step - width / 2, x0 + (k + 0.5) * step + width / 2, y0, y1,\n+                     z_top - depth, z_top + 5.0, mat) for k in range(count)], name)\n+\n+\n+def grooves_x(name, x_face, side, zs, y0, y1, width, depth, mat):\n+    \"\"\"Grooves along a side face at x_face (side -1 the left, 1 the right), at heights zs.\"\"\"\n+    a, b = (x_face - depth, x_face + 5.0) if side > 0 else (x_face - 5.0, x_face + depth)\n+    return join([box(name, a, b, y0, y1, z - width / 2, z + width / 2, mat) for z in zs], name)\n+\n+\n+# ---- The IIGS --------------------------------------------------------------------------------------------------------\n+\n+def computer(m):\n+    w, d = L.WIDTH, L.DEPTH\n+    p = L.PLINTH\n+    plinth = prism_x(\"apple2gs\", [(2.0, 0.0), (d - 2.0, 0.0), (d, 2.0), (d, p[\"height\"]), LIP[1], LIP[0],\n+                                  (0.0, 11.0), (0.0, 2.0)], -w / 2, w / 2, m[\"case\"])\n+    bevel(plinth, 1.5, segments=2)\n+    b = L.BOX\n+    x_face = w / 2 - b[\"inset\"]\n+    body = box(\"box\", -x_face, x_face, *b[\"y\"], *b[\"z\"], m[\"case\"])\n+    bevel(body, 3.0, segments=2)\n+    g = L.TOP_GROOVES\n+    boolean(body, grooves_y(\"top\", -w / 2 + g[\"margin\"], w / 2 - g[\"margin\"], g[\"count\"], *g[\"y\"], L.HEIGHT,\n+                            g[\"width\"], g[\"depth\"], m[\"case\"]))\n+    s = L.SIDE_GROOVES\n+    for side in (-1, 1):\n+        boolean(body, grooves_x(\"side\", side * x_face, side, s[\"z\"], *s[\"y\"], s[\"width\"], s[\"depth\"], m[\"case\"]))\n+    seam = L.SEAM\n+    (ya, za), (yb, zb) = seam[\"front\"], seam[\"back\"]\n+    half = seam[\"width\"] / 2\n+    for side in (-1, 1):                        # the seam between the lid and the base, rising to the back\n+        a, c = (x_face - seam[\"depth\"], w / 2 + 5.0) if side > 0 else (-w / 2 - 5.0, -x_face + seam[\"depth\"])\n+        boolean(body, prism_x(\"seam\", [(ya - 5.0, za - half), (yb + 5.0, zb - half), (yb + 5.0, zb + half),\n+                                       (ya - 5.0, za + half)], a, c, m[\"case\"]))\n+    front = atlas_uv(L.REGIONS[\"front\"], w, b[\"z\"][1] - b[\"z\"][0])\n+    paint(body, m[\"print\"], lambda co: front(co.x + w / 2, b[\"z\"][1] - co.z),\n+          lambda c, n, name: n.y < -0.9 and abs(c.y - b[\"y\"][0]) < 0.5)\n+    lx, ly = L.LIGHT\n+    s = (ly - LIP[0][0]) / math.cos(LIP_SLOPE)\n+    light = solid(\"light\", [[LIP_FRAME @ Vector((x, t, h)) for x, t in\n+                             ((lx + 0.8, s - 4.0), (lx + 0.8, s + 4.0), (lx - 0.8, s + 4.0), (lx - 0.8, s - 4.0))]\n+                            for h in (-0.6, 0.4)], m[\"print\"])\n+    return [plinth, body, painted(light, m, \"green\")]\n+\n+\n+# ---- The monitor -----------------------------------------------------------------------------------------------------\n+\n+def monitor(m):\n+    M = L.MONITOR\n+    w, h, f = M[\"width\"], M[\"height\"], M[\"frame\"]\n+    y0, z0 = L.MONITOR_Y, L.HEIGHT\n+    frame = solid(\"monitor\", [[(x, y0 + y, z0 + z) for x, z in rounded(0.0, h / 2, w, h, 10.0, 4)] for y in (0.0, f)],\n+                  m[\"case\"])\n+    bevel(frame, 4.0, segments=2)\n+    o = M[\"opening\"]\n+    outer, inner = rounded(o[\"x\"], o[\"z\"], *o[\"outer\"]), rounded(o[\"x\"], o[\"z\"], *o[\"inner\"])\n+    boolean(frame, solid(\"opening\", [[(x, y0 + y, z0 + z) for x, z in outline] for outline, y in\n+                                     ((outer, -10.0), (outer, 0.5), (inner, o[\"depth\"]), (inner, o[\"depth\"] + 1.0))],\n+                         m[\"case\"]))\n+    face = atlas_uv(L.REGIONS[\"monitor\"], w, h)\n+    paint(frame, m[\"print\"], lambda co: face(co.x + w / 2, h - (co.z - z0)),\n+          lambda c, n, name: n.y < -0.9 and c.y < y0 + 0.5)\n+    bw, bh = M[\"back\"]\n+    y1, y2, top = y0 + f - 2.0, y0 + M[\"depth\"], z0 + 2.0 + bh\n+    housing = box(\"housing\", -bw / 2, bw / 2, y1, y2, z0 + 2.0, top, m[\"case\"])\n+    bevel(housing, 4.0, segments=2)\n+    boolean(housing, grooves_y(\"top\", -bw / 2 + 14.0, bw / 2 - 14.0, 20, y1 + 10.0, y2 - 10.0, top, 2.6, 1.5,\n+                               m[\"case\"]))\n+    for side in (-1, 1):\n+        boolean(housing, grooves_x(\"side\", side * bw / 2, side, [z0 + 18.0 + 9.5 * k for k in range(25)],\n+                                   y1 + 10.0, y2 - 8.0, 2.6, 1.5, m[\"case\"]))\n+    for z in M[\"knobs\"]:                        # the knobs' recesses in the right side, and their tabs\n+        boolean(housing, box(\"recess\", bw / 2 - 3.0, bw / 2 + 5.0, y1 + 12.0, y1 + 34.0, z0 + z - 3.5, z0 + z + 3.5,\n+                             m[\"dark\"]))\n+    tabs = [box(\"knob\", bw / 2 - 3.5, bw / 2 - 0.6, y1 + 17.0, y1 + 25.0, z0 + z - 2.6, z0 + z + 2.6, m[\"case\"])\n+            for z in M[\"knobs\"]]\n+    gw, gh = M[\"glass\"][\"size\"]\n+    glass = curved(\"glass\", gw, gh, M[\"glass\"][\"bulge\"], atlas_uv(L.MEDIA_REGIONS[\"check\"], 1.0, 1.0), m[\"glass\"])\n+    glass.data.transform(Matrix.Translation((o[\"x\"], y0 + o[\"depth\"], z0 + o[\"z\"])))\n+    return [frame, housing, glass] + tabs\n+\n+\n+def screen(m, name):\n+    gw, gh = L.MONITOR[\"glass\"][\"size\"]\n+    return curved(\"screen_\" + name, gw, gh, L.MONITOR[\"glass\"][\"bulge\"],\n+                  atlas_uv(L.MEDIA_REGIONS[name], 1.0, 1.0), m[\"glass\"])\n+\n+\n+# ---- The drive -------------------------------------------------------------------------------------------------------\n+\n+def drive(m):\n+    (w, d, h), dx, dy = L.DRIVE[\"size\"], L.DRIVE[\"x\"], L.DRIVE[\"y\"]\n+    shell = box(\"drive\", dx - w / 2, dx + w / 2, dy, dy + d, 0.0, h, m[\"case\"])\n+    bevel(shell, 2.5, segments=2)\n+    x0, x1, z0, z1 = L.DRIVE[\"slot\"]\n+    boolean(shell, box(\"slot\", dx + x0, dx + x1, dy - 5.0, dy + 100.0, z0, z1, m[\"dark\"]))\n+    boolean(shell, grooves_y(\"top\", dx - w / 2 + 8.0, dx + w / 2 - 8.0, 10, dy + 20.0, dy + d - 8.0, h, 3.0, 1.4,\n+                             m[\"case\"]))\n+    face = atlas_uv(L.REGIONS[\"drive\"], w, h)\n+    paint(shell, m[\"print\"], lambda co: face(co.x - (dx - w / 2), h - co.z),\n+          lambda c, n, name: n.y < -0.9 and c.y < dy + 0.5 and name == \"case\")\n+    return shell\n+\n+\n+def eject_button(m):\n+    ex0, ex1, ez0, ez1 = L.DRIVE[\"eject\"]\n+    dx, dy = L.DRIVE[\"x\"], L.DRIVE[\"y\"]\n+    return box(\"eject\", dx + ex0, dx + ex1, dy - 1.0, dy + 3.0, ez0, ez1, m[\"case\"])\n+\n+\n+def drive_light(m):\n+    lx, lz = L.DRIVE[\"light\"]\n+    lit = painted(box(\"drive_light\", -2.5, 2.5, -0.4, 0.4, -0.8, 0.8, m[\"print\"]), m, \"lit\")\n+    return lit, Vector((L.DRIVE[\"x\"] + lx, L.DRIVE[\"y\"], lz))\n+\n+\n+def disk(m):\n+    \"\"\"The 3.5-inch disk, its middle at its origin, lying flat, its shutter's end towards +y (into the drive).\"\"\"\n+    (w, length), t = L.DISK[\"size\"], L.DISK[\"thickness\"]\n+    part = box(\"disk\", -w / 2, w / 2, -length / 2, length / 2, -t / 2, t / 2, m[\"disk\"])\n+    bevel(part, 0.6, segments=1)\n+    top = atlas_uv(L.MEDIA_REGIONS[\"disk_top\"], w, length)\n+    bottom = atlas_uv(L.MEDIA_REGIONS[\"disk_bottom\"], w, length)\n+    paint(part, m[\"disk\"], lambda co: top(co.x + w / 2, length / 2 - co.y), lambda c, n, name: n.z > 0.9)\n+    paint(part, m[\"disk\"], lambda co: bottom(w / 2 - co.x, length / 2 - co.y), lambda c, n, name: n.z < -0.9)\n+    x, y = L.MEDIA_SWATCHES[\"edge\"]\n+    paint(part, m[\"disk\"], lambda co: (x / L.ATLAS, 1 - y / L.ATLAS), lambda c, n, name: abs(n.z) <= 0.9)\n+    return part\n+\n+\n+# ---- The keyboard and mouse ------------------------------------------------------------------------------------------\n+\n+def on_keyboard(name, outline, h0, h1, mat, top=None):\n+    \"\"\"A solid from an (x, s) outline on the keyboard's slope, from h0 to h1 off it; top, if given, the outline of its\n+    top at h1 (a key's, tapering to it).\"\"\"\n+    rings = [[(x, s, h0) for x, s in outline], [(x, s, h1 - 2.5 if top else h1) for x, s in outline]]\n+    if top:\n+        rings.append([(x, s, h1) for x, s in top])\n+    return solid(name, [[KB_FRAME @ Vector(p) for p in ring] for ring in rings], mat)\n+\n+\n+def keyboard(m):\n+    \"\"\"The keyboard: its wedge, the strip behind it, its keys (RETURN apart: the clip presses it).\"\"\"\n+    w, d = K[\"width\"], K[\"depth\"]\n+    (h0, h1), cx = K[\"heights\"], K[\"at\"][0]\n+    body = prism_x(\"keyboard\", [(KB_FRONT, 0.0), (KB_FRONT + d, 0.0), (KB_FRONT + d, h1), (KB_FRONT, h0)], cx - w / 2,\n+                   cx + w / 2, m[\"case\"])\n+    bevel(body, 2.0, segments=2)\n+    paint(body, m[\"print\"], key_uv, lambda c, n, name: n.z > 0.9)\n+    (sx0, sx1), sd, sh = L.STRIP[\"x\"], L.STRIP[\"depth\"], L.STRIP[\"height\"]\n+    strip = box(\"strip\", cx + sx0, cx + sx1, KB_FRONT + d - 1.0, KB_FRONT + d + sd, 0.0, sh, m[\"case\"])\n+    bevel(strip, 2.0, segments=2)\n+    paint(strip, m[\"print\"], flat_uv, lambda c, n, name: n.z > 0.9)\n+    (px0, px1), (ps0, ps1) = L.POWER[\"x\"], L.POWER[\"s\"]\n+    rect = [(cx + px0 + L.GAP / 2, KB_FRONT + ps0), (cx + px1 - L.GAP / 2, KB_FRONT + ps0),\n+            (cx + px1 - L.GAP / 2, KB_FRONT + ps1), (cx + px0 + L.GAP / 2, KB_FRONT + ps1)]\n+    power = solid(\"power\", [[(x, y, z) for x, y in outline] for outline, z in\n+                            ((rect, sh - 1.0), (rect, sh + 3.0), (inset(rect, L.KEY_INSET), sh + 5.0))], m[\"key\"])\n+    bevel(power, 0.5, segments=1, angle=30)\n+    paint(power, m[\"print\"], flat_uv, lambda c, n, name: n.z > 0.9)\n+    bx0, bx1 = L.kx(L.BLANK[0]) + L.GAP / 2, L.kx(L.BLANK[1]) - L.GAP / 2\n+    bs0, bs1 = L.ROW_S[5] + L.GAP / 2, L.ROW_S[1] + L.U - L.GAP / 2\n+    blank = on_keyboard(\"blank\", [(bx0, bs0), (bx1, bs0), (bx1, bs1), (bx0, bs1)], -1.0, 2.5, m[\"case\"])\n+    paint(blank, m[\"print\"], key_uv, lambda c, n, name: n.z > 0.9)\n+    static, moving = [body, strip, power, blank], []\n+    for name, xu, row, wu, _, *rest in L.KEYS:\n+        x0, s0, x1, s1 = L.key_rect(xu, row, wu, rest[0] if rest else 1)\n+        outline = [(x0, s0), (x1, s0), (x1, s1), (x0, s1)]\n+        cap = on_keyboard(name, outline, -L.WELL, L.KEY_HEIGHT, m[\"key\"], inset(outline, L.KEY_INSET))\n+        bevel(cap, 0.5, segments=1, angle=30)\n+        paint(cap, m[\"print\"], key_uv, lambda c, n, _name: n.z > 0.9)\n+        (moving if name in (\"return\", \"return_top\") else static).append(cap)\n+    return static, join(moving, \"return\")\n+\n+\n+def mouse(m):\n+    w, d, h = L.MOUSE[\"size\"]\n+    (mx, my), turn = L.MOUSE[\"at\"], L.MOUSE[\"turn\"]\n+    rings = [[(x, y, z) for x, y in rounded(0.0, 0.0, w * k, d * k, 12.0 * k, 4)] for k, z in\n+             ((0.96, 0.0), (1.0, 3.0), (1.0, h * 0.55), (0.92, h * 0.85), (0.72, h))]\n+    part = solid(\"mouse\", rings, m[\"case\"])\n+    for v in part.data.vertices:                # lower at its button's end (+y), domed towards its back\n+        v.co.z *= 0.78 - 0.22 * v.co.y / (d / 2)\n+    top = atlas_uv(L.REGIONS[\"mouse\"], w, d)\n+    paint(part, m[\"print\"], lambda co: top(co.x + w / 2, d / 2 - co.y), lambda c, n, name: n.z > 0.5)\n+    part.data.transform(Matrix.Translation((mx, my, 0.0)) @ Matrix.Rotation(math.radians(turn), 4, \"Z\"))\n+    return part\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+OUT = 110.0          # how far in front of its seat the disk waits: wholly out of the drive\n+\n+\n+def appear(f, f0, f1):\n+    return HIDDEN + (1 - HIDDEN) * back_out(phase(f, f0, f1)) if f >= f0 else HIDDEN\n+\n+\n+def timeline(f):\n+    if f < 6 or f >= 226:\n+        dsk = Pose(scale=HIDDEN)\n+    elif f < 16:\n+        dsk = Pose(y=-OUT, scale=appear(f, 6, 14))\n+    elif f < 198:\n+        dsk = Pose(y=-OUT * (1 - smooth(phase(f, 16, 30))))\n+    elif f < 212:\n+        dsk = Pose(y=-40.0 * back_out(phase(f, 198, 204)))\n+    else:                                       # pulled clear of the slot, then up and away\n+        lift = ease_in(phase(f, 218, 226))\n+        dsk = Pose(y=-40.0 - 70.0 * smooth(phase(f, 212, 220)), z=30.0 * lift, scale=max(HIDDEN, 1 - lift))\n+    reading = (32 <= f < 84 or 196 <= f < 202) and (f // 3) % 4 != 3\n+    shown = \"boot\" if 36 <= f < 90 else \"title\" if 90 <= f < 132 else \"game\" if 132 <= f < 200 else None\n+    press = smooth(phase(f, 120, 123)) * (1 - smooth(phase(f, 126, 130)))\n+    push = smooth(phase(f, 192, 195)) * (1 - smooth(phase(f, 198, 202)))\n+    poses = {\"disk\": dsk, \"drive_light\": Pose() if reading else Pose(scale=HIDDEN),\n+             \"return\": Pose(x=PRESS.x * 2.6 * press, y=PRESS.y * 2.6 * press, z=PRESS.z * 2.6 * press),\n+             \"eject\": Pose(y=1.8 * push)}\n+    for name in (\"boot\", \"title\", \"game\"):\n+        poses[\"screen_\" + name] = Pose() if shown == name else Pose(scale=HIDDEN)\n+    return poses\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    printed = bpy.data.images.load(os.path.join(images, \"apple2gs_print.png\"))\n+    media = bpy.data.images.load(os.path.join(images, \"apple2gs_media.png\"))\n+    colours = dict(L.COLOURS, print=\"#FFFFFF\")\n+    textures = dict(print=printed, glass=media, disk=media)\n+    m = {n: material(n, colours[n], r, textures.get(n)) for n, r in ROUGHNESS.items()}\n+\n+    static, enter = keyboard(m)\n+    whole = join(computer(m) + monitor(m) + [drive(m)] + static + [mouse(m)], \"apple2gs\")\n+    eject = eject_button(m)\n+    lit, lit_at = drive_light(m)\n+    floppy = disk(m)\n+    screens = {\"screen_\" + name: screen(m, name) for name in (\"boot\", \"title\", \"game\")}\n+    for obj in [whole, eject, enter, floppy]:\n+        for p in obj.data.polygons:\n+            p.use_smooth = True\n+        obj.data.set_sharp_from_angle(angle=math.radians(35))\n+\n+    # The node tree: the root (the view and the fit) holds the set; the set holds the moving parts at their places.\n+    root = bpy.data.objects.new(\"apple2gs_root\", None)\n+    scene.collection.objects.link(root)\n+    whole.parent = root\n+    o = L.MONITOR[\"opening\"]\n+    glass_front = Vector((o[\"x\"], L.MONITOR_Y + o[\"depth\"], L.HEIGHT + o[\"z\"]))\n+    length, (x0, x1, z0, z1) = L.DISK[\"size\"][1], L.DRIVE[\"slot\"]\n+    seat = Vector((L.DRIVE[\"x\"] + (x0 + x1) / 2, L.DRIVE[\"y\"] + length / 2 + 4.0, (z0 + z1) / 2))\n+    placed = [(lit, lit_at), (floppy, seat)]\n+    placed += [(part, glass_front + Vector((0.0, -0.4 - 0.3 * k, 0.0))) for k, part in enumerate(screens.values())]\n+    for part, where in placed:\n+        part.parent = whole\n+        part.location = where\n+    eject.parent = whole\n+    enter.parent = whole\n+    nodes = {\"disk\": floppy, \"drive_light\": lit, \"eject\": eject, \"return\": enter, **screens}\n+\n+    # The three-quarter view, then the spec's fit of the set at rest, as the root's transform.\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    points = [tilt @ v.co for v in whole.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [whole, eject, enter, lit, floppy] + list(screens.values())\n+    print(f\"Apple IIGS: {sum(triangles(o_.data) for o_ in meshes)} triangles ({triangles(whole.data)} the set), \"\n+          f\"{len({mat.name for o_ in meshes for mat in o_.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "0b81fff9f6398f0ba6faa850daf84b13b99a22c3",
          "filename": "tools/console-models/apple2gs_layout.py",
          "status": "added",
          "additions": 125,
          "deletions": 0,
          "changes": 125,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fapple2gs_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fapple2gs_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fapple2gs_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,125 @@\n+\"\"\"The Apple IIGS set's measurements, shared by apple2gs_print.py (which draws its printing and screens) and apple2gs.py\n+(which builds it). Plain Python: Blender's Python imports it too.\n+\n+The Apple IIGS (1986) in its platinum case, 290 x 349 x 115 mm (the Computer History Museum's 11 1/4 x 13 3/4 x 4 5/8\n+inches), its AppleColor RGB monitor on it (its screen 9 x 6 3/4 inches, the museum's), its Apple 3.5 Drive beside it\n+(121 x 210 x 56 mm, the museum's), and its ADB keyboard and mouse (57 x 95 x 36 mm; the museum's 2 1/4 x 3 3/4 x\n+1 1/2 inches) in front. Millimetres: x from the IIGS's middle (left to right), y back from its lip's front edge, z up\n+from the table. The rest was measured from photographs, scaled by the IIGS's width and the keys' standard pitch: on\n+Wikimedia Commons the set with its peripherals, the IIGS with its drive, the keyboard and mouse; on Deskthority the\n+keyboard from above and from its side.\n+\n+The IIGS: a plinth with a sloping lip along its front (the power light at its right), the main box on it, Apple IIGS and\n+the rainbow apple at its front's left, grooves across its top from front to back and along its sides, and the seam\n+between its lid and its base rising along its sides to the back. The monitor: its deep front frame round the screen's\n+opening (the apple, the power switch), its housing behind (as far back as the IIGS goes, as photographs show it),\n+grooved along its sides and its top, three knobs in its right side. The drive: a low box, its slot high in its front\n+(the light at its left end, the eject button at its right, a label above it), its top grooved. The keyboard: a wedge,\n+the US layout's keys standing on it (KEYS: rows from the front, 5 the space bar's, to the back, 1 the digits'), a blank\n+column, the keypad at its right; a raised strip behind it with the apple and the power key. The mouse: a box rounded\n+at its back, its button at its front.\n+\n+Two textures. `print` (ATLAS square, REGIONS): `keyboard` (the keyboard seen from above, its strip too: the key tops\n+and legends; mapped onto its faces facing up in its slope's frame), `front` (the IIGS's front face), `monitor` (the\n+monitor's front frame), `drive` (the drive's front), `mouse` (the mouse's top), and swatches. `media` (MEDIA_REGIONS):\n+the screens, each the glass's whole area (`check`, Check startup device! at rest; `boot`, GS/OS starting; `title` and\n+`game`, a made-up game, Odyssey), the disk's two sides. A region is (x, y, width, height) in pixels.\n+\"\"\"\n+\n+ATLAS = 1024\n+\n+# -- The IIGS --------------------------------------------------------------------------------------------------------\n+\n+WIDTH, DEPTH, HEIGHT = 290.0, 349.0, 115.0\n+PLINTH = dict(height=26.0, lip=34.0)          # the plinth; how far its lip runs in front of the main box\n+BOX = dict(y=(34.0, DEPTH), z=(22.0, HEIGHT), inset=0.5)\n+TOP_GROOVES = dict(count=30, width=2.6, depth=1.5, margin=12.0, y=(48.0, DEPTH - 8.0))\n+SIDE_GROOVES = dict(z=[98.0, 89.0, 80.0, 71.0], y=(60.0, DEPTH - 8.0), width=2.4, depth=1.4)\n+SEAM = dict(front=(BOX[\"y\"][0], 26.0), back=(DEPTH, 60.0), width=1.6, depth=1.0)   # (y, z) where it starts and ends\n+LIGHT = (122.0, 14.0)                         # the power light on the lip: x, y\n+\n+# -- The monitor -----------------------------------------------------------------------------------------------------\n+\n+MONITOR = dict(width=290.0, height=270.0, frame=140.0, depth=306.0, back=(274.0, 258.0),   # the front frame (its depth);\n+                                                                                          # the housing behind it\n+               opening=dict(x=0.0, z=150.0, outer=(242.0, 196.0, 10.0), inner=(222.0, 172.0, 8.0), depth=22.0),\n+               glass=dict(size=(228.0, 176.0), bulge=7.0),\n+               switch=(118.0, 30.0), knobs=[193.0, 162.0, 141.0])   # the switch, x, z; the knobs in the right side, z\n+MONITOR_Y = BOX[\"y\"][0] + 4.0                 # the monitor's front, on the IIGS\n+\n+# -- The 3.5 drive ---------------------------------------------------------------------------------------------------\n+\n+DRIVE = dict(size=(121.0, 210.0, 56.0), x=-WIDTH / 2 - 76.0, y=60.0, slot=(-47.0, 45.0, 36.5, 41.0),   # x0 x1 z0 z1\n+             eject=(48.0, 58.0, 36.0, 41.5), light=(-56.5, 38.75), label=(-59.0, -17.0, 43.0, 53.0))\n+\n+# -- The keyboard and mouse ------------------------------------------------------------------------------------------\n+\n+KEYBOARD = dict(width=380.0, depth=105.0, heights=(19.0, 29.0), at=(-20.0, -160.0))   # the case: its plan, its front\n+                                                                                    # and back heights, its middle\n+STRIP = dict(x=(-141.0, 136.0), depth=30.0, height=30.0)   # the raised strip behind the case\n+U, GAP = 19.05, 1.0\n+KEY_HEIGHT, KEY_INSET, WELL = 7.0, 2.0, 1.5\n+KEYS_X0 = -185.25                             # the main block's left edge, in the keyboard's frame (x from its middle)\n+ROW_S = {5: 6.0, 4: 6.0 + U, 3: 6.0 + 2 * U, 2: 6.0 + 3 * U, 1: 6.0 + 4 * U}   # each row's front edge, back from\n+                                                                               # the keyboard's front\n+PAD_U = 15.45                                 # the keypad's left edge, in units\n+BLANK = (14.5, PAD_U)                         # the blank column between the main block and the keypad, in units\n+POWER = dict(x=(-92.0, -54.0), s=(112.0, 128.0))   # the power key on the strip, s back from the keyboard's front\n+STRIP_APPLE = (-115.5, 120.0)\n+\n+\n+def kx(u):\n+    return KEYS_X0 + u * U\n+\n+\n+def key_rect(xu, row, wu, rows=1):\n+    \"\"\"A key's base, (x0, s0, x1, s1) in the keyboard's frame (a tall key spans rows from row down).\"\"\"\n+    s0, s1 = ROW_S[row + rows - 1], ROW_S[row] + U\n+    return kx(xu) + GAP / 2, s0 + GAP / 2, kx(xu + wu) - GAP / 2, s1 - GAP / 2\n+\n+\n+def _row(names, x0, row, legends):\n+    return [(n, x0 + i, row, 1.0, legend) for i, (n, legend) in enumerate(zip(names, legends))]\n+\n+\n+# (name, x in units, row, width in units, legend[, rows]).\n+KEYS = (\n+    [(\"esc\", 0.0, 1, 1.0, \"esc\")]\n+    + _row([f\"d{k}\" for k in \"1234567890\"] + [\"minus\", \"equals\"], 1.0, 1,\n+           [\"!\\n1\", \"@\\n2\", \"#\\n3\", \"$\\n4\", \"%\\n5\", \"^\\n6\", \"&\\n7\", \"*\\n8\", \"(\\n9\", \")\\n0\", \"_\\n-\", \"+\\n=\"])\n+    + [(\"delete\", 13.0, 1, 1.5, \"delete\")]\n+    + [(\"tab\", 0.0, 2, 1.5, \"tab\")]\n+    + _row(list(\"qwertyuiop\") + [\"lbracket\", \"rbracket\"], 1.5, 2, list(\"QWERTYUIOP\") + [\"{\\n[\", \"}\\n]\"])\n+    + [(\"control\", 0.0, 3, 1.75, \"control\")]\n+    + _row(list(\"asdfghjkl\") + [\"semicolon\", \"quote\"], 1.75, 3, list(\"ASDFGHJKL\") + [\":\\n;\", '\"\\n\\''])\n+    + [(\"return\", 12.75, 3, 1.75, \"return\"), (\"return_top\", 13.5, 2, 1.0, \"\")]\n+    + [(\"lshift\", 0.0, 4, 2.25, \"shift\")]\n+    + _row(list(\"zxcvbnm\") + [\"comma\", \"period\", \"slash\"], 2.25, 4, list(\"ZXCVBNM\") + [\"<\\n,\", \">\\n.\", \"?\\n/\"])\n+    + [(\"rshift\", 12.25, 4, 2.25, \"shift\")]\n+    + [(\"caps\", 0.0, 5, 1.0, \"caps\\nlock\"), (\"option\", 1.0, 5, 1.0, \"option\"), (\"apple\", 2.0, 5, 1.75, \"\u2318\"),\n+       (\"grave\", 3.75, 5, 1.0, \"~\\n`\"), (\"space\", 4.75, 5, 4.75, \"\"), (\"backslash\", 9.5, 5, 1.0, \"|\\n\\\\\"),\n+       (\"left\", 10.5, 5, 1.0, \"\u2190\"), (\"right\", 11.5, 5, 1.0, \"\u2192\"), (\"down\", 12.5, 5, 1.0, \"\u2193\"),\n+       (\"up\", 13.5, 5, 1.0, \"\u2191\")]\n+    + _row([\"clear\", \"np_eq\", \"np_slash\", \"np_star\"], PAD_U, 1, [\"clear\", \"=\", \"/\", \"*\"])\n+    + _row([\"np7\", \"np8\", \"np9\", \"np_plus\"], PAD_U, 2, [\"7\", \"8\", \"9\", \"+\"])\n+    + _row([\"np4\", \"np5\", \"np6\", \"np_minus\"], PAD_U, 3, [\"4\", \"5\", \"6\", \"\u2212\"])\n+    + _row([\"np1\", \"np2\", \"np3\"], PAD_U, 4, [\"1\", \"2\", \"3\"])\n+    + [(\"enter\", PAD_U + 3.0, 4, 1.0, \"enter\", 2), (\"np0\", PAD_U, 5, 2.0, \"0\"), (\"np_dot\", PAD_U + 2.0, 5, 1.0, \".\")]\n+)\n+\n+MOUSE = dict(at=(240.0, -160.0), size=(57.0, 95.0, 36.0), button=36.0, turn=-6.0)\n+\n+DISK = dict(size=(90.0, 94.0), thickness=3.3)   # a 3.5-inch disk: across, along (its shutter's end in first)\n+\n+COLOURS = dict(case=\"#E2DED3\", key=\"#DCD8CC\", dark=\"#1A1A1C\", glass=\"#FFFFFF\", disk=\"#FFFFFF\")\n+\n+REGIONS = dict(keyboard=(0, 0, 1024, 364), front=(0, 376, 512, 164), monitor=(520, 376, 504, 469),\n+               drive=(0, 548, 300, 139), mouse=(312, 548, 120, 200))\n+SWATCHES = dict(green=(24, 732), dark=(88, 732), lit=(120, 732), case=(152, 732))\n+\n+MEDIA_REGIONS = dict(check=(0, 0, 512, 395), boot=(512, 0, 512, 395), title=(0, 403, 512, 395),\n+                     game=(548, 670, 440, 340), disk_top=(512, 403, 248, 259), disk_bottom=(768, 403, 248, 259))\n+MEDIA_SWATCHES = dict(black=(24, 820), edge=(56, 820))\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s"
        },
        {
          "sha": "52e2fcf97c8d6eb592dc400af95fef0a0e63e8e3",
          "filename": "tools/console-models/apple2gs_print.py",
          "status": "added",
          "additions": 253,
          "deletions": 0,
          "changes": 253,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fapple2gs_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fapple2gs_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fapple2gs_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,253 @@\n+\"\"\"The Apple IIGS set's printing and screens, for the console theme's Apple IIGS model (tools/console-models/apple2gs.py).\n+\n+Draws its two atlases, their regions as apple2gs_layout.py places them. apple2gs_print.png, each in millimetres as the\n+model's UVs map it: the keyboard seen from above (each key's top with its legend, the blank column's joins, the strip\n+behind with the apple and the power key), the IIGS's front (the rainbow apple and Apple IIGS), the monitor's front\n+frame (the apple, the power switch), the drive's front (the slot's lips, its label, its light, the apple), the mouse's\n+top, and the swatches small parts take their colour from. apple2gs_media.png: the screens, each the glass's whole area\n+(Check startup device! as the IIGS shows it without a disk, white on its medium blue, the rainbow apple; GS/OS\n+starting, its welcome box and its bar; the title screen of a made-up game, Odyssey, and a moment of its play), and the\n+3.5-inch disk's two sides. Run it with the system Python (Pillow):\n+\n+    python tools/console-models/apple2gs_print.py <out folder>\n+\n+It writes apple2gs_print.png and apple2gs_media.png there.\n+\"\"\"\n+\n+import os\n+import random\n+import sys\n+\n+from PIL import Image, ImageDraw, ImageFont\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import apple2gs_layout as L  # noqa: E402\n+from apple2_print import apple  # noqa: E402\n+from psp_print import ship, space  # noqa: E402\n+from retro_pc_print import rgb, shade  # noqa: E402\n+from windows_pc_print import FONTS, Region  # noqa: E402\n+\n+CASE = rgb(L.COLOURS[\"case\"])\n+KEY = rgb(L.COLOURS[\"key\"])\n+LEGEND = (64, 66, 80)\n+\n+\n+# ---- The printing ------------------------------------------------------------------------------------------------------\n+\n+def keyboard():\n+    w, d = L.KEYBOARD[\"width\"], L.KEYBOARD[\"depth\"] + L.STRIP[\"depth\"]\n+    reg = Region(L.REGIONS[\"keyboard\"], w, d, CASE)\n+\n+    def at(x, s):\n+        return x + w / 2, d - s\n+    i = L.KEY_INSET\n+    apple(reg, *at(*L.STRIP_APPLE), 8.0, rainbow=True)\n+    (px0, px1), (ps0, ps1) = L.POWER[\"x\"], L.POWER[\"s\"]\n+    (ax, ay), (bx, by) = at(px0 + i, ps1 - i), at(px1 - i, ps0 + i)\n+    reg.rect_(ax - 0.5, ay - 0.5, bx + 0.5, by + 0.5, shade(KEY, 0.04), r=1.2)\n+    tx, ty = ax + 5.0, (ay + by) / 2                                      # the power key's triangle, pointing left\n+    reg.line([(tx + 3.6, ty - 2.4), (tx, ty), (tx + 3.6, ty + 2.4), (tx + 3.6, ty - 2.4)], LEGEND, 0.35)\n+    x0, x1 = L.kx(L.BLANK[0]) + L.GAP / 2, L.kx(L.BLANK[1]) - L.GAP / 2   # the blank column's joins, row by row\n+    for row in (4, 3, 2, 1):\n+        reg.line([at(x0, L.ROW_S[row]), at(x1, L.ROW_S[row])], shade(CASE, -0.22), 0.5)\n+    for name, xu, row, wu, legend, *rest in L.KEYS:\n+        x0, s0, x1, s1 = L.key_rect(xu, row, wu, rest[0] if rest else 1)\n+        (ax, ay), (bx, by) = at(x0 + i, s1 - i), at(x1 - i, s0 + i)\n+        kw, kh = bx - ax, by - ay\n+        reg.rect_(ax - 0.5, ay - 0.5, bx + 0.5, by + 0.5, shade(KEY, 0.04), r=1.2)\n+        if name == \"apple\":                                               # the open apple and the command sign\n+            apple(reg, ax + 3.4, ay + kh * 0.55, 3.4, outline=LEGEND, width=0.3)\n+            reg.text(legend, \"seguisym.ttf\", 3.2, bx - 3.4, ay + kh * 0.55, LEGEND)\n+            continue\n+        if not legend:\n+            continue\n+        lines = legend.split(\"\\n\")\n+        if len(lines) == 1 and len(legend) == 1:\n+            font = \"seguisym.ttf\" if not legend.isascii() else \"arial.ttf\"\n+            reg.text(legend, font, 3.4, ax + kw * 0.35, ay + kh * 0.55, LEGEND)\n+        elif all(len(line) == 1 for line in lines):                       # a symbol over a digit\n+            for k, line in enumerate(lines):\n+                reg.text(line, \"arial.ttf\", 2.8, ax + 2.4, ay + kh * (0.33 + 0.34 * k), LEGEND, anchor=\"lm\")\n+        else:                                                             # words, at the foot of the key\n+            for k, line in enumerate(lines):\n+                reg.text(line, \"arial.ttf\", 2.1, ax + 1.8, ay + kh * 0.74 - (len(lines) - 1 - k) * 3.0, LEGEND,\n+                         anchor=\"lm\")\n+    return reg\n+\n+\n+def front():\n+    w, h = L.WIDTH, L.BOX[\"z\"][1] - L.BOX[\"z\"][0]\n+    reg = Region(L.REGIONS[\"front\"], w, h, CASE)\n+    apple(reg, 20.0, h - 22.0, 9.0, rainbow=True)\n+    reg.text(\"Apple IIGS\", \"times.ttf\", 7.0, 28.0, h - 21.5, (70, 68, 64), anchor=\"lm\")\n+    return reg\n+\n+\n+def monitor():\n+    M = L.MONITOR\n+    w, h = M[\"width\"], M[\"height\"]\n+    reg = Region(L.REGIONS[\"monitor\"], w, h, CASE)\n+\n+    def at(x, z):\n+        return x + w / 2, h - z\n+    apple(reg, *at(-128.0, 24.0), 9.0, rainbow=True)\n+    sx, sz = M[\"switch\"]\n+    reg.rect_(*at(sx - 0.8, sz + 5.0), *at(sx + 0.8, sz - 5.0), (70, 72, 64))\n+    return reg\n+\n+\n+def drive():\n+    w, d, h = L.DRIVE[\"size\"]\n+    reg = Region(L.REGIONS[\"drive\"], w, h, CASE)\n+\n+    def at(x, z):\n+        return x + w / 2, h - z\n+    x0, x1, z0, z1 = L.DRIVE[\"slot\"]\n+    reg.rect_(*at(x0 - 1.5, z1 + 1.5), *at(x1 + 1.5, z0 - 1.5), shade(CASE, -0.12), r=1.0)   # the slot's lips\n+    lx0, lx1, lz0, lz1 = L.DRIVE[\"label\"]\n+    reg.rect_(*at(lx0, lz1), *at(lx1, lz0), (240, 238, 230), r=0.6)\n+    reg.text(\"3.5 DRIVE\", \"arial.ttf\", 2.2, *at(lx0 + 2.0, lz0 + 6.5), (110, 120, 150), anchor=\"lm\")\n+    lx, lz = L.DRIVE[\"light\"]\n+    reg.rect_(*at(lx - 2.5, lz + 0.8), *at(lx + 2.5, lz - 0.8), (70, 34, 26))\n+    ex0, ex1, ez0, ez1 = L.DRIVE[\"eject\"]\n+    reg.rect_(*at(ex0 - 0.6, ez1 + 0.6), *at(ex1 + 0.6, ez0 - 0.6), shade(CASE, -0.35))      # the eject button's gap\n+    apple(reg, *at(-40.0, 12.0), 9.0, rainbow=True)\n+    return reg\n+\n+\n+def mouse():\n+    w, d, _ = L.MOUSE[\"size\"]\n+    reg = Region(L.REGIONS[\"mouse\"], w, d, CASE)\n+    reg.rect_(4.0, 4.0, w - 4.0, L.MOUSE[\"button\"], shade(CASE, 0.03), r=3.0)\n+    reg.line([(4.0, L.MOUSE[\"button\"]), (w - 4.0, L.MOUSE[\"button\"])], shade(CASE, -0.3), 0.6)\n+    return reg\n+\n+\n+def print_atlas(out):\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), CASE)\n+    for reg in (keyboard(), front(), monitor(), drive(), mouse()):\n+        reg.paste(image)\n+    d = ImageDraw.Draw(image)\n+    for name, colour in ((\"green\", (110, 255, 120)), (\"dark\", rgb(L.COLOURS[\"dark\"])), (\"lit\", (255, 96, 60)),\n+                         (\"case\", CASE)):\n+        x, y = L.SWATCHES[name]\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colour)\n+    path = os.path.join(out, \"apple2gs_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+# ---- The screens -------------------------------------------------------------------------------------------------------\n+\n+W, H = 320, 200                                    # the IIGS's super hi-res screen, its 320 mode\n+BLUE = (36, 56, 214)\n+\n+\n+def check():\n+    img = Image.new(\"RGB\", (W * 4, H * 4), BLUE)\n+    reg = Region((0, 0, W * 4, H * 4), W, H, BLUE)\n+    apple(reg, W / 2, H * 0.42, 34.0, rainbow=True)\n+    reg.text(\"Check startup device!\", \"consolab.ttf\", 9.0, W / 2, H * 0.72, (250, 250, 250))\n+    img.paste(reg.img.convert(\"RGB\").resize(img.size, Image.LANCZOS), (0, 0))\n+    return img\n+\n+\n+def boot():\n+    reg = Region((0, 0, W * 4, H * 4), W, H, (200, 200, 204))\n+    for y in range(0, H, 2):                       # the desktop's grey dither, line by line\n+        reg.line([(0, y), (W, y)], (176, 176, 182), 0.5)\n+    reg.rect_(70.0, 50.0, 250.0, 150.0, (255, 255, 255), outline=(20, 20, 22), width=1.0)\n+    apple(reg, 100.0, 86.0, 26.0, rainbow=True)\n+    reg.text(\"Apple IIGS\", \"timesbd.ttf\", 15.0, 116.0, 80.0, (20, 20, 22), anchor=\"lm\")\n+    reg.text(\"GS/OS\", \"arialbd.ttf\", 9.0, 116.0, 96.0, (20, 20, 22), anchor=\"lm\")\n+    reg.rect_(90.0, 120.0, 230.0, 130.0, (255, 255, 255), outline=(20, 20, 22), width=0.8)\n+    reg.rect_(91.0, 121.0, 170.0, 129.0, (20, 20, 22))\n+    return reg.img.convert(\"RGB\")\n+\n+\n+def title():\n+    img = space((W * 4, H * 4)).convert(\"RGB\")\n+    d = ImageDraw.Draw(img)\n+    d.text((W * 2, 64), \"ODYSSEY\", font=ImageFont.truetype(FONTS + \"ariblk.ttf\", 150), fill=(255, 214, 70),\n+           anchor=\"mt\", stroke_width=6, stroke_fill=(120, 30, 10))\n+    ship(d, 360, 470, 6.0, \"#FFFFFF\", \"#FFB23C\")\n+    d.text((W * 2, 700), \"PRESS RETURN TO PLAY\", font=ImageFont.truetype(FONTS + \"arialbd.ttf\", 44),\n+           fill=(255, 255, 255), anchor=\"mm\")\n+    return img\n+\n+\n+def game():\n+    \"\"\"A moment of Odyssey's play: the ship firing at the asteroids, the score along the top.\"\"\"\n+    img = space((W * 4, H * 4)).convert(\"RGB\")\n+    d = ImageDraw.Draw(img)\n+    rng = random.Random(5)\n+    for _ in range(40):\n+        x, y = rng.randrange(W * 4), rng.randrange(H * 4)\n+        d.rectangle([x, y, x + 3, y + 3], fill=(255, 255, 255))\n+    ship(d, 300, 430, 4.5, \"#FFFFFF\", \"#FFB23C\")\n+    d.line([(390, 430), (720, 430)], fill=(255, 236, 80), width=8)\n+    for ax, ay, r in ((860, 420, 70), (1040, 650, 46), (760, 170, 38)):\n+        d.ellipse([ax - r, ay - r, ax + r, ay + r], fill=(140, 96, 70), outline=(90, 60, 44), width=6)\n+    font = ImageFont.truetype(FONTS + \"consolab.ttf\", 44)\n+    d.text((40, 44), \"SCORE 001250\", font=font, fill=(255, 255, 255), anchor=\"lm\")\n+    d.text((W * 4 - 40, 44), \"SHIPS 3\", font=font, fill=(255, 214, 70), anchor=\"rm\")\n+    return img\n+\n+\n+def glass(picture, size):\n+    \"\"\"A screen's region: the raster in the middle of the tube's dark border.\"\"\"\n+    w, h = size\n+    image = Image.new(\"RGB\", (w, h), (14, 14, 16))\n+    rw, rh = round(w * 0.9), round(h * 0.86)\n+    image.paste(picture.resize((rw, rh), Image.LANCZOS), ((w - rw) // 2, (h - rh) // 2))\n+    return image\n+\n+\n+def disk_side(rect, label):\n+    \"\"\"One side of the 3.5-inch disk, seen looking at it, its shutter's end at the top.\"\"\"\n+    w, l = L.DISK[\"size\"]\n+    blue = (40, 44, 52)\n+    reg = Region(rect, w, l, blue)\n+    reg.rect_(0.0, 0.0, w, l, shade(blue, 0.06), r=2.0)\n+    reg.rect_(26.0, 0.0, 64.0, 36.0, (200, 204, 210))               # the shutter\n+    reg.rect_(36.0, 6.0, 54.0, 26.0, (40, 38, 44), r=1.5)\n+    reg.rect_(37.5, 7.5, 52.5, 24.5, (92, 64, 44), r=1.0)\n+    if label:\n+        reg.rect_(8.0, 40.0, w - 8.0, l - 6.0, (244, 242, 234), r=1.2)\n+        art = space((640, 300)).convert(\"RGB\")\n+        da = ImageDraw.Draw(art)\n+        ship(da, 200, 160, 3.0, \"#FFFFFF\", \"#FFB23C\")\n+        x0, y0, x1, y1 = reg.box(10.0, 42.0, w - 10.0, l - 22.0)\n+        reg.img.paste(art.resize((round(x1 - x0), round(y1 - y0)), Image.LANCZOS), (round(x0), round(y0)))\n+        reg.text(\"ODYSSEY\", \"ariblk.ttf\", 8.0, w / 2, 50.0, (255, 214, 70))\n+        reg.text(\"Apple IIGS  \u00b7  Disk 1\", \"arialbd.ttf\", 3.8, w / 2, l - 13.0, (40, 40, 60))\n+    else:\n+        reg.circle(w / 2, l * 0.6, 12.0, (176, 180, 186))\n+        reg.circle(w / 2, l * 0.6, 3.0, (30, 30, 34))\n+    return reg\n+\n+\n+def media_atlas(out):\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), (14, 14, 16))\n+    for name, picture in ((\"check\", check()), (\"boot\", boot()), (\"title\", title()), (\"game\", game())):\n+        x, y, w, h = L.MEDIA_REGIONS[name]\n+        image.paste(glass(picture, (w, h)), (x, y))\n+    disk_side(L.MEDIA_REGIONS[\"disk_top\"], True).paste(image)\n+    disk_side(L.MEDIA_REGIONS[\"disk_bottom\"], False).paste(image)\n+    d = ImageDraw.Draw(image)\n+    for name, colour in ((\"black\", (14, 14, 16)), (\"edge\", (40, 44, 52))):\n+        x, y = L.MEDIA_SWATCHES[name]\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colour)\n+    path = os.path.join(out, \"apple2gs_media.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    print_atlas(out)\n+    media_atlas(out)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "f37c23a0a124653551781f16edc6696a433c3e73",
          "filename": "tools/console-models/archimedes.py",
          "status": "added",
          "additions": 351,
          "deletions": 0,
          "changes": 351,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Farchimedes.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Farchimedes.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Farchimedes.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,351 @@\n+\"\"\"The console theme's Acorn Archimedes system model (godot/themes/console/models/systems/archimedes.glb): a BBC A3000,\n+its three-button mouse and an Acorn colour monitor.\n+\n+Built in Blender (archimedes_layout.py has the measurements, taken from photographs). The A3000 is its side outline\n+extruded across its width, the drive's slot cut into its right side; its 103 keys stand on the deck in the deck's frame,\n+each tapering to its printed top; the Acorn badge sits on the back block's front face. The mouse is a rounded box, its\n+select button apart, its cable running from its strain relief to its 9-pin plug in the A3000's back (plugs.py's). The\n+monitor is a box, its housing tapering behind, the screen's opening cut into its front, on its base; the tube's faces\n+are curved screens sitting in the opening. The printing is two textures (archimedes_print.py's): `print`, projected from\n+above onto the A3000, its keys, the mouse and the floppy, and from in front onto the badge and the monitor; and `media`,\n+the screens.\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: the right end towards the viewer, the\n+top tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m) holding the set; the set\n+holding the floppy (waiting outside the drive, shrunk to nothing), the drive's eject button and lit light, the tube's\n+four faces (those not shown behind the glass), the mouse and its select button, and the keys the clip presses.\n+\n+The `focused` clip (8 s, looping): the floppy slides into the drive, its light flickering; the mouse clicks the drive's\n+icon and the filer window opens; a double click on !Odyssey, and the game plays on the cursor keys; Escape, the desktop\n+again, and the eject button: the floppy comes out and goes.\n+\n+Two materials (case, glass) and two 1024\u00b2 textures, within the system model budget. Run from the repo root:\n+\n+    python tools/console-models/archimedes_print.py artifacts/archimedes\n+    blender -b --factory-startup --python tools/console-models/archimedes.py -- artifacts/archimedes godot/themes/console/models/systems/archimedes.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bmesh\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import archimedes_layout as L  # noqa: E402\n+import plugs  # noqa: E402\n+from amiga import curved, paint, rounded, solid  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, key, phase, smooth  # noqa: E402\n+from mega_drive_cartridge import (bevel, boolean, box, export, join, material, new_object, prism_x,  # noqa: E402\n+                                  triangles)\n+\n+TILT_YAW = -24.0     # degrees about the vertical: the right end comes towards the viewer\n+TILT_PITCH = 20.0    # degrees about the horizontal: the top tips up towards the viewer\n+ROUGHNESS = dict(case=0.5, glass=0.3)\n+SCREENS = (\"desktop\", \"filer\", \"game_1\", \"game_2\")\n+BEHIND = 3.0              # how far back a screen that isn't shown waits: behind the glass\n+ARROWS = dict(up=\"\u2191\", left=\"\u2190\", down=\"\u2193\", right=\"\u2192\")\n+\n+\n+def atlas_uv(region, w, h):\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+def swatch(name):\n+    x, y = L.SWATCHES[name]\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+def colour(obj, mat, name):\n+    paint(obj, mat, swatch(name), lambda c, n, kind: True)\n+    return obj\n+\n+\n+DECK = atlas_uv(L.REGIONS[\"deck\"], L.WIDTH, L.DEPTH)\n+\n+\n+def deck_uv(co):\n+    return DECK(co.x + L.WIDTH / 2, L.DEPTH - co.y)\n+\n+\n+def on_deck(name, outline, h0, h1, mat, outline1=None):\n+    \"\"\"A solid from an (x, s) outline in the deck's frame, from h0 off it to h1 (its top outline1, if given).\"\"\"\n+    top = outline1 or outline\n+    return solid(name, [[(x, *L.deck_point(s, h0)) for x, s in outline], [(x, *L.deck_point(s, h1)) for x, s in top]],\n+                 mat)\n+\n+\n+# ---- The A3000 -------------------------------------------------------------------------------------------------------\n+\n+def a3000(m):\n+    \"\"\"The case, the drive's slot cut into its right side, printed; the badge on its back block's front face.\"\"\"\n+    half = L.WIDTH / 2\n+    body = prism_x(\"archimedes\", L.PROFILE, -half, half, m[\"case\"])\n+    bevel(body, 3.0, segments=2, angle=40)\n+    y0, y1, z0, z1 = L.DRIVE[\"slot\"]\n+    boolean(body, box(\"slot\", half - 40.0, half + 1.0, y0, y1, z0, z1, m[\"case\"]))\n+    by, bz, bs = L.DRIVE[\"button\"]\n+    boolean(body, box(\"button_hole\", half - 4.0, half + 1.0, by - bs / 2, by + bs / 2, bz - bs / 2, bz + bs / 2,\n+                      m[\"case\"]))\n+    colour(body, m[\"case\"], \"case\")\n+    paint(body, m[\"case\"], deck_uv, lambda c, n, kind: n.z > 0.3)\n+    (dy0, dy1), (dz0, dz1) = L.DRIVE[\"y\"], L.DRIVE[\"z\"]\n+    paint(body, m[\"case\"], swatch(\"key\"), lambda c, n, kind: n.x > 0.9 and dy0 < c.y < dy1 and dz0 < c.z < dz1)\n+    paint(body, m[\"case\"], swatch(\"dark\"), lambda c, n, kind: c.x < half - 0.5 and (\n+        y0 - 0.5 < c.y < y1 + 0.5 and z0 - 0.5 < c.z < z1 + 0.5\n+        or by - bs / 2 - 0.5 < c.y < by + bs / 2 + 0.5 and bz - bs / 2 - 0.5 < c.z < bz + bs / 2 + 0.5))\n+    (bx0, bx1), (bz0, bz1) = L.BADGE[\"x\"], L.BADGE[\"z\"]\n+    plate = box(\"badge\", bx0, bx1, L.BLOCK - 0.8, L.BLOCK + 0.5, bz0, bz1, m[\"case\"])\n+    colour(plate, m[\"case\"], \"case\")\n+    face = atlas_uv(L.REGIONS[\"badge\"], bx1 - bx0, bz1 - bz0)\n+    paint(plate, m[\"case\"], lambda co: face(co.x - bx0, bz1 - co.z), lambda c, n, kind: n.y < -0.9)\n+    return join([body, plate], \"archimedes\")\n+\n+\n+def keys(m):\n+    \"\"\"The keys, each tapering to its printed top; Escape and the cursor keys apart (each its foot's middle at its\n+    origin, with where it goes), the rest joined.\"\"\"\n+    fixed, moving = [], {}\n+    inset, top = L.KEY[\"inset\"], L.KEY[\"height\"]\n+    cursor_x0 = L.KEYS_X0 + 15.5 * L.U - 0.1\n+    for legend, x0, x1, s0, s1 in L.keys():\n+        foot = [(x0 + 0.6, s0 + 0.5), (x1 - 0.6, s0 + 0.5), (x1 - 0.6, s1 - 0.5), (x0 + 0.6, s1 - 0.5)]\n+        cap = [(x0 + inset, s0 + inset - 0.5), (x1 - inset, s0 + inset - 0.5), (x1 - inset, s1 - inset + 0.5),\n+               (x0 + inset, s1 - inset + 0.5)]\n+        part = on_deck(\"key\", foot, -1.0, top, m[\"case\"], cap)\n+        red = legend[0] == \"F\" and legend[1:].isdigit()\n+        colour(part, m[\"case\"], \"red\" if red else \"key\")\n+        paint(part, m[\"case\"], deck_uv, lambda c, n, kind: n.z > 0.85)\n+        name = \"escape\" if legend == \"Esc\" else next((k for k, v in ARROWS.items() if v == legend and x0 > cursor_x0),\n+                                                     None)\n+        if name:\n+            middle = Vector(((x0 + x1) / 2, *L.deck_point((s0 + s1) / 2, -1.0)))\n+            part.data.transform(Matrix.Translation(-middle))\n+            moving[name] = (part, middle)\n+        else:\n+            fixed.append(part)\n+    return join(fixed, \"keys\"), moving\n+\n+\n+def drive_parts(m):\n+    \"\"\"The drive's eject button and its lit light, each its front's middle at its origin, with where it goes.\"\"\"\n+    half = L.WIDTH / 2\n+    by, bz, bs = L.DRIVE[\"button\"]\n+    button = box(\"eject\", -3.0, 0.0, -bs / 2 + 0.4, bs / 2 - 0.4, -bs / 2 + 0.4, bs / 2 - 0.4, m[\"case\"])\n+    colour(button, m[\"case\"], \"key\")\n+    ly, lz = L.DRIVE[\"light\"]\n+    light = box(\"light\", -0.4, 0.0, -1.6, 1.6, -0.8, 0.8, m[\"case\"])\n+    colour(light, m[\"case\"], \"lit\")\n+    return {\"eject\": (button, Vector((half + 0.6, by, bz))), \"light\": (light, Vector((half + 0.3, ly, lz)))}\n+\n+\n+def floppy(m):\n+    \"\"\"The floppy, lying flat, its shutter's edge (going in first, along -x) at its origin, its label up.\"\"\"\n+    w, d, t = L.FLOPPY[\"size\"]\n+    part = box(\"floppy\", 0.0, d, -w / 2, w / 2, -t / 2, t / 2, m[\"case\"])\n+    colour(part, m[\"case\"], \"floppy\")\n+    face = atlas_uv(L.REGIONS[\"floppy\"], w, d)\n+    paint(part, m[\"case\"], lambda co: face(co.y + w / 2, co.x), lambda c, n, kind: n.z > 0.9)\n+    return part\n+\n+\n+def mouse(m):\n+    \"\"\"The mouse and its select button, each its foot's middle at its origin, turned as it lies; and where it sits.\"\"\"\n+    w, length, h = L.MOUSE[\"size\"]\n+    body = solid(\"mouse\", [[(x, y, 0.0) for x, y in rounded(0.0, 0.0, w, length, 8.0, 3)],\n+                           [(x, y, h - 4.0) for x, y in rounded(0.0, -2.0, w - 8.0, length - 12.0, 6.0, 3)]],\n+                 m[\"case\"])\n+    bevel(body, 3.0, segments=3)\n+    colour(body, m[\"case\"], \"case\")\n+    face = atlas_uv(L.REGIONS[\"mouse\"], w, length)\n+    paint(body, m[\"case\"], lambda co: face(co.x + w / 2, length / 2 - co.y), lambda c, n, kind: n.z > 0.9)\n+    bw = (w - 12.0) / 3\n+    button = box(\"select\", -w / 2 + 6.0 + 1.2, -w / 2 + 6.0 + bw - 1.2, length / 2 - 34.0, length / 2 - 8.0, h - 5.0,\n+                 h - 2.6, m[\"case\"])\n+    colour(button, m[\"case\"], \"key\")\n+    x, y, turn = L.MOUSE[\"at\"]\n+    for part in (body, button):\n+        part.data.transform(Matrix.Rotation(math.radians(turn), 4, \"Z\"))\n+    return body, button, Vector((x, y, 0.0))\n+\n+\n+# ---- The monitor -----------------------------------------------------------------------------------------------------\n+\n+def monitor(m):\n+    \"\"\"The monitor at its place: the base, the case with the screen's opening, its housing tapering behind, printed; the\n+    glass (the desktop on it).\"\"\"\n+    M, S = L.MONITOR, L.SCREEN\n+    w, h, d = M[\"width\"], M[\"height\"], M[\"depth\"]\n+    (mx, my), (br, bh) = M[\"at\"], M[\"base\"]\n+    ring = [(mx + br * math.cos(2 * math.pi * k / 32), my + d * 0.45 + br * math.sin(2 * math.pi * k / 32))\n+            for k in range(32)]\n+    base = solid(\"base\", [[(x, y, z) for x, y in ring] for z in (0.0, bh)], m[\"case\"])\n+    colour(base, m[\"case\"], \"monitor\")\n+    case = box(\"monitor\", mx - w / 2, mx + w / 2, my, my + 90.0, bh, bh + h, m[\"case\"])\n+    bevel(case, 6.0, segments=3)\n+    fw, fh, r = S[\"frame\"]\n+    gw, gh = S[\"size\"]\n+    opening = rounded(mx, S[\"z\"], fw, fh, r, 4)\n+    inner = rounded(mx, S[\"z\"], gw + 6.0, gh + 6.0, r * 0.6, 4)\n+    boolean(case, solid(\"opening\", [[(x, y, z) for x, z in ring] for ring, y in\n+                                    ((opening, my - 10.0), (opening, my + 0.01), (inner, my + S[\"depth\"]),\n+                                     (inner, my + S[\"depth\"] + 1.0))], m[\"case\"]))\n+    colour(case, m[\"case\"], \"monitor\")\n+    face = atlas_uv(L.REGIONS[\"bezel\"], w, h)\n+    paint(case, m[\"case\"], lambda co: face(co.x - mx + w / 2, bh + h - co.z),\n+          lambda c, n, kind: n.y < -0.9 and c.y < my + 0.5)\n+    paint(case, m[\"case\"], swatch(\"dark\"),\n+          lambda c, n, kind: my + 0.5 < c.y < my + S[\"depth\"] + 0.5 and abs(c.x - mx) < fw / 2 + 0.5)\n+    housing = solid(\"housing\", [[(mx + x, y, z) for x, z in rounded(0.0, bh + h * 0.52, hw, hh, rr, 4)]\n+                                for y, hw, hh, rr in ((my + 86.0, w - 24.0, h - 24.0, 16.0),\n+                                                      (my + d, w * 0.6, h * 0.62, 24.0))], m[\"case\"])\n+    bevel(housing, 4.0, segments=2, angle=50)\n+    colour(housing, m[\"case\"], \"monitor\")\n+    glass = curved(\"glass\", gw, gh, S[\"bulge\"], atlas_uv(L.MEDIA_REGIONS[\"desktop\"], 1.0, 1.0), m[\"glass\"])\n+    glass.data.transform(Matrix.Translation((mx, my + S[\"depth\"], S[\"z\"])))\n+    return [base, case, housing, glass]\n+\n+\n+def screens(m):\n+    gw, gh = L.SCREEN[\"size\"]\n+    return {name: curved(name, gw, gh, L.SCREEN[\"bulge\"], atlas_uv(L.MEDIA_REGIONS[name], 1.0, 1.0), m[\"glass\"])\n+            for name in SCREENS}\n+\n+\n+def cable(m, points):\n+    \"\"\"The mouse's cable: a tube along the B\u00e9zier curve through four control points.\"\"\"\n+    p0, p1, p2, p3 = (Vector(p) for p in points)\n+    path = [p0 * (1 - t) ** 3 + p1 * 3 * t * (1 - t) ** 2 + p2 * 3 * t * t * (1 - t) + p3 * t ** 3\n+            for t in [k / 20 for k in range(21)]]\n+    bm = bmesh.new()\n+    rings, normal = [], Vector((0.0, 0.0, 1.0))\n+    for k, p in enumerate(path):\n+        tangent = (path[min(k + 1, 20)] - path[max(k - 1, 0)]).normalized()\n+        normal = (normal - tangent * normal.dot(tangent)).normalized()\n+        binormal = tangent.cross(normal)\n+        rings.append([bm.verts.new(p + (normal * math.cos(a) + binormal * math.sin(a)) * 1.8)\n+                      for a in [2 * math.pi * j / 6 for j in range(6)]])\n+    for a, b in zip(rings, rings[1:]):\n+        for j in range(6):\n+            bm.faces.new([a[j], a[(j + 1) % 6], b[(j + 1) % 6], b[j]])\n+    bmesh.ops.recalc_face_normals(bm, faces=bm.faces)\n+    return colour(new_object(\"cable\", bm, m[\"case\"]), m[\"case\"], \"cord\")\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+def tap(f, f0, hold=2):\n+    return smooth(phase(f, f0, f0 + 3)) * (1 - smooth(phase(f, f0 + 3 + hold, f0 + 7 + hold)))\n+\n+\n+def shown(f):\n+    if f < 52 or f >= 174:\n+        return \"desktop\"\n+    if f < 84:\n+        return \"filer\"\n+    return \"game_1\" if ((f - 84) // 6) % 2 == 0 else \"game_2\"\n+\n+\n+def glide(f):\n+    \"\"\"The mouse's offset: to the drive's icon, to !Odyssey, and back.\"\"\"\n+    a = smooth(phase(f, 38, 48))\n+    b = smooth(phase(f, 58, 70))\n+    back = smooth(phase(f, 176, 196))\n+    return (-14.0 * a + 10.0 * b) * (1 - back), (-12.0 * a + 16.0 * b) * (1 - back)\n+\n+\n+def timeline(f):\n+    out = L.FLOPPY[\"out\"]\n+    if f < 10 or f >= 198:\n+        disk = Pose(x=out, scale=HIDDEN)\n+    elif f < 17:\n+        disk = Pose(x=out, scale=max(HIDDEN, back_out(phase(f, 10, 17))))\n+    elif f < 178:\n+        disk = Pose(x=out * (1 - smooth(phase(f, 17, 34))))\n+    elif f < 190:\n+        disk = Pose(x=out * smooth(phase(f, 178, 190)))\n+    else:\n+        disk = Pose(x=out, scale=max(HIDDEN, 1 - ease_in(phase(f, 192, 198))))\n+    reading = 34 <= f < 60 and (f // 3) % 3 != 2 or 178 <= f < 184\n+    mx, my = glide(f)\n+    click = max(tap(f, 48, 1), tap(f, 72, 1), tap(f, 77, 1))\n+    poses = {\"floppy\": disk, \"eject\": Pose(x=-2.0 * tap(f, 174)), \"light\": Pose() if reading else Pose(x=-1.0),\n+             \"mouse\": Pose(x=mx, y=my), \"select\": Pose(x=mx, y=my, z=-1.2 * click),\n+             \"escape\": Pose(z=-2.4 * tap(f, 168))}\n+    for name in SCREENS:\n+        poses[name] = Pose() if name == shown(f) else Pose(y=BEHIND)\n+    for name, times in ((\"up\", (92, 120, 148)), (\"left\", (100, 132)), (\"right\", (108, 140, 156)), (\"down\", (116,))):\n+        poses[name] = Pose(z=-2.4 * max(tap(f, f0) for f0 in times))\n+    return poses\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    m = {\"case\": material(\"case\", L.COLOURS[\"print\"], ROUGHNESS[\"case\"],\n+                          bpy.data.images.load(os.path.join(images, \"archimedes_print.png\"))),\n+         \"glass\": material(\"glass\", L.COLOURS[\"print\"], ROUGHNESS[\"glass\"],\n+                           bpy.data.images.load(os.path.join(images, \"archimedes_media.png\")))}\n+\n+    fixed_keys, moving = keys(m)\n+    moving.update(drive_parts(m))\n+    body, button, at = mouse(m)\n+    moving.update({\"mouse\": (body, at), \"select\": (button, at)})\n+    turn = math.radians(L.MOUSE[\"at\"][2])\n+    ahead = Vector((-math.sin(turn), math.cos(turn), 0.0))\n+    front = at + ahead * (L.MOUSE[\"size\"][1] / 2 - 1.0) + Vector((0.0, 0.0, 10.0))\n+    beige = (lambda o: colour(o, m[\"case\"], \"cord\"))\n+    relief, start, leaving = plugs.place(*plugs.grommet(beige, 3.0), m[\"case\"], front, ahead)\n+    plug, end, back = plugs.place(*plugs.dsub(beige, beige, 13.0), m[\"case\"], (L.WIDTH / 2 - 30.0, L.DEPTH, 30.0),\n+                                  (0.0, 1.0, 0.0))\n+    lead = cable(m, [start, start + leaving * 50.0 + Vector((0.0, 0.0, -8.0)),\n+                     (L.WIDTH / 2 + 30.0, L.DEPTH + 50.0, 1.8), end])\n+    whole = join([a3000(m), fixed_keys, lead] + relief + plug + monitor(m), \"archimedes\")\n+    for p in whole.data.polygons:\n+        p.use_smooth = p.area < 400.0                     # the big flat faces flat\n+    whole.data.set_sharp_from_angle(angle=math.radians(35))\n+\n+    root = bpy.data.objects.new(\"archimedes_root\", None)\n+    scene.collection.objects.link(root)\n+    whole.parent = root\n+    y0, y1, z0, z1 = L.DRIVE[\"slot\"]\n+    moving[\"floppy\"] = (floppy(m), Vector((L.WIDTH / 2 + 3.0 - L.FLOPPY[\"size\"][1], (y0 + y1) / 2, (z0 + z1) / 2)))\n+    (mx, my), S = L.MONITOR[\"at\"], L.SCREEN\n+    glass_front = Vector((mx, my + S[\"depth\"] - 0.4, S[\"z\"]))\n+    moving.update({name: (part, glass_front) for name, part in screens(m).items()})\n+    nodes = {}\n+    for name, (part, where) in moving.items():\n+        if name not in SCREENS:\n+            for p in part.data.polygons:\n+                p.use_smooth = True\n+            part.data.set_sharp_from_angle(angle=math.radians(35))\n+        part.parent = whole\n+        part.location = where\n+        nodes[name] = part\n+\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    points = [tilt @ v.co for v in whole.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [whole] + list(nodes.values())\n+    print(f\"Archimedes: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(whole.data)} the set), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "c1d35210724f94ebd990a314a940f31d50943d3a",
          "filename": "tools/console-models/archimedes_layout.py",
          "status": "added",
          "additions": 117,
          "deletions": 0,
          "changes": 117,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Farchimedes_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Farchimedes_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Farchimedes_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,117 @@\n+\"\"\"The Acorn Archimedes card's measurements, shared by archimedes_print.py (which draws its printing and screens) and\n+archimedes.py (which builds it). Plain Python: Blender's Python imports it too.\n+\n+The BBC A3000 (1989), the Archimedes in one box, 490 mm wide, 340 deep and 85 high (measured from photographs on\n+Wikimedia Commons, scaled by its keys' 19.05 mm pitch: no published figures turned up), its three-button mouse at its\n+right, and an Acorn colour monitor behind it (350 wide, 320 high and 387 deep on its base: the AKF12's spec sheet), a\n+3.5-inch floppy with a made-up game, Odyssey, going into the drive in its right side. Millimetres: x from the A3000's\n+middle (left to right), y back from its front, z up from the table. Measured from photographs on Wikimedia Commons (from\n+the front and above, the front left, its back straight on; the mouse from above; others with their monitors).\n+\n+The A3000: a cream wedge (PROFILE: the front, the keyboard's deck sloping up, the raised back block with its two vent\n+grilles, the back); its keys standing on the deck in the deck's frame (the function row's red F1 to F12, the main block,\n+the cursor keys, the keypad: KEYS, in units of U across and rows up the deck), the Acorn badge on the back block's front\n+face, the BBC badge at the keypad's head; the floppy drive in its right side (DRIVE). The mouse (MOUSE): its three\n+buttons across its front. The monitor (MONITOR): its case, its housing tapering behind, the bezel round the tube, the\n+Acorn badge, the power switch and its light, on its base.\n+\n+Two textures (ATLAS square). `print` (REGIONS): `deck` (the A3000 from above: the keys' tops and legends, the grilles,\n+the BBC badge), `bezel` (the monitor's front), `badge` (the Acorn badge), `floppy` (the floppy's top), `mouse` (the\n+mouse from above), and swatches. `media` (MEDIA_REGIONS): the screens (`desktop`, `filer`, `game_1`, `game_2`: RISC OS's\n+desktop and its icon bar, the floppy's filer window with !Odyssey in it, the game full screen). A region is (x, y,\n+width, height) in pixels.\n+\"\"\"\n+\n+import math\n+\n+ATLAS = 1024\n+U = 19.05\n+\n+WIDTH, DEPTH, HEIGHT = 490.0, 340.0, 85.0\n+FRONT = (10.0, 30.0)              # the deck's front edge (y, z)\n+DECK_BACK = (166.0, 54.0)         # its back edge, at the back block's foot\n+SLOPE = math.atan2(DECK_BACK[1] - FRONT[1], DECK_BACK[0] - FRONT[0])\n+BLOCK = 172.0                     # the back block's front face (y)\n+PROFILE = [                       # the side outline (y, z), round from the front's foot\n+    (4.0, 0.0), (DEPTH - 4.0, 0.0), (DEPTH, 4.0), (DEPTH, HEIGHT - 4.0), (DEPTH - 4.0, HEIGHT), (BLOCK + 4.0, HEIGHT),\n+    (BLOCK, HEIGHT - 4.0), (BLOCK, DECK_BACK[1] + 3.0), DECK_BACK, FRONT, (4.0, FRONT[1] - 1.0), (0.0, FRONT[1] - 4.0),\n+    (0.0, 4.0),\n+]\n+GRILLES = [dict(x=(-208.0, 50.0), y=(206.0, 330.0)), dict(x=(96.0, 236.0), y=(206.0, 330.0))]\n+BADGE = dict(x=(-232.0, -170.0), z=(64.0, 80.0))              # the Acorn badge, on the back block's front face\n+\n+KEYS_X0, ROW1 = -219.0, 138.0     # the keys' left edge; the function row's middle (s)\n+KEY = dict(height=10.0, inset=2.2)\n+# Each key: (legend, x in units from KEYS_X0, width in units, row: 0 the function row, 1 to 5 the main rows (the\n+# number row first), its height in rows).\n+FUNCTION = [(\"Esc\", 0.0, 1.0)] + [(\"F%d\" % (k + 1), 2.0 + k + 0.5 * (k // 4), 1.0) for k in range(12)] + \\\n+    [(\"Print\", 15.5, 1.0), (\"Scroll Lock\", 16.5, 1.0), (\"Break\", 17.5, 1.0)]\n+MAIN = [\n+    [(c, 1.0) for c in \"`1234567890-=\u00a3\"] + [(\"\u2190\", 1.0)],\n+    [(\"Tab\", 1.5)] + [(c, 1.0) for c in \"QWERTYUIOP[]\"] + [(\"\\\\\", 1.5)],\n+    [(\"Ctrl\", 1.75)] + [(c, 1.0) for c in \"ASDFGHJKL;'\"] + [(\"Return\", 2.25)],\n+    [(\"Shift\", 2.25)] + [(c, 1.0) for c in \"ZXCVBNM,./\"] + [(\"Shift\", 2.75)],\n+    [(\"Caps Lock\", 1.5), (None, 0.5), (\"Alt\", 1.5), (\" \", 7.0), (\"Alt\", 1.5), (None, 1.5), (\"Ctrl\", 1.5)],\n+]\n+CURSOR = [(\"Insert\", 15.5, 1), (\"Home\", 16.5, 1), (\"Page Up\", 17.5, 1), (\"Delete\", 15.5, 2), (\"Copy\", 16.5, 2),\n+          (\"Page Down\", 17.5, 2), (\"\u2191\", 16.5, 4), (\"\u2190\", 15.5, 5), (\"\u2193\", 16.5, 5), (\"\u2192\", 17.5, 5)]\n+KEYPAD = [(\"Num Lock\", 19.0, 1, 1), (\"/\", 20.0, 1, 1), (\"*\", 21.0, 1, 1), (\"#\", 22.0, 1, 1), (\"7\", 19.0, 2, 1),\n+          (\"8\", 20.0, 2, 1), (\"9\", 21.0, 2, 1), (\"-\", 22.0, 2, 1), (\"4\", 19.0, 3, 1), (\"5\", 20.0, 3, 1),\n+          (\"6\", 21.0, 3, 1), (\"+\", 22.0, 3, 1), (\"1\", 19.0, 4, 1), (\"2\", 20.0, 4, 1), (\"3\", 21.0, 4, 1),\n+          (\"Enter\", 22.0, 4, 2), (\"0\", 19.0, 5, 1), (\".\", 21.0, 5, 1)]\n+BBC = dict(x=(19.0, 23.0))        # the BBC badge's span (units), in the function row\n+\n+# The drive in the right side: its bezel (y, z); its slot (y0, y1, z0, z1); its eject button's middle (y, z) and size;\n+# its light (y, z).\n+DRIVE = dict(y=(194.0, 306.0), z=(56.0, 74.0), slot=(210.0, 302.0, 62.0, 67.0), button=(201.0, 66.0, 6.0),\n+             light=(201.0, 59.0))\n+FLOPPY = dict(size=(90.0, 94.0, 3.3), shutter=(46.0, 30.0), out=100.0)       # w, d, t; how far out of the slot it waits\n+\n+MOUSE = dict(size=(60.0, 100.0, 34.0), at=(300.0, 112.0, -8.0), buttons=3)  # w, length, h; where it sits and its turn\n+# The monitor: its case; its front's middle (x, y); its base's radius and height. Its screen: the glass's size, its\n+# middle's height, its bulge; the opening round it (w, h, corners' radius) and how deep the glass sits in it.\n+MONITOR = dict(width=350.0, height=320.0, depth=387.0, at=(0.0, 380.0), base=(110.0, 28.0))\n+SCREEN = dict(size=(262.0, 200.0), z=196.0, bulge=6.0, frame=(282.0, 220.0, 12.0), depth=16.0)\n+\n+COLOURS = dict(case=\"#E3DDCB\", key=\"#C7C2B4\", red=\"#D9472B\", monitor=\"#DCD6C4\", print=\"#FFFFFF\")\n+\n+REGIONS = dict(deck=(0, 0, 1024, 711), bezel=(0, 716, 330, 302), badge=(340, 716, 124, 32), floppy=(340, 756, 135, 141),\n+               mouse=(484, 756, 90, 150))\n+SWATCHES = dict(case=(600, 740), key=(632, 740), red=(664, 740), monitor=(696, 740), dark=(728, 740), light=(760, 740),\n+                lit=(792, 740), floppy=(824, 740), cord=(856, 740))\n+MEDIA_REGIONS = dict(desktop=(0, 0, 512, 410), filer=(512, 0, 512, 410), game_1=(0, 416, 512, 410),\n+                     game_2=(512, 416, 512, 410))\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s\n+\n+\n+def deck_point(s, h):\n+    \"\"\"The model's (y, z) of the point s up the deck's slope from its front edge and h off it.\"\"\"\n+    c, k = math.cos(SLOPE), math.sin(SLOPE)\n+    return FRONT[0] + s * c - h * k, FRONT[1] + s * k + h * c\n+\n+\n+def row_s(row):\n+    \"\"\"The middle (s) of a row: 0 the function row, then the main rows, half a row apart from it.\"\"\"\n+    return ROW1 - (row + 0.5 if row else 0.0) * U\n+\n+\n+def keys():\n+    \"\"\"Every key: (legend, x0, x1, s0, s1), its extent across and up the deck.\"\"\"\n+    out = [(legend, KEYS_X0 + x * U, KEYS_X0 + (x + w) * U, row_s(0) - U / 2, row_s(0) + U / 2)\n+           for legend, x, w in FUNCTION]\n+    for r, row in enumerate(MAIN):\n+        x = 0.0\n+        for legend, w in row:\n+            if legend is not None:\n+                out.append((legend, KEYS_X0 + x * U, KEYS_X0 + (x + w) * U, row_s(r + 1) - U / 2,\n+                            row_s(r + 1) + U / 2))\n+            x += w\n+    for legend, x, row in CURSOR:\n+        out.append((legend, KEYS_X0 + x * U, KEYS_X0 + (x + 1) * U, row_s(row) - U / 2, row_s(row) + U / 2))\n+    for legend, x, row, tall in KEYPAD:\n+        w = 2.0 if legend == \"0\" else 1.0\n+        out.append((legend, KEYS_X0 + x * U, KEYS_X0 + (x + w) * U, row_s(row + tall - 1) - U / 2,\n+                    row_s(row) + U / 2))\n+    return out"
        },
        {
          "sha": "8caec4d6bb11ebdb76b4515d384dbfd44775dd83",
          "filename": "tools/console-models/archimedes_print.py",
          "status": "added",
          "additions": 262,
          "deletions": 0,
          "changes": 262,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Farchimedes_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Farchimedes_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Farchimedes_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,262 @@\n+\"\"\"The Acorn Archimedes card's printing and screens (archimedes_print.png, archimedes_media.png), for the console\n+theme's model (archimedes.py).\n+\n+`archimedes_print.png`, laid out as archimedes_layout.REGIONS says: the A3000 from above (the keys' tops and legends,\n+the red function keys, the two grilles, the BBC badge at the keypad's head), the monitor's front (its bezel, Acorn and\n+the green acorn, the power switch and its light), the Acorn badge, the floppy's top (a made-up game, Odyssey), the mouse\n+from above (its three buttons), and swatches. `archimedes_media.png`: the screens, drawn at RISC OS's 640 x 512 (the\n+desktop and its icon bar; the floppy's filer window with !Odyssey in it; two moments of the game, full screen). Run it\n+with the system Python (Pillow):\n+\n+    python tools/console-models/archimedes_print.py <out folder>\n+\"\"\"\n+\n+import os\n+import random\n+import sys\n+\n+from PIL import Image, ImageDraw, ImageFont\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import archimedes_layout as L  # noqa: E402\n+from psp_print import ship, space  # noqa: E402\n+from retro_pc_print import rgb  # noqa: E402\n+from windows_pc_print import FONTS, Region  # noqa: E402\n+\n+CASE = rgb(L.COLOURS[\"case\"])\n+KEY = rgb(L.COLOURS[\"key\"])\n+RED = rgb(L.COLOURS[\"red\"])\n+MONITOR = rgb(L.COLOURS[\"monitor\"])\n+INK = (60, 58, 54)\n+GREEN = (60, 150, 60)\n+BRASS = (196, 170, 96)\n+\n+\n+def shade(colour, k):\n+    return tuple(max(0, min(255, round(c * (1 + k)))) for c in colour)\n+\n+\n+def acorn(reg, x, y, h, fill):\n+    \"\"\"Acorn's acorn, h tall, its middle at (x, y): the nut, its cup and its stalk.\"\"\"\n+    reg.draw.ellipse(reg.box(x - 0.38 * h, y - 0.3 * h, x + 0.38 * h, y + 0.5 * h), fill=fill)\n+    reg.draw.chord(reg.box(x - 0.46 * h, y - 0.5 * h, x + 0.46 * h, y + 0.1 * h), 180, 360, fill=shade(fill, -0.3))\n+    reg.rect_(x - 0.04 * h, y - 0.6 * h, x + 0.04 * h, y - 0.4 * h, shade(fill, -0.3))\n+\n+\n+# ---- The print -------------------------------------------------------------------------------------------------------\n+\n+def deck():\n+    \"\"\"The A3000 from above: x across (-WIDTH/2 at the left), y down from the back to the front.\"\"\"\n+    reg = Region(L.REGIONS[\"deck\"], L.WIDTH, L.DEPTH, CASE)\n+\n+    def at(x, y):\n+        return x + L.WIDTH / 2, L.DEPTH - y\n+\n+    for g in L.GRILLES:                                     # the grilles' slots, running back to front\n+        (x0, x1), (y0, y1) = g[\"x\"], g[\"y\"]\n+        n = int((x1 - x0) / 5.2)\n+        for k in range(n):\n+            x = x0 + (x1 - x0) * (k + 0.5) / n\n+            reg.rect_(*at(x - 1.3, y1), *at(x + 1.3, y0), shade(CASE, -0.45), r=1.2)\n+    (bx0, bx1) = L.BBC[\"x\"]\n+    y0, y1 = (L.deck_point(L.row_s(0) - 8.0, 0.0)[0], L.deck_point(L.row_s(0) + 8.0, 0.0)[0])\n+    xa, xb = L.KEYS_X0 + bx0 * L.U, L.KEYS_X0 + bx1 * L.U\n+    reg.rect_(*at(xa, y1), *at(xb, y0), (200, 186, 120), r=1.0)\n+    reg.rect_(*at(xa + 2.0, y1 - 2.0), *at(xa + 40.0, y0 + 2.0), (40, 40, 44))\n+    for k, line in enumerate((\"BRITISH\", \"BROADCASTING\", \"CORPORATION\")):\n+        reg.text(line, \"arialbd.ttf\", 2.6, *at(xa + 4.0, y1 - 4.5 - 3.4 * k), (230, 230, 226), anchor=\"lm\")\n+    reg.rect_(*at(xa + 44.0, y1 - 2.0), *at(xb - 2.0, y0 + 2.0), (110, 170, 80))\n+    reg.text(\"A3000\", \"ariblk.ttf\", 7.0, *at((xa + 44.0 + xb) / 2, (y0 + y1) / 2), (240, 240, 230))\n+\n+    top_h, inset = L.KEY[\"height\"], L.KEY[\"inset\"]\n+    for legend, x0, x1, s0, s1 in L.keys():\n+        ya = L.deck_point(s0 + inset - 0.5, top_h)[0]\n+        yb = L.deck_point(s1 - inset + 0.5, top_h)[0]\n+        fill = RED if legend[0] == \"F\" and legend[1:].isdigit() else KEY\n+        reg.rect_(*at(x0 + inset, yb), *at(x1 - inset, ya), fill, r=1.2)\n+        x, y = at(x0 + inset + 1.4, yb)\n+        ink = (250, 236, 228) if fill == RED else INK\n+        if len(legend) == 1 and legend != \" \":\n+            reg.text(legend, \"seguisym.ttf\" if ord(legend) > 0x2000 else \"arial.ttf\", 3.8, x, y + 3.2, ink,\n+                     anchor=\"lm\")\n+        elif legend.strip():\n+            for k, word in enumerate(legend.split(\" \")):\n+                reg.text(word, \"arial.ttf\", 2.2, x, y + 2.6 + 2.6 * k, ink, anchor=\"lm\")\n+    return reg\n+\n+\n+def badge():\n+    \"\"\"The Acorn badge: a brass plate, Acorn in dark italic, the green acorn.\"\"\"\n+    (x0, x1), (z0, z1) = L.BADGE[\"x\"], L.BADGE[\"z\"]\n+    w, h = x1 - x0, z1 - z0\n+    reg = Region(L.REGIONS[\"badge\"], w, h, BRASS)\n+    reg.rect_(0.6, 0.6, w - 0.6, h - 0.6, None, r=1.0, outline=shade(BRASS, -0.3), width=0.4)\n+    reg.text(\"Acorn\", \"timesbi.ttf\", 9.0, 4.0, h / 2, (50, 40, 30), anchor=\"lm\")\n+    acorn(reg, w - 9.0, h / 2, 9.0, GREEN)\n+    return reg\n+\n+\n+def bezel():\n+    \"\"\"The monitor's front: x across, z down from its top; the opening dark, Acorn, the power switch and its light.\"\"\"\n+    w, h = L.MONITOR[\"width\"], L.MONITOR[\"height\"]\n+    reg = Region(L.REGIONS[\"bezel\"], w, h, MONITOR)\n+    fw, fh, r = L.SCREEN[\"frame\"]\n+    zc = h - (L.SCREEN[\"z\"] - L.MONITOR[\"base\"][1])\n+    reg.rect_(w / 2 - fw / 2 - 4.0, zc - fh / 2 - 4.0, w / 2 + fw / 2 + 4.0, zc + fh / 2 + 4.0, shade(MONITOR, -0.08),\n+              r=r + 4.0)\n+    reg.rect_(w / 2 - fw / 2, zc - fh / 2, w / 2 + fw / 2, zc + fh / 2, (20, 20, 22), r=r)\n+    reg.text(\"Acorn\", \"timesbi.ttf\", 12.0, 26.0, h - 22.0, (70, 66, 60), anchor=\"lm\")\n+    acorn(reg, 70.0, h - 22.0, 11.0, GREEN)\n+    reg.rect_(w - 64.0, h - 30.0, w - 30.0, h - 14.0, shade(MONITOR, -0.12), r=2.0)\n+    reg.circle(w - 22.0, h - 22.0, 2.4, (40, 200, 60))\n+    return reg\n+\n+\n+def floppy():\n+    \"\"\"The floppy's top: x across, y down from its shutter's edge (the end going in) to its label's.\"\"\"\n+    w, d, _ = L.FLOPPY[\"size\"]\n+    reg = Region(L.REGIONS[\"floppy\"], w, d, (52, 58, 72))\n+    sw, sd = L.FLOPPY[\"shutter\"]\n+    reg.rect_(w / 2 - sw / 2, 0.0, w / 2 + sw / 2, sd, (192, 194, 198))\n+    reg.rect_(w / 2 + 2.0, 6.0, w / 2 + 12.0, sd - 4.0, (40, 40, 44), r=1.0)\n+    reg.rect_(8.0, 40.0, w - 8.0, d - 4.0, (238, 236, 228), r=1.0)\n+    art = space((360, 160)).convert(\"RGB\")\n+    ship(ImageDraw.Draw(art), 100, 82, 2.0, \"#FFFFFF\", \"#FFB23C\")\n+    x0, y0, x1, y1 = reg.box(11.0, 58.0, w - 11.0, d - 7.0)\n+    reg.img.paste(art.resize((round(x1 - x0), round(y1 - y0)), Image.LANCZOS), (round(x0), round(y0)))\n+    reg.text(\"ODYSSEY\", \"ariblk.ttf\", 7.0, w / 2, 49.0, (40, 40, 44))\n+    return reg\n+\n+\n+def mouse():\n+    \"\"\"The mouse from above: x across, y back from its front (the buttons' end).\"\"\"\n+    w, length, _ = L.MOUSE[\"size\"]\n+    reg = Region(L.REGIONS[\"mouse\"], w, length, CASE)\n+    bw = (w - 12.0) / 3\n+    for k in range(3):\n+        reg.rect_(6.0 + k * bw + 1.0, 6.0, 6.0 + (k + 1) * bw - 1.0, 34.0, shade(CASE, -0.12), r=1.5)\n+    return reg\n+\n+\n+# ---- The screens -----------------------------------------------------------------------------------------------------\n+\n+W, H = 640, 512\n+GREY = (187, 187, 187)\n+BAR = (221, 221, 221)\n+\n+\n+def font(size, bold=False):\n+    return ImageFont.truetype(FONTS + (\"arialbd.ttf\" if bold else \"arial.ttf\"), size)\n+\n+\n+def disc_icon(d, x, y):\n+    \"\"\"The floppy drive's icon on the icon bar, its top left at (x, y), and its name under it.\"\"\"\n+    d.rectangle([x, y, x + 34, y + 30], fill=(40, 60, 140), outline=(0, 0, 0))\n+    d.rectangle([x + 9, y, x + 25, y + 11], fill=(200, 200, 204), outline=(0, 0, 0))\n+    d.rectangle([x + 6, y + 15, x + 28, y + 30], fill=(250, 250, 250), outline=(0, 0, 0))\n+    d.text((x + 17, y + 38), \":0\", font=font(13), fill=(0, 0, 0), anchor=\"mm\")\n+\n+\n+def icon_bar(img):\n+    d = ImageDraw.Draw(img)\n+    d.rectangle([0, H - 64, W - 1, H - 1], fill=BAR)\n+    d.line([0, H - 64, W - 1, H - 64], fill=(255, 255, 255), width=2)\n+    disc_icon(d, 20, H - 56)\n+    d.rectangle([80, H - 54, 110, H - 26], fill=(220, 180, 60), outline=(0, 0, 0))   # Apps\n+    d.text((95, H - 18), \"Apps\", font=font(13), fill=(0, 0, 0), anchor=\"mm\")\n+    x = W - 46                                                # the Acorn icon at the right\n+    d.ellipse([x, H - 52, x + 26, H - 20], fill=(70, 150, 60), outline=(0, 0, 0))\n+    d.chord([x - 4, H - 58, x + 30, H - 34], 180, 360, fill=(110, 80, 40), outline=(0, 0, 0))\n+    d.rectangle([W - 96, H - 52, W - 66, H - 22], fill=(255, 255, 255), outline=(0, 0, 0))   # the palette\n+    for k, colour in enumerate(((220, 40, 40), (40, 180, 40), (40, 80, 220), (240, 200, 40))):\n+        d.rectangle([W - 92 + (k % 2) * 13, H - 48 + (k // 2) * 13, W - 81 + (k % 2) * 13, H - 37 + (k // 2) * 13],\n+                    fill=colour)\n+\n+\n+def desktop():\n+    img = Image.new(\"RGB\", (W, H), GREY)\n+    icon_bar(img)\n+    return img\n+\n+\n+def filer():\n+    \"\"\"The desktop and the floppy's filer window, !Odyssey in it.\"\"\"\n+    img = desktop()\n+    d = ImageDraw.Draw(img)\n+    x0, y0, x1, y1 = 150, 90, 520, 330\n+    d.rectangle([x0 + 4, y0 + 4, x1 + 4, y1 + 4], fill=(110, 110, 110))\n+    d.rectangle([x0, y0, x1, y1], fill=(255, 255, 255), outline=(0, 0, 0))\n+    d.rectangle([x0, y0, x1, y0 + 24], fill=(255, 255, 190), outline=(0, 0, 0))     # the title bar, with input focus\n+    for xx in (x0, x0 + 24, x1 - 24):\n+        d.rectangle([xx, y0, xx + 24, y0 + 24], fill=(220, 220, 220), outline=(0, 0, 0))\n+    d.text(((x0 + x1) // 2, y0 + 12), \"adfs::0.$\", font=font(15), fill=(0, 0, 0), anchor=\"mm\")\n+    d.rectangle([x1 - 20, y0 + 24, x1, y1], fill=(200, 230, 255), outline=(0, 0, 0))  # the scroll bar\n+    d.rectangle([x1 - 17, y0 + 50, x1 - 3, y1 - 80], fill=(150, 200, 240))\n+    art = Image.new(\"RGB\", (60, 60), (10, 10, 40))           # !Odyssey's sprite: the ship by the planet\n+    ad = ImageDraw.Draw(art)\n+    ad.ellipse([30, 14, 56, 40], fill=(240, 140, 60))\n+    ship(ad, 18, 30, 0.9, \"#FFFFFF\", \"#FFB23C\")\n+    img.paste(art, (x0 + 40, y0 + 50))\n+    d.rectangle([x0 + 40, y0 + 50, x0 + 100, y0 + 110], outline=(0, 0, 0))\n+    d.text((x0 + 70, y0 + 126), \"!Odyssey\", font=font(15), fill=(0, 0, 0), anchor=\"mm\")\n+    for k, name in enumerate((\"!Help\", \"Levels\")):\n+        xx = x0 + 160 + k * 100\n+        d.rectangle([xx, y0 + 66, xx + 34, y0 + 100], fill=(240, 240, 240), outline=(0, 0, 0))\n+        d.text((xx + 17, y0 + 126), name, font=font(15), fill=(0, 0, 0), anchor=\"mm\")\n+    d.polygon([(x0 + 90, y0 + 90), (x0 + 90, y0 + 112), (x0 + 96, y0 + 106), (x0 + 101, y0 + 116),\n+               (x0 + 104, y0 + 114), (x0 + 99, y0 + 104), (x0 + 107, y0 + 104)], fill=(60, 60, 220),\n+              outline=(0, 0, 0))                                                          # the pointer\n+    return img\n+\n+\n+def game(frame):\n+    \"\"\"The game full screen: space, the ship firing at the ringed planet, the score.\"\"\"\n+    img = Image.new(\"RGB\", (W, H), (8, 6, 30))\n+    d = ImageDraw.Draw(img)\n+    rng = random.Random(9)\n+    for _ in range(80):\n+        x, y = rng.randrange(W), rng.randrange(H)\n+        d.point(((x - frame * 24) % W, y), fill=(255, 255, 255))\n+    d.ellipse([400 - 20 * frame, 150, 540 - 20 * frame, 290], fill=(240, 140, 60))\n+    d.ellipse([310 - 20 * frame, 205, 630 - 20 * frame, 235], outline=(250, 250, 250), width=3)\n+    ship(d, 150, 230 + 30 * frame, 4.0, \"#FFFFFF\", \"#FFB23C\")\n+    for k in range(3):\n+        d.line([(230 + 60 * k + 20 * frame, 230 + 30 * frame), (256 + 60 * k + 20 * frame, 230 + 30 * frame)],\n+               fill=(255, 230, 80), width=4)\n+    d.text((24, 22), \"SCORE %05d\" % (2600 + 400 * frame), font=font(22, True), fill=(255, 230, 80), anchor=\"lm\")\n+    d.text((W - 24, 22), \"ODYSSEY\", font=font(22, True), fill=(120, 220, 255), anchor=\"rm\")\n+    return img\n+\n+\n+def media_atlas(out):\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), (0, 0, 0))\n+    for name, screen in ((\"desktop\", desktop()), (\"filer\", filer()), (\"game_1\", game(0)), (\"game_2\", game(1))):\n+        x, y, w, h = L.MEDIA_REGIONS[name]\n+        image.paste(screen.resize((w, h), Image.LANCZOS), (x, y))\n+    path = os.path.join(out, \"archimedes_media.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+def print_atlas(out):\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), CASE)\n+    for reg in (deck(), badge(), bezel(), floppy(), mouse()):\n+        reg.paste(image)\n+    d = ImageDraw.Draw(image)\n+    colours = dict(case=CASE, key=KEY, red=RED, monitor=MONITOR, dark=(14, 14, 14), light=(60, 70, 60),\n+                   lit=(60, 240, 80), floppy=(52, 58, 72), cord=(210, 204, 190))\n+    for name, (x, y) in L.SWATCHES.items():\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colours[name])\n+    path = os.path.join(out, \"archimedes_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    print_atlas(out)\n+    media_atlas(out)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "b8384ab244bfd9fe325a1d3211d1a979e99465ad",
          "filename": "tools/console-models/atari5200.py",
          "status": "added",
          "additions": 383,
          "deletions": 0,
          "changes": 383,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari5200.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari5200.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fatari5200.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,383 @@\n+\"\"\"The console theme's Atari 5200 system model (godot/themes/console/models/systems/atari5200.glb): an Atari 5200, the\n+four-port model, and its controller.\n+\n+Built in Blender from the real console's proportions (atari5200_layout.py has the measurements, taken from\n+photographs). The console is its side outline extruded across its width (the top sloping up from the front face to the\n+back, the undercut along its sides), a narrower block under its back; the vents and the cartridge slot are cut into its\n+top, the ports' panel and the ports into its front face; the brushed band lies on the top, POWER ON/OFF stands beside\n+it. The controller is a wedge tapering to its front, the stick's well and the keypad's panel cut into its top, the keys,\n+START, PAUSE and RESET on it, the red fire buttons in its sides, the stick in its rubber boot. The printing is one\n+texture drawn by atari5200_print.py: the top along its slope, the front face, the controller's top (planar-mapped onto\n+their faces), the cartridge's label, and swatches.\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: the right side towards the viewer, the\n+top tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m) holding the set; the set\n+holding the cartridge (waiting in its slot, shrunk to nothing), POWER ON/OFF, the lit power light (shrunk to nothing),\n+and the controller; the controller holding its stick, START and a fire button.\n+\n+The `focused` clip (8 s, looping) plays a cartridge: it drops into the slot, POWER goes in and the light comes on; START\n+is pressed and the game is played on the stick and the fire button. Then POWER again, the light goes out, and the\n+cartridge rises out and goes.\n+\n+Four materials (case, key, black, print) and one texture, within the system model budget. Run from the repo root:\n+\n+    python tools/console-models/atari5200_print.py artifacts/atari5200\n+    blender -b --factory-startup --python tools/console-models/atari5200.py -- artifacts/atari5200 godot/themes/console/models/systems/atari5200.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import atari5200_layout as L  # noqa: E402\n+from amiga import paint, rounded, solid  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, ease_out, key, phase, smooth  # noqa: E402\n+from mega_drive_cartridge import bevel, boolean, box, export, join, material, prism_x, triangles  # noqa: E402\n+\n+TILT_YAW = -24.0     # degrees about the vertical: the right side comes towards the viewer\n+TILT_PITCH = 26.0    # degrees about the horizontal: the top tips up towards the viewer\n+ROUGHNESS = dict(case=0.45, key=0.5, black=0.7, print=0.45)\n+\n+# The top's frame: x across, s up the slope from its front edge, h off it.\n+TOP_FRAME = Matrix.Translation((0.0, L.TOP[0][0], L.TOP[0][1])) @ Matrix.Rotation(L.SLOPE, 4, \"X\")\n+UP = TOP_FRAME.to_3x3() @ Vector((0.0, 0.0, 1.0))      # out of the top, in the model's frame\n+P = L.PAD\n+# The controller's top frame: x across, y back from its front end, h off its sloping top; its origin at its front end.\n+PAD_SLOPE = math.atan2(P[\"height\"][1] - P[\"height\"][0], P[\"length\"])\n+PAD_TOP = Matrix.Translation((0.0, -P[\"length\"] / 2, P[\"height\"][0])) @ Matrix.Rotation(PAD_SLOPE, 4, \"X\")\n+\n+\n+# ---- UVs -------------------------------------------------------------------------------------------------------------\n+\n+def atlas_uv(region, w, h):\n+    \"\"\"A face drawn w x h into region: (a, b) millimetres across it (a to the right, b down from its top) to UVs.\"\"\"\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+TOP = atlas_uv(L.REGIONS[\"top\"], L.WIDTH, L.TOP_LENGTH)\n+FRONT = atlas_uv(L.REGIONS[\"front\"], L.WIDTH, L.TOP[0][1])\n+PAD_FACE = atlas_uv(L.REGIONS[\"pad\"], P[\"width\"][1], P[\"length\"])\n+TOP_INVERSE = TOP_FRAME.inverted()\n+PAD_INVERSE = PAD_TOP.inverted()\n+\n+\n+def top_uv(co):\n+    local = TOP_INVERSE @ co\n+    return TOP(local.x + L.WIDTH / 2, L.TOP_LENGTH - local.y)\n+\n+\n+def front_uv(co):\n+    return FRONT(co.x + L.WIDTH / 2, L.TOP[0][1] - co.z)\n+\n+\n+def pad_uv(co):\n+    local = PAD_INVERSE @ co\n+    return PAD_FACE(local.x + P[\"width\"][1] / 2, P[\"length\"] - local.y)\n+\n+\n+def swatch(name):\n+    x, y = L.SWATCHES[name]\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+def on_frame(frame, name, outline, h0, h1, mat):\n+    \"\"\"A solid from an (x, s) outline in a frame, from h0 to h1 off it.\"\"\"\n+    return solid(name, [[frame @ Vector((x, s, h)) for x, s in outline] for h in (h0, h1)], mat)\n+\n+\n+def rect(x0, x1, s0, s1):\n+    return [(x0, s0), (x1, s0), (x1, s1), (x0, s1)]\n+\n+\n+def facing_up(c, n, name):\n+    return n.dot(UP) > 0.97 and name == \"case\"\n+\n+\n+# ---- The console -----------------------------------------------------------------------------------------------------\n+\n+def console(m):\n+    \"\"\"The console: its outline extruded, the block under its back, the vents, the slot, the ports' panel and the\n+    ports cut into it; its top and front printed; the brushed band on its top.\"\"\"\n+    w = L.WIDTH\n+    shell = prism_x(\"atari5200\", L.PROFILE, -w / 2, w / 2, m[\"case\"])\n+    bevel(shell, 1.5, segments=2)\n+    v = L.FRONT_VENTS\n+    slots = []\n+    x = v[\"x\"][0] + v[\"pitch\"] / 2\n+    while x < v[\"x\"][1]:\n+        slots.append(on_frame(TOP_FRAME, \"vent\", rect(x - v[\"width\"] / 2, x + v[\"width\"] / 2, *v[\"s\"]), -v[\"depth\"], 5.0,\n+                              m[\"black\"]))\n+        x += v[\"pitch\"]\n+    for block in L.SLOT_VENTS:\n+        x = block[\"x\"][0] + v[\"pitch\"] / 2\n+        while x < block[\"x\"][1]:\n+            slots.append(on_frame(TOP_FRAME, \"vent\", rect(x - v[\"width\"] / 2, x + v[\"width\"] / 2, *L.SLOT_VENT_S),\n+                                  -v[\"depth\"], 5.0, m[\"black\"]))\n+            x += v[\"pitch\"]\n+    boolean(shell, join(slots, \"vents\"))\n+    (sx0, sx1), (ss0, ss1) = L.SLOT[\"x\"], L.SLOT[\"s\"]\n+    boolean(shell, on_frame(TOP_FRAME, \"slot\", rect(sx0, sx1, ss0, ss1), -L.SLOT[\"depth\"], 5.0, m[\"black\"]))\n+    (px0, px1), (pz0, pz1) = L.PORT_PANEL[\"x\"], L.PORT_PANEL[\"z\"]\n+    boolean(shell, box(\"panel\", px0, px1, -5.0, L.PORT_PANEL[\"depth\"], pz0, pz1, m[\"case\"]))\n+    pw, ph = L.PORTS[\"size\"]\n+    z = L.PORTS[\"z\"]\n+    boolean(shell, join([box(\"port\", x - pw / 2 + 1.0, x + pw / 2 - 1.0, -5.0, L.PORT_PANEL[\"depth\"] + 4.0, z - ph / 2 + 1.0,\n+                             z + ph / 2 - 1.0, m[\"black\"]) for x in L.PORTS[\"x\"]], \"ports\"))\n+    paint(shell, m[\"print\"], top_uv, facing_up)\n+    paint(shell, m[\"print\"], front_uv, lambda c, n, name: n.y < -0.9 and c.y < 8.0)\n+    (bx0, bx1), (by0, by1) = L.BLOCK[\"x\"], L.BLOCK[\"y\"]\n+    # Its top rises into the slab's underside at the back, and stays under the sloping top at its front.\n+    block = prism_x(\"block\", [(by0, 0.0), (by1, 0.0), (by1, L.UNDER + 0.5), (by0, L.UNDER - 0.5)], bx0, bx1, m[\"case\"])\n+    bevel(block, 1.5, segments=2)\n+\n+    (sx0, sx1), (ss0, ss1), r = L.SILVER[\"x\"], L.SILVER[\"s\"], L.SILVER[\"raised\"]\n+    band = on_frame(TOP_FRAME, \"band\", rect(sx0 + 1.6, sx1, ss0, ss1), -0.5, r, m[\"print\"])\n+    paint(band, m[\"print\"], top_uv, lambda c, n, name: n.dot(UP) > 0.97)\n+    paint(band, m[\"print\"], swatch(\"silver\"), lambda c, n, name: n.dot(UP) <= 0.97)\n+    return [shell, block, band]\n+\n+\n+def power(m):\n+    (x0, x1), (s0, s1) = L.POWER[\"x\"], L.POWER[\"s\"]\n+    part = on_frame(TOP_FRAME, \"power\", rect(x0, x1, s0, s1), -1.5, L.POWER[\"height\"], m[\"key\"])\n+    bevel(part, 0.8, segments=2)\n+    paint(part, m[\"print\"], top_uv, lambda c, n, name: n.dot(UP) > 0.97)\n+    return part\n+\n+\n+def light(m):\n+    \"\"\"The lit power light, its middle at its origin.\"\"\"\n+    r = L.LIGHT[\"r\"]\n+    ring = [(r * math.cos(2 * math.pi * k / 16), r * math.sin(2 * math.pi * k / 16)) for k in range(16)]\n+    part = solid(\"light\", [[(x, y, h) for x, y in ring] for h in (-0.2, 0.3)], m[\"print\"])\n+    paint(part, m[\"print\"], swatch(\"red_lit\"), lambda c, n, name: True)\n+    middle = TOP_FRAME @ Vector((L.LIGHT[\"x\"], L.LIGHT[\"s\"], 0.0))\n+    part.data.transform(TOP_FRAME.to_3x3().to_4x4())\n+    return part, middle\n+\n+\n+def cartridge(m):\n+    \"\"\"The cartridge, its bottom's middle at its origin, standing up out of the top: its shell, the grooves along its\n+    ends, its label on its front.\"\"\"\n+    w, d, h = L.CART[\"size\"]\n+    lw, lh = L.CART[\"label\"]\n+    shell = box(\"cartridge\", -w / 2, w / 2, -d / 2, d / 2, 0.0, h, m[\"black\"])\n+    bevel(shell, 1.5, segments=2)\n+    pitch = (h - 16.0) / L.CART[\"ribs\"]\n+    for side in (-1, 1):\n+        a, b = (w / 2 - 2.0, w / 2 + 5.0) if side > 0 else (-w / 2 - 5.0, -w / 2 + 2.0)\n+        boolean(shell, join([box(\"groove\", a, b, -d / 2 - 1.0, d / 2 + 1.0, 8.0 + k * pitch, 8.0 + k * pitch + pitch / 2,\n+                                 m[\"black\"]) for k in range(L.CART[\"ribs\"])], \"grooves\"))\n+    z1 = h - 8.0\n+    decal = box(\"label\", -lw / 2, lw / 2, -d / 2 - 0.2, -d / 2 + 0.3, z1 - lh, z1, m[\"print\"])\n+    face = atlas_uv(L.REGIONS[\"label\"], lw, lh)\n+    paint(decal, m[\"print\"], lambda co: face(co.x + lw / 2, z1 - co.z), lambda c, n, name: n.y < -0.9)\n+    paint(decal, m[\"print\"], swatch(\"silver\"), lambda c, n, name: n.y >= -0.9)\n+    part = join([shell, decal], \"cartridge\")\n+    part.data.transform(TOP_FRAME.to_3x3().to_4x4())\n+    return part\n+\n+\n+# ---- The controller --------------------------------------------------------------------------------------------------\n+\n+def outline():\n+    \"\"\"The controller's plan, anticlockwise from its front left: its front end narrower, its corners rounded.\"\"\"\n+    (w0, w1), length = P[\"width\"], P[\"length\"]\n+    r = 7.0\n+    corners = [(-w0 / 2, 0.0), (w0 / 2, 0.0), (w1 / 2, length), (-w1 / 2, length)]\n+    points = []\n+    for k, (cx, cy) in enumerate(corners):\n+        a0 = (225 + 90 * k) % 360\n+        ox = cx + (r if cx < 0 else -r)\n+        oy = cy + (r if cy < length / 2 else -r)\n+        for j in range(4):\n+            a = math.radians(a0 - 45 + 90 * j / 3)\n+            points.append((ox + r * math.cos(a), oy + r * math.sin(a)))\n+    return points\n+\n+\n+def controller(m):\n+    \"\"\"The controller in its own frame (its plan's middle at the origin, the table): its wedge, the stick's well and\n+    the keypad's panel cut into its top, the keys and the buttons; the stick, START and the right fire button apart.\"\"\"\n+    (h0, h1), length = P[\"height\"], P[\"length\"]\n+    plan = outline()\n+    rings = [[(x, y - length / 2, 0.0) for x, y in plan],\n+             [(x, y - length / 2, h0 + (h1 - h0) * y / length) for x, y in plan]]\n+    body = solid(\"controller\", rings, m[\"case\"])\n+    bevel(body, 2.5, segments=2)\n+    wy, wr = L.WELL[\"y\"], L.WELL[\"r\"]\n+    well = [(wr * math.cos(2 * math.pi * k / 32), wy + wr * math.sin(2 * math.pi * k / 32)) for k in range(32)]\n+    boolean(body, on_frame(PAD_TOP, \"well\", well, -L.WELL[\"depth\"], 5.0, m[\"black\"]))\n+    (ky0, ky1), (kx0, kx1) = L.KEYPAD[\"y\"], L.KEYPAD[\"x\"]\n+    kw, kd = L.KEYPAD[\"key\"]\n+    boolean(body, on_frame(PAD_TOP, \"keypad\", rect(kx0 - kw / 2 - 3.0, kx1 + kw / 2 + 3.0, ky0 - 2.0, ky1 + 2.0), -1.5,\n+                           5.0, m[\"black\"]))\n+    (by0, by1) = L.TOP_BUTTONS[\"y\"]\n+    boolean(body, on_frame(PAD_TOP, \"strip\", rect(-32.0, 32.0, by0 - 1.5, by1 + 1.5), -1.5, 5.0, m[\"black\"]))\n+    paint(body, m[\"print\"], pad_uv, lambda c, n, name: n.z > 0.95 and name == \"case\")\n+    parts = [body]\n+    rows, cols = L.KEYPAD[\"rows\"], L.KEYPAD[\"cols\"]\n+    for i in range(rows):\n+        for j in range(cols):\n+            x = kx0 + (kx1 - kx0) * j / (cols - 1)\n+            y = ky1 - kd / 2 - (ky1 - ky0 - kd) * i / (rows - 1)\n+            k = on_frame(PAD_TOP, \"key\", rect(x - kw / 2, x + kw / 2, y - kd / 2, y + kd / 2), -1.5, L.KEYPAD[\"height\"],\n+                         m[\"key\"])\n+            bevel(k, 0.6, segments=1)\n+            parts.append(k)\n+    for x0, x1 in L.TOP_BUTTONS[\"x\"][1:]:\n+        b = on_frame(PAD_TOP, \"button\", rect(x0, x1, by0, by1), -1.5, L.TOP_BUTTONS[\"height\"], m[\"key\"])\n+        bevel(b, 0.4, segments=1)\n+        parts.append(b)\n+    for part in parts[1:]:\n+        paint(part, m[\"print\"], pad_uv, lambda c, n, name: n.z > 0.95)\n+    (fz0, fz1), out = L.FIRE[\"z\"], L.FIRE[\"out\"]\n+    for side in (-1, 1):\n+        for y0, y1 in L.FIRE[\"y\"]:\n+            if side > 0 and (y0, y1) == L.FIRE[\"y\"][1]:\n+                continue                                       # the clip's: apart\n+            parts.append(fire_button(side, y0, y1, fz0, fz1, out, m))\n+    shell = join(parts, \"controller\")\n+\n+    (x0, x1) = L.TOP_BUTTONS[\"x\"][0]\n+    start = on_frame(PAD_TOP, \"start\", rect(x0, x1, by0, by1), -1.5, L.TOP_BUTTONS[\"height\"], m[\"key\"])\n+    bevel(start, 0.4, segments=1)\n+    paint(start, m[\"print\"], pad_uv, lambda c, n, name: n.z > 0.95)\n+    y0, y1 = L.FIRE[\"y\"][1]\n+    fire = fire_button(1, y0, y1, fz0, fz1, out, m)\n+    return shell, start, fire, stick(m)\n+\n+\n+def fire_button(side, y0, y1, z0, z1, out, m):\n+    \"\"\"A red fire button standing out of the controller's side (side -1 the left, 1 the right).\"\"\"\n+    (w0, w1), length = P[\"width\"], P[\"length\"]\n+    half = (w0 + (w1 - w0) * ((y0 + y1) / 2) / length) / 2\n+    a, b = (half - 1.0, half + out) if side > 0 else (-half - out, -half + 1.0)\n+    part = box(\"fire\", a, b, y0 - length / 2, y1 - length / 2, z0, z1, m[\"print\"])\n+    bevel(part, 0.6, segments=1)\n+    paint(part, m[\"print\"], swatch(\"red\"), lambda c, n, name: True)\n+    return part\n+\n+\n+def stick(m):\n+    \"\"\"The stick, its foot's middle at its origin: the rubber boot, the shaft tapering up, its domed top.\"\"\"\n+    (b0, b1), (r0, r1), length = L.STICK[\"boot\"], L.STICK[\"r\"], L.STICK[\"length\"]\n+\n+    def ring(r, z, n=24):\n+        return [(r * math.cos(2 * math.pi * k / n), r * math.sin(2 * math.pi * k / n), z) for k in range(n)]\n+    boot = solid(\"boot\", [ring(b0, -1.0), ring(b0 * 0.85, 4.0), ring(b1 + 1.0, 9.0), ring(b1, 11.0)], m[\"black\"])\n+    shaft = solid(\"shaft\", [ring(r0, 8.0), ring(r1, 8.0 + length), ring(r1 * 0.8, 10.0 + length),\n+                            ring(r1 * 0.4, 11.0 + length)], m[\"key\"])\n+    for p in shaft.data.polygons:\n+        p.use_smooth = True\n+    return join([boot, shaft], \"stick\")\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+def appear(f, f0, f1):\n+    return HIDDEN + (1 - HIDDEN) * back_out(phase(f, f0, f1)) if f >= f0 else HIDDEN\n+\n+\n+def tap(f, f0, hold=2):\n+    \"\"\"A press at f0: down in three frames, held, back up in four.\"\"\"\n+    return smooth(phase(f, f0, f0 + 3)) * (1 - smooth(phase(f, f0 + 3 + hold, f0 + 7 + hold)))\n+\n+\n+RISE = 90.0          # how far up its slot the cartridge appears\n+\n+\n+def along(d):\n+    \"\"\"A Pose moving d along the slot (out of the top).\"\"\"\n+    return dict(x=UP.x * d, y=UP.y * d, z=UP.z * d)\n+\n+\n+def timeline(f):\n+    if f < 6 or f >= 228:\n+        cart = Pose(scale=HIDDEN)\n+    elif f < 16:\n+        cart = Pose(**along(RISE), scale=appear(f, 6, 14))\n+    elif f < 28:\n+        cart = Pose(**along(RISE * (1 - ease_in(phase(f, 16, 28)))))\n+    elif f < 206:\n+        cart = Pose(squash=0.04 * math.sin(math.pi * phase(f, 28, 34)))\n+    else:\n+        cart = Pose(**along(RISE * ease_out(phase(f, 206, 218))), scale=max(HIDDEN, 1 - ease_in(phase(f, 216, 228))))\n+    press = max(tap(f, 36), tap(f, 198))\n+    t = phase(f, 64, 186)\n+    k = math.sin(math.pi * t)\n+    return {\"cartridge\": cart, \"power\": Pose(**along(-2.0 * press)),\n+            \"power_light\": Pose() if 40 <= f < 200 else Pose(scale=HIDDEN),\n+            \"start\": Pose(z=-1.4 * tap(f, 52)),\n+            \"stick\": Pose(pitch=20.0 * k * math.sin(2 * math.pi * 3.0 * t), roll=20.0 * k * math.cos(2 * math.pi * 2.0 * t)),\n+            \"fire\": Pose(x=-2.0 * max(tap(f, f0) for f0 in (84, 104, 124, 144, 164)))}\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    printed = bpy.data.images.load(os.path.join(images, \"atari5200_print.png\"))\n+    colours = dict(L.COLOURS)\n+    m = {n: material(n, colours[n], r, printed if n == \"print\" else None) for n, r in ROUGHNESS.items()}\n+\n+    whole = join(console(m), \"atari5200\")\n+    button = power(m)\n+    lit, lit_at = light(m)\n+    cart = cartridge(m)\n+    pad, start, fire, joystick = controller(m)\n+    for obj in [whole, button, cart, pad, start, fire, joystick]:\n+        for p in obj.data.polygons:\n+            p.use_smooth = True\n+        obj.data.set_sharp_from_angle(angle=math.radians(35))\n+\n+    # The node tree: the root (the view and the fit) holds the set; the set holds the moving parts at their places.\n+    root = bpy.data.objects.new(\"atari5200_root\", None)\n+    scene.collection.objects.link(root)\n+    whole.parent = root\n+    (sx0, sx1), (ss0, ss1) = L.SLOT[\"x\"], L.SLOT[\"s\"]\n+    seat = TOP_FRAME @ Vector(((sx0 + sx1) / 2, (ss0 + ss1) / 2, -L.CART[\"seat_depth\"]))\n+    for part, where in ((cart, seat), (lit, lit_at)):\n+        part.parent = whole\n+        part.location = where\n+    button.parent = whole\n+    (px, py), turn = P[\"at\"], math.radians(P[\"turn\"])\n+    pad.parent = whole\n+    pad.location = (px, py, 0.0)\n+    pad.rotation_euler = (0.0, 0.0, turn)\n+    stick_at = PAD_TOP @ Vector((0.0, L.WELL[\"y\"], -L.WELL[\"depth\"]))\n+    joystick.parent = pad\n+    joystick.location = stick_at\n+    for part in (start, fire):\n+        part.parent = pad\n+    nodes = {\"cartridge\": cart, \"power\": button, \"power_light\": lit, \"start\": start, \"stick\": joystick, \"fire\": fire}\n+\n+    # The three-quarter view, then the spec's fit of the set at rest, as the root's transform.\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    rest = Matrix.Translation((px, py, 0.0)) @ Matrix.Rotation(turn, 4, \"Z\")\n+    points = [tilt @ v.co for v in whole.data.vertices] + [tilt @ rest @ v.co for v in pad.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [whole, button, lit, cart, pad, start, fire, joystick]\n+    print(f\"Atari 5200: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(whole.data)} the set), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "fa6269a44a7488833b15c6ff1694ec518b29bec5",
          "filename": "tools/console-models/atari5200_layout.py",
          "status": "added",
          "additions": 74,
          "deletions": 0,
          "changes": 74,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari5200_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari5200_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fatari5200_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,74 @@\n+\"\"\"The Atari 5200 set's measurements, shared by atari5200_print.py (which draws its printing) and atari5200.py (which\n+builds it). Plain Python: Blender's Python imports it too.\n+\n+The Atari 5200 (1982), the four-port model, 387 x 337 x 108 mm (the Computer History Museum's 15 1/4 x 13 1/4 x 4 1/4\n+inches), and its controller, 67 mm across (the museum's 2 5/8 inches). Millimetres: x from its middle (left to right),\n+y back from its front face, z up from the table. The rest was measured from Evan-Amos's photographs on Wikimedia Commons\n+(the console from the front left and right, with a cartridge, from the back right and behind, with its storage lid\n+open, with its controller; the controller from above and from the front left; the cartridge), scaled by its width.\n+\n+The console: a slab, its top sloping up from the front face to the back (TOP), the front face holding the four\n+controller ports, the vented base under it set back; at the back the slab overhangs, a narrower block (BLOCK) under\n+it. The top, front to back, along its slope (s): a plain band with the power light at its right, a row of vents, the\n+brushed band with ATARI 5200 at its left and POWER ON/OFF beside its right end, the slot band (the cartridge slot\n+between two blocks of vents), and the storage compartment's lid. The controller: a wedge tapering to its front, the\n+stick in its well at the back, START, PAUSE and RESET along its back edge, the fire buttons in its sides, the keypad's\n+twelve keys in front of the ATARI 5200 strip.\n+\n+One texture (ATLAS square, REGIONS): `top` (the console's top seen from above along its slope: the bands, the logo,\n+POWER ON/OFF, the light's window, the lid's seam), `front` (the front face: the ports' panel), `pad` (the controller's\n+top: the keypad's legends, the strip, START, PAUSE and RESET), `label` (the cartridge's: a made-up game, Odyssey), and\n+swatches. A region is (x, y, width, height) in pixels.\n+\"\"\"\n+\n+import math\n+\n+ATLAS = 1024\n+\n+WIDTH, DEPTH, HEIGHT = 387.0, 337.0, 108.0\n+TOP = ((0.0, 46.0), (DEPTH, HEIGHT))               # the top's front and back edges, (y, z)\n+SLOPE = math.atan2(TOP[1][1] - TOP[0][1], TOP[1][0] - TOP[0][0])\n+TOP_LENGTH = math.hypot(TOP[1][1] - TOP[0][1], TOP[1][0] - TOP[0][0])\n+UNDER = 72.0                                       # the slab's underside at the back\n+PROFILE = [(8.0, 0.0), (148.0, 0.0), (228.0, UNDER), (DEPTH, UNDER), (DEPTH, HEIGHT - 3.0), (DEPTH - 3.0, HEIGHT),\n+           (2.0, TOP[0][1] + 0.5), (0.0, TOP[0][1] - 2.0), (0.0, 14.0), (6.0, 12.0), (6.0, 2.0)]\n+BLOCK = dict(x=(-112.0, 112.0), y=(140.0, 328.0))  # the narrower block under the slab's back\n+BASE_VENTS = dict(z=(3.5, 10.5), x=(-180.0, 180.0), pitch=5.0, width=2.2)   # in the base's front, under the front face\n+\n+# The top's bands, s along the slope from its front edge.\n+FRONT_VENTS = dict(s=(90.0, 102.0), x=(-186.0, 186.0), pitch=5.0, width=2.2, depth=2.0)\n+SILVER = dict(s=(110.0, 186.0), x=(-193.5, 141.0), raised=0.6)\n+POWER = dict(x=(149.0, 182.0), s=(112.0, 134.0), height=3.0)\n+LIGHT = dict(x=171.0, s=70.0, r=3.2)\n+SLOT_BAND = dict(s=(186.0, 262.0))\n+SLOT_VENTS = [dict(x=(-186.0, -66.0)), dict(x=(66.0, 186.0))]\n+SLOT_VENT_S = (194.0, 254.0)\n+SLOT = dict(x=(-58.0, 58.0), s=(206.0, 238.0), depth=56.0)\n+LID = dict(s=(262.0, TOP_LENGTH))\n+\n+# The front face: the ports' panel, recessed, and the four ports in it (each its middle's x).\n+PORT_PANEL = dict(x=(-112.0, 98.0), z=(17.0, 41.0), depth=2.0)\n+PORTS = dict(x=[-84.0, -33.0, 19.0, 71.0], z=29.0, size=(33.0, 12.0))\n+\n+# The cartridge: w x d x h, standing in the slot (its bottom DROP below the top), its label on its front.\n+CART = dict(size=(104.0, 21.0, 112.0), label=(84.0, 64.0), ribs=13, seat_depth=50.0)\n+\n+# The controller in its own plan (x across, y back from its front end): a trapezoid, its back end wider; its height at\n+# each end; the stick's well and the stick; the keypad's keys; START, PAUSE and RESET; the fire buttons in its sides.\n+PAD = dict(length=170.0, width=(58.0, 67.0), height=(22.0, 30.0), at=(-218.0, -96.0), turn=-22.0)\n+WELL = dict(y=128.0, r=21.0, depth=4.0)\n+STICK = dict(r=(9.0, 7.0), length=46.0, boot=(15.0, 6.0))      # the shaft's foot and top radii, its length; the boot\n+KEYPAD = dict(y=(16.0, 90.0), x=(-15.5, 15.5), rows=4, cols=3, key=(11.0, 10.0), height=2.5)\n+STRIP = dict(y=(95.0, 104.0))\n+TOP_BUTTONS = dict(y=(157.0, 167.0), x=[(-30.0, -12.0), (-9.0, 9.0), (12.0, 30.0)], height=1.8)   # START, PAUSE, RESET\n+FIRE = dict(y=[(120.0, 138.0), (141.0, 159.0)], z=(10.0, 20.0), out=3.0)   # two on each side\n+\n+COLOURS = dict(case=\"#1E1E20\", silver=\"#8E9094\", black=\"#0D0D0F\", key=\"#262628\", red=\"#D23A2A\", print=\"#FFFFFF\")\n+\n+REGIONS = dict(top=(0, 0, 800, 710), front=(0, 718, 1024, 116), pad=(808, 0, 216, 540),\n+               label=(0, 842, 240, 182))\n+SWATCHES = dict(red=(840, 572), red_lit=(872, 572), black=(904, 572), case=(936, 572), silver=(968, 572),\n+                yellow=(1000, 572))\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s"
        },
        {
          "sha": "c70fd95f3aba67d158dd9ec486f62a93e9aff2d2",
          "filename": "tools/console-models/atari5200_print.py",
          "status": "added",
          "additions": 204,
          "deletions": 0,
          "changes": 204,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari5200_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari5200_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fatari5200_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,204 @@\n+\"\"\"The Atari 5200 set's printing, for the console theme's Atari 5200 model (tools/console-models/atari5200.py).\n+\n+Draws its atlas, its regions as atari5200_layout.py places them, each in millimetres as the model's planar UVs map it:\n+the console's top seen from above along its slope (the brushed band with Atari's logo, ATARI 5200 and its rainbow line,\n+POWER ON/OFF, the light's window, the vents' slots, the lid's seam); the front face (the ports' panel, the four\n+ports and their numbers); the controller's top (the keypad's yellow legends, the ATARI 5200 strip, START, PAUSE and\n+RESET, the stick's well); the cartridge's label (a made-up game, Odyssey, in the style of the 5200's labels); and the\n+swatches small parts take their colour from. Run it with the system Python (Pillow):\n+\n+    python tools/console-models/atari5200_print.py <out folder>\n+\n+It writes atari5200_print.png there.\n+\"\"\"\n+\n+import os\n+import sys\n+\n+from PIL import Image, ImageDraw, ImageFont\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import atari5200_layout as L  # noqa: E402\n+from psp_print import ship, space  # noqa: E402\n+from retro_pc_print import rgb, shade  # noqa: E402\n+from windows_pc_print import FONTS, Region  # noqa: E402\n+\n+CASE = rgb(L.COLOURS[\"case\"])\n+SILVER = rgb(L.COLOURS[\"silver\"])\n+SLOT_INK = (8, 8, 9)\n+RAINBOW = [(226, 64, 40), (240, 140, 36), (232, 210, 60), (90, 180, 70), (50, 120, 200)]\n+ORANGE = (230, 110, 50)\n+YELLOW = (240, 200, 40)\n+\n+\n+def fuji(reg, cx, cy, h, ink):\n+    \"\"\"Atari's logo, h tall, its middle at (cx, cy): the middle bar, and the two outer bars flaring out at their feet.\"\"\"\n+    t = h * 0.15\n+    reg.rect_(cx - t / 2, cy - h / 2, cx + t / 2, cy + h / 2, ink)\n+    for side in (-1, 1):\n+        x0 = cx + side * (t / 2 + h * 0.06)\n+        x1 = x0 + side * t\n+        foot0, foot1 = cx + side * h * 0.34, cx + side * h * 0.56\n+        reg.poly([(x0, cy - h / 2), (x1, cy - h / 2), (x1, cy - h * 0.05), (x1 + side * h * 0.06, cy + h * 0.2),\n+                  (foot1, cy + h / 2), (foot0, cy + h / 2), (x0 + side * h * 0.03, cy + h * 0.22),\n+                  (x0, cy - h * 0.02)], ink)\n+\n+\n+def rainbow_line(reg, x0, x1, y, width):\n+    n = len(RAINBOW)\n+    for k, colour in enumerate(RAINBOW):\n+        reg.rect_(x0 + (x1 - x0) * k / n, y - width / 2, x0 + (x1 - x0) * (k + 1) / n, y + width / 2, colour)\n+\n+\n+def brand(reg, x, y, h, ink, rainbow=True):\n+    \"\"\"Atari's logo, ATARI and 5200, h tall, its left middle at (x, y), the rainbow line under the words.\"\"\"\n+    fuji(reg, x + h * 0.45, y, h, ink)\n+    f = ImageFont.truetype(FONTS + \"arialbi.ttf\", max(1, round(h * 1.0 * reg.sx)))\n+    reg.draw.text(reg.p(x + h * 1.2, y + h * 0.06), \"ATARI\", font=f, fill=ink, anchor=\"lm\")\n+    width = reg.draw.textlength(\"ATARI\", font=f) / reg.sx\n+    reg.draw.text(reg.p(x + h * 1.2 + width + h * 0.8, y + h * 0.06), \"5200\", font=f, fill=ink, anchor=\"lm\")\n+    if rainbow:\n+        rainbow_line(reg, x + h * 1.2, x + h * 1.2 + width + h * 0.8 + width * 0.95, y + h * 0.72, h * 0.12)\n+\n+\n+# ---- The console's top -------------------------------------------------------------------------------------------------\n+\n+def top():\n+    w, d = L.WIDTH, L.TOP_LENGTH\n+    reg = Region(L.REGIONS[\"top\"], w, d, CASE)\n+\n+    def at(x, s):\n+        return x + w / 2, d - s\n+\n+    def vents(x0, x1, s0, s1, pitch, width):\n+        x = x0 + pitch / 2\n+        while x < x1:\n+            reg.rect_(*at(x - width / 2, s1), *at(x + width / 2, s0), SLOT_INK)\n+            x += pitch\n+    v = L.FRONT_VENTS\n+    vents(*v[\"x\"], *v[\"s\"], v[\"pitch\"], v[\"width\"])\n+    for block in L.SLOT_VENTS:\n+        vents(*block[\"x\"], *L.SLOT_VENT_S, v[\"pitch\"], v[\"width\"])\n+    (sx0, sx1), (ss0, ss1) = L.SILVER[\"x\"], L.SILVER[\"s\"]\n+    reg.rect_(*at(sx0, ss1), *at(sx1, ss0), SILVER)\n+    for k in range(90):                                        # the brushed grain along it\n+        s = ss0 + (ss1 - ss0) * (k + 0.5) / 90\n+        reg.line([at(sx0, s), at(sx1, s)], shade(SILVER, 0.05 if k % 3 else -0.05), 0.2)\n+    brand(reg, *at(sx0 + 30.0, ss0 + 26.0), 9.0, (108, 110, 114))\n+    (px0, px1), (ps0, ps1) = L.POWER[\"x\"], L.POWER[\"s\"]\n+    reg.rect_(*at(px0 - 1.0, ps1 + 1.0), *at(px1 + 1.0, ps0 - 1.0), SLOT_INK)\n+    reg.rect_(*at(px0, ps1), *at(px1, ps0), shade(CASE, 0.06))\n+    reg.text(\"POWER ON/OFF\", \"arialbd.ttf\", 2.6, *at((px0 + px1) / 2, ps1 - 4.0), ORANGE)\n+    lx, ls, lr = L.LIGHT[\"x\"], L.LIGHT[\"s\"], L.LIGHT[\"r\"]\n+    reg.circle(*at(lx, ls), lr + 0.6, SLOT_INK)\n+    reg.circle(*at(lx, ls), lr, (60, 16, 14))\n+    (cx0, cx1), (cs0, cs1) = L.SLOT[\"x\"], L.SLOT[\"s\"]\n+    reg.rect_(*at(cx0 - 3.0, cs1 + 3.0), *at(cx1 + 3.0, cs0 - 3.0), shade(CASE, -0.3), r=2.0)\n+    reg.rect_(*at(cx0, cs1), *at(cx1, cs0), SLOT_INK)\n+    ls0 = L.LID[\"s\"][0]\n+    reg.line([at(-w / 2, ls0), at(w / 2, ls0)], SLOT_INK, 0.6)              # the lid's seam\n+    reg.line([at(-w / 2, ls0 + 0.8), at(w / 2, ls0 + 0.8)], shade(CASE, 0.12), 0.3)\n+    return reg\n+\n+\n+# ---- The front face ----------------------------------------------------------------------------------------------------\n+\n+def front():\n+    w, h = L.WIDTH, L.TOP[0][1]\n+    reg = Region(L.REGIONS[\"front\"], w, h, CASE)\n+\n+    def at(x, z):\n+        return x + w / 2, h - z\n+    (px0, px1), (pz0, pz1) = L.PORT_PANEL[\"x\"], L.PORT_PANEL[\"z\"]\n+    reg.rect_(*at(px0, pz1), *at(px1, pz0), shade(CASE, -0.25), r=1.0)\n+    pw, ph = L.PORTS[\"size\"]\n+    for k, x in enumerate(L.PORTS[\"x\"]):\n+        z = L.PORTS[\"z\"]\n+        reg.rect_(*at(x - pw / 2, z + ph / 2), *at(x + pw / 2, z - ph / 2), (12, 12, 13), r=1.2)\n+        reg.rect_(*at(x - pw / 2 + 3.0, z + ph / 2 - 2.5), *at(x + pw / 2 - 3.0, z - ph / 2 + 2.5), (40, 40, 42), r=0.8)\n+        for j in range(8):\n+            reg.circle(*at(x - 10.5 + 3.0 * j, z + 1.4), 0.45, (180, 160, 100))\n+        for j in range(7):\n+            reg.circle(*at(x - 9.0 + 3.0 * j, z - 1.4), 0.45, (180, 160, 100))\n+        reg.text(str(k + 1), \"arialbd.ttf\", 2.6, *at(x - pw / 2 - 3.6, z), shade(CASE, 0.22))\n+    for x in range(-180, 181, 5):                               # the base's vents, under the face\n+        reg.rect_(*at(x - 1.1, L.BASE_VENTS[\"z\"][1]), *at(x + 1.1, L.BASE_VENTS[\"z\"][0]), SLOT_INK)\n+    return reg\n+\n+\n+# ---- The controller's top ----------------------------------------------------------------------------------------------\n+\n+def pad():\n+    length, (w0, w1) = L.PAD[\"length\"], L.PAD[\"width\"]\n+    w = w1\n+    reg = Region(L.REGIONS[\"pad\"], w, length, CASE)\n+\n+    def at(x, y):\n+        return x + w / 2, length - y\n+    (ky0, ky1), (kx0, kx1) = L.KEYPAD[\"y\"], L.KEYPAD[\"x\"]\n+    reg.rect_(*at(kx0 - 5.0, ky1 + 3.0), *at(kx1 + 5.0, ky0 - 3.0), (14, 14, 15), r=2.0)\n+    legends = [\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"*\", \"0\", \"#\"]\n+    kw, kd = L.KEYPAD[\"key\"]\n+    rows, cols = L.KEYPAD[\"rows\"], L.KEYPAD[\"cols\"]\n+    for i in range(rows):\n+        for j in range(cols):\n+            x = kx0 + (kx1 - kx0) * j / (cols - 1)\n+            y = ky1 - kd / 2 - (ky1 - ky0 - kd) * i / (rows - 1)\n+            reg.rect_(*at(x - kw / 2, y + kd / 2), *at(x + kw / 2, y - kd / 2), rgb(L.COLOURS[\"key\"]), r=1.0)\n+            reg.text(legends[i * cols + j], \"arialbd.ttf\", 6.0, *at(x, y), YELLOW)\n+    (sy0, sy1) = L.STRIP[\"y\"]\n+    reg.rect_(*at(-w / 2, sy1), *at(w / 2, sy0), (186, 188, 192))\n+    for k in range(14):\n+        y = sy0 + (sy1 - sy0) * (k + 0.5) / 14\n+        reg.line([at(-w / 2, y), at(w / 2, y)], (200, 202, 206) if k % 2 else (172, 174, 178), 0.25)\n+    brand(reg, *at(-26.0, (sy0 + sy1) / 2 + 0.6), 5.4, (120, 122, 126))\n+    wy, wr = L.WELL[\"y\"], L.WELL[\"r\"]\n+    reg.circle(*at(0.0, wy), wr + 1.0, (10, 10, 11))\n+    reg.circle(*at(0.0, wy), wr - 3.0, (24, 24, 26))\n+    (by0, by1) = L.TOP_BUTTONS[\"y\"]\n+    for (x0, x1), (label, ink) in zip(L.TOP_BUTTONS[\"x\"], ((\"START\", (70, 200, 90)), (\"PAUSE\", YELLOW),\n+                                                          (\"RESET\", (226, 60, 50)))):\n+        reg.rect_(*at(x0 - 0.8, by1 + 0.8), *at(x1 + 0.8, by0 - 0.8), (8, 8, 9))\n+        reg.rect_(*at(x0, by1), *at(x1, by0), (30, 30, 32))\n+        reg.text(label, \"arialbi.ttf\", 2.8, *at((x0 + x1) / 2, (by0 + by1) / 2), ink)\n+    return reg\n+\n+\n+# ---- The cartridge's label -------------------------------------------------------------------------------------------\n+\n+def label():\n+    lw, lh = L.CART[\"label\"]\n+    reg = Region(L.REGIONS[\"label\"], lw, lh, (206, 208, 212))\n+    for k in range(60):                                       # the label's brushed silver\n+        y = lh * (k + 0.5) / 60\n+        reg.line([(0.0, y), (lw, y)], (214, 216, 220) if k % 2 else (198, 200, 204), 0.3)\n+    reg.text(\"ODYSSEY\", \"arialbi.ttf\", 13.0, lw / 2 - 2.0, 10.0, (52, 104, 190))\n+    for k, colour in enumerate(RAINBOW):                      # the rainbow sweeping into the blue band\n+        reg.poly([(0.0, 18.5 + k * 0.9), (lw * 0.5, 20.0 + k * 0.9), (lw * 0.5, 20.9 + k * 0.9), (0.0, 19.4 + k * 0.9)],\n+                 colour)\n+    reg.rect_(0.0, 22.6, lw, 29.0, (52, 104, 190))\n+    reg.text(\"SPACE ADVENTURE\", \"arialbd.ttf\", 3.6, lw / 2, 25.8, (255, 255, 255))\n+    art = space((640, 300)).convert(\"RGB\")\n+    ship(ImageDraw.Draw(art), 220, 150, 3.0, \"#FFFFFF\", \"#FFB23C\")\n+    x0, y0, x1, y1 = reg.box(0.0, 29.0, lw, lh)\n+    reg.img.paste(art.resize((round(x1 - x0), round(y1 - y0)), Image.LANCZOS), (round(x0), round(y0)))\n+    return reg\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), CASE)\n+    for reg in (top(), front(), pad(), label()):\n+        reg.paste(image)\n+    d = ImageDraw.Draw(image)\n+    for name, colour in ((\"red\", rgb(L.COLOURS[\"red\"])), (\"red_lit\", (255, 70, 50)), (\"black\", rgb(L.COLOURS[\"black\"])),\n+                         (\"case\", CASE), (\"silver\", SILVER), (\"yellow\", YELLOW)):\n+        x, y = L.SWATCHES[name]\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colour)\n+    path = os.path.join(out, \"atari5200_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "de237f33765cc137768e6510623f2806e05951c7",
          "filename": "tools/console-models/atari7800.py",
          "status": "added",
          "additions": 277,
          "deletions": 0,
          "changes": 277,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari7800.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari7800.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fatari7800.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,277 @@\n+\"\"\"The console theme's Atari 7800 system model (godot/themes/console/models/systems/atari7800.glb): an Atari 7800\n+ProSystem and its ProLine joystick.\n+\n+Built in Blender from the real console's proportions (atari7800_layout.py has the measurements, taken from\n+photographs). The console is its side outline extruded across its width (the rounded front bumper, the strip of\n+switches sloping down along the top's front edge, the flat top), its edges rounded, on four feet; the cartridge slot\n+and the vents are cut into its top; the four keys stand on the strip. The joystick is its side outline extruded across\n+its width, its top rising to the back, the stick standing on its boot at its back, the orange fire buttons in its\n+sides. The printing is one texture drawn by atari7800_print.py: the console from above and the joystick's top\n+(planar-mapped onto the faces facing up), the cartridge's label, and swatches.\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: the right end towards the viewer, the\n+top tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m) holding the set; the set\n+holding the cartridge (waiting in its slot, shrunk to nothing), POWER, RESET, the lit power light (shrunk to nothing)\n+and the joystick; the joystick holding its stick and its right fire button.\n+\n+The `focused` clip (8 s, looping) plays a cartridge: it drops into the slot, POWER is pressed and its light comes on,\n+RESET starts the game; the stick is swung about and the fire button pressed. Then POWER again, the light goes out, and\n+the cartridge rises out and goes.\n+\n+Four materials (case, key, print, dark) and one texture, within the system model budget. Run from the repo root:\n+\n+    python tools/console-models/atari7800_print.py artifacts/atari7800\n+    blender -b --factory-startup --python tools/console-models/atari7800.py -- artifacts/atari7800 godot/themes/console/models/systems/atari7800.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import atari7800_layout as L  # noqa: E402\n+from amiga import paint, solid  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, ease_out, key, phase, smooth  # noqa: E402\n+from mega_drive_cartridge import bevel, boolean, box, export, join, material, prism_x, triangles  # noqa: E402\n+\n+TILT_YAW = -24.0     # degrees about the vertical: the right end comes towards the viewer\n+TILT_PITCH = 28.0    # degrees about the horizontal: the top tips up towards the viewer\n+ROUGHNESS = dict(case=0.55, key=0.5, print=0.35, dark=0.7)\n+STRIP_FRAME = Matrix.Translation((0.0, *L.STRIP[0])) @ Matrix.Rotation(L.STRIP_SLOPE, 4, \"X\")\n+PRESS = STRIP_FRAME.to_3x3() @ Vector((0.0, 0.0, -1.0))   # a key's travel, in the model's frame\n+PW, PL = L.PAD[\"size\"]\n+\n+\n+# ---- UVs -------------------------------------------------------------------------------------------------------------\n+\n+def atlas_uv(region, w, h):\n+    \"\"\"A face drawn w x h into region: (a, b) millimetres across it (a to the right, b down from its top) to UVs.\"\"\"\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+TOP = atlas_uv(L.REGIONS[\"top\"], L.WIDTH, L.DEPTH)\n+STICK_TOP = atlas_uv(L.REGIONS[\"stick\"], PL, PW)\n+\n+\n+def top_uv(co):\n+    return TOP(co.x + L.WIDTH / 2, L.DEPTH - co.y)\n+\n+\n+def swatch(name):\n+    x, y = L.SWATCHES[name]\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+def painted(obj, m, name):\n+    paint(obj, m[\"print\"], swatch(name), lambda c, n, _name: True)\n+    return obj\n+\n+\n+def ring(r, z, n=24, at=(0.0, 0.0)):\n+    return [(at[0] + r * math.cos(2 * math.pi * k / n), at[1] + r * math.sin(2 * math.pi * k / n), z) for k in range(n)]\n+\n+\n+# ---- The console -----------------------------------------------------------------------------------------------------\n+\n+def console(m):\n+    \"\"\"The console: its outline extruded, the slot and the vents cut into its top, its top printed; its feet and the\n+    two keys that stay.\"\"\"\n+    w = L.WIDTH\n+    shell = prism_x(\"atari7800\", L.PROFILE, -w / 2, w / 2, m[\"case\"])\n+    bevel(shell, 2.0, segments=2)\n+    (sx0, sx1), (sy0, sy1) = L.SLOT[\"x\"], L.SLOT[\"y\"]\n+    boolean(shell, box(\"slot\", sx0, sx1, sy0, sy1, L.TOP_Z - L.SLOT[\"depth\"], L.TOP_Z + 5.0, m[\"dark\"]))\n+    v = L.VENTS\n+    cuts, x = [], v[\"x\"][0] + v[\"pitch\"] / 2\n+    while x < v[\"x\"][1]:\n+        cuts.append(box(\"vent\", x - v[\"width\"] / 2, x + v[\"width\"] / 2, *v[\"y\"], L.TOP_Z - v[\"depth\"], L.TOP_Z + 5.0,\n+                        m[\"dark\"]))\n+        x += v[\"pitch\"]\n+    boolean(shell, join(cuts, \"vents\"))\n+    paint(shell, m[\"print\"], top_uv, lambda c, n, name: n.z > 0.9 and name == \"case\" and c.z > L.STRIP[0][1] - 1.0)\n+    feet = [box(\"foot\", x - 12.0, x + 12.0, y - 10.0, y + 10.0, 0.0, L.FEET + 1.0, m[\"dark\"])\n+            for x in (-110.0, 110.0) for y in (24.0, L.DEPTH - 24.0)]\n+    parts = [shell] + feet\n+    for name in (\"pause\", \"select\"):\n+        part, where = strip_key(name, m)\n+        part.data.transform(Matrix.Translation(where))\n+        parts.append(part)\n+    return parts\n+\n+\n+def strip_key(name, m):\n+    \"\"\"A key on the strip, its foot's middle at its origin, its top printed.\"\"\"\n+    kw, kh = L.KEYS[\"size\"]\n+    x, s = L.KEYS[name], L.KEYS[\"s\"]\n+    corners = [(-kw / 2, -kh / 2), (kw / 2, -kh / 2), (kw / 2, kh / 2), (-kw / 2, kh / 2)]\n+    frame = STRIP_FRAME.to_3x3().to_4x4()\n+    part = solid(name, [[frame @ Vector((a, b, h)) for a, b in corners] for h in (-1.0, L.KEYS[\"height\"])], m[\"key\"])\n+    bevel(part, 0.4, segments=1)\n+    where = STRIP_FRAME @ Vector((x, s, 0.0))\n+    paint(part, m[\"print\"], lambda co: top_uv(co + where), lambda c, n, _name: n.z > 0.9)\n+    return part, where\n+\n+\n+def light(m):\n+    s = L.KEYS[\"s\"]\n+    where = STRIP_FRAME @ Vector((L.LIGHT[\"x\"], s, 0.05))\n+    frame = STRIP_FRAME.to_3x3().to_4x4()\n+    part = solid(\"power_light\", [[frame @ Vector(p) for p in ring(L.LIGHT[\"r\"] + 0.2, h, 14)] for h in (-0.2, 0.3)],\n+                 m[\"print\"])\n+    return painted(part, m, \"red_lit\"), where\n+\n+\n+def cartridge(m):\n+    \"\"\"The cartridge, its bottom's middle at its origin: its shell, its label on its front (-y) near its top.\"\"\"\n+    w, t, h = L.CART[\"size\"]\n+    lw, lh = L.CART[\"label\"]\n+    shell = box(\"cartridge\", -w / 2, w / 2, -t / 2, t / 2, 0.0, h, m[\"dark\"])\n+    bevel(shell, 1.6, segments=2)\n+    z1 = h - 4.0\n+    decal = box(\"label\", -lw / 2, lw / 2, -t / 2 - 0.2, -t / 2 + 0.3, z1 - lh, z1, m[\"print\"])\n+    face = atlas_uv(L.REGIONS[\"label\"], lw, lh)\n+    paint(decal, m[\"print\"], lambda co: face(co.x + lw / 2, z1 - co.z), lambda c, n, name: n.y < -0.9)\n+    paint(decal, m[\"print\"], swatch(\"silver\"), lambda c, n, name: n.y >= -0.9)\n+    return join([shell, decal], \"cartridge\")\n+\n+\n+# ---- The joystick ----------------------------------------------------------------------------------------------------\n+\n+def top_z(y):\n+    \"\"\"The joystick's top at y (back from its middle).\"\"\"\n+    h0, h1 = L.PAD[\"height\"]\n+    return h0 + (h1 - h0) * (y + PL / 2) / PL\n+\n+\n+def joystick(m):\n+    \"\"\"The joystick in its own frame (its plan's middle at the origin, the table): its base, its top printed, the left\n+    fire button; the stick and the right fire button apart.\"\"\"\n+    h0, h1 = L.PAD[\"height\"]\n+    profile = [(-PL / 2, 0.0), (PL / 2, 0.0), (PL / 2, h1 - 3.0), (PL / 2 - 3.0, h1), (-PL / 2 + 3.0, h0),\n+               (-PL / 2, h0 - 3.0)]\n+    base = prism_x(\"joystick\", profile, -PW / 2, PW / 2, m[\"case\"])\n+    bevel(base, 4.0, segments=3)\n+    paint(base, m[\"print\"], lambda co: STICK_TOP(co.y + PL / 2, co.x + PW / 2), lambda c, n, name: n.z > 0.95)\n+    left = fire(-1, m)\n+    right = fire(1, m)\n+    (b0, b1), (r, length), (kr, kh) = L.STICK[\"boot\"], L.STICK[\"shaft\"], L.STICK[\"knob\"]\n+    boot = solid(\"boot\", [ring(b0, -1.0, 28), ring(b0 * 0.85, 3.0, 28), ring(b1, 8.0, 28)], m[\"dark\"])\n+    shaft = solid(\"shaft\", [ring(r, 6.0, 16), ring(r, length, 16)], m[\"key\"])\n+    knob = solid(\"knob\", [ring(kr * 0.8, length - 1.0, 32), ring(kr, length + 1.5, 32), ring(kr, length + kh - 1.5, 32),\n+                          ring(kr * 0.85, length + kh, 32)], m[\"print\"])\n+    painted(knob, m, \"knob\")\n+    stick = join([boot, shaft, knob], \"stick\")\n+    return join([base, left], \"joystick\"), stick, right\n+\n+\n+def fire(side, m):\n+    \"\"\"An orange fire button in the joystick's side (side -1 the left, 1 the right).\"\"\"\n+    (y0, y1), (z0, z1), out = L.FIRE[\"y\"], L.FIRE[\"z\"], L.FIRE[\"out\"]\n+    a, b = (PW / 2 - 1.0, PW / 2 + out) if side > 0 else (-PW / 2 - out, -PW / 2 + 1.0)\n+    part = painted(box(\"fire\", a, b, y0, y1, z0, z1, m[\"print\"]), m, \"orange\")\n+    bevel(part, 0.8, segments=1)\n+    return part\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+DROP = 96.0          # how far over its seat the cartridge appears\n+\n+\n+def appear(f, f0, f1):\n+    return HIDDEN + (1 - HIDDEN) * back_out(phase(f, f0, f1)) if f >= f0 else HIDDEN\n+\n+\n+def tap(f, f0, hold=2):\n+    \"\"\"A press at f0: down in three frames, held, back up in four.\"\"\"\n+    return smooth(phase(f, f0, f0 + 3)) * (1 - smooth(phase(f, f0 + 3 + hold, f0 + 7 + hold)))\n+\n+\n+def timeline(f):\n+    if f < 8 or f >= 226:\n+        cart = Pose(scale=HIDDEN)\n+    elif f < 18:\n+        cart = Pose(z=DROP, scale=appear(f, 8, 16))\n+    elif f < 30:\n+        cart = Pose(z=DROP * (1 - ease_in(phase(f, 18, 30))))\n+    elif f < 206:\n+        cart = Pose(squash=0.05 * math.sin(math.pi * phase(f, 30, 36)))\n+    else:\n+        cart = Pose(z=DROP * ease_out(phase(f, 206, 218)), scale=max(HIDDEN, 1 - ease_in(phase(f, 214, 226))))\n+    on = max(tap(f, 38), tap(f, 198))\n+    reset = tap(f, 56)\n+    t = phase(f, 70, 190)\n+    k = math.sin(math.pi * t)\n+    return {\"cartridge\": cart,\n+            \"power\": Pose(x=PRESS.x * 1.0 * on, y=PRESS.y * 1.0 * on, z=PRESS.z * 1.0 * on),\n+            \"reset\": Pose(x=PRESS.x * 1.0 * reset, y=PRESS.y * 1.0 * reset, z=PRESS.z * 1.0 * reset),\n+            \"power_light\": Pose() if 42 <= f < 202 else Pose(scale=HIDDEN),\n+            \"stick\": Pose(pitch=20.0 * k * math.sin(2 * math.pi * 3.0 * t), roll=20.0 * k * math.cos(2 * math.pi * 2.0 * t)),\n+            \"fire\": Pose(x=-2.4 * max(tap(f, f0) for f0 in (88, 106, 124, 142, 160, 176)))}\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    printed = bpy.data.images.load(os.path.join(images, \"atari7800_print.png\"))\n+    colours = dict(L.COLOURS, dark=\"#121214\")\n+    m = {n: material(n, colours[n], r, printed if n == \"print\" else None) for n, r in ROUGHNESS.items()}\n+\n+    whole = join(console(m), \"atari7800\")\n+    power, power_at = strip_key(\"power\", m)\n+    reset, reset_at = strip_key(\"reset\", m)\n+    lit, lit_at = light(m)\n+    cart = cartridge(m)\n+    base, stick, button = joystick(m)\n+    for obj in [whole, power, reset, cart, base, stick, button]:\n+        for p in obj.data.polygons:\n+            p.use_smooth = True\n+        obj.data.set_sharp_from_angle(angle=math.radians(35))\n+\n+    # The node tree: the root (the view and the fit) holds the set; the set holds the moving parts at their places.\n+    root = bpy.data.objects.new(\"atari7800_root\", None)\n+    scene.collection.objects.link(root)\n+    whole.parent = root\n+    (sy0, sy1) = L.SLOT[\"y\"]\n+    seat = Vector((0.0, (sy0 + sy1) / 2, L.TOP_Z - L.CART[\"seat_depth\"]))\n+    for part, where in ((cart, seat), (power, power_at), (reset, reset_at), (lit, lit_at)):\n+        part.parent = whole\n+        part.location = where\n+    (px, py), turn = L.PAD[\"at\"], math.radians(L.PAD[\"turn\"])\n+    base.parent = whole\n+    base.location = (px, py, 0.0)\n+    base.rotation_euler = (0.0, 0.0, turn)\n+    stick.parent = base\n+    stick.location = (0.0, L.STICK[\"y\"], top_z(L.STICK[\"y\"]))\n+    button.parent = base\n+    nodes = {\"cartridge\": cart, \"power\": power, \"reset\": reset, \"power_light\": lit, \"stick\": stick, \"fire\": button}\n+\n+    # The three-quarter view, then the spec's fit of the set at rest, as the root's transform.\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    rest = Matrix.Translation((px, py, 0.0)) @ Matrix.Rotation(turn, 4, \"Z\")\n+    points = [tilt @ v.co for v in whole.data.vertices] + [tilt @ rest @ v.co for v in base.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [whole, power, reset, lit, cart, base, stick, button]\n+    print(f\"Atari 7800: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(whole.data)} the set), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "76599b4205f2e50a44875b4ccacc29d990be9066",
          "filename": "tools/console-models/atari7800_layout.py",
          "status": "added",
          "additions": 61,
          "deletions": 0,
          "changes": 61,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari7800_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari7800_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fatari7800_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,61 @@\n+\"\"\"The Atari 7800 set's measurements, shared by atari7800_print.py (which draws its printing) and atari7800.py (which\n+builds it). Plain Python: Blender's Python imports it too.\n+\n+The Atari 7800 ProSystem (1986), the US model, 279 x 216 mm (the Computer History Museum's 11 x 8 1/2 inches), and its\n+ProLine joystick. Millimetres: x from its middle (left to right), y back from its front face, z up from the table. The\n+rest was measured from photographs on Wikimedia Commons (Evan-Amos's views of the console from the front left and right\n+and with its joystick, the joystick from the front left; a 7800 with a cartridge in it), scaled by its width.\n+\n+The console: its side outline (PROFILE) extruded across its width, its edges rounded: the rounded front bumper, the\n+black strip of switches sloping down along the top's front edge (POWER and its light, PAUSE, SELECT and RESET, each a\n+key on the strip), the flat top; the brushed silver band across the top with ATARI 7800 PROSYSTEM and the rainbow line\n+crossing it, the cartridge slot behind the band, the vents along the back. The joystick: a long narrow base, its top\n+rising to the back, the silver plate on its front with ATARI and the rainbow, the stick on a black boot at its back,\n+the orange fire buttons on both sides.\n+\n+One texture (ATLAS square, REGIONS): `top` (the console from above: the band, its logo and line, the strip and its\n+names, the vents' slots), `stick` (the joystick's plate: ATARI and its rainbow), `label` (the cartridge's: a made-up\n+game, Odyssey), and swatches. A region is (x, y, width, height) in pixels.\n+\"\"\"\n+\n+import math\n+\n+ATLAS = 1024\n+\n+WIDTH, DEPTH = 279.0, 216.0\n+FEET = 4.0\n+TOP_Z = 64.0                                       # the flat top\n+# The side outline, (y, z), anticlockwise seen from the right: the bottom on its feet, the back, the flat top, the\n+# strip sloping down to the bumper, the bumper rounding to the bottom.\n+STRIP = ((14.0, 59.0), (36.0, TOP_Z))              # the strip's front and back edges, (y, z)\n+PROFILE = [(8.0, FEET), (DEPTH - 6.0, FEET), (DEPTH, 10.0), (DEPTH, 60.0), (DEPTH - 4.0, TOP_Z), STRIP[1], STRIP[0],\n+           (6.0, 55.5), (1.5, 47.0), (0.0, 35.0), (1.5, 22.0), (5.0, 12.0)]\n+STRIP_SLOPE = math.atan2(STRIP[1][1] - STRIP[0][1], STRIP[1][0] - STRIP[0][0])\n+STRIP_LENGTH = math.hypot(STRIP[1][1] - STRIP[0][1], STRIP[1][0] - STRIP[0][0])\n+\n+KEYS = dict(power=-74.0, pause=-38.0, select=50.0, reset=88.0, s=11.3, size=(24.0, 8.0), height=1.2)   # middles' x\n+LIGHT = dict(x=-56.0, r=1.4)\n+BAND = dict(y=(96.0, 144.0))                       # the silver band across the top, back from the front face\n+LOGO = dict(x=-112.0, y=(106.0, 116.0))            # ATARI over 7800 PROSYSTEM: its left edge, their middles' y\n+RAINBOW = dict(y=(101.0, 138.0), width=0.9)        # the line across the band, from its left end to its right\n+SLOT = dict(x=(-44.0, 44.0), y=(150.0, 176.0), depth=50.0)\n+VENTS = dict(x=(-128.0, 128.0), y=(190.0, 206.0), pitch=7.0, width=3.0, depth=3.0)\n+\n+# The cartridge: w x thickness x h, standing in the slot (its bottom seat_depth below the top), its label on its front.\n+CART = dict(size=(80.0, 22.0, 104.0), label=(66.0, 22.0), seat_depth=44.0)\n+\n+# The joystick, in its own plan (x across, y back from its front end, its middle at the origin): its base, the plate,\n+# the stick's boot and shaft and knob, the fire buttons.\n+PAD = dict(at=(-196.0, -30.0), turn=22.0, size=(50.0, 156.0), height=(16.0, 30.0))\n+PLATE = dict(y=(-74.0, 12.0), inset=5.0)\n+STICK = dict(y=48.0, boot=(14.0, 8.0), shaft=(3.6, 52.0), knob=(10.5, 16.0))\n+FIRE = dict(y=(30.0, 56.0), z=(8.0, 22.0), out=4.0)\n+\n+COLOURS = dict(case=\"#2A2B2E\", key=\"#121214\", print=\"#FFFFFF\")\n+\n+REGIONS = dict(top=(0, 0, 1024, 793), stick=(0, 801, 520, 167), label=(528, 801, 300, 100))   # the stick's drawn\n+SWATCHES = dict(red=(856, 820), red_lit=(888, 820), dark=(920, 820), silver=(952, 820), orange=(984, 820),   # sideways\n+                knob=(856, 852))\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s"
        },
        {
          "sha": "be2ee06317e57669ff28f4ef8a11bc77967c8358",
          "filename": "tools/console-models/atari7800_print.py",
          "status": "added",
          "additions": 146,
          "deletions": 0,
          "changes": 146,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari7800_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari7800_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fatari7800_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,146 @@\n+\"\"\"The Atari 7800 set's printing, for the console theme's Atari 7800 model (tools/console-models/atari7800.py).\n+\n+Draws its atlas, its regions as atari7800_layout.py places them, each in millimetres as the model's planar UVs map it:\n+the console from above (the black strip with POWER, PAUSE, SELECT and RESET and the power light's window, the brushed\n+silver band with Atari's logo, ATARI and 7800 PROSYSTEM and the rainbow line crossing it, the vents' slots); the\n+joystick's top, drawn sideways, its front to the left (the silver plate with ATARI and the rainbow); the cartridge's\n+label (a made-up game, Odyssey, in the style of the 7800's labels); and the swatches small parts take their colour\n+from. Run it with the system Python (Pillow):\n+\n+    python tools/console-models/atari7800_print.py <out folder>\n+\n+It writes atari7800_print.png there.\n+\"\"\"\n+\n+import os\n+import sys\n+\n+from PIL import Image, ImageDraw, ImageFont\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import atari7800_layout as L  # noqa: E402\n+from atari5200_print import fuji  # noqa: E402\n+from retro_pc_print import rgb, shade  # noqa: E402\n+from windows_pc_print import FONTS, Region  # noqa: E402\n+\n+CASE = rgb(L.COLOURS[\"case\"])\n+SILVER = (204, 205, 208)\n+EMBOSS = (168, 169, 173)\n+STRIP = (14, 14, 16)\n+INK = (220, 220, 224)\n+LINE = [(240, 200, 60), (238, 130, 50), (222, 64, 60), (176, 60, 150), (86, 80, 190), (60, 130, 200), (70, 170, 110)]\n+\n+\n+def blend(stops, t):\n+    \"\"\"A colour t (0 to 1) along a list of colour stops.\"\"\"\n+    t = min(max(t, 0.0), 1.0) * (len(stops) - 1)\n+    k = min(int(t), len(stops) - 2)\n+    f = t - k\n+    return tuple(round(a + (b - a) * f) for a, b in zip(stops[k], stops[k + 1]))\n+\n+\n+def brushed(reg, x0, y0, x1, y1, base, lines=80):\n+    \"\"\"A brushed-metal rectangle: fine lines along it.\"\"\"\n+    reg.rect_(x0, y0, x1, y1, base)\n+    for k in range(lines):\n+        y = y0 + (y1 - y0) * (k + 0.5) / lines\n+        reg.line([(x0, y), (x1, y)], shade(base, 0.04 if k % 3 else -0.05), 0.2)\n+\n+\n+# ---- The console -----------------------------------------------------------------------------------------------------\n+\n+def top():\n+    w, d = L.WIDTH, L.DEPTH\n+    reg = Region(L.REGIONS[\"top\"], w, d, CASE)\n+\n+    def at(x, y):\n+        return x + w / 2, d - y\n+    (sy0, _), (sy1, _) = L.STRIP\n+    reg.rect_(*at(-w / 2, sy1), *at(w / 2, sy0), STRIP)\n+    reg.line([at(-w / 2, sy1), at(w / 2, sy1)], shade(CASE, 0.15), 0.4)\n+    kw, kh = L.KEYS[\"size\"]\n+    ky = sy0 + L.KEYS[\"s\"] * (sy1 - sy0) / L.STRIP_LENGTH\n+    for name in (\"power\", \"pause\", \"select\", \"reset\"):\n+        x = L.KEYS[name]\n+        reg.rect_(*at(x - kw / 2, ky + kh / 2), *at(x + kw / 2, ky - kh / 2), (24, 24, 27))\n+        reg.text(name.upper(), \"arialbd.ttf\", 2.8, *at(x, ky), INK)\n+    reg.circle(*at(L.LIGHT[\"x\"], ky), L.LIGHT[\"r\"] + 0.5, (4, 4, 5))\n+    reg.circle(*at(L.LIGHT[\"x\"], ky), L.LIGHT[\"r\"], (70, 16, 14))\n+\n+    (by0, by1) = L.BAND[\"y\"]\n+    (ax, ay), (bx, byy) = at(-w / 2, by1), at(w / 2, by0)\n+    brushed(reg, ax, ay, bx, byy, SILVER)\n+    lx, (ly0, ly1) = L.LOGO[\"x\"], L.LOGO[\"y\"]\n+    fuji(reg, *at(lx + 4.0, ly1), 8.0, EMBOSS)\n+    reg.text(\"ATARI\", \"arialbd.ttf\", 9.0, *at(lx + 10.0, ly1), EMBOSS, anchor=\"lm\")\n+    reg.text(\"7800 PROSYSTEM\", \"arial.ttf\", 7.4, *at(lx, ly0), EMBOSS, anchor=\"lm\")\n+    (ry0, ry1), rw = L.RAINBOW[\"y\"], L.RAINBOW[\"width\"]\n+    n = 120\n+    for k in range(n):                                        # the line, its colours running along it\n+        t0, t1 = k / n, (k + 1) / n\n+        x0, x1 = -w / 2 + w * t0, -w / 2 + w * t1\n+        y0, y1 = ry0 + (ry1 - ry0) * t0, ry0 + (ry1 - ry0) * t1\n+        reg.line([at(x0, y0), at(x1, y1)], blend(LINE, (t0 + t1) / 2), rw)\n+\n+    v = L.VENTS\n+    x = v[\"x\"][0] + v[\"pitch\"] / 2\n+    while x < v[\"x\"][1]:\n+        reg.rect_(*at(x - v[\"width\"] / 2, v[\"y\"][1]), *at(x + v[\"width\"] / 2, v[\"y\"][0]), (6, 6, 7), r=1.2)\n+        x += v[\"pitch\"]\n+    return reg\n+\n+\n+# ---- The joystick, the label -----------------------------------------------------------------------------------------\n+\n+def stick():\n+    \"\"\"The joystick's top seen from above, drawn turned a quarter: along its length to the right (its front at the\n+    left), across it downwards (its left side at the top).\"\"\"\n+    pw, pl = L.PAD[\"size\"]\n+    reg = Region(L.REGIONS[\"stick\"], pl, pw, CASE)\n+\n+    def at(x, y):\n+        return y + pl / 2, x + pw / 2\n+\n+    def box(x0, y0, x1, y1):\n+        \"\"\"A rectangle of the joystick's plan as the region's corners, top left first.\"\"\"\n+        (a, b), (c, e) = at(x0, y0), at(x1, y1)\n+        return min(a, c), min(b, e), max(a, c), max(b, e)\n+    (py0, py1), i = L.PLATE[\"y\"], L.PLATE[\"inset\"]\n+    brushed(reg, *box(-pw / 2 + i, py0, pw / 2 - i, py1), SILVER, 60)\n+    mid = (py0 + py1) / 2\n+    for k in range(5):                                        # the rainbow, short bars along the plate\n+        reg.rect_(*box(-pw / 2 + i + 3.0 + 2.6 * k, mid - 28.0, -pw / 2 + i + 4.8 + 2.6 * k, mid - 10.0),\n+                  blend(LINE, k / 4))\n+    layer = reg.layer()                                       # ATARI, reading along the plate\n+    f = ImageFont.truetype(FONTS + \"arialbd.ttf\", max(1, round(8.0 * reg.sx)))\n+    ImageDraw.Draw(layer).text(reg.p(*at(2.0, mid + 4.0)), \"ATARI\", font=f, fill=EMBOSS + (255,), anchor=\"mm\")\n+    reg.over(layer)\n+    return reg\n+\n+\n+def label():\n+    lw, lh = L.CART[\"label\"]\n+    reg = Region(L.REGIONS[\"label\"], lw, lh, (232, 232, 236))\n+    brushed(reg, 0.0, 0.0, lw, lh, (214, 215, 220), 30)\n+    reg.text(\"ODYSSEY\", \"arialbd.ttf\", 6.0, lw / 2, 6.6, (200, 40, 50))\n+    reg.text(\"ATARI 7800  ProSystem\", \"arialbd.ttf\", 3.2, lw / 2, 14.0, (40, 40, 48))\n+    return reg\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), CASE)\n+    for reg in (top(), stick(), label()):\n+        reg.paste(image)\n+    d = ImageDraw.Draw(image)\n+    for name, colour in ((\"red\", (150, 20, 16)), (\"red_lit\", (255, 50, 40)), (\"dark\", STRIP), (\"silver\", SILVER),\n+                         (\"orange\", (232, 92, 30)), (\"knob\", (22, 22, 24))):\n+        x, y = L.SWATCHES[name]\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colour)\n+    path = os.path.join(out, \"atari7800_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "f7426609c9bb7677ef1c197ad45f658d52bc07fe",
          "filename": "tools/console-models/atari800.py",
          "status": "added",
          "additions": 369,
          "deletions": 0,
          "changes": 369,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari800.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari800.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fatari800.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,369 @@\n+\"\"\"The console theme's Atari 800 system model (godot/themes/console/models/systems/atari800.glb): the Atari 800 and its\n+CX40 joystick.\n+\n+Built in Blender (atari800_layout.py has the measurements, taken from photographs). The shell is its side outline\n+extruded across its width, its top's ends chamfered down to the sides; the lid is the shell's piece over the bay,\n+hollowed, a hair smaller than the hole it leaves; the bay is cut under it, the slots into the bay's floor, and the\n+vents' recess into the top behind it, its fins standing in it. The shell stands on the ports' block and two feet. The\n+keys stand on the deck in its frame, each tapering to its printed top, the function keys in their column at the right,\n+the power light under them. The CX40 is atari_2600.py's, its cord running from its strain relief to its 9-pin plug in\n+port 1 (plugs.py's). The printing is one texture (atari800_print.py's), projected from above onto the deck and the keys,\n+and from in front onto the lid's face, the ports and the bay's back wall.\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: the right end towards the viewer, the\n+top tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m) holding the set; the set\n+holding the lid (on its hinge along its back edge), the cartridge (waiting over the left slot, shrunk to nothing),\n+OPTION and START, and the joystick's shaft and button.\n+\n+The `focused` clip (8 s, looping): the lid swings up, the cartridge drops into the left slot and the lid shuts; OPTION\n+and START; the joystick's shaft waggles and its button fires; then the lid swings up again, the cartridge rises out and\n+goes, and the lid shuts.\n+\n+Four materials (case, keys, joystick, cart) and one 1024\u00b2 texture, within the system model budget. Run from the repo\n+root:\n+\n+    python tools/console-models/atari800_print.py artifacts/atari800\n+    blender -b --factory-startup --python tools/console-models/atari800.py -- artifacts/atari800 godot/themes/console/models/systems/atari800.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bmesh\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import atari800_layout as L  # noqa: E402\n+import plugs  # noqa: E402\n+import atari_2600  # noqa: E402\n+import atari_2600_layout as AL  # noqa: E402\n+from amiga import paint, solid  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, ease_out, key, phase, smooth  # noqa: E402\n+from mega_drive import prism_y  # noqa: E402\n+from mega_drive_cartridge import (bevel, boolean, box, export, join, material, new_object, prism_x,  # noqa: E402\n+                                  triangles)\n+\n+TILT_YAW = -24.0     # degrees about the vertical: the right end comes towards the viewer\n+TILT_PITCH = 24.0    # degrees about the horizontal: the top tips up towards the viewer\n+ROUGHNESS = dict(case=0.5, keys=0.4, cart=0.45)\n+MOVING = (\"option\", \"start\")\n+\n+\n+def atlas_uv(region, w, h):\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+def swatch(name):\n+    x, y = L.SWATCHES[name]\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+def colour(obj, mat, name):\n+    paint(obj, mat, swatch(name), lambda c, n, kind: True)\n+    return obj\n+\n+\n+DECK = atlas_uv(L.REGIONS[\"deck\"], L.WIDTH, L.DECK_BACK[0])\n+BADGE = atlas_uv(L.REGIONS[\"badge\"], L.LID[\"x\"][1] - L.LID[\"x\"][0], L.HEIGHT - L.DECK_BACK[1])\n+PORTS = atlas_uv(L.REGIONS[\"ports\"], L.PORTS[\"block\"][1] - L.PORTS[\"block\"][0], L.BASE)\n+PLATE = atlas_uv(L.REGIONS[\"plate\"], L.BAY[\"x\"][1] - L.BAY[\"x\"][0], L.DECK_BACK[1] - L.BAY[\"floor\"])\n+\n+\n+def deck_uv(co):\n+    return DECK(co.x + L.WIDTH / 2, L.DECK_BACK[0] - co.y)\n+\n+\n+def on_deck(name, outline, h0, h1, mat, outline1=None):\n+    \"\"\"A solid from an (x, s) outline in the deck's frame, from h0 off it to h1 (its top outline1, if given).\"\"\"\n+    top = outline1 or outline\n+    return solid(name, [[(x, *L.deck_point(s, h0)) for x, s in outline], [(x, *L.deck_point(s, h1)) for x, s in top]],\n+                 mat)\n+\n+\n+def duplicate(obj, name):\n+    copy = obj.copy()\n+    copy.data = obj.data.copy()\n+    copy.name = copy.data.name = name\n+    bpy.context.scene.collection.objects.link(copy)\n+    return copy\n+\n+\n+# ---- The computer ----------------------------------------------------------------------------------------------------\n+\n+def shell(m):\n+    \"\"\"The side outline across the width, the top's ends chamfered down to the sides.\"\"\"\n+    half = L.WIDTH / 2\n+    body = prism_x(\"atari800\", L.PROFILE, -half, half, m[\"case\"])\n+    cx, cz = L.CHEEK[\"x\"], L.CHEEK[\"z\"]\n+    fall = (L.HEIGHT - cz) / (half - cx)            # the chamfer's drop per millimetre across\n+    side, inner = cz - 10.0 * fall, L.HEIGHT + 20.0 * fall\n+    ends = [(-half - 10.0, -5.0), (half + 10.0, -5.0), (half + 10.0, side), (cx - 20.0, inner), (-cx + 20.0, inner),\n+            (-half - 10.0, side)]\n+    boolean(body, prism_y(\"ends\", ends, -10.0, L.DEPTH + 10.0, m[\"case\"]), \"INTERSECT\")\n+    bevel(body, 2.5, segments=2, angle=40)\n+    return body\n+\n+\n+def fins(m, outline):\n+    \"\"\"The vents' fins across their width, each the outline (y, z) extruded across its thickness, joined.\"\"\"\n+    (x0, x1), count, fin = L.VENTS[\"x\"], L.VENTS[\"count\"], L.VENTS[\"fin\"]\n+    pitch = (x1 - x0) / count\n+    parts = [prism_x(\"fin\", outline, x0 + k * pitch + (pitch - fin) / 2, x0 + k * pitch + (pitch + fin) / 2, m[\"case\"])\n+             for k in range(count)]\n+    return colour(join(parts, \"fins\"), m[\"case\"], \"case\")\n+\n+\n+def recessed(name, obj, m, x, outline):\n+    \"\"\"Cuts the vents' recess (the outline's floor, up through the top) into obj and paints its floor darker; returns\n+    the fins to stand in it.\"\"\"\n+    (x0, x1), depth = x, L.VENTS[\"depth\"]\n+    ya, yb = min(y for y, z in outline), max(y for y, z in outline)\n+    floor = outline[:len(outline) // 2]\n+    boolean(obj, prism_x(name, floor + [(floor[-1][0], L.HEIGHT + 20.0), (floor[0][0], L.HEIGHT + 20.0)], x0, x1,\n+                         m[\"case\"]))\n+    paint(obj, m[\"case\"], swatch(\"vent\"),\n+          lambda c, n, kind: n.z > 0.3 and x0 - 0.5 < c.x < x1 + 0.5 and ya - 0.5 < c.y < yb + 0.5\n+          and L.top_z(c.y) - depth - 0.5 < c.z < L.top_z(c.y) - 1.0)\n+    return fins(m, outline)\n+\n+\n+def lid(body, m):\n+    \"\"\"The lid: the shell's piece over the bay, hollowed (its face and sides LID[\"wall\"] thick, its top LID[\"top\"]), the\n+    vents' recess cut into its top and their fins in it, its origin on its hinge along its back edge; its face\n+    printed.\"\"\"\n+    (x0, x1), (y0, y1), t, gap = L.LID[\"x\"], L.LID[\"y\"], L.LID[\"wall\"], L.LID[\"gap\"]\n+    part = duplicate(body, \"lid\")\n+    boolean(part, box(\"lid_box\", x0 + gap, x1 - gap, y0, y1 - gap, L.DECK_BACK[1] + gap, L.HEIGHT + 20.0, m[\"case\"]),\n+            \"INTERSECT\")\n+    low, high = L.DECK_BACK[1] - 10.0, L.HEIGHT - L.LID[\"top\"]\n+    hollow = [(L.face_y(low, t), low), (y1 + 5.0, low), (y1 + 5.0, high), (L.face_y(high, t), high)]\n+    boolean(part, prism_x(\"hollow\", hollow, x0 + gap + t, x1 - gap - t, m[\"case\"]))\n+    colour(part, m[\"case\"], \"case\")\n+    paint(part, m[\"case\"], lambda co: BADGE(co.x - x0, L.HEIGHT - co.z), lambda c, n, kind: n.y < -0.8 and n.z > 0.2)\n+    (vy0, vy1), depth = L.VENTS[\"lid\"], L.VENTS[\"depth\"]\n+    outline = [(vy0, L.HEIGHT - depth), (vy1, L.HEIGHT - depth), (vy1, L.HEIGHT), (vy0, L.HEIGHT)]\n+    part = join([part, recessed(\"lid_recess\", part, m, L.VENTS[\"x\"], outline)], \"lid\")\n+    hinge = Vector((0.0, y1 - gap, L.HEIGHT))\n+    part.data.transform(Matrix.Translation(-hinge))\n+    return part, hinge\n+\n+\n+def vents(body, m):\n+    \"\"\"The vents behind the lid: their recess cut into the top and its fall, and the fins standing in it, their tops\n+    flush with the top.\"\"\"\n+    (y0, y1), depth = L.VENTS[\"y\"], L.VENTS[\"depth\"]\n+    fy = L.PROFILE[7][0]                                     # where the fall starts\n+    outline = [(y0, L.top_z(y0) - depth), (fy, L.top_z(fy) - depth), (y1, L.top_z(y1) - depth),\n+               (y1, L.top_z(y1)), (fy, L.top_z(fy)), (y0, L.top_z(y0))]\n+    return recessed(\"recess\", body, m, L.VENTS[\"x\"], outline)\n+\n+\n+def computer(m):\n+    \"\"\"The shell with the lid's hole, the bay, the slots and the vents' recess cut in; the ports' block and the feet\n+    under it; printed. Returns it and the lid.\"\"\"\n+    body = shell(m)\n+    flap = lid(body, m)\n+    (x0, x1), (y0, y1) = L.LID[\"x\"], L.LID[\"y\"]\n+    boolean(body, box(\"lid_hole\", x0, x1, y0, y1, L.DECK_BACK[1], L.HEIGHT + 20.0, m[\"case\"]))\n+    (bx0, bx1), (by0, by1), floor = L.BAY[\"x\"], L.BAY[\"y\"], L.BAY[\"floor\"]\n+    boolean(body, box(\"bay\", bx0, bx1, by0, by1, floor, L.HEIGHT, m[\"case\"]))\n+    (sy0, sy1) = L.SLOTS[\"y\"]\n+    for name in (\"left\", \"right\"):\n+        sx0, sx1 = L.SLOTS[name]\n+        boolean(body, box(\"slot\", sx0, sx1, sy0, sy1, L.SLOTS[\"floor\"], floor + 1.0, m[\"case\"]))\n+\n+    colour(body, m[\"case\"], \"case\")\n+    paint(body, m[\"case\"], deck_uv, lambda c, n, kind: n.z > 0.3 and c.y < L.DECK_BACK[0] - 0.3)\n+    paint(body, m[\"case\"], swatch(\"metal\"),\n+          lambda c, n, kind: bx0 - 0.5 < c.x < bx1 + 0.5 and by0 - 0.5 < c.y < by1 + 0.5\n+          and floor - 0.5 < c.z < L.DECK_BACK[1])\n+    paint(body, m[\"case\"], lambda co: PLATE(co.x - bx0, L.DECK_BACK[1] - co.z),\n+          lambda c, n, kind: n.y < -0.9 and abs(c.y - by1) < 0.6 and floor < c.z < L.DECK_BACK[1] and bx0 < c.x < bx1)\n+    paint(body, m[\"case\"], swatch(\"dark\"),\n+          lambda c, n, kind: c.z < floor - 0.5 and sy0 - 0.5 < c.y < sy1 + 0.5\n+          and any(a - 0.5 < c.x < b + 0.5 for a, b in (L.SLOTS[\"left\"], L.SLOTS[\"right\"])))\n+    grille = vents(body, m)\n+\n+    (px0, px1), (py0, py1) = L.PORTS[\"block\"], L.PORTS[\"y\"]\n+    lean = (py1 - py0) / L.BASE\n+    block = prism_x(\"ports\", [(py1, 0.0), (150.0, 0.0), (150.0, L.BASE + 4.0), (py0 - 4.0 * lean, L.BASE + 4.0)],\n+                    px0, px1, m[\"case\"])\n+    colour(block, m[\"case\"], \"case\")\n+    paint(block, m[\"case\"], lambda co: PORTS(co.x - px0, L.BASE - co.z), lambda c, n, kind: n.y < -0.8)\n+    fx, (fy0, fy1), fs = L.FEET[\"x\"], L.FEET[\"y\"], L.FEET[\"size\"]\n+    feet = [colour(box(\"foot\", side * fx - fs / 2, side * fx + fs / 2, fy0, fy1, 0.0, L.BASE + 2.0, m[\"case\"]),\n+                   m[\"case\"], \"dark\") for side in (-1, 1)]\n+    return join([body, grille, block] + feet, \"atari800\"), flap\n+\n+\n+def keys(m):\n+    \"\"\"The keys: the main block, the function keys and the power light, each tapering to its printed top; OPTION and\n+    START apart (each its foot's middle at its origin, with where it goes), the rest joined.\"\"\"\n+    fixed, moving = [], {}\n+    inset = L.KEY[\"inset\"]\n+\n+    def make(name, x0, x1, s, half, tone, top=L.KEY[\"height\"]):\n+        foot = [(x0 + 0.6, s - half), (x1 - 0.6, s - half), (x1 - 0.6, s + half), (x0 + 0.6, s + half)]\n+        cap = [(x0 + inset, s - half - 0.5 + inset), (x1 - inset, s - half - 0.5 + inset),\n+               (x1 - inset, s + half + 0.5 - inset), (x0 + inset, s + half + 0.5 - inset)]\n+        part = on_deck(name, foot, -1.0, top, m[\"keys\"], cap)\n+        colour(part, m[\"keys\"], tone)\n+        paint(part, m[\"keys\"], deck_uv, lambda c, n, kind: n.z > 0.85)\n+        return part\n+\n+    for legend, x0, x1, s in L.keys():\n+        fixed.append(make(\"key\", x0, x1, s, 9.0, \"key\"))\n+    fx0, fx1 = L.FUNCTION[\"x\"]\n+    for legend, s, name in L.FUNCTION[\"keys\"]:\n+        part = make(name, fx0, fx1, s, 8.5, name)\n+        if name in MOVING:\n+            y, z = L.deck_point(s, -1.0)\n+            middle = Vector(((fx0 + fx1) / 2, y, z))\n+            part.data.transform(Matrix.Translation(-middle))\n+            moving[name] = (part, middle)\n+        else:\n+            fixed.append(part)\n+    fixed.append(make(\"light\", fx0, fx1, L.LIGHT[\"s\"], L.LIGHT[\"half\"], \"light\", L.LIGHT[\"height\"]))\n+    return join(fixed, \"keys\"), moving\n+\n+\n+def cartridge(m):\n+    \"\"\"The cartridge, its foot's middle at its origin, its label facing -y.\"\"\"\n+    w, t, h = L.CART[\"size\"]\n+    part = box(\"cartridge\", -w / 2, w / 2, -t / 2, t / 2, 0.0, h, m[\"cart\"])\n+    bevel(part, 1.6, segments=2)\n+    colour(part, m[\"cart\"], \"cart\")\n+    face = atlas_uv(L.REGIONS[\"cart\"], w, h)\n+    paint(part, m[\"cart\"], lambda co: face(co.x + w / 2, h - co.z), lambda c, n, kind: n.y < -0.9)\n+    return part\n+\n+\n+def cord(m, points):\n+    \"\"\"The joystick's cord: a tube along the B\u00e9zier curve through four control points.\"\"\"\n+    p0, p1, p2, p3 = (Vector(p) for p in points)\n+    path = [p0 * (1 - t) ** 3 + p1 * 3 * t * (1 - t) ** 2 + p2 * 3 * t * t * (1 - t) + p3 * t ** 3\n+            for t in [k / 24 for k in range(25)]]\n+    bm = bmesh.new()\n+    rings, normal = [], Vector((0.0, 0.0, 1.0))\n+    for k, p in enumerate(path):\n+        tangent = (path[min(k + 1, 24)] - path[max(k - 1, 0)]).normalized()\n+        normal = (normal - tangent * normal.dot(tangent)).normalized()\n+        binormal = tangent.cross(normal)\n+        rings.append([bm.verts.new(p + (normal * math.cos(a) + binormal * math.sin(a)) * 2.4)\n+                      for a in [2 * math.pi * j / 8 for j in range(8)]])\n+    for a, b in zip(rings, rings[1:]):\n+        for j in range(8):\n+            bm.faces.new([a[j], a[(j + 1) % 8], b[(j + 1) % 8], b[j]])\n+    bmesh.ops.recalc_face_normals(bm, faces=bm.faces)\n+    return colour(new_object(\"cord\", bm, m[\"keys\"]), m[\"keys\"], \"black\")\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+def tap(f, f0, hold=2):\n+    return smooth(phase(f, f0, f0 + 3)) * (1 - smooth(phase(f, f0 + 3 + hold, f0 + 7 + hold)))\n+\n+\n+def opening(f):\n+    if f < 6:\n+        return 0.0\n+    if f < 44:\n+        return back_out(phase(f, 6, 18), 1.4)\n+    if f < 176:\n+        return 1.0 - ease_in(phase(f, 44, 54))\n+    if f < 204:\n+        return back_out(phase(f, 176, 188), 1.4)\n+    return 1.0 - ease_in(phase(f, 204, 214))\n+\n+\n+def timeline(f):\n+    above = L.CART[\"above\"]\n+    if f < 18 or f >= 200:\n+        cart = Pose(z=above, scale=HIDDEN)\n+    elif f < 26:\n+        cart = Pose(z=above, scale=max(HIDDEN, back_out(phase(f, 18, 25))))\n+    elif f < 38:\n+        cart = Pose(z=above * (1 - ease_in(phase(f, 26, 38))))\n+    elif f < 186:\n+        cart = Pose(z=-1.5 * math.sin(math.pi * phase(f, 38, 44)))\n+    else:\n+        cart = Pose(z=above * ease_out(phase(f, 186, 196)), scale=max(HIDDEN, 1 - ease_in(phase(f, 194, 200))))\n+    t = phase(f, 66, 168)\n+    k = math.sin(math.pi * t)\n+    press = -2.2\n+    return {\"lid\": Pose(pitch=L.LID[\"open\"] * opening(f)), \"cartridge\": cart,\n+            \"option\": Pose(z=press * tap(f, 58)), \"start\": Pose(z=press * tap(f, 62)),\n+            \"shaft\": Pose(pitch=18.0 * k * math.sin(2 * math.pi * 3.0 * t),\n+                          roll=18.0 * k * math.cos(2 * math.pi * 2.0 * t)),\n+            \"fire\": Pose(z=-2.0 * max(tap(f, f0) for f0 in (78, 98, 116, 134, 152)))}\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    atlas = bpy.data.images.load(os.path.join(images, \"atari800_print.png\"))\n+    m = {name: material(name, L.COLOURS[\"print\"], r, atlas) for name, r in ROUGHNESS.items()}\n+\n+    body, (flap, hinge) = computer(m)\n+    fixed_keys, moving_keys = keys(m)\n+    base, (shaft, shaft_at), (button, button_at) = atari_2600.joystick(\n+        {\"case\": material(\"joystick\", \"#18181B\", 0.5), \"print\": m[\"keys\"]})\n+    sx, sy = AL.STICK[\"at\"]\n+    jx, jy = L.JOYSTICK[\"at\"]\n+    move = Matrix.Translation((jx - sx, jy - sy, 0.0))\n+    base.data.transform(move)\n+    shaft_at, button_at = move @ shaft_at, move @ button_at\n+    w, d, h = AL.STICK[\"base\"]\n+    black = (lambda o: colour(o, m[\"keys\"], \"black\"))\n+    relief, start, leaving = plugs.place(*plugs.grommet(black, 4.0), m[\"keys\"], (jx, jy + d / 2 - 1.0, h * 0.4),\n+                                         (0.0, 1.0, 0.0))\n+    (py0, py1), z = L.PORTS[\"y\"], 6.5                           # the plug's middle: it rests on the table\n+    port = Vector((L.PORTS[\"x\"][0], py1 - z * (py1 - py0) / L.BASE, z))\n+    plug, end, back = plugs.place(*plugs.dsub(black, black, 13.0), m[\"keys\"], port, (0.0, -1.0, 0.0))\n+    wire = cord(m, [start, start + leaving * 30.0 + Vector((0.0, 0.0, -10.0)), end + back * 30.0, end])\n+    whole = join([body, fixed_keys, base, wire] + relief + plug, \"atari800\")\n+    for p in whole.data.polygons:\n+        p.use_smooth = True\n+    whole.data.set_sharp_from_angle(angle=math.radians(35))\n+\n+    root = bpy.data.objects.new(\"atari800_root\", None)\n+    scene.collection.objects.link(root)\n+    whole.parent = root\n+    parts = {\"lid\": (flap, hinge), \"cartridge\": (cartridge(m), Vector(L.SEAT)), \"shaft\": (shaft, shaft_at),\n+             \"fire\": (button, button_at)}\n+    parts.update(moving_keys)\n+    nodes = {}\n+    for name, (part, where) in parts.items():\n+        for p in part.data.polygons:\n+            p.use_smooth = True\n+        part.data.set_sharp_from_angle(angle=math.radians(35))\n+        part.parent = whole\n+        part.location = where\n+        nodes[name] = part\n+\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    points = [tilt @ v.co for v in whole.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [whole] + list(nodes.values())\n+    print(f\"Atari 800: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(whole.data)} the set), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "18fc366d6db1fef674ea8ccbff5c0121bccfe11f",
          "filename": "tools/console-models/atari800_layout.py",
          "status": "added",
          "additions": 121,
          "deletions": 0,
          "changes": 121,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari800_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari800_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fatari800_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,121 @@\n+\"\"\"The Atari 800 card's measurements, shared by atari800_print.py (which draws its printing) and atari800.py (which\n+builds it). Plain Python: Blender's Python imports it too.\n+\n+The Atari 800 (1979), 400 mm wide, 318 deep and 124 high (a listing's 15.75 x 12.5 inches; the side photograph's\n+height), its CX40 joystick in front of it (atari_2600.py's), a made-up game, Odyssey, on the cartridge. Millimetres: x\n+from its middle (left to right), y back from its front, z up from the table. The rest was measured from Evan-Amos's\n+photographs on Wikimedia Commons (from the front left and right, the back right, the right side straight on, opened) and\n+another from the front left, scaled by its depth and by its keys' 19.05 mm pitch.\n+\n+The computer: a shell standing BASE off the table on the joystick ports' block (under its front) and two feet; its side\n+outline (PROFILE) the front, the keyboard deck sloping up, the upper housing's sloping front face, its top flat then\n+falling to the back; the top's ends chamfered down to the sides (CHEEK). The keys (dark brown) stand on the deck, the\n+function keys (SYSTEM RESET, OPTION, SELECT, START) in a column at its right, the power light under them. The lid is the\n+housing's front face (the ATARI 800 plate on it) and the front of its top, from the left chamfer to short of the right\n+end, hinged along its back edge (LID); under it the metal cartridge bay (BAY), its two slots (LEFT and RIGHT CARTRIDGE)\n+in its floor. The vents run over the lid's top and on behind it, over the top and its fall: fins in recesses (VENTS).\n+The four joystick ports are in the front of the block under the front (PORTS).\n+\n+The keys are laid out in units of U from KEYS_X0, in rows from the back (ESC's) to the front (the space bar's). The deck\n+is a plane at SLOPE: a key stands on it in the deck's frame (x across, s up the slope from its front edge, h off it),\n+and deck_point() maps a point of that frame to the model's (y, z).\n+\n+One texture (ATLAS square, REGIONS): `deck` (the deck from above: the keys' tops and legends, the function keys', the\n+power light's), `badge` (the lid's front face from in front: the ATARI 800 plate), `ports` (the block's front), `plate`\n+(the bay's back wall, its LEFT CARTRIDGE / RIGHT CARTRIDGE plate), `cart` (the cartridge's front, its label), and\n+swatches; the joystick's top drawn where atari_2600_layout.REGIONS[\"stick\"] puts it and its red where its\n+SWATCHES[\"red\"] does, as atari_2600.joystick() maps them. A region is (x, y, width, height) in pixels.\n+\"\"\"\n+\n+import math\n+\n+ATLAS = 1024\n+U = 19.05\n+\n+WIDTH, DEPTH, HEIGHT = 400.0, 318.0, 123.6\n+BASE = 10.0                      # the shell's underside off the table\n+FRONT = (10.0, 51.1)             # the deck's front edge (y, z)\n+DECK_BACK = (139.0, 78.7)        # where the deck meets the housing's front face\n+SLOPE = math.atan2(DECK_BACK[1] - FRONT[1], DECK_BACK[0] - FRONT[0])\n+TOP_FRONT = 157.0                # the housing's front face's top edge (y)\n+PROFILE = [                      # the side outline (y, z), round from the front's foot\n+    (18.0, BASE), (305.0, BASE), (313.0, 14.0), (317.0, 22.0), (318.0, 32.0), (313.5, 102.0), (310.0, 105.6),\n+    (264.0, HEIGHT), (TOP_FRONT, HEIGHT), DECK_BACK, FRONT, (6.4, 48.8), (3.4, 45.6), (1.2, 42.0), (0.0, 38.0),\n+    (0.0, 30.0), (2.6, 26.9), (4.7, 23.3), (6.9, 19.7), (9.3, 16.0), (13.0, 12.6),\n+]\n+CHEEK = dict(x=140.0, z=109.0)   # the top's chamfer: from x to the side, down to z there\n+\n+KEYS_X0, ROW1 = -176.8, 118.4    # the back row's left edge, less its offset; its middle (s)\n+KEY = dict(height=11.0, inset=2.6)\n+ROWS = [\n+    (0.25, [(\"ESC\", 1.0)] + [(c, 1.0) for c in \"1234567890\"] + [(\"CLEAR\", 1.0), (\"INSERT\", 1.0), (\"DELETE\", 1.0),\n+                                                              (\"BREAK\", 1.0)]),\n+    (0.0, [(\"TAB\", 1.5)] + [(c, 1.0) for c in \"QWERTYUIOP-=\"] + [(\"RETURN\", 1.5)]),\n+    (0.0, [(\"CTRL\", 1.75)] + [(c, 1.0) for c in \"ASDFGHJKL;+*\"] + [(\"CAPS\", 1.25)]),\n+    (-0.25, [(\"SHIFT\", 2.25)] + [(c, 1.0) for c in \"ZXCVBNM,./\"] + [(\"ATARI\", 1.0), (\"SHIFT\", 1.75)]),\n+    (2.0, [(\" \", 9.0)]),\n+]\n+FUNCTION = dict(x=(124.0, 156.0), keys=[(\"SYSTEM RESET\", ROW1 + 2.0, \"reset\"), (\"OPTION\", ROW1 - U + 2.0, \"option\"),\n+                                        (\"SELECT\", ROW1 - 2 * U + 2.0, \"select\"),\n+                                        (\"START\", ROW1 - 3 * U + 2.0, \"start\")])\n+LIGHT = dict(s=ROW1 - 4 * U + 4.0, half=6.0, height=6.0)     # the power light, under START\n+\n+# The lid: the hole it fills (x, y; its hinge along the back); its face's and sides' thickness, its top's; the gap\n+# round it; how far it opens (degrees).\n+LID = dict(x=(-140.0, 115.0), y=(120.0, 200.0), wall=3.0, top=6.0, gap=1.0, open=-115.0)\n+BAY = dict(x=(-125.0, 100.0), y=(141.0, 197.0), floor=55.0)\n+SLOTS = dict(left=(-107.0, -30.0), right=(5.0, 82.0), y=(160.0, 180.0), floor=22.0)\n+# The vents: across (x); behind the lid (y) and on its top (lid); the recess's depth; the fins' count and thickness.\n+VENTS = dict(x=(-136.0, 111.0), y=(202.0, 306.0), lid=(161.0, 196.0), depth=5.0, count=72, fin=1.8)\n+BADGE = dict(fuji=(-134.0, -112.0), plate=(-109.0, -8.0), z=(92.0, 118.0))    # on the lid's face\n+PORTS = dict(block=(-108.0, 60.0), y=(36.0, 40.0), x=[-86.0, -53.0, 4.0, 37.0], z=5.0, size=(20.0, 6.0))\n+FEET = dict(x=170.0, y=(280.0, 300.0), size=24.0)\n+\n+# The cartridge: w, t, h; its label, and how far below its top; how far over its seat it appears.\n+CART = dict(size=(70.0, 18.0, 88.0), label=(56.0, 46.0), top=6.0, above=80.0)\n+SEAT = ((SLOTS[\"left\"][0] + SLOTS[\"left\"][1]) / 2, sum(SLOTS[\"y\"]) / 2, SLOTS[\"floor\"] + 2.0)  # its foot's middle\n+JOYSTICK = dict(at=(-120.0, -112.0))     # where the CX40's middle stands\n+\n+COLOURS = dict(case=\"#D9D0B8\", key=\"#3E3530\", print=\"#FFFFFF\")\n+\n+REGIONS = dict(deck=(0, 0, 1000, 348), badge=(0, 664, 640, 112), ports=(0, 784, 504, 30), plate=(0, 824, 563, 60),\n+               cart=(664, 380, 210, 264))\n+SWATCHES = dict(case=(912, 680), key=(944, 680), dark=(976, 680), reset=(1008, 680), option=(912, 712),\n+                select=(944, 712), start=(976, 712), metal=(1008, 712), cart=(912, 744), black=(944, 744),\n+                light=(976, 744), vent=(1008, 744))\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s\n+\n+\n+def deck_point(s, h):\n+    \"\"\"The model's (y, z) of the point s up the deck's slope from its front edge and h off it.\"\"\"\n+    c, k = math.cos(SLOPE), math.sin(SLOPE)\n+    return FRONT[0] + s * c - h * k, FRONT[1] + s * k + h * c\n+\n+\n+def face_y(z, inset=0.0):\n+    \"\"\"Where the housing's front face is at height z (y), or the line inset into the lid from it.\"\"\"\n+    (y0, z0), (y1, z1) = DECK_BACK, (TOP_FRONT, HEIGHT)\n+    length = math.hypot(y1 - y0, z1 - z0)\n+    ny, nz = (z1 - z0) / length, -(y1 - y0) / length       # into the lid\n+    y0, z0 = y0 + ny * inset, z0 + nz * inset\n+    return y0 + (z - z0) * (y1 - y0) / (z1 - z0)\n+\n+\n+def top_z(y):\n+    \"\"\"The top's height at y: flat, then falling to the back.\"\"\"\n+    (fy, fz), (by, bz) = PROFILE[7], PROFILE[6]\n+    return fz if y <= fy else fz - (y - fy) * (fz - bz) / (by - fy)\n+\n+\n+def keys():\n+    \"\"\"Every key: (legend, x0, x1, s), s its row's middle up the deck; the rows from the back to the front.\"\"\"\n+    out = []\n+    for r, (offset, row) in enumerate(ROWS):\n+        x = KEYS_X0 + offset * U\n+        s = ROW1 - r * U\n+        for legend, width in row:\n+            out.append((legend, x, x + width * U, s))\n+            x += width * U\n+    return out"
        },
        {
          "sha": "1d76aa918fcd894e34d194b2fe75744a4434bab9",
          "filename": "tools/console-models/atari800_print.py",
          "status": "added",
          "additions": 163,
          "deletions": 0,
          "changes": 163,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari800_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari800_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fatari800_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,163 @@\n+\"\"\"The Atari 800 card's printing (atari800_print.png), for the console theme's model (atari800.py).\n+\n+One image, laid out as atari800_layout.REGIONS says: the keyboard deck from above (the keys' tops and legends, the\n+function keys', the power light's), the lid's front face (the ATARI 800 plate), the front of the ports' block (the four\n+joystick ports), the bay's back wall (its LEFT CARTRIDGE / RIGHT CARTRIDGE plate), the cartridge's front (its label: a\n+made-up game, Odyssey), and swatches; the joystick's top (atari_2600_print.py's) and its red where atari_2600_layout.py\n+puts them. Run it with the system Python (Pillow):\n+\n+    python tools/console-models/atari800_print.py <out folder>\n+\"\"\"\n+\n+import os\n+import sys\n+\n+from PIL import Image, ImageDraw, ImageFont\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import atari800_layout as L  # noqa: E402\n+import atari_2600_layout as AL  # noqa: E402\n+from atari5200_print import fuji  # noqa: E402\n+from atari_2600_print import stick_top  # noqa: E402\n+from psp_print import ship, space  # noqa: E402\n+from retro_pc_print import rgb  # noqa: E402\n+from windows_pc_print import FONTS, Region  # noqa: E402\n+\n+CASE = rgb(L.COLOURS[\"case\"])\n+KEY = rgb(L.COLOURS[\"key\"])\n+LEGEND = (236, 232, 222)\n+BROWN = (84, 62, 46)\n+INK = (62, 54, 48)\n+PLATE = (204, 198, 184)\n+METAL = (168, 170, 172)\n+CART = (58, 52, 48)\n+FUNCTION = dict(reset=(190, 118, 44), option=(226, 148, 36), select=(238, 172, 32), start=(244, 192, 40))\n+\n+\n+def outlined(reg, s, name, size, x, y, ink, fill, width, anchor=\"lm\"):\n+    \"\"\"Text drawn hollow: its letters' outlines in ink, filled with fill.\"\"\"\n+    f = ImageFont.truetype(FONTS + name, max(1, round(size * reg.sy)))\n+    reg.draw.text(reg.p(x, y), s, font=f, fill=fill, anchor=anchor, stroke_width=reg.width(width), stroke_fill=ink)\n+\n+\n+def deck():\n+    \"\"\"The deck from above: x across (-200 at the left), y down from where it meets the housing (DECK_BACK) to the\n+    front.\"\"\"\n+    y_back = L.DECK_BACK[0]\n+    reg = Region(L.REGIONS[\"deck\"], L.WIDTH, y_back, CASE)\n+\n+    def at(x, y):\n+        return x + L.WIDTH / 2, y_back - y\n+\n+    def top(x0, x1, s, half, h, fill):\n+        \"\"\"A key's top, half its foot's depth along the slope, h its height; returns its middle.\"\"\"\n+        inset = L.KEY[\"inset\"]\n+        ya, yb = L.deck_point(s - half - 0.5 + inset, h)[0], L.deck_point(s + half + 0.5 - inset, h)[0]\n+        reg.rect_(*at(x0 + inset, yb), *at(x1 - inset, ya), fill, r=1.4)\n+        return (x0 + x1) / 2, (ya + yb) / 2\n+\n+    for legend, x0, x1, s in L.keys():\n+        x, y = top(x0, x1, s, 9.0, L.KEY[\"height\"], KEY)\n+        if legend == \"ATARI\":\n+            fuji(reg, *at(x, y), 5.0, LEGEND)\n+        elif legend.strip():\n+            reg.text(legend, \"arialbd.ttf\", 4.2 if len(legend) == 1 else 2.0, *at(x, y), LEGEND)\n+    fx0, fx1 = L.FUNCTION[\"x\"]\n+    for legend, s, name in L.FUNCTION[\"keys\"]:\n+        x, y = top(fx0, fx1, s, 8.5, L.KEY[\"height\"], FUNCTION[name])\n+        lines = legend.split(\" \")\n+        for k, line in enumerate(lines):\n+            reg.text(line, \"arialbd.ttf\", 2.2, *at(x, y + (len(lines) - 1) * 1.3 - k * 2.6), BROWN)\n+    x, y = top(fx0, fx1, L.LIGHT[\"s\"], L.LIGHT[\"half\"], L.LIGHT[\"height\"], (206, 200, 188))\n+    reg.rect_(*at(x - 7.0, y + 1.6), *at(x + 7.0, y - 1.6), (70, 30, 26), r=0.6)\n+    return reg\n+\n+\n+def badge():\n+    \"\"\"The lid's front face from in front: x across from the lid's left edge, z down from its top.\"\"\"\n+    (x0, x1), h = L.LID[\"x\"], L.HEIGHT - L.DECK_BACK[1]\n+    reg = Region(L.REGIONS[\"badge\"], x1 - x0, h, CASE)\n+\n+    def at(x, z):\n+        return x - x0, L.HEIGHT - z\n+\n+    (fx0, fx1), (px0, px1), (z0, z1) = L.BADGE[\"fuji\"], L.BADGE[\"plate\"], L.BADGE[\"z\"]\n+    middle = (z0 + z1) / 2\n+    reg.rect_(*at(fx0, z1 + 0.5), *at(fx1, z0 - 0.5), INK, r=1.0)\n+    fuji(reg, *at((fx0 + fx1) / 2, middle), 15.0, (226, 224, 216))\n+    reg.rect_(*at(px0, z1), *at(px1, z0), PLATE, r=0.8, outline=(176, 170, 156), width=0.4)\n+    reg.text(\"ATARI\", \"arialbd.ttf\", 15.0, *at(px0 + 7.0, middle), INK, anchor=\"lm\")\n+    outlined(reg, \"800\", \"arialbd.ttf\", 15.0, *at(px0 + 57.0, middle), (150, 96, 64), PLATE, 0.6)\n+    return reg\n+\n+\n+def ports():\n+    \"\"\"The front of the ports' block: x across, z down from the shell's underside.\"\"\"\n+    (bx0, bx1), z1 = L.PORTS[\"block\"], L.BASE\n+    reg = Region(L.REGIONS[\"ports\"], bx1 - bx0, z1, CASE)\n+\n+    def at(x, z):\n+        return x - bx0, z1 - z\n+\n+    pw, ph = L.PORTS[\"size\"]\n+    z = L.PORTS[\"z\"]\n+    for k, x in enumerate(L.PORTS[\"x\"]):\n+        reg.rect_(*at(x - pw / 2, z + ph / 2), *at(x + pw / 2, z - ph / 2), (44, 40, 36), r=1.4)\n+        reg.rect_(*at(x - pw / 2 + 2.0, z + ph / 2 - 1.6), *at(x + pw / 2 - 2.0, z - ph / 2 + 1.6), (10, 10, 10), r=0.8)\n+        reg.text(str(k + 1), \"arialbd.ttf\", 2.6, *at(x + pw / 2 + 3.0, z), (120, 112, 96))\n+    return reg\n+\n+\n+def plate():\n+    \"\"\"The bay's back wall: x across from the bay's left edge, z down from the deck's back edge; the metal, its dark\n+    plate.\"\"\"\n+    (bx0, bx1), h = L.BAY[\"x\"], L.DECK_BACK[1] - L.BAY[\"floor\"]\n+    reg = Region(L.REGIONS[\"plate\"], bx1 - bx0, h, METAL)\n+\n+    def at(x, z):\n+        return x - bx0, L.DECK_BACK[1] - z\n+\n+    mid = (bx0 + bx1) / 2\n+    reg.rect_(*at(bx0 + 7.0, 76.0), *at(bx1 - 7.0, 62.0), (34, 32, 30), r=1.0)\n+    for x0, x1, s in ((bx0 + 10.0, mid - 20.0, \"\u00abLEFT CARTRIDGE\"), (mid + 20.0, bx1 - 10.0, \"RIGHT CARTRIDGE\u00bb\")):\n+        reg.rect_(*at(x0, 73.5), *at(x1, 64.5), None, r=1.0, outline=(150, 148, 142), width=0.4)\n+        reg.text(s, \"arial.ttf\", 5.0, *at((x0 + x1) / 2, 69.0), (196, 194, 188))\n+    fuji(reg, *at(mid, 70.6), 6.0, (196, 194, 188))\n+    reg.text(\"ATARI\", \"arialbd.ttf\", 2.8, *at(mid, 65.4), (196, 194, 188))\n+    return reg\n+\n+\n+def cart():\n+    \"\"\"The cartridge's front: x across from its left edge, y down from its top; LEFT CARTRIDGE over the label.\"\"\"\n+    (w, t, h), (lw, lh), top = L.CART[\"size\"], L.CART[\"label\"], L.CART[\"top\"]\n+    reg = Region(L.REGIONS[\"cart\"], w, h, CART)\n+    x0 = (w - lw) / 2\n+    art = space((440, 283)).convert(\"RGB\")\n+    ship(ImageDraw.Draw(art), 140, 170, 3.0, \"#FFFFFF\", \"#FFB23C\")\n+    bx0, by0, bx1, by1 = reg.box(x0, top + 10.0, x0 + lw, top + lh)\n+    reg.img.paste(art.resize((round(bx1 - bx0), round(by1 - by0)), Image.LANCZOS), (round(bx0), round(by0)))\n+    reg.rect_(x0, top, x0 + lw, top + 10.0, (236, 228, 208))\n+    reg.text(\"ODYSSEY\", \"ariblk.ttf\", 6.0, w / 2, top + 5.0, (62, 48, 40))\n+    reg.text(\"\u00abLEFT CARTRIDGE\", \"arial.ttf\", 2.6, w / 2, top / 2, (150, 142, 132))\n+    return reg\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), CASE)\n+    for reg in (deck(), badge(), ports(), plate(), cart(), stick_top()):\n+        reg.paste(image)\n+    d = ImageDraw.Draw(image)\n+    x, y = AL.SWATCHES[\"red\"]\n+    d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=(200, 30, 26))\n+    colours = dict(case=CASE, key=KEY, dark=(14, 13, 12), metal=METAL, cart=CART, black=(22, 22, 24),\n+                   light=(206, 200, 188), vent=(112, 104, 90), **FUNCTION)\n+    for name, (x, y) in L.SWATCHES.items():\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colours[name])\n+    path = os.path.join(out, \"atari800_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "6be065415c08c8f1d3ae159e6408246b5d55ed7a",
          "filename": "tools/console-models/atari_2600.py",
          "status": "added",
          "additions": 269,
          "deletions": 0,
          "changes": 269,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari_2600.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari_2600.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fatari_2600.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,269 @@\n+\"\"\"The console theme's Atari 2600 system model (godot/themes/console/models/systems/atari2600.glb).\n+\n+Built in Blender from the four-switch woody's proportions (atari_2600_layout.py has the measurements, taken from\n+photographs): its side outline extruded across its width, the grooves cut across its ribbed slope, the cartridge slot\n+and the switches' slots cut into its control panel's face; the chrome levers standing out of their slots; and its CX40\n+joystick in front of its left end, the shaft and the red button standing on its base. The printing (the panel's outline\n+and legends, the wood-grain strip, the cartridge's label, the joystick's top) is one texture, drawn by\n+atari_2600_print.py and projected onto the faces.\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: its right end towards the viewer, its\n+top tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m) holding the set; the set\n+holding the cartridge (Odyssey, a made-up game), the POWER, GAME SELECT and GAME RESET levers, and the joystick's shaft\n+and button. The cartridge waits in the slot shrunk to nothing, so at rest the card is the console switched off.\n+\n+The `focused` clip (8 s, looping) plays a game: the cartridge appears above the slot, turns to show its label and drops\n+in; POWER flips on, GAME SELECT and GAME RESET flick; the joystick's shaft waggles and its button fires; then POWER goes\n+off and the cartridge jumps out and goes.\n+\n+Four materials (case, print, dark, chrome) and one texture, within the system model budget. Run from the repo root:\n+\n+    python tools/console-models/atari_2600_print.py artifacts/a2600\n+    blender -b --factory-startup --python tools/console-models/atari_2600.py -- artifacts/a2600 godot/themes/console/models/systems/atari2600.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import atari_2600_layout as L  # noqa: E402\n+from amiga import paint, rounded, solid  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, key, phase, smooth  # noqa: E402\n+from mega_drive_cartridge import bevel, boolean, box, export, join, material, prism_x, triangles  # noqa: E402\n+\n+TILT_YAW = -25.0     # degrees about the vertical: the right end comes towards the viewer\n+TILT_PITCH = 28.0    # degrees about the horizontal: the top tips up towards the viewer\n+ROUGHNESS = dict(case=0.5, print=0.5, dark=0.7, chrome=0.22)\n+FLIP = 25.0          # degrees a lever leans either way\n+\n+\n+def atlas_uv(region, w, h):\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+def swatch(name):\n+    x, y = L.SWATCHES[name]\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+def face_frame(a, b):\n+    \"\"\"A sloping face's frame: (x, s, h) with s up the slope from a and h off it, to the model's (x, y, z); and its\n+    length.\"\"\"\n+    (y0, z0), angle, length = L.frame(a, b)\n+    return Matrix.Translation((0.0, y0, z0)) @ Matrix.Rotation(angle, 4, \"X\"), length\n+\n+\n+SLOPE, SLOPE_LEN = face_frame(*L.SLOPE)\n+PANEL, PANEL_LEN = face_frame(*L.PANEL)\n+WOOD, WOOD_LEN = face_frame(L.WOOD[\"bottom\"], L.WOOD[\"top\"])\n+\n+\n+def framed(name, frame, x0, x1, s0, s1, h0, h1, mat):\n+    \"\"\"A box in a face's frame.\"\"\"\n+    part = box(name, x0, x1, s0, s1, h0, h1, mat)\n+    part.data.transform(frame)\n+    return part\n+\n+\n+def paint_face(obj, frame, length, region, mat):\n+    \"\"\"Prints the faces lying on a frame's face (its normal, within its length) with the region, seen square on.\"\"\"\n+    inverse = frame.inverted()\n+    normal = (frame.to_3x3() @ Vector((0.0, 0.0, 1.0))).normalized()\n+    uv = atlas_uv(region, L.WIDTH, length)\n+\n+    def keep(c, n, name):\n+        local = inverse @ c\n+        return name == \"case\" and n.dot(normal) > 0.99 and -0.5 < local.y < length + 0.5 and abs(local.z) < 0.5\n+    paint(obj, mat, lambda co: uv((inverse @ co).x + L.WIDTH / 2, length - (inverse @ co).y), keep)\n+\n+\n+# ---- The console -----------------------------------------------------------------------------------------------------\n+\n+def console(m):\n+    body = prism_x(\"atari2600\", L.profile(), -L.WIDTH / 2, L.WIDTH / 2, m[\"case\"])\n+    bevel(body, 2.0, segments=2)\n+    g = L.GROOVES\n+    pitch = SLOPE_LEN / (g[\"count\"] + 1)\n+    boolean(body, join([framed(\"groove\", SLOPE, -L.WIDTH / 2 + 5.0, L.WIDTH / 2 - 5.0, pitch * (k + 1) - g[\"width\"] / 2,\n+                               pitch * (k + 1) + g[\"width\"] / 2, -g[\"depth\"], 5.0, m[\"dark\"]) for k in range(g[\"count\"])],\n+                        \"grooves\"))\n+    (x0, x1), (s0, s1) = L.CART_SLOT[\"x\"], L.CART_SLOT[\"s\"]\n+    boolean(body, framed(\"slot\", PANEL, x0, x1, s0, s1, -L.CART_SLOT[\"depth\"], 5.0, m[\"dark\"]))\n+    sw = L.SWITCH\n+    boolean(body, join([framed(\"switch_slot\", PANEL, x - sw[\"slot\"][0] / 2, x + sw[\"slot\"][0] / 2,\n+                               sw[\"s\"] - sw[\"slot\"][1] / 2, sw[\"s\"] + sw[\"slot\"][1] / 2, -3.0, 5.0, m[\"dark\"])\n+                        for _, x in L.SWITCHES], \"switch_slots\"))\n+    paint_face(body, PANEL, PANEL_LEN, L.REGIONS[\"panel\"], m[\"print\"])\n+    paint_face(body, WOOD, WOOD_LEN, L.REGIONS[\"wood\"], m[\"print\"])\n+    paint(body, m[\"print\"], swatch(\"wood\"),\n+          lambda c, n, name: name == \"case\" and c.y < L.WOOD[\"top\"][0] + 1.0 and abs(n.x) > 0.9 and c.z > 6.0)\n+    return body\n+\n+\n+def lever(name, m):\n+    \"\"\"A switch's chrome lever, its pivot at its origin, pointing along +z.\"\"\"\n+    r, length = L.SWITCH[\"lever\"]\n+    rings = [[(rr * math.cos(2 * math.pi * k / 12), rr * math.sin(2 * math.pi * k / 12), z) for k in range(12)]\n+             for rr, z in ((r * 0.9, -2.0), (r * 0.8, length - 2.0), (r * 1.15, length - 1.2), (r * 1.15, length),\n+                           (r * 0.6, length + 0.8))]\n+    return solid(name, rings, m[\"chrome\"])\n+\n+\n+def cartridge(m):\n+    \"\"\"The cartridge, its middle at its origin, upright, its label facing -y.\"\"\"\n+    c = L.CART\n+    w, h, t = c[\"width\"], c[\"height\"], c[\"thickness\"]\n+    shell = solid(\"cartridge\", [[(x, y, z) for x, y in rounded(0.0, 0.0, w, t, 3.0, 3)] for z in (-h / 2, h / 2)],\n+                  m[\"case\"])\n+    bevel(shell, 1.0, segments=1)\n+    lw, lh, from_top = c[\"label\"]\n+    top = h / 2 - from_top\n+    label = framed(\"label\", Matrix.Rotation(math.radians(90.0), 4, \"X\"), -lw / 2, lw / 2, top - lh, top, t / 2 + 0.1,\n+                   t / 2 + 0.2, m[\"print\"])\n+    uv = atlas_uv(L.REGIONS[\"label\"], lw, lh)\n+    paint(label, m[\"print\"], lambda co: uv(co.x + lw / 2, top - co.z), lambda c_, n, name: n.y < -0.9)\n+    ridges = [box(\"ridge\", -w / 2 + 6.0, w / 2 - 6.0, -t / 2 - 0.6, t / 2 + 0.6, h / 2 - 3.0 - 4.0 * k, h / 2 - 1.8 - 4.0 * k,\n+                  m[\"case\"]) for k in range(1)]\n+    return join([shell, label] + ridges, \"cartridge\")\n+\n+\n+def joystick(m):\n+    \"\"\"The CX40's base, its top printed, at its place; and its shaft and button, each its own node.\"\"\"\n+    sx, sy = L.STICK[\"at\"]\n+    w, d, h = L.STICK[\"base\"]\n+    base = solid(\"joystick\", [[(sx + x, sy + y, z) for x, y in rounded(0.0, 0.0, w, d, 10.0, 4)] for z in (0.0, h)],\n+                 m[\"case\"])\n+    bevel(base, 2.5, segments=2)\n+    top = atlas_uv(L.REGIONS[\"stick\"], w, d)\n+    paint(base, m[\"print\"], lambda co: top(co.x - (sx - w / 2), (sy + d / 2) - co.y), lambda c, n, name: n.z > 0.9)\n+    r, length = L.STICK[\"shaft\"]\n+    boot = [[(r * 2.2 * math.cos(2 * math.pi * k / 16), r * 2.2 * math.sin(2 * math.pi * k / 16), z) for k in range(16)]\n+            for z in (-1.0, 4.0)]\n+    shaft = solid(\"shaft\", boot + [[(rr * math.cos(2 * math.pi * k / 16), rr * math.sin(2 * math.pi * k / 16), z)\n+                                    for k in range(16)] for rr, z in ((r, 8.0), (r * 0.9, length - 4.0), (r, length - 1.0),\n+                                                                      (r * 0.6, length))], m[\"case\"])\n+    angle, br = L.STICK[\"button\"]\n+    a = math.radians(angle)\n+    ring = [(br * math.cos(2 * math.pi * k / 20), br * math.sin(2 * math.pi * k / 20)) for k in range(20)]\n+    button = solid(\"fire\", [[(x, y, z) for x, y in ring] for z in (-2.0, 5.0)], m[\"print\"])\n+    paint(button, m[\"print\"], swatch(\"red\"), lambda c, n, name: True)\n+    shaft_at = Vector((sx + 6.0, sy - 3.0, h))\n+    button_at = Vector((sx + 28.0 * math.cos(a), sy + 28.0 * math.sin(a), h))\n+    return base, (shaft, shaft_at), (button, button_at)\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+NORMAL = (PANEL.to_3x3() @ Vector((0.0, 0.0, 1.0))).normalized()   # out of the panel's face\n+\n+\n+def along(d, **kw):\n+    \"\"\"A pose d along the panel's normal.\"\"\"\n+    return Pose(x=0.0, y=NORMAL.y * d, z=NORMAL.z * d, **kw)\n+\n+\n+def flick(f, f0):\n+    return smooth(phase(f, f0, f0 + 3)) * (1 - smooth(phase(f, f0 + 6, f0 + 10)))\n+\n+\n+def timeline(f):\n+    poses = {}\n+    above = 110.0\n+    if f < 10 or f >= 200:\n+        poses[\"cartridge\"] = Pose(scale=HIDDEN)\n+    elif f < 22:\n+        poses[\"cartridge\"] = along(above, scale=HIDDEN + (1 - HIDDEN) * back_out(phase(f, 10, 18)))\n+    elif f < 40:\n+        poses[\"cartridge\"] = along(above - 10.0 * smooth(phase(f, 22, 40)), spin=360.0 * smooth(phase(f, 22, 40)))\n+    elif f < 172:\n+        t = ease_in(phase(f, 40, 54))\n+        settle = 2.0 * math.exp(-(f - 54) / 3.0) * abs(math.sin(math.pi * (f - 54) / 5.0)) if f >= 54 else 0.0\n+        poses[\"cartridge\"] = along((above - 10.0) * (1 - t) + settle)\n+    else:\n+        t = phase(f, 172, 200)\n+        poses[\"cartridge\"] = along(30.0 * back_out(phase(f, 172, 180)) + 60.0 * ease_in(t), spin=-200.0 * ease_in(t),\n+                                   scale=max(HIDDEN, 1 - ease_in(phase(f, 190, 200))))\n+    on = smooth(phase(f, 60, 64)) * (1 - smooth(phase(f, 164, 168)))\n+    poses[\"power\"] = Pose(pitch=-2 * FLIP * on)                  # up, from OFF to ON\n+    poses[\"game_select\"] = Pose(pitch=2 * FLIP * flick(f, 72))   # pushed down, springing back\n+    poses[\"game_reset\"] = Pose(pitch=2 * FLIP * flick(f, 82))\n+    waggle = phase(f, 92, 150)\n+    if 0.0 < waggle < 1.0:\n+        envelope = math.sin(math.pi * waggle)\n+        poses[\"shaft\"] = Pose(roll=18.0 * envelope * math.sin(waggle * 6 * math.pi),\n+                              pitch=12.0 * envelope * math.sin(waggle * 4 * math.pi + 1.0))\n+    poses[\"fire\"] = Pose(z=-3.0 * sum(flick(f, f0) for f0 in (100, 112, 124, 136)))\n+    return poses\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    printed = bpy.data.images.load(os.path.join(images, \"atari_2600_print.png\"))\n+    colours = dict(case=L.COLOURS[\"case\"], print=\"#FFFFFF\", dark=L.COLOURS[\"dark\"], chrome=L.COLOURS[\"chrome\"])\n+    m = {n: material(n, colours[n], r, printed if n == \"print\" else None) for n, r in ROUGHNESS.items()}\n+\n+    body = console(m)\n+    base, (shaft, shaft_at), (button, button_at) = joystick(m)\n+    levers = {}\n+    for name, x in L.SWITCHES:\n+        part = lever(name, m)\n+        rest = -FLIP if name in (\"power\",) else FLIP              # POWER rests OFF (down); the others up\n+        place = PANEL @ Matrix.Translation((x, L.SWITCH[\"s\"], 0.0)) @ Matrix.Rotation(math.radians(-rest), 4, \"X\")\n+        levers[name] = (part, place)\n+    static = [part for name, (part, place) in levers.items() if name == \"tv_type\"]\n+    for part in static:\n+        part.data.transform(levers[\"tv_type\"][1])\n+    whole = join([body, base] + static, \"atari2600\")\n+    cart = cartridge(m)\n+    for obj in [whole, cart, shaft] + [p for n, (p, _) in levers.items() if n != \"tv_type\"]:\n+        for p in obj.data.polygons:\n+            p.use_smooth = True\n+        obj.data.set_sharp_from_angle(angle=math.radians(35))\n+\n+    root = bpy.data.objects.new(\"atari2600_root\", None)\n+    scene.collection.objects.link(root)\n+    whole.parent = root\n+    nodes = {}\n+    for name, (part, place) in levers.items():\n+        if name == \"tv_type\":\n+            continue\n+        part.parent = whole\n+        part.matrix_basis = place\n+        nodes[name] = part\n+    seat = PANEL @ Vector((0.0, sum(L.CART_SLOT[\"s\"]) / 2, -L.CART_SLOT[\"depth\"] + 2.0 + L.CART[\"height\"] / 2))\n+    cart.parent = whole\n+    cart.matrix_basis = Matrix.Translation(seat) @ Matrix.Rotation(L.frame(*L.PANEL)[1], 4, \"X\")\n+    for part, where in ((shaft, shaft_at), (button, button_at)):\n+        part.parent = whole\n+        part.location = where\n+    nodes.update(cartridge=cart, shaft=shaft, fire=button)\n+\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    points = [tilt @ v.co for v in whole.data.vertices] + [tilt @ (v.co + shaft_at) for v in shaft.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [whole] + list(nodes.values())\n+    print(f\"Atari 2600: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(whole.data)} the set), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "4117c11e77bcade6a1bf4c64fa48c66b998afaaf",
          "filename": "tools/console-models/atari_2600_layout.py",
          "status": "added",
          "additions": 64,
          "deletions": 0,
          "changes": 64,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari_2600_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari_2600_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fatari_2600_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,64 @@\n+\"\"\"The Atari 2600's measurements, shared by atari_2600_print.py (which draws its printing) and atari_2600.py (which builds\n+it). Plain Python: Blender's Python imports it too.\n+\n+The four-switch \"woody\" (1980), 343 x 229 x 89 mm, with its CX40 joystick. Millimetres: x from the console's middle\n+(left to right), y back from its front edge, z up from the table. The rest was measured from photographs on Wikimedia\n+Commons (Evan-Amos's view of the console with its joystick), scaled by its width.\n+\n+Its side outline: the wood-grain strip across its front, the ribbed slope rising behind it to the raised control panel,\n+whose face slopes up to the flat top along the back. On the panel, in an orange outline: POWER and TV TYPE on the left,\n+the cartridge slot in the middle under VIDEO COMPUTER SYSTEM, GAME SELECT and GAME RESET on the right, each switch a\n+chrome lever in a slot. The joystick stands in front of the console's left end.\n+\n+One texture (ATLAS square, REGIONS): `panel` (the control panel's face, seen square on: its outline, legends and the\n+switches' slots), `wood` (the front strip: the grain, ATARI), `label` (the cartridge's label: a made-up game, Odyssey)\n+and `stick` (the joystick's top: the orange ring, TOP), and swatches. A region is (x, y, width, height) in pixels.\n+\"\"\"\n+\n+import math\n+\n+ATLAS = 1024\n+\n+WIDTH, DEPTH, HEIGHT = 343.0, 229.0, 89.0\n+WOOD = dict(bottom=(2.0, 8.0), top=(13.0, 40.0))            # the strip's face: its bottom and top edges, (y, z)\n+SLOPE = ((16.0, 42.0), (118.0, 57.0))                       # the ribbed slope: from (y, z) to (y, z)\n+PANEL = ((124.0, 63.0), (196.0, 86.0))                      # the panel's face: its front and back edges, (y, z)\n+TOP = (222.0, HEIGHT)                                       # the flat top ends here, then the back rounds down\n+GROOVES = dict(count=17, width=2.4, depth=2.4)              # across the ribbed slope\n+\n+\n+def profile():\n+    \"\"\"The side outline, (y, z), anticlockwise seen from the right end.\"\"\"\n+    (ws, wz), (wt, wtz) = WOOD[\"bottom\"], WOOD[\"top\"]\n+    (s0, z0), (s1, z1) = SLOPE\n+    (p0, q0), (p1, q1) = PANEL\n+    return [(2.0, 0.0), (DEPTH - 2.0, 0.0), (DEPTH, 2.0), (DEPTH, HEIGHT - 6.0), (TOP[0], HEIGHT), (p1 + 3.0, HEIGHT),\n+            (p1, q1), (p0, q0), (p0 - 4.0, z1 + 2.0), (s1, z1), (s0, z0), (wt, wtz), (ws, wz), (0.0, 6.0), (0.0, 2.0)]\n+\n+\n+def frame(a, b):\n+    \"\"\"The frame of a sloping face from (y, z) a to (y, z) b: its origin at a, its s axis up the slope, its normal.\"\"\"\n+    (y0, z0), (y1, z1) = a, b\n+    length = math.hypot(y1 - y0, z1 - z0)\n+    angle = math.atan2(z1 - z0, y1 - y0)\n+    return (y0, z0), angle, length\n+\n+\n+PANEL_W = 312.0                                             # the panel's outlined area across, centred\n+SWITCHES = [(\"power\", -128.0), (\"tv_type\", -96.0), (\"game_select\", 96.0), (\"game_reset\", 128.0)]\n+SWITCH = dict(s=36.0, slot=(6.0, 18.0), lever=(3.0, 13.0))  # their middle up the panel's face; the slot; the lever\n+CART_SLOT = dict(x=(-44.0, 44.0), s=(18.0, 42.0), depth=46.0)\n+\n+CART = dict(width=80.0, height=105.0, thickness=21.0, label=(66.0, 72.0, 6.0))  # its label: w, h, from its top\n+\n+STICK = dict(at=(-196.0, -38.0), base=(80.0, 80.0, 34.0), shaft=(8.0, 66.0), button=(118.0, 10.0))\n+# the joystick: its middle; its base w, d, h; the shaft's radius and height; the button's angle (degrees from +x\n+# round its top) and radius.\n+\n+COLOURS = dict(case=\"#1E1E21\", dark=\"#0C0C0E\", chrome=\"#C9CCD1\", orange=\"#E5762A\")\n+\n+REGIONS = dict(panel=(0, 0, 1024, 240), wood=(0, 248, 1024, 100), label=(0, 356, 300, 330), stick=(312, 356, 300, 300))\n+SWATCHES = dict(red=(640, 372), dark=(672, 372), wood=(704, 372))\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s"
        },
        {
          "sha": "4202312b04affca444e4ebabc730fc506d98da51",
          "filename": "tools/console-models/atari_2600_print.py",
          "status": "added",
          "additions": 146,
          "deletions": 0,
          "changes": 146,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari_2600_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari_2600_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fatari_2600_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,146 @@\n+\"\"\"The Atari 2600's printing, for the console theme's Atari 2600 model (tools/console-models/atari_2600.py).\n+\n+Draws its atlas, its regions as atari_2600_layout.py places them, each in millimetres as the model's planar UVs map it:\n+the control panel's face seen square on (the orange outline, POWER, TV TYPE, VIDEO COMPUTER SYSTEM, GAME SELECT and\n+GAME RESET, the switches' slots); the wood-grain strip across the front with ATARI and its mark; the cartridge's label (a\n+made-up game, Odyssey); the joystick's top (the orange ring and TOP); and the swatches small parts take their colour\n+from. Run it with the system Python (Pillow):\n+\n+    python tools/console-models/atari_2600_print.py <out folder>\n+\n+It writes atari_2600_print.png there.\n+\"\"\"\n+\n+import math\n+import os\n+import random\n+import sys\n+\n+from PIL import Image, ImageDraw, ImageFont\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import atari_2600_layout as L  # noqa: E402\n+from psp_print import ship, space  # noqa: E402\n+from retro_pc_print import rgb, shade  # noqa: E402\n+from windows_pc_print import FONTS, Region  # noqa: E402\n+\n+CASE = rgb(L.COLOURS[\"case\"])\n+ORANGE = rgb(L.COLOURS[\"orange\"])\n+RED_LEGEND = (214, 74, 62)\n+\n+\n+def fuji(reg, x, y, size, colour):\n+    \"\"\"Atari's mark, size tall, its middle at (x, y): three columns, the outer two flaring out at their feet.\"\"\"\n+    s = size\n+    reg.rect_(x - 0.08 * s, y - 0.5 * s, x + 0.08 * s, y + 0.5 * s, colour)\n+    for side in (-1, 1):\n+        pts = [(x + side * 0.16 * s, y - 0.5 * s), (x + side * 0.3 * s, y - 0.5 * s), (x + side * 0.3 * s, y - 0.05 * s),\n+               (x + side * 0.58 * s, y + 0.5 * s), (x + side * 0.4 * s, y + 0.5 * s), (x + side * 0.16 * s, y + 0.05 * s)]\n+        reg.poly(pts, colour)\n+\n+\n+def panel():\n+    _, _, length = L.frame(*L.PANEL)\n+    reg = Region(L.REGIONS[\"panel\"], L.WIDTH, length, CASE)\n+\n+    def at(x, s):\n+        return x + L.WIDTH / 2, length - s\n+    hw = L.PANEL_W / 2\n+    a, b = at(-hw, length - 5.0)\n+    c, e = at(hw, 4.0)\n+    reg.rect_(a, b, c, e, ORANGE, r=1.5)\n+    reg.rect_(a + 0.8, b + 0.8, c - 0.8, e - 0.8, shade(CASE, -0.15), r=1.0)\n+    sw = L.SWITCH\n+    for name, x in L.SWITCHES:\n+        sx, sy = at(x, sw[\"s\"])\n+        reg.rect_(sx - sw[\"slot\"][0] / 2, sy - sw[\"slot\"][1] / 2, sx + sw[\"slot\"][0] / 2, sy + sw[\"slot\"][1] / 2,\n+                  (6, 6, 8), r=sw[\"slot\"][0] / 2)\n+    def legend(text, x, s, size=3.4, colour=RED_LEGEND, font=\"arialbd.ttf\"):\n+        reg.text(text, font, size, *at(x, s), colour)\n+    legend(\"POWER\", -122.0, 60.0)\n+    legend(\"ON\", -140.0, 51.0, 3.0)\n+    legend(\"OFF\", -140.0, 27.0, 3.0)\n+    legend(\"TV TYPE\", -88.0, 56.0, 3.4, ORANGE)\n+    legend(\"COLOR\", -110.0, 46.0, 3.0, ORANGE)\n+    legend(\"B\u2022W\", -110.0, 24.0, 3.0, ORANGE)\n+    legend(\"VIDEO COMPUTER SYSTEM\u2122\", 0.0, 52.0, 4.4, (236, 236, 236))\n+    for name, x in L.SWITCHES[2:]:\n+        lines = name.upper().split(\"_\")\n+        legend(lines[0], x, 64.0, 3.0, ORANGE)\n+        legend(lines[1], x, 60.0, 3.0, ORANGE)\n+        for k in range(4):                                     # the arrow's dashes, pointing down\n+            reg.rect_(*at(x + 9.0, 46.0 - k * 4.0), *at(x + 9.0 + 3.0 - k * 0.6, 45.0 - k * 4.0), ORANGE)\n+        reg.poly([at(x + 8.0, 28.0), at(x + 12.0, 28.0), at(x + 10.0, 24.0)], ORANGE)\n+    (x0, x1), (s0, s1) = L.CART_SLOT[\"x\"], L.CART_SLOT[\"s\"]\n+    reg.rect_(*at(x0 - 2.0, s1 + 2.0), *at(x1 + 2.0, s0 - 2.0), shade(CASE, 0.15), r=2.0)\n+    reg.rect_(*at(x0, s1), *at(x1, s0), (8, 8, 10), r=1.0)\n+    return reg\n+\n+\n+def wood():\n+    (y0, z0), (y1, z1) = L.WOOD[\"bottom\"], L.WOOD[\"top\"]\n+    length = math.hypot(y1 - y0, z1 - z0)\n+    reg = Region(L.REGIONS[\"wood\"], L.WIDTH, length, (118, 74, 36))\n+    rng = random.Random(2600)\n+    layer = reg.layer()\n+    d = ImageDraw.Draw(layer)\n+    for k in range(70):                                         # the grain: long wavering streaks\n+        y = rng.uniform(0, length)\n+        colour = rng.choice([(92, 54, 24), (140, 92, 48), (104, 62, 30), (156, 104, 56)])\n+        pts = [(x, y + 0.6 * math.sin(x / rng.uniform(12, 30) + k)) for x in range(0, int(L.WIDTH) + 10, 6)]\n+        d.line([reg.p(px, py) for px, py in pts], fill=colour + (160,), width=max(1, round(rng.uniform(0.2, 0.8) * reg.sx)))\n+    reg.over(layer)\n+    fuji(reg, L.WIDTH - 22.0, length / 2 - 4.0, 12.0, (240, 238, 232))\n+    reg.text(\"ATARI\u00ae\", \"arial.ttf\", 4.4, L.WIDTH - 22.0, length / 2 + 7.5, (240, 238, 232))\n+    return reg\n+\n+\n+def label():\n+    lw, lh, _ = L.CART[\"label\"]\n+    reg = Region(L.REGIONS[\"label\"], lw, lh, (230, 226, 216))\n+    art = space((660, 540))\n+    d = ImageDraw.Draw(art)\n+    ship(d, 220, 330, 3.0, \"#FFFFFF\", \"#FFB23C\")\n+    x0, y0, x1, y1 = reg.box(2.0, 15.0, lw - 2.0, lh - 9.0)\n+    reg.img.paste(art.convert(\"RGB\").resize((round(x1 - x0), round(y1 - y0)), Image.LANCZOS), (round(x0), round(y0)))\n+    reg.rect_(0.0, 0.0, lw, 13.0, (196, 46, 40))\n+    reg.text(\"ODYSSEY\", \"arialbi.ttf\", 9.0, lw / 2, 6.8, (255, 248, 230))\n+    fuji(reg, 7.0, lh - 4.4, 5.0, (40, 40, 44))\n+    reg.text(\"ATARI\", \"arialbd.ttf\", 3.6, 18.0, lh - 4.4, (40, 40, 44), anchor=\"lm\")\n+    reg.text(\"GAME PROGRAM\u2122\", \"arial.ttf\", 2.6, lw - 3.0, lh - 4.4, (40, 40, 44), anchor=\"rm\")\n+    return reg\n+\n+\n+def stick_top():\n+    w, d, _ = L.STICK[\"base\"]\n+    reg = Region(L.REGIONS[\"stick\"], w, d, CASE)\n+    reg.rect_(1.0, 1.0, w - 1.0, d - 1.0, shade(CASE, 0.08), r=10.0)\n+    cx, cy = w / 2 + 6.0, d / 2 + 3.0\n+    for k in range(40):                                         # the ring's dashes\n+        a = 2 * math.pi * k / 40\n+        if k % 10 in (0, 5):\n+            continue\n+        reg.line([(cx + 25 * math.cos(a), cy + 25 * math.sin(a)), (cx + 29 * math.cos(a), cy + 29 * math.sin(a))],\n+                 ORANGE, 1.4)\n+    reg.circle(cx, cy, 20.0, shade(CASE, -0.2))\n+    reg.circle(cx, cy, 15.0, shade(CASE, 0.05))\n+    reg.text(\"TOP\", \"arialbd.ttf\", 4.0, cx - 21.0, cy - 21.0, ORANGE)\n+    return reg\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), CASE)\n+    for reg in (panel(), wood(), label(), stick_top()):\n+        reg.paste(image)\n+    d = ImageDraw.Draw(image)\n+    for name, colour in ((\"red\", (228, 58, 40)), (\"dark\", rgb(L.COLOURS[\"dark\"])), (\"wood\", (118, 74, 36))):\n+        x, y = L.SWATCHES[name]\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colour)\n+    path = os.path.join(out, \"atari_2600_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "56713275721ecab613ccbf92934a63ba7fa7e912",
          "filename": "tools/console-models/atari_st.py",
          "status": "added",
          "additions": 406,
          "deletions": 0,
          "changes": 406,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari_st.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari_st.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fatari_st.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,406 @@\n+\"\"\"The console theme's Atari ST system model (godot/themes/console/models/systems/atarist.glb): an Atari 520STFM, its\n+SC1224 monitor showing GEM's desktop, and its mouse.\n+\n+Built in Blender from the real machines' proportions (atari_st_layout.py has the measurements, taken from\n+photographs). The computer is its side outline extruded across its width: the keyboard deck sloping up from the low\n+front edge, the band with the function keys on its face, the groove, the raised back; the keyboard's wells, the\n+diagonal ribs of the raised back's panel (each groove clipped to the panel), and the floppy drive's slot in its right\n+end are cut into it. Its 94 keys (the UK layout) stand in the wells, tapering to their printed tops; the ten function\n+keys are slanted blades on the band. The monitor is its front frame, the screen's opening sloping in to the curved\n+glass, and its body tapering behind; the mouse lies to the computer's right. The printing is two textures drawn by\n+atari_st_print.py: the computer seen from above (planar-mapped onto every face facing up: the key tops and legends, the\n+function keys, the badge, the lights), the monitor's badge, the mouse's back and swatches; and the screens and the\n+floppy.\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: the right end, with the drive,\n+towards the viewer, the top tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m)\n+holding the set; the set holding the floppy (waiting in the drive, shrunk to nothing), the eject button, the lit drive\n+light, three screens over the glass (the floppy's window, black, and the title screen of a made-up game, Odyssey), and\n+the mouse, holding its left button. At rest the card is the set with GEM's desktop on the screen.\n+\n+The `focused` clip (8 s, looping) loads a game: the floppy slides into the drive and the drive light flickers; the\n+mouse shuffles and double-clicks, and the floppy's window opens; it shuffles and double-clicks again on ODYSSEY.PRG,\n+the screen goes black while the drive reads, and Odyssey's title screen comes up. Then the desktop's back, the eject\n+button goes in and the floppy pops out and goes.\n+\n+Seven materials (case, keys, monitor, dark, print, glass, disk) and two textures, within the system model budget. Run\n+from the repo root:\n+\n+    python tools/console-models/atari_st_print.py artifacts/atarist\n+    blender -b --factory-startup --python tools/console-models/atari_st.py -- artifacts/atarist godot/themes/console/models/systems/atarist.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bmesh\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import atari_st_layout as L  # noqa: E402\n+from amiga import curved, paint, rounded, solid  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, ease_out, key, phase, smooth  # noqa: E402\n+from mega_drive_cartridge import bevel, boolean, box, export, join, material, new_object, prism_x, triangles  # noqa: E402\n+from msx_layout import inset  # noqa: E402\n+\n+TILT_YAW = -25.0     # degrees about the vertical: the right end (the drive) comes towards the viewer\n+TILT_PITCH = 24.0    # degrees about the horizontal: the top tips up towards the viewer\n+ROUGHNESS = dict(case=0.6, keys=0.55, monitor=0.6, dark=0.8, print=0.55, glass=0.2, disk=0.45)\n+\n+# The deck's frame: x across, s up the slope from its front edge, h off it; and the band's.\n+DECK_FRAME = Matrix.Translation((0.0, L.DECK[0][0], L.DECK[0][1])) @ Matrix.Rotation(L.SLOPE, 4, \"X\")\n+BAND_FRAME = Matrix.Translation((0.0, L.BAND[0][0], L.BAND[0][1])) @ Matrix.Rotation(L.BAND_SLOPE, 4, \"X\")\n+\n+\n+# ---- UVs -------------------------------------------------------------------------------------------------------------\n+\n+def atlas_uv(region, w, h):\n+    \"\"\"A face drawn w x h into region: (a, b) millimetres across it (a to the right, b down from its top) to UVs.\"\"\"\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+TOP = atlas_uv(L.PRINT_REGIONS[\"top\"], L.WIDTH, L.DEPTH)\n+\n+\n+def top_uv(co):\n+    \"\"\"The computer seen from above.\"\"\"\n+    return TOP(co.x + L.WIDTH / 2, L.DEPTH - co.y)\n+\n+\n+def swatch(where):\n+    x, y = where\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+# ---- Geometry --------------------------------------------------------------------------------------------------------\n+\n+def on_frame(name, frame, outline, h0, h1, mat):\n+    \"\"\"A solid from an (x, s) outline in a sloping frame, from h0 to h1 off it.\"\"\"\n+    return solid(name, [[frame @ Vector((x, s, h)) for x, s in outline] for h in (h0, h1)], mat)\n+\n+\n+def clip(poly, x0, x1, y0, y1):\n+    \"\"\"A convex (x, y) polygon clipped to the rectangle x0..x1, y0..y1 (Sutherland and Hodgman).\"\"\"\n+    for axis, limit, keep_above in ((0, x0, True), (0, x1, False), (1, y0, True), (1, y1, False)):\n+        def inside(p):\n+            return p[axis] >= limit if keep_above else p[axis] <= limit\n+        out = []\n+        for i, p in enumerate(poly):\n+            q = poly[i - 1]\n+            if inside(p):\n+                if not inside(q):\n+                    t = (limit - q[axis]) / (p[axis] - q[axis])\n+                    out.append(tuple(q[k] + (p[k] - q[k]) * t for k in range(2)))\n+                out.append(p)\n+            elif inside(q):\n+                t = (limit - q[axis]) / (p[axis] - q[axis])\n+                out.append(tuple(q[k] + (p[k] - q[k]) * t for k in range(2)))\n+        poly = out\n+        if not poly:\n+            break\n+    return poly\n+\n+\n+def vent_grooves(m):\n+    \"\"\"The raised back's ribbed panel: grooves running at 45 degrees (x - y constant along each), each clipped to the\n+    panel.\"\"\"\n+    v = L.VENT\n+    (x0, x1), (y0, y1) = v[\"x\"], v[\"y\"]\n+    h = v[\"width\"] / math.sqrt(2)                  # half a groove's extent along x, its width across it being v[\"width\"]\n+    cutters = []\n+    c = x0 - y1\n+    while c < x1 - y0:\n+        strip = [(y0 + c - h, y0), (y0 + c + h, y0), (y1 + c + h, y1), (y1 + c - h, y1)]\n+        piece = clip(strip, x0, x1, y0, y1)\n+        if len(piece) >= 3:\n+            cutters.append(solid(\"rib\", [[(x, y, z) for x, y in piece] for z in (L.HEIGHT - v[\"depth\"], L.HEIGHT + 5.0)],\n+                                 m[\"case\"]))\n+        c += v[\"pitch\"] * math.sqrt(2)\n+    return join(cutters, \"ribs\")\n+\n+\n+def computer(m):\n+    \"\"\"The 520STFM: the body cut for its wells, the groove, the ribs and the drive, its top printed.\"\"\"\n+    body = prism_x(\"st\", L.profile(), -L.WIDTH / 2, L.WIDTH / 2, m[\"case\"])\n+    bevel(body, 1.8, segments=2)\n+    boolean(body, join([on_frame(\"well\", DECK_FRAME, outline, -L.WELL, 14.0, m[\"dark\"]) for outline in L.wells()],\n+                       \"wells\"))\n+    g0, g1, gd = L.GROOVE\n+    boolean(body, box(\"groove\", -L.WIDTH / 2 - 5.0, L.WIDTH / 2 + 5.0, g0, g1, L.HEIGHT - gd, L.HEIGHT + 5.0, m[\"dark\"]))\n+    boolean(body, vent_grooves(m))\n+    d = L.DRIVE\n+    edge = L.WIDTH / 2\n+    y0, y1, z0, z1 = d[\"slot\"]\n+    boolean(body, box(\"slot\", edge - d[\"depth\"], edge + 5.0, y0, y1, z0, z1, m[\"dark\"]))\n+    y0, y1, z0, z1 = d[\"bulge\"]\n+    boolean(body, box(\"bulge\", edge - 6.0, edge + 5.0, y0, y1, z0, z1, m[\"dark\"]))\n+    paint(body, m[\"print\"], top_uv, lambda c, n, name: n.z > 0.5 and name == \"case\")\n+    return body\n+\n+\n+def key_solid(name, outline, mat, m):\n+    \"\"\"A key: from the well's floor up its sides, tapering to its printed top.\"\"\"\n+    h = L.KEY_HEIGHT\n+    rings = [[(x, s, -L.WELL - 0.5) for x, s in outline], [(x, s, h - 4.5) for x, s in outline],\n+             [(x, s, h) for x, s in inset(outline, L.KEY_INSET)]]\n+    cap = solid(name, rings, mat)\n+    bevel(cap, 0.6, segments=1, angle=30)\n+    cap.data.transform(DECK_FRAME)\n+    paint(cap, m[\"print\"], top_uv, lambda c, n, _name: n.z > 0.9)\n+    return cap\n+\n+\n+def keys(m):\n+    \"\"\"The keys in their wells, RETURN among them, and the function keys' blades on the band.\"\"\"\n+    caps = []\n+    for name, xu, row, wu, _, *rest in L.KEYS:\n+        x0, s0, x1, s1 = L.key_rect(xu, row, wu, rest[0] if rest else 1)\n+        caps.append(key_solid(name, [(x0, s0), (x1, s0), (x1, s1), (x0, s1)], m[\"keys\"], m))\n+    caps.append(key_solid(\"return\", L.return_outline(), m[\"keys\"], m))\n+    f = L.FKEYS\n+    for k in range(10):\n+        x0 = f[\"x0\"] + k * f[\"width\"]\n+        x1 = x0 + f[\"width\"] - f[\"gap\"]\n+        (s0, s1), lean = f[\"s\"], f[\"lean\"]\n+        blade = on_frame(f\"f{k + 1}\", BAND_FRAME, [(x0, s0), (x1, s0), (x1 + lean, s1), (x0 + lean, s1)], -1.0,\n+                         f[\"height\"], m[\"case\"])\n+        bevel(blade, 0.5, segments=1)\n+        paint(blade, m[\"print\"], top_uv, lambda c, n, _name: n.z > 0.5)\n+        caps.append(blade)\n+    return caps\n+\n+\n+def eject_button(m):\n+    y0, y1, z0, z1, out = L.DRIVE[\"eject\"]\n+    button = box(\"eject\", L.WIDTH / 2 - 1.0, L.WIDTH / 2 + out, y0, y1, z0, z1, m[\"case\"])\n+    bevel(button, 0.5, segments=1)\n+    return button\n+\n+\n+def drive_light(m):\n+    \"\"\"The drive light lit: a bar just over the printed one on the band, its middle at its origin.\"\"\"\n+    x, s = L.LIGHTS[\"drive\"]\n+    w, d = L.LIGHTS[\"size\"]\n+    lit = on_frame(\"drive_light\", BAND_FRAME, [(x - w / 2 - 0.2, s - d / 2 - 0.2), (x + w / 2 + 0.2, s - d / 2 - 0.2),\n+                                               (x + w / 2 + 0.2, s + d / 2 + 0.2), (x - w / 2 - 0.2, s + d / 2 + 0.2)],\n+                   0.05, 0.3, m[\"print\"])\n+    paint(lit, m[\"print\"], swatch(L.PRINT_SWATCHES[\"lit\"]), lambda c, n, name: True)\n+    middle = BAND_FRAME @ Vector((x, s, 0.2))\n+    lit.data.transform(Matrix.Translation(-middle))\n+    return lit, middle\n+\n+\n+# ---- The monitor -----------------------------------------------------------------------------------------------------\n+\n+def monitor(m):\n+    \"\"\"The SC1224, its front's middle at the origin, facing -y: the frame and its opening, the body tapering behind,\n+    the glass, the badge and the light. Moved to its place.\"\"\"\n+    M = L.MONITOR\n+    w, h, fd = M[\"width\"], M[\"height\"], M[\"frame\"]\n+    b = M[\"body\"]\n+    back_w, back_h = b[\"back\"]\n+    front = rounded(0.0, h / 2, w, h, 14.0, 4)\n+    inner = rounded(0.0, h / 2, w - 24.0, h - 20.0, 12.0, 4)\n+    rear = rounded(0.0, b[\"lift\"] + back_h / 2, back_w, back_h, 12.0, 4)\n+    whole = solid(\"monitor\", [[(x, y, z) for x, z in outline] for outline, y in\n+                              ((front, 0.0), (front, fd), (inner, fd), (rear, b[\"depth\"]))], m[\"monitor\"])\n+    bevel(whole, 2.0, segments=2)\n+    o = M[\"opening\"]\n+    outer, inner = rounded(0.0, o[\"z\"], *o[\"outer\"]), rounded(0.0, o[\"z\"], *o[\"inner\"])\n+    boolean(whole, solid(\"opening\", [[(x, y, z) for x, z in outline] for outline, y in\n+                                     ((outer, -10.0), (outer, 0.5), (inner, o[\"depth\"]), (inner, o[\"depth\"] + 1.0))],\n+                         m[\"monitor\"]))\n+\n+    gw, gh = M[\"glass\"][\"size\"]\n+    glass = curved(\"glass\", gw, gh, M[\"glass\"][\"bulge\"], atlas_uv(L.MEDIA_REGIONS[\"desktop\"], 1.0, 1.0), m[\"glass\"])\n+    glass.data.transform(Matrix.Translation((0.0, o[\"depth\"], o[\"z\"])))\n+\n+    bm = bmesh.new()\n+    layer = bm.loops.layers.uv.new(\"UVMap\")\n+    (x0, x1), (z0, z1) = M[\"badge\"][\"x\"], M[\"badge\"][\"z\"]\n+    corners = [(x0, z0), (x1, z0), (x1, z1), (x0, z1)]                  # wound to face -y\n+    face = bm.faces.new([bm.verts.new((x, -0.15, z)) for x, z in corners])\n+    badge_uv = atlas_uv(L.PRINT_REGIONS[\"badge\"], x1 - x0, z1 - z0)\n+    for loop, (x, z) in zip(face.loops, corners):\n+        loop[layer].uv = badge_uv(x - x0, z1 - z)\n+    badge = new_object(\"badge\", bm, m[\"print\"])\n+    lx, lz = M[\"light\"]\n+    light = box(\"light\", lx - 4.0, lx + 4.0, -0.6, 0.5, lz - 1.0, lz + 1.0, m[\"print\"])\n+    paint(light, m[\"print\"], swatch(L.PRINT_SWATCHES[\"green\"]), lambda c, n, name: True)\n+\n+    parts = [whole, glass, badge, light]\n+    at = Matrix.Translation((M[\"at\"][0], M[\"at\"][1], 0.0))\n+    for obj in parts:\n+        obj.data.transform(at)\n+    return parts\n+\n+\n+def screen(m, name, uv):\n+    \"\"\"A screen over the glass, its middle at its origin (the clip opens and closes it about there).\"\"\"\n+    gw, gh = L.MONITOR[\"glass\"][\"size\"]\n+    return curved(name, gw, gh, L.MONITOR[\"glass\"][\"bulge\"], uv, m[\"glass\"])\n+\n+\n+# ---- The mouse and the floppy ----------------------------------------------------------------------------------------\n+\n+def mouse(m):\n+    \"\"\"The mouse, its middle at its origin, its buttons towards +y: the body with the right button, and the left\n+    button apart (and where it sits).\"\"\"\n+    w, d, h = L.MOUSE[\"size\"]\n+    b = L.MOUSE[\"buttons\"]\n+    body = box(\"mouse\", -w / 2, w / 2, -d / 2, d / 2, 0.0, h - 4.0, m[\"case\"])\n+    right = box(\"right_button\", 0.6, w / 2, d / 2 - b, d / 2, h - 6.0, h, m[\"case\"])\n+    body = join([body, right], \"mouse\")\n+    bevel(body, 2.5, segments=2)\n+    top = atlas_uv(L.PRINT_REGIONS[\"mouse\"], w, d)\n+    paint(body, m[\"print\"], lambda co: top(co.x + w / 2, d / 2 - co.y), lambda c, n, name: n.z > 0.9)\n+    left = box(\"mouse_button\", -w / 2, -0.6, -b / 2, b / 2, -2.0, 4.0, m[\"case\"])\n+    bevel(left, 1.2, segments=2)\n+    paint(left, m[\"print\"], lambda co: top(co.x + w / 2, b / 2 - co.y), lambda c, n, name: n.z > 0.9)\n+    return body, left, Vector((0.0, d / 2 - b / 2, h - 4.0))\n+\n+\n+def disk(m):\n+    \"\"\"The floppy, its middle at its origin, its shutter's edge towards -x (into the drive), its label up.\"\"\"\n+    l, w, t = L.DISK[\"length\"], L.DISK[\"width\"], L.DISK[\"thickness\"]\n+    shell = box(\"floppy\", -l / 2, l / 2, -w / 2, w / 2, -t / 2, t / 2, m[\"disk\"])\n+    bevel(shell, 0.8, segments=2)\n+    top, bottom = atlas_uv(L.MEDIA_REGIONS[\"disk_top\"], l, w), atlas_uv(L.MEDIA_REGIONS[\"disk_bottom\"], l, w)\n+    paint(shell, m[\"disk\"], lambda co: top(co.x + l / 2, w / 2 - co.y), lambda c, n, name: n.z > 0.9)\n+    paint(shell, m[\"disk\"], lambda co: bottom(co.x + l / 2, w / 2 + co.y), lambda c, n, name: n.z < -0.9)\n+    paint(shell, m[\"disk\"], swatch(L.MEDIA_SWATCHES[\"edge\"]), lambda c, n, name: abs(n.z) <= 0.9)\n+    return shell\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+def appear(f, f0, f1):\n+    return HIDDEN + (1 - HIDDEN) * back_out(phase(f, f0, f1)) if f >= f0 else HIDDEN\n+\n+\n+def flat(v):\n+    \"\"\"A screen squeezed to v of its height (switching on or off).\"\"\"\n+    return Pose(axes=(1.0, 1.0, max(HIDDEN, v)))\n+\n+\n+def tap(f, f0, hold=1):\n+    \"\"\"A click at f0: down in two frames, held, back up in two.\"\"\"\n+    return smooth(phase(f, f0, f0 + 2)) * (1 - smooth(phase(f, f0 + 2 + hold, f0 + 4 + hold)))\n+\n+\n+CLICKS = (58, 63, 92, 97)\n+\n+\n+def timeline(f):\n+    out = L.WIDTH / 2 + 24.0 + L.DISK[\"length\"] / 2 - L.SEAT[0]    # the floppy's middle beside the drive\n+\n+    # The floppy: appears beside the drive, slides in; later pops out and goes.\n+    if f < 8 or f >= 232:\n+        floppy = Pose(scale=HIDDEN)\n+    elif f < 18:\n+        floppy = Pose(x=out, scale=appear(f, 8, 16))\n+    elif f < 214:\n+        floppy = Pose(x=out * (1 - smooth(phase(f, 18, 34))))\n+    elif f < 222:\n+        floppy = Pose(x=40.0 * back_out(phase(f, 214, 220)))\n+    else:\n+        t = ease_in(phase(f, 222, 232))\n+        floppy = Pose(x=40.0 + 80.0 * t, z=24.0 * t, scale=max(HIDDEN, 1 - ease_in(phase(f, 226, 232))))\n+\n+    # The drive light, while the drive reads: on, with a short blink every few frames.\n+    reading = 34 <= f < 48 or 70 <= f < 78 or 104 <= f < 150 or 214 <= f < 218\n+    light = Pose() if reading and (f // 4) % 4 != 3 else Pose(scale=HIDDEN)\n+\n+    # The mouse: shuffles to the floppy's window and double-clicks, shuffles on and double-clicks ODYSSEY.PRG.\n+    shuffle_a = smooth(phase(f, 44, 56))\n+    shuffle_b = smooth(phase(f, 80, 90))\n+    back = smooth(phase(f, 196, 210))\n+    mouse = Pose(x=(7.0 * shuffle_a - 4.0 * shuffle_b) * (1 - back), y=(6.0 * shuffle_a - 5.0 * shuffle_b) * (1 - back))\n+    button = Pose(z=-1.6 * max(tap(f, f0) for f0 in CLICKS))\n+\n+    # The screens: the window opens after the first double-click; black while the game loads; the title opening.\n+    window = Pose() if 66 <= f < 102 else Pose(scale=HIDDEN)\n+    black = Pose() if 102 <= f < 150 else Pose(scale=HIDDEN)\n+    if f < 148 or f >= 206:\n+        title = Pose(scale=HIDDEN)\n+    elif f < 154:\n+        title = flat(0.02 + 0.98 * ease_out(phase(f, 148, 154)))\n+    elif f < 200:\n+        title = Pose()\n+    else:\n+        title = flat(1 - ease_in(phase(f, 200, 206)))\n+\n+    eject = 1.4 * smooth(phase(f, 210, 213)) * (1 - smooth(phase(f, 216, 220)))\n+    return {\"floppy\": floppy, \"drive_light\": light, \"mouse\": mouse, \"mouse_button\": button, \"screen_window\": window,\n+            \"screen_black\": black, \"screen_title\": title, \"eject\": Pose(x=-eject)}\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    printed = bpy.data.images.load(os.path.join(images, \"atari_st_print.png\"))\n+    media = bpy.data.images.load(os.path.join(images, \"atari_st_media.png\"))\n+    colours = dict(L.COLOURS, print=\"#FFFFFF\", glass=\"#FFFFFF\", disk=\"#FFFFFF\")\n+    textures = dict(print=printed, glass=media, disk=media)\n+    m = {n: material(n, colours[n], r, textures.get(n)) for n, r in ROUGHNESS.items()}\n+\n+    whole = join([computer(m)] + keys(m) + monitor(m), \"atarist\")\n+    eject = eject_button(m)\n+    lit, lit_at = drive_light(m)\n+    floppy = disk(m)\n+    rodent, button, button_at = mouse(m)\n+    unlit = swatch(L.MEDIA_SWATCHES[\"black\"])(None)\n+    screens = {\"screen_window\": screen(m, \"screen_window\", atlas_uv(L.MEDIA_REGIONS[\"window\"], 1.0, 1.0)),\n+               \"screen_black\": screen(m, \"screen_black\", lambda a, b: unlit),\n+               \"screen_title\": screen(m, \"screen_title\", atlas_uv(L.MEDIA_REGIONS[\"title\"], 1.0, 1.0))}\n+    for obj in [whole, eject, floppy, rodent, button]:\n+        for p in obj.data.polygons:\n+            p.use_smooth = True\n+        obj.data.set_sharp_from_angle(angle=math.radians(35))\n+\n+    # The node tree: the root (the view and the fit) holds the set; the set holds the moving parts at their places.\n+    root = bpy.data.objects.new(\"atarist_root\", None)\n+    scene.collection.objects.link(root)\n+    whole.parent = root\n+    M, o = L.MONITOR, L.MONITOR[\"opening\"]\n+    glass_front = Vector((M[\"at\"][0], M[\"at\"][1] + o[\"depth\"], o[\"z\"]))\n+    mx, my = L.MOUSE[\"at\"]\n+    placed = [(floppy, Vector(L.SEAT)), (lit, lit_at), (rodent, Vector((mx, my, 0.0)))]\n+    placed += [(part, glass_front + Vector((0.0, -0.5 - 0.4 * k, 0.0))) for k, part in enumerate(screens.values())]\n+    for part, where in placed:\n+        part.parent = whole\n+        part.location = where\n+    rodent.rotation_euler = (0.0, 0.0, math.radians(L.MOUSE[\"spin\"]))\n+    eject.parent = whole\n+    button.parent = rodent\n+    button.location = button_at\n+    nodes = {\"floppy\": floppy, \"drive_light\": lit, \"mouse\": rodent, \"mouse_button\": button, \"eject\": eject, **screens}\n+\n+    # The three-quarter view, then the spec's fit of the set at rest, as the root's transform.\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    rest = Matrix.Translation((mx, my, 0.0)) @ Matrix.Rotation(math.radians(L.MOUSE[\"spin\"]), 4, \"Z\")\n+    points = [tilt @ v.co for v in whole.data.vertices] + [tilt @ rest @ v.co for v in rodent.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [whole, eject, lit, floppy, rodent, button] + list(screens.values())\n+    print(f\"Atari ST: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(whole.data)} the set), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "ea307a9358985b64c76fcbbe9b6ead05c6c1da86",
          "filename": "tools/console-models/atari_st_layout.py",
          "status": "added",
          "additions": 187,
          "deletions": 0,
          "changes": 187,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari_st_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari_st_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fatari_st_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,187 @@\n+\"\"\"The Atari 520STFM, its SC1224 monitor and its mouse: the measurements shared by atari_st_print.py (which draws their\n+printing) and atari_st.py (which builds them). Plain Python: Blender's Python imports it too.\n+\n+Millimetres: x from the computer's middle (left to right), y back from its front edge, z up from the table. The\n+computer is 478 x 290 x 62 mm: the keyboard deck sloping up from the low front edge, the band behind it with the ten\n+function keys on its sloping face and the badge at its right, then, past a groove, the raised back, its top a panel of\n+diagonal ribs, the floppy drive in its right end. The monitor stands behind it, its front MONITOR[\"at\"], 356 x 279 mm,\n+its body cut from about 350 mm deep to 236 (the launcher fits a card's largest side). The mouse lies to the\n+computer's right. Measured from photographs: the Bibliotheque nationale de France's set of a 520ST (from above, each\n+side, the back), Evan-Amos's 1040STF with an SC1224 on Wikimedia Commons, and two SC1224s side by side; scaled by the\n+keys' 19.05 mm pitch and the SC1224's 279 mm height (the Computer History Museum's record).\n+\n+The keyboard is the UK one. Its keys are laid out in units of U from the main block's left edge (KEYS_X0), in rows\n+from the front (row 5, the space bar's) to the back (row 1, the digits'). The deck is a plane at SLOPE: a key stands on\n+it in the slope's frame (x across, s up the slope from the deck's front edge, h off it), and deck_point() maps a point\n+of that frame to the model's (y, z). The function keys stand on the band's face, a plane of its own (band_point()).\n+\n+Two textures. `print` (ATLAS square, PRINT_REGIONS): `top` (the computer seen from above: the key tops and legends, the\n+function keys, the badge and the lights; planar-mapped onto every face facing up), `badge` (the monitor's: ATARI and\n+SC1224), `mouse` (its back), and swatches. `media` (MEDIA_REGIONS): the screens, each the glass's whole width and\n+height with the ST's 320 x 200 in its middle (`desktop`, the glass at rest: GEM's desktop with its two floppy disks and\n+the trash; `window`, the floppy's window open over it, ODYSSEY.PRG chosen; `title`, the title screen of a made-up game,\n+Odyssey), and the floppy's two sides. A region is (x, y, width, height) in pixels.\n+\"\"\"\n+\n+import math\n+\n+ATLAS = 1024\n+\n+# -- The computer ----------------------------------------------------------------------------------------------------\n+\n+WIDTH, DEPTH, HEIGHT = 478.0, 290.0, 62.0\n+DECK = ((5.0, 27.0), (112.0, 40.0))     # the deck's front and back edges, (y, z)\n+SLOPE = math.atan2(DECK[1][1] - DECK[0][1], DECK[1][0] - DECK[0][0])\n+BAND = ((114.0, 45.0), (150.0, 60.0))   # the band's face: its front (bottom) and back (top) edges, (y, z)\n+GROOVE = (152.0, 157.0, 3.0)            # between the band and the raised back: y0, y1, depth\n+FOOT = 3.0                              # the dark foot band under the case\n+\n+\n+def profile():\n+    \"\"\"The computer's side outline, (y, z), anticlockwise seen from its right end.\"\"\"\n+    (fy, fz), (by, bz) = BAND\n+    return [(3.0, 0.0), (DEPTH - 3.0, 0.0), (DEPTH, 3.0), (DEPTH, HEIGHT - 5.0), (DEPTH - 5.0, HEIGHT),\n+            (by + 2.0, HEIGHT), (by, bz), (fy, fz), (DECK[1][0], DECK[1][1]), DECK[0], (0.0, DECK[0][1] - 4.0),\n+            (0.0, 3.0)]\n+\n+\n+def deck_point(s, h):\n+    \"\"\"The model's (y, z) of the point s up the deck's slope from its front edge and h off it.\"\"\"\n+    c, k = math.cos(SLOPE), math.sin(SLOPE)\n+    return DECK[0][0] + s * c - h * k, DECK[0][1] + s * k + h * c\n+\n+\n+BAND_SLOPE = math.atan2(BAND[1][1] - BAND[0][1], BAND[1][0] - BAND[0][0])\n+\n+\n+def band_point(s, h):\n+    \"\"\"The model's (y, z) of the point s up the band's face from its bottom edge and h off it.\"\"\"\n+    c, k = math.cos(BAND_SLOPE), math.sin(BAND_SLOPE)\n+    return BAND[0][0] + s * c - h * k, BAND[0][1] + s * k + h * c\n+\n+\n+VENT = dict(x=(-226.0, 226.0), y=(165.0, 283.0), depth=1.6, pitch=7.6, width=3.4)   # the ribbed panel on the top\n+DRIVE = dict(slot=(176.0, 268.0, 28.0, 33.0), bulge=(205.0, 239.0, 26.0, 37.0), depth=30.0,  # y0, y1, z0, z1\n+             eject=(252.0, 264.0, 15.0, 21.0, 1.6))                                         # ... and how far out\n+\n+# The keyboard: KEYS_X0 is the main block's left edge, U a key's pitch, GAP between two keys. A key stands from the\n+# well's floor (WELL below the deck) to its top, KEY_HEIGHT above the deck, tapering by KEY_INSET on every side.\n+U, GAP = 19.05, 1.0\n+KEYS_X0 = -222.0\n+ROW_S = {5: 11.0, 4: 30.05, 3: 49.1, 2: 68.15, 1: 87.2}   # each row's front edge, up the slope\n+WELL, KEY_HEIGHT, KEY_INSET = 3.0, 9.5, 2.2\n+CURSOR_U, PAD_U = 15.9, 19.2                              # where the cursor block and the keypad start, in units\n+\n+\n+def kx(u):\n+    return KEYS_X0 + u * U\n+\n+\n+def key_rect(xu, row, wu, rows=1):\n+    \"\"\"A key's base, (x0, s0, x1, s1) in the slope's frame (a tall key spans rows from row down).\"\"\"\n+    s0, s1 = ROW_S[row + rows - 1], ROW_S[row] + U\n+    return kx(xu) + GAP / 2, s0 + GAP / 2, kx(xu + wu) - GAP / 2, s1 - GAP / 2\n+\n+\n+def _row(names, x0, row, legends):\n+    return [(n, x0 + i, row, 1.0, legend) for i, (n, legend) in enumerate(zip(names, legends))]\n+\n+\n+# (name, x in units, row, width in units, legend[, rows]): a legend's line break splits its upper and lower characters.\n+# RETURN is an L over rows 2 and 3, its foot the wider (RETURN below).\n+KEYS = (\n+    [(\"esc\", 0.0, 1, 1.0, \"Esc\")]\n+    + _row([f\"d{k}\" for k in \"1234567890\"] + [\"minus\", \"equals\", \"grave\"], 1.0, 1,\n+           [\"!\\n1\", '\"\\n2', \"\u00a3\\n3\", \"$\\n4\", \"%\\n5\", \"^\\n6\", \"&\\n7\", \"*\\n8\", \"(\\n9\", \")\\n0\", \"_\\n-\", \"+\\n=\", \"~\\n`\"])\n+    + [(\"backspace\", 14.0, 1, 1.5, \"Backspace\")]\n+    + [(\"tab\", 0.0, 2, 1.5, \"Tab\")]\n+    + _row(list(\"qwertyuiop\") + [\"lbracket\", \"rbracket\"], 1.5, 2, list(\"QWERTYUIOP\") + [\"{\\n[\", \"}\\n]\"])\n+    + [(\"delete\", 14.5, 2, 1.0, \"Delete\")]\n+    + [(\"control\", 0.0, 3, 1.75, \"Control\")]\n+    + _row(list(\"asdfghjkl\") + [\"semicolon\", \"quote\"], 1.75, 3, list(\"ASDFGHJKL\") + [\":\\n;\", \"@\\n'\"])\n+    + [(\"hash\", 14.5, 3, 1.0, \"~\\n#\")]\n+    + [(\"lshift\", 0.0, 4, 1.25, \"Shift\")]\n+    + _row([\"backslash\"] + list(\"zxcvbnm\") + [\"comma\", \"period\", \"slash\"], 1.25, 4,\n+           [\"|\\n\\\\\"] + list(\"ZXCVBNM\") + [\"<\\n,\", \">\\n.\", \"?\\n/\"])\n+    + [(\"rshift\", 12.25, 4, 1.75, \"Shift\")]\n+    + [(\"alternate\", 1.5, 5, 1.5, \"Alternate\"), (\"space\", 3.0, 5, 8.75, \"\"), (\"caps\", 11.75, 5, 1.5, \"CapsLock\")]\n+    + [(\"help\", CURSOR_U, 1, 1.5, \"Help\"), (\"undo\", CURSOR_U + 1.5, 1, 1.5, \"Undo\")]\n+    + _row([\"insert\", \"up\", \"home\"], CURSOR_U, 2, [\"Insert\", \"\u2191\", \"Clr\\nHome\"])\n+    + _row([\"left\", \"down\", \"right\"], CURSOR_U, 3, [\"\u2190\", \"\u2193\", \"\u2192\"])\n+    + _row([\"np_lpar\", \"np_rpar\", \"np_slash\", \"np_star\"], PAD_U, 1, [\"(\", \")\", \"/\", \"*\"])\n+    + _row([\"np7\", \"np8\", \"np9\", \"np_minus\"], PAD_U, 2, [\"7\", \"8\", \"9\", \"-\"])\n+    + _row([\"np4\", \"np5\", \"np6\", \"np_plus\"], PAD_U, 3, [\"4\", \"5\", \"6\", \"+\"])\n+    + _row([\"np1\", \"np2\", \"np3\"], PAD_U, 4, [\"1\", \"2\", \"3\"])\n+    + [(\"np_enter\", PAD_U + 3.0, 4, 1.0, \"Enter\", 2), (\"np0\", PAD_U, 5, 2.0, \"0\"), (\"np_dot\", PAD_U + 2.0, 5, 1.0, \".\")]\n+)\n+RETURN = dict(top=(13.5, 14.5), foot=(12.75, 14.5))       # its two rows' x, in units: row 3's foot the wider\n+\n+\n+def return_outline():\n+    \"\"\"RETURN's base, an (x, s) outline anticlockwise from its foot's front left corner.\"\"\"\n+    (a, b), (c, _) = RETURN[\"top\"], RETURN[\"foot\"]\n+    g = GAP / 2\n+    s3, s2, s1 = ROW_S[3] + g, ROW_S[2] + g, ROW_S[2] + U - g\n+    return [(kx(c) + g, s3), (kx(b) - g, s3), (kx(b) - g, s1), (kx(a) + g, s1), (kx(a) + g, s2), (kx(c) + g, s2)]\n+\n+\n+def wells():\n+    \"\"\"The wells the keys stand in, each an (x, s) outline: the main block (its bottom row starting in, ending short),\n+    the cursor block, and the keypad.\"\"\"\n+    m = 1.2\n+    s5, s4, s3, s1 = ROW_S[5] - m, ROW_S[4], ROW_S[3], ROW_S[1] + U + m\n+\n+    def rect(x0, x1, a, b):\n+        return [(x0, a), (x1, a), (x1, b), (x0, b)]\n+    return [\n+        [(kx(1.5) - m, s5), (kx(13.25) + m, s5), (kx(13.25) + m, s4 - m), (kx(14.0) + m, s4 - m), (kx(14.0) + m, s3 - m),\n+         (kx(15.5) + m, s3 - m), (kx(15.5) + m, s1), (kx(0) - m, s1), (kx(0) - m, s4 - m), (kx(1.5) - m, s4 - m)],\n+        rect(kx(CURSOR_U) - m, kx(CURSOR_U + 3.0) + m, ROW_S[3] - m, s1),\n+        rect(kx(PAD_U) - m, kx(PAD_U + 4.0) + m, s5, s1),\n+    ]\n+\n+\n+# The function keys: ten slanted blades on the band's face, F1's left edge at FKEYS[\"x0\"], each `width` across and\n+# leaning `lean` to the right at its top.\n+FKEYS = dict(x0=-216.0, width=31.4, gap=1.4, s=(5.0, 30.0), height=2.4, lean=9.0)\n+BADGE = dict(x=(117.0, 221.0), s=(17.0, 28.0))          # ATARI 520STFM on the band's face, right of the keys\n+LIGHTS = dict(drive=(200.0, 9.0), power=(-211.0, 4.0), size=(9.0, 1.6))  # x and s on the band (drive), the deck (power)\n+\n+# -- The monitor -----------------------------------------------------------------------------------------------------\n+\n+MONITOR = dict(\n+    at=(0.0, 300.0),                    # its front's middle, in the computer's (x, y)\n+    width=356.0, height=279.0, frame=36.0,                      # the front frame's size and depth\n+    body=dict(back=(270.0, 196.0), depth=236.0, lift=30.0),     # the body: its back's width and height; the whole\n+                                                                # monitor's depth; how high its back's bottom is\n+    opening=dict(z=130.0, outer=(282.0, 214.0, 16.0), inner=(266.0, 199.0, 10.0), depth=22.0),\n+    glass=dict(size=(268.0, 201.0), bulge=7.0),\n+    badge=dict(x=(-150.0, -36.0), z=(9.0, 21.0)), light=(118.0, 15.0),\n+)\n+\n+# -- The mouse and the floppy ----------------------------------------------------------------------------------------\n+\n+MOUSE = dict(at=(290.0, 74.0), size=(64.0, 100.0, 30.0), buttons=36.0, spin=-8.0)   # its middle; w, d, h; turned\n+DISK = dict(length=94.0, width=90.0, thickness=3.3)\n+SEAT = (WIDTH / 2 - DISK[\"length\"] / 2 - 10.0, (DRIVE[\"slot\"][0] + DRIVE[\"slot\"][1]) / 2,\n+        (DRIVE[\"slot\"][2] + DRIVE[\"slot\"][3]) / 2)\n+\n+# -- The atlases -----------------------------------------------------------------------------------------------------\n+\n+COLOURS = dict(case=\"#B2AA95\", keys=\"#CFC8B2\", top_keys=\"#DCD6C2\", vent=\"#9A9381\", monitor=\"#B4BAB2\", dark=\"#24231F\",\n+               disk=\"#3C3E44\")\n+\n+PRINT_REGIONS = dict(top=(0, 0, 1024, 632), badge=(0, 640, 400, 44), mouse=(408, 640, 160, 250))\n+PRINT_SWATCHES = dict(lit=(600, 664), off=(632, 664), green=(664, 664), dark=(696, 664), vent=(728, 664),\n+                      monitor=(760, 664))\n+\n+SCREEN_PX = (640, 480)       # a screen's region, the glass's proportions\n+RASTER = (576, 400)          # the lit picture in its middle; the ST's 320 x 200 drawn into it\n+MEDIA_REGIONS = dict(desktop=(0, 0) + SCREEN_PX, window=(0, 488) + SCREEN_PX, title=(648, 0, 376, 282),\n+                     disk_top=(648, 290, 376, 360), disk_bottom=(648, 658, 376, 360))\n+MEDIA_SWATCHES = dict(black=(24, 996), edge=(56, 996))\n+\n+# -- The focused clip ------------------------------------------------------------------------------------------------\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s"
        },
        {
          "sha": "3ebbcac2d4dec2f13282b4a789c35663c3ff92fe",
          "filename": "tools/console-models/atari_st_print.py",
          "status": "added",
          "additions": 424,
          "deletions": 0,
          "changes": 424,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari_st_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fatari_st_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fatari_st_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,424 @@\n+\"\"\"The Atari 520STFM's, its SC1224's and its mouse's printing, for the console theme's Atari ST model\n+(tools/console-models/atari_st.py).\n+\n+Draws its two atlases, their regions as atari_st_layout.py places them. atari_st_print.png: the computer seen from\n+above, in millimetres as the model's planar UVs map it (each key's top with its legend, the function keys' blades,\n+the badge, the power light lit and the drive light off), the monitor's badge (ATARI and SC1224), the mouse's back, and\n+the swatches small parts take their colour from. atari_st_media.png: the screens, each the glass's whole width and\n+height, a dark border round the ST's 320 x 200 (GEM's desktop as TOS shows it, green, its two floppy disks and the\n+trash; the floppy's window open over it, ODYSSEY.PRG chosen; and the title screen of a made-up game, Odyssey), and the\n+floppy's two sides. Run it with the system Python (Pillow):\n+\n+    python tools/console-models/atari_st_print.py <out folder>\n+\n+It writes atari_st_print.png and atari_st_media.png there.\n+\"\"\"\n+\n+import math\n+import os\n+import random\n+import sys\n+\n+from PIL import Image, ImageDraw, ImageFilter, ImageFont\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import atari_st_layout as L  # noqa: E402\n+from msx_layout import inset  # noqa: E402\n+from psp_print import ship  # noqa: E402\n+from retro_pc_print import rgb, shade  # noqa: E402\n+from windows_pc_print import FONTS, Region  # noqa: E402\n+\n+CASE = rgb(L.COLOURS[\"case\"])\n+LEGEND = (70, 68, 62)\n+BLUE = (40, 120, 220)\n+\n+\n+def top_point(x, y):\n+    \"\"\"A point of the computer (x from its middle, y back from its front) in the top region's millimetres.\"\"\"\n+    return x + L.WIDTH / 2, L.DEPTH - y\n+\n+\n+def fuji(reg, x, y, size, colour):\n+    \"\"\"Atari's mark, size tall, its middle at (x, y): three columns, the outer two flaring out at their feet.\"\"\"\n+    s = size\n+    reg.rect_(x - 0.08 * s, y - 0.5 * s, x + 0.08 * s, y + 0.5 * s, colour)\n+    for side in (-1, 1):\n+        pts = [(x + side * 0.16 * s, y - 0.5 * s), (x + side * 0.3 * s, y - 0.5 * s), (x + side * 0.3 * s, y - 0.05 * s),\n+               (x + side * 0.58 * s, y + 0.5 * s), (x + side * 0.4 * s, y + 0.5 * s), (x + side * 0.16 * s, y + 0.05 * s)]\n+        reg.poly(pts, colour)\n+\n+\n+def slanted(reg, text, font, size, x, y, fill, shear=0.18, anchor=\"lm\"):\n+    layer = reg.layer()\n+    f = ImageFont.truetype(FONTS + font, max(1, round(size * reg.sx)))\n+    ImageDraw.Draw(layer).text(reg.p(x, y), text, font=f, fill=fill + (255,), anchor=anchor)\n+    yc = reg.p(0, y)[1]\n+    reg.over(layer.transform(layer.size, Image.AFFINE, (1, shear, -shear * yc, 0, 1, 0)))\n+\n+\n+def badge_plate(reg, x0, y0, x1, y1, model):\n+    \"\"\"The slanted plate: Atari's mark and ATARI in blue, the model in black, on silver.\"\"\"\n+    lean = (y1 - y0) * 0.9\n+    reg.poly([(x0 + lean, y0), (x1, y0), (x1 - lean, y1), (x0, y1)], (224, 226, 228))\n+    h = y1 - y0\n+    fuji(reg, x0 + lean + 2.6, (y0 + y1) / 2, h * 0.62, (30, 30, 34))\n+    slanted(reg, \"ATARI\", \"arialbd.ttf\", h * 0.8, x0 + lean + 6.4, (y0 + y1) / 2, BLUE, shear=0.0)\n+    slanted(reg, model, \"arialbd.ttf\", h * 0.82, x1 - lean - 1.0, (y0 + y1) / 2, (30, 30, 34), anchor=\"rm\")\n+\n+\n+# ---- The computer's top ----------------------------------------------------------------------------------------------\n+\n+def deck_top(points, h):\n+    \"\"\"An (x, s) outline on the deck, raised h off it, in the top region's millimetres.\"\"\"\n+    return [top_point(x, L.deck_point(s, h)[0]) for x, s in points]\n+\n+\n+def key_top(reg, name, outline, legend):\n+    fill = rgb(L.COLOURS[\"top_keys\"])\n+    xs = [x for x, _ in outline]\n+    ss = [s for _, s in outline]\n+    i = L.KEY_INSET\n+    inner = [(min(xs) + i, min(ss) + i), (max(xs) - i, min(ss) + i), (max(xs) - i, max(ss) - i), (min(xs) + i, max(ss) - i)]\n+    if name == \"return\":\n+        inner = inset(outline, i)\n+    pts = deck_top(inner, L.KEY_HEIGHT)\n+    reg.poly(pts, fill)\n+    xs = [x for x, _ in pts]\n+    ys = [y for _, y in pts]\n+    x0, y0, x1, y1 = min(xs), min(ys), max(xs), max(ys)\n+    if name != \"return\":\n+        reg.rect_(x0 + 0.9, y0 + 0.9, x1 - 0.9, y1 - 0.9, shade(fill, 0.04), r=1.2)\n+    if not legend:\n+        return\n+    lines = legend.split(\"\\n\")\n+    if name == \"return\":\n+        x0 = top_point(L.kx(L.RETURN[\"foot\"][0]), 0.0)[0]\n+        y0 = deck_top([(0.0, L.ROW_S[3] + L.U - i)], L.KEY_HEIGHT)[0][1]\n+        reg.text(\"Return\", \"arial.ttf\", 2.1, x0 + 1.8, y0 + 2.6, LEGEND, anchor=\"lm\")\n+        return\n+    if len(lines) == 1 and len(legend) == 1 and not legend.isalnum() and name.startswith((\"up\", \"down\", \"left\", \"right\")):\n+        reg.text(legend, \"seguisym.ttf\", 3.6, (x0 + x1) / 2, (y0 + y1) / 2, LEGEND)\n+        return\n+    if len(lines) == 1 and len(legend) == 1:\n+        reg.text(legend, \"arial.ttf\", 3.6 if legend.isalpha() else 3.2, (x0 + x1) / 2 if name.startswith(\"np\")\n+                 else x0 + 2.6, (y0 + y1) / 2 if name.startswith(\"np\") else y0 + 3.0, LEGEND)\n+        return\n+    size = 2.1 if max(len(line) for line in lines) > 2 else 2.8\n+    for k, line in enumerate(lines):\n+        reg.text(line, \"arial.ttf\", size, x0 + 1.6, y0 + 2.4 + k * (size + 1.1), LEGEND, anchor=\"lm\")\n+\n+\n+def band_top(points):\n+    \"\"\"An (x, s) outline on the band's face, in the top region's millimetres.\"\"\"\n+    return [top_point(x, L.band_point(s, 0.0)[0]) for x, s in points]\n+\n+\n+def fkey_outline(k):\n+    f = L.FKEYS\n+    x0 = f[\"x0\"] + k * f[\"width\"]\n+    x1 = x0 + f[\"width\"] - f[\"gap\"]\n+    (s0, s1), lean = f[\"s\"], f[\"lean\"]\n+    return [(x0, s0), (x1, s0), (x1 + lean, s1), (x0 + lean, s1)]\n+\n+\n+def top():\n+    reg = Region(L.PRINT_REGIONS[\"top\"], L.WIDTH, L.DEPTH, CASE)\n+    (vx0, vx1), (vy0, vy1) = L.VENT[\"x\"], L.VENT[\"y\"]\n+    vent = rgb(L.COLOURS[\"vent\"])\n+    reg.rect_(*top_point(vx0, vy1), *top_point(vx1, vy0), vent)\n+    layer = reg.layer()                                     # the grooves' floors, darker, at 45 degrees (x - y constant)\n+    d = ImageDraw.Draw(layer)\n+    h = L.VENT[\"width\"] / math.sqrt(2)\n+    c = vx0 - vy1\n+    while c < vx1 - vy0:\n+        d.polygon([reg.p(*top_point(x, y)) for x, y in ((vy0 + c - h, vy0), (vy0 + c + h, vy0), (vy1 + c + h, vy1),\n+                                                         (vy1 + c - h, vy1))], fill=shade(vent, -0.3) + (255,))\n+        c += L.VENT[\"pitch\"] * math.sqrt(2)\n+    mask = Image.new(\"L\", layer.size, 0)\n+    ImageDraw.Draw(mask).rectangle(reg.box(*top_point(vx0, vy1), *top_point(vx1, vy0)), fill=255)\n+    reg.img.paste(layer, (0, 0), Image.composite(layer.getchannel(\"A\"), Image.new(\"L\", layer.size, 0), mask))\n+    for name, xu, row, wu, legend, *rest in L.KEYS:\n+        x0, s0, x1, s1 = L.key_rect(xu, row, wu, rest[0] if rest else 1)\n+        key_top(reg, name, [(x0, s0), (x1, s0), (x1, s1), (x0, s1)], legend)\n+    key_top(reg, \"return\", L.return_outline(), \"Return\")\n+\n+    # The function keys: slanted blades, each a shade lighter at its top, its name at its foot.\n+    for k in range(10):\n+        pts = band_top(fkey_outline(k))\n+        reg.poly(pts, shade(CASE, 0.08))\n+        a, b = pts[0], pts[1]\n+        reg.line([a, b], shade(CASE, -0.3), 0.3)\n+        reg.text(f\"F{k + 1}\", \"arial.ttf\", 1.9, (pts[0][0] + pts[3][0]) / 2 + 4.0, (pts[0][1] + pts[3][1]) / 2 + 0.6,\n+                 shade(CASE, -0.25))\n+\n+    (bx0, bx1), (bs0, bs1) = L.BADGE[\"x\"], L.BADGE[\"s\"]\n+    (_, by1), (_, by0) = top_point(0, L.band_point(bs0, 0)[0]), top_point(0, L.band_point(bs1, 0)[0])\n+    badge_plate(reg, top_point(bx0, 0)[0], by0, top_point(bx1, 0)[0], by1, \"520STFM\")\n+    w, d = L.LIGHTS[\"size\"]\n+    for name, colour, on in ((\"drive\", (110, 48, 30), False), (\"power\", (120, 255, 110), True)):\n+        x, s = L.LIGHTS[name]\n+        y = L.band_point(s, 0)[0] if name == \"drive\" else L.deck_point(s, 0)[0]\n+        cx, cy = top_point(x, y)\n+        if on:\n+            glow = reg.layer()\n+            ImageDraw.Draw(glow).rounded_rectangle(reg.box(cx - w / 2 - 2, cy - d - 1, cx + w / 2 + 2, cy + d + 1),\n+                                                   radius=2 * reg.sx, fill=colour + (110,))\n+            reg.over(glow.filter(ImageFilter.GaussianBlur(1.2 * reg.sx)))\n+        reg.rect_(cx - w / 2, cy - d / 2, cx + w / 2, cy + d / 2, colour, r=0.4)\n+    return reg\n+\n+\n+def monitor_badge():\n+    b = L.MONITOR[\"badge\"]\n+    w, h = b[\"x\"][1] - b[\"x\"][0], b[\"z\"][1] - b[\"z\"][0]\n+    reg = Region(L.PRINT_REGIONS[\"badge\"], w, h, rgb(L.COLOURS[\"monitor\"]))\n+    fuji(reg, 4.0, h / 2, h * 0.6, (40, 40, 44))\n+    reg.text(\"ATARI\", \"arialbd.ttf\", h * 0.62, 8.0, h / 2, BLUE, anchor=\"lm\")\n+    lean = h * 0.7\n+    reg.poly([(44.0 + lean, 1.5), (w - 2.0, 1.5), (w - 2.0 - lean, h - 1.5), (44.0, h - 1.5)], (205, 208, 206))\n+    slanted(reg, \"SC1224\", \"arialbd.ttf\", h * 0.6, w - 6.0 - lean, h / 2, (70, 72, 76), anchor=\"rm\")\n+    return reg\n+\n+\n+def mouse_top():\n+    w, d, _ = L.MOUSE[\"size\"]\n+    reg = Region(L.PRINT_REGIONS[\"mouse\"], w, d, rgb(L.COLOURS[\"case\"]))\n+    b = L.MOUSE[\"buttons\"]\n+    reg.rect_(0.0, 0.0, w, b, shade(CASE, 0.05))\n+    reg.rect_(w / 2 - 0.4, 0.0, w / 2 + 0.4, b, shade(CASE, -0.35))\n+    reg.rect_(0.0, b - 0.4, w, b + 0.4, shade(CASE, -0.35))\n+    fuji(reg, w / 2, d - 20.0, 9.0, shade(CASE, -0.2))\n+    return reg\n+\n+\n+def print_atlas(out):\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), CASE)\n+    for reg in (top(), monitor_badge(), mouse_top()):\n+        reg.paste(image)\n+    d = ImageDraw.Draw(image)\n+    for name, colour in ((\"lit\", (255, 150, 60)), (\"off\", (110, 48, 30)), (\"green\", (120, 255, 110)),\n+                         (\"dark\", rgb(L.COLOURS[\"dark\"])), (\"vent\", rgb(L.COLOURS[\"vent\"])),\n+                         (\"monitor\", rgb(L.COLOURS[\"monitor\"]))):\n+        x, y = L.PRINT_SWATCHES[name]\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colour)\n+    path = os.path.join(out, \"atari_st_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+# ---- The screens -----------------------------------------------------------------------------------------------------\n+\n+WHITE, BLACK = (255, 255, 255), (0, 0, 0)\n+GREEN = (0, 219, 0)                    # the ST's desktop green: 0, 7, 0 on its 3-bit scale, a shade under full\n+FONT = ImageFont.truetype(FONTS + \"consolab.ttf\", 9)\n+\n+\n+def st_text(d, x, y, s, fill):\n+    d.text((x, y), s, font=FONT, fill=fill, anchor=\"lm\")\n+\n+\n+def pointer(d, x, y):\n+    arrow = [(0, 0), (0, 11), (3, 8), (5, 13), (7, 12), (5, 7), (9, 7)]\n+    d.polygon([(x + a, y + b) for a, b in arrow], fill=BLACK, outline=WHITE)\n+\n+\n+def caption(d, x, y, text, inverted=False):\n+    w = len(text) * 5 + 4\n+    d.rectangle([x - w // 2, y - 4, x + w // 2, y + 4], fill=BLACK if inverted else WHITE)\n+    small = ImageFont.truetype(FONTS + \"consola.ttf\", 7)\n+    d.text((x, y), text, font=small, fill=WHITE if inverted else BLACK, anchor=\"mm\")\n+\n+\n+def disk_icon(d, x, y, letter):\n+    \"\"\"TOS's floppy disk: a drawer, its slot and its letter, its name under it.\"\"\"\n+    d.rectangle([x - 12, y - 10, x + 12, y + 8], fill=WHITE, outline=BLACK)\n+    d.rectangle([x - 9, y - 7, x + 9, y + 1], outline=BLACK)\n+    d.rectangle([x - 5, y + 3, x + 5, y + 5], fill=BLACK)\n+    st_text(d, x - 2, y - 3, letter, BLACK)\n+    caption(d, x, y + 15, \"FLOPPY DISK\")\n+\n+\n+def trash_icon(d, x, y):\n+    d.rectangle([x - 8, y - 12, x + 8, y - 10], fill=WHITE, outline=BLACK)\n+    d.polygon([(x - 7, y - 9), (x + 7, y - 9), (x + 6, y + 8), (x - 6, y + 8)], fill=WHITE, outline=BLACK)\n+    for k in (-3, 0, 3):\n+        d.line([(x + k, y - 6), (x + k, y + 5)], fill=BLACK)\n+    caption(d, x, y + 15, \"TRASH\")\n+\n+\n+def desktop(with_pointer=True):\n+    img = Image.new(\"RGB\", (320, 200), GREEN)\n+    d = ImageDraw.Draw(img)\n+    d.fontmode = \"1\"\n+    d.rectangle([0, 0, 319, 9], fill=WHITE)\n+    d.line([(0, 9), (319, 9)], fill=BLACK)\n+    for x, word in ((4, \"Desk\"), (40, \"File\"), (76, \"View\"), (112, \"Options\")):\n+        st_text(d, x, 5, word, BLACK)\n+    disk_icon(d, 24, 30, \"A\")\n+    disk_icon(d, 24, 70, \"B\")\n+    trash_icon(d, 24, 172)\n+    if with_pointer:\n+        pointer(d, 196, 112)\n+    return img\n+\n+\n+def window():\n+    img = desktop(with_pointer=False)\n+    d = ImageDraw.Draw(img)\n+    d.fontmode = \"1\"\n+    x0, y0, x1, y1 = 70, 26, 300, 150\n+    d.rectangle([x0, y0, x1, y1], fill=WHITE, outline=BLACK)\n+    for k in range(0, 9, 2):                               # the title bar's stripes\n+        d.line([(x0 + 11, y0 + 1 + k), (x1 - 11, y0 + 1 + k)], fill=BLACK)\n+    d.rectangle([x0 + 120 - 16, y0 + 1, x0 + 120 + 16, y0 + 9], fill=WHITE)\n+    st_text(d, x0 + 109, y0 + 5, \"A:\\\\\", BLACK)\n+    d.rectangle([x0, y0, x0 + 10, y0 + 10], fill=WHITE, outline=BLACK)\n+    d.rectangle([x0 + 3, y0 + 3, x0 + 7, y0 + 7], outline=BLACK)\n+    d.rectangle([x1 - 10, y0, x1, y0 + 10], fill=WHITE, outline=BLACK)\n+    d.line([(x0, y0 + 10), (x1, y0 + 10)], fill=BLACK)\n+    small = ImageFont.truetype(FONTS + \"consola.ttf\", 8)\n+    d.text((x0 + 4, y0 + 15), \"61440 bytes used in 3 items.\", font=small, fill=BLACK, anchor=\"lm\")\n+    d.line([(x0, y0 + 20), (x1, y0 + 20)], fill=BLACK)\n+    d.line([(x1 - 10, y0 + 20), (x1 - 10, y1)], fill=BLACK)\n+    d.rectangle([x1 - 9, y0 + 32, x1 - 1, y0 + 70], fill=(170, 170, 170))\n+    d.line([(x0, y1 - 10), (x1, y1 - 10)], fill=BLACK)\n+\n+    def icon(cx, cy, kind, name, chosen=False):\n+        ink, paper = (WHITE, BLACK) if chosen else (BLACK, WHITE)\n+        if kind == \"prg\":\n+            d.rectangle([cx - 11, cy - 10, cx + 11, cy + 8], fill=paper, outline=ink)\n+            d.rectangle([cx - 11, cy - 10, cx + 11, cy - 6], fill=ink)\n+        elif kind == \"txt\":\n+            d.polygon([(cx - 8, cy - 10), (cx + 4, cy - 10), (cx + 8, cy - 6), (cx + 8, cy + 8), (cx - 8, cy + 8)],\n+                      fill=paper, outline=ink)\n+            for k in range(4):\n+                d.line([(cx - 5, cy - 4 + 3 * k), (cx + 5, cy - 4 + 3 * k)], fill=ink)\n+        else:\n+            d.polygon([(cx - 11, cy - 6), (cx - 4, cy - 6), (cx - 2, cy - 9), (cx + 11, cy - 9), (cx + 11, cy + 8),\n+                       (cx - 11, cy + 8)], fill=paper, outline=ink)\n+        caption(d, cx, cy + 15, name, inverted=chosen)\n+    icon(x0 + 34, y0 + 46, \"prg\", \"ODYSSEY.PRG\", chosen=True)\n+    icon(x0 + 94, y0 + 46, \"txt\", \"READ_ME.TXT\")\n+    icon(x0 + 154, y0 + 46, \"dir\", \"SAVES\")\n+    pointer(d, x0 + 40, y0 + 44)\n+    return img\n+\n+\n+def title_screen():\n+    \"\"\"Odyssey's title screen, 320 x 200: the sky banded as the ST's 3-bit palette bands it, stars, a ringed planet,\n+    the ship, the title in raster-bar colours, PRESS FIRE.\"\"\"\n+    w, h = 320, 200\n+    img = Image.new(\"RGB\", (w, h))\n+    d = ImageDraw.Draw(img)\n+    d.fontmode = \"1\"\n+    stops = [(0.0, (0, 0, 2)), (0.5, (3, 0, 5)), (0.8, (6, 1, 4)), (1.0, (7, 4, 2))]   # 0-7 per channel\n+    for y in range(h):\n+        t = y / h\n+        for (t0, c0), (t1, c1) in zip(stops, stops[1:]):\n+            if t0 <= t <= t1:\n+                u = (t - t0) / (t1 - t0)\n+                colour = tuple(round(a + (b - a) * u) * 255 // 7 for a, b in zip(c0, c1))\n+        d.line([(0, y), (w, y)], fill=colour)\n+    rng = random.Random(1040)\n+    for _ in range(80):\n+        x, y = rng.randrange(w), rng.randrange(120)\n+        d.point((x, y), fill=(255, 255, 255) if rng.random() < 0.5 else (146, 182, 255))\n+    d.ellipse([206, 96, 300, 190], fill=(255, 109, 36))\n+    d.ellipse([212, 100, 284, 166], fill=(255, 146, 72))\n+    d.arc([174, 128, 332, 158], 160, 380, fill=(255, 219, 182), width=3)\n+    ship(d, 118, 140, 2.0, (255, 255, 255), (255, 182, 0))\n+    for k in range(4):\n+        d.line([(46 - k * 8, 136 + k * 3), (86 - k * 5, 136 + k * 3)], fill=(255, 219, 109), width=2)\n+    font = ImageFont.truetype(FONTS + \"ariblk.ttf\", 40)\n+    mask = Image.new(\"L\", (w, 60), 0)\n+    dm = ImageDraw.Draw(mask)\n+    dm.fontmode = \"1\"\n+    dm.text((w / 2, 30), \"ODYSSEY\", font=font, fill=255, anchor=\"mm\")\n+    bars = Image.new(\"RGB\", (w, 60))\n+    db = ImageDraw.Draw(bars)\n+    for y in range(60):                                       # the raster bars through the letters\n+        t = abs(math.sin(y / 60 * math.pi * 2.0))\n+        db.line([(0, y), (w, y)], fill=(255, round(73 + 182 * t) // 36 * 36, round(255 * (1 - t)) // 36 * 36))\n+    img.paste((36, 0, 73), (2, 14), mask)\n+    img.paste(bars, (0, 12), mask)\n+    d = ImageDraw.Draw(img)\n+    d.fontmode = \"1\"\n+    d.text((w / 2, 178), \"PRESS FIRE TO START\", font=ImageFont.truetype(FONTS + \"consolab.ttf\", 11),\n+           fill=(255, 255, 109), anchor=\"mm\")\n+    return img\n+\n+\n+def glass_image(raster, size):\n+    \"\"\"A screen's region: the tube's dark border, the 320 x 200 picture in its middle.\"\"\"\n+    sw, sh = L.SCREEN_PX\n+    rw, rh = L.RASTER\n+    image = Image.new(\"RGB\", (sw, sh), (20, 22, 21))\n+    picture = raster.resize((rw * 2, rh * 2), Image.NEAREST).resize((rw, rh), Image.LANCZOS)\n+    image.paste(picture, ((sw - rw) // 2, (sh - rh) // 2))\n+    return image.resize(size, Image.LANCZOS)\n+\n+\n+# ---- The floppy ------------------------------------------------------------------------------------------------------\n+\n+def disk_side(rect, label):\n+    \"\"\"One side of the floppy, seen looking at it, its shutter's edge to the left: the shell, the shutter, and either\n+    the label (the label side) or the hub.\"\"\"\n+    grey = rgb(L.COLOURS[\"disk\"])\n+    reg = Region(rect, L.DISK[\"length\"], L.DISK[\"width\"], grey)\n+    reg.rect_(0.0, 0.0, 94.0, 90.0, shade(grey, 0.06), r=2.0)\n+    reg.rect_(0.0, 15.0, 36.0, 75.0, shade(grey, -0.18))\n+    reg.rect_(0.0, 19.0, 32.0, 71.0, (196, 200, 206))\n+    reg.rect_(8.0, 37.0, 27.0, 53.0, (40, 38, 44), r=1.5)\n+    reg.rect_(9.5, 38.5, 25.5, 51.5, (92, 64, 44), r=1.0)\n+    reg.circle(88.0, 7.0, 2.2, (16, 16, 20))\n+    if label:\n+        reg.rect_(39.0, 5.5, 92.0, 84.5, (240, 238, 230), r=1.2)\n+        art = Image.new(\"RGB\", (790, 530), (240, 238, 230))\n+        da = ImageDraw.Draw(art)\n+        da.rectangle([0, 0, 790, 150], fill=(30, 60, 160))\n+        ship(da, 150, 75, 3.0, (255, 255, 255), (255, 182, 0))\n+        da.text((480, 75), \"ODYSSEY\", font=ImageFont.truetype(FONTS + \"ariblk.ttf\", 96), fill=(255, 230, 90),\n+                anchor=\"mm\")\n+        da.text((395, 240), \"ATARI ST  \u00b7  DISK 1\", font=ImageFont.truetype(FONTS + \"arialbd.ttf\", 50),\n+                fill=(40, 40, 60), anchor=\"mm\")\n+        for y in (330, 400, 470):\n+            da.line([(50, y), (740, y)], fill=(180, 176, 168), width=4)\n+        art = art.rotate(90, expand=True)\n+        x0, y0, x1, y1 = reg.box(40.0, 6.5, 91.0, 83.5)\n+        reg.img.paste(art.resize((round(x1 - x0), round(y1 - y0)), Image.LANCZOS), (round(x0), round(y0)))\n+        reg.poly([(3.0, 6.0), (8.0, 3.5), (8.0, 8.5)], shade(grey, 0.35))\n+    else:\n+        cx, cy = 52.0, 45.0\n+        reg.circle(cx, cy, 14.5, shade(grey, -0.25))\n+        reg.circle(cx, cy, 12.5, (176, 180, 186))\n+        reg.circle(cx, cy, 11.0, (196, 200, 206))\n+        reg.rect_(cx - 2.2, cy - 2.2, cx + 2.2, cy + 2.2, (30, 30, 34))\n+        reg.rect_(cx + 4.0, cy + 2.0, cx + 8.0, cy + 6.5, (30, 30, 34))\n+        reg.rect_(86.0, 79.0, 91.0, 86.0, (230, 230, 232), r=0.6)\n+    return reg\n+\n+\n+def media_atlas(out):\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), (20, 22, 21))\n+    for name, raster in ((\"desktop\", desktop()), (\"window\", window()), (\"title\", title_screen())):\n+        x, y, w, h = L.MEDIA_REGIONS[name]\n+        image.paste(glass_image(raster, (w, h)), (x, y))\n+    disk_side(L.MEDIA_REGIONS[\"disk_top\"], True).paste(image)\n+    disk_side(L.MEDIA_REGIONS[\"disk_bottom\"], False).paste(image)\n+    d = ImageDraw.Draw(image)\n+    for name, colour in ((\"black\", (6, 7, 6)), (\"edge\", rgb(L.COLOURS[\"disk\"]))):\n+        x, y = L.MEDIA_SWATCHES[name]\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colour)\n+    path = os.path.join(out, \"atari_st_media.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    print_atlas(out)\n+    media_atlas(out)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "b73d49284417c2b129492eb4b7ab31f2ac41c5a8",
          "filename": "tools/console-models/bbcmicro.py",
          "status": "added",
          "additions": 306,
          "deletions": 0,
          "changes": 306,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fbbcmicro.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fbbcmicro.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fbbcmicro.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,306 @@\n+\"\"\"The console theme's BBC Micro system model (godot/themes/console/models/systems/bbcmicro.glb): the BBC Micro Model B,\n+a twin disk drive on it and a Microvitec Cub monitor on the drive.\n+\n+Built in Blender (bbcmicro_layout.py has the measurements, taken from photographs). The computer is its case (its\n+plan's corners rounded, its top's edges softened) with the keyboard panel sunk into its front half, the clear strip\n+along the panel's back, the blank plate and the keys standing in the panel, each key a frustum, its top printed with its\n+legend. The drive is a box on the computer's back half, its two slots cut into its front, a latch on each; the monitor\n+a box on the drive, the bezel printed on its front and the tube's recess cut into it, the tube's face curved. The\n+printing is one texture (bbcmicro_print.py's), projected from above onto the panel and the keys and from in front onto\n+the drive's and the monitor's fronts.\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: the right end towards the viewer, the\n+top tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m) holding the set; the set\n+holding the floppy (waiting in front of drive 0, shrunk to nothing), drive 0's latch and light (shrunk to nothing while\n+it's off), the tube's four faces (those not shown a little behind the one that is) and the keys the clip presses\n+(SHIFT, BREAK, RETURN, Z, X and the space bar).\n+\n+The `focused` clip (8 s, looping): the floppy slides into drive 0 and the latch turns down; SHIFT and BREAK boot it, the\n+drive's light flickers and the title comes up in teletext; SPACE, and the game's played in Mode 2 on Z, X and RETURN;\n+BREAK brings the boot screen back, the latch turns up and the floppy slides out.\n+\n+Four materials (case, keys, screen, floppy) and one 1024\u00b2 texture, within the system model budget. Run from the repo\n+root:\n+\n+    python tools/console-models/bbcmicro_print.py artifacts/bbcmicro\n+    blender -b --factory-startup --python tools/console-models/bbcmicro.py -- artifacts/bbcmicro godot/themes/console/models/systems/bbcmicro.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import bbcmicro_layout as L  # noqa: E402\n+from amiga import curved, paint, rounded, solid  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, key, phase, smooth  # noqa: E402\n+from mega_drive_cartridge import bevel, boolean, box, export, join, material, triangles  # noqa: E402\n+\n+TILT_YAW = -24.0     # degrees about the vertical: the right end comes towards the viewer\n+TILT_PITCH = 16.0    # degrees about the horizontal: the top tips up towards the viewer\n+ROUGHNESS = dict(case=0.55, keys=0.35, screen=0.3, floppy=0.4)\n+TOP = L.FEET + L.HEIGHT                      # the computer's top\n+FLOOR = TOP - L.PANEL[\"depth\"]               # the keyboard panel's floor\n+DRIVE_TOP = TOP + L.DRIVE[\"height\"]\n+MOVING = {\"SHIFT\": \"shift\", \"BREAK\": \"break\", \"RETURN\": \"return\", \"Z\": \"z\", \"X\": \"x\", \" \": \"space\"}\n+\n+\n+def atlas_uv(region, w, h):\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+def swatch(name):\n+    x, y = L.SWATCHES[name]\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+def colour(obj, mat, name):\n+    paint(obj, mat, swatch(name), lambda c, n, kind: True)\n+    return obj\n+\n+\n+def upright(name, points, z0, z1, mat):\n+    return solid(name, [[(x, y, z) for x, y in points] for z in (z0, z1)], mat)\n+\n+\n+(PX0, PX1), (PY0, PY1) = L.PANEL[\"x\"], L.PANEL[\"y\"]\n+PANEL = atlas_uv(L.REGIONS[\"panel\"], PX1 - PX0, PY1 - PY0)\n+\n+\n+def panel_uv(co):\n+    return PANEL(co.x - PX0, PY1 - co.y)\n+\n+\n+# ---- The computer ----------------------------------------------------------------------------------------------------\n+\n+def computer(m):\n+    \"\"\"The case with the panel sunk in, the strip, the plate, the feet; printed.\"\"\"\n+    case = upright(\"bbcmicro\", rounded(0.0, L.DEPTH / 2, L.WIDTH, L.DEPTH, 8.0, 4), L.FEET, TOP, m[\"case\"])\n+    bevel(case, 3.0, segments=2, angle=40)\n+    boolean(case, box(\"panel\", PX0, PX1, PY0, PY1, FLOOR, TOP + 5.0, m[\"case\"]))\n+    colour(case, m[\"case\"], \"case\")\n+    paint(case, m[\"case\"], panel_uv, lambda c, n, kind: n.z > 0.9 and abs(c.z - FLOOR) < 0.5)\n+    paint(case, m[\"case\"], swatch(\"black\"),\n+          lambda c, n, kind: abs(n.z) < 0.5 and PX0 - 0.5 < c.x < PX1 + 0.5 and PY0 - 0.5 < c.y < PY1 + 0.5\n+          and c.z > FLOOR - 0.5)\n+    s0, s1 = L.STRIP[\"y\"]\n+    strip = box(\"strip\", PX0, PX1, s0, s1, FLOOR - 0.5, TOP - 1.5, m[\"case\"])\n+    colour(strip, m[\"case\"], \"black\")\n+    paint(strip, m[\"case\"], panel_uv, lambda c, n, kind: n.z > 0.9)\n+    (p0, p1), (q0, q1) = L.PLATE[\"x\"], L.PLATE[\"y\"]\n+    plate = box(\"plate\", p0, p1, q0, q1, FLOOR - 0.5, FLOOR + 5.0, m[\"case\"])\n+    colour(plate, m[\"case\"], \"dark\")\n+    paint(plate, m[\"case\"], panel_uv, lambda c, n, kind: n.z > 0.9)\n+    feet = [colour(box(\"foot\", x - 12.0, x + 12.0, y - 8.0, y + 8.0, 0.0, L.FEET + 0.5, m[\"case\"]), m[\"case\"], \"dark\")\n+            for x in (-170.0, 170.0) for y in (30.0, 320.0)]\n+    return join([case, strip, plate] + feet, \"bbcmicro\")\n+\n+\n+def keys(m):\n+    \"\"\"The keys: those the clip presses apart (each its foot's middle at its origin, with where it goes), the rest\n+    joined.\"\"\"\n+    fixed, moving = [], {}\n+    shifts = 0\n+    for legend, tone, x0, x1, y in L.keys():\n+        bottom = [(x, yy) for x, yy in ((x0 + 0.6, y - 9.0), (x1 - 0.6, y - 9.0), (x1 - 0.6, y + 9.0),\n+                                       (x0 + 0.6, y + 9.0))]\n+        top = [(x, yy) for x, yy in ((x0 + 2.8, y - 6.8), (x1 - 2.8, y - 6.8), (x1 - 2.8, y + 6.8),\n+                                     (x0 + 2.8, y + 6.8))]\n+        part = solid(\"key\", [[(x, yy, FLOOR) for x, yy in bottom],\n+                             [(x, yy, FLOOR + L.KEYS[\"height\"]) for x, yy in top]], m[\"keys\"])\n+        colour(part, m[\"keys\"], tone)\n+        paint(part, m[\"keys\"], panel_uv, lambda c, n, kind: n.z > 0.9)\n+        name = MOVING.get(legend)\n+        if legend == \"SHIFT\":\n+            shifts += 1\n+            name = \"shift\" if shifts == 1 else None\n+        if name:\n+            middle = Vector(((x0 + x1) / 2, y, FLOOR))\n+            part.data.transform(Matrix.Translation(-middle))\n+            moving[name] = (part, middle)\n+        else:\n+            fixed.append(part)\n+    return join(fixed, \"keys\"), moving\n+\n+\n+# ---- The drive and the monitor ---------------------------------------------------------------------------------------\n+\n+def drive(m):\n+    \"\"\"The twin drive on the computer: its box, its slots cut in, drive 1's latch; its front printed.\"\"\"\n+    d = L.DRIVE\n+    w, depth, h, y0 = d[\"width\"], d[\"depth\"], d[\"height\"], d[\"y\"]\n+    part = upright(\"drive\", rounded(0.0, y0 + depth / 2, w, depth, 6.0, 3), TOP, DRIVE_TOP, m[\"case\"])\n+    bevel(part, 2.0, segments=2, angle=40)\n+    sw, sh = L.BAYS[\"slot\"]\n+    zc = TOP + L.BAYS[\"z\"]\n+    for x in L.BAYS[\"x\"]:\n+        boolean(part, box(\"slot\", x - sw / 2, x + sw / 2, y0 - 5.0, y0 + 40.0, zc - sh / 2, zc + sh / 2, m[\"case\"]))\n+    colour(part, m[\"case\"], \"case\")\n+    face = atlas_uv(L.REGIONS[\"drive\"], w, h)\n+    paint(part, m[\"case\"], lambda co: face(co.x + w / 2, DRIVE_TOP - co.z),\n+          lambda c, n, kind: n.y < -0.9 and c.y < y0 + 1.0)\n+    paint(part, m[\"case\"], swatch(\"black\"),\n+          lambda c, n, kind: c.y > y0 + 0.5 and abs(c.z - zc) < sh / 2 + 0.5 and any(abs(c.x - x) < sw / 2 + 0.5\n+                                                                                     for x in L.BAYS[\"x\"]))\n+    latch = latch_part(m)\n+    latch.data.transform(Matrix.Translation((L.BAYS[\"x\"][1], y0 - 2.0, zc)))\n+    return join([part, latch], \"drive\")\n+\n+\n+def latch_part(m):\n+    \"\"\"A drive's latch, open (lying along the slot), its middle at its origin.\"\"\"\n+    lw, lh = L.BAYS[\"latch\"]\n+    part = box(\"latch\", -lw / 2, lw / 2, -4.0, 0.0, -lh / 2, lh / 2, m[\"keys\"])\n+    bevel(part, 1.0, segments=1)\n+    return colour(part, m[\"keys\"], \"grey\")\n+\n+\n+def monitor(m):\n+    \"\"\"The monitor on the drive: its box, the tube's recess cut into its front; its bezel printed.\"\"\"\n+    mon = L.MONITOR\n+    w, h, depth, y0 = mon[\"width\"], mon[\"height\"], mon[\"depth\"], L.DRIVE[\"y\"]\n+    part = upright(\"monitor\", rounded(0.0, y0 + depth / 2, w, depth, 6.0, 3), DRIVE_TOP, DRIVE_TOP + h, m[\"case\"])\n+    bevel(part, 3.0, segments=2, angle=40)\n+    sw, sh = L.SCREEN[\"size\"]\n+    zc = DRIVE_TOP + L.SCREEN[\"z\"]\n+    boolean(part, solid(\"recess\", [[(x, y, z) for x, z in rounded(0.0, zc, sw + 16.0, sh + 16.0, 18.0, 5)]\n+                                   for y in (y0 - 5.0, y0 + 14.0)], m[\"case\"]))\n+    colour(part, m[\"case\"], \"monitor\")\n+    face = atlas_uv(L.REGIONS[\"bezel\"], w, h)\n+    paint(part, m[\"case\"], lambda co: face(co.x + w / 2, DRIVE_TOP + h - co.z),\n+          lambda c, n, kind: n.y < -0.9 and c.y < y0 + 1.0)\n+    paint(part, m[\"case\"], swatch(\"bezel\"),\n+          lambda c, n, kind: y0 + 0.5 < c.y < y0 + 14.5 and abs(c.x) < sw / 2 + 9.0\n+          and abs(c.z - zc) < sh / 2 + 9.0)\n+    return part\n+\n+\n+def screen(m, name):\n+    \"\"\"One of the tube's faces, its middle at its origin, facing the viewer.\"\"\"\n+    sw, sh = L.SCREEN[\"size\"]\n+    rx, ry, rw, rh = L.REGIONS[name]\n+    return curved(name, sw, sh, L.SCREEN[\"bulge\"], lambda a, b: ((rx + a * rw) / L.ATLAS, 1 - (ry + b * rh) / L.ATLAS),\n+                  m[\"screen\"], 16, 12)\n+\n+\n+def floppy(m):\n+    \"\"\"The floppy, lying flat, its middle at its origin, its label up and towards the viewer, going in along +y.\"\"\"\n+    s, t = L.FLOPPY[\"size\"], L.FLOPPY[\"thickness\"]\n+    part = box(\"floppy\", -s / 2, s / 2, -s / 2, s / 2, -t / 2, t / 2, m[\"floppy\"])\n+    colour(part, m[\"floppy\"], \"floppy\")\n+    top = atlas_uv(L.REGIONS[\"floppy\"], s, s)\n+    paint(part, m[\"floppy\"], lambda co: top(co.x + s / 2, co.y + s / 2), lambda c, n, kind: n.z > 0.9)\n+    return part\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+SCREENS = (\"boot\", \"title\", \"game_1\", \"game_2\")\n+BEHIND = 1.5              # how far back the tube's faces not shown wait\n+\n+\n+def tap(f, f0, hold=2):\n+    return smooth(phase(f, f0, f0 + 3)) * (1 - smooth(phase(f, f0 + 3 + hold, f0 + 7 + hold)))\n+\n+\n+def shown(f):\n+    if f < 60 or f >= 188:\n+        return \"boot\"\n+    if f < 100:\n+        return \"title\"\n+    return \"game_1\" if ((f - 100) // 8) % 2 == 0 else \"game_2\"\n+\n+\n+def timeline(f):\n+    out = L.FLOPPY[\"out\"]\n+    if f < 8 or f >= 232:\n+        disk = Pose(y=-out, scale=HIDDEN)\n+    elif f < 14:\n+        disk = Pose(y=-out, scale=max(HIDDEN, back_out(phase(f, 8, 13))))\n+    elif f < 34:\n+        disk = Pose(y=-out * (1 - smooth(phase(f, 14, 34))))\n+    elif f < 204:\n+        disk = Pose()\n+    elif f < 224:\n+        disk = Pose(y=-out * smooth(phase(f, 204, 224)))\n+    else:\n+        disk = Pose(y=-out, scale=max(HIDDEN, 1 - ease_in(phase(f, 224, 232))))\n+    shut = smooth(phase(f, 36, 42)) * (1 - smooth(phase(f, 196, 202)))\n+    reading = 52 <= f < 92 and (f // 3) % 4 != 3\n+    poses = {\"floppy\": disk, \"latch\": Pose(roll=90.0 * shut), \"drive_light\": Pose() if reading else Pose(scale=HIDDEN)}\n+    for name in SCREENS:\n+        poses[name] = Pose() if name == shown(f) else Pose(y=BEHIND)\n+    press = -2.6\n+    poses[\"shift\"] = Pose(z=press * smooth(phase(f, 44, 47)) * (1 - smooth(phase(f, 53, 56))))\n+    poses[\"break\"] = Pose(z=press * max(tap(f, 48), tap(f, 184)))\n+    poses[\"space\"] = Pose(z=press * tap(f, 96))\n+    poses[\"z\"] = Pose(z=press * max(tap(f, f0) for f0 in (108, 132, 156)))\n+    poses[\"x\"] = Pose(z=press * max(tap(f, f0) for f0 in (120, 144, 168)))\n+    poses[\"return\"] = Pose(z=press * max(tap(f, f0) for f0 in (116, 140, 162)))\n+    return poses\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    atlas = bpy.data.images.load(os.path.join(images, \"bbcmicro_print.png\"))\n+    m = {name: material(name, L.COLOURS[\"print\"], r, atlas) for name, r in ROUGHNESS.items()}\n+\n+    fixed_keys, moving_keys = keys(m)\n+    whole = join([computer(m), fixed_keys, drive(m), monitor(m)], \"bbcmicro\")\n+    for p in whole.data.polygons:\n+        p.use_smooth = True\n+    whole.data.set_sharp_from_angle(angle=math.radians(35))\n+\n+    root = bpy.data.objects.new(\"bbcmicro_root\", None)\n+    scene.collection.objects.link(root)\n+    whole.parent = root\n+    y0 = L.DRIVE[\"y\"]\n+    zc = TOP + L.BAYS[\"z\"]\n+    x0 = L.BAYS[\"x\"][0]\n+    lw, lh = L.BAYS[\"latch\"]\n+    light = colour(box(\"drive_light\", -1.6, 1.6, -0.6, 0.0, -1.6, 1.6, m[\"keys\"]), m[\"keys\"], \"lit\")\n+    bw, bh = L.BAYS[\"size\"]\n+    parts = {\"floppy\": (floppy(m), Vector((x0, y0 + L.FLOPPY[\"size\"] / 2 + 4.0, zc))),\n+             \"latch\": (latch_part(m), Vector((x0, y0 - 2.0, zc))),\n+             \"drive_light\": (light, Vector((x0 - bw / 2 + 10.0, y0 - 0.2, zc - bh / 2 + 8.0)))}\n+    sz = DRIVE_TOP + L.SCREEN[\"z\"]\n+    for name in SCREENS:\n+        parts[name] = (screen(m, name), Vector((0.0, y0 + 13.5, sz)))\n+    parts.update(moving_keys)\n+    nodes = {}\n+    for name, (part, where) in parts.items():\n+        for p in part.data.polygons:\n+            p.use_smooth = True\n+        part.data.set_sharp_from_angle(angle=math.radians(35))\n+        part.parent = whole\n+        part.location = where\n+        nodes[name] = part\n+\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    points = [tilt @ v.co for v in whole.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [whole] + list(nodes.values())\n+    print(f\"BBC Micro: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(whole.data)} the set), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "314b01188ab1b2c73b7b7fd06631b917b990b82a",
          "filename": "tools/console-models/bbcmicro_layout.py",
          "status": "added",
          "additions": 80,
          "deletions": 0,
          "changes": 80,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fbbcmicro_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fbbcmicro_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fbbcmicro_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,80 @@\n+\"\"\"The BBC Micro card's measurements, shared by bbcmicro_print.py (which draws its printing and screens) and\n+bbcmicro.py (which builds it). Plain Python: Blender's Python imports it too.\n+\n+The BBC Micro Model B (1981), 409 mm wide, 358 deep and 78 high (the Home Computer Hall of Fame's figures), a twin\n+5.25-inch disk drive on its back half and a Microvitec Cub colour monitor on the drive, 352 wide, 300 high and 415 deep\n+(the Science Museum Group's record of one from the BBC's own collection), stacked as schools had them; a made-up game,\n+Odyssey, on the floppy. Millimetres: x from the middle (left to right), y back from the computer's front, z up from the\n+table. The computer was measured from photographs of Model Bs on Wikimedia Commons (from above and in front, at an\n+angle), its keys on the 19.05 mm pitch; the monitor from the Science Museum Group's photograph of it.\n+\n+The computer: the beige case, the black keyboard panel sunk into its front half; along the panel's back the clear strip\n+(the speaker's grille at its left, BRITISH BROADCASTING CORPORATION MICROCOMPUTER SYSTEM and the owl at its right); the\n+red function keys f0 to f9 and BREAK, the keys in five rows (the cursor keys and COPY brown), the space bar; the blank\n+plate at the panel's left, the three lights (cassette motor, caps lock, shift lock). The drive: a beige box, its two\n+drives' fronts, each with its slot, its latch and its light. The monitor: its khaki steel case, the dark brown bezel\n+round the tube, CUB, the red power switch.\n+\n+One texture (ATLAS square, REGIONS): `panel` (the keyboard panel from above, its keys' legends where their tops are),\n+the screens (`boot`, `title`, `game_1`, `game_2`: Mode 7's boot and title, Mode 2's game), `bezel` (the monitor's\n+front), `drive` (the drive's front), `floppy` (the floppy's top), and swatches. A region is (x, y, width, height) in\n+pixels. \"\"\"\n+\n+ATLAS = 1024\n+U = 19.05                         # the keys' pitch\n+\n+WIDTH, DEPTH, HEIGHT = 409.0, 358.0, 74.0\n+FEET = 4.0                        # the case stands on them: 78 in all\n+PANEL = dict(x=(-180.0, 180.0), y=(22.0, 182.0), depth=10.0)   # the keyboard panel, sunk into the lid\n+STRIP = dict(y=(160.0, 182.0))    # the clear strip along the panel's back\n+KEYS = dict(x=-142.0, y=148.0, height=12.0)   # the first row's left edge and middle (y); the keys' height off the floor\n+# Each row: its offset from the left edge in keys, then (legend, width in keys, colour) for each key.\n+ROWS = [\n+    (2.25, [(f\"f{k}\", 1.0, \"red\") for k in range(10)] + [(\"BREAK\", 1.0, \"black\")]),\n+    (0.0, [(\"ESCAPE\", 1.0, \"black\")] + [(c, 1.0, \"black\") for c in \"1234567890=^\\\\\"]\n+     + [(\"\u2190\", 1.0, \"brown\"), (\"\u2192\", 1.0, \"brown\")]),\n+    (0.0, [(\"TAB\", 1.5, \"black\")] + [(c, 1.0, \"black\") for c in \"QWERTYUIOP@[\u00a3\"]\n+     + [(\"\u2191\", 1.0, \"brown\"), (\"\u2193\", 1.0, \"brown\")]),\n+    (0.0, [(\"CAPS LOCK\", 1.0, \"black\"), (\"CTRL\", 1.25, \"black\")] + [(c, 1.0, \"black\") for c in \"ASDFGHJKL;:]\"]\n+     + [(\"RETURN\", 2.0, \"black\")]),\n+    (0.0, [(\"SHIFT LOCK\", 1.0, \"black\"), (\"SHIFT\", 1.75, \"black\")] + [(c, 1.0, \"black\") for c in \"ZXCVBNM,./\"]\n+     + [(\"SHIFT\", 1.75, \"black\"), (\"DELETE\", 1.0, \"black\"), (\"COPY\", 1.0, \"brown\")]),\n+    (3.25, [(\" \", 8.5, \"black\")]),\n+]\n+PLATE = dict(x=(-176.0, -148.0), y=(56.0, 98.0))     # the blank plate at the panel's left\n+LIGHTS = dict(y=34.0, x=[-150.0, -122.0, -94.0])     # cassette motor, caps lock, shift lock\n+\n+DRIVE = dict(width=360.0, depth=250.0, height=80.0, y=186.0)   # the twin drive on the computer, just behind the\n+                                                                # strip\n+BAYS = dict(x=[-88.0, 88.0], size=(150.0, 42.0), z=40.0, slot=(112.0, 5.0), latch=(30.0, 7.0))   # z: above the\n+                                                                # drive's foot\n+MONITOR = dict(width=352.0, height=300.0, depth=415.0)        # on the drive, its front with the drive's\n+SCREEN = dict(size=(272.0, 204.0), z=160.0, bulge=6.0)        # the tube's face, its middle's height above the monitor's\n+                                                              # foot\n+FLOPPY = dict(size=133.0, thickness=1.6, out=150.0, label=(110.0, 34.0))   # out: how far in front of its seat it\n+                                                                # waits\n+\n+COLOURS = dict(case=\"#D6CDB6\", monitor=\"#9E8E6C\", bezel=\"#3A3029\", print=\"#FFFFFF\")\n+\n+SCREEN_PX = (240, 180)\n+REGIONS = dict(panel=(0, 0, 1008, 448), boot=(0, 456) + SCREEN_PX, title=(248, 456) + SCREEN_PX,\n+               game_1=(496, 456) + SCREEN_PX, game_2=(744, 456) + SCREEN_PX, bezel=(0, 644, 352, 300),\n+               drive=(360, 644, 432, 108), floppy=(360, 760, 186, 186))\n+SWATCHES = dict(case=(820, 680), monitor=(852, 680), bezel=(884, 680), black=(916, 680), red=(948, 680),\n+                brown=(980, 680), dark=(820, 712), lit=(852, 712), floppy=(884, 712), grey=(916, 712))\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s\n+\n+\n+def keys():\n+    \"\"\"Every key: (legend, colour, x0, x1, y), y its row's middle; the rows from the back (the function keys) to the\n+    front.\"\"\"\n+    out = []\n+    for r, (offset, row) in enumerate(ROWS):\n+        x = KEYS[\"x\"] + offset * U\n+        y = KEYS[\"y\"] - r * U\n+        for legend, width, colour in row:\n+            out.append((legend, colour, x, x + width * U, y))\n+            x += width * U\n+    return out"
        },
        {
          "sha": "944cca47631ec297aaa125783118088e920d3004",
          "filename": "tools/console-models/bbcmicro_print.py",
          "status": "added",
          "additions": 241,
          "deletions": 0,
          "changes": 241,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fbbcmicro_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fbbcmicro_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fbbcmicro_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,241 @@\n+\"\"\"The BBC Micro card's printing and screens (bbcmicro_print.png), for the console theme's model (bbcmicro.py).\n+\n+One image, laid out as bbcmicro_layout.REGIONS says: the keyboard panel from above (the keys' tops and legends where\n+they stand, the clear strip with the speaker's grille, BRITISH BROADCASTING CORPORATION MICROCOMPUTER SYSTEM and the\n+owl, the blank plate, the three lights and their legends), the screens (Mode 7's boot screen and a made-up game's title,\n+Odyssey, in teletext; two moments of its play in Mode 2's eight colours), the monitor's bezel (CUB, the power switch),\n+the drive's front (its two drives), the floppy's top (its label), and swatches. Run it with the system Python (Pillow):\n+\n+    python tools/console-models/bbcmicro_print.py <out folder>\n+\"\"\"\n+\n+import math\n+import os\n+import random\n+import sys\n+\n+from PIL import Image, ImageDraw, ImageFont\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import bbcmicro_layout as L  # noqa: E402\n+from retro_pc_print import rgb  # noqa: E402\n+from windows_pc_print import FONTS, Region  # noqa: E402\n+\n+PANEL = (28, 27, 30)\n+KEY = dict(black=(42, 38, 36), red=(200, 58, 46), brown=(92, 82, 72))\n+LEGEND = (236, 236, 236)\n+CREAM = (214, 200, 160)\n+# Mode 2's and Mode 7's eight colours.\n+COLOURS = dict(black=(0, 0, 0), red=(255, 0, 0), green=(0, 255, 0), yellow=(255, 255, 0), blue=(0, 0, 255),\n+               magenta=(255, 0, 255), cyan=(0, 255, 255), white=(255, 255, 255))\n+\n+\n+# ---- The keyboard panel ----------------------------------------------------------------------------------------------\n+\n+def panel():\n+    \"\"\"The panel from above: x across (-180 at the left), y down from its back (182).\"\"\"\n+    (x0, x1), (y0, y1) = L.PANEL[\"x\"], L.PANEL[\"y\"]\n+    reg = Region(L.REGIONS[\"panel\"], x1 - x0, y1 - y0, PANEL)\n+\n+    def at(x, y):\n+        return x - x0, y1 - y\n+\n+    # The strip: the grille's slots at its left, the name and the owl at its right.\n+    s0, s1 = L.STRIP[\"y\"]\n+    reg.rect_(*at(x0, s1), *at(x1, s0), (38, 38, 42))\n+    for k in range(8):\n+        x = -172.0 + k * 5.0\n+        reg.rect_(*at(x, s1 - 4.0), *at(x + 2.2, s0 + 4.0), (10, 10, 12), r=1.0)\n+    reg.text(\"BRITISH BROADCASTING CORPORATION\", \"times.ttf\", 2.6, *at(118.0, s1 - 6.0), CREAM, anchor=\"rm\")\n+    reg.text(\"MICROCOMPUTER SYSTEM\", \"times.ttf\", 4.4, *at(118.0, s0 + 7.0), CREAM, anchor=\"rm\")\n+    rng = random.Random(4)\n+    for i in range(7):                                          # the owl, a pattern of dots\n+        for j in range(6):\n+            if (i - 3) ** 2 / 9 + (j - 2.5) ** 2 / 7 < 1.1 and rng.random() < 0.85:\n+                reg.rect_(*at(126.0 + i * 3.0, s1 - 3.5 - j * 2.6), *at(127.8 + i * 3.0, s1 - 5.1 - j * 2.6), CREAM)\n+    # The blank plate and the lights.\n+    (p0, p1), (q0, q1) = L.PLATE[\"x\"], L.PLATE[\"y\"]\n+    reg.rect_(*at(p0, q1), *at(p1, q0), (18, 18, 20), r=1.5)\n+    for x, label, lit in zip(L.LIGHTS[\"x\"], (\"cassette\\nmotor\", \"caps\\nlock\", \"shift\\nlock\"), (False, True, False)):\n+        reg.circle(*at(x, L.LIGHTS[\"y\"]), 1.6, (255, 40, 30) if lit else (90, 20, 18), outline=(60, 60, 64), width=0.3)\n+        for k, line in enumerate(label.split(\"\\n\")):\n+            reg.text(line, \"arial.ttf\", 2.4, *at(x, L.LIGHTS[\"y\"] + 8.0 - k * 2.8), CREAM)\n+    # The keys' tops: each its colour, its legend.\n+    for legend, colour, kx0, kx1, ky in L.keys():\n+        reg.rect_(*at(kx0 + 2.8, ky + 6.8), *at(kx1 - 2.8, ky - 6.8), KEY[colour], r=1.4)\n+        if legend.strip():\n+            size = 4.6 if len(legend) <= 2 else 2.2\n+            lines = legend.split(\" \") if len(legend) > 6 and \" \" in legend else [legend]\n+            for k, line in enumerate(lines):\n+                dy = (len(lines) - 1) * 1.4 - k * 2.8\n+                reg.text(line, \"arialbd.ttf\" if len(legend) > 2 else \"arial.ttf\", size, *at((kx0 + kx1) / 2, ky + dy),\n+                         LEGEND)\n+    return reg\n+\n+\n+# ---- The screens -----------------------------------------------------------------------------------------------------\n+\n+def mode7(cells, sixels=None):\n+    \"\"\"A Mode 7 screen: 40 x 25 cells of (row, column, text, colour, double); sixels (x, y, colour) on its 80 x 75\n+    block grid.\"\"\"\n+    w, h = 480, 360\n+    cw, ch = w / 40, h / 25\n+    img = Image.new(\"RGB\", (w, h), (0, 0, 0))\n+    d = ImageDraw.Draw(img)\n+    for x, y, colour in sixels or []:\n+        d.rectangle([x * cw / 2, y * ch / 3, (x + 1) * cw / 2 - 1, (y + 1) * ch / 3 - 1], fill=COLOURS[colour])\n+    font = ImageFont.truetype(FONTS + \"consolab.ttf\", round(ch * 0.95))\n+    for row, col, text, colour, double in cells:\n+        if double:\n+            layer = Image.new(\"L\", (round(cw * len(text)), round(ch)), 0)\n+            ImageDraw.Draw(layer).text((0, 0), text, font=font, fill=255)\n+            layer = layer.resize((layer.width, round(ch * 2)), Image.NEAREST)\n+            img.paste(COLOURS[colour], (round(col * cw), round(row * ch)), layer)\n+        else:\n+            d.text((col * cw, row * ch), text, font=font, fill=COLOURS[colour])\n+    return img\n+\n+\n+def boot():\n+    return mode7([(0, 0, \"BBC Computer 32K\", \"white\", False), (2, 0, \"Acorn DFS\", \"white\", False),\n+                  (4, 0, \"BASIC\", \"white\", False), (6, 0, \">_\", \"white\", False)])\n+\n+\n+BLOCK_FONT = {   # 5 x 7, for teletext's block-graphics titles\n+    \"O\": [\".###.\", \"#...#\", \"#...#\", \"#...#\", \"#...#\", \"#...#\", \".###.\"],\n+    \"D\": [\"####.\", \"#...#\", \"#...#\", \"#...#\", \"#...#\", \"#...#\", \"####.\"],\n+    \"Y\": [\"#...#\", \"#...#\", \".#.#.\", \"..#..\", \"..#..\", \"..#..\", \"..#..\"],\n+    \"S\": [\".####\", \"#....\", \"#....\", \".###.\", \"....#\", \"....#\", \"####.\"],\n+    \"E\": [\"#####\", \"#....\", \"#....\", \"####.\", \"#....\", \"#....\", \"#####\"],\n+}\n+\n+\n+def title():\n+    sixels = []\n+    word = \"ODYSSEY\"\n+    x0 = (80 - (len(word) * 6 - 1)) // 2\n+    for k, letter in enumerate(word):                           # the title in block graphics\n+        for j, row in enumerate(BLOCK_FONT[letter]):\n+            for i, bit in enumerate(row):\n+                if bit == \"#\":\n+                    sixels.append((x0 + k * 6 + i, 5 + j, \"yellow\"))\n+    cx, cy, r = 52, 44, 12\n+    for y in range(75):\n+        for x in range(80):\n+            dx, dy = (x - cx) / r, (y - cy) / (r * 1.0)\n+            if dx * dx + dy * dy <= 1.0:\n+                sixels.append((x, y, \"red\" if dx + dy > -0.4 else \"yellow\"))\n+            elif abs((x - cx) ** 2 / (2.0 * r) ** 2 + (y - cy) ** 2 / (0.4 * r) ** 2 - 1.0) < 0.12 and not (\n+                    dy < 0 and abs(dx) < 1.0):\n+                sixels.append((x, y, \"white\"))\n+    for x, y in ((8, 22), (20, 30), (70, 18), (14, 56), (36, 64), (66, 62), (30, 14), (76, 50)):\n+        sixels.append((x, y, \"white\"))\n+    for x, y in ((16, 45), (17, 45), (18, 45), (19, 45), (20, 45), (17, 44), (18, 44), (17, 46), (18, 46), (14, 43),\n+                 (14, 47), (15, 44), (15, 46)):\n+        sixels.append((x, y, \"cyan\"))\n+    return mode7([(21, 14, \"PRESS SPACE\", \"cyan\", True), (24, 8, \"Z / X  MOVE   RETURN  FIRE\", \"green\", False)],\n+                 sixels)\n+\n+\n+def game(frame):\n+    \"\"\"A moment of play in Mode 2: 160 x 256 logical pixels, shown at 4:3 as blocks.\"\"\"\n+    w, h = 480, 360\n+    img = Image.new(\"RGB\", (w, h), (0, 0, 0))\n+    d = ImageDraw.Draw(img)\n+    px, py = w / 160, h / 128\n+\n+    def block(x, y, colour, size=1):\n+        d.rectangle([x * px, y * py, (x + size) * px - 1, (y + size) * py - 1], fill=COLOURS[colour])\n+\n+    rng = random.Random(9 + frame)\n+    for _ in range(40):\n+        block(rng.randrange(160), rng.randrange(14, 128), \"white\")\n+    ship_x = (62, 84)[frame]\n+    for dx, dy, colour in ((0, 0, \"yellow\"), (-1, 1, \"yellow\"), (1, 1, \"yellow\"), (0, 1, \"white\"), (-2, 2, \"yellow\"),\n+                           (2, 2, \"yellow\"), (-1, 2, \"white\"), (0, 2, \"white\"), (1, 2, \"white\")):\n+        block(ship_x + dx * 3, 108 + dy * 3, colour, 3)\n+    for k, (x, y, s) in enumerate(((30, 40, 9), (110, 30, 12), (80, 66, 7))):\n+        y += frame * 6\n+        for i in range(-s, s + 1, 2):\n+            for j in range(-s, s + 1, 2):\n+                if i * i + j * j <= s * s:\n+                    block(x + i, y + j, \"magenta\" if (i + j) % 4 else \"red\", 2)\n+    if frame == 1:\n+        for j in range(4):\n+            block(ship_x, 96 - j * 8, \"cyan\", 2)\n+    font = ImageFont.truetype(FONTS + \"consolab.ttf\", 22)\n+    d.text((12, 6), \"SCORE 0\" + (\"120\" if frame == 0 else \"240\"), font=font, fill=COLOURS[\"yellow\"])\n+    d.text((330, 6), \"SHIPS 3\", font=font, fill=COLOURS[\"green\"])\n+    return img\n+\n+\n+# ---- The monitor, the drive, the floppy ------------------------------------------------------------------------------\n+\n+def bezel():\n+    \"\"\"The monitor's front: x across (-176 at the left), z down from its top (300).\"\"\"\n+    w, h = L.MONITOR[\"width\"], L.MONITOR[\"height\"]\n+    reg = Region(L.REGIONS[\"bezel\"], w, h, rgb(L.COLOURS[\"bezel\"]))\n+    reg.rect_(4.0, 4.0, w - 4.0, h - 4.0, None, r=6.0, outline=(72, 60, 52), width=1.2)\n+    sw, sh = L.SCREEN[\"size\"]\n+    cz = h - L.SCREEN[\"z\"]\n+    reg.rect_(w / 2 - sw / 2 - 8.0, cz - sh / 2 - 8.0, w / 2 + sw / 2 + 8.0, cz + sh / 2 + 8.0, (24, 20, 18), r=18.0)\n+    for k, (letter, colour) in enumerate(((\"C\", (214, 50, 40)), (\"U\", (60, 170, 70)), (\"B\", (60, 90, 200)))):\n+        reg.text(letter, \"arialbd.ttf\", 9.0, 36.0 + k * 7.0, h - 16.0, colour)\n+    reg.rect_(w - 40.0, h - 20.0, w - 30.0, h - 12.0, (200, 40, 34), r=1.5)\n+    return reg\n+\n+\n+def drive():\n+    \"\"\"The drive's front: x across (-180 at the left), z down from its top (80).\"\"\"\n+    w, h = L.DRIVE[\"width\"], L.DRIVE[\"height\"]\n+    reg = Region(L.REGIONS[\"drive\"], w, h, rgb(L.COLOURS[\"case\"]))\n+    bw, bh = L.BAYS[\"size\"]\n+    sw, sh = L.BAYS[\"slot\"]\n+    for k, x in enumerate(L.BAYS[\"x\"]):\n+        cx, cz = x + w / 2, h - L.BAYS[\"z\"]\n+        reg.rect_(cx - bw / 2, cz - bh / 2, cx + bw / 2, cz + bh / 2, (60, 58, 56), r=2.0)\n+        reg.rect_(cx - sw / 2, cz - sh / 2, cx + sw / 2, cz + sh / 2, (8, 8, 10), r=1.0)\n+        reg.circle(cx - bw / 2 + 10.0, cz + bh / 2 - 8.0, 1.8, (90, 20, 18))\n+        reg.text(str(k), \"arialbd.ttf\", 4.0, cx - bw / 2 + 10.0, cz + bh / 2 - 16.0, (200, 196, 186))\n+    return reg\n+\n+\n+def floppy():\n+    \"\"\"The floppy's top: x across; down the image from its trailing edge (the label's) to the edge that goes in\n+    first.\"\"\"\n+    s = L.FLOPPY[\"size\"]\n+    reg = Region(L.REGIONS[\"floppy\"], s, s, (24, 24, 26))\n+    lw, lh = L.FLOPPY[\"label\"]\n+    reg.rect_((s - lw) / 2, 4.0, (s + lw) / 2, 4.0 + lh, (244, 242, 236), r=1.0)\n+    for k in range(4):\n+        reg.line([((s - lw) / 2 + 3.0, 12.0 + k * 7.0), ((s + lw) / 2 - 3.0, 12.0 + k * 7.0)], (170, 190, 220), 0.3)\n+    reg.text(\"ODYSSEY\", \"segoeprb.ttf\", 9.0, s / 2, 16.0, (40, 60, 170))\n+    reg.text(\"CHAIN \\\"ODYSSEY\\\"\", \"segoepr.ttf\", 4.6, s / 2, 30.0, (40, 60, 170))\n+    reg.circle(s / 2, s / 2 + 6.0, 19.0, (80, 76, 70))\n+    reg.circle(s / 2, s / 2 + 6.0, 14.0, (24, 24, 26))\n+    reg.rect_(s / 2 - 6.0, s - 40.0, s / 2 + 6.0, s - 6.0, (80, 76, 70), r=6.0)\n+    reg.circle(s / 2 + 26.0, s / 2 + 6.0, 2.0, (80, 76, 70))\n+    return reg\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), rgb(L.COLOURS[\"case\"]))\n+    for reg in (panel(), bezel(), drive(), floppy()):\n+        reg.paste(image)\n+    for name, screen in ((\"boot\", boot()), (\"title\", title()), (\"game_1\", game(0)), (\"game_2\", game(1))):\n+        x, y, w, h = L.REGIONS[name]\n+        image.paste(screen.resize((w, h), Image.LANCZOS), (x, y))\n+    d = ImageDraw.Draw(image)\n+    colours = dict(case=rgb(L.COLOURS[\"case\"]), monitor=rgb(L.COLOURS[\"monitor\"]), bezel=rgb(L.COLOURS[\"bezel\"]),\n+                   black=KEY[\"black\"], red=KEY[\"red\"], brown=KEY[\"brown\"], dark=(10, 10, 12), lit=(255, 50, 30),\n+                   floppy=(24, 24, 26), grey=(60, 58, 56))\n+    for name, (x, y) in L.SWATCHES.items():\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colours[name])\n+    path = os.path.join(out, \"bbcmicro_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "d3965044eebcdcaa888a007696cd00a4510e167a",
          "filename": "tools/console-models/cd32.py",
          "status": "added",
          "additions": 304,
          "deletions": 0,
          "changes": 304,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fcd32.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fcd32.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fcd32.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,304 @@\n+\"\"\"The console theme's Amiga CD32 system model (godot/themes/console/models/systems/amigacd32.glb): the CD32 and its\n+pad.\n+\n+Built in Blender from the real console's proportions (cd32_layout.py has the measurements, taken from photographs).\n+The console is its side outline extruded across its width on a recessed base; the lid's seat, the CD's well under it\n+and the vents' grooves are cut into its top. The lid is a slab on its own hinge, the round dome on it; RESET stands on\n+the front face, the power light lit beside it. The pad is its traced outline lofted into a rounded slab, its D-pad,\n+its four coloured buttons, the play/pause bar and its shoulder buttons on it. The printing is one texture drawn by\n+cd32_print.py (the console from above, planar-mapped onto every face facing up; its front face; the pad's top; the\n+CD's two sides; swatches).\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: the right end towards the viewer, the\n+top tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m) holding the set; the set\n+holding the lid (its origin on its hinge), the CD (waiting in its well, shrunk to nothing), the CD light (shrunk to\n+nothing), and the pad's D-pad and its four buttons.\n+\n+The `focused` clip (8 s, looping) plays a CD: the lid swings up, the CD drops into the well and the lid shuts; the CD\n+light flickers while it loads; the D-pad rocks and the red, blue, green and yellow buttons are pressed; then the lid\n+swings up again, the CD lifts out and goes, and the lid shuts.\n+\n+Five materials (case, dark, pad, print, disc) and one texture, within the system model budget. Run from the repo root:\n+\n+    python tools/console-models/cd32_print.py artifacts/amigacd32\n+    blender -b --factory-startup --python tools/console-models/cd32.py -- artifacts/amigacd32 godot/themes/console/models/systems/amigacd32.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import cd32_layout as L  # noqa: E402\n+from amiga import paint, rounded, solid  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, ease_out, key, phase, smooth, wobble  # noqa: E402\n+from mega_drive_cartridge import bevel, boolean, box, export, join, material, prism_x, triangles  # noqa: E402\n+\n+TILT_YAW = -25.0     # degrees about the vertical: the right end comes towards the viewer\n+TILT_PITCH = 30.0    # degrees about the horizontal: the top tips up towards the viewer\n+ROUGHNESS = dict(case=0.55, dark=0.7, pad=0.55, print=0.5, disc=0.2)\n+FRONT_H = L.FRONT[1][1]\n+\n+\n+def atlas_uv(region, w, h):\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+TOP = atlas_uv(L.REGIONS[\"top\"], L.WIDTH, L.DEPTH)\n+FRONT = atlas_uv(L.REGIONS[\"front\"], L.WIDTH, FRONT_H)\n+\n+\n+def top_uv(co):\n+    return TOP(co.x + L.WIDTH / 2, L.DEPTH - co.y)\n+\n+\n+def front_uv(co):\n+    return FRONT(co.x + L.WIDTH / 2, FRONT_H - co.z)\n+\n+\n+def swatch(name):\n+    x, y = L.SWATCHES[name]\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+def painted(obj, m, name):\n+    paint(obj, m[\"print\"], swatch(name), lambda c, n, _name: True)\n+    return obj\n+\n+\n+def prism(name, outline, z0, z1, mat):\n+    return solid(name, [[(x, y, z) for x, y in outline] for z in (z0, z1)], mat)\n+\n+\n+def circle(cx, cy, r, n=24):\n+    return [(cx + r * math.cos(2 * math.pi * k / n), cy + r * math.sin(2 * math.pi * k / n)) for k in range(n)]\n+\n+\n+# ---- The console -----------------------------------------------------------------------------------------------------\n+\n+def console(m):\n+    \"\"\"The console: the body on its base, the lid's seat, the well and the vents cut; RESET and the power light; its top\n+    and front printed.\"\"\"\n+    w, d = L.WIDTH, L.DEPTH\n+    i = L.FOOT[\"inset\"]\n+    base = box(\"base\", -w / 2 + i, w / 2 - i, i, d - i, 0.0, L.FOOT[\"height\"] + 1.0, m[\"dark\"])\n+    body = prism_x(\"amigacd32\", L.profile(), -w / 2, w / 2, m[\"case\"])\n+    bevel(body, 2.0, segments=2)\n+    (x0, x1), (y0, y1), t = L.LID[\"x\"], L.LID[\"y\"], L.LID[\"thickness\"]\n+    boolean(body, box(\"seat\", x0, x1, y0, y1 + 10.0, L.TOP_Z - t, L.TOP_Z + 10.0, m[\"case\"]))\n+    (wx, wy), wr = L.WELL[\"at\"], L.WELL[\"r\"]\n+    boolean(body, prism(\"well\", circle(wx, wy, wr, 48), L.TOP_Z - t - L.WELL[\"depth\"], L.TOP_Z, m[\"dark\"]))\n+    v = L.VENTS\n+    boolean(body, join([box(\"vent\", *v[\"x\"], y - v[\"width\"] / 2, y + v[\"width\"] / 2, L.TOP_Z - v[\"depth\"],\n+                            L.TOP_Z + 5.0, m[\"dark\"]) for y in v[\"y\"]], \"vents\"))\n+    paint(body, m[\"print\"], top_uv, lambda c, n, name: n.z > 0.3 and name == \"case\")\n+    paint(body, m[\"print\"], front_uv, lambda c, n, name: n.y < -0.9 and name == \"case\")\n+    spindle = prism(\"spindle\", circle(wx, wy, 8.0, 20), L.TOP_Z - t - L.WELL[\"depth\"], L.SPINDLE_Z, m[\"dark\"])\n+    (rx0, rx1), (rz0, rz1), out = L.RESET[\"x\"], L.RESET[\"z\"], L.RESET[\"out\"]\n+    reset = solid(\"reset\", [[(x, y, z) for x, z in rounded((rx0 + rx1) / 2, (rz0 + rz1) / 2, rx1 - rx0, rz1 - rz0,\n+                                                            (rz1 - rz0) / 2 - 0.2, 4)]\n+                            for y in (1.0, -out)], m[\"case\"])\n+    bevel(reset, 0.8, segments=1)\n+    px, pz = L.LIGHTS[\"power\"]\n+    lw, lh = L.LIGHTS[\"size\"]\n+    power = painted(box(\"power_light\", px - lw / 2, px + lw / 2, -0.4, 0.5, pz - lh / 2, pz + lh / 2, m[\"print\"]), m,\n+                    \"lit\")\n+    return [base, body, spindle, reset, power]\n+\n+\n+def lid(m):\n+    \"\"\"The lid, its origin on its hinge along its back edge: the slab and its dome, its top printed.\"\"\"\n+    (x0, x1), (y0, y1), t = L.LID[\"x\"], L.LID[\"y\"], L.LID[\"thickness\"]\n+    slab = box(\"lid\", x0 + 0.5, x1 - 0.5, y0 + 0.5, y1, L.TOP_Z - t + 0.4, L.TOP_Z, m[\"case\"])\n+    (dx, dy), r = L.DOME[\"at\"], L.DOME[\"r\"]\n+    dome = solid(\"dome\", [[(x, y, z) for x, y in circle(dx, dy, rr, 64)] for rr, z in\n+                          ((r, L.TOP_Z - 1.0), (r, L.TOP_Z + L.DOME[\"rise\"] - 1.5), (r - 2.0, L.TOP_Z + L.DOME[\"rise\"]))],\n+                 m[\"case\"])\n+    part = join([slab, dome], \"lid\")\n+    bevel(part, 0.8, segments=2, angle=50)\n+    paint(part, m[\"print\"], top_uv, lambda c, n, name: n.z > 0.5)\n+    hinge = Vector((0.0, y1, L.TOP_Z - t / 2))\n+    part.data.transform(Matrix.Translation(-hinge))\n+    return part, hinge\n+\n+\n+def access_light(m):\n+    x, z = L.LIGHTS[\"access\"]\n+    w, h = L.LIGHTS[\"size\"]\n+    part = painted(box(\"access\", -w / 2, w / 2, -0.6, 0.4, -h / 2, h / 2, m[\"print\"]), m, \"amber\")\n+    return part, Vector((x, 0.0, z))\n+\n+\n+def disc(m):\n+    r, t = L.DISC[\"r\"], L.DISC[\"thickness\"]\n+    part = prism(\"disc\", circle(0.0, 0.0, r, 48), -t / 2, t / 2, m[\"disc\"])\n+    top = atlas_uv(L.REGIONS[\"disc\"], 2 * r, 2 * r)\n+    bottom = atlas_uv(L.REGIONS[\"data\"], 2 * r, 2 * r)\n+    paint(part, m[\"disc\"], lambda co: top(co.x + r, r - co.y), lambda c, n, name: n.z > 0.9)\n+    paint(part, m[\"disc\"], lambda co: bottom(co.x + r, r + co.y), lambda c, n, name: n.z <= 0.9)\n+    return part\n+\n+\n+# ---- The pad ---------------------------------------------------------------------------------------------------------\n+\n+def pad_frame():\n+    \"\"\"The pad's frame: its local (x, y), y from its front edge back; its middle at PAD[\"at\"].\"\"\"\n+    (px, py), turn = L.PAD[\"at\"], L.PAD[\"turn\"]\n+    return (Matrix.Translation((px, py, 0.0)) @ Matrix.Rotation(math.radians(turn), 4, \"Z\")\n+            @ Matrix.Translation((0.0, -L.PAD[\"length\"] / 2, 0.0)))\n+\n+\n+def pad_outline():\n+    points = list(L.PAD_OUTLINE)\n+    area = sum(a[0] * b[1] - b[0] * a[1] for a, b in zip(points, points[1:] + points[:1]))\n+    return points if area > 0 else list(reversed(points))\n+\n+\n+def pad(m):\n+    \"\"\"The pad's body and its fixed parts (the play/pause bar, the shoulder buttons), at its place.\"\"\"\n+    p = L.PAD\n+    t = p[\"thickness\"]\n+    body = prism(\"pad\", pad_outline(), 0.0, t, m[\"pad\"])\n+    bevel(body, 6.0, segments=4, angle=30)\n+    top = atlas_uv(L.REGIONS[\"pad\"], p[\"width\"], p[\"length\"])\n+    paint(body, m[\"print\"], lambda co: top(co.x + p[\"width\"] / 2, p[\"length\"] - co.y), lambda c, n, name: n.z > 0.8)\n+    paint(body, m[\"print\"], swatch(\"grey\"), lambda c, n, name: n.z <= 0.8)\n+    x, y, w, h = p[\"pause\"]\n+    bar = painted(prism(\"pause\", rounded(x, y, w, h, h / 2 - 0.1, 3), t - 2.0, t + 1.2, m[\"print\"]), m, \"dark\")\n+    parts = [body, bar]\n+    for side in (-1, 1):\n+        shoulder = painted(box(\"shoulder\", side * 56.0 - 13.0, side * 56.0 + 13.0, 76.0, 82.0, t - 10.0, t - 3.0,\n+                               m[\"print\"]), m, \"dark\")\n+        bevel(shoulder, 1.5, segments=1)\n+        parts.append(shoulder)\n+    for part in parts:\n+        part.data.transform(pad_frame())\n+    return parts\n+\n+\n+def pad_parts(m):\n+    \"\"\"The D-pad and the four buttons, each its middle at its origin, and where each goes.\"\"\"\n+    p = L.PAD\n+    t = p[\"thickness\"]\n+    dx, dy, r = p[\"dpad\"]\n+    dpad = painted(prism(\"dpad\", circle(0.0, 0.0, r, 32), -2.0, 2.4, m[\"print\"]), m, \"dark\")\n+    bumps = [painted(prism(\"bump\", circle(r * 0.62 * math.cos(a), r * 0.62 * math.sin(a), 1.3, 10), 2.0, 3.2,\n+                           m[\"print\"]), m, \"dark\") for a in (0.0, math.pi / 2, math.pi, 3 * math.pi / 2)]\n+    dpad = join([dpad] + bumps, \"dpad\")\n+    parts = [(\"dpad\", dpad, Vector((dx, dy, 0.0)))]\n+    for name, (x, y, br) in p[\"buttons\"].items():\n+        button = painted(prism(name, circle(0.0, 0.0, br, 24), -2.0, 3.2, m[\"print\"]), m, name)\n+        bevel(button, 1.0, segments=2)\n+        parts.append((name, button, Vector((x, y, 0.0))))\n+    frame = pad_frame()\n+    turn = Matrix.Rotation(math.radians(L.PAD[\"turn\"]), 4, \"Z\")\n+    placed = []\n+    for name, part, where in parts:\n+        part.data.transform(turn)\n+        at = frame @ where\n+        placed.append((name, part, Vector((at.x, at.y, t))))\n+    return placed\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+def appear(f, f0, f1):\n+    return HIDDEN + (1 - HIDDEN) * back_out(phase(f, f0, f1)) if f >= f0 else HIDDEN\n+\n+\n+def tap(f, f0, hold=2):\n+    return smooth(phase(f, f0, f0 + 2)) * (1 - smooth(phase(f, f0 + 2 + hold, f0 + 5 + hold)))\n+\n+\n+PRESSES = dict(red=(110, 130, 158), blue=(118, 142), green=(150, 166), yellow=(136, 172))\n+ROCKS = [(96, 106, 0.0, 9.0), (122, 132, 9.0, 0.0), (150, 160, 0.0, -9.0)]\n+\n+\n+def lid_angle(f):\n+    open_ = L.LID[\"open\"]\n+    if f < 186:\n+        return (open_ * back_out(phase(f, 8, 22), 1.3) * (1 - smooth(phase(f, 48, 60)))\n+                + wobble(f, 60, 2.0, period=8.0, decay=3.0))\n+    return (open_ * back_out(phase(f, 186, 198), 1.3) * (1 - smooth(phase(f, 214, 226)))\n+            + wobble(f, 226, 2.0, period=8.0, decay=3.0))\n+\n+\n+def timeline(f):\n+    if f < 22 or f >= 212:\n+        cd = Pose(scale=HIDDEN)\n+    elif f < 30:\n+        cd = Pose(z=60.0, scale=appear(f, 22, 28))\n+    elif f < 42:\n+        cd = Pose(z=60.0 * (1 - ease_in(phase(f, 30, 42))))\n+    elif f < 198:\n+        cd = Pose()\n+    else:\n+        cd = Pose(z=60.0 * ease_out(phase(f, 198, 206)), scale=max(HIDDEN, 1 - ease_in(phase(f, 204, 212))))\n+    reading = 62 <= f < 98 and (f // 3) % 4 != 3\n+    poses = {\"lid\": Pose(pitch=lid_angle(f)), \"disc\": cd, \"access\": Pose() if reading else Pose(scale=HIDDEN)}\n+    for name, frames in PRESSES.items():\n+        poses[name] = Pose(z=-2.0 * max(tap(f, f0) for f0 in frames))\n+    pitch = roll = 0.0\n+    for f0, f1, pa, ro in ROCKS:\n+        t = smooth(phase(f, f0, f0 + 3)) * (1 - smooth(phase(f, f1, f1 + 4)))\n+        pitch, roll = pitch + pa * t, roll + ro * t\n+    poses[\"dpad\"] = Pose(pitch=pitch, roll=roll)\n+    return poses\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    printed = bpy.data.images.load(os.path.join(images, \"cd32_print.png\"))\n+    colours = dict(L.COLOURS, print=\"#FFFFFF\")\n+    textures = dict(print=printed, disc=printed)\n+    m = {n: material(n, colours[n], r, textures.get(n)) for n, r in ROUGHNESS.items()}\n+\n+    whole = join(console(m) + pad(m), \"amigacd32\")\n+    cover, hinge = lid(m)\n+    light, light_at = access_light(m)\n+    cd = disc(m)\n+    buttons = pad_parts(m)\n+    for obj in [whole, cover] + [part for _, part, _ in buttons]:\n+        for poly in obj.data.polygons:\n+            poly.use_smooth = True\n+        obj.data.set_sharp_from_angle(angle=math.radians(40))\n+\n+    root = bpy.data.objects.new(\"amigacd32_root\", None)\n+    scene.collection.objects.link(root)\n+    whole.parent = root\n+    (wx, wy) = L.WELL[\"at\"]\n+    seat = Vector((wx, wy, L.SPINDLE_Z + L.DISC[\"thickness\"] / 2))\n+    placed = [(cover, hinge), (light, light_at), (cd, seat)] + [(part, where) for _, part, where in buttons]\n+    for part, where in placed:\n+        part.parent = whole\n+        part.location = where\n+    nodes = {\"lid\": cover, \"access\": light, \"disc\": cd, **{name: part for name, part, _ in buttons}}\n+\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    points = [tilt @ v.co for v in whole.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [whole, cover, light, cd] + [part for _, part, _ in buttons]\n+    print(f\"CD32: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(whole.data)} the set), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "431062767b37232a7672cb572551ee5c4b151176",
          "filename": "tools/console-models/cd32_layout.py",
          "status": "added",
          "additions": 68,
          "deletions": 0,
          "changes": 68,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fcd32_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fcd32_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fcd32_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,68 @@\n+\"\"\"The Amiga CD32's measurements, shared by cd32_print.py (which draws its printing) and cd32.py (which builds it).\n+Plain Python: Blender's Python imports it too.\n+\n+The Amiga CD32 (1993), 305 x 213 mm (the Computer History Museum's 12 x 8 3/8 inches), and its pad, 184 x 80 mm, lying\n+in front of its left end. Millimetres: x from the console's middle (left to right), y back from its front, z up from\n+the table. The rest was measured from Evan-Amos's photographs on Wikimedia Commons (the console from its front\n+corners, with its lid open, with its pad, and the pad from above), scaled by the console's width; the pad's outline\n+was traced from its photograph from above (its cable cut away).\n+\n+The console: a dark grey box on a recessed base, its front face (the control strip at the right: RESET, the two\n+lights, the volume slider, the headphone socket) rising to a slope (the AMIGA CD32 badge at the right), then the flat\n+top: at the left the lid over the CD well, hinged along its back, a round dome on it with 32bit printed and a smoked\n+window in its front half; at the right the vents' grooves and Commodore.\n+\n+One texture (ATLAS square, REGIONS): `top` (the console seen from above: the lettering, the badge, the dome's\n+printing; planar-mapped onto every face facing up), `front` (the front face), `pad` (the pad's top), `disc` and\n+`data` (the CD's two sides: a made-up game, Odyssey), and swatches. A region is (x, y, width, height) in pixels.\n+\"\"\"\n+\n+ATLAS = 1024\n+\n+WIDTH, DEPTH, HEIGHT = 305.0, 213.0, 66.0\n+FOOT = dict(height=8.0, inset=6.0)                     # the recessed base under it\n+FRONT = ((0.0, 8.0), (4.0, 40.0))                      # the front face: its foot and its top, (y, z)\n+SLOPE = ((4.0, 40.0), (58.0, 62.0))                    # the slope up to the flat top\n+TOP_Z = HEIGHT                                         # the flat top, from the slope's top to the back\n+\n+\n+def profile():\n+    \"\"\"The side outline, (y, z), anticlockwise seen from the right end.\"\"\"\n+    (fy0, fz0), (fy1, fz1) = FRONT\n+    (_, _), (sy1, sz1) = SLOPE\n+    return [(fy0, fz0), (DEPTH, fz0), (DEPTH, TOP_Z - 3.0), (DEPTH - 3.0, TOP_Z), (sy1 + 6.0, TOP_Z), (sy1, sz1),\n+            (fy1, fz1)]\n+\n+\n+LID = dict(x=(-150.0, -4.0), y=(54.0, 209.0), thickness=6.0, open=-100.0)   # its plan; hinged along its back edge\n+DOME = dict(at=(-77.0, 131.0), r=70.0, rise=5.0)       # the round dome on the lid\n+WELL = dict(at=(-77.0, 131.0), r=62.0, depth=14.0)     # the CD's well under the lid\n+VENTS = dict(x=(14.0, 140.0), y=[96.0 + 6.0 * k for k in range(16)], width=3.0, depth=2.0)\n+RESET = dict(x=(30.0, 62.0), z=(18.0, 34.0), out=3.0)  # the RESET button, on the front face\n+LIGHTS = dict(power=(84.0, 30.0), access=(84.0, 22.0), size=(14.0, 2.4))   # x, z on the front face\n+\n+DISC = dict(r=60.0, hole=7.5, thickness=1.2)\n+SPINDLE_Z = TOP_Z - WELL[\"depth\"] + 3.0                # where the CD sits in the well\n+\n+# The pad: its outline traced from above (y from its front edge, towards the player, back to the shoulder buttons;\n+# the U's opening at the back), lying at PAD[\"at\"], turned PAD[\"turn\"] degrees.\n+PAD_OUTLINE = [(0.0, 47.3), (38.7, 50.9), (39.7, 53.3), (34.5, 67.5), (34.5, 72.3), (36.4, 75.1), (40.7, 77.0),\n+               (41.6, 79.4), (70.1, 79.4), (71.6, 76.5), (75.8, 75.1), (80.1, 71.3), (83.4, 64.2), (89.1, 44.7),\n+               (92.0, 23.8), (89.6, 17.1), (85.8, 13.3), (75.4, 9.5), (39.7, 2.4), (2.1, 0.0), (-36.4, 1.9),\n+               (-80.1, 10.9), (-85.8, 13.3), (-89.6, 17.1), (-92.0, 27.1), (-88.2, 49.4), (-82.0, 68.5),\n+               (-76.8, 74.6), (-71.1, 76.5), (-70.1, 78.9), (-41.6, 78.9), (-37.8, 75.6), (-35.4, 72.7),\n+               (-34.9, 68.0), (-39.7, 53.7), (-38.7, 50.9)]\n+PAD = dict(at=(-110.0, -78.0), turn=8.0, thickness=26.0, length=80.4, width=184.0,\n+           dpad=(-65.4, 47.9, 13.4), buttons=dict(green=(53.6, 53.3, 5.9), yellow=(70.9, 58.8, 5.9),\n+                                                  red=(58.5, 36.0, 7.1), blue=(75.4, 40.9, 5.9)),\n+           pause=(22.8, 22.6, 23.8, 5.0), logo=(-19.8, 32.0, 46.0), band=26.0)\n+\n+COLOURS = dict(case=\"#3A3B3E\", dark=\"#141416\", pad=\"#4A4B4F\", disc=\"#FFFFFF\")\n+\n+REGIONS = dict(top=(0, 0, 1024, 715), front=(0, 723, 1024, 120), pad=(0, 851, 400, 173), disc=(408, 851, 172, 172),\n+               data=(588, 851, 172, 172))\n+SWATCHES = dict(dark=(784, 872), green=(816, 872), yellow=(848, 872), red=(880, 872), blue=(912, 872),\n+                lit=(944, 872), amber=(976, 872), grey=(784, 904), smoke=(816, 904))\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s"
        },
        {
          "sha": "336617cff34cb7fe9ee0994982b2c78323c77915",
          "filename": "tools/console-models/cd32_print.py",
          "status": "added",
          "additions": 169,
          "deletions": 0,
          "changes": 169,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fcd32_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fcd32_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fcd32_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,169 @@\n+\"\"\"The Amiga CD32's printing, for the console theme's CD32 model (tools/console-models/cd32.py).\n+\n+Draws its atlas, its regions as cd32_layout.py places them, each in millimetres as the model's planar UVs map it: the\n+console seen from above (32bit on the dome, its smoked window, Commodore, the AMIGA CD32 badge on the slope); its front\n+face (RESET, the power and CD lights off, the volume slider's slot and scale, the headphone socket); the pad's top\n+(its D-pad's ring and dots, AMIGA CD32, the play/pause slot and its marks, the buttons' marks); the CD's two sides (a\n+made-up game, Odyssey, and the shiny side); and the swatches small parts take their colour from. Run it with the\n+system Python (Pillow):\n+\n+    python tools/console-models/cd32_print.py <out folder>\n+\n+It writes cd32_print.png there.\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+from PIL import Image, ImageDraw, ImageFont\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import cd32_layout as L  # noqa: E402\n+from psp_print import ship, space  # noqa: E402\n+from retro_pc_print import rgb, shade  # noqa: E402\n+from windows_pc_print import FONTS, Region  # noqa: E402\n+\n+CASE = rgb(L.COLOURS[\"case\"])\n+PAD = rgb(L.COLOURS[\"pad\"])\n+RED = (214, 64, 44)\n+WHITE = (232, 232, 232)\n+\n+\n+def amiga_cd32(reg, x, y, size):\n+    \"\"\"AMIGA in red serif italics, CD in white, 32 in small red: its left edge at x, its middle at y.\"\"\"\n+    f1 = ImageFont.truetype(FONTS + \"timesbi.ttf\", max(1, round(size * reg.sx)))\n+    f2 = ImageFont.truetype(FONTS + \"ariblk.ttf\", max(1, round(size * 0.92 * reg.sx)))\n+    f3 = ImageFont.truetype(FONTS + \"ariblk.ttf\", max(1, round(size * 0.48 * reg.sx)))\n+    px, py = reg.p(x, y)\n+    reg.draw.text((px, py), \"AMIGA\", font=f1, fill=RED, anchor=\"lm\")\n+    px += f1.getlength(\"AMIGA\") + size * 0.4 * reg.sx\n+    reg.draw.text((px, py), \"CD\", font=f2, fill=WHITE, anchor=\"lm\")\n+    px += f2.getlength(\"CD\") + size * 0.06 * reg.sx\n+    reg.draw.text((px, py - size * 0.24 * reg.sy), \"32\", font=f3, fill=RED, anchor=\"lm\")\n+\n+\n+def top():\n+    reg = Region(L.REGIONS[\"top\"], L.WIDTH, L.DEPTH, CASE)\n+\n+    def at(x, y):\n+        return x + L.WIDTH / 2, L.DEPTH - y\n+    (dx, dy), r = L.DOME[\"at\"], L.DOME[\"r\"]\n+    reg.circle(*at(dx, dy), r, shade(CASE, 0.06))\n+    reg.circle(*at(dx, dy), r, None, outline=shade(CASE, -0.3), width=0.5)\n+    layer = reg.layer()                                      # the smoked window: the dome's front half, lens-shaped\n+    d = ImageDraw.Draw(layer)\n+    d.chord(reg.box(*at(dx - r * 0.82, dy + r * 0.25), *at(dx + r * 0.82, dy - r * 0.95)), 0, 180,\n+            fill=(14, 14, 16, 255))\n+    reg.over(layer)\n+    reg.line([at(dx - r * 0.92, dy + r * 0.36), at(dx + r * 0.92, dy + r * 0.36)], shade(CASE, -0.3), 0.5)\n+    layer = reg.layer()\n+    f = ImageFont.truetype(FONTS + \"ariblk.ttf\", max(1, round(13.0 * reg.sx)))\n+    ImageDraw.Draw(layer).text(reg.p(*at(dx - 4.0, dy + r * 0.62)), \"32bit\", font=f, fill=(206, 206, 210, 255),\n+                               anchor=\"mm\")\n+    reg.over(layer.transform(layer.size, Image.AFFINE, (1, 0.15, -0.15 * reg.p(0, at(0, dy + r * 0.62)[1])[1],\n+                                                        0, 1, 0)))\n+    reg.text(\"Commodore\", \"arialbd.ttf\", 9.0, *at(98.0, 190.0), WHITE)\n+    reg.rect_(*at(16.0, 52.0), *at(142.0, 30.0), (24, 24, 26))\n+    amiga_cd32(reg, *at(22.0, 41.0), 11.0)\n+    reg.text(\"DO NOT STARE INTO BEAM\", \"arial.ttf\", 2.6, *at(-104.0, 32.0), shade(CASE, -0.25))\n+    return reg\n+\n+\n+FRONT_H = L.FRONT[1][1]\n+\n+\n+def front():\n+    reg = Region(L.REGIONS[\"front\"], L.WIDTH, FRONT_H, CASE)\n+\n+    def at(x, z):\n+        return x + L.WIDTH / 2, FRONT_H - z\n+    (x0, x1), (z0, z1) = L.RESET[\"x\"], L.RESET[\"z\"]\n+    reg.rect_(*at(x0 - 2.0, z1 + 2.0), *at(x1 + 2.0, z0 - 2.0), shade(CASE, -0.2), r=(z1 - z0) / 2 + 2.0)\n+    reg.text(\"RESET\", \"arial.ttf\", 2.4, *at((x0 + x1) / 2, z0 - 5.0), shade(CASE, 0.35))\n+    w, h = L.LIGHTS[\"size\"]\n+    for name, colour in ((\"power\", (60, 30, 26)), (\"access\", (60, 46, 20))):\n+        x, z = L.LIGHTS[name]\n+        reg.rect_(*at(x - w / 2, z + h / 2), *at(x + w / 2, z - h / 2), colour, r=0.6)\n+    reg.rect_(*at(104.0, 28.0), *at(130.0, 24.0), (12, 12, 13))\n+    for k in range(14):\n+        reg.rect_(*at(104.0 + 2.0 * k, 19.0), *at(104.6 + 2.0 * k, 17.0 - k * 0.12), shade(CASE, 0.25))\n+    reg.circle(*at(140.0, 26.0), 2.6, (10, 10, 11))\n+    reg.circle(*at(140.0, 26.0), 1.2, (60, 60, 64))\n+    return reg\n+\n+\n+def pad():\n+    reg = Region(L.REGIONS[\"pad\"], L.PAD[\"width\"], L.PAD[\"length\"], PAD)\n+    p = L.PAD\n+\n+    def at(x, y):\n+        return x + L.PAD[\"width\"] / 2, L.PAD[\"length\"] - y\n+    reg.line([at(-48.0, p[\"band\"]), at(48.0, p[\"band\"])], shade(PAD, -0.4), 0.8)\n+    dx, dy, r = p[\"dpad\"]\n+    reg.circle(*at(dx, dy), r + 1.2, shade(PAD, -0.35))\n+    for k, glyph in enumerate((\"\u25b2\", \"\u25b6\", \"\u25bc\", \"\u25c0\")):\n+        a = math.pi / 2 - k * math.pi / 2\n+        reg.text(glyph, \"seguisym.ttf\", 2.2, *at(dx + (r + 4.0) * math.cos(a), dy + (r + 4.0) * math.sin(a)),\n+                 shade(PAD, -0.4))\n+    for name, (x, y, br) in p[\"buttons\"].items():\n+        reg.circle(*at(x, y), br + 1.0, shade(PAD, -0.3))\n+    reg.line([at(53.6, 53.3), at(70.9, 58.8)], shade(PAD, -0.3), 7.0)\n+    reg.line([at(58.5, 36.0), at(75.4, 40.9)], shade(PAD, -0.3), 7.0)\n+    lx, ly, lw = p[\"logo\"]\n+    amiga_cd32(reg, *at(lx - lw / 2, ly), 6.4)\n+    x, y, w, h = p[\"pause\"]\n+    reg.rect_(*at(x - w / 2, y + h / 2), *at(x + w / 2, y - h / 2), (16, 16, 18), r=h / 2)\n+    reg.text(\"II \u25b6\", \"seguisym.ttf\", 2.6, *at(x + 4.0, y - 6.0), shade(PAD, -0.4))\n+    reg.text(\"\u25c0\u25c0\", \"seguisym.ttf\", 2.6, *at(-62.0, 72.0), shade(PAD, -0.4))\n+    reg.text(\"\u25b6\u25b6\", \"seguisym.ttf\", 2.6, *at(62.0, 72.0), shade(PAD, -0.4))\n+    return reg\n+\n+\n+def disc_side(region, label):\n+    r = L.DISC[\"r\"]\n+    reg = Region(region, 2 * r, 2 * r, (40, 40, 44))\n+    c = r\n+    if label:\n+        art = space((560, 560))\n+        d = ImageDraw.Draw(art)\n+        ship(d, 200, 240, 3.6, \"#FFFFFF\", \"#FFB23C\")\n+        x0, y0, x1, y1 = reg.box(0.0, 0.0, 2 * r, 2 * r)\n+        reg.img.paste(art.convert(\"RGB\").resize((round(x1 - x0), round(y1 - y0)), Image.LANCZOS), (0, 0))\n+        reg.text(\"ODYSSEY\", \"ariblk.ttf\", 15.0, c, c - 34.0, (255, 214, 70))\n+        reg.rect_(0.0, c + 20.0, 2 * r, 2 * r, (20, 20, 22))\n+        amiga_cd32(reg, c - 32.0, c + 32.0, 10.0)\n+    else:\n+        for k in range(60):                                  # the shiny side: silver, a faint rainbow sheen\n+            t = k / 60\n+            colour = (round(196 + 22 * math.sin(6.3 * t)), round(198 + 22 * math.sin(6.3 * t + 2.1)),\n+                      round(204 + 22 * math.sin(6.3 * t + 4.2)))\n+            reg.circle(c, c, r * (1 - t * 0.8), colour)\n+    reg.circle(c, c, 17.0, (205, 208, 214))\n+    reg.circle(c, c, L.DISC[\"hole\"], (30, 30, 34))\n+    mask = Image.new(\"L\", reg.img.size, 0)\n+    ImageDraw.Draw(mask).ellipse(reg.box(0.0, 0.0, 2 * r, 2 * r), fill=255)\n+    out = Image.new(\"RGBA\", reg.img.size, (190, 192, 198, 255))\n+    out.paste(reg.img, (0, 0), mask)\n+    reg.img = out\n+    return reg\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), CASE)\n+    for reg in (top(), front(), pad(), disc_side(L.REGIONS[\"disc\"], True), disc_side(L.REGIONS[\"data\"], False)):\n+        reg.paste(image)\n+    d = ImageDraw.Draw(image)\n+    for name, colour in ((\"dark\", rgb(L.COLOURS[\"dark\"])), (\"green\", (92, 200, 160)), (\"yellow\", (242, 210, 60)),\n+                         (\"red\", (224, 87, 58)), (\"blue\", (77, 104, 216)), (\"lit\", (255, 70, 50)),\n+                         (\"amber\", (255, 180, 50)), (\"grey\", PAD), (\"smoke\", (16, 16, 18))):\n+        x, y = L.SWATCHES[name]\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colour)\n+    path = os.path.join(out, \"cd32_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "2de6f16fed08d2a21ebe4991e98906de5725a367",
          "filename": "tools/console-models/cdi.py",
          "status": "added",
          "additions": 332,
          "deletions": 0,
          "changes": 332,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fcdi.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fcdi.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fcdi.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,332 @@\n+\"\"\"The console theme's Philips CD-i system model (godot/themes/console/models/systems/cdimono1.glb): a CD-i 220 and its\n+gamepad.\n+\n+Built in Blender from the real player's proportions (cdi_layout.py has the measurements, taken from photographs). The\n+body is a box on four feet, the tray's opening cut into its front and the vents into its top; the buttons stand proud\n+of the front, the display's smoked window over its place. The tray is its door and, behind it, the black platform the\n+disc lies in. The gamepad is its arched outline extruded, a raised plate on it, the ring round the thumbpad and the\n+buttons' slanted well cut into the plate, the thumbpad and the three buttons standing in them. The printing is one\n+texture drawn by cdi_print.py: the front (planar-mapped onto the faces facing forward: the door's dots and logo,\n+PHILIPS, the buttons' names), the window three ways (dark, the disc read, playing), the disc's label, the gamepad's top,\n+and swatches.\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: the right side towards the viewer, the\n+top tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m) holding the set; the set\n+holding the tray (and on it the disc, shrunk to nothing), OPEN, the two lit windows (shrunk to nothing) and the gamepad;\n+the gamepad holding its thumbpad and its three buttons.\n+\n+The `focused` clip (8 s, looping) plays a disc: OPEN is pressed and the tray slides out; the disc drops onto it and the\n+tray goes in; the display reads the disc (CD-i), then plays, the gamepad's thumbpad and buttons played. Then OPEN again,\n+the tray comes out, the disc lifts away and the tray goes back in.\n+\n+Five materials (case, button, black, glass, print) and one texture, within the system model budget. Run from the repo\n+root:\n+\n+    python tools/console-models/cdi_print.py artifacts/cdi\n+    blender -b --factory-startup --python tools/console-models/cdi.py -- artifacts/cdi godot/themes/console/models/systems/cdimono1.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bmesh\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import cdi_layout as L  # noqa: E402\n+from amiga import curved, paint, rounded, solid  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, ease_out, key, phase, smooth, wobble  # noqa: E402\n+from mega_drive_cartridge import bevel, boolean, box, export, join, material, new_object, triangles  # noqa: E402\n+from msx_layout import inset  # noqa: E402\n+\n+TILT_YAW = -22.0     # degrees about the vertical: the right side comes towards the viewer\n+TILT_PITCH = 25.0    # degrees about the horizontal: the top tips up towards the viewer\n+ROUGHNESS = dict(case=0.55, button=0.5, black=0.6, glass=0.12, print=0.5)\n+DOOR_FRONT = 1.0     # the door's face, back from the front's\n+\n+\n+# ---- UVs -------------------------------------------------------------------------------------------------------------\n+\n+def atlas_uv(region, w, h):\n+    \"\"\"A face drawn w x h into region: (a, b) millimetres across it (a to the right, b down from its top) to UVs.\"\"\"\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+FRONT = atlas_uv(L.REGIONS[\"front\"], L.WIDTH, L.HEIGHT - L.FOOT)\n+\n+\n+def front_uv(co):\n+    \"\"\"The front seen from in front.\"\"\"\n+    return FRONT(co.x + L.WIDTH / 2, L.HEIGHT - co.z)\n+\n+\n+def swatch(name):\n+    x, y = L.SWATCHES[name]\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+def facing_front(c, n, name):\n+    return n.y < -0.9\n+\n+\n+# ---- The player ------------------------------------------------------------------------------------------------------\n+\n+def body(m):\n+    \"\"\"The body on its feet: the tray's opening cut into its front, the vents into its top, its front printed.\"\"\"\n+    w = L.WIDTH\n+    shell = box(\"cdi\", -w / 2, w / 2, 0.0, L.DEPTH, L.FOOT, L.HEIGHT, m[\"case\"])\n+    bevel(shell, 2.0, segments=2)\n+    (dx0, dx1), (dz0, dz1) = L.DOOR[\"x\"], L.DOOR[\"z\"]\n+    boolean(shell, box(\"opening\", dx0, dx1, -5.0, 150.0, dz0, dz1, m[\"black\"]))\n+    v = L.VENTS\n+    step = (v[\"y\"][1] - v[\"y\"][0]) / v[\"count\"]\n+    for x0, x1 in v[\"columns\"]:\n+        boolean(shell, join([box(\"vent\", x0, x1, v[\"y\"][0] + (k + 0.5) * step - v[\"width\"] / 2,\n+                                 v[\"y\"][0] + (k + 0.5) * step + v[\"width\"] / 2, L.HEIGHT - v[\"depth\"], L.HEIGHT + 5.0,\n+                                 m[\"black\"]) for k in range(v[\"count\"])], \"vents\"))\n+    paint(shell, m[\"print\"], front_uv, lambda c, n, name: n.y < -0.9 and c.y < 0.5 and name == \"case\")\n+    (fx0, fx1, fx2, fx3), ys = L.FEET[\"x\"], L.FEET[\"y\"]\n+    feet = [box(\"foot\", a, b, y0, y1, 0.0, L.FOOT + 1.0, m[\"black\"]) for a, b in ((fx0, fx1), (fx2, fx3))\n+            for y0, y1 in ys]\n+    return [shell] + feet\n+\n+\n+def button(name, x0, x1, z0, z1, m):\n+    part = box(name, x0, x1, -L.BUTTON_OUT, 1.0, z0, z1, m[\"button\"])\n+    bevel(part, 0.9, segments=2)\n+    return part\n+\n+\n+def window(m):\n+    (x0, x1), (z0, z1) = L.WINDOW[\"x\"], L.WINDOW[\"z\"]\n+    part = box(\"window\", x0, x1, -L.WINDOW[\"out\"], 1.0, z0, z1, m[\"glass\"])\n+    bevel(part, 0.6, segments=1)\n+    face = atlas_uv(L.REGIONS[\"display\"], x1 - x0, z1 - z0)\n+    paint(part, m[\"glass\"], lambda co: face(co.x - x0, z1 - co.z), facing_front)\n+    return part\n+\n+\n+def lit_window(m, name):\n+    \"\"\"A lit window over the dark one, its middle at its origin.\"\"\"\n+    (x0, x1), (z0, z1) = L.WINDOW[\"x\"], L.WINDOW[\"z\"]\n+    w, h = x1 - x0, z1 - z0\n+    face = atlas_uv(L.REGIONS[name], w, h)\n+    part = curved(name, w - 0.6, h - 0.6, 0.0, lambda a, b: face(0.3 + a * (w - 0.6), 0.3 + b * (h - 0.6)), m[\"glass\"],\n+                  nu=1, nv=1)\n+    return part, Vector(((x0 + x1) / 2, -L.WINDOW[\"out\"] - 0.15, (z0 + z1) / 2))\n+\n+\n+def tray(m):\n+    \"\"\"The tray, at rest in the body: its door, the platform behind it with the disc's recess.\"\"\"\n+    (dx0, dx1), (dz0, dz1) = L.DOOR[\"x\"], L.DOOR[\"z\"]\n+    g = 0.6\n+    door = box(\"tray\", dx0 + g, dx1 - g, DOOR_FRONT, DOOR_FRONT + 3.0, dz0 + g, dz1 - g, m[\"case\"])\n+    bevel(door, 0.6, segments=1)\n+    paint(door, m[\"print\"], front_uv, lambda c, n, name: n.y < -0.9)\n+    p = L.PLATFORM\n+    platform = box(\"platform\", *p[\"x\"], DOOR_FRONT + 3.0, DOOR_FRONT + 3.0 + p[\"depth\"], p[\"top\"] - p[\"thickness\"],\n+                   p[\"top\"], m[\"black\"])\n+    rx, ry = L.RECESS[\"centre\"]\n+    r = L.RECESS[\"diameter\"] / 2\n+    boolean(platform, solid(\"recess\", [[(rx + r * math.cos(a), DOOR_FRONT + ry + r * math.sin(a), z)\n+                                        for a in (2 * math.pi * k / 48 for k in range(48))]\n+                                       for z in (p[\"top\"] - L.RECESS[\"depth\"], p[\"top\"] + 5.0)], m[\"black\"]))\n+    return join([door, platform], \"tray\")\n+\n+\n+def disc(m):\n+    \"\"\"The disc, its middle at its origin, lying flat: a ring, its label on its top.\"\"\"\n+    ro, ri, t = L.DISC[\"diameter\"] / 2, L.DISC[\"hole\"] / 2, L.DISC[\"thickness\"]\n+    n = 64\n+    bm = bmesh.new()\n+    rings = [[bm.verts.new((r * math.cos(2 * math.pi * k / n), r * math.sin(2 * math.pi * k / n), z)) for k in range(n)]\n+             for r, z in ((ro, -t / 2), (ro, t / 2), (ri, t / 2), (ri, -t / 2))]\n+    for a, b in zip(rings, rings[1:] + rings[:1]):\n+        for k in range(n):\n+            j = (k + 1) % n\n+            bm.faces.new([a[k], a[j], b[j], b[k]])\n+    bmesh.ops.recalc_face_normals(bm, faces=bm.faces)\n+    part = new_object(\"disc\", bm, m[\"print\"])\n+    d = L.DISC[\"diameter\"]\n+    top = atlas_uv(L.REGIONS[\"disc_top\"], d, d)\n+    paint(part, m[\"print\"], lambda co: top(co.x + d / 2, d / 2 - co.y), lambda c, n_, name: n_.z > 0.9)\n+    paint(part, m[\"print\"], swatch(\"silver\"), lambda c, n_, name: n_.z <= 0.9)\n+    return part\n+\n+\n+# ---- The gamepad -----------------------------------------------------------------------------------------------------\n+\n+def pad(m):\n+    \"\"\"The gamepad in its own frame (its plan's origin, the table): its base, the plate on it with the thumbpad's ring\n+    and the buttons' well cut into it; the thumbpad and the buttons apart (the clip plays them), each its foot's middle\n+    at its origin.\"\"\"\n+    P = L.PAD\n+    outline = L.PAD_OUTLINE\n+    base = solid(\"pad\", [[(x, y, z) for x, y in outline] for z in (0.0, P[\"base\"])], m[\"case\"])\n+    bevel(base, 3.0, segments=2)\n+    i, top = P[\"plate\"]\n+    plate = solid(\"plate\", [[(x, y, z) for x, y in inset(outline, i)] for z in (P[\"base\"] - 1.0, top)], m[\"case\"])\n+    bevel(plate, 2.0, segments=2)\n+    d = P[\"dpad\"]\n+    cx, cy = d[\"at\"]\n+    floor = top - 2.5\n+    boolean(plate, solid(\"ring\", [[(cx + d[\"ring\"] * math.cos(a), cy + d[\"ring\"] * math.sin(a), z)\n+                                   for a in (2 * math.pi * k / 40 for k in range(40))] for z in (floor, top + 5.0)],\n+                         m[\"button\"]))\n+    b = P[\"buttons\"]\n+    (ax, ay), (bx, by) = b[\"at\"][0], b[\"at\"][-1]\n+    turn = math.atan2(by - ay, bx - ax)\n+    r = b[\"r\"] + b[\"margin\"]\n+    well = rounded(0.0, 0.0, math.hypot(bx - ax, by - ay) + 2 * r, 2 * r, r - 0.01, 6)\n+    mx, my = (ax + bx) / 2, (ay + by) / 2\n+    c, s = math.cos(turn), math.sin(turn)\n+    boolean(plate, solid(\"well\", [[(mx + x * c - y * s, my + x * s + y * c, z) for x, y in well]\n+                                  for z in (top - 2.0, top + 5.0)], m[\"button\"]))\n+    xs = [x for x, _ in L.PAD_OUTLINE]\n+    w = max(xs) - min(xs)\n+    face = atlas_uv(L.REGIONS[\"pad\"], w, 101.0)\n+    paint(plate, m[\"print\"], lambda co: face(co.x + w / 2, 101.0 - co.y), lambda c_, n, name: n.z > 0.9 and\n+          name == \"case\")\n+    shell = join([base, plate], \"pad\")\n+\n+    thumb = solid(\"dpad\", [[(d[\"disc\"] * math.cos(a), d[\"disc\"] * math.sin(a), z)\n+                            for a in (2 * math.pi * k / 32 for k in range(32))] for z in (-1.0, d[\"height\"])],\n+                  m[\"button\"])\n+    bevel(thumb, 1.2, segments=2)\n+    cross = join([box(\"arm\", -12.0, 12.0, -4.2, 4.2, d[\"height\"] - 0.5, d[\"height\"] + 1.6, m[\"button\"]),\n+                  box(\"arm\", -4.2, 4.2, -12.0, 12.0, d[\"height\"] - 0.5, d[\"height\"] + 1.6, m[\"button\"])], \"cross\")\n+    thumb = join([thumb, cross], \"dpad\")\n+    buttons = []\n+    for k, (x, y) in enumerate(b[\"at\"]):\n+        part = solid(f\"button_{k + 1}\", [[(b[\"r\"] * math.cos(a), b[\"r\"] * math.sin(a), z)\n+                                          for a in (2 * math.pi * j / 28 for j in range(28))]\n+                                         for z in (-1.0, b[\"height\"])], m[\"button\"])\n+        bevel(part, 1.4, segments=2)\n+        buttons.append((part, Vector((x, y, top - 2.0))))\n+    return shell, (thumb, Vector((cx, cy, floor))), buttons\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+def appear(f, f0, f1):\n+    return HIDDEN + (1 - HIDDEN) * back_out(phase(f, f0, f1)) if f >= f0 else HIDDEN\n+\n+\n+def tap(f, f0, hold=2):\n+    \"\"\"A press at f0: down in three frames, held, back up in four.\"\"\"\n+    return smooth(phase(f, f0, f0 + 3)) * (1 - smooth(phase(f, f0 + 3 + hold, f0 + 7 + hold)))\n+\n+\n+TILTS = [(112, \"right\"), (126, \"up\"), (140, \"left\"), (154, \"down\"), (176, \"right\")]\n+PRESSES = {\"button_1\": (118, 166), \"button_2\": (132, 186), \"button_3\": (150,)}\n+\n+\n+def tray_out(f):\n+    if f < 54:\n+        return back_out(phase(f, 12, 26), 1.2)\n+    if f < 196:\n+        return 1 - smooth(phase(f, 54, 70))\n+    if f < 220:\n+        return back_out(phase(f, 196, 210), 1.2)\n+    return 1 - smooth(phase(f, 222, 238))\n+\n+\n+def timeline(f):\n+    if f < 26 or f >= 222:\n+        dsk = Pose(scale=HIDDEN)\n+    elif f < 36:\n+        dsk = Pose(z=70.0, scale=appear(f, 26, 34))\n+    elif f < 48:\n+        dsk = Pose(z=70.0 * (1 - ease_in(phase(f, 36, 48))))\n+    elif f < 210:\n+        dsk = Pose(z=wobble(f, 48, 1.2, period=6.0, decay=5.0))\n+    else:\n+        dsk = Pose(z=60.0 * ease_out(phase(f, 210, 218)), scale=max(HIDDEN, 1 - ease_in(phase(f, 214, 222))))\n+    tilt = dict(right=(0.0, 1.0), left=(0.0, -1.0), up=(-1.0, 0.0), down=(1.0, 0.0))\n+    pitch = roll = 0.0\n+    for f0, way in TILTS:\n+        k = tap(f, f0, hold=4)\n+        pitch += 8.0 * tilt[way][0] * k\n+        roll += 8.0 * tilt[way][1] * k\n+    poses = {\"tray\": Pose(y=-L.TRAY_OUT * tray_out(f)), \"disc\": dsk,\n+             \"open\": Pose(y=1.5 * max(tap(f, f0) for f0 in (8, 50, 192))),\n+             \"display_disc\": Pose() if 72 <= f < 108 and (f < 80 or f % 8 < 6) else Pose(scale=HIDDEN),\n+             \"display_play\": Pose() if 108 <= f < 194 else Pose(scale=HIDDEN),\n+             \"dpad\": Pose(pitch=pitch, roll=roll)}\n+    for name, frames in PRESSES.items():\n+        poses[name] = Pose(z=-2.4 * max(tap(f, f0) for f0 in frames))\n+    return poses\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    printed = bpy.data.images.load(os.path.join(images, \"cdi_print.png\"))\n+    colours = dict(L.COLOURS, print=\"#FFFFFF\")\n+    m = {n: material(n, colours[n], r, printed if n in (\"print\", \"glass\") else None) for n, r in ROUGHNESS.items()}\n+\n+    buttons = {name: button(name, x0, x1, z0, z1, m) for name, x0, x1, z0, z1 in L.BUTTONS}\n+    opener = buttons.pop(\"open\")\n+    whole = join(body(m) + list(buttons.values()) + [window(m)], \"cdimono1\")\n+    drawer = tray(m)\n+    platter = disc(m)\n+    lit = {name: lit_window(m, name) for name in (\"display_disc\", \"display_play\")}\n+    shell, (thumb, thumb_at), presses = pad(m)\n+    for obj in [whole, opener, drawer, platter, shell, thumb] + [part for part, _ in presses]:\n+        for p in obj.data.polygons:\n+            p.use_smooth = True\n+        obj.data.set_sharp_from_angle(angle=math.radians(35))\n+\n+    # The node tree: the root (the view and the fit) holds the set; the set holds the moving parts at their places.\n+    root = bpy.data.objects.new(\"cdimono1_root\", None)\n+    scene.collection.objects.link(root)\n+    whole.parent = root\n+    rx, ry = L.RECESS[\"centre\"]\n+    seat = Vector((rx, DOOR_FRONT + ry, L.PLATFORM[\"top\"] - L.RECESS[\"depth\"] + L.DISC[\"thickness\"] / 2 + 0.1))\n+    for part in (drawer, opener):\n+        part.parent = whole\n+    platter.parent = drawer\n+    platter.location = seat\n+    for name, (part, where) in lit.items():\n+        part.parent = whole\n+        part.location = where\n+    (px, py), turn = L.PAD[\"at\"], math.radians(L.PAD[\"turn\"])\n+    shell.parent = whole\n+    shell.location = (px, py, 0.0)\n+    shell.rotation_euler = (0.0, 0.0, turn)\n+    thumb.parent = shell\n+    thumb.location = thumb_at\n+    for part, where in presses:\n+        part.parent = shell\n+        part.location = where\n+    nodes = {\"tray\": drawer, \"disc\": platter, \"open\": opener, \"dpad\": thumb, **{n: p for n, (p, _) in lit.items()},\n+             **{part.name: part for part, _ in presses}}\n+\n+    # The three-quarter view, then the spec's fit of the set at rest, as the root's transform.\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    rest = Matrix.Translation((px, py, 0.0)) @ Matrix.Rotation(turn, 4, \"Z\")\n+    points = [tilt @ v.co for v in whole.data.vertices] + [tilt @ rest @ v.co for v in shell.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [whole, opener, drawer, platter, shell, thumb] + [p for p, _ in lit.values()] + [p for p, _ in presses]\n+    print(f\"CD-i: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(whole.data)} the set), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "5a9d92f6a5064186355212be0f2a5423cb8ff911",
          "filename": "tools/console-models/cdi_layout.py",
          "status": "added",
          "additions": 72,
          "deletions": 0,
          "changes": 72,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fcdi_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fcdi_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fcdi_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,72 @@\n+\"\"\"The Philips CD-i 220's measurements, shared by cdi_print.py (which draws its printing and its display) and cdi.py\n+(which builds it). Plain Python: Blender's Python imports it too.\n+\n+The CD-i 220, one of Philips's consumer 200 series of CD-i players, a grey hi-fi box: 420 x 286 x 90 mm (HiFi Engine's\n+figures, its feet included), and its gamepad in front of it. Millimetres: x from its middle (left to right), y back from\n+its front face, z up from the table. The rest was measured from photographs on Wikimedia Commons (Evan-Amos's views of\n+it from the front left and right and with its gamepad, a view of it from in front, the gamepad from above), scaled by\n+its width.\n+\n+The body: a box on four feet, the vents in its top at the back right. Its front, left to right: the disc tray's door\n+(the dots and the Compact Disc Interactive logo on it, PHILIPS on the strip under it), the panel with PREVIOUS, PLAY,\n+NEXT and OPEN, the display's smoked window (it pulls down: the gamepad's socket is behind it), and the panel with\n+ON/OFF, PAUSE and STOP. The tray: its door, and behind it the platform the disc lies in, sliding out of the body's\n+opening. The gamepad (in front of the display, clear of the tray's way): the CD-i's own, its outline (PAD_OUTLINE) a\n+wide arch, a raised plate on it, the thumbpad in its ring at the left, three buttons in a slanted recess at the right.\n+\n+One texture (ATLAS square, REGIONS): `front` (the front seen from in front: the door's dots and logo, PHILIPS, the\n+buttons' names, the lines between the panels), `display` (the window: smoked red, the display's segments unlit, the\n+model's name and PULL TO OPEN), `display_disc` and `display_play` (the window lit: the disc read, CD-i's logo; and\n+playing, the track and its time), `disc_top` (the disc's label: a made-up game, Odyssey), `pad` (the gamepad's top:\n+CD-i, the thumbpad's marks, the buttons' dots, SPEED), and swatches. A region is (x, y, width, height) in pixels.\n+\"\"\"\n+\n+ATLAS = 1024\n+\n+WIDTH, DEPTH, HEIGHT = 420.0, 286.0, 88.0\n+FOOT = 8.0                                                  # the feet's height: the body stands on them\n+FEET = dict(x=(-196.0, -166.0, 166.0, 196.0), y=((14.0, 34.0), (252.0, 272.0)))   # each foot's x0, x1; y0, y1\n+\n+# The front, (x, z): the tray's door, the panels, the display's window; the strip along the top.\n+DOOR = dict(x=(-206.4, -28.7), z=(25.6, 78.0), inset=1.5)\n+OPENING = dict(x=(-204.0, -31.0), z=(27.0, 76.0), depth=150.0)     # the body's opening behind the door\n+MIDDLE = dict(x=(-27.0, 20.0))\n+WINDOW = dict(x=(21.5, 165.0), z=(24.0, 74.0), out=1.0)\n+RIGHT_PANEL = dict(x=(167.0, 208.0))\n+TOP_STRIP = 78.0                                            # the line along the front under the top strip\n+\n+# The buttons, (name, x0, x1, z0, z1), each 2 mm proud of the front.\n+BUTTONS = [(\"previous\", 0.0, 11.0, 58.0, 62.5), (\"play\", 0.0, 11.0, 46.0, 50.5), (\"next\", 0.0, 11.0, 34.0, 38.5),\n+           (\"open\", -21.0, -10.0, 34.0, 38.5), (\"onoff\", 172.0, 204.0, 58.0, 64.0), (\"pause\", 172.0, 184.0, 26.0, 30.5),\n+           (\"stop\", 192.0, 204.0, 26.0, 30.5)]\n+BUTTON_OUT = 2.0\n+\n+# The tray: its door, and behind it the platform (x0, x1, its depth, its top) with the disc's recess; how far it\n+# slides out.\n+PLATFORM = dict(x=(-195.0, -40.0), depth=140.0, top=46.0, thickness=6.0)\n+DISC = dict(diameter=120.0, hole=15.0, thickness=1.2)\n+RECESS = dict(centre=((-195.0 - 40.0) / 2, 72.0), diameter=122.0, depth=1.6)   # (x, y back from the door's front)\n+TRAY_OUT = 128.0\n+\n+# The vents in the top: three columns of slots running across, at the back right.\n+VENTS = dict(columns=[(104.0, 128.0), (136.0, 160.0), (168.0, 192.0)], y=(152.0, 268.0), count=24, width=1.8, depth=1.5)\n+\n+# The gamepad: its outline in its own plan (x across, y away from the player), its middle where AT puts it, turned by\n+# TURN degrees; its base, the plate on it, the thumbpad and the buttons.\n+PAD_OUTLINE = [(60.0, 2.0), (75.0, 8.0), (88.0, 25.0), (95.0, 45.0), (94.0, 62.0), (88.0, 78.0), (75.0, 90.0),\n+               (55.0, 97.0), (25.0, 100.0), (0.0, 101.0), (-25.0, 100.0), (-55.0, 97.0), (-75.0, 90.0),\n+               (-88.0, 78.0), (-94.0, 62.0), (-95.0, 45.0), (-88.0, 25.0), (-75.0, 8.0), (-60.0, 2.0), (-46.0, 5.0),\n+               (-36.0, 15.0), (-24.0, 30.0), (-12.0, 38.0), (0.0, 40.0), (12.0, 38.0), (24.0, 30.0), (36.0, 15.0),\n+               (46.0, 5.0)]\n+PAD = dict(at=(96.0, -112.0), turn=-9.0, base=14.0, plate=(7.0, 22.0),    # its base's height; the plate's inset, top\n+           dpad=dict(at=(-50.0, 62.0), ring=22.0, disc=16.0, height=5.0),\n+           buttons=dict(at=[(34.0, 48.0), (52.0, 55.0), (70.0, 62.0)], r=8.0, height=4.0, margin=2.5))\n+\n+COLOURS = dict(case=\"#5D5E61\", button=\"#4C4D50\", black=\"#111113\", glass=\"#FFFFFF\")\n+\n+REGIONS = dict(front=(0, 0, 1024, 195), display=(0, 203, 300, 105), display_disc=(308, 203, 300, 105),\n+               display_play=(616, 203, 300, 105), disc_top=(0, 316, 256, 256), pad=(264, 316, 380, 202))\n+SWATCHES = dict(case=(680, 340), button=(712, 340), black=(744, 340), silver=(776, 340), dark=(808, 340))\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s"
        },
        {
          "sha": "d91a3b5ca6d89837672a384112fd7e2b76b1f611",
          "filename": "tools/console-models/cdi_print.py",
          "status": "added",
          "additions": 198,
          "deletions": 0,
          "changes": 198,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fcdi_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fcdi_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fcdi_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,198 @@\n+\"\"\"The Philips CD-i 220's printing and display, for the console theme's CD-i model (tools/console-models/cdi.py).\n+\n+Draws its atlas, its regions as cdi_layout.py places them, each in millimetres as the model's planar UVs map it: the\n+front seen from in front (the door's dots and the Compact Disc Interactive logo, PHILIPS on the strip under it, the\n+buttons' names, the lines between the panels); the display's window three ways (smoked red, its segments unlit, CDI\n+220 CD-INTERACTIVE PLAYER and PULL TO OPEN; lit, the disc read: CD-i in its digits; lit, playing: PLAY, the track and\n+its time); the disc's label (a made-up game, Odyssey); the gamepad's top (CD-i, the thumbpad's marks, the buttons'\n+dots, SPEED); and the swatches small parts take their colour from. Run it with the system Python (Pillow):\n+\n+    python tools/console-models/cdi_print.py <out folder>\n+\n+It writes cdi_print.png there.\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+from PIL import Image, ImageDraw\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import cdi_layout as L  # noqa: E402\n+from psp_print import ship, space  # noqa: E402\n+from retro_pc_print import rgb, shade  # noqa: E402\n+from windows_pc_print import Region  # noqa: E402\n+\n+CASE = rgb(L.COLOURS[\"case\"])\n+LABEL = (196, 198, 200)\n+SMOKE = (44, 16, 18)\n+VFD = (150, 244, 228)\n+UNLIT = (60, 26, 28)\n+\n+\n+# ---- The front ---------------------------------------------------------------------------------------------------------\n+\n+def disc_logo(reg, x, y, h, ink):\n+    \"\"\"The Compact Disc Interactive logo, h tall, its top left at (x, y): COMPACT over disc, Interactive in a box.\"\"\"\n+    reg.text(\"COMPACT\", \"arial.ttf\", h * 0.16, x + h * 0.62, y + h * 0.1, ink)\n+    reg.text(\"disc\", \"arialbd.ttf\", h * 0.62, x + h * 0.62, y + h * 0.48, ink)\n+    reg.rect_(x + h * 0.05, y + h * 0.78, x + h * 1.2, y + h * 1.02, None, outline=ink, width=0.25)\n+    reg.text(\"Interactive\", \"arial.ttf\", h * 0.17, x + h * 0.62, y + h * 0.9, ink)\n+\n+\n+def front():\n+    w, h = L.WIDTH, L.HEIGHT - L.FOOT\n+    reg = Region(L.REGIONS[\"front\"], w, h, CASE)\n+\n+    def at(x, z):\n+        return x + w / 2, L.HEIGHT - z\n+    line = shade(CASE, -0.3)\n+    (ax, ay), (bx, _) = at(-w / 2, L.TOP_STRIP), at(w / 2, L.TOP_STRIP)\n+    reg.line([(ax, ay), (bx, ay)], line, 0.5)\n+    reg.line([(ax, ay + 0.5), (bx, ay + 0.5)], shade(CASE, 0.1), 0.3)\n+    for x in (L.MIDDLE[\"x\"][1] + 0.75, L.RIGHT_PANEL[\"x\"][0] - 1.0):            # the lines between the panels\n+        reg.line([at(x, L.FOOT), at(x, L.TOP_STRIP)], line, 0.5)\n+    (dx0, dx1), (dz0, dz1) = L.DOOR[\"x\"], L.DOOR[\"z\"]\n+    reg.rect_(*at(dx0 - 0.8, dz1 + 0.8), *at(dx1 + 0.8, dz0 - 0.8), shade(CASE, -0.4))   # the door's gap\n+    reg.rect_(*at(dx0, dz1), *at(dx1, dz0), shade(CASE, -0.03))\n+    for k in range(7):                                      # the door's dots\n+        reg.circle(*at(dx0 + 8.0 + 2.6 * k, dz1 - 9.0), 0.35, shade(CASE, 0.22))\n+    disc_logo(reg, *at(dx0 + 7.0, dz0 + 14.0), 10.0, shade(CASE, 0.22))\n+    reg.text(\"PHILIPS\", \"arialbd.ttf\", 5.4, *at(dx0 + 22.0, (L.FOOT + dz0) / 2), (214, 214, 216))\n+    for name, x0, x1, z0, z1 in L.BUTTONS:                  # the buttons' names over them\n+        reg.rect_(*at(x0 - 0.7, z1 + 0.7), *at(x1 + 0.7, z0 - 0.7), shade(CASE, -0.35), r=1.2)\n+        label = {\"previous\": \"PREVIOUS\", \"play\": \"PLAY\", \"next\": \"NEXT\", \"open\": \"OPEN\", \"onoff\": \"ON / OFF\",\n+                 \"pause\": \"PAUSE\", \"stop\": \"STOP\"}[name]\n+        reg.text(label, \"arial.ttf\", 1.9, *at((x0 + x1) / 2, z1 + 2.4), LABEL)\n+    (wx0, wx1), (wz0, wz1) = L.WINDOW[\"x\"], L.WINDOW[\"z\"]\n+    reg.rect_(*at(wx0 - 0.6, wz1 + 0.6), *at(wx1 + 0.6, wz0 - 0.6), shade(CASE, -0.4))\n+    return reg\n+\n+\n+# ---- The display -------------------------------------------------------------------------------------------------------\n+\n+SEGMENTS = {\"0\": \"abcdef\", \"1\": \"bc\", \"2\": \"abged\", \"3\": \"abgcd\", \"4\": \"fgbc\", \"5\": \"afgcd\", \"6\": \"afgedc\", \"7\": \"abc\",\n+            \"8\": \"abcdefg\", \"9\": \"abcdfg\", \"-\": \"g\", \"C\": \"afed\", \"d\": \"bcdeg\", \"i\": \"e\", \" \": \"\"}\n+\n+\n+def digit(reg, ch, x, y, w, h, colour):\n+    \"\"\"A seven-segment digit, w x h, its top left at (x, y).\"\"\"\n+    t = w * 0.16\n+    segs = {\"a\": (x + t, y, x + w - t, y + t), \"g\": (x + t, y + h / 2 - t / 2, x + w - t, y + h / 2 + t / 2),\n+            \"d\": (x + t, y + h - t, x + w - t, y + h), \"f\": (x, y + t, x + t, y + h / 2 - t / 2),\n+            \"b\": (x + w - t, y + t, x + w, y + h / 2 - t / 2), \"e\": (x, y + h / 2 + t / 2, x + t, y + h - t),\n+            \"c\": (x + w - t, y + h / 2 + t / 2, x + w, y + h - t)}\n+    for s in SEGMENTS[ch]:\n+        x0, y0, x1, y1 = segs[s]\n+        reg.rect_(x0, y0, x1, y1, colour, r=t * 0.3)\n+    if ch == \"i\":\n+        reg.rect_(x, y + h / 2 - t * 1.6, x + t, y + h / 2 - t * 0.6, colour)\n+\n+\n+def display(name):\n+    \"\"\"The window: smoked red, the segments faint behind it; lit as the disc is read, or playing.\"\"\"\n+    w, h = L.WINDOW[\"x\"][1] - L.WINDOW[\"x\"][0], L.WINDOW[\"z\"][1] - L.WINDOW[\"z\"][0]\n+    reg = Region(L.REGIONS[name], w, h, SMOKE)\n+    lit = name != \"display\"\n+\n+    def ink(on):\n+        return VFD if lit and on else UNLIT\n+    text = {\"display\": \"88888888\", \"display_disc\": \"  Cd-i  \", \"display_play\": \"01  0012\"}[name]\n+    for k, ch in enumerate(text):\n+        if not lit:\n+            ch = \"8\"\n+        digit(reg, ch, 30.0 + 10.0 * k, 17.0, 7.0, 12.0, ink(ch != \"8\" or lit))\n+    if name == \"display_play\":\n+        reg.circle(30.0 + 10.0 * 5 + 8.6, 20.5, 0.6, VFD)\n+        reg.circle(30.0 + 10.0 * 5 + 8.6, 25.5, 0.6, VFD)\n+    playing = name == \"display_play\"\n+    labels = [(\"MUTE\", 14.0, 8.0, False), (\"PAUSE\", 14.0, 14.0, False), (\"PLAY\", 14.0, 20.0, playing),\n+              (\"SHUFFLE\", 44.0, 9.0, False), (\"REPEAT\", 78.0, 11.0, False), (\"SCAN\", 104.0, 12.0, False),\n+              (\"TRACK\", 38.0, 33.5, playing), (\"TOTAL\", 62.0, 33.5, False), (\"TIME\", 100.0, 33.5, playing)]\n+    for word, x, y, on in labels:\n+        reg.text(word, \"arial.ttf\", 2.4, x, y, ink(on))\n+    reg.poly([(9.0, 23.0), (14.0, 26.0), (9.0, 29.0)], ink(playing))\n+    for k, (word, on) in enumerate(((\"COMPACT DISC\", lit), (\"INTERACTIVE\", lit), (\"GRAPHICS\", False))):\n+        y = 15.0 + 5.2 * k\n+        if k:\n+            reg.rect_(116.0, y - 2.0, 136.0, y + 2.0, None, outline=ink(on), width=0.3)\n+        reg.text(word, \"arial.ttf\", 2.2, 126.0, y, ink(on))\n+    reg.text(\"CDI 220  CD-INTERACTIVE PLAYER\", \"arial.ttf\", 2.0, 4.0, h - 3.2, (150, 120, 120), anchor=\"lm\")\n+    reg.text(\"PULL TO OPEN\", \"arial.ttf\", 2.0, w - 4.0, h - 3.2, (150, 120, 120), anchor=\"rm\")\n+    sheen = reg.layer()\n+    ImageDraw.Draw(sheen).rectangle(reg.box(0.0, 0.0, w, h * 0.18), fill=(255, 255, 255, 26))\n+    reg.over(sheen)\n+    return reg\n+\n+\n+# ---- The disc, the gamepad ---------------------------------------------------------------------------------------------\n+\n+def disc_top():\n+    d = L.DISC[\"diameter\"]\n+    reg = Region(L.REGIONS[\"disc_top\"], d, d, (196, 200, 206))\n+    c = d / 2\n+    art = space((600, 600)).convert(\"RGB\")\n+    da = ImageDraw.Draw(art)\n+    ship(da, 180, 330, 4.0, \"#FFFFFF\", \"#FFB23C\")\n+    x0, y0, x1, y1 = reg.box(c - 58.0, c - 58.0, c + 58.0, c + 58.0)\n+    mask = Image.new(\"L\", reg.img.size, 0)\n+    dm = ImageDraw.Draw(mask)\n+    dm.ellipse(reg.box(c - 58.0, c - 58.0, c + 58.0, c + 58.0), fill=255)\n+    dm.ellipse(reg.box(c - 22.0, c - 22.0, c + 22.0, c + 22.0), fill=0)\n+    layer = Image.new(\"RGBA\", reg.img.size, (0, 0, 0, 0))\n+    layer.paste(art.resize((round(x1 - x0), round(y1 - y0)), Image.LANCZOS), (round(x0), round(y0)))\n+    reg.img.paste(layer, (0, 0), mask)\n+    reg.circle(c, c, 22.0, outline=(150, 154, 160), width=0.4)\n+    reg.text(\"ODYSSEY\", \"ariblk.ttf\", 11.0, c, c - 36.0, (255, 214, 70))\n+    disc_logo(reg, c + 26.0, c + 26.0, 12.0, (236, 236, 240))\n+    reg.circle(c, c, 7.5, (60, 62, 66))\n+    return reg\n+\n+\n+def pad():\n+    xs = [x for x, _ in L.PAD_OUTLINE]\n+    w, d = max(xs) - min(xs), 101.0\n+    reg = Region(L.REGIONS[\"pad\"], w, d, CASE)\n+\n+    def at(x, y):\n+        return x + w / 2, d - y\n+    ink = shade(CASE, -0.25)\n+    p = L.PAD[\"dpad\"]\n+    cx, cy = at(*p[\"at\"])\n+    r = p[\"ring\"] - 3.0\n+    for k in range(4):                                       # the ring's triangles and dots\n+        a = math.radians(90 * k)\n+        tx, ty = cx + r * math.cos(a), cy - r * math.sin(a)\n+        reg.poly([(tx + 1.4 * math.cos(a), ty - 1.4 * math.sin(a)),\n+                  (tx - 1.2 * math.cos(a) + 1.3 * math.sin(a), ty + 1.2 * math.sin(a) + 1.3 * math.cos(a)),\n+                  (tx - 1.2 * math.cos(a) - 1.3 * math.sin(a), ty + 1.2 * math.sin(a) - 1.3 * math.cos(a))], ink)\n+        b = a + math.pi / 4\n+        reg.circle(cx + r * math.cos(b), cy - r * math.sin(b), 0.6, ink)\n+    reg.text(\"CD-i\", \"arialbi.ttf\", 9.0, *at(2.0, 80.0), shade(CASE, -0.12))\n+    for k, (bx, by) in enumerate(L.PAD[\"buttons\"][\"at\"]):   # the buttons' dots\n+        tx, ty = at(bx, by + L.PAD[\"buttons\"][\"r\"] + 3.6)\n+        text = (\"\u2022\", \"\u2022\u2022\", \"\u2022+\u2022\u2022\")[k]\n+        reg.text(text, \"seguisym.ttf\", 2.6, tx, ty, ink)\n+    reg.text(\"SPEED\", \"arialbd.ttf\", 2.4, *at(52.0, 22.0), ink)\n+    reg.text(\"I  II  III\", \"arial.ttf\", 2.0, *at(64.0, 14.0), ink)\n+    return reg\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), CASE)\n+    for reg in (front(), display(\"display\"), display(\"display_disc\"), display(\"display_play\"), disc_top(), pad()):\n+        reg.paste(image)\n+    d = ImageDraw.Draw(image)\n+    for name, colour in ((\"case\", CASE), (\"button\", rgb(L.COLOURS[\"button\"])), (\"black\", rgb(L.COLOURS[\"black\"])),\n+                         (\"silver\", (200, 204, 210)), (\"dark\", shade(CASE, -0.4))):\n+        x, y = L.SWATCHES[name]\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colour)\n+    path = os.path.join(out, \"cdi_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "17f36ac9caac96b3386739b690739b004b91a856",
          "filename": "tools/console-models/cdtv.py",
          "status": "added",
          "additions": 276,
          "deletions": 0,
          "changes": 276,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fcdtv.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fcdtv.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fcdtv.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,276 @@\n+\"\"\"The console theme's Commodore CDTV system model (godot/themes/console/models/systems/cdtv.glb): the CDTV and its\n+remote controller.\n+\n+Built in Blender (cdtv_layout.py has the measurements, taken from a period review and photographs). The case is a box on\n+four silver feet, the front's foot a strip recessed under a ledge, the slot and the eject button's hole cut into the\n+front; the display's faces and the LEDs sit on the front. The remote is a slab, its keys standing on its middle, the\n+D-pad on its left end and A and B on its right. The printing is one texture (cdtv_print.py's), projected from in front\n+onto the front and the display, and from above onto the top's grille, the remote and the caddy.\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: the right end towards the viewer, the\n+top tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m) holding the set; the set\n+holding the caddy (waiting in front of the slot, shrunk to nothing), the eject button, the two LEDs and the display's\n+five faces (each lit one in front of the front face, the rest inside the case), and the remote's D-pad, A and B.\n+\n+The `focused` clip (8 s, looping): the caddy slides into the slot; the green LED comes on, the yellow one flickers while\n+the disc is read, and the clock gives way to track 01, its time counting; the D-pad rocks and A and B fire; then the\n+eject button, the caddy slides out and goes, and the clock is back.\n+\n+Three materials (case, remote, caddy) and one 1024\u00b2 texture, within the system model budget. Run from the repo root:\n+\n+    python tools/console-models/cdtv_print.py artifacts/cdtv\n+    blender -b --factory-startup --python tools/console-models/cdtv.py -- artifacts/cdtv godot/themes/console/models/systems/cdtv.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import cdtv_layout as L  # noqa: E402\n+from amiga import paint, rounded, solid  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, ease_out, key, phase, smooth  # noqa: E402\n+from mega_drive_cartridge import bevel, boolean, box, export, join, material, triangles  # noqa: E402\n+\n+TILT_YAW = -24.0     # degrees about the vertical: the right end comes towards the viewer\n+TILT_PITCH = 24.0    # degrees about the horizontal: the top tips up towards the viewer\n+ROUGHNESS = dict(case=0.45, remote=0.5, caddy=0.35)\n+FACES = (\"clock\", \"time_0\", \"time_1\", \"time_2\", \"time_3\")\n+LIT, DARK = -0.3, 1.0     # where a lit face or LED sits (y): in front of the front face; and an unlit one, inside\n+\n+\n+def atlas_uv(region, w, h):\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+def swatch(name):\n+    x, y = L.SWATCHES[name]\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+def colour(obj, mat, name):\n+    paint(obj, mat, swatch(name), lambda c, n, kind: True)\n+    return obj\n+\n+\n+FRONT = atlas_uv(L.REGIONS[\"front\"], L.WIDTH, L.HEIGHT - L.FEET[\"height\"])\n+VENTS = atlas_uv(L.REGIONS[\"vents\"], L.WIDTH, 130.0)\n+\n+\n+def front_uv(co):\n+    return FRONT(co.x + L.WIDTH / 2, L.HEIGHT - co.z)\n+\n+\n+def disc(name, x, y, z0, z1, r, mat, segments=24):\n+    ring = [(x + r * math.cos(2 * math.pi * k / segments), y + r * math.sin(2 * math.pi * k / segments))\n+            for k in range(segments)]\n+    return solid(name, [[(px, py, z) for px, py in ring] for z in (z0, z1)], mat)\n+\n+\n+# ---- The CDTV --------------------------------------------------------------------------------------------------------\n+\n+def case(m):\n+    \"\"\"The case, the strip, the slot and the eject button's hole cut in; the feet; printed.\"\"\"\n+    half, foot = L.WIDTH / 2, L.FEET[\"height\"]\n+    body = box(\"cdtv\", -half, half, 0.0, L.DEPTH, foot, L.HEIGHT, m[\"case\"])\n+    bevel(body, 1.5, segments=2)\n+    z0, z1 = L.STRIP[\"z\"]\n+    boolean(body, box(\"strip\", -half - 1.0, half + 1.0, -1.0, L.STRIP[\"depth\"], z0 - 1.0, z1, m[\"case\"]))\n+    (sx0, sx1), (sz0, sz1) = L.SLOT[\"x\"], L.SLOT[\"z\"]\n+    boolean(body, box(\"slot\", sx0, sx1, -1.0, L.SLOT[\"depth\"], sz0, sz1, m[\"case\"]))\n+    (ex0, ex1), (ez0, ez1) = L.EJECT[\"x\"], L.EJECT[\"z\"]\n+    boolean(body, box(\"eject_hole\", ex0, ex1, -1.0, L.EJECT[\"depth\"], ez0, ez1, m[\"case\"]))\n+    colour(body, m[\"case\"], \"case\")\n+    paint(body, m[\"case\"], front_uv, lambda c, n, kind: n.y < -0.9)\n+    paint(body, m[\"case\"], lambda co: VENTS(co.x + half, 320.0 - co.y), lambda c, n, kind: n.z > 0.9 and c.y > 190.0)\n+\n+    def inside(c):\n+        \"\"\"In the slot or the eject button's hole.\"\"\"\n+        return (sx0 - 0.5 < c.x < sx1 + 0.5 and sz0 - 0.5 < c.z < sz1 + 0.5\n+                or ex0 - 0.5 < c.x < ex1 + 0.5 and ez0 - 0.5 < c.z < ez1 + 0.5)\n+    paint(body, m[\"case\"], swatch(\"dark\"), lambda c, n, kind: c.y > 0.5 and inside(c))\n+    feet = [colour(disc(\"foot\", side * L.FEET[\"x\"], y, 0.0, foot + 1.0, L.FEET[\"r\"], m[\"case\"]), m[\"case\"], \"silver\")\n+            for side in (-1, 1) for y in L.FEET[\"y\"]]\n+    return join([body] + feet, \"cdtv\")\n+\n+\n+def face(m, name):\n+    \"\"\"One of the display's faces, facing the viewer, its middle at its origin.\"\"\"\n+    (x0, x1), (z0, z1) = L.DISPLAY[\"x\"], L.DISPLAY[\"z\"]\n+    w, h = x1 - x0, z1 - z0\n+    part = box(name, -w / 2, w / 2, -0.1, 0.1, -h / 2, h / 2, m[\"case\"])\n+    colour(part, m[\"case\"], \"dark\")\n+    uv = atlas_uv(L.REGIONS[name], w, h)\n+    paint(part, m[\"case\"], lambda co: uv(co.x + w / 2, h / 2 - co.z), lambda c, n, kind: n.y < -0.9)\n+    return part\n+\n+\n+def led(m, name):\n+    r = L.LEDS[\"r\"]\n+    part = disc(name, 0.0, 0.0, -0.1, 0.1, r, m[\"case\"], 12)\n+    part.data.transform(Matrix.Rotation(math.radians(90.0), 4, \"X\"))\n+    return colour(part, m[\"case\"], name)\n+\n+\n+def eject(m):\n+    \"\"\"The eject button, its front's middle at its origin.\"\"\"\n+    (ex0, ex1), (ez0, ez1) = L.EJECT[\"x\"], L.EJECT[\"z\"]\n+    w, h = ex1 - ex0 - 1.0, ez1 - ez0 - 1.0\n+    part = box(\"eject\", -w / 2, w / 2, 0.0, 3.0, -h / 2, h / 2, m[\"case\"])\n+    bevel(part, 0.5, segments=1)\n+    return colour(part, m[\"case\"], \"key\")\n+\n+\n+def caddy(m):\n+    \"\"\"The caddy, lying flat, its leading edge's middle at its origin, its top printed (the disc through its window).\"\"\"\n+    w, length, t = L.CADDY[\"size\"]\n+    part = box(\"caddy\", -w / 2, w / 2, -length, 0.0, -t / 2, t / 2, m[\"caddy\"])\n+    bevel(part, 1.2, segments=2)\n+    colour(part, m[\"caddy\"], \"caddy\")\n+    top = atlas_uv(L.REGIONS[\"caddy\"], w, length)\n+    paint(part, m[\"caddy\"], lambda co: top(co.x + w / 2, -co.y), lambda c, n, kind: n.z > 0.9)\n+    return part\n+\n+\n+# ---- The remote ------------------------------------------------------------------------------------------------------\n+\n+def remote(m):\n+    \"\"\"The remote at its place: the slab, its keys; the D-pad, A and B apart (each with where it goes).\"\"\"\n+    length, depth, height = L.REMOTE[\"size\"]\n+    x, y, turn = L.REMOTE[\"at\"]\n+    place = Matrix.Translation((x, y, 0.0)) @ Matrix.Rotation(math.radians(turn), 4, \"Z\")\n+    slab = solid(\"remote\", [[(px, py, 0.0) for px, py in rounded(0.0, 0.0, length, depth, 10.0, 4)],\n+                            [(px, py, height) for px, py in rounded(0.0, 0.0, length - 4.0, depth - 4.0, 8.0, 4)]],\n+                 m[\"remote\"])\n+    bevel(slab, 1.5, segments=2)\n+    colour(slab, m[\"remote\"], \"remote\")\n+    top = atlas_uv(L.REGIONS[\"remote\"], length, depth)\n+    paint(slab, m[\"remote\"], lambda co: top(co.x + length / 2, depth / 2 - co.y), lambda c, n, kind: n.z > 0.9)\n+    parts = [slab]\n+    kw, kh = L.REMOTE[\"key\"]\n+    for (kx, ky), name in zip(L.KEYS, L.KEY_NAMES):\n+        part = box(\"key\", kx - kw / 2, kx + kw / 2, ky - kh / 2, ky + kh / 2, height - 1.0, height + 2.2, m[\"remote\"])\n+        bevel(part, 0.6, segments=1)\n+        parts.append(colour(part, m[\"remote\"], \"red\" if name == \"POWER\" else \"key\"))\n+    whole = join(parts, \"remote\")\n+    whole.data.transform(place)\n+\n+    px, py, reach, arm = L.REMOTE[\"pad\"]\n+    cross = join([box(\"arm\", -reach, reach, -arm / 2, arm / 2, -1.0, 4.0, m[\"remote\"]),\n+                  box(\"arm\", -arm / 2, arm / 2, -reach, reach, -1.0, 4.0, m[\"remote\"])], \"pad\")\n+    bevel(cross, 1.2, segments=2, angle=30)\n+    colour(cross, m[\"remote\"], \"remote\")\n+    moving = {\"pad\": (cross, place @ Vector((px, py, height)))}\n+    for name in (\"a\", \"b\"):\n+        bx, by = L.REMOTE[name]\n+        part = disc(name, 0.0, 0.0, -1.0, 3.0, L.REMOTE[\"r\"], m[\"remote\"], 20)\n+        bevel(part, 0.8, segments=2)\n+        moving[name] = (colour(part, m[\"remote\"], \"remote\"), place @ Vector((bx, by, height)))\n+    for part, _ in moving.values():\n+        part.data.transform(Matrix.Rotation(math.radians(turn), 4, \"Z\"))\n+    return whole, moving\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+def tap(f, f0, hold=2):\n+    return smooth(phase(f, f0, f0 + 3)) * (1 - smooth(phase(f, f0 + 3 + hold, f0 + 7 + hold)))\n+\n+\n+def shown(f):\n+    if f < 60 or f >= 176:\n+        return \"clock\"\n+    return \"time_%d\" % min(3, (f - 60) // 30)\n+\n+\n+def timeline(f):\n+    out, up = L.CADDY[\"out\"], 30.0\n+    if f < 10 or f >= 198:\n+        disk = Pose(y=-out, z=up, scale=HIDDEN)\n+    elif f < 17:\n+        disk = Pose(y=-out, z=up, scale=max(HIDDEN, back_out(phase(f, 10, 17))))\n+    elif f < 24:\n+        disk = Pose(y=-out, z=up * (1 - ease_in(phase(f, 17, 24))))\n+    elif f < 176:\n+        disk = Pose(y=-out * (1 - smooth(phase(f, 24, 42))))\n+    elif f < 190:\n+        disk = Pose(y=-out * smooth(phase(f, 176, 190)))\n+    else:\n+        disk = Pose(y=-out, z=up * ease_out(phase(f, 190, 198)), scale=max(HIDDEN, 1 - ease_in(phase(f, 194, 198))))\n+    poses = {\"caddy\": disk, \"eject\": Pose(y=1.8 * tap(f, 170))}\n+    for name in FACES:\n+        poses[name] = Pose() if name == shown(f) else Pose(y=DARK - LIT)\n+    playing = 44 <= f < 174\n+    reading = 46 <= f < 90 and (f // 3) % 3 != 2\n+    poses[\"green\"] = Pose() if playing else Pose(y=DARK - LIT)\n+    poses[\"yellow\"] = Pose() if reading else Pose(y=DARK - LIT)\n+    t = phase(f, 70, 168)\n+    k = math.sin(math.pi * t)\n+    poses[\"pad\"] = Pose(pitch=7.0 * k * math.sin(2 * math.pi * 3.0 * t), roll=7.0 * k * math.cos(2 * math.pi * 2.0 * t))\n+    poses[\"a\"] = Pose(z=-1.8 * max(tap(f, f0) for f0 in (84, 104, 124, 144)))\n+    poses[\"b\"] = Pose(z=-1.8 * max(tap(f, f0) for f0 in (96, 136, 156)))\n+    return poses\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    atlas = bpy.data.images.load(os.path.join(images, \"cdtv_print.png\"))\n+    m = {name: material(name, L.COLOURS[\"print\"], r, atlas) for name, r in ROUGHNESS.items()}\n+\n+    controller, moving = remote(m)\n+    whole = join([case(m), controller], \"cdtv\")\n+    for p in whole.data.polygons:\n+        p.use_smooth = p.area < 400.0                     # the big flat faces flat\n+    whole.data.set_sharp_from_angle(angle=math.radians(35))\n+\n+    root = bpy.data.objects.new(\"cdtv_root\", None)\n+    scene.collection.objects.link(root)\n+    whole.parent = root\n+    (sx0, sx1), (sz0, sz1) = L.SLOT[\"x\"], L.SLOT[\"z\"]\n+    w, length, _ = L.CADDY[\"size\"]\n+    moving[\"caddy\"] = (caddy(m), Vector(((sx0 + sx1) / 2, length - L.CADDY[\"proud\"], (sz0 + sz1) / 2)))\n+    (ex0, ex1), (ez0, ez1) = L.EJECT[\"x\"], L.EJECT[\"z\"]\n+    moving[\"eject\"] = (eject(m), Vector(((ex0 + ex1) / 2, -1.0, (ez0 + ez1) / 2)))\n+    (dx0, dx1), (dz0, dz1) = L.DISPLAY[\"x\"], L.DISPLAY[\"z\"]\n+    for name in FACES:\n+        moving[name] = (face(m, name), Vector(((dx0 + dx1) / 2, LIT, (dz0 + dz1) / 2)))\n+    for name in (\"green\", \"yellow\"):\n+        lx, lz = L.LEDS[name]\n+        moving[name] = (led(m, name), Vector((lx, LIT, lz)))\n+    nodes = {}\n+    for name, (part, where) in moving.items():\n+        for p in part.data.polygons:\n+            p.use_smooth = True\n+        part.data.set_sharp_from_angle(angle=math.radians(35))\n+        part.parent = whole\n+        part.location = where\n+        nodes[name] = part\n+\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    points = [tilt @ v.co for v in whole.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [whole] + list(nodes.values())\n+    print(f\"CDTV: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(whole.data)} the set), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "7f645565e2561397d483a8406934024b202ae085",
          "filename": "tools/console-models/cdtv_layout.py",
          "status": "added",
          "additions": 68,
          "deletions": 0,
          "changes": 68,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fcdtv_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fcdtv_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fcdtv_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,68 @@\n+\"\"\"The Commodore CDTV card's measurements, shared by cdtv_print.py (which draws its printing) and cdtv.py (which builds\n+it). Plain Python: Blender's Python imports it too.\n+\n+The Commodore CDTV (1991), 430 mm wide, 330 deep and 95 high on its 15 mm silver feet (a 1993 review's figures), its\n+remote controller in front of it, a CD caddy with a made-up game, Odyssey, going into its slot. Millimetres: x from its\n+middle (left to right), y back from its front, z up from the table. The front's layout was measured from photographs on\n+Wikimedia Commons (one straight on, under a 1084 monitor; one from the front left; its controls close up; the remote),\n+scaled by its width; the review gives the front's order from left to right.\n+\n+The case: black, a box on four silver feet (FEET), the front's foot a recessed strip (STRIP) under a ledge with the\n+small buttons; the top's back half a vent grille (VENTS). On the front, from the left: the power switch over the\n+headphone jack (POWER, JACK), the green LED, the caddy's panel (PANEL) with the slot (SLOT) and the eject button under\n+its right end (EJECT), the yellow LED, the infrared window (IR), the cyan display (DISPLAY: the clock, then the track\n+and its time), the play and stop buttons, the volume rocker, and Commodore CDTV over them. The remote (REMOTE): a long\n+black slab, the D-pad on its left end, A and B on its right, the keys between (KEYS).\n+\n+One texture (ATLAS square, REGIONS): `front` (the front: its panels, labels and buttons' faces), `vents` (the top's\n+grille), `remote` (the remote from above), `caddy` (the caddy's top: the disc through its window), the display's faces\n+(`clock`, `time_0` to `time_3`), and swatches. A region is (x, y, width, height) in pixels.\n+\"\"\"\n+\n+ATLAS = 1024\n+\n+WIDTH, DEPTH, HEIGHT = 430.0, 330.0, 95.0\n+FEET = dict(height=15.0, r=16.0, x=180.0, y=(40.0, 290.0))\n+STRIP = dict(z=(FEET[\"height\"], 27.0), depth=3.0)\n+VENTS = dict(y=(200.0, 310.0), x=(-180.0, 180.0))\n+\n+POWER = dict(x=(-207.0, -184.0), z=(65.0, 75.0))\n+JACK = dict(at=(-196.0, 42.0), r=4.0)\n+LEDS = dict(green=(-173.0, 69.0), yellow=(-29.0, 69.0), r=1.6)\n+PANEL = dict(x=(-169.0, -35.0), z=(27.0, 79.0))\n+SLOT = dict(x=(-164.0, -36.0), z=(52.0, 62.0), depth=170.0)\n+EJECT = dict(x=(-58.0, -40.0), z=(39.0, 47.0), depth=2.5)\n+IR = dict(x=(-21.0, 8.0), z=(43.0, 57.0))\n+DISPLAY = dict(x=(15.0, 77.0), z=(43.0, 57.0))\n+PLAY = dict(x=(85.0, 119.0), z=(50.0, 61.0))\n+STOP = dict(x=(85.0, 119.0), z=(36.0, 47.0))\n+VOLUME = dict(x=(125.0, 201.0), z=(36.0, 61.0))\n+LOGO = dict(x=(110.0, 205.0), z=(70.0, 88.0))\n+SMALL = dict(x=[(85.0, 101.0), (103.0, 119.0), (140.0, 165.0), (170.0, 195.0)], z=(19.0, 24.0),\n+             names=[\"\u25c4\u25c4\", \"\u25ba\u25ba\", \"CD/TV\", \"RESET\"])\n+\n+# The caddy: w, length (going in), t; how far out of the slot its front end stays when it is in; how far out it waits.\n+CADDY = dict(size=(124.0, 155.0, 8.6), proud=2.0, out=165.0, window=(112.0, 124.0), disc=60.0)\n+\n+# The remote: length, depth, height; where its middle lies (x, y) and its turn; its end sections' length; the D-pad\n+# (its middle along the left end, its arms' reach and width), A and B (their middles, radius), the keys (their size).\n+REMOTE = dict(size=(216.0, 70.0, 20.0), at=(-40.0, -62.0, -5.0), ends=36.0, pad=(-90.0, 4.0, 13.0, 8.0),\n+              a=(82.0, -2.0), b=(98.0, 6.0), r=6.0, key=(11.0, 6.0))\n+# The keys on the remote's middle (their middles, from its middle): the number pad (1 2 3 ESCAPE, 4 5 6 0, 7 8 9\n+# ENTER), the CD keys (GENLOCK CD/TV POWER, REW PLAY FF), STOP, and the volume pair; POWER is red.\n+KEYS = ([(-58.0 + 16.5 * c, 20.0 - 11.0 * r) for r in range(3) for c in range(4)]\n+        + [(10.0 + 15.0 * c, 20.0 - 11.0 * r) for r in range(2) for c in range(3)]\n+        + [(25.0, -13.0), (58.0, 9.0), (58.0, -4.0)])\n+KEY_NAMES = [\"1\", \"2\", \"3\", \"ESCAPE\", \"4\", \"5\", \"6\", \"0\", \"7\", \"8\", \"9\", \"ENTER\", \"GENLOCK\", \"CD/TV\", \"POWER\", \"REW\",\n+             \"PLAY/PAUSE\", \"FF\", \"STOP\", \"\u25b2\", \"\u25bc\"]\n+\n+COLOURS = dict(case=\"#1C1C1E\", print=\"#FFFFFF\")\n+\n+REGIONS = dict(front=(0, 0, 1024, 192), vents=(0, 200, 1024, 316), remote=(0, 524, 648, 210),\n+               caddy=(656, 524, 248, 310), clock=(0, 744, 248, 56), time_0=(256, 744, 248, 56),\n+               time_1=(0, 808, 248, 56), time_2=(256, 808, 248, 56), time_3=(0, 872, 248, 56))\n+SWATCHES = dict(case=(912, 880), silver=(944, 880), dark=(976, 880), key=(1008, 880), red=(912, 912),\n+                green=(944, 912), yellow=(976, 912), remote=(1008, 912), caddy=(912, 944), off=(944, 944))\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s"
        },
        {
          "sha": "13099bc3d938d58470bd95566e278d38dc7174dd",
          "filename": "tools/console-models/cdtv_print.py",
          "status": "added",
          "additions": 253,
          "deletions": 0,
          "changes": 253,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fcdtv_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fcdtv_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fcdtv_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,253 @@\n+\"\"\"The Commodore CDTV card's printing (cdtv_print.png), for the console theme's model (cdtv.py).\n+\n+One image, laid out as cdtv_layout.REGIONS says: the front (the power switch and the headphone jack, the LEDs, the\n+caddy's panel with the Compact Disc mark, the infrared window, the play, stop and volume buttons' faces, Commodore CDTV,\n+the small buttons' labels on the strip), the top's vent grille, the remote from above (its frame, its name, the keys'\n+labels, the end sections' ribs), the caddy's top (a made-up game, Odyssey, through its window), the display's faces (the\n+clock, then the track and its time, in cyan), and swatches. Run it with the system Python (Pillow):\n+\n+    python tools/console-models/cdtv_print.py <out folder>\n+\"\"\"\n+\n+import os\n+import sys\n+\n+from PIL import Image, ImageDraw, ImageFilter\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import cdtv_layout as L  # noqa: E402\n+from psp_print import ship, space  # noqa: E402\n+from retro_pc_print import rgb  # noqa: E402\n+from windows_pc_print import Region  # noqa: E402\n+\n+CASE = rgb(L.COLOURS[\"case\"])\n+PANEL = (36, 36, 39)\n+GREY = (150, 150, 152)\n+WHITE = (226, 226, 222)\n+GOLD = (214, 176, 96)\n+CYAN = (80, 220, 255)\n+KEY = (128, 126, 122)\n+\n+\n+def shade(colour, k):\n+    return tuple(max(0, min(255, round(c * (1 + k)))) for c in colour)\n+\n+\n+def chicken_head(reg, x, y, h, ink):\n+    \"\"\"Commodore's mark, h tall, its middle at (x, y): the C, and the flag's two halves in its mouth.\"\"\"\n+    r = h / 2\n+    reg.circle(x, y, r, ink)\n+    reg.circle(x + 0.12 * h, y, r * 0.58, CASE)\n+    reg.rect_(x + 0.05 * h, y - 0.36 * h, x + 0.6 * h, y + 0.36 * h, CASE)\n+    reg.poly([(x + 0.12 * h, y - 0.05 * h), (x + 0.5 * h, y - 0.05 * h), (x + 0.5 * h, y - 0.3 * h)], ink)\n+    reg.poly([(x + 0.12 * h, y + 0.05 * h), (x + 0.5 * h, y + 0.05 * h), (x + 0.5 * h, y + 0.3 * h)], (190, 40, 40))\n+\n+\n+def compact_disc(reg, x, y, h, ink):\n+    \"\"\"The Compact Disc mark, h tall, its middle at (x, y).\"\"\"\n+    reg.text(\"COMPACT\", \"arialbd.ttf\", h * 0.22, x, y - h * 0.34, ink)\n+    reg.text(\"disc\", \"ariblk.ttf\", h * 0.62, x, y + h * 0.14, ink)\n+    reg.line([(x - h * 0.9, y - h * 0.5), (x + h * 0.9, y - h * 0.5)], ink, h * 0.05)\n+\n+\n+def front():\n+    \"\"\"The front: x across (-WIDTH/2 at the left), z down from its top.\"\"\"\n+    reg = Region(L.REGIONS[\"front\"], L.WIDTH, L.HEIGHT - L.FEET[\"height\"], CASE)\n+\n+    def at(x, z):\n+        return x + L.WIDTH / 2, L.HEIGHT - z\n+\n+    z0, z1 = L.STRIP[\"z\"]\n+    reg.rect_(*at(-L.WIDTH / 2, z1), *at(L.WIDTH / 2, z0), shade(CASE, -0.25))\n+    reg.line([at(-L.WIDTH / 2, z1 - 0.3), at(L.WIDTH / 2, z1 - 0.3)], shade(CASE, 0.6), 0.4)\n+    for (x0, x1), name in zip(L.SMALL[\"x\"], L.SMALL[\"names\"]):\n+        za, zb = L.SMALL[\"z\"]\n+        reg.rect_(*at(x0, zb), *at(x1, za), shade(CASE, 0.25), r=0.6)\n+        reg.text(name, \"seguisym.ttf\" if \"\u25c4\" in name or \"\u25ba\" in name else \"arialbd.ttf\", 2.4,\n+                 *at((x0 + x1) / 2, zb + 2.0), GREY)\n+\n+    (px0, px1), (pz0, pz1) = L.POWER[\"x\"], L.POWER[\"z\"]\n+    reg.rect_(*at(px0, pz1), *at(px1, pz0), shade(CASE, 0.35), r=1.0)\n+    reg.rect_(*at(px0 + 1.5, pz1 - 1.5), *at(px0 + 9.0, pz0 + 1.5), shade(CASE, 0.6), r=0.6)\n+    reg.text(\"POWER\", \"arialbd.ttf\", 2.2, *at((px0 + px1) / 2, pz1 + 2.6), GREY)\n+    jx, jz = L.JACK[\"at\"]\n+    reg.circle(*at(jx, jz), L.JACK[\"r\"] + 1.2, shade(CASE, 0.5))\n+    reg.circle(*at(jx, jz), L.JACK[\"r\"], (6, 6, 6))\n+    reg.text(\"\u2229\", \"seguisym.ttf\", 3.0, *at(jx, jz + 7.0), GREY)\n+    for name in (\"green\", \"yellow\"):\n+        lx, lz = L.LEDS[name]\n+        reg.circle(*at(lx, lz), L.LEDS[\"r\"], (40, 60, 40) if name == \"green\" else (70, 60, 30))\n+\n+    (cx0, cx1), (cz0, cz1) = L.PANEL[\"x\"], L.PANEL[\"z\"]\n+    reg.rect_(*at(cx0, cz1), *at(cx1, cz0), PANEL, r=1.0)\n+    compact_disc(reg, *at((cx0 + cx1) / 2, cz1 - 9.0), 9.0, GREY)\n+    (sx0, sx1), (sz0, sz1) = L.SLOT[\"x\"], L.SLOT[\"z\"]\n+    reg.rect_(*at(sx0, sz1), *at(sx1, sz0), (6, 6, 6))\n+    (ex0, ex1), (ez0, ez1) = L.EJECT[\"x\"], L.EJECT[\"z\"]\n+    reg.rect_(*at(ex0 - 1.0, ez1 + 1.0), *at(ex1 + 1.0, ez0 - 1.0), (10, 10, 10), r=0.8)\n+\n+    (ix0, ix1), (iz0, iz1) = L.IR[\"x\"], L.IR[\"z\"]\n+    reg.rect_(*at(ix0, iz1), *at(ix1, iz0), (32, 8, 10), r=1.0)\n+    reg.text(\"CD\", \"arialbd.ttf\", 2.6, *at(ix0 + 7.0, (iz0 + iz1) / 2), (110, 40, 40))\n+    (dx0, dx1), (dz0, dz1) = L.DISPLAY[\"x\"], L.DISPLAY[\"z\"]\n+    reg.rect_(*at(dx0 - 1.0, dz1 + 1.0), *at(dx1 + 1.0, dz0 - 1.0), (8, 10, 12), r=1.0)\n+\n+    for box, label in ((L.PLAY, \"\u25ba \u2758\u2758\"), (L.STOP, \"\u25a0\")):\n+        (bx0, bx1), (bz0, bz1) = box[\"x\"], box[\"z\"]\n+        reg.rect_(*at(bx0, bz1), *at(bx1, bz0), shade(CASE, 0.3), r=1.0)\n+        reg.text(label, \"seguisym.ttf\", 4.0, *at((bx0 + bx1) / 2, (bz0 + bz1) / 2),\n+                 (200, 50, 40) if label == \"\u25a0\" else WHITE)\n+    (vx0, vx1), (vz0, vz1) = L.VOLUME[\"x\"], L.VOLUME[\"z\"]\n+    reg.rect_(*at(vx0, vz1), *at(vx1, vz0), shade(CASE, 0.3), r=1.0)\n+    mid = (vx0 + vx1) / 2\n+    reg.line([at(mid + 4.0, vz1), at(mid - 4.0, vz0)], shade(CASE, -0.4), 0.8)\n+    reg.text(\"\u25bc DOWN\", \"seguisym.ttf\", 3.2, *at(vx0 + 16.0, vz1 - 6.0), WHITE)\n+    reg.text(\"UP \u25b2\", \"seguisym.ttf\", 3.2, *at(vx1 - 14.0, vz1 - 6.0), WHITE)\n+    reg.text(\"\u2229 VOLUME\", \"seguisym.ttf\", 2.8, *at(mid, vz1 + 3.0), GREY)\n+\n+    (lx0, lx1), (lz0, lz1) = L.LOGO[\"x\"], L.LOGO[\"z\"]\n+    chicken_head(reg, *at(lx0 + 6.0, lz1 - 5.0), 8.0, GOLD)\n+    reg.text(\"Commodore\", \"arialbd.ttf\", 9.0, *at(lx0 + 12.0, lz1 - 5.0), WHITE, anchor=\"lm\")\n+    reg.text(\"CDTV\", \"arial.ttf\", 7.0, *at(lx1 - 2.0, lz0 + 3.5), GREY, anchor=\"rm\")\n+    return reg\n+\n+\n+def vents():\n+    \"\"\"The top's back from above: x across, y down from the back edge; the grille's rows of slots.\"\"\"\n+    y0, y1 = 190.0, 320.0\n+    reg = Region(L.REGIONS[\"vents\"], L.WIDTH, y1 - y0, CASE)\n+\n+    def at(x, y):\n+        return x + L.WIDTH / 2, y1 - y\n+\n+    (vx0, vx1), (vy0, vy1) = L.VENTS[\"x\"], L.VENTS[\"y\"]\n+    rows, cols = 6, 36\n+    for r in range(rows):\n+        for c in range(cols):\n+            x = vx0 + (vx1 - vx0) * (c + 0.5) / cols\n+            y = vy0 + (vy1 - vy0) * (r + 0.5) / rows\n+            reg.rect_(*at(x - 2.2, y + 6.0), *at(x + 2.2, y - 6.0), (6, 6, 7), r=1.6)\n+    return reg\n+\n+\n+def remote():\n+    \"\"\"The remote from above: x along it (-length/2 at the left), y down from its back edge.\"\"\"\n+    length, depth, _ = L.REMOTE[\"size\"]\n+    reg = Region(L.REGIONS[\"remote\"], length, depth, (30, 30, 34))\n+\n+    def at(x, y):\n+        return x + length / 2, depth / 2 - y\n+\n+    ends = L.REMOTE[\"ends\"]\n+    for side in (-1, 1):                                    # the end sections' ribs\n+        x0 = side * (length / 2 - ends)\n+        for k in range(4):\n+            y = depth / 2 - 6.0 - 3.0 * k\n+            reg.line([at(min(x0, side * length / 2 - side * 4.0), y), at(max(x0, side * length / 2 - side * 4.0), y)],\n+                     (14, 14, 16), 0.9)\n+        reg.line([at(x0, depth / 2), at(x0 + side * 4.0, -depth / 2)], (14, 14, 16), 1.2)\n+    w = length / 2 - ends - 4.0\n+    reg.rect_(*at(-w, depth / 2 - 5.0), *at(w, -depth / 2 + 5.0), None, r=1.5, outline=(70, 70, 76), width=0.4)\n+    chicken_head(reg, *at(-w + 7.0, 27.0), 5.0, WHITE)\n+    reg.text(\"Commodore\", \"arialbd.ttf\", 5.0, *at(-w + 11.0, 27.0), WHITE, anchor=\"lm\")\n+    reg.text(\"CDTV REMOTE CONTROLLER\", \"arial.ttf\", 2.6, *at(-w + 46.0, 27.0), WHITE, anchor=\"lm\")\n+    compact_disc(reg, *at(w - 8.0, 27.0), 6.0, WHITE)\n+    kw, kh = L.REMOTE[\"key\"]\n+    for (x, y), name in zip(L.KEYS, L.KEY_NAMES):\n+        reg.text(name, \"seguisym.ttf\" if name in (\"\u25b2\", \"\u25bc\") else \"arial.ttf\",\n+                 2.4 if len(name) > 1 else 2.8, *at(x, y + kh / 2 + 2.0), WHITE)\n+        reg.rect_(*at(x - kw / 2, y + kh / 2), *at(x + kw / 2, y - kh / 2), (200, 40, 36) if name == \"POWER\" else KEY,\n+                  r=0.8)\n+    for name, (x, y) in ((\"A\", L.REMOTE[\"a\"]), (\"B\", L.REMOTE[\"b\"])):\n+        reg.text(name, \"arialbd.ttf\", 3.0, *at(x, y + L.REMOTE[\"r\"] + 3.0), WHITE)\n+    return reg\n+\n+\n+def caddy():\n+    \"\"\"The caddy's top: x across, y down from its leading edge (the end going in) to its trailing edge.\"\"\"\n+    w, length, _ = L.CADDY[\"size\"]\n+    reg = Region(L.REGIONS[\"caddy\"], w, length, (24, 24, 26))\n+    ww, wl = L.CADDY[\"window\"]\n+    cx, cy = w / 2, 10.0 + wl / 2\n+    r = L.CADDY[\"disc\"]\n+    disc = reg.layer()                                      # the disc, seen through the window: clipped to it\n+    d = ImageDraw.Draw(disc)\n+    d.ellipse(reg.box(cx - r, cy - r, cx + r, cy + r), fill=(176, 180, 186, 255))\n+    art = space((480, 480)).convert(\"RGB\")\n+    ship(ImageDraw.Draw(art), 130, 300, 2.4, \"#FFFFFF\", \"#FFB23C\")\n+    ring = Image.new(\"L\", art.size, 0)\n+    ImageDraw.Draw(ring).ellipse([16, 16, 464, 464], fill=255)\n+    ImageDraw.Draw(ring).ellipse([196, 196, 284, 284], fill=0)\n+    x0, y0, x1, y1 = reg.box(cx - r + 3.0, cy - r + 3.0, cx + r - 3.0, cy + r - 3.0)\n+    size = (round(x1 - x0), round(y1 - y0))\n+    disc.paste(art.resize(size, Image.LANCZOS).convert(\"RGBA\"), (round(x0), round(y0)),\n+               ring.resize(size, Image.LANCZOS))\n+    d.ellipse(reg.box(cx - 7.5, cy - 7.5, cx + 7.5, cy + 7.5), fill=(200, 204, 208, 255))\n+    d.ellipse(reg.box(cx - 3.5, cy - 3.5, cx + 3.5, cy + 3.5), fill=(30, 30, 34, 255))\n+    window = Image.new(\"L\", disc.size, 0)\n+    ImageDraw.Draw(window).rounded_rectangle(reg.box(cx - ww / 2, cy - wl / 2, cx + ww / 2, cy + wl / 2),\n+                                             radius=4.0 * reg.sx, fill=255)\n+    disc.putalpha(Image.composite(disc.getchannel(\"A\"), window, window))\n+    reg.rect_(cx - ww / 2, cy - wl / 2, cx + ww / 2, cy + wl / 2, (50, 54, 60), r=4.0)\n+    reg.over(disc)\n+    reg.text(\"ODYSSEY\", \"ariblk.ttf\", 7.0, cx, cy - 30.0, (255, 214, 70))\n+    reg.rect_(cx - ww / 2, cy - wl / 2, cx + ww / 2, cy + wl / 2, None, r=4.0, outline=(90, 94, 100), width=0.8)\n+    reg.rect_(12.0, length - 22.0, w - 12.0, length - 8.0, (230, 228, 220), r=1.0)\n+    reg.text(\"CDTV\", \"arialbd.ttf\", 5.0, w / 2, length - 15.0, (40, 40, 44))\n+    return reg\n+\n+\n+SEGMENTS = dict(a=((0.1, 0.0), (0.9, 0.0)), b=((1.0, 0.1), (1.0, 0.45)), c=((1.0, 0.55), (1.0, 0.9)),\n+                d=((0.1, 1.0), (0.9, 1.0)), e=((0.0, 0.55), (0.0, 0.9)), f=((0.0, 0.1), (0.0, 0.45)),\n+                g=((0.1, 0.5), (0.9, 0.5)))\n+DIGITS = {\"0\": \"abcdef\", \"1\": \"bc\", \"2\": \"abged\", \"3\": \"abgcd\", \"4\": \"fgbc\", \"5\": \"afgcd\", \"6\": \"afgedc\", \"7\": \"abc\",\n+          \"8\": \"abcdefg\", \"9\": \"abcdfg\"}\n+\n+\n+def display(text):\n+    \"\"\"One of the display's faces: seven-segment digits in cyan, its colon a pair of dots.\"\"\"\n+    (x0, x1), (z0, z1) = L.DISPLAY[\"x\"], L.DISPLAY[\"z\"]\n+    w, h = x1 - x0, z1 - z0\n+    region = {\"12:00\": \"clock\"}.get(text, \"time_\" + text[-1])\n+    reg = Region(L.REGIONS[region], w, h, (8, 10, 12))\n+    dw, dh, x = 4.4, 8.0, 3.0\n+    layer = reg.layer()\n+    d = ImageDraw.Draw(layer)\n+    for c in text:\n+        if c == \":\":\n+            for y in (h / 2 - 1.6, h / 2 + 1.6):\n+                d.ellipse(reg.box(x + 0.2, y - 0.5, x + 1.2, y + 0.5), fill=CYAN + (255,))\n+            x += 2.4\n+            continue\n+        if c == \" \":\n+            x += 4.0\n+            continue\n+        for seg in DIGITS[c]:\n+            (ax, ay), (bx, by) = SEGMENTS[seg]\n+            d.line([reg.p(x + ax * dw, (h - dh) / 2 + ay * dh), reg.p(x + bx * dw, (h - dh) / 2 + by * dh)],\n+                   fill=CYAN + (255,), width=reg.width(0.9))\n+        x += dw + 2.4\n+    glow = layer.filter(ImageFilter.GaussianBlur(reg.width(0.6)))\n+    reg.over(glow)\n+    reg.over(layer)\n+    return reg\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), CASE)\n+    for reg in (front(), vents(), remote(), caddy(), display(\"12:00\"), display(\"01 00:00\"), display(\"01 00:01\"),\n+                display(\"01 00:02\"), display(\"01 00:03\")):\n+        reg.paste(image)\n+    d = ImageDraw.Draw(image)\n+    colours = dict(case=CASE, silver=(196, 198, 200), dark=(6, 6, 6), key=KEY, red=(200, 40, 36), green=(90, 255, 90),\n+                   yellow=(255, 210, 60), remote=(30, 30, 34), caddy=(24, 24, 26), off=(30, 34, 30))\n+    for name, (x, y) in L.SWATCHES.items():\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colours[name])\n+    path = os.path.join(out, \"cdtv_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "f1e1de4b5c47d31382c88c4c4fdad22c4e70fa0d",
          "filename": "tools/console-models/channelf.py",
          "status": "added",
          "additions": 314,
          "deletions": 0,
          "changes": 314,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fchannelf.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fchannelf.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fchannelf.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,314 @@\n+\"\"\"The console theme's Fairchild Channel F system model (godot/themes/console/models/systems/channelf.glb): the Channel\n+F and its two controllers.\n+\n+Built in Blender (channelf_layout.py has the measurements, taken from photographs). The console is its side outline\n+extruded across its width and cut to its sides sloping in to the foot: walnut on the sides and the front's plinth, black\n+on top; the slot is cut into the lid's front face and runs back under the lid, its door hinged along its top. The keys,\n+the INSERT CARTRIDGE plate and the eject bar stand on the front panel, the chrome strip on the lid. The controllers are\n+each a rounded grip, a neck and a knob, lying on their sides, their cables wired into the console's left side, each\n+through a strain relief at both ends (plugs.py's). The printing is one texture (channelf_print.py's), projected from\n+above onto the panel, its keys, the plate, the bar, the strip and the Videocart, from in front onto the door, and along\n+the walnut and the grips.\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: the right end towards the viewer, the\n+top tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m) holding the set; the set\n+holding the Videocart (waiting in front of the slot, shrunk to nothing), the door, RESET and 1, the eject bar, and the\n+first controller's knob.\n+\n+The `focused` clip (8 s, looping): the Videocart comes down in front of the slot and slides in, pushing the door in;\n+RESET, then 1 (hockey); the first controller's knob tilts and pushes; then Press to Eject, the Videocart slides back\n+out, the door shuts behind it, and it goes.\n+\n+Three materials (plastic, walnut, cart) and one 1024\u00b2 texture, within the system model budget. Run from the repo root:\n+\n+    python tools/console-models/channelf_print.py artifacts/channelf\n+    blender -b --factory-startup --python tools/console-models/channelf.py -- artifacts/channelf godot/themes/console/models/systems/channelf.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bmesh\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import channelf_layout as L  # noqa: E402\n+import plugs  # noqa: E402\n+from amiga import paint, solid  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, ease_out, key, phase, smooth  # noqa: E402\n+from mega_drive import prism_y  # noqa: E402\n+from mega_drive_cartridge import (bevel, boolean, box, export, join, material, new_object, prism_x,  # noqa: E402\n+                                  triangles)\n+\n+TILT_YAW = -24.0     # degrees about the vertical: the right end comes towards the viewer\n+TILT_PITCH = 26.0    # degrees about the horizontal: the top tips up towards the viewer\n+ROUGHNESS = dict(plastic=0.3, walnut=0.5, cart=0.45)\n+\n+\n+def atlas_uv(region, w, h):\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+def swatch(name):\n+    x, y = L.SWATCHES[name]\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+def colour(obj, mat, name):\n+    paint(obj, mat, swatch(name), lambda c, n, kind: True)\n+    return obj\n+\n+\n+PANEL = atlas_uv(L.REGIONS[\"panel\"], L.WIDTH, L.LID[\"y\"])\n+WOOD = atlas_uv(L.REGIONS[\"wood\"], L.DEPTH, L.HEIGHT)\n+\n+\n+def panel_uv(co):\n+    return PANEL(co.x + L.WIDTH / 2, L.LID[\"y\"] - co.y)\n+\n+\n+# ---- The console -----------------------------------------------------------------------------------------------------\n+\n+def console(m):\n+    \"\"\"The walnut and the black: the side outline, its sides sloping in to the foot, the slot cut in; printed.\"\"\"\n+    half, foot = L.WIDTH / 2, L.SIDE[\"foot\"]\n+    body = prism_x(\"channelf\", L.PROFILE, -half - 1.0, half + 1.0, m[\"plastic\"])\n+    lean = foot / L.HEIGHT                                    # how far the sides slope in, per millimetre down\n+    sides = [(-half + foot + lean, -1.0), (half - foot - lean, -1.0), (half + 6.0 * lean, L.HEIGHT + 6.0),\n+             (-half - 6.0 * lean, L.HEIGHT + 6.0)]\n+    boolean(body, prism_y(\"sides\", sides, -10.0, L.DEPTH + 10.0, m[\"plastic\"]), \"INTERSECT\")\n+    bevel(body, 2.0, segments=2, angle=40)\n+    (x0, x1), (z0, z1) = L.SLOT[\"x\"], L.SLOT[\"z\"]\n+    boolean(body, box(\"slot\", x0, x1, L.LID[\"y\"] - 5.0, L.LID[\"y\"] + L.SLOT[\"depth\"], z0, z1, m[\"plastic\"]))\n+    colour(body, m[\"plastic\"], \"black\")\n+    paint(body, m[\"plastic\"], panel_uv, lambda c, n, kind: n.z > 0.5 and c.y < L.LID[\"y\"] - 0.5)\n+    paint(body, m[\"plastic\"], swatch(\"gloss\"), lambda c, n, kind: n.z > 0.5 and c.y > L.LID[\"y\"] - 0.5)\n+    paint(body, m[\"walnut\"], lambda co: WOOD(co.y, L.HEIGHT - co.z), lambda c, n, kind: abs(n.x) > 0.5)\n+    paint(body, m[\"walnut\"], lambda co: WOOD(co.x + half + 10.0, L.HEIGHT - co.z),\n+          lambda c, n, kind: n.y < -0.5 and c.z < L.SIDE[\"black\"] - 0.5)\n+    paint(body, m[\"plastic\"], swatch(\"dark\"),\n+          lambda c, n, kind: x0 - 0.5 < c.x < x1 + 0.5 and c.y > L.LID[\"y\"] - 0.5 and z0 - 0.5 < c.z < z1 + 0.5)\n+    return body\n+\n+\n+def fittings(m):\n+    \"\"\"On the panel: the keys, the plate, the eject bar; on the lid: the chrome strip. RESET, 1 and the bar apart (each\n+    its foot's middle at its origin, with where it goes); the rest joined.\"\"\"\n+    fixed, moving = [], {}\n+    k, y = L.KEYS[\"size\"] / 2, L.KEYS[\"y\"]\n+    for x, name in zip(L.KEYS[\"x\"], L.KEYS[\"names\"]):\n+        z = L.panel_z(y)\n+        part = box(\"key\", x - k, x + k, y - k, y + k, z - 1.0, z + L.KEYS[\"height\"], m[\"plastic\"])\n+        bevel(part, 1.0, segments=2)\n+        colour(part, m[\"plastic\"], \"yellow\" if name == \"RESET\" else \"cream\")\n+        paint(part, m[\"plastic\"], panel_uv, lambda c, n, kind: n.z > 0.9)\n+        if name in (\"RESET\", \"1\"):\n+            middle = Vector((x, y, z - 1.0))\n+            part.data.transform(Matrix.Translation(-middle))\n+            moving[\"reset\" if name == \"RESET\" else \"one\"] = (part, middle)\n+        else:\n+            fixed.append(part)\n+    (px0, px1), (py0, py1), h = L.PLATE[\"x\"], L.PLATE[\"y\"], L.PLATE[\"height\"]\n+    plate = prism_x(\"plate\", [(py0, L.panel_z(py0) - 1.0), (py1, L.panel_z(py1) - 1.0), (py1, L.panel_z(py1) + h),\n+                              (py0, L.panel_z(py0) + h)], px0, px1, m[\"plastic\"])\n+    bevel(plate, 0.6, segments=1)\n+    colour(plate, m[\"plastic\"], \"black\")\n+    paint(plate, m[\"plastic\"], panel_uv, lambda c, n, kind: n.z > 0.9)\n+    fixed.append(plate)\n+    (ex0, ex1), (ey0, ey1) = L.EJECT[\"x\"], L.EJECT[\"y\"]\n+    z = L.panel_z((ey0 + ey1) / 2)\n+    bar = box(\"eject\", ex0, ex1, ey0, ey1, z - 1.0, z + L.EJECT[\"height\"], m[\"plastic\"])\n+    bevel(bar, 1.0, segments=2)\n+    colour(bar, m[\"plastic\"], \"black\")\n+    paint(bar, m[\"plastic\"], panel_uv, lambda c, n, kind: n.z > 0.9)\n+    middle = Vector(((ex0 + ex1) / 2, (ey0 + ey1) / 2, z - 1.0))\n+    bar.data.transform(Matrix.Translation(-middle))\n+    moving[\"eject\"] = (bar, middle)\n+    (sx0, sx1), (sy0, sy1) = L.STRIP[\"x\"], L.STRIP[\"y\"]\n+    strip = box(\"strip\", sx0, sx1, sy0, sy1, L.HEIGHT - 1.0, L.HEIGHT + 1.5, m[\"plastic\"])\n+    bevel(strip, 0.8, segments=2)\n+    colour(strip, m[\"plastic\"], \"black\")\n+    face = atlas_uv(L.REGIONS[\"strip\"], L.WIDTH, sy1 - sy0)\n+    paint(strip, m[\"plastic\"], lambda co: face(co.x + L.WIDTH / 2, sy1 - co.y), lambda c, n, kind: n.z > 0.9)\n+    fixed.append(strip)\n+    return fixed, moving\n+\n+\n+def door(m):\n+    \"\"\"The cartridge door, its origin on its hinge along its top; its face ribbed.\"\"\"\n+    (x0, x1), (z0, z1), g = L.SLOT[\"x\"], L.SLOT[\"z\"], L.DOOR[\"inset\"]\n+    y = L.LID[\"y\"] + 2.0\n+    part = box(\"door\", x0 + g, x1 - g, y, y + 2.0, z0 + g, z1 - g, m[\"plastic\"])\n+    colour(part, m[\"plastic\"], \"black\")\n+    face = atlas_uv(L.REGIONS[\"door\"], x1 - x0, z1 - z0)\n+    paint(part, m[\"plastic\"], lambda co: face(co.x - x0, z1 - co.z), lambda c, n, kind: n.y < -0.9)\n+    hinge = Vector(((x0 + x1) / 2, y + 1.0, z1 - g))\n+    part.data.transform(Matrix.Translation(-hinge))\n+    return part, hinge\n+\n+\n+def videocart(m):\n+    \"\"\"The Videocart, its origin at its front end's foot, its label on its top.\"\"\"\n+    w, length, t = L.CART[\"size\"]\n+    part = box(\"videocart\", -w / 2, w / 2, 0.0, length, 0.0, t, m[\"cart\"])\n+    bevel(part, 2.0, segments=2)\n+    colour(part, m[\"cart\"], \"cart\")\n+    face = atlas_uv(L.REGIONS[\"cart\"], w, length)\n+    paint(part, m[\"cart\"], lambda co: face(co.x + w / 2, length - co.y), lambda c, n, kind: n.z > 0.9)\n+    return part\n+\n+\n+# ---- The controllers -------------------------------------------------------------------------------------------------\n+\n+def controller(m, place, live):\n+    \"\"\"One controller lying on its side at place (x, y, turn): the grip, the neck and the knob at its +x end; the neck\n+    and the knob apart when live (with where they go). Returns the joined parts, the moving ones, and where its cable\n+    leaves it.\"\"\"\n+    x, y, turn = place\n+    spin = Matrix.Translation((x, y, 0.0)) @ Matrix.Rotation(math.radians(turn), 4, \"Z\")\n+    length, width, height = L.CONTROLLER[\"grip\"]\n+    grip = box(\"grip\", -length / 2, length / 2, -width / 2, width / 2, 0.0, height, m[\"plastic\"])\n+    bevel(grip, 7.0, segments=4)\n+    colour(grip, m[\"plastic\"], \"black\")\n+    face = atlas_uv(L.REGIONS[\"grip\"], length, height)\n+    paint(grip, m[\"plastic\"], lambda co: face(co.x + length / 2, height - co.z), lambda c, n, kind: abs(n.y) > 0.8)\n+    r, neck_length = L.CONTROLLER[\"neck\"]\n+    kw, kh, kt = L.CONTROLLER[\"knob\"]\n+    # The neck, then the knob flaring from it like a mushroom's cap, its section an oval kw by kh.\n+    shape = [(-2.0, r), (neck_length, r), (neck_length + 0.2 * kt, 0.55), (neck_length + 0.45 * kt, 0.9),\n+             (neck_length + 0.75 * kt, 1.0), (neck_length + 0.92 * kt, 0.85), (neck_length + kt, 0.5)]\n+    rings = []\n+    for a, size in shape:\n+        ry, rz = (size, size) if size > 1.5 else (size * kw / 2, size * kh / 2)\n+        rings.append([(a, ry * math.cos(2 * math.pi * k / 20), rz * math.sin(2 * math.pi * k / 20)) for k in range(20)])\n+    head = colour(solid(\"knob\", rings, m[\"plastic\"]), m[\"plastic\"], \"black\")\n+    head.data.transform(Matrix.Rotation(math.radians(turn), 4, \"Z\"))\n+    where = spin @ Vector((length / 2, 0.0, height / 2))\n+    grip.data.transform(spin)\n+    parts, moving = [grip], {}\n+    if live:\n+        moving[\"knob\"] = (head, where)\n+    else:\n+        head.data.transform(Matrix.Translation(where))\n+        parts.append(head)\n+    return join(parts, \"controller\"), moving, spin @ Vector((-length / 2 + 3.0, 0.0, height / 2))\n+\n+\n+def cable(name, m, points, radius=2.2):\n+    \"\"\"A cable: a tube along the B\u00e9zier curve through four control points.\"\"\"\n+    p0, p1, p2, p3 = (Vector(p) for p in points)\n+    path = [p0 * (1 - t) ** 3 + p1 * 3 * t * (1 - t) ** 2 + p2 * 3 * t * t * (1 - t) + p3 * t ** 3\n+            for t in [k / 24 for k in range(25)]]\n+    bm = bmesh.new()\n+    rings, normal = [], Vector((0.0, 0.0, 1.0))\n+    for k, p in enumerate(path):\n+        tangent = (path[min(k + 1, 24)] - path[max(k - 1, 0)]).normalized()\n+        normal = (normal - tangent * normal.dot(tangent)).normalized()\n+        binormal = tangent.cross(normal)\n+        rings.append([bm.verts.new(p + (normal * math.cos(a) + binormal * math.sin(a)) * radius)\n+                      for a in [2 * math.pi * j / 8 for j in range(8)]])\n+    for a, b in zip(rings, rings[1:]):\n+        for j in range(8):\n+            bm.faces.new([a[j], a[(j + 1) % 8], b[(j + 1) % 8], b[j]])\n+    bmesh.ops.recalc_face_normals(bm, faces=bm.faces)\n+    return colour(new_object(name, bm, m[\"plastic\"]), m[\"plastic\"], \"black\")\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+def tap(f, f0, hold=2):\n+    return smooth(phase(f, f0, f0 + 3)) * (1 - smooth(phase(f, f0 + 3 + hold, f0 + 7 + hold)))\n+\n+\n+def timeline(f):\n+    out, up = L.CART[\"out\"], 30.0\n+    if f < 10 or f >= 200:\n+        cart = Pose(y=-out, z=up, scale=HIDDEN)\n+    elif f < 17:\n+        cart = Pose(y=-out, z=up, scale=max(HIDDEN, back_out(phase(f, 10, 17))))\n+    elif f < 24:\n+        cart = Pose(y=-out, z=up * (1 - ease_in(phase(f, 17, 24))))\n+    elif f < 178:\n+        cart = Pose(y=-out * (1 - smooth(phase(f, 24, 40))))\n+    elif f < 190:\n+        cart = Pose(y=-out * smooth(phase(f, 178, 190)))\n+    else:\n+        cart = Pose(y=-out, z=up * ease_out(phase(f, 190, 198)), scale=max(HIDDEN, 1 - ease_in(phase(f, 194, 200))))\n+    opened = smooth(phase(f, 25, 30)) * (1 - smooth(phase(f, 188, 194)))\n+    t = phase(f, 62, 168)\n+    k = math.sin(math.pi * t)\n+    turn = math.radians(L.CONTROLLER[\"at\"][0][2])\n+    push = 4.0 * k * math.sin(2 * math.pi * 2.0 * t)\n+    return {\"videocart\": cart, \"door\": Pose(pitch=L.DOOR[\"open\"] * opened),\n+            \"reset\": Pose(z=-2.0 * tap(f, 46)), \"one\": Pose(z=-2.0 * tap(f, 54)), \"eject\": Pose(z=-2.0 * tap(f, 172)),\n+            \"knob\": Pose(x=push * math.cos(turn), y=push * math.sin(turn),\n+                         spin=22.0 * k * math.sin(2 * math.pi * 3.0 * t))}\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    atlas = bpy.data.images.load(os.path.join(images, \"channelf_print.png\"))\n+    m = {name: material(name, L.COLOURS[\"print\"], r, atlas) for name, r in ROUGHNESS.items()}\n+\n+    parts, moving = fittings(m)\n+    parts.append(console(m))\n+    black = (lambda o: colour(o, m[\"plastic\"], \"black\"))\n+    side = Vector((-L.HEIGHT, 0.0, -L.SIDE[\"foot\"])).normalized()          # out of the left side, leaning in\n+    for k, place in enumerate(L.CONTROLLER[\"at\"]):\n+        grip, live, exit_ = controller(m, place, k == 0)\n+        moving.update(live)\n+        turn = math.radians(place[2])\n+        relief, start, leaving = plugs.place(*plugs.grommet(black, 4.2), m[\"plastic\"], exit_,\n+                                             (-math.cos(turn), -math.sin(turn), 0.0))\n+        z, y = L.PORT[\"z\"], L.PORT[\"y\"] - 26.0 * k                         # the controllers are wired in\n+        entry = Vector((-L.WIDTH / 2 + L.SIDE[\"foot\"] * (1 - z / L.HEIGHT), y, z))\n+        sleeve, end, back = plugs.place(*plugs.grommet(black, 4.6), m[\"plastic\"], entry, side)\n+        parts += [grip, cable(\"cable\", m, [start, start + leaving * 50.0 + Vector((0.0, 0.0, -10.0)),\n+                                           end + back * 60.0 + Vector((0.0, 0.0, -20.0)), end])] + relief + sleeve\n+    whole = join(parts, \"channelf\")\n+    for p in whole.data.polygons:\n+        p.use_smooth = True\n+    whole.data.set_sharp_from_angle(angle=math.radians(35))\n+\n+    root = bpy.data.objects.new(\"channelf_root\", None)\n+    scene.collection.objects.link(root)\n+    whole.parent = root\n+    (x0, x1), (z0, _) = L.SLOT[\"x\"], L.SLOT[\"z\"]\n+    moving[\"videocart\"] = (videocart(m), Vector(((x0 + x1) / 2, L.CART[\"front\"], z0 + 1.0)))\n+    moving[\"door\"] = door(m)\n+    nodes = {}\n+    for name, (part, where) in moving.items():\n+        for p in part.data.polygons:\n+            p.use_smooth = True\n+        part.data.set_sharp_from_angle(angle=math.radians(35))\n+        part.parent = whole\n+        part.location = where\n+        nodes[name] = part\n+\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    points = [tilt @ v.co for v in whole.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [whole] + list(nodes.values())\n+    print(f\"Channel F: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(whole.data)} the set), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "d7db5248b843ce28112ff48adc1620e72644ca4f",
          "filename": "tools/console-models/channelf_layout.py",
          "status": "added",
          "additions": 65,
          "deletions": 0,
          "changes": 65,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fchannelf_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fchannelf_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fchannelf_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,65 @@\n+\"\"\"The Fairchild Channel F card's measurements, shared by channelf_print.py (which draws its printing) and channelf.py\n+(which builds it). Plain Python: Blender's Python imports it too.\n+\n+The Fairchild Channel F (1976), the first one, 305 mm wide, 330 deep and 89 high (the Computer History Museum's 3.5 x 12\n+x 13 inches), its two controllers lying in front of it, a Videocart (the size of an 8-track tape, as they were made)\n+with a made-up game, Odyssey, going into its slot. Millimetres: x from its middle (left to right), y back from its\n+front, z up from the table. Measured from photographs on Wikimedia Commons (Evan-Amos's from the front left; one\n+straight down on it; others from the front and of its Videocarts).\n+\n+The console: walnut sides and front, sloping in to its foot all round (SIDE), the black top between them (PROFILE): the\n+front panel sloping gently up to the lid, the lid over the controllers' compartment behind it, its glossy top with the\n+chrome strip (FAIRCHILD, VIDEO ENTERTAINMENT SYSTEM). On the panel: RESET and the keys 1 to 4 (KEYS), and at its right\n+the cartridge's door in the lid's front face (SLOT, DOOR, hinged along its top), the INSERT CARTRIDGE plate in front of\n+it and the Press to Eject bar (PLATE, EJECT). The controllers are the pistol grips, each with its knob on its neck at\n+one end (CONTROLLER), lying on their sides, their cables running to the console's left side.\n+\n+One texture (ATLAS square, REGIONS): `panel` (the front panel from above: the keys' tops, TIME MODE HOLD START and the\n+rest, INSERT CARTRIDGE, Press to Eject), `strip` (the lid's chrome strip from above), `door` (the cartridge door's\n+ribbed face), `cart` (the Videocart's top: its label), `wood` (the walnut's grain), `grip` (a controller's ribbed side)\n+and swatches. A region is (x, y, width, height) in pixels.\n+\"\"\"\n+\n+ATLAS = 1024\n+\n+WIDTH, DEPTH, HEIGHT = 305.0, 330.0, 89.0\n+SIDE = dict(foot=15.0, black=44.0)    # how far the walnut slopes in at the foot; the walnut's top at the front\n+PANEL = dict(front=(4.0, 47.0), back=(83.0, 52.0))      # the front panel's front and back edges (y, z)\n+LID = dict(y=85.0, top=HEIGHT)\n+PROFILE = [                           # the side outline (y, z), round from the front's foot\n+    (SIDE[\"foot\"], 0.0), (DEPTH - SIDE[\"foot\"], 0.0), (DEPTH, HEIGHT - 6.0), (DEPTH - 3.0, HEIGHT),\n+    (LID[\"y\"] + 3.0, HEIGHT), (LID[\"y\"], HEIGHT - 3.0), (LID[\"y\"], PANEL[\"back\"][1] + 2.0), PANEL[\"back\"],\n+    PANEL[\"front\"], (0.0, SIDE[\"black\"]),\n+]\n+STRIP = dict(x=(-135.0, 136.0), y=(252.0, 276.0))\n+\n+KEYS = dict(x=[-102.4, -81.1, -61.0, -42.1, -22.0], y=39.0, size=13.0, height=5.0,\n+            names=[\"RESET\", \"1\", \"2\", \"3\", \"4\"])\n+SLOT = dict(x=(12.0, 118.0), z=(52.0, 78.0), depth=125.0)     # the mouth in the lid's front face; how far it runs back\n+DOOR = dict(inset=1.0, open=80.0)                               # its gap round the mouth; how far it swings in\n+PLATE = dict(x=(22.0, 108.0), y=(58.0, 82.0), height=2.0)\n+EJECT = dict(x=(27.0, 119.0), y=(18.0, 36.0), height=4.0)\n+\n+# The Videocart: w, length, t; its front end's y when it is in; how far out in front of that it waits.\n+CART = dict(size=(102.0, 137.0, 22.0), front=45.0, out=110.0)\n+# The controllers: the grip's length, width and height; the neck's radius and length; the knob's width, height and\n+# thickness; where each lies (x, y of its grip's middle) and its turn (degrees).\n+CONTROLLER = dict(grip=(150.0, 42.0, 34.0), neck=(8.0, 12.0), knob=(46.0, 42.0, 18.0),\n+                  at=[(-118.0, -66.0, -16.0), (-80.0, -122.0, -22.0)])\n+PORT = dict(y=250.0, z=30.0)          # where the cables go into the console's left side\n+\n+COLOURS = dict(black=\"#151517\", wood=\"#5A3420\", cart=\"#F2C230\", print=\"#FFFFFF\")\n+\n+REGIONS = dict(panel=(0, 0, 800, 236), strip=(0, 244, 800, 62), door=(0, 314, 280, 70), cart=(0, 392, 206, 276),\n+               wood=(216, 392, 400, 160), grip=(216, 560, 400, 110))\n+SWATCHES = dict(black=(912, 400), wood=(944, 400), cream=(976, 400), yellow=(1008, 400), cart=(912, 432),\n+                chrome=(944, 432), dark=(976, 432), gloss=(1008, 432))\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s\n+\n+\n+def panel_z(y):\n+    \"\"\"The front panel's height at y.\"\"\"\n+    (y0, z0), (y1, z1) = PANEL[\"front\"], PANEL[\"back\"]\n+    return z0 + (y - y0) * (z1 - z0) / (y1 - y0)"
        },
        {
          "sha": "9f7bb766e5e36af92ebd9132ace97e95535d1799",
          "filename": "tools/console-models/channelf_print.py",
          "status": "added",
          "additions": 164,
          "deletions": 0,
          "changes": 164,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fchannelf_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fchannelf_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fchannelf_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,164 @@\n+\"\"\"The Fairchild Channel F card's printing (channelf_print.png), for the console theme's model (channelf.py).\n+\n+One image, laid out as channelf_layout.REGIONS says: the front panel from above (RESET and the keys' tops, TIME MODE\n+HOLD START, the times, HOCKEY TENNIS GAME 3 GAME 4, INSERT CARTRIDGE, Press to Eject), the lid's chrome strip, the\n+cartridge door's ribs, the Videocart's top (its label: a made-up game, Odyssey), the walnut's grain, a controller's\n+ribbed grip, and swatches. Run it with the system Python (Pillow):\n+\n+    python tools/console-models/channelf_print.py <out folder>\n+\"\"\"\n+\n+import math\n+import os\n+import random\n+import sys\n+\n+from PIL import Image, ImageDraw\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import channelf_layout as L  # noqa: E402\n+from psp_print import ship, space  # noqa: E402\n+from retro_pc_print import rgb  # noqa: E402\n+from windows_pc_print import Region  # noqa: E402\n+\n+BLACK = rgb(L.COLOURS[\"black\"])\n+WOOD = rgb(L.COLOURS[\"wood\"])\n+CART = rgb(L.COLOURS[\"cart\"])\n+WHITE = (232, 232, 226)\n+CREAM = (232, 226, 200)\n+YELLOW = (232, 188, 40)\n+CHROME = (200, 200, 196)\n+\n+\n+def panel():\n+    \"\"\"The front panel from above: x across, y down from the lid's front face to the panel's front edge.\"\"\"\n+    back = L.LID[\"y\"]\n+    reg = Region(L.REGIONS[\"panel\"], L.WIDTH, back, BLACK)\n+\n+    def at(x, y):\n+        return x + L.WIDTH / 2, back - y\n+\n+    xs, y, k = L.KEYS[\"x\"], L.KEYS[\"y\"], L.KEYS[\"size\"] / 2\n+    reg.rect_(*at(xs[0] - 13.0, 62.0), *at(xs[-1] + 13.0, 18.0), (30, 30, 32), r=2.0)\n+    for word, x in zip((\"TIME\", \"MODE\", \"HOLD\", \"START\"), xs[1:]):\n+        reg.text(word, \"arialbd.ttf\", 3.4, *at(x, 57.0), WHITE)\n+    reg.text(\"RESET\", \"arialbd.ttf\", 3.0, *at(xs[0], 51.5), YELLOW)\n+    for word, x in zip((\"2 MIN\", \"5 MIN\", \"10 MIN\", \"20 MIN\"), xs[1:]):\n+        reg.text(word, \"arialbd.ttf\", 2.6, *at(x, 51.5), WHITE)\n+    for ly in (48.5, 29.0):\n+        reg.line([at(xs[0] - 8.0, ly), at(xs[-1] + 8.0, ly)], (160, 156, 140), 0.3)\n+    for word, x in zip((\"HOCKEY\", \"TENNIS\", \"GAME 3\", \"GAME 4\"), xs[1:]):\n+        reg.text(word, \"arialbd.ttf\", 2.6, *at(x, 25.5), WHITE)\n+    for x, name in zip(xs, L.KEYS[\"names\"]):\n+        reg.rect_(*at(x - k, y + k), *at(x + k, y - k), YELLOW if name == \"RESET\" else CREAM, r=1.6)\n+        if name != \"RESET\":\n+            reg.text(name, \"arialbd.ttf\", 6.0, *at(x, y), BLACK)\n+\n+    (px0, px1), (py0, py1) = L.PLATE[\"x\"], L.PLATE[\"y\"]\n+    reg.rect_(*at(px0, py1), *at(px1, py0), (12, 12, 14), r=3.0)\n+    reg.rect_(*at(px0 + 4.0, py1 - 3.0), *at(px1 - 4.0, py0 + 3.0), None, r=3.0, outline=(214, 190, 120), width=0.5)\n+    mx, my = (px0 + px1) / 2, (py0 + py1) / 2\n+    reg.text(\"INSERT\", \"arialbd.ttf\", 3.2, *at(mx - 6.0, my), WHITE, anchor=\"rm\")\n+    reg.text(\"CARTRIDGE\", \"arialbd.ttf\", 3.2, *at(mx + 6.0, my), WHITE, anchor=\"lm\")\n+    reg.line([at(mx - 3.0, my - 1.4), at(mx, my + 1.6), at(mx + 3.0, my - 1.4)], WHITE, 0.5)\n+    (ex0, ex1), (ey0, ey1) = L.EJECT[\"x\"], L.EJECT[\"y\"]\n+    reg.rect_(*at(ex0, ey1), *at(ex1, ey0), (22, 22, 24), r=1.0)\n+    for k in range(7):\n+        for x0 in (ex0 + 2.0, ex1 - 17.0):\n+            yy = ey0 + 2.5 + k * (ey1 - ey0 - 5.0) / 6\n+            reg.line([at(x0, yy), at(x0 + 15.0, yy)], (60, 60, 62), 0.5)\n+    reg.rect_(*at(ex0 + 20.0, ey1 - 4.0), *at(ex1 - 20.0, ey0 + 4.0), (40, 40, 42), r=1.0)\n+    reg.text(\"Press to Eject\", \"arialbd.ttf\", 3.6, *at((ex0 + ex1) / 2, (ey0 + ey1) / 2), WHITE)\n+    return reg\n+\n+\n+def strip():\n+    \"\"\"The lid's chrome strip from above: x across, y down from its back edge.\"\"\"\n+    (x0, x1), (y0, y1) = L.STRIP[\"x\"], L.STRIP[\"y\"]\n+    reg = Region(L.REGIONS[\"strip\"], L.WIDTH, y1 - y0, BLACK)\n+\n+    def at(x, y):\n+        return x + L.WIDTH / 2, y1 - y\n+\n+    reg.rect_(*at(x0, y1), *at(x1, y0), (18, 18, 20), r=1.5)\n+    for ly in (y0 + 2.5, y1 - 2.5):\n+        reg.line([at(x0 + 3.0, ly), at(x1 - 3.0, ly)], CHROME, 0.7)\n+    my = (y0 + y1) / 2\n+    reg.rect_(*at(-112.0, my + 4.0), *at(-74.0, my - 4.0), CHROME)\n+    for k in range(3):\n+        reg.line([at(-112.0, my - 2.0 + 2.0 * k), at(-74.0, my - 2.0 + 2.0 * k)], (18, 18, 20), 0.25)\n+    reg.text(\"FAIRCHILD\", \"arialbd.ttf\", 3.6, *at(-93.0, my), (18, 18, 20))\n+    reg.text(\"VIDEO   ENTERTAINMENT   SYSTEM\", \"arialbd.ttf\", 3.4, *at(40.0, my), WHITE)\n+    return reg\n+\n+\n+def door():\n+    \"\"\"The cartridge door's face: ribbed.\"\"\"\n+    (x0, x1), (z0, z1) = L.SLOT[\"x\"], L.SLOT[\"z\"]\n+    w, h = x1 - x0, z1 - z0\n+    reg = Region(L.REGIONS[\"door\"], w, h, (20, 20, 22))\n+    for k in range(11):\n+        y = 1.5 + k * (h - 3.0) / 10\n+        reg.line([(2.0, y), (w - 2.0, y)], (52, 52, 56), 0.8)\n+    return reg\n+\n+\n+def cart():\n+    \"\"\"The Videocart's top: x across, y down from its back end to its front (the grip's ribs); the label showing in\n+    front of the slot.\"\"\"\n+    w, length, _ = L.CART[\"size\"]\n+    reg = Region(L.REGIONS[\"cart\"], w, length, CART)\n+    reg.rect_(8.0, 22.0, w - 8.0, length - 14.0, (16, 16, 18), r=2.0)\n+    for k in range(6):\n+        y = length - 12.0 + k * 2.0\n+        reg.line([(4.0, y), (w - 4.0, y)], (212, 164, 30), 0.6)\n+    art = space((440, 168)).convert(\"RGB\")\n+    ship(ImageDraw.Draw(art), 120, 86, 2.3, \"#FFFFFF\", \"#FFB23C\")\n+    x0, y0, x1, y1 = reg.box(12.0, 101.0, w - 12.0, length - 17.0)\n+    reg.img.paste(art.resize((round(x1 - x0), round(y1 - y0)), Image.LANCZOS), (round(x0), round(y0)))\n+    reg.text(\"ODYSSEY\", \"ariblk.ttf\", 7.0, w / 2, 95.0, (240, 196, 48))\n+    reg.text(\"VIDEOCART-26\", \"arialbd.ttf\", 3.0, 12.0, 88.0, WHITE, anchor=\"lm\")\n+    reg.text(\"FAIRCHILD\", \"arialbd.ttf\", 3.0, w - 12.0, 88.0, WHITE, anchor=\"rm\")\n+    return reg\n+\n+\n+def wood():\n+    \"\"\"Walnut: dark brown, its grain running along.\"\"\"\n+    reg = Region(L.REGIONS[\"wood\"], 400.0, 160.0, WOOD)\n+    rng = random.Random(11)\n+    for _ in range(140):\n+        y0, tone = rng.uniform(0.0, 160.0), rng.uniform(-0.35, 0.25)\n+        colour = tuple(max(0, min(255, round(c * (1 + tone)))) for c in WOOD)\n+        wave, phase, width = rng.uniform(0.5, 3.0), rng.uniform(0, 2 * math.pi), rng.uniform(0.4, 1.6)\n+        reg.line([(x, y0 + wave * math.sin(x / rng.uniform(30.0, 70.0) + phase)) for x in range(0, 401, 10)], colour,\n+                 width)\n+    return reg\n+\n+\n+def grip():\n+    \"\"\"A controller's grip: black, ribbed across.\"\"\"\n+    length, _, h = L.CONTROLLER[\"grip\"]\n+    reg = Region(L.REGIONS[\"grip\"], length, h, (14, 14, 16))\n+    for k in range(34):\n+        x = 8.0 + k * (length - 16.0) / 33\n+        reg.line([(x, 3.0), (x, h - 3.0)], (46, 46, 50), 0.9)\n+    return reg\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), BLACK)\n+    for reg in (panel(), strip(), door(), cart(), wood(), grip()):\n+        reg.paste(image)\n+    d = ImageDraw.Draw(image)\n+    colours = dict(black=BLACK, wood=WOOD, cream=CREAM, yellow=YELLOW, cart=CART, chrome=CHROME, dark=(6, 6, 6),\n+                   gloss=(14, 14, 16))\n+    for name, (x, y) in L.SWATCHES.items():\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colours[name])\n+    path = os.path.join(out, \"channelf_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "c866a14f6df9e5a9dbd0b440a916cbb887f14eae",
          "filename": "tools/console-models/colecovision.py",
          "status": "added",
          "additions": 342,
          "deletions": 0,
          "changes": 342,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fcolecovision.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fcolecovision.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fcolecovision.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,342 @@\n+\"\"\"The console theme's ColecoVision system model (godot/themes/console/models/systems/colecovision.glb): the\n+ColecoVision, one controller in its bay and the other in front of it.\n+\n+Built in Blender from the real console's proportions (colecovision_layout.py has the measurements, taken from\n+photographs). The console is a rounded black box: the controller bay, the cartridge panel, the slats, the power\n+switch's slot, RESET's recess and the cartridge slot are cut into its top, the silver band's recess into its front,\n+and the expansion interface's door stands out below it. Each controller is a slab with its knob (a joystick with an\n+aluminium cap on a collar), its twelve keys and a grey fire button down each side. The printing is one texture drawn\n+by colecovision_print.py (the console from above, planar-mapped onto every face facing up; its front; a controller's\n+top; the cartridge's label; swatches).\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: the right end towards the viewer, the\n+top tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m) holding the set; the set\n+holding the cartridge (waiting in the slot, shrunk to nothing), the power switch, RESET, and the front controller's\n+knob, its key 1 and its fire button.\n+\n+The `focused` clip (8 s, looping) plays a game: the cartridge drops into the slot, the switch slides ON; key 1 picks\n+the skill level, then the knob circles and the fire button goes in, again and again; RESET; then the switch slides\n+OFF and the cartridge comes out and goes.\n+\n+Five materials (case, dark, key, cart, print) and one texture, within the system model budget. Run from the repo\n+root:\n+\n+    python tools/console-models/colecovision_print.py artifacts/colecovision\n+    blender -b --factory-startup --python tools/console-models/colecovision.py -- artifacts/colecovision godot/themes/console/models/systems/colecovision.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bmesh\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import colecovision_layout as L  # noqa: E402\n+from amiga import paint, rounded, solid  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, ease_out, key, phase, smooth  # noqa: E402\n+from mega_drive_cartridge import bevel, boolean, box, export, join, material, new_object, triangles  # noqa: E402\n+\n+TILT_YAW = -25.0     # degrees about the vertical: the right end comes towards the viewer\n+TILT_PITCH = 30.0    # degrees about the horizontal: the top tips up towards the viewer\n+ROUGHNESS = dict(case=0.6, dark=0.75, key=0.5, cart=0.5, print=0.5)\n+P = L.PAD\n+\n+\n+# ---- UVs -------------------------------------------------------------------------------------------------------------\n+\n+def atlas_uv(region, w, h):\n+    \"\"\"A face drawn w x h into region: (a, b) millimetres across it (a to the right, b down from its top) to UVs.\"\"\"\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+TOP = atlas_uv(L.REGIONS[\"top\"], L.WIDTH, L.DEPTH)\n+FRONT = atlas_uv(L.REGIONS[\"front\"], L.WIDTH, L.HEIGHT)\n+PAD_TOP = atlas_uv(L.REGIONS[\"pad\"], P[\"length\"], P[\"width\"])\n+\n+\n+def top_uv(co):\n+    return TOP(co.x + L.WIDTH / 2, L.DEPTH - co.y)\n+\n+\n+def front_uv(co):\n+    return FRONT(co.x + L.WIDTH / 2, L.HEIGHT - co.z)\n+\n+\n+def pad_uv(co):\n+    \"\"\"A controller's top, in its own frame (x along it from its middle, the knob's end at -x).\"\"\"\n+    return PAD_TOP(co.x + P[\"length\"] / 2, P[\"width\"] / 2 - co.y)\n+\n+\n+def swatch(name):\n+    x, y = L.SWATCHES[name]\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+def painted(obj, m, name):\n+    paint(obj, m[\"print\"], swatch(name), lambda c, n, _name: True)\n+    return obj\n+\n+\n+def prism(name, outline, z0, z1, mat):\n+    return solid(name, [[(x, y, z) for x, y in outline] for z in (z0, z1)], mat)\n+\n+\n+def circle(cx, cy, r, n=24):\n+    return [(cx + r * math.cos(2 * math.pi * k / n), cy + r * math.sin(2 * math.pi * k / n)) for k in range(n)]\n+\n+\n+# ---- The console -----------------------------------------------------------------------------------------------------\n+\n+def console(m):\n+    \"\"\"The console: the box, its top's and front's cuts, the expansion door; printed.\"\"\"\n+    w, d, h = L.WIDTH, L.DEPTH, L.HEIGHT\n+    body = box(\"colecovision\", -w / 2, w / 2, 0.0, d, 0.0, h, m[\"case\"])\n+    bevel(body, L.EDGE, segments=2)\n+    b = L.BAY\n+    boolean(body, box(\"bay\", *b[\"x\"], *b[\"y\"], h - b[\"depth\"], h + 5.0, m[\"dark\"]))\n+    p = L.PANEL\n+    boolean(body, box(\"panel\", *p[\"x\"], *p[\"y\"], h - p[\"depth\"], h + 5.0, m[\"case\"]))\n+    grooves = []\n+    for s in L.SLATS:\n+        (x0, x1), (y0, y1) = s[\"x\"], s[\"y\"]\n+        step = (x1 - x0) / s[\"count\"]\n+        for k in range(s[\"count\"]):\n+            x = x0 + (k + 0.5) * step\n+            grooves.append(box(\"slat\", x - step * 0.28, x + step * 0.28, y0, y1, h - 3.0, h + 5.0, m[\"dark\"]))\n+    boolean(body, join(grooves, \"slats\"))\n+    sw = L.SWITCH\n+    boolean(body, box(\"switch_slot\", *sw[\"x\"], *sw[\"y\"], h - 6.0, h + 5.0, m[\"dark\"]))\n+    r = L.RESET\n+    boolean(body, box(\"reset_well\", *r[\"x\"], *r[\"y\"], h - 3.0, h + 5.0, m[\"case\"]))\n+    sl = L.SLOT\n+    boolean(body, box(\"slot\", *sl[\"x\"], *sl[\"y\"], h - sl[\"depth\"], h + 5.0, m[\"dark\"]))\n+    bd = L.BAND\n+    boolean(body, box(\"band\", -w / 2 + 8.0, w / 2 - 8.0, -5.0, bd[\"depth\"], *bd[\"z\"], m[\"case\"]))\n+    paint(body, m[\"print\"], top_uv, lambda c, n, name: n.z > 0.9 and name == \"case\" and c.z > h - 2.0)\n+    paint(body, m[\"print\"], front_uv, lambda c, n, name: n.y < -0.9 and c.y < bd[\"depth\"] + 0.2 and name == \"case\")\n+    e = L.EXPANSION\n+    door = box(\"expansion\", *e[\"x\"], -e[\"out\"], 2.0, *e[\"z\"], m[\"case\"])\n+    bevel(door, 1.5, segments=2)\n+    boolean(door, box(\"grip\", (e[\"x\"][0] + e[\"x\"][1]) / 2 - 14.0, (e[\"x\"][0] + e[\"x\"][1]) / 2 + 14.0, -e[\"out\"] - 2.0,\n+                      -e[\"out\"] + 1.0, e[\"z\"][0] + 3.0, e[\"z\"][0] + 6.0, m[\"dark\"]))\n+    return [body, door]\n+\n+\n+def switch_knob(m):\n+    (x0, x1), (y0, y1) = L.SWITCH[\"x\"], L.SWITCH[\"y\"]\n+    knob = box(\"switch\", -(x1 - x0) / 2 + 1.0, (x1 - x0) / 2 - 1.0, -4.0, 4.0, -4.0, 3.0, m[\"case\"])\n+    bevel(knob, 0.8, segments=1)\n+    return knob, Vector(((x0 + x1) / 2, y0 + 5.0, L.HEIGHT - 3.0))\n+\n+\n+def reset_button(m):\n+    (x0, x1), (y0, y1) = L.RESET[\"x\"], L.RESET[\"y\"]\n+    button = box(\"reset\", -(x1 - x0) / 2 + 2.0, (x1 - x0) / 2 - 2.0, -(y1 - y0) / 2 + 2.0, (y1 - y0) / 2 - 2.0, -3.0,\n+                 1.5, m[\"case\"])\n+    bevel(button, 1.2, segments=2)\n+    boolean(button, prism(\"dimple\", circle(0.0, 0.0, 3.0, 16), 0.8, 3.0, m[\"dark\"]))\n+    return button, Vector(((x0 + x1) / 2, (y0 + y1) / 2, L.HEIGHT - 1.0))\n+\n+\n+def cartridge(m):\n+    \"\"\"The cartridge, its bottom's middle at its origin, its label (-y) to the front, ribs down its sides.\"\"\"\n+    w, t, h = L.CART[\"size\"]\n+    shell = box(\"cartridge\", -w / 2, w / 2, -t / 2, t / 2, 0.0, h, m[\"cart\"])\n+    bevel(shell, 1.5, segments=2)\n+    ribs = [box(\"rib\", side * w / 2 - 1.0, side * w / 2 + 1.0, -t / 2 - 1.0, t / 2 + 1.0, z - 0.6, z + 0.6, m[\"dark\"])\n+            for side in (-1, 1) for z in (h - 8.0 - 4.0 * k for k in range(L.CART[\"ribs\"]))]\n+    boolean(shell, join(ribs, \"ribs\"))\n+    lw, lh = L.CART[\"label\"]\n+    bm = bmesh.new()\n+    layer = bm.loops.layers.uv.new(\"UVMap\")\n+    x0, x1, z0, z1 = -lw / 2, lw / 2, h - 5.0 - lh, h - 5.0\n+    corners = [(x0, z0), (x1, z0), (x1, z1), (x0, z1)]                  # wound to face -y\n+    face = bm.faces.new([bm.verts.new((x, -t / 2 - 0.12, z)) for x, z in corners])\n+    uv = atlas_uv(L.REGIONS[\"label\"], lw, lh)\n+    for loop, (x, z) in zip(face.loops, corners):\n+        loop[layer].uv = uv(x - x0, z1 - z)\n+    sticker = new_object(\"label\", bm, m[\"print\"])\n+    return join([shell, sticker], \"cartridge\")\n+\n+\n+# ---- The controllers -------------------------------------------------------------------------------------------------\n+\n+def key_rects():\n+    \"\"\"The twelve keys' (name, middle along, middle across) in a controller's frame.\"\"\"\n+    pitch_across, pitch_along, _, _ = P[\"keys\"]\n+    names = [\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"*\", \"0\", \"#\"]\n+    return [(names[row * 3 + col], P[\"first_row\"] + row * pitch_along, (col - 1) * pitch_across)\n+            for row in range(4) for col in range(3)]\n+\n+\n+def a_key(name, m):\n+    \"\"\"A key, its top's middle at its origin, its top printed.\"\"\"\n+    _, _, w, h = P[\"keys\"]\n+    part = box(f\"key_{name}\", -h / 2, h / 2, -w / 2, w / 2, -4.0, 0.0, m[\"key\"])\n+    bevel(part, 0.8, segments=1)\n+    return part\n+\n+\n+def side_button(m, side):\n+    x, length, out = P[\"side\"]\n+    w = P[\"width\"]\n+    part = painted(box(\"fire\", -length / 2, length / 2, -1.0, out, -3.0, 3.0, m[\"print\"]), m, \"grey\")\n+    bevel(part, 0.6, segments=1)\n+    part.data.transform(Matrix.Rotation(math.radians(0 if side > 0 else 180), 4, \"Z\"))\n+    return part, Vector((x, side * w / 2, P[\"height\"] * 0.5))\n+\n+\n+def knob(m):\n+    \"\"\"The knob, its origin at its foot (it tilts about there): the shaft, the black cap and its aluminium top.\"\"\"\n+    _, r, height = P[\"knob\"]\n+    shaft = prism(\"shaft\", circle(0.0, 0.0, r * 0.45, 16), -2.0, height * 0.5, m[\"case\"])\n+    cap = prism(\"cap\", circle(0.0, 0.0, r, 28), height * 0.45, height, m[\"case\"])\n+    bevel(cap, 1.6, segments=2)\n+    top = painted(prism(\"cap_top\", circle(0.0, 0.0, r * 0.8, 28), height - 0.5, height + 0.3, m[\"print\"]), m,\n+                  \"aluminium\")\n+    return join([shaft, cap, top], \"knob\")\n+\n+\n+def controller(m, frame, moving):\n+    \"\"\"A controller at frame: its body, collar and keys (the knob, key 1 and the fire button apart if moving, each\n+    with where it goes).\"\"\"\n+    length, width, height = P[\"length\"], P[\"width\"], P[\"height\"]\n+    body = prism(\"controller\", rounded(0.0, 0.0, length, width, 5.0, 3), 0.0, height, m[\"case\"])\n+    bevel(body, 2.0, segments=2)\n+    kx, kr, _ = P[\"knob\"]\n+    collar = prism(\"collar\", circle(kx, 0.0, kr + 4.0, 28), height - 1.0, height + 4.0, m[\"case\"])\n+    bevel(collar, 1.0, segments=1)\n+    paint(body, m[\"print\"], pad_uv, lambda c, n, name: n.z > 0.9)\n+    paint(collar, m[\"print\"], pad_uv, lambda c, n, name: n.z > 0.9)\n+    static = [body, collar]\n+    parts = {}\n+    for name, x, y in key_rects():\n+        cap = a_key(name, m)\n+        cap.data.transform(Matrix.Translation((x, y, height + 2.0)))\n+        paint(cap, m[\"print\"], pad_uv, lambda c, n, _name: n.z > 0.9)\n+        if moving and name == \"1\":\n+            cap.data.transform(Matrix.Translation((-x, -y, -(height + 2.0))))\n+            parts[\"key_1\"] = (cap, Vector((x, y, height + 2.0)))\n+        else:\n+            static.append(cap)\n+    stick = knob(m)\n+    for side in (-1, 1):\n+        button, at = side_button(m, side)\n+        if moving and side < 0:\n+            parts[\"fire\"] = (button, at)\n+        else:\n+            button.data.transform(Matrix.Translation(at))\n+            static.append(button)\n+    if moving:\n+        parts[\"knob\"] = (stick, Vector((kx, 0.0, height + 2.0)))\n+    else:\n+        stick.data.transform(Matrix.Translation((kx, 0.0, height + 2.0)))\n+        static.append(stick)\n+    for part in static:\n+        part.data.transform(frame)\n+    turn = frame.to_3x3().to_4x4()\n+    placed = {}\n+    for name, (part, at) in parts.items():\n+        part.data.transform(turn)\n+        placed[name] = (part, frame @ at)\n+    return static, placed\n+\n+\n+def pad_frame(spec, lift=0.0):\n+    (px, py), turn = spec[\"at\"], spec[\"turn\"]\n+    return Matrix.Translation((px, py, lift)) @ Matrix.Rotation(math.radians(turn), 4, \"Z\")\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+def appear(f, f0, f1):\n+    return HIDDEN + (1 - HIDDEN) * back_out(phase(f, f0, f1)) if f >= f0 else HIDDEN\n+\n+\n+def tap(f, f0, hold=2):\n+    return smooth(phase(f, f0, f0 + 2)) * (1 - smooth(phase(f, f0 + 2 + hold, f0 + 5 + hold)))\n+\n+\n+FIRES = (92, 104, 116, 132, 148)\n+INWARD = Matrix.Rotation(math.radians(L.FREE_PAD[\"turn\"]), 3, \"Z\") @ Vector((0.0, 1.0, 0.0))   # the fire button's travel\n+\n+\n+def timeline(f):\n+    if f < 6 or f >= 222:\n+        cart = Pose(scale=HIDDEN)\n+    elif f < 16:\n+        cart = Pose(z=80.0, scale=appear(f, 6, 14))\n+    elif f < 28:\n+        cart = Pose(z=80.0 * (1 - ease_in(phase(f, 16, 28))))\n+    elif f < 204:\n+        cart = Pose(squash=0.04 * math.sin(math.pi * phase(f, 28, 34)))\n+    else:\n+        cart = Pose(z=80.0 * ease_out(phase(f, 204, 214)), scale=max(HIDDEN, 1 - ease_in(phase(f, 212, 222))))\n+    on = smooth(phase(f, 36, 42)) * (1 - smooth(phase(f, 192, 198)))\n+    t = phase(f, 78, 160)\n+    lean = 16.0 * math.sin(math.pi * t)\n+    angle = 2 * math.pi * 2.5 * t\n+    return {\"cartridge\": cart, \"switch\": Pose(y=L.SWITCH[\"travel\"] * on), \"reset\": Pose(z=-1.5 * tap(f, 172)),\n+            \"key_1\": Pose(z=-1.8 * tap(f, 58)), \"fire\": Pose(*(INWARD * 1.4 * max(tap(f, f0) for f0 in FIRES))),\n+            \"knob\": Pose(pitch=lean * math.cos(angle), roll=lean * math.sin(angle))}\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    printed = bpy.data.images.load(os.path.join(images, \"colecovision_print.png\"))\n+    colours = dict(case=L.COLOURS[\"case\"], dark=L.COLOURS[\"dark\"], key=L.COLOURS[\"key\"], cart=L.COLOURS[\"cart\"],\n+                   print=\"#FFFFFF\")\n+    m = {n: material(n, colours[n], r, printed if n == \"print\" else None) for n, r in ROUGHNESS.items()}\n+\n+    bay_static, _ = controller(m, pad_frame(L.BAY_PAD, L.HEIGHT - L.BAY[\"depth\"]), moving=False)\n+    free_static, free_parts = controller(m, pad_frame(L.FREE_PAD), moving=True)\n+    whole = join(console(m) + bay_static + free_static, \"colecovision\")\n+    slider, slider_at = switch_knob(m)\n+    reset, reset_at = reset_button(m)\n+    cart = cartridge(m)\n+    for obj in [whole, slider, reset, cart] + [part for part, _ in free_parts.values()]:\n+        for poly in obj.data.polygons:\n+            poly.use_smooth = True\n+        obj.data.set_sharp_from_angle(angle=math.radians(40))\n+\n+    # The node tree: the root (the view and the fit) holds the set; the set holds the moving parts at their places.\n+    root = bpy.data.objects.new(\"colecovision_root\", None)\n+    scene.collection.objects.link(root)\n+    whole.parent = root\n+    sl = L.SLOT\n+    seat = Vector(((sl[\"x\"][0] + sl[\"x\"][1]) / 2, (sl[\"y\"][0] + sl[\"y\"][1]) / 2, L.HEIGHT - L.CART[\"inserted\"]))\n+    placed = [(slider, slider_at), (reset, reset_at), (cart, seat)] + list(free_parts.values())\n+    for part, where in placed:\n+        part.parent = whole\n+        part.location = where\n+    nodes = {\"cartridge\": cart, \"switch\": slider, \"reset\": reset, **{n: p for n, (p, _) in free_parts.items()}}\n+\n+    # The three-quarter view, then the spec's fit of the set at rest, as the root's transform.\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    points = [tilt @ v.co for v in whole.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [whole, slider, reset, cart] + [part for part, _ in free_parts.values()]\n+    print(f\"ColecoVision: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(whole.data)} the set), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "f7387631270e73d4be0ab3d906baed7a3c31019f",
          "filename": "tools/console-models/colecovision_layout.py",
          "status": "added",
          "additions": 54,
          "deletions": 0,
          "changes": 54,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fcolecovision_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fcolecovision_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fcolecovision_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,54 @@\n+\"\"\"The ColecoVision's measurements, shared by colecovision_print.py (which draws its printing) and colecovision.py\n+(which builds it). Plain Python: Blender's Python imports it too.\n+\n+The ColecoVision (1982), 356 x 205 x 52 mm (HobbyDB's 14 x 8 x 2 inches), one controller in its bay and the other out\n+in front of it. Millimetres: x from the console's middle (left to right), y back from its front, z up from the table.\n+The rest was measured from photographs on Wikimedia Commons (Evan-Amos's views of the console from each corner and with\n+a controller, and of the controller; views from above, and with a cartridge in), scaled by the console's width.\n+\n+The console: a black box, the silver band across its front (COLECOVISION's rainbow lettering, VIDEO GAME SYSTEM,\n+EXPANSION MODULE INTERFACE) with the expansion interface's door standing out below its right half. On its top, the\n+controller bay at the back left; the cartridge panel at the back right, slats along its back, the cartridge standing in\n+the slot at its front, behind the silver INSERT CARTRIDGE HERE plate (ON and OFF, RESET); along the front, slats at the\n+left, the power switch, and RESET's button at the right. The controller: a slab, the knob (a joystick with an\n+aluminium cap) at one end, the twelve keys at the other, a grey fire button down each side.\n+\n+One texture (ATLAS square, REGIONS): `top` (the console seen from above: the slats' frames, the plate, COLECO on the\n+controllers' docks; planar-mapped onto every face facing up), `front` (its front face), `pad` (a controller's top: the\n+keys' numbers, COLECO), `label` (the cartridge's: a made-up game, Odyssey), and swatches. A region is (x, y, width,\n+height) in pixels.\n+\"\"\"\n+\n+ATLAS = 1024\n+\n+WIDTH, DEPTH, HEIGHT = 356.0, 205.0, 52.0\n+EDGE = 4.0\n+BAND = dict(z=(12.0, 32.0), depth=0.8)                     # the silver band across the front, sunk a little\n+EXPANSION = dict(x=(48.0, 150.0), z=(0.0, 20.0), out=12.0)  # the expansion interface's door, below the band\n+BAY = dict(x=(-165.0, -9.0), y=(64.0, 197.0), depth=24.0)   # the controller bay\n+PANEL = dict(x=(0.0, 166.0), y=(72.0, 196.0), depth=1.5)    # the cartridge panel, sunk a little\n+SLATS = [dict(x=(-160.0, -36.0), y=(16.0, 58.0), count=26, vertical=True),       # the front left's slats\n+         dict(x=(4.0, 162.0), y=(178.0, 196.0), count=36, vertical=True)]       # along the panel's back\n+SWITCH = dict(x=(-14.0, -2.0), y=(24.0, 46.0), travel=10.0)                    # the power switch's slot\n+RESET = dict(x=(140.0, 160.0), y=(22.0, 48.0))                                 # RESET's button, its recess\n+PLATE = dict(x=(18.0, 130.0), y=(18.0, 52.0))                                  # INSERT CARTRIDGE HERE\n+SLOT = dict(x=(30.0, 116.0), y=(56.0, 80.0), depth=48.0)                       # the cartridge slot\n+\n+CART = dict(size=(82.0, 20.0, 104.0), label=(70.0, 66.0), inserted=44.0, ribs=9)   # w, thick, tall; label; in slot\n+\n+# The controllers: each length x width x height, its knob at one end, its keys at the other.\n+PAD = dict(length=150.0, width=66.0, height=22.0, knob=(-46.0, 14.0, 20.0), keys=(17.0, 16.0, 13.0, 12.5),\n+           first_row=16.0, side=(-30.0, 9.0, 3.0))  # the knob's middle (along it), radius, height; the keys' pitch\n+                                                    # across and along, their size across and along; the first\n+                                                    # row's middle (along it); the side buttons' middle, length, out\n+BAY_PAD = dict(at=(-87.0, 162.0), turn=0.0)          # the one in the bay: its middle, its knob's end to the left\n+FREE_PAD = dict(at=(-232.0, -40.0), turn=-64.0)      # the one in front: its knob towards the back\n+\n+COLOURS = dict(case=\"#18181A\", dark=\"#0A0A0B\", silver=\"#C6C8CC\", key=\"#D9DBD6\", grey=\"#8C8E91\", cart=\"#141416\")\n+\n+REGIONS = dict(top=(0, 0, 1024, 590), front=(0, 598, 1024, 162), pad=(0, 768, 470, 207), label=(478, 768, 280, 256))\n+SWATCHES = dict(dark=(784, 792), silver=(816, 792), key=(848, 792), grey=(880, 792), aluminium=(912, 792),\n+                red=(944, 792))\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s"
        },
        {
          "sha": "9a8635601ccc14aa25073489cf53a5d5c9227074",
          "filename": "tools/console-models/colecovision_print.py",
          "status": "added",
          "additions": 177,
          "deletions": 0,
          "changes": 177,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fcolecovision_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fcolecovision_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fcolecovision_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,177 @@\n+\"\"\"The ColecoVision's printing, for the console theme's ColecoVision model (tools/console-models/colecovision.py).\n+\n+Draws its atlas, its regions as colecovision_layout.py places them, each in millimetres as the model's planar UVs map\n+it: the console seen from above (the silver INSERT CARTRIDGE HERE plate with ON, OFF and RESET, RESET's recess); its\n+front face (the silver band with COLECOVISION's rainbow lettering, VIDEO GAME SYSTEM, EXPANSION MODULE INTERFACE and\n+the COLECO badge); a controller's top (the keys' numbers, the star and the hash, COLECO, the knob's collar); the\n+cartridge's label (a made-up game, Odyssey, in the black-and-white style of Coleco's labels); and the swatches small\n+parts take their colour from. Run it with the system Python (Pillow):\n+\n+    python tools/console-models/colecovision_print.py <out folder>\n+\n+It writes colecovision_print.png there.\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+from PIL import Image, ImageDraw, ImageFont\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import colecovision_layout as L  # noqa: E402\n+from psp_print import ship, space  # noqa: E402\n+from retro_pc_print import rgb, shade  # noqa: E402\n+from windows_pc_print import FONTS, Region  # noqa: E402\n+\n+CASE = rgb(L.COLOURS[\"case\"])\n+SILVER = rgb(L.COLOURS[\"silver\"])\n+KEY = rgb(L.COLOURS[\"key\"])\n+RED = (214, 40, 36)\n+RAINBOW = [(226, 52, 52), (240, 140, 40), (240, 214, 40), (90, 190, 70), (40, 170, 210), (70, 90, 210),\n+           (150, 70, 200)]\n+\n+\n+def brushed(reg, x0, y0, x1, y1):\n+    \"\"\"A brushed aluminium strip: silver with fine streaks along it.\"\"\"\n+    reg.rect_(x0, y0, x1, y1, SILVER)\n+    layer = reg.layer()\n+    d = ImageDraw.Draw(layer)\n+    k = 0\n+    y = y0\n+    while y < y1:\n+        shade_ = 245 if k % 3 == 0 else 150\n+        d.line([reg.p(x0, y), reg.p(x1, y)], fill=(shade_, shade_, shade_ + 4, 40))\n+        y += 0.35\n+        k += 1\n+    reg.over(layer)\n+\n+\n+def rainbow_text(reg, text, x, y, size):\n+    \"\"\"Lettering outlined in the rainbow, letter by letter, left to right, its left edge at x and its middle at y.\"\"\"\n+    f = ImageFont.truetype(FONTS + \"ariblk.ttf\", max(1, round(size * reg.sx)))\n+    px, py = reg.p(x, y)\n+    for k, ch in enumerate(text):\n+        colour = RAINBOW[k % len(RAINBOW)]\n+        reg.draw.text((px, py), ch, font=f, fill=CASE, anchor=\"lm\", stroke_width=max(1, round(0.35 * reg.sx)),\n+                      stroke_fill=colour)\n+        px += f.getlength(ch) + 0.4 * reg.sx\n+\n+\n+def top():\n+    reg = Region(L.REGIONS[\"top\"], L.WIDTH, L.DEPTH, CASE)\n+\n+    def at(x, y):\n+        return x + L.WIDTH / 2, L.DEPTH - y\n+    (x0, x1), (y0, y1) = L.PLATE[\"x\"], L.PLATE[\"y\"]\n+    brushed(reg, *at(x0, y1), *at(x1, y0))\n+    reg.rect_(*at(x0, y0 + 9.0), *at(x1, y0), (14, 14, 16))\n+    reg.rect_(*at(x0, y1), *at(x1, y0), None, outline=(60, 60, 64), width=0.6)\n+    reg.text(\"INSERT CARTRIDGE HERE\", \"times.ttf\", 3.4, *at((x0 + x1) / 2 + 10.0, y1 - 5.0), (30, 30, 32))\n+    reg.text(\"ON\", \"times.ttf\", 3.2, *at(x0 + 6.0, y1 - 12.0), (30, 30, 32))\n+    reg.text(\"OFF\", \"times.ttf\", 3.2, *at(x0 + 7.0, y0 + 4.5), RED)\n+    reg.text(\"RESET\", \"times.ttf\", 3.2, *at(x1 - 9.0, y0 + 4.5), RED)\n+    (x0, x1), (y0, y1) = L.SWITCH[\"x\"], L.SWITCH[\"y\"]\n+    reg.rect_(*at(x0 - 3.0, y1 + 3.0), *at(x1 + 3.0, y0 - 3.0), shade(CASE, -0.4))\n+    return reg\n+\n+\n+def front():\n+    reg = Region(L.REGIONS[\"front\"], L.WIDTH, L.HEIGHT, CASE)\n+\n+    def at(x, z):\n+        return x + L.WIDTH / 2, L.HEIGHT - z\n+    (z0, z1) = L.BAND[\"z\"]\n+    brushed(reg, *at(-170.0, z1), *at(170.0, z0))\n+    reg.rect_(*at(-170.0, z1), *at(170.0, z0), None, outline=(60, 60, 64), width=0.4)\n+    rainbow_text(reg, \"COLECO\", *at(-164.0, z0 + 6.0), 8.0)\n+    rainbow_text(reg, \"VISION\", *at(-116.0, z0 + 6.0), 8.0)\n+    reg.text(\"VIDEO GAME SYSTEM\", \"times.ttf\", 4.0, *at(-60.0, z0 + 4.0), (24, 24, 26), anchor=\"lm\")\n+    (e0, e1) = L.EXPANSION[\"x\"]\n+    reg.text(\"EXPANSION MODULE INTERFACE\", \"times.ttf\", 3.2, *at((e0 + e1) / 2, z1 - 4.0), (24, 24, 26))\n+    reg.rect_(*at(152.0, z0 + 8.0), *at(168.0, z0 + 3.0), (20, 20, 22), r=0.6)\n+    reg.rect_(*at(152.6, z0 + 7.4), *at(167.4, z0 + 3.6), None, outline=SILVER, width=0.25)\n+    reg.text(\"COLECO\", \"arialbd.ttf\", 2.8, *at(160.0, z0 + 5.5), SILVER)\n+    return reg\n+\n+\n+def pad():\n+    p = L.PAD\n+    length, width = p[\"length\"], p[\"width\"]\n+    reg = Region(L.REGIONS[\"pad\"], length, width, CASE)\n+\n+    def at(x, y):\n+        return x + length / 2, width / 2 - y\n+    kx = p[\"knob\"][0]\n+    reg.circle(*at(kx, 0.0), p[\"knob\"][1] + 5.0, shade(CASE, -0.3))\n+    for k in range(8):                                        # the collar's notches\n+        a = 2 * math.pi * k / 8\n+        reg.circle(*at(kx + (p[\"knob\"][1] + 3.0) * math.cos(a), (p[\"knob\"][1] + 3.0) * math.sin(a)), 0.8,\n+                   shade(CASE, 0.15))\n+    # COLECO, embossed, running along the controller between the knob and the keys.\n+    layer = reg.layer()\n+    f = ImageFont.truetype(FONTS + \"ariblk.ttf\", max(1, round(4.6 * reg.sx)))\n+    tile = Image.new(\"RGBA\", (round(24 * reg.sx), round(7 * reg.sx)), (0, 0, 0, 0))\n+    ImageDraw.Draw(tile).text((tile.width / 2, tile.height / 2), \"COLECO\", font=f, fill=shade(CASE, 0.2) + (255,),\n+                              anchor=\"mm\")\n+    tile = tile.rotate(90, expand=True)\n+    cx, cy = reg.p(*at(-16.0, 0.0))\n+    layer.paste(tile, (round(cx - tile.width / 2), round(cy - tile.height / 2)))\n+    reg.over(layer)\n+    pitch_across, pitch_along, w, h = p[\"keys\"]\n+    for row in range(4):\n+        for col in range(3):\n+            x = p[\"first_row\"] + row * pitch_along\n+            y = (col - 1) * pitch_across                         # col 0 at the bottom: 1, 4, 7, *\n+            name = [\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"*\", \"0\", \"#\"][row * 3 + col]\n+            dark = name in \"*#\"\n+            fill = (24, 24, 26) if dark else KEY\n+            reg.rect_(*at(x - h / 2, y + w / 2), *at(x + h / 2, y - w / 2), fill, r=1.6)\n+            tile = Image.new(\"RGBA\", (round(14 * reg.sx), round(14 * reg.sx)), (0, 0, 0, 0))\n+            ft = ImageFont.truetype(FONTS + (\"arialbd.ttf\" if dark else \"times.ttf\"), max(1, round(8.0 * reg.sx)))\n+            ImageDraw.Draw(tile).text((tile.width / 2, tile.height / 2), name, font=ft,\n+                                      fill=((240, 240, 240) if dark else (20, 20, 22)) + (255,), anchor=\"mm\")\n+            tile = tile.rotate(90)                              # upright with the knob at the top\n+            px, py = reg.p(*at(x, y))\n+            reg.img.alpha_composite(tile, (round(px - tile.width / 2), round(py - tile.height / 2)))\n+    return reg\n+\n+\n+def label():\n+    lw, lh = L.CART[\"label\"]\n+    reg = Region(L.REGIONS[\"label\"], lw, lh, (14, 14, 16))\n+    reg.rect_(1.5, 1.5, lw - 1.5, lh - 1.5, None, r=4.0, outline=(236, 236, 236), width=0.6)\n+    reg.text(\"COLECO\", \"ariblk.ttf\", 6.0, lw / 2, 9.0, (236, 236, 236))\n+    reg.text(\"VISION\", \"ariblk.ttf\", 6.0, lw / 2, 15.0, (236, 236, 236))\n+    reg.text(\"PRESENTS\", \"arialbd.ttf\", 2.8, lw / 2, 20.5, (236, 236, 236))\n+    art = space((560, 300))\n+    d = ImageDraw.Draw(art)\n+    ship(d, 180, 170, 2.8, \"#FFFFFF\", \"#FFB23C\")\n+    x0, y0, x1, y1 = reg.box(5.0, 24.0, lw - 5.0, lh - 12.0)\n+    reg.img.paste(art.convert(\"RGB\").resize((round(x1 - x0), round(y1 - y0)), Image.LANCZOS), (round(x0), round(y0)))\n+    layer = reg.layer()\n+    f = ImageFont.truetype(FONTS + \"ariblk.ttf\", max(1, round(9.0 * reg.sx)))\n+    ImageDraw.Draw(layer).text(reg.p(lw / 2, 32.0), \"ODYSSEY\", font=f, fill=(226, 52, 52, 255), anchor=\"mm\",\n+                               stroke_width=max(1, round(0.6 * reg.sx)), stroke_fill=(60, 200, 230, 255))\n+    reg.over(layer)\n+    reg.text(\"A Space Trip for One or Two Players\", \"arial.ttf\", 2.4, lw / 2, lh - 7.0, (236, 236, 236))\n+    return reg\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), CASE)\n+    for reg in (top(), front(), pad(), label()):\n+        reg.paste(image)\n+    d = ImageDraw.Draw(image)\n+    for name, colour in ((\"dark\", rgb(L.COLOURS[\"dark\"])), (\"silver\", SILVER), (\"key\", KEY),\n+                         (\"grey\", rgb(L.COLOURS[\"grey\"])), (\"aluminium\", (200, 202, 206)), (\"red\", RED)):\n+        x, y = L.SWATCHES[name]\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colour)\n+    path = os.path.join(out, \"colecovision_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "4ebbaae731c7f66fdd3b19dd6c3b843cf9030daa",
          "filename": "tools/console-models/desktop.py",
          "status": "added",
          "additions": 253,
          "deletions": 0,
          "changes": 253,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fdesktop.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fdesktop.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fdesktop.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,253 @@\n+\"\"\"The console theme's Desktop Apps system model (godot/themes/console/models/systems/desktop.glb): an unbranded\n+laptop, open, its screen showing a made-up desktop of shortcuts.\n+\n+Built in Blender (desktop_layout.py has the measurements). Desktop Apps is the shortcuts on the user's desktop, so it\n+has no hardware of its own: the card is a laptop, as Flash's is a monitor and PICO-8's its cartridge. The base is its\n+rounded slab with the keyboard's well and the touchpad sunk into it, its top printed; the lid is built upright and\n+turned back on its hinge, the bezel round the screen. The printing is one texture (desktop_print.py's).\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: the right side towards the viewer, the\n+top tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m) holding the base; the base\n+holding the lid (at its hinge); the lid holding, in the screen's plane, the cursor, the Odyssey shortcut selected\n+(waiting inside the lid) and the game's window (shrunk to nothing), the window its three screens (those not shown a\n+little behind the one that is, so a swap never shows one shrinking).\n+\n+The `focused` clip (8 s, looping): the cursor glides to the Odyssey shortcut and double-clicks it, and it's selected;\n+the game's window zooms open from it: the title, then the game; the cursor goes to the window's close button and\n+clicks, the window closes and the cursor goes back.\n+\n+Two materials (body, screen) and one 1024\u00b2 texture, within the system model budget. Run from the repo root:\n+\n+    python tools/console-models/desktop_print.py artifacts/desktop\n+    blender -b --factory-startup --python tools/console-models/desktop.py -- artifacts/desktop godot/themes/console/models/systems/desktop.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import desktop_layout as L  # noqa: E402\n+from amiga import paint, rounded, solid  # noqa: E402\n+from clip import HIDDEN, Pose, ease_in, key, phase, smooth  # noqa: E402\n+from mega_drive_cartridge import bevel, boolean, box, export, join, material, triangles  # noqa: E402\n+\n+TILT_YAW = -24.0     # degrees about the vertical: the right side comes towards the viewer\n+TILT_PITCH = 16.0    # degrees about the horizontal: the top tips up towards the viewer\n+ROUGHNESS = dict(body=0.4, screen=0.55)       # a matte screen: a glossy one washed the desktop out\n+SW, SH = L.SCREEN[\"size\"]\n+\n+\n+def atlas_uv(region, w, h):\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+def swatch(name):\n+    x, y = L.SWATCHES[name]\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+def colour(obj, mat, name):\n+    paint(obj, mat, swatch(name), lambda c, n, kind: True)\n+    return obj\n+\n+\n+def flat(name, w, h, uv, mat):\n+    \"\"\"A flat quad facing -y, w x h about its origin (x across, z up); uv(a, b) for a across from its left and b down\n+    from its top, 0 to 1.\"\"\"\n+    part = solid(name, [[(x, y, z) for x, z in ((-w / 2, -h / 2), (w / 2, -h / 2), (w / 2, h / 2), (-w / 2, h / 2))]\n+                        for y in (0.0, 0.05)], mat)\n+    paint(part, mat, lambda co: uv(co.x / w + 0.5, 0.5 - co.z / h), lambda c, n, kind: n.y < -0.9)\n+    paint(part, mat, swatch(\"black\"), lambda c, n, kind: n.y >= -0.9)\n+    return part\n+\n+\n+def region_uv(name):\n+    rx, ry, rw, rh = L.REGIONS[name]\n+    return lambda a, b: ((rx + a * rw) / L.ATLAS, 1 - (ry + b * rh) / L.ATLAS)\n+\n+\n+def on_screen(u, v):\n+    \"\"\"A point (u across from the screen's middle, v up from its bottom) in the lid's frame.\"\"\"\n+    return Vector((u, 0.0, L.SCREEN[\"bottom\"] + v))\n+\n+\n+# ---- The laptop ------------------------------------------------------------------------------------------------------\n+\n+def base(m):\n+    \"\"\"The base: its slab, the keyboard's well and the touchpad sunk in, the hinge, the feet; its top printed.\"\"\"\n+    slab = solid(\"desktop\", [[(x, y, z) for x, y in rounded(0.0, L.DEPTH / 2, L.WIDTH, L.DEPTH, L.ROUND[\"plan\"], 5)]\n+                             for z in (0.0, L.BASE)], m[\"body\"])\n+    bevel(slab, L.ROUND[\"edge\"], segments=2, angle=40)\n+    (wx0, wx1), (wy0, wy1) = L.WELL[\"x\"], L.WELL[\"y\"]\n+    boolean(slab, box(\"well\", wx0, wx1, wy0, wy1, L.BASE - L.WELL[\"depth\"], L.BASE + 5.0, m[\"body\"]))\n+    (tx0, tx1), (ty0, ty1) = L.TOUCHPAD[\"x\"], L.TOUCHPAD[\"y\"]\n+    pad = rounded((tx0 + tx1) / 2, (ty0 + ty1) / 2, tx1 - tx0, ty1 - ty0, 4.0, 3)\n+    boolean(slab, solid(\"touchpad\", [[(x, y, z) for x, y in pad] for z in (L.BASE - L.TOUCHPAD[\"depth\"], L.BASE + 5.0)],\n+                        m[\"body\"]))\n+    colour(slab, m[\"body\"], \"body\")\n+    top = atlas_uv(L.REGIONS[\"keyboard\"], L.WIDTH, L.DEPTH)\n+    paint(slab, m[\"body\"], lambda co: top(co.x + L.WIDTH / 2, L.DEPTH - co.y), lambda c, n, kind: n.z > 0.9)\n+    hy, hz = L.LID[\"hinge\"]\n+    hinge = solid(\"hinge\", [[(x, hy + 4.2 * math.cos(a), hz + 4.2 * math.sin(a)) for a in\n+                             [2 * math.pi * k / 12 for k in range(12)]] for x in (-150.0, 150.0)], m[\"body\"])\n+    colour(hinge, m[\"body\"], \"hinge\")\n+    feet = [colour(solid(\"foot\", [[(x + 6.0 * math.cos(a), y + 6.0 * math.sin(a), z) for a in\n+                                   [2 * math.pi * k / 10 for k in range(10)]] for z in (-1.0, 0.5)], m[\"body\"]),\n+                   m[\"body\"], \"black\") for x in (-150.0, 150.0) for y in (22.0, 216.0)]\n+    return join([slab, hinge] + feet, \"desktop\")\n+\n+\n+def lid(m):\n+    \"\"\"The lid, built upright in its own frame (its hinge's line at the origin, its screen facing -y): its shell, the\n+    bezel, the screen at rest.\"\"\"\n+    w, h, t = L.LID[\"width\"], L.LID[\"height\"], L.LID[\"thickness\"]\n+    shell = solid(\"lid\", [[(x, y, z) for x, z in rounded(0.0, h / 2, w, h, L.ROUND[\"plan\"], 5)] for y in (0.0, t)],\n+                  m[\"body\"])\n+    bevel(shell, 1.6, segments=2, angle=40)\n+    colour(shell, m[\"body\"], \"lid\")\n+    paint(shell, m[\"screen\"], swatch(\"bezel\"), lambda c, n, kind: n.y < -0.9)\n+    screen = flat(\"screen\", SW, SH, region_uv(\"desktop\"), m[\"screen\"])\n+    screen.data.transform(Matrix.Translation(on_screen(0.0, SH / 2) + Vector((0.0, -0.1, 0.0))))\n+    return join([shell, screen], \"lid\")\n+\n+\n+def cursor(m):\n+    \"\"\"The mouse pointer, its tip at its origin, white with a black edge, facing -y.\"\"\"\n+    s = L.CURSOR[\"size\"] / 12.0\n+    arrow = [(0.0, 0.0), (0.0, -12.0), (3.0, -9.2), (5.2, -14.0), (7.0, -13.2), (4.8, -8.6), (8.6, -8.6)]\n+\n+    def shape(name, grow, y0, y1, mat_name):\n+        cx, cz = 3.0, -8.0\n+        pts = [((x - cx) * grow + cx, (z - cz) * grow + cz) for x, z in arrow]\n+        part = solid(name, [[(x * s, y, z * s) for x, z in pts] for y in (y0, y1)], m[\"screen\"])\n+        return colour(part, m[\"screen\"], mat_name)\n+    return join([shape(\"cursor\", 1.0, -0.2, 0.0, \"white\"), shape(\"cursor_edge\", 1.22, 0.05, 0.25, \"black\")], \"cursor\")\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+REST = L.CURSOR[\"rest\"]\n+ICON = (L.ICONS[\"x\"] + 2.0, L.ICONS[\"top\"] - 2.0)       # where the cursor clicks the Odyssey shortcut\n+CLOSE = L.WINDOW[\"close\"]\n+FRONT = -1.0              # how far in front of the screen the shown things stand; the hidden ones stay just behind them\n+\n+\n+def glide(f, f0, f1, a, b):\n+    t = smooth(phase(f, f0, f1))\n+    return a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t\n+\n+\n+def click(f, f0):\n+    return 0.18 * math.sin(math.pi * phase(f, f0, f0 + 4))\n+\n+\n+def showing(f):\n+    if f < 92 or f >= 184:\n+        return \"title\"\n+    return \"game_1\" if ((f - 92) // 8) % 2 == 0 else \"game_2\"\n+\n+\n+def timeline(f):\n+    if f < 40:\n+        u, v = glide(f, 8, 34, REST, ICON)\n+    elif f < 150:\n+        u, v = ICON\n+    elif f < 198:\n+        u, v = glide(f, 150, 176, ICON, CLOSE)\n+    else:\n+        u, v = glide(f, 198, 226, CLOSE, REST)\n+    press = max(click(f, 38), click(f, 45), click(f, 180))\n+    rest = on_screen(*REST)\n+    here = on_screen(u, v)\n+    poses = {\"cursor\": Pose(x=here.x - rest.x, z=here.z - rest.z, scale=1.0 - press)}\n+    poses[\"selected\"] = Pose(y=FRONT - 0.5) if 38 <= f < 52 else Pose()\n+    # The window: from the shortcut to its place, growing; later shrinking away.\n+    (wu, wv) = L.WINDOW[\"at\"]\n+    iu, iv = L.ICONS[\"x\"], L.ICONS[\"top\"]\n+    if f < 50 or f >= 196:\n+        win = Pose(scale=HIDDEN)\n+    elif f < 62:\n+        t = smooth(phase(f, 50, 62))                    # no overshoot: it would spill past the screen's edge\n+        win = Pose(x=(iu - wu) * (1 - t), z=(iv - wv) * (1 - t), scale=max(HIDDEN, 0.08 + 0.92 * t))\n+    elif f < 184:\n+        win = Pose()\n+    else:\n+        win = Pose(scale=max(HIDDEN, 1.0 - ease_in(phase(f, 184, 196))))\n+    poses[\"window\"] = win\n+    lit = showing(f)\n+    for name in (\"title\", \"game_1\", \"game_2\"):\n+        poses[name] = Pose() if name == lit else Pose(y=0.6)\n+    return poses\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    atlas = bpy.data.images.load(os.path.join(images, \"desktop_print.png\"))\n+    m = {name: material(name, L.COLOURS[\"print\"], r, atlas) for name, r in ROUGHNESS.items()}\n+\n+    laptop = base(m)\n+    cover = lid(m)\n+    for obj in (laptop, cover):\n+        for p in obj.data.polygons:\n+            p.use_smooth = True\n+        obj.data.set_sharp_from_angle(angle=math.radians(35))\n+\n+    root = bpy.data.objects.new(\"desktop_root\", None)\n+    scene.collection.objects.link(root)\n+    laptop.parent = root\n+    hy, hz = L.LID[\"hinge\"]\n+    cover.parent = laptop\n+    cover.location = (0.0, hy, hz)\n+    cover.rotation_euler = (math.radians(-(L.LID[\"open\"] - 90.0)), 0.0, 0.0)\n+\n+    pointer = cursor(m)\n+    pointer.parent = cover\n+    pointer.location = on_screen(*REST) + Vector((0.0, FRONT - 1.0, 0.0))\n+    u0, v0, u1, v1 = (L.ICONS[\"x\"] - 14.0, L.ICONS[\"top\"] - L.ICONS[\"size\"] / 2 - 8.5, L.ICONS[\"x\"] + 14.0,\n+                      L.ICONS[\"top\"] + L.ICONS[\"size\"] / 2 + 1.5)\n+    chosen = flat(\"selected\", u1 - u0, v1 - v0, region_uv(\"selected\"), m[\"screen\"])\n+    chosen.parent = cover\n+    chosen.location = on_screen((u0 + u1) / 2, (v0 + v1) / 2) + Vector((0.0, 0.4, 0.0))   # waiting inside the lid\n+    window = bpy.data.objects.new(\"window\", None)\n+    scene.collection.objects.link(window)\n+    window.parent = cover\n+    window.location = on_screen(*L.WINDOW[\"at\"]) + Vector((0.0, FRONT, 0.0))\n+    nodes = {\"cursor\": pointer, \"selected\": chosen, \"window\": window}\n+    ww, wh = L.WINDOW[\"size\"]\n+    for name in (\"title\", \"game_1\", \"game_2\"):\n+        part = flat(name, ww, wh, region_uv(name), m[\"screen\"])\n+        part.parent = window\n+        part.location = (0.0, 0.0, 0.0)\n+        nodes[name] = part\n+\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    bpy.context.view_layer.update()\n+    lid_world = Matrix.Translation((0.0, hy, hz)) @ Matrix.Rotation(math.radians(-(L.LID[\"open\"] - 90.0)), 4, \"X\")\n+    points = [tilt @ v.co for v in laptop.data.vertices] + [tilt @ lid_world @ v.co for v in cover.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [laptop, cover, pointer, chosen] + [nodes[n] for n in (\"title\", \"game_1\", \"game_2\")]\n+    print(f\"Desktop Apps: {sum(triangles(o.data) for o in meshes)} triangles, \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root, window] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "d81622ac936574713b47766531ad1f9a1ea1bbaa",
          "filename": "tools/console-models/desktop_layout.py",
          "status": "added",
          "additions": 41,
          "deletions": 0,
          "changes": 41,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fdesktop_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fdesktop_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fdesktop_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,41 @@\n+\"\"\"The Desktop Apps card's measurements, shared by desktop_print.py (which draws its printing and screens) and\n+desktop.py (which builds it). Plain Python: Blender's Python imports it too.\n+\n+Desktop Apps is the shortcuts on the user's desktop, so it has no hardware of its own: the card is an unbranded\n+15.6-inch laptop, open, its screen showing a made-up desktop of shortcuts (each icon with its shortcut arrow), one of\n+them a made-up game, Odyssey. A laptop rather than a tower, so it isn't Windows' card. Millimetres: x from its middle\n+(left to right), y back from the base's front edge, z up from the table; the lid's own frame is x across, z up the lid\n+from its hinge, y back from the screen's face.\n+\n+The base: its slab, the keyboard sunk into it, the touchpad, the speaker grilles beside the keyboard. The lid: open\n+LID[\"open\"] degrees from the base, the bezel round the screen.\n+\n+One texture (ATLAS square, REGIONS): `desktop` (the screen at rest), `title`, `game_1` and `game_2` (the game's window,\n+three ways: its title bar and its content), `selected` (the Odyssey shortcut selected), `keyboard` (the base from\n+above), and swatches. A region is (x, y, width, height) in pixels.\n+\"\"\"\n+\n+ATLAS = 1024\n+\n+WIDTH, DEPTH, BASE = 356.0, 240.0, 17.0\n+ROUND = dict(plan=9.0, edge=2.0)\n+WELL = dict(x=(-140.0, 140.0), y=(96.0, 222.0), depth=0.8)      # the keyboard's well\n+TOUCHPAD = dict(x=(-58.0, 58.0), y=(12.0, 84.0), depth=0.5)\n+LID = dict(width=356.0, height=232.0, thickness=6.0, open=112.0, hinge=(234.0, 17.0))   # the hinge: (y, z) on the base\n+SCREEN = dict(size=(344.0, 193.5), bottom=27.0)                  # the screen's size and how far up the lid it starts\n+\n+# On the screen (u across from its middle, v up from its bottom): the shortcuts' column, the game's window.\n+ICONS = dict(x=-156.0, top=178.0, pitch=30.0, size=18.0)        # the first icon's middle, the spacing, its size\n+ODYSSEY = 0                                                       # the game's shortcut is the first\n+WINDOW = dict(at=(14.0, 104.0), size=(232.0, 145.0), close=(122.0, 172.5))   # its middle; its close button\n+CURSOR = dict(rest=(52.0, 62.0), size=12.0)\n+\n+COLOURS = dict(body=\"#6B6E74\", print=\"#FFFFFF\")\n+\n+REGIONS = dict(desktop=(0, 0, 640, 360), selected=(648, 0, 64, 64), title=(0, 368, 320, 200),\n+               game_1=(328, 368, 320, 200), game_2=(656, 368, 320, 200), keyboard=(0, 576, 640, 432))\n+SWATCHES = dict(body=(760, 40), dark=(792, 40), black=(824, 40), white=(856, 40), bezel=(888, 40), hinge=(920, 40),\n+                key=(952, 40), lid=(984, 40))\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s"
        },
        {
          "sha": "fda921468b53d584313e15408be11afd6a449c92",
          "filename": "tools/console-models/desktop_print.py",
          "status": "added",
          "additions": 263,
          "deletions": 0,
          "changes": 263,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fdesktop_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fdesktop_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fdesktop_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,263 @@\n+\"\"\"The Desktop Apps card's printing and screens (desktop_print.png), for the console theme's model (desktop.py).\n+\n+One image, laid out as desktop_layout.REGIONS says: the screen at rest (a made-up desktop: its wallpaper, a column of\n+shortcuts, each icon with its shortcut arrow, the first a made-up game, Odyssey; the taskbar and its clock), the\n+Odyssey shortcut selected, the game's window three ways (its title bar, then its title screen and two moments of its\n+play), the base from above (the keyboard in its well, the touchpad, the speaker grilles), and swatches. Run it with\n+the system Python (Pillow):\n+\n+    python tools/console-models/desktop_print.py <out folder>\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+from PIL import Image, ImageDraw, ImageFilter, ImageFont\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import desktop_layout as L  # noqa: E402\n+from psp_print import ship, space  # noqa: E402\n+from retro_pc_print import rgb  # noqa: E402\n+from windows_pc_print import FONTS, Region  # noqa: E402\n+\n+BODY = rgb(L.COLOURS[\"body\"])\n+WHITE = (246, 247, 250)\n+SW, SH = L.SCREEN[\"size\"]\n+NAMES = [\"Odyssey\", \"Notes\", \"Music\", \"Photos\", \"Files\", \"Browser\"]\n+\n+\n+# ---- The desktop -----------------------------------------------------------------------------------------------------\n+\n+def wallpaper(reg):\n+    \"\"\"A made-up wallpaper: an indigo to teal gradient, soft bands of light, a ringed planet low on the right.\"\"\"\n+    w, h = reg.img.size\n+    layer = Image.new(\"RGBA\", (w, h))\n+    d = ImageDraw.Draw(layer)\n+    for y in range(h):\n+        for x in range(0, w, 4):\n+            t = 0.55 * x / w + 0.45 * y / h\n+            colour = (round(28 + 10 * t), round(30 + 110 * t), round(92 + 60 * t), 255)\n+            d.line([(x, y), (x + 3, y)], fill=colour)\n+    bands = Image.new(\"RGBA\", (w, h), (0, 0, 0, 0))\n+    bd = ImageDraw.Draw(bands)\n+    for k, (amp, base, alpha) in enumerate(((0.07, 0.62, 70), (0.05, 0.7, 50), (0.09, 0.55, 40))):\n+        pts = [(x, h * (base + amp * math.sin(2 * math.pi * x / w * (1.2 + 0.3 * k) + k))) for x in range(0, w + 8, 8)]\n+        bd.line(pts, fill=(200, 236, 255, alpha), width=max(2, round(h / 60)))\n+    layer = Image.alpha_composite(layer, bands.filter(ImageFilter.GaussianBlur(h / 90)))\n+    reg.img.alpha_composite(layer)\n+    cx, cy, r = reg.p(SW * 0.8, SH * 0.68)[0], reg.p(0, SH * 0.68)[1], SH * 0.13 * reg.sx\n+    reg.draw.ellipse([cx - r, cy - r, cx + r, cy + r], fill=(242, 140, 82, 255))\n+    reg.draw.ellipse([cx - r * 0.82, cy - r * 0.95, cx + r * 0.7, cy + r * 0.5], fill=(247, 166, 106, 255))\n+    reg.draw.arc([cx - r * 1.8, cy - r * 0.36, cx + r * 1.8, cy + r * 0.36], 160, 380, fill=(240, 230, 255, 255),\n+                 width=max(2, round(r / 9)))\n+\n+\n+def glyph(reg, name, cx, cy, s):\n+    \"\"\"An icon, s across, its middle at (cx, cy) in the region's millimetres: a rounded tile and its picture.\"\"\"\n+    tiles = dict(Odyssey=(70, 40, 140), Notes=(250, 210, 70), Music=(236, 80, 130), Photos=(60, 150, 236),\n+                 Files=(240, 176, 60), Browser=(40, 170, 160))\n+    h = s / 2\n+    reg.rect_(cx - h, cy - h, cx + h, cy + h, tiles[name], r=s * 0.22)\n+    if name == \"Odyssey\":\n+        art = space((120, 120)).convert(\"RGB\")\n+        a, b, c, d = reg.box(cx - h + 1.0, cy - h + 1.0, cx + h - 1.0, cy + h - 1.0)\n+        mask = Image.new(\"L\", (round(c - a), round(d - b)), 0)\n+        ImageDraw.Draw(mask).rounded_rectangle([0, 0, mask.width - 1, mask.height - 1], radius=s * 0.18 * reg.sx,\n+                                               fill=255)\n+        reg.img.paste(art.resize(mask.size, Image.LANCZOS), (round(a), round(b)), mask)\n+        dd = ImageDraw.Draw(reg.img)\n+        ship(dd, cx * reg.sx - s * 0.12 * reg.sx, cy * reg.sy, s * 0.03 * reg.sx, \"#FFFFFF\", \"#FFB23C\")\n+    elif name == \"Notes\":\n+        reg.rect_(cx - h * 0.55, cy - h * 0.62, cx + h * 0.55, cy + h * 0.62, WHITE, r=0.6)\n+        for k in range(4):\n+            reg.line([(cx - h * 0.4, cy - h * 0.3 + k * h * 0.22), (cx + h * 0.4, cy - h * 0.3 + k * h * 0.22)],\n+                     (150, 150, 160), 0.4)\n+    elif name == \"Music\":\n+        reg.circle(cx - h * 0.18, cy + h * 0.32, h * 0.2, WHITE)\n+        reg.rect_(cx - h * 0.02, cy - h * 0.5, cx + h * 0.08, cy + h * 0.32, WHITE)\n+        reg.poly([(cx + h * 0.08, cy - h * 0.5), (cx + h * 0.45, cy - h * 0.3), (cx + h * 0.08, cy - h * 0.18)], WHITE)\n+    elif name == \"Photos\":\n+        reg.poly([(cx - h * 0.6, cy + h * 0.45), (cx - h * 0.15, cy - h * 0.2), (cx + h * 0.15, cy + h * 0.15),\n+                  (cx + h * 0.3, cy), (cx + h * 0.6, cy + h * 0.45)], WHITE)\n+        reg.circle(cx + h * 0.32, cy - h * 0.35, h * 0.14, (255, 240, 160))\n+    elif name == \"Files\":\n+        reg.rect_(cx - h * 0.6, cy - h * 0.35, cx - h * 0.05, cy - h * 0.15, (255, 226, 150), r=0.4)\n+        reg.rect_(cx - h * 0.6, cy - h * 0.22, cx + h * 0.6, cy + h * 0.5, (255, 226, 150), r=0.6)\n+    else:\n+        reg.circle(cx, cy, h * 0.55, None, outline=WHITE, width=0.6)\n+        reg.line([(cx - h * 0.55, cy), (cx + h * 0.55, cy)], WHITE, 0.5)\n+        reg.draw.ellipse(reg.box(cx - h * 0.22, cy - h * 0.55, cx + h * 0.22, cy + h * 0.55), outline=WHITE,\n+                         width=reg.width(0.5))\n+    # The shortcut's arrow, at the icon's lower left.\n+    a = s * 0.3\n+    x0, y0 = cx - h, cy + h - a\n+    reg.rect_(x0, y0, x0 + a, y0 + a, WHITE, r=0.3)\n+    reg.draw.arc(reg.box(x0 + a * 0.2, y0 + a * 0.3, x0 + a * 0.95, y0 + a * 1.05), 180, 270, fill=(30, 100, 220),\n+                 width=reg.width(a * 0.14))\n+    reg.poly([(x0 + a * 0.55, y0 + a * 0.18), (x0 + a * 0.82, y0 + a * 0.32), (x0 + a * 0.55, y0 + a * 0.5)],\n+             (30, 100, 220))\n+\n+\n+def icon_middle(k):\n+    \"\"\"The k-th shortcut's icon's middle, (u, v) on the screen.\"\"\"\n+    return L.ICONS[\"x\"], L.ICONS[\"top\"] - k * L.ICONS[\"pitch\"]\n+\n+\n+def selection_box(k):\n+    \"\"\"The box the selection highlight fills round the k-th shortcut and its label, (u0, v0, u1, v1).\"\"\"\n+    u, v = icon_middle(k)\n+    s = L.ICONS[\"size\"]\n+    return u - 14.0, v - s / 2 - 8.5, u + 14.0, v + s / 2 + 1.5\n+\n+\n+def desktop(selected=False):\n+    \"\"\"The screen: u across (-172 at the left), v up (0 at the bottom); drawn as a region of the screen's size.\"\"\"\n+    reg = Region((0, 0, 640, 360), SW, SH, (20, 20, 30))\n+\n+    def at(u, v):\n+        return u + SW / 2, SH - v\n+\n+    wallpaper(reg)\n+    if selected:\n+        u0, v0, u1, v1 = selection_box(L.ODYSSEY)\n+        layer = reg.layer()\n+        ImageDraw.Draw(layer).rounded_rectangle(reg.box(*at(u0, v1), *at(u1, v0)), radius=1.6 * reg.sx,\n+                                                fill=(150, 200, 255, 90), outline=(190, 225, 255, 200),\n+                                                width=reg.width(0.4))\n+        reg.over(layer)\n+    s = L.ICONS[\"size\"]\n+    for k, name in enumerate(NAMES):\n+        u, v = icon_middle(k)\n+        glyph(reg, name, *at(u, v), s)\n+        shadow = reg.layer()                                       # the label's soft shadow, then the label\n+        font = ImageFont.truetype(FONTS + \"segoeui.ttf\", max(1, round(3.4 * reg.sx)))\n+        ImageDraw.Draw(shadow).text(reg.p(*at(u + 0.3, v - s / 2 - 4.5)), name, font=font, fill=(0, 0, 0, 150),\n+                                    anchor=\"mm\")\n+        reg.over(shadow.filter(ImageFilter.GaussianBlur(1.2)))\n+        reg.text(name, \"segoeui.ttf\", 3.4, *at(u, v - s / 2 - 4.2), WHITE)\n+    # The taskbar: its start button, a few apps, the clock.\n+    bar = reg.layer()\n+    ImageDraw.Draw(bar).rectangle(reg.box(*at(-SW / 2, 11.0), *at(SW / 2, 0.0)), fill=(14, 18, 34, 200))\n+    reg.over(bar)\n+    reg.circle(*at(-SW / 2 + 8.0, 5.5), 3.0, (120, 180, 255))\n+    reg.circle(*at(-SW / 2 + 8.0, 5.5), 1.2, (14, 18, 34))\n+    for k, name in enumerate((\"Odyssey\", \"Notes\", \"Files\", \"Browser\")):\n+        glyph(reg, name, *at(-20.0 + k * 12.0, 5.5), 7.0)\n+    reg.text(\"12:00\", \"segoeui.ttf\", 3.2, *at(SW / 2 - 12.0, 5.5), WHITE)\n+    return reg.img.convert(\"RGB\")\n+\n+\n+# ---- The game's window -----------------------------------------------------------------------------------------------\n+\n+def window(frame):\n+    \"\"\"The game's window: its title bar, its content (frame 0 the title screen, 1 and 2 its play).\"\"\"\n+    ww, wh = L.WINDOW[\"size\"]\n+    reg = Region((0, 0, 320, 200), ww, wh, (20, 20, 30))\n+    bar = 9.0\n+    art = space((640, 400)).convert(\"RGB\")\n+    d = ImageDraw.Draw(art)\n+    if frame == 0:\n+        ship(d, 200, 250, 4.2, \"#FFFFFF\", \"#FFB23C\")\n+    else:\n+        sx = (150, 230)[frame - 1]\n+        ship(d, sx, 230, 3.0, \"#FFFFFF\", \"#FFB23C\")\n+        for k, (x, y, r) in enumerate(((420, 120, 26), (520, 300, 34), (330, 330, 18))):\n+            x -= 40 * frame\n+            d.polygon([(x + r * math.cos(a), y + r * 0.85 * math.sin(a)) for a in [j * 0.9 + k for j in range(7)]],\n+                      fill=(150, 130, 120), outline=(90, 80, 76))\n+        if frame == 2:\n+            for j in range(3):\n+                d.line([(sx + 70 + j * 40, 230), (sx + 90 + j * 40, 230)], fill=(255, 230, 120), width=5)\n+    x0, y0, x1, y1 = reg.box(0.0, bar, ww, wh)\n+    reg.img.paste(art.resize((round(x1 - x0), round(y1 - y0)), Image.LANCZOS), (round(x0), round(y0)))\n+    if frame == 0:\n+        reg.text(\"ODYSSEY\", \"ariblk.ttf\", 22.0, ww / 2, bar + 40.0, (255, 214, 70))\n+        reg.text(\"PRESS ENTER\", \"segoeuib.ttf\", 7.0, ww / 2, wh - 22.0, WHITE)\n+    else:\n+        reg.text(\"SCORE 0\" + (\"120\" if frame == 1 else \"240\"), \"segoeuib.ttf\", 6.0, 8.0, bar + 8.0, WHITE, anchor=\"lm\")\n+    reg.rect_(0.0, 0.0, ww, bar, (32, 34, 44))\n+    glyph(reg, \"Odyssey\", 5.0, bar / 2, 5.6)\n+    reg.text(\"Odyssey\", \"segoeui.ttf\", 4.0, 10.0, bar / 2, WHITE, anchor=\"lm\")\n+    for k, symbol in enumerate((\"min\", \"max\", \"close\")):\n+        x = ww - 6.0 - (2 - k) * 12.0\n+        if symbol == \"close\":\n+            reg.rect_(x - 6.0, 0.0, x + 6.0, bar, (196, 43, 28))\n+            reg.line([(x - 1.8, bar / 2 - 1.8), (x + 1.8, bar / 2 + 1.8)], WHITE, 0.5)\n+            reg.line([(x - 1.8, bar / 2 + 1.8), (x + 1.8, bar / 2 - 1.8)], WHITE, 0.5)\n+        elif symbol == \"max\":\n+            reg.rect_(x - 1.8, bar / 2 - 1.8, x + 1.8, bar / 2 + 1.8, None, outline=WHITE, width=0.4)\n+        else:\n+            reg.line([(x - 1.8, bar / 2), (x + 1.8, bar / 2)], WHITE, 0.4)\n+    reg.rect_(0.0, 0.0, ww, wh, None, outline=(80, 84, 96), width=0.4)\n+    return reg.img.convert(\"RGB\")\n+\n+\n+# ---- The base --------------------------------------------------------------------------------------------------------\n+\n+def keyboard():\n+    \"\"\"The base from above: x across (-178 at the left), y down from the back (240).\"\"\"\n+    reg = Region(L.REGIONS[\"keyboard\"], L.WIDTH, L.DEPTH, BODY)\n+\n+    def at(x, y):\n+        return x + L.WIDTH / 2, L.DEPTH - y\n+\n+    (wx0, wx1), (wy0, wy1) = L.WELL[\"x\"], L.WELL[\"y\"]\n+    reg.rect_(*at(wx0, wy1), *at(wx1, wy0), (40, 42, 46), r=2.0)\n+    pitch = (wx1 - wx0 - 4.0) / 15\n+    rows = [(wy1 - 6.0, 0.5, [1] * 15)]                                   # the function row, half height\n+    y = wy1 - 12.0\n+    for widths in ([1] * 13 + [2], [1.5] + [1] * 12 + [1.5], [1.8] + [1] * 11 + [2.2], [2.3] + [1] * 10 + [2.7],\n+                   [1, 1, 1, 1.3, 6.4, 1.3, 1, 1, 1, 1]):\n+        rows.append((y - pitch / 2, 1.0, widths))\n+        y -= pitch\n+    for yc, hfrac, widths in rows:\n+        x = wx0 + 2.0\n+        total = sum(widths)\n+        scale = (wx1 - wx0 - 4.0) / (total * pitch)\n+        for wkey in widths:\n+            w = wkey * pitch * scale\n+            h = pitch * hfrac\n+            reg.rect_(*at(x + 0.9, yc + h / 2 - 0.9), *at(x + w - 0.9, yc - h / 2 + 0.9), (24, 25, 28), r=1.6)\n+            reg.rect_(*at(x + 1.4, yc + h / 2 - 1.4), *at(x + w - 1.4, yc - h / 2 + 1.6), (30, 31, 35), r=1.4)\n+            x += w\n+    (tx0, tx1), (ty0, ty1) = L.TOUCHPAD[\"x\"], L.TOUCHPAD[\"y\"]\n+    reg.rect_(*at(tx0, ty1), *at(tx1, ty0), (98, 101, 107), r=4.0, outline=(84, 86, 92), width=0.4)\n+    for side in (-1, 1):\n+        for i in range(4):\n+            for j in range(26):\n+                reg.circle(*at(side * (wx1 + 9.0 + i * 3.2), wy0 + 4.0 + j * 4.6), 0.7, (60, 62, 66))\n+    reg.rect_(*at(wx1 + 6.0, wy1 + 9.0), *at(wx1 + 18.0, wy1 + 4.0), (40, 42, 46), r=1.0)     # the power key\n+    return reg\n+\n+\n+def paste(image, name, img):\n+    x, y, w, h = L.REGIONS[name]\n+    image.paste(img.resize((w, h), Image.LANCZOS), (x, y))\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), BODY)\n+    plain, chosen = desktop(), desktop(selected=True)\n+    paste(image, \"desktop\", plain)\n+    u0, v0, u1, v1 = selection_box(L.ODYSSEY)\n+    sx, sy = chosen.width / SW, chosen.height / SH             # the desktop's own pixels per millimetre (supersampled)\n+    crop = chosen.crop((round((u0 + SW / 2) * sx), round((SH - v1) * sy), round((u1 + SW / 2) * sx),\n+                        round((SH - v0) * sy)))\n+    paste(image, \"selected\", crop)\n+    for k, name in enumerate((\"title\", \"game_1\", \"game_2\")):\n+        paste(image, name, window(k))\n+    keyboard().paste(image)\n+    d = ImageDraw.Draw(image)\n+    colours = dict(body=BODY, dark=(40, 42, 46), black=(8, 8, 10), white=WHITE, bezel=(14, 14, 16), hinge=(30, 31, 34),\n+                   key=(30, 31, 35), lid=(96, 99, 106))\n+    for name, (x, y) in L.SWATCHES.items():\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colours[name])\n+    path = os.path.join(out, \"desktop_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "b3e2dc90e20d2cbaf7c6c040cfac4be437208a94",
          "filename": "tools/console-models/dragon32.py",
          "status": "added",
          "additions": 243,
          "deletions": 0,
          "changes": 243,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fdragon32.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fdragon32.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fdragon32.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,243 @@\n+\"\"\"The console theme's Dragon 32 system model (godot/themes/console/models/systems/dragon32.glb): a Dragon 32 in front\n+of the portable television the ZX Spectrum's card has.\n+\n+Built in Blender (dragon32_layout.py has the measurements, taken from photographs and the museums' figures). The\n+computer is its side outline extruded across its width; the keyboard's well is sunk into the slope in the slope's frame,\n+the ribs cut across the back block and the cartridge's slot into the right side. Its 53 keys stand in the well, each\n+tapering to its printed top. Behind it stands zx_spectrum.py's television. The printing is two textures\n+(dragon32_print.py's): `print`, projected from above onto the slope and the keys (with the television's front where its\n+layout puts it) and onto the cartridge's top; and `media`, the screens.\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: the right end towards the viewer, the\n+top tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m) holding the set; the set\n+holding the cartridge (waiting outside the slot, shrunk to nothing), the television's four screens (those not shown\n+behind the glass), and the keys the clip presses.\n+\n+The `focused` clip (8 s, looping): the cartridge slides into the slot in the right side and starts: Odyssey's title;\n+SPACE, and the game played on the arrow keys; then the cartridge comes out, the Dragon starts again, OK.\n+\n+Four materials (case, tv, dark, glass) and two 1024\u00b2 textures, within the system model budget. Run from the repo root:\n+\n+    python tools/console-models/dragon32_print.py artifacts/dragon32\n+    blender -b --factory-startup --python tools/console-models/dragon32.py -- artifacts/dragon32 godot/themes/console/models/systems/dragon32.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import dragon32_layout as L  # noqa: E402\n+import zx_spectrum_layout as SL  # noqa: E402\n+from amiga import curved, paint, rounded, solid  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, key, phase, smooth  # noqa: E402\n+from mega_drive_cartridge import bevel, boolean, box, export, join, material, prism_x, triangles  # noqa: E402\n+from zx_spectrum import television  # noqa: E402\n+\n+TILT_YAW = -24.0     # degrees about the vertical: the right end comes towards the viewer\n+TILT_PITCH = 20.0    # degrees about the horizontal: the top tips up towards the viewer\n+ROUGHNESS = dict(case=0.5, tv=0.45, dark=0.7, glass=0.35)\n+SCREENS = (\"boot\", \"title\", \"game_1\", \"game_2\")\n+BEHIND = 3.0              # how far back a screen that isn't shown waits: behind the glass\n+PRESSED = dict(up=\"\u2191\", down=\"\u2193\", left=\"\u2190\", right=\"\u2192\", space=\" \")\n+\n+\n+def atlas_uv(region, w, h):\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+def swatch(name):\n+    x, y = L.SWATCHES[name]\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+def colour(obj, mat, name):\n+    paint(obj, mat, swatch(name), lambda c, n, kind: True)\n+    return obj\n+\n+\n+TOP = atlas_uv(L.PRINT_REGIONS[\"top\"], L.WIDTH, L.TOP)\n+\n+\n+def top_uv(co):\n+    return TOP(co.x + L.WIDTH / 2, L.TOP - co.y)\n+\n+\n+def on_slope(name, outline, h0, h1, mat, outline1=None):\n+    \"\"\"A solid from an (x, s) outline in the slope's frame, from h0 off it to h1 (its top outline1, if given).\"\"\"\n+    top = outline1 or outline\n+    return solid(name, [[(x, *L.slope_point(s, h0)) for x, s in outline], [(x, *L.slope_point(s, h1)) for x, s in top]],\n+                 mat)\n+\n+\n+# ---- The Dragon ------------------------------------------------------------------------------------------------------\n+\n+def dragon(m):\n+    \"\"\"The case: the well, the ribs and the slot cut in; printed.\"\"\"\n+    half = L.WIDTH / 2\n+    body = prism_x(\"dragon32\", L.PROFILE, -half, half, m[\"case\"])\n+    bevel(body, 2.5, segments=2, angle=40)\n+    (wx0, wx1), (ws0, ws1), depth, r = L.WELL[\"x\"], L.WELL[\"s\"], L.WELL[\"depth\"], L.WELL[\"r\"]\n+    outline = rounded((wx0 + wx1) / 2, (ws0 + ws1) / 2, wx1 - wx0, ws1 - ws0, r, 4)\n+    boolean(body, on_slope(\"well\", outline, -depth, 30.0, m[\"case\"]))\n+    (ry0, ry1), n = L.RIBS[\"y\"], L.RIBS[\"count\"]\n+    for k in range(n):\n+        y = ry0 + (ry1 - ry0) * (k + 0.5) / n\n+        boolean(body, box(\"groove\", -half + 8.0, half - 8.0, y - 1.5, y + 1.5, L.HEIGHT - 1.5, L.HEIGHT + 1.0,\n+                          m[\"case\"]))\n+    (sy0, sy1), (sz0, sz1) = L.SLOT[\"y\"], L.SLOT[\"z\"]\n+    boolean(body, box(\"slot\", half - L.SLOT[\"depth\"], half + 1.0, sy0, sy1, sz0, sz1, m[\"case\"]))\n+    colour(body, m[\"case\"], \"case\")\n+    paint(body, m[\"case\"], top_uv, lambda c, n_, kind: n_.z > 0.3 and c.y < L.TOP - 0.5)\n+    paint(body, m[\"case\"], swatch(\"rib\"),\n+          lambda c, n_, kind: ry0 - 1.0 < c.y < ry1 + 1.0 and c.z > L.HEIGHT - 2.0 and n_.z < 0.9)\n+    paint(body, m[\"case\"], swatch(\"dark\"),\n+          lambda c, n_, kind: c.x < half - 0.5 and sy0 - 0.5 < c.y < sy1 + 0.5 and sz0 - 0.5 < c.z < sz1 + 0.5)\n+    return body\n+\n+\n+def keys(m):\n+    \"\"\"The keys, standing in the well, each tapering to its printed top; those the clip presses apart (each its foot's\n+    middle at its origin, with where it goes), the rest joined.\"\"\"\n+    fixed, moving = [], {}\n+    inset, top, floor = L.KEY[\"inset\"], L.KEY[\"height\"], -L.WELL[\"depth\"]\n+    for legend, x0, x1, s in L.keys():\n+        foot = [(x0 + 0.6, s - 9.0), (x1 - 0.6, s - 9.0), (x1 - 0.6, s + 9.0), (x0 + 0.6, s + 9.0)]\n+        cap = [(x0 + inset, s - 9.5 + inset), (x1 - inset, s - 9.5 + inset), (x1 - inset, s + 9.5 - inset),\n+               (x0 + inset, s + 9.5 - inset)]\n+        part = on_slope(\"key\", foot, floor - 1.0, top, m[\"case\"], cap)\n+        colour(part, m[\"case\"], \"key\")\n+        paint(part, m[\"case\"], top_uv, lambda c, n, kind: n.z > 0.85)\n+        name = next((k for k, v in PRESSED.items() if v == legend and k not in moving), None)\n+        if name:\n+            middle = Vector(((x0 + x1) / 2, *L.slope_point(s, floor - 1.0)))\n+            part.data.transform(Matrix.Translation(-middle))\n+            moving[name] = (part, middle)\n+        else:\n+            fixed.append(part)\n+    return join(fixed, \"keys\"), moving\n+\n+\n+def cartridge(m):\n+    \"\"\"The cartridge, its end going in (along -x) at its origin's x, its middle across and its foot's middle; its label\n+    up.\"\"\"\n+    length, w, t = L.CART[\"size\"]\n+    part = box(\"cartridge\", 0.0, length, -w / 2, w / 2, -t / 2, t / 2, m[\"case\"])\n+    bevel(part, 1.6, segments=2)\n+    colour(part, m[\"case\"], \"cart\")\n+    face = atlas_uv(L.PRINT_REGIONS[\"cartridge\"], w, length)\n+    paint(part, m[\"case\"], lambda co: face(co.y + w / 2, length - co.x), lambda c, n, kind: n.z > 0.9)\n+    return part\n+\n+\n+def screens(m):\n+    \"\"\"The television's four screens, each the glass's whole area.\"\"\"\n+    gw, gh = SL.TV[\"glass\"][\"size\"]\n+    return {name: curved(name, gw, gh, SL.TV[\"glass\"][\"bulge\"], atlas_uv(L.MEDIA_REGIONS[name], 1.0, 1.0), m[\"glass\"])\n+            for name in SCREENS}\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+def tap(f, f0, hold=2):\n+    return smooth(phase(f, f0, f0 + 3)) * (1 - smooth(phase(f, f0 + 3 + hold, f0 + 7 + hold)))\n+\n+\n+def shown(f):\n+    if f < 40 or f >= 178:\n+        return \"boot\"\n+    if f < 62:\n+        return \"title\"\n+    return \"game_1\" if ((f - 62) // 6) % 2 == 0 else \"game_2\"\n+\n+\n+def timeline(f):\n+    out = L.CART[\"out\"]\n+    if f < 10 or f >= 194:\n+        cart = Pose(x=out, scale=HIDDEN)\n+    elif f < 17:\n+        cart = Pose(x=out, scale=max(HIDDEN, back_out(phase(f, 10, 17))))\n+    elif f < 172:\n+        cart = Pose(x=out * (1 - smooth(phase(f, 17, 34))))\n+    elif f < 186:\n+        cart = Pose(x=out * smooth(phase(f, 172, 186)))\n+    else:\n+        cart = Pose(x=out, scale=max(HIDDEN, 1 - ease_in(phase(f, 186, 194))))\n+    poses = {\"cartridge\": cart}\n+    for name in SCREENS:\n+        poses[name] = Pose() if name == shown(f) else Pose(y=BEHIND)\n+    press = -2.6\n+    poses[\"space\"] = Pose(z=press * tap(f, 56))\n+    for name, times in ((\"up\", (70, 98, 126, 154)), (\"down\", (84, 140)), (\"left\", (78, 112, 146)),\n+                        (\"right\", (90, 104, 132, 160))):\n+        poses[name] = Pose(z=press * max(tap(f, f0) for f0 in times))\n+    return poses\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    m = {\"case\": material(\"case\", L.COLOURS[\"print\"], ROUGHNESS[\"case\"],\n+                          bpy.data.images.load(os.path.join(images, \"dragon32_print.png\"))),\n+         \"tv\": material(\"tv\", SL.COLOURS[\"tv\"], ROUGHNESS[\"tv\"]),\n+         \"dark\": material(\"dark\", L.COLOURS[\"dark\"], ROUGHNESS[\"dark\"]),\n+         \"glass\": material(\"glass\", L.COLOURS[\"print\"], ROUGHNESS[\"glass\"],\n+                           bpy.data.images.load(os.path.join(images, \"dragon32_media.png\")))}\n+    m[\"print\"] = m[\"case\"]                                    # television() prints its front with the set's atlas\n+\n+    fixed_keys, moving = keys(m)\n+    tv = television(m, m[\"glass\"], atlas_uv(L.MEDIA_REGIONS[\"boot\"], 1.0, 1.0))\n+    shift = Matrix.Translation((L.TV_AT[0] - SL.TV[\"at\"][0], L.TV_AT[1] - SL.TV[\"at\"][1], 0.0))\n+    for part in tv:\n+        part.data.transform(shift)\n+    whole = join([dragon(m), fixed_keys] + tv, \"dragon32\")\n+    for p in whole.data.polygons:\n+        p.use_smooth = True\n+    whole.data.set_sharp_from_angle(angle=math.radians(35))\n+\n+    root = bpy.data.objects.new(\"dragon32_root\", None)\n+    scene.collection.objects.link(root)\n+    whole.parent = root\n+    (sy0, sy1), (sz0, sz1) = L.SLOT[\"y\"], L.SLOT[\"z\"]\n+    seat = Vector((L.WIDTH / 2 + L.CART[\"proud\"] - L.CART[\"size\"][0], (sy0 + sy1) / 2, (sz0 + sz1) / 2))\n+    moving[\"cartridge\"] = (cartridge(m), seat)\n+    s = SL.TV[\"screen\"]\n+    glass_front = Vector((L.TV_AT[0] + s[\"x\"], L.TV_AT[1] + s[\"depth\"] - 0.4, s[\"z\"]))\n+    moving.update({name: (part, glass_front) for name, part in screens(m).items()})\n+    nodes = {}\n+    for name, (part, where) in moving.items():\n+        if name not in SCREENS:\n+            for p in part.data.polygons:\n+                p.use_smooth = True\n+            part.data.set_sharp_from_angle(angle=math.radians(35))\n+        part.parent = whole\n+        part.location = where\n+        nodes[name] = part\n+\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    points = [tilt @ v.co for v in whole.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [whole] + list(nodes.values())\n+    print(f\"Dragon 32: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(whole.data)} the set), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "3598c12542dadd2a8fe52926860b92621f8be538",
          "filename": "tools/console-models/dragon32_layout.py",
          "status": "added",
          "additions": 85,
          "deletions": 0,
          "changes": 85,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fdragon32_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fdragon32_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fdragon32_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,85 @@\n+\"\"\"The Dragon 32 card's measurements, shared by dragon32_print.py (which draws its printing and screens) and dragon32.py\n+(which builds it). Plain Python: Blender's Python imports it too.\n+\n+The Dragon 32 (1982), 325 mm wide, 378 deep and 96 high (the museums' 322 to 325 x 375 to 380 x 90 to 98), in front of\n+the portable television the ZX Spectrum's card has (zx_spectrum.py's, its front's middle at TV_AT), a ROM cartridge with\n+a made-up game, Odyssey, going into the slot in its right side. Millimetres: x from its middle (left to right), y back\n+from its front, z up from the table. Measured from photographs on Wikimedia Commons (from above, which its keys' 19.05\n+mm pitch scales; its sides and back straight on; others from the front).\n+\n+The computer: a beige wedge (PROFILE: the front, the slope up to the raised back block, its top, the chamfer to the\n+back); the keyboard's well sunk into the slope (WELL), its black keys standing in it in the slope's frame (KEYS, in\n+units of U across and rows up the slope); the DRAGON 32 badge on the slope behind them (BADGE), the ribs across the back\n+block's front (RIBS, grooves); the cartridge's slot in the right side (SLOT).\n+\n+One texture `print` (ATLAS square, PRINT_REGIONS): `top` (the Dragon's front TOP millimetres from above: the keys' tops\n+and legends, the badge), `cartridge` (its label), and swatches; the television's front where zx_spectrum_layout.py's\n+PRINT_REGIONS[\"tv\"] puts it and its swatches where its PRINT_SWATCHES do, as television() maps them. One `media`\n+(MEDIA_REGIONS): the screens, the VDG's 256 x 192 in its black border (the start-up screen in black on green, the game's\n+title, two moments of the game). A region is (x, y, width, height) in pixels.\n+\"\"\"\n+\n+import math\n+\n+ATLAS = 1024\n+U = 19.05\n+\n+WIDTH, DEPTH, HEIGHT = 325.0, 378.0, 96.0\n+FRONT = (3.0, 41.0)               # the slope's front edge (y, z)\n+SLOPE_BACK = (165.0, 89.0)        # its back edge, at the back block's step\n+SLOPE = math.atan2(SLOPE_BACK[1] - FRONT[1], SLOPE_BACK[0] - FRONT[0])\n+PROFILE = [                       # the side outline (y, z), round from the front's foot\n+    (4.0, 0.0), (DEPTH - 4.0, 0.0), (DEPTH, 4.0), (DEPTH, 56.0), (DEPTH - 2.0, 60.0), (347.0, HEIGHT),\n+    (172.0, HEIGHT), (168.0, 92.0), SLOPE_BACK, FRONT, (0.0, FRONT[1] - 3.0), (0.0, 4.0),\n+]\n+WELL = dict(x=(-154.0, 154.0), s=(22.0, 132.0), depth=4.0, r=6.0)     # the keyboard's well, in the slope's frame\n+BADGE = dict(x=(-143.0, 22.0), s=(146.0, 160.0))\n+RIBS = dict(y=(178.0, 250.0), count=6)\n+\n+KEYS_X0, ROW1 = -119.0, 113.0     # row 1's left edge; its middle (s)\n+KEY = dict(height=12.0, inset=2.4)\n+ROWS = [\n+    (0.0, [(c, 1.0) for c in \"1234567890:-\"] + [(\"BREAK\", 1.0)]),\n+    (-0.75, [(\"\u2191\", 1.0)] + [(c, 1.0) for c in \"QWERTYUIOP@\"] + [(\"\u2190\", 1.0), (\"\u2192\", 1.0)]),\n+    (-0.5, [(\"\u2193\", 1.0)] + [(c, 1.0) for c in \"ASDFGHJKL;\"] + [(\"ENTER\", 2.0), (\"CLEAR\", 1.0)]),\n+    (-0.6, [(\"SHIFT\", 1.5)] + [(c, 1.0) for c in \"ZXCVBNM,./\"] + [(\"SHIFT\", 1.75)]),\n+    (1.95, [(\" \", 8.4)]),\n+]\n+SHIFTED = dict(zip(\"1234567890:-\", \"!\\\"#$%&'()0*=\"))\n+\n+SLOT = dict(y=(98.0, 181.0), z=(12.0, 31.0), depth=96.0)    # the cartridge's slot in the right side\n+# The cartridge: its length going in (x), its width (y) and thickness (z); how far out it waits; how much of it stands\n+# out of the slot when it is in.\n+CART = dict(size=(92.0, 80.0, 17.0), out=100.0, proud=4.0)\n+\n+TV_AT = (0.0, 400.0)              # the television's front's middle\n+\n+COLOURS = dict(case=\"#D9CDAE\", key=\"#232325\", print=\"#FFFFFF\", dark=\"#0A0A0B\")\n+\n+TOP = 175.0                       # how far back the top's region reaches (y): the slope, its keys and the badge\n+PRINT_REGIONS = dict(top=(0, 0, 1024, 551), cartridge=(620, 600, 200, 174))\n+SWATCHES = dict(case=(852, 620), key=(884, 620), dark=(916, 620), cart=(948, 620), rib=(852, 652))\n+MEDIA_REGIONS = dict(boot=(0, 0, 320, 246), title=(336, 0, 320, 246), game_1=(672, 0, 320, 246),\n+                     game_2=(0, 256, 320, 246))\n+VDG_PX = (256, 192)\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s\n+\n+\n+def slope_point(s, h):\n+    \"\"\"The model's (y, z) of the point s up the slope from its front edge and h off it.\"\"\"\n+    c, k = math.cos(SLOPE), math.sin(SLOPE)\n+    return FRONT[0] + s * c - h * k, FRONT[1] + s * k + h * c\n+\n+\n+def keys():\n+    \"\"\"Every key: (legend, x0, x1, s), s its row's middle up the slope; the rows from the back to the front.\"\"\"\n+    out = []\n+    for r, (offset, row) in enumerate(ROWS):\n+        x = KEYS_X0 + offset * U\n+        s = ROW1 - r * U\n+        for legend, width in row:\n+            out.append((legend, x, x + width * U, s))\n+            x += width * U\n+    return out"
        },
        {
          "sha": "4366edfe3e68b6d871fda8f7623f0be506ab9cf8",
          "filename": "tools/console-models/dragon32_print.py",
          "status": "added",
          "additions": 185,
          "deletions": 0,
          "changes": 185,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fdragon32_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fdragon32_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fdragon32_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,185 @@\n+\"\"\"The Dragon 32 card's printing and screens (dragon32_print.png, dragon32_media.png), for the console theme's model\n+(dragon32.py).\n+\n+`dragon32_print.png`, laid out as dragon32_layout.PRINT_REGIONS says: the Dragon's slope from above (the black keys'\n+tops and their white legends, the DRAGON 32 badge with the red dragon), the cartridge's label (a made-up game, Odyssey),\n+and swatches; the television's front (zx_spectrum_print.py's) and its swatches where zx_spectrum_layout.py puts them.\n+`dragon32_media.png`: the screens, the VDG's 256 x 192 in its black border: the start-up screen (black on green), the\n+game's title and two moments of it in the VDG's colours. Run it with the system Python (Pillow):\n+\n+    python tools/console-models/dragon32_print.py <out folder>\n+\"\"\"\n+\n+import os\n+import random\n+import sys\n+\n+from PIL import Image, ImageDraw\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import dragon32_layout as L  # noqa: E402\n+import zx_spectrum_layout as SL  # noqa: E402\n+from psp_print import ship, space  # noqa: E402\n+from retro_pc_print import rgb  # noqa: E402\n+from vic20_print import chars  # noqa: E402\n+from windows_pc_print import Region  # noqa: E402\n+from zx_spectrum_print import tv_front  # noqa: E402\n+\n+CASE = rgb(L.COLOURS[\"case\"])\n+KEY = rgb(L.COLOURS[\"key\"])\n+WHITE = (236, 236, 230)\n+RAINBOW = [(220, 40, 40), (240, 130, 30), (240, 210, 40), (60, 170, 60), (40, 120, 220), (130, 60, 180)]\n+\n+\n+def dragon(reg, x, y, h, fill):\n+    \"\"\"The Dragon's mark, h tall, its middle at (x, y): a winged dragon in a few strokes.\"\"\"\n+    k = h / 10.0\n+    reg.poly([(x - 5 * k, y + 3 * k), (x - 1 * k, y - 1 * k), (x + 2 * k, y - 5 * k), (x + 1 * k, y - 0.5 * k),\n+              (x + 5 * k, y - 3 * k), (x + 2.5 * k, y + 1 * k), (x + 4 * k, y + 4 * k), (x, y + 1.5 * k),\n+              (x - 2.5 * k, y + 4.5 * k)], fill)\n+\n+\n+def top():\n+    \"\"\"The slope from above: x across (-WIDTH/2 at the left), y down from TOP to the front.\"\"\"\n+    reg = Region(L.PRINT_REGIONS[\"top\"], L.WIDTH, L.TOP, CASE)\n+\n+    def at(x, y):\n+        return x + L.WIDTH / 2, L.TOP - y\n+\n+    (bx0, bx1), (bs0, bs1) = L.BADGE[\"x\"], L.BADGE[\"s\"]\n+    y0, y1 = L.slope_point(bs0, 0.0)[0], L.slope_point(bs1, 0.0)[0]\n+    reg.rect_(*at(bx0, y1), *at(bx1, y0), (18, 18, 20), r=1.0)\n+    my = (y0 + y1) / 2\n+    dragon(reg, *at(bx0 + 18.0, my), 12.0, (220, 40, 40))\n+    x = bx0 + 58.0\n+    for c, colour in zip(\"DRAGON\", RAINBOW):\n+        reg.text(c, \"ariblk.ttf\", 10.0, *at(x, my + 1.0), colour, anchor=\"lm\")\n+        x += 8.6\n+    reg.text(\"32\", \"ariblk.ttf\", 10.0, *at(x + 1.0, my + 1.0), WHITE, anchor=\"lm\")\n+    reg.text(\"DATA LTD.\", \"arialbd.ttf\", 1.8, *at(x - 14.0, my - 5.4), WHITE)\n+\n+    top_h, inset = L.KEY[\"height\"], L.KEY[\"inset\"]\n+    for legend, x0, x1, s in L.keys():\n+        ya = L.slope_point(s - 9.5 + inset, top_h)[0]\n+        yb = L.slope_point(s + 9.5 - inset, top_h)[0]\n+        reg.rect_(*at(x0 + inset, yb), *at(x1 - inset, ya), KEY, r=1.2)\n+        x, y = at((x0 + x1) / 2, (ya + yb) / 2)\n+        if legend in L.SHIFTED:\n+            reg.text(L.SHIFTED[legend], \"arial.ttf\", 3.0, x, y - 2.6, WHITE)\n+            reg.text(\"\u00d8\" if legend == \"0\" else legend, \"arial.ttf\", 3.8, x, y + 2.0, WHITE)\n+        elif len(legend) == 1 and legend != \" \":\n+            reg.text(legend, \"seguisym.ttf\" if ord(legend) > 0x2000 else \"arial.ttf\", 4.4, x, y, WHITE)\n+        elif legend.strip():\n+            reg.text(legend, \"arialbd.ttf\", 2.4, x, y, WHITE)\n+    return reg\n+\n+\n+def cartridge():\n+    \"\"\"The cartridge's top: x across (its width, along y in the model), y down from its outer end to the end going\n+    in.\"\"\"\n+    length, w, _ = L.CART[\"size\"]\n+    reg = Region(L.PRINT_REGIONS[\"cartridge\"], w, length, (236, 232, 222))\n+    reg.rect_(6.0, 6.0, w - 6.0, length - 24.0, (20, 20, 26), r=1.5)\n+    art = space((440, 300)).convert(\"RGB\")\n+    ship(ImageDraw.Draw(art), 140, 170, 3.0, \"#FFFFFF\", \"#FFB23C\")\n+    x0, y0, x1, y1 = reg.box(8.0, 18.0, w - 8.0, length - 26.0)\n+    reg.img.paste(art.resize((round(x1 - x0), round(y1 - y0)), Image.LANCZOS), (round(x0), round(y0)))\n+    reg.text(\"ODYSSEY\", \"ariblk.ttf\", 7.0, w / 2, 12.0, (255, 214, 70))\n+    dragon(reg, 12.0, length - 14.0, 8.0, (220, 40, 40))\n+    reg.text(\"DRAGON DATA\", \"arialbd.ttf\", 4.0, 22.0, length - 14.0, (40, 40, 44), anchor=\"lm\")\n+    return reg\n+\n+\n+# ---- The screens -----------------------------------------------------------------------------------------------------\n+\n+VDG = dict(green=(0, 255, 0), dark=(0, 64, 0), yellow=(255, 255, 0), blue=(0, 0, 255), red=(255, 0, 0),\n+           buff=(255, 255, 255), cyan=(0, 255, 255), magenta=(255, 0, 255), orange=(255, 128, 0), black=(0, 0, 0))\n+\n+\n+def boot():\n+    \"\"\"The start-up screen: the VDG's text, 32 columns of 16 rows 12 pixels tall, dark on green.\"\"\"\n+    img = Image.new(\"RGB\", L.VDG_PX, VDG[\"green\"])\n+    for row, line in enumerate([\"(C) 1982 DRAGON DATA LTD\", \"16K BASIC INTERPRETER 1.0\", \"(C) 1982 BY MICROSOFT\", \"\",\n+                                \"OK\"]):\n+        chars(img, 0, row * 1.5, line, VDG[\"dark\"])\n+    ImageDraw.Draw(img).rectangle([0, 60, 7, 71], fill=VDG[\"dark\"])\n+    return img\n+\n+\n+def title():\n+    img = Image.new(\"RGB\", L.VDG_PX, VDG[\"black\"])\n+    d = ImageDraw.Draw(img)\n+    rng = random.Random(32)\n+    for _ in range(50):\n+        d.point((rng.randrange(256), rng.randrange(192)), fill=rng.choice([VDG[\"buff\"], VDG[\"cyan\"], VDG[\"yellow\"]]))\n+    chars(img, 4, 2, \"ODYSSEY\", VDG[\"yellow\"], scale=3)\n+    chars(img, 8, 6, \"A SPACE TRIP\", VDG[\"cyan\"])\n+    d.ellipse([160, 90, 214, 144], fill=VDG[\"orange\"])\n+    d.line([(144, 128), (232, 108)], fill=VDG[\"buff\"], width=2)\n+    ship(d, 70, 118, 1.6, \"#FFFFFF\", \"#FF0000\")\n+    chars(img, 4, 21, \"PRESS SPACE TO START\", VDG[\"green\"])\n+    return img\n+\n+\n+def game(frame):\n+    img = Image.new(\"RGB\", L.VDG_PX, VDG[\"black\"])\n+    d = ImageDraw.Draw(img)\n+    rng = random.Random(64)\n+    for _ in range(50):\n+        x, y = rng.randrange(256), rng.randrange(176)\n+        d.point(((x - frame * 10) % 256, y + 16), fill=VDG[\"buff\"])\n+    chars(img, 0, 0, \"SCORE 0%d40\" % (frame + 1), VDG[\"yellow\"])\n+    chars(img, 24, 0, \"SHIPS 3\", VDG[\"cyan\"])\n+    d.ellipse([180 - 8 * frame, 70, 228 - 8 * frame, 118], fill=VDG[\"orange\"])\n+    d.line([(166 - 8 * frame, 104), (242 - 8 * frame, 88)], fill=VDG[\"buff\"], width=2)\n+    ship(d, 56, 100 + 14 * frame, 1.5, \"#FFFFFF\", \"#FF0000\")\n+    for k in range(3):\n+        d.line([(90 + 32 * k + 12 * frame, 100 + 14 * frame), (102 + 32 * k + 12 * frame, 100 + 14 * frame)],\n+               fill=VDG[\"yellow\"], width=2)\n+    return img\n+\n+\n+def glass(picture):\n+    \"\"\"A screen's region: the VDG's black border filling the glass, its picture in its middle.\"\"\"\n+    w, h = 320, 246\n+    image = Image.new(\"RGB\", (w, h), VDG[\"black\"])\n+    pw, ph = round(w * 0.8), round(h * 0.8)\n+    image.paste(picture.resize((pw, ph), Image.NEAREST), ((w - pw) // 2, (h - ph) // 2))\n+    return image\n+\n+\n+def media_atlas(out):\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), (0, 0, 0))\n+    for name, picture in ((\"boot\", boot()), (\"title\", title()), (\"game_1\", game(0)), (\"game_2\", game(1))):\n+        x, y, w, h = L.MEDIA_REGIONS[name]\n+        image.paste(glass(picture).resize((w, h), Image.LANCZOS), (x, y))\n+    path = os.path.join(out, \"dragon32_media.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+def print_atlas(out):\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), CASE)\n+    for reg in (top(), cartridge(), tv_front()):\n+        reg.paste(image)\n+    d = ImageDraw.Draw(image)\n+    for name, colour in ((\"black\", rgb(SL.COLOURS[\"bezel\"])), (\"silver\", rgb(SL.COLOURS[\"handle\"])),\n+                         (\"red\", (255, 40, 30))):\n+        x, y = SL.PRINT_SWATCHES[name]\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colour)\n+    colours = dict(case=CASE, key=KEY, dark=(12, 12, 12), cart=(236, 232, 222), rib=(196, 186, 160))\n+    for name, (x, y) in L.SWATCHES.items():\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colours[name])\n+    path = os.path.join(out, \"dragon32_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    print_atlas(out)\n+    media_atlas(out)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "6502c0e2c211f973a6a0fcb3fb74d82946e7c417",
          "filename": "tools/console-models/dreamcast.py",
          "status": "added",
          "additions": 357,
          "deletions": 0,
          "changes": 357,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fdreamcast.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fdreamcast.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fdreamcast.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,357 @@\n+\"\"\"The console theme's Dreamcast system model (godot/themes/console/models/systems/dreamcast.glb): the European\n+Dreamcast and its controller with a VMU in its slot.\n+\n+Built in Blender (dreamcast_layout.py has the measurements, taken from photographs and published figures). The body is\n+its side outline (its top rising a little to the back) extruded across its width and cut round its plan's corners,\n+standing on its base set in under it; the ports' panel is sunk into its front, the ports into the panel, the disc's well\n+into its top under the lid and the vents into its right side; POWER and OPEN stand in its top's front corners, the power\n+light by POWER. The lid is a disc lying on the top, hinged at its back, the spindle standing in the well. The controller\n+is its smoothed outline with a rounded profile, the stick, the D-pad, A B X Y and START standing on it, the VMU's screen\n+in its slot, its cable running from between its grips to port A. The printing is one texture (dreamcast_print.py's),\n+projected from above onto the top, the lid, the controller and the GD-ROM, and from in front onto the front.\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: the right end towards the viewer, the\n+top tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m) holding the set; the set\n+holding the lid (on its hinge), the GD-ROM (waiting over the well, shrunk to nothing), POWER and OPEN, the power light\n+(lit and unlit, swapped by depth), the VMU's screens (blank and lit, swapped by depth), and the controller's stick, A\n+and X.\n+\n+The `focused` clip (8 s, looping): OPEN, the lid swings up, the GD-ROM comes down onto the spindle and the lid shuts;\n+POWER, the light comes on and the VMU shows the game's ship; the stick turns while A and X fire; then POWER again, OPEN,\n+the GD-ROM rises out and goes, and the lid shuts.\n+\n+Two materials (case, disc) and one 1024\u00b2 texture, within the system model budget. Run from the repo root:\n+\n+    python tools/console-models/dreamcast_print.py artifacts/dreamcast\n+    blender -b --factory-startup --python tools/console-models/dreamcast.py -- artifacts/dreamcast godot/themes/console/models/systems/dreamcast.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bmesh\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import dreamcast_layout as L  # noqa: E402\n+import plugs  # noqa: E402\n+from amiga import paint, rounded, solid  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, ease_out, key, phase, smooth  # noqa: E402\n+from mega_drive import prism_z  # noqa: E402\n+from mega_drive_cartridge import (bevel, boolean, box, export, join, material, new_object, prism_x,  # noqa: E402\n+                                  triangles)\n+from saturn import disc, inset, smooth_outline  # noqa: E402\n+\n+TILT_YAW = -24.0     # degrees about the vertical: the right end comes towards the viewer\n+TILT_PITCH = 28.0    # degrees about the horizontal: the top tips up towards the viewer\n+ROUGHNESS = dict(case=0.45, disc=0.2)\n+SINK = 1.0                # how far an unshown light or screen sinks\n+\n+\n+def atlas_uv(region, w, h):\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+def swatch(name):\n+    x, y = L.SWATCHES[name]\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+def colour(obj, mat, name):\n+    paint(obj, mat, swatch(name), lambda c, n, kind: True)\n+    return obj\n+\n+\n+TOP = atlas_uv(L.REGIONS[\"top\"], L.WIDTH, L.DEPTH)\n+FRONT = atlas_uv(L.REGIONS[\"front\"], L.WIDTH, L.TOP[0])\n+PAD = atlas_uv(L.REGIONS[\"pad\"], 160.0, 160.0)\n+\n+\n+def top_uv(co):\n+    return TOP(co.x + L.WIDTH / 2, L.DEPTH - co.y)\n+\n+\n+def pad_uv(co):\n+    return PAD(co.x + 80.0, 80.0 - co.y)\n+\n+\n+# ---- The console -----------------------------------------------------------------------------------------------------\n+\n+def console(m):\n+    \"\"\"The body with the panel, the ports, the well and the vents cut in, on its base; printed.\"\"\"\n+    half, b = L.WIDTH / 2, L.BASE\n+    body = prism_x(\"dreamcast\", [(0.0, b[\"height\"]), (L.DEPTH, b[\"height\"]), (L.DEPTH, L.TOP[1]), (0.0, L.TOP[0])],\n+                   -half - 1.0, half + 1.0, m[\"case\"])\n+    boolean(body, prism_z(\"plan\", rounded(0.0, L.DEPTH / 2, L.WIDTH, L.DEPTH, 10.0, 4), 0.0, L.TOP[1] + 5.0,\n+                          m[\"case\"]), \"INTERSECT\")\n+    bevel(body, 3.0, segments=3, angle=30)\n+    (px0, px1), (pz0, pz1) = L.PANEL[\"x\"], L.PANEL[\"z\"]\n+    boolean(body, box(\"panel\", px0, px1, -2.0, L.PANEL[\"depth\"], pz0, pz1, m[\"case\"]))\n+    (pw, ph), pz = L.PORTS[\"size\"], L.PORTS[\"z\"]\n+    for x in L.PORTS[\"x\"]:\n+        boolean(body, box(\"port\", x - pw / 2, x + pw / 2, L.PANEL[\"depth\"] - 1.0, L.PANEL[\"depth\"] + L.PORTS[\"depth\"],\n+                          pz - ph / 2, pz + ph / 2, m[\"case\"]))\n+    (lx, ly) = L.LID[\"at\"]\n+    boolean(body, disc(\"well\", lx, ly, L.WELL[\"r\"], L.TOP[0] - L.WELL[\"depth\"], L.TOP[1] + 5.0, m[\"case\"], 48))\n+    (vy0, vy1), (vz0, vz1), n = L.VENTS[\"y\"], L.VENTS[\"z\"], L.VENTS[\"count\"]\n+    for k in range(n):\n+        y = vy0 + (vy1 - vy0) * (k + 0.5) / n\n+        boolean(body, box(\"vent\", half - 3.0, half + 2.0, y - 2.0, y + 2.0, vz0, vz1, m[\"case\"]))\n+    colour(body, m[\"case\"], \"case\")\n+    paint(body, m[\"case\"], top_uv, lambda c, n_, kind: n_.z > 0.5 and c.z > L.TOP[0] - 2.5)\n+    paint(body, m[\"case\"], lambda co: FRONT(co.x + half, L.TOP[0] - co.z), lambda c, n_, kind: n_.y < -0.9)\n+\n+    def dark(c, n_):\n+        \"\"\"In the well, a port or a vent.\"\"\"\n+        return (math.hypot(c.x - lx, c.y - ly) < L.WELL[\"r\"] + 0.5 and c.z < L.TOP[0] - 2.5\n+                or c.y > L.PANEL[\"depth\"] + 0.5 and pz - ph / 2 - 0.5 < c.z < pz + ph / 2 + 0.5 and c.y < 20.0\n+                or half - 3.5 < c.x and vy0 - 1.0 < c.y < vy1 + 1.0 and vz0 - 0.5 < c.z < vz1 + 0.5 and n_.x < 0.9)\n+    paint(body, m[\"case\"], swatch(\"dark\"), lambda c, n_, kind: dark(c, n_))\n+    base = box(\"base\", -half + b[\"inset\"], half - b[\"inset\"], b[\"inset\"], L.DEPTH - b[\"inset\"], 0.0, b[\"height\"] + 1.0,\n+               m[\"case\"])\n+    bevel(base, 2.0, segments=2)\n+    colour(base, m[\"case\"], \"grey\")\n+    spindle = disc(\"spindle\", lx, ly, 7.0, L.TOP[0] - L.WELL[\"depth\"] - 1.0, L.TOP[0] - L.WELL[\"depth\"] + 8.0,\n+                   m[\"case\"], 20)\n+    colour(spindle, m[\"case\"], \"grey\")\n+    return join([body, base, spindle], \"dreamcast\")\n+\n+\n+def buttons(m):\n+    \"\"\"POWER and OPEN, each its foot's middle at its origin, with where it goes; and the power light, lit and unlit.\"\"\"\n+    out = {}\n+    for name in (\"power\", \"open\"):\n+        x, y = L.BUTTONS[name]\n+        part = disc(name, 0.0, 0.0, L.BUTTONS[\"r\"], -1.5, 2.4, m[\"case\"], 28)\n+        bevel(part, 1.0, segments=2)\n+        colour(part, m[\"case\"], \"button\")\n+        paint(part, m[\"case\"], lambda co, x=x, y=y: top_uv(Vector((co.x + x, co.y + y, 0.0))),\n+              lambda c, n, kind: n.z > 0.9)\n+        out[name] = (part, Vector((x, y, L.top_z(y))))\n+    lx, ly = L.BUTTONS[\"light\"]\n+    for name, tone in ((\"light\", \"lit\"), (\"light_off\", \"off\")):\n+        part = disc(name, 0.0, 0.0, 1.6, -0.4, 0.3, m[\"case\"], 12)\n+        out[name] = (colour(part, m[\"case\"], tone), Vector((lx, ly, L.top_z(ly))))\n+    return out\n+\n+\n+def lid(m):\n+    \"\"\"The lid: a disc lying on the top, printed, its origin on its hinge at its back.\"\"\"\n+    (lx, ly), r = L.LID[\"at\"], L.LID[\"r\"]\n+    ring = [(lx + r * math.cos(2 * math.pi * k / 64), ly + r * math.sin(2 * math.pi * k / 64)) for k in range(64)]\n+    top = [(lx + (r - 3.0) * math.cos(2 * math.pi * k / 64), ly + (r - 3.0) * math.sin(2 * math.pi * k / 64))\n+           for k in range(64)]\n+    part = solid(\"lid\", [[(x, y, L.top_z(y) - L.LID[\"thickness\"]) for x, y in ring],\n+                         [(x, y, L.top_z(y) + 0.6) for x, y in ring], [(x, y, L.top_z(y) + 1.6) for x, y in top]],\n+                 m[\"case\"])\n+    colour(part, m[\"case\"], \"case\")\n+    paint(part, m[\"case\"], top_uv, lambda c, n, kind: n.z > 0.5)\n+    hinge = Vector((lx, ly + r, L.top_z(ly + r) + 1.0))\n+    part.data.transform(Matrix.Translation(-hinge))\n+    return part, hinge\n+\n+\n+def gd_rom(m):\n+    \"\"\"The GD-ROM, its middle at its origin, its label up.\"\"\"\n+    r, t = L.DISC[\"r\"], L.DISC[\"thickness\"]\n+    part = disc(\"gd_rom\", 0.0, 0.0, r, -t / 2, t / 2, m[\"disc\"], 64)\n+    colour(part, m[\"disc\"], \"disc\")\n+    face = atlas_uv(L.REGIONS[\"disc\"], 2 * r, 2 * r)\n+    paint(part, m[\"disc\"], lambda co: face(co.x + r, r - co.y), lambda c, n, kind: n.z > 0.9)\n+    return part\n+\n+\n+# ---- The controller --------------------------------------------------------------------------------------------------\n+\n+def controller(m):\n+    \"\"\"The controller at its place: the body, the D-pad, B, Y, START and the VMU's face; the stick, A, X and the VMU's\n+    screens apart (each with where it goes). Returns them and where its cable leaves it.\"\"\"\n+    P = L.PAD\n+    x, y, turn = P[\"at\"]\n+    place = Matrix.Translation((x, y, 0.0)) @ Matrix.Rotation(math.radians(turn), 4, \"Z\")\n+    spin = Matrix.Rotation(math.radians(turn), 4, \"Z\")\n+    outline = smooth_outline(P[\"points\"])\n+    h = P[\"height\"]\n+    # A rounded profile, its top easing into the flat so the print's edge does not crease.\n+    profile = ((0.0, 5.0), (0.12, 1.8), (0.3, 0.0), (0.55, 0.4), (0.78, 2.0), (0.9, 3.4), (0.96, 4.8), (0.99, 6.0),\n+               (1.0, 7.4))\n+    body = solid(\"controller\", [[(px, py, h * z) for px, py in (inset(outline, d) if d else outline)]\n+                                for z, d in profile], m[\"case\"])\n+    colour(body, m[\"case\"], \"case\")\n+    paint(body, m[\"case\"], pad_uv, lambda c, n, kind: n.z > 0.85)\n+    parts = [body]\n+    (dx, dy), reach, width = P[\"dpad\"]\n+    for w, d in ((reach * 2, width), (width, reach * 2)):\n+        arm = box(\"dpad\", dx - w / 2, dx + w / 2, dy - d / 2, dy + d / 2, h - 2.0, h + 3.0, m[\"case\"])\n+        bevel(arm, 1.0, segments=2)\n+        parts.append(colour(arm, m[\"case\"], \"grey\"))\n+    (cx, cy), reach, br = P[\"abxy\"]\n+    moving = {}\n+    for name, (ox, oy), tone in ((\"a\", (0, -1), \"red\"), (\"b\", (1, 0), \"blue\"), (\"x\", (-1, 0), \"yellow\"),\n+                                 (\"y\", (0, 1), \"green\")):\n+        bx, by = cx + ox * reach, cy + oy * reach\n+        button = disc(name, 0.0, 0.0, br, -1.0, 3.6, m[\"case\"], 20)\n+        colour(button, m[\"case\"], tone)\n+        if name in (\"a\", \"x\"):\n+            moving[name] = (button, place @ Vector((bx, by, h)))\n+        else:\n+            button.data.transform(Matrix.Translation((bx, by, h)))\n+            parts.append(button)\n+    (tx, ty), ts = P[\"start\"]\n+    start = solid(\"start\", [[(tx - ts / 2, ty - ts * 0.4, z), (tx + ts / 2, ty - ts * 0.4, z), (tx, ty + ts * 0.5, z)]\n+                            for z in (h - 1.0, h + 2.0)], m[\"case\"])\n+    parts.append(colour(start, m[\"case\"], \"grey\"))\n+    (sx, sy), sw, sh = P[\"slot\"]\n+    vmu = box(\"vmu\", sx - sw / 2 + 3.0, sx + sw / 2 - 3.0, sy - sh / 2 + 3.0, sy + sh / 2 - 3.0, h - 2.0, h + 1.4,\n+              m[\"case\"])\n+    bevel(vmu, 2.0, segments=2)\n+    colour(vmu, m[\"case\"], \"case\")\n+    paint(vmu, m[\"case\"], pad_uv, lambda c, n, kind: n.z > 0.9)\n+    parts.append(vmu)\n+    whole = join(parts, \"controller\")\n+    whole.data.transform(place)\n+\n+    (scx, scy), scw, sch = P[\"screen\"]\n+    for name in (\"vmu_off\", \"vmu_on\"):\n+        face = atlas_uv(L.REGIONS[name], scw, sch)\n+        screen = box(name, -scw / 2, scw / 2, -sch / 2, sch / 2, -0.3, 0.3, m[\"case\"])\n+        colour(screen, m[\"case\"], \"grey\")\n+        paint(screen, m[\"case\"], lambda co, face=face: face(co.x + scw / 2, sch / 2 - co.y),\n+              lambda c, n, kind: n.z > 0.9)\n+        moving[name] = (screen, place @ Vector((scx, scy, h + 1.5)))\n+    (stx, sty), base_r, cap_r = P[\"stick\"]\n+    stick = solid(\"stick\", [[(r * math.cos(2 * math.pi * k / 24), r * math.sin(2 * math.pi * k / 24), z)\n+                             for k in range(24)] for r, z in ((4.0, -1.0), (4.0, 6.0), (cap_r, 7.0), (cap_r, 10.0),\n+                                                              (cap_r - 2.0, 9.2))], m[\"case\"])\n+    colour(stick, m[\"case\"], \"grey\")\n+    socket = disc(\"socket\", stx, sty, base_r, h - 1.0, h + 1.0, m[\"case\"], 24)\n+    colour(socket, m[\"case\"], \"grey\")\n+    socket.data.transform(place)\n+    moving[\"stick\"] = (stick, place @ Vector((stx, sty, h + 1.0)))\n+    for part, _ in moving.values():\n+        part.data.transform(spin)\n+    exit_ = (place @ Vector((0.0, -34.5, h * 0.42)), spin @ Vector((0.0, -1.0, 0.0)))   # between the grips\n+    return join([whole, socket], \"controller\"), moving, exit_\n+\n+\n+def cable(m, points):\n+    \"\"\"The controller's cable: a tube along the B\u00e9zier curve through four control points.\"\"\"\n+    p0, p1, p2, p3 = (Vector(p) for p in points)\n+    path = [p0 * (1 - t) ** 3 + p1 * 3 * t * (1 - t) ** 2 + p2 * 3 * t * t * (1 - t) + p3 * t ** 3\n+            for t in [k / 24 for k in range(25)]]\n+    bm = bmesh.new()\n+    rings, normal = [], Vector((0.0, 0.0, 1.0))\n+    for k, p in enumerate(path):\n+        tangent = (path[min(k + 1, 24)] - path[max(k - 1, 0)]).normalized()\n+        normal = (normal - tangent * normal.dot(tangent)).normalized()\n+        binormal = tangent.cross(normal)\n+        rings.append([bm.verts.new(p + (normal * math.cos(a) + binormal * math.sin(a)) * 2.4)\n+                      for a in [2 * math.pi * j / 8 for j in range(8)]])\n+    for a, b in zip(rings, rings[1:]):\n+        for j in range(8):\n+            bm.faces.new([a[j], a[(j + 1) % 8], b[(j + 1) % 8], b[j]])\n+    bmesh.ops.recalc_face_normals(bm, faces=bm.faces)\n+    return colour(new_object(\"cable\", bm, m[\"case\"]), m[\"case\"], \"cable\")\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+def tap(f, f0, hold=2):\n+    return smooth(phase(f, f0, f0 + 3)) * (1 - smooth(phase(f, f0 + 3 + hold, f0 + 7 + hold)))\n+\n+\n+def opening(f):\n+    if f < 10:\n+        return 0.0\n+    if f < 42:\n+        return back_out(phase(f, 10, 20), 1.4)\n+    if f < 176:\n+        return 1.0 - ease_in(phase(f, 42, 50))\n+    if f < 204:\n+        return back_out(phase(f, 176, 186), 1.4)\n+    return 1.0 - ease_in(phase(f, 204, 212))\n+\n+\n+def timeline(f):\n+    above = L.DISC[\"above\"]\n+    if f < 20 or f >= 200:\n+        gd = Pose(z=above, scale=HIDDEN)\n+    elif f < 27:\n+        gd = Pose(z=above, scale=max(HIDDEN, back_out(phase(f, 20, 27))))\n+    elif f < 186:\n+        gd = Pose(z=above * (1 - ease_in(phase(f, 27, 38))), spin=-360.0 * 8.0 * phase(f, 58, 166))\n+    else:\n+        gd = Pose(z=above * ease_out(phase(f, 186, 196)), scale=max(HIDDEN, 1 - ease_in(phase(f, 194, 200))))\n+    on = 56 <= f < 168\n+    playing = 60 <= f < 168\n+    t = phase(f, 70, 164)\n+    k = math.sin(math.pi * t)\n+    return {\"lid\": Pose(pitch=L.LID[\"open\"] * opening(f)), \"gd_rom\": gd,\n+            \"open\": Pose(z=-1.8 * max(tap(f, 7), tap(f, 173))), \"power\": Pose(z=-1.8 * max(tap(f, 54), tap(f, 166))),\n+            \"light\": Pose() if on else Pose(z=-SINK), \"light_off\": Pose(z=-SINK) if on else Pose(),\n+            \"vmu_on\": Pose() if playing else Pose(z=-SINK), \"vmu_off\": Pose(z=-SINK) if playing else Pose(),\n+            \"stick\": Pose(pitch=16.0 * k * math.sin(2 * math.pi * 3.0 * t),\n+                          roll=16.0 * k * math.cos(2 * math.pi * 2.0 * t)),\n+            \"a\": Pose(z=-2.0 * max(tap(f, f0) for f0 in (78, 102, 126, 150))),\n+            \"x\": Pose(z=-2.0 * max(tap(f, f0) for f0 in (90, 114, 138, 158)))}\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    atlas = bpy.data.images.load(os.path.join(images, \"dreamcast_print.png\"))\n+    m = {name: material(name, L.COLOURS[\"print\"], r, atlas) for name, r in ROUGHNESS.items()}\n+\n+    body, moving, (exit_at, exit_dir) = controller(m)\n+    grey, lilac = (lambda o: colour(o, m[\"case\"], \"grey\")), (lambda o: colour(o, m[\"case\"], \"cable\"))\n+    relief, start, leaving = plugs.place(*plugs.grommet(lilac, 4.4), m[\"case\"], exit_at, exit_dir)\n+    port = Vector((L.PORTS[\"x\"][0], L.PANEL[\"depth\"], L.PORTS[\"z\"]))\n+    plug, end, back = plugs.place(*plugs.dreamcast(grey, lilac), m[\"case\"], port, (0.0, -1.0, 0.0))\n+    lead = cable(m, [start, start + leaving * 40.0 + Vector((0.0, 0.0, -8.0)), end + back * 50.0, end])\n+    whole = join([console(m), body, lead] + relief + plug, \"dreamcast\")\n+    for p in whole.data.polygons:\n+        p.use_smooth = p.area < 400.0                     # the big flat faces flat\n+    whole.data.set_sharp_from_angle(angle=math.radians(35))\n+\n+    root = bpy.data.objects.new(\"dreamcast_root\", None)\n+    scene.collection.objects.link(root)\n+    whole.parent = root\n+    moving.update(buttons(m))\n+    moving[\"lid\"] = lid(m)\n+    (lx, ly) = L.LID[\"at\"]\n+    moving[\"gd_rom\"] = (gd_rom(m), Vector((lx, ly, L.TOP[0] - L.WELL[\"depth\"] + 8.0 + L.DISC[\"thickness\"] / 2)))\n+    nodes = {}\n+    for name, (part, where) in moving.items():\n+        for p in part.data.polygons:\n+            p.use_smooth = True\n+        part.data.set_sharp_from_angle(angle=math.radians(35))\n+        part.parent = whole\n+        part.location = where\n+        nodes[name] = part\n+\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    points = [tilt @ v.co for v in whole.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [whole] + list(nodes.values())\n+    print(f\"Dreamcast: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(whole.data)} the set), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "8f6bb0fd4b58111f6a44d03efaaf3dd827d4fbef",
          "filename": "tools/console-models/dreamcast_layout.py",
          "status": "added",
          "additions": 63,
          "deletions": 0,
          "changes": 63,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fdreamcast_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fdreamcast_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fdreamcast_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,63 @@\n+\"\"\"The Dreamcast card's measurements, shared by dreamcast_print.py (which draws its printing) and dreamcast.py (which\n+builds it). Plain Python: Blender's Python imports it too.\n+\n+The Sega Dreamcast (1998), the European one (its swirl blue), 190 mm wide, 196 deep and 76 high (the Science Museum\n+Group's 75 x 190 x 195; others give 190 x 195.8 x 75.5 to 78), its controller (147 wide, 152 long: dimensions.com's\n+figures) in front of it at its left with a VMU in its slot, a GD-ROM with a made-up game, Odyssey, going under its lid.\n+Millimetres: x from its middle (left to right), y back from its front, z up from the table. Measured from Evan-Amos's\n+photographs on Wikimedia Commons (from the front left and right, the back right, with its controller; the controller\n+with and without its VMU).\n+\n+The console: its body (the front face over the base set in, the top rising a little to the back), the ports' panel sunk\n+into its front with A B C D (PANEL, PORTS), the vents in its right side (VENTS); on its top POWER and OPEN in the front\n+corners (BUTTONS), the power light by POWER, and the round lid over the disc's well, hinged at its back (LID, WELL), the\n+arrow at its front. The controller (PAD): its outline (the grips reaching towards the player, the cable between them),\n+the VMU in its slot (its screen, SCREEN), the analogue stick, the D-pad, A B X Y, START.\n+\n+One texture (ATLAS square, REGIONS): `top` (the console from above: the swirl and Dreamcast on the lid, its arrow, the\n+creases, POWER and OPEN), `front` (the front: SEGA, A B C D, the ports), `pad` (the controller from above: the swirl,\n+the VMU's face round its screen, the letters), the VMU's screens (`vmu_off`, `vmu_on`), `disc` (the GD-ROM's label), and\n+swatches. A region is (x, y, width, height) in pixels.\n+\"\"\"\n+\n+ATLAS = 1024\n+\n+WIDTH, DEPTH = 190.0, 196.0\n+TOP = (68.0, 76.0)                # the top's height at the front and at the back\n+BASE = dict(inset=5.0, height=9.0)                         # the base, set in under the body\n+PANEL = dict(x=(-80.0, 50.0), z=(14.0, 46.0), depth=3.0)\n+PORTS = dict(x=[-66.0, -36.0, -6.0, 24.0], z=24.0, size=(16.0, 13.0), depth=10.0)\n+VENTS = dict(y=(10.0, 60.0), z=(16.0, 50.0), count=7)\n+BUTTONS = dict(power=(-72.0, 24.0), open=(72.0, 24.0), r=10.0, light=(-54.0, 12.0))\n+LID = dict(at=(0.0, 112.0), r=76.0, thickness=4.0, open=-62.0)\n+WELL = dict(r=66.0, depth=24.0)\n+ARROW = [(-14.0, 50.0), (14.0, 50.0), (0.0, 30.0)]          # the lid's arrow, at its front (plan)\n+\n+DISC = dict(r=60.0, hole=7.5, thickness=1.6, above=60.0)\n+\n+# The controller: its outline's points (x across, y away from the player) round from the top's left, smoothed; its\n+# height; the VMU's slot and its screen (middle, w, h); the stick (middle, base's and cap's radii), the D-pad (middle,\n+# arm's length and width), A B X Y (the diamond's middle, its reach, the buttons' radius), START (middle, size); where\n+# it lies (x, y) and its turn.\n+PAD = dict(points=[(-60.0, 60.0), (-32.0, 72.0), (0.0, 76.0), (32.0, 72.0), (60.0, 60.0), (72.0, 34.0), (74.0, 0.0),\n+                   (70.0, -32.0), (66.0, -62.0), (56.0, -76.0), (42.0, -74.0), (33.0, -56.0), (16.0, -40.0),\n+                   (0.0, -36.0), (-16.0, -40.0), (-33.0, -56.0), (-42.0, -74.0), (-56.0, -76.0), (-66.0, -62.0),\n+                   (-70.0, -32.0), (-74.0, 0.0), (-72.0, 34.0)],\n+           height=26.0, slot=((0.0, 24.0), 46.0, 60.0), screen=((0.0, 30.0), 34.0, 25.0),\n+           stick=((-46.0, 36.0), 13.0, 9.5), dpad=((-38.0, -12.0), 13.0, 8.0), abxy=((46.0, 6.0), 13.0, 5.5),\n+           start=((0.0, -20.0), 9.0), at=(-150.0, -104.0, 10.0))\n+\n+COLOURS = dict(case=\"#EEEDEA\", print=\"#FFFFFF\")\n+\n+REGIONS = dict(top=(0, 0, 520, 536), front=(0, 544, 520, 208), pad=(528, 0, 296, 304), vmu_off=(832, 0, 136, 100),\n+               vmu_on=(832, 108, 136, 100), disc=(528, 312, 300, 300))\n+SWATCHES = dict(case=(560, 640), dark=(592, 640), button=(624, 640), lit=(656, 640), off=(688, 640), disc=(720, 640),\n+                cable=(752, 640), red=(560, 672), blue=(592, 672), yellow=(624, 672), green=(656, 672), grey=(688, 672))\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s\n+\n+\n+def top_z(y):\n+    \"\"\"The top's height at y.\"\"\"\n+    return TOP[0] + (TOP[1] - TOP[0]) * y / DEPTH"
        },
        {
          "sha": "b878665a0dadd6b8c1389d2bdf7549a8781a822c",
          "filename": "tools/console-models/dreamcast_print.py",
          "status": "added",
          "additions": 179,
          "deletions": 0,
          "changes": 179,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fdreamcast_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fdreamcast_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fdreamcast_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,179 @@\n+\"\"\"The Dreamcast card's printing (dreamcast_print.png), for the console theme's model (dreamcast.py).\n+\n+One image, laid out as dreamcast_layout.REGIONS says: the console from above (the lid's blue swirl and Dreamcast, its\n+arrow, its rim, the two creases to the back corners, POWER and OPEN), its front (SEGA over the ports' panel, A B C D),\n+the controller from above (the swirl and Dreamcast, the VMU's face round its screen, the D-pad's and buttons' marks,\n+START), the VMU's screens (off, and the made-up game's ship on it), the GD-ROM's label (a made-up game, Odyssey), and\n+swatches. Run it with the system Python (Pillow):\n+\n+    python tools/console-models/dreamcast_print.py <out folder>\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+from PIL import Image, ImageDraw\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import dreamcast_layout as L  # noqa: E402\n+from psp_print import ship, space  # noqa: E402\n+from retro_pc_print import rgb  # noqa: E402\n+from windows_pc_print import Region  # noqa: E402\n+\n+CASE = rgb(L.COLOURS[\"case\"])\n+BLUE = (40, 90, 200)\n+GREY = (150, 150, 152)\n+INK = (110, 110, 114)\n+LCD = (176, 206, 168)\n+PIXEL = (40, 56, 44)\n+\n+\n+def shade(colour, k):\n+    return tuple(max(0, min(255, round(c * (1 + k)))) for c in colour)\n+\n+\n+def swirl(reg, x, y, r, ink, width):\n+    \"\"\"The Dreamcast's swirl: a spiral of two and a half turns, its middle at (x, y).\"\"\"\n+    points = []\n+    for k in range(121):\n+        a = 2.5 * 2 * math.pi * k / 120\n+        rr = r * (0.08 + 0.92 * k / 120)\n+        points.append(reg.p(x + rr * math.cos(a), y + rr * math.sin(a)))\n+    reg.draw.line(points, fill=ink, width=reg.width(width), joint=\"curve\")\n+\n+\n+def badge(reg, x, y, size, ink):\n+    \"\"\"The swirl over Dreamcast, its middle at (x, y).\"\"\"\n+    swirl(reg, x, y - size * 0.45, size * 0.42, ink, size * 0.1)\n+    reg.text(\"Dreamcast\", \"timesi.ttf\", size * 0.5, x, y + size * 0.42, ink)\n+\n+\n+def top():\n+    \"\"\"The console from above: x across (-WIDTH/2 at the left), y down from the back to the front.\"\"\"\n+    reg = Region(L.REGIONS[\"top\"], L.WIDTH, L.DEPTH, CASE)\n+\n+    def at(x, y):\n+        return x + L.WIDTH / 2, L.DEPTH - y\n+\n+    (lx, ly), r = L.LID[\"at\"], L.LID[\"r\"]\n+    for side in (-1, 1):                                    # the creases from the lid to the back corners\n+        reg.line([at(side * r * 0.98, ly + 6.0), at(side * (L.WIDTH / 2 - 2.0), L.DEPTH - 4.0)], shade(CASE, -0.12),\n+                 0.6)\n+    reg.circle(*at(lx, ly), r + 1.2, shade(CASE, -0.18))\n+    reg.circle(*at(lx, ly), r, shade(CASE, 0.02))\n+    badge(reg, *at(0.0, ly + 30.0), 22.0, BLUE)\n+    reg.poly([at(x, y) for x, y in L.ARROW], shade(CASE, -0.1))\n+    for name, (x, y) in ((\"POWER\", L.BUTTONS[\"power\"]), (\"OPEN\", L.BUTTONS[\"open\"])):\n+        reg.circle(*at(x, y), L.BUTTONS[\"r\"] + 1.0, shade(CASE, -0.1))\n+        reg.circle(*at(x, y), L.BUTTONS[\"r\"], shade(CASE, 0.03))\n+        reg.text(name, \"arial.ttf\", 3.0, *at(x, y), INK)\n+    return reg\n+\n+\n+def front():\n+    \"\"\"The front from in front: x across, z down from the front's top.\"\"\"\n+    h = L.TOP[0]\n+    reg = Region(L.REGIONS[\"front\"], L.WIDTH, h, CASE)\n+\n+    def at(x, z):\n+        return x + L.WIDTH / 2, h - z\n+\n+    (px0, px1), (pz0, pz1) = L.PANEL[\"x\"], L.PANEL[\"z\"]\n+    reg.rect_(*at(px0, pz1), *at(px1, pz0), shade(CASE, -0.05), r=2.0)\n+    reg.text(\"SEGA\", \"ariblk.ttf\", 6.0, *at(-21.0, pz1 - 3.6), shade(CASE, -0.3))\n+    (pw, ph), pz = L.PORTS[\"size\"], L.PORTS[\"z\"]\n+    for c, x in zip(\"ABCD\", L.PORTS[\"x\"]):\n+        reg.rect_(*at(x - pw / 2 - 2.0, pz + ph / 2 + 2.0), *at(x + pw / 2 + 2.0, pz - ph / 2 - 2.0),\n+                  shade(CASE, -0.12), r=5.0)\n+        reg.rect_(*at(x - pw / 2, pz + ph / 2), *at(x + pw / 2, pz - ph / 2), (24, 24, 26), r=4.0)\n+        reg.text(c, \"arial.ttf\", 3.2, *at(x, pz + ph / 2 + 3.6), INK)\n+    reg.text(\"Compatible with\", \"arial.ttf\", 2.0, *at(68.0, 24.0), INK)\n+    reg.text(\"Windows CE\", \"arialbd.ttf\", 2.6, *at(68.0, 20.0), INK)\n+    return reg\n+\n+\n+def pad():\n+    \"\"\"The controller from above: x across (-80 at the left), y down from its top (80) to its grips' ends.\"\"\"\n+    w, h = 160.0, 160.0\n+    reg = Region(L.REGIONS[\"pad\"], w, h, CASE)\n+\n+    def at(x, y):\n+        return x + w / 2, 80.0 - y\n+\n+    badge(reg, *at(0.0, 66.0), 13.0, BLUE)\n+    (sx, sy), sw, sh = L.PAD[\"slot\"]\n+    reg.rect_(*at(sx - sw / 2, sy + sh / 2), *at(sx + sw / 2, sy - sh / 2), (216, 216, 214), r=8.0)\n+    reg.rect_(*at(sx - sw / 2 + 3.0, sy + sh / 2 - 3.0), *at(sx + sw / 2 - 3.0, sy - sh / 2 + 3.0), (226, 226, 222),\n+              r=6.0)\n+    (vx, vy), vw, vh = L.PAD[\"screen\"]\n+    reg.rect_(*at(vx - vw / 2 - 2.0, vy + vh / 2 + 2.0), *at(vx + vw / 2 + 2.0, vy - vh / 2 - 2.0), (90, 92, 96),\n+              r=2.0)\n+    (cx, cy), reach, _ = L.PAD[\"abxy\"]\n+    for c, (dx, dy), colour in ((\"A\", (0, -1), (220, 60, 50)), (\"B\", (1, 0), (40, 110, 220)),\n+                                (\"X\", (-1, 0), (240, 200, 40)), (\"Y\", (0, 1), (60, 180, 90))):\n+        reg.text(c, \"arialbd.ttf\", 3.6, *at(cx + dx * reach, cy + dy * reach), shade(colour, -0.3))\n+    (tx, ty), ts = L.PAD[\"start\"]\n+    reg.poly([at(tx - ts / 2, ty - ts * 0.4), at(tx + ts / 2, ty - ts * 0.4), at(tx, ty + ts * 0.5)],\n+             shade(CASE, -0.1))\n+    reg.text(\"START\", \"arial.ttf\", 2.6, *at(tx, ty - ts * 0.4 - 3.0), INK)\n+    return reg\n+\n+\n+def vmu(lit):\n+    \"\"\"The VMU's screen: its 48 x 32 LCD, blank, or the made-up game's ship and its name.\"\"\"\n+    reg = Region(L.REGIONS[\"vmu_on\" if lit else \"vmu_off\"], 48.0, 32.0, LCD)\n+    if lit:\n+        img = Image.new(\"1\", (48, 32), 1)\n+        d = ImageDraw.Draw(img)\n+        ship(d, 13, 17, 0.5, 0, 0)\n+        d.ellipse([30, 10, 41, 21], outline=0)\n+        d.line([(27, 18), (45, 13)], fill=0)\n+        for x, y in ((6, 4), (22, 6), (40, 27), (12, 27), (44, 4)):\n+            d.point((x, y), fill=0)\n+        layer = Image.new(\"RGB\", (48, 32), LCD)\n+        layer.paste(Image.new(\"RGB\", (48, 32), PIXEL), (0, 0), Image.eval(img, lambda v: 255 - v * 255).convert(\"L\"))\n+        x0, y0, x1, y1 = reg.box(0.0, 0.0, 48.0, 32.0)\n+        reg.img.paste(layer.resize((round(x1 - x0), round(y1 - y0)), Image.NEAREST), (round(x0), round(y0)))\n+    return reg\n+\n+\n+def disc():\n+    \"\"\"The GD-ROM's label side: the silver rim, the label (a made-up game, Odyssey), GD-ROM and the swirl.\"\"\"\n+    r = L.DISC[\"r\"]\n+    reg = Region(L.REGIONS[\"disc\"], 2 * r, 2 * r, CASE)\n+    reg.circle(r, r, r, (196, 198, 204))\n+    art = space((480, 480)).convert(\"RGB\")\n+    ship(ImageDraw.Draw(art), 140, 300, 3.0, \"#FFFFFF\", \"#FFB23C\")\n+    mask = Image.new(\"L\", art.size, 0)\n+    ImageDraw.Draw(mask).ellipse([10, 10, 470, 470], fill=255)\n+    ImageDraw.Draw(mask).ellipse([168, 168, 312, 312], fill=0)\n+    x0, y0, x1, y1 = reg.box(4.0, 4.0, 2 * r - 4.0, 2 * r - 4.0)\n+    size = (round(x1 - x0), round(y1 - y0))\n+    reg.img.paste(art.resize(size, Image.LANCZOS), (round(x0), round(y0)), mask.resize(size, Image.LANCZOS))\n+    reg.text(\"ODYSSEY\", \"ariblk.ttf\", 12.0, r, r - 30.0, (255, 214, 70))\n+    swirl(reg, r - 22.0, r + 36.0, 5.0, (236, 236, 240), 1.0)\n+    reg.text(\"GD-ROM\", \"ariblk.ttf\", 6.0, r + 8.0, r + 36.0, (236, 236, 240))\n+    reg.circle(r, r, 18.0, (200, 204, 210))\n+    reg.circle(r, r, L.DISC[\"hole\"], (60, 60, 64))\n+    return reg\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), CASE)\n+    for reg in (top(), front(), pad(), vmu(False), vmu(True), disc()):\n+        reg.paste(image)\n+    d = ImageDraw.Draw(image)\n+    colours = dict(case=CASE, dark=(30, 30, 32), button=shade(CASE, -0.04), lit=(255, 150, 30), off=(150, 120, 90),\n+                   disc=(196, 198, 204), cable=(200, 196, 220), red=(220, 60, 50), blue=(40, 110, 220),\n+                   yellow=(240, 200, 40), green=(60, 180, 90), grey=(206, 206, 204))\n+    for name, (x, y) in L.SWATCHES.items():\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colours[name])\n+    path = os.path.join(out, \"dreamcast_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "e2aa81c61b54d8e0ce6fa8a8841842e7d50cda87",
          "filename": "tools/console-models/electron.py",
          "status": "added",
          "additions": 274,
          "deletions": 0,
          "changes": 274,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Felectron.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Felectron.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Felectron.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,274 @@\n+\"\"\"The console theme's Acorn Electron system model (godot/themes/console/models/systems/electron.glb): an Acorn\n+Electron with a Plus 1 on its back, in front of the portable television the ZX Spectrum's card has.\n+\n+Built in Blender (electron_layout.py has the measurements, taken from photographs). The computer is its side outline (a\n+wedge, its top sloping up to the back) extruded across its width, the keys' opening cut into its top; its 56 keys stand\n+in the opening in the slope's frame, each tapering to its printed top. The Plus 1 is a box on its back, its two slots\n+cut into its top, the second under its cover. Behind it stands zx_spectrum.py's television. The printing is one texture\n+(electron_print.py's), planar-mapped from above onto every face facing up.\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: the right end towards the viewer, the\n+top tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m) holding the set; the set\n+holding the cartridge (waiting over the first slot, shrunk to nothing), the first slot's cover (on its hinge), the\n+television's four screens (those not shown behind the glass) and the keys the clip presses.\n+\n+The `focused` clip (8 s, looping): the cartridge appears over the Plus 1, turns to show its label and drops into the\n+first slot, the cover swinging down out of its way; CTRL and BREAK start it, its title comes up; SPACE, and the game's\n+played on Z, X and RETURN; BREAK brings the start-up screen back and the cartridge rises out and goes.\n+\n+Four materials (case, tv, dark, glass) and one 1024\u00b2 texture, within the system model budget. Run from the repo root:\n+\n+    python tools/console-models/electron_print.py artifacts/electron\n+    blender -b --factory-startup --python tools/console-models/electron.py -- artifacts/electron godot/themes/console/models/systems/electron.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import electron_layout as L  # noqa: E402\n+import zx_spectrum_layout as SL  # noqa: E402\n+from amiga import curved, paint, solid  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, ease_out, key, phase, smooth  # noqa: E402\n+from mega_drive_cartridge import (bevel, boolean, box, export, join, material, prism_x,  # noqa: E402\n+                                  triangles)\n+from zx_spectrum import television  # noqa: E402\n+\n+TILT_YAW = -24.0     # degrees about the vertical: the right end comes towards the viewer\n+TILT_PITCH = 20.0    # degrees about the horizontal: the top tips up towards the viewer\n+ROUGHNESS = dict(case=0.5, tv=0.45, dark=0.7, glass=0.35)\n+MOVING = {\"CTRL\": \"ctrl\", \"BREAK\": \"break\", \"RETURN\": \"return\", \"Z\": \"z\", \"X\": \"x\", \" \": \"space\"}\n+TOTAL_DEPTH = L.PLUS1[\"y\"][1]\n+\n+\n+def atlas_uv(region, w, h):\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+def swatch(name):\n+    x, y = L.SWATCHES[name]\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+def colour(obj, mat, name):\n+    paint(obj, mat, swatch(name), lambda c, n, kind: True)\n+    return obj\n+\n+\n+TOP = atlas_uv(L.REGIONS[\"top\"], L.WIDTH, TOTAL_DEPTH)\n+\n+\n+def top_uv(co):\n+    return TOP(co.x + L.WIDTH / 2, TOTAL_DEPTH - co.y)\n+\n+\n+def on_slope(name, outline, h0, h1, mat):\n+    \"\"\"A solid from an (x, s) outline in the slope's frame, from h0 to h1 off it.\"\"\"\n+    return solid(name, [[(x, *L.top_point(s, h)) for x, s in outline] for h in (h0, h1)], mat)\n+\n+\n+# ---- The computer and the Plus 1 -------------------------------------------------------------------------------------\n+\n+def computer(m):\n+    \"\"\"The wedge, the keys' opening cut into its top, the Plus 1 on its back with its slots; printed from above.\"\"\"\n+    half = L.WIDTH / 2\n+    body = prism_x(\"electron\", L.profile(), -half, half, m[\"case\"])\n+    bevel(body, 2.4, segments=2, angle=40)\n+    (ox0, ox1), (s0, s1), depth = L.OPENING[\"x\"], L.OPENING[\"s\"], L.OPENING[\"depth\"]\n+    boolean(body, on_slope(\"opening\", [(ox0, s0), (ox1, s0), (ox1, s1), (ox0, s1)], -depth, 20.0, m[\"case\"]))\n+    (py0, py1), ph = L.PLUS1[\"y\"], L.PLUS1[\"height\"]\n+    plus1 = box(\"plus1\", -half, half, py0 + 0.5, py1, 0.0, ph, m[\"case\"])\n+    bevel(plus1, 2.4, segments=2, angle=40)\n+    (cx0, cx1), sd = L.PLUS1[\"x\"], L.PLUS1[\"depth\"]\n+    for sy0, sy1 in L.PLUS1[\"slots\"]:\n+        boolean(plus1, box(\"slot\", cx0, cx1, sy0, sy1, ph - sd, ph + 5.0, m[\"case\"]))\n+    parts = [body, plus1]\n+    for part in parts:\n+        colour(part, m[\"case\"], \"case\")\n+        paint(part, m[\"case\"], top_uv, lambda c, n, kind: n.z > 0.5)\n+        paint(part, m[\"case\"], swatch(\"dark\"),\n+              lambda c, n, kind: cx0 - 0.5 < c.x < cx1 + 0.5 and c.z < ph - 0.5 and c.y > py0\n+              and any(a - 0.5 < c.y < b + 0.5 for a, b in L.PLUS1[\"slots\"]))\n+    sy0, sy1 = L.PLUS1[\"slots\"][1]                              # the second slot's cover, shut\n+    cover = box(\"cover\", cx0 + 0.4, cx1 - 0.4, sy0 + 0.4, sy1 - 0.4, ph - 2.0, ph - 0.4, m[\"case\"])\n+    colour(cover, m[\"case\"], \"case\")\n+    paint(cover, m[\"case\"], top_uv, lambda c, n, kind: n.z > 0.9)\n+    return join(parts + [cover], \"electron\")\n+\n+\n+def keys(m):\n+    \"\"\"The keys, each tapering from the opening's floor to its printed top in the slope's frame: those the clip presses\n+    apart (each its foot's middle at its origin, with where it goes), the rest joined.\"\"\"\n+    fixed, moving = [], {}\n+    inset, h1 = L.KEY[\"inset\"], L.KEY[\"height\"] - L.OPENING[\"depth\"]\n+    for legend, x0, x1, s in L.keys():\n+        foot = [(x0 + 0.6, s - 9.0), (x1 - 0.6, s - 9.0), (x1 - 0.6, s + 9.0), (x0 + 0.6, s + 9.0)]\n+        cap = [(x0 + inset, s - 9.5 + inset), (x1 - inset, s - 9.5 + inset), (x1 - inset, s + 9.5 - inset),\n+               (x0 + inset, s + 9.5 - inset)]\n+        h0 = -L.OPENING[\"depth\"]\n+        part = solid(\"key\", [[(x, *L.top_point(ss, h0)) for x, ss in foot],\n+                             [(x, *L.top_point(ss, h1)) for x, ss in cap]], m[\"case\"])\n+        colour(part, m[\"case\"], \"key\")\n+        paint(part, m[\"case\"], top_uv, lambda c, n, kind: n.z > 0.9)\n+        name = MOVING.get(legend)\n+        if name and name not in moving:\n+            y, z = L.top_point(s, h0)\n+            middle = Vector(((x0 + x1) / 2, y, z))\n+            part.data.transform(Matrix.Translation(-middle))\n+            moving[name] = (part, middle)\n+        else:\n+            fixed.append(part)\n+    return join(fixed, \"keys\"), moving\n+\n+\n+def cover(m):\n+    \"\"\"The first slot's cover, its origin on its hinge along the slot's back edge, shut.\"\"\"\n+    (cx0, cx1), ph = L.PLUS1[\"x\"], L.PLUS1[\"height\"]\n+    sy0, sy1 = L.PLUS1[\"slots\"][0]\n+    part = box(\"cover_1\", cx0 + 0.4, cx1 - 0.4, sy0 + 0.4, sy1 - 0.4, ph - 2.0, ph - 0.4, m[\"case\"])\n+    colour(part, m[\"case\"], \"case\")\n+    paint(part, m[\"case\"], top_uv, lambda c, n, kind: n.z > 0.9)\n+    hinge = Vector((0.0, sy1 - 0.4, ph - 1.2))\n+    part.data.transform(Matrix.Translation(-hinge))\n+    return part, hinge\n+\n+\n+def cartridge(m):\n+    \"\"\"The cartridge, its foot's middle at its origin, its label facing -y.\"\"\"\n+    w, t, h = L.CART[\"size\"]\n+    part = box(\"cartridge\", -w / 2, w / 2, -t / 2, t / 2, 0.0, h, m[\"case\"])\n+    bevel(part, 1.6, segments=2)\n+    colour(part, m[\"case\"], \"cart\")\n+    lw, lh = L.CART[\"label\"]\n+    face = atlas_uv(L.REGIONS[\"label\"], lw, lh)\n+    z1 = h - 6.0\n+    paint(part, m[\"case\"], lambda co: face(co.x + lw / 2, z1 - co.z),\n+          lambda c, n, kind: n.y < -0.9 and abs(c.x) < lw / 2 and z1 - lh < c.z < z1)\n+    return part\n+\n+\n+def screens(m):\n+    \"\"\"The television's four screens (boot, title, game_1, game_2), each the glass's whole area.\"\"\"\n+    gw, gh = SL.TV[\"glass\"][\"size\"]\n+    out = {}\n+    for name in (\"boot\", \"title\", \"game_1\", \"game_2\"):\n+        out[name] = curved(name, gw, gh, SL.TV[\"glass\"][\"bulge\"], atlas_uv(L.REGIONS[name], 1.0, 1.0), m[\"glass\"])\n+    return out\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+BEHIND = 3.0              # how far back a screen that isn't shown waits: behind the glass\n+\n+\n+def tap(f, f0, hold=2):\n+    return smooth(phase(f, f0, f0 + 3)) * (1 - smooth(phase(f, f0 + 3 + hold, f0 + 7 + hold)))\n+\n+\n+def shown(f):\n+    if f < 52 or f >= 188:\n+        return \"boot\"\n+    if f < 96:\n+        return \"title\"\n+    return \"game_1\" if ((f - 96) // 8) % 2 == 0 else \"game_2\"\n+\n+\n+def timeline(f):\n+    above = L.CART[\"above\"]\n+    if f < 8 or f >= 214:\n+        cart = Pose(z=above, scale=HIDDEN)\n+    elif f < 16:\n+        cart = Pose(z=above, scale=max(HIDDEN, back_out(phase(f, 8, 15))))\n+    elif f < 30:\n+        cart = Pose(z=above, spin=360.0 * smooth(phase(f, 16, 30)))\n+    elif f < 42:\n+        cart = Pose(z=above * (1 - ease_in(phase(f, 30, 42))))\n+    elif f < 194:\n+        cart = Pose(z=-1.6 * math.sin(math.pi * phase(f, 42, 48)))\n+    elif f < 206:\n+        cart = Pose(z=above * ease_out(phase(f, 194, 206)))\n+    else:\n+        cart = Pose(z=above, scale=max(HIDDEN, 1 - ease_in(phase(f, 206, 214))))\n+    swing = 92.0 * smooth(phase(f, 36, 41)) * (1 - smooth(phase(f, 202, 208)))\n+    poses = {\"cartridge\": cart, \"cover\": Pose(pitch=-swing)}\n+    for name in (\"boot\", \"title\", \"game_1\", \"game_2\"):\n+        poses[name] = Pose() if name == shown(f) else Pose(y=BEHIND)\n+    press = -2.4\n+    poses[\"ctrl\"] = Pose(z=press * smooth(phase(f, 44, 47)) * (1 - smooth(phase(f, 52, 55))))\n+    poses[\"break\"] = Pose(z=press * max(tap(f, 47), tap(f, 184)))\n+    poses[\"space\"] = Pose(z=press * tap(f, 92))\n+    poses[\"z\"] = Pose(z=press * max(tap(f, f0) for f0 in (104, 128, 152)))\n+    poses[\"x\"] = Pose(z=press * max(tap(f, f0) for f0 in (116, 140, 164)))\n+    poses[\"return\"] = Pose(z=press * max(tap(f, f0) for f0 in (110, 134, 160)))\n+    return poses\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    atlas = bpy.data.images.load(os.path.join(images, \"electron_print.png\"))\n+    m = {\"case\": material(\"case\", L.COLOURS[\"print\"], ROUGHNESS[\"case\"], atlas),\n+         \"tv\": material(\"tv\", SL.COLOURS[\"tv\"], ROUGHNESS[\"tv\"]),\n+         \"dark\": material(\"dark\", L.COLOURS[\"dark\"], ROUGHNESS[\"dark\"]),\n+         \"glass\": material(\"glass\", L.COLOURS[\"print\"], ROUGHNESS[\"glass\"], atlas)}\n+    m[\"print\"] = m[\"case\"]                                    # television() prints its front with the set's atlas\n+\n+    fixed_keys, moving_keys = keys(m)\n+    tv = television(m, m[\"glass\"], atlas_uv(L.REGIONS[\"boot\"], 1.0, 1.0))\n+    shift = Matrix.Translation((L.TV_AT[0] - SL.TV[\"at\"][0], L.TV_AT[1] - SL.TV[\"at\"][1], 0.0))\n+    for part in tv:\n+        part.data.transform(shift)\n+    whole = join([computer(m), fixed_keys] + tv, \"electron\")\n+    for p in whole.data.polygons:\n+        p.use_smooth = True\n+    whole.data.set_sharp_from_angle(angle=math.radians(35))\n+\n+    root = bpy.data.objects.new(\"electron_root\", None)\n+    scene.collection.objects.link(root)\n+    whole.parent = root\n+    s = SL.TV[\"screen\"]\n+    glass_front = Vector((L.TV_AT[0] + s[\"x\"], L.TV_AT[1] + s[\"depth\"] - 0.4, s[\"z\"]))\n+    flap, hinge = cover(m)\n+    (cx0, cx1) = L.PLUS1[\"x\"]\n+    sy0, sy1 = L.PLUS1[\"slots\"][0]\n+    seat = Vector(((cx0 + cx1) / 2, (sy0 + sy1) / 2, L.PLUS1[\"height\"] - L.CART[\"seat\"]))\n+    parts = {\"cartridge\": (cartridge(m), seat), \"cover\": (flap, hinge)}\n+    parts.update({name: (part, glass_front) for name, part in screens(m).items()})\n+    parts.update(moving_keys)\n+    nodes = {}\n+    for name, (part, where) in parts.items():\n+        for p in part.data.polygons:\n+            p.use_smooth = True\n+        part.data.set_sharp_from_angle(angle=math.radians(35))\n+        part.parent = whole\n+        part.location = where\n+        nodes[name] = part\n+\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    points = [tilt @ v.co for v in whole.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [whole] + list(nodes.values())\n+    print(f\"Electron: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(whole.data)} the set), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "16fe82e7346c52fcb35a1bf136b64f49e3fbdd4b",
          "filename": "tools/console-models/electron_layout.py",
          "status": "added",
          "additions": 86,
          "deletions": 0,
          "changes": 86,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Felectron_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Felectron_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Felectron_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,86 @@\n+\"\"\"The Acorn Electron card's measurements, shared by electron_print.py (which draws its printing and screens) and\n+electron.py (which builds it). Plain Python: Blender's Python imports it too.\n+\n+The Acorn Electron (1983), 340 mm wide, 160 deep and 65 high (a retro computing database's figures), a Plus 1 on its\n+back (340 x 90 x 50, as the period's pages give it) with a ROM cartridge going into its slot, in front of the portable\n+television the ZX Spectrum's card has (zx_spectrum.py's, its front's middle at TV_AT). Millimetres: x from the\n+computer's middle (left to right), y back from its front, z up from the table. The rest was measured from photographs of\n+Electrons on Wikimedia Commons (from above with a Plus 1 on, from the front corners), scaled by its width and by its\n+keys' 19.05 mm pitch.\n+\n+The computer: a wedge, its top sloping up from the front to the back; the keys (the UK layout) standing in the opening\n+in its top, the dark band behind them (its grid of lines, acorn electron and the green acorn). The Plus 1: a box on its\n+back, the two cartridge slots in its top's right half, each under a hinged cover.\n+\n+The keys are laid out in units of U from the back row's left edge (KEYS_X0), in rows from the back (the digits') to the\n+front (the space bar's). The top is a plane at SLOPE: a key stands on it in the slope's frame (x across, s up the slope\n+from its front edge, h off it), and top_point() maps a point of that frame to the model's (y, z).\n+\n+One texture (ATLAS square, REGIONS): `top` (the computer and the Plus 1 seen from above: the key tops and legends, the\n+band; planar-mapped onto every face facing up), `label` (the cartridge's), the screens (`boot`, `title`, `game_1`,\n+`game_2`), and swatches; the television's front drawn where zx_spectrum_layout.py's PRINT_REGIONS[\"tv\"] puts it and its\n+swatches where its PRINT_SWATCHES do, as television() maps them. A region is (x, y, width, height) in pixels.\n+\"\"\"\n+\n+import math\n+\n+ATLAS = 1024\n+U = 19.05\n+\n+WIDTH, DEPTH = 340.0, 160.0\n+FRONT = (8.0, 26.0)              # the top's front edge (y, z)\n+BACK = (154.0, 62.0)             # where the top meets the back's rounding\n+SLOPE = math.atan2(BACK[1] - FRONT[1], BACK[0] - FRONT[0])\n+OPENING = dict(x=(-160.0, 152.0), s=(6.0, 116.0), depth=5.0)     # the keys' opening in the top\n+BAND = dict(s=(118.0, 148.0))    # the dark band behind the keys\n+KEYS_X0, KEY_ROW1 = -146.0, 104.0   # the back row's left edge; its middle (s)\n+KEY = dict(height=12.0, inset=2.6)  # a key's height off the opening's floor; how far its top is drawn in\n+ROWS = [\n+    (0.0, [(\"ESCAPE\", 1.0)] + [(c, 1.0) for c in \"1234567890=^\\\\\"] + [(\"BREAK\", 1.0)]),\n+    (0.5, [(\"CAPS LK\", 1.25)] + [(c, 1.0) for c in \"QWERTYUIOP\u00a3_\"] + [(\"COPY\", 1.0)]),\n+    (0.75, [(\"CTRL\", 1.25)] + [(c, 1.0) for c in \"ASDFGHJKL;:\"] + [(\"RETURN\", 1.75)]),\n+    (0.9, [(\"SHIFT\", 1.6)] + [(c, 1.0) for c in \"ZXCVBNM,./\"] + [(\"SHIFT\", 1.25), (\"DELETE\", 1.0)]),\n+    (3.0, [(\" \", 7.5)]),\n+]\n+\n+PLUS1 = dict(y=(160.0, 250.0), height=52.0, slots=[(176.0, 192.0), (212.0, 228.0)], x=(28.0, 152.0), depth=34.0)\n+CART = dict(size=(104.0, 15.0, 78.0), seat=34.0, label=(80.0, 52.0), above=70.0)   # w, t, h; how deep it sits;\n+                                                                                   # how far over it it appears\n+TV_AT = (0.0, 262.0)             # the television's front's middle\n+\n+COLOURS = dict(case=\"#E4DCC6\", key=\"#ECE6D4\", band=\"#3B4038\", dark=\"#0A0A0B\", print=\"#FFFFFF\")\n+\n+REGIONS = dict(top=(0, 0, 850, 625), label=(858, 0, 160, 104), boot=(828, 796, 192, 148), title=(632, 640, 192, 148),\n+               game_1=(828, 640, 192, 148), game_2=(632, 796, 192, 148))\n+SWATCHES = dict(case=(872, 140), key=(904, 140), band=(936, 140), dark=(968, 140), cart=(872, 172), lit=(904, 172))\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s\n+\n+\n+def top_point(s, h):\n+    \"\"\"The model's (y, z) of the point s up the top's slope from its front edge and h off it.\"\"\"\n+    c, k = math.cos(SLOPE), math.sin(SLOPE)\n+    return FRONT[0] + s * c - h * k, FRONT[1] + s * k + h * c\n+\n+\n+def profile():\n+    \"\"\"The computer's side outline, (y, z), round from its front's foot: the front, the slope, the back rounding over\n+    and down, the underside.\"\"\"\n+    by, bz = BACK\n+    back = [(by + (DEPTH - by) * math.sin(math.radians(a)), bz - 6.0 + 6.0 * math.cos(math.radians(a)))\n+            for a in range(0, 91, 15)]\n+    return [(2.0, 0.0), (DEPTH - 2.0, 0.0), (DEPTH, 2.0)] + list(reversed(back)) + [FRONT, (0.0, FRONT[1] - 4.0),\n+                                                                                    (0.0, 2.0)]\n+\n+\n+def keys():\n+    \"\"\"Every key: (legend, x0, x1, s), s its row's middle up the slope; the rows from the back to the front.\"\"\"\n+    out = []\n+    for r, (offset, row) in enumerate(ROWS):\n+        x = KEYS_X0 + offset * U\n+        s = KEY_ROW1 - r * U\n+        for legend, width in row:\n+            out.append((legend, x, x + width * U, s))\n+            x += width * U\n+    return out"
        },
        {
          "sha": "785f561681024c67494256ac58844460cb72de45",
          "filename": "tools/console-models/electron_print.py",
          "status": "added",
          "additions": 116,
          "deletions": 0,
          "changes": 116,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Felectron_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Felectron_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Felectron_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,116 @@\n+\"\"\"The Acorn Electron card's printing and screens (electron_print.png), for the console theme's model (electron.py).\n+\n+One image, laid out as electron_layout.REGIONS says: the computer and the Plus 1 from above (the key tops and legends,\n+the dark band with its grid, acorn electron and the green acorn, the Plus 1's slot covers), the cartridge's label (a\n+made-up game, Odyssey), the screens (the Electron's start-up screen, then the game's title and two moments of its play\n+in Mode 2's eight colours, drawn as the BBC Micro's are), the television's front (zx_spectrum_print.py's, where its\n+layout puts it), and swatches. Run it with the system Python (Pillow):\n+\n+    python tools/console-models/electron_print.py <out folder>\n+\"\"\"\n+\n+import os\n+import sys\n+\n+from PIL import Image, ImageDraw, ImageFont\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import electron_layout as L  # noqa: E402\n+import zx_spectrum_layout as SL  # noqa: E402\n+from bbcmicro_print import game, mode7, title  # noqa: E402\n+from psp_print import ship, space  # noqa: E402\n+from retro_pc_print import rgb  # noqa: E402\n+from windows_pc_print import FONTS, Region  # noqa: E402\n+from zx_spectrum_print import tv_front  # noqa: E402\n+\n+CASE = rgb(L.COLOURS[\"case\"])\n+KEY = rgb(L.COLOURS[\"key\"])\n+BAND = rgb(L.COLOURS[\"band\"])\n+INK = (40, 40, 44)\n+GREEN = (110, 200, 60)\n+\n+\n+def top():\n+    \"\"\"The computer and the Plus 1 from above: x across (-170 at the left), y down from the Plus 1's back (250).\"\"\"\n+    w, d = L.WIDTH, L.PLUS1[\"y\"][1]\n+    reg = Region(L.REGIONS[\"top\"], w, d, CASE)\n+\n+    def at(x, y):\n+        return x + w / 2, d - y\n+\n+    # The opening's floor and the band.\n+    (ox0, ox1), (s0, s1) = L.OPENING[\"x\"], L.OPENING[\"s\"]\n+    reg.rect_(*at(ox0, L.top_point(s1, 0.0)[0]), *at(ox1, L.top_point(s0, 0.0)[0]), (176, 168, 150))\n+    (b0, b1) = L.BAND[\"s\"]\n+    y0, y1 = L.top_point(b0, 0.0)[0], L.top_point(b1, 0.0)[0]\n+    reg.rect_(*at(-w / 2 + 6.0, y1), *at(w / 2 - 6.0, y0), BAND)\n+    x = -w / 2 + 6.0\n+    while x < w / 2 - 6.0:\n+        reg.line([at(x, y0), at(x, y1)], (88, 96, 84), 0.25)\n+        x += 4.0\n+    yy = y0\n+    while yy < y1:\n+        reg.line([at(-w / 2 + 6.0, yy), at(w / 2 - 6.0, yy)], (88, 96, 84), 0.25)\n+        yy += 4.0\n+    my = (y0 + y1) / 2\n+    reg.text(\"acorn\", \"arialbd.ttf\", 7.0, *at(-140.0, my), (240, 240, 236), anchor=\"lm\")\n+    reg.draw.ellipse(reg.box(*at(-111.5, my + 4.6), *at(-104.5, my - 4.8)), fill=GREEN)\n+    reg.text(\"electron\", \"arialbd.ttf\", 7.0, *at(-102.0, my), (240, 240, 236), anchor=\"lm\")\n+    # The keys' tops.\n+    for legend, kx0, kx1, s in L.keys():\n+        ya, yb = L.top_point(s - 9.5 + L.KEY[\"inset\"], L.KEY[\"height\"] - L.OPENING[\"depth\"])[0], \\\n+            L.top_point(s + 9.5 - L.KEY[\"inset\"], L.KEY[\"height\"] - L.OPENING[\"depth\"])[0]\n+        reg.rect_(*at(kx0 + L.KEY[\"inset\"], yb), *at(kx1 - L.KEY[\"inset\"], ya), KEY, r=1.2)\n+        if legend.strip():\n+            size = 4.4 if len(legend) <= 2 else 2.2\n+            reg.text(legend, \"arialbd.ttf\" if len(legend) > 2 else \"arial.ttf\", size,\n+                     *at((kx0 + kx1) / 2, (ya + yb) / 2), INK)\n+    # The Plus 1's slot covers.\n+    (cx0, cx1) = L.PLUS1[\"x\"]\n+    for (sy0, sy1) in L.PLUS1[\"slots\"]:\n+        reg.rect_(*at(cx0 - 3.0, sy1 + 3.0), *at(cx1 + 3.0, sy0 - 3.0), (200, 192, 172), r=2.0)\n+        reg.rect_(*at(cx0, sy1), *at(cx1, sy0), (220, 212, 192), r=1.0)\n+    reg.text(\"PLUS 1\", \"arialbd.ttf\", 4.0, *at(-110.0, 205.0), (150, 142, 124))\n+    return reg\n+\n+\n+def label():\n+    lw, lh = L.CART[\"label\"]\n+    reg = Region(L.REGIONS[\"label\"], lw, lh, (20, 20, 24))\n+    art = space((480, 312)).convert(\"RGB\")\n+    ship(ImageDraw.Draw(art), 150, 190, 3.0, \"#FFFFFF\", \"#FFB23C\")\n+    x0, y0, x1, y1 = reg.box(0.0, 12.0, lw, lh)\n+    reg.img.paste(art.resize((round(x1 - x0), round(y1 - y0)), Image.LANCZOS), (round(x0), round(y0)))\n+    reg.rect_(0.0, 0.0, lw, 12.0, (16, 16, 18))\n+    reg.text(\"ODYSSEY\", \"ariblk.ttf\", 7.0, lw / 2, 6.0, (255, 214, 70))\n+    return reg\n+\n+\n+def boot():\n+    return mode7([(0, 0, \"Acorn Electron\", \"white\", False), (2, 0, \"BASIC\", \"white\", False),\n+                  (4, 0, \">_\", \"white\", False)])\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), CASE)\n+    for reg in (top(), label(), tv_front()):\n+        reg.paste(image)\n+    for name, screen in ((\"boot\", boot()), (\"title\", title()), (\"game_1\", game(0)), (\"game_2\", game(1))):\n+        x, y, w, h = L.REGIONS[name]\n+        image.paste(screen.resize((w, h), Image.LANCZOS), (x, y))\n+    d = ImageDraw.Draw(image)\n+    for name, colour in ((\"black\", rgb(SL.COLOURS[\"bezel\"])), (\"silver\", rgb(SL.COLOURS[\"handle\"])),\n+                         (\"red\", (255, 40, 30))):\n+        x, y = SL.PRINT_SWATCHES[name]\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colour)\n+    colours = dict(case=CASE, key=KEY, band=BAND, dark=rgb(L.COLOURS[\"dark\"]), cart=(24, 24, 26), lit=(255, 60, 40))\n+    for name, (x, y) in L.SWATCHES.items():\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colours[name])\n+    path = os.path.join(out, \"electron_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "ab74103659b8563d5bd4436539c1863a0d505213",
          "filename": "tools/console-models/fds.py",
          "status": "added",
          "additions": 307,
          "deletions": 0,
          "changes": 307,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Ffds.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Ffds.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Ffds.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,307 @@\n+\"\"\"The console theme's Famicom Disk System model (godot/themes/console/models/systems/fds.glb): a Famicom with its RAM\n+adapter, sitting on its Disk System drive, a disk card going in.\n+\n+Built in Blender from the real machines' proportions (fds_layout.py has the measurements, taken from photographs). The\n+drive is a rounded red box, the seam cut round it, the black panel, the slot's bay and the slot (deep enough for the\n+whole card) cut into its front. The Famicom on it: the dark red base; the white shell, its side outline extruded\n+across its width (the front slope, the front deck, the step, the back deck); the EJECT lever, POWER and RESET on the\n+front deck; the RAM adapter lying on the back deck; the two controllers standing in their docks down its sides. The\n+printing is one texture drawn by fds_print.py (the set from above, planar-mapped onto every face facing up; the front\n+slope along it; the drive's front; the controllers' faces; the disk card's two sides; swatches).\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: the right side, with controller II,\n+towards the viewer, the top tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m)\n+holding the set; the set holding the disk card (waiting in the slot, shrunk to nothing), the eject button, the\n+operating lamp (shrunk to nothing) and POWER.\n+\n+The `focused` clip (8 s, looping) plays a two-sided game: POWER slides on; the card slides into the drive, side A up,\n+and the lamp flickers while it reads; the eject button goes in, the card pops out, turns over in the air and goes back\n+in, side B up, and the lamp flickers again; then it's ejected and goes, and POWER slides off.\n+\n+Five materials (red, white, dark, base, print) and one texture, within the system model budget. Run from the repo\n+root:\n+\n+    python tools/console-models/fds_print.py artifacts/fds\n+    blender -b --factory-startup --python tools/console-models/fds.py -- artifacts/fds godot/themes/console/models/systems/fds.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bmesh\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import fds_layout as L  # noqa: E402\n+from amiga import paint, solid  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, ease_out, key, phase, smooth  # noqa: E402\n+from mega_drive_cartridge import bevel, boolean, box, export, join, material, new_object, prism_x, triangles  # noqa: E402\n+\n+TILT_YAW = -30.0     # degrees about the vertical: the right side comes towards the viewer\n+TILT_PITCH = 24.0    # degrees about the horizontal: the top tips up towards the viewer\n+ROUGHNESS = dict(red=0.45, white=0.5, dark=0.5, base=0.55, print=0.45)\n+TOP_DEPTH = 250.0    # the top region's depth (fds_print.py's)\n+FY, FZ = L.FAMICOM[\"y\"], L.FAMICOM[\"z\"]\n+\n+\n+# ---- UVs -------------------------------------------------------------------------------------------------------------\n+\n+def atlas_uv(region, w, h):\n+    \"\"\"A face drawn w x h into region: (a, b) millimetres across it (a to the right, b down from its top) to UVs.\"\"\"\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+TOP = atlas_uv(L.REGIONS[\"top\"], L.WIDTH, TOP_DEPTH)\n+DRIVE_FACE = atlas_uv(L.REGIONS[\"drive\"], L.WIDTH, L.DRIVE[\"height\"])\n+\n+\n+def top_uv(co):\n+    return TOP(co.x + L.WIDTH / 2, TOP_DEPTH - co.y)\n+\n+\n+def drive_uv(co):\n+    return DRIVE_FACE(co.x + L.WIDTH / 2, L.DRIVE[\"height\"] - co.z)\n+\n+\n+def swatch(name):\n+    x, y = L.SWATCHES[name]\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+def painted(obj, m, name):\n+    paint(obj, m[\"print\"], swatch(name), lambda c, n, _name: True)\n+    return obj\n+\n+\n+# ---- The drive -------------------------------------------------------------------------------------------------------\n+\n+def drive(m):\n+    \"\"\"The drive: the box, the seam round it, its front's panel, bay and slot; its front and top printed.\"\"\"\n+    w, d, h = L.WIDTH, L.DRIVE[\"depth\"], L.DRIVE[\"height\"]\n+    shell = box(\"fds\", -w / 2, w / 2, 0.0, d, 0.0, h, m[\"red\"])\n+    bevel(shell, L.DRIVE[\"r\"], segments=3)\n+    s = L.DRIVE[\"seam\"]\n+    ring = box(\"seam\", -w / 2 - 5.0, w / 2 + 5.0, -5.0, d + 5.0, s - 0.6, s + 0.6, m[\"base\"])\n+    boolean(ring, box(\"inner\", -w / 2 + 0.9, w / 2 - 0.9, 0.9, d - 0.9, s - 2.0, s + 2.0, m[\"base\"]))\n+    boolean(shell, ring)\n+    p, b, sl = L.PANEL, L.BAY, L.SLOT\n+    boolean(shell, box(\"panel\", *p[\"x\"], -5.0, p[\"depth\"], *p[\"z\"], m[\"dark\"]))\n+    boolean(shell, box(\"bay\", *b[\"x\"], -5.0, p[\"depth\"] + b[\"depth\"], *b[\"z\"], m[\"dark\"]))\n+    boolean(shell, box(\"slot\", *sl[\"x\"], -5.0, sl[\"depth\"], *sl[\"z\"], m[\"dark\"]))\n+    paint(shell, m[\"print\"], drive_uv, lambda c, n, name: n.y < -0.9 and c.y < p[\"depth\"] + 0.3)\n+    paint(shell, m[\"print\"], top_uv, lambda c, n, name: n.z > 0.9 and c.z > h - 0.5)\n+    return shell\n+\n+\n+def eject_button(m):\n+    e = L.EJECT\n+    face = L.PANEL[\"depth\"]\n+    button = painted(box(\"eject\", *e[\"x\"], face - e[\"out\"], face + 2.0, *e[\"z\"], m[\"print\"]), m, \"yellow\")\n+    bevel(button, 0.6, segments=1)\n+    return button\n+\n+\n+def lamp(m):\n+    \"\"\"The operating lamp lit: a disc just over the printed one, its middle at its origin.\"\"\"\n+    x, z, r = L.LAMP\n+    y = L.PANEL[\"depth\"] - 0.15\n+    bm = bmesh.new()\n+    ring = [bm.verts.new((r * 1.2 * math.cos(2 * math.pi * k / 12), 0.0, r * 1.2 * math.sin(2 * math.pi * k / 12)))\n+            for k in range(12)]\n+    bm.faces.new(ring)                                     # wound to face -y\n+    lit = painted(new_object(\"lamp\", bm, m[\"print\"]), m, \"lit\")\n+    return lit, Vector((x, y, z))\n+\n+\n+# ---- The Famicom -----------------------------------------------------------------------------------------------------\n+\n+def place(obj):\n+    \"\"\"Moves a part built in the Famicom's frame (y from its front, z from its foot) to its place on the drive.\"\"\"\n+    obj.data.transform(Matrix.Translation((0.0, FY, FZ)))\n+    return obj\n+\n+\n+def band_uv():\n+    \"\"\"The front slope's UVs: across it, and down it from its top.\"\"\"\n+    (y0, z0), (y1, z1) = L.SLOPE\n+    length = L.slope_length()\n+    uv = atlas_uv(L.REGIONS[\"band\"], L.WIDTH, length)\n+\n+    def of(co):\n+        y, z = co.y - FY, co.z - FZ\n+        s = ((y - y1) * (y0 - y1) + (z - z1) * (z0 - z1)) / length\n+        return uv(co.x + L.WIDTH / 2, s)\n+    return of\n+\n+\n+def on_slope(c, n):\n+    (y0, z0), (y1, z1) = L.SLOPE\n+    return n.y < -0.4 and n.z > 0.4 and z0 - 0.5 < c.z - FZ < z1 + 0.5 and c.y - FY < y1 + 0.5\n+\n+\n+def famicom(m):\n+    \"\"\"The Famicom: the base, the shell, the EJECT lever, RESET, the RAM adapter, the controllers; printed.\"\"\"\n+    b = L.BASE\n+    base = place(box(\"base\", -b[\"x\"], b[\"x\"], 2.0, 218.0, 0.0, b[\"height\"], m[\"base\"]))\n+    bevel(base, 1.5, segments=2)\n+    shell = place(prism_x(\"famicom\", L.shell_profile(), -74.0, 74.0, m[\"white\"]))\n+    bevel(shell, 1.5, segments=2)\n+    paint(shell, m[\"print\"], band_uv(), lambda c, n, name: on_slope(c, n))\n+    paint(shell, m[\"print\"], top_uv, lambda c, n, name: n.z > 0.5 and not on_slope(c, n))\n+\n+    lv = L.LEVER\n+    lever = place(prism_x(\"lever\", [(lv[\"y\"][0], 35.0), (lv[\"y\"][1], 37.0), (lv[\"y\"][1], 52.0),\n+                                    (lv[\"y\"][1] - 9.0, lv[\"top\"]), (lv[\"y\"][0] + 7.0, lv[\"top\"])],\n+                          *lv[\"x\"], m[\"red\"]))\n+    bevel(lever, 1.0, segments=2)\n+    rx, ry = L.RESET[\"at\"]\n+    rw, rd, rh = L.RESET[\"size\"]\n+    reset = place(box(\"reset\", rx - rw / 2, rx + rw / 2, ry - rd / 2, ry + rd / 2, 34.0, 36.0 + rh, m[\"red\"]))\n+    bevel(reset, 0.8, segments=1)\n+    a = L.ADAPTER\n+    adapter = place(box(\"adapter\", *a[\"x\"], *a[\"y\"], 43.0, a[\"top\"], m[\"dark\"]))\n+    bevel(adapter, 2.0, segments=2)\n+    paint(adapter, m[\"print\"], top_uv, lambda c, n, name: n.z > 0.9)\n+    t = L.TAB\n+    tab = place(box(\"tab\", *t[\"x\"], -t[\"out\"], 3.0, *t[\"z\"], m[\"red\"]))\n+    bevel(tab, 0.8, segments=1)\n+\n+    c = L.CONTROLLERS\n+    pads = []\n+    for side, region in ((-1, \"pad1\"), (1, \"pad2\")):\n+        x0, x1 = (c[\"x\"][0], c[\"x\"][1]) if side > 0 else (-c[\"x\"][1], -c[\"x\"][0])\n+        pad = place(box(\"controller\", x0, x1, *c[\"y\"], *c[\"z\"], m[\"red\"]))\n+        bevel(pad, 3.0, segments=2)\n+        face = atlas_uv(L.REGIONS[region], c[\"y\"][1] - c[\"y\"][0], c[\"z\"][1] - c[\"z\"][0])\n+        (y0, y1), (z0, z1) = c[\"y\"], c[\"z\"]\n+        if side > 0:                                       # seen from the right: the front to the left\n+            uv = (lambda f: lambda co: f(co.y - FY - y0, z1 - (co.z - FZ)))(face)\n+        else:                                              # seen from the left: the front to the right\n+            uv = (lambda f: lambda co: f(y1 - (co.y - FY), z1 - (co.z - FZ)))(face)\n+        paint(pad, m[\"print\"], uv, lambda cc, n, name, s=side: n.x * s > 0.9)\n+        pads.append(pad)\n+    return [base, shell, lever, reset, adapter, tab] + pads\n+\n+\n+def power_slider(m):\n+    px, py = L.POWER[\"at\"]\n+    w, d, h = L.POWER[\"size\"]\n+    slider = box(\"power\", -w / 2, w / 2, -d / 2, d / 2, -1.5, h - 1.5, m[\"red\"])\n+    bevel(slider, 0.8, segments=1)\n+    deck_z = 34.0 + (py - L.DECK[0][0]) / (L.DECK[1][0] - L.DECK[0][0]) * 3.0\n+    return slider, Vector((px, py + FY, deck_z + FZ))\n+\n+\n+# ---- The disk card ---------------------------------------------------------------------------------------------------\n+\n+def disk(m):\n+    \"\"\"The disk card, its middle at its origin, lying flat, its window's end towards +y (into the slot), side A up.\"\"\"\n+    (w, l), t = L.DISK[\"size\"], L.DISK[\"thickness\"]\n+    card = box(\"disk\", -w / 2, w / 2, -l / 2, l / 2, -t / 2, t / 2, m[\"print\"])\n+    bevel(card, 0.5, segments=1)\n+    side_a = atlas_uv(L.REGIONS[\"side_a\"], w, l)\n+    side_b = atlas_uv(L.REGIONS[\"side_b\"], w, l)\n+    paint(card, m[\"print\"], lambda co: side_a(co.x + w / 2, l / 2 - co.y), lambda c, n, name: n.z > 0.9)\n+    paint(card, m[\"print\"], lambda co: side_b(w / 2 - co.x, l / 2 - co.y), lambda c, n, name: n.z < -0.9)\n+    paint(card, m[\"print\"], swatch(\"yellow\"), lambda c, n, name: abs(n.z) <= 0.9)\n+    return card\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+OUT = 100.0          # how far in front of its seat the card waits: wholly out of the slot\n+\n+\n+def appear(f, f0, f1):\n+    return HIDDEN + (1 - HIDDEN) * back_out(phase(f, f0, f1)) if f >= f0 else HIDDEN\n+\n+\n+def card(f):\n+    if f < 10 or f >= 226:\n+        return Pose(scale=HIDDEN)\n+    if f < 20:\n+        return Pose(y=-OUT, scale=appear(f, 10, 18))\n+    if f < 102:\n+        return Pose(y=-OUT * (1 - smooth(phase(f, 20, 36))))\n+    if f < 110:\n+        return Pose(y=-30.0 * back_out(phase(f, 102, 108)))\n+    if f < 118:                                            # out, and up\n+        t = smooth(phase(f, 110, 118))\n+        return Pose(y=-30.0 - (OUT - 30.0) * t, z=30.0 * t)\n+    if f < 136:                                            # turned over in the air: side B up\n+        return Pose(y=-OUT, z=30.0, roll=180.0 * smooth(phase(f, 118, 136)))\n+    if f < 144:\n+        return Pose(y=-OUT, z=30.0 * (1 - smooth(phase(f, 136, 144))), roll=180.0)\n+    if f < 206:\n+        return Pose(y=-OUT * (1 - smooth(phase(f, 144, 160))), roll=180.0)\n+    if f < 214:\n+        return Pose(y=-30.0 * back_out(phase(f, 206, 212)), roll=180.0)\n+    t = ease_out(phase(f, 214, 222))\n+    return Pose(y=-30.0 - 40.0 * t, z=30.0 * t, roll=180.0, scale=max(HIDDEN, 1 - ease_in(phase(f, 218, 226))))\n+\n+\n+def timeline(f):\n+    reading = (38 <= f < 92 or 162 <= f < 198) and (f // 3) % 5 != 4\n+    press = max(smooth(phase(f, f0, f0 + 2)) * (1 - smooth(phase(f, f0 + 4, f0 + 7))) for f0 in (100, 204))\n+    on = smooth(phase(f, 4, 8)) * (1 - smooth(phase(f, 230, 234)))\n+    return {\"disk\": card(f), \"lamp\": Pose() if reading else Pose(scale=HIDDEN), \"eject\": Pose(y=2.0 * press),\n+            \"power\": Pose(x=L.POWER[\"travel\"] * on)}\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    printed = bpy.data.images.load(os.path.join(images, \"fds_print.png\"))\n+    colours = dict(L.COLOURS, print=\"#FFFFFF\")\n+    m = {n: material(n, colours[n], r, printed if n == \"print\" else None) for n, r in ROUGHNESS.items()}\n+\n+    whole = join([drive(m)] + famicom(m), \"fds\")\n+    button = eject_button(m)\n+    lit, lit_at = lamp(m)\n+    slider, slider_at = power_slider(m)\n+    card_ = disk(m)\n+    for obj in [whole, button, slider, card_]:\n+        for p in obj.data.polygons:\n+            p.use_smooth = True\n+        obj.data.set_sharp_from_angle(angle=math.radians(40))\n+\n+    # The node tree: the root (the view and the fit) holds the set; the set holds the moving parts at their places.\n+    root = bpy.data.objects.new(\"fds_root\", None)\n+    scene.collection.objects.link(root)\n+    whole.parent = root\n+    sl = L.SLOT\n+    (w, length), t = L.DISK[\"size\"], L.DISK[\"thickness\"]\n+    seat = Vector(((sl[\"x\"][0] + sl[\"x\"][1]) / 2, -L.DISK[\"inside\"] + length / 2, (sl[\"z\"][0] + sl[\"z\"][1]) / 2))\n+    for part, where in ((lit, lit_at), (slider, slider_at), (card_, seat)):\n+        part.parent = whole\n+        part.location = where\n+    button.parent = whole\n+    nodes = {\"disk\": card_, \"lamp\": lit, \"eject\": button, \"power\": slider}\n+\n+    # The three-quarter view, then the spec's fit of the set at rest, as the root's transform.\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    points = [tilt @ v.co for v in whole.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [whole, button, lit, slider, card_]\n+    print(f\"FDS: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(whole.data)} the set), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "12a8f8bab0678b2fc9e6ca7537491fd99562816a",
          "filename": "tools/console-models/fds_layout.py",
          "status": "added",
          "additions": 77,
          "deletions": 0,
          "changes": 77,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Ffds_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Ffds_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Ffds_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,77 @@\n+\"\"\"The Famicom Disk System's measurements, shared by fds_print.py (which draws its printing) and fds.py (which builds\n+it). Plain Python: Blender's Python imports it too.\n+\n+The Family Computer (HVC-001), 150 x 220 x 60 mm, sitting on its Disk System's drive (HVC-022), 150 x 246 x 73 mm (both\n+Nintendo's figures), the RAM adapter (HVC-023) in its cartridge slot, its two controllers in their docks. Millimetres:\n+x from the set's middle (left to right), y back from the drive's front, z up from the table. The rest was measured from\n+Evan-Amos's photographs on Wikimedia Commons (the Famicom on its drive, the drive alone, the Famicom alone and with its\n+controller out, a disk card), scaled by their widths; the disk card is 76 x 90 mm.\n+\n+The drive: a red box in two halves, a seam round it, the black panel in its front (the disk's slot in its bay, the\n+yellow eject button, Nintendo HVC-022 FAMILY COMPUTER DISK SYSTEM, the operating lamp, grooved ends). The Famicom on\n+it, its front set back: the dark red base; the white shell, its front sloping up to the front deck (POWER, the EJECT\n+lever, RESET) and stepping up behind it to the back deck, where the black RAM adapter lies in the slot; the\n+controllers standing in their red docks down its sides, their gold faces outwards; FAMILY COMPUTER on the red band\n+across the front slope.\n+\n+One texture (ATLAS square, REGIONS): `top` (the Famicom and the drive seen from above: the decks' printing, the RAM\n+adapter's top; planar-mapped onto every face facing up), `band` (the front slope, along it), `drive` (the drive's\n+front), `pad1` and `pad2` (the controllers' faces), `side_a` and `side_b` (the disk card's two sides: a made-up game,\n+Odyssey), and swatches. A region is (x, y, width, height) in pixels.\n+\"\"\"\n+\n+import math\n+\n+ATLAS = 1024\n+\n+# -- The drive -------------------------------------------------------------------------------------------------------\n+\n+WIDTH = 150.0\n+DRIVE = dict(depth=246.0, height=73.0, seam=37.0, r=4.0)\n+PANEL = dict(x=(-70.0, 70.0), z=(6.0, 65.0), depth=2.0)    # the black panel sunk into its front\n+BAY = dict(x=(-50.0, 38.0), z=(39.0, 60.0), depth=4.0)     # the slot's bay in it\n+SLOT = dict(x=(-41.0, 37.0), z=(47.5, 51.5), depth=96.0)   # the disk's slot, deep enough for the whole card\n+EJECT = dict(x=(11.0, 24.0), z=(30.0, 35.5), out=3.0)      # the yellow eject button\n+LAMP = (51.0, 23.0, 1.6)                                   # the operating lamp: x, z, radius\n+\n+# -- The Famicom -----------------------------------------------------------------------------------------------------\n+\n+FAMICOM = dict(y=20.0, depth=220.0, z=DRIVE[\"height\"])     # its front (back from the drive's front) and its foot\n+\n+\n+def shell_profile():\n+    \"\"\"The white shell's side outline, (y, z) from the Famicom's front and foot: the front slope, the front deck, the\n+    step, the back deck.\"\"\"\n+    return [(0.0, 7.0), (220.0, 7.0), (220.0, 41.0), (217.0, 44.0), (100.0, 44.0), (96.0, 37.0), (28.0, 34.0),\n+            (4.0, 11.0), (0.0, 8.5)]\n+\n+\n+BASE = dict(x=74.0, height=7.5)                             # the dark red base under the shell\n+SLOPE = ((4.0, 11.0), (28.0, 34.0))                         # the front slope: its foot and its top, (y, z)\n+DECK = ((28.0, 34.0), (96.0, 37.0))                         # the front deck\n+POWER = dict(at=(-36.0, 52.0), size=(16.0, 10.0, 5.0), travel=6.0)    # its middle (x, y from the front); w, d, h\n+RESET = dict(at=(37.0, 42.0), size=(18.0, 15.0, 3.0))\n+LEVER = dict(x=(-10.0, 10.0), y=(56.0, 84.0), top=56.0)    # the EJECT lever: a wedge up to its top\n+CONTROLLERS = dict(y=(78.0, 206.0), z=(7.5, 57.5), x=(61.0, 76.0))     # in their docks: along, up, across (each side)\n+ADAPTER = dict(x=(-56.0, 56.0), y=(98.0, 228.0), top=58.0, grooves=6)  # the RAM adapter on the back deck\n+TAB = dict(x=(-26.0, -6.0), z=(1.0, 13.0), out=3.0)        # the expansion port's cover, at the base's front\n+\n+# -- The disk card ---------------------------------------------------------------------------------------------------\n+\n+DISK = dict(size=(76.0, 90.0), thickness=3.0, inside=6.0)  # w, length; how much stays out of the slot once in\n+\n+COLOURS = dict(red=\"#B2202A\", white=\"#EDE8DC\", dark=\"#141416\", base=\"#7A1A1E\", yellow=\"#F3B700\")\n+\n+REGIONS = dict(top=(0, 0, 400, 656), band=(408, 0, 400, 104), drive=(408, 112, 400, 196),\n+               pad1=(408, 316, 400, 156), pad2=(408, 480, 400, 156), side_a=(816, 0, 208, 246),\n+               side_b=(816, 254, 208, 246))\n+SWATCHES = dict(red=(16, 680), white=(48, 680), dark=(80, 680), base=(112, 680), yellow=(144, 680), lit=(176, 680),\n+                lamp_off=(208, 680), gold=(240, 680))\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s\n+\n+\n+def slope_length():\n+    (y0, z0), (y1, z1) = SLOPE\n+    return math.hypot(y1 - y0, z1 - z0)"
        },
        {
          "sha": "77b81dce2dcd01b02d617bfed7b155f6b42a47f9",
          "filename": "tools/console-models/fds_print.py",
          "status": "added",
          "additions": 219,
          "deletions": 0,
          "changes": 219,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Ffds_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Ffds_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Ffds_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,219 @@\n+\"\"\"The Famicom Disk System's printing, for the console theme's Famicom Disk System model (tools/console-models/fds.py).\n+\n+Draws its atlas, its regions as fds_layout.py places them, each in millimetres as the model's planar UVs map it: the\n+set seen from above (the Famicom's front deck with POWER, EJECT and RESET and the warnings beside them, the RAM\n+adapter's top with Nintendo's badge, FAMILY COMPUTER DISK SYSTEM and HVC-023, its grooves); the Famicom's front slope\n+(FAMILY COMPUTER on the red band); the drive's front (Nintendo HVC-022 FAMILY COMPUTER DISK SYSTEM, the grooved ends,\n+the operating lamp's box, \u30a4\u30b8\u30a7\u30af\u30c8); the two controllers' faces (the gold plates, the D-pads, SELECT and START on I,\n+VOLUME and the microphone on II, B and A); the disk card's two sides (a made-up game, Odyssey, side A and side B);\n+and the swatches small parts take their colour from. Run it with the system Python (Pillow):\n+\n+    python tools/console-models/fds_print.py <out folder>\n+\n+It writes fds_print.png there.\n+\"\"\"\n+\n+import os\n+import sys\n+\n+from PIL import Image, ImageDraw, ImageFont\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import fds_layout as L  # noqa: E402\n+from psp_print import ship, space  # noqa: E402\n+from retro_pc_print import rgb, shade  # noqa: E402\n+from windows_pc_print import FONTS, Region  # noqa: E402\n+\n+RED = rgb(L.COLOURS[\"red\"])\n+WHITE = rgb(L.COLOURS[\"white\"])\n+DARK = rgb(L.COLOURS[\"dark\"])\n+YELLOW = rgb(L.COLOURS[\"yellow\"])\n+GOLD = (196, 168, 112)\n+GOLD_TEXT = (218, 196, 140)\n+TOP_DEPTH = 250.0           # the top region's depth: the drive's front to the adapter's back\n+JP = \"msgothic.ttc\"\n+\n+\n+def slanted(reg, text, font, size, x, y, fill, shear=0.22, anchor=\"mm\"):\n+    layer = reg.layer()\n+    f = ImageFont.truetype(FONTS + font, max(1, round(size * reg.sx)))\n+    ImageDraw.Draw(layer).text(reg.p(x, y), text, font=f, fill=fill + (255,), anchor=anchor)\n+    yc = reg.p(0, y)[1]\n+    reg.over(layer.transform(layer.size, Image.AFFINE, (1, shear, -shear * yc, 0, 1, 0)))\n+\n+\n+def nintendo(reg, x, y, size, fill, box=None):\n+    \"\"\"Nintendo's name in its racetrack outline (or on a filled one, box).\"\"\"\n+    w, h = size * 3.6, size * 1.25\n+    if box is not None:\n+        reg.rect_(x - w / 2, y - h / 2, x + w / 2, y + h / 2, box, r=h / 2)\n+    reg.rect_(x - w / 2, y - h / 2, x + w / 2, y + h / 2, None, r=h / 2, outline=fill, width=size * 0.09)\n+    reg.text(\"Nintendo\", \"arialbd.ttf\", size * 0.82, x, y + size * 0.03, fill)\n+\n+\n+# ---- The set from above ------------------------------------------------------------------------------------------------\n+\n+def top():\n+    reg = Region(L.REGIONS[\"top\"], L.WIDTH, TOP_DEPTH, RED)\n+    fy = L.FAMICOM[\"y\"]\n+\n+    def at(x, y):\n+        \"\"\"A point of the Famicom (x, y from its front) in the region's millimetres.\"\"\"\n+        return x + L.WIDTH / 2, TOP_DEPTH - (y + fy)\n+    reg.rect_(*at(-74.0, 220.0), *at(74.0, 0.0), WHITE)\n+    grey = shade(WHITE, -0.22)\n+    for x, y, words in ((-33.0, 52.0, \"POWER\"), (37.0, 42.0, \"RESET\")):\n+        reg.rect_(*at(x - 15.0, y + 11.0), *at(x + 15.0, y - 11.0), shade(WHITE, -0.04), r=1.0)\n+        reg.text(words, \"arialbd.ttf\", 2.4, *at(x - 14.0 if x < 0 else x + 14.0, y + 13.0), grey,\n+                 anchor=\"lm\" if x < 0 else \"rm\")\n+    reg.text(\"EJECT\", \"arialbd.ttf\", 2.4, *at(0.0, 50.0), grey)\n+    for x, y, lines in ((-32.0, 80.0, (\"\u25cf\u30ab\u30bb\u30c3\u30c8\u3092\u629c\u304d\", \"\u5dee\u3057\u3059\u308b\u6642\u306f\u3001\", \"\u5fc5\u305a\u672c\u4f53\u306e\u96fb\u6e90\u3092\", \"\u5207\u3063\u3066\u304f\u3060\u3055\u3044\u3002\")),\n+                        (36.0, 70.0, (\"\u25cf\u30ea\u30bb\u30c3\u30c8\u30b9\u30a4\u30c3\u30c1\", \"\u3092\u62bc\u3059\u3068\u3001\u305d\u308c\u307e\u3067\", \"\u8a18\u61b6\u3055\u308c\u3066\u3044\u305f\", \"\u5f97\u70b9\u304c\u6d88\u53bb\u3055\u308c\u307e\u3059\u3002\"))):\n+        reg.rect_(*at(x - 13.0, y + 12.0), *at(x + 13.0, y - 8.0), WHITE, outline=(80, 80, 80), width=0.25)\n+        for k, line in enumerate(lines):\n+            reg.text(line, JP, 2.2, *at(x - 12.0, y + 9.5 - 3.0 * k), (60, 60, 60), anchor=\"lm\")\n+        ax, ay = at(x, y - 12.0)\n+        reg.poly([(ax - 4.0, ay - 2.0), (ax + 4.0, ay - 2.0), (ax, ay + 2.5)], (120, 120, 120))\n+        reg.poly([(ax - 2.6, ay - 1.4), (ax + 2.6, ay - 1.4), (ax, ay + 1.4)], WHITE)\n+\n+    # The RAM adapter: black, its grooves at each end, Nintendo's badge, its names.\n+    (ax0, ax1), (ay0, ay1) = L.ADAPTER[\"x\"], L.ADAPTER[\"y\"]\n+    reg.rect_(*at(ax0, ay1), *at(ax1, ay0), DARK)\n+    for k in range(L.ADAPTER[\"grooves\"]):\n+        for y in (ay0 + 5.0 + 3.0 * k, ay1 - 5.0 - 3.0 * k):\n+            reg.line([at(ax0 + 2.0, y), at(ax1 - 2.0, y)], (44, 44, 48), 0.9)\n+    reg.rect_(*at(8.0, 182.0), *at(52.0, 170.0), (230, 230, 230))\n+    reg.text(\"Nintendo\", \"arialbd.ttf\", 6.4, *at(30.0, 176.0), DARK)\n+    slanted(reg, \"FAMILY COMPUTER  DISK  SYSTEM\", \"arial.ttf\", 2.6, *at(16.0, 160.0), (220, 220, 222), shear=0.0)\n+    reg.rect_(*at(30.0, 154.5), *at(52.0, 149.5), (220, 220, 222))\n+    reg.text(\"HVC-023\", \"arialbd.ttf\", 3.4, *at(41.0, 152.0), DARK)\n+    return reg\n+\n+\n+def band():\n+    length = L.slope_length()\n+    reg = Region(L.REGIONS[\"band\"], L.WIDTH, length, WHITE)\n+    # The band runs up the slope: b from the slope's top (0) down to its foot (length).\n+    reg.rect_(0.0, length * 0.32, L.WIDTH - 22.0, length * 0.92, RED)\n+    for y in (length * 0.2, length * 0.96):\n+        reg.line([(6.0, y), (L.WIDTH - 2.0, y)], GOLD_TEXT, 0.5)\n+    slanted(reg, \"FAMILY COMPUTER\", \"arialbd.ttf\", 10.0, 70.0, length * 0.6, GOLD_TEXT, shear=0.24)\n+    reg.text(\"\u2122\", \"arial.ttf\", 4.0, 118.0, length * 0.42, GOLD_TEXT)\n+    slanted(reg, \"Nintendo\u00ae\", \"arialbd.ttf\", 4.0, 122.0, length * 0.82, GOLD_TEXT, shear=0.2)\n+    return reg\n+\n+\n+def drive():\n+    reg = Region(L.REGIONS[\"drive\"], L.WIDTH, L.DRIVE[\"height\"], RED)\n+\n+    def at(x, z):\n+        return x + L.WIDTH / 2, L.DRIVE[\"height\"] - z\n+    (px0, px1), (pz0, pz1) = L.PANEL[\"x\"], L.PANEL[\"z\"]\n+    reg.rect_(*at(px0, pz1), *at(px1, pz0), DARK)\n+    for x0, x1, z0, z1 in ((px0, px0 + 10.0, pz0, pz1), (px1 - 22.0, px1, pz0, pz0 + 18.0)):\n+        for k in range(int((z1 - z0) / 2.4)):                 # the grooved ends\n+            z = z0 + 1.2 + 2.4 * k\n+            reg.line([at(x0 + 1.0, z), at(x1 - 1.0, z)], (40, 40, 44), 0.9)\n+    lx, lz, _ = L.LAMP\n+    reg.rect_(*at(px1 - 22.0, lz + 8.0), *at(px1 - 1.0, lz - 6.0), (20, 20, 22), outline=(44, 44, 48), width=0.4)\n+    reg.circle(*at(lx, lz), L.LAMP[2], (70, 16, 16))\n+    reg.text(\"\u25c0\u4f5c\u52d5\u4e2d\", JP, 3.0, *at(lx + 3.0, lz), (220, 220, 220), anchor=\"lm\")\n+    reg.text(\"Nintendo\", \"arialbd.ttf\", 5.4, *at(-48.0, 30.0), (240, 240, 240), anchor=\"lm\")\n+    reg.rect_(*at(-48.0, 25.0), *at(-31.0, 20.6), (240, 240, 240))\n+    reg.text(\"HVC-022\", \"arialbd.ttf\", 3.6, *at(-39.5, 22.8), DARK)\n+    reg.text(\"FAMILY COMPUTER  DISK  SYSTEM\", \"arial.ttf\", 2.8, *at(-28.0, 14.0), (230, 230, 230), anchor=\"lm\")\n+    reg.text(\"\u30a4\u30b8\u30a7\u30af\u30c8\", JP, 2.6, *at(17.5, 26.0), (230, 230, 230))\n+    return reg\n+\n+\n+# ---- The controllers ---------------------------------------------------------------------------------------------------\n+\n+PAD_W, PAD_H = 128.0, 50.0\n+\n+\n+def pad_face(region, number):\n+    \"\"\"A controller's face, its cable's end to the left: the red border, the gold plate, the D-pad, the buttons.\"\"\"\n+    reg = Region(region, PAD_W, PAD_H, RED)\n+    reg.rect_(4.0, 13.0 if number == 1 else 4.0, PAD_W - 4.0, PAD_H - 4.0, GOLD)\n+    if number == 1:\n+        reg.rect_(4.0, 4.0, 52.0, 13.0, GOLD)\n+    for y in (PAD_H - 9.0, PAD_H - 7.6):\n+        reg.line([(6.0, y), (PAD_W - 6.0, y)], (120, 30, 30), 0.35)\n+    reg.rect_(8.0, 7.0 if number == 2 else 15.0, 14.0, 13.0 if number == 2 else 21.0, DARK)\n+    reg.text(\"I\" if number == 1 else \"II\", \"timesbd.ttf\", 4.2, 11.0, 10.0 if number == 2 else 18.0, GOLD)\n+    cx, cy, s, a = 22.0, 33.0, 20.0, 6.5                   # the D-pad: its middle, its span, its arm\n+    reg.rect_(cx - a / 2, cy - s / 2, cx + a / 2, cy + s / 2, (24, 24, 26), r=0.8)\n+    reg.rect_(cx - s / 2, cy - a / 2, cx + s / 2, cy + a / 2, (24, 24, 26), r=0.8)\n+    reg.circle(cx, cy, 1.2, (44, 44, 48))\n+    if number == 1:\n+        reg.rect_(44.0, 28.0, 76.0, 38.0, RED, r=5.0)\n+        for x in (52.0, 68.0):\n+            reg.rect_(x - 5.0, 31.5, x + 5.0, 34.5, (24, 24, 26), r=1.5)\n+        reg.text(\"SELECT\", \"arialbd.ttf\", 2.4, 52.0, 25.0, (60, 30, 30))\n+        reg.text(\"START\", \"arialbd.ttf\", 2.4, 68.0, 25.0, (60, 30, 30))\n+    else:\n+        reg.rect_(32.0, 20.0, 44.0, 22.4, (30, 30, 32))\n+        reg.rect_(32.5, 19.0, 35.5, 23.4, (60, 60, 64))\n+        reg.poly([(46.0, 25.0), (56.0, 19.5), (56.0, 25.0)], (60, 30, 30))\n+        reg.text(\"VOLUME\", \"arialbd.ttf\", 2.2, 60.0, 22.0, (60, 30, 30), anchor=\"lm\")\n+        reg.rect_(72.0, 6.0, 92.0, 20.0, RED, r=1.0)\n+        for i in range(6):\n+            for j in range(4):\n+                reg.circle(74.2 + 3.1 * i, 8.2 + 3.2 * j, 0.6, shade(RED, -0.4))\n+        reg.text(\"MIC.\", \"arialbd.ttf\", 2.2, 82.0, 24.0, (60, 30, 30))\n+    for x, name in ((98.0, \"B\"), (114.0, \"A\")):\n+        reg.circle(x, 34.0, 6.2, (40, 20, 20))\n+        reg.circle(x, 34.0, 5.2, (24, 24, 26))\n+        reg.circle(x - 1.5, 32.5, 1.6, (70, 70, 74))\n+        reg.rect_(x - 2.4, 22.0, x + 2.4, 26.4, RED, r=1.0)\n+        reg.text(name, \"arialbd.ttf\", 3.2, x, 24.2, GOLD_TEXT)\n+    return reg\n+\n+\n+# ---- The disk card -----------------------------------------------------------------------------------------------------\n+\n+def disk_side(region, side):\n+    \"\"\"One side of the disk card, its window's end at the top: the yellow shell, the window, the hub, the label (side\n+    A's or B's), NINTENDO raised along its foot.\"\"\"\n+    w, h = L.DISK[\"size\"]\n+    reg = Region(region, w, h, YELLOW)\n+    reg.rect_(1.0, 1.0, w - 1.0, h - 1.0, shade(YELLOW, 0.05), r=1.5)\n+    reg.rect_(w / 2 - 4.5, 6.0, w / 2 + 3.0, 30.0, (110, 104, 92))       # the window, the disk showing\n+    reg.circle(w / 2, 40.0, 7.5, (90, 86, 80))\n+    reg.circle(w / 2, 40.0, 6.2, (232, 228, 218))\n+    reg.circle(w / 2, 40.0, 1.6, (180, 176, 168))\n+    reg.rect_(6.0, 50.0, w - 6.0, h - 13.0, (250, 248, 240), r=1.0)\n+    art = space((560, 220))\n+    d = ImageDraw.Draw(art)\n+    ship(d, 160, 120, 2.4, \"#FFFFFF\", \"#FFB23C\")\n+    x0, y0, x1, y1 = reg.box(7.0, 51.0, w - 7.0, h - 20.0)\n+    reg.img.paste(art.convert(\"RGB\").resize((round(x1 - x0), round(y1 - y0)), Image.LANCZOS), (round(x0), round(y0)))\n+    reg.text(\"ODYSSEY\", \"ariblk.ttf\", 6.2, w / 2, 56.5, (255, 214, 70))\n+    reg.rect_(6.0, h - 20.0, w - 6.0, h - 13.0, (206, 34, 40))\n+    reg.text(f\"SIDE {side}\", \"arialbd.ttf\", 4.6, w / 2, h - 16.5, (255, 250, 236))\n+    reg.rect_(8.0, 9.0, 16.0, 17.0, (206, 34, 40) if side == \"A\" else (36, 80, 190), r=1.0)\n+    reg.text(side, \"arialbd.ttf\", 5.2, 12.0, 13.2, (255, 255, 255))\n+    for (dx, dy), colour in (((-0.3, -0.3), shade(YELLOW, 0.25)), ((0.4, 0.4), shade(YELLOW, -0.25)),\n+                             ((0.0, 0.0), shade(YELLOW, 0.02))):\n+        reg.text(\"NINTENDO\", \"ariblk.ttf\", 7.2, w / 2 + dx, h - 6.0 + dy, colour)\n+    return reg\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), RED)\n+    for reg in (top(), band(), drive(), pad_face(L.REGIONS[\"pad1\"], 1), pad_face(L.REGIONS[\"pad2\"], 2),\n+                disk_side(L.REGIONS[\"side_a\"], \"A\"), disk_side(L.REGIONS[\"side_b\"], \"B\")):\n+        reg.paste(image)\n+    d = ImageDraw.Draw(image)\n+    for name, colour in ((\"red\", RED), (\"white\", WHITE), (\"dark\", DARK), (\"base\", rgb(L.COLOURS[\"base\"])),\n+                         (\"yellow\", YELLOW), (\"lit\", (255, 40, 30)), (\"lamp_off\", (70, 16, 16)), (\"gold\", GOLD)):\n+        x, y = L.SWATCHES[name]\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colour)\n+    path = os.path.join(out, \"fds_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "73989e9764736766a39f81c01c45325cf5ec4448",
          "filename": "tools/console-models/flash.py",
          "status": "added",
          "additions": 223,
          "deletions": 0,
          "changes": 223,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fflash.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fflash.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fflash.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,223 @@\n+\"\"\"The console theme's Flash system model (godot/themes/console/models/systems/flash.glb): a 2000s LCD monitor playing\n+a Flash game in a browser, and its mouse.\n+\n+Flash has no hardware (flash_layout.py): its card is a silver 17-inch LCD on its oval foot, the browser filling its\n+screen, and a mouse beside it. The panel is its bezel (the screen's recess cut into its front) and the housing behind\n+it; the neck rises from the foot to the housing; the buttons and the blue power light sit along the bezel's lower lip,\n+the brand plate in its middle. The mouse is lofted from its outline, domed, its top printed. The printing is one\n+texture drawn by flash_print.py: the screens, the plate, the mouse's top and swatches.\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH; then the model spec's fit: standing on\n+y = 0, centred, the largest side 1 m) holding the set; the set holding the screens over the glass (the preloader's\n+bar filling, Odyssey's title, two frames of play, the right-click menu), shrunk to nothing, so at rest the card shows\n+the preloader's splash with the Flash logo; and the mouse.\n+\n+The `focused` clip (8 s, looping) plays it: the preloader's bar fills, Odyssey's title comes up, the mouse moves and\n+clicks PLAY (it dips under the finger), two frames of play take turns, a right click brings up the menu ending in About\n+Adobe Flash Player, and the page loads again.\n+\n+Four materials (silver, black, glass, mouse) and one texture, within the system model budget. Run from the repo root:\n+\n+    python tools/console-models/flash_print.py artifacts/flash\n+    blender -b --factory-startup --python tools/console-models/flash.py -- artifacts/flash godot/themes/console/models/systems/flash.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bmesh\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import flash_layout as L  # noqa: E402\n+from amiga import curved, paint, rounded, solid  # noqa: E402\n+from clip import HIDDEN, Pose, key, phase, smooth  # noqa: E402\n+from mega_drive_cartridge import bevel, boolean, box, export, join, material, new_object, triangles  # noqa: E402\n+\n+TILT_YAW = -24.0     # degrees about the vertical: the right side comes towards the viewer\n+TILT_PITCH = 12.0    # degrees about the horizontal: the top tips back a little\n+ROUGHNESS = dict(silver=0.35, black=0.5, glass=0.15, mouse=0.4)\n+STATES = (\"load\", \"title\", \"game1\", \"game2\", \"menu\")\n+\n+\n+def atlas_uv(region, w, h):\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+def swatch(name):\n+    x, y = L.SWATCHES[name]\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+def painted(obj, mat, name):\n+    paint(obj, mat, swatch(name), lambda c, n, _name: True)\n+    return obj\n+\n+\n+def ellipse(cx, cy, rx, ry, n=32):\n+    return [(cx + rx * math.cos(2 * math.pi * k / n), cy + ry * math.sin(2 * math.pi * k / n)) for k in range(n)]\n+\n+\n+# ---- The monitor -----------------------------------------------------------------------------------------------------\n+\n+GLASS_Z = L.PANEL[\"bottom\"] + L.PANEL[\"lip\"] + L.GLASS[1] / 2     # the glass's middle's height\n+\n+\n+def monitor(m):\n+    \"\"\"The panel (the bezel, the screen's recess, the housing behind), the neck, the foot, the buttons, the light, the\n+    plate; the glass at rest.\"\"\"\n+    p = L.PANEL\n+    w, h, b = p[\"width\"], p[\"height\"], p[\"bottom\"]\n+    bezel = solid(\"flash\", [[(x, y, z) for x, z in rounded(0.0, b + h / 2, w, h, 10.0, 4)] for y in (0.0, 22.0)],\n+                  m[\"silver\"])\n+    bevel(bezel, 2.0, segments=2)\n+    gw, gh = L.GLASS\n+    boolean(bezel, box(\"recess\", -gw / 2 - 1.0, gw / 2 + 1.0, -5.0, 3.0, GLASS_Z - gh / 2 - 1.0, GLASS_Z + gh / 2 + 1.0,\n+                       m[\"black\"]))\n+    housing = solid(\"housing\", [[(x, y, z) for x, z in rounded(0.0, b + h * 0.55, wd, hd, 18.0, 4)] for y, wd, hd in\n+                                ((20.0, w - 30.0, h - 40.0), (p[\"depth\"], w * 0.62, h * 0.55))], m[\"silver\"])\n+    bevel(housing, 3.0, segments=2, angle=50)\n+    s = L.STAND\n+    nw, nd = s[\"neck\"]\n+    neck = box(\"neck\", -nw / 2, nw / 2, s[\"neck_y\"] - nd / 2, s[\"neck_y\"] + nd / 2, 4.0, b + s[\"top\"], m[\"silver\"])\n+    bevel(neck, 4.0, segments=2)\n+    bw, bd, bh = s[\"base\"]\n+    foot = solid(\"foot\", [[(x, y, z) for x, y in ellipse(0.0, s[\"base_y\"], rx, ry, 48)] for rx, ry, z in\n+                          ((bw / 2 - 4.0, bd / 2 - 4.0, 0.0), (bw / 2, bd / 2, 4.0), (bw / 2 - 10.0, bd / 2 - 10.0, bh))],\n+                 m[\"silver\"])\n+    parts = [bezel, housing, neck, foot]\n+    bx, bz = L.BUTTONS[\"x\"], b + 12.0\n+    for x in bx:\n+        button = box(\"button\", x - 4.0, x + 4.0, -1.2, 1.0, bz - 1.5, bz + 1.5, m[\"black\"])\n+        parts.append(button)\n+    lx, lz, lr = L.LED\n+    led = solid(\"led\", [[(x, y, z) for x, z in ellipse(lx, b + 12.0, lr, lr, 12)] for y in (-1.0, 1.0)], m[\"black\"])\n+    parts.append(painted(led, m[\"glass\"], \"blue\"))\n+\n+    bm = bmesh.new()\n+    layer = bm.loops.layers.uv.new(\"UVMap\")\n+    pw, ph = 60.0, 9.0\n+    corners = [(-pw / 2, b + 7.5), (pw / 2, b + 7.5), (pw / 2, b + 7.5 + ph), (-pw / 2, b + 7.5 + ph)]   # facing -y\n+    face = bm.faces.new([bm.verts.new((x, -0.15, z)) for x, z in corners])\n+    uv = atlas_uv(L.REGIONS[\"plate\"], pw, ph)\n+    for loop, (x, z) in zip(face.loops, corners):\n+        loop[layer].uv = uv(x + pw / 2, b + 7.5 + ph - z)\n+    parts.append(new_object(\"plate\", bm, m[\"glass\"]))\n+\n+    glass = curved(\"glass\", gw, gh, 0.0, atlas_uv(L.REGIONS[\"rest\"], 1.0, 1.0), m[\"glass\"], 8, 6)\n+    glass.data.transform(Matrix.Translation((0.0, 2.6, GLASS_Z)))\n+    parts.append(glass)\n+    return parts\n+\n+\n+def screens(m):\n+    gw, gh = L.GLASS\n+    return {name: curved(\"screen_\" + name, gw, gh, 0.0, atlas_uv(L.REGIONS[name], 1.0, 1.0), m[\"glass\"], 8, 6)\n+            for name in STATES}\n+\n+\n+# ---- The mouse -------------------------------------------------------------------------------------------------------\n+\n+def mouse(m):\n+    \"\"\"The mouse, its middle at its origin, its buttons towards +y: an egg lofted up to a dome, its top printed (the\n+    buttons' seams, the wheel).\"\"\"\n+    w, length, h = L.MOUSE[\"size\"]\n+    rings = []\n+    for z, scale in ((0.0, 0.94), (3.0, 1.0), (h * 0.55, 0.97), (h * 0.8, 0.84), (h * 0.95, 0.6), (h, 0.3)):\n+        rings.append([(x * scale, y * scale + (1 - scale) * 6.0, z) for x, y in egg(w, length)])\n+    body = solid(\"mouse\", rings, m[\"mouse\"])\n+    for poly in body.data.polygons:\n+        poly.use_smooth = True\n+    top = atlas_uv(L.REGIONS[\"mouse\"], w, length)\n+    paint(body, m[\"mouse\"], lambda co: top(co.x + w / 2, length / 2 - co.y), lambda c, n, name: n.z > 0.3)\n+    paint(body, m[\"mouse\"], swatch(\"grey\"), lambda c, n, name: n.z <= 0.3)\n+    return body\n+\n+\n+def egg(w, length, n=32):\n+    \"\"\"The mouse's outline: an egg, wider at the back (-y).\"\"\"\n+    points = []\n+    for k in range(n):\n+        a = 2 * math.pi * k / n\n+        y = length / 2 * math.sin(a)\n+        x = w / 2 * math.cos(a) * (1.0 - 0.12 * math.sin(a))\n+        points.append((x, y))\n+    return points\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+def timeline(f):\n+    shown = None\n+    if 30 <= f < 60:\n+        shown = \"load\"\n+    elif 60 <= f < 100:\n+        shown = \"title\"\n+    elif 100 <= f < 164:\n+        shown = \"game1\" if ((f - 100) // 8) % 2 == 0 else \"game2\"\n+    elif 164 <= f < 206:\n+        shown = \"menu\"\n+    poses = {\"screen_\" + name: Pose() if name == shown else Pose(scale=HIDDEN) for name in STATES}\n+    glide = smooth(phase(f, 66, 88)) * (1 - smooth(phase(f, 206, 230)))\n+    nudge = smooth(phase(f, 150, 162)) * (1 - smooth(phase(f, 206, 230)))\n+    poses[\"mouse\"] = Pose(x=10.0 * glide, y=8.0 * glide - 6.0 * nudge)\n+    click = max(smooth(phase(f, f0, f0 + 2)) * (1 - smooth(phase(f, f0 + 4, f0 + 7))) for f0 in (92, 160))\n+    poses[\"mouse\"].offset.z -= 1.2 * click                    # a click: the mouse dips under the finger\n+    poses[\"mouse\"].squash = 0.04 * click\n+    return poses\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    printed = bpy.data.images.load(os.path.join(images, \"flash_print.png\"))\n+    colours = dict(L.COLOURS)\n+    textures = dict(glass=printed, mouse=printed)\n+    m = {n: material(n, colours[n], r, textures.get(n)) for n, r in ROUGHNESS.items()}\n+\n+    whole = join(monitor(m), \"flash\")\n+    for poly in whole.data.polygons:\n+        poly.use_smooth = True\n+    whole.data.set_sharp_from_angle(angle=math.radians(40))\n+    glass = screens(m)\n+    rodent = mouse(m)\n+\n+    root = bpy.data.objects.new(\"flash_root\", None)\n+    scene.collection.objects.link(root)\n+    whole.parent = root\n+    for k, part in enumerate(glass.values()):\n+        part.parent = whole\n+        part.location = Vector((0.0, 2.2 - 0.25 * k, GLASS_Z))\n+    mx, my = L.MOUSE[\"at\"]\n+    rodent.parent = whole\n+    rodent.location = Vector((mx, my, 0.0))\n+    rodent.rotation_euler = (0.0, 0.0, math.radians(L.MOUSE[\"turn\"]))\n+    nodes = {**{\"screen_\" + k: v for k, v in glass.items()}, \"mouse\": rodent}\n+\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    rest = Matrix.Translation((mx, my, 0.0)) @ Matrix.Rotation(math.radians(L.MOUSE[\"turn\"]), 4, \"Z\")\n+    points = [tilt @ v.co for v in whole.data.vertices] + [tilt @ rest @ v.co for v in rodent.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [whole, rodent] + list(glass.values())\n+    print(f\"Flash: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(whole.data)} the set), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "47423c178687d210d58513eb6b72981daf938b8f",
          "filename": "tools/console-models/flash_layout.py",
          "status": "added",
          "additions": 45,
          "deletions": 0,
          "changes": 45,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fflash_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fflash_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fflash_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,45 @@\n+\"\"\"Flash's card, a 2000s monitor playing a Flash game: the measurements shared by flash_print.py (which draws its\n+printing and screens) and flash.py (which builds it). Plain Python: Blender's Python imports it too.\n+\n+Flash has no hardware: its games were played in a web browser, the Flash Player's red logo on their preloaders, its\n+right-click menu ending in About Adobe Flash Player. The card is that: a silver 17-inch LCD monitor of the time on its\n+oval foot, a browser window filling its screen with a Flash game in the page (a made-up game, Odyssey), and a mouse\n+beside its foot. Millimetres: x from the monitor's middle (left to right), y back from its panel's front, z up from the\n+table. A 17-inch 4:3 panel's active area is 338 x 270 mm; the rest is typical of the monitors of the time.\n+\n+The Flash logo (F_LOGO) is the white f traced from the Flash Player icon on Wikimedia Commons, normalised to its\n+square (x right, y down, 0 to 1).\n+\n+One texture (ATLAS square, REGIONS): the screens, each the glass's whole area (`rest`, the preloader's splash with the\n+Flash logo; `load`, its bar filling; `title`, Odyssey's title with the pointer on PLAY; `game1` and `game2`, two frames\n+of play; `menu`, the right-click menu over the game), the brand plate under the screen, the mouse's top, and swatches.\n+A region is (x, y, width, height) in pixels.\n+\"\"\"\n+\n+ATLAS = 1024\n+\n+PANEL = dict(width=370.0, height=306.0, depth=52.0, bottom=92.0, bezel=16.0, lip=24.0)   # the bezel; the deeper bottom\n+GLASS = (338.0, 270.0)\n+STAND = dict(base=(240.0, 196.0, 16.0), base_y=70.0, neck=(70.0, 30.0), neck_y=96.0, top=150.0)\n+LED = (156.0, -14.0, 2.2)                                   # the power light: x, z from the panel's bottom edge, radius\n+BUTTONS = dict(x=[96.0 + 13.0 * k for k in range(4)], z=-12.0, size=(8.0, 3.0))   # under the screen, at the right\n+\n+MOUSE = dict(at=(196.0, -62.0), turn=-12.0, size=(64.0, 116.0, 36.0), split=50.0)   # w, length, h; buttons' length\n+\n+F_LOGO = [(0.742, 0.179), (0.761, 0.179), (0.761, 0.307), (0.714, 0.312), (0.693, 0.319), (0.664, 0.338),\n+          (0.628, 0.377), (0.597, 0.435), (0.695, 0.436), (0.695, 0.563), (0.547, 0.563), (0.506, 0.655),\n+          (0.477, 0.703), (0.434, 0.752), (0.384, 0.788), (0.351, 0.804), (0.318, 0.815), (0.273, 0.823),\n+          (0.242, 0.824), (0.242, 0.697), (0.283, 0.692), (0.320, 0.678), (0.346, 0.659), (0.373, 0.627),\n+          (0.410, 0.557), (0.463, 0.422), (0.497, 0.347), (0.518, 0.312), (0.545, 0.276), (0.583, 0.238),\n+          (0.636, 0.205), (0.694, 0.186)]\n+\n+COLOURS = dict(silver=\"#B8BBC0\", black=\"#121214\", glass=\"#FFFFFF\", mouse=\"#C9CBCF\", dark=\"#2A2B2E\")\n+\n+SCREEN_PX = (400, 320)\n+REGIONS = dict(rest=(0, 0) + SCREEN_PX, load=(408, 0) + SCREEN_PX, title=(0, 328) + SCREEN_PX,\n+               game1=(408, 328) + SCREEN_PX, game2=(0, 656) + SCREEN_PX, menu=(408, 656) + SCREEN_PX,\n+               plate=(816, 0, 208, 60), mouse=(816, 68, 120, 216))\n+SWATCHES = dict(silver=(832, 312), black=(864, 312), blue=(896, 312), grey=(928, 312))\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s"
        },
        {
          "sha": "ed7d212bcb32d7f9aa80b3aa996a007ce6d33c2f",
          "filename": "tools/console-models/flash_print.py",
          "status": "added",
          "additions": 220,
          "deletions": 0,
          "changes": 220,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fflash_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fflash_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fflash_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,220 @@\n+\"\"\"Flash's card's screens and printing, for the console theme's Flash model (tools/console-models/flash.py).\n+\n+Draws its atlas, its regions as flash_layout.py places them: the screens, each a browser window of the 2000s filling\n+the glass (its blue title bar, its menus, the address bar, the page with a Flash game in it, the status bar), the game\n+in its states (the preloader's splash with the Flash logo, its bar filling; Odyssey's title, the pointer on PLAY; two\n+frames of play; the right-click menu ending in About Adobe Flash Player); the brand plate under the screen; the mouse's\n+top; and the swatches small parts take their colour from. Odyssey and the site it's on (odyssey.example, a domain kept\n+for examples) are made up. Run it with the system Python (Pillow):\n+\n+    python tools/console-models/flash_print.py <out folder>\n+\n+It writes flash_print.png there.\n+\"\"\"\n+\n+import os\n+import random\n+import sys\n+\n+from PIL import Image, ImageDraw, ImageFont\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import flash_layout as L  # noqa: E402\n+from psp_print import ship, space  # noqa: E402\n+from retro_pc_print import rgb  # noqa: E402\n+from windows_pc_print import FONTS, gradient  # noqa: E402\n+\n+SS = 3                       # the screens drawn this many times larger, then resampled\n+W, H = L.SCREEN_PX\n+MOVIE = (40, 74, 360, 290)   # the Flash movie in the page: x0, y0, x1, y1\n+\n+\n+def font(name, size):\n+    return ImageFont.truetype(FONTS + name, max(1, round(size * SS)))\n+\n+\n+def box(x0, y0, x1, y1):\n+    return [x0 * SS, y0 * SS, x1 * SS, y1 * SS]\n+\n+\n+def flash_logo(img, cx, cy, s):\n+    \"\"\"The Flash Player's logo, s across, its middle at (cx, cy): the red square, the white f.\"\"\"\n+    d = ImageDraw.Draw(img)\n+    tile = gradient((round(s * SS), round(s * SS)), [(0.0, (236, 32, 36)), (1.0, (150, 18, 24))])\n+    mask = Image.new(\"L\", tile.size, 0)\n+    ImageDraw.Draw(mask).rounded_rectangle([0, 0, tile.width - 1, tile.height - 1], radius=round(s * 0.08 * SS),\n+                                           fill=255)\n+    img.paste(tile, (round((cx - s / 2) * SS), round((cy - s / 2) * SS)), mask)\n+    d.polygon([((cx - s / 2 + x * s) * SS, (cy - s / 2 + y * s) * SS) for x, y in L.F_LOGO], fill=(255, 255, 255))\n+\n+\n+def pointer(d, x, y, hand=False):\n+    \"\"\"The mouse pointer, its tip at (x, y): the arrow, or the hand over a link.\"\"\"\n+    if hand:\n+        pts = [(0, 0), (2, -1), (3, 0), (3, 5), (5, 4), (7, 5), (9, 5), (11, 7), (11, 13), (9, 16), (3, 16), (0, 10),\n+               (-2, 7), (-1, 6), (0, 7)]\n+    else:\n+        pts = [(0, 0), (0, 15), (4, 11), (7, 17), (9, 16), (6, 10), (11, 10)]\n+    d.polygon([((x + a) * SS, (y + b) * SS) for a, b in pts], fill=(255, 255, 255), outline=(0, 0, 0),\n+              width=max(1, SS // 2))\n+\n+\n+def browser():\n+    \"\"\"The browser window: the title bar, menus, address bar, the page round the movie, the status bar.\"\"\"\n+    img = Image.new(\"RGB\", (W * SS, H * SS), (236, 233, 216))\n+    d = ImageDraw.Draw(img)\n+    bar = gradient((W * SS, 18 * SS), [(0.0, (0, 88, 238)), (0.15, (61, 149, 255)), (0.5, (0, 84, 227)),\n+                                       (1.0, (0, 70, 206))])\n+    img.paste(bar.convert(\"RGB\"), (0, 0))\n+    d.text((6 * SS, 9 * SS), \"Odyssey - Play it now! - Odyssey Navigator\", font=font(\"tahomabd.ttf\", 7.5),\n+           fill=(255, 255, 255), anchor=\"lm\")\n+    for k, colour in enumerate(((0, 84, 227), (0, 84, 227), (224, 76, 48))):\n+        x = W - 46 + k * 15\n+        d.rounded_rectangle(box(x, 3, x + 13, 15), radius=2 * SS, fill=colour, outline=(255, 255, 255),\n+                            width=max(1, SS // 2))\n+    d.line(box(W - 43, 12, W - 37, 12), fill=(255, 255, 255), width=SS)\n+    d.rectangle(box(W - 28, 6, W - 21, 12), outline=(255, 255, 255), width=SS)\n+    d.line(box(W - 13, 6, W - 7, 12), fill=(255, 255, 255), width=SS)\n+    d.line(box(W - 13, 12, W - 7, 6), fill=(255, 255, 255), width=SS)\n+    for k, word in enumerate((\"File\", \"Edit\", \"View\", \"Favorites\", \"Tools\", \"Help\")):\n+        d.text(((6 + [0, 22, 44, 68, 108, 136][k]) * SS, 25 * SS), word, font=font(\"tahoma.ttf\", 7.0),\n+               fill=(0, 0, 0), anchor=\"lm\")\n+    d.line(box(0, 32, W, 32), fill=(172, 168, 153), width=SS)\n+    d.text((6 * SS, 41 * SS), \"Address\", font=font(\"tahoma.ttf\", 7.0), fill=(80, 80, 80), anchor=\"lm\")\n+    d.rectangle(box(40, 35, W - 40, 47), fill=(255, 255, 255), outline=(127, 157, 185), width=SS)\n+    d.text((44 * SS, 41 * SS), \"http://odyssey.example/arcade/odyssey.swf\", font=font(\"tahoma.ttf\", 7.0),\n+           fill=(0, 0, 0), anchor=\"lm\")\n+    d.rounded_rectangle(box(W - 34, 35, W - 6, 47), radius=2 * SS, fill=(60, 170, 60))\n+    d.text(((W - 20) * SS, 41 * SS), \"Go\", font=font(\"tahomabd.ttf\", 7.0), fill=(255, 255, 255), anchor=\"mm\")\n+    page = gradient((W * SS, (H - 64) * SS), [(0.0, (26, 34, 70)), (1.0, (10, 12, 30))])\n+    img.paste(page.convert(\"RGB\"), (0, 50 * SS))\n+    d.text((W / 2 * SS, 62 * SS), \"ODYSSEY ARCADE  \u00b7  FREE ONLINE GAMES\", font=font(\"verdanab.ttf\", 7.0),\n+           fill=(255, 196, 60), anchor=\"mm\")\n+    d.rectangle(box(0, H - 14, W, H), fill=(236, 233, 216))\n+    d.line(box(0, H - 14, W, H - 14), fill=(172, 168, 153), width=SS)\n+    d.text((6 * SS, (H - 7) * SS), \"Done\", font=font(\"tahoma.ttf\", 7.0), fill=(0, 0, 0), anchor=\"lm\")\n+    d.text(((W - 8) * SS, (H - 7) * SS), \"Internet\", font=font(\"tahoma.ttf\", 7.0), fill=(0, 0, 0), anchor=\"rm\")\n+    return img\n+\n+\n+def movie_space(seed=0):\n+    \"\"\"Odyssey's backdrop in the movie's area.\"\"\"\n+    x0, y0, x1, y1 = MOVIE\n+    art = space(((x1 - x0) * SS, (y1 - y0) * SS))\n+    rng = random.Random(seed)\n+    d = ImageDraw.Draw(art)\n+    for _ in range(30):\n+        x, y = rng.randrange(art.width), rng.randrange(art.height)\n+        d.ellipse([x - SS, y - SS, x + SS, y + SS], fill=(255, 255, 255, 255))\n+    return art.convert(\"RGB\")\n+\n+\n+def screen(state):\n+    img = browser()\n+    d = ImageDraw.Draw(img)\n+    x0, y0, x1, y1 = MOVIE\n+    cx, cy = (x0 + x1) / 2, (y0 + y1) / 2\n+    if state in (\"rest\", \"load\"):\n+        d.rectangle(box(x0, y0, x1, y1), fill=(16, 16, 18))\n+        flash_logo(img, cx, cy - 20, 92)\n+        d.text((cx * SS, (cy + 44) * SS), \"Loading...\", font=font(\"verdana.ttf\", 8.0), fill=(220, 220, 220),\n+               anchor=\"mm\")\n+        d.rectangle(box(cx - 80, cy + 56, cx + 80, cy + 64), outline=(200, 200, 200), width=SS)\n+        if state == \"load\":\n+            d.rectangle(box(cx - 78, cy + 58, cx - 78 + 156 * 0.7, cy + 62), fill=(236, 40, 40))\n+            d.text(((cx + 92) * SS, (cy + 60) * SS), \"70%\", font=font(\"verdana.ttf\", 7.0), fill=(220, 220, 220),\n+                   anchor=\"lm\")\n+    else:\n+        img.paste(movie_space(1 if state == \"game2\" else 0), (x0 * SS, y0 * SS))\n+        if state == \"title\":\n+            d.text((cx * SS, (y0 + 52) * SS), \"ODYSSEY\", font=font(\"ariblk.ttf\", 34.0), fill=(255, 214, 70),\n+                   anchor=\"mm\", stroke_width=SS * 2, stroke_fill=(120, 30, 10))\n+            ship(d, (cx - 60) * SS, (cy + 20) * SS, 1.8 * SS, \"#FFFFFF\", \"#FFB23C\")\n+            d.rounded_rectangle(box(cx - 40, cy + 50, cx + 40, cy + 74), radius=6 * SS, fill=(240, 120, 30),\n+                                outline=(255, 230, 160), width=SS)\n+            d.text((cx * SS, (cy + 62) * SS), \"PLAY\", font=font(\"ariblk.ttf\", 12.0), fill=(255, 255, 255),\n+                   anchor=\"mm\")\n+            pointer(d, cx + 14, cy + 62, hand=True)\n+        else:\n+            frame = 0 if state in (\"game1\", \"menu\") else 1\n+            sx = cx - 90 + frame * 12\n+            ship(d, sx * SS, (cy + 10) * SS, 1.5 * SS, \"#FFFFFF\", \"#FFB23C\")\n+            d.line(box(sx + 26, cy + 10, sx + 96 + frame * 30, cy + 10), fill=(255, 236, 80), width=2 * SS)\n+            for ax, ay, r in ((cx + 60, cy + 10 - frame * 4, 16), (cx + 110, cy - 50, 12), (cx + 20, cy + 60, 10)):\n+                if frame == 1 and ax == cx + 60:\n+                    for k in range(14):                        # it bursts\n+                        a, b = random.Random(k).uniform(-18, 18), random.Random(k + 40).uniform(-18, 18)\n+                        d.ellipse(box(ax + a - 2, ay + b - 2, ax + a + 2, ay + b + 2), fill=(255, 200, 60))\n+                    continue\n+                d.ellipse(box(ax - r, ay - r, ax + r, ay + r), fill=(140, 96, 70), outline=(90, 60, 44), width=SS)\n+                d.ellipse(box(ax - r * 0.4, ay - r * 0.5, ax, ay - r * 0.1), fill=(170, 124, 96))\n+            d.text(((x0 + 8) * SS, (y0 + 10) * SS), f\"SCORE {1200 + frame * 250:06d}\", font=font(\"verdanab.ttf\", 8.0),\n+                   fill=(255, 255, 255), anchor=\"lm\")\n+            if state == \"menu\":\n+                mx, my = cx + 30, cy - 40\n+                items = [\"Zoom In\", \"Zoom Out\", \"Show All\", \"Quality\", \"\", \"Settings...\", \"Global Settings...\", \"\",\n+                         \"About Adobe Flash Player 10...\"]\n+                heights = [6 if not item else 15 for item in items]\n+                width = 150\n+                d.rectangle(box(mx + 3, my + 3, mx + width + 3, my + sum(heights) + 7), fill=(30, 30, 30))\n+                d.rectangle(box(mx, my, mx + width, my + sum(heights) + 4), fill=(245, 245, 245),\n+                            outline=(130, 130, 130), width=SS)\n+                y = my + 2\n+                for item, h in zip(items, heights):\n+                    if not item:\n+                        d.line(box(mx + 4, y + 3, mx + width - 4, y + 3), fill=(190, 190, 190), width=SS)\n+                    else:\n+                        if item.startswith(\"About\"):\n+                            d.rectangle(box(mx + 2, y, mx + width - 2, y + h), fill=(49, 106, 197))\n+                        d.text(((mx + 14) * SS, (y + h / 2) * SS), item, font=font(\"tahoma.ttf\", 7.0),\n+                               fill=(255, 255, 255) if item.startswith(\"About\") else (0, 0, 0), anchor=\"lm\")\n+                        if item == \"Quality\":\n+                            d.text(((mx + width - 8) * SS, (y + h / 2) * SS), \"\u25b8\", font=font(\"seguisym.ttf\", 7.0),\n+                                   fill=(0, 0, 0), anchor=\"mm\")\n+                    y += h\n+                pointer(d, mx + 60, my + sum(heights) - 4)\n+            else:\n+                pointer(d, cx + 30, cy - 40)\n+    return img.resize(L.SCREEN_PX, Image.LANCZOS)\n+\n+\n+def plate():\n+    x, y, w, h = L.REGIONS[\"plate\"]\n+    img = Image.new(\"RGB\", (w * SS, h * SS), rgb(L.COLOURS[\"silver\"]))\n+    d = ImageDraw.Draw(img)\n+    d.text((w / 2 * SS, h / 2 * SS), \"ODYSSEY\", font=font(\"arialbd.ttf\", 22.0), fill=(70, 72, 78), anchor=\"mm\")\n+    return img.resize((w, h), Image.LANCZOS)\n+\n+\n+def mouse_top():\n+    x, y, w, h = L.REGIONS[\"mouse\"]\n+    img = Image.new(\"RGB\", (w * SS, h * SS), rgb(L.COLOURS[\"mouse\"]))\n+    d = ImageDraw.Draw(img)\n+    split = L.MOUSE[\"split\"] / L.MOUSE[\"size\"][1] * h\n+    d.line(box(0, split, w, split), fill=(110, 112, 118), width=SS)\n+    d.line(box(w / 2, 0, w / 2, split), fill=(110, 112, 118), width=SS)\n+    d.rounded_rectangle(box(w / 2 - 6, split * 0.25, w / 2 + 6, split * 0.75), radius=5 * SS, fill=(60, 62, 66))\n+    return img.resize((w, h), Image.LANCZOS)\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), (10, 10, 12))\n+    for state in (\"rest\", \"load\", \"title\", \"game1\", \"game2\", \"menu\"):\n+        x, y, _, _ = L.REGIONS[state]\n+        image.paste(screen(state), (x, y))\n+    for name, art in ((\"plate\", plate()), (\"mouse\", mouse_top())):\n+        x, y, _, _ = L.REGIONS[name]\n+        image.paste(art, (x, y))\n+    d = ImageDraw.Draw(image)\n+    for name, colour in ((\"silver\", rgb(L.COLOURS[\"silver\"])), (\"black\", rgb(L.COLOURS[\"black\"])),\n+                         (\"blue\", (70, 160, 255)), (\"grey\", rgb(L.COLOURS[\"mouse\"]))):\n+        x, y = L.SWATCHES[name]\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colour)\n+    path = os.path.join(out, \"flash_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "0c0a685ddfd1cc38e7483ef9f01a95b163017c6b",
          "filename": "tools/console-models/gameandwatch.py",
          "status": "added",
          "additions": 247,
          "deletions": 0,
          "changes": 247,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fgameandwatch.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fgameandwatch.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fgameandwatch.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,247 @@\n+\"\"\"The console theme's Game & Watch system model (godot/themes/console/models/systems/gameandwatch.glb): a Game & Watch\n+Multi Screen, open on the table, a made-up game on its two screens.\n+\n+Built in Blender from the real handheld's proportions (gameandwatch_layout.py has the measurements, taken from\n+photographs). Each half is a slab, its edges rounded, its face printed; the lower half lies on the table, the D-pad,\n+GAME A, GAME B, TIME and JUMP standing on its face; the upper half turns on the hinges along the lower half's back edge,\n+open, its title plate and side panels printed. The printing is one texture drawn by gameandwatch_print.py: the two\n+faces (each with its screen at rest: the clock), the screens while the game's played, and swatches.\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: the right end towards the viewer, the top\n+tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m) holding the lower half; the\n+lower half holding the D-pad, GAME A, JUMP, three screens over its screen and the upper half (its origin on the hinge);\n+the upper half holding three screens over its own.\n+\n+The `focused` clip (8 s, looping) plays a game: GAME A is pressed; the rocks fall down the top screen while the ship\n+dodges along the bottom one, the D-pad rocking left and right, JUMP firing; then the game ends, the handheld shuts with\n+a snap and springs open again, back to the clock.\n+\n+Four materials (case, key, glass, print) and one texture, within the system model budget. Run from the repo root:\n+\n+    python tools/console-models/gameandwatch_print.py artifacts/gameandwatch\n+    blender -b --factory-startup --python tools/console-models/gameandwatch.py -- artifacts/gameandwatch godot/themes/console/models/systems/gameandwatch.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import gameandwatch_layout as L  # noqa: E402\n+from amiga import curved, paint, rounded, solid  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, key, phase, smooth  # noqa: E402\n+from mega_drive_cartridge import bevel, box, export, join, material, triangles  # noqa: E402\n+\n+TILT_YAW = -20.0     # degrees about the vertical: the right end comes towards the viewer\n+TILT_PITCH = 22.0    # degrees about the horizontal: the top tips up towards the viewer\n+ROUGHNESS = dict(case=0.45, key=0.4, glass=0.3, print=0.45)\n+W, D, T = L.WIDTH, L.DEPTH, L.HALF\n+\n+\n+# ---- UVs -------------------------------------------------------------------------------------------------------------\n+\n+def atlas_uv(region, w, h):\n+    \"\"\"A face drawn w x h into region: (a, b) millimetres across it (a to the right, b down from its top) to UVs.\"\"\"\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+LOWER = atlas_uv(L.REGIONS[\"lower\"], W, D)\n+UPPER = atlas_uv(L.REGIONS[\"upper\"], W, D)\n+\n+\n+def lower_uv(co):\n+    return LOWER(co.x + W / 2, D - co.y)\n+\n+\n+def upper_uv(co):\n+    \"\"\"The upper half's face, in its own frame (y back from the hinge: its face lies from y = -D to 0).\"\"\"\n+    return UPPER(co.x + W / 2, D + co.y)\n+\n+\n+def swatch(name):\n+    x, y = L.SWATCHES[name]\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+def painted(obj, m, name):\n+    paint(obj, m[\"print\"], swatch(name), lambda c, n, _name: True)\n+    return obj\n+\n+\n+def upright(name, points, z0, z1, mat):\n+    \"\"\"A solid from an (x, y) plan, from z0 up to z1.\"\"\"\n+    return solid(name, [[(x, y, z) for x, y in points] for z in (z0, z1)], mat)\n+\n+\n+def circle(cx, cy, r, n=28):\n+    return [(cx + r * math.cos(2 * math.pi * k / n), cy + r * math.sin(2 * math.pi * k / n)) for k in range(n)]\n+\n+\n+# ---- The halves ------------------------------------------------------------------------------------------------------\n+\n+def lower_half(m):\n+    \"\"\"The lower half: its slab, its face printed; GAME B and TIME; the hinges' knuckles along its back edge.\"\"\"\n+    slab = box(\"gameandwatch\", -W / 2, W / 2, 0.0, D, 0.0, T, m[\"case\"])\n+    bevel(slab, 2.0, segments=2)\n+    paint(slab, m[\"print\"], lower_uv, lambda c, n, name: n.z > 0.9)\n+    parts = [slab]\n+    sw, sh = L.SMALL[\"size\"]\n+    for name in (\"game_b\", \"time\"):\n+        u, v = L.SMALL[name]\n+        pill = upright(name, [(u + a, v + b) for a, b in rounded(0.0, 0.0, sw, sh, sh / 2 - 0.01, 4)], T - 1.0,\n+                       T + L.SMALL[\"height\"], m[\"key\"])\n+        bevel(pill, 0.5, segments=1)\n+        parts.append(pill)\n+    for x0, x1 in ((-W / 2 + 4.0, -W / 2 + 20.0), (W / 2 - 20.0, W / 2 - 4.0)):\n+        knuckle = solid(\"knuckle\", [[(x, D + 0.5 + 2.6 * math.cos(a), T + 2.6 * math.sin(a))\n+                                     for a in (2 * math.pi * k / 12 for k in range(12))] for x in (x0, x1)], m[\"case\"])\n+        parts.append(knuckle)\n+    return join(parts, \"gameandwatch\")\n+\n+\n+def upper_half(m):\n+    \"\"\"The upper half in its own frame (its origin on the hinge, shut: lying face down on the lower half).\"\"\"\n+    slab = box(\"upper\", -W / 2, W / 2, -D, 0.0, 0.0, T, m[\"case\"])\n+    bevel(slab, 2.0, segments=2)\n+    paint(slab, m[\"print\"], upper_uv, lambda c, n, name: n.z < -0.9)\n+    tab = box(\"latch\", -6.0, 6.0, -D - 2.0, -D + 1.0, 4.0, T + 0.5, m[\"case\"])\n+    bevel(tab, 0.8, segments=1)\n+    return join([slab, tab], \"upper\")\n+\n+\n+def dpad(m):\n+    s, a = L.DPAD[\"size\"] / 2, L.DPAD[\"arm\"] / 2\n+    cross = [(a, s), (-a, s), (-a, a), (-s, a), (-s, -a), (-a, -a), (-a, -s), (a, -s), (a, -a), (s, -a), (s, a), (a, a)]\n+    part = upright(\"dpad\", cross, -1.0, L.DPAD[\"height\"], m[\"key\"])\n+    bevel(part, 0.6, segments=1)\n+    return part, Vector((*L.DPAD[\"at\"], T))\n+\n+\n+def game_a(m):\n+    sw, sh = L.SMALL[\"size\"]\n+    part = upright(\"game_a\", rounded(0.0, 0.0, sw, sh, sh / 2 - 0.01, 4), -1.0, L.SMALL[\"height\"], m[\"key\"])\n+    bevel(part, 0.5, segments=1)\n+    return part, Vector((*L.SMALL[\"game_a\"], T))\n+\n+\n+def jump(m):\n+    part = solid(\"jump\", [[(x, y, z) for x, y in circle(0.0, 0.0, L.JUMP[\"r\"] * k)] for k, z in\n+                          ((1.0, -1.0), (1.0, L.JUMP[\"height\"] - 0.8), (0.8, L.JUMP[\"height\"]))], m[\"key\"])\n+    return part, Vector((*L.JUMP[\"at\"], T))\n+\n+\n+def screen(m, name, size, upper):\n+    w, h = size\n+    part = curved(name, w, h, 0.0, atlas_uv(L.REGIONS[name], 1.0, 1.0), m[\"glass\"], nu=1, nv=1)\n+    # face up on the lower half (its picture's top towards the hinge), face down on the upper half\n+    part.data.transform(Matrix.Rotation(math.radians(90.0 if upper else -90.0), 4, \"X\"))\n+    return part\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+def tap(f, f0, hold=2):\n+    \"\"\"A press at f0: down in three frames, held, back up in four.\"\"\"\n+    return smooth(phase(f, f0, f0 + 3)) * (1 - smooth(phase(f, f0 + 3 + hold, f0 + 7 + hold)))\n+\n+\n+FRAMES = [(40, 1), (62, 2), (84, 3), (106, 1), (128, 2), (150, 3)]   # each step of the game: its frame, its screen\n+\n+\n+def timeline(f):\n+    shown = 0\n+    for f0, k in FRAMES:\n+        if f0 <= f < 172:\n+            shown = k\n+    poses = {}\n+    for k in (1, 2, 3):\n+        on = Pose() if shown == k else Pose(scale=HIDDEN)\n+        poses[f\"lower_{k}\"] = on\n+        poses[f\"upper_{k}\"] = on\n+    # the D-pad rocks the ship's way (left for frame 1, right for frame 3); JUMP fires on frame 2\n+    rock = 0.0\n+    for f0, k in FRAMES:\n+        if k != 2:\n+            rock += (-1 if k == 1 else 1) * tap(f, f0 - 3, 3)\n+    poses[\"dpad\"] = Pose(roll=6.0 * rock)\n+    poses[\"jump\"] = Pose(z=-1.4 * max(tap(f, f0 - 3) for f0, k in FRAMES if k == 2))\n+    poses[\"game_a\"] = Pose(z=-1.0 * tap(f, 28))\n+    # the snap shut and open again\n+    if 180 <= f < 194:\n+        shut = ease_in(phase(f, 180, 194))\n+    elif 194 <= f < 200:\n+        shut = 1.0\n+    elif 200 <= f < 222:\n+        shut = 1 - back_out(phase(f, 200, 222), 1.4)\n+    else:\n+        shut = 0.0\n+    poses[\"upper\"] = Pose(pitch=-L.OPEN * shut)\n+    return poses\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    printed = bpy.data.images.load(os.path.join(images, \"gameandwatch_print.png\"))\n+    m = {n: material(n, L.COLOURS[n], r, printed if n in (\"print\", \"glass\") else None) for n, r in ROUGHNESS.items()}\n+\n+    lower = lower_half(m)\n+    upper = upper_half(m)\n+    parts = {\"dpad\": dpad(m), \"game_a\": game_a(m), \"jump\": jump(m)}\n+    (lu, lv), (uu, uv) = L.LOWER_LCD[\"at\"], L.UPPER_LCD[\"at\"]\n+    lower_screens = {f\"lower_{k}\": (screen(m, f\"lower_{k}\", L.LOWER_LCD[\"size\"], False), Vector((lu, lv, T + 0.15)))\n+                     for k in (1, 2, 3)}\n+    upper_screens = {f\"upper_{k}\": (screen(m, f\"upper_{k}\", L.UPPER_LCD[\"size\"], True), Vector((uu, -uv, -0.15)))\n+                     for k in (1, 2, 3)}\n+    for obj in [lower, upper] + [p for p, _ in parts.values()]:\n+        for poly in obj.data.polygons:\n+            poly.use_smooth = True\n+        obj.data.set_sharp_from_angle(angle=math.radians(35))\n+\n+    # The node tree: the root (the view and the fit) holds the lower half; the lower half its parts and the upper half,\n+    # open on its hinge; the upper half its screens.\n+    root = bpy.data.objects.new(\"gameandwatch_root\", None)\n+    scene.collection.objects.link(root)\n+    lower.parent = root\n+    for part, where in list(parts.values()) + list(lower_screens.values()):\n+        part.parent = lower\n+        part.location = where\n+    upper.parent = lower\n+    upper.location = (0.0, D, T)\n+    upper.rotation_euler = (math.radians(L.OPEN), 0.0, 0.0)\n+    for part, where in upper_screens.values():\n+        part.parent = upper\n+        part.location = where\n+    nodes = {\"upper\": upper, **{n: p for n, (p, _) in parts.items()}, **{n: p for n, (p, _) in lower_screens.items()},\n+             **{n: p for n, (p, _) in upper_screens.items()}}\n+\n+    # The three-quarter view, then the spec's fit of the open handheld at rest, as the root's transform.\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    hinge = Matrix.Translation((0.0, D, T)) @ Matrix.Rotation(math.radians(L.OPEN), 4, \"X\")\n+    points = [tilt @ v.co for v in lower.data.vertices] + [tilt @ hinge @ v.co for v in upper.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = ([lower, upper] + [p for p, _ in parts.values()] + [p for p, _ in lower_screens.values()]\n+              + [p for p, _ in upper_screens.values()])\n+    print(f\"Game & Watch: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(lower.data)} the lower half), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "c08569f2497ffa5dc4e1088d171967c64aa5d700",
          "filename": "tools/console-models/gameandwatch_layout.py",
          "status": "added",
          "additions": 51,
          "deletions": 0,
          "changes": 51,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fgameandwatch_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fgameandwatch_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fgameandwatch_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,51 @@\n+\"\"\"The Game & Watch's measurements, shared by gameandwatch_print.py (which draws its printing and screens) and\n+gameandwatch.py (which builds it). Plain Python: Blender's Python imports it too.\n+\n+A Game & Watch Multi Screen (1982) in the brown and cream of Donkey Kong II, open on the table, a made-up game on its\n+two screens: Odyssey. Each half is 122 x 74 mm and 12.5 mm thick (the Digital Game Museum's 4 3/4 x 3 x 1 inches for\n+the closed handheld). Millimetres: x from its middle (left to right), y back from the lower half's front edge, z up from\n+the table; on each half's face, u across from its middle and v from its front edge (the lower half's) or from the hinge\n+(the upper half's). The rest was measured from photographs of a Donkey Kong II on Wikimedia Commons (from above, from in\n+front, open at an angle), scaled by its width.\n+\n+The lower half: its brown body, its face cream with the brown panels at its sides: GAME & WATCH and the D-pad\n+(CONTROLLER) at the left, GAME A, GAME B and TIME with ALARM and ACL, and JUMP at the right; the screen in the middle,\n+the Nintendo plate under it. The upper half, hinged at the lower half's back edge, open: its face cream, the title\n+plate over its screen, the raised panels at its sides, four screws; the ribbon between the halves.\n+\n+One texture (ATLAS square, REGIONS): `lower` and `upper` (the two faces, each with its screen at rest: the clock on the\n+lower one), the screens while the game's played (`lower_1` to `lower_3`, `upper_1` to `upper_3`: an LCD's black\n+segments over its printed backdrop), and swatches. A region is (x, y, width, height) in pixels.\n+\"\"\"\n+\n+ATLAS = 1024\n+\n+WIDTH, DEPTH, HALF = 122.0, 74.0, 12.5               # each half: w x d, its thickness\n+OPEN = -115.0                                         # the upper half's angle from shut, about the hinge\n+\n+# The lower half's face, (u, v): v from its front edge.\n+LOWER_LCD = dict(at=(-1.0, 39.0), size=(54.0, 35.0))\n+DPAD = dict(at=(-46.0, 23.0), size=16.0, arm=5.6, height=2.6)\n+SMALL = dict(game_a=(41.5, 63.0), game_b=(41.5, 54.5), time=(41.5, 46.0), size=(7.4, 3.4), height=1.6)\n+LIGHTS = dict(alarm=(49.5, 58.0), acl=(49.5, 50.5))\n+JUMP = dict(at=(43.5, 23.0), r=4.8, height=2.4)\n+BADGE = dict(at=(-46.0, 57.0), size=(14.0, 10.0))\n+NINTENDO = dict(at=(-1.0, 12.0), size=(31.0, 6.0))\n+PANELS = dict(left=(-58.0, -33.0), right=(33.0, 58.0))   # the brown side panels, u\n+\n+# The upper half's face, (u, v): v from the hinge.\n+UPPER_LCD = dict(at=(0.2, 34.6), size=(56.0, 37.0))\n+TITLE = dict(at=(0.0, 61.0), size=(31.0, 6.0))\n+SCREWS = [(-30.0, 59.0), (30.0, 59.0), (-43.0, 10.0), (43.0, 10.0)]\n+SIDE = dict(left=(-56.0, -34.0), right=(34.0, 56.0), v=(4.0, 70.0))   # the raised side panels\n+\n+COLOURS = dict(case=\"#6A3424\", key=\"#141416\", glass=\"#FFFFFF\", print=\"#FFFFFF\")\n+\n+SCREEN_PX = (184, 120)\n+REGIONS = dict(lower=(0, 0, 640, 388), upper=(0, 396, 640, 388),\n+               lower_1=(648, 0) + SCREEN_PX, lower_2=(840, 0) + SCREEN_PX, lower_3=(648, 128) + SCREEN_PX,\n+               upper_1=(840, 128) + SCREEN_PX, upper_2=(648, 256) + SCREEN_PX, upper_3=(840, 256) + SCREEN_PX)\n+SWATCHES = dict(brown=(664, 400), cream=(696, 400), dark=(728, 400), lit=(760, 400), metal=(792, 400))\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s"
        },
        {
          "sha": "4205468bfc95329c343278f1260622eea8bb768d",
          "filename": "tools/console-models/gameandwatch_print.py",
          "status": "added",
          "additions": 199,
          "deletions": 0,
          "changes": 199,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fgameandwatch_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fgameandwatch_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fgameandwatch_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,199 @@\n+\"\"\"The Game & Watch's printing and screens, for the console theme's Game & Watch model\n+(tools/console-models/gameandwatch.py).\n+\n+Draws its atlas, its regions as gameandwatch_layout.py places them, each in millimetres as the model's planar UVs map\n+it: the lower half's face (the brown panels, GAME & WATCH, CONTROLLER, the buttons' names and their rings, ALARM and\n+ACL, JUMP, the Nintendo plate, the ribbon along its back, its screen at rest showing the clock) and the upper half's\n+(the title plate, ODYSSEY, the raised side panels, the screws, its screen at rest); the screens while the game's played,\n+three frames each (an LCD's black segments, the ship along the bottom and the rocks falling at the top, over the\n+printed backdrop of a made-up game, Odyssey); and the swatches small parts take their colour from. Run it with the\n+system Python (Pillow):\n+\n+    python tools/console-models/gameandwatch_print.py <out folder>\n+\n+It writes gameandwatch_print.png there.\n+\"\"\"\n+\n+import math\n+import os\n+import random\n+import sys\n+\n+from PIL import Image, ImageDraw\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import gameandwatch_layout as L  # noqa: E402\n+from cdi_print import digit  # noqa: E402\n+from retro_pc_print import rgb, shade  # noqa: E402\n+from windows_pc_print import Region  # noqa: E402\n+\n+BROWN = rgb(L.COLOURS[\"case\"])\n+CREAM = (232, 226, 210)\n+PANEL = (240, 236, 224)\n+INK = (64, 40, 30)\n+SEGMENT = (24, 26, 22)\n+LCD = (200, 204, 184)\n+\n+\n+# ---- The LCD screens -------------------------------------------------------------------------------------------------\n+\n+def backdrop(reg, w, h, upper):\n+    \"\"\"The printed picture behind the LCD: a pale planet and stars above, the ground's line below.\"\"\"\n+    rng = random.Random(23 if upper else 29)\n+    for _ in range(26):\n+        reg.circle(rng.uniform(1.0, w - 1.0), rng.uniform(1.0, h - 1.0), 0.25, (150, 160, 190))\n+    if upper:\n+        reg.circle(w * 0.78, h * 0.32, h * 0.22, (232, 176, 140))\n+        reg.draw.arc(reg.box(w * 0.78 - h * 0.4, h * 0.32 - h * 0.08, w * 0.78 + h * 0.4, h * 0.32 + h * 0.08), 160,\n+                     380, fill=(210, 160, 200), width=reg.width(0.5))\n+    else:\n+        reg.line([(0.0, h - 5.0), (w, h - 5.0)], (150, 130, 110), 0.8)\n+        for k in range(9):\n+            reg.line([(k * w / 8, h - 5.0), (k * w / 8 - 3.0, h)], (170, 150, 130), 0.4)\n+\n+\n+def ship_segment(reg, x, y, s, colour):\n+    \"\"\"The ship as an LCD segment: an arrowhead pointing up, s tall, its middle at (x, y).\"\"\"\n+    reg.poly([(x, y - s / 2), (x + s * 0.42, y + s / 2), (x, y + s * 0.28), (x - s * 0.42, y + s / 2)], colour)\n+\n+\n+def rock_segment(reg, x, y, s, colour):\n+    points = [(x + s / 2 * math.cos(a) * (0.8 + 0.2 * ((k * 7) % 3) / 2), y + s / 2 * math.sin(a))\n+              for k, a in enumerate(math.radians(36 * j) for j in range(10))]\n+    reg.poly(points, colour)\n+\n+\n+def lcd(upper, frame):\n+    \"\"\"A screen: the LCD's grey-green, the backdrop, the segments for frame (0 at rest).\"\"\"\n+    w, h = L.UPPER_LCD[\"size\"] if upper else L.LOWER_LCD[\"size\"]\n+    px = (L.SCREEN_PX[0] * 4, L.SCREEN_PX[1] * 4)\n+    reg = Region((0, 0) + px, w, h, LCD)\n+    backdrop(reg, w, h, upper)\n+    lanes = [w * 0.25, w * 0.5, w * 0.75]\n+    if upper:\n+        if frame:\n+            lane = (frame - 1) % 3\n+            rock_segment(reg, lanes[lane], 8.0 + 9.0 * frame, 7.0, SEGMENT)\n+            score = f\"{frame * 10:04d}\"\n+            for k, ch in enumerate(score):\n+                digit(reg, ch, w - 17.0 + 4.0 * k, 2.0, 3.0, 5.0, SEGMENT)\n+        else:\n+            for k in range(4):\n+                digit(reg, \"0\", w - 17.0 + 4.0 * k, 2.0, 3.0, 5.0, SEGMENT)\n+    else:\n+        lane = {0: 1, 1: 0, 2: 1, 3: 2}[frame]\n+        ship_segment(reg, lanes[lane], h - 11.0, 8.0, SEGMENT)\n+        if frame == 2:                                           # a shot, going up\n+            reg.rect_(lanes[lane] - 0.5, 4.0, lanes[lane] + 0.5, h - 17.0, SEGMENT)\n+        if frame == 0:                                           # the clock, at rest\n+            for k, ch in enumerate(\"1200\"):\n+                digit(reg, ch, 4.0 + 5.4 * k + (2.0 if k > 1 else 0.0), 3.0, 4.2, 7.0, SEGMENT)\n+            reg.circle(15.5, 5.0, 0.5, SEGMENT)\n+            reg.circle(15.5, 8.0, 0.5, SEGMENT)\n+            reg.text(\"AM\", \"arialbd.ttf\", 2.4, 1.6, 5.0, SEGMENT, anchor=\"lm\")\n+        else:\n+            reg.text(\"GAME A\", \"arialbd.ttf\", 2.2, 2.0, 3.0, SEGMENT, anchor=\"lm\")\n+    return reg.img.convert(\"RGB\")\n+\n+\n+# ---- The faces -------------------------------------------------------------------------------------------------------\n+\n+def lower():\n+    w, d = L.WIDTH, L.DEPTH\n+    reg = Region(L.REGIONS[\"lower\"], w, d, BROWN)\n+\n+    def at(u, v):\n+        return u + w / 2, d - v\n+    reg.rect_(*at(-w / 2 + 2.5, d - 2.5), *at(w / 2 - 2.5, 2.5), CREAM, r=1.5)\n+    for (u0, u1), lean in ((L.PANELS[\"left\"], -1), (L.PANELS[\"right\"], 1)):    # the brown panels, their inner corners cut\n+        cut = 8.0\n+        if lean < 0:\n+            reg.poly([at(u0, d - 6.0), at(u1, d - 6.0), at(u1, 36.0), at(u1 - cut, 30.0), at(u0, 30.0)], BROWN)\n+        else:\n+            reg.poly([at(u0, d - 6.0), at(u1, d - 6.0), at(u1, 30.0), at(u0 + cut, 30.0), at(u0, 36.0)], BROWN)\n+    (bu, bv), (bw, bh) = L.BADGE[\"at\"], L.BADGE[\"size\"]\n+    reg.rect_(*at(bu - bw / 2, bv + bh / 2), *at(bu + bw / 2, bv - bh / 2), (196, 190, 176), r=0.8)\n+    for k, word in enumerate((\"GAME\", \"&\", \"WATCH\")):\n+        reg.text(word, \"arialbd.ttf\", 2.8, *at(bu, bv + 3.0 - 3.0 * k), INK)\n+    (du, dv) = L.DPAD[\"at\"]\n+    s = L.DPAD[\"size\"] / 2 + 1.0\n+    a = L.DPAD[\"arm\"] / 2 + 1.0\n+    for x0, y0, x1, y1 in ((-s, -a, s, a), (-a, -s, a, s)):\n+        reg.rect_(*at(du + x0, dv + y1), *at(du + x1, dv + y0), (200, 194, 180), r=1.0)\n+    reg.text(\"CONTROLLER\", \"arialbd.ttf\", 2.4, *at(du, 10.0), INK)\n+    sw, sh = L.SMALL[\"size\"]\n+    for name, label in ((\"game_a\", \"GAME A\"), (\"game_b\", \"GAME B\"), (\"time\", \"TIME\")):\n+        u, v = L.SMALL[name]\n+        reg.rect_(*at(u - sw / 2 - 0.8, v + sh / 2 + 0.8), *at(u + sw / 2 + 0.8, v - sh / 2 - 0.8), CREAM, r=sh / 2 + 0.8)\n+        reg.text(label, \"arialbd.ttf\", 1.7, *at(u, v - sh / 2 - 1.9), CREAM)\n+    for name, label in ((\"alarm\", \"ALARM\"), (\"acl\", \"ACL\")):\n+        u, v = L.LIGHTS[name]\n+        reg.circle(*at(u, v), 0.9, (180, 176, 160))\n+        reg.text(label, \"arial.ttf\", 1.5, *at(u, v - 2.4), CREAM)\n+    (ju, jv), jr = L.JUMP[\"at\"], L.JUMP[\"r\"]\n+    reg.circle(*at(ju, jv), jr + 1.4, (204, 198, 184))\n+    reg.text(\"JUMP\", \"arialbd.ttf\", 2.6, *at(ju, 10.0), INK)\n+    (nu, nv), (nw, nh) = L.NINTENDO[\"at\"], L.NINTENDO[\"size\"]\n+    reg.rect_(*at(nu - nw / 2, nv + nh / 2), *at(nu + nw / 2, nv - nh / 2), BROWN, r=nh / 2)\n+    reg.rect_(*at(nu - nw / 2 + 1.0, nv + nh / 2 - 1.0), *at(nu + nw / 2 - 1.0, nv - nh / 2 + 1.0), (200, 190, 176),\n+              r=nh / 2 - 1.0)\n+    reg.text(\"Nintendo\", \"arialbi.ttf\", 3.6, *at(nu, nv), BROWN)\n+    for k in range(40):                                        # the ribbon along the back\n+        u = -34.0 + 68.0 * k / 39\n+        reg.rect_(*at(u - 0.5, d - 0.5), *at(u + 0.5, d - 4.0), (70, 80, 110) if k % 2 else (230, 230, 236))\n+    (lu, lv), (lw, lh) = L.LOWER_LCD[\"at\"], L.LOWER_LCD[\"size\"]\n+    reg.rect_(*at(lu - lw / 2 - 3.0, lv + lh / 2 + 3.0), *at(lu + lw / 2 + 3.0, lv - lh / 2 - 3.0), PANEL, r=1.0)\n+    paste_lcd(reg, at, (lu, lv), (lw, lh), lcd(False, 0))\n+    return reg\n+\n+\n+def upper():\n+    w, d = L.WIDTH, L.DEPTH\n+    reg = Region(L.REGIONS[\"upper\"], w, d, BROWN)\n+\n+    def at(u, v):\n+        return u + w / 2, d - v\n+    reg.rect_(*at(-w / 2 + 2.5, d - 2.5), *at(w / 2 - 2.5, 2.5), CREAM, r=1.5)\n+    (s0, s1) = L.SIDE[\"v\"]\n+    for (u0, u1) in (L.SIDE[\"left\"], L.SIDE[\"right\"]):\n+        reg.rect_(*at(u0, s1), *at(u1, s0), PANEL, r=1.0, outline=shade(CREAM, -0.12), width=0.4)\n+        reg.line([at(u0 + 1.0, (s0 + s1) / 2), at(u1 - 1.0, (s0 + s1) / 2)], shade(CREAM, -0.1), 0.4)\n+    (tu, tv), (tw, th) = L.TITLE[\"at\"], L.TITLE[\"size\"]\n+    reg.rect_(*at(tu - tw / 2, tv + th / 2), *at(tu + tw / 2, tv - th / 2), BROWN, r=th / 2)\n+    reg.text(\"ODYSSEY\", \"arialbd.ttf\", 4.0, *at(tu, tv), (236, 226, 210))\n+    for u, v in L.SCREWS:\n+        reg.circle(*at(u, v), 1.5, (190, 190, 194), outline=(120, 120, 124), width=0.3)\n+        reg.line([at(u - 1.0, v), at(u + 1.0, v)], (110, 110, 114), 0.3)\n+    (lu, lv), (lw, lh) = L.UPPER_LCD[\"at\"], L.UPPER_LCD[\"size\"]\n+    reg.rect_(*at(lu - lw / 2 - 3.0, lv + lh / 2 + 3.0), *at(lu + lw / 2 + 3.0, lv - lh / 2 - 3.0), PANEL, r=1.0)\n+    paste_lcd(reg, at, (lu, lv), (lw, lh), lcd(True, 0))\n+    return reg\n+\n+\n+def paste_lcd(reg, at, middle, size, picture):\n+    (u, v), (w, h) = middle, size\n+    reg.rect_(*at(u - w / 2 - 0.6, v + h / 2 + 0.6), *at(u + w / 2 + 0.6, v - h / 2 - 0.6), (30, 30, 28))\n+    x0, y0, x1, y1 = reg.box(*at(u - w / 2, v + h / 2), *at(u + w / 2, v - h / 2))\n+    reg.img.paste(picture.resize((round(x1 - x0), round(y1 - y0)), Image.LANCZOS), (round(x0), round(y0)))\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), BROWN)\n+    for reg in (lower(), upper()):\n+        reg.paste(image)\n+    for name in (\"lower_1\", \"lower_2\", \"lower_3\", \"upper_1\", \"upper_2\", \"upper_3\"):\n+        x, y, w, h = L.REGIONS[name]\n+        image.paste(lcd(name.startswith(\"upper\"), int(name[-1])).resize((w, h), Image.LANCZOS), (x, y))\n+    d = ImageDraw.Draw(image)\n+    for name, colour in ((\"brown\", BROWN), (\"cream\", CREAM), (\"dark\", rgb(L.COLOURS[\"key\"])), (\"lit\", (255, 60, 40)),\n+                         (\"metal\", (190, 190, 194))):\n+        x, y = L.SWATCHES[name]\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colour)\n+    path = os.path.join(out, \"gameandwatch_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "2ffce492e3523583eb0d4351940ee5a7ee94f4ff",
          "filename": "tools/console-models/gamecube.py",
          "status": "added",
          "additions": 408,
          "deletions": 0,
          "changes": 408,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fgamecube.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fgamecube.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fgamecube.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,408 @@\n+\"\"\"The console theme's GameCube system model (godot/themes/console/models/systems/gc.glb): an indigo GameCube, its\n+handle round the back, and its controller.\n+\n+Built in Blender from the real console's proportions (gamecube_layout.py has the measurements, taken from\n+photographs). The console is a cube rounded at its edges, the front panel joined onto its front; the seam near its\n+foot, the side vents' grids, the controller ports, the memory card slots, the lid's recess, the disc's well and the\n+buttons' sockets are cut into it. The lid lies in its recess on its own hinge; the handle is a half ring round the\n+back. The controller is its outline (traced from a photograph) lofted into a rounded slab, its sticks, buttons and\n+D-pad on it. The printing is two textures drawn by gamecube_print.py: the console from above (planar-mapped onto every\n+face facing up: the lid's window and logo, the buttons' names), the front panel, the controller's top and swatches;\n+and the mini disc's two sides.\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: the right side towards the viewer,\n+the top tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m) holding the set; the\n+set holding the lid (its origin on its hinge), the disc (waiting in its well, shrunk to nothing), POWER, OPEN, the\n+power light (shrunk to nothing), and the controller's stick, C stick, A and B.\n+\n+The `focused` clip (8 s, looping) plays a game: OPEN goes in and the lid springs up; the disc drops onto the spindle\n+and the lid shuts; POWER goes in and the light comes on; the stick circles, the C stick flicks, A and B are pressed.\n+Then OPEN again: the lid springs up, the disc rises out and goes, the lid shuts, and POWER goes off.\n+\n+Five materials (case, grey, dark, print, disc) and two textures, within the system model budget. Run from the repo\n+root:\n+\n+    python tools/console-models/gamecube_print.py artifacts/gc\n+    blender -b --factory-startup --python tools/console-models/gamecube.py -- artifacts/gc godot/themes/console/models/systems/gc.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bmesh\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import gamecube_layout as L  # noqa: E402\n+from amiga import paint, rounded, solid  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, ease_out, key, phase, smooth, wobble  # noqa: E402\n+from mega_drive_cartridge import bevel, boolean, box, export, join, material, new_object, triangles  # noqa: E402\n+\n+TILT_YAW = -25.0     # degrees about the vertical: the right side comes towards the viewer\n+TILT_PITCH = 26.0    # degrees about the horizontal: the top tips up towards the viewer\n+ROUGHNESS = dict(case=0.45, grey=0.55, dark=0.6, print=0.45, disc=0.2)\n+PAD_W = 150.0\n+\n+\n+# ---- UVs -------------------------------------------------------------------------------------------------------------\n+\n+def atlas_uv(region, w, h, size=L.ATLAS):\n+    \"\"\"A face drawn w x h into region: (a, b) millimetres across it (a to the right, b down from its top) to UVs.\"\"\"\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / size, 1 - (ry + b / h * rh) / size)\n+\n+\n+TOP = atlas_uv(L.REGIONS[\"top\"], L.WIDTH, L.DEPTH)\n+\n+\n+def top_uv(co):\n+    return TOP(co.x + L.WIDTH / 2, L.DEPTH - co.y)\n+\n+\n+def swatch(name):\n+    x, y = L.SWATCHES[name]\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+def painted(obj, m, name):\n+    paint(obj, m[\"print\"], swatch(name), lambda c, n, _name: True)\n+    return obj\n+\n+\n+def prism(name, outline, z0, z1, mat):\n+    \"\"\"A solid from an (x, y) outline, from z0 to z1.\"\"\"\n+    return solid(name, [[(x, y, z) for x, y in outline] for z in (z0, z1)], mat)\n+\n+\n+def disc_outline(cx, cy, r, n=24):\n+    return [(cx + r * math.cos(2 * math.pi * k / n), cy + r * math.sin(2 * math.pi * k / n)) for k in range(n)]\n+\n+\n+def cylinder_y(name, cx, cz, r, y0, y1, mat, n=20):\n+    \"\"\"A cylinder along y, its axis at (cx, cz), from y0 to y1.\"\"\"\n+    ring = [(cx + r * math.cos(2 * math.pi * k / n), cz + r * math.sin(2 * math.pi * k / n)) for k in range(n)]\n+    return solid(name, [[(x, y, z) for x, z in ring] for y in (y0, y1)], mat)\n+\n+\n+# ---- The console -----------------------------------------------------------------------------------------------------\n+\n+def body(m):\n+    \"\"\"The cube and its front panel, cut for the seam, the vents, the ports and slots, the lid's recess, the disc's\n+    well and the buttons' sockets; its top and the panel printed.\"\"\"\n+    w, d, h = L.WIDTH, L.DEPTH, L.HEIGHT\n+    cube = box(\"gc\", -w / 2, w / 2, 0.0, d, 0.0, h, m[\"case\"])\n+    bevel(cube, L.EDGE, segments=3)\n+    p = L.PANEL\n+    (x0, x1), (z0, z1) = p[\"x\"], p[\"z\"]\n+    face = [(x, z) for x, z in rounded((x0 + x1) / 2, (z0 + z1) / 2, x1 - x0, z1 - z0, 6.0, 4)]\n+    panel = solid(\"panel\", [[(x, y, z) for x, z in face] for y in (-p[\"out\"], 6.0)], m[\"grey\"])\n+    bevel(panel, 1.0, segments=2)\n+    boolean(cube, panel, \"UNION\")\n+\n+    s = L.SEAM                              # round the cube's faces, passing behind the panel\n+    ring = box(\"seam\", -w / 2 - 5.0, w / 2 + 5.0, -0.5, d + 5.0, s[\"z\"] - 0.5, s[\"z\"] + 0.5, m[\"dark\"])\n+    boolean(ring, box(\"inner\", -w / 2 + s[\"depth\"], w / 2 - s[\"depth\"], s[\"depth\"], d - s[\"depth\"], s[\"z\"] - 2.0,\n+                      s[\"z\"] + 2.0, m[\"dark\"]))\n+    boolean(cube, ring)\n+\n+    v = L.VENT\n+    holes = []\n+    for side in (-1, 1):\n+        for i in range(v[\"columns\"]):\n+            for j in range(v[\"rows\"]):\n+                y = v[\"y\"] + (i - (v[\"columns\"] - 1) / 2) * v[\"pitch\"]\n+                z = v[\"z\"] + (j - (v[\"rows\"] - 1) / 2) * v[\"pitch\"]\n+                x = side * w / 2\n+                holes.append(box(\"vent\", x - v[\"depth\"], x + v[\"depth\"], y - v[\"hole\"] / 2, y + v[\"hole\"] / 2,\n+                                 z - v[\"hole\"] / 2, z + v[\"hole\"] / 2, m[\"dark\"]))\n+    boolean(cube, join(holes, \"vents\"))\n+    boolean(cube, join([cylinder_y(\"port\", x, L.PORTS[\"z\"], L.PORTS[\"r\"], -p[\"out\"] - 3.0, L.PORTS[\"depth\"], m[\"dark\"])\n+                        for x in L.PORTS[\"x\"]], \"ports\"))\n+    sw, sh = L.SLOTS[\"size\"]\n+    boolean(cube, join([box(\"slot\", x - sw / 2, x + sw / 2, -p[\"out\"] - 3.0, 4.0, L.SLOTS[\"z\"] - sh / 2,\n+                            L.SLOTS[\"z\"] + sh / 2, m[\"dark\"]) for x in L.SLOTS[\"x\"]], \"slots\"))\n+\n+    lid = L.LID\n+    (lx0, lx1), (ly0, ly1) = lid[\"x\"], lid[\"y\"]\n+    recess = [(x, y) for x, y in rounded(0.0, (ly0 + ly1) / 2, lx1 - lx0, ly1 - ly0, 4.0, 3)]\n+    cutter = prism(\"recess\", recess, h - lid[\"thickness\"], h + 5.0, m[\"case\"])\n+    boolean(cutter, corners(m, 3.0))\n+    boolean(cube, cutter)\n+    wx, wy = L.WINDOW[\"at\"]\n+    boolean(cube, prism(\"well\", disc_outline(wx, wy, L.DISC[\"r\"] + 3.0), h - 30.0, h - lid[\"thickness\"] + 0.5,\n+                        m[\"dark\"]))\n+    boolean(cube, join([prism(\"socket\", disc_outline(x, y, r + 1.6), h - 1.6, h + 5.0, m[\"case\"])\n+                        for x, y, r in L.BUTTONS.values()], \"sockets\"))\n+\n+    paint(cube, m[\"print\"], top_uv, lambda c, n, name: n.z > 0.9 and name == \"case\" and c.z > h - 2.0)\n+    pw, ph = x1 - x0, z1 - z0\n+    face_uv = atlas_uv(L.REGIONS[\"panel\"], pw, ph)\n+    paint(cube, m[\"print\"], lambda co: face_uv(co.x - x0, z1 - co.z),\n+          lambda c, n, name: n.y < -0.9 and name == \"grey\" and c.y < -p[\"out\"] + 0.3)\n+    return cube\n+\n+\n+def handle(m):\n+    \"\"\"The handle: a half ring round the back, a band standing on its edge, leaning back; its pivots on the sides.\"\"\"\n+    cx, cy, cz = L.HANDLE[\"at\"]\n+    r, bw, t = L.HANDLE[\"r\"], L.HANDLE[\"width\"], L.HANDLE[\"thickness\"]\n+    rings = []\n+    for k in range(25):\n+        a = math.radians(180.0 * k / 24)\n+        out = Vector((math.cos(a), math.sin(a), 0.0))\n+        rings.append([Vector((0.0, 0.0, 0.0)) + out * rr + Vector((0.0, 0.0, dz)) for rr, dz in\n+                      ((r - t / 2, -bw / 2), (r + t / 2, -bw / 2), (r + t / 2, bw / 2), (r - t / 2, bw / 2))])\n+    band = solid(\"handle\", rings, m[\"dark\"])\n+    bevel(band, 1.5, segments=2)\n+    band.data.transform(Matrix.Translation((cx, cy, cz)) @ Matrix.Rotation(math.radians(L.HANDLE[\"tilt\"]), 4, \"X\"))\n+    pivots = [cylinder_x(\"pivot\", side * (L.WIDTH / 2), cy, cz, 7.0, side, m) for side in (-1, 1)]\n+    return [band] + pivots\n+\n+\n+def cylinder_x(name, x, y, z, r, side, m, n=16):\n+    ring = [(y + r * math.cos(2 * math.pi * k / n), z + r * math.sin(2 * math.pi * k / n)) for k in range(n)]\n+    part = solid(name, [[(xx, yy, zz) for yy, zz in ring] for xx in (x - side * 1.0, x + side * 3.0)], m[\"dark\"])\n+    bevel(part, 0.8, segments=1)\n+    return part\n+\n+\n+def corners(m, gap):\n+    \"\"\"The lid's corners cut round the buttons: a cylinder round each, gap wider than the button's socket.\"\"\"\n+    return join([prism(\"corner\", disc_outline(x, y, r + 1.6 + gap), L.HEIGHT - 20.0, L.HEIGHT + 10.0, m[\"case\"])\n+                 for x, y, r in L.BUTTONS.values()], \"corners\")\n+\n+\n+def lid_part(m):\n+    \"\"\"The lid, its origin on its hinge along the back: the slab in its recess, its top printed.\"\"\"\n+    lid = L.LID\n+    (lx0, lx1), (ly0, ly1) = lid[\"x\"], lid[\"y\"]\n+    outline = [(x, y) for x, y in rounded(0.0, (ly0 + ly1) / 2, lx1 - lx0 - 0.8, ly1 - ly0 - 0.8, 3.6, 3)]\n+    slab = prism(\"lid\", outline, L.HEIGHT - lid[\"thickness\"] + 0.4, L.HEIGHT, m[\"case\"])\n+    boolean(slab, corners(m, 3.4))\n+    bevel(slab, 0.8, segments=2)\n+    paint(slab, m[\"print\"], top_uv, lambda c, n, name: n.z > 0.9)\n+    hinge = Vector((0.0, lid[\"hinge\"][0], lid[\"hinge\"][1]))\n+    slab.data.transform(Matrix.Translation(-hinge))\n+    return slab, hinge\n+\n+\n+def button(name, r, colour, m, height=2.4):\n+    part = painted(prism(name, disc_outline(0.0, 0.0, r), -2.0, height, m[\"print\"]), m, colour)\n+    bevel(part, 0.8, segments=2)\n+    return part\n+\n+\n+def light(m):\n+    x = L.PORTS[\"x\"][0]\n+    z = L.PORTS[\"z\"] + L.PORTS[\"r\"] + 13.0\n+    y = -L.PANEL[\"out\"] - 0.2\n+    lit = painted(box(\"light\", x - 1.2, x + 1.2, y - 0.3, y, z - 1.2, z + 1.2, m[\"print\"]), m, \"lit\")\n+    lit.data.transform(Matrix.Translation((-x, -y, -z)))\n+    return lit, Vector((x, y, z))\n+\n+\n+def disc(m):\n+    \"\"\"The mini disc, its middle at its origin, its label up.\"\"\"\n+    r, t = L.DISC[\"r\"], L.DISC[\"thickness\"]\n+    part = prism(\"disc\", disc_outline(0.0, 0.0, r, 40), -t / 2, t / 2, m[\"disc\"])\n+    top = atlas_uv(L.DISC_REGIONS[\"label\"], 2 * r, 2 * r, L.DISC_ATLAS)\n+    bottom = atlas_uv(L.DISC_REGIONS[\"data\"], 2 * r, 2 * r, L.DISC_ATLAS)\n+    paint(part, m[\"disc\"], lambda co: top(co.x + r, r - co.y), lambda c, n, name: n.z > 0.9)\n+    paint(part, m[\"disc\"], lambda co: bottom(co.x + r, r + co.y), lambda c, n, name: n.z <= 0.9)\n+    return part\n+\n+\n+# ---- The controller --------------------------------------------------------------------------------------------------\n+\n+def pad_outline():\n+    points = list(L.PAD_OUTLINE)\n+    area = sum(a[0] * b[1] - b[0] * a[1] for a, b in zip(points, points[1:] + points[:1]))\n+    return points if area > 0 else list(reversed(points))\n+\n+\n+def pad_frame():\n+    \"\"\"The controller's frame: its local (x, y), y from its grips' tips (towards the player).\"\"\"\n+    (px, py), turn = L.PAD[\"at\"], L.PAD[\"turn\"]\n+    return (Matrix.Translation((px, py, 0.0)) @ Matrix.Rotation(math.radians(turn), 4, \"Z\")\n+            @ Matrix.Translation((0.0, -L.PAD[\"length\"] / 2, 0.0)))\n+\n+\n+def kidney(name, x, y, w, h, turn, m):\n+    outline = [(x + a * math.cos(math.radians(turn)) - b * math.sin(math.radians(turn)),\n+                y + a * math.sin(math.radians(turn)) + b * math.cos(math.radians(turn)))\n+               for a, b in rounded(0.0, 0.0, w, h, min(w, h) / 2 - 0.2, 4)]\n+    t = L.PAD[\"thickness\"]\n+    part = painted(prism(name, outline, t - 3.0, t + 2.6, m[\"print\"]), m, \"white\")\n+    bevel(part, 0.8, segments=2)\n+    return part\n+\n+\n+def controller(m):\n+    \"\"\"The controller's body and its fixed parts (X, Y, START, the D-pad, the shoulders), at its place.\"\"\"\n+    p = L.PAD\n+    t = p[\"thickness\"]\n+    body = prism(\"controller\", pad_outline(), 0.0, t, m[\"case\"])\n+    bevel(body, 9.0, segments=5, angle=30)\n+    top = atlas_uv(L.REGIONS[\"pad\"], PAD_W, p[\"length\"])\n+    paint(body, m[\"print\"], lambda co: top(co.x + PAD_W / 2, p[\"length\"] - co.y), lambda c, n, name: n.z > 0.8)\n+    parts = [body, kidney(\"x\", *p[\"x\"], m), kidney(\"y\", *p[\"y\"], m)]\n+    sx, sy, sr = p[\"start\"]\n+    start = button(\"start\", sr, \"white\", m)\n+    start.data.transform(Matrix.Translation((sx, sy, t)))\n+    parts.append(start)\n+    dx, dy, span, arm = p[\"dpad\"]\n+    cross = [(arm / 2, -span / 2), (arm / 2, -arm / 2), (span / 2, -arm / 2), (span / 2, arm / 2), (arm / 2, arm / 2),\n+             (arm / 2, span / 2), (-arm / 2, span / 2), (-arm / 2, arm / 2), (-span / 2, arm / 2), (-span / 2, -arm / 2),\n+             (-arm / 2, -arm / 2), (-arm / 2, -span / 2)]\n+    dpad = painted(prism(\"dpad\", [(dx + x, dy + y) for x, y in cross], t - 3.0, t + 2.4, m[\"print\"]), m, \"white\")\n+    bevel(dpad, 0.6, segments=1)\n+    parts.append(dpad)\n+    for side, colour in ((-1, \"grey\"), (1, \"grey\")):          # the shoulder buttons, L and R\n+        shoulder = painted(box(\"shoulder\", side * 62.0 - 11.0, side * 62.0 + 11.0, 99.0, 108.0, t - 8.0, t - 1.0,\n+                               m[\"print\"]), m, colour)\n+        bevel(shoulder, 2.5, segments=2)\n+        parts.append(shoulder)\n+    for part in parts:\n+        part.data.transform(pad_frame())\n+    return parts\n+\n+\n+def pad_parts(m):\n+    \"\"\"The stick, the C stick, A and B, each its middle at its origin, and where each goes.\"\"\"\n+    p = L.PAD\n+    t = p[\"thickness\"]\n+    parts = []\n+    for name, (x, y, r), colour in ((\"a\", p[\"a\"], \"green\"), (\"b\", p[\"b\"], \"red\")):\n+        parts.append((name, button(name, r, colour, m, 3.0), Vector((x, y, 0.0))))\n+    for name, (x, y), cap, colour in ((\"stick\", p[\"stick\"], p[\"cap\"], \"white\"), (\"c\", p[\"c\"], p[\"c_cap\"], \"yellow\")):\n+        shaft = painted(prism(\"shaft\", disc_outline(0.0, 0.0, 3.4, 16), -4.0, 6.0, m[\"print\"]), m, \"dark\")\n+        top = painted(prism(\"cap\", disc_outline(0.0, 0.0, cap, 24), 6.0, 10.0, m[\"print\"]), m, colour)\n+        bevel(top, 1.4, segments=2)\n+        parts.append((name, join([shaft, top], name), Vector((x, y, 0.0))))\n+    frame = pad_frame()\n+    turn = Matrix.Rotation(math.radians(L.PAD[\"turn\"]), 4, \"Z\")\n+    placed = []\n+    for name, part, where in parts:\n+        part.data.transform(turn)\n+        at = frame @ where\n+        placed.append((name, part, Vector((at.x, at.y, t))))\n+    return placed\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+def appear(f, f0, f1):\n+    return HIDDEN + (1 - HIDDEN) * back_out(phase(f, f0, f1)) if f >= f0 else HIDDEN\n+\n+\n+def tap(f, f0, hold=2):\n+    \"\"\"A press at f0: down in two frames, held, back up in three.\"\"\"\n+    return smooth(phase(f, f0, f0 + 2)) * (1 - smooth(phase(f, f0 + 2 + hold, f0 + 5 + hold)))\n+\n+\n+PRESSES = dict(open=(8, 184), power=(66, 228), a=(86, 100, 114, 150), b=(128,))\n+\n+\n+def lid_angle(f):\n+    open_ = L.LID[\"open\"]\n+    if f < 50:\n+        return open_ * back_out(phase(f, 10, 22), 1.4)\n+    if f < 186:\n+        return open_ * (1 - smooth(phase(f, 50, 60))) + wobble(f, 60, 2.0, period=8.0, decay=3.0)\n+    if f < 216:\n+        return open_ * back_out(phase(f, 186, 198), 1.4)\n+    return open_ * (1 - smooth(phase(f, 216, 226))) + wobble(f, 226, 2.0, period=8.0, decay=3.0)\n+\n+\n+def timeline(f):\n+    # The disc: appears over the well, drops onto the spindle, spins; later rises out and goes.\n+    if f < 24 or f >= 214:\n+        spun = Pose(scale=HIDDEN)\n+    elif f < 34:\n+        spun = Pose(z=60.0, scale=appear(f, 24, 32))\n+    elif f < 46:\n+        spun = Pose(z=60.0 * (1 - ease_in(phase(f, 34, 46))))\n+    elif f < 200:\n+        spun = Pose(spin=-30.0 * (f - 46))\n+    else:\n+        spun = Pose(z=60.0 * ease_out(phase(f, 200, 208)), scale=max(HIDDEN, 1 - ease_in(phase(f, 206, 214))))\n+\n+    on = 68 <= f < 230\n+    poses = {\"lid\": Pose(pitch=lid_angle(f)), \"disc\": spun, \"light\": Pose() if on else Pose(scale=HIDDEN)}\n+    for name, frames in PRESSES.items():\n+        d = max(tap(f, f0) for f0 in frames)\n+        poses[name] = Pose(z=-(1.4 if name in (\"open\", \"power\") else 2.0) * d)\n+\n+    # The stick circles twice; the C stick flicks right and back.\n+    t = phase(f, 80, 140)\n+    lean = 18.0 * math.sin(math.pi * t)\n+    angle = 2 * math.pi * 2 * t\n+    poses[\"stick\"] = Pose(pitch=lean * math.cos(angle), roll=lean * math.sin(angle))\n+    flick = math.sin(math.pi * phase(f, 158, 168)) * 18.0\n+    poses[\"c\"] = Pose(roll=flick)\n+    return poses\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    printed = bpy.data.images.load(os.path.join(images, \"gamecube_print.png\"))\n+    discs = bpy.data.images.load(os.path.join(images, \"gamecube_disc.png\"))\n+    colours = dict(L.COLOURS, print=\"#FFFFFF\")\n+    textures = dict(print=printed, disc=discs)\n+    m = {n: material(n, colours[n], r, textures.get(n)) for n, r in ROUGHNESS.items()}\n+\n+    reset = button(\"reset\", L.BUTTONS[\"reset\"][2], \"case\", m)\n+    rx, ry, _ = L.BUTTONS[\"reset\"]\n+    reset.data.transform(Matrix.Translation((rx, ry, L.HEIGHT - 1.0)))\n+    whole = join([body(m), reset] + handle(m) + controller(m), \"gc\")\n+    cover, hinge = lid_part(m)\n+    power, opener = button(\"power\", L.BUTTONS[\"power\"][2], \"grey\", m), button(\"open\", L.BUTTONS[\"open\"][2], \"case\", m)\n+    lit, lit_at = light(m)\n+    spinning = disc(m)\n+    sticks = pad_parts(m)\n+    for obj in [whole, cover, power, opener] + [part for _, part, _ in sticks]:\n+        for p in obj.data.polygons:\n+            p.use_smooth = True\n+        obj.data.set_sharp_from_angle(angle=math.radians(55))\n+\n+    # The node tree: the root (the view and the fit) holds the set; the set holds the moving parts at their places.\n+    root = bpy.data.objects.new(\"gc_root\", None)\n+    scene.collection.objects.link(root)\n+    whole.parent = root\n+    px, py, _ = L.BUTTONS[\"power\"]\n+    ox, oy, _ = L.BUTTONS[\"open\"]\n+    placed = [(cover, hinge), (power, Vector((px, py, L.HEIGHT - 1.0))), (opener, Vector((ox, oy, L.HEIGHT - 1.0))),\n+              (lit, lit_at), (spinning, Vector(L.SPINDLE))] + [(part, where) for _, part, where in sticks]\n+    for part, where in placed:\n+        part.parent = whole\n+        part.location = where\n+    nodes = {\"lid\": cover, \"power\": power, \"open\": opener, \"light\": lit, \"disc\": spinning,\n+             **{name: part for name, part, _ in sticks}}\n+\n+    # The three-quarter view, then the spec's fit of the set at rest, as the root's transform.\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    points = [tilt @ v.co for v in whole.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [whole, cover, power, opener, lit, spinning] + [part for _, part, _ in sticks]\n+    print(f\"GameCube: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(whole.data)} the set), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "d0a1163fa74c71daa0ae29deb9476aa84c163e68",
          "filename": "tools/console-models/gamecube_layout.py",
          "status": "added",
          "additions": 64,
          "deletions": 0,
          "changes": 64,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fgamecube_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fgamecube_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fgamecube_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,64 @@\n+\"\"\"The Nintendo GameCube's measurements, shared by gamecube_print.py (which draws its printing) and gamecube.py (which\n+builds it). Plain Python: Blender's Python imports it too.\n+\n+The GameCube (2001) in indigo, 150 x 161 x 110 mm (Nintendo's figures), its handle at the back, and its controller.\n+Millimetres: x from the console's middle (left to right), y back from its front face, z up from the table. The rest\n+was measured from Evan-Amos's photographs on Wikimedia Commons (the console from each corner, with its lid open, and\n+with its controller), scaled by the console's width; the controller's outline was traced from its photograph from\n+above, scaled to 150 mm across.\n+\n+The console: a cube with rounded edges, a seam round it near its foot; the light grey panel on its front with the\n+four controller ports and the two memory card slots; a grid of square vents in each side; on its top the lid (its\n+hinge along the back, its round black window with NINTENDO GAMECUBE), POWER (light grey) at the back left, RESET at the\n+front left, OPEN at the front right, Nintendo's name at the back right; the black handle round the back. The\n+controller lies to its left.\n+\n+Two textures. `print` (ATLAS square, REGIONS): `top` (the console seen from above: the lid's seam, its window and the\n+logo, the buttons' names), `panel` (the front panel seen from in front: the ports' rims and numbers, the slots and\n+their names), `pad` (the controller's top: its colours, the sticks' gates, the logo), and swatches. `disc` (DISC_ATLAS):\n+the mini disc's label side (a made-up game, Odyssey) and its shiny side. A region is (x, y, width, height) in pixels.\n+\"\"\"\n+\n+ATLAS = 1024\n+DISC_ATLAS = 512\n+\n+WIDTH, DEPTH, HEIGHT = 150.0, 161.0, 110.0\n+EDGE = 7.0                                                 # its edges' rounding\n+SEAM = dict(z=20.0, depth=0.8)                             # the seam round it near its foot\n+PANEL = dict(x=(-62.0, 62.0), z=(10.0, 86.0), out=2.0)     # the front panel, standing out of the front\n+PORTS = dict(x=(-45.0, -15.0, 15.0, 45.0), z=60.0, r=8.0, depth=10.0)\n+SLOTS = dict(x=(-30.0, 30.0), z=27.0, size=(40.0, 5.0))\n+VENT = dict(columns=8, rows=9, hole=6.4, pitch=8.6, y=88.0, z=58.0, depth=4.0)   # each side's grid: its middle (y, z)\n+LID = dict(x=(-58.0, 58.0), y=(16.0, 150.0), thickness=6.0, hinge=(152.0, HEIGHT - 3.0), open=-70.0)\n+WINDOW = dict(at=(0.0, 86.0), r=36.0)                      # the lid's black window, its middle (x, y)\n+BUTTONS = dict(power=(-61.0, 140.0, 8.5), reset=(-61.0, 20.0, 8.5), open=(61.0, 20.0, 8.5))   # x, y, radius\n+HANDLE = dict(at=(0.0, 150.0, 96.0), r=79.0, width=13.0, thickness=7.0, tilt=-18.0)  # the half ring's middle,\n+                                                                                     # radius, band, its lean\n+DISC = dict(r=40.0, hole=7.5, thickness=1.2)\n+SPINDLE = (0.0, 86.0, HEIGHT - 26.0)                       # where the disc sits, its middle\n+\n+# The controller: its outline traced from above, y from its grips' tips, x from its middle; it lies at PAD[\"at\"],\n+# turned PAD[\"turn\"] degrees.\n+PAD_OUTLINE = [(0.0, 115.1), (19.5, 113.7), (36.5, 109.3), (41.9, 109.7), (48.6, 108.4), (58.9, 103.0), (65.6, 94.5),\n+               (69.6, 87.3), (71.9, 77.5), (74.6, 47.0), (75.0, 21.9), (73.7, 10.3), (71.4, 4.9), (66.0, 0.0),\n+               (61.6, 0.0), (57.1, 2.7), (54.0, 8.5), (48.1, 36.3), (45.0, 33.6), (41.4, 36.7), (36.5, 29.6),\n+               (29.8, 25.5), (19.5, 25.1), (15.4, 26.4), (10.1, 30.4), (5.6, 38.1), (4.3, 46.6), (-0.2, 47.0),\n+               (-3.4, 47.0), (-6.0, 40.7), (-8.7, 33.6), (-15.4, 27.3), (-22.2, 25.1), (-27.1, 25.1), (-34.3, 27.8),\n+               (-39.2, 32.2), (-43.2, 39.0), (-46.3, 39.0), (-47.7, 40.3), (-54.4, 8.5), (-56.6, 4.5), (-61.6, 0.9),\n+               (-67.8, 1.8), (-70.5, 4.5), (-73.2, 10.3), (-75.0, 21.9), (-75.0, 42.5), (-71.9, 81.9), (-69.6, 89.6),\n+               (-66.5, 94.5), (-53.1, 106.6), (-49.0, 108.4), (-41.4, 107.9), (-23.1, 113.3), (-2.5, 115.1)]\n+PAD = dict(at=(-168.0, 40.0), turn=16.0, thickness=24.0, length=115.1,\n+           stick=(-46.3, 83.1), rim=18.8, gate=11.9, cap=9.9, dpad=(-25.0, 48.4, 19.8, 6.9),\n+           start=(-0.7, 78.9, 3.5), a=(44.4, 80.6, 8.9), b=(27.5, 72.2, 5.5),\n+           x=(61.2, 83.6, 7.4, 14.9, -14.0), y=(39.9, 94.5, 14.9, 7.4, 10.0),     # the kidneys: x, y, w, h, turn\n+           c=(23.1, 48.9), c_gate=9.9, c_cap=6.9, logo=(0.2, 102.9))\n+\n+COLOURS = dict(case=\"#5F58A6\", grey=\"#C9CACC\", dark=\"#1C1D1F\", disc=\"#FFFFFF\", panel_dark=\"#8E9093\")\n+\n+REGIONS = dict(top=(0, 0, 512, 550), panel=(520, 0, 504, 309), pad=(520, 317, 504, 387))\n+SWATCHES = dict(grey=(16, 576), dark=(48, 576), green=(80, 576), red=(112, 576), yellow=(144, 576), white=(176, 576),\n+                case=(208, 576), lit=(240, 576))\n+DISC_REGIONS = dict(label=(0, 0, 256, 256), data=(256, 0, 256, 256))\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s"
        },
        {
          "sha": "1824de051a9ef457f12d324df30b2410f864aa76",
          "filename": "tools/console-models/gamecube_print.py",
          "status": "added",
          "additions": 176,
          "deletions": 0,
          "changes": 176,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fgamecube_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fgamecube_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fgamecube_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,176 @@\n+\"\"\"The Nintendo GameCube's printing, for the console theme's GameCube model (tools/console-models/gamecube.py).\n+\n+Draws its two atlases, their regions as gamecube_layout.py places them, each in millimetres as the model's planar UVs\n+map it. gamecube_print.png: the console seen from above (the lid's seam, its black window with NINTENDO GAMECUBE, the\n+buttons' sockets and names, Nintendo's name); the front panel seen from in front (the ports' rims and their dots, the\n+memory card slots and their names); the controller's top (the sticks' rims and gates, START/PAUSE, the logo); and the\n+swatches small parts take their colour from. gamecube_disc.png: the mini disc's label side (a made-up game, Odyssey)\n+and its shiny side. Run it with the system Python (Pillow):\n+\n+    python tools/console-models/gamecube_print.py <out folder>\n+\n+It writes gamecube_print.png and gamecube_disc.png there.\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+from PIL import Image, ImageDraw, ImageFont\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import gamecube_layout as L  # noqa: E402\n+from psp_print import ship, space  # noqa: E402\n+from retro_pc_print import rgb, shade  # noqa: E402\n+from windows_pc_print import FONTS, Region  # noqa: E402\n+\n+CASE = rgb(L.COLOURS[\"case\"])\n+GREY = rgb(L.COLOURS[\"grey\"])\n+LOGO_GREY = (200, 202, 206)\n+\n+\n+def logo(reg, cx, cy, w, colour):\n+    \"\"\"NINTENDO over GAMECUBE, w across, its middle at (cx, cy).\"\"\"\n+    reg.text(\"N I N T E N D O\", \"arialbd.ttf\", w * 0.105, cx, cy - w * 0.1, colour)\n+    layer = reg.layer()\n+    f = ImageFont.truetype(FONTS + \"ariblk.ttf\", max(1, round(w * 0.2 * reg.sx)))\n+    ImageDraw.Draw(layer).text(reg.p(cx, cy + w * 0.06), \"GAMECUBE\", font=f, fill=colour + (255,), anchor=\"mm\")\n+    reg.over(layer)\n+\n+\n+def octagon(cx, cy, r, turn=22.5):\n+    return [(cx + r * math.cos(math.radians(turn + 45 * k)), cy + r * math.sin(math.radians(turn + 45 * k)))\n+            for k in range(8)]\n+\n+\n+def top():\n+    reg = Region(L.REGIONS[\"top\"], L.WIDTH, L.DEPTH, CASE)\n+\n+    def at(x, y):\n+        return x + L.WIDTH / 2, L.DEPTH - y\n+    (x0, x1), (y0, y1) = L.LID[\"x\"], L.LID[\"y\"]\n+    reg.rect_(*at(x0, y1), *at(x1, y0), None, r=4.0, outline=shade(CASE, -0.35), width=0.5)\n+    for name, (x, y, r) in L.BUTTONS.items():\n+        reg.circle(*at(x, y), r + 1.6, shade(CASE, -0.12))\n+        reg.circle(*at(x, y), r + 1.6, None, outline=shade(CASE, -0.35), width=0.4)\n+        label = name.upper()\n+        lx = x + (12.0 if x < 0 else -12.0)\n+        reg.text(label, \"arial.ttf\", 2.2, *at(lx, y + (6.0 if y < 80 else -6.0)), shade(CASE, -0.3))\n+    wx, wy = L.WINDOW[\"at\"]\n+    reg.circle(*at(wx, wy), L.WINDOW[\"r\"], rgb(L.COLOURS[\"dark\"]))\n+    logo(reg, *at(wx, wy), 40.0, LOGO_GREY)\n+    nx, ny = 44.0, 146.0\n+    reg.rect_(*at(nx - 14.0, ny + 3.0), *at(nx + 14.0, ny - 3.0), None, r=3.0, outline=shade(CASE, 0.18), width=0.35)\n+    reg.text(\"Nintendo\", \"arialbd.ttf\", 3.6, *at(nx, ny), shade(CASE, 0.18))\n+    return reg\n+\n+\n+def panel():\n+    (x0, x1), (z0, z1) = L.PANEL[\"x\"], L.PANEL[\"z\"]\n+    w, h = x1 - x0, z1 - z0\n+    reg = Region(L.REGIONS[\"panel\"], w, h, GREY)\n+\n+    def at(x, z):\n+        return x - x0, z1 - z\n+    for k, x in enumerate(L.PORTS[\"x\"]):\n+        reg.circle(*at(x, L.PORTS[\"z\"]), L.PORTS[\"r\"] + 2.4, shade(GREY, -0.08))\n+        for j in range(k + 1):\n+            reg.circle(*at(x - (k * 2.0) / 2 + 2.0 * j, L.PORTS[\"z\"] + L.PORTS[\"r\"] + 6.0), 0.55, shade(GREY, -0.4))\n+    sw, sh = L.SLOTS[\"size\"]\n+    for name, x in zip((\"SLOT-A\", \"SLOT-B\"), L.SLOTS[\"x\"]):\n+        z = L.SLOTS[\"z\"]\n+        reg.rect_(*at(x - sw / 2 - 2.0, z + sh / 2 + 2.0), *at(x + sw / 2 + 2.0, z - sh / 2 - 2.0), shade(GREY, -0.05),\n+                  r=1.5, outline=shade(GREY, -0.3), width=0.35)\n+        reg.rect_(*at(x - sw / 2, z + sh / 2), *at(x + sw / 2, z - sh / 2), shade(GREY, -0.5), r=0.8)\n+        reg.text(name, \"arial.ttf\", 2.6, *at(x, z - sh / 2 - 5.0), shade(GREY, -0.35))\n+    reg.circle(*at(L.PORTS[\"x\"][0], L.PORTS[\"z\"] + L.PORTS[\"r\"] + 13.0), 0.9, (40, 80, 50))   # the power light, off\n+    return reg\n+\n+\n+def pad():\n+    w, length = 150.0, L.PAD[\"length\"]\n+    reg = Region(L.REGIONS[\"pad\"], w, length, CASE)\n+    p = L.PAD\n+\n+    def at(x, y):\n+        return x + w / 2, length - y\n+    sx, sy = p[\"stick\"]\n+    reg.circle(*at(sx, sy), p[\"rim\"], shade(CASE, -0.12))\n+    reg.circle(*at(sx, sy), p[\"rim\"] - 2.0, shade(CASE, 0.04))\n+    reg.poly([at(x, y) for x, y in octagon(sx, sy, p[\"gate\"])], shade(CASE, -0.3))\n+    cx, cy = p[\"c\"]\n+    reg.circle(*at(cx, cy), p[\"c_gate\"] + 2.5, shade(CASE, -0.08))\n+    reg.poly([at(x, y) for x, y in octagon(cx, cy, p[\"c_gate\"])], (226, 160, 20))\n+    ax, ay, ar = p[\"a\"]\n+    reg.circle(*at(ax, ay), ar + 2.0, shade(CASE, -0.1))\n+    dx, dy, span, _ = p[\"dpad\"]\n+    reg.circle(*at(dx, dy), span / 2 + 2.5, shade(CASE, -0.08))\n+    reg.text(\"START/PAUSE\", \"arialbd.ttf\", 2.3, *at(p[\"start\"][0], p[\"start\"][1] + 7.0), shade(CASE, -0.3))\n+    logo(reg, *at(*p[\"logo\"]), 36.0, (214, 214, 222))\n+    return reg\n+\n+\n+def print_atlas(out):\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), CASE)\n+    for reg in (top(), panel(), pad()):\n+        reg.paste(image)\n+    d = ImageDraw.Draw(image)\n+    for name, colour in ((\"grey\", GREY), (\"dark\", rgb(L.COLOURS[\"dark\"])), (\"green\", (40, 186, 150)),\n+                         (\"red\", (214, 30, 40)), (\"yellow\", (250, 190, 30)), (\"white\", (226, 226, 230)),\n+                         (\"case\", CASE), (\"lit\", (90, 255, 140))):\n+        x, y = L.SWATCHES[name]\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colour)\n+    path = os.path.join(out, \"gamecube_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+# ---- The disc --------------------------------------------------------------------------------------------------------\n+\n+def disc_side(region, label):\n+    x, y, w, h = region\n+    r = L.DISC[\"r\"]\n+    reg = Region(region, 2 * r, 2 * r, (40, 40, 44))\n+    c = r\n+    if label:\n+        art = space((600, 600))\n+        d = ImageDraw.Draw(art)\n+        ship(d, 220, 250, 3.6, \"#FFFFFF\", \"#FFB23C\")\n+        x0, y0, x1, y1 = reg.box(0.0, 0.0, 2 * r, 2 * r)\n+        reg.img.paste(art.convert(\"RGB\").resize((round(x1 - x0), round(y1 - y0)), Image.LANCZOS), (0, 0))\n+        reg.rect_(0.0, c + 10.0, 2 * r, 2 * r, (18, 18, 20))\n+        reg.text(\"ODYSSEY\", \"ariblk.ttf\", 9.0, c, c - 22.0, (255, 214, 70))\n+        logo(reg, c, c + 20.0, 26.0, (220, 220, 226))\n+    else:\n+        for k in range(60):                                  # the shiny side: silver, a faint rainbow sheen\n+            t = k / 60\n+            colour = (round(196 + 22 * math.sin(6.3 * t)), round(198 + 22 * math.sin(6.3 * t + 2.1)),\n+                      round(204 + 22 * math.sin(6.3 * t + 4.2)))\n+            reg.circle(c, c, r * (1 - t * 0.8), colour)\n+    reg.circle(c, c, 11.0, (205, 208, 214))\n+    reg.circle(c, c, L.DISC[\"hole\"], (30, 30, 34))\n+    mask = Image.new(\"L\", reg.img.size, 0)\n+    ImageDraw.Draw(mask).ellipse(reg.box(0.0, 0.0, 2 * r, 2 * r), fill=255)\n+    out = Image.new(\"RGBA\", reg.img.size, (190, 192, 198, 255))\n+    out.paste(reg.img, (0, 0), mask)\n+    reg.img = out\n+    return reg\n+\n+\n+def disc_atlas(out):\n+    image = Image.new(\"RGB\", (L.DISC_ATLAS, L.DISC_ATLAS), (190, 192, 198))\n+    disc_side(L.DISC_REGIONS[\"label\"], True).paste(image)\n+    disc_side(L.DISC_REGIONS[\"data\"], False).paste(image)\n+    path = os.path.join(out, \"gamecube_disc.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    print_atlas(out)\n+    disc_atlas(out)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "584360e7112c2c22058385b12bc5636b5601be7f",
          "filename": "tools/console-models/gx4000.py",
          "status": "added",
          "additions": 436,
          "deletions": 0,
          "changes": 436,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fgx4000.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fgx4000.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fgx4000.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,436 @@\n+\"\"\"The console theme's Amstrad GX4000 system model (godot/themes/console/models/systems/gx4000.glb): the GX4000 and its\n+controller.\n+\n+Built in Blender (gx4000_layout.py has the measurements, taken from photographs). The body is lofted through\n+cross-sections along its width, each following the plan's curved front: the front's vertical face, the slope up to the\n+crest, the flat top, the back; the top lower towards the ends, the ends rounded off, the corners drawn in. The rails are\n+half-ellipses swept along the top, their backs domed; the round pads stand at their fronts; the cartridge slot is cut\n+into the body and the switch's slot into the right rail. The controller is its outline given its depth, its edges\n+rounded, the hood raised at its back; its cord runs to the left joystick port. The printing is one texture\n+(gx4000_print.py's), projected from above onto the top and from in front onto the front face.\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: the right end towards the viewer, the\n+top tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m) holding the set; the set\n+holding the cartridge (waiting over the slot, shrunk to nothing), the slot's two flaps (each on its hinge), the power\n+switch, the power light (shrunk to nothing while it's off), PAUSE, and the controller's D-pad and buttons.\n+\n+The `focused` clip (8 s, looping): the cartridge appears over the slot and drops in, pushing the flaps down; the switch\n+slides on and the light lights; the game's played on the D-pad, 1 and 2, PAUSE pressed and pressed again; then the\n+switch goes off and the cartridge rises out, the flaps springing shut behind it, and goes.\n+\n+Two materials (body, gloss) and one 1024\u00b2 texture, within the system model budget. Run from the repo root:\n+\n+    python tools/console-models/gx4000_print.py artifacts/gx4000\n+    blender -b --factory-startup --python tools/console-models/gx4000.py -- artifacts/gx4000 godot/themes/console/models/systems/gx4000.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bmesh\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import gx4000_layout as L  # noqa: E402\n+from amiga import paint, rounded, solid  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, ease_out, key, phase, smooth  # noqa: E402\n+from mega_drive_cartridge import bevel, boolean, box, export, join, material, new_object, triangles  # noqa: E402\n+\n+TILT_YAW = -24.0     # degrees about the vertical: the right end comes towards the viewer\n+TILT_PITCH = 30.0    # degrees about the horizontal: the top tips up towards the viewer\n+RINGS = 49\n+ROUGHNESS = dict(body=0.5, gloss=0.3)\n+\n+\n+def atlas_uv(region, w, h):\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+def swatch(name):\n+    x, y = L.SWATCHES[name]\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+def table(points, x):\n+    \"\"\"Linear interpolation in a table of (x, value), x from the first to the last.\"\"\"\n+    x = min(max(x, points[0][0]), points[-1][0])\n+    for (x0, v0), (x1, v1) in zip(points, points[1:]):\n+        if x0 <= x <= x1:\n+            return v0 + (v1 - v0) * (x - x0) / (x1 - x0)\n+    return points[-1][1]\n+\n+\n+def arc(cy, cz, r, a0, a1, n):\n+    angles = [math.radians(a0 + (a1 - a0) * k / n) for k in range(n)]\n+    return [(cy + r * math.cos(a), cz + r * math.sin(a)) for a in angles]\n+\n+\n+def bezier(p0, p1, p2, p3, n):\n+    out = []\n+    for k in range(n):\n+        t = k / n\n+        a, b, c, d = (1 - t) ** 3, 3 * t * (1 - t) ** 2, 3 * t * t * (1 - t), t ** 3\n+        out.append((a * p0[0] + b * p1[0] + c * p2[0] + d * p3[0], a * p0[1] + b * p1[1] + c * p2[1] + d * p3[1]))\n+    return out\n+\n+\n+def curve(p0, p1, p2, p3, n):\n+    \"\"\"A cubic Bezier's n + 1 points, in 3D.\"\"\"\n+    p0, p1, p2, p3 = (Vector(p) for p in (p0, p1, p2, p3))\n+    return [p0 * (1 - t) ** 3 + p1 * 3 * t * (1 - t) ** 2 + p2 * 3 * t * t * (1 - t) + p3 * t ** 3\n+            for t in [k / n for k in range(n + 1)]]\n+\n+\n+def circle(cx, cy, r, n=24):\n+    return [(cx + r * math.cos(2 * math.pi * k / n), cy + r * math.sin(2 * math.pi * k / n)) for k in range(n)]\n+\n+\n+def top_height(x):\n+    return table(L.TOP_HEIGHT, abs(x))\n+\n+\n+def front_y(x):\n+    return table(L.FRONT_CURVE, abs(x))\n+\n+\n+# ---- The console -----------------------------------------------------------------------------------------------------\n+\n+def section(x):\n+    \"\"\"The body's cross-section at x, (y, z) points round from the front face's foot: up the face, the slope, the top,\n+    the back and the underside.\"\"\"\n+    yf, h = front_y(x), top_height(x)\n+    crest = L.CREST * h / L.TOP_HEIGHT[0][1]\n+    face = min(L.FACE, crest - 6.0)\n+    rb, rk = L.ROUND[\"bottom\"], L.ROUND[\"back\"]\n+    base = L.FEET\n+    points = [(yf, base + rb), (yf, (base + rb + face) / 2)]\n+    points += bezier((yf, face), (yf + 0.5, face + 6.0), (yf + L.SLOPE * 0.55, crest), (yf + L.SLOPE, crest), 8)\n+    points += bezier((yf + L.SLOPE, crest), (yf + L.SLOPE + 20.0, crest + (h - crest) * 0.6), (96.0, h), (110.0, h), 6)\n+    points += [(110.0, h), (140.0, h)] + arc(L.DEPTH - rk, h - rk, rk, 90.0, 0.0, 4)\n+    points += [(L.DEPTH, h - rk), (L.DEPTH, (h - rk + base + rb) / 2)]\n+    points += arc(L.DEPTH - rb, base + rb, rb, 0.0, -90.0, 3) + [(110.0, base), (60.0, base)]\n+    points += arc(yf + rb, base + rb, rb, -90.0, -180.0, 3)\n+    return points\n+\n+\n+def drawn_in(r, x):\n+    e = min(r, max(0.0, abs(x) - (L.WIDTH / 2 - r)))\n+    return r - math.sqrt(r * r - e * e)\n+\n+\n+def body(m):\n+    \"\"\"The body lofted along x, drawn in round the plan's corners and the ends' bottom edges, capped at the ends.\"\"\"\n+    rings = []\n+    for k in range(RINGS):\n+        x = L.WIDTH / 2 * math.sin(math.pi / 2 * (2 * k / (RINGS - 1) - 1))\n+        dp, db = drawn_in(L.ROUND[\"plan\"], x), drawn_in(L.ROUND[\"bottom\"], x)\n+        yf = front_y(x)\n+        span = L.DEPTH - yf\n+        ring = []\n+        for y, z in section(x):\n+            t = (y - yf) / span                                      # 0 at the front, 1 at the back\n+            ring.append((x, yf + dp + (y - yf) * (span - 2 * dp) / span if 0 <= t <= 1 else y,\n+                         z + db * (1 - (z - L.FEET) / max(1.0, top_height(x) - L.FEET))))\n+        rings.append(ring)\n+    bm = bmesh.new()\n+    verts = [[bm.verts.new(p) for p in ring] for ring in rings]\n+    n = len(rings[0])\n+    for a, b in zip(verts, verts[1:]):\n+        for i in range(n):\n+            j = (i + 1) % n\n+            bm.faces.new([a[i], a[j], b[j], b[i]])\n+    ends = [bm.faces.new(verts[0]), bm.faces.new(list(reversed(verts[-1])))]\n+    bmesh.ops.triangulate(bm, faces=ends, quad_method=\"BEAUTY\", ngon_method=\"EAR_CLIP\")\n+    bmesh.ops.recalc_face_normals(bm, faces=bm.faces)\n+    return new_object(\"gx4000\", bm, m[\"body\"])\n+\n+\n+def rail(m, side):\n+    \"\"\"A rail: a half-ellipse swept from its front end back, its back domed; sunk into the top.\"\"\"\n+    cx, half, rise, (y0, y1) = side * L.RAILS[\"x\"], L.RAILS[\"half\"], L.RAILS[\"rise\"], L.RAILS[\"y\"]\n+    base = top_height(L.RAILS[\"x\"]) - 8.0\n+    tall = rise + 8.0\n+    rings = []\n+    steps = [y0 + (y1 - 24.0 - y0) * k / 10 for k in range(11)] + [y1 - 24.0 + 24.0 * math.sin(math.pi / 2 * k / 6)\n+                                                                    for k in range(1, 7)]\n+    for y in steps:\n+        s = 1.0 if y <= y1 - 24.0 else math.sqrt(max(0.02, 1 - ((y - (y1 - 24.0)) / 24.0) ** 2))\n+        ring = [(cx + half * s * math.cos(math.radians(a)), y, base + tall * s * math.sin(math.radians(a)))\n+                for a in range(0, 181, 12)]\n+        ring += [(cx - half * s, y, base - 6.0), (cx + half * s, y, base - 6.0)]\n+        rings.append(ring)\n+    part = solid(\"rail\", rings, m[\"body\"])\n+    return part\n+\n+\n+def console(m):\n+    \"\"\"The body, the rails (the switch's slot in the right one), the pads, the slot cut into the body, the feet;\n+    printed from above and from in front.\"\"\"\n+    shell = body(m)\n+    (sx, sy), (sl, sw) = L.SLOT[\"at\"], L.SLOT[\"size\"]\n+    top = top_height(sx)\n+    boolean(shell, solid(\"slot\", [[(x, y, z) for x, y in rounded(sx, sy, sl, sw, 3.0, 3)]\n+                                  for z in (top - L.SLOT[\"depth\"], top + 10.0)], m[\"body\"]))\n+    rails = [rail(m, -1), rail(m, 1)]\n+    (wx0, wx1), (wy0, wy1) = L.SWITCH[\"x\"], L.SWITCH[\"y\"]\n+    for part in (shell, rails[1]):\n+        boolean(part, box(\"switch_slot\", wx0, wx1, wy0, wy1, top - 4.0, top + 20.0, m[\"body\"]))\n+    pads = []\n+    for side in (-1, 1):\n+        x, y = side * L.RAILS[\"x\"], L.PADS[\"y\"]\n+        z = top_height(L.RAILS[\"x\"]) - 1.0\n+        pad = solid(\"pad\", [[(px, py, pz) for px, py in circle(x, y, L.PADS[\"r\"], 40)] for pz in (18.0, z)], m[\"body\"])\n+        bevel(pad, 1.0, segments=2)\n+        pads.append(pad)\n+    feet = [box(\"foot\", x - 10.0, x + 10.0, y - 8.0, y + 8.0, 0.0, L.FEET + 1.0, m[\"body\"])\n+            for x in (-90.0, 90.0) for y in (45.0, 160.0)]\n+    whole = join([shell] + rails + pads + feet, \"gx4000\")\n+\n+    paint(whole, m[\"body\"], swatch(\"body\"), lambda c, n, name: True)\n+    above = atlas_uv(L.REGIONS[\"top\"], L.WIDTH, L.DEPTH)\n+    paint(whole, m[\"body\"], lambda co: above(co.x + L.WIDTH / 2, L.DEPTH - co.y), lambda c, n, name: n.z > 0.35)\n+    ahead = atlas_uv(L.REGIONS[\"front\"], L.WIDTH, 36.0)\n+    paint(whole, m[\"body\"], lambda co: ahead(co.x + L.WIDTH / 2, 36.0 - co.z),\n+          lambda c, n, name: n.y < -0.6 and n.z <= 0.35 and c.y < 40.0)\n+    inside = lambda c: abs(c.x - sx) < sl / 2 + 0.5 and abs(c.y - sy) < sw / 2 + 0.5 and c.z < top - 0.5   # noqa: E731\n+    paint(whole, m[\"body\"], swatch(\"grey\"), lambda c, n, name: inside(c))\n+    paint(whole, m[\"body\"], swatch(\"dark\"),\n+          lambda c, n, name: (wx0 - 0.5 < c.x < wx1 + 0.5 and wy0 - 0.5 < c.y < wy1 + 0.5 and c.z < top + 7.0)\n+          or (inside(c) and c.z < top - L.SLOT[\"depth\"] + 0.5))\n+    return whole\n+\n+\n+# ---- The moving parts ------------------------------------------------------------------------------------------------\n+\n+def switch(m):\n+    \"\"\"The pink switch, its middle at its origin.\"\"\"\n+    (x0, x1), (y0, y1), h = L.SWITCH[\"x\"], L.SWITCH[\"y\"], L.SWITCH[\"height\"]\n+    part = box(\"power\", -(x1 - x0) / 2 + 0.8, (x1 - x0) / 2 - 0.8, -5.0, 5.0, -h, h, m[\"gloss\"])\n+    bevel(part, 0.8, segments=1)\n+    paint(part, m[\"gloss\"], swatch(\"pink\"), lambda c, n, name: True)\n+    return part, Vector(((x0 + x1) / 2, y1 - 10.0, top_height(L.RAILS[\"x\"]) + L.RAILS[\"rise\"] - 2.0))   # off: back\n+\n+\n+def light(m):\n+    (x, y), (w, d) = L.LIGHT[\"at\"], L.LIGHT[\"size\"]\n+    part = box(\"power_light\", -w / 2, w / 2, -d / 2, d / 2, -0.3, 0.3, m[\"gloss\"])\n+    paint(part, m[\"gloss\"], swatch(\"lit\"), lambda c, n, name: True)\n+    return part, Vector((x, y, top_height(L.RAILS[\"x\"]) - 0.6))\n+\n+\n+def pause(m):\n+    \"\"\"PAUSE: a thin disc on the right pad's middle, its print the pad's.\"\"\"\n+    x, y, r = L.RAILS[\"x\"], L.PADS[\"y\"], L.PADS[\"r\"] - 3.0\n+    z = top_height(L.RAILS[\"x\"]) - 1.0\n+    part = solid(\"pause\", [[(px, py, pz) for px, py in circle(0.0, 0.0, r, 32)] for pz in (-2.0, 0.6)], m[\"body\"])\n+    above = atlas_uv(L.REGIONS[\"top\"], L.WIDTH, L.DEPTH)\n+    paint(part, m[\"body\"], swatch(\"body\"), lambda c, n, name: True)\n+    paint(part, m[\"body\"], lambda co: above(co.x + x + L.WIDTH / 2, L.DEPTH - (co.y + y)), lambda c, n, name: n.z > 0.9)\n+    return part, Vector((x, y, z))\n+\n+\n+def flap(m, side):\n+    \"\"\"One of the slot's flaps, its origin on its hinge (the slot's front edge, side -1, or its back edge, side 1),\n+    reaching to the middle, drooping a little.\"\"\"\n+    (sx, sy), (sl, sw), f = L.SLOT[\"at\"], L.SLOT[\"size\"], L.FLAPS\n+    reach = sw / 2 - 0.3\n+    y0, y1 = (0.0, reach) if side < 0 else (-reach, 0.0)\n+    part = box(\"flap\", -sl / 2 + 1.0, sl / 2 - 1.0, y0, y1, -f[\"thickness\"], 0.0, m[\"body\"])\n+    paint(part, m[\"body\"], swatch(\"grey\"), lambda c, n, name: True)\n+    part.data.transform(Matrix.Rotation(math.radians(f[\"droop\"] * side), 4, \"X\"))\n+    return part, Vector((sx, sy + side * sw / 2, top_height(sx) - f[\"sunk\"]))\n+\n+\n+def cartridge(m):\n+    \"\"\"The cartridge, its foot's middle at its origin, its label to the front (-y).\"\"\"\n+    w, t, h = L.CART[\"size\"]\n+    part = solid(\"cartridge\", [[(x, y, z) for x, z in rounded(0.0, h / 2, w, h, 4.0, 3)] for y in (-t / 2, t / 2)],\n+                 m[\"body\"])\n+    bevel(part, 1.2, segments=2)\n+    paint(part, m[\"body\"], swatch(\"grey\"), lambda c, n, name: True)\n+    lw, lh = L.CART[\"label\"]\n+    corners = ((-lw / 2, h - 4.0 - lh), (lw / 2, h - 4.0 - lh), (lw / 2, h - 4.0), (-lw / 2, h - 4.0))\n+    label = solid(\"label\", [[(x, y, z) for x, z in corners] for y in (-t / 2 - 0.15, -t / 2 + 0.5)], m[\"gloss\"])\n+    face = atlas_uv(L.REGIONS[\"cart\"], lw, lh)\n+    paint(label, m[\"gloss\"], swatch(\"grey\"), lambda c, n, name: True)\n+    paint(label, m[\"gloss\"], lambda co: face(co.x + lw / 2, h - 4.0 - co.z), lambda c, n, name: n.y < -0.9)\n+    return join([part, label], \"cartridge\")\n+\n+\n+# ---- The controller --------------------------------------------------------------------------------------------------\n+\n+def pad_outline():\n+    \"\"\"The controller's outline from above, its front-left corner rounded wider; y back from its front edge.\"\"\"\n+    pw, pd, _ = L.PAD[\"size\"]\n+    r, big = L.PAD[\"r\"], L.PAD[\"corner\"]\n+    points = []\n+    for (cx, cy, rr, a0) in ((pw / 2 - r, pd - r, r, 0), (-pw / 2 + r, pd - r, r, 90), (-pw / 2 + big, big, big, 180),\n+                             (pw / 2 - r, r, r, 270)):\n+        angles = [math.radians(a0 + 90 * k / 6) for k in range(7)]\n+        points += [(cx + rr * math.cos(a), cy + rr * math.sin(a)) for a in angles]\n+    return points\n+\n+\n+def pad_frame():\n+    (x, y), turn = L.PAD[\"at\"], L.PAD[\"turn\"]\n+    return Matrix.Translation((x, y, 0.0)) @ Matrix.Rotation(math.radians(turn), 4, \"Z\")\n+\n+\n+def controller(m):\n+    \"\"\"The controller in the console's frame: its body and hood, printed; its D-pad and buttons, each with where its\n+    node goes.\"\"\"\n+    pw, pd, ph = L.PAD[\"size\"]\n+    shell = solid(\"controller\", [[(x, y, z) for x, y in pad_outline()] for z in (0.0, ph)], m[\"body\"])\n+    bevel(shell, 3.0, segments=3)\n+    (hx0, hx1), (hy0, hy1), hh, point = L.HOOD[\"x\"], L.HOOD[\"y\"], L.HOOD[\"height\"], L.HOOD[\"point\"]\n+    hood = solid(\"hood\", [[(x, y, z) for x, y in ((hx1, hy1), (hx0, hy1), (hx0, hy0 + point), ((hx0 + hx1) / 2, hy0),\n+                                                  (hx1, hy0 + point))] for z in (ph - 1.0, ph + hh)], m[\"body\"])\n+    bevel(hood, 2.0, segments=2)\n+    whole = join([shell, hood], \"controller\")\n+    paint(whole, m[\"body\"], swatch(\"body\"), lambda c, n, name: True)\n+    face = atlas_uv(L.REGIONS[\"pad\"], pw, pd)\n+    paint(whole, m[\"body\"], lambda co: face(co.x + pw / 2, pd - co.y), lambda c, n, name: n.z > 0.9 and c.z < ph + 0.5)\n+    to_world = pad_frame()\n+    whole.data.transform(to_world)\n+    turn = Matrix.Rotation(math.radians(L.PAD[\"turn\"]), 4, \"Z\")\n+    nodes = {}\n+    d = L.DPAD\n+    h, w = d[\"size\"] / 2, d[\"arm\"] / 2\n+    cross = [(w, -h), (w, -w), (h, -w), (h, w), (w, w), (w, h), (-w, h), (-w, w), (-h, w), (-h, -w), (-w, -w), (-w, -h)]\n+    dpad = solid(\"dpad\", [[(x, y, z) for x, y in cross] for z in (-1.5, d[\"height\"])], m[\"body\"])\n+    bevel(dpad, 0.8, segments=1)\n+    paint(dpad, m[\"body\"], swatch(\"grey\"), lambda c, n, name: True)\n+    dpad.data.transform(turn)\n+    nodes[\"dpad\"] = (dpad, to_world @ Vector((d[\"at\"][0], d[\"at\"][1], ph)))\n+    for k, (bx, by) in enumerate(L.BUTTONS[\"at\"]):\n+        button = solid(f\"button_{k + 1}\", [[(x, y, z) for x, y in circle(0.0, 0.0, L.BUTTONS[\"r\"], 24)]\n+                                            for z in (-1.5, L.BUTTONS[\"height\"])], m[\"gloss\"])\n+        bevel(button, 1.2, segments=2)\n+        paint(button, m[\"gloss\"], swatch(\"red\"), lambda c, n, name: True)\n+        nodes[f\"button_{k + 1}\"] = (button, to_world @ Vector((bx, by, ph)))\n+    start = to_world @ Vector(((hx0 + hx1) / 2, pd + 1.0, ph - 4.0))\n+    (jx, jz), _ = L.PORTS[\"joy_1\"]\n+    end = Vector((jx, front_y(jx) - 1.0, jz))\n+    cord = tube(curve(start, start + Vector((0.0, 40.0, 0.0)), end + Vector((0.0, -50.0, -10.0)), end, 24), 2.6, m)\n+    return whole, cord, nodes\n+\n+\n+def tube(points, r, m, sides=8):\n+    bm = bmesh.new()\n+    rings = []\n+    normal = Vector((0.0, 0.0, 1.0))\n+    n = len(points) - 1\n+    for k, p in enumerate(points):\n+        tangent = (points[min(k + 1, n)] - points[max(k - 1, 0)]).normalized()\n+        normal = (normal - tangent * normal.dot(tangent)).normalized()\n+        binormal = tangent.cross(normal)\n+        rings.append([bm.verts.new(p + (normal * math.cos(2 * math.pi * j / sides)\n+                                        + binormal * math.sin(2 * math.pi * j / sides)) * r) for j in range(sides)])\n+    for a, b in zip(rings, rings[1:]):\n+        for j in range(sides):\n+            bm.faces.new([a[j], a[(j + 1) % sides], b[(j + 1) % sides], b[j]])\n+    bmesh.ops.recalc_face_normals(bm, faces=bm.faces)\n+    cord = new_object(\"cord\", bm, m[\"body\"])\n+    paint(cord, m[\"body\"], swatch(\"body\"), lambda c, n, name: True)\n+    return cord\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+def tap(f, f0, hold=2):\n+    return smooth(phase(f, f0, f0 + 3)) * (1 - smooth(phase(f, f0 + 3 + hold, f0 + 7 + hold)))\n+\n+\n+def timeline(f):\n+    above = L.CART[\"above\"]\n+    if f < 6 or f >= 222:\n+        cart = Pose(scale=HIDDEN)\n+    elif f < 14:\n+        cart = Pose(z=above, scale=max(HIDDEN, back_out(phase(f, 6, 13))))\n+    elif f < 24:\n+        cart = Pose(z=above + 2.0 * math.sin(2 * math.pi * phase(f, 14, 24)))\n+    elif f < 36:\n+        cart = Pose(z=above * (1 - ease_in(phase(f, 24, 36))))\n+    elif f < 196:\n+        cart = Pose(z=-2.5 * math.sin(math.pi * phase(f, 36, 44)))\n+    elif f < 212:\n+        cart = Pose(z=above * ease_out(phase(f, 196, 212)))\n+    else:\n+        cart = Pose(z=above, scale=max(HIDDEN, 1 - ease_in(phase(f, 212, 222))))\n+    on = smooth(phase(f, 46, 51)) * (1 - smooth(phase(f, 186, 191)))\n+    if f < 31:\n+        swing = 0.0\n+    elif f < 201:\n+        swing = smooth(phase(f, 31, 34))\n+    else:\n+        swing = 1.0 - back_out(phase(f, 201, 210))\n+    swing *= L.FLAPS[\"open\"] - L.FLAPS[\"droop\"]\n+    t = phase(f, 58, 172)\n+    k = math.sin(math.pi * t)\n+    return {\"cartridge\": cart, \"power\": Pose(y=-L.SWITCH[\"travel\"] * on),\n+            \"flap_front\": Pose(pitch=-swing), \"flap_back\": Pose(pitch=swing),\n+            \"power_light\": Pose() if 50 <= f < 188 else Pose(scale=HIDDEN),\n+            \"pause\": Pose(z=-1.4 * max(tap(f, 118), tap(f, 146))),\n+            \"dpad\": Pose(pitch=10.0 * k * math.sin(2 * math.pi * 3.0 * t),\n+                         roll=10.0 * k * math.cos(2 * math.pi * 2.0 * t)),\n+            \"button_1\": Pose(z=-L.BUTTONS[\"press\"] * max(tap(f, f0) for f0 in (64, 84, 104, 130, 160))),\n+            \"button_2\": Pose(z=-L.BUTTONS[\"press\"] * max(tap(f, f0) for f0 in (74, 94, 136, 166)))}\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    atlas = bpy.data.images.load(os.path.join(images, \"gx4000_print.png\"))\n+    m = {name: material(name, L.COLOURS[\"print\"], r, atlas) for name, r in ROUGHNESS.items()}\n+\n+    body_ = console(m)\n+    pad_body, cord, pad_nodes = controller(m)\n+    whole = join([body_, pad_body, cord], \"gx4000\")\n+    for p in whole.data.polygons:\n+        p.use_smooth = True\n+    whole.data.set_sharp_from_angle(angle=math.radians(35))\n+\n+    root = bpy.data.objects.new(\"gx4000_root\", None)\n+    scene.collection.objects.link(root)\n+    whole.parent = root\n+    parts = {\"power\": switch(m), \"power_light\": light(m), \"pause\": pause(m), \"flap_front\": flap(m, -1),\n+             \"flap_back\": flap(m, 1)}\n+    cart = cartridge(m)\n+    (sx, sy) = L.SLOT[\"at\"]\n+    parts[\"cartridge\"] = (cart, Vector((sx, sy, top_height(sx) - L.CART[\"seat\"])))\n+    parts.update(pad_nodes)\n+    nodes = {}\n+    for name, (part, where) in parts.items():\n+        for p in part.data.polygons:\n+            p.use_smooth = True\n+        part.data.set_sharp_from_angle(angle=math.radians(35))\n+        part.parent = whole\n+        part.location = where\n+        nodes[name] = part\n+\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    points = [tilt @ v.co for v in whole.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [whole] + list(nodes.values())\n+    print(f\"GX4000: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(whole.data)} the set), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "c73900a17b4c13ae789216f27e8eae4490235b64",
          "filename": "tools/console-models/gx4000_layout.py",
          "status": "added",
          "additions": 64,
          "deletions": 0,
          "changes": 64,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fgx4000_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fgx4000_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fgx4000_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,64 @@\n+\"\"\"The Amstrad GX4000's measurements, shared by gx4000_print.py (which draws its printing) and gx4000.py (which builds\n+it). Plain Python: Blender's Python imports it too.\n+\n+The GX4000 (1990) and its controller, a made-up game, Odyssey, in its slot. The console is 250 mm wide, 184 deep and 44\n+high (the Video Games Museum's 25 x 18.4 x 4.4 cm; Evan-Amos's straight photographs of it on Wikimedia Commons, from the\n+front and from below, have the same proportions). Millimetres: x from its middle (left to right), y back from the tip of\n+its front's curve, z up from the table. The plan's front curve and the heights across it were traced from those two\n+photographs, scaled by its width; the top from a museum's photograph of one from above (Wikimedia Commons) and\n+Evan-Amos's from the front left and right; the controller from his photographs of it.\n+\n+The console: its body lofted along its width (the front's vertical face under the slope that rises to the top, flat\n+behind, the back face; lower towards the ends, which are rounded off), the two raised rails along its top (vents on\n+their inner sides) ending in round pads at the front (the power light's on the left, PAUSE on the right), the pink power\n+switch on the right rail, the cartridge slot between them (its two flaps meeting in the middle), AMSTRAD on the front\n+slope, the ports in the front (the stereo jack, the joystick ports either side of the analogue one, the light gun's);\n+the louvres along the back. The controller: its outline, the raised hood at its back where the cord comes out, the D-pad\n+in its dish, buttons 1 and 2, AMSTRAD.\n+\n+One texture (ATLAS square, REGIONS): `top` (the console from above: x across, y down from the back), `front` (its front\n+face, x across, z down from the top of the region), `pad` (the controller from above), `cart` (the cartridge's label),\n+and swatches. A region is (x, y, width, height) in pixels.\"\"\"\n+\n+ATLAS = 1024\n+\n+WIDTH, DEPTH, HEIGHT = 250.0, 184.0, 44.0\n+FEET = 2.0\n+# The plan's front edge: (|x|, how far back from the tip), traced from below.\n+FRONT_CURVE = [(0.0, 0.0), (15.0, 0.5), (25.0, 1.7), (35.0, 3.5), (45.0, 5.9), (55.0, 9.0), (65.0, 12.8), (75.0, 16.3),\n+               (85.0, 19.3), (95.0, 21.4), (105.0, 23.0), (115.0, 23.7), (125.0, 23.8)]\n+# The top's height across the console (|x|, z), traced from the front: flat between the rails, falling to the ends.\n+TOP_HEIGHT = [(0.0, 37.0), (60.0, 37.0), (75.0, 36.0), (85.0, 34.6), (95.0, 32.0), (105.0, 28.7), (115.0, 24.7),\n+              (125.0, 18.0)]\n+FACE = 22.0                      # the front face's top, where the slope starts (at most a little under the top)\n+SLOPE = 34.0                     # how far back the slope reaches its crest\n+CREST = 34.0                     # the crest's height (at the middle; the top's height scales it towards the ends)\n+ROUND = dict(plan=14.0, bottom=8.0, back=8.0)\n+\n+RAILS = dict(x=69.0, half=20.5, rise=7.0, y=(60.0, 170.0))   # each rail's middle, half width, height over the top,\n+                                                              # front and back ends (y)\n+PADS = dict(y=48.0, r=19.0, height=1.2)                       # the round pads at the rails' fronts\n+LIGHT = dict(at=(-69.0, 58.0), size=(5.0, 2.0))               # the power light, at the left pad's back\n+SWITCH = dict(x=(60.0, 71.0), y=(66.0, 92.0), travel=10.0, height=5.0)   # the pink switch, its slot on the right rail\n+SLOT = dict(at=(-4.0, 76.0), size=(72.0, 18.0), depth=30.0)\n+FLAPS = dict(thickness=1.6, sunk=1.4, droop=6.0, open=84.0)   # the slot's two flaps, hinged along its long edges\n+LOGO = dict(at=(-3.0, 30.0), size=(35.0, 15.0))               # AMSTRAD on the front slope (x, y), from above\n+# The front face's ports: (x, z) of their middles, their sizes.\n+PORTS = dict(jack=((-81.0, 14.8), 3.0), joy_1=((-45.0, 17.2), (21.0, 10.8)), analogue=((-0.6, 17.2), (30.0, 11.5)),\n+             joy_2=((42.8, 17.2), (21.0, 10.8)), gun=((77.0, 16.4), (15.0, 12.6)))\n+\n+CART = dict(size=(68.0, 16.0, 78.0), seat=30.0, label=(56.0, 44.0), above=60.0)   # w, t, h; how deep it sits\n+\n+# The controller, from above in its own frame (x across, y back from its front edge), and where it lies.\n+PAD = dict(size=(128.0, 58.0, 20.0), r=7.0, corner=20.0, at=(-118.0, -96.0), turn=12.0)\n+HOOD = dict(x=(-30.0, 32.0), y=(30.0, 58.0), height=5.0, point=12.0)   # the raised hood at its back\n+DPAD = dict(at=(-41.0, 28.0), dish=15.0, size=24.0, arm=8.4, height=2.6)\n+BUTTONS = dict(at=[(25.0, 15.0), (45.0, 13.0)], r=6.6, height=3.4, press=2.2)\n+\n+COLOURS = dict(body=\"#E3E3DE\", grey=\"#B9B9B5\", pink=\"#D7206A\", red=\"#D2223A\", dark=\"#2A2A2C\", print=\"#FFFFFF\")\n+\n+REGIONS = dict(top=(0, 0, 750, 552), front=(0, 560, 750, 108), pad=(0, 676, 512, 232), cart=(520, 676, 240, 189))\n+SWATCHES = dict(body=(800, 40), grey=(832, 40), pink=(864, 40), red=(896, 40), dark=(928, 40), lit=(960, 40))\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s"
        },
        {
          "sha": "b58f86e110a088e80639228d73a6c0f442895e7b",
          "filename": "tools/console-models/gx4000_print.py",
          "status": "added",
          "additions": 172,
          "deletions": 0,
          "changes": 172,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fgx4000_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fgx4000_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fgx4000_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,172 @@\n+\"\"\"The Amstrad GX4000's printing (gx4000_print.png), for the console theme's model (gx4000.py).\n+\n+One image, laid out as gx4000_layout.REGIONS says: the console's top from above (the louvres along the back, the lines\n+of its steps, the vents on the rails' inner sides, the wings' chevrons, the pads' symbols, AMSTRAD on the front slope),\n+its front face (the seam, the stereo jack, the joystick ports either side of the analogue one, the light gun's), the\n+controller from above (its raised border, the D-pad's dish, AMSTRAD, 1 and 2), the cartridge's label (a made-up game,\n+Odyssey), and swatches. Run it with the system Python (Pillow):\n+\n+    python tools/console-models/gx4000_print.py <out folder>\n+\"\"\"\n+\n+import os\n+import sys\n+\n+from PIL import Image, ImageDraw, ImageFont\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import gx4000_layout as L  # noqa: E402\n+from psp_print import ship, space  # noqa: E402\n+from retro_pc_print import rgb  # noqa: E402\n+from windows_pc_print import FONTS, Region  # noqa: E402\n+\n+BODY = rgb(L.COLOURS[\"body\"])\n+GREY = rgb(L.COLOURS[\"grey\"])\n+PINK = rgb(L.COLOURS[\"pink\"])\n+DARK = rgb(L.COLOURS[\"dark\"])\n+LINE = (196, 196, 191)\n+WHITE = (250, 250, 248)\n+\n+\n+def amstrad(reg, cx, cy, w, h):\n+    \"\"\"AMSTRAD's logo, w x h about (cx, cy): white letters on a grey box, a pink stripe slanting behind them.\"\"\"\n+    x0, y0 = cx - w / 2, cy - h / 2\n+    reg.rect_(x0, y0, x0 + w, y0 + h, (138, 141, 138))\n+    reg.poly([(x0 + w * 0.56, y0 + h), (x0 + w * 0.70, y0 + h), (x0 + w * 0.86, y0), (x0 + w * 0.72, y0)], PINK)\n+    f = ImageFont.truetype(FONTS + \"bahnschrift.ttf\", 200)\n+    f.set_variation_by_name(\"Bold\")\n+    mask = Image.new(\"L\", (round(f.getlength(\"AMSTRAD\")) + 60, 280), 0)\n+    ImageDraw.Draw(mask).text((30, 140), \"AMSTRAD\", font=f, fill=255, anchor=\"lm\")\n+    mask = mask.crop(mask.getbbox())\n+    a, b, c, d = reg.box(x0 + w * 0.06, y0 + h * 0.2, x0 + w * 0.94, y0 + h * 0.8)\n+    mask = mask.resize((max(1, round(c - a)), max(1, round(d - b))), Image.LANCZOS)\n+    lean = round(mask.height * 0.18)\n+    wide = Image.new(\"L\", (mask.width + lean, mask.height), 0)\n+    wide.paste(mask, (0, 0))\n+    wide = wide.transform(wide.size, Image.AFFINE, (1, 0.18, -0.18 * mask.height, 0, 1, 0), resample=Image.BICUBIC)\n+    reg.img.paste(Image.new(\"RGBA\", wide.size, WHITE + (255,)), (round(a), round(b)), wide)\n+\n+\n+# ---- The console -----------------------------------------------------------------------------------------------------\n+\n+def top():\n+    \"\"\"The console from above: x across (-125 at the left), y down from the back (184).\"\"\"\n+    reg = Region(L.REGIONS[\"top\"], L.WIDTH, L.DEPTH, BODY)\n+\n+    def at(x, y):\n+        return x + L.WIDTH / 2, L.DEPTH - y\n+\n+    # The louvres along the back, between the wings.\n+    for k in range(40):\n+        x = -96.0 + 4.8 * k\n+        reg.line([at(x, 183.0), at(x + 2.6, 172.0)], LINE, 1.1)\n+    # The steps' lines across the top, and the wings' chevrons.\n+    for y in (150.0, 128.0):\n+        reg.line([at(-48.0, y), at(48.0, y)], LINE, 0.5)\n+    for side in (-1, 1):\n+        x0 = side * (L.RAILS[\"x\"] + L.RAILS[\"half\"] + 1.0)\n+        x1 = side * 124.0\n+        for y in (142.0, 104.0):\n+            reg.line([at(x0, y), at((x0 + x1) / 2, y - 7.0), at(x1, y)], LINE, 0.6)\n+    # The vents on the rails' inner sides: the left rail's along its length, the right's behind the switch.\n+    rx, half = L.RAILS[\"x\"], L.RAILS[\"half\"]\n+    for k in range(8):\n+        y = 78.0 + 10.0 * k\n+        reg.rect_(*at(-rx + half - 9.5, y + 1.6), *at(-rx + half - 1.5, y - 1.6), DARK, r=0.8)\n+    for k in range(5):\n+        y = 108.0 + 11.0 * k\n+        reg.rect_(*at(rx - half + 1.5, y + 1.6), *at(rx - half + 9.5, y - 1.6), DARK, r=0.8)\n+    # The pads: the power symbol on the left, II on the right.\n+    py, pr = L.PADS[\"y\"], L.PADS[\"r\"]\n+    reg.circle(*at(-rx, py), pr, (222, 222, 217), outline=LINE, width=0.5)\n+    reg.circle(*at(rx, py), pr, (222, 222, 217), outline=LINE, width=0.5)\n+    reg.circle(*at(-rx, py + 8.0), 2.2, None, outline=(150, 150, 146), width=0.5)\n+    reg.line([at(-rx, py + 10.8), at(-rx, py + 8.4)], (150, 150, 146), 0.5)\n+    for dx in (-1.6, 1.6):\n+        reg.rect_(*at(rx + dx - 0.5, py - 2.0), *at(rx + dx + 0.5, py - 6.0), (150, 150, 146))\n+    # AMSTRAD on the front slope.\n+    (lx, ly), (lw, lh) = L.LOGO[\"at\"], L.LOGO[\"size\"]\n+    amstrad(reg, *at(lx, ly), lw, lh)\n+    return reg\n+\n+\n+def front():\n+    \"\"\"The front face: x across (-125 at the left), z down from 36.\"\"\"\n+    reg = Region(L.REGIONS[\"front\"], L.WIDTH, 36.0, BODY)\n+\n+    def at(x, z):\n+        return x + L.WIDTH / 2, 36.0 - z\n+\n+    reg.line([at(-125.0, 15.2), at(125.0, 15.2)], LINE, 0.4)\n+    (jx, jz), jr = L.PORTS[\"jack\"]\n+    reg.circle(*at(jx, jz), jr + 1.2, (205, 205, 200))\n+    reg.circle(*at(jx, jz), jr, (34, 34, 36))\n+    reg.circle(*at(jx, jz), jr * 0.45, (90, 90, 92))\n+    for name, pins in ((\"joy_1\", (5, 4)), (\"analogue\", (8, 7)), (\"joy_2\", (5, 4))):\n+        (cx, cz), (w, h) = L.PORTS[name]\n+        shell = [at(cx - w / 2, cz + h / 2), at(cx + w / 2, cz + h / 2), at(cx + w / 2 - 2.0, cz - h / 2),\n+                 at(cx - w / 2 + 2.0, cz - h / 2)]\n+        reg.poly(shell, (176, 178, 180))\n+        inner = [at(cx - w / 2 + 1.6, cz + h / 2 - 1.4), at(cx + w / 2 - 1.6, cz + h / 2 - 1.4),\n+                 at(cx + w / 2 - 3.2, cz - h / 2 + 1.4), at(cx - w / 2 + 3.2, cz - h / 2 + 1.4)]\n+        reg.poly(inner, (30, 30, 32))\n+        for row, n in ((1.8, pins[0]), (-1.8, pins[1])):\n+            for k in range(n):\n+                x = cx - (n - 1) * 1.4 + 2.8 * k\n+                reg.circle(*at(x, cz + row), 0.5, (196, 168, 96))\n+    (gx, gz), (gw, gh) = L.PORTS[\"gun\"]\n+    reg.rect_(*at(gx - gw / 2, gz + gh / 2), *at(gx + gw / 2, gz - gh / 2), (36, 36, 38))\n+    reg.rect_(*at(gx - gw / 2 + 2.4, gz + gh / 2 - 2.4), *at(gx + gw / 2 - 2.4, gz - gh / 2 + 3.6), (14, 14, 16))\n+    return reg\n+\n+\n+# ---- The controller and the cartridge --------------------------------------------------------------------------------\n+\n+def pad():\n+    \"\"\"The controller from above: x across (-64 at the left), y down from its back edge.\"\"\"\n+    pw, pd, _ = L.PAD[\"size\"]\n+    reg = Region(L.REGIONS[\"pad\"], pw, pd, BODY)\n+\n+    def at(x, y):\n+        return x + pw / 2, pd - y\n+\n+    reg.rect_(*at(-pw / 2 + 4.0, pd - 4.0), *at(pw / 2 - 4.0, 4.0), None, r=5.0, outline=LINE, width=0.5)\n+    (dx, dy), dish = L.DPAD[\"at\"], L.DPAD[\"dish\"]\n+    reg.circle(*at(dx, dy), dish, (176, 177, 173), outline=(160, 160, 156), width=0.6)\n+    amstrad(reg, *at(-4.0, 17.0), 30.0, 12.0)\n+    for k, (bx, by) in enumerate(L.BUTTONS[\"at\"]):\n+        reg.circle(*at(bx, by), L.BUTTONS[\"r\"] + 1.6, (232, 232, 228), outline=LINE, width=0.4)\n+        reg.text(str(k + 1), \"arialbd.ttf\", 4.0, *at(bx, by - 11.0), (60, 60, 62))\n+    return reg\n+\n+\n+def cart():\n+    lw, lh = L.CART[\"label\"]\n+    reg = Region(L.REGIONS[\"cart\"], lw, lh, (24, 20, 40))\n+    art = space((560, 440)).convert(\"RGB\")\n+    ship(ImageDraw.Draw(art), 170, 270, 3.4, \"#FFFFFF\", \"#FFB23C\")\n+    x0, y0, x1, y1 = reg.box(0.0, 0.0, lw, lh)\n+    reg.img.paste(art.resize((round(x1 - x0), round(y1 - y0)), Image.LANCZOS), (round(x0), round(y0)))\n+    reg.rect_(0.0, 0.0, lw, 8.0, WHITE)\n+    reg.rect_(0.0, 8.0, lw, 9.0, PINK)\n+    reg.text(\"GX4000\", \"arialbd.ttf\", 4.2, lw - 2.0, 4.0, (60, 60, 62), anchor=\"rm\")\n+    reg.text(\"ODYSSEY\", \"ariblk.ttf\", 7.4, lw / 2, 16.0, (255, 214, 70))\n+    return reg\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), BODY)\n+    for reg in (top(), front(), pad(), cart()):\n+        reg.paste(image)\n+    d = ImageDraw.Draw(image)\n+    colours = dict(body=BODY, grey=GREY, pink=PINK, red=rgb(L.COLOURS[\"red\"]), dark=DARK, lit=(255, 60, 40))\n+    for name, (x, y) in L.SWATCHES.items():\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colours[name])\n+    path = os.path.join(out, \"gx4000_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "c68f26f8015e233d1e7da23e71f4a1e886578798",
          "filename": "tools/console-models/intellivision.py",
          "status": "added",
          "additions": 248,
          "deletions": 0,
          "changes": 248,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fintellivision.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fintellivision.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fintellivision.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,248 @@\n+\"\"\"The console theme's Intellivision system model (godot/themes/console/models/systems/intellivision.glb): Mattel's\n+Intellivision, its two controllers docked in its top.\n+\n+Built in Blender (intellivision_layout.py has the measurements, taken from photographs). The body is its side profile\n+(the front leaning out to the top's edge) across its width, its edges rounded, the docks' band sunk into its top and the\n+cartridge slot cut into its right end; the controllers are rounded slabs lying in the band, their discs separate. The\n+printing is one texture (intellivision_print.py's), projected from above onto the tops and from in front onto the\n+front.\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: the right end towards the viewer, the\n+top tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m) holding the console; the\n+console holding the cartridge (waiting off its right end, shrunk to nothing), the OFF-ON switch, the left controller\n+and the right one (holding its disc).\n+\n+The `focused` clip (8 s, looping): the cartridge appears off the right end and slides into the slot; the switch goes to\n+ON; the right controller rises out of its dock and tips towards the viewer, its disc rocking as the game's played,\n+then settles back; the switch goes off and the cartridge slides out and goes.\n+\n+Two materials (body, gloss) and one 1024\u00b2 texture, within the system model budget. Run from the repo root:\n+\n+    python tools/console-models/intellivision_print.py artifacts/intellivision\n+    blender -b --factory-startup --python tools/console-models/intellivision.py -- artifacts/intellivision godot/themes/console/models/systems/intellivision.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import intellivision_layout as L  # noqa: E402\n+from amiga import paint, rounded, solid  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, ease_out, key, phase, smooth  # noqa: E402\n+from mega_drive_cartridge import bevel, boolean, box, export, join, material, prism_x, triangles  # noqa: E402\n+\n+TILT_YAW = -24.0     # degrees about the vertical: the right end comes towards the viewer\n+TILT_PITCH = 30.0    # degrees about the horizontal: the top tips up towards the viewer\n+ROUGHNESS = dict(body=0.45, gloss=0.3)\n+Z0 = L.FEET          # the body's underside, on its feet\n+\n+\n+def atlas_uv(region, w, h):\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+def swatch(name):\n+    x, y = L.SWATCHES[name]\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+def colour(obj, mat, name):\n+    paint(obj, mat, swatch(name), lambda c, n, kind: True)\n+    return obj\n+\n+\n+def circle(cx, cy, r, n=24):\n+    return [(cx + r * math.cos(2 * math.pi * k / n), cy + r * math.sin(2 * math.pi * k / n)) for k in range(n)]\n+\n+\n+def upright(name, points, z0, z1, mat):\n+    return solid(name, [[(x, y, z) for x, y in points] for z in (z0, z1)], mat)\n+\n+\n+TOP = atlas_uv(L.REGIONS[\"top\"], L.WIDTH, L.DEPTH)\n+FRONT = atlas_uv(L.REGIONS[\"front\"], L.WIDTH, L.TOP)\n+PAD = atlas_uv(L.REGIONS[\"pad\"], L.PAD[\"length\"], L.PAD[\"width\"])\n+\n+\n+# ---- The console -----------------------------------------------------------------------------------------------------\n+\n+def console(m):\n+    \"\"\"The body: its profile across its width, its edges rounded, the band and the slot cut in, the feet; printed.\"\"\"\n+    half = L.WIDTH / 2\n+    body = prism_x(\"intellivision\", [(y, Z0 + z) for y, z in L.PROFILE], -half, half, m[\"body\"])\n+    bevel(body, 3.0, segments=2, angle=40)\n+    (b0, b1), depth = L.BAND[\"y\"], L.BAND[\"depth\"]\n+    boolean(body, box(\"band\", -half + 10.0, half - 10.0, b0, b1, Z0 + L.TOP - depth, Z0 + L.TOP + 5.0, m[\"body\"]))\n+    (s0, s1), (sz0, sz1) = L.SLOT[\"y\"], L.SLOT[\"z\"]\n+    boolean(body, box(\"slot\", half - 130.0, half + 5.0, s0, s1, Z0 + sz0, Z0 + sz1, m[\"body\"]))\n+    colour(body, m[\"body\"], \"body\")\n+    paint(body, m[\"body\"], lambda co: TOP(co.x + half, L.DEPTH - co.y),\n+          lambda c, n, kind: n.z > 0.9 and c.z > Z0 + L.TOP - depth - 0.5)\n+    paint(body, m[\"body\"], lambda co: FRONT(co.x + half, Z0 + L.TOP - co.z),\n+          lambda c, n, kind: n.y < -0.5 and c.y < 12.0)\n+    paint(body, m[\"body\"], swatch(\"slot\"),\n+          lambda c, n, kind: c.x > half - 130.0 and s0 - 0.5 < c.y < s1 + 0.5 and Z0 + sz0 - 0.5 < c.z < Z0 + sz1 + 0.5)\n+    feet = [colour(upright(\"foot\", circle(x, y, 9.0, 12), 0.0, Z0 + 1.0, m[\"body\"]), m[\"body\"], \"dark\")\n+            for x in (-160.0, 160.0) for y in (30.0, 200.0)]\n+    return join([body] + feet, \"intellivision\")\n+\n+\n+def switch(m):\n+    (sx, sy), (sw, sh) = L.SWITCH[\"at\"], L.SWITCH[\"size\"]\n+    part = box(\"power\", -sw / 2 + 4.0, sw / 2 - 12.0, -sh / 2 + 1.0, sh / 2 - 1.0, -1.0, 3.0, m[\"gloss\"])\n+    bevel(part, 0.8, segments=1)\n+    colour(part, m[\"gloss\"], \"gold\")\n+    return part, Vector((sx - L.SWITCH[\"travel\"] / 2, sy, Z0 + L.TOP))\n+\n+\n+# ---- A controller ----------------------------------------------------------------------------------------------------\n+\n+def controller(m, name):\n+    \"\"\"A controller in its own frame (x across it, y along it from its disc's end (-76) to its cord's (+76), z up from\n+    its underside): its body, the keypad's plate, the side buttons; its disc apart, with where its node goes.\"\"\"\n+    p = L.PAD\n+    length, width, t = p[\"length\"], p[\"width\"], p[\"thickness\"]\n+    body = upright(name, rounded(0.0, 0.0, width, length, 8.0, 4), 0.0, t, m[\"body\"])\n+    bevel(body, 2.4, segments=2, angle=40)\n+\n+    def uv(co):                                               # the region's u from the cord's end, v across\n+        return PAD(length / 2 - co.y, co.x + width / 2)\n+    colour(body, m[\"body\"], \"body\")\n+    paint(body, m[\"body\"], uv, lambda c, n, kind: n.z > 0.9)\n+    (u0, u1), (v0, v1) = L.KEYPAD[\"u\"], L.KEYPAD[\"v\"]\n+    plate = upright(\"keypad\", rounded((v0 + v1) / 2, length / 2 - (u0 + u1) / 2, v1 - v0 + 4.0, u1 - u0 + 4.0, 4.0, 3),\n+                    t - 1.0, t + 1.2, m[\"gloss\"])\n+    paint(plate, m[\"gloss\"], uv, lambda c, n, kind: True)\n+    parts = [body, plate]\n+    (s0, s1), (sw, sh) = L.SIDE[\"u\"], L.SIDE[\"size\"]\n+    for side in (-1, 1):\n+        x0, x1 = sorted((side * (width / 2 - 0.5), side * (width / 2 - 0.5 + sh)))\n+        for su in (s0, s1):\n+            bump = box(\"side\", x0, x1, length / 2 - su - sw / 2, length / 2 - su + sw / 2, t * 0.3, t * 0.75, m[\"body\"])\n+            parts.append(colour(bump, m[\"body\"], \"dark\"))\n+    whole = join(parts, name)\n+    du, r = L.DISC[\"u\"], L.DISC[\"r\"]\n+    disc = upright(name + \"_disc\", circle(0.0, 0.0, r, 32), -1.0, L.DISC[\"height\"], m[\"gloss\"])\n+    bevel(disc, 0.8, segments=2)\n+    colour(disc, m[\"gloss\"], \"metal\")\n+    paint(disc, m[\"gloss\"], lambda co: uv(Vector((co.x, co.y + length / 2 - du, co.z))), lambda c, n, kind: n.z > 0.9)\n+    return whole, disc, Vector((0.0, length / 2 - du, t))\n+\n+\n+def cartridge(m):\n+    \"\"\"The cartridge, lying flat, its label up, its middle at its origin, pointing into the slot (-x).\"\"\"\n+    w, length, t = L.CART[\"size\"]\n+    part = upright(\"cartridge\", rounded(0.0, 0.0, length, w, 4.0, 3), -t / 2, t / 2, m[\"body\"])\n+    bevel(part, 1.0, segments=2)\n+    colour(part, m[\"body\"], \"cart\")\n+    lw, lh = L.CART[\"label\"]\n+    label = atlas_uv(L.REGIONS[\"cart\"], lw, lh)\n+    middle = 16.0                                             # the label's middle along it: towards the outer end\n+    paint(part, m[\"body\"], lambda co: label(lw / 2 - co.y, middle + lh / 2 - co.x),\n+          lambda c, n, kind: n.z > 0.9 and abs(c.x - middle) < lh / 2 and abs(c.y) < lw / 2)\n+    return part\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+def tap(f, f0, hold=2):\n+    return smooth(phase(f, f0, f0 + 3)) * (1 - smooth(phase(f, f0 + 3 + hold, f0 + 7 + hold)))\n+\n+\n+def timeline(f):\n+    out = L.CART[\"out\"]\n+    if f < 6 or f >= 222:\n+        cart = Pose(x=out, scale=HIDDEN)\n+    elif f < 14:\n+        cart = Pose(x=out, scale=max(HIDDEN, back_out(phase(f, 6, 13))))\n+    elif f < 34:\n+        cart = Pose(x=out * (1 - smooth(phase(f, 14, 34))))\n+    elif f < 200:\n+        cart = Pose(x=-1.5 * math.sin(math.pi * phase(f, 34, 40)))\n+    elif f < 214:\n+        cart = Pose(x=out * smooth(phase(f, 200, 214)))\n+    else:\n+        cart = Pose(x=out, scale=max(HIDDEN, 1 - ease_in(phase(f, 214, 222))))\n+    on = smooth(phase(f, 38, 43)) * (1 - smooth(phase(f, 192, 197)))\n+    lift = smooth(phase(f, 48, 66)) * (1 - smooth(phase(f, 170, 188)))\n+    t = phase(f, 70, 166)\n+    k = math.sin(math.pi * t)\n+    tilt = L.DISC[\"tilt\"]\n+    return {\"cartridge\": cart, \"power\": Pose(x=L.SWITCH[\"travel\"] * on),\n+            \"right_pad\": Pose(z=46.0 * lift + 3.0 * math.sin(math.pi * phase(f, 66, 74)), pitch=40.0 * lift,\n+                              roll=4.0 * k * math.sin(2 * math.pi * 2.0 * t)),\n+            \"right_pad_disc\": Pose(pitch=tilt * k * math.sin(2 * math.pi * 3.0 * t),\n+                                   roll=tilt * k * math.cos(2 * math.pi * 2.0 * t))}\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    atlas = bpy.data.images.load(os.path.join(images, \"intellivision_print.png\"))\n+    m = {name: material(name, L.COLOURS[\"print\"], r, atlas) for name, r in ROUGHNESS.items()}\n+\n+    body = console(m)\n+    floor = Z0 + L.TOP - L.BAND[\"depth\"]\n+    dx, dy = L.PAD[\"dock\"]\n+    left, left_disc, left_disc_at = controller(m, \"left_pad\")\n+    left.data.transform(Matrix.Translation((dx, dy, floor)) @ Matrix.Rotation(math.radians(-90.0), 4, \"Z\"))\n+    left_disc.data.transform(Matrix.Translation(Vector((dx, dy, floor))\n+                                                + Matrix.Rotation(math.radians(-90.0), 3, \"Z\") @ left_disc_at))\n+    whole = join([body, left, left_disc], \"intellivision\")\n+    for p in whole.data.polygons:\n+        p.use_smooth = True\n+    whole.data.set_sharp_from_angle(angle=math.radians(35))\n+\n+    root = bpy.data.objects.new(\"intellivision_root\", None)\n+    scene.collection.objects.link(root)\n+    whole.parent = root\n+    right, right_disc, right_disc_at = controller(m, \"right_pad\")\n+    right.data.transform(Matrix.Rotation(math.radians(90.0), 4, \"Z\"))\n+    right_disc.data.transform(Matrix.Rotation(math.radians(90.0), 4, \"Z\"))\n+    lever, lever_at = switch(m)\n+    cart = cartridge(m)\n+    w, length, t = L.CART[\"size\"]\n+    stick_out = 40.0\n+    cart_at = Vector((L.WIDTH / 2 + stick_out - length / 2, sum(L.SLOT[\"y\"]) / 2, Z0 + sum(L.SLOT[\"z\"]) / 2))\n+    nodes = {}\n+    for name, part, where, parent in ((\"right_pad\", right, Vector((-dx, dy, floor)), whole),\n+                                      (\"power\", lever, lever_at, whole), (\"cartridge\", cart, cart_at, whole)):\n+        part.parent = parent\n+        part.location = where\n+        nodes[name] = part\n+    right_disc.parent = right\n+    right_disc.location = Matrix.Rotation(math.radians(90.0), 3, \"Z\") @ right_disc_at\n+    nodes[\"right_pad_disc\"] = right_disc\n+    for part in nodes.values():\n+        for p in part.data.polygons:\n+            p.use_smooth = True\n+        part.data.set_sharp_from_angle(angle=math.radians(35))\n+\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    points = [tilt @ v.co for v in whole.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [whole] + list(nodes.values())\n+    print(f\"Intellivision: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(whole.data)} the set), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "79b39ee2a681827f7fb19f12d96304c39281c241",
          "filename": "tools/console-models/intellivision_layout.py",
          "status": "added",
          "additions": 51,
          "deletions": 0,
          "changes": 51,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fintellivision_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fintellivision_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fintellivision_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,51 @@\n+\"\"\"The Intellivision's measurements, shared by intellivision_print.py (which draws its printing) and intellivision.py\n+(which builds it). Plain Python: Blender's Python imports it too.\n+\n+Mattel's Intellivision (1979), the Master Component, 394 mm wide, 229 deep and 70 high (the 15.5 x 9 x 2.75 inches a\n+Georgia Tech archive records; a 40 x 24 x 7 cm listing agrees), its two controllers docked in its top, a made-up game,\n+Odyssey, on the cartridge. Millimetres: x from its middle (left to right), y back from its front, z up from the table.\n+The rest was measured from Evan-Amos's photographs on Wikimedia Commons (the console from the front left and right and\n+the back left and right, the set, a controller from above at an angle), scaled by its width.\n+\n+The console: the dark brown body, the wood-grain front; on its top the gold strips (the back one; the front one with\n+MATTEL ELECTRONICS INTELLIVISION Intelligent Television, RESET and the OFF-ON switch), the middle band between them,\n+sunk in, where the controllers dock (the left one's disc at the left end, the right one's at the right, their keypads\n+inwards) either side of the vents; the cartridge slot in its right end. A controller: its body, the keypad of twelve\n+keys (1 to 9, clear, 0, enter) in its gold frame, the gold disc, the side buttons.\n+\n+One texture (ATLAS square, REGIONS): `top` (the console from above: x across, y down from the back), `front` (its\n+front: x across, z down from the top), `pad` (a controller from above: x along it from its keypad's end, y across),\n+`cart` (the cartridge's label), and swatches. A region is (x, y, width, height) in pixels.\n+\"\"\"\n+\n+ATLAS = 1024\n+\n+WIDTH, DEPTH, HEIGHT = 394.0, 229.0, 64.0       # the body; its feet make up the 70\n+FEET = 6.0\n+# The side profile, (y, z above the feet), round from the front's foot: the front leaning out to the top's edge.\n+PROFILE = [(10.0, 0.0), (219.0, 0.0), (229.0, 8.0), (229.0, 52.0), (222.0, 58.0), (8.0, 58.0), (0.0, 50.0),\n+           (2.0, 14.0)]\n+TOP = 58.0\n+STRIPS = dict(front=(14.0, 76.0), back=(156.0, 212.0))   # the gold strips' y spans\n+BAND = dict(y=(82.0, 150.0), depth=5.0)                 # the docks' band, sunk in\n+VENTS = dict(x=(-34.0, 34.0))\n+SLOT = dict(y=(70.0, 160.0), z=(16.0, 30.0))             # the cartridge slot in the right end (z above the feet)\n+SWITCH = dict(at=(124.0, 34.0), size=(26.0, 9.0), travel=12.0)   # the OFF-ON switch on the front strip (x, y)\n+RESET = dict(at=(96.0, 52.0), size=(30.0, 7.0))\n+\n+# A controller: its size, and its parts along it from its keypad's end (u) and across it (v, from its middle).\n+PAD = dict(length=152.0, width=62.0, thickness=18.0, dock=(-110.0, 112.0))   # docked: the left one's middle (x, y)\n+KEYPAD = dict(u=(12.0, 82.0), v=(-24.0, 24.0), keys=(3, 4), key=13.0)\n+DISC = dict(u=118.0, r=19.0, height=2.4, tilt=8.0)\n+SIDE = dict(u=(30.0, 60.0), size=(14.0, 3.0))\n+\n+CART = dict(size=(64.0, 116.0, 13.0), out=80.0, label=(54.0, 70.0))   # w, length, t; how far out it waits\n+\n+COLOURS = dict(body=\"#3B2D25\", print=\"#FFFFFF\")\n+\n+REGIONS = dict(top=(0, 0, 788, 458), front=(0, 466, 788, 128), pad=(0, 602, 608, 248), cart=(616, 602, 216, 280))\n+SWATCHES = dict(body=(820, 40), gold=(852, 40), wood=(884, 40), dark=(916, 40), key=(948, 40), metal=(980, 40),\n+                cart=(820, 72), slot=(852, 72))\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s"
        },
        {
          "sha": "e58534a46edb758e24971236686254e14e4f3836",
          "filename": "tools/console-models/intellivision_print.py",
          "status": "added",
          "additions": 175,
          "deletions": 0,
          "changes": 175,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fintellivision_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fintellivision_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fintellivision_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,175 @@\n+\"\"\"The Intellivision's printing (intellivision_print.png), for the console theme's model (intellivision.py).\n+\n+One image, laid out as intellivision_layout.REGIONS says: the console's top from above (the gold strips, MATTEL\n+ELECTRONICS INTELLIVISION Intelligent Television, RESET and OFF-ON, the docks' band and the vents), its front (the wood\n+grain), a controller from above (its keypad's twelve keys in their gold frame, the disc's brushed rings), the\n+cartridge's label (a made-up game, Odyssey), and swatches. Run it with the system Python (Pillow):\n+\n+    python tools/console-models/intellivision_print.py <out folder>\n+\"\"\"\n+\n+import math\n+import os\n+import random\n+import sys\n+\n+from PIL import Image, ImageDraw, ImageFilter, ImageFont\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import intellivision_layout as L  # noqa: E402\n+from psp_print import ship, space  # noqa: E402\n+from retro_pc_print import rgb  # noqa: E402\n+from windows_pc_print import FONTS, Region  # noqa: E402\n+\n+BODY = rgb(L.COLOURS[\"body\"])\n+GOLD = (204, 178, 132)\n+GOLD_DARK = (150, 124, 84)\n+INK = (52, 40, 30)\n+WOOD = (110, 62, 32)\n+\n+\n+def brushed(reg, x0, y0, x1, y1, colour, vertical=False):\n+    \"\"\"A brushed-metal strip: the colour with fine streaks along it.\"\"\"\n+    reg.rect_(x0, y0, x1, y1, colour)\n+    rng = random.Random(3)\n+    for _ in range(int((y1 - y0 if not vertical else x1 - x0) * 3)):\n+        t = rng.uniform(0.0, 1.0)\n+        shade = tuple(max(0, min(255, c + rng.randint(-14, 12))) for c in colour)\n+        if vertical:\n+            x = x0 + (x1 - x0) * t\n+            reg.line([(x, y0), (x, y1)], shade, 0.15)\n+        else:\n+            y = y0 + (y1 - y0) * t\n+            reg.line([(x0, y), (x1, y)], shade, 0.15)\n+\n+\n+def wood(size, seed=5):\n+    \"\"\"A wood-grain image: warm brown with darker streaks wandering along it.\"\"\"\n+    w, h = size\n+    img = Image.new(\"RGB\", size, WOOD)\n+    d = ImageDraw.Draw(img)\n+    rng = random.Random(seed)\n+    for _ in range(int(h * 1.4)):\n+        y = rng.uniform(0, h)\n+        amp, period, phase = rng.uniform(1, h * 0.04), rng.uniform(w * 0.2, w * 0.8), rng.uniform(0, 6.28)\n+        shade = rng.choice(((74, 38, 18), (92, 50, 24), (128, 76, 40), (66, 34, 16)))\n+        pts = [(x, y + amp * math.sin(2 * math.pi * x / period + phase)) for x in range(0, w + 6, 6)]\n+        d.line(pts, fill=shade, width=rng.choice((1, 1, 2, 3)))\n+    return img.filter(ImageFilter.GaussianBlur(0.6))\n+\n+\n+# ---- The console -----------------------------------------------------------------------------------------------------\n+\n+def top():\n+    \"\"\"The console from above: x across (-197 at the left), y down from the back (229).\"\"\"\n+    reg = Region(L.REGIONS[\"top\"], L.WIDTH, L.DEPTH, BODY)\n+\n+    def at(x, y):\n+        return x + L.WIDTH / 2, L.DEPTH - y\n+\n+    half = L.WIDTH / 2\n+    for y0, y1 in (L.STRIPS[\"back\"], L.STRIPS[\"front\"]):\n+        brushed(reg, *at(-half + 14.0, y1), *at(half - 14.0, y0), GOLD)\n+        reg.rect_(*at(-half + 14.0, y1), *at(half - 14.0, y0), None, outline=GOLD_DARK, width=0.5)\n+    (b0, b1) = L.BAND[\"y\"]\n+    reg.rect_(*at(-half + 10.0, b1), *at(half - 10.0, b0), (34, 26, 22))\n+    (v0, v1) = L.VENTS[\"x\"]\n+    for row in range(2):\n+        for k in range(14):\n+            x = v0 + 3.0 + k * (v1 - v0 - 6.0) / 13\n+            y = b0 + 14.0 + row * 24.0\n+            reg.rect_(*at(x - 1.0, y + 18.0), *at(x + 1.0, y), (10, 8, 8), r=0.8)\n+    f0, f1 = L.STRIPS[\"front\"]\n+    reg.text(\"MATTEL ELECTRONICS\", \"arialbd.ttf\", 3.6, *at(-170.0, (f0 + f1) / 2 + 4.0), INK, anchor=\"lm\")\n+    reg.text(\"INTELLIVISION\", \"timesbd.ttf\", 9.0, *at(-122.0, (f0 + f1) / 2 + 4.0), INK, anchor=\"lm\")\n+    reg.text(\"Intelligent Television\", \"times.ttf\", 5.0, *at(-58.0, (f0 + f1) / 2 - 6.0), INK, anchor=\"lm\")\n+    (rx, ry), (rw, rh) = L.RESET[\"at\"], L.RESET[\"size\"]\n+    reg.rect_(*at(rx - rw / 2, ry + rh / 2), *at(rx + rw / 2, ry - rh / 2), (40, 30, 24), r=2.0)\n+    reg.text(\"RESET\", \"arial.ttf\", 3.0, *at(rx + 6.0, ry + rh / 2 + 3.0), INK)\n+    (sx, sy), (sw, sh) = L.SWITCH[\"at\"], L.SWITCH[\"size\"]\n+    reach = sw / 2 + L.SWITCH[\"travel\"] / 2                   # the switch's slot, its whole travel\n+    reg.rect_(*at(sx - reach, sy + sh / 2), *at(sx + reach, sy - sh / 2), (34, 26, 22), r=2.0)\n+    reg.text(\"OFF\", \"arial.ttf\", 3.0, *at(sx - 10.0, sy + sh / 2 + 3.0), INK)\n+    reg.text(\"ON\", \"arial.ttf\", 3.0, *at(sx + 10.0, sy + sh / 2 + 3.0), INK)\n+    return reg\n+\n+\n+def front():\n+    \"\"\"The front: x across, z down from the top (58): the dark rim, the wood grain.\"\"\"\n+    reg = Region(L.REGIONS[\"front\"], L.WIDTH, L.TOP, BODY)\n+    x0, y0, x1, y1 = reg.box(0.0, 8.0, L.WIDTH, 52.0)\n+    grain = wood((round(x1 - x0), round(y1 - y0)))\n+    reg.img.paste(grain, (round(x0), round(y0)))\n+    return reg\n+\n+\n+# ---- The controller and the cartridge --------------------------------------------------------------------------------\n+\n+def pad():\n+    \"\"\"A controller from above: u along it from its keypad's end (0 at the left), v across (-31 at the top).\"\"\"\n+    length, width = L.PAD[\"length\"], L.PAD[\"width\"]\n+    reg = Region(L.REGIONS[\"pad\"], length, width, BODY)\n+\n+    def at(u, v):\n+        return u, v + width / 2\n+\n+    (u0, u1), (v0, v1) = L.KEYPAD[\"u\"], L.KEYPAD[\"v\"]\n+    reg.rect_(*at(u0 - 2.0, v0 - 2.0), *at(u1 + 2.0, v1 + 2.0), GOLD_DARK, r=4.0)\n+    reg.rect_(*at(u0, v0), *at(u1, v1), (230, 214, 178), r=3.0)\n+    cols, rows = L.KEYPAD[\"keys\"]\n+    labels = [[\"1\", \"2\", \"3\"], [\"4\", \"5\", \"6\"], [\"7\", \"8\", \"9\"], [\"clear\", \"0\", \"enter\"]]\n+    pu, pv = (u1 - u0) / rows, (v1 - v0) / cols\n+    for r in range(rows):\n+        for c in range(cols):\n+            cu, cv = u0 + pu * (r + 0.5), v0 + pv * (c + 0.5)\n+            k = L.KEYPAD[\"key\"] / 2\n+            reg.rect_(*at(cu - k * 0.9, cv - k), *at(cu + k * 0.9, cv + k), (238, 228, 200), r=2.4,\n+                      outline=(170, 150, 110), width=0.4)\n+            text = labels[r][c]\n+            layer = reg.layer()\n+            font = ImageFont.truetype(FONTS + \"arialbd.ttf\", max(1, round((5.0 if len(text) == 1 else 2.6) * reg.sx)))\n+            glyph = Image.new(\"RGBA\", (round(16 * reg.sx), round(16 * reg.sx)), (0, 0, 0, 0))\n+            ImageDraw.Draw(glyph).text((glyph.width / 2, glyph.height / 2), text, font=font, fill=INK + (255,),\n+                                       anchor=\"mm\")\n+            glyph = glyph.rotate(90, resample=Image.BICUBIC)        # the keys read from the controller's cord end\n+            x, y = reg.p(*at(cu, cv))\n+            layer.alpha_composite(glyph, (round(x - glyph.width / 2), round(y - glyph.height / 2)))\n+            reg.over(layer)\n+    du, r = L.DISC[\"u\"], L.DISC[\"r\"]\n+    reg.circle(*at(du, 0.0), r + 2.0, (30, 24, 20))\n+    reg.circle(*at(du, 0.0), r, (196, 176, 140))\n+    for k in range(1, 9):\n+        reg.circle(*at(du, 0.0), r * k / 9, None, outline=(176, 156, 120) if k % 2 else (214, 194, 156), width=0.4)\n+    return reg\n+\n+\n+def cart():\n+    lw, lh = L.CART[\"label\"]\n+    reg = Region(L.REGIONS[\"cart\"], lw, lh, (24, 20, 40))\n+    art = space((440, 560)).convert(\"RGB\")\n+    ship(ImageDraw.Draw(art), 140, 320, 3.2, \"#FFFFFF\", \"#FFB23C\")\n+    x0, y0, x1, y1 = reg.box(0.0, 12.0, lw, lh)\n+    reg.img.paste(art.resize((round(x1 - x0), round(y1 - y0)), Image.LANCZOS), (round(x0), round(y0)))\n+    reg.rect_(0.0, 0.0, lw, 12.0, (236, 226, 196))\n+    reg.text(\"INTELLIVISION\", \"timesbd.ttf\", 5.4, lw / 2, 6.0, INK)\n+    reg.text(\"ODYSSEY\", \"ariblk.ttf\", 8.0, lw / 2, 22.0, (255, 214, 70))\n+    return reg\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), BODY)\n+    for reg in (top(), front(), pad(), cart()):\n+        reg.paste(image)\n+    d = ImageDraw.Draw(image)\n+    colours = dict(body=BODY, gold=GOLD, wood=WOOD, dark=(30, 24, 20), key=(238, 228, 200), metal=(196, 176, 140),\n+                   cart=(40, 34, 30), slot=(8, 6, 6))\n+    for name, (x, y) in L.SWATCHES.items():\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colours[name])\n+    path = os.path.join(out, \"intellivision_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "154dca5a3ca74f46c3a5434c3ab9c49deecd3371",
          "filename": "tools/console-models/jaguar.py",
          "status": "added",
          "additions": 459,
          "deletions": 0,
          "changes": 459,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fjaguar.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fjaguar.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fjaguar.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,459 @@\n+\"\"\"The console theme's Atari Jaguar and Jaguar CD system models (godot/themes/console/models/systems/atarijaguar.glb and\n+atarijaguarcd.glb): the Jaguar and its controller, and the same with the Jaguar CD on it.\n+\n+Built in Blender from the real console's proportions (jaguar_layout.py has the measurements, taken from photographs).\n+The Jaguar's body is its rounded plan lofted up into a low pillow; its round nose is lofted on its front, the dish\n+recessed into its top, the slot's frame raised at its back with the slot through it, the two ribbed wings beside the\n+frame; the red power button stands on the nose. The controller is its outline lofted up, its edges rounded, the D-pad\n+in its ring, PAUSE and OPTION, the red A, B and C, the keypad's keys on their plate. The Jaguar CD is its plan (its\n+round front, its back section) lofted up on the Jaguar's top, the well for the disc under its lid, its own slot's frame\n+raised at its back. The printing is one texture drawn by jaguar_print.py: the Jaguar, the controller and the Jaguar CD\n+from above (planar-mapped onto the faces facing up), the CD's and the cartridge's labels, and swatches.\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: the right side towards the viewer, the\n+top tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m) holding the set; the set\n+holding the power button, the lit power light (shrunk to nothing) and the controller (holding its D-pad and A, B and\n+C); and either the cartridge (waiting in its slot, shrunk to nothing) or, on the Jaguar CD, the lid (its origin on its\n+hinge) and the disc (waiting in the well, shrunk to nothing).\n+\n+The `focused` clip (8 s, looping) plays a game: the cartridge drops into the slot (or the Jaguar CD's lid swings up, the\n+disc drops in and the lid shuts); the power button is pressed and its light comes on; the game is played on the D-pad,\n+A, B and C. Then the power button again, and the cartridge rises out and goes (or the lid swings up, the disc goes, and\n+the lid shuts).\n+\n+Four materials (case, key, red, print) and one texture, within the system model budget. Run from the repo root:\n+\n+    python tools/console-models/jaguar_print.py artifacts/jaguar\n+    blender -b --factory-startup --python tools/console-models/jaguar.py -- artifacts/jaguar atarijaguar godot/themes/console/models/systems/atarijaguar.glb\n+\n+and the same with atarijaguarcd.\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bmesh\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import jaguar_layout as L  # noqa: E402\n+from amiga import paint, rounded, solid  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, ease_out, key, phase, smooth, wobble  # noqa: E402\n+from mega_drive_cartridge import bevel, boolean, box, export, join, material, new_object, prism_x, triangles  # noqa: E402\n+from msx_layout import inset  # noqa: E402\n+\n+TILT_YAW = -24.0     # degrees about the vertical: the right side comes towards the viewer\n+TILT_PITCH = 28.0    # degrees about the horizontal: the top tips up towards the viewer\n+ROUGHNESS = dict(case=0.6, key=0.5, red=0.4, print=0.55)\n+PAD_W = 2 * max(x for x, _ in L.PAD_OUTLINE)\n+\n+\n+# ---- UVs -------------------------------------------------------------------------------------------------------------\n+\n+def atlas_uv(region, w, h):\n+    \"\"\"A face drawn w x h into region: (a, b) millimetres across it (a to the right, b down from its top) to UVs.\"\"\"\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+TOP = atlas_uv(L.REGIONS[\"top\"], L.WIDTH, L.DEPTH)\n+PAD_TOP = atlas_uv(L.REGIONS[\"pad\"], PAD_W, L.PAD_D)\n+CD_Y0 = L.CD[\"at\"] - L.CD[\"r\"]\n+CD_TOP = atlas_uv(L.REGIONS[\"cd\"], 2 * L.CD[\"r\"], L.CD[\"back\"][2] - CD_Y0)\n+\n+\n+def top_uv(co):\n+    return TOP(co.x + L.WIDTH / 2, L.DEPTH - co.y)\n+\n+\n+def cd_uv(co):\n+    return CD_TOP(co.x + L.CD[\"r\"], L.CD[\"back\"][2] - co.y)\n+\n+\n+def swatch(name):\n+    x, y = L.SWATCHES[name]\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+def painted(obj, m, name):\n+    paint(obj, m[\"print\"], swatch(name), lambda c, n, _name: True)\n+    return obj\n+\n+\n+def upright(name, points, z0, z1, mat):\n+    \"\"\"A solid from an (x, y) plan, from z0 up to z1.\"\"\"\n+    return solid(name, [[(x, y, z) for x, y in points] for z in (z0, z1)], mat)\n+\n+\n+def circle(cx, cy, r, n=32):\n+    return [(cx + r * math.cos(2 * math.pi * k / n), cy + r * math.sin(2 * math.pi * k / n)) for k in range(n)]\n+\n+\n+def facing_up(c, n, name):\n+    return n.z > 0.85\n+\n+\n+# ---- The Jaguar ------------------------------------------------------------------------------------------------------\n+\n+def jaguar(m):\n+    \"\"\"The Jaguar: its body, the nose, the frame and the wings; the dish and the slot cut into them; its top\n+    printed.\"\"\"\n+    (y0, y1), corner = L.BODY[\"y\"], L.BODY[\"corner\"]\n+    rings = [[(x, y, z) for x, y in rounded(0.0, (y0 + y1) / 2, L.WIDTH - 2 * k, y1 - y0 - 2 * k, corner - k * 0.6, 6)]\n+             for k, z in L.BODY[\"rings\"]]                 # each ring smaller (and its corners tighter) to the top\n+    body = solid(\"atarijaguar\", rings, m[\"case\"])\n+    for p in body.data.polygons:\n+        p.use_smooth = True\n+    (dx, dy), r = L.DISH[\"at\"], L.DISH[\"r\"]\n+    top_z = L.BODY[\"rings\"][-1][1]\n+    boolean(body, upright(\"dish\", circle(dx, dy, r, 48), top_z - L.DISH[\"depth\"], top_z + 20.0, m[\"case\"]))\n+\n+    n = L.NOSE\n+    half = [(n[\"x\"] * math.cos(math.radians(a)), n[\"y\"][1] - (n[\"y\"][1] - n[\"y\"][0]) * math.sin(math.radians(a)))\n+            for a in range(0, 181, 10)]\n+    nose = solid(\"nose\", [[(x * k, n[\"y\"][1] - (n[\"y\"][1] - y) * k, z) for x, y in half] for k, z in\n+                          ((1.0, 0.0), (1.0, n[\"top\"] - 10.0), (0.94, n[\"top\"]), (0.7, n[\"height\"]))], m[\"case\"])\n+    for p in nose.data.polygons:\n+        p.use_smooth = True\n+\n+    (fx0, fx1), (fy0, fy1) = L.FRAME[\"x\"], L.FRAME[\"y\"]\n+    frame = box(\"frame\", fx0, fx1, fy0, fy1, top_z - 10.0, L.FRAME[\"top\"], m[\"case\"])\n+    bevel(frame, 3.0, segments=2)\n+    (sx0, sx1), (sy0, sy1) = L.SLOT[\"x\"], L.SLOT[\"y\"]\n+    w = L.WINGS\n+    wings = []\n+    for side in (-1, 1):\n+        a, b = side * w[\"x\"][0], side * w[\"x\"][1]\n+        z_in, z_out = w[\"top\"]\n+        profile = [(w[\"y\"][0], 0.0), (w[\"y\"][1], 0.0), (w[\"y\"][1], z_in), (w[\"y\"][0] + 10.0, z_in)]\n+        wing = prism_x(\"wing\", profile, min(a, b), max(a, b), m[\"case\"])\n+        # its top slopes down to its outer edge\n+        for v in wing.data.vertices:\n+            if v.co.z > 1.0:\n+                t = (abs(v.co.x) - w[\"x\"][0]) / (w[\"x\"][1] - w[\"x\"][0])\n+                v.co.z = z_in + (z_out - z_in) * t\n+        bevel(wing, 1.5, segments=2)\n+        pitch, (rib_w, rib_d) = (w[\"y\"][1] - w[\"y\"][0] - 14.0) / w[\"ribs\"], w[\"rib\"]\n+        cuts = [box(\"rib\", min(a, b) - 5.0, max(a, b) + 5.0, w[\"y\"][0] + 12.0 + k * pitch,\n+                    w[\"y\"][0] + 12.0 + k * pitch + rib_w, -1.0, 80.0, m[\"key\"]) for k in range(w[\"ribs\"])]\n+        groove = join(cuts, \"ribs\")\n+        for v in groove.data.vertices:                  # each groove's floor follows the sloping top, rib_d under it\n+            if v.co.z < 0.0:\n+                t = (abs(v.co.x) - w[\"x\"][0]) / (w[\"x\"][1] - w[\"x\"][0])\n+                v.co.z = (z_in + (z_out - z_in) * max(0.0, min(1.0, t))) - rib_d\n+        boolean(wing, groove)\n+        wings.append(wing)\n+    for part in (body, frame):                          # the slot through each (overlapping, they're cut apart)\n+        boolean(part, box(\"slot\", sx0, sx1, sy0, sy1, L.FRAME[\"top\"] - L.SLOT[\"depth\"], L.FRAME[\"top\"] + 5.0, m[\"key\"]))\n+    whole = join([body, nose, frame] + wings, \"atarijaguar\")\n+    paint(whole, m[\"print\"], top_uv, lambda c, n, name: n.z > 0.6 and name == \"case\")\n+    return whole\n+\n+\n+def power(m):\n+    (px, py), (pw, ph) = L.POWER[\"at\"], L.POWER[\"size\"]\n+    oval = [(pw / 2 * math.cos(2 * math.pi * k / 24), ph / 2 * math.sin(2 * math.pi * k / 24)) for k in range(24)]\n+    part = solid(\"power\", [[(x, y, z) for x, y in oval] for z in (-1.0, L.POWER[\"height\"])], m[\"red\"])\n+    bevel(part, 1.4, segments=2)\n+    return part, Vector((px, py, L.NOSE[\"height\"] - 0.6))\n+\n+\n+def power_light(m):\n+    (px, py) = L.POWER[\"at\"]\n+    part = painted(upright(\"power_light\", circle(0.0, 0.0, 1.4, 12), -0.2, 0.3, m[\"print\"]), m, \"lit\")\n+    return part, Vector((px, py - 9.0, L.NOSE[\"height\"] - 1.6))\n+\n+\n+def cartridge(m):\n+    \"\"\"The cartridge, its bottom's middle at its origin: its shell, its label on its front (-y).\"\"\"\n+    w, t, h = L.CART[\"size\"]\n+    lw, lh = L.CART[\"label\"]\n+    shell = box(\"cartridge\", -w / 2, w / 2, -t / 2, t / 2, 0.0, h, m[\"case\"])\n+    bevel(shell, 2.0, segments=2)\n+    grip = box(\"grip\", -w / 2 + 6.0, w / 2 - 6.0, -t / 2 - 1.0, t / 2 + 1.0, h - 10.0, h, m[\"case\"])\n+    bevel(grip, 1.4, segments=2)\n+    z1 = h - 13.0\n+    decal = box(\"label\", -lw / 2, lw / 2, -t / 2 - 0.2, -t / 2 + 0.3, z1 - lh, z1, m[\"print\"])\n+    face = atlas_uv(L.REGIONS[\"label\"], lw, lh)\n+    paint(decal, m[\"print\"], lambda co: face(co.x + lw / 2, z1 - co.z), lambda c, n, name: n.y < -0.9)\n+    paint(decal, m[\"print\"], swatch(\"dark\"), lambda c, n, name: n.y >= -0.9)\n+    return join([shell, grip, decal], \"cartridge\")\n+\n+\n+# ---- The Jaguar CD ---------------------------------------------------------------------------------------------------\n+\n+def cd_plan():\n+    \"\"\"The Jaguar CD's plan, anticlockwise: its round front, then its back section.\"\"\"\n+    r, cy = L.CD[\"r\"], L.CD[\"at\"]\n+    bx0, bx1, by = L.CD[\"back\"]\n+    points = [(r * math.cos(math.radians(a)), cy + r * math.sin(math.radians(a))) for a in range(180, 361, 6)]\n+    x_side = r\n+    y_side = cy + 30.0\n+    points += [(x_side, y_side), (bx1, y_side + 26.0), (bx1, by), (bx0, by), (bx0, y_side + 26.0), (-x_side, y_side)]\n+    return points\n+\n+\n+def jaguar_cd(m):\n+    \"\"\"The Jaguar CD on the Jaguar's top: its body (the well cut into it, its own slot's frame at its back).\"\"\"\n+    foot, top = L.CD[\"foot\"], L.CD[\"top\"]\n+    plan = cd_plan()\n+    body = solid(\"jaguarcd\", [[(x, y, z) for x, y in inset(plan, k)] for k, z in\n+                              ((2.0, foot), (0.0, foot + 6.0), (0.0, top - 3.0), (2.5, top))], m[\"case\"])\n+    for p in body.data.polygons:\n+        p.use_smooth = True\n+    r, cy = L.LID[\"r\"], L.CD[\"at\"]\n+    boolean(body, upright(\"well\", circle(0.0, cy, L.DISC[\"diameter\"] / 2 + L.DISC[\"well\"], 48),\n+                          top - 8.0, top + 10.0, m[\"key\"]))\n+    (fx0, fx1), (fy0, fy1) = L.CD_FRAME[\"x\"], L.CD_FRAME[\"y\"]\n+    frame = box(\"cd_frame\", fx0, fx1, fy0, fy1, top - 6.0, L.CD_FRAME[\"top\"], m[\"case\"])\n+    bevel(frame, 3.0, segments=2)\n+    (sx0, sx1), (sy0, sy1) = L.CD_SLOT[\"x\"], L.CD_SLOT[\"y\"]\n+    for part in (body, frame):\n+        boolean(part, box(\"cd_slot\", sx0, sx1, sy0, sy1, L.CD_FRAME[\"top\"] - L.CD_SLOT[\"depth\"],\n+                          L.CD_FRAME[\"top\"] + 5.0, m[\"key\"]))\n+    whole = join([body, frame], \"jaguarcd\")\n+    paint(whole, m[\"print\"], cd_uv, lambda c, n, name: n.z > 0.85 and name == \"case\")\n+    spindle = upright(\"spindle\", circle(0.0, cy, 7.0, 20), top - 9.0, top - 4.0, m[\"key\"])\n+    return [whole, spindle]\n+\n+\n+def lid(m):\n+    \"\"\"The Jaguar CD's lid, its origin on its hinge (its back edge's top).\"\"\"\n+    r, cy, back = L.LID[\"r\"], L.CD[\"at\"], L.LID[\"back\"]\n+    top, h = L.CD[\"top\"], L.LID[\"height\"]\n+    plan = [(r * math.cos(math.radians(a)), cy + r * math.sin(math.radians(a))) for a in range(180, 361, 6)]\n+    plan += [(r, back), (-r, back)]\n+    part = solid(\"lid\", [[(x, y, z) for x, y in inset(plan, k)] for k, z in ((0.0, top - 0.5), (0.0, top + h - 1.5),\n+                                                                              (1.5, top + h))], m[\"case\"])\n+    bevel(part, 1.0, segments=2)\n+    paint(part, m[\"print\"], cd_uv, lambda c, n, name: n.z > 0.85)\n+    hinge = Vector((0.0, back, top + h))\n+    part.data.transform(Matrix.Translation(-hinge))\n+    return part, hinge\n+\n+\n+def disc(m):\n+    \"\"\"The CD, its middle at its origin, lying flat: a ring, its label on its top.\"\"\"\n+    ro, ri, t = L.DISC[\"diameter\"] / 2, L.DISC[\"hole\"] / 2, L.DISC[\"thickness\"]\n+    n = 64\n+    bm = bmesh.new()\n+    rings = [[bm.verts.new((r * math.cos(2 * math.pi * k / n), r * math.sin(2 * math.pi * k / n), z)) for k in range(n)]\n+             for r, z in ((ro, -t / 2), (ro, t / 2), (ri, t / 2), (ri, -t / 2))]\n+    for a, b in zip(rings, rings[1:] + rings[:1]):\n+        for k in range(n):\n+            j = (k + 1) % n\n+            bm.faces.new([a[k], a[j], b[j], b[k]])\n+    bmesh.ops.recalc_face_normals(bm, faces=bm.faces)\n+    part = new_object(\"disc\", bm, m[\"print\"])\n+    dm = L.DISC[\"diameter\"]\n+    face = atlas_uv(L.REGIONS[\"disc\"], dm, dm)\n+    paint(part, m[\"print\"], lambda co: face(co.x + dm / 2, dm / 2 - co.y), lambda c, n_, name: n_.z > 0.9)\n+    paint(part, m[\"print\"], swatch(\"silver\"), lambda c, n_, name: n_.z <= 0.9)\n+    return part\n+\n+\n+# ---- The controller --------------------------------------------------------------------------------------------------\n+\n+def controller(m):\n+    \"\"\"The controller in its own frame (its front edge's middle at the origin, the table): its body, PAUSE, OPTION and\n+    the keypad; the D-pad, A, B and C apart, each its foot's middle at its origin.\"\"\"\n+    h0, h1 = L.PAD[\"height\"]\n+    outline = L.PAD_OUTLINE\n+    rings = [[(x, y, z) for x, y in inset(outline, k)] for k, z in ((2.5, 0.0), (0.0, 4.0), (0.0, h0 - 5.0),\n+                                                                     (2.5, h0 - 1.0), (5.0, h0))]\n+    body = solid(\"controller\", rings, m[\"case\"])\n+    # its top rises to the back\n+    for v in body.data.vertices:\n+        if v.co.z > 1.0:\n+            v.co.z += (h1 - h0) * v.co.y / L.PAD_D * (v.co.z / h0)\n+    for p in body.data.polygons:\n+        p.use_smooth = True\n+    paint(body, m[\"print\"], lambda co: PAD_TOP(co.x + PAD_W / 2, L.PAD_D - co.y), facing_up)\n+    parts = [body]\n+\n+    def surface(y):\n+        return h0 + (h1 - h0) * y / L.PAD_D\n+\n+    sw, sh = L.SMALL[\"size\"]\n+    for name in (\"pause\", \"option\"):\n+        x, y = L.SMALL[name]\n+        c, s = math.cos(math.radians(L.SMALL[\"turn\"])), math.sin(math.radians(L.SMALL[\"turn\"]))\n+        pill = [(x + c * u - s * v, y + s * u + c * v) for u, v in rounded(0.0, 0.0, sw, sh, sh / 2 - 0.01, 4)]\n+        part = upright(name, pill, surface(y) - 1.0, surface(y) + L.SMALL[\"height\"], m[\"key\"])\n+        bevel(part, 0.5, segments=1)\n+        parts.append(part)\n+    (kx0, kx1), (ky0, ky1), (kw, kh) = L.KEYPAD[\"x\"], L.KEYPAD[\"y\"], L.KEYPAD[\"key\"]\n+    for i in range(4):\n+        for j in range(3):\n+            x = kx0 + (kx1 - kx0) * j / 2\n+            y = ky1 - (ky1 - ky0) * i / 3\n+            k = upright(\"key\", [(x + a, y + b) for a, b in rounded(0.0, 0.0, kw, kh, kh / 2 - 0.01, 4)],\n+                        surface(y) - 1.0, surface(y) + L.KEYPAD[\"height\"], m[\"key\"])\n+            bevel(k, 0.5, segments=1)\n+            parts.append(k)\n+    shell = join(parts, \"controller\")\n+\n+    moving = {}\n+    (dx, dy), size, arm = L.DPAD[\"at\"], L.DPAD[\"size\"], L.DPAD[\"arm\"]\n+    s, a = size / 2, arm / 2\n+    cross = [(a, s), (-a, s), (-a, a), (-s, a), (-s, -a), (-a, -a), (-a, -s), (a, -s), (a, -a), (s, -a), (s, a), (a, a)]\n+    pad = upright(\"dpad\", cross, -2.0, L.DPAD[\"height\"], m[\"key\"])\n+    bevel(pad, 0.8, segments=1)\n+    moving[\"dpad\"] = (pad, Vector((dx, dy, surface(dy))))\n+    bw, bh = L.ABC[\"size\"]\n+    c, si = math.cos(math.radians(L.ABC[\"turn\"])), math.sin(math.radians(L.ABC[\"turn\"]))\n+    for name in (\"a\", \"b\", \"c\"):\n+        x, y = L.ABC[name]\n+        shape = [(c * u - si * v, si * u + c * v) for u, v in rounded(0.0, 0.0, bw, bh, 3.0, 3)]\n+        part = upright(name, shape, -2.0, L.ABC[\"height\"], m[\"red\"])\n+        bevel(part, 1.0, segments=2)\n+        moving[name] = (part, Vector((x, y, surface(y))))\n+    return shell, moving\n+\n+\n+# ---- The focused clips -----------------------------------------------------------------------------------------------\n+\n+DROP = 96.0\n+\n+\n+def appear(f, f0, f1):\n+    return HIDDEN + (1 - HIDDEN) * back_out(phase(f, f0, f1)) if f >= f0 else HIDDEN\n+\n+\n+def tap(f, f0, hold=2):\n+    \"\"\"A press at f0: down in three frames, held, back up in four.\"\"\"\n+    return smooth(phase(f, f0, f0 + 3)) * (1 - smooth(phase(f, f0 + 3 + hold, f0 + 7 + hold)))\n+\n+\n+def playing(f):\n+    \"\"\"The power button, its light and the controller, the same on both.\"\"\"\n+    t = phase(f, 80, 190)\n+    k = math.sin(math.pi * t)\n+    return {\"power\": Pose(z=-1.6 * max(tap(f, 60), tap(f, 196))),\n+            \"power_light\": Pose() if 64 <= f < 200 else Pose(scale=HIDDEN),\n+            \"dpad\": Pose(pitch=8.0 * k * math.sin(2 * math.pi * 3.0 * t), roll=8.0 * k * math.cos(2 * math.pi * 2.0 * t)),\n+            \"a\": Pose(z=-2.0 * max(tap(f, f0) for f0 in (96, 124, 152, 176))),\n+            \"b\": Pose(z=-2.0 * max(tap(f, f0) for f0 in (108, 140, 168))),\n+            \"c\": Pose(z=-2.0 * max(tap(f, f0) for f0 in (116, 160)))}\n+\n+\n+def timeline_jaguar(f):\n+    if f < 8 or f >= 226:\n+        cart = Pose(scale=HIDDEN)\n+    elif f < 18:\n+        cart = Pose(z=DROP, scale=appear(f, 8, 16))\n+    elif f < 32:\n+        cart = Pose(z=DROP * (1 - ease_in(phase(f, 18, 32))))\n+    elif f < 206:\n+        cart = Pose(squash=0.05 * math.sin(math.pi * phase(f, 32, 38)))\n+    else:\n+        cart = Pose(z=DROP * ease_out(phase(f, 206, 218)), scale=max(HIDDEN, 1 - ease_in(phase(f, 214, 226))))\n+    return {\"cartridge\": cart, **playing(f)}\n+\n+\n+OPEN = -70.0\n+\n+\n+def lid_angle(f):\n+    if f < 10:\n+        return 0.0\n+    if f < 42:\n+        return OPEN * back_out(phase(f, 10, 22), 1.2)\n+    if f < 60:\n+        return OPEN * (1 - smooth(phase(f, 42, 52))) + wobble(f, 52, 2.0, period=6.0, decay=4.0)\n+    if f < 202:\n+        return 0.0\n+    if f < 224:\n+        return OPEN * back_out(phase(f, 202, 212), 1.2)\n+    return OPEN * (1 - smooth(phase(f, 224, 236)))\n+\n+\n+def timeline_cd(f):\n+    if f < 16 or f >= 222:\n+        cd = Pose(scale=HIDDEN)\n+    elif f < 24:\n+        cd = Pose(z=80.0, scale=appear(f, 16, 24))\n+    elif f < 38:\n+        cd = Pose(z=80.0 * (1 - ease_in(phase(f, 24, 38))), spin=90.0 * phase(f, 24, 38))\n+    elif f < 208:\n+        cd = Pose(z=wobble(f, 38, 1.2, period=6.0, decay=5.0), spin=90.0 + 720.0 * smooth(phase(f, 66, 190)))\n+    else:\n+        cd = Pose(z=70.0 * ease_out(phase(f, 208, 216)), spin=810.0, scale=max(HIDDEN, 1 - ease_in(phase(f, 212, 222))))\n+    return {\"lid\": Pose(pitch=lid_angle(f)), \"disc\": cd, **playing(f)}\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, kind, out = os.path.abspath(args[0]), args[1], os.path.abspath(args[2])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    printed = bpy.data.images.load(os.path.join(images, \"jaguar_print.png\"))\n+    m = {n: material(n, L.COLOURS[n], r, printed if n == \"print\" else None) for n, r in ROUGHNESS.items()}\n+\n+    parts = [jaguar(m)]\n+    button, button_at = power(m)\n+    lit, lit_at = power_light(m)\n+    pad, moving = controller(m)\n+    if kind == \"atarijaguarcd\":\n+        parts += jaguar_cd(m)\n+        cover, hinge = lid(m)\n+        media = disc(m)\n+        seat = Vector((0.0, L.CD[\"at\"], L.CD[\"top\"] - 9.0 + 5.0 + L.DISC[\"thickness\"] / 2 + 0.1))\n+        extra = [(cover, hinge), (media, seat)]\n+        nodes = {\"lid\": cover, \"disc\": media}\n+        timeline = timeline_cd\n+    else:\n+        cart = cartridge(m)\n+        seat = Vector((0.0, sum(L.SLOT[\"y\"]) / 2, L.FRAME[\"top\"] - L.CART[\"seat_depth\"]))\n+        extra = [(cart, seat)]\n+        nodes = {\"cartridge\": cart}\n+        timeline = timeline_jaguar\n+    whole = join(parts, kind)\n+    for obj in [whole, button, pad] + [p for p, _ in extra] + [p for p, _ in moving.values()]:\n+        for poly in obj.data.polygons:\n+            poly.use_smooth = True\n+        obj.data.set_sharp_from_angle(angle=math.radians(35))\n+\n+    # The node tree: the root (the view and the fit) holds the set; the set holds the moving parts at their places.\n+    root = bpy.data.objects.new(f\"{kind}_root\", None)\n+    scene.collection.objects.link(root)\n+    whole.parent = root\n+    for part, where in [(button, button_at), (lit, lit_at)] + extra:\n+        part.parent = whole\n+        part.location = where\n+    (px, py), turn = L.PAD[\"at\"], math.radians(L.PAD[\"turn\"])\n+    pad.parent = whole\n+    pad.location = (px, py, 0.0)\n+    pad.rotation_euler = (0.0, 0.0, turn)\n+    for part, where in moving.values():\n+        part.parent = pad\n+        part.location = where\n+    nodes.update({\"power\": button, \"power_light\": lit, **{n: p for n, (p, _) in moving.items()}})\n+\n+    # The three-quarter view, then the spec's fit of the set at rest, as the root's transform.\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    rest = Matrix.Translation((px, py, 0.0)) @ Matrix.Rotation(turn, 4, \"Z\")\n+    points = [tilt @ v.co for v in whole.data.vertices] + [tilt @ rest @ v.co for v in pad.data.vertices]\n+    if kind == \"atarijaguarcd\":\n+        points += [tilt @ Matrix.Translation(hinge) @ v.co for v in cover.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [whole, button, lit, pad] + [p for p, _ in extra] + [p for p, _ in moving.values()]\n+    print(f\"{kind}: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(whole.data)} the set), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "306e6b8b48fd9f5cb1ff6d153b2272697df42c64",
          "filename": "tools/console-models/jaguar_layout.py",
          "status": "added",
          "additions": 69,
          "deletions": 0,
          "changes": 69,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fjaguar_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fjaguar_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fjaguar_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,69 @@\n+\"\"\"The Atari Jaguar set's measurements, shared by jaguar_print.py (which draws its printing) and jaguar.py (which builds\n+it). Plain Python: Blender's Python imports it too.\n+\n+Two cards, one per system: `atarijaguar` (the Jaguar, 1993, and its controller) and `atarijaguarcd` (the same with the\n+Jaguar CD on it, 1995). The Jaguar is 241 x 254 mm (the Computer History Museum's 9 1/2 x 10 inches), about 64 mm high\n+at its fins (an Atari FAQ's 2 1/2 inches). Millimetres: x from its middle (left to right), y back from its front face\n+(the nose's), z up from the table. The rest was measured from Evan-Amos's photographs on Wikimedia Commons (the Jaguar\n+with its controller, from the back right; the controller from above; the Jaguar CD on the Jaguar, alone, and open),\n+scaled by the Jaguar's width.\n+\n+The Jaguar: a low, pillowed body; the round nose at its front (the red power button on it, 64-BIT INTERACTIVE\n+MULTIMEDIA SYSTEM and Atari's logo round it); the shallow dish in the middle of its top; the cartridge slot's raised\n+frame at the back between the two ribbed wings; JAGUAR in red script at the top left. The controller: its wide outline\n+(PAD_OUTLINE, traced from the photograph from above), the D-pad in its ring at the top left, PAUSE and OPTION, the red A,\n+B and C on a diagonal, the twelve keys of the keypad on the plate below, the ridges by the cable. The Jaguar CD: its\n+plan a rounded stadium, the lid (the clear crescent window, JAGUAR CD) hinged at its back, COMPACT DIGITAL MEDIA PLAYER,\n+its own cartridge slot raised at its back.\n+\n+One texture per card (ATLAS square, REGIONS): `top` (the Jaguar from above: JAGUAR, the dish's rim, the nose's\n+printing, the slot's frame), `pad` (the controller from above), `cd` (the Jaguar CD from above: the lid, its window and\n+logo, CLOSE HERE, its printing), `disc` (the CD's label: a made-up game, Odyssey), `label` (the cartridge's), and\n+swatches. A region is (x, y, width, height) in pixels.\n+\"\"\"\n+\n+ATLAS = 1024\n+\n+WIDTH, DEPTH = 241.0, 254.0\n+BODY = dict(y=(22.0, DEPTH), corner=24.0, rings=[(0.0, 0.0), (0.0, 20.0), (7.0, 36.0), (26.0, 45.0)])  # (inset, z)\n+NOSE = dict(x=78.0, y=(0.0, 58.0), height=38.0, top=31.0)      # a half-ellipse: its half-width, its span, heights\n+POWER = dict(at=(0.0, 22.0), size=(22.0, 11.0), height=2.5)\n+DISH = dict(at=(10.0, 112.0), r=64.0, depth=2.0)\n+FRAME = dict(x=(-78.0, 78.0), y=(176.0, 246.0), top=52.0)        # the slot's raised frame\n+SLOT = dict(x=(-58.0, 58.0), y=(196.0, 226.0), depth=40.0)\n+WINGS = dict(x=(84.0, 120.0), y=(165.0, 250.0), top=(50.0, 30.0), ribs=7, rib=(3.0, 4.0))   # inner, outer top z\n+LOGO = dict(at=(-66.0, 62.0), size=(60.0, 16.0))\n+\n+# The cartridge: w x thickness x h, standing in the slot (its bottom seat_depth below the frame's top), its label on\n+# its front.\n+CART = dict(size=(110.0, 26.0, 86.0), label=(90.0, 46.0), seat_depth=34.0)\n+\n+# The controller, in its own plan (x across, y back from its front edge, its middle at the origin).\n+PAD_OUTLINE = [(0.0, 0.0), (71.8, 0.4), (77.3, 12.9), (81.2, 28.8), (82.5, 58.4), (75.2, 90.7), (69.6, 100.5),\n+               (61.4, 109.1), (51.6, 115.2), (35.2, 119.0), (28.4, 122.9), (0.0, 124.6), (-28.8, 122.9),\n+               (-35.2, 119.0), (-48.6, 116.4), (-60.2, 110.0), (-67.0, 103.6), (-75.2, 90.2), (-79.9, 73.0),\n+               (-82.1, 59.3), (-82.1, 37.8), (-77.8, 15.5), (-71.3, 0.4)]\n+PAD = dict(at=(-196.0, -60.0), turn=12.0, height=(20.0, 30.0))   # its front's and back's heights\n+PAD_D = 124.6\n+DPAD = dict(at=(-49.0, 85.0), ring=18.0, size=24.0, arm=9.0, height=3.0)\n+ABC = dict(a=(57.0, 98.0), b=(42.0, 83.0), c=(25.0, 69.0), size=(16.0, 13.0), turn=-35.0, height=3.4)\n+SMALL = dict(pause=(-15.0, 72.0), option=(0.0, 72.0), size=(11.0, 5.5), turn=55.0, height=1.6)\n+KEYPAD = dict(x=(-28.0, 28.0), y=(14.0, 48.0), key=(13.0, 5.4), plate=(-40.0, 40.0, 4.0, 58.0), height=1.6)\n+\n+# The Jaguar CD: its plan (a semicircle at the front, straight sides back), its heights; the lid (its hinge at its\n+# back); the slot's frame at its back.\n+CD = dict(r=88.0, at=118.0, back=(-76.0, 76.0, 246.0), foot=44.0, top=72.0)   # the round front, the back section\n+LID = dict(r=78.0, back=172.0, height=4.0)                      # its round front (same middle), its back edge\n+CD_FRAME = dict(x=(-76.0, 76.0), y=(184.0, 246.0), top=86.0)\n+CD_SLOT = dict(x=(-58.0, 58.0), y=(196.0, 226.0), depth=40.0)\n+DISC = dict(diameter=120.0, hole=15.0, thickness=1.2, well=4.0)   # the well under the lid, round the disc\n+\n+COLOURS = dict(case=\"#1E1E21\", key=\"#141416\", red=\"#C8302A\", print=\"#FFFFFF\")\n+\n+REGIONS = dict(top=(0, 0, 486, 512), pad=(494, 0, 530, 400), cd=(0, 520, 400, 446), disc=(494, 408, 256, 256),\n+               label=(758, 408, 266, 136))\n+SWATCHES = dict(red=(774, 568), dark=(806, 568), grey=(838, 568), silver=(870, 568), window=(902, 568),\n+                lit=(934, 568))\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s"
        },
        {
          "sha": "03c04baecb74e3c7bfb0ccaabeca172f3b307a19",
          "filename": "tools/console-models/jaguar_print.py",
          "status": "added",
          "additions": 201,
          "deletions": 0,
          "changes": 201,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fjaguar_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fjaguar_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fjaguar_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,201 @@\n+\"\"\"The Atari Jaguar set's printing, for the console theme's Jaguar and Jaguar CD models (tools/console-models/jaguar.py).\n+\n+Draws one atlas for both, its regions as jaguar_layout.py places them, each in millimetres as the model's planar UVs\n+map it: the Jaguar from above (JAGUAR in red script, the dish's rim, Atari's logo and 64-BIT INTERACTIVE MULTIMEDIA\n+SYSTEM on the nose, the power button's ring); the controller from above (the D-pad's ring, the ridges and Atari's logo\n+by the cable, PAUSE and OPTION, A, B and C, the keypad's plate and its numbers); the Jaguar CD from above (its lid with\n+the crescent window and JAGUAR CD, CLOSE HERE, Atari's logo and COMPACT DIGITAL MEDIA PLAYER); the CD's label and the\n+cartridge's (a made-up game, Odyssey); and the swatches small parts take their colour from. Run it with the system\n+Python (Pillow):\n+\n+    python tools/console-models/jaguar_print.py <out folder>\n+\n+It writes jaguar_print.png there.\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+from PIL import Image, ImageDraw, ImageFont\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import jaguar_layout as L  # noqa: E402\n+from atari5200_print import fuji  # noqa: E402\n+from psp_print import ship, space  # noqa: E402\n+from retro_pc_print import rgb, shade  # noqa: E402\n+from windows_pc_print import FONTS, Region  # noqa: E402\n+\n+CASE = rgb(L.COLOURS[\"case\"])\n+RED = rgb(L.COLOURS[\"red\"])\n+INK = (210, 210, 214)\n+LOGO_RED = (178, 52, 40)\n+\n+\n+def jaguar(reg, cx, cy, w, h, colour, cd=False):\n+    \"\"\"JAGUAR in a slanted brush script, w x h, its middle at (cx, cy), the claw marks trailing from its R; CD under it,\n+    when it's the Jaguar CD's.\"\"\"\n+    mask = Image.new(\"L\", (1400, 300), 0)\n+    d = ImageDraw.Draw(mask)\n+    f = ImageFont.truetype(FONTS + \"BRUSHSCI.TTF\", 240)\n+    d.text((40, 150), \"JAGUAR\", font=f, fill=255, anchor=\"lm\")\n+    for k in range(3):                                         # the claw marks after the R\n+        y = 150 + 30 * k\n+        d.line([(1000, y), (1360, y - 30)], fill=255, width=14)\n+    mask = mask.crop(mask.getbbox())\n+    mask = mask.transform(mask.size, Image.AFFINE, (1, 0.25, -0.25 * mask.height / 2, 0, 1, 0))\n+    x0, y0, x1, y1 = reg.box(cx - w / 2, cy - h / 2, cx + w / 2, cy + h / 2)\n+    mask = mask.resize((max(1, round(x1 - x0)), max(1, round(y1 - y0))), Image.LANCZOS)\n+    reg.img.paste(Image.new(\"RGBA\", mask.size, colour + (255,)), (round(x0), round(y0)), mask)\n+    if cd:\n+        reg.text(\"CD\", \"BRUSHSCI.TTF\", h * 0.9, cx + w * 0.22, cy + h * 0.85, colour)\n+\n+\n+# ---- The Jaguar --------------------------------------------------------------------------------------------------------\n+\n+def top():\n+    w, d = L.WIDTH, L.DEPTH\n+    reg = Region(L.REGIONS[\"top\"], w, d, CASE)\n+\n+    def at(x, y):\n+        return x + w / 2, d - y\n+    (dx, dy), r = L.DISH[\"at\"], L.DISH[\"r\"]\n+    reg.circle(*at(dx, dy), r + 1.5, shade(CASE, -0.25))\n+    reg.circle(*at(dx, dy), r, shade(CASE, 0.06))\n+    (lx, ly), (lw, lh) = L.LOGO[\"at\"], L.LOGO[\"size\"]\n+    jaguar(reg, *at(lx, ly), lw, lh, LOGO_RED)\n+    (px, py), (pw, ph) = L.POWER[\"at\"], L.POWER[\"size\"]\n+    reg.draw.ellipse(reg.box(*at(px - pw / 2 - 1.2, py + ph / 2 + 1.2), *at(px + pw / 2 + 1.2, py - ph / 2 - 1.2)),\n+                     fill=(10, 10, 11))\n+    reg.circle(*at(px, py - 9.0), 1.2, (60, 16, 14))           # the power light\n+    fuji(reg, *at(-12.0, 12.0), 4.4, INK)\n+    reg.text(\"ATARI\", \"arialbd.ttf\", 3.8, *at(-8.0, 12.0), INK, anchor=\"lm\")\n+    reg.text(\"64-BIT  INTERACTIVE  MULTIMEDIA  SYSTEM\", \"arialbd.ttf\", 2.6, *at(0.0, 5.0), INK)\n+    (fx0, fx1), (fy0, fy1) = L.FRAME[\"x\"], L.FRAME[\"y\"]\n+    reg.rect_(*at(fx0, fy1), *at(fx1, fy0), shade(CASE, -0.06), r=3.0)\n+    return reg\n+\n+\n+# ---- The controller ------------------------------------------------------------------------------------------------\n+\n+def pad():\n+    w = 2 * max(x for x, _ in L.PAD_OUTLINE)\n+    d = L.PAD_D\n+    reg = Region(L.REGIONS[\"pad\"], w, d, CASE)\n+\n+    def at(x, y):\n+        return x + w / 2, d - y\n+    (dx, dy), ring = L.DPAD[\"at\"], L.DPAD[\"ring\"]\n+    reg.circle(*at(dx, dy), ring + 1.5, (12, 12, 13))\n+    reg.circle(*at(dx, dy), ring - 2.5, (24, 24, 26))\n+    for k in range(6):\n+        a = math.radians(60 * k + 30)\n+        reg.circle(*at(dx + (ring - 0.5) * math.cos(a), dy + (ring - 0.5) * math.sin(a)), 0.6, (60, 60, 64))\n+    for k in range(3):                                         # the ridges under the cable\n+        y = 108.0 - 6.0 * k\n+        reg.draw.arc(reg.box(*at(-34.0 + 6.0 * k, y + 8.0), *at(34.0 - 6.0 * k, y - 8.0)), 200, 340,\n+                     fill=shade(CASE, -0.3), width=reg.width(1.2))\n+    reg.rect_(*at(-30.0, 124.0), *at(30.0, 114.0), shade(CASE, 0.05), r=4.0)\n+    fuji(reg, *at(-10.0, 119.0), 4.0, shade(CASE, -0.2))\n+    reg.text(\"ATARI\", \"arialbd.ttf\", 3.6, *at(-6.0, 119.0), shade(CASE, -0.2), anchor=\"lm\")\n+    for name, label in ((\"pause\", \"PAUSE\"), (\"option\", \"OPTION\")):\n+        x, y = L.SMALL[name]\n+        reg.text(label, \"arialbd.ttf\", 2.6, *at(x - 4.0, y + 4.0), INK)\n+    for name in (\"a\", \"b\", \"c\"):\n+        x, y = L.ABC[name]\n+        reg.text(name.upper(), \"arialbd.ttf\", 4.2, *at(x - 9.0, y + 8.0), INK)\n+    px0, px1, py0, py1 = L.KEYPAD[\"plate\"]\n+    reg.rect_(*at(px0 - 12.0, py1 + 4.0), *at(px1 + 12.0, 0.0), shade(CASE, 0.03), r=6.0)\n+    reg.line([at(px0 - 8.0, py1 + 2.0), at(px1 + 8.0, py1 + 2.0)], (8, 8, 9), 0.9)\n+    reg.rect_(*at(px0, py1), *at(px1, py0), shade(CASE, 0.07), r=2.0)\n+    labels = [\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"*\", \"0\", \"#\"]\n+    (kx0, kx1), (ky0, ky1) = L.KEYPAD[\"x\"], L.KEYPAD[\"y\"]\n+    for i in range(4):\n+        for j in range(3):\n+            x = kx0 + (kx1 - kx0) * j / 2\n+            y = ky1 - (ky1 - ky0) * i / 3\n+            reg.text(labels[i * 3 + j], \"arial.ttf\", 3.0, *at(x, y + 4.6), shade(CASE, -0.35))\n+    return reg\n+\n+\n+# ---- The Jaguar CD -----------------------------------------------------------------------------------------------------\n+\n+def cd():\n+    r, cy = L.CD[\"r\"], L.CD[\"at\"]\n+    y0, y1 = cy - r, L.CD[\"back\"][2]\n+    w, d = 2 * r, y1 - y0\n+    reg = Region(L.REGIONS[\"cd\"], w, d, CASE)\n+\n+    def at(x, y):\n+        return x + w / 2, y1 - y\n+    lr = L.LID[\"r\"]\n+    lid = [at(lr * math.cos(math.radians(a)), cy + lr * math.sin(math.radians(a))) for a in range(0, -181, -5)]\n+    lid += [at(-lr, L.LID[\"back\"]), at(lr, L.LID[\"back\"])]\n+    reg.poly(lid, shade(CASE, 0.05))\n+    reg.line(lid + lid[:1], (8, 8, 9), 0.6)\n+    reg.draw.ellipse(reg.box(*at(-62.0, 150.0), *at(46.0, 66.0)), fill=shade(CASE, 0.1), outline=(10, 10, 11),\n+                     width=reg.width(0.5))\n+    for k in range(18):                                        # the crescent window\n+        t = k / 17\n+        a = math.radians(200 + 140 * t)\n+        x, y = -44.0 + 16.0 * math.cos(a), 108.0 + 22.0 * math.sin(a)\n+        reg.circle(*at(x, y), 3.4 + 1.6 * math.sin(math.pi * t), (150, 160, 176))\n+    jaguar(reg, *at(8.0, 112.0), 52.0, 14.0, LOGO_RED, cd=True)\n+    reg.text(\"CLOSE HERE\", \"arialbd.ttf\", 3.0, *at(-20.0, 46.0), INK)\n+    reg.text(\"COMPACT  DIGITAL  MEDIA  PLAYER\", \"arialbd.ttf\", 3.0, *at(18.0, 178.0), INK)\n+    fuji(reg, *at(10.0, 184.0), 3.6, INK)\n+    reg.text(\"ATARI\", \"arialbd.ttf\", 3.2, *at(14.0, 184.0), INK, anchor=\"lm\")\n+    return reg\n+\n+\n+# ---- The labels --------------------------------------------------------------------------------------------------------\n+\n+def disc():\n+    dm = L.DISC[\"diameter\"]\n+    reg = Region(L.REGIONS[\"disc\"], dm, dm, (20, 20, 24))\n+    c = dm / 2\n+    art = space((600, 600)).convert(\"RGB\")\n+    ship(ImageDraw.Draw(art), 180, 330, 4.0, \"#FFFFFF\", \"#FFB23C\")\n+    mask = Image.new(\"L\", reg.img.size, 0)\n+    md = ImageDraw.Draw(mask)\n+    md.ellipse(reg.box(c - 58.0, c - 58.0, c + 58.0, c + 58.0), fill=255)\n+    md.ellipse(reg.box(c - 21.0, c - 21.0, c + 21.0, c + 21.0), fill=0)\n+    x0, y0, x1, y1 = reg.box(c - 58.0, c - 58.0, c + 58.0, c + 58.0)\n+    layer = Image.new(\"RGBA\", reg.img.size, (0, 0, 0, 0))\n+    layer.paste(art.resize((round(x1 - x0), round(y1 - y0)), Image.LANCZOS), (round(x0), round(y0)))\n+    reg.img.paste(layer, (0, 0), mask)\n+    reg.circle(c, c, 21.0, (176, 180, 186), outline=(150, 150, 156), width=0.4)\n+    reg.text(\"ODYSSEY\", \"ariblk.ttf\", 11.0, c, c - 36.0, (255, 214, 70))\n+    reg.circle(c, c, 7.5, (40, 40, 44))\n+    return reg\n+\n+\n+def label():\n+    lw, lh = L.CART[\"label\"]\n+    reg = Region(L.REGIONS[\"label\"], lw, lh, (20, 20, 22))\n+    art = space((900, 460)).convert(\"RGB\")\n+    ship(ImageDraw.Draw(art), 300, 250, 3.6, \"#FFFFFF\", \"#FFB23C\")\n+    x0, y0, x1, y1 = reg.box(0.0, 0.0, lw, lh)\n+    reg.img.paste(art.resize((round(x1 - x0), round(y1 - y0)), Image.LANCZOS), (round(x0), round(y0)))\n+    reg.text(\"ODYSSEY\", \"ariblk.ttf\", 9.0, lw / 2, 9.0, (255, 214, 70))\n+    jaguar(reg, lw - 16.0, lh - 6.0, 24.0, 6.0, (230, 70, 50))\n+    return reg\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), CASE)\n+    for reg in (top(), pad(), cd(), disc(), label()):\n+        reg.paste(image)\n+    d = ImageDraw.Draw(image)\n+    for name, colour in ((\"red\", RED), (\"dark\", (10, 10, 11)), (\"grey\", (60, 60, 64)), (\"silver\", (200, 204, 210)),\n+                         (\"window\", (150, 160, 176)), (\"lit\", (255, 60, 40))):\n+        x, y = L.SWATCHES[name]\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colour)\n+    path = os.path.join(out, \"jaguar_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "2215558b2fd0290e0c627177e54f61a1d330c2d7",
          "filename": "tools/console-models/lynx.py",
          "status": "added",
          "additions": 357,
          "deletions": 0,
          "changes": 357,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Flynx.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Flynx.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Flynx.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,357 @@\n+\"\"\"The console theme's Atari Lynx system model (godot/themes/console/models/systems/atarilynx.glb): a Lynx II.\n+\n+Built in Blender from the real handheld's proportions (lynx_layout.py has the measurements, taken from photographs).\n+The body is its front outline lofted back, its edges rounded, the grips' bulges behind its ends; the grips stand a\n+little proud of the glossy middle, the D-pad's well and the wells of the two pairs of A and B cut into them. The\n+window's cover, the six buttons beside it, the D-pad and A and B stand on the front; the game card's slot is cut into\n+the top at the back. The printing is one texture drawn by lynx_print.py: the front (planar-mapped onto the faces facing\n+forward: the grips, the middle, the window's surround and the switched-off screen, the logos and the names), the\n+screens, the card's label, and swatches.\n+\n+A node tree: a root (the turn to a three-quarter view, TILT: its left end towards the viewer and its face tipped up;\n+then the model spec's fit: standing on y = 0, centred, the largest side 1 m) holding the console, its origin at its\n+foot's middle; the console holding the game card (waiting in its slot, shrunk to nothing), ON and OFF, OPTION 2 and\n+PAUSE (FLIP), the top pair's A and the foot pair's A and B, the D-pad, and three screens (the title of a made-up game,\n+Odyssey; a moment of its play; the same turned over), shrunk to nothing, so they ride along when the console moves.\n+\n+The `focused` clip (8 s, looping) plays a game and uses the Lynx's own trick: the card hovers over the console showing\n+its label, turns round and drops into the slot; ON, and the title comes up; A, and the game; then FLIP turns the picture\n+over and the console hops and turns over with it, its D-pad now at the right, and the game is played on the other pair\n+of A and B; FLIP again, and it hops back. Then OFF, and the card rises out and goes.\n+\n+Four materials (case, key, glass, print) and one texture, within the system model budget. Run from the repo root:\n+\n+    python tools/console-models/lynx_print.py artifacts/lynx\n+    blender -b --factory-startup --python tools/console-models/lynx.py -- artifacts/lynx godot/themes/console/models/systems/atarilynx.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import lynx_layout as L  # noqa: E402\n+from amiga import curved, paint, rounded, solid  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, ease_out, key, phase, smooth  # noqa: E402\n+from mega_drive_cartridge import bevel, boolean, box, export, join, material, triangles  # noqa: E402\n+from msx_layout import inset  # noqa: E402\n+\n+TILT = (20.0, 10.0)       # degrees: yaw (left end nearer), pitch (face up)\n+ROUGHNESS = dict(case=0.6, key=0.3, glass=0.35, print=0.5)\n+W, H, D = L.WIDTH, L.HEIGHT, L.DEPTH\n+MID = H / 2               # the layout's z = 0, in the console's frame (z up from its foot)\n+\n+\n+# ---- UVs and outlines ------------------------------------------------------------------------------------------------\n+\n+def atlas_uv(region, w, h):\n+    \"\"\"A face drawn w x h into region: (a, b) millimetres across it (a to the right, b down from its top) to UVs.\"\"\"\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+FRONT = atlas_uv(L.REGIONS[\"front\"], W, H)\n+\n+\n+def front_uv(co):\n+    return FRONT(co.x + W / 2, H - co.z)\n+\n+\n+def swatch(name):\n+    x, y = L.SWATCHES[name]\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+def outline():\n+    \"\"\"The front outline, anticlockwise (x, z) in the console's frame.\"\"\"\n+    right = L.RIGHT_HALF\n+    points = right + [(-x, z) for x, z in reversed(right[1:-1])]\n+    return [(x, z + MID) for x, z in reversed(points)]\n+\n+\n+def clip_x(points, x0, keep_right):\n+    \"\"\"A convex outline clipped to x >= x0 (keep_right) or x <= x0.\"\"\"\n+    def inside(p):\n+        return p[0] >= x0 if keep_right else p[0] <= x0\n+    out = []\n+    for i, p in enumerate(points):\n+        q = points[(i + 1) % len(points)]\n+        if inside(p):\n+            out.append(p)\n+        if inside(p) != inside(q):\n+            t = (x0 - p[0]) / (q[0] - p[0])\n+            out.append((x0, p[1] + t * (q[1] - p[1])))\n+    return out\n+\n+\n+def plate(name, points, y0, y1, mat):\n+    \"\"\"A solid from an (x, z) outline, from y0 to y1 (y back from the front face).\"\"\"\n+    return solid(name, [[(x, y, z) for x, z in points] for y in (y0, y1)], mat)\n+\n+\n+def facing_front(c, n, name):\n+    return n.y < -0.9\n+\n+\n+# ---- The console -----------------------------------------------------------------------------------------------------\n+\n+def body(m):\n+    \"\"\"The body, its grips, the bulges behind them; the wells cut into the grips, the slot into the top; its front\n+    printed.\"\"\"\n+    shape = outline()\n+    shell = plate(\"atarilynx\", shape, 0.0, D, m[\"case\"])\n+    bevel(shell, 3.0, segments=2)\n+    (cx0, cx1) = (-L.CARD[\"size\"][0] / 2 - 1.0, L.CARD[\"size\"][0] / 2 + 1.0)\n+    boolean(shell, box(\"slot\", cx0, cx1, L.CARD[\"y\"] - L.CARD[\"size\"][2] / 2 - 0.5, L.CARD[\"y\"] + L.CARD[\"size\"][2] / 2 + 0.5,\n+                       H - L.CARD[\"size\"][1] - 2.0, H + 5.0, m[\"key\"]))\n+    boolean(shell, box(\"recess\", -31.0, 31.0, D - 2.5, D + 5.0, H - 22.0, H + 5.0, m[\"case\"]))\n+    paint(shell, m[\"print\"], front_uv, lambda c, n, name: n.y < -0.9 and name == \"case\")\n+    parts = [shell]\n+    for side in (-1, 1):\n+        grip = plate(\"grip\", clip_x(shape, side * L.GRIP_X, side > 0), -L.PROUD, 1.5, m[\"case\"])\n+        bevel(grip, 1.4, segments=2)\n+        bulge_outline = clip_x(shape, side * L.BULGE[\"x\"], side > 0)\n+        rings = [[(x, D - 3.0, z) for x, z in bulge_outline],\n+                 [(x, D + L.BULGE[\"depth\"] * 0.6, z) for x, z in inset(bulge_outline, 3.0)],\n+                 [(x, D + L.BULGE[\"depth\"], z) for x, z in inset(bulge_outline, 11.0)]]\n+        bulge = solid(\"bulge\", rings, m[\"case\"])\n+        parts += [grip, bulge]\n+    grips = join(parts[1::2], \"grips\")\n+    (dx, dz), size, arm, well = L.DPAD[\"at\"], L.DPAD[\"size\"], L.DPAD[\"arm\"], L.DPAD[\"well\"]\n+    boolean(grips, plate(\"dpad_well\", cross(dx, dz + MID, size + 2.0, arm + 2.0), -L.PROUD - 5.0, -L.PROUD + well,\n+                         m[\"key\"]))\n+    a = L.AB\n+    for pair, turn in ((a[\"top\"], a[\"turn\"][0]), (a[\"bottom\"], a[\"turn\"][1])):\n+        for x, z in pair:\n+            boolean(grips, plate(\"ab_well\", square(x, z + MID, a[\"well\"], turn, 2.0), -L.PROUD - 5.0, -L.PROUD + 1.2,\n+                                 m[\"key\"]))\n+    paint(grips, m[\"print\"], front_uv, lambda c, n, name: n.y < -0.9 and name == \"case\")\n+    bulges = parts[2::2]\n+    return [shell, grips] + bulges\n+\n+\n+def cross(cx, cz, size, arm):\n+    \"\"\"A plus sign's outline, anticlockwise, its middle at (cx, cz).\"\"\"\n+    s, a = size / 2, arm / 2\n+    points = [(a, s), (-a, s), (-a, a), (-s, a), (-s, -a), (-a, -a), (-a, -s), (a, -s), (a, -a), (s, -a), (s, a), (a, a)]\n+    return [(cx + x, cz + z) for x, z in points]\n+\n+\n+def square(cx, cz, size, turn, r):\n+    \"\"\"A rounded square's outline, anticlockwise, its middle at (cx, cz), turned by turn degrees.\"\"\"\n+    c, s = math.cos(math.radians(turn)), math.sin(math.radians(turn))\n+    return [(cx + c * x - s * z, cz + s * x + c * z) for x, z in rounded(0.0, 0.0, size, size, r, 3)]\n+\n+\n+def window(m):\n+    (wx, wz), (ww, wh) = L.WINDOW[\"at\"], L.WINDOW[\"size\"]\n+    part = plate(\"window\", rounded(wx, wz + MID, ww, wh, L.WINDOW[\"corner\"], 5), -L.WINDOW[\"height\"], 0.5, m[\"glass\"])\n+    bevel(part, 1.2, segments=2)\n+    paint(part, m[\"glass\"], front_uv, facing_front)\n+    paint(part, m[\"glass\"], swatch(\"key\"), lambda c, n, name: n.y >= -0.9)\n+    return part\n+\n+\n+def side_button(name, x, z, m):\n+    \"\"\"A button beside the window, its middle at its origin (it is pressed into the front).\"\"\"\n+    w, h = L.LEFT_BUTTONS[\"size\"]\n+    part = plate(name, rounded(0.0, 0.0, w, h, 1.8, 3), -L.LEFT_BUTTONS[\"height\"], 0.5, m[\"key\"])\n+    bevel(part, 0.7, segments=1)\n+    return part, Vector((x, 0.0, z + MID))\n+\n+\n+def dpad(m):\n+    \"\"\"The D-pad, its middle at its origin, standing out of its well.\"\"\"\n+    size, arm, h = L.DPAD[\"size\"], L.DPAD[\"arm\"], L.DPAD[\"height\"]\n+    part = plate(\"dpad\", cross(0.0, 0.0, size, arm), -L.PROUD - h, -L.PROUD + L.DPAD[\"well\"] + 0.5, m[\"key\"])\n+    bevel(part, 1.0, segments=2)\n+    (dx, dz) = L.DPAD[\"at\"]\n+    return part, Vector((dx, 0.0, dz + MID))\n+\n+\n+def ab_button(name, turn, m):\n+    \"\"\"A or B, its middle at its origin: a rounded square, its top domed.\"\"\"\n+    a = L.AB\n+    size, h = a[\"size\"], a[\"height\"]\n+    rings = [[(x, y, z) for x, z in square(0.0, 0.0, size * k, turn, 3.0 * k)] for k, y in\n+             ((1.0, -L.PROUD + 1.0), (1.0, -L.PROUD - h * 0.6), (0.82, -L.PROUD - h * 0.92), (0.5, -L.PROUD - h))]\n+    part = solid(name, rings, m[\"key\"])\n+    return part\n+\n+\n+# ---- The card and the screens ----------------------------------------------------------------------------------------\n+\n+def card(m):\n+    \"\"\"The game card, its foot's middle at its origin, standing in the slot, its label facing back.\"\"\"\n+    w, h, t = L.CARD[\"size\"]\n+    shell = box(\"card\", -w / 2, w / 2, -t / 2, t / 2, 0.0, h, m[\"case\"])\n+    bevel(shell, 0.8, segments=1)\n+    lip = box(\"lip\", -w / 2 + 6.0, w / 2 - 6.0, -t / 2 - 0.8, t / 2 + 0.8, h - L.CARD[\"lip\"], h, m[\"case\"])\n+    bevel(lip, 0.6, segments=1)\n+    shell = join([shell, lip], \"card\")\n+    face = atlas_uv(L.REGIONS[\"card\"], w, h)\n+    paint(shell, m[\"print\"], lambda co: face(w / 2 - co.x, h - co.z), lambda c, n, name: n.y > 0.9 and c.y > t / 2 - 0.1)\n+    return shell\n+\n+\n+def screen(m, name):\n+    sw, sh = L.SCREEN[\"size\"]\n+    return curved(name, sw, sh, 0.0, atlas_uv(L.REGIONS[name], 1.0, 1.0), m[\"glass\"], nu=1, nv=1)\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+HOVER = 72.0              # how far above its seat the card hovers: clear of the top\n+\n+\n+def appear(f, f0, f1):\n+    return HIDDEN + (1 - HIDDEN) * back_out(phase(f, f0, f1)) if f >= f0 else HIDDEN\n+\n+\n+def tap(f, f0, hold=2):\n+    \"\"\"A press at f0: down in three frames, held, back up in four.\"\"\"\n+    return smooth(phase(f, f0, f0 + 3)) * (1 - smooth(phase(f, f0 + 3 + hold, f0 + 7 + hold)))\n+\n+\n+def turned(degrees, lift):\n+    \"\"\"The console turned by degrees about its line of sight, moved so it still stands on the floor, centred, lift\n+    above it.\"\"\"\n+    t = math.radians(degrees)\n+    s, c = math.sin(t), math.cos(t)\n+    stand = W / 2 * abs(s) + H / 2 * abs(c)\n+    return Pose(x=-H / 2 * s, z=stand - H / 2 * c + lift, roll=degrees)\n+\n+\n+def timeline(f):\n+    # The card: it hovers showing its label, turns round and drops into the slot; later it rises out and goes.\n+    if f < 6 or f >= 226:\n+        crd = Pose(scale=HIDDEN)\n+    elif f < 14:\n+        crd = Pose(z=HOVER, spin=180.0, scale=appear(f, 6, 14))\n+    elif f < 30:\n+        crd = Pose(z=HOVER + 2.0 * math.sin(2 * math.pi * phase(f, 14, 30)), spin=180.0)\n+    elif f < 42:\n+        crd = Pose(z=HOVER + 6.0 * math.sin(math.pi * phase(f, 30, 42)), spin=180.0 + 180.0 * smooth(phase(f, 30, 42)))\n+    elif f < 52:\n+        crd = Pose(z=HOVER * (1 - ease_in(phase(f, 42, 52))), spin=360.0)\n+    elif f < 212:\n+        crd = Pose(spin=360.0)\n+    else:\n+        crd = Pose(z=HOVER * ease_out(phase(f, 212, 222)), spin=360.0, scale=max(HIDDEN, 1 - ease_in(phase(f, 218, 226))))\n+\n+    # The console: it hops and turns over with FLIP, and back.\n+    if f < 108:\n+        console = Pose()\n+    elif f < 134:\n+        t = phase(f, 108, 134)\n+        console = turned(180.0 * smooth(t), 26.0 * math.sin(math.pi * t))\n+    elif f < 176:\n+        console = turned(180.0, 0.0)\n+    elif f < 202:\n+        t = phase(f, 176, 202)\n+        console = turned(180.0 + 180.0 * smooth(t), 26.0 * math.sin(math.pi * t))\n+    else:\n+        console = Pose(roll=360.0)\n+\n+    def shown(name, f0, f1, *more):\n+        spans = [(f0, f1)] + list(zip(more[::2], more[1::2]))\n+        for a, b in spans:\n+            if a <= f < b:\n+                grow = min(1.0, (f - a + 1) / 4.0) if name == \"title\" else 1.0\n+                fade = 1.0 if name != \"game\" or b != 208 else min(1.0, (b - f) / 4.0)\n+                return Pose(axes=(1.0, 1.0, max(HIDDEN, grow * fade)))\n+        return Pose(scale=HIDDEN)\n+    flip = max(tap(f, 100), tap(f, 168))\n+    play = phase(f, 136, 168)\n+    rock = math.sin(math.pi * play)\n+    return {\"card\": crd, \"console\": console,\n+            \"title\": shown(\"title\", 60, 88), \"game\": shown(\"game\", 88, 104, 172, 208),\n+            \"game_flipped\": shown(\"game_flipped\", 104, 172),\n+            \"on\": Pose(y=1.5 * tap(f, 56)), \"off\": Pose(y=1.5 * tap(f, 204)),\n+            \"pause\": Pose(y=1.5 * flip), \"option2\": Pose(y=1.5 * flip),\n+            \"a_top\": Pose(y=1.5 * tap(f, 84)),\n+            \"a_bottom\": Pose(y=1.5 * max(tap(f, f0) for f0 in (140, 156))),\n+            \"b_bottom\": Pose(y=1.5 * max(tap(f, f0) for f0 in (148, 162))),\n+            \"dpad\": Pose(pitch=7.0 * rock * math.sin(2 * math.pi * 2.0 * play),\n+                         spin=7.0 * rock * math.cos(2 * math.pi * 3.0 * play))}\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    printed = bpy.data.images.load(os.path.join(images, \"lynx_print.png\"))\n+    m = {n: material(n, L.COLOURS[n], r, printed if n in (\"print\", \"glass\") else None) for n, r in ROUGHNESS.items()}\n+\n+    static = body(m) + [window(m)]\n+    moving = {}\n+    names = ((\"on\", \"off\", \"backlight\"), (\"option1\", \"pause\", \"option2\"))\n+    for column, row_names in zip((L.LEFT_BUTTONS, L.RIGHT_BUTTONS), names):\n+        for name, z in zip(row_names, column[\"z\"]):\n+            part, where = side_button(name, column[\"x\"], z, m)\n+            if name in (\"on\", \"off\", \"pause\", \"option2\"):\n+                moving[name] = (part, where)\n+            else:\n+                part.data.transform(Matrix.Translation(where))\n+                static.append(part)\n+    for pair, turn, which in ((L.AB[\"top\"], L.AB[\"turn\"][0], \"top\"), (L.AB[\"bottom\"], L.AB[\"turn\"][1], \"bottom\")):\n+        for (x, z), letter in zip(pair, (\"b\", \"a\")):\n+            name = f\"{letter}_{which}\"\n+            part = ab_button(name, turn, m)\n+            where = Vector((x, 0.0, z + MID))\n+            if name in (\"a_top\", \"a_bottom\", \"b_bottom\"):\n+                moving[name] = (part, where)\n+            else:\n+                part.data.transform(Matrix.Translation(where))\n+                static.append(part)\n+    moving[\"dpad\"] = dpad(m)\n+    console = join(static, \"console\")\n+    crd = card(m)\n+    (sx, sz) = L.SCREEN[\"at\"]\n+    screens = {name: (screen(m, name), Vector((sx, -L.WINDOW[\"height\"] - 0.15 - 0.1 * k, sz + MID)))\n+               for k, name in enumerate((\"title\", \"game\", \"game_flipped\"))}\n+    for obj in [console, crd] + [p for p, _ in moving.values()]:\n+        for p in obj.data.polygons:\n+            p.use_smooth = True\n+        obj.data.set_sharp_from_angle(angle=math.radians(35))\n+\n+    # The node tree: the root (the view and the fit) holds the console; the console its parts at their places.\n+    root = bpy.data.objects.new(\"atarilynx_root\", None)\n+    scene.collection.objects.link(root)\n+    console.parent = root\n+    w, h, t = L.CARD[\"size\"]\n+    seat = Vector((0.0, L.CARD[\"y\"], H - h))\n+    placed = [(crd, seat)] + list(moving.values()) + list(screens.values())\n+    for part, where in placed:\n+        part.parent = console\n+        part.location = where\n+    nodes = {\"console\": console, \"card\": crd, **{n: p for n, (p, _) in moving.items()},\n+             **{n: p for n, (p, _) in screens.items()}}\n+\n+    # The three-quarter view, then the spec's fit of the console at rest, as the root's transform.\n+    yaw, pitch = TILT\n+    tilt = Matrix.Rotation(math.radians(yaw), 4, \"Z\") @ Matrix.Rotation(math.radians(-pitch), 4, \"X\")\n+    points = [tilt @ v.co for v in console.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [console, crd] + [p for p, _ in moving.values()] + [p for p, _ in screens.values()]\n+    print(f\"Lynx: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(console.data)} the console), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "dbfc49334ae639f99fb779c07efbfe0121d3be50",
          "filename": "tools/console-models/lynx_layout.py",
          "status": "added",
          "additions": 58,
          "deletions": 0,
          "changes": 58,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Flynx_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Flynx_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Flynx_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,58 @@\n+\"\"\"The Atari Lynx II's measurements, shared by lynx_print.py (which draws its printing and screens) and lynx.py (which\n+builds it). Plain Python: Blender's Python imports it too.\n+\n+The Lynx II (1991), 235 x 108 x 51 mm (the figures Retro Isle and Atari Gamer give; its grips' bulges at the back\n+included), standing up facing the viewer as the handheld models do. Millimetres: x from its middle (left to right), y\n+back from its front face, z up from its middle. The rest was measured from Evan-Amos's photographs on Wikimedia Commons\n+(the Lynx II from in front, flat, which gives the front's layout, and from the back, the top, the bottom and at an\n+angle), scaled by its width.\n+\n+The body: its front outline (OUTLINE: a long rounded shape, its ends bulging), lofted back, the grips' bulges behind\n+its ends; the grips at the ends a little proud of the glossy middle. On the front: the screen's window in the middle,\n+the LYNX logo over it and ATARI under it; ON, OFF and BACKLIGHT at its left, OPTION 1, PAUSE and OPTION 2 at its right;\n+the D-pad in the left grip; two pairs of A and B in the right grip, one at its top and one at its foot (for playing it\n+the other way up); the speaker's slots between them. The game card slides down into the slot at the top of its back.\n+\n+One texture (ATLAS square, REGIONS): `front` (the front from in front: the grips, the middle, the window's surround and\n+the switched-off screen, the logos, the buttons' names and their brackets, the speaker, the D-pad's well), the screens\n+(`title`, a made-up game, Odyssey; `game`, a moment of its play; `game_flipped`, the same turned over, as FLIP shows it),\n+`card` (the game card's label), and swatches. A region is (x, y, width, height) in pixels.\n+\"\"\"\n+\n+WIDTH, HEIGHT, DEPTH = 235.0, 108.0, 33.0        # the body; the grips' bulges reach BULGE further back\n+BULGE = dict(x=74.0, depth=17.0)                 # the bulges: from |x| = 74 to the ends\n+\n+# The front outline's right half, from the top's middle round to the bottom's (x, z); the left half mirrors it.\n+RIGHT_HALF = [(0.0, 54.0), (60.0, 54.0), (90.0, 54.0), (100.0, 53.0), (106.0, 50.2), (110.5, 44.5), (113.6, 36.0),\n+              (115.8, 24.0), (117.1, 12.0), (117.5, 0.0), (117.1, -12.0), (115.8, -24.0), (113.6, -36.0),\n+              (110.5, -44.5), (106.0, -50.2), (100.0, -53.0), (90.0, -54.0), (60.0, -54.0), (0.0, -54.0)]\n+GRIP_X = 59.4                                    # the grips: from |x| = GRIP_X out, PROUD in front of the middle\n+PROUD = 1.8\n+\n+SCREEN = dict(at=(0.5, 0.5), size=(75.0, 47.0))                 # the LCD: its middle (x, z), w x h\n+WINDOW = dict(at=(0.6, 0.6), size=(93.5, 68.0), corner=8.0, height=2.6)   # the window's cover, standing proud\n+LEFT_BUTTONS = dict(x=-55.6, z=[16.9, 1.0, -15.4], size=(7.6, 10.6), height=2.6)   # ON, OFF, BACKLIGHT\n+RIGHT_BUTTONS = dict(x=57.7, z=[17.1, 0.7, -15.4], size=(7.6, 10.6), height=2.6)   # OPTION 1, PAUSE, OPTION 2\n+DPAD = dict(at=(-90.9, 0.6), size=31.0, arm=10.6, height=3.6, well=1.4)\n+AB = dict(top=[(72.6, 36.9), (91.9, 41.8)], bottom=[(73.3, -35.6), (92.7, -41.0)], turn=(14.2, -15.6),   # B, A\n+          well=16.0, size=12.4, height=3.0)\n+LYNX = dict(at=(3.2, 41.9), width=47.0, height=5.6)\n+LIGHT = dict(at=(-49.0, 42.8), r=1.4)\n+ATARI = dict(at=(1.3, -39.7), height=3.6)\n+SPEAKER = dict(x=(84.0, 109.0), z=(-27.0, 27.0), count=6)\n+GROOVES = dict(x=(-71.5, -67.6), z=(-21.0, 21.0))\n+\n+# The game card: w x h x thickness; its seat in the slot at the back's top (its middle's y; its top flush with the\n+# body's top).\n+CARD = dict(size=(52.0, 65.0, 4.5), y=DEPTH - 4.0, lip=6.0)\n+\n+COLOURS = dict(case=\"#28282B\", key=\"#131315\", glass=\"#FFFFFF\", print=\"#FFFFFF\")\n+\n+ATLAS = 1024\n+SCREEN_PX = (320, 200)\n+REGIONS = dict(front=(0, 0, 1024, 472), title=(0, 480) + SCREEN_PX, game=(328, 480) + SCREEN_PX,\n+               game_flipped=(656, 480) + SCREEN_PX, card=(0, 688, 208, 260))\n+SWATCHES = dict(case=(240, 712), key=(272, 712), red=(304, 712), lcd=(336, 712), card=(368, 712))\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s"
        },
        {
          "sha": "50a212b01bf2d43d1a437e9ccb1057d02d8a4ef0",
          "filename": "tools/console-models/lynx_print.py",
          "status": "added",
          "additions": 203,
          "deletions": 0,
          "changes": 203,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Flynx_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Flynx_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Flynx_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,203 @@\n+\"\"\"The Atari Lynx II's printing and screens, for the console theme's Lynx model (tools/console-models/lynx.py).\n+\n+Draws its atlas, its regions as lynx_layout.py places them, each in millimetres as the model's planar UVs map it (the\n+screens in their pixels): the front from in front (the grips' grey and the glossy black middle, the window's surround\n+and the switched-off screen, LYNX in orange over it and ATARI under it, the power light, the buttons' names and their\n+brackets, the D-pad's well and the grooves beside it, the wells of A and B, the speaker's slots); the screens (the\n+title of a made-up game, Odyssey; a moment of its play; the same turned over, as FLIP shows it); the game card's label;\n+and the swatches small parts take their colour from. Run it with the system Python (Pillow):\n+\n+    python tools/console-models/lynx_print.py <out folder>\n+\n+It writes lynx_print.png there.\n+\"\"\"\n+\n+import math\n+import os\n+import random\n+import sys\n+\n+from PIL import Image, ImageDraw, ImageFont\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import lynx_layout as L  # noqa: E402\n+from atari5200_print import fuji  # noqa: E402\n+from psp_print import ship, space  # noqa: E402\n+from retro_pc_print import rgb, shade  # noqa: E402\n+from windows_pc_print import FONTS, Region  # noqa: E402\n+\n+GRIP = (40, 40, 43)\n+MIDDLE = (12, 12, 13)\n+ORANGE = (236, 128, 34)\n+LCD_OFF = (44, 46, 44)\n+\n+\n+def outline():\n+    right = L.RIGHT_HALF\n+    return right + [(-x, z) for x, z in reversed(right[1:-1])]\n+\n+\n+# ---- The front ---------------------------------------------------------------------------------------------------------\n+\n+def front():\n+    w, h = L.WIDTH, L.HEIGHT\n+    reg = Region(L.REGIONS[\"front\"], w, h, rgb(L.COLOURS[\"case\"]))\n+\n+    def at(x, z):\n+        return x + w / 2, h / 2 - z\n+    reg.poly([at(x, z) for x, z in outline()], GRIP)\n+    rng = random.Random(3)                                     # the grips' fine texture\n+    for _ in range(9000):\n+        x, z = rng.uniform(-w / 2, w / 2), rng.uniform(-h / 2, h / 2)\n+        reg.circle(*at(x, z), 0.12, shade(GRIP, rng.uniform(-0.12, 0.1)))\n+    g = L.GRIP_X\n+    reg.poly([at(-g, 55.0), at(g, 55.0), at(g + 1.0, 0.0), at(g, -55.0), at(-g, -55.0), at(-g - 1.0, 0.0)], MIDDLE)\n+    for side in (-1, 1):                                       # the grips' edges along the middle\n+        reg.line([at(side * g, 54.0), at(side * (g + 1.0), 0.0), at(side * g, -54.0)], (10, 10, 11), 0.5)\n+\n+    (wx, wz), (ww, wh) = L.WINDOW[\"at\"], L.WINDOW[\"size\"]\n+    reg.rect_(*at(wx - ww / 2, wz + wh / 2), *at(wx + ww / 2, wz - wh / 2), (8, 8, 9), r=L.WINDOW[\"corner\"])\n+    (sx, sz), (sw, sh) = L.SCREEN[\"at\"], L.SCREEN[\"size\"]\n+    reg.rect_(*at(sx - sw / 2, sz + sh / 2), *at(sx + sw / 2, sz - sh / 2), LCD_OFF)\n+\n+    lx, lz = L.LYNX[\"at\"]                                     # LYNX: heavy letters stretched wide, as its logo\n+    mask = Image.new(\"L\", (900, 200), 0)\n+    ImageDraw.Draw(mask).text((10, 100), \"LYNX\", font=ImageFont.truetype(FONTS + \"ariblk.ttf\", 150), fill=255,\n+                              anchor=\"lm\")\n+    mask = mask.crop(mask.getbbox())\n+    x0, y0, x1, y1 = reg.box(*at(lx - L.LYNX[\"width\"] / 2, lz + L.LYNX[\"height\"] / 2),\n+                             *at(lx + L.LYNX[\"width\"] / 2, lz - L.LYNX[\"height\"] / 2))\n+    mask = mask.resize((round(x1 - x0), round(y1 - y0)), Image.LANCZOS)\n+    reg.img.paste(Image.new(\"RGBA\", mask.size, ORANGE + (255,)), (round(x0), round(y0)), mask)\n+    reg.circle(*at(*L.LIGHT[\"at\"]), L.LIGHT[\"r\"], (70, 30, 24))\n+    reg.text(\"POWER\", \"arial.ttf\", 2.0, *at(L.LIGHT[\"at\"][0], L.LIGHT[\"at\"][1] - 3.2), ORANGE)\n+    ax, az = L.ATARI[\"at\"]\n+    fuji(reg, *at(ax - 8.0, az), L.ATARI[\"height\"], (190, 190, 194))\n+    reg.text(\"ATARI\", \"arial.ttf\", 3.8, *at(ax - 4.6, az), (190, 190, 194), anchor=\"lm\")\n+\n+    def bracket(x, z0, z1, side, label_z=None):\n+        \"\"\"A bracket from z0 to z1 beside the buttons at x (side 1: to their right).\"\"\"\n+        bx = x + side * 5.2\n+        reg.line([at(x + side * 3.6, z0), at(bx, z0), at(bx, z1), at(x + side * 3.6, z1)], ORANGE, 0.3)\n+    b = L.LEFT_BUTTONS\n+    bw, bh = b[\"size\"]\n+    for z in b[\"z\"]:\n+        reg.rect_(*at(b[\"x\"] - bw / 2 - 0.6, z + bh / 2 + 0.6), *at(b[\"x\"] + bw / 2 + 0.6, z - bh / 2 - 0.6), (4, 4, 5),\n+                  r=1.4)\n+    z_on, z_off, z_back = b[\"z\"]\n+    bracket(b[\"x\"], z_on + 3.0, z_off - 3.0, 1)\n+    reg.text(\"ON\", \"arial.ttf\", 2.2, *at(b[\"x\"] + 2.0, z_on + bh / 2 + 2.0), ORANGE)\n+    reg.text(\"POWER\", \"arial.ttf\", 2.0, *at(b[\"x\"], z_on - bh / 2 - 1.8), ORANGE)\n+    reg.text(\"OFF\", \"arial.ttf\", 2.2, *at(b[\"x\"] + 2.0, z_off - bh / 2 - 2.0), ORANGE)\n+    reg.text(\"BACKLIGHT\", \"arial.ttf\", 2.2, *at(b[\"x\"] + 4.0, z_back - bh / 2 - 2.2), ORANGE)\n+    bracket(b[\"x\"], z_back + 3.0, z_back - bh / 2 - 1.0, -1)\n+    r = L.RIGHT_BUTTONS\n+    for z in r[\"z\"]:\n+        reg.rect_(*at(r[\"x\"] - bw / 2 - 0.6, z + bh / 2 + 0.6), *at(r[\"x\"] + bw / 2 + 0.6, z - bh / 2 - 0.6), (4, 4, 5),\n+                  r=1.4)\n+    z1, z2, z3 = r[\"z\"]\n+    reg.text(\"OPTION 1\", \"arial.ttf\", 2.2, *at(r[\"x\"] - 1.5, z1 + bh / 2 + 2.0), ORANGE)\n+    reg.text(\"RESTART\", \"arial.ttf\", 2.2, *at(r[\"x\"] - 1.0, (z1 + z2) / 2), ORANGE)\n+    reg.text(\"FLIP\", \"arial.ttf\", 2.2, *at(r[\"x\"] - 1.0, (z2 + z3) / 2), ORANGE)\n+    reg.text(\"OPTION 2\", \"arial.ttf\", 2.2, *at(r[\"x\"] - 1.5, z3 - bh / 2 - 2.0), ORANGE)\n+    bracket(r[\"x\"], z1 + 2.0, z2 + 1.0, 1)\n+    bracket(r[\"x\"], z2 - 1.0, z3 - 2.0, -1)\n+    reg.text(\"II\", \"arialbd.ttf\", 1.8, *at(r[\"x\"] + 6.4, z2), (150, 150, 154))\n+\n+    (dx, dz), size, arm = L.DPAD[\"at\"], L.DPAD[\"size\"], L.DPAD[\"arm\"] + 2.0\n+    for a, b_ in (((size + 2.0) / 2, arm / 2), (arm / 2, (size + 2.0) / 2)):\n+        reg.rect_(*at(dx - a, dz + b_), *at(dx + a, dz - b_), (14, 14, 15), r=2.0)\n+    (gx0, gx1), (gz0, gz1) = L.GROOVES[\"x\"], L.GROOVES[\"z\"]\n+    for gx in (gx0, gx1):\n+        reg.line([at(gx + 1.2 * (1 - (z / gz1) ** 2), z) for z in [gz0 + (gz1 - gz0) * k / 12 for k in range(13)]],\n+                 (30, 30, 32), 0.45)\n+    for pair, turn in ((L.AB[\"top\"], L.AB[\"turn\"][0]), (L.AB[\"bottom\"], L.AB[\"turn\"][1])):\n+        for (x, z) in pair:\n+            c, s = math.cos(math.radians(turn)), math.sin(math.radians(turn))\n+            half = L.AB[\"well\"] / 2\n+            corners = [(x + c * u - s * v, z + s * u + c * v) for u, v in ((-half, -half), (half, -half), (half, half),\n+                                                                            (-half, half))]\n+            reg.poly([at(px, pz) for px, pz in corners], (14, 14, 15))\n+    (sx0, sx1), (sz0, sz1), n = L.SPEAKER[\"x\"], L.SPEAKER[\"z\"], L.SPEAKER[\"count\"]\n+    for k in range(n):\n+        x = sx0 + (sx1 - sx0) * k / (n - 1)\n+        span = (sz1 - sz0) * (0.62 + 0.38 * math.sin(math.pi * (k + 0.5) / n))\n+        for j in range(9):\n+            z0 = -span / 2 + span * j / 9\n+            z1_ = z0 + span / 9 * 0.62\n+            bow = 1.6 * (1 - ((z0 + z1_) / span) ** 2)\n+            reg.line([at(x - bow, z0), at(x - bow, z1_)], (14, 14, 15), 0.7)\n+    return reg\n+\n+\n+# ---- The screens -------------------------------------------------------------------------------------------------------\n+\n+W, H = 320, 200                                      # the Lynx's 160 x 102, doubled\n+\n+\n+def title():\n+    img = space((W, H)).convert(\"RGB\")\n+    d = ImageDraw.Draw(img)\n+    d.text((W / 2, 22), \"ODYSSEY\", font=ImageFont.truetype(FONTS + \"ariblk.ttf\", 40), fill=(255, 214, 70), anchor=\"mt\",\n+           stroke_width=2, stroke_fill=(120, 30, 10))\n+    ship(d, 90, 120, 2.0, \"#FFFFFF\", \"#FFB23C\")\n+    d.text((W / 2, 182), \"PRESS A\", font=ImageFont.truetype(FONTS + \"arialbd.ttf\", 16), fill=(255, 255, 255),\n+           anchor=\"mm\")\n+    return img\n+\n+\n+def game():\n+    img = space((W, H)).convert(\"RGB\")\n+    d = ImageDraw.Draw(img)\n+    rng = random.Random(11)\n+    for _ in range(30):\n+        x, y = rng.randrange(W), rng.randrange(H)\n+        d.rectangle([x, y, x + 1, y + 1], fill=(255, 255, 255))\n+    ship(d, 70, 110, 1.6, \"#FFFFFF\", \"#FFB23C\")\n+    d.line([(104, 110), (200, 110)], fill=(255, 236, 80), width=3)\n+    for ax, ay, r in ((236, 104, 24), (280, 160, 14), (210, 40, 11)):\n+        d.ellipse([ax - r, ay - r, ax + r, ay + r], fill=(140, 96, 70), outline=(90, 60, 44), width=2)\n+    font = ImageFont.truetype(FONTS + \"arialbd.ttf\", 14)\n+    d.text((8, 12), \"SCORE 001250\", font=font, fill=(255, 255, 255), anchor=\"lm\")\n+    d.text((W - 8, 12), \"x3\", font=font, fill=(255, 214, 70), anchor=\"rm\")\n+    return img\n+\n+\n+# ---- The card's label --------------------------------------------------------------------------------------------------\n+\n+def card():\n+    cw, ch, _ = L.CARD[\"size\"]\n+    reg = Region(L.REGIONS[\"card\"], cw, ch, rgb(\"#2A2A2D\"))\n+    reg.rect_(3.0, 9.0, cw - 3.0, ch - 3.0, (240, 238, 232), r=1.0)\n+    art = space((420, 460)).convert(\"RGB\")\n+    ship(ImageDraw.Draw(art), 140, 260, 3.0, \"#FFFFFF\", \"#FFB23C\")\n+    x0, y0, x1, y1 = reg.box(4.0, 18.0, cw - 4.0, ch - 4.0)\n+    reg.img.paste(art.resize((round(x1 - x0), round(y1 - y0)), Image.LANCZOS), (round(x0), round(y0)))\n+    reg.rect_(4.0, 10.0, cw - 4.0, 18.0, ORANGE)\n+    reg.text(\"ODYSSEY\", \"ariblk.ttf\", 5.6, cw / 2, 14.2, (30, 20, 10))\n+    for k in range(5):                                        # the grip's ribs along the top edge\n+        reg.rect_(cw / 2 - 12.0 + 6.0 * k, 2.0, cw / 2 - 10.0 + 6.0 * k, 7.0, (20, 20, 22))\n+    return reg\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), rgb(L.COLOURS[\"case\"]))\n+    for reg in (front(), card()):\n+        reg.paste(image)\n+    flipped = game().rotate(180)\n+    for name, picture in ((\"title\", title()), (\"game\", game()), (\"game_flipped\", flipped)):\n+        x, y, w, h = L.REGIONS[name]\n+        image.paste(picture.resize((w, h), Image.LANCZOS), (x, y))\n+    d = ImageDraw.Draw(image)\n+    for name, colour in ((\"case\", rgb(L.COLOURS[\"case\"])), (\"key\", rgb(L.COLOURS[\"key\"])), (\"red\", (255, 60, 40)),\n+                         (\"lcd\", LCD_OFF), (\"card\", rgb(\"#2A2A2D\"))):\n+        x, y = L.SWATCHES[name]\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colour)\n+    path = os.path.join(out, \"lynx_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "815bdb6c335d9b2dee6f09af7652029be6c46e77",
          "filename": "tools/console-models/macintosh.py",
          "status": "added",
          "additions": 352,
          "deletions": 0,
          "changes": 352,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmacintosh.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmacintosh.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fmacintosh.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,352 @@\n+\"\"\"The console theme's Macintosh system model (godot/themes/console/models/systems/macintosh.glb): the original Apple\n+Macintosh, its keyboard and its mouse.\n+\n+Built in Blender (macintosh_layout.py has the measurements, taken from its housing's design patent and photographs). The\n+case is its side outline extruded across its width; the screen's sunk frame, the floppy slot and its finger recess are\n+cut into the front face in the face's frame, the handle into the back of the top, the vents into the sides' foot. The\n+tube's faces are curved screens sitting in the frame; the rainbow Apple a badge on the face. The keyboard is a slab, its\n+keys standing on its sloping top, its coiled cord sagging between the modular plugs in its back and the chin's front;\n+the mouse a rounded box, its button a plate across the front of its top, its cable running from its strain relief round\n+the case's right to its 9-pin plug in the back (the plugs are plugs.py's). The printing and the screens are one texture\n+(macintosh_print.py's), projected from above onto the keys, the mouse and the floppy, and onto the badge and the screens\n+along the face.\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: the right end towards the viewer, the\n+top tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m) holding the set; the set\n+holding the floppy (waiting in front of the slot, shrunk to nothing), the tube's four faces (each in its frame, those\n+not shown a hair behind the one shown), the mouse and its button.\n+\n+The `focused` clip (8 s, looping): the floppy slides into the drive; the question mark gives way to the happy Mac, then\n+Welcome to Macintosh., then the game in its window, the mouse moving and clicking; then the floppy comes out of the\n+drive and goes, and the question mark is back.\n+\n+Four materials (case, keys, screen, floppy) and one 1024\u00b2 texture, within the system model budget. Run from the repo\n+root:\n+\n+    python tools/console-models/macintosh_print.py artifacts/macintosh\n+    blender -b --factory-startup --python tools/console-models/macintosh.py -- artifacts/macintosh godot/themes/console/models/systems/macintosh.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bmesh\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import macintosh_layout as L  # noqa: E402\n+import plugs  # noqa: E402\n+from amiga import curved, paint, rounded, solid  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, key, phase, smooth  # noqa: E402\n+from mega_drive_cartridge import (bevel, boolean, box, export, join, material, new_object, prism_x,  # noqa: E402\n+                                  triangles)\n+\n+TILT_YAW = -24.0     # degrees about the vertical: the right end comes towards the viewer\n+TILT_PITCH = 16.0    # degrees about the horizontal: the top tips up towards the viewer\n+ROUGHNESS = dict(case=0.55, keys=0.45, screen=0.3, floppy=0.4)\n+OUT = Vector(L.face_point(0.0, 0.0, 1.0)) - Vector(L.face_point(0.0, 0.0, 0.0))     # out of the front face\n+UP = Vector(L.face_point(0.0, 1.0, 0.0)) - Vector(L.face_point(0.0, 0.0, 0.0))      # up it\n+BEHIND = 1.5              # how far back the tube's faces not shown wait\n+SCREENS = (\"disk\", \"happy\", \"welcome\", \"game\")\n+\n+\n+def atlas_uv(region, w, h):\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+def swatch(name):\n+    x, y = L.SWATCHES[name]\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+def colour(obj, mat, name):\n+    paint(obj, mat, swatch(name), lambda c, n, kind: True)\n+    return obj\n+\n+\n+def on_face(name, outline, w0, w1, mat, outline1=None):\n+    \"\"\"A solid from a (u, v) outline in the front face's frame, from w0 out of it to w1 (its outline there outline1).\"\"\"\n+    top = outline1 or outline\n+    return solid(name, [[L.face_point(u, v, w0) for u, v in outline], [L.face_point(u, v, w1) for u, v in top]], mat)\n+\n+\n+def rect(u0, u1, v0, v1):\n+    return [(u0, v0), (u1, v0), (u1, v1), (u0, v1)]\n+\n+\n+def face_frame(c):\n+    \"\"\"A point's (u, v, w) in the front face's frame.\"\"\"\n+    d = c - Vector(L.face_point(0.0, 0.0, 0.0))\n+    return c.x, d.dot(UP), d.dot(OUT)\n+\n+\n+# ---- The case --------------------------------------------------------------------------------------------------------\n+\n+def case(m):\n+    \"\"\"The case with the screen's frame, the slot, its finger recess, the handle and the vents cut in; painted.\"\"\"\n+    half = L.WIDTH / 2\n+    body = prism_x(\"macintosh\", L.PROFILE, -half, half, m[\"case\"])\n+    bevel(body, 4.0, segments=3, angle=30)\n+    (fw, fh, fr), (gw, gh, gr), sv, depth = L.SCREEN[\"frame\"], L.SCREEN[\"glass\"], L.SCREEN[\"v\"], L.SCREEN[\"depth\"]\n+    rim = rounded(0.0, sv, fw, fh, fr, 4)\n+    tube = rounded(0.0, sv, gw, gh, gr, 4)\n+    boolean(body, solid(\"frame\", [[L.face_point(u, v, w) for u, v in ring] for ring, w in\n+                                  ((tube, -depth), (rim, 0.0), (rim, 8.0))], m[\"case\"]))\n+    (u0, u1), (v0, v1) = L.SLOT[\"u\"], L.SLOT[\"v\"]\n+    boolean(body, on_face(\"slot\", rect(u0, u1, v0, v1), -L.SLOT[\"depth\"], 5.0, m[\"case\"]))\n+    (nu0, nu1), (nv0, nv1) = L.NOTCH[\"u\"], L.NOTCH[\"v\"]\n+    notch = rounded((nu0 + nu1) / 2, (nv0 + nv1) / 2, nu1 - nu0, nv1 - nv0, L.NOTCH[\"r\"], 3)\n+    boolean(body, on_face(\"notch\", notch, -L.NOTCH[\"depth\"], 5.0, m[\"case\"]))\n+    hx, hy, hz = L.HANDLE[\"x\"], L.HANDLE[\"y\"], L.HANDLE[\"z\"]\n+    boolean(body, box(\"handle\", -hx, hx, hy, L.DEPTH + 10.0, hz, L.HEIGHT + 10.0, m[\"case\"]))\n+    (vy0, vy1), vh, vd = L.VENTS[\"y\"], L.VENTS[\"height\"], L.VENTS[\"depth\"]\n+    for z in L.VENTS[\"z\"]:\n+        for side in (-1, 1):\n+            boolean(body, box(\"vent\", side * half - vd, side * half + vd, vy0, vy1, z - vh / 2, z + vh / 2, m[\"case\"]))\n+    colour(body, m[\"case\"], \"case\")\n+    paint(body, m[\"case\"], swatch(\"tube\"), lambda c, n, kind: -depth - 0.5 < face_frame(c)[2] < -depth + 0.5\n+          and abs(c.x) < gw / 2 + 1.0 and abs(face_frame(c)[1] - sv) < gh / 2 + 1.0)\n+    paint(body, m[\"case\"], swatch(\"dark\"), lambda c, n, kind: u0 - 0.5 < c.x < u1 + 0.5\n+          and v0 - 0.5 < face_frame(c)[1] < v1 + 0.5 and face_frame(c)[2] < -L.NOTCH[\"depth\"] - 0.5)\n+    paint(body, m[\"case\"], swatch(\"dark\"), lambda c, n, kind: abs(abs(c.x) - half) < vd + 0.3 and vy0 - 0.5 < c.y\n+          < vy1 + 0.5 and any(abs(c.z - z) < vh / 2 + 0.3 for z in L.VENTS[\"z\"]) and abs(n.x) < 0.9)\n+    (au0, au1), (av0, av1) = L.APPLE[\"u\"], L.APPLE[\"v\"]\n+    badge = on_face(\"apple\", rect(au0, au1, av0, av1), -0.5, 0.5, m[\"case\"])\n+    colour(badge, m[\"case\"], \"case\")\n+    face = atlas_uv(L.REGIONS[\"apple\"], au1 - au0, av1 - av0)\n+    paint(badge, m[\"case\"], lambda co: face(co.x - au0, av1 - face_frame(co)[1]),\n+          lambda c, n, kind: n.dot(OUT) > 0.9)\n+    return join([body, badge], \"macintosh\")\n+\n+\n+def screen(m, name):\n+    \"\"\"One of the tube's faces, its middle at its origin, facing out of the front face.\"\"\"\n+    gw, gh, _ = L.SCREEN[\"glass\"]\n+    rx, ry, rw, rh = L.REGIONS[name]\n+    part = curved(name, gw, gh, L.SCREEN[\"bulge\"], lambda a, b: ((rx + a * rw) / L.ATLAS, 1 - (ry + b * rh) / L.ATLAS),\n+                  m[\"screen\"], 16, 12)\n+    part.data.transform(Matrix.Rotation(-L.LEAN, 4, \"X\"))\n+    return part\n+\n+\n+# ---- The keyboard, the mouse, the floppy, the cords ------------------------------------------------------------------\n+\n+def keyboard(m):\n+    \"\"\"The keyboard: the slab and its keys, at its place.\"\"\"\n+    w, d = L.KEYBOARD[\"size\"]\n+    hf, hb = L.KEYBOARD[\"height\"]\n+    slope = math.atan2(hb - hf, d)\n+    c, k = math.cos(slope), math.sin(slope)\n+\n+    def deck(s, h):\n+        return s * c - h * k, hf + s * k + h * c\n+    slab = prism_x(\"keyboard\", [(0.0, 0.0), (d, 0.0), (d, hb), (0.0, hf)], -w / 2, w / 2, m[\"case\"])\n+    bevel(slab, 3.0, segments=3, angle=30)\n+    colour(slab, m[\"case\"], \"case\")\n+    face = atlas_uv(L.REGIONS[\"keys\"], w, d)\n+    parts, inset = [slab], L.KEY[\"inset\"]\n+    for legend, x0, x1, s in L.keys():\n+        foot = [(x0 + 0.5, s - 9.0), (x1 - 0.5, s - 9.0), (x1 - 0.5, s + 9.0), (x0 + 0.5, s + 9.0)]\n+        cap = [(x0 + inset, s - 9.5 + inset), (x1 - inset, s - 9.5 + inset), (x1 - inset, s + 9.5 - inset),\n+               (x0 + inset, s + 9.5 - inset)]\n+        part = solid(\"key\", [[(x, *deck(v, -1.0)) for x, v in foot], [(x, *deck(v, L.KEY[\"height\"])) for x, v in cap]],\n+                     m[\"keys\"])\n+        colour(part, m[\"keys\"], \"key\")\n+        paint(part, m[\"keys\"], lambda co: face(co.x + w / 2, d - co.y), lambda c_, n, kind: n.z > 0.9)\n+        parts.append(part)\n+    whole = join(parts, \"keyboard\")\n+    x, y = L.KEYBOARD[\"at\"]\n+    whole.data.transform(Matrix.Translation((x, y - d, 0.0)))\n+    return whole\n+\n+\n+def mouse(m):\n+    \"\"\"The mouse and its button, each its foot's middle at its origin, its front (the button, the cable) to +y; and\n+    where its middle sits.\"\"\"\n+    w, length, h = L.MOUSE[\"size\"]\n+    body = solid(\"mouse\", [[(x, y, 0.0) for x, y in rounded(0.0, 0.0, w, length, 6.0, 3)],\n+                           [(x, y, h) for x, y in rounded(0.0, 0.0, w - 6.0, length - 6.0, 4.0, 3)]], m[\"case\"])\n+    bevel(body, 2.5, segments=3)\n+    colour(body, m[\"case\"], \"case\")\n+    face = atlas_uv(L.REGIONS[\"mouse\"], w, length)\n+    paint(body, m[\"case\"], lambda co: face(co.x + w / 2, length / 2 - co.y), lambda c, n, kind: n.z > 0.9)\n+    span = length * L.MOUSE[\"button\"]\n+    button = box(\"button\", -w / 2 + 6.0, w / 2 - 6.0, length / 2 - span, length / 2 - 4.0, h - 1.0, h + 0.8, m[\"case\"])\n+    bevel(button, 0.6, segments=1)\n+    colour(button, m[\"case\"], \"case\")\n+    paint(button, m[\"case\"], lambda co: face(co.x + w / 2, length / 2 - co.y), lambda c, n, kind: n.z > 0.9)\n+    x, y, turn = L.MOUSE[\"at\"]\n+    for part in (body, button):\n+        part.data.transform(Matrix.Rotation(math.radians(turn), 4, \"Z\"))\n+    return body, button, Vector((x, y, 0.0))\n+\n+\n+def floppy(m):\n+    \"\"\"The floppy, its back edge's middle at its origin, lying in the slot's plane, its shutter going in first.\"\"\"\n+    w, d, t = L.FLOPPY[\"size\"]\n+    part = box(\"floppy\", -w / 2, w / 2, 0.0, d, -t / 2, t / 2, m[\"floppy\"])\n+    colour(part, m[\"floppy\"], \"floppy\")\n+    top = atlas_uv(L.REGIONS[\"floppy\"], w, d)\n+    paint(part, m[\"floppy\"], lambda co: top(co.x + w / 2, d - co.y), lambda c, n, kind: n.z > 0.9)\n+    frame = Matrix(((1.0, 0.0, 0.0, 0.0), (0.0, -OUT.y, UP.y, 0.0), (0.0, -OUT.z, UP.z, 0.0), (0.0, 0.0, 0.0, 1.0)))\n+    part.data.transform(frame)                           # +y going in (against the face), +z up the face\n+    return part\n+\n+\n+def tube(name, m, path, radius, tone):\n+    \"\"\"A cable: a tube along a path of points.\"\"\"\n+    bm = bmesh.new()\n+    rings, normal = [], Vector((0.0, 0.0, 1.0))\n+    n = len(path)\n+    for k, p in enumerate(path):\n+        tangent = (path[min(k + 1, n - 1)] - path[max(k - 1, 0)]).normalized()\n+        normal = (normal - tangent * normal.dot(tangent))\n+        if normal.length < 1e-6:\n+            normal = Vector((1.0, 0.0, 0.0))\n+        normal.normalize()\n+        binormal = tangent.cross(normal)\n+        rings.append([bm.verts.new(p + (normal * math.cos(a) + binormal * math.sin(a)) * radius)\n+                      for a in [2 * math.pi * j / 6 for j in range(6)]])\n+    for a, b in zip(rings, rings[1:]):\n+        for j in range(6):\n+            bm.faces.new([a[j], a[(j + 1) % 6], b[(j + 1) % 6], b[j]])\n+    bmesh.ops.recalc_face_normals(bm, faces=bm.faces)\n+    return colour(new_object(name, bm, m[\"case\"]), m[\"case\"], tone)\n+\n+\n+def bezier(points, count):\n+    p0, p1, p2, p3 = (Vector(p) for p in points)\n+    return [p0 * (1 - t) ** 3 + p1 * 3 * t * (1 - t) ** 2 + p2 * 3 * t * t * (1 - t) + p3 * t ** 3\n+            for t in [k / (count - 1) for k in range(count)]]\n+\n+\n+def coiled(points, turns, radius):\n+    \"\"\"A coiled cord's path: a helix wound round the B\u00e9zier curve through four control points.\"\"\"\n+    centre = bezier(points, turns * 10 + 1)\n+    out, normal = [], Vector((0.0, 0.0, 1.0))\n+    for k, p in enumerate(centre):\n+        tangent = (centre[min(k + 1, len(centre) - 1)] - centre[max(k - 1, 0)]).normalized()\n+        normal = (normal - tangent * normal.dot(tangent)).normalized()\n+        binormal = tangent.cross(normal)\n+        a = 2 * math.pi * k / 10\n+        fade = min(1.0, k / 10, (len(centre) - 1 - k) / 10)     # straight where it plugs in\n+        out.append(p + (normal * math.cos(a) + binormal * math.sin(a)) * radius * fade)\n+    return out\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+def tap(f, f0, hold=2):\n+    return smooth(phase(f, f0, f0 + 3)) * (1 - smooth(phase(f, f0 + 3 + hold, f0 + 7 + hold)))\n+\n+\n+def shown(f):\n+    if f < 36 or f >= 178:\n+        return \"disk\"\n+    if f < 64:\n+        return \"happy\"\n+    if f < 92:\n+        return \"welcome\"\n+    return \"game\"\n+\n+\n+def timeline(f):\n+    far = L.FLOPPY[\"size\"][1] + L.FLOPPY[\"out\"]\n+    if f < 10 or f >= 198:\n+        out, scale = far, HIDDEN\n+    elif f < 16:\n+        out, scale = far, max(HIDDEN, back_out(phase(f, 10, 16)))\n+    elif f < 176:\n+        out, scale = far * (1 - smooth(phase(f, 16, 32))), 1.0\n+    elif f < 186:\n+        out, scale = 40.0 * smooth(phase(f, 176, 186)), 1.0\n+    else:\n+        out, scale = 40.0 + (far - 40.0) * smooth(phase(f, 186, 196)), max(HIDDEN, 1 - ease_in(phase(f, 192, 198)))\n+    d = OUT * out\n+    poses = {\"floppy\": Pose(x=d.x, y=d.y, z=d.z, scale=scale)}\n+    behind = -OUT * BEHIND\n+    for name in SCREENS:\n+        poses[name] = Pose() if name == shown(f) else Pose(x=behind.x, y=behind.y, z=behind.z)\n+    t = phase(f, 96, 170)\n+    k = math.sin(math.pi * t)\n+    mx, my = 14.0 * k * math.sin(2 * math.pi * 1.5 * t), 9.0 * k * math.sin(2 * math.pi * 2.5 * t)\n+    poses[\"mouse\"] = Pose(x=mx, y=my)\n+    poses[\"button\"] = Pose(x=mx, y=my, z=-0.8 * max(tap(f, f0, 1) for f0 in (112, 134, 154)))\n+    return poses\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    atlas = bpy.data.images.load(os.path.join(images, \"macintosh_print.png\"))\n+    m = {name: material(name, L.COLOURS[\"print\"], r, atlas) for name, r in ROUGHNESS.items()}\n+\n+    body, button, at = mouse(m)\n+    kw, kd = L.KEYBOARD[\"size\"]\n+    kx, ky = L.KEYBOARD[\"at\"]\n+    jx, jz = L.JACK\n+    chin = L.PROFILE[2][0]\n+    beige = (lambda o: colour(o, m[\"case\"], \"cord\"))\n+    k_plug, k_end, k_back = plugs.place(*plugs.modular(beige, beige), m[\"case\"], (kx + 80.0, ky, 20.0), (0.0, 1.0, 0.0))\n+    j_plug, j_end, j_back = plugs.place(*plugs.modular(beige, beige), m[\"case\"], (jx, chin, jz), (0.0, -1.0, 0.0))\n+    cord = tube(\"keyboard_cord\", m, coiled([k_end, k_end + k_back * 20.0 + Vector((0.0, 0.0, -12.0)),\n+                                            j_end + j_back * 30.0 + Vector((0.0, 0.0, -14.0)), j_end], 9, 3.2), 1.2,\n+                \"cord\")\n+    turn = math.radians(L.MOUSE[\"at\"][2])\n+    ahead = Vector((-math.sin(turn), math.cos(turn), 0.0))\n+    front = at + ahead * (L.MOUSE[\"size\"][1] / 2 - 1.0) + Vector((0.0, 0.0, 12.0))\n+    relief, start, leaving = plugs.place(*plugs.grommet(beige, 3.2), m[\"case\"], front, ahead)\n+    port = Vector((L.WIDTH / 2 - 30.0, L.DEPTH, 20.0))\n+    m_plug, m_end, m_back = plugs.place(*plugs.dsub(beige, beige, 13.0), m[\"case\"], port, (0.0, 1.0, 0.0))\n+    cable = tube(\"mouse_cable\", m, bezier([start, start + leaving * 40.0 + Vector((0.0, 0.0, -9.0)),\n+                                           (L.WIDTH / 2 + 40.0, L.DEPTH + 60.0, 2.4), m_end], 30), 1.6, \"cord\")\n+    whole = join([case(m), keyboard(m), cord, cable] + k_plug + j_plug + relief + m_plug, \"macintosh\")\n+    for p in whole.data.polygons:\n+        p.use_smooth = p.area < 400.0                     # the big flat faces flat: no streaks across the sides\n+    whole.data.set_sharp_from_angle(angle=math.radians(35))\n+\n+    root = bpy.data.objects.new(\"macintosh_root\", None)\n+    scene.collection.objects.link(root)\n+    whole.parent = root\n+    (u0, u1), (v0, v1) = L.SLOT[\"u\"], L.SLOT[\"v\"]\n+    glass = L.face_point(0.0, L.SCREEN[\"v\"], -L.SCREEN[\"depth\"] + 0.6)\n+    parts = {\"floppy\": (floppy(m), Vector(L.face_point((u0 + u1) / 2, (v0 + v1) / 2, -2.0))), \"mouse\": (body, at),\n+             \"button\": (button, at)}\n+    for name in SCREENS:\n+        parts[name] = (screen(m, name), Vector(glass))\n+    nodes = {}\n+    for name, (part, where) in parts.items():\n+        if name not in SCREENS:\n+            for p in part.data.polygons:\n+                p.use_smooth = True\n+            part.data.set_sharp_from_angle(angle=math.radians(35))\n+        part.parent = whole\n+        part.location = where\n+        nodes[name] = part\n+\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    points = [tilt @ v.co for v in whole.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [whole] + list(nodes.values())\n+    print(f\"Macintosh: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(whole.data)} the set), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "ae174dc042a742a3584dc93ccf26a1d149af0049",
          "filename": "tools/console-models/macintosh_layout.py",
          "status": "added",
          "additions": 88,
          "deletions": 0,
          "changes": 88,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmacintosh_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmacintosh_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fmacintosh_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,88 @@\n+\"\"\"The Macintosh card's measurements, shared by macintosh_print.py (which draws its printing and screens) and\n+macintosh.py (which builds it). Plain Python: Blender's Python imports it too.\n+\n+The original Apple Macintosh (1984), 244 mm wide, 281 deep and 345 high (13.6 x 9.6 x 10.9 inches), its keyboard (340 x\n+149, LACMA's figures for one) and mouse (98 x 64 x 38) in front of it, a 3.5-inch floppy with a made-up game, Odyssey,\n+going into its drive. Millimetres: x from its middle (left to right), y back from the front face's foot, z up from the\n+table. Measured from the drawings of its housing's design patent (US D285,687: from the front, the side, above and\n+behind) and from photographs on Wikimedia Commons.\n+\n+The case: its side outline (PROFILE: the front face leaning back, the top rising a little towards it, the chamfer at the\n+back's top, the chin set back under the front face), the handle cut into the back of its top, the vents in its sides'\n+foot. In the front face (FACE: its own frame, u across, v up it, w out of it): the screen's sunk frame and the tube's\n+face in it (SCREEN), the floppy slot (SLOT) and the finger recess at its right end, the rainbow Apple. The keyboard\n+(KEYBOARD) is a slab, its keys standing on its sloping top; the mouse (MOUSE) a box, its button across the front of its\n+top. The keyboard's coiled cord runs to the chin's front, the mouse's cable round the right to the back.\n+\n+One texture (ATLAS square, REGIONS): `keys` (the keyboard from above: the keys' tops and legends), the screens (`disk`\n+the question mark asking for a disk, `happy` the happy Mac, `welcome` Welcome to Macintosh., `game` the game in its\n+window), `floppy` (the floppy's top: its shutter and label), `mouse` (the mouse from above), `apple` (the rainbow Apple)\n+and swatches. A region is (x, y, width, height) in pixels.\n+\"\"\"\n+\n+import math\n+\n+ATLAS = 1024\n+U = 19.05\n+\n+WIDTH, DEPTH, HEIGHT = 244.0, 280.7, 345.0\n+LEAN = math.radians(7.9)          # the front face's lean back from the vertical\n+FOOT = 69.7                       # the front face's foot (z), where the chin starts under it\n+PROFILE = [                       # the side outline (y, z), round from the front face's foot\n+    (0.0, FOOT), (28.0, 66.3), (37.0, 60.0), (37.0, 0.0), (DEPTH, 0.0), (DEPTH, 280.7), (256.8, 328.4),\n+    (42.0, HEIGHT - 2.0), (37.8, 342.0),\n+]\n+HANDLE = dict(x=45.0, y=175.0, z=318.0)          # the handle's half width; where it starts (y); its floor\n+VENTS = dict(y=(99.5, 258.8), z=[38.0, 44.5, 51.0, 57.5, 64.0], height=2.6, depth=1.6)   # in the sides\n+\n+SCREEN = dict(frame=(200.0, 161.5, 12.0), glass=(184.4, 149.1, 8.0), v=172.6, depth=14.0, bulge=4.0)   # w, h, r\n+SLOT = dict(u=(-39.5, 60.8), v=(36.6, 43.4), depth=100.0)\n+NOTCH = dict(u=(58.1, 101.4), v=(20.2, 64.0), depth=5.0, r=4.0)          # the finger recess at the slot's end\n+APPLE = dict(u=(-100.3, -84.0), v=(17.6, 35.5))\n+JACK = (79.0, 27.0)               # the keyboard's jack in the chin's front (x, z)\n+\n+KEYBOARD = dict(size=(340.0, 149.0), height=(32.0, 46.0), at=(-14.0, -36.0))   # w, d; front and back; its back edge\n+KEYS_X0, ROW1 = -142.9, 116.0     # in the keyboard's frame: x from its middle, v from its front edge\n+KEY = dict(height=8.0, inset=2.2)\n+ROWS = [\n+    (0.0, [(c, 1.0) for c in \"`1234567890-=\"] + [(\"Backspace\", 2.0)]),\n+    (0.0, [(\"Tab\", 1.5)] + [(c, 1.0) for c in \"QWERTYUIOP[]\"] + [(\"\\\\\", 1.5)]),\n+    (0.0, [(\"Caps Lock\", 1.75)] + [(c, 1.0) for c in \"ASDFGHJKL;'\"] + [(\"Return\", 2.25)]),\n+    (0.0, [(\"Shift\", 2.25)] + [(c, 1.0) for c in \"ZXCVBNM,./\"] + [(\"Shift\", 2.75)]),\n+    (0.0, [(\"Option\", 1.5), (\"\u2318\", 1.5), (\" \", 9.0), (\"Enter\", 1.5), (\"Option\", 1.5)]),\n+]\n+\n+# The mouse: w, length, h; how much of its length the button takes; where its middle sits (x, y) and its turn.\n+MOUSE = dict(size=(64.0, 98.0, 38.0), button=0.42, at=(196.0, -92.0, -6.0))\n+# The floppy: w, d, t; the shutter's width and depth; how far out of the slot it waits.\n+FLOPPY = dict(size=(90.0, 94.0, 3.3), shutter=(46.0, 30.0), out=70.0)\n+\n+COLOURS = dict(case=\"#D6CEBA\", key=\"#B9B19C\", print=\"#FFFFFF\")\n+\n+REGIONS = dict(keys=(0, 0, 680, 298), disk=(0, 306, 256, 206), happy=(264, 306, 256, 206),\n+               welcome=(528, 306, 256, 206), game=(0, 520, 256, 206), floppy=(264, 520, 180, 188),\n+               mouse=(452, 520, 128, 196), apple=(588, 520, 48, 54))\n+SWATCHES = dict(case=(912, 760), key=(944, 760), dark=(976, 760), tube=(1008, 760), floppy=(912, 792),\n+                shutter=(944, 792), cord=(976, 792), legend=(1008, 792))\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s\n+\n+\n+def face_point(u, v, w):\n+    \"\"\"The model's point u across the front face from its middle, v up it from its foot and w out of it.\"\"\"\n+    s, c = math.sin(LEAN), math.cos(LEAN)\n+    return u, v * s - w * c, FOOT + v * c + w * s\n+\n+\n+def keys():\n+    \"\"\"Every key: (legend, x0, x1, v), v its row's middle in the keyboard's frame; the rows from the back to the\n+    front.\"\"\"\n+    out = []\n+    for r, (offset, row) in enumerate(ROWS):\n+        x = KEYS_X0 + offset * U\n+        v = ROW1 - r * U\n+        for legend, width in row:\n+            out.append((legend, x, x + width * U, v))\n+            x += width * U\n+    return out"
        },
        {
          "sha": "ccd56a5ed196900628d2447801be853d62e27b78",
          "filename": "tools/console-models/macintosh_print.py",
          "status": "added",
          "additions": 255,
          "deletions": 0,
          "changes": 255,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmacintosh_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmacintosh_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fmacintosh_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,255 @@\n+\"\"\"The Macintosh card's printing and screens (macintosh_print.png), for the console theme's model (macintosh.py).\n+\n+One image, laid out as macintosh_layout.REGIONS says: the keyboard from above (the keys' tops and legends), the four\n+screens (drawn at the Macintosh's 512 x 342, one bit deep, then set into the tube's face: the disk with its blinking\n+question mark, the happy Mac, Welcome to Macintosh., and a made-up game, Odyssey, in its window), the floppy's top (its\n+shutter and label), the mouse from above (its button), the rainbow Apple, and swatches. Run it with the system Python\n+(Pillow):\n+\n+    python tools/console-models/macintosh_print.py <out folder>\n+\"\"\"\n+\n+import math\n+import os\n+import random\n+import sys\n+\n+from PIL import Image, ImageDraw, ImageFont\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import macintosh_layout as L  # noqa: E402\n+from psp_print import ship, space  # noqa: E402\n+from retro_pc_print import rgb  # noqa: E402\n+from windows_pc_print import FONTS, Region  # noqa: E402\n+\n+CASE = rgb(L.COLOURS[\"case\"])\n+KEY = rgb(L.COLOURS[\"key\"])\n+LEGEND = (70, 66, 60)\n+RAINBOW = [(97, 187, 70), (253, 184, 39), (245, 130, 31), (224, 58, 62), (150, 61, 151), (0, 157, 220)]\n+W, H = 512, 342                  # the Macintosh's screen\n+TUBE = (22, 26, 24)\n+\n+\n+# ---- The screens -----------------------------------------------------------------------------------------------------\n+\n+def grey(img, box=None):\n+    \"\"\"The desktop's grey: every other pixel black, in a checkerboard.\"\"\"\n+    x0, y0, x1, y1 = box or (0, 0, img.width, img.height)\n+    px = img.load()\n+    for y in range(y0, y1):\n+        for x in range(x0, x1):\n+            px[x, y] = 0 if (x + y) % 2 else 1\n+\n+\n+def font(size, bold=True):\n+    return ImageFont.truetype(FONTS + (\"arialbd.ttf\" if bold else \"arial.ttf\"), size)\n+\n+\n+def mac_icon(d, x, y, k):\n+    \"\"\"The happy Mac, k pixels to its pixel, its top left at (x, y): the case, the screen with its smile.\"\"\"\n+    def r(x0, y0, x1, y1, fill):\n+        d.rectangle([x + x0 * k, y + y0 * k, x + x1 * k - 1, y + y1 * k - 1], fill=fill)\n+    r(0, 0, 22, 30, 0)\n+    r(1, 1, 21, 29, 1)\n+    r(3, 3, 19, 17, 0)\n+    r(4, 4, 18, 16, 1)\n+    for ex in (7, 14):                                      # the eyes\n+        r(ex, 7, ex + 1, 10, 0)\n+    r(11, 8, 12, 12, 0)                                     # the nose\n+    r(10, 11, 11, 12, 0)\n+    r(7, 13, 8, 14, 0)                                      # the smile\n+    r(8, 14, 14, 15, 0)\n+    r(14, 13, 15, 14, 0)\n+    r(4, 22, 9, 23, 0)                                      # the drive\n+    r(13, 22, 19, 23, 0)\n+    r(0, 30, 22, 33, 0)                                     # the foot\n+    r(1, 30, 21, 32, 1)\n+\n+\n+def disk_icon(d, x, y, k):\n+    \"\"\"The floppy with its question mark, k pixels to its pixel, its top left at (x, y).\"\"\"\n+    def r(x0, y0, x1, y1, fill):\n+        d.rectangle([x + x0 * k, y + y0 * k, x + x1 * k - 1, y + y1 * k - 1], fill=fill)\n+    r(0, 0, 30, 30, 0)\n+    r(1, 1, 29, 29, 1)\n+    r(26, 0, 30, 4, 1)                                      # the cut corner\n+    r(7, 1, 22, 11, 0)                                      # the shutter\n+    r(8, 2, 21, 10, 1)\n+    r(16, 3, 19, 9, 0)\n+    r(5, 15, 25, 29, 0)                                     # the label\n+    r(6, 16, 24, 29, 1)\n+    d.text((x + 15 * k, y + 22 * k), \"?\", font=font(10 * k), fill=0, anchor=\"mm\")\n+\n+\n+def screen_disk():\n+    img = Image.new(\"1\", (W, H), 1)\n+    grey(img)\n+    disk_icon(ImageDraw.Draw(img), W // 2 - 60, H // 2 - 60, 4)        # four times its size, to read on a card\n+    return img\n+\n+\n+def screen_happy():\n+    img = Image.new(\"1\", (W, H), 1)\n+    grey(img)\n+    mac_icon(ImageDraw.Draw(img), W // 2 - 44, H // 2 - 66, 4)\n+    return img\n+\n+\n+def screen_welcome():\n+    img = Image.new(\"1\", (W, H), 1)\n+    grey(img)\n+    d = ImageDraw.Draw(img)\n+    d.rounded_rectangle([W // 2 - 130, H // 2 - 34, W // 2 + 130, H // 2 + 34], 8, fill=1, outline=0, width=2)\n+    d.rounded_rectangle([W // 2 - 125, H // 2 - 29, W // 2 + 125, H // 2 + 29], 6, outline=0, width=1)\n+    d.text((W // 2, H // 2), \"Welcome to Macintosh.\", font=font(19), fill=0, anchor=\"mm\")\n+    return img\n+\n+\n+def screen_game():\n+    \"\"\"The game in its window: the menu bar, the title bar's stripes, space with the ship and the ringed planet.\"\"\"\n+    img = Image.new(\"1\", (W, H), 1)\n+    grey(img)\n+    d = ImageDraw.Draw(img)\n+    d.rectangle([0, 0, W - 1, 19], fill=1)\n+    d.line([0, 19, W - 1, 19], fill=0)\n+    d.ellipse([14, 4, 24, 15], fill=0)                      # the Apple\n+    d.rectangle([18, 1, 20, 4], fill=0)\n+    for x, word in ((44, \"File\"), (90, \"Edit\"), (138, \"Game\")):\n+        d.text((x, 10), word, font=font(13), fill=0, anchor=\"lm\")\n+    x0, y0, x1, y1 = 30, 34, W - 30, H - 14\n+    d.rectangle([x0 + 3, y0 + 3, x1 + 3, y1 + 3], fill=0)  # the window's shadow\n+    d.rectangle([x0, y0, x1, y1], fill=1, outline=0)\n+    for k in range(6):                                      # the title bar\n+        d.line([x0 + 3, y0 + 4 + 2 * k, x1 - 3, y0 + 4 + 2 * k], fill=0)\n+    d.rectangle([W // 2 - 44, y0 + 2, W // 2 + 44, y0 + 16], fill=1)\n+    d.text((W // 2, y0 + 9), \"Odyssey\", font=font(13), fill=0, anchor=\"mm\")\n+    d.line([x0, y0 + 19, x1, y0 + 19], fill=0)\n+    d.rectangle([x0 + 1, y0 + 20, x1 - 1, y1 - 1], fill=0)  # space\n+    rng = random.Random(5)\n+    for _ in range(90):\n+        d.point((rng.randint(x0 + 2, x1 - 2), rng.randint(y0 + 21, y1 - 2)), fill=1)\n+    px = img.load()\n+    cx, cy, r = 340, 190, 52                                # the planet, dithered\n+    for y in range(cy - r, cy + r):\n+        for x in range(cx - r, cx + r):\n+            if (x - cx) ** 2 + (y - cy) ** 2 < r * r:\n+                lit = (x - cx + r) / (2 * r)\n+                px[x, y] = 1 if (x * 7 + y * 13) % 8 < 8 * (1 - lit) * 0.9 + 1 else 0\n+    d.ellipse([cx - 92, cy - 16, cx + 92, cy + 16], outline=1, width=2)   # its ring\n+    for x in range(cx - r, cx + r):                         # the ring behind the planet hides\n+        for y in range(cy - 16, cy + 1):\n+            on_ring = abs(((x - cx) / 92) ** 2 + ((y - cy) / 16) ** 2 - 1) < 0.12\n+            if (x - cx) ** 2 + (y - cy) ** 2 < r * r and px[x, y] == 1 and on_ring:\n+                px[x, y] = 0\n+    d.polygon([(110, 186), (176, 172), (110, 158), (124, 172)], fill=1)    # the ship\n+    d.polygon([(110, 166), (96, 172), (110, 178)], fill=1)\n+    d.text((x0 + 10, y1 - 14), \"SCORE 2600\", font=font(11), fill=1, anchor=\"lm\")\n+    return img\n+\n+\n+def tube(img):\n+    \"\"\"A screen set into the tube's face: its raster in the middle, the glass's dark round it, a little glow.\"\"\"\n+    rw, rh = 238, 159\n+    glass = Image.new(\"RGB\", (256, 206), TUBE)\n+    raster = img.convert(\"L\").resize((rw, rh), Image.LANCZOS)\n+    raster = Image.merge(\"RGB\", [raster.point(lambda v, k=k: round(base + v * k)) for base, k in\n+                                 ((26, 0.86), (30, 0.86), (28, 0.84))])                  # a faint green cast\n+    glass.paste(raster, ((256 - rw) // 2, (206 - rh) // 2))\n+    return glass\n+\n+\n+# ---- The printing ----------------------------------------------------------------------------------------------------\n+\n+def keys():\n+    \"\"\"The keyboard from above: x across (-w/2 at the left), v down from its back edge to its front.\"\"\"\n+    w, d = L.KEYBOARD[\"size\"]\n+    reg = Region(L.REGIONS[\"keys\"], w, d, CASE)\n+    (hf, hb), inset, top = L.KEYBOARD[\"height\"], L.KEY[\"inset\"], L.KEY[\"height\"]\n+    slope = math.atan2(hb - hf, d)\n+\n+    def at(x, v):\n+        return x + w / 2, d - v\n+\n+    for legend, x0, x1, v in L.keys():\n+        va = v - 9.5 + inset - top * math.sin(slope)\n+        vb = v + 9.5 - inset - top * math.sin(slope)\n+        reg.rect_(*at(x0 + inset, vb), *at(x1 - inset, va), KEY, r=1.4)\n+        x, y = at((x0 + x1) / 2, (va + vb) / 2)\n+        if legend == \"\u2318\":\n+            reg.text(legend, \"seguisym.ttf\", 4.6, x, y, LEGEND)\n+        elif len(legend) == 1 and legend != \" \":\n+            reg.text(legend, \"arial.ttf\", 4.2, x0 + w / 2 + inset + 2.0, y - 2.4, LEGEND, anchor=\"lm\")\n+        elif legend.strip():\n+            reg.text(legend, \"arial.ttf\", 2.6, x0 + w / 2 + inset + 1.6, y + 3.0, LEGEND, anchor=\"lm\")\n+    return reg\n+\n+\n+def floppy():\n+    \"\"\"The floppy's top: x across, y back from its shutter's edge (the edge going in) to its label's.\"\"\"\n+    w, d, _ = L.FLOPPY[\"size\"]\n+    reg = Region(L.REGIONS[\"floppy\"], w, d, (52, 58, 72))\n+    sw, sd = L.FLOPPY[\"shutter\"]\n+    reg.rect_(w / 2 - sw / 2, 0.0, w / 2 + sw / 2, sd, (192, 194, 198))\n+    reg.rect_(w / 2 + 2.0, 6.0, w / 2 + 12.0, sd - 4.0, (40, 40, 44), r=1.0)\n+    reg.rect_(8.0, 40.0, w - 8.0, d - 4.0, (238, 236, 228), r=1.0)\n+    art = space((360, 160)).convert(\"RGB\")\n+    ship(ImageDraw.Draw(art), 100, 82, 2.0, \"#FFFFFF\", \"#FFB23C\")\n+    x0, y0, x1, y1 = reg.box(11.0, 58.0, w - 11.0, d - 7.0)\n+    reg.img.paste(art.resize((round(x1 - x0), round(y1 - y0)), Image.LANCZOS), (round(x0), round(y0)))\n+    reg.text(\"ODYSSEY\", \"ariblk.ttf\", 7.0, w / 2, 49.0, (40, 40, 44))\n+    return reg\n+\n+\n+def mouse():\n+    \"\"\"The mouse from above: x across, y back from its front (the button's end) to the end under the hand.\"\"\"\n+    w, length, _ = L.MOUSE[\"size\"]\n+    reg = Region(L.REGIONS[\"mouse\"], w, length, CASE)\n+    reg.rect_(4.0, 2.0, w - 4.0, length * L.MOUSE[\"button\"], (196, 188, 168), r=2.0, outline=(150, 142, 124),\n+              width=0.5)\n+    return reg\n+\n+\n+def apple():\n+    \"\"\"The rainbow Apple on its badge.\"\"\"\n+    reg = Region(L.REGIONS[\"apple\"], 16.3, 17.9, (200, 192, 172))\n+    layer = reg.layer()\n+    d = ImageDraw.Draw(layer)\n+    mask = Image.new(\"L\", layer.size, 0)\n+    m = ImageDraw.Draw(mask)\n+    k = reg.sx\n+\n+    def e(cx, cy, rx, ry, fill):\n+        m.ellipse([(cx - rx) * k, (cy - ry) * k, (cx + rx) * k, (cy + ry) * k], fill=fill)\n+    e(6.1, 10.4, 3.9, 4.6, 255)\n+    e(10.2, 10.4, 3.9, 4.6, 255)\n+    e(8.15, 8.6, 4.4, 3.4, 255)\n+    e(13.6, 9.6, 1.9, 2.2, 0)                               # the bite\n+    m.ellipse([8.2 * k, 2.6 * k, 10.6 * k, 5.4 * k], fill=255)   # the leaf\n+    for i, colour in enumerate(RAINBOW):\n+        d.rectangle([0, (2.6 + i * 2.25) * k, layer.width, (2.6 + (i + 1) * 2.25) * k], fill=colour + (255,))\n+    layer.putalpha(mask)\n+    reg.over(layer)\n+    return reg\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), CASE)\n+    for reg in (keys(), floppy(), mouse(), apple()):\n+        reg.paste(image)\n+    for name, screen in ((\"disk\", screen_disk()), (\"happy\", screen_happy()), (\"welcome\", screen_welcome()),\n+                         (\"game\", screen_game())):\n+        x, y, w, h = L.REGIONS[name]\n+        image.paste(tube(screen).resize((w, h), Image.LANCZOS), (x, y))\n+    d = ImageDraw.Draw(image)\n+    colours = dict(case=CASE, key=KEY, dark=(16, 16, 16), tube=TUBE, floppy=(52, 58, 72), shutter=(192, 194, 198),\n+                   cord=(204, 196, 176), legend=LEGEND)\n+    for name, (x, y) in L.SWATCHES.items():\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=colours[name])\n+    path = os.path.join(out, \"macintosh_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "a7e14287960ed09464ae0ef2a394d79815924e01",
          "filename": "tools/console-models/master_system.py",
          "status": "added",
          "additions": 325,
          "deletions": 0,
          "changes": 325,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmaster_system.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmaster_system.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fmaster_system.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,325 @@\n+\"\"\"The console theme's Master System model (godot/themes/console/models/systems/mastersystem.glb): the first Sega\n+Master System and its Control Pad.\n+\n+Built in Blender from the real console's proportions (master_system_layout.py has the measurements, taken from\n+photographs). The console is its side outline extruded across its width (the front band, the bevel, the hood's top\n+rising to the ridge, the back's slope), its ends sloped in above the slab and ribbed below it; the red panel is sunk\n+into the hood's right part, sloping more gently, with the cartridge slot at its back; POWER's recess, the ports' recess\n+and the card slot are cut into the front band. RESET and PAUSE stand on the panel, POWER in its recess, and the dust\n+flap closes the slot. The Control Pad lies in front of the console's left end: its body, the raised block at its back\n+right, the D-pad in its well and the two buttons. The printing is one texture drawn by master_system_print.py (the top\n+seen from above, planar-mapped onto every face facing up; the front band seen from in front; the cartridge's label; the\n+pad's top; and swatches).\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: the right end towards the viewer, the\n+top tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m) holding the set; the set\n+holding the cartridge (waiting in the slot, shrunk to nothing), the dust flap (on its hinge), POWER, PAUSE, the\n+D-pad and the pad's two buttons.\n+\n+The `focused` clip (8 s, looping) plays a game: the cartridge drops into the slot, the flap swinging down out of its\n+way, and POWER goes in; the D-pad rocks and the buttons are pressed, PAUSE twice; then POWER again, and the cartridge\n+comes out and goes, the flap swinging shut.\n+\n+Five materials (case, dark, button, cart, print) and one texture, within the system model budget. Run from the\n+repo root:\n+\n+    python tools/console-models/master_system_print.py artifacts/mastersystem\n+    blender -b --factory-startup --python tools/console-models/master_system.py -- artifacts/mastersystem godot/themes/console/models/systems/mastersystem.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bmesh\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import master_system_layout as L  # noqa: E402\n+from amiga import paint, rounded, solid  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, ease_out, key, phase, smooth  # noqa: E402\n+from mega_drive_cartridge import bevel, boolean, box, export, join, material, new_object, prism_x, triangles  # noqa: E402\n+\n+TILT_YAW = -25.0     # degrees about the vertical: the right end comes towards the viewer\n+TILT_PITCH = 30.0    # degrees about the horizontal: the top tips up towards the viewer\n+ROUGHNESS = dict(case=0.45, dark=0.8, button=0.5, cart=0.5, print=0.45)\n+FRONT_H = L.BAND[\"bevel\"][1]\n+\n+\n+# ---- UVs -------------------------------------------------------------------------------------------------------------\n+\n+def atlas_uv(region, w, h):\n+    \"\"\"A face drawn w x h into region: (a, b) millimetres across it (a to the right, b down from its top) to UVs.\"\"\"\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+TOP = atlas_uv(L.REGIONS[\"top\"], L.WIDTH, L.DEPTH)\n+FRONT = atlas_uv(L.REGIONS[\"front\"], L.WIDTH, FRONT_H)\n+\n+\n+def top_uv(co):\n+    return TOP(co.x + L.WIDTH / 2, L.DEPTH - co.y)\n+\n+\n+def front_uv(co):\n+    return FRONT(co.x + L.WIDTH / 2, FRONT_H - co.z)\n+\n+\n+def swatch(name):\n+    x, y = L.SWATCHES[name]\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+# ---- The console -----------------------------------------------------------------------------------------------------\n+\n+PANEL_SLOPE = math.atan2(L.PANEL[\"z\"][1] - L.PANEL[\"z\"][0], L.PANEL[\"y\"][1] - L.PANEL[\"y\"][0])\n+PANEL_FRAME = Matrix.Translation((0.0, L.PANEL[\"y\"][0], L.PANEL[\"z\"][0])) @ Matrix.Rotation(PANEL_SLOPE, 4, \"X\")\n+\n+\n+def panel_y(y):\n+    \"\"\"The panel's (y, z) frame: the distance along its slope of the point over y.\"\"\"\n+    return (y - L.PANEL[\"y\"][0]) / math.cos(PANEL_SLOPE)\n+\n+\n+def console(m):\n+    \"\"\"The console: the body with its ends sloped and ribbed, the panel's recess, the slot and the front band's\n+    recesses cut, its top and front printed.\"\"\"\n+    body = prism_x(\"mastersystem\", L.profile(), -L.WIDTH / 2, L.WIDTH / 2, m[\"case\"])\n+    bevel(body, 1.5, segments=2)\n+    top, inset = L.ENDS[\"top\"], L.ENDS[\"inset\"]\n+    reach = inset * (L.HEIGHT + 10.0 - top) / (L.HEIGHT - top)\n+    for side in (-1, 1):                                  # the hood's ends, sloping in from the slab's ends\n+        e = side * L.WIDTH / 2\n+        outline = [(e, top), (e + side * 20.0, top), (e + side * 20.0, L.HEIGHT + 10.0), (e - side * reach, L.HEIGHT + 10.0)]\n+        cutter = solid(\"end\", [[(x, y, z) for x, z in outline] for y in (-5.0, L.DEPTH + 5.0)], m[\"case\"])\n+        boolean(body, cutter)\n+    r = L.RIBS\n+    boolean(body, join([box(\"rib\", x0, x1, 10.0, L.DEPTH - 10.0, z - r[\"width\"] / 2, z + r[\"width\"] / 2, m[\"dark\"])\n+                        for z in r[\"z\"] for x0, x1 in ((L.WIDTH / 2 - r[\"depth\"], L.WIDTH / 2 + 5.0),\n+                                                        (-L.WIDTH / 2 - 5.0, -L.WIDTH / 2 + r[\"depth\"]))], \"ribs\"))\n+    (x0, x1), (y0, y1), (z0, z1) = L.PANEL[\"x\"], L.PANEL[\"y\"], L.PANEL[\"z\"]\n+    boolean(body, prism_x(\"panel\", [(y0, z0), (y1, z1), (y1, L.HEIGHT + 20.0), (y0, L.HEIGHT + 20.0)], x0, x1,\n+                          m[\"case\"]))\n+    s = L.SLOT\n+    boolean(body, box(\"slot\", *s[\"x\"], *s[\"y\"], s[\"bottom\"], L.HEIGHT + 10.0, m[\"dark\"]))\n+    p = L.POWER\n+    boolean(body, box(\"power_well\", p[\"x\"][0] - 1.0, p[\"x\"][1] + 1.0, -5.0, 4.0, p[\"z\"][0] - 1.0, p[\"z\"][1] + 1.0,\n+                      m[\"dark\"]))\n+    q = L.PORTS\n+    boolean(body, box(\"ports\", *q[\"x\"], -5.0, q[\"depth\"], q[\"z\"][0] - 6.0, q[\"z\"][1], m[\"dark\"]))\n+    c = L.CARD\n+    boolean(body, box(\"card\", *c[\"x\"], -5.0, c[\"depth\"], c[\"z\"][0] - 6.0, c[\"z\"][1], m[\"dark\"]))\n+    paint(body, m[\"print\"], top_uv, lambda cc, n, name: n.z > 0.5 and name == \"case\")\n+    paint(body, m[\"print\"], front_uv, lambda cc, n, name: n.y < -0.9 and cc.z < FRONT_H and cc.y < 12.0)\n+    return body\n+\n+\n+def panel_button(name, x0, x1, m):\n+    \"\"\"A button on the red panel, standing off its slope.\"\"\"\n+    (a, b), h = L.BUTTONS[\"y\"], L.BUTTONS[\"height\"]\n+    s0, s1 = panel_y(a), panel_y(b)\n+    button = solid(name, [[PANEL_FRAME @ Vector((x, s, z)) for x, s in ((x0, s0), (x1, s0), (x1, s1), (x0, s1))]\n+                          for z in (-1.0, h)], m[\"button\"])\n+    bevel(button, 0.5, segments=1)\n+    return button\n+\n+\n+def power_button(m):\n+    p = L.POWER\n+    button = box(\"power\", p[\"x\"][0], p[\"x\"][1], -p[\"out\"], 4.0, p[\"z\"][0], p[\"z\"][1], m[\"button\"])\n+    bevel(button, 0.6, segments=1)\n+    return button\n+\n+\n+def flap(m):\n+    \"\"\"The slot's dust flap, its origin on its hinge just behind the slot's front edge: it hangs there, out of the\n+    cartridge's way, once swung down.\"\"\"\n+    (x0, x1), (y0, y1) = L.SLOT[\"x\"], L.SLOT[\"y\"]\n+    plate = box(\"flap\", x0 + 1.0, x1 - 1.0, y0 + 0.1, y1 - 0.4, L.SLOT[\"flap\"] - 0.8, L.SLOT[\"flap\"], m[\"print\"])\n+    paint(plate, m[\"print\"], swatch(\"grey\"), lambda c, n, name: True)\n+    hinge = Vector((0.0, y0 + 0.9, L.SLOT[\"flap\"]))\n+    plate.data.transform(Matrix.Translation(-hinge))\n+    return plate, hinge\n+\n+\n+def cartridge(m):\n+    \"\"\"The cartridge, its bottom's middle at its origin, its front (-y) towards the player: the shell, three grooves\n+    under the label, the label.\"\"\"\n+    w, t, h = L.CART[\"size\"]\n+    lw, lh = L.CART[\"label\"]\n+    shell = box(\"cartridge\", -w / 2, w / 2, -t / 2, t / 2, 0.0, h, m[\"cart\"])\n+    bevel(shell, 1.0, segments=2)\n+    grooves = [box(\"groove\", -w / 2 - 1.0, w / 2 + 1.0, -t / 2 - 1.0, -t / 2 + 0.5, z - 0.4, z + 0.4, m[\"dark\"])\n+               for z in (h - lh - 6.0, h - lh - 8.0, h - lh - 10.0)]\n+    boolean(shell, join(grooves, \"grooves\"))\n+    bm = bmesh.new()\n+    layer = bm.loops.layers.uv.new(\"UVMap\")\n+    x0, x1, z0, z1 = -lw / 2, lw / 2, h - 2.5 - lh, h - 2.5\n+    corners = [(x0, z0), (x1, z0), (x1, z1), (x0, z1)]                  # wound to face -y\n+    face = bm.faces.new([bm.verts.new((x, -t / 2 - 0.12, z)) for x, z in corners])\n+    label_uv = atlas_uv(L.REGIONS[\"label\"], lw, lh)\n+    for loop, (x, z) in zip(face.loops, corners):\n+        loop[layer].uv = label_uv(x - x0, z1 - z)\n+    sticker = new_object(\"label\", bm, m[\"print\"])\n+    return join([shell, sticker], \"cartridge\")\n+\n+\n+# ---- The Control Pad -------------------------------------------------------------------------------------------------\n+\n+def pad_frame():\n+    (px, py), turn = L.PAD[\"at\"], L.PAD[\"turn\"]\n+    return Matrix.Translation((px, py, 0.0)) @ Matrix.Rotation(math.radians(turn), 4, \"Z\")\n+\n+\n+def pad(m):\n+    \"\"\"The pad's body, at its place: the lower body, the raised block, the D-pad's well, its top printed.\"\"\"\n+    w, d, h = L.PAD[\"size\"]\n+    b = L.PAD[\"block\"]\n+    body = solid(\"pad\", [[(x, y, z) for x, y in rounded(0.0, 0.0, w, d, 4.0, 3)] for z in (0.0, h - b[\"height\"])],\n+                 m[\"case\"])\n+    boolean(body, box(\"block\", *b[\"x\"], *b[\"y\"], h - b[\"height\"] - 1.0, h, m[\"case\"]), \"UNION\")\n+    bevel(body, 1.2, segments=2)\n+    x0, x1, y0, y1 = L.PAD[\"well\"]\n+    boolean(body, box(\"well\", x0, x1, y0, y1, h - b[\"height\"] - 2.0, h, m[\"case\"]))\n+    top = atlas_uv(L.REGIONS[\"pad\"], w, d)\n+    paint(body, m[\"print\"], lambda co: top(co.x + w / 2, d / 2 - co.y), lambda c, n, name: n.z > 0.9)\n+    body.data.transform(pad_frame())\n+    return body\n+\n+\n+def pad_parts(m):\n+    \"\"\"The D-pad and the two buttons, each its middle at its origin, and where each goes.\"\"\"\n+    w, d, h = L.PAD[\"size\"]\n+    top = h - L.PAD[\"block\"][\"height\"]\n+    x0, x1, y0, y1 = L.PAD[\"well\"]\n+    cx, cy = (x0 + x1) / 2, (y0 + y1) / 2\n+    s, dh = L.PAD[\"dpad\"][\"size\"], L.PAD[\"dpad\"][\"height\"]\n+    ring = rounded(0.0, 0.0, s, s, 6.0, 3)\n+    dpad = solid(\"dpad\", [[(x, y, z) for x, y in ring] for z in (-1.0, dh - 1.5)]\n+                 + [[(x * 0.9, y * 0.9, dh) for x, y in ring]], m[\"button\"])\n+    bevel(dpad, 0.6, segments=1)\n+    parts = [(\"dpad\", dpad, Vector((cx, cy, top - 2.0)))]\n+    b = L.PAD[\"buttons\"]\n+    for k, x in enumerate(b[\"x\"]):\n+        disc = [(b[\"r\"] * math.cos(2 * math.pi * j / 20), b[\"r\"] * math.sin(2 * math.pi * j / 20)) for j in range(20)]\n+        button = solid(f\"button{k + 1}\", [[(xx, yy, z) for xx, yy in disc] for z in (-1.5, b[\"height\"])], m[\"button\"])\n+        bevel(button, 0.6, segments=1)\n+        parts.append((f\"button{k + 1}\", button, Vector((x, b[\"y\"], top))))\n+    frame = pad_frame()\n+    for _, part, _ in parts:\n+        part.data.transform(Matrix.Rotation(math.radians(L.PAD[\"turn\"]), 4, \"Z\"))\n+    return [(name, part, frame @ where) for name, part, where in parts]\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+def appear(f, f0, f1):\n+    return HIDDEN + (1 - HIDDEN) * back_out(phase(f, f0, f1)) if f >= f0 else HIDDEN\n+\n+\n+def tap(f, f0, hold=2):\n+    \"\"\"A press at f0: down in three frames, held, back up in four.\"\"\"\n+    return smooth(phase(f, f0, f0 + 3)) * (1 - smooth(phase(f, f0 + 3 + hold, f0 + 7 + hold)))\n+\n+\n+def rock(f, f0, f1):\n+    \"\"\"The D-pad held over from f0 to f1: over in three frames, back in four.\"\"\"\n+    return smooth(phase(f, f0, f0 + 3)) * (1 - smooth(phase(f, f1, f1 + 4)))\n+\n+\n+ROCKS = [(62, 72, 0.0, 9.0), (78, 88, 0.0, 9.0), (96, 104, 9.0, 0.0), (136, 148, 0.0, -9.0)]   # f0, f1, pitch, roll\n+PRESSES = dict(button1=(110, 120, 156), button2=(128,), pause=(170, 182), power=(42, 196))\n+\n+\n+def timeline(f):\n+    # The cartridge: appears over its slot, drops in with a bump; later rises out and goes.\n+    if f < 6 or f >= 224:\n+        cart = Pose(scale=HIDDEN)\n+    elif f < 18:\n+        cart = Pose(z=70.0, scale=appear(f, 6, 16))\n+    elif f < 30:\n+        cart = Pose(z=70.0 * (1 - ease_in(phase(f, 18, 30))))\n+    elif f < 204:\n+        cart = Pose(squash=0.05 * math.sin(math.pi * phase(f, 30, 36)))\n+    else:\n+        cart = Pose(z=70.0 * ease_out(phase(f, 204, 216)), scale=max(HIDDEN, 1 - ease_in(phase(f, 214, 224))))\n+\n+    # The flap swings down as the cartridge comes, and back up once it's gone.\n+    swing = -80.0 * smooth(phase(f, 20, 25)) * (1 - smooth(phase(f, 214, 222)))\n+    poses = {\"cartridge\": cart, \"flap\": Pose(pitch=swing)}\n+\n+    pitch = roll = 0.0\n+    for f0, f1, p, r in ROCKS:\n+        t = rock(f, f0, f1)\n+        pitch, roll = pitch + p * t, roll + r * t\n+    poses[\"dpad\"] = Pose(pitch=pitch, roll=roll)\n+    for name, frames in PRESSES.items():\n+        d = max(tap(f, f0) for f0 in frames)\n+        if name == \"power\":\n+            poses[name] = Pose(y=2.0 * d)\n+        elif name == \"pause\":\n+            n = (PANEL_FRAME.to_3x3() @ Vector((0.0, 0.0, -1.0)))\n+            poses[name] = Pose(x=n.x * 1.6 * d, y=n.y * 1.6 * d, z=n.z * 1.6 * d)\n+        else:\n+            poses[name] = Pose(z=-2.0 * d)\n+    return poses\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    printed = bpy.data.images.load(os.path.join(images, \"master_system_print.png\"))\n+    colours = dict(L.COLOURS, print=\"#FFFFFF\")\n+    m = {n: material(n, colours[n], r, printed if n == \"print\" else None) for n, r in ROUGHNESS.items()}\n+\n+    reset = panel_button(\"reset\", *L.BUTTONS[\"reset\"], m)\n+    whole = join([console(m), reset, pad(m)], \"mastersystem\")\n+    pause = panel_button(\"pause\", *L.BUTTONS[\"pause\"], m)\n+    power = power_button(m)\n+    cover, hinge = flap(m)\n+    cart = cartridge(m)\n+    parts = pad_parts(m)\n+    for obj in [whole, pause, power, cart] + [part for _, part, _ in parts]:\n+        for p in obj.data.polygons:\n+            p.use_smooth = True\n+        obj.data.set_sharp_from_angle(angle=math.radians(35))\n+\n+    # The node tree: the root (the view and the fit) holds the set; the set holds the moving parts at their places.\n+    root = bpy.data.objects.new(\"mastersystem_root\", None)\n+    scene.collection.objects.link(root)\n+    whole.parent = root\n+    seat = Vector(((L.SLOT[\"x\"][0] + L.SLOT[\"x\"][1]) / 2, L.SLOT[\"y\"][0] + 1.0 + L.CART[\"size\"][1] / 2,\n+                   L.HEIGHT - 1.0 - L.CART[\"inserted\"]))\n+    for part, where in [(cover, hinge), (cart, seat)] + [(part, where) for _, part, where in parts]:\n+        part.parent = whole\n+        part.location = where\n+    for part in (pause, power):\n+        part.parent = whole\n+    nodes = {\"cartridge\": cart, \"flap\": cover, \"pause\": pause, \"power\": power, **{name: part for name, part, _ in parts}}\n+\n+    # The three-quarter view, then the spec's fit of the set at rest, as the root's transform.\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    points = [tilt @ v.co for v in whole.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [whole, pause, power, cover, cart] + [part for _, part, _ in parts]\n+    print(f\"Master System: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(whole.data)} the set), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "107ffd9f5140a1bb7e4bb47b3f5c95b7c1ae6854",
          "filename": "tools/console-models/master_system_layout.py",
          "status": "added",
          "additions": 68,
          "deletions": 0,
          "changes": 68,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmaster_system_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmaster_system_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fmaster_system_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,68 @@\n+\"\"\"The Sega Master System's measurements, shared by master_system_print.py (which draws its printing) and\n+master_system.py (which builds it). Plain Python: Blender's Python imports it too.\n+\n+The first Master System (1986), 365 x 168 x 73 mm (the Computer History Museum's record), with its Control Pad.\n+Millimetres: x from the console's middle (left to right), y back from its front edge, z up from the table. The rest was\n+measured from photographs on Wikimedia Commons (Evan-Amos's views of the console from each corner, of its pads and of a\n+cartridge; a view of it from above with a cartridge in), scaled by its width; the cartridge is the community's\n+measured 108 x 69 x 16 mm.\n+\n+The console: a slab whose front band carries POWER at its left, the two control ports in a recess and the card slot at\n+its right, a red stripe along its top edge; on it the hood, its top rising from the stripe to the ridge along the back,\n+its ends and back sloping down (the back's slope full of vent holes), ribs along the slab's ends. In the hood's right\n+part a red panel is sunk, sloping more gently than the hood, RESET and PAUSE across it and the diagram under them; at\n+its back the cartridge slot, its dust flap hinged along its front edge. The Control Pad lies in front of its left end.\n+\n+One texture (ATLAS square, REGIONS): `top` (the whole console seen from above: the hood's lettering and stripe, the red\n+panel's printing, the vent holes; planar-mapped onto every face facing up), `front` (the front band seen from in\n+front: POWER's label, the ports' and the card slot's legends), `label` (the cartridge's: a made-up game, Odyssey, on\n+the red grid), `pad` (the Control Pad's top), and swatches. A region is (x, y, width, height) in pixels.\n+\"\"\"\n+\n+ATLAS = 1024\n+\n+WIDTH, DEPTH, HEIGHT = 365.0, 168.0, 73.0\n+BAND = dict(z=(4.0, 28.0), bevel=(8.0, 36.0))     # the front face's bottom and top; the bevel up to the hood's front\n+FOOT = 4.0                                         # the base's recessed foot band\n+RIDGE = (128.0, 136.0)                             # the hood's flat ridge, along y\n+BACK = (DEPTH, 42.0)                               # the back slope's foot, (y, z)\n+ENDS = dict(top=34.0, inset=38.0)                  # the hood's ends slope in from the slab's ends to inset at its top\n+\n+\n+def hood_z(y):\n+    \"\"\"The hood's top at y, from the stripe up to the ridge.\"\"\"\n+    (y0, z0), y1 = BAND[\"bevel\"], RIDGE[0]\n+    t = min(1.0, max(0.0, (y - y0) / (y1 - y0)))\n+    return z0 + (HEIGHT - z0) * t\n+\n+\n+def profile():\n+    \"\"\"The console's side outline, (y, z), anticlockwise seen from the right end.\"\"\"\n+    return [(2.0, 0.0), (DEPTH - 2.0, 0.0), (DEPTH, FOOT), BACK, (RIDGE[1], HEIGHT), (RIDGE[0], HEIGHT), BAND[\"bevel\"],\n+            (0.0, BAND[\"z\"][1]), (0.0, FOOT), (2.0, FOOT)]\n+\n+\n+PANEL = dict(x=(4.0, 117.0), y=(10.0, 117.0), z=(33.5, 56.0))   # the red panel: across, along, its height there\n+SLOT = dict(x=(5.5, 115.5), y=(117.5, 135.5), bottom=30.0, flap=62.0)   # the cartridge slot; its flap's height\n+BUTTONS = dict(reset=(8.0, 30.0), pause=(92.0, 114.0), y=(98.0, 108.0), height=3.0)   # RESET and PAUSE\n+RIBS = dict(z=[8.0 + 2.6 * k for k in range(9)], depth=1.4, width=1.2)   # along both ends of the slab\n+\n+POWER = dict(x=(-145.0, -119.0), z=(7.0, 21.0), out=2.5)\n+PORTS = dict(x=(-34.0, 22.0), z=(FOOT, 18.0), depth=7.0, at=(-19.0, 7.0), size=(21.0, 9.0))\n+CARD = dict(x=(54.0, 111.0), z=(FOOT, 27.0), depth=9.0, slot=(60.0, 105.0, 13.0, 15.5))\n+\n+CART = dict(size=(108.0, 16.0, 69.0), label=(102.0, 27.0), inserted=40.0)   # w, thick, tall; its label, w x h\n+\n+PAD = dict(at=(-125.0, -64.0), size=(115.0, 52.0, 19.0), turn=6.0,   # its middle; w, d, h; turned (degrees)\n+           block=dict(x=(10.0, 57.5), y=(6.0, 26.0), height=5.0),      # the raised block at its back right\n+           well=(-52.0, -14.0, -18.0, 14.0),                          # the D-pad's well: x0, x1, y0, y1\n+           dpad=dict(size=27.0, height=6.0), buttons=dict(x=(19.0, 40.0), y=-9.0, r=6.6, height=3.0),\n+           stripes=(-9.0, 8.0, -20.0, 2.0))                           # behind SEGA: x0, x1, y0, y1\n+\n+COLOURS = dict(case=\"#1E1E20\", dark=\"#0B0B0C\", grey=\"#5E5F62\", red=\"#C8262C\", cart=\"#232325\", button=\"#151516\")\n+\n+REGIONS = dict(top=(0, 0, 1024, 472), front=(0, 480, 1024, 120), label=(0, 608, 408, 108), pad=(416, 608, 460, 208))\n+SWATCHES = dict(dark=(904, 624), grey=(936, 624), red=(968, 624), button=(1000, 624))\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s"
        },
        {
          "sha": "665963b163e08cf5d6d623ac3027a34054a2756c",
          "filename": "tools/console-models/master_system_print.py",
          "status": "added",
          "additions": 209,
          "deletions": 0,
          "changes": 209,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmaster_system_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmaster_system_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fmaster_system_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,209 @@\n+\"\"\"The Sega Master System's printing, for the console theme's Master System model (tools/console-models/master_system.py).\n+\n+Draws its atlas, its regions as master_system_layout.py places them, each in millimetres as the model's planar UVs map\n+it: the console's top seen from above (the red stripe, SEGA MASTER SYSTEM/Power Base, the red panel with RESET and\n+PAUSE, CARTRIDGE INPUT and the diagram, the back slope's vent holes); its front band seen from in front (POWER's\n+label, CONTROL 1 and 2 over the ports, the card slot and CARD INPUT); the cartridge's label (a made-up game, Odyssey,\n+on the red grid); the Control Pad's top (CONTROL PAD, the D-pad's well, the SEGA mark, 1 START and 2); and the\n+swatches small parts take their colour from. Run it with the system Python (Pillow):\n+\n+    python tools/console-models/master_system_print.py <out folder>\n+\n+It writes master_system_print.png there.\n+\"\"\"\n+\n+import os\n+import sys\n+\n+from PIL import Image, ImageDraw, ImageFont\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import master_system_layout as L  # noqa: E402\n+from retro_pc_print import rgb, shade  # noqa: E402\n+from windows_pc_print import FONTS, Region  # noqa: E402\n+\n+CASE = rgb(L.COLOURS[\"case\"])\n+RED = rgb(L.COLOURS[\"red\"])\n+GREY = rgb(L.COLOURS[\"grey\"])\n+WHITE = (236, 236, 234)\n+INK = (14, 14, 15)\n+\n+\n+def sega(reg, x, y, size, fill, outline=None, anchor=\"lm\"):\n+    \"\"\"SEGA's mark: heavy, wide, slanted capitals (an outline round them if given).\"\"\"\n+    layer = reg.layer()\n+    d = ImageDraw.Draw(layer)\n+    f = ImageFont.truetype(FONTS + \"ariblk.ttf\", max(1, round(size * reg.sx)))\n+    if outline is not None:\n+        d.text(reg.p(x, y), \"SEGA\", font=f, fill=outline + (255,), anchor=anchor, stroke_width=max(1, round(0.3 * reg.sx)),\n+               stroke_fill=outline + (255,))\n+    d.text(reg.p(x, y), \"SEGA\", font=f, fill=fill + (255,), anchor=anchor)\n+    yc = reg.p(0, y)[1]\n+    reg.over(layer.transform(layer.size, Image.AFFINE, (1, 0.2, -0.2 * yc, 0, 1, 0)))\n+\n+\n+def boxed(reg, text, x0, y0, x1, y1, fill, ink, size, outline=None):\n+    reg.rect_(x0, y0, x1, y1, fill, outline=outline, width=0.3)\n+    reg.text(text, \"arial.ttf\", size, (x0 + x1) / 2, (y0 + y1) / 2, ink)\n+\n+\n+# ---- The top ---------------------------------------------------------------------------------------------------------\n+\n+def top():\n+    reg = Region(L.REGIONS[\"top\"], L.WIDTH, L.DEPTH, CASE)\n+\n+    def at(x, y):\n+        return x + L.WIDTH / 2, L.DEPTH - y\n+    (px0, px1), (py0, py1) = L.PANEL[\"x\"], L.PANEL[\"y\"]\n+\n+    # The red stripe along the hood's front edge, either side of the panel.\n+    for a, b in ((-L.WIDTH / 2, px0), (px1, L.WIDTH / 2)):\n+        reg.rect_(*at(a, 10.6), *at(b, 9.6), RED)\n+        reg.rect_(*at(a, 12.4), *at(b, 12.0), shade(CASE, 0.18))\n+\n+    sega(reg, *at(-124.0, 55.0), 8.6, WHITE, outline=(120, 120, 124))\n+    reg.text(\"MASTER SYSTEM/Power Base\", \"times.ttf\", 7.4, *at(-97.0, 54.4), WHITE, anchor=\"lm\")\n+\n+    # The red panel: RESET and PAUSE across its back, CARTRIDGE INPUT over them, the diagram, the hatching at its front.\n+    reg.rect_(*at(px0, py1), *at(px1, py0), RED)\n+    by0, by1 = L.BUTTONS[\"y\"]\n+    for (a, b), label, lx in ((L.BUTTONS[\"reset\"], \"RESET\", 0), (L.BUTTONS[\"pause\"], \"PAUSE\", 1)):\n+        x0, x1 = (px0 + 2.0, (px0 + px1) / 2 - 1.0) if lx == 0 else ((px0 + px1) / 2 + 1.0, px1 - 2.0)\n+        reg.rect_(*at(x0, by1 + 1.6), *at(x1, by0 - 1.6), INK, r=0.6)\n+        tx = (b + x1) / 2 if lx == 0 else (x0 + a) / 2\n+        reg.text(label, \"arial.ttf\", 3.0, *at(tx, (by0 + by1) / 2), WHITE)\n+        reg.rect_(*at(a - 0.6, by1 + 0.6), *at(b + 0.6, by0 - 0.6), (40, 40, 42), r=0.5)\n+    reg.text(\"CARTRIDGE  INPUT  \u25b2\", \"seguisym.ttf\", 2.8, *at((px0 + px1) / 2 + 4.0, py1 - 3.0), WHITE)\n+\n+    def line(points, width=0.35):\n+        reg.line([at(x, y) for x, y in points], WHITE, width)\n+    line([(22.0, 82.0), (22.0, 46.0), (60.0, 46.0)])\n+    line([(60.0, 70.0), (60.0, 82.0), (72.0, 82.0)])\n+    line([(75.0, 58.0), (88.0, 58.0), (88.0, 66.0)])\n+    line([(88.0, 50.0), (88.0, 40.0), (75.0, 40.0)])\n+    line([(88.0, 58.0), (96.0, 58.0)])\n+    boxed(reg, \"CARTRIDGE\", *at(14.0, 88.0), *at(36.0, 84.0), WHITE, INK, 2.2)\n+    boxed(reg, \"CARD\", *at(18.0, 34.0), *at(34.0, 30.0), WHITE, INK, 2.2)\n+    boxed(reg, \"CONTROL 1\", *at(66.0, 86.0), *at(88.0, 80.0), RED, WHITE, 2.1, outline=WHITE)\n+    boxed(reg, \"CONTROL 2\", *at(66.0, 43.0), *at(88.0, 37.0), RED, WHITE, 2.1, outline=WHITE)\n+    boxed(reg, \"TV SYSTEM\", *at(94.0, 62.0), *at(114.0, 54.0), INK, WHITE, 2.1, outline=WHITE)\n+    tri = [at(27.0, 74.0), at(27.0, 50.0), at(70.0, 62.0)]\n+    reg.poly(tri, INK)\n+    reg.poly([at(52.0, 64.2), at(52.0, 59.8), at(62.0, 62.0)], (60, 170, 70))\n+    reg.text(\"POWER ON\", \"arial.ttf\", 2.2, *at(37.0, 62.0), WHITE)\n+    for k in range(40):                                   # the hatching along its front edge\n+        x = px0 + 2.0 + k * 2.8\n+        if x + 2.0 > px1:\n+            break\n+        line([(x, py0 + 1.0), (x + 2.0, py0 + 5.0)], 0.45)\n+\n+    # The back slope's vent holes, four rows of small squares, broken where the slot's frame stands.\n+    for row in range(4):\n+        y = L.RIDGE[1] + 5.0 + row * 7.0\n+        for k in range(64):\n+            x = -L.WIDTH / 2 + 18.0 + k * 5.2\n+            if x > L.WIDTH / 2 - 18.0:\n+                break\n+            if L.SLOT[\"x\"][0] - 6.0 < x < L.SLOT[\"x\"][1] + 4.0 and row < 2:\n+                continue\n+            reg.rect_(*at(x, y + 1.6), *at(x + 2.8, y - 1.6), (6, 6, 7))\n+    return reg\n+\n+\n+# ---- The front band --------------------------------------------------------------------------------------------------\n+\n+FRONT_H = L.BAND[\"bevel\"][1]       # the front region's height: the band from the table to the bevel's top\n+\n+\n+def front():\n+    reg = Region(L.REGIONS[\"front\"], L.WIDTH, FRONT_H, CASE)\n+\n+    def at(x, z):\n+        return x + L.WIDTH / 2, FRONT_H - z\n+    (a, b) = L.POWER[\"x\"]\n+    boxed(reg, \"POWER\", *at(a, 27.0), *at(b, 23.6), RED, WHITE, 2.6)\n+    reg.poly([at((a + b) / 2 - 0.9, 22.9), at((a + b) / 2 + 0.9, 22.9), at((a + b) / 2, 21.9)], WHITE)\n+    for k, x in enumerate(L.PORTS[\"at\"]):\n+        boxed(reg, f\"CONTROL  {k + 1}\", *at(x - 13.0, 26.6), *at(x + 13.0, 23.2), CASE, WHITE, 2.4, outline=WHITE)\n+        reg.poly([at(x - 0.9, 22.5), at(x + 0.9, 22.5), at(x, 21.5)], WHITE)\n+        w, h = L.PORTS[\"size\"]\n+        cz = (L.PORTS[\"z\"][0] + L.PORTS[\"z\"][1]) / 2\n+        reg.rect_(*at(x - w / 2, cz + h / 2), *at(x + w / 2, cz - h / 2), (8, 8, 9), r=1.6)\n+        for j in range(5):\n+            reg.circle(*at(x - 5.6 + 2.8 * j, cz + 1.3), 0.5, (150, 130, 80))\n+        for j in range(4):\n+            reg.circle(*at(x - 4.2 + 2.8 * j, cz - 1.3), 0.5, (150, 130, 80))\n+    x0, x1, z0, z1 = L.CARD[\"slot\"]\n+    reg.rect_(*at(x0, z1), *at(x1, z0), (6, 6, 7))\n+    boxed(reg, \"CARD   INPUT\", *at(L.CARD[\"x\"][1] + 14.0, 26.6), *at(L.CARD[\"x\"][1] + 40.0, 23.0), CASE, WHITE, 2.4,\n+          outline=WHITE)\n+    reg.poly([at(L.CARD[\"x\"][1] + 11.5, 24.8), at(L.CARD[\"x\"][1] + 13.0, 25.8), at(L.CARD[\"x\"][1] + 13.0, 23.8)], WHITE)\n+    for k in range(4):                                    # the bars beside it, growing\n+        xx, h = L.CARD[\"x\"][1] + 4.0 + k * 1.4, 0.6 + 0.45 * k\n+        reg.rect_(*at(xx, 24.8 + h / 2), *at(xx + 0.6, 24.8 - h / 2), WHITE)\n+    reg.rect_(*at(-L.WIDTH / 2, L.BAND[\"z\"][1] + 0.2), *at(L.WIDTH / 2, L.BAND[\"z\"][1] - 0.4), RED)\n+    return reg\n+\n+\n+# ---- The cartridge's label and the pad -------------------------------------------------------------------------------\n+\n+def label():\n+    lw, lh = L.CART[\"label\"]\n+    reg = Region(L.REGIONS[\"label\"], lw, lh, (214, 62, 58))\n+    step = 4.3\n+    for k in range(int(lw / step) + 1):\n+        reg.line([(k * step, 0.0), (k * step, lh)], (112, 22, 24), 0.25)\n+    for k in range(int(lh / step) + 1):\n+        reg.line([(0.0, k * step), (lw, k * step)], (112, 22, 24), 0.25)\n+    reg.text(\"Odyssey\", \"timesbd.ttf\", 11.0, 4.0, 9.0, (252, 248, 240), anchor=\"lm\")\n+    reg.text(\"\u2122\", \"times.ttf\", 3.0, 45.0, 4.6, (252, 248, 240))\n+    sega(reg, lw - 24.0, lh - 6.0, 6.6, (252, 248, 240))\n+    return reg\n+\n+\n+def pad():\n+    w, d, _ = L.PAD[\"size\"]\n+    reg = Region(L.REGIONS[\"pad\"], w, d, CASE)\n+\n+    def at(x, y):\n+        return x + w / 2, d / 2 - y\n+    x0, x1, y0, y1 = L.PAD[\"well\"]\n+    reg.rect_(*at(x0 - 3.0, y1 + 3.0), *at(x1 + 3.0, y0 - 3.0), INK, r=1.0)\n+    reg.rect_(*at(x0 - 2.0, y1 + 2.0), *at(x1 + 2.0, y0 - 2.0), RED, r=0.8)\n+    reg.rect_(*at(x0 - 1.6, y1 + 1.6), *at(x1 + 1.6, y0 - 1.6), INK, r=0.6)\n+    reg.rect_(*at(x0, y1), *at(x1, y0), GREY)\n+    cx, cy = (x0 + x1) / 2, (y0 + y1) / 2\n+    for corner in ((x0, y0), (x1, y0), (x1, y1), (x0, y1)):\n+        reg.line([at(*corner), at(cx + (corner[0] - cx) * 0.62, cy + (corner[1] - cy) * 0.62)], INK, 0.5)\n+    for dx, dy, glyph in ((0.0, 14.6, \"\u25b2\"), (0.0, -14.6, \"\u25bc\"), (-17.6, 0.0, \"\u25c0\"), (17.6, 0.0, \"\u25b6\")):\n+        reg.text(glyph, \"seguisym.ttf\", 2.0, *at(cx + dx, cy + dy), (220, 220, 220))\n+    reg.text(\"CONTROL PAD\", \"timesbd.ttf\", 4.4, *at(cx, y1 + 5.6), WHITE)\n+    sx0, sx1, sy0, sy1 = L.PAD[\"stripes\"]\n+    for k in range(13):\n+        x = sx0 + k * (sx1 - sx0) / 12\n+        reg.line([at(x, sy0), at(x, sy1)], (70, 70, 74), 0.45)\n+    sega(reg, *at((sx0 + sx1) / 2, (sy0 + sy1) / 2), 5.2, WHITE, anchor=\"mm\")\n+    b = L.PAD[\"buttons\"]\n+    for k, x in enumerate(b[\"x\"]):\n+        reg.rect_(*at(x - 9.5, b[\"y\"] + 9.0), *at(x + 9.5, b[\"y\"] - 9.0), GREY, r=0.6)\n+        reg.rect_(*at(x - 9.5, b[\"y\"] + 14.0), *at(x + 9.5, b[\"y\"] + 10.2), RED)\n+        reg.text(\"1  START\" if k == 0 else \"2\", \"arialbd.ttf\", 2.5, *at(x, b[\"y\"] + 12.1), WHITE)\n+    return reg\n+\n+\n+def main(out):\n+    os.makedirs(out, exist_ok=True)\n+    image = Image.new(\"RGB\", (L.ATLAS, L.ATLAS), CASE)\n+    for reg in (top(), front(), label(), pad()):\n+        reg.paste(image)\n+    d = ImageDraw.Draw(image)\n+    for name in (\"dark\", \"grey\", \"red\", \"button\"):\n+        x, y = L.SWATCHES[name]\n+        d.rectangle([x - 12, y - 12, x + 11, y + 11], fill=rgb(L.COLOURS[name]))\n+    path = os.path.join(out, \"master_system_print.png\")\n+    image.save(path, optimize=True)\n+    print(path)\n+\n+\n+if __name__ == \"__main__\":\n+    main(sys.argv[1])"
        },
        {
          "sha": "524ae3983c4d1fbfc5f73e36ea78fe93c3614196",
          "filename": "tools/console-models/mega_cd.py",
          "status": "added",
          "additions": 233,
          "deletions": 0,
          "changes": 233,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmega_cd.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmega_cd.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fmega_cd.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,233 @@\n+\"\"\"The console theme's Mega-CD system model (godot/themes/console/models/systems/segacd.glb): the first Mega-CD with\n+the Mega Drive sitting on it.\n+\n+Built in Blender (mega_cd_layout.py has the measurements, taken from photographs). The Mega-CD is a rounded black box\n+on four feet, the glossy panel sunk into its front, the tray's opening behind the door, and the connecting block on its\n+right end rising beside the Mega Drive; the Mega Drive is mega_drive.py's own, built by its build() and set on top.\n+The tray is the door (MEGA-CD on it) and the plate behind it with the CD's well, hidden in the box until it slides\n+out. The printing is two textures: mega_cd_print.py's (the front panel, the door, the CD's two sides, swatches) and\n+mega_drive_print.py's (the Mega Drive's top), both drawn into the same folder.\n+\n+A node tree: a root (the turn to a three-quarter view, TILT_YAW and TILT_PITCH: the right end towards the viewer, the\n+top tipped up; then the model spec's fit: standing on y = 0, centred, the largest side 1 m) holding the set; the set\n+holding the tray (holding the CD, waiting in its well shrunk to nothing) and the READY and ACCESS lights (shrunk to\n+nothing).\n+\n+The `focused` clip (8 s, looping) plays a CD: the tray slides out, the CD drops into its well, the tray takes it in;\n+READY lights and ACCESS flickers while it loads; later the tray slides out again, the CD lifts off and goes, and the\n+tray goes back.\n+\n+Seven materials (the Mega Drive's print, gloss, dark and reset; case, cd_print, disc) and two textures, within the\n+system model budget. Run from the repo root:\n+\n+    python tools/console-models/mega_drive_print.py artifacts/segacd\n+    python tools/console-models/mega_cd_print.py artifacts/segacd\n+    blender -b --factory-startup --python tools/console-models/mega_cd.py -- artifacts/segacd godot/themes/console/models/systems/segacd.glb\n+\"\"\"\n+\n+import math\n+import os\n+import sys\n+\n+import bpy\n+from mathutils import Matrix, Vector\n+\n+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n+import mega_cd_layout as L  # noqa: E402\n+from amiga import paint, rounded, solid  # noqa: E402\n+from clip import HIDDEN, Pose, back_out, ease_in, ease_out, key, phase, smooth  # noqa: E402\n+from mega_drive import build as mega_drive  # noqa: E402\n+from mega_drive_cartridge import bevel, boolean, box, export, join, material, triangles  # noqa: E402\n+\n+TILT_YAW = -25.0     # degrees about the vertical: the right end comes towards the viewer\n+TILT_PITCH = 28.0    # degrees about the horizontal: the top tips up towards the viewer\n+\n+\n+def atlas_uv(region, w, h):\n+    rx, ry, rw, rh = region\n+    return lambda a, b: ((rx + a / w * rw) / L.ATLAS, 1 - (ry + b / h * rh) / L.ATLAS)\n+\n+\n+def swatch(name):\n+    x, y = L.SWATCHES[name]\n+    return lambda co: (x / L.ATLAS, 1 - y / L.ATLAS)\n+\n+\n+def prism(name, outline, z0, z1, mat):\n+    return solid(name, [[(x, y, z) for x, y in outline] for z in (z0, z1)], mat)\n+\n+\n+def circle(cx, cy, r, n=24):\n+    return [(cx + r * math.cos(2 * math.pi * k / n), cy + r * math.sin(2 * math.pi * k / n)) for k in range(n)]\n+\n+\n+def mega_cd(m):\n+    \"\"\"The Mega-CD: the box on its feet, the front panel's recess and the tray's opening, the connecting block; its\n+    front printed.\"\"\"\n+    w, d = L.WIDTH, L.DEPTH\n+    body = prism(\"segacd\", rounded(0.0, 0.0, w, d, 10.0, 4), L.FOOT, L.HEIGHT, m[\"case\"])\n+    bevel(body, 3.0, segments=2)\n+    b = L.BLOCK\n+    lower = box(\"block\", b[\"x\"][0], b[\"x\"][1], *b[\"y\"], L.FOOT + 2.0, L.HEIGHT, m[\"case\"])\n+    upper = box(\"block_top\", b[\"inner\"], b[\"x\"][1], *b[\"y\"], L.HEIGHT - 2.0, b[\"top\"], m[\"case\"])\n+    block = join([lower, upper], \"block\")\n+    boolean(body, block, \"UNION\")\n+    bevel(body, 1.5, segments=1, angle=50)\n+    p = L.PANEL\n+    (x0, x1), (z0, z1) = p[\"x\"], p[\"z\"]\n+    face = rounded((x0 + x1) / 2, (z0 + z1) / 2, x1 - x0, z1 - z0, (z1 - z0) / 2 - 0.1, 6)\n+    boolean(body, solid(\"panel\", [[(x, y, z) for x, z in face] for y in (L.FRONT_Y - 5.0, L.FRONT_Y + p[\"depth\"])],\n+                        m[\"gloss\"]))\n+    dr = L.DOOR\n+    boolean(body, box(\"opening\", dr[\"x\"][0] - 0.5, dr[\"x\"][1] + 0.5, L.FRONT_Y - 5.0, L.FRONT_Y + L.TRAY[\"depth\"] + 10.0,\n+                      dr[\"z\"][0] - 0.5, dr[\"z\"][1] + 0.5, m[\"gloss\"]))\n+    front = atlas_uv(L.REGIONS[\"front\"], x1 - x0, z1 - z0)\n+    paint(body, m[\"cd_print\"], lambda co: front(co.x - x0, z1 - co.z),\n+          lambda c, n, name: n.y < -0.9 and abs(c.y - (L.FRONT_Y + p[\"depth\"])) < 0.3)\n+    feet = []\n+    for sx in (-1, 1):\n+        for sy in (-1, 1):\n+            foot = prism(\"foot\", rounded(sx * (w / 2 - 34.0), sy * (d / 2 - 30.0), 52.0, 40.0, 10.0, 3), 0.0,\n+                         L.FOOT + 1.0, m[\"case\"])\n+            feet.append(foot)\n+    return join([body] + feet, \"segacd\")\n+\n+\n+def tray(m):\n+    \"\"\"The tray, its origin at its door's middle: the door (MEGA-CD on it) and the plate behind it with the CD's well;\n+    and where the CD's middle sits, in its frame.\"\"\"\n+    dr, t = L.DOOR, L.TRAY\n+    door = box(\"tray\", *dr[\"x\"], L.FRONT_Y + L.PANEL[\"depth\"] - dr[\"out\"], L.FRONT_Y + L.PANEL[\"depth\"] + 2.0,\n+               *dr[\"z\"], m[\"case\"])\n+    bevel(door, 0.6, segments=1)\n+    face = atlas_uv(L.REGIONS[\"door\"], dr[\"x\"][1] - dr[\"x\"][0], dr[\"z\"][1] - dr[\"z\"][0])\n+    paint(door, m[\"cd_print\"], lambda co: face(co.x - dr[\"x\"][0], dr[\"z\"][1] - co.z), lambda c, n, name: n.y < -0.9)\n+    y0 = L.FRONT_Y + L.PANEL[\"depth\"] + 2.0\n+    plate = box(\"plate\", dr[\"x\"][0] + 1.0, dr[\"x\"][1] - 1.0, y0, y0 + t[\"depth\"], *t[\"z\"], m[\"gloss\"])\n+    wy, wr = t[\"well\"]\n+    cx = (dr[\"x\"][0] + dr[\"x\"][1]) / 2\n+    boolean(plate, prism(\"well\", circle(cx, y0 + wy, wr + 0.6, 48), t[\"z\"][1] - 1.6, t[\"z\"][1] + 2.0, m[\"gloss\"]))\n+    paint(plate, m[\"cd_print\"], swatch(\"dark\"), lambda c, n, name: True)\n+    part = join([door, plate], \"tray\")\n+    middle = Vector((cx, L.FRONT_Y + L.PANEL[\"depth\"], (dr[\"z\"][0] + dr[\"z\"][1]) / 2))\n+    part.data.transform(Matrix.Translation(-middle))\n+    seat = Vector((cx, y0 + wy, t[\"z\"][1] - 1.6 + L.DISC[\"thickness\"] / 2 + 0.1)) - middle\n+    return part, middle, seat\n+\n+\n+def disc(m):\n+    r, t = L.DISC[\"r\"], L.DISC[\"thickness\"]\n+    part = prism(\"disc\", circle(0.0, 0.0, r, 48), -t / 2, t / 2, m[\"disc\"])\n+    top = atlas_uv(L.REGIONS[\"disc\"], 2 * r, 2 * r)\n+    bottom = atlas_uv(L.REGIONS[\"data\"], 2 * r, 2 * r)\n+    paint(part, m[\"disc\"], lambda co: top(co.x + r, r - co.y), lambda c, n, name: n.z > 0.9)\n+    paint(part, m[\"disc\"], lambda co: bottom(co.x + r, r + co.y), lambda c, n, name: n.z <= 0.9)\n+    return part\n+\n+\n+def light(m, name, colour):\n+    x, z = L.LIGHTS[name]\n+    pw, ph = L.LIGHTS[\"size\"]\n+    y = L.FRONT_Y + L.PANEL[\"depth\"] - 0.25\n+    part = box(name, x - pw / 2 + 2.0, x + pw / 2 - 2.0, y - 0.3, y, z - ph / 2 - 2.2, z - ph / 2 - 0.8, m[\"cd_print\"])\n+    paint(part, m[\"cd_print\"], swatch(colour), lambda c, n, _name: True)\n+    middle = Vector((x, y - 0.15, z - ph / 2 - 1.5))       # a lit bar under its legend\n+    part.data.transform(Matrix.Translation(-middle))\n+    return part, middle\n+\n+\n+# ---- The focused clip ------------------------------------------------------------------------------------------------\n+\n+def appear(f, f0, f1):\n+    return HIDDEN + (1 - HIDDEN) * back_out(phase(f, f0, f1)) if f >= f0 else HIDDEN\n+\n+\n+def out(f):\n+    if f < 60:\n+        return smooth(phase(f, 8, 26))\n+    if f < 186:\n+        return 1 - smooth(phase(f, 44, 60))\n+    if f < 218:\n+        return smooth(phase(f, 188, 204))\n+    return 1 - smooth(phase(f, 220, 236))\n+\n+\n+def timeline(f):\n+    if f < 26 or f >= 218:\n+        cd = Pose(scale=HIDDEN)\n+    elif f < 32:\n+        cd = Pose(z=50.0, scale=appear(f, 26, 31))\n+    elif f < 42:\n+        cd = Pose(z=50.0 * (1 - ease_in(phase(f, 32, 42))))\n+    elif f < 206:\n+        cd = Pose()\n+    else:\n+        cd = Pose(z=50.0 * ease_out(phase(f, 206, 212)), scale=max(HIDDEN, 1 - ease_in(phase(f, 210, 218))))\n+    reading = (64 <= f < 120 or 150 <= f < 160) and (f // 3) % 4 != 3\n+    return {\"tray\": Pose(y=-L.TRAY[\"travel\"] * out(f)), \"disc\": cd,\n+            \"ready\": Pose() if 62 <= f < 186 else Pose(scale=HIDDEN), \"access\": Pose() if reading else Pose(scale=HIDDEN)}\n+\n+\n+# ---- Main ------------------------------------------------------------------------------------------------------------\n+\n+def main():\n+    args = sys.argv[sys.argv.index(\"--\") + 1:]\n+    images, out_path = os.path.abspath(args[0]), os.path.abspath(args[1])\n+    bpy.ops.wm.read_factory_settings(use_empty=True)\n+    scene = bpy.context.scene\n+    scene.render.fps = L.FPS\n+    md_print = bpy.data.images.load(os.path.join(images, \"megadrive_print.png\"))\n+    cd_print = bpy.data.images.load(os.path.join(images, \"mega_cd_print.png\"))\n+    m = {\n+        \"print\": material(\"print\", \"#FFFFFF\", 0.55, md_print),\n+        \"gloss\": material(\"gloss\", \"#FFFFFF\", 0.18, md_print),\n+        \"dark\": material(\"dark\", \"#060607\", 0.9),\n+        \"reset\": material(\"reset\", \"#C4C5BC\", 0.5),\n+        \"case\": material(\"case\", L.COLOURS[\"case\"], 0.5),\n+        \"cd_print\": material(\"cd_print\", \"#FFFFFF\", 0.25, cd_print),\n+        \"disc\": material(\"disc\", \"#FFFFFF\", 0.2, cd_print),\n+    }\n+    m_cd = dict(m, gloss=m[\"cd_print\"])                     # the Mega-CD's glossy parts take the panel's printing\n+\n+    md = mega_drive(m)\n+    md.data.transform(Matrix.Translation((0.0, L.MD_BACK, L.MD_LIFT)))\n+    base = mega_cd(m_cd)\n+    paint(base, m[\"cd_print\"], swatch(\"dark\"), lambda c, n, name: name == \"cd_print\" and abs(n.y) < 0.5)\n+    whole = join([base, md], \"segacd\")\n+    for poly in whole.data.polygons:\n+        poly.use_smooth = True\n+    whole.data.set_sharp_from_angle(angle=math.radians(35))\n+    drawer, drawer_at, seat = tray(m_cd)\n+    cd = disc(m)\n+    lights = {\"ready\": light(m, \"ready\", \"green\"), \"access\": light(m, \"access\", \"red\")}\n+\n+    root = bpy.data.objects.new(\"segacd_root\", None)\n+    scene.collection.objects.link(root)\n+    whole.parent = root\n+    drawer.parent = whole\n+    drawer.location = drawer_at\n+    cd.parent = drawer\n+    cd.location = seat\n+    for part, where in lights.values():\n+        part.parent = whole\n+        part.location = where\n+    nodes = {\"tray\": drawer, \"disc\": cd, **{name: part for name, (part, _) in lights.items()}}\n+\n+    tilt = Matrix.Rotation(math.radians(TILT_PITCH), 4, \"X\") @ Matrix.Rotation(math.radians(TILT_YAW), 4, \"Z\")\n+    points = [tilt @ v.co for v in whole.data.vertices]\n+    lo = Vector([min(p[i] for p in points) for i in range(3)])\n+    hi = Vector([max(p[i] for p in points) for i in range(3)])\n+    size = hi - lo\n+    root.matrix_world = Matrix.Scale(1 / max(size), 4) @ Matrix.Translation((-(lo.x + hi.x) / 2, -(lo.y + hi.y) / 2,\n+                                                                             -lo.z)) @ tilt\n+\n+    key(\"focused\", nodes, timeline, L.LENGTH)\n+    meshes = [whole, drawer, cd] + [part for part, _ in lights.values()]\n+    print(f\"Mega-CD: {sum(triangles(o.data) for o in meshes)} triangles ({triangles(whole.data)} the set), \"\n+          f\"{len({mat.name for o in meshes for mat in o.data.materials})} materials, \"\n+          f\"size {size.x:.1f} x {size.y:.1f} x {size.z:.1f} mm tilted, before the fit\")\n+    export([root] + meshes, out_path, animations=True)\n+\n+\n+if __name__ == \"__main__\":\n+    main()"
        },
        {
          "sha": "f62183e7f954efee6596cf258da71c469a143fa1",
          "filename": "tools/console-models/mega_cd_layout.py",
          "status": "added",
          "additions": 42,
          "deletions": 0,
          "changes": 42,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmega_cd_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmega_cd_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fmega_cd_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8",
          "patch": "@@ -0,0 +1,42 @@\n+\"\"\"The Mega-CD's measurements, shared by mega_cd_print.py (which draws its printing) and mega_cd.py (which builds it).\n+Plain Python: Blender's Python imports it too.\n+\n+The first Mega-CD (1991) with mega_drive.py's Mega Drive sitting on it. Millimetres in the Mega Drive's frame: x from\n+the middle (left to right), y back from the middle (the fronts at -y), z up from the table. The Mega-CD is 268 x 230 x 64\n+mm, its feet included, measured from photographs on Wikimedia Commons (Evan-Amos's views of the Mega-CD alone and with\n+its Mega Drive, and two views of the set from the front and the front right), scaled by the Mega Drive's 280 mm width.\n+\n+The Mega-CD: a black box on four feet, a glossy panel sunk into its front (the tray's door with MEGA-CD at the left,\n+SEGA under it; the READY, compact disc and ACCESS panel at the right, the four lights' legends under it), and the\n+connecting block on its right end, rising beside the Mega Drive. The Mega Drive sits on its top, its front over the\n+Mega-CD's.\n+\n+One texture (ATLAS square, REGIONS): `front` (the Mega-CD's front panel, seen from in front), `door` (the tray's door),\n+`disc` and `data` (the CD's two sides: a made-up game, Odyssey), and swatches; the Mega Drive keeps mega_drive_print.py's\n+own. A region is (x, y, width, height) in pixels.\n+\"\"\"\n+\n+ATLAS = 1024\n+\n+WIDTH, DEPTH, HEIGHT = 268.0, 230.0, 64.0\n+FOOT = 6.0\n+FRONT_Y = -DEPTH / 2                                      # the Mega-CD's front\n+PANEL = dict(x=(-126.0, 126.0), z=(15.0, 56.0), depth=2.0)   # the glossy panel, sunk into the front\n+DOOR = dict(x=(-118.0, -8.0), z=(29.0, 47.0), out=1.0)    # the tray's door, in the panel\n+TRAY = dict(depth=150.0, z=(30.0, 35.0), travel=118.0, well=(78.0, 60.5))   # its plate; how far it comes out; the\n+                                                                            # disc's well (back from the door, radius)\n+LIGHTS = dict(ready=(46.0, 39.0), access=(104.0, 39.0), size=(22.0, 7.0))   # x, z of each pill on the panel\n+BLOCK = dict(x=(130.0, 172.0), y=(-56.0, 104.0), top=108.0, inner=142.0)    # the connecting block; above the\n+                                                                           # Mega-CD's top it stands clear of x\n+MD_LIFT = HEIGHT                                          # the Mega Drive stands on the Mega-CD's top\n+MD_BACK = 4.0                                             # and a little back from its front\n+\n+DISC = dict(r=60.0, hole=7.5, thickness=1.2)\n+\n+COLOURS = dict(case=\"#151517\", gloss=\"#09090A\", disc=\"#FFFFFF\")\n+\n+REGIONS = dict(front=(0, 0, 1024, 168), door=(0, 176, 512, 84), disc=(0, 268, 256, 256), data=(264, 268, 256, 256))\n+SWATCHES = dict(green=(552, 296), red=(584, 296), dark=(616, 296), grey=(648, 296))\n+\n+FPS = 30\n+LENGTH = 240                 # 8 s"
        },
        {
          "sha": "e4c56abfc264f0810aafc55aad49a74a49cd41cc",
          "filename": "tools/console-models/mega_cd_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmega_cd_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmega_cd_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fmega_cd_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "f28a65f4d832ae9e0d5991f2f3f6b18d4bbfe767",
          "filename": "tools/console-models/mega_drive.py",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmega_drive.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmega_drive.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fmega_drive.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "751f296f79805db818792283b476787b2bcd0858",
          "filename": "tools/console-models/mega_drive_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmega_drive_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmega_drive_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fmega_drive_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "aadda2635bbfa153865fc95db844c71b707421c4",
          "filename": "tools/console-models/mega_drive_print.py",
          "status": "modified",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmega_drive_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmega_drive_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fmega_drive_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "8dc8c5ff1e0d92581ca39e2ae2f4276d3f919d37",
          "filename": "tools/console-models/msx.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmsx.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmsx.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fmsx.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "dc9f37e8f166396bcaa3dcdeb9ad9a4eae9f1306",
          "filename": "tools/console-models/msx2.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmsx2.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmsx2.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fmsx2.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "bef206036a3aa81be7fc7712da5e3901c445f361",
          "filename": "tools/console-models/msx2_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmsx2_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmsx2_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fmsx2_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "c82419ffd58caaa62b8dd23901f151e0a05c2871",
          "filename": "tools/console-models/msx2_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmsx2_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmsx2_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fmsx2_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "338cc76315ef1ad2a6306b8262e4fa37103bd4a8",
          "filename": "tools/console-models/msx_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmsx_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmsx_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fmsx_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "588ad252445134fea1fb5501dce5a034962626b2",
          "filename": "tools/console-models/msx_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmsx_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fmsx_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fmsx_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "944952e931cd6a94e4a846df96d87fe0c1c84a2d",
          "filename": "tools/console-models/n64.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fn64.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fn64.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fn64.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "e91ff1e4285c277ee3e1b194605acf87334c8f5a",
          "filename": "tools/console-models/n64_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fn64_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fn64_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fn64_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "1159d97aa01300757e3c4c9d26b7cf7cfebec82f",
          "filename": "tools/console-models/n64_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fn64_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fn64_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fn64_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "29cd189058133817be84fe81ffd8bca45acfa746",
          "filename": "tools/console-models/n64dd.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fn64dd.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fn64dd.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fn64dd.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "936af522d471a492ff5c136ef69c32f0b579c1ae",
          "filename": "tools/console-models/n64dd_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fn64dd_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fn64dd_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fn64dd_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "2ecc8e0795cba0f268ac09bd61c16fb4256c3b65",
          "filename": "tools/console-models/n64dd_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fn64dd_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fn64dd_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fn64dd_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "5b0a916f1ffce22fb64d2d7f3e78fc57cf2d2c4d",
          "filename": "tools/console-models/naomi.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fnaomi.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fnaomi.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fnaomi.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "76bbd6a4af477b8867ca12195724f9671dda6eba",
          "filename": "tools/console-models/naomi_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fnaomi_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fnaomi_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fnaomi_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "27725d93c83712df4a79d649b1eb63564b1258ce",
          "filename": "tools/console-models/naomi_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fnaomi_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fnaomi_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fnaomi_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "155ccffc3ee1653c1b9d034612b2c4ca53b08aa2",
          "filename": "tools/console-models/neogeo.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fneogeo.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fneogeo.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fneogeo.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "5775ef784135cb9af94b9defdcad7d6bcb586e63",
          "filename": "tools/console-models/neogeo_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fneogeo_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fneogeo_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fneogeo_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "9d41fd98d14ebd6a600cda82b3432ed7bc090795",
          "filename": "tools/console-models/neogeo_pocket.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fneogeo_pocket.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fneogeo_pocket.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fneogeo_pocket.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "36378ac24754b4adf4c60778720bd6f7439db095",
          "filename": "tools/console-models/neogeo_pocket_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fneogeo_pocket_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fneogeo_pocket_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fneogeo_pocket_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "571c800fffab45baf47da974adcf59c93d2a2f7e",
          "filename": "tools/console-models/neogeo_pocket_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fneogeo_pocket_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fneogeo_pocket_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fneogeo_pocket_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "0ecb6af0e6f74e90126ff8dac91970d4d13e4db3",
          "filename": "tools/console-models/neogeo_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fneogeo_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fneogeo_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fneogeo_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "d0db04391eb1a6b1ca704c083a8e1bf2f73ceed1",
          "filename": "tools/console-models/neogeocd.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fneogeocd.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fneogeocd.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fneogeocd.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "d3964fb256b96633e77d68380e356ed023a9ec25",
          "filename": "tools/console-models/neogeocd_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fneogeocd_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fneogeocd_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fneogeocd_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "26a278fbbd0edd106a39e649d20e1c4a16a39359",
          "filename": "tools/console-models/neogeocd_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fneogeocd_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fneogeocd_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fneogeocd_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "767ff48b85839ab64fb073632f3e779ae3b25b71",
          "filename": "tools/console-models/nes.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fnes.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fnes.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fnes.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "ed389f3dabeb21221e7b71d0cdd90629ad957283",
          "filename": "tools/console-models/nes_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fnes_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fnes_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fnes_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "f3e338c9a64de30a2928c64897e9319f3a2d3712",
          "filename": "tools/console-models/nes_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fnes_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fnes_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fnes_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "11e937f2bc0b1edccdf5c36ba15bda57b8f8246a",
          "filename": "tools/console-models/nintendo_ds.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fnintendo_ds.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fnintendo_ds.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fnintendo_ds.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "1a93eeafa1a0ac033bdfc6f311f6a1ceafc11912",
          "filename": "tools/console-models/nintendo_ds_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fnintendo_ds_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fnintendo_ds_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fnintendo_ds_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "80a5f315eb66a2fc7eb5f41a0fcb639de4e01cec",
          "filename": "tools/console-models/nintendo_ds_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fnintendo_ds_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fnintendo_ds_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fnintendo_ds_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "9b66c42ab1fcce6d7c1bddbb3bc1bba7a2ef55b3",
          "filename": "tools/console-models/odyssey2.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fodyssey2.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fodyssey2.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fodyssey2.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "9f4e150c1d277a714b1a55652e0cb46fe02d57c0",
          "filename": "tools/console-models/odyssey2_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fodyssey2_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fodyssey2_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fodyssey2_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "be72d624df2c2c083b0f398e42078a0e1a525fe4",
          "filename": "tools/console-models/odyssey2_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fodyssey2_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fodyssey2_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fodyssey2_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "29150fc0c3baeb949d199eea563c0c686981e3ec",
          "filename": "tools/console-models/oric.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Foric.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Foric.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Foric.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "da7656432a9df455226022dab6b293a54c096ba9",
          "filename": "tools/console-models/oric_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Foric_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Foric_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Foric_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "009c343cb46b9dfd98e5724f5c48b5d354a08526",
          "filename": "tools/console-models/oric_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Foric_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Foric_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Foric_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "f6c07e0de06f0bb5dc5e749f11ab19e31538590a",
          "filename": "tools/console-models/pc98.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpc98.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpc98.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fpc98.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "3568b7fb676c817de80ffb855145911f6aefbbb2",
          "filename": "tools/console-models/pc98_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpc98_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpc98_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fpc98_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "ae1e72c678694f8eec20212591848ccbd2f92e92",
          "filename": "tools/console-models/pc98_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpc98_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpc98_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fpc98_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "bbabd0198de40e86892b696b305553073efaffcd",
          "filename": "tools/console-models/pc_engine.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpc_engine.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpc_engine.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fpc_engine.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "935f4db1b55b80eba99ae86b9ac09161cf6c6930",
          "filename": "tools/console-models/pc_engine_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpc_engine_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpc_engine_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fpc_engine_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "e79e29daaecc01eca57962f978797f74b2642fc8",
          "filename": "tools/console-models/pc_engine_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpc_engine_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpc_engine_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fpc_engine_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "1ce707082f321c8a1f783a4feffec8f1c6155756",
          "filename": "tools/console-models/pcenginecd.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpcenginecd.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpcenginecd.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fpcenginecd.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "0ee92a7547627412f19b6de64f13e3a2bce20602",
          "filename": "tools/console-models/pcenginecd_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpcenginecd_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpcenginecd_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fpcenginecd_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "3d0fa7cd8521a167feb3fb95d50ff0e12a31d787",
          "filename": "tools/console-models/pcenginecd_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpcenginecd_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpcenginecd_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fpcenginecd_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "accb296662d127e6ca441ccb995eee1662b6be9e",
          "filename": "tools/console-models/pcfx.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpcfx.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpcfx.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fpcfx.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "e17453f18d9a8b297e46c04dae56e83d3e77df21",
          "filename": "tools/console-models/pcfx_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpcfx_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpcfx_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fpcfx_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "817700cb691ec5df5d50e41a3629abcf21d448d4",
          "filename": "tools/console-models/pcfx_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpcfx_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpcfx_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fpcfx_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "3ba25d13024b95c8738c3b0c45b072b0709ee6fe",
          "filename": "tools/console-models/pico8.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpico8.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpico8.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fpico8.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "41a177656ec3881c7756a46955ca4842a3c1d165",
          "filename": "tools/console-models/pico8_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpico8_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpico8_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fpico8_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "4f369dee2419e6d32daba1f5f80c5aaa96a98038",
          "filename": "tools/console-models/pico8_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpico8_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpico8_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fpico8_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "0836d4dd51fc31220f6d8bf4f855ac8b550db4b4",
          "filename": "tools/console-models/plugs.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fplugs.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fplugs.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fplugs.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "c33d03cf84b183e399d880741dd606e457bb44c3",
          "filename": "tools/console-models/plus4.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fplus4.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fplus4.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fplus4.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "447dfe9dbe5e6232f1e49d9b29aca5a416bca603",
          "filename": "tools/console-models/plus4_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fplus4_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fplus4_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fplus4_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "06361d2255f22180401d611b11341388bd4e816f",
          "filename": "tools/console-models/plus4_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fplus4_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fplus4_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fplus4_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "2be28d37187edddc0646e3cdafd6b0b77df5e4e9",
          "filename": "tools/console-models/pokemini.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpokemini.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpokemini.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fpokemini.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "e8d50402c5dab29b2bc012cc5ca08a9cc161de86",
          "filename": "tools/console-models/pokemini_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpokemini_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpokemini_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fpokemini_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "efe77f7c5886171160c1dae4aaf544a2792dcbf9",
          "filename": "tools/console-models/pokemini_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpokemini_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpokemini_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fpokemini_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "c2ffcdc966da9464774744397fd23212c522ec57",
          "filename": "tools/console-models/ps2.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fps2.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fps2.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fps2.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "9e47cdfc7229ce65a33f2af17a2fbe912ed3d091",
          "filename": "tools/console-models/ps2_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fps2_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fps2_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fps2_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "d363bfeda5834b61146de04fe4713a0345c9433a",
          "filename": "tools/console-models/ps2_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fps2_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fps2_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fps2_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "78db112132bb9656407056c3997a0dcf33325626",
          "filename": "tools/console-models/ps3.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fps3.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fps3.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fps3.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "444d258d98a153761c1af7c2a64378cb9614941e",
          "filename": "tools/console-models/ps3_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fps3_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fps3_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fps3_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "e2cbcb13e9c40ea81302af6c64a500106036e279",
          "filename": "tools/console-models/ps3_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fps3_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fps3_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fps3_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "838df5793fc4af1befd4e31407088b2576eeb0fb",
          "filename": "tools/console-models/ps4.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fps4.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fps4.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fps4.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "f32ebb5f9d7c0e23563d93833ed5727590ba09ac",
          "filename": "tools/console-models/ps4_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fps4_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fps4_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fps4_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "31433cbb6aa65c818603e428302327f155f815bd",
          "filename": "tools/console-models/ps4_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fps4_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fps4_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fps4_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "58502bd3364673515f6590fc45137193c14acc13",
          "filename": "tools/console-models/ps5.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fps5.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fps5.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fps5.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "d6fda1918a33aee92677f57e7352bd708c88059a",
          "filename": "tools/console-models/ps5_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fps5_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fps5_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fps5_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "b78c4959ecfd6292b1a54cedd3ae94a7156a8500",
          "filename": "tools/console-models/ps5_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fps5_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fps5_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fps5_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "6acce0fd5585689516aa9483609b97517283b017",
          "filename": "tools/console-models/psx.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpsx.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpsx.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fpsx.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "7622361432d47e5d75d20407e4363da9ec71e36f",
          "filename": "tools/console-models/psx_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpsx_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpsx_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fpsx_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "6b2071442ee6cb1298a63b374048deb7ab51e457",
          "filename": "tools/console-models/psx_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpsx_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fpsx_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fpsx_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "936671774b6923237dfd149de151db0646655b4e",
          "filename": "tools/console-models/saturn.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsaturn.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsaturn.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fsaturn.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "6c379c86c57d94ad935ef69aa9f247743abaa260",
          "filename": "tools/console-models/saturn_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsaturn_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsaturn_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fsaturn_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "365bfe8312bd9b4c6fb95bc28d43aae1c1f7e277",
          "filename": "tools/console-models/saturn_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsaturn_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsaturn_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fsaturn_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "7c252763f1acaf4bbc969b076b341f5206ab2596",
          "filename": "tools/console-models/sega32x.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsega32x.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsega32x.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fsega32x.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "9d060b3e8b6eb62e6fb9716fe675cec2200150b7",
          "filename": "tools/console-models/sega32x_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsega32x_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsega32x_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fsega32x_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "203bff430a7e2bf8fe329d795bab2df9b5d8e59a",
          "filename": "tools/console-models/sega32x_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsega32x_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsega32x_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fsega32x_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "939e19e188ee0e8ed9311d6fc343794a3656e9cd",
          "filename": "tools/console-models/sgb.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsgb.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsgb.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fsgb.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "361a7ef3014cc8655a190595953a202277c56954",
          "filename": "tools/console-models/sgb_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsgb_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsgb_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fsgb_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "a872a364f7828bf4132825b49c80a8e3632ebddf",
          "filename": "tools/console-models/sgb_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsgb_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsgb_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fsgb_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "5f2463d01c3a5859540d1be48d72406150004487",
          "filename": "tools/console-models/snes.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsnes.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsnes.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fsnes.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "a69ab99f791f44326570e173315df81a2e08fd1f",
          "filename": "tools/console-models/snes_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsnes_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsnes_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fsnes_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "1524f83237acf74b6f5f7f6d07d4d4a9c777c87e",
          "filename": "tools/console-models/snes_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsnes_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsnes_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fsnes_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "9ad45bb62d520b4bd2c6ec021b080ddc586edfdd",
          "filename": "tools/console-models/supervision.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsupervision.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsupervision.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fsupervision.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "92e605af1b788442a07717210a5e485a7224f537",
          "filename": "tools/console-models/supervision_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsupervision_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsupervision_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fsupervision_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "d42927b79471916f6ac4cf8839e143506f106176",
          "filename": "tools/console-models/supervision_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsupervision_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fsupervision_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fsupervision_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "7a2705821a8d040019dc99472d0f71b3d09a9708",
          "filename": "tools/console-models/threedo.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fthreedo.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fthreedo.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fthreedo.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "5d9e5685bf6d4cdbe6cf4b94f2df0bb86d42082a",
          "filename": "tools/console-models/threedo_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fthreedo_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fthreedo_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fthreedo_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "35784a4183694b0f4060cf807411c7a1ae348c41",
          "filename": "tools/console-models/threedo_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fthreedo_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fthreedo_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fthreedo_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "2a4f8e20412306c9a67c2f6b681a6a5e22737d24",
          "filename": "tools/console-models/ti99.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fti99.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fti99.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fti99.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "b168680b5814327089130e96a163bc4c2ec2a55b",
          "filename": "tools/console-models/ti99_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fti99_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fti99_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fti99_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "c80b9cacb38ebc19ac8402e4c0e7b1257a51ebdb",
          "filename": "tools/console-models/ti99_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fti99_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fti99_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fti99_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "3875cfa90a1f349de688ae9a6c9bbf8c6030c80e",
          "filename": "tools/console-models/vectrex.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fvectrex.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fvectrex.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fvectrex.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "d2b2920a61fd605fa5cd2d985d99b14248afe030",
          "filename": "tools/console-models/vectrex_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fvectrex_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fvectrex_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fvectrex_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "b1a9b0cb9fb1d20191fc89190f9e3ad3361fec5e",
          "filename": "tools/console-models/vectrex_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fvectrex_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fvectrex_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fvectrex_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "99fc830e776d4d83f066b63ceebe8ac8f08790ea",
          "filename": "tools/console-models/vic20.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fvic20.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fvic20.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fvic20.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "b4f2780b1c530d351cb0f5bc77e60207e028f10a",
          "filename": "tools/console-models/vic20_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fvic20_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fvic20_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fvic20_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "ea1f0ee4be84afa2ff8313d765e7dbc6300305a7",
          "filename": "tools/console-models/vic20_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fvic20_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fvic20_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fvic20_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "22ca2bae3f4c1e27c4508bf96be7a6e8a5b6184c",
          "filename": "tools/console-models/virtualboy.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fvirtualboy.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fvirtualboy.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fvirtualboy.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "511815392a99eb2d1d0b7f2fc3a55899896c6b77",
          "filename": "tools/console-models/virtualboy_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fvirtualboy_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fvirtualboy_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fvirtualboy_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "408d88e8cfe47e9c856ff079f99a1b0b5bd1c403",
          "filename": "tools/console-models/virtualboy_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fvirtualboy_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fvirtualboy_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fvirtualboy_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "94220915190dd999a7f94b3dca28173d2ac12932",
          "filename": "tools/console-models/wii.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fwii.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fwii.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fwii.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "f5147be1486255cb8986af892c90524396377348",
          "filename": "tools/console-models/wii_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fwii_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fwii_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fwii_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "11bff4b9e5835c060e86d0da893623aeeeb0ce07",
          "filename": "tools/console-models/wii_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fwii_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fwii_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fwii_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "614ab06e2b698280b17828524823f1df1573dd46",
          "filename": "tools/console-models/wiiu.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fwiiu.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fwiiu.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fwiiu.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "fbb3797d5de49d969bf079ebc3ac986fa593eb9c",
          "filename": "tools/console-models/wiiu_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fwiiu_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fwiiu_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fwiiu_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "f6d39a235626ddbc7d7514d48b7c04c11d43173d",
          "filename": "tools/console-models/wiiu_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fwiiu_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fwiiu_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fwiiu_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "fa345c7e7d55a135d350bf12690ed005fdd61fea",
          "filename": "tools/console-models/xbox.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fxbox.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fxbox.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fxbox.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "c66ffc794461d41d8941a863721dcf8d61569445",
          "filename": "tools/console-models/xbox360.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fxbox360.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fxbox360.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fxbox360.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "8d5fa136b09f1711566c0d9533ec4cde21838a19",
          "filename": "tools/console-models/xbox360_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fxbox360_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fxbox360_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fxbox360_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "6b3f0b8f29ec2e7690750657cfe68c397cf7ac9e",
          "filename": "tools/console-models/xbox360_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fxbox360_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fxbox360_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fxbox360_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "df2b603f8e9a6d5c32b90e235209d5318755fc28",
          "filename": "tools/console-models/xbox_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fxbox_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fxbox_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fxbox_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "baa9aaea600b3e350066763b131635b3b27b568e",
          "filename": "tools/console-models/xbox_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fxbox_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fxbox_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fxbox_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "9b2a53a5d1ab3bc52a002278ac9755093901f907",
          "filename": "tools/console-models/xegs.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fxegs.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fxegs.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fxegs.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "27ca84d7c3e9b7d79e3302911af887a67f0e4588",
          "filename": "tools/console-models/xegs_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fxegs_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fxegs_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fxegs_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "0ccc41ee56f9f4cf476a7c5af99192917ef91051",
          "filename": "tools/console-models/xegs_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fxegs_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fxegs_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fxegs_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "48f214434758018c2ff4cea03c254b4fc2fe581f",
          "filename": "tools/console-models/zx81.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fzx81.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fzx81.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fzx81.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "2a1fb82eed6deeba234899af94d67dbc1ace980d",
          "filename": "tools/console-models/zx81_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fzx81_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fzx81_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fzx81_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "bb74eddd0e36441a8f1a5d1c04954937b0bde704",
          "filename": "tools/console-models/zx81_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fzx81_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fzx81_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fzx81_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "9afac0d5782eab00583e67a28e6b6f83817f1d13",
          "filename": "tools/console-models/zx_spectrum.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fzx_spectrum.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fzx_spectrum.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fzx_spectrum.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "993bc696ebc94d892f8699d0086e0051fc6b933a",
          "filename": "tools/console-models/zx_spectrum_layout.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fzx_spectrum_layout.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fzx_spectrum_layout.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fzx_spectrum_layout.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        },
        {
          "sha": "c6571858a074994f40babc2bc31f84461b7ee6f5",
          "filename": "tools/console-models/zx_spectrum_print.py",
          "status": "added",
          "additions": 0,
          "deletions": 0,
          "changes": 0,
          "blob_url": "https://github.com/GeekRhapsody/OdysseyLauncher/blob/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fzx_spectrum_print.py",
          "raw_url": "https://github.com/GeekRhapsody/OdysseyLauncher/raw/bc1b85931871a65b6948764d00cd5182e2e873b8/tools%2Fconsole-models%2Fzx_spectrum_print.py",
          "contents_url": "https://api.github.com/repos/GeekRhapsody/OdysseyLauncher/contents/tools%2Fconsole-models%2Fzx_spectrum_print.py?ref=bc1b85931871a65b6948764d00cd5182e2e873b8"
        }
      ]
    }
  }
]
