mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-14 19:27:09 -04:00
A Stores section (rail, under Games; Setup › Stores gate) with each store's sign-in, library, storefront shelves and a download queue. Each store has two tabs, Store and Library, and Library filters to All or Installed; libraries open from a saved copy and refresh in the background, with install sizes on every card. Games install to internal storage, or a card chosen at Install time, and are registered in the Steam client as non-Steam games, so they launch through Steam and droiddeck-proton from either the front end or the client. - Downloads: GOG, Epic and Amazon engines in Rust (app/src/main/rust/stores, built by cargo-ndk, nothing compiled in the tree). One adaptive fetch core with a latency brake, stalled-host handoff and every CDN a store offers. Per-stage progress, pause/resume, two-step cancel that deletes, failed downloads kept for retry, a foreground service with Wi-Fi and wake locks and notification controls. - Games tab: store source chips (Steam, GOG, Epic, Amazon, Custom) and store art. - Epic: per-launch exchange code from the app and from Steam's Play button, "Epic sign-in (EOS)", "Launch offline", and a game's web links opened in Android's browser for EOS consent. The EOS overlay crashed games and is left out. - Cloud saves (GOG, Epic): pulled before launch, pushed after the game or the session ends, no upload before a first download, conflicts never overwritten, backups both ways. - Logs: every store line redacted; the engine log goes to daily files.
75 lines
3.1 KiB
Python
75 lines
3.1 KiB
Python
"""The shortcuts writer and a store install: the app registers a game GOG, Epic or Amazon
|
|
installed through the same listing as an added folder, pointing the shortcut at the game's
|
|
launcher .bat when it has one. These pin the parts of the writer that contract relies on."""
|
|
import importlib.machinery
|
|
import importlib.util
|
|
import json
|
|
from pathlib import Path
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
import zlib
|
|
|
|
BIN = Path(__file__).resolve().parents[1] / 'linuxfs/overlay/usr/local/bin'
|
|
|
|
|
|
def load(name):
|
|
loader = importlib.machinery.SourceFileLoader(name, str(BIN / name))
|
|
spec = importlib.util.spec_from_loader(name, loader)
|
|
module = importlib.util.module_from_spec(spec)
|
|
loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
shortcuts = load('droiddeck-steam-shortcuts')
|
|
|
|
FOLDER = '/root/Games/Stores/Epic/Celeste'
|
|
LAUNCHER = FOLDER + '/.droiddeck-launch.bat'
|
|
|
|
|
|
def app_id(exe, name):
|
|
"""The appid the app computes for a shortcut (AddedGames.scanGame): CRC32 of the quoted exe
|
|
and the name, high bit set - what Steam itself derives for a shortcut it is given."""
|
|
return (zlib.crc32(('"%s"' % exe + name).encode()) | 0x80000000) & 0xFFFFFFFF
|
|
|
|
|
|
class StoreShortcutsTest(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temp = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.temp.cleanup)
|
|
self.root = Path(self.temp.name)
|
|
self.steam = self.root / 'Steam'
|
|
self.config = self.steam / 'userdata/123/config'
|
|
self.config.mkdir(parents=True)
|
|
self.listing = self.root / 'added-games.json'
|
|
|
|
def write(self, *games):
|
|
self.listing.write_text(json.dumps(list(games)))
|
|
subprocess.run(['python3', str(BIN / 'droiddeck-steam-shortcuts'), str(self.steam), str(self.listing)],
|
|
check=True, capture_output=True)
|
|
return shortcuts.parse((self.config / 'shortcuts.vdf').read_bytes())['shortcuts']
|
|
|
|
def test_a_launcher_bat_is_the_shortcuts_exe_and_the_folder_its_start_dir(self):
|
|
game = dict(name='Celeste', exe=LAUNCHER, folder=FOLDER, dir=FOLDER, appid=app_id(LAUNCHER, 'Celeste'), seen=0)
|
|
entries = self.write(game)
|
|
entry = next(iter(entries.values()))
|
|
self.assertEqual('"%s"' % LAUNCHER, entry['Exe'])
|
|
self.assertEqual('"%s"' % FOLDER, entry['StartDir'])
|
|
self.assertEqual('Celeste', entry['AppName'])
|
|
self.assertIn('droiddeck-app', entry['tags'].values())
|
|
|
|
def test_the_appid_the_app_computes_is_the_one_written(self):
|
|
appid = app_id(LAUNCHER, 'Celeste')
|
|
entries = self.write(dict(name='Celeste', exe=LAUNCHER, folder=FOLDER, dir=FOLDER, appid=appid, seen=0))
|
|
entry = next(iter(entries.values()))
|
|
self.assertEqual(appid, entry['appid'] & 0xFFFFFFFF)
|
|
|
|
def test_an_uninstalled_game_leaves_the_listing_and_the_shortcut_goes_with_it(self):
|
|
game = dict(name='Celeste', exe=LAUNCHER, folder=FOLDER, dir=FOLDER, appid=app_id(LAUNCHER, 'Celeste'), seen=0)
|
|
self.assertEqual(1, len(self.write(game)))
|
|
self.assertEqual(0, len(self.write()))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|