1
0
Fork 0
mirror of synced 2024-05-14 09:32:51 +12:00

Fixed some Bugs

This commit is contained in:
Dummerle 2021-03-18 12:45:59 +01:00
parent 68ef350777
commit dcd6a84279
46 changed files with 1537 additions and 839 deletions

View file

@ -5,8 +5,8 @@ name: New Release
on:
release:
types: [created]
types: [ created ]
workflow_dispatch:
jobs:
@ -15,40 +15,40 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.8'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twine
- name: Build and publish
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
run: |
python setup.py sdist bdist_wheel
twine upload dist/*
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.8'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twine
- name: Build and publish
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
run: |
python setup.py sdist bdist_wheel
twine upload dist/*
aur-publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Generate PKGBUILD
run:
./.github/GenPKG.sh
- name: Upload
uses: KSXGitHub/github-actions-deploy-aur@v2.2.3
with:
pkgname: rare
pkgbuild: ./.github/rare/PKGBUILD
commit_username: Dummerle
commit_email: ${{ secrets.MAIL }}
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
commit_message: Update AUR package
- uses: actions/checkout@v2
- name: Generate PKGBUILD
run:
./.github/GenPKG.sh
- name: Upload
uses: KSXGitHub/github-actions-deploy-aur@v2.2.3
with:
pkgname: rare
pkgbuild: ./.github/rare/PKGBUILD
commit_username: Dummerle
commit_email: ${{ secrets.MAIL }}
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
commit_message: Update AUR package

View file

@ -2,8 +2,9 @@
## A frontend for legendary, the open source Epic Games Launcher alternative
Rare is currently considered beta software and in no way feature-complete. You will probably run into issues, so it is recommend to make a backup. If you have features you want to have in this app, create an issue on github, contact me on Discord (Dummerle#7419) or build it yourself. Please report bugs so I can fix them.
Rare is currently considered beta software and in no way feature-complete. You will probably run into issues, so it is
recommend to make a backup. If you have features you want to have in this app, create an issue on github, contact me on
Discord (Dummerle#7419) or build it yourself. Please report bugs so I can fix them.
### Requirements
@ -19,16 +20,18 @@ Rare is currently considered beta software and in no way feature-complete. You w
Execute *pip install Rare* for all users Or *pip install Rare --user* for only one user
**Note**: On Linux must be /home/user/.local/bin in PATH
### Windows Simple
Download Rare.exe and place it somewhere in PATH
**Note**
Using the exe-file could cause an error with the stylesheets
Using the exe file could cause errors
## Linux
- For Arch Linux is an AUR package available: [rare-git](https://aur.archlinux.org/packages/rare-git)
- For Arch Linux is an AUR package available: [rare-git](https://aur.archlinux.org/packages/rare-git) or [rare](https://aur.archlinux.org/packages/rare)
- Other distributions have to install it with pip or clone the repo and install it manually: *python3 setup.py install*
## Implemented
@ -37,12 +40,13 @@ Using the exe-file could cause an error with the stylesheets
- Authentication(Import from existing installation and via Browser)
- Download progress bar
- Settings (Legendary and games)
- Sync Cloud Saves
- Translations (English and German)
## Planned
- Sync Cloud Saves
- Offline mode
- More Translations
- More Translations (Need help)
## Images

View file

@ -53,7 +53,8 @@ class InstallInfoDialog(QDialog):
super(InstallInfoDialog, self).__init__()
self.layout = QVBoxLayout()
self.infos = QLabel(self.tr(
"Download size: {}GB\nInstall size: {}GB").format(round(dl_size / 1024 ** 3, 2), round(install_size / 1024 ** 3, 2)))
"Download size: {}GB\nInstall size: {}GB").format(round(dl_size / 1024 ** 3, 2),
round(install_size / 1024 ** 3, 2)))
self.layout.addWidget(self.infos)
self.btn_layout = QHBoxLayout()

View file

@ -3,6 +3,7 @@ from logging import getLogger
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel, QLineEdit
from custom_legendary.core import LegendaryCore
logger = getLogger("BrowserLogin")
@ -21,7 +22,8 @@ class BrowserLogin(QWidget):
self.layout.addWidget(self.back)
self.info_text = QLabel(self.tr(
"Opens a browser. You login and copy the json code in the field below. Click <a href='{}'>here</a> to open Browser").format(self.url))
"Opens a browser. You login and copy the json code in the field below. Click <a href='{}'>here</a> to open Browser").format(
self.url))
self.info_text.setWordWrap(True)
self.info_text.setOpenExternalLinks(True)
self.layout.addWidget(self.info_text)

View file

@ -4,6 +4,7 @@ from logging import getLogger
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel, QButtonGroup, QRadioButton
from custom_legendary.core import LegendaryCore
logger = getLogger("Import")

View file

@ -1,9 +1,9 @@
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QLabel, QStackedLayout, QWidget, QPushButton
from custom_legendary.core import LegendaryCore
# Login Opportunities: Browser, Import
from Rare.Components.Dialogs.Login.BrowserLogin import BrowserLogin
from Rare.Components.Dialogs.Login.ImportWidget import ImportWidget
from custom_legendary.core import LegendaryCore
class LoginDialog(QDialog):

View file

@ -41,4 +41,4 @@ class PathInputDialog(QDialog):
def ok(self):
self.path = self.input.text()
self.close()
self.close()

View file

@ -2,10 +2,10 @@ from logging import getLogger
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import QDialog, QLabel, QProgressBar, QVBoxLayout
from custom_legendary.core import LegendaryCore
from Rare.Components.Dialogs.Login.LoginDialog import LoginDialog
from Rare.utils.utils import download_images
from custom_legendary.core import LegendaryCore
logger = getLogger("Login")
@ -73,7 +73,7 @@ class LaunchDialog(QDialog):
exit(0)
def launch(self):
#self.core = core
# self.core = core
self.info_pb.setMaximum(len(self.core.get_game_list()))
self.info_text.setText(self.tr("Downloading Images"))
self.thread = LaunchThread(self.core, self)

View file

@ -1,16 +1,16 @@
from PyQt5.QtCore import QSize
from PyQt5.QtGui import QFont
from PyQt5.QtWidgets import QTabWidget, QTabBar, QWidget, QToolButton, QWidgetAction, QMenu
from Rare.Components.Tabs.CloudSaves.CloudSaves import SyncSaves
from custom_legendary.core import LegendaryCore
from qtawesome import icon
# from Rare.Components.Tabs.Account.AccountWidget import MiniWidget
from Rare.Components.Tabs.Account.AccountWidget import MiniWidget
from Rare.Components.Tabs.CloudSaves.CloudSaves import SyncSaves
from Rare.Components.Tabs.Downloads.DownloadTab import DownloadTab
from Rare.Components.Tabs.Games.GamesTab import GameTab
from Rare.Components.Tabs.Settings.SettingsTab import SettingsTab
from Rare.utils.Models import InstallOptions
from custom_legendary.core import LegendaryCore
class TabWidget(QTabWidget):

View file

@ -2,6 +2,7 @@ import webbrowser
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QMessageBox, QLabel, QPushButton
from custom_legendary.core import LegendaryCore

View file

@ -39,6 +39,7 @@ class _DownloadThread(QThread):
class SyncWidget(QWidget):
reload = pyqtSignal()
def __init__(self, igame: InstalledGame, save, core: LegendaryCore):
super(SyncWidget, self).__init__()
self.layout = QVBoxLayout()
@ -152,6 +153,7 @@ class SyncWidget(QWidget):
self.core.lgd.set_installed_game(self.igame.app_name, self.igame)
self.save_path_text.setText(self.igame.save_path)
self.reload.emit()
def upload(self):
self.logger.info("Uploading Saves for game " + self.igame.title)
self.info_text.setText(self.tr("Uploading..."))
@ -182,4 +184,4 @@ class SyncWidget(QWidget):
self.upload_button.setDisabled(True)
self.download_button.setDisabled(True)
self.download_button.setStyleSheet("QPushButton{background-color: black}")
self.reload.emit()
self.reload.emit()

View file

@ -9,17 +9,14 @@ from PyQt5.QtCore import QThread, pyqtSignal, Qt
from PyQt5.QtWidgets import QWidget, QMessageBox, QVBoxLayout, QLabel, QGridLayout, QProgressBar, QPushButton, QDialog, \
QListWidget, QHBoxLayout
from Rare.Components.Dialogs.InstallDialog import InstallInfoDialog
from Rare.utils.Models import InstallOptions, KillDownloadException
from custom_legendary.core import LegendaryCore
from custom_legendary.downloader.manager import DLManager
from custom_legendary.models.downloading import UIUpdate
from custom_legendary.models.game import Game
from custom_legendary.utils.selective_dl import games
from Rare.Components.Dialogs.InstallDialog import InstallInfoDialog
from Rare.utils.Models import InstallOptions, KillDownloadException
logger = getLogger("Download")
@ -46,7 +43,7 @@ class DownloadThread(QThread):
time.sleep(1)
while self.dlm.is_alive():
if self.kill:
#raise KillDownloadException()
# raise KillDownloadException()
# TODO kill download queue, workers
pass
try:

View file

@ -4,8 +4,6 @@ from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtGui import QPixmap, QKeyEvent
from PyQt5.QtWidgets import QWidget, QPushButton, QVBoxLayout, QLabel, QHBoxLayout, QTabWidget, QMessageBox, \
QProgressBar, QStackedWidget
from custom_legendary.core import LegendaryCore
from custom_legendary.models.game import InstalledGame, Game
from qtawesome import icon
from Rare.Components.Tabs.Games.GameInfo.GameSettings import GameSettings
@ -13,6 +11,8 @@ from Rare.utils import LegendaryApi
from Rare.utils.LegendaryApi import VerifyThread
from Rare.utils.QtExtensions import SideTabBar
from Rare.utils.utils import IMAGE_DIR
from custom_legendary.core import LegendaryCore
from custom_legendary.models.game import InstalledGame, Game
class InfoTabs(QTabWidget):

View file

@ -1,12 +1,12 @@
import os
from PyQt5.QtWidgets import QWidget, QLabel, QVBoxLayout, QComboBox, QFileDialog, QPushButton, QMessageBox
from custom_legendary.core import LegendaryCore
from custom_legendary.models.game import InstalledGame, Game
from Rare.Components.Tabs.Settings.Linux import LinuxSettings
from Rare.Components.Tabs.Settings.SettingsWidget import SettingsWidget
from Rare.utils.QtExtensions import PathEdit
from custom_legendary.core import LegendaryCore
from custom_legendary.models.game import InstalledGame, Game
class GameSettings(QWidget):
@ -44,7 +44,7 @@ class GameSettings(QWidget):
for i in os.listdir(os.path.expanduser("~/.steam/steam/steamapps/common")):
if i.startswith("Proton"):
wrapper = '"' + os.path.join(os.path.expanduser("~/.steam/steam/steamapps/common"), i,
"proton") + '" run'
"proton") + '" run'
self.possible_proton_wrappers.append(wrapper)
except FileNotFoundError as e:
print("Unable to find Proton:", e)

View file

@ -1,6 +1,5 @@
from PyQt5.QtCore import Qt, pyqtSignal, QSettings
from PyQt5.QtWidgets import *
from custom_legendary.core import LegendaryCore
from Rare.Components.Tabs.Games.GameWidgetInstalled import GameWidgetInstalled
from Rare.Components.Tabs.Games.GameWidgetListUninstalled import UninstalledGameWidget
@ -8,6 +7,7 @@ from Rare.Components.Tabs.Games.GameWidgetUninstalled import GameWidgetUninstall
from Rare.Components.Tabs.Games.InstalledListWidget import GameWidget
from Rare.utils.Models import InstallOptions
from Rare.utils.QtExtensions import FlowLayout
from custom_legendary.core import LegendaryCore
class GameList(QScrollArea):
@ -89,4 +89,4 @@ class GameList(QScrollArea):
self.update()
def import_game(self):
pass
pass

View file

@ -4,12 +4,12 @@ from logging import getLogger
from PyQt5.QtCore import QEvent, pyqtSignal, QSettings, QSize, Qt
from PyQt5.QtGui import QPixmap, QMouseEvent
from PyQt5.QtWidgets import *
from custom_legendary.core import LegendaryCore
from custom_legendary.models.game import InstalledGame
from qtawesome import icon
from Rare.utils import LegendaryApi
from Rare.utils.QtExtensions import ClickableLabel
from custom_legendary.core import LegendaryCore
from custom_legendary.models.game import InstalledGame
logger = getLogger("GameWidgetInstalled")
@ -87,9 +87,9 @@ class GameWidgetInstalled(QWidget):
self.info_label.setObjectName("info_label")
self.layout.addWidget(self.info_label)
#p = self.palette()
#p.setColor(self.backgroundRole(), Qt.red)
#self.setPalette(p)
# p = self.palette()
# p.setColor(self.backgroundRole(), Qt.red)
# self.setPalette(p)
self.setLayout(self.layout)
self.setFixedWidth(self.sizeHint().width())
@ -108,7 +108,7 @@ class GameWidgetInstalled(QWidget):
else:
self.info_label.setText(self.info_text)
def mousePressEvent(self, e:QMouseEvent):
def mousePressEvent(self, e: QMouseEvent):
# left button
if e.button() == 1:
if self.update_available:
@ -119,7 +119,6 @@ class GameWidgetInstalled(QWidget):
elif e.button() == 2:
pass
def launch(self, offline=False, skip_version_check=False):
if not self.running:
logger.info("Launching " + self.game.title)

View file

@ -4,13 +4,13 @@ from logging import getLogger
from PyQt5.QtCore import pyqtSignal, QSettings
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel
from custom_legendary.core import LegendaryCore
from custom_legendary.models.game import Game
from Rare.Components.Dialogs.InstallDialog import InstallDialog
from Rare.utils.Models import InstallOptions
from Rare.utils.QtExtensions import ClickableLabel
from Rare.utils.utils import download_image
from custom_legendary.core import LegendaryCore
from custom_legendary.models.game import Game
logger = getLogger("Uninstalled")

View file

@ -4,10 +4,10 @@ from logging import getLogger
from PyQt5.QtCore import QProcess, pyqtSignal, QSettings
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QPushButton, QStyle, QVBoxLayout
from custom_legendary.core import LegendaryCore
from custom_legendary.models.game import InstalledGame
from Rare.utils import LegendaryApi
from custom_legendary.core import LegendaryCore
from custom_legendary.models.game import InstalledGame
logger = getLogger("GameWidget")

View file

@ -9,12 +9,12 @@ class About(QWidget):
self.title = QLabel("<h2>About</h2>")
self.layout.addWidget(self.title)
self.dev = QLabel(self.tr("Developer:")+"<a href='https://github.com/Dummerle'>Dummerle</a>")
self.dev = QLabel(self.tr("Developer:") + "<a href='https://github.com/Dummerle'>Dummerle</a>")
self.dev.setToolTip("Github")
self.dev.setOpenExternalLinks(True)
self.dev.setWordWrap(True)
self.layout.addWidget(self.dev)
self.lgd_dev = QLabel(self.tr("Legendary developer:")+"<a href='https://github.com/derrod/'>derrod</a>")
self.lgd_dev = QLabel(self.tr("Legendary developer:") + "<a href='https://github.com/derrod/'>derrod</a>")
self.lgd_dev.setOpenExternalLinks(True)
self.lgd_dev.setToolTip("Github")
self.layout.addWidget(self.lgd_dev)

View file

@ -2,6 +2,7 @@ from logging import getLogger
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QWidget, QCheckBox, QVBoxLayout, QWidgetAction, QMenu, QToolButton, QHBoxLayout, QLabel
from custom_legendary.core import LegendaryCore
logger = getLogger("DXVK Settings")

View file

@ -2,10 +2,10 @@ from logging import getLogger
from PyQt5.QtGui import QIntValidator
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QFileDialog, QPushButton, QLineEdit
from custom_legendary.core import LegendaryCore
from Rare.Components.Tabs.Settings.SettingsWidget import SettingsWidget
from Rare.utils.QtExtensions import PathEdit
from custom_legendary.core import LegendaryCore
logger = getLogger("LegendarySettings")
@ -15,7 +15,7 @@ class LegendarySettings(QWidget):
super(LegendarySettings, self).__init__()
self.layout = QVBoxLayout()
self.core = core
self.title = QLabel("<h2>"+self.tr("Legendary settings")+"</h2>")
self.title = QLabel("<h2>" + self.tr("Legendary settings") + "</h2>")
self.layout.addWidget(self.title)
# Default installation directory

View file

@ -1,11 +1,11 @@
from logging import getLogger
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton, QFileDialog, QLineEdit
from custom_legendary.core import LegendaryCore
from Rare.Components.Tabs.Settings.DXVK.Dxvk import DxvkWidget
from Rare.Components.Tabs.Settings.SettingsWidget import SettingsWidget
from Rare.utils.QtExtensions import PathEdit
from custom_legendary.core import LegendaryCore
logger = getLogger("LinuxSettings")

View file

@ -16,7 +16,7 @@ class RareSettings(QWidget):
def __init__(self):
super(RareSettings, self).__init__()
self.layout = QVBoxLayout()
self.title = QLabel("<h2>"+self.tr("Rare settings")+"</h2>")
self.title = QLabel("<h2>" + self.tr("Rare settings") + "</h2>")
self.layout.addWidget(self.title)
settings = QSettings()
img_dir = settings.value("img_dir", type=str)

View file

@ -1,19 +1,16 @@
import logging
import os
from PyQt5.QtCore import QSettings, QTranslator
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication
from Rare.Components.MainWindow import MainWindow
from Rare.Components.Launch.LaunchDialog import LaunchDialog
from Rare import lang_path, style_path
from Rare.Components.Launch.LaunchDialog import LaunchDialog
from Rare.Components.MainWindow import MainWindow
from Rare.utils.utils import get_lang
from custom_legendary.core import LegendaryCore
import os
logging.basicConfig(
format='[%(name)s] %(levelname)s: %(message)s',
level=logging.INFO
@ -45,4 +42,4 @@ def main():
launch_dialog.exec_()
mainwindow = MainWindow(core)
app.exec_()
app.exec_()

View file

@ -7,11 +7,12 @@ QLabel {
background-color: transparent;
}
QTabBar#main_tab_bar{
QTabBar#main_tab_bar {
border-bottom: none;
background-color: #2b2b2c;
}
QTabBar::tab#main_tab_bar{
QTabBar::tab#main_tab_bar {
border-bottom: noneq;
}
@ -19,7 +20,8 @@ QTabBar::tab#main_tab_bar {
border-bottom: none;
padding: 5px
}
QTabBar::tab:selected#main_tab_bar{
QTabBar::tab:selected#main_tab_bar {
background-color: gray;
}
@ -45,7 +47,8 @@ QPushButton {
background-color: #3c3f41;
padding: 3px;
}
QPushButton:hover{
QPushButton:hover {
background-color: #223;
}
@ -62,11 +65,12 @@ QPushButton#menu {
border-style: none;
}
QPushButton#menu_button{
QPushButton#menu_button {
background-color: transparent;
border: none;
}
QPushButton:hover#menu_button{
QPushButton:hover#menu_button {
background-color: #334;
}
@ -74,12 +78,11 @@ QLineEdit {
border: 1px solid white;
}
QCheckBox{
QCheckBox {
color: #F0F0F0;
}
#list_widget {
border-top: 2px solid white;
}
@ -100,7 +103,7 @@ QTabBar::tab::selected#settings_bar {
background-color: darkslategrey;
}
#search_bar{
#search_bar {
padding: 3px;
border-radius: 5px;
background-color: #334;

View file

@ -2,4 +2,4 @@ import os
__version__ = "0.9.4"
style_path = os.path.join(os.path.dirname(__file__), "Styles/")
lang_path = os.path.join(os.path.dirname(__file__), "languages/")
lang_path = os.path.join(os.path.dirname(__file__), "languages/")

View file

@ -8,7 +8,6 @@ if __name__ == '__main__':
exit(0)
from Rare.Main import main
main()
"""

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,7 @@ from logging import getLogger
from sys import stdout
from PyQt5.QtCore import QProcess, QProcessEnvironment, QThread, pyqtSignal
from PyQt5.QtWidgets import QMessageBox, QWidget
from custom_legendary.core import LegendaryCore
from custom_legendary.models.game import VerifyResult
from custom_legendary.utils.lfs import validate_files
@ -125,7 +125,7 @@ class VerifyThread(QThread):
if not missing and not failed:
logger.info('Verification finished successfully.')
self.summary.emit((0,0))
self.summary.emit((0, 0))
else:
logger.error(f'Verification failed, {len(failed)} file(s) corrupted, {len(missing)} file(s) are missing.')

View file

@ -6,14 +6,15 @@ from logging import getLogger
import requests
from PIL import Image
from PyQt5.QtCore import pyqtSignal, QLocale, QSettings
from custom_legendary.core import LegendaryCore
from Rare import lang_path
from custom_legendary.core import LegendaryCore
logger = getLogger("Utils")
s = QSettings("Rare","Rare")
s = QSettings("Rare", "Rare")
IMAGE_DIR = s.value("img_dir", os.path.expanduser("~/.cache/rare"), type=str)
logger.info("IMAGE DIRECTORY: "+IMAGE_DIR)
logger.info("IMAGE DIRECTORY: " + IMAGE_DIR)
def download_images(signal: pyqtSignal, core: LegendaryCore):
if not os.path.isdir(IMAGE_DIR):

View file

@ -1,9 +1,9 @@
# !/usr/bin/env python
# coding: utf-8
import requests
import logging
import requests
from requests.auth import HTTPBasicAuth
from custom_legendary.models.exceptions import InvalidCredentialsError
@ -157,7 +157,7 @@ class EPCAPI:
f'{user_id}/{app_name}')
r.raise_for_status()
return r.json()
def create_game_cloud_saves(self, app_name, filenames):
return self.get_user_cloud_saves(app_name, filenames=filenames)

View file

@ -12,7 +12,6 @@ import shlex
import subprocess
import time
import webbrowser
from distutils.util import strtobool
from getpass import getuser
from logging.handlers import QueueListener
@ -22,7 +21,7 @@ from sys import exit, stdout
from custom_legendary import __version__, __codename__
from custom_legendary.core import LegendaryCore
from custom_legendary.models.exceptions import InvalidCredentialsError
from custom_legendary.models.game import SaveGameStatus, VerifyResult
from custom_legendary.models.game import SaveGameStatus
from custom_legendary.utils.cli import get_boolean_choice, sdl_prompt
from custom_legendary.utils.custom_parser import AliasedSubParsersAction
@ -220,7 +219,7 @@ class LegendaryCLI:
self.core.install_game(game)
print(f' * {game.title} (App name: {game.app_name} | Version: {game.version} | '
f'{game.install_size / (1024*1024*1024):.02f} GiB)')
f'{game.install_size / (1024 * 1024 * 1024):.02f} GiB)')
if args.include_dir:
print(f' + Location: {game.install_path}')
if not os.path.exists(game.install_path):
@ -570,7 +569,7 @@ class LegendaryCLI:
exit(0)
logger.info('Downloads are resumable, you can interrupt the download with '
'CTRL-C and resume it using the same command later on.')
'CTRL-C and resume it using the same command later on.')
start_t = time.time()
@ -584,7 +583,8 @@ class LegendaryCLI:
while dlm.is_alive():
try:
status = status_queue.get(timeout=0.1)
logger.info(f'= Progress: {status.progress:.02f}% ({status.processed_chunks}/{status.chunk_tasks}), '
logger.info(
f'= Progress: {status.progress:.02f}% ({status.processed_chunks}/{status.chunk_tasks}), '
f'Running for {str(datetime.timedelta(seconds=status.runtime))}, '
f'ETA: {str(datetime.timedelta(seconds=status.estimated_time_left))}')
logger.info(f' - Downloaded: {status.total_downloaded / 1024 / 1024:.02f} MiB, '
@ -937,7 +937,7 @@ class LegendaryCLI:
self.core.lgd.clean_tmp_data()
after = self.core.lgd.get_dir_size()
logger.info(f'Cleanup complete! Removed {(before - after)/1024/1024:.02f} MiB.')
logger.info(f'Cleanup complete! Removed {(before - after) / 1024 / 1024:.02f} MiB.')
def main():

View file

@ -5,35 +5,35 @@ import logging
import os
import shlex
import shutil
from base64 import b64decode
from collections import defaultdict
from datetime import datetime, timezone
from locale import getdefaultlocale
from multiprocessing import Queue
from random import choice as randchoice
from requests import session
from requests.exceptions import HTTPError
from typing import List, Dict, Callable
from uuid import uuid4
from requests import session
from requests.exceptions import HTTPError
from custom_legendary.api.egs import EPCAPI
from custom_legendary.downloader.manager import DLManager
from custom_legendary.lfs.egl import EPCLFS
from custom_legendary.lfs.lgndry import LGDLFS
from custom_legendary.utils.lfs import clean_filename, delete_folder, delete_filelist, validate_files
from custom_legendary.models.chunk import Chunk
from custom_legendary.models.downloading import AnalysisResult, ConditionCheckResult
from custom_legendary.models.egl import EGLManifest
from custom_legendary.models.exceptions import InvalidCredentialsError
from custom_legendary.models.game import GameAsset, Game, InstalledGame, SaveGameFile, SaveGameStatus, VerifyResult
from custom_legendary.models.json_manifest import JSONManifest
from custom_legendary.models.manifest import Manifest, ManifestMeta
from custom_legendary.models.chunk import Chunk
from custom_legendary.models.manifest import Manifest
from custom_legendary.utils.game_workarounds import is_opt_enabled
from custom_legendary.utils.savegame_helper import SaveGameHelper
from custom_legendary.utils.lfs import clean_filename, delete_folder, delete_filelist, validate_files
from custom_legendary.utils.manifests import combine_manifests
from custom_legendary.utils.wine_helpers import read_registry, get_shell_folders
from custom_legendary.utils.savegame_helper import SaveGameHelper
from custom_legendary.utils.selective_dl import get_sdl_appname
from custom_legendary.utils.wine_helpers import read_registry, get_shell_folders
# ToDo: instead of true/false return values for success/failure actually raise an exception that the CLI/GUI
@ -349,11 +349,11 @@ class LegendaryCore:
params.extend(shlex.split(install.launch_parameters, posix=False))
params.extend([
'-AUTH_LOGIN=unused',
f'-AUTH_PASSWORD={game_token}',
'-AUTH_TYPE=exchangecode',
f'-epicapp={app_name}',
'-epicenv=Prod'])
'-AUTH_LOGIN=unused',
f'-AUTH_PASSWORD={game_token}',
'-AUTH_TYPE=exchangecode',
f'-epicapp={app_name}',
'-epicenv=Prod'])
if install.requires_ot and not offline:
self.log.info('Getting ownership token.')
@ -370,10 +370,10 @@ class LegendaryCore:
language_code = self.language_code
params.extend([
'-EpicPortal',
f'-epicusername={user_name}',
f'-epicuserid={account_id}',
f'-epiclocale={language_code}'
'-EpicPortal',
f'-epicusername={user_name}',
f'-epicuserid={account_id}',
f'-epiclocale={language_code}'
])
if extra_args:
@ -431,7 +431,7 @@ class LegendaryCore:
'{appdata}': os.path.expandvars('%APPDATA%'),
'{userdir}': os.path.expandvars('%userprofile%/documents'),
# '{userprofile}': os.path.expandvars('%userprofile%'), # possibly wrong
'{usersavedgames}': os.path.expandvars('%userprofile%/Saved Games')
'{usersavedgames}': os.path.expandvars('%userprofile%/Saved Games')
})
else:
# attempt to get WINE prefix from config
@ -589,7 +589,7 @@ class LegendaryCore:
self.log.debug(f'Writing "{fpath}"...')
with open(fpath, 'wb') as fh:
for cp in fm.chunk_parts:
fh.write(chunks[cp.guid_num][cp.offset:cp.offset+cp.size])
fh.write(chunks[cp.guid_num][cp.offset:cp.offset + cp.size])
# set modified time to savegame creation timestamp
m_date = datetime.strptime(f_parts[4], '%Y.%m.%d-%H.%M.%S.manifest')
@ -696,7 +696,7 @@ class LegendaryCore:
else:
return None
def verify_game(self, app_name: str, callback: Callable[[int, int], None]=print):
def verify_game(self, app_name: str, callback: Callable[[int, int], None] = print):
if not self.is_installed(app_name):
self.log.error(f'Game "{app_name}" is not installed')
return
@ -747,7 +747,8 @@ class LegendaryCore:
if not missing and not failed:
self.log.info('Verification finished successfully.')
else:
raise RuntimeError(f'Verification failed, {len(failed)} file(s) corrupted, {len(missing)} file(s) are missing.')
raise RuntimeError(
f'Verification failed, {len(failed)} file(s) corrupted, {len(missing)} file(s) are missing.')
def prepare_download(self, app_name: str, base_path: str = '', no_install: bool = False,
status_q: Queue = None, max_shm: int = 0, max_workers: int = 0,
@ -761,7 +762,8 @@ class LegendaryCore:
ignore_space_req: bool = False,
disable_delta: bool = False, override_delta_manifest: str = '',
egl_guid: str = '', reset_sdl: bool = False,
sdl_prompt: Callable[[str, str], List[str]] = list) -> (DLManager, AnalysisResult, Game, InstalledGame, bool, str):
sdl_prompt: Callable[[str, str], List[str]] = list) -> (
DLManager, AnalysisResult, Game, InstalledGame, bool, str):
if self.is_installed(app_name):
igame = self.get_installed_game(app_name)
if igame.needs_verification and not repair:
@ -784,7 +786,7 @@ class LegendaryCore:
if not game:
raise RuntimeError(f'Could not find "{app_name}" in list of available games,'
f'did you type the name correctly?')
f'did you type the name correctly?')
if game.is_dlc:
self.log.info('Install candidate is DLC')
@ -889,10 +891,10 @@ class LegendaryCore:
else:
if not game_folder:
if game.is_dlc:
game_folder = base_game.metadata.get('customAttributes', {}).\
game_folder = base_game.metadata.get('customAttributes', {}). \
get('FolderName', {}).get('value', base_game.app_name)
else:
game_folder = game.metadata.get('customAttributes', {}).\
game_folder = game.metadata.get('customAttributes', {}). \
get('FolderName', {}).get('value', game.app_name)
if not base_path:
@ -992,8 +994,8 @@ class LegendaryCore:
raise RuntimeError('Nothing to do.')
res = self.check_installation_conditions(analysis=anlres, install=igame, game=game,
updating=self.is_installed(app_name),
ignore_space_req=ignore_space_req)
updating=self.is_installed(app_name),
ignore_space_req=ignore_space_req)
if res.warnings or res.failures:
self.log.info('Installation requirements check returned the following results:')
@ -1179,8 +1181,8 @@ class LegendaryCore:
# If there's no in-progress installation assume the game doesn't need to be verified
if mf and not os.path.exists(os.path.join(app_path, '.egstore', 'bps')):
needs_verify = False
if os.path.exists(os.path.join(app_path, '.egstore', 'Pending')):
if os.listdir(os.path.join(app_path, '.egstore', 'Pending')):
if os.path.exists(os.path.join(app_path, '.egstore', 'Pending')):
if os.listdir(os.path.join(app_path, '.egstore', 'Pending')):
needs_verify = True
if not needs_verify:
@ -1304,13 +1306,13 @@ class LegendaryCore:
os.makedirs(egstore_folder)
# copy manifest and create mancpn file in .egstore folder
with open(os.path.join(egstore_folder, f'{egl_game.installation_guid}.manifest',), 'wb') as mf:
with open(os.path.join(egstore_folder, f'{egl_game.installation_guid}.manifest', ), 'wb') as mf:
mf.write(manifest_data)
mancpn = dict(FormatVersion=0, AppName=app_name,
CatalogItemId=lgd_game.asset_info.catalog_item_id,
CatalogNamespace=lgd_game.asset_info.namespace)
with open(os.path.join(egstore_folder, f'{egl_game.installation_guid}.mancpn',), 'w') as mcpnf:
with open(os.path.join(egstore_folder, f'{egl_game.installation_guid}.mancpn', ), 'w') as mcpnf:
json.dump(mancpn, mcpnf, indent=4, sort_keys=True)
# And finally, write the file for EGL

View file

@ -5,7 +5,6 @@
import logging
import os
import time
from collections import Counter, defaultdict, deque
from logging.handlers import QueueHandler
from multiprocessing import cpu_count, Process, Queue as MPQueue
@ -172,7 +171,7 @@ class DLManager(Process):
file_prefix_filter = [f.lower() for f in file_prefix_filter]
files_to_skip = set(i.filename for i in manifest.file_manifest_list.elements if not
any(i.filename.lower().startswith(pfx) for pfx in file_prefix_filter))
any(i.filename.lower().startswith(pfx) for pfx in file_prefix_filter))
self.log.info(f'Found {len(files_to_skip)} files to skip based on include prefix(es)')
mc.added -= files_to_skip
mc.changed -= files_to_skip

View file

@ -1,15 +1,15 @@
# coding: utf-8
import os
import requests
import time
import logging
import os
import time
from logging.handlers import QueueHandler
from multiprocessing import Process
from multiprocessing.shared_memory import SharedMemory
from queue import Empty
import requests
from custom_legendary.models.chunk import Chunk
from custom_legendary.models.downloading import DownloaderTaskResult, WriterTaskResult
@ -252,13 +252,13 @@ class FileWorker(Process):
chunk_guid=j.chunk_guid,
release_memory=j.release_memory,
shm=j.shm, size=j.chunk_size,
time_delta=post_write-pre_write))
time_delta=post_write - pre_write))
else:
self.o_q.put(WriterTaskResult(success=True, filename=j.filename,
chunk_guid=j.chunk_guid,
release_memory=j.release_memory,
shm=j.shm, size=j.chunk_size,
time_delta=post_write-pre_write))
time_delta=post_write - pre_write))
except Exception as e:
logger.warning(f'Job {j.filename} failed with: {e!r}, fetching next one...')
self.o_q.put(WriterTaskResult(success=False, filename=j.filename, chunk_guid=j.chunk_guid))

View file

@ -3,7 +3,6 @@
import configparser
import json
import os
from typing import List
from custom_legendary.models.egl import EGLManifest

View file

@ -1,10 +1,9 @@
# coding: utf-8
import json
import os
import configparser
import json
import logging
import os
from pathlib import Path
from custom_legendary.models.game import *

View file

@ -2,7 +2,6 @@
import struct
import zlib
from hashlib import sha1
from io import BytesIO
from uuid import uuid4
@ -50,7 +49,7 @@ class Chunk:
@data.setter
def data(self, value: bytes):
if len(value) > 1024*1024:
if len(value) > 1024 * 1024:
raise ValueError('Provided data is too large (> 1 MiB)!')
# data is now uncompressed
if self.compressed:

View file

@ -169,6 +169,7 @@ class AnalysisResult:
class ConditionCheckResult:
"""Result object used in Core to identify problems that would prevent an installation from succeeding"""
def __init__(self, failures=None, warnings=None):
self.failures = failures
self.warnings = warnings

View file

@ -3,7 +3,6 @@ from distutils.util import strtobool
from custom_legendary.models.game import InstalledGame, Game
_template = {
'AppCategories': ['public', 'games', 'applications'],
'AppName': '',

View file

@ -2,7 +2,6 @@
import json
import struct
from copy import deepcopy
from custom_legendary.models.manifest import (
@ -37,6 +36,7 @@ class JSONManifest(Manifest):
Manifest-compatible reader for JSON based manifests
"""
def __init__(self):
super().__init__()
self.json_data = None
@ -127,7 +127,7 @@ class JSONCDL(CDL):
_ci.hash = blob_to_num(chl.pop(guid))
_ci.sha_hash = bytes.fromhex(csl.pop(guid))
_ci.group_num = blob_to_num(dgl.pop(guid))
_ci.window_size = 1024*1024
_ci.window_size = 1024 * 1024
_cdl.elements.append(_ci)
for _dc in (cfl, chl, csl, dgl):
@ -149,7 +149,7 @@ class JSONFML(FML):
for _fmj in json_data.pop('FileManifestList'):
_fm = FileManifest()
_fm.filename = _fmj.pop('Filename', '')
_fm.hash = blob_to_num(_fmj.pop('FileHash')).to_bytes(160//8, 'little')
_fm.hash = blob_to_num(_fmj.pop('FileHash')).to_bytes(160 // 8, 'little')
_fm.flags |= int(_fmj.pop('bIsReadOnly', False))
_fm.flags |= int(_fmj.pop('bIsCompressed', False)) << 1
_fm.flags |= int(_fmj.pop('bIsUnixExecutable', False)) << 2

View file

@ -602,9 +602,9 @@ class FileManifest:
return '<FileManifest (filename="{}", symlink_target="{}", hash={}, flags={}, ' \
'install_tags=[{}], chunk_parts=[{}], file_size={})>'.format(
self.filename, self.symlink_target, self.hash.hex(), self.flags,
', '.join(self.install_tags), cp_repr, self.file_size
)
self.filename, self.symlink_target, self.hash.hex(), self.flags,
', '.join(self.install_tags), cp_repr, self.file_size
)
class ChunkPart:

View file

@ -1,5 +1,6 @@
import argparse
# reference: https://gist.github.com/sampsyo/471779#gistcomment-2886157

View file

@ -1,10 +1,9 @@
# coding: utf-8
import os
import shutil
import hashlib
import logging
import os
import shutil
from typing import List, Iterator
from custom_legendary.models.game import VerifyResult
@ -37,7 +36,7 @@ def delete_filelist(path: str, filenames: List[str],
_dir, _fn = os.path.split(filename)
if _dir:
dirs.add(_dir)
try:
os.remove(os.path.join(path, _dir, _fn))
except Exception as e:
@ -63,14 +62,14 @@ def delete_filelist(path: str, filenames: List[str],
if not silent:
logger.error(f'Failed removing directory "{_dir}" with {e!r}')
no_error = False
if delete_root_directory:
try:
os.rmdir(path)
except Exception as e:
if not silent:
logger.error(f'Removing game directory failed with {e!r}')
return no_error
@ -101,7 +100,7 @@ def validate_files(base_path: str, filelist: List[tuple], hash_type='sha1') -> I
try:
with open(full_path, 'rb') as f:
real_file_hash = hashlib.new(hash_type)
while chunk := f.read(1024*1024):
while chunk := f.read(1024 * 1024):
real_file_hash.update(chunk)
result_hash = real_file_hash.hexdigest()

View file

@ -1,6 +1,5 @@
import logging
import os
from datetime import datetime
from fnmatch import fnmatch
from hashlib import sha1
@ -130,7 +129,7 @@ class SaveGameHelper:
self.log.warning(f'Got EOF for "{f.filename}" with {remaining} bytes remaining! '
f'File may have been corrupted/modified.')
break
cur_buffer.write(_tmp)
fhash.update(_tmp) # update sha1 hash with new data
f.chunk_parts.append(cp)

View file

@ -1,5 +1,5 @@
import os
import configparser
import os
def read_registry(wine_pfx):