mirror of
https://github.com/MyLuxy/overdrive.git
synced 2026-10-17 18:02:48 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2211b28c7c | ||
|
|
a337cd7493 | ||
|
|
3bac8bf76f | ||
|
|
ece84f340d | ||
|
|
a86383499d | ||
|
|
1deb8f95d9 | ||
|
|
2e6285e3c6 | ||
|
|
1a373b7f23 | ||
|
|
6ba1131b09 | ||
|
|
ce257ec2f2 | ||
|
|
214b992769 | ||
|
|
c3b284f1d5 | ||
|
|
9189fccfd1 | ||
|
|
9c6a598ca8 | ||
|
|
9ca7ad56b7 | ||
|
|
1f21d10db9 | ||
|
|
7dc1f1defe | ||
|
|
4ad7ccb277 | ||
|
|
87cc62099c | ||
|
|
db6f723fc1 | ||
|
|
041ae791b2 | ||
|
|
511865a47b | ||
|
|
d6ebf939a5 | ||
|
|
c4f0579cb7 | ||
|
|
fae429219d |
No files matched your search
@@ -90,7 +90,7 @@ body:
|
||||
id: logs
|
||||
attributes:
|
||||
label: Log
|
||||
description: If the app won't install or start, the relevant lines from `/data/shadowmount/debug.log` help a lot.
|
||||
description: For downloads and anything inside the app, copy `/data/ps5-react/PPSA99058/app.log` over FTP right after it happens. If a game won't install or start, the relevant lines from `/data/shadowmount/debug.log` help a lot.
|
||||
render: text
|
||||
|
||||
- type: checkboxes
|
||||
|
||||
+1
-1
@@ -110,7 +110,7 @@
|
||||
{ "id": "PPSA02484", "title": "Nioh Remastered The Complete Edition", "platform": "ps5", "providers": ["buzzheavier", "datanodes", "datavaults", "filekeeper", "mediafire", "vikingfile"], "direct": true, "developer": "KOEI TECMO GAMES CO., LTD.", "publisher": "KOEI TECMO GAMES CO., LTD.", "releaseDate": "2017-11-07", "year": 2017, "genres": ["Action", "Role Playing Games"], "features": ["Single-player", "Online multiplayer", "Co-op", "Cloud saves"], "metacritic": 84, "recommendations": 29676, "sizeGb": 74.356, "art": { "cover": "https://image.api.playstation.com/vulcan/ap/rnd/202011/0514/EGffra35ulXCwGK6VjlsmEMP.png?w=384&thumb=false", "coverFallbacks": ["https://cdn.cloudflare.steamstatic.com/steam/apps/485510/library_600x900.jpg"], "tint": "#66523c", "hero": "https://cdn.cloudflare.steamstatic.com/steam/apps/485510/library_hero.jpg", "logo": "https://cdn.cloudflare.steamstatic.com/steam/apps/485510/logo.png", "logoAspect": 1.373 }, "work": 40 },
|
||||
{ "id": "PPSA14404", "title": "METAL GEAR SOLID 3: Snake Eater \u2013 Master Collection", "platform": "ps5", "providers": ["1fichier", "buzzheavier", "datanodes", "datavaults", "filekeeper", "gofile", "mediafire", "vikingfile"], "direct": true, "developer": "KONAMI", "publisher": "KONAMI", "releaseDate": "2023-10-24", "year": 2023, "genres": ["Action"], "features": ["Single-player"], "recommendations": 5476, "sizeGb": 15.348, "art": { "cover": "https://image.api.playstation.com/vulcan/ap/rnd/202310/1313/f1d35dfa393b81cde32ec36b4c26fc2f27d005c975f32578.png?w=384&thumb=false", "coverFallbacks": ["https://cdn.cloudflare.steamstatic.com/steam/apps/2131650/library_600x900.jpg"], "tint": "#8f9777", "hero": "https://cdn.cloudflare.steamstatic.com/steam/apps/2131650/library_hero.jpg", "logo": "https://cdn.cloudflare.steamstatic.com/steam/apps/2131650/logo.png", "logoAspect": 1.778, "logoDark": true } },
|
||||
{ "id": "PPSA10249", "title": "VIEWFINDER", "platform": "ps5", "providers": ["1fichier", "buzzheavier", "datanodes", "datavaults", "filekeeper", "mediafire", "vikingfile"], "direct": true, "developer": "Sad Owl Studios", "publisher": "Thunderful Publishing", "releaseDate": "2023-07-18", "year": 2023, "genres": ["Adventure"], "features": ["Single-player"], "metacritic": 84, "recommendations": 12126, "sizeGb": 14.991, "art": { "cover": "https://image.api.playstation.com/vulcan/ap/rnd/202507/1711/19e6a1c5304b7a4fa3c03eaa726cad6c336b124359cf590a.png?w=384&thumb=false", "coverFallbacks": ["https://cdn.cloudflare.steamstatic.com/steam/apps/1382070/library_600x900.jpg", "https://archive.org/download/ps4-fpkg-collection-english-icons/CUSA36827.png"], "tint": "#5f7984", "hero": "https://cdn.cloudflare.steamstatic.com/steam/apps/1382070/library_hero.jpg", "logo": "https://cdn.cloudflare.steamstatic.com/steam/apps/1382070/logo.png", "logoAspect": 1.778 }, "work": 41 },
|
||||
{ "id": "PPSA20800", "title": "Metaphor ReFantazio", "platform": "ps5", "providers": ["1fichier", "buzzheavier", "datanodes", "datavaults", "filekeeper", "mediafire", "vikingfile"], "direct": true, "developer": "ATLUS", "publisher": "SEGA", "releaseDate": "2024-10-10", "year": 2024, "genres": ["Role Playing Games"], "features": ["Single-player", "DualSense features", "Cloud saves"], "metacritic": 92, "recommendations": 25425, "sizeGb": 71.483, "art": { "cover": "https://image.api.playstation.com/vulcan/ap/rnd/202404/2608/d08c4e27d5bef8721c62ece6cc74be5037b6423c727e454b.png?w=384&thumb=false", "coverFallbacks": ["https://image.api.playstation.com/vulcan/ap/rnd/202512/1100/a36cb300b6e41b5b1b816c6043a15caf3aed2e375f5e6966.png?w=384&thumb=false", "https://image.api.playstation.com/vulcan/ap/rnd/202405/2406/672cc5f97e84c564649093d4f6a3726f328e0de1814ab2e0.png?w=384&thumb=false", "https://cdn.cloudflare.steamstatic.com/steam/apps/2679460/library_600x900.jpg"], "tint": "#51585e", "hero": "https://cdn.cloudflare.steamstatic.com/steam/apps/2679460/library_hero.jpg", "logo": "https://cdn.cloudflare.steamstatic.com/steam/apps/2679460/logo.png", "logoAspect": 1.778, "logoDark": true } },
|
||||
{ "id": "PPSA20800", "title": "Metaphor ReFantazio", "platform": "ps5", "providers": ["1fichier", "buzzheavier", "datanodes", "datavaults", "filekeeper", "vikingfile"], "direct": true, "developer": "ATLUS", "publisher": "SEGA", "releaseDate": "2024-10-10", "year": 2024, "genres": ["Role Playing Games"], "features": ["Single-player", "DualSense features", "Cloud saves"], "metacritic": 92, "recommendations": 25425, "sizeGb": 71.483, "art": { "cover": "https://image.api.playstation.com/vulcan/ap/rnd/202404/2608/d08c4e27d5bef8721c62ece6cc74be5037b6423c727e454b.png?w=384&thumb=false", "coverFallbacks": ["https://image.api.playstation.com/vulcan/ap/rnd/202512/1100/a36cb300b6e41b5b1b816c6043a15caf3aed2e375f5e6966.png?w=384&thumb=false", "https://image.api.playstation.com/vulcan/ap/rnd/202405/2406/672cc5f97e84c564649093d4f6a3726f328e0de1814ab2e0.png?w=384&thumb=false", "https://cdn.cloudflare.steamstatic.com/steam/apps/2679460/library_600x900.jpg"], "tint": "#51585e", "hero": "https://cdn.cloudflare.steamstatic.com/steam/apps/2679460/library_hero.jpg", "logo": "https://cdn.cloudflare.steamstatic.com/steam/apps/2679460/logo.png", "logoAspect": 1.778, "logoDark": true } },
|
||||
{ "id": "PPSA14396", "title": "METAL GEAR SOLID 2: Sons of Liberty \u2013 Master Collection", "platform": "ps5", "providers": ["1fichier", "buzzheavier", "datanodes", "datavaults", "filekeeper", "gofile", "mediafire", "vikingfile"], "direct": true, "developer": "KONAMI", "publisher": "KONAMI", "releaseDate": "2023-10-24", "year": 2023, "genres": ["Action"], "features": ["Single-player"], "recommendations": 3601, "sizeGb": 11.468, "art": { "cover": "https://image.api.playstation.com/vulcan/ap/rnd/202310/1013/60a3367159f2b8c636677da1b7c07f7c9159b1e4edac4517.png?w=384&thumb=false", "coverFallbacks": ["https://cdn.cloudflare.steamstatic.com/steam/apps/2131640/library_600x900.jpg"], "tint": "#c3c6c2", "hero": "https://cdn.cloudflare.steamstatic.com/steam/apps/2131640/library_hero.jpg", "logo": "https://cdn.cloudflare.steamstatic.com/steam/apps/2131640/logo.png", "logoAspect": 1.778, "logoDark": true } },
|
||||
{ "id": "PPSA13427", "title": "Horizon Zero Dawn Remastered", "platform": "ps5", "providers": ["1fichier", "datanodes", "datavaults", "filekeeper", "urlvanish", "vikingfile"], "direct": true, "developer": "Guerrilla Games", "publisher": "PlayStation Publishing LLC", "releaseDate": "2024-10-31", "year": 2024, "genres": ["Role Playing Games", "Action"], "features": ["Single-player", "DualSense features", "Cloud saves"], "recommendations": 6944, "sizeGb": 135.024, "art": { "cover": "https://cdn.cloudflare.steamstatic.com/steam/apps/2561580/library_600x900.jpg", "coverFallbacks": ["https://image.api.playstation.com/vulcan/ap/rnd/202409/2716/16b33fa9a5c7285ba86a035b4a1c5f8eb430b407eae35ffd.png?w=384&thumb=false", "https://image.api.playstation.com/vulcan/ap/rnd/202010/0221/vC7trMorHJgbImp8PCQvpI0p.png?w=384&thumb=false", "https://cover.spectrumlibrary.online/COVER/PPSA13427.png"], "tint": "#8b90b0", "hero": "https://cdn.cloudflare.steamstatic.com/steam/apps/2561580/library_hero.jpg", "logo": "https://cdn.cloudflare.steamstatic.com/steam/apps/2561580/logo.png", "logoAspect": 1.778 }, "work": 42, "region": "EUR" },
|
||||
{ "id": "PPSA07382", "title": "Flintlock The Siege of Dawn", "platform": "ps5", "providers": ["1fichier", "buzzheavier", "datanodes", "datavaults", "filekeeper", "gofile", "mediafire", "vikingfile"], "direct": true, "developer": "A44 Games", "publisher": "Kepler Interactive", "releaseDate": "2024-07-17", "year": 2024, "genres": ["Action", "Role Playing Games", "Adventure"], "features": ["Single-player", "DualSense features", "Cloud saves"], "recommendations": 1370, "sizeGb": 24.584, "art": { "cover": "https://image.api.playstation.com/vulcan/ap/rnd/202406/2009/6832b0713642518db72cf6f6187756d2854a1b537c380e80.png?w=384&thumb=false", "coverFallbacks": ["https://image.api.playstation.com/vulcan/ap/rnd/202405/1513/71e3d103bc17be588cbff6cf08cbd3914ff4f4ffcd490d3f.png?w=384&thumb=false", "https://cdn.cloudflare.steamstatic.com/steam/apps/1832040/library_600x900.jpg"], "tint": "#736f72", "hero": "https://cdn.cloudflare.steamstatic.com/steam/apps/1832040/library_hero.jpg", "logo": "https://cdn.cloudflare.steamstatic.com/steam/apps/1832040/logo.png", "logoAspect": 1.767 } },
|
||||
|
||||
@@ -1 +1 @@
|
||||
[{"id":"r806d5463d107","format":"ffpfsc","filename":"PPSA20800 Metaphor ReFantazio.ffpfsc","url":"https://archive.org/download/PPSA20800-Metaphor-ReFantazio/PPSA20800%20Metaphor%20ReFantazio.ffpfsc","requiresBrowser":false,"sizeBytes":47972220928,"version":"","category":"game","provider":"Archive.org","source":"orbit","flow":"direct","label":"","record":"orbit-19","health":"ok","unavailableReason":"Source unavailable: HTTP 401. Choose another source."},{"id":"rf0ec322e32c6","format":"binary","filename":"PPSA20800-game-unknown-rf0ec322e32c6.binary","url":"https://buzzheavier.com/m9ynp7ok2p5f","browserUrl":"https://buzzheavier.com/m9ynp7ok2p5f","requiresBrowser":true,"sizeBytes":null,"version":"","category":"game","provider":"Buzzheavier","source":"pegasus-0","flow":"buzzheavier","label":"USA 8.xx Base - Buzzheavier part 1","volume":"part 1","archivePassword":"DLPSGAME.COM","record":"pegasus-0-394"},{"id":"rf3a4632bbd82","format":"binary","filename":"PPSA20800-game-unknown-rf3a4632bbd82.binary","url":"https://buzzheavier.com/5lhocbnj0siv","browserUrl":"https://buzzheavier.com/5lhocbnj0siv","requiresBrowser":true,"sizeBytes":null,"version":"","category":"game","provider":"Buzzheavier","source":"pegasus-0","flow":"buzzheavier","label":"USA 8.xx Base - Buzzheavier part 2","volume":"part 2","archivePassword":"DLPSGAME.COM","record":"pegasus-0-394"},{"id":"r133adf740782","format":"binary","filename":"PPSA20800-game-unknown-r133adf740782.binary","url":"https://buzzheavier.com/ds3lh8cmfx3b","browserUrl":"https://buzzheavier.com/ds3lh8cmfx3b","requiresBrowser":true,"sizeBytes":null,"version":"","category":"game","provider":"Buzzheavier","source":"pegasus-0","flow":"buzzheavier","label":"USA 8.xx Base - Buzzheavier part 3","volume":"part 3","archivePassword":"DLPSGAME.COM","record":"pegasus-0-394"},{"id":"r1a55e1970e76","format":"binary","filename":"PPSA20800-game-unknown-r1a55e1970e76.binary","url":"https://buzzheavier.com/kxx4bwavhi3u","browserUrl":"https://buzzheavier.com/kxx4bwavhi3u","requiresBrowser":true,"sizeBytes":null,"version":"","category":"game","provider":"Buzzheavier","source":"pegasus-0","flow":"buzzheavier","label":"USA 8.xx Base - Buzzheavier part 4","volume":"part 4","archivePassword":"DLPSGAME.COM","record":"pegasus-0-394"},{"id":"rc1ee2f65c843","format":"binary","filename":"PPSA20800-game-unknown-rc1ee2f65c843.binary","url":"https://buzzheavier.com/5eg41ipiejkt","browserUrl":"https://buzzheavier.com/5eg41ipiejkt","requiresBrowser":true,"sizeBytes":null,"version":"","category":"game","provider":"Buzzheavier","source":"pegasus-0","flow":"buzzheavier","label":"USA 8.xx Base - Buzzheavier part 5","volume":"part 5","archivePassword":"DLPSGAME.COM","record":"pegasus-0-394"},{"id":"r86f956049e6c","format":"binary","filename":"PPSA20800-game-unknown-r86f956049e6c.binary","url":"https://buzzheavier.com/i168unn1ohq5","browserUrl":"https://buzzheavier.com/i168unn1ohq5","requiresBrowser":true,"sizeBytes":null,"version":"","category":"game","provider":"Buzzheavier","source":"pegasus-0","flow":"buzzheavier","label":"USA 8.xx Base - Buzzheavier part 6","volume":"part 6","archivePassword":"DLPSGAME.COM","record":"pegasus-0-394"},{"id":"r628e341fc7c7","format":"binary","filename":"PPSA20800-game-unknown-r628e341fc7c7.binary","url":"https://buzzheavier.com/ubuavfaczy4j","browserUrl":"https://buzzheavier.com/ubuavfaczy4j","requiresBrowser":true,"sizeBytes":null,"version":"","category":"game","provider":"Buzzheavier","source":"pegasus-0","flow":"buzzheavier","label":"USA 8.xx Base - Buzzheavier part 7","volume":"part 7","archivePassword":"DLPSGAME.COM","record":"pegasus-0-394"},{"id":"r08e5f02357d5","format":"binary","filename":"PPSA20800-game-unknown-r08e5f02357d5.binary","url":"https://buzzheavier.com/iuxi6cde6u06","browserUrl":"https://buzzheavier.com/iuxi6cde6u06","requiresBrowser":true,"sizeBytes":null,"version":"","category":"game","provider":"Buzzheavier","source":"pegasus-0","flow":"buzzheavier","label":"USA 8.xx Base - Buzzheavier part 8","volume":"part 8","archivePassword":"DLPSGAME.COM","record":"pegasus-0-394"},{"id":"r69f40d1b050f","format":"binary","filename":"PPSA20800-game-unknown-r69f40d1b050f.binary","url":"https://www.mediafire.com/file/xfqrpzrcari2pk2/[DLPSGAME.COM]-PPSA20800.part1.rar/file","browserUrl":"https://www.mediafire.com/file/xfqrpzrcari2pk2/[DLPSGAME.COM]-PPSA20800.part1.rar/file","requiresBrowser":true,"sizeBytes":null,"version":"","category":"game","provider":"MediaFire","source":"pegasus-0","flow":"mediafire","label":"USA 8.xx Base - Mediafire part 1","volume":"part 1","archivePassword":"DLPSGAME.COM","record":"pegasus-0-394"},{"id":"rea147be013e1","format":"binary","filename":"PPSA20800-game-unknown-rea147be013e1.binary","url":"https://www.mediafire.com/file/a95ijaqnfg5p3g9/[DLPSGAME.COM]-PPSA20800.part2.rar/file","browserUrl":"https://www.mediafire.com/file/a95ijaqnfg5p3g9/[DLPSGAME.COM]-PPSA20800.part2.rar/file","requiresBrowser":true,"sizeBytes":null,"version":"","category":"gamLine truncated
|
||||
[{"id":"r806d5463d107","format":"ffpfsc","filename":"PPSA20800 Metaphor ReFantazio.ffpfsc","url":"https://archive.org/download/PPSA20800-Metaphor-ReFantazio/PPSA20800%20Metaphor%20ReFantazio.ffpfsc","requiresBrowser":false,"sizeBytes":47972220928,"version":"","category":"game","provider":"Archive.org","source":"orbit","flow":"direct","label":"","record":"orbit-19","health":"ok","unavailableReason":"Source unavailable: HTTP 401. Choose another source."},{"id":"rf0ec322e32c6","format":"binary","filename":"PPSA20800-game-unknown-rf0ec322e32c6.binary","url":"https://buzzheavier.com/m9ynp7ok2p5f","browserUrl":"https://buzzheavier.com/m9ynp7ok2p5f","requiresBrowser":true,"sizeBytes":null,"version":"","category":"game","provider":"Buzzheavier","source":"pegasus-0","flow":"buzzheavier","label":"USA 8.xx Base - Buzzheavier part 1","volume":"part 1","archivePassword":"DLPSGAME.COM","record":"pegasus-0-394"},{"id":"rf3a4632bbd82","format":"binary","filename":"PPSA20800-game-unknown-rf3a4632bbd82.binary","url":"https://buzzheavier.com/5lhocbnj0siv","browserUrl":"https://buzzheavier.com/5lhocbnj0siv","requiresBrowser":true,"sizeBytes":null,"version":"","category":"game","provider":"Buzzheavier","source":"pegasus-0","flow":"buzzheavier","label":"USA 8.xx Base - Buzzheavier part 2","volume":"part 2","archivePassword":"DLPSGAME.COM","record":"pegasus-0-394"},{"id":"r133adf740782","format":"binary","filename":"PPSA20800-game-unknown-r133adf740782.binary","url":"https://buzzheavier.com/ds3lh8cmfx3b","browserUrl":"https://buzzheavier.com/ds3lh8cmfx3b","requiresBrowser":true,"sizeBytes":null,"version":"","category":"game","provider":"Buzzheavier","source":"pegasus-0","flow":"buzzheavier","label":"USA 8.xx Base - Buzzheavier part 3","volume":"part 3","archivePassword":"DLPSGAME.COM","record":"pegasus-0-394"},{"id":"r1a55e1970e76","format":"binary","filename":"PPSA20800-game-unknown-r1a55e1970e76.binary","url":"https://buzzheavier.com/kxx4bwavhi3u","browserUrl":"https://buzzheavier.com/kxx4bwavhi3u","requiresBrowser":true,"sizeBytes":null,"version":"","category":"game","provider":"Buzzheavier","source":"pegasus-0","flow":"buzzheavier","label":"USA 8.xx Base - Buzzheavier part 4","volume":"part 4","archivePassword":"DLPSGAME.COM","record":"pegasus-0-394"},{"id":"rc1ee2f65c843","format":"binary","filename":"PPSA20800-game-unknown-rc1ee2f65c843.binary","url":"https://buzzheavier.com/5eg41ipiejkt","browserUrl":"https://buzzheavier.com/5eg41ipiejkt","requiresBrowser":true,"sizeBytes":null,"version":"","category":"game","provider":"Buzzheavier","source":"pegasus-0","flow":"buzzheavier","label":"USA 8.xx Base - Buzzheavier part 5","volume":"part 5","archivePassword":"DLPSGAME.COM","record":"pegasus-0-394"},{"id":"r86f956049e6c","format":"binary","filename":"PPSA20800-game-unknown-r86f956049e6c.binary","url":"https://buzzheavier.com/i168unn1ohq5","browserUrl":"https://buzzheavier.com/i168unn1ohq5","requiresBrowser":true,"sizeBytes":null,"version":"","category":"game","provider":"Buzzheavier","source":"pegasus-0","flow":"buzzheavier","label":"USA 8.xx Base - Buzzheavier part 6","volume":"part 6","archivePassword":"DLPSGAME.COM","record":"pegasus-0-394"},{"id":"r628e341fc7c7","format":"binary","filename":"PPSA20800-game-unknown-r628e341fc7c7.binary","url":"https://buzzheavier.com/ubuavfaczy4j","browserUrl":"https://buzzheavier.com/ubuavfaczy4j","requiresBrowser":true,"sizeBytes":null,"version":"","category":"game","provider":"Buzzheavier","source":"pegasus-0","flow":"buzzheavier","label":"USA 8.xx Base - Buzzheavier part 7","volume":"part 7","archivePassword":"DLPSGAME.COM","record":"pegasus-0-394"},{"id":"r08e5f02357d5","format":"binary","filename":"PPSA20800-game-unknown-r08e5f02357d5.binary","url":"https://buzzheavier.com/iuxi6cde6u06","browserUrl":"https://buzzheavier.com/iuxi6cde6u06","requiresBrowser":true,"sizeBytes":null,"version":"","category":"game","provider":"Buzzheavier","source":"pegasus-0","flow":"buzzheavier","label":"USA 8.xx Base - Buzzheavier part 8","volume":"part 8","archivePassword":"DLPSGAME.COM","record":"pegasus-0-394"},{"id":"r60bd63dc4751","format":"binary","filename":"PPSA20800-game-unknown-r60bd63dc4751.binary","url":"https://buzzheavier.com/p6swerx5etln","browserUrl":"https://buzzheavier.com/p6swerx5etln","requiresBrowser":true,"sizeBytes":null,"version":"","category":"game","provider":"Buzzheavier","source":"pegasus-0","flow":"buzzheavier","label":"USA 8.xx Base - Buzzheavier part 9","volume":"part 9","archivePassword":"DLPSGAME.COM","record":"pegasus-0-394"},{"id":"r1f226708a6dc","format":"binary","filename":"PPSA20800-game-unknown-r1f226708a6dc.binary","url":"https://vikingfile.com/f/qFEE5wXXzU","browserUrl":"https://vikingfile.com/f/qFEE5wXXzU","requiresBrowser":true,"sizeBytes":null,"version":"","category":"game","provider":"VikingFile","source":"pegasus-0","flow":"vikingfile","label":"USA 8.xx DLC (@Pkykzhack) - Vikingfile","archivePassword":"DLPSGAME.COM","record":"pegasus-0-394"},{"id":"r2885d3b2d28bLine truncated
|
||||
@@ -9,6 +9,7 @@ import { formatNumber2 } from '../i18n/core.js';
|
||||
import { useT } from '../i18n/index.js';
|
||||
import { downloadQueue, driveName, storageRoots } from '../state/downloads.js';
|
||||
import { formatBytes } from '../state/size.js';
|
||||
import { useSpeedLimit } from '../state/speed-limit.js';
|
||||
import { ProgressBar } from '../ui/ProgressBar.jsx';
|
||||
import { FileSystem, ScrollView, Text, View } from '@ps5-react/core';
|
||||
|
||||
@@ -38,6 +39,7 @@ import { FileSystem, ScrollView, Text, View } from '@ps5-react/core';
|
||||
function Activity({ onFocus }) {
|
||||
const t2 = useT();
|
||||
const { active: active2, speed, left, connections, peak, low, mean } = useActivity();
|
||||
const limit = useSpeedLimit();
|
||||
return /* @__PURE__ */ (0, jsx)(Block, { title: t2("downloads.panel.activity"), children: /* @__PURE__ */ (0, jsxs)(View, { focusable: true, focusKey: PANEL_KEY, onFocus, style: (_state) => [{ gap: 18, borderRadius: 12, padding: 12 }, _state.focused ? { backgroundColor: "#212125" } : null], children: [
|
||||
/* @__PURE__ */ (0, jsx)(Text, { style: { fontFamily: "Inter-Regular", fontSize: 45, fontWeight: 600, color: "#f2f2f3", lineHeight: 54, letterSpacing: -1.13 }, children: active2 ? formatSpeed(speed) : t2("downloads.panel.idle") }),
|
||||
/* @__PURE__ */ (0, jsxs)(View, { style: { gap: 9 }, children: [
|
||||
@@ -52,7 +54,9 @@ import { FileSystem, ScrollView, Text, View } from '@ps5-react/core';
|
||||
/* @__PURE__ */ (0, jsx)(Figure, { label: t2("downloads.panel.peak"), value: peak ? formatSpeed(peak) : "\u2014" }),
|
||||
/* @__PURE__ */ (0, jsx)(Figure, { label: t2("downloads.panel.low"), value: low ? formatSpeed(low) : "\u2014" }),
|
||||
/* @__PURE__ */ (0, jsx)(Figure, { label: t2("downloads.panel.average"), value: mean ? formatSpeed(mean) : "\u2014" }),
|
||||
/* @__PURE__ */ (0, jsx)(Figure, { label: t2("downloads.panel.connections"), value: connections ? formatNumber2(connections) : "\u2014" })
|
||||
/* @__PURE__ */ (0, jsx)(Figure, { label: t2("downloads.panel.connections"), value: connections ? formatNumber2(connections) : "\u2014" }),
|
||||
// Why a download goes no faster: the cap chosen in Settings (issue #15).
|
||||
/* @__PURE__ */ (0, jsx)(Figure, { label: t2("settings.speedLimit"), value: limit ? t2("units.speed", { n: String(limit) }) : t2("settings.speedUnlimited") })
|
||||
] }),
|
||||
/* @__PURE__ */ (0, jsx)(Text, { style: [{ fontFamily: "Inter-Regular", fontSize: 21, color: "#8a8a94", lineHeight: 30 }, INNER], children: awake() ? t2("downloads.panel.awake") : " " })
|
||||
] }) });
|
||||
|
||||
@@ -18,6 +18,8 @@ import { t } from '../i18n/core.js';
|
||||
// The engine's heap had no room for the extraction; a fresh launch frees it.
|
||||
[/not enough memory to extract/i, "noMemory", "reopenApp"],
|
||||
[/archive password/i, "archivePassword", "otherSource"],
|
||||
[/not a game file/i, "notGameFile", "otherSource"],
|
||||
[/fake PKGs install only/i, "pkgFirmware", "otherSource"],
|
||||
// A set's last volume ends where the archive goes on, or its first is not where it starts: the source lists
|
||||
// too few volumes, and downloading them again fails the same way. A lone archive the worker cannot open
|
||||
// fails with the same words; that one is downloaded again.
|
||||
|
||||
@@ -67,9 +67,13 @@ import { DownloadFormats, Text, View, motion } from '@ps5-react/core';
|
||||
const lacking = download.status === "idle" && need && free[storage] !== null && free[storage] < need + MARGIN2;
|
||||
const stale = ["paused", "failed"].includes(download.status) && STALE_PARTIAL.test(cause(error2));
|
||||
const status = lacking ? "no-space" : stale ? "stale" : download.status;
|
||||
// A new download first shows what the chosen source is and how to use it (DownloadTipsDialog);
|
||||
// resuming, installing or pausing one already queued goes straight on.
|
||||
const begin = () => {
|
||||
if (stale) restart();
|
||||
else if (!lacking) download.advance();
|
||||
else if (lacking) return;
|
||||
else if (status === "idle" && release) onRequestDialog({ kind: "tips", game, release, submit: () => download.advance(), changeSource: () => chooseSource() });
|
||||
else download.advance();
|
||||
};
|
||||
const { openEdition, focusLater, push } = useNavActions();
|
||||
const editions = editionsOf(game);
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
// Shown before a new download starts: what the chosen source is, what to watch for with it, and
|
||||
// tips for choosing one behind a dropdown, so the provider's browser page opens only once the player
|
||||
// has read it and pressed Continue.
|
||||
|
||||
import '../assets/Inter-Regular.ttf';
|
||||
import { Fragment, jsx, jsxs } from 'react/jsx-runtime';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { describeSource, isDlc, requiredFirmware } from './sources.js';
|
||||
import { useT } from '../i18n/index.js';
|
||||
import { sourceKind } from '../state/download-sources.js';
|
||||
import { isPs5Package } from '../state/firmware.js';
|
||||
import { useNavActions } from '../state/nav.jsx';
|
||||
import { isPatchOnly } from '../state/release-sets.js';
|
||||
import { Button2 } from '../ui/Button.jsx';
|
||||
import { Cover } from '../ui/Cover.jsx';
|
||||
import { FocusRing } from '../ui/FocusRing.jsx';
|
||||
import { Icon } from '../ui/Icon.jsx';
|
||||
import { colors } from '../ui/colors.js';
|
||||
import { FocusScope, Text, View, useIsFocused } from '@ps5-react/core';
|
||||
|
||||
var LABEL = { fontFamily: "Inter-Regular", fontSize: 19, fontWeight: 600, color: colors.muted, letterSpacing: 1.5 };
|
||||
var BODY = { fontFamily: "Inter-Regular", fontSize: 22, color: colors.muted, lineHeight: 31 };
|
||||
var TINT = { warn: [colors.warn, "#fde68a1f"], info: [colors.info, "#a5f3fc1a"] };
|
||||
var TIPS = ["firmware", "fullGame", "top", "retry"];
|
||||
// Text wraps only at a set width here, never at flex: the dialog's 1200 px inside, less a note's
|
||||
// 44 px badge and its gap, or half of it (less the 44 px column gap) for a tip.
|
||||
var NOTE_TEXT = 1200 - 44 - 18;
|
||||
var TIP_WIDTH = (1200 - 44) / 2;
|
||||
var TIP_TEXT = TIP_WIDTH - 9 - 16;
|
||||
// The notes that apply to this source, most important first. The firmware comes from its label,
|
||||
// never from the console, which can be told a raised version.
|
||||
function notesFor(release) {
|
||||
const notes = [];
|
||||
if (isDlc(release)) notes.push({ tone: "warn", icon: "package", key: "dlc" });
|
||||
if (isPatchOnly(release)) notes.push({ tone: "warn", icon: "fileMinus", key: "patch" });
|
||||
if (isPs5Package(release)) notes.push({ tone: "warn", icon: "alert", key: "package" });
|
||||
let kind = "direct";
|
||||
try {
|
||||
kind = sourceKind(release.parts?.[0]?.sources[0] ?? release);
|
||||
} catch {
|
||||
}
|
||||
if (kind === "vikingfile") notes.push({ tone: "info", icon: "globe", key: "browserViking" });
|
||||
else if (kind === "browser" || kind === "datanodes") notes.push({ tone: "info", icon: "globe", key: "browserOther" });
|
||||
const need = requiredFirmware(release);
|
||||
if (need !== null && !isDlc(release)) notes.push({ tone: "info", icon: "cpu", key: "firmware", values: { need: `${need}.xx` } });
|
||||
return notes;
|
||||
}
|
||||
function Note({ tone, icon, text }) {
|
||||
const [ink, wash] = TINT[tone];
|
||||
return /* @__PURE__ */ (0, jsxs)(View, { style: { flexDirection: "row", alignItems: "flex-start", gap: 18 }, children: [
|
||||
/* @__PURE__ */ (0, jsx)(View, { style: { width: 44, height: 44, borderRadius: 12, alignItems: "center", justifyContent: "center", backgroundColor: wash }, children: /* @__PURE__ */ (0, jsx)(Icon, { name: icon, size: 24, color: ink }) }),
|
||||
/* @__PURE__ */ (0, jsx)(Text, { style: [BODY, { width: NOTE_TEXT, paddingTop: 6 }], children: text })
|
||||
] });
|
||||
}
|
||||
function Tip({ title, body }) {
|
||||
// The title on its own line and the line under it: short, and wrapping cleanly in any language.
|
||||
return /* @__PURE__ */ (0, jsxs)(View, { style: { width: TIP_WIDTH, flexDirection: "row", alignItems: "flex-start", gap: 16 }, children: [
|
||||
/* @__PURE__ */ (0, jsx)(View, { style: { width: 9, height: 9, borderRadius: 5, marginTop: 12, backgroundColor: colors.muted } }),
|
||||
/* @__PURE__ */ (0, jsxs)(View, { style: { gap: 2 }, children: [
|
||||
/* @__PURE__ */ (0, jsx)(Text, { style: [BODY, { width: TIP_TEXT, fontWeight: 600, color: colors.fg }], children: title }),
|
||||
/* @__PURE__ */ (0, jsx)(Text, { style: [BODY, { width: TIP_TEXT }], children: body })
|
||||
] })
|
||||
] });
|
||||
}
|
||||
function ToggleFace({ open }) {
|
||||
const t2 = useT();
|
||||
const focused = useIsFocused();
|
||||
return /* @__PURE__ */ (0, jsxs)(Fragment, { children: [
|
||||
/* @__PURE__ */ (0, jsx)(FocusRing, {}),
|
||||
/* @__PURE__ */ (0, jsx)(View, { style: { width: 44, height: 44, borderRadius: 12, alignItems: "center", justifyContent: "center", backgroundColor: colors.surface2 }, children: /* @__PURE__ */ (0, jsx)(Icon, { name: "lightbulb", size: 24, color: colors.fg }) }),
|
||||
/* @__PURE__ */ (0, jsx)(Text, { style: { flex: 1, fontFamily: "Inter-Regular", fontSize: 23, fontWeight: 600, color: colors.fg }, children: t2("tips.toggle") }),
|
||||
/* @__PURE__ */ (0, jsx)(Icon, { name: open ? "chevronUp" : "chevronDown", size: 26, color: focused ? colors.fg : colors.muted })
|
||||
] });
|
||||
}
|
||||
function DownloadTipsDialog({ game, release, submit, changeSource, onClose }) {
|
||||
const t2 = useT();
|
||||
const [open, setOpen] = (0, useState)(false);
|
||||
const { focusLater } = useNavActions();
|
||||
// The page moves focus with focusLater, retried until it lands: one still pending (back to the
|
||||
// Download button, from a sheet that just closed) took focus off this dialog on the console.
|
||||
// A newer request replaces it, so asking here is what makes the tips row win.
|
||||
(0, useEffect)(() => {
|
||||
focusLater("tips:toggle");
|
||||
}, []);
|
||||
const { name, detail: detail2, size } = describeSource(release);
|
||||
const notes = notesFor(release);
|
||||
const proceed = () => {
|
||||
onClose();
|
||||
submit();
|
||||
};
|
||||
// The source list replaces this dialog in place, as the options sheet's "next" items do: closing
|
||||
// first would send focus back to the Download button underneath.
|
||||
const pick = () => {
|
||||
changeSource();
|
||||
};
|
||||
const cancel = () => {
|
||||
onClose();
|
||||
return true;
|
||||
};
|
||||
return (
|
||||
// A modal like the update dialog: the page underneath takes no input until it closes.
|
||||
/* @__PURE__ */ (0, jsx)(FocusScope, { trap: true, autoFocus: true, onBack: cancel, onAction: () => true, children: /* @__PURE__ */ (0, jsx)(View, { style: { position: "absolute", top: 0, right: 0, bottom: 0, left: 0, zIndex: 50, alignItems: "center", justifyContent: "center", backgroundColor: "#000000d9" }, children: /* @__PURE__ */ (0, jsxs)(View, { style: { width: 1320, gap: 26, borderRadius: 24, borderWidth: 2, borderColor: colors.line, backgroundColor: colors.surface, paddingHorizontal: 60, paddingTop: 44, paddingBottom: 40 }, children: [
|
||||
/* @__PURE__ */ (0, jsxs)(View, { style: { gap: 6 }, children: [
|
||||
/* @__PURE__ */ (0, jsx)(Text, { style: { fontFamily: "Inter-Regular", fontSize: 19, fontWeight: 600, color: colors.subtle, letterSpacing: 3 }, children: t2("tips.eyebrow").toLocaleUpperCase() }),
|
||||
/* @__PURE__ */ (0, jsx)(Text, { style: { fontFamily: "Inter-Regular", fontSize: 44, fontWeight: 600, color: colors.fg, letterSpacing: -1.1, lineHeight: 52 }, children: t2("tips.title") })
|
||||
] }),
|
||||
/* @__PURE__ */ (0, jsxs)(View, { style: { gap: 14 }, children: [
|
||||
/* @__PURE__ */ (0, jsx)(Text, { style: LABEL, children: t2("tips.downloading").toLocaleUpperCase() }),
|
||||
/* @__PURE__ */ (0, jsxs)(View, { style: { flexDirection: "row", alignItems: "center", gap: 24, borderRadius: 16, backgroundColor: colors.surface2, paddingHorizontal: 22, paddingVertical: 18 }, children: [
|
||||
/* @__PURE__ */ (0, jsx)(Cover, { game, label: false, style: { width: 78, height: 78, borderRadius: 10, overflow: "hidden" } }),
|
||||
/* @__PURE__ */ (0, jsxs)(View, { style: { flex: 1, gap: 4 }, children: [
|
||||
/* @__PURE__ */ (0, jsx)(Text, { style: { fontFamily: "Inter-Regular", fontSize: 26, fontWeight: 600, color: colors.fg, numberOfLines: 1, ellipsizeMode: "tail" }, children: game.title }),
|
||||
/* @__PURE__ */ (0, jsx)(Text, { style: { fontFamily: "Inter-Regular", fontSize: 21, color: colors.muted, numberOfLines: 1, ellipsizeMode: "tail" }, children: [detail2, name].filter(Boolean).join(" \xB7 ") })
|
||||
] }),
|
||||
size ? /* @__PURE__ */ (0, jsx)(Text, { style: { fontFamily: "Inter-Regular", fontSize: 22, color: colors.fg }, children: size }) : null
|
||||
] })
|
||||
] }),
|
||||
notes.length ? /* @__PURE__ */ (0, jsxs)(View, { style: { gap: 14 }, children: [
|
||||
/* @__PURE__ */ (0, jsx)(Text, { style: LABEL, children: t2("tips.about").toLocaleUpperCase() }),
|
||||
/* @__PURE__ */ (0, jsx)(View, { style: { gap: 12 }, children: notes.map((note) => /* @__PURE__ */ (0, jsx)(Note, { tone: note.tone, icon: note.icon, text: t2(`tips.note.${note.key}`, note.values) }, note.key)) })
|
||||
] }) : null,
|
||||
/* @__PURE__ */ (0, jsxs)(View, { style: { gap: 14 }, children: [
|
||||
// Closed at first, so the dialog stays short; anyone can open it again any time.
|
||||
/* @__PURE__ */ (0, jsx)(View, { focusKey: "tips:toggle", nextFocusDown: "tips:continue", onPress: () => setOpen(!open), style: (_state) => [{ position: "relative", flexDirection: "row", alignItems: "center", gap: 18, borderRadius: 14, marginHorizontal: -16, paddingHorizontal: 16, paddingVertical: 12 }, _state.focused ? { backgroundColor: colors.surface2 } : null], children: /* @__PURE__ */ (0, jsx)(ToggleFace, { open }) }),
|
||||
open ? /* @__PURE__ */ (0, jsx)(View, { style: { flexDirection: "row", flexWrap: "wrap", rowGap: 16, columnGap: 44 }, children: TIPS.map((key) => /* @__PURE__ */ (0, jsx)(Tip, { title: t2(`tips.tip.${key}.title`), body: t2(`tips.tip.${key}.body`) }, key)) }) : null
|
||||
] }),
|
||||
/* @__PURE__ */ (0, jsxs)(View, { style: { flexDirection: "row", alignItems: "center", gap: 18, borderTopWidth: 2, borderColor: colors.line, paddingTop: 26 }, children: [
|
||||
/* @__PURE__ */ (0, jsx)(Button2, { variant: "primary", size: "lg", icon: "download", label: t2("tips.continue"), halo: false, focusKey: "tips:continue", onPress: proceed }),
|
||||
/* @__PURE__ */ (0, jsx)(Button2, { variant: "secondary", size: "lg", icon: "layers", label: t2("tips.changeSource"), focusKey: "tips:source", onPress: pick })
|
||||
] })
|
||||
] }) }) })
|
||||
);
|
||||
}
|
||||
|
||||
export { DownloadTipsDialog };
|
||||
@@ -1,10 +1,10 @@
|
||||
// ../pub-game-store/native/game/GameFacts.jsx
|
||||
// reconstructed from the esbuild bundle in eboot.bin
|
||||
// MODIFIED: the fact labels stay in English.
|
||||
// MODIFIED: the fact labels stay in English; Title ID and the minimum firmware are added.
|
||||
|
||||
import '../assets/Inter-Regular.ttf';
|
||||
import { jsx, jsxs } from 'react/jsx-runtime';
|
||||
import { preferredRelease } from './sources.js';
|
||||
import { preferredRelease, requiredFirmware } from './sources.js';
|
||||
import { useT } from '../i18n/index.js';
|
||||
import en from '../i18n/locales/en.json';
|
||||
import { formatDate3 } from '../state/catalog.js';
|
||||
@@ -19,15 +19,20 @@ import { Text, View } from '@ps5-react/core';
|
||||
function GameFacts({ game }) {
|
||||
const t2 = useT();
|
||||
const releases = game.releases || [];
|
||||
const bytes = releases[preferredRelease(releases)]?.sizeBytes;
|
||||
const preferred = releases[preferredRelease(releases)];
|
||||
const bytes = preferred?.sizeBytes;
|
||||
// The firmware the recommended source needs (issue #13); shown only where its label names one.
|
||||
const firmware = preferred ? requiredFirmware(preferred) : null;
|
||||
const size = bytes ? [label("game.facts.downloadSize"), formatBytes(bytes)] : [label("game.facts.installSize"), game.sizeGb ? formatSize(game.sizeGb) : "\u2014"];
|
||||
const platform = /* @__PURE__ */ (0, jsx)(PlatformBadge, { platforms: game.platforms });
|
||||
const rows = [
|
||||
[label("game.facts.platform"), platform],
|
||||
[label("game.facts.titleId"), game.id],
|
||||
[label("game.facts.publisher"), game.publisher || "\u2014"],
|
||||
[label("game.facts.releaseDate"), formatDate3(game.releaseDate)],
|
||||
firmware === null ? null : [label("game.facts.firmware"), `${firmware}.xx`],
|
||||
size
|
||||
];
|
||||
].filter(Boolean);
|
||||
return /* @__PURE__ */ (0, jsxs)(View, { style: { width: 432 }, children: [
|
||||
/* @__PURE__ */ (0, jsx)(Text, { style: { fontFamily: "Inter-Regular", fontSize: 30, fontWeight: 600, color: "#f2f2f3", lineHeight: 42, letterSpacing: -0.75 }, children: t2("game.details") }),
|
||||
/* @__PURE__ */ (0, jsx)(View, { style: { marginTop: 24, borderBottomWidth: 2, borderColor: "#2a2a2f" }, children: rows.map(
|
||||
|
||||
@@ -20,7 +20,17 @@ import { Text, View, useIsFocused } from '@ps5-react/core';
|
||||
browser: { title: "sources.section.browser", icon: "external" },
|
||||
dlc: { title: "sources.section.dlc", icon: "download", note: "sources.section.dlcNote" }
|
||||
};
|
||||
function RowFace({ icon, name, detail: detail2, size, recommended, current, cdn }) {
|
||||
// Whether the source suits this console: a tinted icon and words, readable without the colour.
|
||||
function StatusLine({ status }) {
|
||||
// A DLC is neither: a neutral note, not a pass or a warning.
|
||||
const tint = status.tone === "ok" ? colors.ok : status.tone === "info" ? colors.muted : colors.warn;
|
||||
const icon = status.tone === "ok" ? "circleCheck" : status.tone === "info" ? "package" : "alert";
|
||||
return /* @__PURE__ */ (0, jsxs)(View, { style: { flexDirection: "row", alignItems: "center", gap: 9 }, children: [
|
||||
/* @__PURE__ */ (0, jsx)(Icon, { name: icon, size: 18, color: tint }),
|
||||
/* @__PURE__ */ (0, jsx)(Text, { style: { width: 423, fontFamily: "Inter-Regular", fontSize: 20, color: tint, numberOfLines: 1, ellipsizeMode: "tail", lineHeight: 28 }, children: status.text })
|
||||
] });
|
||||
}
|
||||
function RowFace({ icon, name, detail: detail2, size, recommended, current, cdn, status }) {
|
||||
const t2 = useT();
|
||||
const focused = useIsFocused();
|
||||
const ink2 = focused ? colors.fg : colors.muted;
|
||||
@@ -33,7 +43,8 @@ import { Text, View, useIsFocused } from '@ps5-react/core';
|
||||
/* @__PURE__ */ (0, jsx)(Text, { style: { fontFamily: "Inter-Regular", fontSize: 23, fontWeight: 600, color: "#f2f2f3" }, children: name }),
|
||||
recommended ? /* @__PURE__ */ (0, jsx)(View, { style: { borderRadius: 3, backgroundColor: "#f2f2f3", paddingHorizontal: 9, paddingVertical: 2 }, children: /* @__PURE__ */ (0, jsx)(Text, { style: { fontFamily: "Inter-Regular", fontSize: 18, fontWeight: 600, color: "#000000", lineHeight: 24 }, children: t2("sources.recommended") }) }) : null
|
||||
] }),
|
||||
/* @__PURE__ */ (0, jsx)(Text, { style: [{ width: 450, fontFamily: "Inter-Regular", fontSize: 21, numberOfLines: 2, ellipsizeMode: "tail", lineHeight: 30 }, { color: ink2 }], children: detail2 || t2("sources.noDetails") })
|
||||
/* @__PURE__ */ (0, jsx)(Text, { style: [{ width: 450, fontFamily: "Inter-Regular", fontSize: 21, numberOfLines: 2, ellipsizeMode: "tail", lineHeight: 30 }, { color: ink2 }], children: detail2 || t2("sources.noDetails") }),
|
||||
status ? /* @__PURE__ */ (0, jsx)(StatusLine, { status }) : null
|
||||
] }),
|
||||
/* @__PURE__ */ (0, jsx)(Text, { style: [{ width: 108, fontFamily: "Inter-Regular", fontSize: 21, textAlign: "end", lineHeight: 30 }, { color: ink2 }], children: size ?? "" }),
|
||||
/* @__PURE__ */ (0, jsx)(View, { style: { width: 27, alignItems: "flex-end" }, children: current ? /* @__PURE__ */ (0, jsx)(Icon, { name: "check", size: 18, color: colors.fg }) : null })
|
||||
@@ -48,7 +59,7 @@ import { Text, View, useIsFocused } from '@ps5-react/core';
|
||||
note ? /* @__PURE__ */ (0, jsx)(Text, { style: { width: 660, fontFamily: "Inter-Regular", fontSize: 21, color: "#8a8a94", numberOfLines: 2, ellipsizeMode: "tail", lineHeight: 30 }, children: t2(note) }) : null
|
||||
] }),
|
||||
items.map(({ release, index }, row) => {
|
||||
const { name, detail: detail2, size } = describeSource(release);
|
||||
const { name, detail: detail2, size, status } = describeSource(release);
|
||||
return /* @__PURE__ */ (0, jsx)(View, { autoFocus: index === selected || revealed && row === 0, onPress: () => onPick(index), style: (_state) => [{ position: "relative", minHeight: 84, flexDirection: "row", alignItems: "center", gap: 18, borderRadius: 12, paddingHorizontal: 18, paddingVertical: 12 }, _state.focused ? { backgroundColor: "#212125" } : null], children: /* @__PURE__ */ (0, jsx)(
|
||||
RowFace,
|
||||
{
|
||||
@@ -56,6 +67,7 @@ import { Text, View, useIsFocused } from '@ps5-react/core';
|
||||
name,
|
||||
detail: detail2,
|
||||
size,
|
||||
status,
|
||||
cdn: isOverdriveCdn(release.provider),
|
||||
recommended: index === recommended && group !== "browser",
|
||||
current: index === selected
|
||||
|
||||
+2
-1
@@ -6,6 +6,7 @@ import { jsx, jsxs } from 'react/jsx-runtime';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { BrowserCaptureDialog } from '../downloads/BrowserCaptureDialog.jsx';
|
||||
import { URLDialog } from '../downloads/URLDialog.jsx';
|
||||
import { DownloadTipsDialog } from './DownloadTipsDialog.jsx';
|
||||
import { About } from './About.jsx';
|
||||
import { GameFacts } from './GameFacts.jsx';
|
||||
import { GameHero } from './GameHero.jsx';
|
||||
@@ -50,7 +51,7 @@ import { AnimatePresence, FocusScope, ScrollView, Text, View } from '@ps5-react/
|
||||
const [toast, setToast] = (0, useState)(null);
|
||||
const [viewing, setViewing] = (0, useState)(null);
|
||||
const { focusLater } = useNavActions();
|
||||
const Dialog = { source: SourcePicker, options: OptionsSheet, version: VersionSheet }[dialog?.kind] ?? URLDialog;
|
||||
const Dialog = { source: SourcePicker, options: OptionsSheet, version: VersionSheet, tips: DownloadTipsDialog }[dialog?.kind] ?? URLDialog;
|
||||
const related = relatedGames(game);
|
||||
const about = game.about.filter((text) => text.trim());
|
||||
(0, useEffect)(() => {
|
||||
|
||||
+28
-5
@@ -3,6 +3,8 @@
|
||||
|
||||
import { t } from '../i18n/core.js';
|
||||
import { sourceKind } from '../state/download-sources.js';
|
||||
import { isPs5Package } from '../state/firmware.js';
|
||||
import { isPatchOnly } from '../state/release-sets.js';
|
||||
import { formatBytes } from '../state/size.js';
|
||||
|
||||
var KIND = {
|
||||
@@ -33,6 +35,24 @@ import { formatBytes } from '../state/size.js';
|
||||
if (kind === "direct" || kind === "manifest") return "direct";
|
||||
return kind === "browser" || kind === "datanodes" || kind === "unavailable" || GATED.has(kind) ? "browser" : "page";
|
||||
}
|
||||
// The firmware a source needs, from its label ("USA 10.xx Base", "10.xx BackPort 4.xx"): a backport
|
||||
// runs from the lowest firmware it names, anything else needs the highest. Null when it names none.
|
||||
function requiredFirmware(release) {
|
||||
const named = [...(release.label || "").matchAll(/\b(\d{1,2})\.xx\b/gi)].map((match) => Number(match[1]));
|
||||
if (!named.length) return null;
|
||||
return isBackport(release) ? Math.min(...named) : Math.max(...named);
|
||||
}
|
||||
// A line under the source for what it is beyond a game image (issue #13). Nothing reads the
|
||||
// console's firmware here: the jailbreak can raise what the title is told (13.59 on a 13.20
|
||||
// console), so a "works on your firmware" verdict could be wrong either way. The firmware a source
|
||||
// needs is shown where the player checks it themselves (game details, the download tips).
|
||||
function compatibility(release) {
|
||||
if (isPatchOnly(release)) return { tone: "warn", text: t("sources.status.patch") };
|
||||
// A PS5 PKG's firmware limit outweighs its being a DLC: the source list has room for one line.
|
||||
if (isPs5Package(release)) return { tone: "warn", text: t("sources.status.package") };
|
||||
if (isDlc(release)) return { tone: "info", text: t("sources.status.dlc") };
|
||||
return null;
|
||||
}
|
||||
var SPEED = { "archive.org": "sources.speed.archive", mediafire: "sources.speed.mediafire", "overdrive cdn": "sources.cdnFastest" };
|
||||
var isOverdriveCdn = (provider) => /^overdrive\s*cdn$/i.test(provider ?? "");
|
||||
function describeSource(release) {
|
||||
@@ -40,7 +60,7 @@ import { formatBytes } from '../state/size.js';
|
||||
const detail2 = [
|
||||
edition(release),
|
||||
release.version ? `v${release.version.replace(/^v/i, "")}` : null,
|
||||
KIND[release.format] && t(KIND[release.format]),
|
||||
isPatchOnly(release) ? t("sources.kind.patch") : KIND[release.format] && t(KIND[release.format]),
|
||||
parts ? t("sources.parts", { count: parts }) : null
|
||||
].filter(Boolean).join(" \xB7 ");
|
||||
const reach = access(release);
|
||||
@@ -50,7 +70,8 @@ import { formatBytes } from '../state/size.js';
|
||||
detail: speed ? [detail2, t(speed)].filter(Boolean).join(" \xB7 ") : detail2,
|
||||
size: release.sizeBytes ? formatBytes(release.sizeBytes) : null,
|
||||
group: release.unavailableReason ? "unavailable" : reach,
|
||||
reason: release.unavailableReason ?? null
|
||||
reason: release.unavailableReason ?? null,
|
||||
status: release.unavailableReason ? null : compatibility(release)
|
||||
};
|
||||
}
|
||||
var version = (value) => {
|
||||
@@ -63,7 +84,7 @@ import { formatBytes } from '../state/size.js';
|
||||
var recordOf = (release) => (release.parts?.[0].sources[0] ?? release).record;
|
||||
var sameFormat = (a, b) => a.format === b.format || a.format === "binary" || b.format === "binary";
|
||||
function equivalent(a, b) {
|
||||
if (Boolean(a.parts) !== Boolean(b.parts) || a.parts?.length !== b.parts?.length || isDlc(a) !== isDlc(b)) return false;
|
||||
if (Boolean(a.parts) !== Boolean(b.parts) || a.parts?.length !== b.parts?.length || isDlc(a) !== isDlc(b) || isPatchOnly(a) !== isPatchOnly(b)) return false;
|
||||
if (a.sha256 && b.sha256) return a.sha256 === b.sha256;
|
||||
const [ea, eb] = [edition(a).toLowerCase(), edition(b).toLowerCase()];
|
||||
if (ea && eb && ea !== eb) return false;
|
||||
@@ -80,6 +101,8 @@ import { formatBytes } from '../state/size.js';
|
||||
var ranked = (release, index) => ({ release, index, rank: [
|
||||
isDlc(release) ? 1 : 0,
|
||||
release.unavailableReason ? 1 : 0,
|
||||
isPatchOnly(release) ? 1 : 0,
|
||||
isPs5Package(release) ? 1 : 0,
|
||||
hostRank(release),
|
||||
GROUP_RANK[describeSource(release).group],
|
||||
HEALTH_RANK[release.health] ?? 1,
|
||||
@@ -92,7 +115,7 @@ import { formatBytes } from '../state/size.js';
|
||||
return releases.map(ranked).sort(compare)[0]?.index ?? 0;
|
||||
}
|
||||
function fallbacks(releases, selected) {
|
||||
return releases.map(ranked).filter(({ release, index, rank }) => index !== selected && rank[3] <= GROUP_RANK.page && equivalent(releases[selected], release)).sort(compare).map(({ release }) => release);
|
||||
return releases.map(ranked).filter(({ release, index }) => index !== selected && GROUP_RANK[describeSource(release).group] <= GROUP_RANK.page && equivalent(releases[selected], release)).sort(compare).map(({ release }) => release);
|
||||
}
|
||||
function sourceSections(releases) {
|
||||
const sections = ["direct", "page", "browser", "dlc", "unavailable"].map((group) => ({ group, items: [] }));
|
||||
@@ -106,4 +129,4 @@ import { formatBytes } from '../state/size.js';
|
||||
return sections.filter((section) => section.items.length);
|
||||
}
|
||||
|
||||
export { describeSource, fallbacks, isOverdriveCdn, preferredRelease, sourceSections };
|
||||
export { describeSource, fallbacks, isDlc, isOverdriveCdn, preferredRelease, requiredFirmware, sourceSections };
|
||||
+12
-1
@@ -150,7 +150,18 @@
|
||||
percent: "{n}%",
|
||||
date: "{y}\u5E74{M}\u6708{d}\u65E5",
|
||||
list: ["\u3001", "\u3001", "\u3001"]
|
||||
}
|
||||
},
|
||||
{
|
||||
code: "fa",
|
||||
name: "فارسی",
|
||||
rtl: true,
|
||||
group: ",",
|
||||
decimal: ".",
|
||||
percent: "{n}%",
|
||||
date: "{y}/{M}/{d}",
|
||||
months: ["ژانویه", "فوریه", "مارس", "آوریل", "مه", "ژوئن", "ژوئیه", "اوت", "سپتامبر", "اکتبر", "نوامبر", "دسامبر"],
|
||||
list: ["، ", " و ", " و "]
|
||||
},
|
||||
];
|
||||
var byCode = new Map(LOCALES.map((locale2) => [locale2.code, locale2]));
|
||||
var localeData = (code) => byCode.get(code) ?? byCode.get(SOURCE_LOCALE);
|
||||
|
||||
@@ -172,6 +172,8 @@
|
||||
"errors.fileChanged": "تغير الملف على الخادم",
|
||||
"errors.interrupted": "توقف عند إغلاق التطبيق",
|
||||
"errors.linkGone": "رابط التحميل لم يعد يعمل",
|
||||
"errors.notGameFile": "أرسل المضيف صفحة ويب بدلاً من اللعبة",
|
||||
"errors.pkgFirmware": "لا تُثبَّت حزم PKG المزيفة لـ PS5 إلا حتى البرنامج الثابت 11.60",
|
||||
"errors.missingParts": "هذا المصدر تنقصه أجزاء",
|
||||
"errors.needsBrowser": "هذا المصدر يحتاج متصفح ويب",
|
||||
"errors.noConnection": "لا يوجد اتصال بالخادم",
|
||||
@@ -317,6 +319,7 @@
|
||||
"language.name.de": "الألمانية",
|
||||
"language.name.en": "الإنجليزية",
|
||||
"language.name.es": "الإسبانية",
|
||||
"language.name.fa": "الفارسية",
|
||||
"language.name.fr": "الفرنسية",
|
||||
"language.name.hi": "الهندية",
|
||||
"language.name.id": "الإندونيسية",
|
||||
@@ -343,6 +346,10 @@
|
||||
"search.resultsFor": "النتائج لـ \"{query}\"",
|
||||
"search.tryGenre": "تحقق من الإملاء، أو جرب فئة:",
|
||||
"settings.controller": "وحدة التحكم",
|
||||
"settings.downloads": "التنزيلات",
|
||||
"settings.speedLimit": "حد السرعة",
|
||||
"settings.speedLimitDetail": "يترك سعة للأجهزة الأخرى. لا تُقيَّد تحديثات التطبيق أبدًا.",
|
||||
"settings.speedUnlimited": "غير محدود",
|
||||
"settings.title": "الإعدادات",
|
||||
"settings.vibration": "الاهتزاز أثناء التصفح",
|
||||
"settings.vibrationDetail": "نبضة خفيفة عند التنقل والاختيار والرجوع",
|
||||
@@ -359,6 +366,10 @@
|
||||
"sources.kind.archive": "أرشيف",
|
||||
"sources.kind.image": "صورة اللعبة",
|
||||
"sources.kind.package": "حزمة",
|
||||
"sources.kind.patch": "ملفات الباك بورت فقط، وليست اللعبة كاملة",
|
||||
"sources.status.dlc": "محتوى إضافي: يحتاج اللعبة الأساسية",
|
||||
"sources.status.package": "لا تُثبَّت حزم PKG المزيفة لـ PS5 إلا حتى البرنامج الثابت 11.60",
|
||||
"sources.status.patch": "تحتاج اللعبة الأساسية، ولا تُثبَّت وحدها",
|
||||
"sources.noDetails": "لا توجد تفاصيل",
|
||||
"sources.parts": "{count, plural, zero {لا أجزاء} one {# جزء} two {# جزءان} few {# أجزاء} many {# جزء} other {# جزء}}",
|
||||
"sources.recommended": "موصى به",
|
||||
@@ -378,6 +389,27 @@
|
||||
"time.seconds": "{n} ثانية",
|
||||
"toast.openDownloads": "افتح التحميلات",
|
||||
"toast.viewGame": "عرض اللعبة",
|
||||
"tips.eyebrow": "التنزيل",
|
||||
"tips.title": "قبل التنزيل",
|
||||
"tips.downloading": "أنت تنزّل",
|
||||
"tips.about": "حول هذا المصدر",
|
||||
"tips.toggle": "نصائح لاختيار المصدر",
|
||||
"tips.continue": "متابعة",
|
||||
"tips.changeSource": "تغيير المصدر",
|
||||
"tips.note.dlc": "هذا محتوى إضافي وليس اللعبة. ثبّت اللعبة الأساسية أولًا.",
|
||||
"tips.note.patch": "هذه ملفات باك بورت فقط وليست اللعبة كاملة. لا تُثبَّت وحدها.",
|
||||
"tips.note.package": "هذه حزمة PKG مزيفة لـ PS5. لا تُثبَّت إلا على البرنامج الثابت 11.60 أو أقدم.",
|
||||
"tips.note.browserViking": "يُفتح هذا المصدر في متصفح PS5. أكمل التحقق ثم انتظر. لا تضغط Download ولا أي إعلان.",
|
||||
"tips.note.browserOther": "يُفتح هذا المصدر في متصفح PS5 ثم يطلب رابط الملف. إن أمكن، اختر مصدرًا أعلى في القائمة.",
|
||||
"tips.note.firmware": "يتطلب هذا الإصدار البرنامج الثابت {need} أو أحدث. تحقق من إصدارك في الإعدادات > النظام > برنامج النظام.",
|
||||
"tips.tip.firmware.title": "تحقق من البرنامج الثابت",
|
||||
"tips.tip.firmware.body": "Base 8.xx يتطلب 8.xx أو أحدث. BackPort للأجهزة الأقدم.",
|
||||
"tips.tip.fullGame.title": "اختر اللعبة الكاملة",
|
||||
"tips.tip.fullGame.body": "Base أو BackPort، لا محتوى إضافيًا ولا ملفات باك بورت.",
|
||||
"tips.tip.top.title": "ابدأ من الأعلى",
|
||||
"tips.tip.top.body": "المصادر الأولى هي الأفضل.",
|
||||
"tips.tip.retry.title": "فشل؟ جرّب مصدرًا آخر",
|
||||
"tips.tip.retry.body": "لمعظم الألعاب عدة مصادر.",
|
||||
"units.gigabytes": "{n} GB",
|
||||
"units.megabytes": "{n} MB",
|
||||
"units.millions": "{n}M",
|
||||
|
||||
@@ -172,6 +172,8 @@
|
||||
"errors.fileChanged": "ফাইল সার্ভারে পরিবর্তিত হয়েছে",
|
||||
"errors.interrupted": "অ্যাপ বন্ধ হওয়ার সময় থেমেছিল",
|
||||
"errors.linkGone": "ডাউনলোড লিঙ্ক আর কাজ করে না",
|
||||
"errors.notGameFile": "হোস্ট গেমের বদলে একটি ওয়েব পেজ পাঠিয়েছে",
|
||||
"errors.pkgFirmware": "PS5-এর ফেক PKG শুধু ফার্মওয়্যার 11.60 পর্যন্ত ইনস্টল হয়",
|
||||
"errors.missingParts": "এই উৎসে কিছু অংশ নেই",
|
||||
"errors.needsBrowser": "এই উৎসের একটি ওয়েব ব্রাউজার প্রয়োজন",
|
||||
"errors.noConnection": "সার্ভারের সাথে কোনো সংযোগ নেই",
|
||||
@@ -317,6 +319,7 @@
|
||||
"language.name.de": "জার্মান",
|
||||
"language.name.en": "ইংরেজি",
|
||||
"language.name.es": "স্প্যানিশ",
|
||||
"language.name.fa": "ফারসি",
|
||||
"language.name.fr": "ফ্রেঞ্চ",
|
||||
"language.name.hi": "হিন্দি",
|
||||
"language.name.id": "ইন্দোনেশিয়ান",
|
||||
@@ -343,6 +346,10 @@
|
||||
"search.resultsFor": "'{query}' এর জন্য ফলাফল",
|
||||
"search.tryGenre": "বানান পরীক্ষা করুন, বা একটি ঘরানা চেষ্টা করুন:",
|
||||
"settings.controller": "কন্ট্রোলার",
|
||||
"settings.downloads": "ডাউনলোড",
|
||||
"settings.speedLimit": "গতির সীমা",
|
||||
"settings.speedLimitDetail": "অন্য ডিভাইসের জন্য ব্যান্ডউইথ রাখে। অ্যাপ আপডেট কখনো সীমিত হয় না।",
|
||||
"settings.speedUnlimited": "সীমাহীন",
|
||||
"settings.title": "সেটিংস",
|
||||
"settings.vibration": "ব্রাউজ করার সময় কম্পন",
|
||||
"settings.vibrationDetail": "সরানো, বেছে নেওয়া ও ফিরে যাওয়ার সময় হালকা কম্পন",
|
||||
@@ -359,6 +366,10 @@
|
||||
"sources.kind.archive": "সংরক্ষণাগার",
|
||||
"sources.kind.image": "গেম ইমেজ",
|
||||
"sources.kind.package": "প্যাকেজ",
|
||||
"sources.kind.patch": "শুধু ব্যাকপোর্ট ফাইল, পুরো গেম নয়",
|
||||
"sources.status.dlc": "DLC: বেস গেম লাগবে",
|
||||
"sources.status.package": "PS5-এর ফেক PKG শুধু ফার্মওয়্যার 11.60 পর্যন্ত ইনস্টল হয়",
|
||||
"sources.status.patch": "বেস গেম লাগবে, একা ইনস্টল হবে না",
|
||||
"sources.noDetails": "কোনো বিবরণ নেই",
|
||||
"sources.parts": "{count, plural, one {# অংশ} other {# অংশ}}",
|
||||
"sources.recommended": "সুপারিশকৃত",
|
||||
@@ -378,6 +389,27 @@
|
||||
"time.seconds": "{n} সেকেন্ড",
|
||||
"toast.openDownloads": "ডাউনলোড খুলুন",
|
||||
"toast.viewGame": "গেম দেখুন",
|
||||
"tips.eyebrow": "ডাউনলোড",
|
||||
"tips.title": "ডাউনলোডের আগে",
|
||||
"tips.downloading": "আপনি ডাউনলোড করছেন",
|
||||
"tips.about": "এই সোর্স সম্পর্কে",
|
||||
"tips.toggle": "সোর্স বাছাইয়ের টিপস",
|
||||
"tips.continue": "চালিয়ে যান",
|
||||
"tips.changeSource": "সোর্স বদলান",
|
||||
"tips.note.dlc": "এটি DLC, গেম নয়। আগে বেস গেম ইনস্টল করুন।",
|
||||
"tips.note.patch": "এগুলো শুধু ব্যাকপোর্ট ফাইল, পুরো গেম নয়। একা ইনস্টল হয় না।",
|
||||
"tips.note.package": "এটি PS5-এর ফেক PKG। শুধু ফার্মওয়্যার 11.60 বা তার আগের সংস্করণে ইনস্টল হয়।",
|
||||
"tips.note.browserViking": "এই সোর্স PS5 ব্রাউজারে খোলে। যাচাই শেষ করে অপেক্ষা করুন। Download বা কোনো বিজ্ঞাপনে চাপবেন না।",
|
||||
"tips.note.browserOther": "এই সোর্স PS5 ব্রাউজারে খোলে, তারপর ফাইলের লিংক চায়। পারলে তালিকার ওপরের সোর্স বেছে নিন।",
|
||||
"tips.note.firmware": "এই সংস্করণে ফার্মওয়্যার {need} বা নতুন লাগবে। আপনারটি সেটিংস > সিস্টেম > সিস্টেম সফটওয়্যারে দেখুন।",
|
||||
"tips.tip.firmware.title": "ফার্মওয়্যার দেখুন",
|
||||
"tips.tip.firmware.body": "Base 8.xx-এ 8.xx বা নতুন লাগে। BackPort পুরোনো কনসোলের জন্য।",
|
||||
"tips.tip.fullGame.title": "পুরো গেম নিন",
|
||||
"tips.tip.fullGame.body": "Base বা BackPort, DLC বা ব্যাকপোর্ট ফাইল নয়।",
|
||||
"tips.tip.top.title": "ওপর থেকে শুরু করুন",
|
||||
"tips.tip.top.body": "ওপরের সোর্সগুলো সবচেয়ে ভালো।",
|
||||
"tips.tip.retry.title": "ব্যর্থ? অন্য সোর্স",
|
||||
"tips.tip.retry.body": "বেশিরভাগ গেমের একাধিক সোর্স আছে।",
|
||||
"units.gigabytes": "{n} GB",
|
||||
"units.megabytes": "{n} MB",
|
||||
"units.millions": "{n}M",
|
||||
|
||||
@@ -172,6 +172,8 @@
|
||||
"errors.fileChanged": "Datei hat sich auf dem Server geändert",
|
||||
"errors.interrupted": "Gestoppt, als die App geschlossen wurde",
|
||||
"errors.linkGone": "Download-Link funktioniert nicht mehr",
|
||||
"errors.notGameFile": "Host hat Webseite statt Spiel gesendet",
|
||||
"errors.pkgFirmware": "PS5-Fake-PKGs nur bis Firmware 11.60",
|
||||
"errors.missingParts": "Dieser Quelle fehlen Teile",
|
||||
"errors.needsBrowser": "Diese Quelle benötigt einen Webbrowser",
|
||||
"errors.noConnection": "Keine Verbindung zum Server",
|
||||
@@ -317,6 +319,7 @@
|
||||
"language.name.de": "Deutsch",
|
||||
"language.name.en": "Englisch",
|
||||
"language.name.es": "Spanisch",
|
||||
"language.name.fa": "Persisch",
|
||||
"language.name.fr": "Französisch",
|
||||
"language.name.hi": "Hindi",
|
||||
"language.name.id": "Indonesisch",
|
||||
@@ -343,9 +346,13 @@
|
||||
"search.resultsFor": "Ergebnisse für « {query} »",
|
||||
"search.tryGenre": "Überprüfen Sie die Schreibweise oder versuchen Sie ein Genre:",
|
||||
"settings.controller": "Controller",
|
||||
"settings.downloads": "Downloads",
|
||||
"settings.speedLimit": "Tempolimit",
|
||||
"settings.speedLimitDetail": "Lässt Bandbreite für andere Geräte. Updates sind nie begrenzt.",
|
||||
"settings.speedUnlimited": "Unbegrenzt",
|
||||
"settings.title": "Einstellungen",
|
||||
"settings.vibration": "Vibration beim Navigieren",
|
||||
"settings.vibrationDetail": "Ein leichter Impuls beim Bewegen, Auswählen und Zurückgehen",
|
||||
"settings.vibrationDetail": "Leichter Impuls beim Bewegen, Wählen und Zurück",
|
||||
"shadowMount.offAction": "Das Spiel erscheint, sobald du es lädst. Füge es zum Autoload deines Jailbreaks hinzu, damit es immer läuft.",
|
||||
"shadowMount.offTitle": "ShadowMount läuft nicht",
|
||||
"sort.label": "Sortieren",
|
||||
@@ -359,6 +366,10 @@
|
||||
"sources.kind.archive": "Archiv",
|
||||
"sources.kind.image": "Spielimage",
|
||||
"sources.kind.package": "Paket",
|
||||
"sources.kind.patch": "Nur Backport-Dateien, kein Vollspiel",
|
||||
"sources.status.dlc": "DLC: braucht das Hauptspiel",
|
||||
"sources.status.package": "PS5-Fake-PKGs nur bis Firmware 11.60",
|
||||
"sources.status.patch": "Nur mit Hauptspiel installierbar",
|
||||
"sources.noDetails": "Keine Details",
|
||||
"sources.parts": "{count, plural, one {# Teil} other {# Teile}}",
|
||||
"sources.recommended": "Empfohlen",
|
||||
@@ -378,6 +389,27 @@
|
||||
"time.seconds": "{n} s",
|
||||
"toast.openDownloads": "Downloads öffnen",
|
||||
"toast.viewGame": "Spiel ansehen",
|
||||
"tips.eyebrow": "Download",
|
||||
"tips.title": "Vor dem Download",
|
||||
"tips.downloading": "Du lädst herunter",
|
||||
"tips.about": "Zu dieser Quelle",
|
||||
"tips.toggle": "Tipps zur Wahl der Quelle",
|
||||
"tips.continue": "Weiter",
|
||||
"tips.changeSource": "Quelle ändern",
|
||||
"tips.note.dlc": "Das ist ein DLC, kein Spiel. Installiere zuerst das Hauptspiel.",
|
||||
"tips.note.patch": "Nur Backport-Dateien, kein Vollspiel. Allein nicht installierbar.",
|
||||
"tips.note.package": "Das ist ein PS5-Fake-PKG. Nur bis Firmware 11.60 installierbar.",
|
||||
"tips.note.browserViking": "Öffnet sich im PS5-Browser. Verifizierung abschließen und warten. Weder Download noch Werbung drücken.",
|
||||
"tips.note.browserOther": "Öffnet sich im PS5-Browser und fragt dann nach dem Dateilink. Wähle besser eine Quelle weiter oben.",
|
||||
"tips.note.firmware": "Braucht Firmware {need} oder neuer. Prüfe deine unter Einstellungen > System > Systemsoftware.",
|
||||
"tips.tip.firmware.title": "Firmware prüfen",
|
||||
"tips.tip.firmware.body": "Base 8.xx braucht 8.xx+. BackPort ist für ältere Konsolen.",
|
||||
"tips.tip.fullGame.title": "Vollspiel nehmen",
|
||||
"tips.tip.fullGame.body": "Base oder BackPort, kein DLC, keine Backport-Dateien.",
|
||||
"tips.tip.top.title": "Oben anfangen",
|
||||
"tips.tip.top.body": "Die ersten Quellen sind am besten.",
|
||||
"tips.tip.retry.title": "Fehler? Andere Quelle",
|
||||
"tips.tip.retry.body": "Meist gibt es mehrere.",
|
||||
"units.gigabytes": "{n} GB",
|
||||
"units.megabytes": "{n} MB",
|
||||
"units.millions": "{n}M",
|
||||
|
||||
@@ -173,6 +173,8 @@
|
||||
"errors.fileChanged": "The file changed on the server",
|
||||
"errors.interrupted": "Stopped when the app closed",
|
||||
"errors.linkGone": "Download link no longer works",
|
||||
"errors.notGameFile": "The host sent a web page instead of the game",
|
||||
"errors.pkgFirmware": "PS5 fake PKGs only install on firmware 11.60 or older",
|
||||
"errors.missingParts": "This source is missing parts",
|
||||
"errors.needsBrowser": "This source needs a web browser",
|
||||
"errors.noConnection": "No connection to the server",
|
||||
@@ -249,10 +251,12 @@
|
||||
"game.back.search": "Search",
|
||||
"game.details": "Details",
|
||||
"game.facts.downloadSize": "Download size",
|
||||
"game.facts.firmware": "Min. firmware",
|
||||
"game.facts.installSize": "Install size",
|
||||
"game.facts.platform": "Platform",
|
||||
"game.facts.publisher": "Publisher",
|
||||
"game.facts.releaseDate": "Release date",
|
||||
"game.facts.titleId": "Title ID",
|
||||
"game.favorite": "Favorite",
|
||||
"game.features": "Features",
|
||||
"game.metacritic": "Metacritic",
|
||||
@@ -318,6 +322,7 @@
|
||||
"language.name.de": "German",
|
||||
"language.name.en": "English",
|
||||
"language.name.es": "Spanish",
|
||||
"language.name.fa": "Persian",
|
||||
"language.name.fr": "French",
|
||||
"language.name.hi": "Hindi",
|
||||
"language.name.id": "Indonesian",
|
||||
@@ -344,6 +349,10 @@
|
||||
"search.resultsFor": "Results for \u201C{query}\u201D",
|
||||
"search.tryGenre": "Check the spelling, or try a genre:",
|
||||
"settings.controller": "Controller",
|
||||
"settings.downloads": "Downloads",
|
||||
"settings.speedLimit": "Speed limit",
|
||||
"settings.speedLimitDetail": "Leaves bandwidth for other devices. App updates are never limited.",
|
||||
"settings.speedUnlimited": "Unlimited",
|
||||
"settings.title": "Settings",
|
||||
"settings.vibration": "Vibration while browsing",
|
||||
"settings.vibrationDetail": "A light pulse as you move, select and go back",
|
||||
@@ -360,6 +369,10 @@
|
||||
"sources.kind.archive": "Archive",
|
||||
"sources.kind.image": "Game image",
|
||||
"sources.kind.package": "Package",
|
||||
"sources.kind.patch": "Backport files only, not the full game",
|
||||
"sources.status.dlc": "DLC: needs the base game",
|
||||
"sources.status.package": "PS5 fake PKGs only install on firmware 11.60 or older",
|
||||
"sources.status.patch": "Needs the base game, won't install alone",
|
||||
"sources.noDetails": "No details",
|
||||
"sources.parts": "{count, plural, one {# part} other {# parts}}",
|
||||
"sources.recommended": "Recommended",
|
||||
@@ -379,6 +392,27 @@
|
||||
"time.seconds": "{n} s",
|
||||
"toast.openDownloads": "Open Downloads",
|
||||
"toast.viewGame": "View game",
|
||||
"tips.eyebrow": "Download",
|
||||
"tips.title": "Before you download",
|
||||
"tips.downloading": "You're downloading",
|
||||
"tips.about": "About this source",
|
||||
"tips.toggle": "Tips for choosing a source",
|
||||
"tips.continue": "Continue",
|
||||
"tips.changeSource": "Change source",
|
||||
"tips.note.dlc": "This is a DLC, not the game. Install the base game first.",
|
||||
"tips.note.patch": "These are backport files only, not the full game. They won't install on their own.",
|
||||
"tips.note.package": "This is a PS5 fake PKG. It only installs on firmware 11.60 or older.",
|
||||
"tips.note.browserViking": "This source opens in the PS5 browser. Complete the verification, then wait. Don't press Download or any ad.",
|
||||
"tips.note.browserOther": "This source opens in the PS5 browser and then asks for the file's link. If you can, pick a source higher in the list.",
|
||||
"tips.note.firmware": "This version needs firmware {need} or newer. Check yours in Settings > System > System Software.",
|
||||
"tips.tip.firmware.title": "Check the firmware",
|
||||
"tips.tip.firmware.body": "Base 8.xx needs 8.xx or newer. BackPort is for older consoles.",
|
||||
"tips.tip.fullGame.title": "Get the full game",
|
||||
"tips.tip.fullGame.body": "Base or BackPort, not DLC or backport files.",
|
||||
"tips.tip.top.title": "Start from the top",
|
||||
"tips.tip.top.body": "The first sources work best.",
|
||||
"tips.tip.retry.title": "Failed? Try another",
|
||||
"tips.tip.retry.body": "Most games have several sources.",
|
||||
"units.gigabytes": "{n} GB",
|
||||
"units.megabytes": "{n} MB",
|
||||
"units.millions": "{n}M",
|
||||
@@ -414,7 +448,7 @@
|
||||
"versions.title": "Choose version",
|
||||
"capture.title": "Vikingfile verification",
|
||||
"capture.preparing": "Preparing the file verification\u2026",
|
||||
"capture.capturing": "Complete verification and select Download",
|
||||
"capture.capturing": "Complete the verification, then wait",
|
||||
"capture.validating": "Checking the captured file\u2026",
|
||||
"capture.detail": "Return here after selecting Download. Your download starts automatically when the file is verified. Capture lasts up to 3 minutes."
|
||||
"capture.detail": "Don't press Download or any ad: the link is captured by itself. Then return here and the download starts automatically. Capture lasts up to 3 minutes."
|
||||
}
|
||||
@@ -172,6 +172,8 @@
|
||||
"errors.fileChanged": "El archivo cambió en el servidor",
|
||||
"errors.interrupted": "Detenido cuando la app se cerró",
|
||||
"errors.linkGone": "El enlace de descarga ya no funciona",
|
||||
"errors.notGameFile": "El servidor envió una página web en lugar del juego",
|
||||
"errors.pkgFirmware": "Los PKG falsos de PS5 solo se instalan hasta el firmware 11.60",
|
||||
"errors.missingParts": "A esta fuente le faltan partes",
|
||||
"errors.needsBrowser": "Esta fuente necesita un navegador web",
|
||||
"errors.noConnection": "Sin conexión al servidor",
|
||||
@@ -317,6 +319,7 @@
|
||||
"language.name.de": "Alemán",
|
||||
"language.name.en": "English",
|
||||
"language.name.es": "Español",
|
||||
"language.name.fa": "Persa",
|
||||
"language.name.fr": "Francés",
|
||||
"language.name.hi": "Hindi",
|
||||
"language.name.id": "Indonesio",
|
||||
@@ -343,6 +346,10 @@
|
||||
"search.resultsFor": "Resultados para “{query}”",
|
||||
"search.tryGenre": "Comprueba la ortografía o prueba un género:",
|
||||
"settings.controller": "Mando",
|
||||
"settings.downloads": "Descargas",
|
||||
"settings.speedLimit": "Límite de velocidad",
|
||||
"settings.speedLimitDetail": "Deja ancho de banda a otros dispositivos. Las actualizaciones de la app nunca se limitan.",
|
||||
"settings.speedUnlimited": "Sin límite",
|
||||
"settings.title": "Ajustes",
|
||||
"settings.vibration": "Vibración al navegar",
|
||||
"settings.vibrationDetail": "Un leve pulso al moverte, seleccionar y volver",
|
||||
@@ -359,6 +366,10 @@
|
||||
"sources.kind.archive": "Archivo",
|
||||
"sources.kind.image": "Imagen de juego",
|
||||
"sources.kind.package": "Paquete",
|
||||
"sources.kind.patch": "Solo archivos de backport, no el juego completo",
|
||||
"sources.status.dlc": "DLC: necesita el juego base",
|
||||
"sources.status.package": "Los PKG falsos de PS5 solo se instalan hasta el firmware 11.60",
|
||||
"sources.status.patch": "Necesita el juego base, no se instala sola",
|
||||
"sources.noDetails": "Sin detalles",
|
||||
"sources.parts": "{count, plural, one {# parte} many {# millones de partes} other {# partes}}",
|
||||
"sources.recommended": "Recomendado",
|
||||
@@ -378,6 +389,27 @@
|
||||
"time.seconds": "{n} s",
|
||||
"toast.openDownloads": "Abrir Descargas",
|
||||
"toast.viewGame": "Ver juego",
|
||||
"tips.eyebrow": "Descarga",
|
||||
"tips.title": "Antes de descargar",
|
||||
"tips.downloading": "Vas a descargar",
|
||||
"tips.about": "Sobre esta fuente",
|
||||
"tips.toggle": "Consejos para elegir la fuente",
|
||||
"tips.continue": "Continuar",
|
||||
"tips.changeSource": "Cambiar fuente",
|
||||
"tips.note.dlc": "Esto es un DLC, no el juego. Instala primero el juego base.",
|
||||
"tips.note.patch": "Son solo archivos de backport, no el juego completo. Solos no se instalan.",
|
||||
"tips.note.package": "Esto es un PKG falso de PS5. Solo se instala con firmware 11.60 o anterior.",
|
||||
"tips.note.browserViking": "Esta fuente se abre en el navegador de la PS5. Completa la verificación y espera. No pulses Descargar ni ningún anuncio.",
|
||||
"tips.note.browserOther": "Esta fuente se abre en el navegador de la PS5 y luego pide el enlace del archivo. Si puedes, elige una fuente más arriba.",
|
||||
"tips.note.firmware": "Esta versión requiere firmware {need} o posterior. Comprueba el tuyo en Ajustes > Sistema > Software del sistema.",
|
||||
"tips.tip.firmware.title": "Mira el firmware",
|
||||
"tips.tip.firmware.body": "Base 8.xx requiere 8.xx o más. BackPort es para consolas antiguas.",
|
||||
"tips.tip.fullGame.title": "Elige el juego completo",
|
||||
"tips.tip.fullGame.body": "Base o BackPort, no DLC ni archivos de backport.",
|
||||
"tips.tip.top.title": "Empieza por arriba",
|
||||
"tips.tip.top.body": "Las primeras fuentes funcionan mejor.",
|
||||
"tips.tip.retry.title": "¿Falla? Prueba otra",
|
||||
"tips.tip.retry.body": "Casi todos los juegos tienen varias.",
|
||||
"units.gigabytes": "{n} GB",
|
||||
"units.megabytes": "{n} MB",
|
||||
"units.millions": "{n}M",
|
||||
|
||||
@@ -0,0 +1,452 @@
|
||||
{
|
||||
"browse.addToFavorites": "افزودن به علاقهمندیها",
|
||||
"browse.continueDownloading": "ادامهٔ دانلود",
|
||||
"browse.continueFailed": "ناموفق · برای تلاش دوباره باز کنید",
|
||||
"browse.favorited": "علاقهمندی",
|
||||
"browse.folderInstall": "نصبهای پوشهای بهزودی دیگر بهروزرسانی نمیشوند. نسخهٔ تکفایلی را دستی نصب کنید",
|
||||
"browse.footer": "کتابخانهٔ Spectrum",
|
||||
"browse.popularGenres": "ژانرهای محبوب",
|
||||
"browse.popularNow": "محبوب الان",
|
||||
"browse.recentlyViewed": "اخیراً دیدهشده",
|
||||
"browse.reviews": "{count} نقد",
|
||||
"browse.viewGame": "مشاهدهٔ بازی",
|
||||
"catalog.empty.browseAll": "مرور کل کتابخانه",
|
||||
"catalog.empty.filtersHint": "یکی از فیلترهای بالا را بردارید یا بخش «فیلترها» را باز کنید و تغییر دهید.",
|
||||
"catalog.empty.filtersTitle": "هیچ بازی با این فیلترها پیدا نشد",
|
||||
"catalog.empty.queryHint": "املا را چک کنید، فیلتری را بردارید یا عنوان دیگری امتحان کنید.",
|
||||
"catalog.empty.queryTitle": "چیزی برای «{query}» پیدا نشد",
|
||||
"catalog.empty.wishlistHint": "یک بازی را باز کنید و «افزودن به علاقهمندیها» را بزنید تا اینجا بماند.",
|
||||
"catalog.empty.wishlistTitle": "هنوز علاقهمندی ندارید",
|
||||
"catalog.favorites": "علاقهمندیهای شما",
|
||||
"catalog.gameCount": "{count, plural, one {# بازی} other {# بازی}}",
|
||||
"catalog.library": "مرور کتابخانه",
|
||||
"catalog.resultsFor": "نتایج برای «{query}»",
|
||||
"common.seeAll": "مشاهدهٔ همه",
|
||||
"download.browser.action": "صفحه را باز کنید، تأیید را بگذرانید، سپس لینک فایل را پایین وارد کنید.",
|
||||
"download.browser.partTitle": "بخش {part} از {parts} به صفحهٔ میزبان نیاز دارد",
|
||||
"download.browser.title": "این میزبان اول به صفحهٔ وبش نیاز دارد",
|
||||
"download.button.awaiting-browser": "باز کردن صفحهٔ میزبان",
|
||||
"download.button.downloaded": "اسکن با ShadowMount",
|
||||
"download.button.extracted": "فایلها استخراج شدند",
|
||||
"download.button.extracting": "در حال استخراج",
|
||||
"download.button.extraction-required": "آرشیو دانلود شد",
|
||||
"download.button.failed": "تلاش دوباره",
|
||||
"download.button.file-downloaded": "فایل دانلود شد",
|
||||
"download.button.finishing": "در حال اتمام",
|
||||
"download.button.idle": "دانلود",
|
||||
"download.button.installed": "نصب شد",
|
||||
"download.button.installer-required": "نصب PKG",
|
||||
"download.button.installing": "در حال نصب",
|
||||
"download.button.no-space": "فضای کافی نیست",
|
||||
"download.button.pause": "توقف موقت",
|
||||
"download.button.paused": "ادامه",
|
||||
"download.button.pausing": "در حال توقف",
|
||||
"download.button.queued": "توقف دانلود در صف",
|
||||
"download.button.removing": "در حال حذف فایلها",
|
||||
"download.button.stale": "شروع دوبارهٔ دانلود",
|
||||
"download.button.unavailable": "منبع دانلودی نیست",
|
||||
"download.button.verifying": "در حال تأیید",
|
||||
"download.enterLink": "لینک فایل را وارد کنید",
|
||||
"download.enterPartLink": "لینک فایل بخش {part} از {parts} را وارد کنید",
|
||||
"download.fileLink": "لینک فایل",
|
||||
"download.fileLinkDetail": "لینک خودِ فایل را بنویسید؛ همانی که بعد از تأیید میزبان دانلود را شروع میکند، نه آدرس صفحه.",
|
||||
"download.free": "{size} آزاد",
|
||||
"download.moreOptions": "گزینههای بیشتر",
|
||||
"download.noFit": "روی این درایو جا نمیشود",
|
||||
"download.openQueue": "باز کردن دانلودها",
|
||||
"download.optionsTitle": "گزینههای دانلود",
|
||||
"download.otherSource.detail": "میزبان یا نسخهٔ دیگری انتخاب کنید.",
|
||||
"download.otherSource.label": "انتخاب منبع دیگر",
|
||||
"download.partFileLink": "بخش {part} از {parts}: لینک فایل",
|
||||
"download.remove.detail": "متوقفش میکند و هر فایل دانلودشده را روی درایو میگذارد.",
|
||||
"download.remove.label": "حذف از صف",
|
||||
"download.restart.detail": "دانلود را از اول شروع میکند.",
|
||||
"download.restart.detailSaved": "{size} دانلودشده را پاک میکند و از صفر شروع میکند.",
|
||||
"download.restart.label": "شروع از صفر",
|
||||
"download.saveTo": "ذخیره روی {drive}",
|
||||
"download.soFar": "{size} تا الان",
|
||||
"download.source": "منبع: {source}",
|
||||
"download.space.free": "{size} روی {drive} آزاد کنید و دوباره امتحان کنید.",
|
||||
"download.space.otherDrive": "{drive} دارای {size} فضای آزاد است: در «ذخیره روی» انتخابش کنید.",
|
||||
"download.status.awaitingLink": "در انتظار لینک فایل",
|
||||
"download.status.needs": "نیاز به {size}",
|
||||
"download.status.noSpace": "روی این درایو جا نمیشود",
|
||||
"download.status.progress": "{done} از {total}",
|
||||
"download.status.ready": "آمادهٔ دانلود",
|
||||
"download.status.unavailable": "این ورودی کاتالوگ آدرس فایلی ندارد",
|
||||
"downloadState.awaiting-browser": "در انتظار تأیید مرورگر",
|
||||
"downloadState.connecting": "در حال اتصال…",
|
||||
"downloadState.downloaded": "آمادهٔ نصب",
|
||||
"downloadState.downloading": "در حال دانلود",
|
||||
"downloadState.extracted": "فایلها استخراج شدند",
|
||||
"downloadState.extracting": "در حال استخراج فایلهای دانلودشده…",
|
||||
"downloadState.extraction-required": "دانلود شد؛ آرشیو را استخراج کنید",
|
||||
"downloadState.failed": "ناموفق",
|
||||
"downloadState.file-downloaded": "فایل دانلود شد؛ فرمتش را بررسی کنید",
|
||||
"downloadState.finishing": "در حال اتمام…",
|
||||
"downloadState.installed": "نصب شد",
|
||||
"downloadState.installer-required": "PKG را نصب کنید",
|
||||
"downloadState.installing": "در حال نصب…",
|
||||
"downloadState.paused": "متوقفشده",
|
||||
"downloadState.pausing": "در حال توقف…",
|
||||
"downloadState.queued": "در صف",
|
||||
"downloadState.removing": "در حال حذف…",
|
||||
"downloadState.resolving": "در حال یافتن لینک…",
|
||||
"downloadState.verifying": "در حال تأیید…",
|
||||
"downloads.action.continueOnPage": "ادامه در صفحهٔ بازی",
|
||||
"downloads.action.gamePage": "صفحهٔ بازی",
|
||||
"downloads.action.install": "نصب",
|
||||
"downloads.action.pause": "توقف موقت",
|
||||
"downloads.action.pausing": "در حال توقف",
|
||||
"downloads.action.restart": "شروع دوباره",
|
||||
"downloads.action.resume": "ادامه",
|
||||
"downloads.action.retry": "تلاش دوباره",
|
||||
"downloads.afterVerification": "بعد از تأیید به اینجا برگردید و آدرس نهایی فایل را وارد کنید.",
|
||||
"downloads.archiveNotDeleted": "آرشیو دانلودشده حذف نشد: {error}",
|
||||
"downloads.browseGames": "مرور بازیها",
|
||||
"downloads.empty": "دانلودی نیست",
|
||||
"downloads.emptyDetail": "بازیهایی که دانلود میکنید با پیشرفتشان اینجا ظاهر میشوند.",
|
||||
"downloads.extractedReady": "{parts, select, all {همهٔ بخشها} other {آرشیو}} تأیید و استخراج شد. آمادهٔ نصب است.",
|
||||
"downloads.extractedTo": "استخراج شد به {path}",
|
||||
"downloads.installFailed": "کنسول بسته را نپذیرفت. برای تلاش دوباره، گزینهٔ «نصب» را انتخاب کنید.",
|
||||
"downloads.installHint": "Overdrive را ببندید تا تمام شود: ShadowMount وقتی هیچ بازی باز نباشد نصب میکند.",
|
||||
"downloads.installRequested": "نصب درخواست شد. Overdrive را ببندید تا تمام شود: ShadowMount وقتی هیچ بازی باز نباشد نصب میکند.",
|
||||
"downloads.installed": "نصب شد. روی صفحهٔ اصلی است.",
|
||||
"downloads.notice.captured": "لینک گرفته شد، به Overdrive برگردید",
|
||||
"downloads.notice.complete": "دانلود کامل شد",
|
||||
"downloads.notice.failed": "دانلود ناموفق بود",
|
||||
"downloads.notice.installed": "بازی نصب شد",
|
||||
"downloads.packagesExtracted": "پکیجها به {path} استخراج شدند. نصبکنندهٔ PKG لازم است.",
|
||||
"downloads.panel.activity": "فعالیت",
|
||||
"downloads.panel.average": "میانگین",
|
||||
"downloads.panel.awake": "کنسول تا پایان دانلودها بیدار میماند.",
|
||||
"downloads.panel.connections": "اتصالات",
|
||||
"downloads.panel.downloading": "در حال دانلود",
|
||||
"downloads.panel.idle": "بیکار",
|
||||
"downloads.panel.installNote": "بازیهای تمامشده از طریق ShadowMount نصب میشوند که منتظر میماند تا هیچ بازی باز نباشد. Overdrive را ببندید تا تمام شود.",
|
||||
"downloads.panel.installing": "در حال نصب",
|
||||
"downloads.panel.low": "کم",
|
||||
"downloads.panel.none": "هیچ",
|
||||
"downloads.panel.peak": "اوج",
|
||||
"downloads.panel.storage": "فضای ذخیرهسازی",
|
||||
"downloads.panel.timeLeft": "زمان باقیمانده",
|
||||
"downloads.panel.unavailable": "در دسترس نیست",
|
||||
"downloads.panel.used": "{used} از {total} استفادهشده",
|
||||
"downloads.part": "بخش {part} از {parts}",
|
||||
"downloads.problem": "{title}. {action}",
|
||||
"downloads.section.active": "فعال",
|
||||
"downloads.section.completed": "تمامشده",
|
||||
"downloads.section.stopped": "متوقفشده",
|
||||
"downloads.stats.download": "دانلود",
|
||||
"downloads.stats.write": "نوشتن",
|
||||
"downloads.summary.completed": "{count} تمامشده",
|
||||
"downloads.summary.failed": "{count} ناموفق",
|
||||
"downloads.summary.inProgress": "{count} در حال انجام",
|
||||
"downloads.summary.paused": "{count} متوقفشده",
|
||||
"downloads.switchedSource": "{failed} کار نکرد، پس Overdrive به {next} سوییچ کرد.",
|
||||
"downloads.timeLeft": "{time} باقیمانده",
|
||||
"downloads.title": "دانلودها",
|
||||
"downloads.unavailable": "دانلودها در دسترس نیست",
|
||||
"downloads.writingToDisk": "در حال نوشتن روی دیسک…",
|
||||
"drives.extended": "ذخیرهسازی توسعهیافته {number}",
|
||||
"drives.internal": "حافظهٔ داخلی",
|
||||
"drives.thisDevice": "این دستگاه",
|
||||
"drives.usb": "درایو USB {number}",
|
||||
"errors.action.checkConnection": "اتصال اینترنت کنسول را چک کنید، سپس Retry را بزنید.",
|
||||
"errors.action.freeOrOtherDrive": "فضا آزاد کنید یا در «ذخیره روی» درایو دیگری انتخاب کنید.",
|
||||
"errors.action.freeThenResume": "روی درایو فضا آزاد کنید، سپس Resume را بزنید.",
|
||||
"errors.action.freeThenRetry": "روی درایو فضا آزاد کنید، سپس Retry را بزنید یا لغو کنید و جای دیگری ذخیره کنید.",
|
||||
"errors.action.openSource": "منبع را باز کنید، سپس آدرس نهایی فایل را وارد کنید.",
|
||||
"errors.action.otherSource": "لغوش کنید و منبع دیگری انتخاب کنید.",
|
||||
"errors.action.reconnect": "درایو اصلی را دوباره وصل کنید، سپس Resume را بزنید.",
|
||||
"errors.action.reopenApp": "Overdrive را ببندید و دوباره باز کنید، سپس Retry را بزنید.",
|
||||
"errors.action.restart": "برای دانلود دوباره از اول شروع کنید.",
|
||||
"errors.action.restartFromZero": "از صفر شروع کنید.",
|
||||
"errors.action.resume": "برای ادامه، گزینهٔ «ادامه» را انتخاب کنید.",
|
||||
"errors.action.retryLater": "چند دقیقه دیگر دوباره امتحان کنید.",
|
||||
"errors.action.retryOrOtherSource": "دوباره امتحان کنید یا منبع دیگری انتخاب کنید.",
|
||||
"errors.action.retryPart": "برای دانلود دوبارهٔ این بخش، گزینهٔ «تلاش دوباره» را انتخاب کنید.",
|
||||
"errors.archivePassword": "رمز آرشیو کار نمیکند",
|
||||
"errors.archiveTooLarge": "این آرشیو روی کنسول قابل استخراج نیست",
|
||||
"errors.damaged": "فایل آسیبدیده رسید",
|
||||
"errors.driveDisconnected": "درایو قطع شد",
|
||||
"errors.fileChanged": "فایل روی سرور تغییر کرده",
|
||||
"errors.interrupted": "با بسته شدن برنامه متوقف شد",
|
||||
"errors.linkGone": "لینک دانلود دیگر کار نمیکند",
|
||||
"errors.notGameFile": "میزبان به جای بازی یک صفحهٔ وب فرستاد",
|
||||
"errors.pkgFirmware": "PKGهای جعلی PS5 فقط تا فریمور 11.60 نصب میشوند",
|
||||
"errors.missingParts": "این منبع بخشهایی کم دارد",
|
||||
"errors.needsBrowser": "این منبع به مرورگر وب نیاز دارد",
|
||||
"errors.noConnection": "اتصال به سرور برقرار نیست",
|
||||
"errors.noMemory": "حافظهٔ کافی برای استخراج نیست",
|
||||
"errors.noSpace": "فضای آزاد کافی نیست",
|
||||
"errors.noSpaceOn": "فضای کافی روی {root} نیست: نیاز به {need}، {free} آزاد{queued, select, yes { بعد از دانلودهای در صف} other {}}",
|
||||
"errors.partIncomplete": "بخش {part} ناقص به نظر میرسد",
|
||||
"errors.partialDeleted": "فایلهای دانلودشدهاش حذف شدند",
|
||||
"errors.serverBusy": "سرور شلوغ است",
|
||||
"errors.staleProgress": "پیشرفت ذخیرهشده قابل ادامه نیست",
|
||||
"feature.cloudSaves": "ذخیرهسازی ابری",
|
||||
"feature.coOp": "همکاری",
|
||||
"feature.crossPlay": "بازی متقابل",
|
||||
"feature.dualsenseFeatures": "قابلیتهای DualSense",
|
||||
"feature.onlineMultiplayer": "چندنفره آنلاین",
|
||||
"feature.singlePlayer": "تکنفره",
|
||||
"files.byShadowMount": "ShadowMount نصبش میکند",
|
||||
"files.confirmDelete": "دوباره بزنید تا حذف شود · {size}",
|
||||
"files.deleteFailed": "این فایل حذف نشد. دوباره امتحان کنید.",
|
||||
"files.deleted": "{name} حذف شد",
|
||||
"files.drives": "درایوها",
|
||||
"files.empty": "این درایو خالی است",
|
||||
"files.emptyDetail": "فایلهای دانلودشده اینجا نشان داده میشوند.",
|
||||
"files.free": "فضای آزاد: {free} از {total}",
|
||||
"files.installDone": "نصب شد. روی صفحهٔ اصلی است.",
|
||||
"files.installFailed": "کنسول این پکیج را نپذیرفت. مطمئن شوید kstuff بارگذاری شده و دوباره امتحان کنید.",
|
||||
"files.installed": "نصبشده",
|
||||
"files.installing": "در حال نصب · {percent}",
|
||||
"files.kind.all": "همهٔ فایلها",
|
||||
"files.kind.archives": "آرشیوها",
|
||||
"files.kind.extracted": "استخراجشده",
|
||||
"files.kind.images": "ایمیجهای بازی",
|
||||
"files.kind.other": "سایر فایلها",
|
||||
"files.kind.packages": "پکیجها",
|
||||
"files.kinds": "نمایش",
|
||||
"files.notInstalled": "نصب نشده",
|
||||
"files.otherData": "بازیها و سیستم",
|
||||
"files.shadowMountOff": "ShadowMount در حال اجرا نیست",
|
||||
"files.shadowMountOn": "ShadowMount در حال اجراست",
|
||||
"files.summary": "{count, plural, one {# فایل} other {# فایل}} · {size}",
|
||||
"files.title": "فایلها",
|
||||
"files.type.archive": "آرشیو",
|
||||
"files.type.file": "فایل",
|
||||
"files.type.folder": "پوشه",
|
||||
"files.type.image": "ایمیج بازی",
|
||||
"files.type.package": "PKG",
|
||||
"files.waitingShadowMount": "منتظر ShadowMount",
|
||||
"filters.access.direct": "دانلود مستقیم",
|
||||
"filters.clearAll": "پاک کردن همه",
|
||||
"filters.clearFacet": "پاک کردن",
|
||||
"filters.facet.access": "دانلود",
|
||||
"filters.facet.feature": "قابلیتها",
|
||||
"filters.facet.genre": "ژانر",
|
||||
"filters.facet.platform": "پلتفرم",
|
||||
"filters.facet.provider": "میزبان دانلود",
|
||||
"filters.facet.release": "انتشار",
|
||||
"filters.facet.size": "حجم",
|
||||
"filters.favorites": "علاقهمندیها",
|
||||
"filters.more": "+{count} بیشتر",
|
||||
"filters.queryChip": "«{query}»",
|
||||
"filters.release.current": "۲۰۲۰–۲۰۲۳",
|
||||
"filters.release.older": "قبل از ۲۰۲۰",
|
||||
"filters.release.recent": "از ۲۰۲۴ به بعد",
|
||||
"filters.selected": "{count} انتخابشده",
|
||||
"filters.show": "نمایش {count, plural, one {# بازی} other {# بازی}}",
|
||||
"filters.size.large": "بیش از ۵۰ گیگابایت",
|
||||
"filters.size.medium": "۱۰–۵۰ گیگابایت",
|
||||
"filters.size.small": "کمتر از ۱۰ گیگابایت",
|
||||
"filters.title": "فیلترها",
|
||||
"game.about": "دربارهٔ این بازی",
|
||||
"game.addToFavorites": "افزودن به علاقهمندیها",
|
||||
"game.back.browse": "کتابخانه",
|
||||
"game.back.downloads": "دانلودها",
|
||||
"game.back.search": "جستجو",
|
||||
"game.details": "جزئیات",
|
||||
"game.facts.downloadSize": "حجم دانلود",
|
||||
"game.facts.installSize": "حجم نصب",
|
||||
"game.facts.platform": "پلتفرم",
|
||||
"game.facts.publisher": "ناشر",
|
||||
"game.facts.releaseDate": "تاریخ انتشار",
|
||||
"game.favorite": "علاقهمندی",
|
||||
"game.features": "قابلیتها",
|
||||
"game.metacritic": "Metacritic",
|
||||
"game.moreLikeThis": "مشابه این",
|
||||
"game.released": "منتشرشده در {year}",
|
||||
"game.shotPosition": "{index} / {total}",
|
||||
"game.showLess": "نمایش کمتر",
|
||||
"game.showMore": "نمایش بیشتر",
|
||||
"genre.action": "اکشن",
|
||||
"genre.adventure": "ماجراجویی",
|
||||
"genre.arcade": "آرکید",
|
||||
"genre.boardGame": "بازی رومیزی",
|
||||
"genre.brainTraining": "تمرین ذهن",
|
||||
"genre.casual": "کژوال",
|
||||
"genre.educational": "آموزشی",
|
||||
"genre.family": "خانوادگی",
|
||||
"genre.fighting": "مبارزه",
|
||||
"genre.horror": "ترسناک",
|
||||
"genre.indie": "مستقل",
|
||||
"genre.musicRhythm": "موسیقی/ریتم",
|
||||
"genre.party": "مهمانی",
|
||||
"genre.puzzle": "پازل",
|
||||
"genre.quiz": "کوییز",
|
||||
"genre.racing": "مسابقهای",
|
||||
"genre.rpg": "نقشآفرینی",
|
||||
"genre.shooter": "تیراندازی",
|
||||
"genre.simulation": "شبیهسازی",
|
||||
"genre.sport": "ورزشی",
|
||||
"genre.sports": "ورزشی",
|
||||
"genre.strategy": "استراتژی",
|
||||
"hints.back": "بازگشت",
|
||||
"hints.clear": "پاک کردن",
|
||||
"hints.close": "بستن",
|
||||
"hints.confirm": "تأیید",
|
||||
"hints.confirmDelete": "حذف قطعی",
|
||||
"hints.delete": "حذف",
|
||||
"hints.favorite": "علاقهمندی",
|
||||
"hints.filters": "فیلترها",
|
||||
"hints.gamePage": "صفحهٔ بازی",
|
||||
"hints.install": "نصب",
|
||||
"hints.pauseResume": "توقف / ادامه",
|
||||
"hints.previousNext": "قبلی / بعدی",
|
||||
"hints.remove": "حذف",
|
||||
"hints.search": "جستجو",
|
||||
"hints.sections": "بخشها",
|
||||
"hints.select": "انتخاب",
|
||||
"hints.space": "فاصله",
|
||||
"hints.toggle": "تغییر وضعیت",
|
||||
"hints.top": "بالا",
|
||||
"hints.type": "تایپ",
|
||||
"hints.view": "مشاهده",
|
||||
"install.added": "به دانلودها اضافه شد",
|
||||
"install.complete": "دانلود کامل شد",
|
||||
"install.manage": "در دانلودها مدیریت کنید",
|
||||
"keyboard.clear": "پاک کردن",
|
||||
"keyboard.delete": "حذف",
|
||||
"keyboard.done": "تمام",
|
||||
"keyboard.lowercase": "حروف کوچک",
|
||||
"keyboard.space": "فاصله",
|
||||
"keyboard.uppercase": "حروف بزرگ",
|
||||
"language.name.ar": "عربی",
|
||||
"language.name.bn": "بنگالی",
|
||||
"language.name.de": "آلمانی",
|
||||
"language.name.en": "انگلیسی",
|
||||
"language.name.es": "اسپانیایی",
|
||||
"language.name.fa": "فارسی",
|
||||
"language.name.fr": "فرانسوی",
|
||||
"language.name.hi": "هندی",
|
||||
"language.name.id": "اندونزیایی",
|
||||
"language.name.it": "ایتالیایی",
|
||||
"language.name.ja": "ژاپنی",
|
||||
"language.name.pt-BR": "پرتغالی (برزیل)",
|
||||
"language.name.ru": "روسی",
|
||||
"language.name.ur": "اردو",
|
||||
"language.name.zh-Hans": "چینی (سادهشده)",
|
||||
"language.subtitle": "عنوان و توضیحات بازیها همانطور که فروشگاه فهرست کرده میمانند.",
|
||||
"language.system": "زبان سیستم",
|
||||
"language.title": "زبان",
|
||||
"nav.allGames": "همهٔ بازیها",
|
||||
"nav.browse": "مرور",
|
||||
"nav.discover": "کشف",
|
||||
"nav.files": "فایلها",
|
||||
"nav.new": "تازهها",
|
||||
"nav.search": "جستجو",
|
||||
"nav.top": "برترینها",
|
||||
"search.noMatch": "هیچ بازیای با «{query}» مطابقت ندارد",
|
||||
"search.placeholder": "جستجو در کتابخانه",
|
||||
"search.popular": "محبوب",
|
||||
"search.recent": "جستجوهای اخیر",
|
||||
"search.resultsFor": "نتایج برای «{query}»",
|
||||
"search.tryGenre": "املا را چک کنید یا یک ژانر امتحان کنید:",
|
||||
"settings.controller": "دسته",
|
||||
"settings.downloads": "دانلودها",
|
||||
"settings.speedLimit": "محدودیت سرعت",
|
||||
"settings.speedLimitDetail": "پهنای باند را برای دستگاههای دیگر نگه میدارد. بهروزرسانیهای برنامه هرگز محدود نمیشوند.",
|
||||
"settings.speedUnlimited": "نامحدود",
|
||||
"settings.title": "تنظیمات",
|
||||
"settings.vibration": "لرزش هنگام مرور",
|
||||
"settings.vibrationDetail": "ضربهٔ ملایم هنگام حرکت، انتخاب و بازگشت",
|
||||
"shadowMount.offAction": "بازی وقتی بارگذاری شود ظاهر میشود. آن را به autoload جیلبریک اضافه کنید تا روشن بماند.",
|
||||
"shadowMount.offTitle": "ShadowMount در حال اجرا نیست",
|
||||
"sort.label": "مرتبسازی",
|
||||
"sort.newest": "تازهها",
|
||||
"sort.rating": "برترینها",
|
||||
"sort.relevance": "محبوبترین",
|
||||
"sort.size": "بزرگترین نصبها",
|
||||
"sort.title": "الف تا ی",
|
||||
"sources.browserNote": "هر بخش صفحهٔ یک میزبان را باز میکند، سپس لینک فایل را با دسته وارد میکنید.",
|
||||
"sources.cdnFastest": "سریعترین",
|
||||
"sources.kind.archive": "آرشیو",
|
||||
"sources.kind.image": "ایمیج بازی",
|
||||
"sources.kind.package": "پکیج",
|
||||
"sources.kind.patch": "فقط فایلهای بکپورت، نه کل بازی",
|
||||
"sources.status.dlc": "DLC: بازی اصلی لازم است",
|
||||
"sources.status.package": "PKGهای جعلی PS5 فقط تا فریمور 11.60 نصب میشوند",
|
||||
"sources.status.patch": "بازی اصلی لازم است، بهتنهایی نصب نمیشود",
|
||||
"sources.noDetails": "بدون جزئیات",
|
||||
"sources.parts": "{count, plural, one {# بخش} other {# بخش}}",
|
||||
"sources.recommended": "پیشنهادی",
|
||||
"sources.section.browser": "باز کردن در مرورگر وب",
|
||||
"sources.section.direct": "دانلود در Overdrive",
|
||||
"sources.section.dlc": "افزونهها (DLC)",
|
||||
"sources.section.dlcNote": "محتوای اضافه برای بازی، نه خود بازی.",
|
||||
"sources.section.page": "از صفحات میزبان",
|
||||
"sources.section.pageNote": "Overdrive لینک فایل را از صفحه میخواند. اگر میزبان تأیید بخواهد، شما را آنجا میفرستد.",
|
||||
"sources.section.versions": "نسخه",
|
||||
"sources.showBrowser": "{count, plural, one {نمایش # منبع مرورگر وب} other {نمایش # منبع مرورگر وب}}",
|
||||
"sources.speed.archive": "چند اتصال همزمان",
|
||||
"sources.speed.mediafire": "محدود به حدود ۳ مگابایت بر ثانیه",
|
||||
"sources.title": "منبع دانلود",
|
||||
"time.hoursMinutes": "{hours} س {minutes} د",
|
||||
"time.minutes": "{n} دقیقه",
|
||||
"time.seconds": "{n} ثانیه",
|
||||
"toast.openDownloads": "باز کردن دانلودها",
|
||||
"toast.viewGame": "مشاهدهٔ بازی",
|
||||
"tips.eyebrow": "دانلود",
|
||||
"tips.title": "پیش از دانلود",
|
||||
"tips.downloading": "در حال دانلود",
|
||||
"tips.about": "دربارهٔ این منبع",
|
||||
"tips.toggle": "نکتههایی برای انتخاب منبع",
|
||||
"tips.continue": "ادامه",
|
||||
"tips.changeSource": "تغییر منبع",
|
||||
"tips.note.dlc": "این یک DLC است، نه خود بازی. اول بازی اصلی را نصب کنید.",
|
||||
"tips.note.patch": "اینها فقط فایلهای بکپورت هستند، نه کل بازی. بهتنهایی نصب نمیشوند.",
|
||||
"tips.note.package": "این یک PKG جعلی PS5 است. فقط روی فریمور 11.60 یا قدیمیتر نصب میشود.",
|
||||
"tips.note.browserViking": "این منبع در مرورگر PS5 باز میشود. تأیید را کامل کنید و صبر کنید. روی Download یا هیچ تبلیغی نزنید.",
|
||||
"tips.note.browserOther": "این منبع در مرورگر PS5 باز میشود و بعد لینک فایل را میخواهد. اگر میتوانید، منبعی بالاتر در فهرست انتخاب کنید.",
|
||||
"tips.note.firmware": "این نسخه به فریمور {need} یا جدیدتر نیاز دارد. نسخهٔ خود را در تنظیمات > سیستم > نرمافزار سیستم ببینید.",
|
||||
"tips.tip.firmware.title": "فریمور را ببینید",
|
||||
"tips.tip.firmware.body": "Base 8.xx به 8.xx یا جدیدتر نیاز دارد. BackPort برای کنسولهای قدیمی است.",
|
||||
"tips.tip.fullGame.title": "بازی کامل را بگیرید",
|
||||
"tips.tip.fullGame.body": "Base یا BackPort، نه DLC یا فایلهای بکپورت.",
|
||||
"tips.tip.top.title": "از بالا شروع کنید",
|
||||
"tips.tip.top.body": "منابع اول بهتریناند.",
|
||||
"tips.tip.retry.title": "نشد؟ منبع دیگر",
|
||||
"tips.tip.retry.body": "بیشتر بازیها چند منبع دارند.",
|
||||
"units.gigabytes": "{n} گیگابایت",
|
||||
"units.megabytes": "{n} مگابایت",
|
||||
"units.millions": "{n} میلیون",
|
||||
"units.speed": "{n} مگابایت/ث",
|
||||
"units.thousands": "{n} هزار",
|
||||
"update.available": "بهروزرسانی موجود است",
|
||||
"update.close": "بستن",
|
||||
"update.detail": "نسخهٔ جدیدی از Overdrive آماده است. درجا نصب میشود و خودش دوباره باز میشود.",
|
||||
"update.done": "بهروزرسانی شد",
|
||||
"update.failed": "بهروزرسانی ناموفق",
|
||||
"update.install": "الان بهروزرسانی کن",
|
||||
"update.installing": "در حال نصب…",
|
||||
"update.later": "بعداً",
|
||||
"update.quit": "بستن Overdrive",
|
||||
"update.reopen": "در حال بسته شدن. Overdrive را دوباره از صفحهٔ اصلی باز کنید",
|
||||
"update.reopenInstalled": "Overdrive به نسخهٔ {version} بهروز شد. آن را ببندید و دوباره از صفحهٔ اصلی باز کنید.",
|
||||
"update.replaceByHand": "فایل update.zip را از github.com/MyLuxy/overdrive دانلود کنید، Overdrive را ببندید و فایل .exfat را جایگزین فایل قبلی کنید.",
|
||||
"update.replaceFailed": "نمیتوان ایمیج نصبشده را وقتی Overdrive باز است جایگزین کرد.",
|
||||
"update.restartConsole": "اگر همچنان نسخهٔ قدیمی باز میشود، کنسول را ریاستارت کنید.",
|
||||
"update.restarting": "در حال راهاندازی دوبارهٔ Overdrive…",
|
||||
"update.retry": "تلاش دوباره",
|
||||
"update.updating": "در حال بهروزرسانی",
|
||||
"urlDialog.cancel": "لغو",
|
||||
"urlDialog.confirm": "تأیید آدرس",
|
||||
"urlDialog.controls": "مربع: حذف · R2: تأیید · دایره: لغو",
|
||||
"urlDialog.empty": "(خالی)",
|
||||
"versions.name": "نسخهٔ {platform}",
|
||||
"versions.note.ps4": "روی PS5 از طریق سازگاری عقبرو اجرا میشود",
|
||||
"versions.note.ps5": "بهصورت بومی روی PS5 اجرا میشود",
|
||||
"versions.recommended": "پیشنهادی · بهصورت بومی روی PS5 اجرا میشود",
|
||||
"versions.sources": "{count, plural, one {# منبع} other {# منبع}}",
|
||||
"versions.thisPage": "این صفحه",
|
||||
"versions.title": "انتخاب نسخه",
|
||||
"capture.title": "تأیید Vikingfile",
|
||||
"capture.preparing": "آمادهسازی تأیید فایل…",
|
||||
"capture.capturing": "تأیید را کامل کنید، سپس صبر کنید",
|
||||
"capture.validating": "در حال بررسی فایل ضبطشده…",
|
||||
"capture.detail": "روی Download یا هیچ تبلیغی نزنید: لینک خودش گرفته میشود. سپس به اینجا برگردید تا دانلود خودکار شروع شود. گرفتن لینک تا ۳ دقیقه طول میکشد."
|
||||
}
|
||||
@@ -172,6 +172,8 @@
|
||||
"errors.fileChanged": "Le fichier a changé sur le serveur",
|
||||
"errors.interrupted": "Arrêté quand l'app s'est fermée",
|
||||
"errors.linkGone": "Le lien de téléchargement ne fonctionne plus",
|
||||
"errors.notGameFile": "L'hébergeur a envoyé une page web au lieu du jeu",
|
||||
"errors.pkgFirmware": "Les faux PKG PS5 ne s'installent que jusqu'au firmware 11.60",
|
||||
"errors.missingParts": "Il manque des parties à cette source",
|
||||
"errors.needsBrowser": "Cette source nécessite un navigateur web",
|
||||
"errors.noConnection": "Pas de connexion au serveur",
|
||||
@@ -317,6 +319,7 @@
|
||||
"language.name.de": "Allemand",
|
||||
"language.name.en": "Anglais",
|
||||
"language.name.es": "Espagnol",
|
||||
"language.name.fa": "Persan",
|
||||
"language.name.fr": "Français",
|
||||
"language.name.hi": "Hindi",
|
||||
"language.name.id": "Indonésien",
|
||||
@@ -343,6 +346,10 @@
|
||||
"search.resultsFor": "Résultats pour « {query} »",
|
||||
"search.tryGenre": "Vérifiez l'orthographe, ou essayez un genre :",
|
||||
"settings.controller": "Manette",
|
||||
"settings.downloads": "Téléchargements",
|
||||
"settings.speedLimit": "Limite de vitesse",
|
||||
"settings.speedLimitDetail": "Laisse de la bande passante aux autres appareils. Les mises à jour de l'app ne sont jamais limitées.",
|
||||
"settings.speedUnlimited": "Illimitée",
|
||||
"settings.title": "Réglages",
|
||||
"settings.vibration": "Vibration pendant la navigation",
|
||||
"settings.vibrationDetail": "Une légère impulsion quand vous vous déplacez, sélectionnez ou revenez",
|
||||
@@ -359,6 +366,10 @@
|
||||
"sources.kind.archive": "Archive",
|
||||
"sources.kind.image": "Image",
|
||||
"sources.kind.package": "Paquet",
|
||||
"sources.kind.patch": "Fichiers de backport seuls, pas le jeu complet",
|
||||
"sources.status.dlc": "DLC : nécessite le jeu de base",
|
||||
"sources.status.package": "Les faux PKG PS5 ne s'installent que jusqu'au firmware 11.60",
|
||||
"sources.status.patch": "Nécessite le jeu de base, ne s'installe pas seul",
|
||||
"sources.noDetails": "Aucun détail",
|
||||
"sources.parts": "{count, plural, one {# partie} many {# millions de parties} other {# parties}}",
|
||||
"sources.recommended": "Recommandé",
|
||||
@@ -378,6 +389,27 @@
|
||||
"time.seconds": "{n} s",
|
||||
"toast.openDownloads": "Ouvrir Téléchargements",
|
||||
"toast.viewGame": "Voir le jeu",
|
||||
"tips.eyebrow": "Téléchargement",
|
||||
"tips.title": "Avant de télécharger",
|
||||
"tips.downloading": "Tu télécharges",
|
||||
"tips.about": "À propos de cette source",
|
||||
"tips.toggle": "Conseils pour choisir la source",
|
||||
"tips.continue": "Continuer",
|
||||
"tips.changeSource": "Changer de source",
|
||||
"tips.note.dlc": "C'est un DLC, pas le jeu. Installe d'abord le jeu de base.",
|
||||
"tips.note.patch": "Ce sont seulement des fichiers de backport, pas le jeu complet. Seuls, ils ne s'installent pas.",
|
||||
"tips.note.package": "C'est un faux PKG PS5. Il ne s'installe qu'en firmware 11.60 ou antérieur.",
|
||||
"tips.note.browserViking": "Cette source s'ouvre dans le navigateur de la PS5. Termine la vérification, puis patiente. N'appuie ni sur Télécharger ni sur une pub.",
|
||||
"tips.note.browserOther": "Cette source s'ouvre dans le navigateur de la PS5, puis demande le lien du fichier. Si possible, choisis une source plus haut.",
|
||||
"tips.note.firmware": "Cette version nécessite le firmware {need} ou plus récent. Vérifie le tien dans Paramètres > Système > Logiciel système.",
|
||||
"tips.tip.firmware.title": "Vérifie le firmware",
|
||||
"tips.tip.firmware.body": "Base 8.xx nécessite 8.xx ou plus. BackPort est pour les anciennes consoles.",
|
||||
"tips.tip.fullGame.title": "Prends le jeu complet",
|
||||
"tips.tip.fullGame.body": "Base ou BackPort, pas de DLC ni de fichiers backport.",
|
||||
"tips.tip.top.title": "Commence en haut",
|
||||
"tips.tip.top.body": "Les premières sources marchent le mieux.",
|
||||
"tips.tip.retry.title": "Échec ? Essaie une autre",
|
||||
"tips.tip.retry.body": "La plupart des jeux en ont plusieurs.",
|
||||
"units.gigabytes": "{n} Go",
|
||||
"units.megabytes": "{n} Mo",
|
||||
"units.millions": "{n}M",
|
||||
|
||||
@@ -172,6 +172,8 @@
|
||||
"errors.fileChanged": "फ़ाइल सर्वर पर बदल गई",
|
||||
"errors.interrupted": "जब ऐप बंद हुआ तो रुका",
|
||||
"errors.linkGone": "डाउनलोड लिंक अब काम नहीं करता",
|
||||
"errors.notGameFile": "होस्ट ने गेम की जगह एक वेब पेज भेजा",
|
||||
"errors.pkgFirmware": "PS5 के फ़ेक PKG केवल फ़र्मवेयर 11.60 तक इंस्टॉल होते हैं",
|
||||
"errors.missingParts": "इस स्रोत में भाग कम हैं",
|
||||
"errors.needsBrowser": "यह स्रोत एक वेब ब्राउज़र की आवश्यकता है",
|
||||
"errors.noConnection": "सर्वर से कोई कनेक्शन नहीं",
|
||||
@@ -317,6 +319,7 @@
|
||||
"language.name.de": "जर्मन",
|
||||
"language.name.en": "अंग्रेजी",
|
||||
"language.name.es": "स्पेनिश",
|
||||
"language.name.fa": "फ़ारसी",
|
||||
"language.name.fr": "फ्रांसीसी",
|
||||
"language.name.hi": "हिंदी",
|
||||
"language.name.id": "इंडोनेशियन",
|
||||
@@ -343,6 +346,10 @@
|
||||
"search.resultsFor": "'{query}' के लिए परिणाम",
|
||||
"search.tryGenre": "वर्तनी जाँचें, या एक शैली आजमाएँ:",
|
||||
"settings.controller": "कंट्रोलर",
|
||||
"settings.downloads": "डाउनलोड",
|
||||
"settings.speedLimit": "स्पीड सीमा",
|
||||
"settings.speedLimitDetail": "दूसरे डिवाइसों के लिए बैंडविड्थ छोड़ता है। ऐप अपडेट कभी सीमित नहीं होते।",
|
||||
"settings.speedUnlimited": "असीमित",
|
||||
"settings.title": "सेटिंग्स",
|
||||
"settings.vibration": "ब्राउज़ करते समय वाइब्रेशन",
|
||||
"settings.vibrationDetail": "आगे बढ़ने, चुनने और वापस जाने पर हल्का कंपन",
|
||||
@@ -359,6 +366,10 @@
|
||||
"sources.kind.archive": "संग्रह",
|
||||
"sources.kind.image": "गेम इमेज",
|
||||
"sources.kind.package": "पैकेज",
|
||||
"sources.kind.patch": "सिर्फ़ बैकपोर्ट फ़ाइलें, पूरा गेम नहीं",
|
||||
"sources.status.dlc": "DLC: बेस गेम चाहिए",
|
||||
"sources.status.package": "PS5 के फ़ेक PKG केवल फ़र्मवेयर 11.60 तक इंस्टॉल होते हैं",
|
||||
"sources.status.patch": "बेस गेम चाहिए, अकेले इंस्टॉल नहीं होगा",
|
||||
"sources.noDetails": "कोई विवरण नहीं",
|
||||
"sources.parts": "{count, plural, one {# भाग} other {# भाग}}",
|
||||
"sources.recommended": "सुझाया गया",
|
||||
@@ -378,6 +389,27 @@
|
||||
"time.seconds": "{n} सेकंड",
|
||||
"toast.openDownloads": "डाउनलोड खोलें",
|
||||
"toast.viewGame": "गेम देखें",
|
||||
"tips.eyebrow": "डाउनलोड",
|
||||
"tips.title": "डाउनलोड से पहले",
|
||||
"tips.downloading": "आप डाउनलोड कर रहे हैं",
|
||||
"tips.about": "इस सोर्स के बारे में",
|
||||
"tips.toggle": "सोर्स चुनने के सुझाव",
|
||||
"tips.continue": "जारी रखें",
|
||||
"tips.changeSource": "सोर्स बदलें",
|
||||
"tips.note.dlc": "यह DLC है, गेम नहीं। पहले बेस गेम इंस्टॉल करें।",
|
||||
"tips.note.patch": "ये सिर्फ़ बैकपोर्ट फ़ाइलें हैं, पूरा गेम नहीं। अकेले इंस्टॉल नहीं होतीं।",
|
||||
"tips.note.package": "यह PS5 का फ़ेक PKG है। यह केवल फ़र्मवेयर 11.60 या पुराने पर इंस्टॉल होता है।",
|
||||
"tips.note.browserViking": "यह सोर्स PS5 ब्राउज़र में खुलता है। वेरिफ़िकेशन पूरा करें, फिर रुकें। Download या किसी विज्ञापन को न दबाएँ।",
|
||||
"tips.note.browserOther": "यह सोर्स PS5 ब्राउज़र में खुलता है और फिर फ़ाइल का लिंक माँगता है। हो सके तो सूची में ऊपर वाला सोर्स चुनें।",
|
||||
"tips.note.firmware": "इस वर्ज़न को फ़र्मवेयर {need} या नया चाहिए। अपना सेटिंग्स > सिस्टम > सिस्टम सॉफ़्टवेयर में देखें।",
|
||||
"tips.tip.firmware.title": "फ़र्मवेयर देखें",
|
||||
"tips.tip.firmware.body": "Base 8.xx को 8.xx या नया चाहिए। BackPort पुराने कंसोल के लिए है।",
|
||||
"tips.tip.fullGame.title": "पूरा गेम लें",
|
||||
"tips.tip.fullGame.body": "Base या BackPort, DLC या बैकपोर्ट फ़ाइलें नहीं।",
|
||||
"tips.tip.top.title": "ऊपर से शुरू करें",
|
||||
"tips.tip.top.body": "ऊपर के सोर्स सबसे अच्छे हैं।",
|
||||
"tips.tip.retry.title": "न चले? दूसरा सोर्स",
|
||||
"tips.tip.retry.body": "ज़्यादातर गेम के कई सोर्स हैं।",
|
||||
"units.gigabytes": "{n} GB",
|
||||
"units.megabytes": "{n} MB",
|
||||
"units.millions": "{n}M",
|
||||
|
||||
@@ -172,6 +172,8 @@
|
||||
"errors.fileChanged": "File berubah di server",
|
||||
"errors.interrupted": "Dihentikan saat aplikasi ditutup",
|
||||
"errors.linkGone": "Tautan unduhan tidak lagi berfungsi",
|
||||
"errors.notGameFile": "Host mengirim halaman web, bukan gamenya",
|
||||
"errors.pkgFirmware": "PKG palsu PS5 hanya bisa dipasang hingga firmware 11.60",
|
||||
"errors.missingParts": "Sumber ini kekurangan bagian",
|
||||
"errors.needsBrowser": "Sumber ini memerlukan browser web",
|
||||
"errors.noConnection": "Tidak ada koneksi ke server",
|
||||
@@ -317,6 +319,7 @@
|
||||
"language.name.de": "Jerman",
|
||||
"language.name.en": "English",
|
||||
"language.name.es": "Spanyol",
|
||||
"language.name.fa": "Bahasa Persia",
|
||||
"language.name.fr": "Perancis",
|
||||
"language.name.hi": "Hindi",
|
||||
"language.name.id": "Indonesia",
|
||||
@@ -343,6 +346,10 @@
|
||||
"search.resultsFor": "Hasil untuk \"{query}\"",
|
||||
"search.tryGenre": "Periksa ejaan, atau coba genre:",
|
||||
"settings.controller": "Kontroler",
|
||||
"settings.downloads": "Unduhan",
|
||||
"settings.speedLimit": "Batas kecepatan",
|
||||
"settings.speedLimitDetail": "Menyisakan bandwidth untuk perangkat lain. Pembaruan aplikasi tidak pernah dibatasi.",
|
||||
"settings.speedUnlimited": "Tanpa batas",
|
||||
"settings.title": "Pengaturan",
|
||||
"settings.vibration": "Getaran saat menjelajah",
|
||||
"settings.vibrationDetail": "Getaran ringan saat berpindah, memilih, dan kembali",
|
||||
@@ -359,6 +366,10 @@
|
||||
"sources.kind.archive": "Arsip",
|
||||
"sources.kind.image": "Citra game",
|
||||
"sources.kind.package": "Paket",
|
||||
"sources.kind.patch": "Hanya file backport, bukan game lengkap",
|
||||
"sources.status.dlc": "DLC: butuh game dasar",
|
||||
"sources.status.package": "PKG palsu PS5 hanya bisa dipasang hingga firmware 11.60",
|
||||
"sources.status.patch": "Butuh game dasar, tidak bisa dipasang sendiri",
|
||||
"sources.noDetails": "Tidak ada detail",
|
||||
"sources.parts": "{count, plural, other {# bagian}}",
|
||||
"sources.recommended": "Direkomendasikan",
|
||||
@@ -378,6 +389,27 @@
|
||||
"time.seconds": "{n} det",
|
||||
"toast.openDownloads": "Buka Unduhan",
|
||||
"toast.viewGame": "Lihat game",
|
||||
"tips.eyebrow": "Unduh",
|
||||
"tips.title": "Sebelum mengunduh",
|
||||
"tips.downloading": "Kamu mengunduh",
|
||||
"tips.about": "Tentang sumber ini",
|
||||
"tips.toggle": "Tips memilih sumber",
|
||||
"tips.continue": "Lanjut",
|
||||
"tips.changeSource": "Ganti sumber",
|
||||
"tips.note.dlc": "Ini DLC, bukan game-nya. Pasang game dasar dulu.",
|
||||
"tips.note.patch": "Ini hanya file backport, bukan game lengkap. Tidak bisa dipasang sendiri.",
|
||||
"tips.note.package": "Ini PKG palsu PS5. Hanya bisa dipasang di firmware 11.60 atau lebih lama.",
|
||||
"tips.note.browserViking": "Sumber ini terbuka di browser PS5. Selesaikan verifikasi, lalu tunggu. Jangan tekan Download atau iklan.",
|
||||
"tips.note.browserOther": "Sumber ini terbuka di browser PS5 lalu meminta tautan file. Kalau bisa, pilih sumber yang lebih atas.",
|
||||
"tips.note.firmware": "Versi ini butuh firmware {need} atau lebih baru. Cek milikmu di Pengaturan > Sistem > Perangkat Lunak Sistem.",
|
||||
"tips.tip.firmware.title": "Cek firmware",
|
||||
"tips.tip.firmware.body": "Base 8.xx butuh 8.xx ke atas. BackPort untuk konsol lama.",
|
||||
"tips.tip.fullGame.title": "Ambil game lengkap",
|
||||
"tips.tip.fullGame.body": "Base atau BackPort, bukan DLC atau file backport.",
|
||||
"tips.tip.top.title": "Mulai dari atas",
|
||||
"tips.tip.top.body": "Sumber teratas paling bagus.",
|
||||
"tips.tip.retry.title": "Gagal? Coba yang lain",
|
||||
"tips.tip.retry.body": "Kebanyakan game punya beberapa.",
|
||||
"units.gigabytes": "{n} GB",
|
||||
"units.megabytes": "{n} MB",
|
||||
"units.millions": "{n}J",
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
"browse.recentlyViewed": "Visti di recente",
|
||||
"browse.reviews": "{count} recensioni",
|
||||
"browse.viewGame": "Vedi gioco",
|
||||
"capture.capturing": "Completa la verifica e seleziona Download",
|
||||
"capture.detail": "Torna qui dopo aver selezionato Download. Il download parte da solo quando il file è verificato. L'acquisizione dura al massimo 3 minuti.",
|
||||
"capture.capturing": "Completa la verifica, poi aspetta",
|
||||
"capture.detail": "Non premere Download né le pubblicità: il link viene catturato da solo. Poi torna qui e il download parte in automatico. La cattura dura fino a 3 minuti.",
|
||||
"capture.preparing": "Preparazione della verifica del file…",
|
||||
"capture.title": "Verifica Vikingfile",
|
||||
"capture.validating": "Controllo del file acquisito…",
|
||||
@@ -178,6 +178,8 @@
|
||||
"errors.fileChanged": "Il file è cambiato sul server",
|
||||
"errors.interrupted": "Interrotto alla chiusura dell'app",
|
||||
"errors.linkGone": "Il link di download non funziona più",
|
||||
"errors.notGameFile": "L'host ha inviato una pagina web invece del gioco",
|
||||
"errors.pkgFirmware": "I fake PKG per PS5 si installano solo fino al firmware 11.60",
|
||||
"errors.missingParts": "In questa fonte mancano delle parti",
|
||||
"errors.needsBrowser": "Questa fonte richiede un browser web",
|
||||
"errors.noConnection": "Nessuna connessione al server",
|
||||
@@ -323,6 +325,7 @@
|
||||
"language.name.de": "Tedesco",
|
||||
"language.name.en": "Inglese",
|
||||
"language.name.es": "Spagnolo",
|
||||
"language.name.fa": "Persiano",
|
||||
"language.name.fr": "Francese",
|
||||
"language.name.hi": "Hindi",
|
||||
"language.name.id": "Indonesiano",
|
||||
@@ -349,6 +352,10 @@
|
||||
"search.resultsFor": "Risultati per “{query}”",
|
||||
"search.tryGenre": "Controlla l'ortografia, oppure prova un genere:",
|
||||
"settings.controller": "Controller",
|
||||
"settings.downloads": "Download",
|
||||
"settings.speedLimit": "Limite di velocità",
|
||||
"settings.speedLimitDetail": "Lascia banda agli altri dispositivi. Gli aggiornamenti dell'app non sono mai limitati.",
|
||||
"settings.speedUnlimited": "Illimitata",
|
||||
"settings.title": "Impostazioni",
|
||||
"settings.vibration": "Vibrazione durante la navigazione",
|
||||
"settings.vibrationDetail": "Un leggero impulso quando ti sposti, selezioni e torni indietro",
|
||||
@@ -365,6 +372,10 @@
|
||||
"sources.kind.archive": "Archivio",
|
||||
"sources.kind.image": "Immagine di gioco",
|
||||
"sources.kind.package": "Pacchetto",
|
||||
"sources.kind.patch": "Solo file di backport, non il gioco completo",
|
||||
"sources.status.dlc": "DLC: serve il gioco base",
|
||||
"sources.status.package": "I fake PKG per PS5 si installano solo fino al firmware 11.60",
|
||||
"sources.status.patch": "Serve il gioco base, da sola non si installa",
|
||||
"sources.noDetails": "Nessun dettaglio",
|
||||
"sources.parts": "{count, plural, one {# parte} other {# parti}}",
|
||||
"sources.recommended": "Consigliata",
|
||||
@@ -384,6 +395,27 @@
|
||||
"time.seconds": "{n} s",
|
||||
"toast.openDownloads": "Apri Download",
|
||||
"toast.viewGame": "Vedi gioco",
|
||||
"tips.eyebrow": "Download",
|
||||
"tips.title": "Prima di scaricare",
|
||||
"tips.downloading": "Stai scaricando",
|
||||
"tips.about": "Su questa fonte",
|
||||
"tips.toggle": "Consigli per scegliere la fonte",
|
||||
"tips.continue": "Continua",
|
||||
"tips.changeSource": "Cambia fonte",
|
||||
"tips.note.dlc": "Questo è un DLC, non il gioco. Installa prima il gioco base.",
|
||||
"tips.note.patch": "Questi sono solo file di backport, non il gioco completo. Da soli non si installano.",
|
||||
"tips.note.package": "Questo è un fake PKG per PS5. Si installa solo con firmware 11.60 o precedente.",
|
||||
"tips.note.browserViking": "Questa fonte si apre nel browser della PS5. Completa la verifica e aspetta. Non premere Download né le pubblicità.",
|
||||
"tips.note.browserOther": "Questa fonte si apre nel browser della PS5 e poi chiede il link del file. Se puoi, scegli una fonte più in alto nella lista.",
|
||||
"tips.note.firmware": "Questa versione richiede firmware {need} o successivo. Controlla il tuo in Impostazioni > Sistema > Software di sistema.",
|
||||
"tips.tip.firmware.title": "Guarda il firmware",
|
||||
"tips.tip.firmware.body": "Base 8.xx richiede 8.xx o più. BackPort è per console più vecchie.",
|
||||
"tips.tip.fullGame.title": "Prendi il gioco completo",
|
||||
"tips.tip.fullGame.body": "Base o BackPort, non DLC o file di backport.",
|
||||
"tips.tip.top.title": "Parti dall'alto",
|
||||
"tips.tip.top.body": "Le prime fonti funzionano meglio.",
|
||||
"tips.tip.retry.title": "Non va? Cambia fonte",
|
||||
"tips.tip.retry.body": "Quasi tutti i giochi ne hanno più di una.",
|
||||
"units.gigabytes": "{n} GB",
|
||||
"units.megabytes": "{n} MB",
|
||||
"units.millions": "{n} Mln",
|
||||
|
||||
@@ -172,6 +172,8 @@
|
||||
"errors.fileChanged": "サーバー上のファイルが変更されました",
|
||||
"errors.interrupted": "アプリの終了時に停止しました",
|
||||
"errors.linkGone": "ダウンロードリンクが無効です",
|
||||
"errors.notGameFile": "ホストがゲームではなくウェブページを送信しました",
|
||||
"errors.pkgFirmware": "PS5の偽PKGはファームウェア11.60までしかインストールできません",
|
||||
"errors.missingParts": "この入手元にはパートが足りません",
|
||||
"errors.needsBrowser": "この入手元にはWebブラウザが必要です",
|
||||
"errors.noConnection": "サーバーに接続できません",
|
||||
@@ -317,6 +319,7 @@
|
||||
"language.name.de": "ドイツ語",
|
||||
"language.name.en": "英語",
|
||||
"language.name.es": "スペイン語",
|
||||
"language.name.fa": "ペルシア語",
|
||||
"language.name.fr": "フランス語",
|
||||
"language.name.hi": "ヒンディー語",
|
||||
"language.name.id": "インドネシア語",
|
||||
@@ -343,6 +346,10 @@
|
||||
"search.resultsFor": "「{query}」の検索結果",
|
||||
"search.tryGenre": "スペルを確認するか、ジャンルから探してみてください:",
|
||||
"settings.controller": "コントローラー",
|
||||
"settings.downloads": "ダウンロード",
|
||||
"settings.speedLimit": "速度制限",
|
||||
"settings.speedLimitDetail": "ほかの機器に帯域を残します。アプリのアップデートは制限されません。",
|
||||
"settings.speedUnlimited": "無制限",
|
||||
"settings.title": "設定",
|
||||
"settings.vibration": "操作時の振動",
|
||||
"settings.vibrationDetail": "移動、選択、戻る操作のときに軽く振動します",
|
||||
@@ -359,6 +366,10 @@
|
||||
"sources.kind.archive": "アーカイブ",
|
||||
"sources.kind.image": "ゲームイメージ",
|
||||
"sources.kind.package": "パッケージ",
|
||||
"sources.kind.patch": "バックポート用ファイルのみ(ゲーム本体ではありません)",
|
||||
"sources.status.dlc": "DLC:本編が必要です",
|
||||
"sources.status.package": "PS5の偽PKGはファームウェア11.60までしかインストールできません",
|
||||
"sources.status.patch": "本編が必要です。単体ではインストールできません",
|
||||
"sources.noDetails": "詳細なし",
|
||||
"sources.parts": "{count, plural, other {#パート}}",
|
||||
"sources.recommended": "おすすめ",
|
||||
@@ -378,6 +389,27 @@
|
||||
"time.seconds": "{n}秒",
|
||||
"toast.openDownloads": "ダウンロードを開く",
|
||||
"toast.viewGame": "ゲームを見る",
|
||||
"tips.eyebrow": "ダウンロード",
|
||||
"tips.title": "ダウンロードの前に",
|
||||
"tips.downloading": "ダウンロードするもの",
|
||||
"tips.about": "このソースについて",
|
||||
"tips.toggle": "ソース選びのヒント",
|
||||
"tips.continue": "続ける",
|
||||
"tips.changeSource": "ソースを変更",
|
||||
"tips.note.dlc": "これはDLCで、ゲーム本体ではありません。先に本編をインストールしてください。",
|
||||
"tips.note.patch": "これはバックポート用ファイルのみで、ゲーム本体ではありません。単体ではインストールできません。",
|
||||
"tips.note.package": "これはPS5の偽PKGです。ファームウェア11.60以前でのみインストールできます。",
|
||||
"tips.note.browserViking": "このソースはPS5のブラウザで開きます。認証を完了して待ってください。Downloadボタンや広告は押さないでください。",
|
||||
"tips.note.browserOther": "このソースはPS5のブラウザで開き、ファイルのリンクを求められます。できればリストの上のソースを選んでください。",
|
||||
"tips.note.firmware": "このバージョンにはファームウェア{need}以降が必要です。設定 > システム > システムソフトウェアで確認してください。",
|
||||
"tips.tip.firmware.title": "ファームウェアを確認",
|
||||
"tips.tip.firmware.body": "Base 8.xxは8.xx以降が必要。BackPortは古い本体向け。",
|
||||
"tips.tip.fullGame.title": "ゲーム本体を選ぶ",
|
||||
"tips.tip.fullGame.body": "BaseかBackPort。DLCやバックポート用ファイルは不可。",
|
||||
"tips.tip.top.title": "上から選ぶ",
|
||||
"tips.tip.top.body": "上のソースほどおすすめです。",
|
||||
"tips.tip.retry.title": "失敗したら別のソース",
|
||||
"tips.tip.retry.body": "多くのゲームに複数あります。",
|
||||
"units.gigabytes": "{n} GB",
|
||||
"units.megabytes": "{n} MB",
|
||||
"units.millions": "{n}M",
|
||||
|
||||
@@ -172,6 +172,8 @@
|
||||
"errors.fileChanged": "O arquivo mudou no servidor",
|
||||
"errors.interrupted": "Interrompido quando o app fechou",
|
||||
"errors.linkGone": "Link de download não funciona mais",
|
||||
"errors.notGameFile": "O servidor enviou uma página da web em vez do jogo",
|
||||
"errors.pkgFirmware": "PKGs falsos de PS5 só instalam até o firmware 11.60",
|
||||
"errors.missingParts": "Faltam partes nesta fonte",
|
||||
"errors.needsBrowser": "Esta fonte precisa de navegador web",
|
||||
"errors.noConnection": "Sem conexão com o servidor",
|
||||
@@ -317,6 +319,7 @@
|
||||
"language.name.de": "Alemão",
|
||||
"language.name.en": "English",
|
||||
"language.name.es": "Español",
|
||||
"language.name.fa": "Persa",
|
||||
"language.name.fr": "Français",
|
||||
"language.name.hi": "Hindi",
|
||||
"language.name.id": "Indonésio",
|
||||
@@ -343,6 +346,10 @@
|
||||
"search.resultsFor": "Resultados para “{query}”",
|
||||
"search.tryGenre": "Verifique a ortografia ou tente um gênero:",
|
||||
"settings.controller": "Controle",
|
||||
"settings.downloads": "Downloads",
|
||||
"settings.speedLimit": "Limite de velocidade",
|
||||
"settings.speedLimitDetail": "Deixa banda para outros dispositivos. Atualizações do app nunca são limitadas.",
|
||||
"settings.speedUnlimited": "Ilimitado",
|
||||
"settings.title": "Configurações",
|
||||
"settings.vibration": "Vibração ao navegar",
|
||||
"settings.vibrationDetail": "Um pulso leve ao mover, selecionar e voltar",
|
||||
@@ -359,6 +366,10 @@
|
||||
"sources.kind.archive": "Arquivo",
|
||||
"sources.kind.image": "Imagem de jogo",
|
||||
"sources.kind.package": "Pacote",
|
||||
"sources.kind.patch": "Só arquivos de backport, não o jogo completo",
|
||||
"sources.status.dlc": "DLC: precisa do jogo base",
|
||||
"sources.status.package": "PKGs falsos de PS5 só instalam até o firmware 11.60",
|
||||
"sources.status.patch": "Precisa do jogo base, não instala sozinho",
|
||||
"sources.noDetails": "Sem detalhes",
|
||||
"sources.parts": "{count, plural, one {# parte} many {# milhões de partes} other {# partes}}",
|
||||
"sources.recommended": "Recomendado",
|
||||
@@ -378,6 +389,27 @@
|
||||
"time.seconds": "{n} s",
|
||||
"toast.openDownloads": "Abrir Downloads",
|
||||
"toast.viewGame": "Ver jogo",
|
||||
"tips.eyebrow": "Download",
|
||||
"tips.title": "Antes de baixar",
|
||||
"tips.downloading": "Você está baixando",
|
||||
"tips.about": "Sobre esta fonte",
|
||||
"tips.toggle": "Dicas para escolher a fonte",
|
||||
"tips.continue": "Continuar",
|
||||
"tips.changeSource": "Trocar fonte",
|
||||
"tips.note.dlc": "Isto é um DLC, não o jogo. Instale o jogo base primeiro.",
|
||||
"tips.note.patch": "São só arquivos de backport, não o jogo completo. Sozinhos não instalam.",
|
||||
"tips.note.package": "Isto é um PKG falso de PS5. Só instala no firmware 11.60 ou anterior.",
|
||||
"tips.note.browserViking": "Esta fonte abre no navegador do PS5. Conclua a verificação e aguarde. Não aperte Download nem anúncios.",
|
||||
"tips.note.browserOther": "Esta fonte abre no navegador do PS5 e depois pede o link do arquivo. Se puder, escolha uma fonte mais acima.",
|
||||
"tips.note.firmware": "Esta versão requer firmware {need} ou mais recente. Confira o seu em Configurações > Sistema > Software do sistema.",
|
||||
"tips.tip.firmware.title": "Veja o firmware",
|
||||
"tips.tip.firmware.body": "Base 8.xx requer 8.xx ou mais. BackPort é para consoles antigos.",
|
||||
"tips.tip.fullGame.title": "Pegue o jogo completo",
|
||||
"tips.tip.fullGame.body": "Base ou BackPort, não DLC nem arquivos de backport.",
|
||||
"tips.tip.top.title": "Comece pelo topo",
|
||||
"tips.tip.top.body": "As primeiras fontes funcionam melhor.",
|
||||
"tips.tip.retry.title": "Falhou? Tente outra",
|
||||
"tips.tip.retry.body": "A maioria dos jogos tem várias.",
|
||||
"units.gigabytes": "{n} GB",
|
||||
"units.megabytes": "{n} MB",
|
||||
"units.millions": "{n}M",
|
||||
@@ -413,7 +445,7 @@
|
||||
"versions.title": "Escolher versão",
|
||||
"capture.title": "Verificação do Vikingfile",
|
||||
"capture.preparing": "Preparando a verificação do arquivo…",
|
||||
"capture.capturing": "Passe a verificação e aperte Download",
|
||||
"capture.capturing": "Conclua a verificação e aguarde",
|
||||
"capture.validating": "Conferindo o arquivo capturado…",
|
||||
"capture.detail": "Volte aqui após apertar Download. O download começa automaticamente quando o arquivo é validado. A captura dura até 3 minutos."
|
||||
"capture.detail": "Não aperte Download nem anúncios: o link é capturado sozinho. Depois volte aqui e o download começa automaticamente. A captura dura até 3 minutos."
|
||||
}
|
||||
@@ -172,6 +172,8 @@
|
||||
"errors.fileChanged": "Файл изменился на сервере",
|
||||
"errors.interrupted": "Остановлено при закрытии приложения",
|
||||
"errors.linkGone": "Ссылка на загрузку больше не работает",
|
||||
"errors.notGameFile": "Хостинг прислал веб-страницу вместо игры",
|
||||
"errors.pkgFirmware": "Фейковые PKG для PS5 ставятся только до прошивки 11.60",
|
||||
"errors.missingParts": "В этом источнике не хватает частей",
|
||||
"errors.needsBrowser": "Этот источник требует веб-браузер",
|
||||
"errors.noConnection": "Нет соединения с сервером",
|
||||
@@ -317,6 +319,7 @@
|
||||
"language.name.de": "Немецкий",
|
||||
"language.name.en": "English",
|
||||
"language.name.es": "Испанский",
|
||||
"language.name.fa": "Персидский",
|
||||
"language.name.fr": "Французский",
|
||||
"language.name.hi": "Хинди",
|
||||
"language.name.id": "Индонезийский",
|
||||
@@ -343,6 +346,10 @@
|
||||
"search.resultsFor": "Результаты для \"{query}\"",
|
||||
"search.tryGenre": "Проверьте орфографию или попробуйте жанр:",
|
||||
"settings.controller": "Геймпад",
|
||||
"settings.downloads": "Загрузки",
|
||||
"settings.speedLimit": "Ограничение скорости",
|
||||
"settings.speedLimitDetail": "Оставляет канал другим устройствам. Обновления приложения не ограничиваются.",
|
||||
"settings.speedUnlimited": "Без лимита",
|
||||
"settings.title": "Настройки",
|
||||
"settings.vibration": "Вибрация при навигации",
|
||||
"settings.vibrationDetail": "Лёгкий отклик при перемещении, выборе и возврате",
|
||||
@@ -359,6 +366,10 @@
|
||||
"sources.kind.archive": "Архив",
|
||||
"sources.kind.image": "Образ игры",
|
||||
"sources.kind.package": "Пакет",
|
||||
"sources.kind.patch": "Только файлы бэкпорта, не полная игра",
|
||||
"sources.status.dlc": "DLC: нужна базовая игра",
|
||||
"sources.status.package": "Фейковые PKG для PS5 ставятся только до прошивки 11.60",
|
||||
"sources.status.patch": "Нужна базовая игра, отдельно не ставится",
|
||||
"sources.noDetails": "Нет деталей",
|
||||
"sources.parts": "{count, plural, one {# часть} few {# части} many {# частей} other {# частей}}",
|
||||
"sources.recommended": "Рекомендуемый",
|
||||
@@ -378,6 +389,27 @@
|
||||
"time.seconds": "{n} с",
|
||||
"toast.openDownloads": "Открыть Загрузки",
|
||||
"toast.viewGame": "Просмотреть игру",
|
||||
"tips.eyebrow": "Загрузка",
|
||||
"tips.title": "Перед загрузкой",
|
||||
"tips.downloading": "Вы скачиваете",
|
||||
"tips.about": "Об этом источнике",
|
||||
"tips.toggle": "Советы по выбору источника",
|
||||
"tips.continue": "Продолжить",
|
||||
"tips.changeSource": "Сменить источник",
|
||||
"tips.note.dlc": "Это DLC, а не игра. Сначала установите основную игру.",
|
||||
"tips.note.patch": "Это только файлы бэкпорта, а не полная игра. Отдельно они не устанавливаются.",
|
||||
"tips.note.package": "Это фейковый PKG для PS5. Он ставится только на прошивке 11.60 и ниже.",
|
||||
"tips.note.browserViking": "Этот источник откроется в браузере PS5. Пройдите проверку и подождите. Не нажимайте Download и рекламу.",
|
||||
"tips.note.browserOther": "Этот источник откроется в браузере PS5 и затем попросит ссылку на файл. Если можете, выберите источник выше в списке.",
|
||||
"tips.note.firmware": "Этой версии нужна прошивка {need} или новее. Свою смотрите в Настройки > Система > Системное ПО.",
|
||||
"tips.tip.firmware.title": "Проверьте прошивку",
|
||||
"tips.tip.firmware.body": "Base 8.xx — нужна 8.xx и новее. BackPort — для старых консолей.",
|
||||
"tips.tip.fullGame.title": "Берите полную игру",
|
||||
"tips.tip.fullGame.body": "Base или BackPort, не DLC и не файлы бэкпорта.",
|
||||
"tips.tip.top.title": "Начните сверху",
|
||||
"tips.tip.top.body": "Первые источники работают лучше.",
|
||||
"tips.tip.retry.title": "Ошибка? Другой источник",
|
||||
"tips.tip.retry.body": "У большинства игр их несколько.",
|
||||
"units.gigabytes": "{n} ГБ",
|
||||
"units.megabytes": "{n} МБ",
|
||||
"units.millions": "{n}M",
|
||||
|
||||
@@ -172,6 +172,8 @@
|
||||
"errors.fileChanged": "فائل سرور پر بدل گئی",
|
||||
"errors.interrupted": "ایپ بند ہونے پر روک دی گئی",
|
||||
"errors.linkGone": "ڈاؤن لوڈ لنک اب کام نہیں کرتی",
|
||||
"errors.notGameFile": "ہوسٹ نے گیم کے بجائے ویب پیج بھیجا",
|
||||
"errors.pkgFirmware": "PS5 کے جعلی PKG صرف فرم ویئر 11.60 تک انسٹال ہوتے ہیں",
|
||||
"errors.missingParts": "اس ذریعے میں حصے کم ہیں",
|
||||
"errors.needsBrowser": "اس ذریعے کو ویب براؤزر کی ضرورت ہے",
|
||||
"errors.noConnection": "سرور سے کوئی رابطہ نہیں",
|
||||
@@ -317,6 +319,7 @@
|
||||
"language.name.de": "جرمن",
|
||||
"language.name.en": "انگریزی",
|
||||
"language.name.es": "ہسپانوی",
|
||||
"language.name.fa": "فارسی",
|
||||
"language.name.fr": "فرانسیسی",
|
||||
"language.name.hi": "ہندی",
|
||||
"language.name.id": "انڈونیشین",
|
||||
@@ -343,6 +346,10 @@
|
||||
"search.resultsFor": "\"{query}\" کے نتائج",
|
||||
"search.tryGenre": "ہجے کی جانچ کریں، یا کوئی صنف کوشش کریں:",
|
||||
"settings.controller": "کنٹرولر",
|
||||
"settings.downloads": "ڈاؤن لوڈز",
|
||||
"settings.speedLimit": "رفتار کی حد",
|
||||
"settings.speedLimitDetail": "دوسرے آلات کے لیے بینڈوڈتھ چھوڑتا ہے۔ ایپ اپڈیٹس کبھی محدود نہیں ہوتیں۔",
|
||||
"settings.speedUnlimited": "لامحدود",
|
||||
"settings.title": "ترتیبات",
|
||||
"settings.vibration": "براؤز کرتے وقت وائبریشن",
|
||||
"settings.vibrationDetail": "حرکت، انتخاب اور واپس جانے پر ہلکی سی وائبریشن",
|
||||
@@ -359,6 +366,10 @@
|
||||
"sources.kind.archive": "آرکائیو",
|
||||
"sources.kind.image": "گیم کی تصویر",
|
||||
"sources.kind.package": "پیکج",
|
||||
"sources.kind.patch": "صرف بیک پورٹ فائلیں، مکمل گیم نہیں",
|
||||
"sources.status.dlc": "DLC: بنیادی گیم درکار ہے",
|
||||
"sources.status.package": "PS5 کے جعلی PKG صرف فرم ویئر 11.60 تک انسٹال ہوتے ہیں",
|
||||
"sources.status.patch": "بنیادی گیم درکار ہے، اکیلے انسٹال نہیں ہوتا",
|
||||
"sources.noDetails": "کوئی تفصیلات نہیں",
|
||||
"sources.parts": "{count, plural, one {# حصہ} other {# حصے}}",
|
||||
"sources.recommended": "سفارش کی جاتی ہے",
|
||||
@@ -378,6 +389,27 @@
|
||||
"time.seconds": "{n} سیکنڈ",
|
||||
"toast.openDownloads": "ڈاؤن لوڈز کھولیں",
|
||||
"toast.viewGame": "گیم دیکھیں",
|
||||
"tips.eyebrow": "ڈاؤن لوڈ",
|
||||
"tips.title": "ڈاؤن لوڈ سے پہلے",
|
||||
"tips.downloading": "آپ ڈاؤن لوڈ کر رہے ہیں",
|
||||
"tips.about": "اس سورس کے بارے میں",
|
||||
"tips.toggle": "سورس چننے کے مشورے",
|
||||
"tips.continue": "جاری رکھیں",
|
||||
"tips.changeSource": "سورس بدلیں",
|
||||
"tips.note.dlc": "یہ DLC ہے، گیم نہیں۔ پہلے بنیادی گیم انسٹال کریں۔",
|
||||
"tips.note.patch": "یہ صرف بیک پورٹ فائلیں ہیں، مکمل گیم نہیں۔ اکیلے انسٹال نہیں ہوتیں۔",
|
||||
"tips.note.package": "یہ PS5 کا جعلی PKG ہے۔ صرف فرم ویئر 11.60 یا پرانے پر انسٹال ہوتا ہے۔",
|
||||
"tips.note.browserViking": "یہ سورس PS5 براؤزر میں کھلتا ہے۔ تصدیق مکمل کریں، پھر انتظار کریں۔ Download یا کسی اشتہار کو نہ دبائیں۔",
|
||||
"tips.note.browserOther": "یہ سورس PS5 براؤزر میں کھلتا ہے اور پھر فائل کا لنک مانگتا ہے۔ ہو سکے تو فہرست میں اوپر والا سورس چنیں۔",
|
||||
"tips.note.firmware": "اس ورژن کو فرم ویئر {need} یا نیا درکار ہے۔ اپنا ترتیبات > سسٹم > سسٹم سافٹ ویئر میں دیکھیں۔",
|
||||
"tips.tip.firmware.title": "فرم ویئر دیکھیں",
|
||||
"tips.tip.firmware.body": "Base 8.xx کو 8.xx یا نیا چاہیے۔ BackPort پرانے کنسولز کے لیے ہے۔",
|
||||
"tips.tip.fullGame.title": "مکمل گیم لیں",
|
||||
"tips.tip.fullGame.body": "Base یا BackPort، DLC یا بیک پورٹ فائلیں نہیں۔",
|
||||
"tips.tip.top.title": "اوپر سے شروع کریں",
|
||||
"tips.tip.top.body": "پہلے سورس سب سے بہتر ہیں۔",
|
||||
"tips.tip.retry.title": "ناکام؟ دوسرا سورس",
|
||||
"tips.tip.retry.body": "زیادہ تر گیمز کے کئی سورس ہیں۔",
|
||||
"units.gigabytes": "{n} GB",
|
||||
"units.megabytes": "{n} MB",
|
||||
"units.millions": "{n}M",
|
||||
|
||||
@@ -172,6 +172,8 @@
|
||||
"errors.fileChanged": "服务器上的文件已更改",
|
||||
"errors.interrupted": "应用关闭时已停止",
|
||||
"errors.linkGone": "下载链接已失效",
|
||||
"errors.notGameFile": "托管网站发送的是网页而不是游戏",
|
||||
"errors.pkgFirmware": "PS5 伪 PKG 仅能在 11.60 及以下固件安装",
|
||||
"errors.missingParts": "此来源缺少部分文件",
|
||||
"errors.needsBrowser": "此来源需要网页浏览器",
|
||||
"errors.noConnection": "无法连接到服务器",
|
||||
@@ -317,6 +319,7 @@
|
||||
"language.name.de": "德语",
|
||||
"language.name.en": "英语",
|
||||
"language.name.es": "西班牙语",
|
||||
"language.name.fa": "波斯语",
|
||||
"language.name.fr": "法语",
|
||||
"language.name.hi": "印地语",
|
||||
"language.name.id": "印度尼西亚语",
|
||||
@@ -343,6 +346,10 @@
|
||||
"search.resultsFor": "“{query}”的搜索结果",
|
||||
"search.tryGenre": "请检查拼写,或试试某个类型:",
|
||||
"settings.controller": "手柄",
|
||||
"settings.downloads": "下载",
|
||||
"settings.speedLimit": "限速",
|
||||
"settings.speedLimitDetail": "为其他设备保留带宽。应用更新始终不限速。",
|
||||
"settings.speedUnlimited": "不限速",
|
||||
"settings.title": "设置",
|
||||
"settings.vibration": "浏览时振动",
|
||||
"settings.vibrationDetail": "移动、选择和返回时轻微振动",
|
||||
@@ -359,6 +366,10 @@
|
||||
"sources.kind.archive": "压缩包",
|
||||
"sources.kind.image": "游戏镜像",
|
||||
"sources.kind.package": "安装包",
|
||||
"sources.kind.patch": "仅含移植补丁文件,不是完整游戏",
|
||||
"sources.status.dlc": "DLC:需要本体游戏",
|
||||
"sources.status.package": "PS5 伪 PKG 仅能在 11.60 及以下固件安装",
|
||||
"sources.status.patch": "需要本体游戏,无法单独安装",
|
||||
"sources.noDetails": "无详情",
|
||||
"sources.parts": "{count, plural, other {# 个部分}}",
|
||||
"sources.recommended": "推荐",
|
||||
@@ -378,6 +389,27 @@
|
||||
"time.seconds": "{n} 秒",
|
||||
"toast.openDownloads": "打开下载",
|
||||
"toast.viewGame": "查看游戏",
|
||||
"tips.eyebrow": "下载",
|
||||
"tips.title": "下载之前",
|
||||
"tips.downloading": "你正在下载",
|
||||
"tips.about": "关于此来源",
|
||||
"tips.toggle": "选择来源的提示",
|
||||
"tips.continue": "继续",
|
||||
"tips.changeSource": "更换来源",
|
||||
"tips.note.dlc": "这是 DLC,不是游戏本体。请先安装本体游戏。",
|
||||
"tips.note.patch": "这只是移植补丁文件,不是完整游戏,无法单独安装。",
|
||||
"tips.note.package": "这是 PS5 伪 PKG,只能在 11.60 或更早的固件上安装。",
|
||||
"tips.note.browserViking": "此来源会在 PS5 浏览器中打开。完成验证后请等待,不要点击 Download 或任何广告。",
|
||||
"tips.note.browserOther": "此来源会在 PS5 浏览器中打开,之后会要求输入文件链接。如可以,请选择列表中更靠前的来源。",
|
||||
"tips.note.firmware": "此版本需要 {need} 或更新的固件。请在 设置 > 系统 > 系统软件 中查看你的版本。",
|
||||
"tips.tip.firmware.title": "检查固件",
|
||||
"tips.tip.firmware.body": "Base 8.xx 需要 8.xx 或更新。BackPort 适用于旧主机。",
|
||||
"tips.tip.fullGame.title": "选择完整游戏",
|
||||
"tips.tip.fullGame.body": "选 Base 或 BackPort,不要 DLC 或移植补丁文件。",
|
||||
"tips.tip.top.title": "从顶部开始",
|
||||
"tips.tip.top.body": "靠前的来源效果最好。",
|
||||
"tips.tip.retry.title": "失败?换个来源",
|
||||
"tips.tip.retry.body": "大多数游戏都有多个来源。",
|
||||
"units.gigabytes": "{n} GB",
|
||||
"units.megabytes": "{n} MB",
|
||||
"units.millions": "{n}M",
|
||||
|
||||
@@ -7,6 +7,7 @@ import { INCOMPLETE_PART, STALE_PARTIAL, cause } from '../downloads/errors.js';
|
||||
import { t } from '../i18n/core.js';
|
||||
import { archivePassword } from './archive-password.js';
|
||||
import { BrowserRequired, archiveRefusal, cancellation, createSourceResolver, sourceKind, webURL } from './download-sources.js';
|
||||
import { PKG_FIRMWARE_ERROR, fakePackagesInstall, isPs5Title } from './firmware.js';
|
||||
import { readJSON } from './read-json.js';
|
||||
import { mountedImages, shadowRequest, within2 } from './shadowmount.js';
|
||||
import { MARGIN2, spaceShortfall, stillNeeded } from './space.js';
|
||||
@@ -220,7 +221,10 @@ import { VIKING_PREFIX, capturedVikingFile, describeVikingCapture, linkPageMetad
|
||||
notifications.show(t("downloads.notice.installed"), job2.title);
|
||||
} catch (error2) {
|
||||
job2.state = "installer-required";
|
||||
job2.error = error2.message;
|
||||
// The install is always tried: the firmware the title is told can be raised by the jailbreak.
|
||||
// Refused where it reads above 11.60 (an archive can turn out to hold a PKG, issue #6), the
|
||||
// likely reason is said instead of the service's code; a full drive still reads as one.
|
||||
job2.error = isPs5Title(job2.gameId) && !fakePackagesInstall() && !/space/i.test(error2.message) ? PKG_FIRMWARE_ERROR : error2.message;
|
||||
job2.message = t("downloads.installFailed");
|
||||
}
|
||||
job2.bytesPerSecond = 0;
|
||||
@@ -373,6 +377,10 @@ import { VIKING_PREFIX, capturedVikingFile, describeVikingCapture, linkPageMetad
|
||||
if (!archives?.inspect) return;
|
||||
const { kind, refusal } = archives.inspect([job2.downloadDestination]);
|
||||
if (refusal) throw new Error(refusal);
|
||||
// A source listed without a format is kept only for what its headers turn out to be: when they
|
||||
// are none Overdrive knows, the host sent something else, usually a web page. Leaving it as a
|
||||
// downloaded ".binary" (issue #8) told the user nothing.
|
||||
if (!kind && job2.release.format === "binary") throw new Error("Not a game file: the host sent something else.");
|
||||
if (!kind) return;
|
||||
const artifact2 = formats.detected(job2.release, job2.root, kind);
|
||||
if (artifact2.nextAction === "extraction-required") {
|
||||
@@ -408,6 +416,25 @@ import { VIKING_PREFIX, capturedVikingFile, describeVikingCapture, linkPageMetad
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// The extractor writes files 0666, without execute: ShadowMount mounts the folder, but its
|
||||
// eboot.bin won't launch (CE-107750-0, issue #11) until everything is opened up, as the updater
|
||||
// does for Overdrive's own folder. A frame every 256 entries keeps a large game from stalling the UI.
|
||||
async function openUp(path) {
|
||||
let count = 0;
|
||||
const visit = async (dir2) => {
|
||||
fs.chmod(dir2, 511);
|
||||
for (const entry2 of fs.readDir(dir2)) {
|
||||
if (++count % 256 === 0) await wait(0);
|
||||
if (entry2.isDirectory) await visit(`${dir2}/${entry2.name}`);
|
||||
else fs.chmod(`${dir2}/${entry2.name}`, 511);
|
||||
}
|
||||
};
|
||||
try {
|
||||
await visit(path);
|
||||
} catch (error2) {
|
||||
console.log("[download-queue] permissions not opened up:", error2.message);
|
||||
}
|
||||
}
|
||||
function extraction(job2, files) {
|
||||
if (!archives) throw new Error("Archive extraction requires the updated PS5 React runtime.");
|
||||
if (releasesOf) {
|
||||
@@ -561,6 +588,7 @@ import { VIKING_PREFIX, capturedVikingFile, describeVikingCapture, linkPageMetad
|
||||
storageIdentity(job2);
|
||||
fs.rename(folder, destination);
|
||||
if (folder !== job2.output) await fs.removeAsync(job2.output);
|
||||
await openUp(destination);
|
||||
job2.output = destination;
|
||||
job2.publishTo = destination;
|
||||
job2.artifact.nextAction = "shadowmount";
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { DeviceInfo } from '@ps5-react/core';
|
||||
|
||||
// Fake PKGs install only up to this firmware; above it the system refuses them, after the whole
|
||||
// download (issues #6 and #17). Version strings read like "13.200.001": major and two minor digits.
|
||||
// What the title is told can be raised by the jailbreak (a console showing 13.20 in System Software
|
||||
// reported 13.59), so the version only warns and explains a refusal; it never hides a source.
|
||||
var LAST_PKG_FIRMWARE = [11, 60];
|
||||
var PKG_FIRMWARE_ERROR = "PS5 fake PKGs install only on firmware 11.60 or older. Choose another source.";
|
||||
var PKG_FORMATS = /* @__PURE__ */ new Set(["pkg", "fpkg"]);
|
||||
function firmwareVersion() {
|
||||
let text;
|
||||
try {
|
||||
text = DeviceInfo.get().firmware;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
const match = /(\d+)\.(\d{2})/.exec(text || "");
|
||||
return match ? [Number(match[1]), Number(match[2])] : null;
|
||||
}
|
||||
// The console's firmware for display ("13.20"), or null where it can't be read.
|
||||
function consoleFirmware() {
|
||||
const version = firmwareVersion();
|
||||
return version ? { major: version[0], text: `${version[0]}.${String(version[1]).padStart(2, "0")}` } : null;
|
||||
}
|
||||
// Unknown firmware (the desktop preview, an old runtime) counts as one that installs them.
|
||||
function fakePackagesInstall() {
|
||||
const version = firmwareVersion();
|
||||
if (!version) return true;
|
||||
const [major, minor] = version, [lastMajor, lastMinor] = LAST_PKG_FIRMWARE;
|
||||
return major < lastMajor || major === lastMajor && minor <= lastMinor;
|
||||
}
|
||||
var isPackage = (release) => PKG_FORMATS.has(release.format);
|
||||
// PS4 fake PKGs install on the PS5's later firmware too; the 11.60 limit is for PS5 ones (Orbit
|
||||
// Store's notes say the same). The title ID tells them apart; the filename starts with it too.
|
||||
var isPs5Title = (titleId) => /^PPSA/i.test(titleId || "");
|
||||
var isPs5Package = (release) => isPackage(release) && isPs5Title(release.titleId || release.filename);
|
||||
|
||||
export { PKG_FIRMWARE_ERROR, consoleFirmware, fakePackagesInstall, isPackage, isPs5Package, isPs5Title };
|
||||
@@ -12,7 +12,10 @@ import { releaseSets } from './release-sets.js';
|
||||
if (!releases) {
|
||||
const parsed = JSON.parse(read(file));
|
||||
if (!Array.isArray(parsed)) throw new Error("Invalid release data; rebuild the catalog.");
|
||||
releases = releaseSets(parsed);
|
||||
// Each source carries its game's title ID (CUSA for PS4, PPSA for PS5): the firmware limit on
|
||||
// fake PKGs is the PS5's alone, and a source never says which console it is for.
|
||||
const titleId = /([a-z0-9]+)\.json$/i.exec(file)[1].toUpperCase();
|
||||
releases = releaseSets(parsed.map((release) => ({ ...release, titleId })));
|
||||
if (games2.size === KEPT2) games2.delete(games2.keys().next().value);
|
||||
}
|
||||
games2.delete(file);
|
||||
|
||||
@@ -12,7 +12,15 @@ import { archivePassword } from './archive-password.js';
|
||||
return `${(first >>> 0).toString(16)}${(second >>> 0).toString(16)}`;
|
||||
}
|
||||
var hostKey = (provider) => provider.toLowerCase().replace(/^www\./, "").replace(/\.[a-z]+$/, "");
|
||||
var VOLUME_NAME = /\bpart(\d+)\.(?:rar|7z|zip)\b/i;
|
||||
// DLPSGAME's "backport files" downloads hold only the eboot and a few files to copy over the base
|
||||
// game (issue #8). The catalog sometimes numbers one as a part of a full backport archive; it is
|
||||
// its own file, so it never joins a set.
|
||||
var PATCH_FILES = /backport[+_ -]*files/i;
|
||||
var isPatchOnly = (release) => {
|
||||
const first = release.parts?.[0]?.sources[0] ?? release;
|
||||
return PATCH_FILES.test(`${first.url || ""} ${first.browserUrl || ""} ${release.label || ""}`);
|
||||
};
|
||||
var VOLUME_NAME =/\bpart(\d+)\.(?:rar|7z|zip)\b/i;
|
||||
function missingVolumes(parts, declaredBytes) {
|
||||
if (parts.some((part, index) => part.number !== index + 1)) return "gap";
|
||||
const volumes = parts.map((part) => VOLUME_NAME.exec(part.name || "")?.[1]);
|
||||
@@ -26,7 +34,7 @@ import { archivePassword } from './archive-password.js';
|
||||
const groups = /* @__PURE__ */ new Map();
|
||||
const output = [];
|
||||
for (const release of releases) {
|
||||
const match = /\bpart\s*(\d+)\b/i.exec(release.label || release.volume || "");
|
||||
const match = !isPatchOnly(release) && /\bpart\s*(\d+)\b/i.exec(release.label || release.volume || "");
|
||||
if (!match) {
|
||||
output.push(release);
|
||||
continue;
|
||||
@@ -81,4 +89,4 @@ import { archivePassword } from './archive-password.js';
|
||||
};
|
||||
var sameRelease = (saved, release) => saved.id === release.id || Boolean(link(saved)) && link(saved) === link(release) && saved.parts?.length === release.parts?.length;
|
||||
|
||||
export { releaseSets, sameRelease };
|
||||
export { isPatchOnly, releaseSets, sameRelease };
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { loadList, saveList } from './saved.js';
|
||||
import { Downloads } from '@ps5-react/core';
|
||||
|
||||
// A cap on what game downloads take from the connection (issue #15), in MB/s as the app shows
|
||||
// speeds (formatSpeed's MB is 1024 ** 2 bytes); 0 is no cap. Kept in settings.json beside the vibration, and
|
||||
// handed to the download engine at launch and on every change, running downloads included.
|
||||
// App updates are enqueued unlimited and never wait on it.
|
||||
var LIMITS = [0, 50, 25, 10, 5, 2, 1];
|
||||
var SETTINGS = "settings.json";
|
||||
var saved = loadList(SETTINGS, "settings").speedLimit;
|
||||
var speedLimit = LIMITS.includes(saved) ? saved : 0;
|
||||
var listeners = /* @__PURE__ */ new Set();
|
||||
function apply() {
|
||||
try {
|
||||
Downloads.setRateLimit(speedLimit * 1024 ** 2);
|
||||
} catch (error) {
|
||||
console.log("[speed-limit] not applied:", error.message);
|
||||
}
|
||||
}
|
||||
function setSpeedLimit(limit) {
|
||||
if (!LIMITS.includes(limit)) return;
|
||||
speedLimit = limit;
|
||||
const { speedLimit: _old, ...settings } = loadList(SETTINGS, "settings");
|
||||
saveList(SETTINGS, "settings", limit ? { ...settings, speedLimit: limit } : settings);
|
||||
apply();
|
||||
for (const listener of listeners) listener(limit);
|
||||
}
|
||||
// The next value one step up (faster, then unlimited) or down (slower), stopping at the ends.
|
||||
function stepSpeedLimit(direction) {
|
||||
const index = LIMITS.indexOf(speedLimit);
|
||||
setSpeedLimit(LIMITS[Math.min(LIMITS.length - 1, Math.max(0, index + direction))]);
|
||||
}
|
||||
function useSpeedLimit() {
|
||||
const [limit, setLimit] = (0, useState)(speedLimit);
|
||||
(0, useEffect)(() => {
|
||||
listeners.add(setLimit);
|
||||
return () => {
|
||||
listeners.delete(setLimit);
|
||||
};
|
||||
}, []);
|
||||
return limit;
|
||||
}
|
||||
apply();
|
||||
|
||||
export { LIMITS, setSpeedLimit, stepSpeedLimit, useSpeedLimit };
|
||||
@@ -155,7 +155,7 @@ import { formatBytes } from './size.js';
|
||||
}
|
||||
async function fetchRelease(update, onProgress) {
|
||||
const zip = `${workDir}/${update.version}.zip`;
|
||||
const download = downloads.enqueue({ url: update.url, destination: zip, sha256: update.sha256, expectedBytes: update.size, rejectHtml: true });
|
||||
const download = downloads.enqueue({ url: update.url, destination: zip, sha256: update.sha256, expectedBytes: update.size, rejectHtml: true, unlimited: true });
|
||||
const off = download.subscribe(({ written }) => onProgress({ state: "downloading", written: written || 0, total: update.size }));
|
||||
try {
|
||||
await download.done;
|
||||
|
||||
+3
-2
@@ -36,7 +36,8 @@ import { Text, motion, useIsFocused } from '@ps5-react/core';
|
||||
label && /* @__PURE__ */ (0, jsx)(Text, { style: [TEXT[variant], TEXT_SIZE[size], focused && variant === "ghost" && { color: "#f2f2f3" }], children: label })
|
||||
] });
|
||||
}
|
||||
function Button2({ variant = "secondary", size = "md", label, icon, iconFill, children, onPress, style, ...rest2 }) {
|
||||
// `halo: false` gives a primary button the plain ring the page's other buttons have.
|
||||
function Button2({ variant = "secondary", size = "md", label, icon, iconFill, children, onPress, style, halo = true, ...rest2 }) {
|
||||
return /* @__PURE__ */ (0, jsxs)(
|
||||
motion.View,
|
||||
{
|
||||
@@ -46,7 +47,7 @@ import { Text, motion, useIsFocused } from '@ps5-react/core';
|
||||
transition: pressMotion,
|
||||
style: ({ focused }) => [BASE, SIZE2[size], BOX[variant], focused && FOCUSED_BOX[variant], style],
|
||||
children: [
|
||||
variant === "primary" ? /* @__PURE__ */ (0, jsx)(FocusRing, { halo: true, radius: 6 }) : /* @__PURE__ */ (0, jsx)(FocusRing, {}),
|
||||
variant === "primary" && halo ? /* @__PURE__ */ (0, jsx)(FocusRing, { halo: true, radius: 6 }) : /* @__PURE__ */ (0, jsx)(FocusRing, {}),
|
||||
children,
|
||||
/* @__PURE__ */ (0, jsx)(Content, { variant, size, label, icon, iconFill })
|
||||
]
|
||||
|
||||
@@ -53,6 +53,10 @@ import { px } from './scale.js';
|
||||
"m7.5 4.27 9 5.15"
|
||||
],
|
||||
disc: [ring(12, 12, 10), ring(12, 12, 2)],
|
||||
// lucide 0.462 cpu, lightbulb and file-minus, for the download tips.
|
||||
cpu: ["M6 4h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z", "M10 9h4a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1z", "M15 2v2", "M15 20v2", "M2 15h2", "M2 9h2", "M20 15h2", "M20 9h2", "M9 2v2", "M9 20v2"],
|
||||
lightbulb: ["M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5", "M9 18h6", "M10 22h4"],
|
||||
fileMinus: ["M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z", "M14 2v4a2 2 0 0 0 2 2h4", "M9 15h6"],
|
||||
archive: ["M3 3h18a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1Z", "M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8", "M10 12h4"],
|
||||
folder: ["M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"],
|
||||
file: ["M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z", "M14 2v4a2 2 0 0 0 2 2h4"],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// ../pub-game-store/native/ui/LanguageSheet.jsx
|
||||
// reconstructed from the esbuild bundle in eboot.bin
|
||||
// MODIFIED: the sheet is the app's settings, with navigation vibration above the languages.
|
||||
// MODIFIED: the sheet is the app's settings: a download speed limit, navigation vibration, then the languages.
|
||||
|
||||
import '../assets/Inter-Regular.ttf';
|
||||
import { Fragment, jsx, jsxs } from 'react/jsx-runtime';
|
||||
@@ -11,6 +11,7 @@ import { Icon } from './Icon.jsx';
|
||||
import { SideSheet } from './SideSheet.jsx';
|
||||
import { colors } from './colors.js';
|
||||
import { setNavigationHaptics, useNavigationHaptics } from './haptics.js';
|
||||
import { LIMITS, setSpeedLimit, useSpeedLimit } from '../state/speed-limit.js';
|
||||
import { Text, View, useIsFocused } from '@ps5-react/core';
|
||||
|
||||
var nameOf = (code) => LOCALES.find((locale2) => locale2.code === code).name;
|
||||
@@ -28,6 +29,29 @@ import { Text, View, useIsFocused } from '@ps5-react/core';
|
||||
function Row4({ code, name, detail: detail2, checked, onPress }) {
|
||||
return /* @__PURE__ */ (0, jsx)(View, { focusKey: `language:${code}`, onPress, style: (_state) => [{ position: "relative", flexDirection: "row", alignItems: "center", gap: 24, borderRadius: 12, paddingHorizontal: 18, paddingVertical: 18 }, _state.focused ? { backgroundColor: "#212125" } : null], children: /* @__PURE__ */ (0, jsx)(LanguageFace, { name, detail: detail2, checked }) });
|
||||
}
|
||||
// One segment of the speed limit (issue #15): every value in view, the chosen one filled. The D-pad
|
||||
// moves along the row and Cross picks, so no value has to be typed.
|
||||
function SpeedSegment({ limit, chosen }) {
|
||||
const t2 = useT();
|
||||
return /* @__PURE__ */ (0, jsxs)(View, { focusKey: `settings:speed:${limit}`, autoFocus: chosen, onPress: () => setSpeedLimit(limit), style: { position: "relative", flex: limit ? 1 : 2, height: 60, alignItems: "center", justifyContent: "center", borderRadius: 12, backgroundColor: chosen ? colors.fg : colors.surface2 }, children: [
|
||||
/* @__PURE__ */ (0, jsx)(FocusRing, {}),
|
||||
/* @__PURE__ */ (0, jsx)(Text, { style: { fontFamily: "Inter-Regular", fontSize: 21, fontWeight: 600, color: chosen ? colors.accentFg : colors.muted, numberOfLines: 1 }, children: limit ? String(limit) : t2("settings.speedUnlimited") })
|
||||
] });
|
||||
}
|
||||
function SpeedLimit() {
|
||||
const t2 = useT();
|
||||
const limit = useSpeedLimit();
|
||||
return /* @__PURE__ */ (0, jsxs)(View, { style: { gap: 18, paddingHorizontal: 18, paddingTop: 6, paddingBottom: 18 }, children: [
|
||||
/* @__PURE__ */ (0, jsxs)(View, { style: { gap: 3 }, children: [
|
||||
/* @__PURE__ */ (0, jsxs)(View, { style: { flexDirection: "row", justifyContent: "space-between", alignItems: "center" }, children: [
|
||||
/* @__PURE__ */ (0, jsx)(Text, { style: { fontFamily: "Inter-Regular", fontSize: 23, fontWeight: 600, color: colors.fg }, children: t2("settings.speedLimit") }),
|
||||
/* @__PURE__ */ (0, jsx)(Text, { style: { fontFamily: "Inter-Regular", fontSize: 21, color: colors.fg }, children: limit ? t2("units.speed", { n: String(limit) }) : t2("settings.speedUnlimited") })
|
||||
] }),
|
||||
/* @__PURE__ */ (0, jsx)(Text, { style: { fontFamily: "Inter-Regular", fontSize: 21, color: colors.muted, lineHeight: 30 }, children: t2("settings.speedLimitDetail") })
|
||||
] }),
|
||||
/* @__PURE__ */ (0, jsx)(View, { style: { flexDirection: "row", gap: 9 }, children: LIMITS.map((value) => /* @__PURE__ */ (0, jsx)(SpeedSegment, { limit: value, chosen: value === limit }, value)) })
|
||||
] });
|
||||
}
|
||||
function SectionLabel({ icon, title, detail: detail2 }) {
|
||||
const text = /* @__PURE__ */ (0, jsxs)(View, { style: { flex: 1, gap: 3 }, children: [
|
||||
/* @__PURE__ */ (0, jsx)(Text, { style: { fontFamily: "Inter-Regular", fontSize: 19, fontWeight: 600, color: colors.muted, letterSpacing: 1.5 }, children: title }),
|
||||
@@ -46,11 +70,13 @@ import { Text, View, useIsFocused } from '@ps5-react/core';
|
||||
chooseLanguage(code);
|
||||
onClose();
|
||||
};
|
||||
// The vibration row stays open on press, so the player feels the change at once; a language
|
||||
// choice still closes the sheet as before. Focus opens on the top row, where both are in view.
|
||||
// The speed limit and vibration stay open on press, so the change shows (or is felt) at once; a
|
||||
// language choice still closes the sheet as before. Focus opens on the chosen speed, at the top.
|
||||
return /* @__PURE__ */ (0, jsx)(SideSheet, { title: t2("settings.title"), onClose, children: /* @__PURE__ */ (0, jsxs)(View, { style: { gap: 6, padding: 9, paddingBottom: 48 }, children: [
|
||||
/* @__PURE__ */ (0, jsx)(SectionLabel, { icon: "download", title: t2("settings.downloads") }),
|
||||
/* @__PURE__ */ (0, jsx)(SpeedLimit, {}),
|
||||
/* @__PURE__ */ (0, jsx)(SectionLabel, { title: t2("settings.controller") }),
|
||||
/* @__PURE__ */ (0, jsx)(View, { focusKey: "settings:vibration", autoFocus: true, onPress: () => setNavigationHaptics(!vibration), style: (_state) => [{ position: "relative", flexDirection: "row", alignItems: "center", gap: 24, borderRadius: 12, paddingHorizontal: 18, paddingVertical: 18 }, _state.focused ? { backgroundColor: "#212125" } : null], children: /* @__PURE__ */ (0, jsx)(LanguageFace, { name: t2("settings.vibration"), detail: t2("settings.vibrationDetail"), checked: vibration }) }),
|
||||
/* @__PURE__ */ (0, jsx)(View, { focusKey: "settings:vibration", onPress: () => setNavigationHaptics(!vibration), style: (_state) => [{ position: "relative", flexDirection: "row", alignItems: "center", gap: 24, borderRadius: 12, paddingHorizontal: 18, paddingVertical: 18 }, _state.focused ? { backgroundColor: "#212125" } : null], children: /* @__PURE__ */ (0, jsx)(LanguageFace, { name: t2("settings.vibration"), detail: t2("settings.vibrationDetail"), checked: vibration }) }),
|
||||
/* @__PURE__ */ (0, jsx)(SectionLabel, { icon: "languages", title: t2("language.title"), detail: t2("language.subtitle") }),
|
||||
/* @__PURE__ */ (0, jsx)(Row4, { code: "system", name: t2("language.system"), detail: nameOf(systemLocale()), checked: choice2 === null, onPress: () => pick2(null) }),
|
||||
LOCALES.map(
|
||||
|
||||
+5
-1
@@ -11,7 +11,11 @@
|
||||
fg: "#f2f2f3",
|
||||
muted: "#a3a3ab",
|
||||
subtle: "#8a8a94",
|
||||
accentFg: "#0d0d0f"
|
||||
accentFg: "#0d0d0f",
|
||||
// Soft status tones beside the CDN pastels: a source that suits the console, or one to look at first.
|
||||
ok: "#a7f3d0",
|
||||
warn: "#fde68a",
|
||||
info: "#a5f3fc"
|
||||
};
|
||||
|
||||
export { colors };
|
||||
+1
-1
@@ -2,6 +2,6 @@
|
||||
// reconstructed from the esbuild bundle in eboot.bin
|
||||
|
||||
|
||||
var VERSION = "1.0.3";
|
||||
var VERSION = "1.0.4";
|
||||
|
||||
export { VERSION };
|
||||
@@ -1,5 +1,19 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased — Vikingfile capture without the Download button
|
||||
|
||||
- Browser capture finds the verified Vikingfile link as the page holds it once the verification
|
||||
passes, a JSON string with escaped slashes, so the user no longer selects Download and the
|
||||
advertisements around it. The scanner reads at about 8 MiB/s with a 1 GiB session budget, so it
|
||||
keeps looking through a slow verification; both changes follow Orbit Store 1.3.5.
|
||||
|
||||
## Unreleased — download speed limit
|
||||
|
||||
- Add `Downloads.setRateLimit(bytesPerSecond)` (native ABI v17, `network.setRateLimit`): every file
|
||||
download shares the cap, and a new value applies at once, to running downloads too. A capped
|
||||
download opens about one connection per MB/s and resumes its paused connections in turn, so none
|
||||
sits long enough to hit the 30-second low-speed cutoff. `unlimited: true` exempts a download.
|
||||
|
||||
## Unreleased — comparing upscalers
|
||||
|
||||
- `<Video compare={{upscale, split}}>` (native ABI v15, `video.setCompare`): right of `split` (0 to 1
|
||||
|
||||
@@ -155,6 +155,10 @@ include opt-in `rejectHtml`, exact `expectedBytes`, `storageRoot` identity check
|
||||
for receipt-backed SHA-256 verification of published files, and normalized
|
||||
`pieces: [{url, offset, size, sha1}]`. These options share ABI v2 on both hosts;
|
||||
there are no additional global native functions.
|
||||
`Downloads.setRateLimit(bytesPerSecond)` (ABI v17) caps what all file downloads
|
||||
receive together, 0 for no limit. It applies at once, running downloads included;
|
||||
a capped download opens about one connection per MB/s. A download enqueued with
|
||||
`unlimited: true`, such as an app's own update, is exempt.
|
||||
PS5 builds require `networking: true` and `filesystemAccess: "console"`; desktop
|
||||
networking is available by default. See [NETWORKING.md](NETWORKING.md) for
|
||||
options, resource limits, errors, lifecycle and the hardware-validation gap.
|
||||
@@ -623,14 +627,15 @@ controls, slashes, backslashes or `..`. The deadline is an integer from 1 to
|
||||
completion. `cancel()` shuts down the connection, stops the payload's next
|
||||
read, and rejects `done` with `AbortError`. Host exit stops and joins the worker.
|
||||
|
||||
The user must complete the provider's verification and select Download in the
|
||||
system browser. This API does not bypass verification or open the page; the app
|
||||
The user must complete the provider's verification in the system browser; the
|
||||
verified link is captured as Vikingfile's page holds it (a JSON string, slashes
|
||||
escaped), so the user need not select Download or its advertisements. This API does not bypass verification or open the page; the app
|
||||
opens it with `Linking.openURL` after submitting capture. Only a uniquely
|
||||
identified browser network process is read, with identity checked before every
|
||||
read. Scanning is limited to anonymous readable/writable nonexecutable user
|
||||
pages, 64 KiB every 32 ms, 128 MiB per session. No dumps, cookies, arbitrary
|
||||
pages, 64 KiB every 8 ms, 1 GiB per session. No dumps, cookies, arbitrary
|
||||
process selection or writes. Only complete ASCII/UTF-16 matching routes are
|
||||
returned. Provenance: `native/ps5/payloads/browser/README.md` (Orbit Store 1.0.1).
|
||||
returned. Provenance: `native/ps5/payloads/browser/README.md` (Orbit Store 1.0.1 and 1.3.5).
|
||||
|
||||
A captured URL may already be stale; the app must validate host, decoded
|
||||
filename, HTTP status, content type and length before starting immediately.
|
||||
|
||||
@@ -274,9 +274,19 @@ void device_info(DeviceInfo& info) {
|
||||
}
|
||||
SystemSwVersion version{};
|
||||
version.size = sizeof version;
|
||||
if (sceKernelGetSystemSwVersion(&version) == 0)
|
||||
const int version_result = sceKernelGetSystemSwVersion(&version);
|
||||
if (version_result == 0)
|
||||
std::snprintf(info.firmware, sizeof info.firmware, "%.*s",
|
||||
static_cast<int>(sizeof version.version_string), version.version_string);
|
||||
// Once per launch: the raw answer, since what the title is told can differ from the version
|
||||
// System Software shows (a console showing 13.20 reported 13.59).
|
||||
static bool version_logged = false;
|
||||
if (!version_logged) {
|
||||
version_logged = true;
|
||||
async_log::write("[PS5-REACT] system software version=0x%08x string=\"%.*s\" result=0x%08x",
|
||||
version.version, static_cast<int>(sizeof version.version_string), version.version_string,
|
||||
static_cast<unsigned>(version_result));
|
||||
}
|
||||
int temperature = 0;
|
||||
if (sceKernelGetCpuTemperature(&temperature) == 0) info.cpu_temperature = temperature;
|
||||
if (sceKernelGetSocSensorTemperature(0, &temperature) == 0) info.soc_temperature = temperature;
|
||||
|
||||
@@ -7,8 +7,15 @@ The bundle contains `orbit-store-1.0.1-source.tar.gz`, backend/browser_*.
|
||||
`browser_url.c` adapts the filename-bound route validation in backend/providers.c.
|
||||
The release/catalog dependency was replaced by an explicit selected filename.
|
||||
|
||||
From Orbit Store 1.3.5 (same licence), `browser_match.c` decodes JSON's escaped
|
||||
slash once, so the verified Vikingfile link is found before the page's Download
|
||||
button is pressed, and the scanner's overlap, read pace and session budget follow
|
||||
its `browser_scan.h`. Source bundle:
|
||||
https://github.com/saawant12/orbit-store-ps5/releases/download/v1.3.5/orbit-store-1.3.5-source-bundle.zip
|
||||
(`orbit-store-1.3.5-source.tar.gz`, backend/browser_match.c and backend/browser_scan.*).
|
||||
|
||||
Only the uniquely identified SceNKNetworkProcess/SceNKNetworkProc is read.
|
||||
Identity is rechecked before reads; no executable/file-backed pages, writes,
|
||||
cookies, dumps or arbitrary process selectors. Each session has a 128 MiB total
|
||||
read budget, 64 KiB reads separated by 32 ms, and a maximum 180-second deadline.
|
||||
cookies, dumps or arbitrary process selectors. Each session has a 1 GiB total
|
||||
read budget, 64 KiB reads separated by 8 ms, and a maximum 180-second deadline.
|
||||
Only a complete matching Vikingfile URL is returned. Buffers are wiped.
|
||||
@@ -1,4 +1,4 @@
|
||||
// Adapted from Orbit Store 1.0.1 by saawant12 (https://github.com/saawant12/orbit-store-ps5).
|
||||
// Adapted from Orbit Store 1.0.1 and 1.3.5 by saawant12 (https://github.com/saawant12/orbit-store-ps5).
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Read-only browser capture; original corresponding source is recorded in README.md.
|
||||
#include "browser_match.h"
|
||||
@@ -9,6 +9,26 @@ static bool boundary(unsigned char c) {
|
||||
return c == 0 || c == ' ' || c == '\t' || c == '\r' || c == '\n' ||
|
||||
c == '"' || c == '\'' || c == '<' || c == '>';
|
||||
}
|
||||
/* One URL character at *position, in ASCII (step 1) or UTF-16LE (step 2). Vikingfile's verified
|
||||
* link is already in the network process once the verification passes, but as JSON, before a
|
||||
* click on the page's Download button (and its advertisements) makes a navigation URL of it
|
||||
* (Orbit Store 1.3.5). JSON's optional slash escape is decoded, once; no other escape is a
|
||||
* download address. */
|
||||
static bool next_url_char(const unsigned char *bytes, size_t length, size_t step,
|
||||
size_t *position, unsigned char *out) {
|
||||
if (*position > length || length - *position < step) return false;
|
||||
unsigned char c = bytes[*position];
|
||||
if (step == 2 && bytes[*position + 1]) return false;
|
||||
*position += step;
|
||||
if (c == '\\') {
|
||||
if (length - *position < step || bytes[*position] != '/' ||
|
||||
(step == 2 && bytes[*position + 1])) return false;
|
||||
*position += step;
|
||||
c = '/';
|
||||
}
|
||||
*out = c;
|
||||
return true;
|
||||
}
|
||||
bool browser_match(void *context, const unsigned char *bytes, size_t length) {
|
||||
BrowserMatch *match = context;
|
||||
for (size_t which = 0; which < 2; which++) {
|
||||
@@ -17,17 +37,19 @@ bool browser_match(void *context, const unsigned char *bytes, size_t length) {
|
||||
for (size_t step = 1; step <= 2; step++) {
|
||||
for (size_t i = 0; (plen + 1) * step <= length && i <= length - (plen + 1) * step; i++) {
|
||||
if (bytes[i] != 'h' || (i >= step && !boundary(bytes[i - step]))) continue;
|
||||
size_t position = i;
|
||||
bool valid = true;
|
||||
for (size_t j = 0; j < plen; j++) {
|
||||
if (bytes[i + j * step] != (unsigned char)prefix[j] ||
|
||||
(step == 2 && bytes[i + j * step + 1])) { valid = false; break; }
|
||||
unsigned char c;
|
||||
if (!next_url_char(bytes, length, step, &position, &c) ||
|
||||
c != (unsigned char)prefix[j]) { valid = false; break; }
|
||||
}
|
||||
if (!valid) continue;
|
||||
char candidate[sizeof match->url];
|
||||
memcpy(candidate, prefix, plen);
|
||||
for (size_t j = plen; j < sizeof candidate && (j + 1) * step <= length - i; j++) {
|
||||
unsigned char c = bytes[i + j * step];
|
||||
if (step == 2 && bytes[i + j * step + 1]) break;
|
||||
for (size_t j = plen; j < sizeof candidate; j++) {
|
||||
unsigned char c;
|
||||
if (!next_url_char(bytes, length, step, &position, &c)) break;
|
||||
if (boundary(c)) {
|
||||
candidate[j] = 0;
|
||||
if (browser_capture_url(match->filename, candidate)) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Adapted from Orbit Store 1.0.1 by saawant12 (https://github.com/saawant12/orbit-store-ps5).
|
||||
// Adapted from Orbit Store 1.0.1 and 1.3.5 by saawant12 (https://github.com/saawant12/orbit-store-ps5).
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Read-only browser capture; original corresponding source is recorded in README.md.
|
||||
#include "browser_platform.h"
|
||||
@@ -86,8 +86,8 @@ bool probe_target_active(void *context) {
|
||||
|
||||
int probe_target_read(void *context, uint64_t address, void *bytes, size_t length) {
|
||||
ProbeTarget *target = context;
|
||||
/* Rate limit: at most 64 KiB every 32 ms, roughly 2 MiB/s. */
|
||||
struct timespec delay = {.tv_nsec = 32000000};
|
||||
/* Rate limit: at most 64 KiB every 8 ms, roughly 8 MiB/s. */
|
||||
struct timespec delay = {.tv_nsec = PROBE_READ_DELAY_NS};
|
||||
nanosleep(&delay, NULL);
|
||||
if (!probe_target_active(target)) return -1;
|
||||
return kernel_proc_copyout(target->pid, (intptr_t)address, bytes, length);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Adapted from Orbit Store 1.0.1 by saawant12 (https://github.com/saawant12/orbit-store-ps5).
|
||||
// Adapted from Orbit Store 1.0.1 and 1.3.5 by saawant12 (https://github.com/saawant12/orbit-store-ps5).
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Read-only browser capture; original corresponding source is recorded in README.md.
|
||||
#include "browser_scan.h"
|
||||
@@ -21,7 +21,8 @@ ProbeResult probe_scan_region(ProbeReader *reader, uint64_t start, uint64_t end,
|
||||
if (!length || length == PROBE_MARKER_MAX) return PROBE_INVALID;
|
||||
unsigned char wide[PROBE_MARKER_MAX * 2] = {0};
|
||||
for (size_t i = 0; i < length; i++) wide[i * 2] = (unsigned char)marker[i];
|
||||
const size_t overlap = reader->match ? PROBE_MATCH_MAX * 2 - 1 : length * 2 - 1;
|
||||
/* A bounded URL may use two-byte JSON slash escapes in UTF-16. */
|
||||
const size_t overlap = reader->match ? PROBE_MATCH_MAX * 4 - 1 : length * 2 - 1;
|
||||
unsigned char *buffer = malloc(PROBE_CHUNK + overlap);
|
||||
if (!buffer) return PROBE_INVALID;
|
||||
size_t carried = 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Adapted from Orbit Store 1.0.1 by saawant12 (https://github.com/saawant12/orbit-store-ps5).
|
||||
// Adapted from Orbit Store 1.0.1 and 1.3.5 by saawant12 (https://github.com/saawant12/orbit-store-ps5).
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Read-only browser capture; original corresponding source is recorded in README.md.
|
||||
#ifndef ORBIT_BROWSER_SCAN_H
|
||||
@@ -11,6 +11,10 @@
|
||||
#define PROBE_CHUNK 65536U
|
||||
#define PROBE_MARKER_MAX 256U
|
||||
#define PROBE_MATCH_MAX 544U /* Percent-encoded filename and route, including NUL. */
|
||||
/* Cumulative reads, not resident memory: enough to keep scanning through the whole verification
|
||||
* window at about 8 MiB/s, a late human verification included (Orbit Store 1.3.5). */
|
||||
#define PROBE_READ_DELAY_NS 8000000L
|
||||
#define PROBE_SESSION_BUDGET (1024ULL * 1024ULL * 1024ULL)
|
||||
typedef struct {
|
||||
void *context;
|
||||
int (*read)(void *, uint64_t, void *, size_t);
|
||||
|
||||
@@ -70,7 +70,7 @@ int main(void) {
|
||||
if (found != 1) return fail("The browser could not be identified safely. Reopen the page and retry.");
|
||||
BrowserMatch match = {.filename = filename};
|
||||
ProbeReader reader = {.context = &target, .read = probe_target_read, .active = active,
|
||||
.match = browser_match, .match_context = &match, .remaining = 128U * 1024U * 1024U};
|
||||
.match = browser_match, .match_context = &match, .remaining = PROBE_SESSION_BUDGET};
|
||||
ProbeResult result = PROBE_ABSENT;
|
||||
while (active(&target) && reader.remaining) {
|
||||
result = probe_target_scan(&target, &reader, prefix);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (C) 2026 half144 and PS5 React contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution term: see LICENSE-ATTRIBUTION.
|
||||
// Native API contract (ABI v16) between the hosts and `@ps5-react/core`. The input actions hosts
|
||||
// Native API contract (ABI v17) between the hosts and `@ps5-react/core`. The input actions hosts
|
||||
// pass to `__ps5ReactDispatch` are a separate contract with its own version (runtime/js/input.js).
|
||||
//
|
||||
// host_api.cpp (shared) installs `globalThis.__ps5ReactNative` through
|
||||
@@ -54,12 +54,15 @@
|
||||
// switches after this frame (PS5: restarts EGL, the TV resyncs)
|
||||
// network.download(url, path, options) task ID; queues native binary file I/O
|
||||
// options: expectedBytes, storageRoot, pieces[{url, offset, size, sha1}],
|
||||
// sha256, connections, adaptive, rangeBytes, resume, recoverCompleted, headers, rejectHtml
|
||||
// sha256, connections, adaptive, rangeBytes, resume, recoverCompleted, headers, rejectHtml,
|
||||
// unlimited (ABI v17; exempt from setRateLimit)
|
||||
// network.request(url, options) task ID; queues bounded HTTP text I/O
|
||||
// options: method, body, maxBytes, headers, followRedirects (default true)
|
||||
// terminal snapshots: status, body, effective url, allowlisted lowercase headers
|
||||
// network.cancel(id) requests cancellation
|
||||
// network.poll() progress/results; consumes terminal snapshots
|
||||
// network.setRateLimit(bytes) ABI v17; bytes per second all file downloads share, 0 for no limit;
|
||||
// applies at once, running downloads included, except unlimited ones
|
||||
// archives.extract(sources, destination, maxBytes) task ID; bounded native archive worker
|
||||
// archives.inspect(sources) {kind, refusal}; file type from leading bytes and, for an archive,
|
||||
// a decoder-memory refusal its headers so far justify; partial files ok
|
||||
|
||||
@@ -125,6 +125,8 @@ struct Transfer {
|
||||
bool length_known = false;
|
||||
std::uint64_t range_begin = 0, range_end = 0, range_total = 0;
|
||||
bool content_range = false, probe = false, paused = false, active = false, draining = false, redirected = false, cut = false;
|
||||
// Paused by the rate limit until its shared budget refills (paused waits for a free disk block).
|
||||
bool rate_paused = false;
|
||||
// A 200 or 206 that is not the range asked for but carries the file's validators: a proxy or a busy
|
||||
// node answering oddly, retried a few times rather than taken for a changed file.
|
||||
bool glitch = false;
|
||||
@@ -636,6 +638,7 @@ public:
|
||||
void cancel(std::uint32_t id);
|
||||
std::vector<Snapshot> poll();
|
||||
bool running() const { return running_; }
|
||||
void set_rate_limit(std::uint64_t bytes_per_second) { rate_limit_ = bytes_per_second; }
|
||||
private:
|
||||
std::mutex mutex_, disk_mutex_;
|
||||
std::condition_variable wake_, disk_wake_, sync_wake_;
|
||||
@@ -648,6 +651,14 @@ private:
|
||||
std::atomic<bool> stopping_{false}, writer_stopping_{false};
|
||||
std::atomic<unsigned> write_max_us_{0}, sync_max_us_{0};
|
||||
std::atomic<std::uint64_t> write_calls_{0}, write_bytes_{0};
|
||||
// The rate limit: a budget of bytes refilled each pass of the transfer loop, which callbacks spend.
|
||||
// Only the network thread touches the budget; the limit itself is set from JavaScript's thread.
|
||||
std::atomic<std::uint64_t> rate_limit_{0};
|
||||
double rate_budget_ = 0;
|
||||
Clock::time_point rate_refilled_{};
|
||||
std::size_t rate_turn_ = 0;
|
||||
void refill_rate(Clock::time_point now);
|
||||
void resume_rate_paused(Job& job);
|
||||
std::uint64_t perform_us_ = 0;
|
||||
pthread_t network_thread_{}, sync_thread_{};
|
||||
std::array<pthread_t, writer_count> writer_threads_{};
|
||||
@@ -887,6 +898,29 @@ std::size_t Service::header(char* data, std::size_t size, std::size_t count, voi
|
||||
return length;
|
||||
}
|
||||
|
||||
void Service::refill_rate(Clock::time_point now) {
|
||||
const auto limit = static_cast<double>(rate_limit_.load(std::memory_order_relaxed));
|
||||
const double seconds = std::min(1.0, std::chrono::duration<double>(now-rate_refilled_).count());
|
||||
rate_refilled_ = now;
|
||||
if (!limit) { rate_budget_ = 0; return; }
|
||||
// At most a quarter second's worth (256 KiB at least, above any one callback) banks up.
|
||||
rate_budget_ = std::min(rate_budget_+limit*seconds, std::max(limit/4, 256.0*1024));
|
||||
}
|
||||
|
||||
// Resumes transfers the limit paused, starting one further along each pass so none waits long.
|
||||
void Service::resume_rate_paused(Job& job) {
|
||||
const bool capped = rate_limit_.load(std::memory_order_relaxed) != 0;
|
||||
for (std::size_t step = 0; step < transfers_.size(); ++step) {
|
||||
Transfer& t = transfers_[(rate_turn_+step) % transfers_.size()];
|
||||
if (!t.active || !t.rate_paused) continue;
|
||||
if (capped && rate_budget_ < 64.0*1024) { rate_turn_ = (rate_turn_+step) % transfers_.size(); return; }
|
||||
t.rate_paused = false;
|
||||
const auto result = curl_easy_pause(t.curl, CURLPAUSE_CONT);
|
||||
if (result != CURLE_OK) { job.fail(curl_easy_strerror(result)); return; }
|
||||
}
|
||||
rate_turn_ = (rate_turn_+1) % transfers_.size();
|
||||
}
|
||||
|
||||
std::size_t Service::body(char* data, std::size_t size, std::size_t count, void* opaque) {
|
||||
auto& t = *static_cast<Transfer*>(opaque);
|
||||
if (size && count > block_size/size) { t.error = "unexpected callback block size"; return 0; }
|
||||
@@ -919,6 +953,11 @@ std::size_t Service::body(char* data, std::size_t size, std::size_t count, void*
|
||||
const std::size_t keep = t.cut ? static_cast<std::size_t>(std::min<std::uint64_t>(length, limit-t.accepted)) : length;
|
||||
if (!t.cut && (t.accepted > limit || length > limit-t.accepted)) { t.error = "response exceeds expected size"; return 0; }
|
||||
if (!keep) return length;
|
||||
if (!job.request.unlimited && t.owner->rate_limit_.load(std::memory_order_relaxed)) {
|
||||
// Over the budget: curl hands these bytes back once the transfer loop resumes the transfer.
|
||||
if (t.owner->rate_budget_ < static_cast<double>(keep)) { t.rate_paused = true; return CURL_WRITEFUNC_PAUSE; }
|
||||
t.owner->rate_budget_ -= static_cast<double>(keep);
|
||||
}
|
||||
if (t.block && block_size-t.fill < keep) t.owner->flush(t);
|
||||
if (!t.block) {
|
||||
t.block = t.owner->take_block();
|
||||
@@ -941,7 +980,7 @@ bool Service::configure(Transfer& t) {
|
||||
t.headers = nullptr; t.status = 0; t.header_bytes = 0;
|
||||
t.etag.clear(); t.last_modified.clear(); t.encoding.clear(); t.error.clear(); t.content_type.clear();
|
||||
t.response_headers.clear(); t.html_prefix.clear(); t.sample.clear();
|
||||
t.content_range = false; t.length_known = false; t.paused = false; t.retry_after = 0; t.glitch = false;
|
||||
t.content_range = false; t.length_known = false; t.paused = false; t.rate_paused = false; t.retry_after = 0; t.glitch = false;
|
||||
t.requested = t.length; t.cut = false;
|
||||
Job& job = *t.job;
|
||||
bool ok = true;
|
||||
@@ -1391,7 +1430,11 @@ void Service::run_job(const std::shared_ptr<Job>& job) {
|
||||
for (Transfer& t : transfers_) t.probe = false;
|
||||
if (prepared && !job->cancelled && !job->recovering) {
|
||||
{ std::lock_guard lock(job->mutex); job->snapshot.state = "downloading"; }
|
||||
const unsigned limit = job->ranged ? job->request.connections : 1;
|
||||
// A capped download needs few connections: about one per MB/s, so each gets bytes often enough to
|
||||
// stay over curl's low-speed cutoff (1 byte/s for 30 s) between its turns.
|
||||
const std::uint64_t capped = job->request.unlimited ? 0 : rate_limit_.load();
|
||||
const unsigned limit = !job->ranged ? 1 : !capped ? job->request.connections :
|
||||
std::min(std::max(static_cast<unsigned>(capped/(1024*1024)), 2u), job->request.connections);
|
||||
const unsigned initial = job->request.adaptive ? std::min(8u, limit) : limit;
|
||||
std::vector<Window> windows;
|
||||
const auto window_for = [&](const std::string& url) {
|
||||
@@ -1439,6 +1482,8 @@ void Service::run_job(const std::shared_ptr<Job>& job) {
|
||||
platform_log("download: resumed after a suspension; ranges it cut retry without counting");
|
||||
}
|
||||
last_pass = now; last_wall = wall;
|
||||
refill_rate(now);
|
||||
resume_rate_paused(*job);
|
||||
unsigned in_flight = 0;
|
||||
for (Transfer& t : transfers_) if (t.active || t.draining) ++in_flight;
|
||||
if (job->request.adaptive) for (Window& w : windows) w.grow(now, limit);
|
||||
@@ -1643,6 +1688,7 @@ void stop() { service.stop(); }
|
||||
std::uint32_t enqueue(Request request, std::string& error) { return service.enqueue(std::move(request), error); }
|
||||
void cancel(std::uint32_t id) { service.cancel(id); }
|
||||
std::vector<Snapshot> poll() { return service.poll(); }
|
||||
void set_rate_limit(std::uint64_t bytes_per_second) { service.set_rate_limit(bytes_per_second); }
|
||||
bool downloading() { return downloading_file; }
|
||||
bool running() { return service.running(); }
|
||||
const char* version() { return curl_version(); }
|
||||
@@ -1656,6 +1702,7 @@ std::uint32_t enqueue(Request, std::string& error) {
|
||||
}
|
||||
void cancel(std::uint32_t) {}
|
||||
std::vector<Snapshot> poll() { return {}; }
|
||||
void set_rate_limit(std::uint64_t) {}
|
||||
bool downloading() { return false; }
|
||||
bool running() { return false; }
|
||||
const char* version() { return "disabled"; }
|
||||
|
||||
@@ -23,6 +23,8 @@ struct Request {
|
||||
unsigned connections = 8;
|
||||
bool resume = true, adaptive = true, recover_completed = false;
|
||||
bool follow_redirects = true, reject_html = false;
|
||||
// Exempt from set_rate_limit (an app's own update).
|
||||
bool unlimited = false;
|
||||
};
|
||||
struct Snapshot {
|
||||
std::uint32_t id = 0;
|
||||
@@ -41,6 +43,9 @@ void stop();
|
||||
std::uint32_t enqueue(Request request, std::string& error);
|
||||
void cancel(std::uint32_t id);
|
||||
std::vector<Snapshot> poll();
|
||||
// Caps what file downloads receive together, in bytes per second; 0 lifts the cap. It applies at once,
|
||||
// to downloads already running too, except those marked unlimited.
|
||||
void set_rate_limit(std::uint64_t bytes_per_second);
|
||||
// True while a download to a file runs; image loading yields connections and memory to it.
|
||||
bool downloading();
|
||||
// True once start() succeeded: the platform's network stack (PS5: libSceNet) is up.
|
||||
|
||||
@@ -156,6 +156,7 @@ JSValue begin(JSContext* ctx, JSValueConst, int argc, JSValueConst* argv, bool d
|
||||
!boolean_option(ctx, options, "resume", request.resume) || !boolean_option(ctx, options, "adaptive", request.adaptive) ||
|
||||
!boolean_option(ctx, options, "recoverCompleted", request.recover_completed) ||
|
||||
!boolean_option(ctx, options, "rejectHtml", request.reject_html) ||
|
||||
!boolean_option(ctx, options, "unlimited", request.unlimited) ||
|
||||
!string_option(ctx, options, "sha256", request.sha256, 64) || !digest(request.sha256, 64) ||
|
||||
!number_option(ctx, options, "expectedBytes", request.expected_bytes, 1, 9007199254740991ULL) ||
|
||||
!pieces(ctx, options, request) || !mirrors(ctx, options, request))
|
||||
@@ -209,11 +210,18 @@ JSValue poll(JSContext* ctx, JSValueConst, int, JSValueConst*) {
|
||||
return out;
|
||||
}
|
||||
JSValue info(JSContext* ctx, JSValueConst, int, JSValueConst*) { return JS_NewString(ctx, network::version()); }
|
||||
// Bytes per second every file download shares; 0 lifts the cap.
|
||||
JSValue rate_limit(JSContext* ctx, JSValueConst, int argc, JSValueConst* argv) {
|
||||
double bytes = 0;
|
||||
if (!argc || JS_ToFloat64(ctx, &bytes, argv[0]) || !(bytes >= 0) || bytes > 9007199254740991.0)
|
||||
return JS_ThrowTypeError(ctx, "network.setRateLimit: expected bytes per second, 0 for no limit");
|
||||
network::set_rate_limit(static_cast<std::uint64_t>(bytes)); return JS_UNDEFINED;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
JSValue ps5_react_network_api(JSContext* ctx) {
|
||||
JSValue out = JS_NewObject(ctx);
|
||||
for (const auto& item : {std::pair<const char*, JSCFunction*>{"download", &download}, {"request", &request}, {"cancel", &cancel}, {"poll", &poll}, {"version", &info}})
|
||||
for (const auto& item : {std::pair<const char*, JSCFunction*>{"download", &download}, {"request", &request}, {"cancel", &cancel}, {"poll", &poll}, {"version", &info}, {"setRateLimit", &rate_limit}})
|
||||
JS_SetPropertyStr(ctx, out, item.first, JS_NewCFunction(ctx, item.second, item.first, 0));
|
||||
return out;
|
||||
}
|
||||
@@ -485,6 +485,13 @@ export const Downloads = {
|
||||
enqueueManifest({manifest, destination, ...options}) {
|
||||
return Downloads.enqueue({...options, ...normalizeDownloadManifest(manifest), destination});
|
||||
},
|
||||
/** Bytes per second every file download shares, 0 for no limit (ABI v17). It applies at once,
|
||||
* to running downloads too; one enqueued with `unlimited: true` (an app's own update) is exempt. */
|
||||
setRateLimit(bytesPerSecond) {
|
||||
if (!Number.isFinite(bytesPerSecond) || bytesPerSecond < 0)
|
||||
throw new TypeError('Downloads.setRateLimit: expected bytes per second, 0 for no limit.');
|
||||
host().network.setRateLimit(Math.floor(bytesPerSecond));
|
||||
},
|
||||
get transportVersion() { return host().network.version(); },
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user