├── lug-logo.png ├── rsi-launcher.png ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ └── bug_report.md ├── pull_request_template.md └── CONTRIBUTING.md ├── lib └── sc-launch.sh ├── README.md ├── LICENSE └── lug-helper.sh /lug-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starcitizen-lug/lug-helper/HEAD/lug-logo.png -------------------------------------------------------------------------------- /rsi-launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starcitizen-lug/lug-helper/HEAD/rsi-launcher.png -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Wiki 4 | url: https://wiki.starcitizen-lug.org 5 | about: Problems running or installing Star Citizen, troubleshooting steps, and recent news/changes. 6 | - name: Community Support 7 | url: https://discord.gg/QRexSTkF25 8 | about: Get help from the community in our Discord tech support channel. 9 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Current behavior 2 | 3 | 4 | ## Changes made 5 | 6 | 7 | ## Other information 8 | 9 | 10 | ## Checklist 11 | 12 | - [ ] I've read the [Contributor's Guide](https://github.com/starcitizen-lug/lug-helper?tab=contributing-ov-file) 13 | - [ ] I have fully tested this PR 14 | - [ ] The code is well documented 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for improving the Helper tool. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Bugs or issues with the Helper tool. See the Wiki & Discord options below for 4 | problems running Star Citizen. 5 | title: '' 6 | labels: '' 7 | assignees: '' 8 | 9 | --- 10 | 11 | **Describe the bug** 12 | Please do not report problems running Star Citizen here; this form is for bugs in the LUG Helper tool. 13 | For problems running Star Citizen, see our Wiki for troubleshooting info and links to community support: https://wiki.starcitizen-lug.org/ 14 | 15 | **To Reproduce** 16 | Steps to reproduce the behavior: 17 | 1. Go to '...' 18 | 2. Click on '....' 19 | 3. Scroll down to '....' 20 | 4. See error 21 | 22 | **Expected behavior** 23 | A clear and concise description of what you expected to happen. 24 | 25 | **Screenshots** 26 | If applicable, add screenshots to help explain your problem. 27 | 28 | **OS Info:** 29 | - Distro: [e.g. Arch, Ubuntu] 30 | - Version if applicable [e.g. 22.04] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributor's Guide 2 | ### Project Goals 3 | The LUG Helper is a purpose-built bash script for the [Star Citizen Linux Users Group](https://wiki.starcitizen-lug.org/). Our community is a diverse group of Penguins running many differnet Linux distros on all kinds of hardware. As such, ease of use and compatibility are primary focuses of the project. 4 | 5 | The Helper is designed to be easy and intuitive for novice Penguins who may be using Linux for the very first time. It clearly communicates to the user what is being done and aims to provide working defaults without overwhelming the user with too many unnecessary choices. 6 | 7 | ### Pull Request Guidelines 8 | With the above project goals in mind, please consider the following guidelines when submitting Pull Requests: 9 | - Avoid overwhelming the user with choices and, instead, provide defaults that "Just Work". 10 | - Any messages or options presented to the user should be clear and concise. 11 | - The Helper should not make any changes to the user's system without first asking or notifying the user. 12 | - Avoid duplicating code. Make use of the existing helper functions. See [Code Structure and Overview](https://github.com/starcitizen-lug/lug-helper/wiki/Code-Structure-and-Overview). 13 | - For significant contributions, please ask if it's desired or already being worked on. 14 | - Please, no AI-generated code. 15 | 16 | ### Code Syntax and Formatting Guidelines 17 | - Match existing code styling and syntax for consistency and legibility. 18 | - Use POSIX-compliant code where possible for portability. 19 | - Where non-POSIX, bash-specific syntax is necessary for functionality or because it vastly simplifies code maintenance, check in which bash version the feature was introduced. Verify that the syntax will work on LTS distros. 20 | - Where possible, code should be easy to understand by someone moderately competent with shell script. 21 | - Avoid overly simplified one-liners that are difficult to parse. Break it up. 22 | - Please comment your code! 23 | 24 | --- 25 | ❤️ Many thanks to everyone who has [contributed](https://github.com/starcitizen-lug/lug-helper/graphs/contributors) to the project! 26 | -------------------------------------------------------------------------------- /lib/sc-launch.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This script launches Star Citizen using Wine. 4 | # It is meant to be used after installation via the LUG Helper. 5 | # 6 | # Usage: 7 | # Run from your terminal or use the .desktop files installed by the Helper. 8 | # 9 | # version: 2.4 10 | # License: GPLv3.0 11 | 12 | ############################################################################ 13 | # ENVIRONMENT VARIABLES 14 | ############################################################################ 15 | # Add additional environment variables to this section as needed 16 | # Example: 17 | # export NEW_VARIABLE="value" 18 | ############################################################################ 19 | export WINEPREFIX="$HOME/Games/star-citizen" 20 | 21 | launch_log="$WINEPREFIX/sc-launch.log" 22 | unset SDL_VIDEODRIVER 23 | 24 | export WINEDLLOVERRIDES=winemenubuilder.exe=d # Prevent updates from overwriting our .desktop entries 25 | export WINEDEBUG=-all # Cut down on console debug messages 26 | # Nvidia cache options 27 | export __GL_SHADER_DISK_CACHE=1 28 | export __GL_SHADER_DISK_CACHE_SIZE=10737418240 29 | export __GL_SHADER_DISK_CACHE_PATH="$WINEPREFIX" 30 | export __GL_SHADER_DISK_CACHE_SKIP_CLEANUP=1 31 | # Mesa (AMD/Intel) shader cache options 32 | export MESA_SHADER_CACHE_DIR="$WINEPREFIX" 33 | export MESA_SHADER_CACHE_MAX_SIZE="10G" 34 | # Performance options 35 | export WINEESYNC=1 36 | export WINEFSYNC=1 37 | #export DXVK_ASYNC=1 38 | # Optional HUDs 39 | #export DXVK_HUD=fps,compiler 40 | #export MANGOHUD=1 41 | 42 | ############################################################################ 43 | # END ENVIRONMENT VARIABLES 44 | ############################################################################ 45 | 46 | ################## 47 | # Wine binary path 48 | ################## 49 | # To use a custom wine runner, set the path to its bin directory 50 | # export wine_path="/path/to/custom/runner/bin" 51 | export wine_path="$(command -v wine | xargs dirname)" 52 | 53 | ######################## 54 | # Command line arguments 55 | ######################## 56 | # shell - Drop into a Wine maintenance shell 57 | # config - Wine configuration 58 | # controllers - Game controller configuration 59 | # Usage: ./sc-launch.sh shell 60 | case "$1" in 61 | "shell") 62 | echo "Entering Wine prefix maintenance shell. Type 'exit' when done." 63 | export PATH="$wine_path:$PATH"; export PS1="Wine: " 64 | cd "$WINEPREFIX"; pwd; /usr/bin/env bash --norc; exit 0 65 | ;; 66 | "config") 67 | /usr/bin/env bash --norc -c "\"${wine_path}\"/winecfg"; exit 0 68 | ;; 69 | "controllers") 70 | /usr/bin/env bash --norc -c "\"${wine_path}\"/wine control joy.cpl"; exit 0 71 | ;; 72 | esac 73 | 74 | ########################## 75 | # Update check and cleanup 76 | ########################## 77 | # Kill existing wine processes before launch 78 | update_check() { 79 | while "$wine_path"/winedbg --command "info proc" | grep -qi "rsi.*setup"; do 80 | echo "RSI Setup process detected. Exiting."; exit 0 81 | done 82 | } 83 | "$wine_path"/wineserver -k 84 | 85 | ############################################################################ 86 | # Launch the game 87 | ############################################################################ 88 | "$wine_path"/wine "C:\Program Files\Roberts Space Industries\RSI Launcher\RSI Launcher.exe" > "$launch_log" 2>&1 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LUG Helper 2 | **Star Citizen's Linux Users Group Helper Script** 3 | https://robertsspaceindustries.com/orgs/LUG 4 | 5 | ### *Greetings, fellow Penguin!* 6 | _**This script is designed to help you manage and optimize Star Citizen on Linux.**_ 7 | 8 | Zenity menus are used for a GUI experience with a fallback to terminal-based menus where Zenity is unavailable. 9 | Command line arguments are available for quickly launching functions from the terminal. 10 | 11 | Configuration is saved in *$XDG_CONFIG_HOME/starcitizen-lug/* 12 | Keybinds are backed up to *$XDG_CONFIG_HOME/starcitizen-lug/keybinds/* 13 | 14 | ## Options 15 | 16 | `Preflight Check` 17 | - Runs a series of system optimization checks and offers to fix any issues 18 | - Checks that vm.max_map_count is set to at least 16777216 19 | - This sets the maxmimum number of "memory map areas" a process can have. While most applications need less than a thousand maps, Star Citizen requires access to more 20 | - Checks that the hard open file descriptors limit is set to at least 524288 21 | - This limits the maximum number of open files on your system. On some Linux distributions, the default is set too low for Star Citizen 22 | 23 | `Install Star Citizen` 24 | - Installs Star Citizen using Wine 25 | 26 | `Manage Wine Runners` 27 | - Quickly install and delete custom Wine runners 28 | 29 | `Manage DXVK` 30 | - Update or switch to a different DXVK in the game's Wine prefix 31 | 32 | `Maintenance and Troubleshooting` 33 | - `Target a different Star Citizen installation` 34 | - Select a different wine prefix for the Helper to target in its operations 35 | 36 | - `Update/Repair launch script` 37 | - Update the game launch script to the latest version or repair broken paths and .desktop files 38 | 39 | - `Edit launch script` 40 | - Edit the game launch script 41 | 42 | - `Open Wine prefix configuration` 43 | - Runs *winecfg* in the game's Wine prefix 44 | 45 | - `Open Wine controller configuration` 46 | - Opens Wine's game controller configuration in the Wine prefix 47 | 48 | - `Install PowerShell into Wine prefix` 49 | - Uses winetricks to install PowerShell 50 | 51 | - `Update/Re-install RSI Launcher` 52 | - Re-install the latest version of the RSI Launcher 53 | 54 | - `Display Helper and Star Citizen directories` 55 | - Show all the directories currently in use by both the Helper and Star Citizen 56 | 57 | - `Reset Helper configs` 58 | - Delete the configs saved by the helper in *$XDG_CONFIG_HOME/starcitizen-lug/* 59 | 60 | `Get a random Penguin's Star Citizen referral code` 61 | - Display a referral code for a random member of the Star Citizen Linux Users Group 62 | 63 | 64 | 65 | ## Installation 66 | 67 | **From Source:** 68 | 1. Download it! https://github.com/starcitizen-lug/lug-helper/releases 69 | 2. Extract it! 70 | 3. Run it! 71 | 72 | **Arch Linux:** https://aur.archlinux.org/packages/lug-helper/ 73 | **NixOS:** https://github.com/LovingMelody/nix-citizen 74 | **Fedora:** https://copr.fedorainfracloud.org/coprs/jackgreiner/lug-helper 75 | 76 | _Dependencies: **bash**, **coreutils**, **curl**, **polkit** (these should be installed by default on most distributions)_ 77 | _Winetricks Dependencies: **cabextract**, **unzip**_ 78 | _Optional Dependencies: **zenity** (for GUI)_ 79 | 80 | ## Made with <3 81 | **Author:** https://github.com/the-sane 82 | 83 | ❤️ Many thanks to everyone who has contributed to the project! 84 | You can view commit graphs for our contributors [here](https://github.com/starcitizen-lug/lug-helper/graphs/contributors). 85 | 86 | Runner Downloader inspired by https://github.com/richardtatum/sc-runner-updater 87 | 88 | ## Contributing 89 | Please read the [Contributor's Guide](https://github.com/starcitizen-lug/lug-helper?tab=contributing-ov-file). 90 | For a high level overview of the script's functions, please see [Code Structure and Overview](https://github.com/starcitizen-lug/lug-helper/wiki/Code-Structure-and-Overview) on the wiki. 91 | Packagers, please see the [Packager's Guide](https://github.com/starcitizen-lug/lug-helper/wiki/Packagers-Guide). 92 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /lug-helper.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################ 4 | # Star Citizen Linux Users Group Helper Script 5 | ############################################################################ 6 | # 7 | # Greetings, Space Penguin! 8 | # 9 | # This script is designed to help you run Star Citizen on Linux. 10 | # 11 | # Please see the project's github repo for more information: 12 | # https://github.com/starcitizen-lug/lug-helper 13 | # 14 | # made with <3 by https://github.com/the-sane 15 | # 16 | # License: GPLv3.0 17 | ############################################################################ 18 | 19 | # Check if script is run as root 20 | if [ "$(id -u)" -eq 0 ]; then 21 | echo "This script is not supposed to be run as root!" 22 | exit 1 23 | fi 24 | 25 | # Check for dependencies 26 | if [ ! -x "$(command -v curl)" ]; then 27 | # Print to stderr and also try warning the user through zenity or notify-send 28 | printf "lug-helper.sh: The required package 'curl' was not found on this system.\n" 1>&2 29 | if [ -x "$(command -v zenity)" ]; then 30 | zenity --error --width="400" --title="Star Citizen LUG Helper" --text="The required package 'curl' was not found on this system." 31 | elif [ -x "$(command -v notify-send)" ]; then 32 | notify-send "lug-helper" "The required package 'curl' was not found on this system.\n" --icon=dialog-warning 33 | fi 34 | exit 1 35 | fi 36 | if [ ! -x "$(command -v mktemp)" ] || [ ! -x "$(command -v touch)" ] || [ ! -x "$(command -v chmod)" ] || [ ! -x "$(command -v sort)" ] || [ ! -x "$(command -v basename)" ] || [ ! -x "$(command -v realpath)" ] || [ ! -x "$(command -v dirname)" ] || [ ! -x "$(command -v cut)" ] || [ ! -x "$(command -v numfmt)" ] || [ ! -x "$(command -v tr)" ]; then 37 | # coreutils 38 | # Print to stderr and also try warning the user through zenity or notify-send 39 | printf "lug-helper.sh: One or more required packages were not found on this system.\nPlease check that 'coreutils' is installed!\n" 1>&2 40 | if [ -x "$(command -v zenity)" ]; then 41 | zenity --error --width="400" --title="Star Citizen LUG Helper" --text="One or more required packages were not found on this system.\n\nPlease check that 'coreutils' is installed!" 42 | elif [ -x "$(command -v notify-send)" ]; then 43 | notify-send "lug-helper" "One or more required packages were not found on this system.\nPlease check that 'coreutils' is installed!\n" --icon=dialog-warning 44 | fi 45 | exit 1 46 | fi 47 | if [ ! -x "$(command -v xargs)" ]; then 48 | # findutils 49 | # Print to stderr and also try warning the user through zenity or notify-send 50 | printf "lug-helper.sh: One or more required packages were not found on this system.\nPlease check that 'findutils' or the following packages are installed:\n- xargs\n" 1>&2 51 | if [ -x "$(command -v zenity)" ]; then 52 | zenity --error --width="400" --title="Star Citizen LUG Helper" --text="One or more required packages were not found on this system.\n\nPlease check that 'findutils' or the following packages are installed:\n- xargs" 53 | elif [ -x "$(command -v notify-send)" ]; then 54 | notify-send "lug-helper" "One or more required packages were not found on this system.\nPlease check that 'findutils' or the following packages are installed:\n- xargs\n" --icon=dialog-warning 55 | fi 56 | exit 1 57 | fi 58 | if [ ! -x "$(command -v cabextract)" ] || [ ! -x "$(command -v unzip)" ]; then 59 | # winetricks dependencies 60 | # Print to stderr and also try warning the user through zenity or notify-send 61 | printf "lug-helper.sh: One or more required packages were not found on this system.\nPlease check that the following winetricks dependencies (or winetricks itself) are installed:\n- cabextract\n- unzip\n" 1>&2 62 | if [ -x "$(command -v zenity)" ]; then 63 | zenity --error --width="400" --title="Star Citizen LUG Helper" --text="One or more required packages were not found on this system.\n\nPlease check that the following winetricks dependencies (or winetricks itself) are installed:\n- cabextract\n- unzip" 64 | elif [ -x "$(command -v notify-send)" ]; then 65 | notify-send "lug-helper" "One or more required packages were not found on this system.\nPlease check that the following winetricks dependencies (or winetricks itself) are installed:\n- cabextract\n- unzip\n" --icon=dialog-warning 66 | fi 67 | exit 1 68 | fi 69 | 70 | ######## Config ############################################################ 71 | 72 | wine_conf="winedir.conf" 73 | game_conf="gamedir.conf" 74 | firstrun_conf="firstrun.conf" 75 | 76 | # Use XDG base directories if defined 77 | if [ -f "${XDG_CONFIG_HOME:-$HOME/.config}/user-dirs.dirs" ]; then 78 | # Source the user's xdg directories 79 | source "${XDG_CONFIG_HOME:-$HOME/.config}/user-dirs.dirs" 80 | fi 81 | conf_dir="${XDG_CONFIG_HOME:-$HOME/.config}" 82 | data_dir="${XDG_DATA_HOME:-$HOME/.local/share}" 83 | 84 | # .config subdirectory 85 | conf_subdir="starcitizen-lug" 86 | 87 | # Helper directory 88 | helper_dir="$(realpath "$0" | xargs -0 dirname)" 89 | 90 | # Temporary directory 91 | tmp_dir="$(mktemp -d -t "lughelper.XXXXXXXXXX")" 92 | trap 'rm -r --interactive=never "$tmp_dir"' EXIT 93 | 94 | # Set a maximum number of versions to display from each download url 95 | max_download_items=25 96 | 97 | ######## Game Directories ################################################## 98 | 99 | # The game's base directory name 100 | sc_base_dir="StarCitizen" 101 | # The default install location within a WINE prefix: 102 | default_install_path="drive_c/Program Files/Roberts Space Industries" 103 | 104 | # Remaining directory paths are set at the end of the getdirs() function 105 | 106 | ######## Bundled Files ##################################################### 107 | 108 | rsi_icon_name="rsi-launcher.png" 109 | wine_launch_script_name="sc-launch.sh" 110 | 111 | # Default to files in the Helper directory for a git download 112 | rsi_icon="$helper_dir/$rsi_icon_name" 113 | wine_launch_script="$helper_dir/lib/$wine_launch_script_name" 114 | 115 | # Build our array of search paths, supporting packaged versions of this script 116 | # Search XDG_DATA_DIRS and fall back to /usr/share/ 117 | IFS=':' read -r -a data_dirs_array <<< "$XDG_DATA_DIRS:/usr/share/" 118 | 119 | # Locate our files in the search array 120 | for searchdir in "${data_dirs_array[@]}"; do 121 | # Check if we've found all our files and break the loop 122 | if [ -f "$rsi_icon" ] && [ -f "$wine_launch_script" ]; then 123 | break 124 | fi 125 | 126 | # rsi-launcher.png 127 | if [ ! -f "$rsi_icon" ] && [ -f "$searchdir/icons/hicolor/256x256/apps/$rsi_icon_name" ]; then 128 | rsi_icon="$searchdir/icons/hicolor/256x256/apps/$rsi_icon_name" 129 | fi 130 | 131 | # sc-launch.sh 132 | if [ ! -f "$wine_launch_script" ] && [ -f "$searchdir/lug-helper/$wine_launch_script_name" ]; then 133 | wine_launch_script="$searchdir/lug-helper/$wine_launch_script_name" 134 | fi 135 | done 136 | 137 | ######## Runners ########################################################### 138 | 139 | # URLs for downloading Wine runners 140 | # Elements in this array must be added in quoted pairs of: "description" "url" 141 | # The first string in the pair is expected to contain the runner description 142 | # The second is expected to contain the api releases url 143 | # ie. "RawFox" "https://api.github.com/repos/rawfoxDE/raw-wine/releases" 144 | runner_sources=( 145 | "LUG" "https://api.github.com/repos/starcitizen-lug/lug-wine/releases" 146 | "Kron4ek" "https://api.github.com/repos/Kron4ek/Wine-Builds/releases" 147 | "RawFox" "https://api.github.com/repos/starcitizen-lug/raw-wine/releases" 148 | "Mactan" "https://api.github.com/repos/mactan-sc/mactan-sc-wine/releases" 149 | ) 150 | 151 | ######## DXVK ############################################################## 152 | 153 | # URLs for downloading dxvk versions 154 | dxvk_async_source="https://gitlab.com/api/v4/projects/Ph42oN%2Fdxvk-gplasync/releases/permalink/latest" 155 | 156 | ######## Requirements ###################################################### 157 | 158 | # Minimum amount of RAM in GiB 159 | memory_required="16" 160 | # Minimum amount of combined RAM + swap in GiB 161 | memory_combined_required="40" 162 | 163 | ######## Links / Versions ################################################## 164 | 165 | # LUG Wiki 166 | lug_wiki="https://wiki.starcitizen-lug.org" 167 | 168 | # NixOS section in Wiki 169 | lug_wiki_nixos="https://wiki.starcitizen-lug.org/Alternative-Installations#nix-installation" 170 | 171 | # RSI Installer version and url 172 | rsi_installer_base_url="https://install.robertsspaceindustries.com/rel/2" 173 | rsi_installer_latest_yml="${rsi_installer_base_url}/latest.yml" 174 | 175 | # Github repo and script version info 176 | repo="starcitizen-lug/lug-helper" 177 | releases_url="https://github.com/${repo}/releases" 178 | current_version="v4.6" 179 | 180 | ############################################################################ 181 | ############################################################################ 182 | ############################################################################ 183 | 184 | 185 | # MARK: try_exec() 186 | # Try to execute a supplied command with either user or root privileges 187 | # Expects two string arguments 188 | # Usage: try_exec [root|user] "command" 189 | try_exec() { 190 | # This function expects two string arguments 191 | if [ "$#" -lt 2 ]; then 192 | printf "\nScript error: The try_exec() function expects two arguments. Aborting.\n" 193 | read -n 1 -s -p "Press any key..." 194 | exit 0 195 | fi 196 | 197 | exec_type="$1" 198 | exec_command="$2" 199 | 200 | if [ "$exec_type" = "root" ]; then 201 | # Use pollkit's pkexec for gui authentication with a fallback to sudo 202 | if [ -x "$(command -v pkexec)" ]; then 203 | pkexec sh -c "$exec_command" 204 | 205 | # Check the exit status 206 | exit_code="$?" 207 | if [ "$exit_code" -eq 126 ] || [ "$exit_code" -eq 127 ]; then 208 | # User cancel or error 209 | debug_print continue "pkexec returned an error. Falling back to sudo..." 210 | else 211 | # Successful execution, return here 212 | return 0 213 | fi 214 | fi 215 | # Fall back to sudo if pkexec is unavailable or returned an error 216 | if [ -x "$(command -v sudo)" ]; then 217 | sudo sh -c "$exec_command" 218 | 219 | # Check the exit status 220 | if [ "$?" -eq 1 ]; then 221 | # Error 222 | return 1 223 | fi 224 | else 225 | # We don't know how to perform this operation with elevated privileges 226 | printf "\nNeither Polkit nor sudo appear to be installed. Unable to execute the command with the required privileges.\n" 227 | return 1 228 | fi 229 | elif [ "$exec_type" = "user" ]; then 230 | sh -c "$exec_command" 231 | 232 | # Check the exit status 233 | if [ "$?" -eq 1 ]; then 234 | # Error 235 | return 1 236 | fi 237 | else 238 | debug_print exit "Script Error: Invalid arguemnt passed to the try_exec function. Aborting." 239 | fi 240 | 241 | return 0 242 | } 243 | 244 | # MARK: debug_print() 245 | # Echo a formatted debug message to the terminal and optionally exit 246 | # Accepts either "continue" or "exit" as the first argument 247 | # followed by the string to be echoed 248 | debug_print() { 249 | # This function expects two string arguments 250 | if [ "$#" -lt 2 ]; then 251 | printf "\nScript error: The debug_print function expects two arguments. Aborting.\n" 252 | read -n 1 -s -p "Press any key..." 253 | exit 0 254 | fi 255 | 256 | # Echo the provided string and, optionally, exit the script 257 | case "$1" in 258 | "continue") 259 | printf "\n%b\n" "$2" 260 | ;; 261 | "exit") 262 | # Write an error to stderr and exit 263 | printf "lug-helper.sh: %b\n" "$2" 1>&2 264 | read -n 1 -s -p "Press any key..." 265 | exit 1 266 | ;; 267 | *) 268 | printf "lug-helper.sh: Unknown argument provided to debug_print function. Aborting.\n" 1>&2 269 | read -n 1 -s -p "Press any key..." 270 | exit 0 271 | ;; 272 | esac 273 | } 274 | 275 | # MARK: message() 276 | # Display a message to the user. 277 | # Expects the first argument to indicate the message type, followed by 278 | # a string of arguments that will be passed to zenity or echoed to the user. 279 | # 280 | # To call this function, use the following format: message [type] "[string]" 281 | # See the message types below for instructions on formatting the string. 282 | message() { 283 | # Sanity check 284 | if [ "$#" -lt 2 ]; then 285 | debug_print exit "Script error: The message function expects at least two arguments. Aborting." 286 | fi 287 | 288 | # Use zenity messages if available 289 | if [ "$use_zenity" -eq 1 ]; then 290 | case "$1" in 291 | "info") 292 | # info message 293 | # call format: message info "text to display" 294 | margs=("--info" "--no-wrap" "--text=") 295 | shift 1 # drop the message type argument and shift up to the text 296 | ;; 297 | "warning") 298 | # warning message 299 | # call format: message warning "text to display" 300 | margs=("--warning" "--text=") 301 | shift 1 # drop the message type argument and shift up to the text 302 | ;; 303 | "error") 304 | # error message 305 | # call format: message error "text to display" 306 | margs=("--error" "--text=") 307 | shift 1 # drop the message type argument and shift up to the text 308 | ;; 309 | "question") 310 | # question 311 | # call format: if message question "question to ask?"; then... 312 | margs=("--question" "--text=") 313 | shift 1 # drop the message type argument and shift up to the text 314 | ;; 315 | "options") 316 | # formats the buttons with two custom options 317 | # call format: if message options left_button_name right_button_name "which one do you want?"; then... 318 | # The right button returns 0 (ok), the left button returns 1 (cancel) 319 | if [ "$#" -lt 4 ]; then 320 | debug_print exit "Script error: The options type in the message function expects four arguments. Aborting." 321 | fi 322 | margs=("--question" "--cancel-label=$2" "--ok-label=$3" "--text=") 323 | shift 3 # drop the type and button label arguments and shift up to the text 324 | ;; 325 | *) 326 | debug_print exit "Script Error: Invalid message type passed to the message function. Aborting." 327 | ;; 328 | esac 329 | 330 | # Display the message 331 | zenity "${margs[@]}""$@" --width="420" --title="Star Citizen LUG Helper" 332 | else 333 | # Fall back to text-based messages when zenity is not available 334 | case "$1" in 335 | "info") 336 | # info message 337 | # call format: message info "text to display" 338 | printf "\n%b\n\n" "$2" 339 | if [ "$cmd_line" != "true" ]; then 340 | # Don't pause if we've been invoked via command line arguments 341 | read -n 1 -s -p "Press any key..." 342 | fi 343 | ;; 344 | "warning") 345 | # warning message 346 | # call format: message warning "text to display" 347 | printf "\n%b\n\n" "$2" 348 | read -n 1 -s -p "Press any key..." 349 | ;; 350 | "error") 351 | # error message. Does not clear the screen 352 | # call format: message error "text to display" 353 | printf "\n%b\n\n" "$2" 354 | read -n 1 -s -p "Press any key..." 355 | ;; 356 | "question") 357 | # question 358 | # call format: if message question "question to ask?"; then... 359 | printf "\n%b\n" "$2" 360 | while read -p "[y/n]: " yn; do 361 | case "$yn" in 362 | [Yy]*) 363 | return 0 364 | ;; 365 | [Nn]*) 366 | return 1 367 | ;; 368 | *) 369 | printf "Please type 'y' or 'n'\n" 370 | ;; 371 | esac 372 | done 373 | ;; 374 | "options") 375 | # Choose from two options 376 | # call format: if message options left_button_name right_button_name "which one do you want?"; then... 377 | printf "\n%b\n1: %b\n2: %b\n" "$4" "$3" "$2" 378 | while read -p "[1/2]: " option; do 379 | case "$option" in 380 | 1*) 381 | return 0 382 | ;; 383 | 2*) 384 | return 1 385 | ;; 386 | *) 387 | printf "Please type '1' or '2'\n" 388 | ;; 389 | esac 390 | done 391 | ;; 392 | *) 393 | debug_print exit "Script Error: Invalid message type passed to the message function. Aborting." 394 | ;; 395 | esac 396 | fi 397 | } 398 | 399 | # MARK: progress_bar() 400 | # Display a zenity progress bar that pulsates until its PID is killed. 401 | # Takes a start or a stop argument, followed by a message string. 402 | # 403 | # To call this function, use the following format: progress_bar [start|stop] "[string]". 404 | # The first string argument should be either "start" or "stop". 405 | # If "start" is specified, a second string argument contains the text to display to the user. 406 | # If "stop" is specified, no second argument is used and the progress bar's PID is killed. 407 | # 408 | # This function does not verify whether or not start was called before stop. 409 | progress_bar() { 410 | # This function expects at least one string argument 411 | if [ -z "$1" ]; then 412 | debug_print exit "Script error: The progress_bar function expects at least one string argument. Aborting." 413 | fi 414 | # If the first argument is start, a second argument is required 415 | if [ "$1" = "start" ] && [ -z "$2" ]; then 416 | debug_print exit "Script error: The progress_bar function expects a second string argument when starting the progress bar. Aborting." 417 | fi 418 | 419 | # Don't do anything if not using zenity 420 | if [ "$use_zenity" -eq 0 ]; then 421 | return 0 422 | fi 423 | 424 | if [ "$1" = "start" ]; then 425 | # If another progress bar is already running, do nothing 426 | if [ -f "$tmp_dir/zenity_progress_bar_running" ]; then 427 | debug_print continue "Script error: A progress_bar function instance is already running, but a new progress bar was called. This is not handled." 428 | return 0 429 | fi 430 | 431 | # Show a zenity pulsating progress bar and use a temp file to track it inside the subshell 432 | touch "$tmp_dir/zenity_progress_bar_running" 433 | while [ -f "$tmp_dir/zenity_progress_bar_running" ]; do 434 | sleep 1 435 | done | zenity --progress --pulsate --no-cancel --auto-close --title="Star Citizen LUG Helper" --text="$2" 2>/dev/null & 436 | 437 | trap 'progress_bar stop' SIGINT # catch sigint to cleanly kill the zenity progress window 438 | elif [ "$1" = "stop" ]; then 439 | # Stop the zenity progress window 440 | rm --interactive=never "$tmp_dir/zenity_progress_bar_running" 2>/dev/null 441 | trap - SIGINT # Remove the trap 442 | else 443 | debug_print exit "Script error: The progress_bar function expects either 'start' or 'stop' as the first argument. Aborting." 444 | fi 445 | } 446 | 447 | # MARK: menu() 448 | # Display a menu to the user. 449 | # Uses Zenity for a gui menu with a fallback to plain old text. 450 | # 451 | # How to call this function: 452 | # 453 | # Requires the following variables: 454 | # - The array "menu_options" should contain the strings of each option. 455 | # - The array "menu_actions" should contain function names to be called. 456 | # - The strings "menu_text_zenity" and "menu_text_terminal" should contain 457 | # the menu description formatted for zenity and the terminal, respectively. 458 | # This text will be displayed above the menu options. 459 | # Zenity supports Pango Markup for text formatting. 460 | # - The integer "menu_height" specifies the height of the zenity menu. 461 | # - The string "menu_type" should contain either "radiolist" or "checklist". 462 | # - The string "cancel_label" should contain the text of the cancel button. 463 | # 464 | # The final element in each array is expected to be a quit option. 465 | # 466 | # IMPORTANT: The indices of the elements in "menu_actions" 467 | # *MUST* correspond to the indeces in "menu_options". 468 | # In other words, it is expected that menu_actions[1] is the correct action 469 | # to be executed when menu_options[1] is selected, and so on for each element. 470 | # 471 | # See MAIN at the bottom of this script for an example of generating a menu. 472 | menu() { 473 | # Sanity checks 474 | if [ "${#menu_options[@]}" -eq 0 ]; then 475 | debug_print exit "Script error: The array 'menu_options' was not set before calling the menu function. Aborting." 476 | elif [ "${#menu_actions[@]}" -eq 0 ]; then 477 | debug_print exit "Script error: The array 'menu_actions' was not set before calling the menu function. Aborting." 478 | elif [ -z "$menu_text_zenity" ]; then 479 | debug_print exit "Script error: The string 'menu_text_zenity' was not set before calling the menu function. Aborting." 480 | elif [ -z "$menu_text_terminal" ]; then 481 | debug_print exit "Script error: The string 'menu_text_terminal' was not set before calling the menu function. Aborting." 482 | elif [ -z "$menu_height" ]; then 483 | debug_print exit "Script error: The string 'menu_height' was not set before calling the menu function. Aborting." 484 | elif [ "$menu_type" != "radiolist" ] && [ "$menu_type" != "checklist" ]; then 485 | debug_print exit "Script error: Unknown menu_type in menu() function. Aborting." 486 | elif [ -z "$cancel_label" ]; then 487 | debug_print exit "Script error: The string 'cancel_label' was not set before calling the menu function. Aborting." 488 | fi 489 | 490 | # Use Zenity if it is available 491 | if [ "$use_zenity" -eq 1 ]; then 492 | # Format the options array for Zenity by adding 493 | # TRUE or FALSE to indicate default selections 494 | # ie: "TRUE" "List item 1" "FALSE" "List item 2" "FALSE" "List item 3" 495 | unset zen_options 496 | for (( i=0; i<"${#menu_options[@]}"-1; i++ )); do 497 | if [ "$i" -eq 0 ]; then 498 | # Set the first element 499 | if [ "$menu_type" = "radiolist" ]; then 500 | # Select the first radio button by default 501 | zen_options=("TRUE") 502 | else 503 | # Don't select the first checklist item 504 | zen_options=("FALSE") 505 | fi 506 | else 507 | # Deselect all remaining items 508 | zen_options+=("FALSE") 509 | fi 510 | # Add the menu list item 511 | zen_options+=("${menu_options[i]}") 512 | done 513 | 514 | # Display the zenity radio button menu 515 | choice="$(zenity --list --"$menu_type" --width="510" --height="$menu_height" --text="$menu_text_zenity" --title="Star Citizen LUG Helper" --hide-header --cancel-label "$cancel_label" --column="" --column="Option" "${zen_options[@]}")" 516 | 517 | # Match up choice with an element in menu_options 518 | matched="false" 519 | if [ "$menu_type" = "radiolist" ]; then 520 | # Loop through the options array to match the chosen option 521 | for (( i=0; i<"${#menu_options[@]}"; i++ )); do 522 | if [ "$choice" = "${menu_options[i]}" ]; then 523 | # Execute the corresponding action for a radiolist menu 524 | ${menu_actions[i]} 525 | matched="true" 526 | break 527 | fi 528 | done 529 | elif [ "$menu_type" = "checklist" ]; then 530 | # choice will be empty if no selection was made 531 | # Unfortunately, it's also empty when the user presses cancel 532 | # so we can't differentiate between those two states 533 | 534 | # Convert choice string to array elements for checklists 535 | IFS='|' read -r -a choices <<< "$choice" 536 | 537 | # Fetch the function to be called 538 | function_call="$(echo "${menu_actions[0]}" | awk '{print $1}')" 539 | 540 | # Loop through the options array to match the chosen option(s) 541 | unset arguments_array 542 | for (( i=0; i<"${#menu_options[@]}"; i++ )); do 543 | for (( j=0; j<"${#choices[@]}"; j++ )); do 544 | if [ "${choices[j]}" = "${menu_options[i]}" ]; then 545 | arguments_array+=("$(echo "${menu_actions[i]}" | awk '{print $2}')") 546 | matched="true" 547 | fi 548 | done 549 | done 550 | 551 | # Call the function with all matched elements as arguments 552 | if [ "$matched" = "true" ]; then 553 | $function_call "${arguments_array[@]}" 554 | fi 555 | fi 556 | 557 | # If no match was found, the user clicked cancel 558 | if [ "$matched" = "false" ]; then 559 | # Execute the last option in the actions array 560 | "${menu_actions[${#menu_actions[@]}-1]}" 561 | fi 562 | else 563 | # Use a text menu if Zenity is not available 564 | clear 565 | printf "\n%b\n\n" "$menu_text_terminal" 566 | 567 | PS3="Enter selection number: " 568 | select choice in "${menu_options[@]}" 569 | do 570 | # Loop through the options array to match the chosen option 571 | matched="false" 572 | for (( i=0; i<"${#menu_options[@]}"; i++ )); do 573 | if [ "$choice" = "${menu_options[i]}" ]; then 574 | clear 575 | # Execute the corresponding action 576 | ${menu_actions[i]} 577 | matched="true" 578 | break 579 | fi 580 | done 581 | 582 | # Check if we're done looping the menu 583 | if [ "$matched" = "true" ]; then 584 | # Match was found and actioned, so exit the menu 585 | break 586 | else 587 | # If no match was found, the user entered an invalid option 588 | printf "\nInvalid selection.\n" 589 | continue 590 | fi 591 | done 592 | fi 593 | } 594 | 595 | # MARK: menu_loop_done() 596 | # Called when the user clicks cancel on a looping menu 597 | # Causes a return to the main menu 598 | menu_loop_done() { 599 | looping_menu="false" 600 | } 601 | 602 | # MARK: getdirs() 603 | # Get paths to the user's wine prefix, game directory, and a backup directory 604 | # Returns 3 if the user was asked to select new directories 605 | getdirs() { 606 | # Sanity checks 607 | if [ ! -d "$conf_dir" ]; then 608 | message error "Config directory not found. The Helper is unable to proceed.\n\n$conf_dir" 609 | return 1 610 | fi 611 | if [ ! -d "$conf_dir/$conf_subdir" ]; then 612 | mkdir -p "$conf_dir/$conf_subdir" 613 | fi 614 | 615 | # Initialize a return value 616 | retval=0 617 | 618 | # Check if the config files already exist 619 | if [ -f "$conf_dir/$conf_subdir/$wine_conf" ]; then 620 | wine_prefix="$(cat "$conf_dir/$conf_subdir/$wine_conf")" 621 | if [ ! -d "$wine_prefix" ]; then 622 | debug_print continue "The saved wine prefix does not exist, ignoring." 623 | wine_prefix="" 624 | rm --interactive=never "${conf_dir:?}/$conf_subdir/$wine_conf" 625 | fi 626 | fi 627 | if [ -f "$conf_dir/$conf_subdir/$game_conf" ]; then 628 | game_path="$(cat "$conf_dir/$conf_subdir/$game_conf")" 629 | # Note: We check for the parent dir here because the game may not have been fully installed yet 630 | # which means sc_base_dir may not yet have been created. But the parent RSI dir must exist 631 | if [ ! -d "$(dirname "$game_path")" ] || [ "$(basename "$game_path")" != "$sc_base_dir" ]; then 632 | debug_print continue "Unexpected game path found in config file, ignoring." 633 | game_path="" 634 | rm --interactive=never "${conf_dir:?}/$conf_subdir/$game_conf" 635 | fi 636 | fi 637 | 638 | # If we don't have the directory paths we need yet, 639 | # ask the user to provide them 640 | if [ -z "$wine_prefix" ] || [ -z "$game_path" ]; then 641 | message info "At the next screen, please select the directory where you installed Star Citizen (your Wine prefix)\nIt will be remembered for future use.\n\nDefault install path: ~/Games/star-citizen" 642 | if [ "$use_zenity" -eq 1 ]; then 643 | # Using Zenity file selection menus 644 | # Get the wine prefix directory 645 | while [ -z "$wine_prefix" ]; do 646 | wine_prefix="$(zenity --file-selection --directory --title="Select your Star Citizen Wine prefix directory" --filename="$HOME/Games/star-citizen" 2>/dev/null)" 647 | if [ "$?" -eq -1 ]; then 648 | message error "An unexpected error has occurred. The Helper is unable to proceed." 649 | return 1 650 | elif [ -z "$wine_prefix" ]; then 651 | # User clicked cancel 652 | message warning "Operation cancelled.\nNo changes have been made to your game." 653 | return 1 654 | fi 655 | 656 | if ! message question "You selected:\n\n$wine_prefix\n\nIs this correct?"; then 657 | wine_prefix="" 658 | fi 659 | done 660 | 661 | # Get the game path 662 | if [ -z "$game_path" ]; then 663 | if [ -d "$wine_prefix/$default_install_path" ]; then 664 | # Default: prefix/drive_c/Program Files/Roberts Space Industries/StarCitizen 665 | game_path="$wine_prefix/$default_install_path/$sc_base_dir" 666 | else 667 | message info "Unable to detect the default game install path!\n\n$wine_prefix/$default_install_path/$sc_base_dir\n\nDid you change the install location in the RSI Setup?\nDoing that is generally a bad idea but, if you are sure you want to proceed,\nselect your '$sc_base_dir' game directory on the next screen" 668 | while true; do 669 | game_path="$(zenity --file-selection --directory --title="Select your Star Citizen directory" --filename="$wine_prefix/$default_install_path" 2>/dev/null)" 670 | 671 | if [ "$?" -eq -1 ]; then 672 | message error "An unexpected error has occurred. The Helper is unable to proceed." 673 | return 1 674 | elif [ -z "$game_path" ]; then 675 | # User clicked cancel or something else went wrong 676 | message warning "Operation cancelled.\nNo changes have been made to your game." 677 | return 1 678 | elif [ "$(basename "$game_path")" != "$sc_base_dir" ]; then 679 | message warning "You must select the base game directory named '$sc_base_dir'\n\nie. [prefix]/drive_c/Program Files/Roberts Space Industries/StarCitizen" 680 | else 681 | # All good 682 | break 683 | fi 684 | done 685 | fi 686 | fi 687 | else 688 | # No Zenity, use terminal-based menus 689 | clear 690 | # Get the wine prefix directory 691 | if [ -z "$wine_prefix" ]; then 692 | printf "Enter the full path to your Star Citizen Wine prefix directory (case sensitive)\n" 693 | printf "ie. /home/USER/Games/star-citizen\n" 694 | while read -rp ": " wine_prefix; do 695 | if [ ! -d "$wine_prefix" ]; then 696 | printf "That directory is invalid or does not exist. Please try again.\n\n" 697 | else 698 | break 699 | fi 700 | done 701 | fi 702 | 703 | # Get the game path 704 | if [ -z "$game_path" ]; then 705 | if [ -d "$wine_prefix/$default_install_path/s" ]; then 706 | # Default: prefix/drive_c/Program Files/Roberts Space Industries/StarCitizen 707 | game_path="$wine_prefix/$default_install_path/$sc_base_dir" 708 | else 709 | printf "\nUnable to detect the default game install path!\nDid you change the install location in the RSI Setup?\nDoing that is generally a bad idea but, if you are sure you want to proceed...\n\n" 710 | printf "Enter the full path to your %s installation directory (case sensitive)\n" "$sc_base_dir" 711 | printf "ie. /home/USER/Games/star-citizen/drive_c/Program Files/Roberts Space Industries/StarCitizen\n" 712 | while read -rp ": " game_path; do 713 | if [ ! -d "$game_path" ]; then 714 | printf "That directory is invalid or does not exist. Please try again.\n\n" 715 | elif [ "$(basename "$game_path")" != "$sc_base_dir" ]; then 716 | printf "You must enter the full path to the directory named '%s'\n\n" "$sc_base_dir" 717 | else 718 | break 719 | fi 720 | done 721 | fi 722 | fi 723 | fi 724 | 725 | # Set a return code to indicate to other functions in this script that the user had to select new directories here 726 | retval=3 727 | fi 728 | 729 | # Save the paths to config files 730 | if [ ! -f "$conf_dir/$conf_subdir/$wine_conf" ]; then 731 | echo "$wine_prefix" > "$conf_dir/$conf_subdir/$wine_conf" 732 | fi 733 | if [ ! -f "$conf_dir/$conf_subdir/$game_conf" ]; then 734 | echo "$game_path" > "$conf_dir/$conf_subdir/$game_conf" 735 | fi 736 | 737 | return "$retval" 738 | } 739 | 740 | 741 | ############################################################################ 742 | ######## begin preflight check functions ################################### 743 | ############################################################################ 744 | 745 | # MARK: preflight_check() 746 | # Check that the system is optimized for Star Citizen 747 | # Accepts an optional string argument, "wine" 748 | # This argument is used by the install functions to indicate which 749 | # Preflight Check functions should be called and cause the Preflight Check 750 | # to only output problems that must be fixed 751 | # 752 | # There are two options for automatically fixing problems: 753 | # See existing functions for examples of setting 754 | # preflight_root_actions or preflight_user_actions 755 | preflight_check() { 756 | # Initialize variables 757 | unset preflight_pass 758 | unset preflight_fail 759 | unset preflight_action_funcs 760 | unset preflight_root_actions 761 | unset preflight_user_actions 762 | unset preflight_fix_results 763 | unset preflight_manual 764 | unset preflight_followup 765 | unset preflight_fail_string 766 | unset preflight_pass_string 767 | unset preflight_fix_results_string 768 | unset install_mode 769 | retval=0 770 | 771 | # Capture optional argument that determines which install function called us 772 | install_mode="$1" 773 | 774 | # Check the optional argument for valid values 775 | if [ -n "$install_mode" ] && [ "$install_mode" != "wine" ]; then 776 | debug_print exit "Script error: Unexpected argument passed to the preflight_check function. Aborting." 777 | fi 778 | 779 | # Call the optimization functions to perform the checks 780 | memory_check 781 | avx_check 782 | mapcount_check 783 | filelimit_check 784 | 785 | # Populate info strings with the results and add formatting 786 | if [ "${#preflight_fail[@]}" -gt 0 ]; then 787 | # Failed checks 788 | preflight_fail_string="Failed Checks:" 789 | for (( i=0; i<"${#preflight_fail[@]}"; i++ )); do 790 | if [ "$i" -eq 0 ]; then 791 | preflight_fail_string="$preflight_fail_string\n- ${preflight_fail[i]//\\n/\\n }" 792 | else 793 | preflight_fail_string="$preflight_fail_string\n\n- ${preflight_fail[i]//\\n/\\n }" 794 | fi 795 | done 796 | # Add extra newlines if there are also passes to report 797 | if [ "${#preflight_pass[@]}" -gt 0 ]; then 798 | preflight_fail_string="$preflight_fail_string\n\n" 799 | fi 800 | fi 801 | if [ "${#preflight_pass[@]}" -gt 0 ]; then 802 | # Passed checks 803 | preflight_pass_string="Passed Checks:" 804 | for (( i=0; i<"${#preflight_pass[@]}"; i++ )); do 805 | preflight_pass_string="$preflight_pass_string\n- ${preflight_pass[i]//\\n/\\n }" 806 | done 807 | fi 808 | for (( i=0; i<"${#preflight_manual[@]}"; i++ )); do 809 | # Instructions for manually fixing problems 810 | if [ "$i" -eq 0 ]; then 811 | preflight_manual_string="${preflight_manual[i]}" 812 | else 813 | preflight_manual_string="$preflight_manual_string\n\n${preflight_manual[i]}" 814 | fi 815 | done 816 | 817 | # Format a message heading 818 | message_heading="Preflight Check Results" 819 | if [ "$use_zenity" -eq 1 ]; then 820 | message_heading="$message_heading" 821 | fi 822 | 823 | # Display the results of the preflight check 824 | if [ -z "$preflight_fail_string" ]; then 825 | # If install_mode was set by an install function, we won't bother the user when all checks pass 826 | if [ -z "$install_mode" ]; then 827 | # All checks pass! 828 | message info "$message_heading\n\nYour system is optimized for Star Citizen!\n\n$preflight_pass_string" 829 | fi 830 | 831 | return 0 832 | else 833 | if [ "${#preflight_action_funcs[@]}" -eq 0 ]; then 834 | # We have failed checks, but they're issues we can't automatically fix 835 | message warning "$message_heading\n\n$preflight_fail_string$preflight_pass_string" 836 | elif message question "$message_heading\n\n$preflight_fail_string$preflight_pass_string\n\nWould you like these configuration issues to be fixed for you?"; then 837 | # We have failed checks, but we can fix them for the user 838 | # Call functions to build fixes for any issues found 839 | for (( i=0; i<"${#preflight_action_funcs[@]}"; i++ )); do 840 | ${preflight_action_funcs[i]} 841 | done 842 | 843 | # Populate a string of actions to be executed with root privileges 844 | for (( i=0; i<"${#preflight_root_actions[@]}"; i++ )); do 845 | if [ "$i" -eq 0 ]; then 846 | preflight_root_actions_string="${preflight_root_actions[i]}" 847 | else 848 | preflight_root_actions_string="$preflight_root_actions_string; ${preflight_root_actions[i]}" 849 | fi 850 | done 851 | # Populate a string of actions to be executed with user privileges 852 | for (( i=0; i<"${#preflight_user_actions[@]}"; i++ )); do 853 | if [ "$i" -eq 0 ]; then 854 | preflight_user_actions_string="${preflight_user_actions[i]}" 855 | else 856 | preflight_user_actions_string="$preflight_user_actions_string; ${preflight_user_actions[i]}" 857 | fi 858 | done 859 | 860 | # Execute the root privilege actions set by the functions 861 | if [ -n "$preflight_root_actions_string" ]; then 862 | # Try to execute the actions as root 863 | try_exec root "$preflight_root_actions_string" 864 | if [ "$?" -eq 1 ]; then 865 | message error "The Preflight Check was unable to finish fixing problems.\nDid authentication fail? See terminal for more information.\n\nReturning to main menu." 866 | return 0 867 | fi 868 | fi 869 | # Execute the user privilege actions set by the functions 870 | if [ -n "$preflight_user_actions_string" ]; then 871 | # Try to execute the actions as root 872 | try_exec user "$preflight_user_actions_string" 873 | if [ "$?" -eq 1 ]; then 874 | message error "The Preflight Check was unable to finish fixing problems.\nSee terminal for more information.\n\nReturning to main menu." 875 | return 0 876 | fi 877 | fi 878 | 879 | # Call any followup functions 880 | for (( i=0; i<"${#preflight_followup[@]}"; i++ )); do 881 | ${preflight_followup[i]} 882 | done 883 | 884 | # Populate the results string 885 | for (( i=0; i<"${#preflight_fix_results[@]}"; i++ )); do 886 | if [ "$i" -eq 0 ]; then 887 | preflight_fix_results_string="${preflight_fix_results[i]}" 888 | else 889 | preflight_fix_results_string="$preflight_fix_results_string\n\n${preflight_fix_results[i]}" 890 | fi 891 | done 892 | 893 | # Display the results 894 | message info "$preflight_fix_results_string" 895 | else 896 | # User declined to automatically fix configuration issues 897 | # Show manual configuration options 898 | if [ -n "$preflight_manual_string" ]; then 899 | message info "$preflight_manual_string" 900 | fi 901 | fi 902 | 903 | return 1 904 | fi 905 | } 906 | 907 | # MARK: memory_check() 908 | # Check system memory and swap space 909 | memory_check() { 910 | # Get totals in bytes 911 | memtotal="$(LC_NUMERIC=C awk '/MemTotal/ {printf $2}' /proc/meminfo)" 912 | swaptotal="$(LC_NUMERIC=C awk '/SwapTotal/ {printf $2}' /proc/meminfo)" 913 | memtotal="$(($memtotal * 1024))" 914 | swaptotal="$(($swaptotal * 1024))" 915 | combtotal="$(($memtotal + $swaptotal))" 916 | 917 | # Convert to whole number GiB 918 | memtotal="$(numfmt --to=iec-i --format="%.0f" --suffix="B" "$memtotal")" 919 | swaptotal="$(numfmt --to=iec-i --format="%.0f" --suffix="B" "$swaptotal")" 920 | combtotal="$(numfmt --to=iec-i --format="%.0f" --suffix="B" "$combtotal")" 921 | 922 | if [ "${memtotal: -3}" != "GiB" ] || [ "${memtotal::-3}" -lt "$(($memory_required-1))" ]; then 923 | # Minimum requirements are not met 924 | preflight_fail+=("Your system has $memtotal of memory.\n${memory_required}GiB is the minimum required to avoid crashes.") 925 | elif [ "${memtotal::-3}" -ge "$memory_combined_required" ]; then 926 | # System has sufficient RAM 927 | preflight_pass+=("Your system has $memtotal of memory.") 928 | elif [ "${combtotal::-3}" -ge "$memory_combined_required" ]; then 929 | # System has sufficient combined RAM + swap 930 | preflight_pass+=("Your system has $memtotal memory and $swaptotal swap.") 931 | else 932 | # Recommend swap 933 | swap_recommended="$(($memory_combined_required - ${memtotal::-3}))" 934 | preflight_fail+=("Your system has $memtotal memory and $swaptotal swap.\nWe recommend at least ${swap_recommended}GiB swap to avoid crashes.") 935 | fi 936 | } 937 | 938 | # MARK: avx_check() 939 | # Check CPU for the required AVX extension 940 | avx_check() { 941 | if grep -q "avx" /proc/cpuinfo; then 942 | preflight_pass+=("Your CPU supports the necessary AVX instruction set.") 943 | else 944 | preflight_fail+=("Your CPU does not appear to support AVX instructions.\nThis requirement was added to Star Citizen in version 3.11") 945 | fi 946 | } 947 | 948 | ############################################################################ 949 | ######## begin mapcount functions ########################################## 950 | ############################################################################ 951 | 952 | # MARK: mapcount_check() 953 | # Check vm.max_map_count for the correct setting 954 | mapcount_check() { 955 | mapcount="$(cat /proc/sys/vm/max_map_count)" 956 | # Add to the results and actions arrays 957 | if [ "$mapcount" -ge 16777216 ]; then 958 | # All good 959 | preflight_pass+=("vm.max_map_count is set to $mapcount.") 960 | elif grep -E -x -q "vm.max_map_count" /etc/sysctl.conf /etc/sysctl.d/* 2>/dev/null; then 961 | # Was it supposed to have been set by sysctl? 962 | preflight_fail+=("vm.max_map_count is configured to at least 16777216 but the setting has not been loaded by your system.") 963 | # Add the function that will be called to change the configuration 964 | preflight_action_funcs+=("mapcount_once") 965 | 966 | # Add info for manually changing the setting 967 | preflight_manual+=("To change vm.max_map_count until the next reboot, run:\nsudo sysctl -w vm.max_map_count=16777216") 968 | else 969 | # The setting should be changed 970 | preflight_fail+=("vm.max_map_count is $mapcount\nand should be set to at least 16777216\nto give the game access to sufficient memory.") 971 | # Add the function that will be called to change the configuration 972 | preflight_action_funcs+=("mapcount_set") 973 | 974 | # Add info for manually changing the setting 975 | if [ -d "/etc/sysctl.d" ]; then 976 | # Newer versions of sysctl 977 | preflight_manual+=("To change vm.max_map_count permanently, add the following line to\n'/etc/sysctl.d/99-starcitizen-max_map_count.conf' and reload with 'sudo sysctl --system'\n vm.max_map_count = 16777216\n\nOr, to change vm.max_map_count temporarily until next boot, run:\n sudo sysctl -w vm.max_map_count=16777216") 978 | else 979 | # Older versions of sysctl 980 | preflight_manual+=("To change vm.max_map_count permanently, add the following line to\n'/etc/sysctl.conf' and reload with 'sudo sysctl -p':\n vm.max_map_count = 16777216\n\nOr, to change vm.max_map_count temporarily until next boot, run:\n sudo sysctl -w vm.max_map_count=16777216") 981 | fi 982 | fi 983 | } 984 | 985 | # MARK: mapcount_set() 986 | # Set vm.max_map_count 987 | mapcount_set() { 988 | if [ -d "/etc/sysctl.d" ]; then 989 | # Newer versions of sysctl 990 | preflight_root_actions+=('printf "\n# Added by LUG-Helper:\nvm.max_map_count = 16777216\n" > /etc/sysctl.d/99-starcitizen-max_map_count.conf && sysctl --quiet --system') 991 | preflight_fix_results+=("The vm.max_map_count configuration has been added to:\n/etc/sysctl.d/99-starcitizen-max_map_count.conf") 992 | else 993 | # Older versions of sysctl 994 | preflight_root_actions+=('printf "\n# Added by LUG-Helper:\nvm.max_map_count = 16777216" >> /etc/sysctl.conf && sysctl -p') 995 | preflight_fix_results+=("The vm.max_map_count configuration has been added to:\n/etc/sysctl.conf") 996 | fi 997 | 998 | # Verify that the setting took effect 999 | preflight_followup+=("mapcount_confirm") 1000 | } 1001 | 1002 | # MARK: mapcount_once() 1003 | # Sets vm.max_map_count for the current session only 1004 | mapcount_once() { 1005 | preflight_root_actions+=('sysctl -w vm.max_map_count=16777216') 1006 | preflight_fix_results+=("vm.max_map_count was changed until the next boot.") 1007 | preflight_followup+=("mapcount_confirm") 1008 | } 1009 | 1010 | # MARK: mapcount_confirm() 1011 | # Check if setting vm.max_map_count was successful 1012 | mapcount_confirm() { 1013 | if [ "$(cat /proc/sys/vm/max_map_count)" -lt 16777216 ]; then 1014 | preflight_fix_results+=("WARNING: As far as this Helper can detect, vm.max_map_count\nwas not successfully configured on your system.\nYou will most likely experience crashes.") 1015 | fi 1016 | } 1017 | 1018 | ############################################################################ 1019 | ######## end mapcount functions ############################################ 1020 | ############################################################################ 1021 | 1022 | ############################################################################ 1023 | ######## begin filelimit functions ######################################### 1024 | ############################################################################ 1025 | 1026 | # MARK: filelimit_check() 1027 | # Check the open file descriptors limit 1028 | filelimit_check() { 1029 | filelimit="$(ulimit -Hn)" 1030 | 1031 | # Add to the results and actions arrays 1032 | if [ "$filelimit" -ge 524288 ]; then 1033 | # All good 1034 | preflight_pass+=("Hard open file descriptors limit is set to $filelimit.") 1035 | else 1036 | # The file limit should be changed 1037 | preflight_fail+=("Your hard open file descriptors limit is $filelimit\nand should be set to at least 524288\nto increase the maximum number of open files.") 1038 | # Add the function that will be called to change the configuration 1039 | preflight_action_funcs+=("filelimit_set") 1040 | 1041 | # Add info for manually changing the settings 1042 | if [ -f "/etc/systemd/system.conf" ]; then 1043 | # Using systemd 1044 | preflight_manual+=("To change your open file descriptors limit, add the following to\n'/etc/systemd/system.conf.d/99-starcitizen-filelimit.conf':\n\n[Manager]\nDefaultLimitNOFILE=524288") 1045 | elif [ -f "/etc/security/limits.conf" ]; then 1046 | # Using limits.conf 1047 | preflight_manual+=("To change your open file descriptors limit, add the following line to\n'/etc/security/limits.conf':\n * hard nofile 524288") 1048 | else 1049 | # Don't know what method to use 1050 | preflight_manual+=("This Helper is unable to detect the correct method of setting\nthe open file descriptors limit on your system.\n\nWe recommend manually configuring this limit to at least 524288.") 1051 | fi 1052 | fi 1053 | } 1054 | 1055 | # MARK: filelimit_set() 1056 | # Set the open file descriptors limit 1057 | filelimit_set() { 1058 | if [ -f "/etc/systemd/system.conf" ]; then 1059 | # Using systemd 1060 | # Append to the file 1061 | preflight_root_actions+=('mkdir -p /etc/systemd/system.conf.d && printf "[Manager]\n# Added by LUG-Helper:\nDefaultLimitNOFILE=524288\n" > /etc/systemd/system.conf.d/99-starcitizen-filelimit.conf && systemctl daemon-reexec') 1062 | preflight_fix_results+=("The open files limit configuration has been added to:\n/etc/systemd/system.conf.d/99-starcitizen-filelimit.conf") 1063 | elif [ -f "/etc/security/limits.conf" ]; then 1064 | # Using limits.conf 1065 | # Insert before the last line in the file 1066 | preflight_root_actions+=('sed -i "\$i#Added by LUG-Helper:" /etc/security/limits.conf; sed -i "\$i* hard nofile 524288" /etc/security/limits.conf') 1067 | preflight_fix_results+=("The open files limit configuration has been appended to:\n/etc/security/limits.conf") 1068 | else 1069 | # Don't know what method to use 1070 | preflight_fix_results+=("This Helper is unable to detect the correct method of setting\nthe open file descriptors limit on your system.\n\nWe recommend manually configuring this limit to at least 524288.") 1071 | fi 1072 | 1073 | # Verify that setting the limit was successful 1074 | preflight_followup+=("filelimit_confirm") 1075 | } 1076 | 1077 | # MARK: filelimit_confirm() 1078 | # Check if setting the open file descriptors limit was successful 1079 | filelimit_confirm() { 1080 | if [ "$(ulimit -Hn)" -lt 524288 ]; then 1081 | preflight_fix_results+=("WARNING: As far as this Helper can detect, the open files limit\nwas not successfully configured on your system.\nYou may experience crashes.") 1082 | fi 1083 | } 1084 | 1085 | ############################################################################ 1086 | ######## end filelimit functions ########################################### 1087 | ############################################################################ 1088 | 1089 | ############################################################################ 1090 | ######## end preflight check functions ##################################### 1091 | ############################################################################ 1092 | 1093 | ############################################################################ 1094 | ######## begin download functions ########################################## 1095 | ############################################################################ 1096 | 1097 | # MARK: download_manage() 1098 | # Manage downloads. Called by a dedicated download type manage function, ie runner_manage() 1099 | # 1100 | # This function expects the following variables to be set: 1101 | # 1102 | # - The string download_sources is a formatted array containing the URLs 1103 | # of items to download. It should be pointed to the appropriate 1104 | # array set at the top of the script using indirect expansion. 1105 | # See runner_sources at the top and runner_manage() for examples. 1106 | # - The string download_dir should contain the location where the 1107 | # downloaded item will be installed to. 1108 | # - The string "download_menu_heading" should contain the type of item 1109 | # being downloaded. It will appear in the menu heading. 1110 | # - The string "download_menu_description" should contain a description of 1111 | # the item being downloaded. It will appear in the menu subheading. 1112 | # - The integer "download_menu_height" specifies the height of the zenity menu. 1113 | # 1114 | # This function also expects one string argument containing the type of item to 1115 | # be downloaded. ie. runner or dxvk. 1116 | # 1117 | # See runner_manage() for a configuration example. 1118 | download_manage() { 1119 | # This function expects a string to be passed as an argument 1120 | if [ -z "$1" ]; then 1121 | debug_print exit "Script error: The download_manage function expects a string argument. Aborting." 1122 | fi 1123 | 1124 | # Sanity checks 1125 | if [ -z "$download_sources" ]; then 1126 | debug_print exit "Script error: The string 'download_sources' was not set before calling the download_manage function. Aborting." 1127 | elif [ -z "$download_dir" ]; then 1128 | debug_print exit "Script error: The string 'download_dir' was not set before calling the download_manage function. Aborting." 1129 | elif [ -z "$download_menu_heading" ]; then 1130 | debug_print exit "Script error: The string 'download_menu_heading' was not set before calling the download_manage function. Aborting." 1131 | elif [ -z "$download_menu_description" ]; then 1132 | debug_print exit "Script error: The string 'download_menu_description' was not set before calling the download_manage function. Aborting." 1133 | elif [ -z "$download_menu_height" ]; then 1134 | debug_print exit "Script error: The string 'download_menu_height' was not set before calling the download_manage function. Aborting." 1135 | fi 1136 | 1137 | # Get the type of item we're downloading from the function arguments 1138 | download_type="$1" 1139 | 1140 | # The download management menu will loop until the user cancels 1141 | looping_menu="true" 1142 | while [ "$looping_menu" = "true" ]; do 1143 | # Fetch wine prefix 1144 | if [ -f "$conf_dir/$conf_subdir/$wine_conf" ]; then 1145 | game_prefix="$(cat "$conf_dir/$conf_subdir/$wine_conf")" 1146 | else 1147 | game_prefix="Not configured" 1148 | fi 1149 | 1150 | # Configure the menu 1151 | menu_text_zenity="Manage Your $download_menu_heading\n\n$download_menu_description\n\nWine prefix: $game_prefix" 1152 | menu_text_terminal="Manage Your $download_menu_heading\n\n$download_menu_description\nWine prefix: $game_prefix" 1153 | menu_text_height="$download_menu_height" 1154 | menu_type="radiolist" 1155 | 1156 | # Configure the menu options 1157 | delete="Remove an installed $download_type" 1158 | back="Return to the main menu" 1159 | unset menu_options 1160 | unset menu_actions 1161 | 1162 | # Initialize success 1163 | unset post_download_required 1164 | 1165 | # Set variables for the current wine runner configured in the launch script 1166 | if [ "$download_type" = "runner" ]; then 1167 | get_current_runner 1168 | fi 1169 | 1170 | # Loop through the download_sources array and create a menu item 1171 | # for each one. Even numbered elements will contain the item name 1172 | for (( i=0; i<"${#download_sources[@]}"; i=i+2 )); do 1173 | # Set the options to be displayed in the menu 1174 | menu_options+=("Install a $download_type from ${download_sources[i]}") 1175 | # Set the corresponding functions to be called for each of the options 1176 | menu_actions+=("download_select_install $i") 1177 | done 1178 | 1179 | # Complete the menu by adding options to uninstall an item 1180 | # or go back to the previous menu 1181 | menu_options+=("$delete" "$back") 1182 | menu_actions+=("download_select_delete" "menu_loop_done") 1183 | 1184 | # Calculate the total height the menu should be 1185 | # menu_option_height = pixels per menu option 1186 | # #menu_options[@] = number of menu options 1187 | # menu_text_height = height of the title/description text 1188 | # menu_text_height_zenity4 = added title/description height for libadwaita bigness 1189 | menu_height="$(($menu_option_height * ${#menu_options[@]} + $menu_text_height + $menu_text_height_zenity4))" 1190 | 1191 | # Set the label for the cancel button 1192 | cancel_label="Go Back" 1193 | 1194 | # Call the menu function. It will use the options as configured above 1195 | menu 1196 | 1197 | # Perform post-download actions and display messages or instructions 1198 | if [ -n "$post_download_required" ] && [ "$post_download_type" != "none" ]; then 1199 | post_download 1200 | fi 1201 | done 1202 | } 1203 | 1204 | # MARK: runner_manage() 1205 | # Configure the download_manage function for wine runners 1206 | runner_manage() { 1207 | # We'll want to instruct the user on how to use the downloaded runner 1208 | # Valid options are "none" or "configure-wine" 1209 | post_download_type="configure-wine" 1210 | 1211 | # Use indirect expansion to point download_sources 1212 | # to the runner_sources array set at the top of the script 1213 | declare -n download_sources=runner_sources 1214 | 1215 | # Get directories so we know where the wine prefix is 1216 | getdirs 1217 | 1218 | # Set variables for the latest default runner 1219 | set_latest_default_runner 1220 | # Sanity check 1221 | if [ "$?" -eq 1 ]; then 1222 | message error "Could not fetch the latest default wine runner. The Github API may be down or rate limited." 1223 | return 1 1224 | fi 1225 | 1226 | # Set the download directory for wine runners 1227 | download_dir="$wine_prefix/runners" 1228 | 1229 | # Configure the text displayed in the menus 1230 | download_menu_heading="Wine Runners" 1231 | download_menu_description="The runners listed below are wine builds created for Star Citizen" 1232 | download_menu_height="320" 1233 | 1234 | # Set the string sed will match against when editing the launch script 1235 | # This will be used to detect the appropriate variable and replace its value 1236 | # with the path to the downloaded item 1237 | post_download_sed_string="export wine_path=" 1238 | # Set the value of the above variable that will be restored after a runner is deleted 1239 | # In this case, we want to revert to the configured default wine runner 1240 | post_delete_restore_value="${download_dir}/${default_runner}/bin" 1241 | 1242 | # Call the download_manage function with the above configuration 1243 | # The argument passed to the function is used for special handling 1244 | # and displayed in the menus and dialogs. 1245 | download_manage "runner" 1246 | } 1247 | 1248 | # MARK: download_select_install() 1249 | # List available items for download. Called by download_manage() 1250 | # 1251 | # The following variables are expected to be set before calling this function: 1252 | # - download_sources (array) 1253 | # - download_type (string) 1254 | # - download_dir (string) 1255 | download_select_install() { 1256 | # This function expects an element number for the sources array to be passed in as an argument 1257 | if [ -z "$1" ]; then 1258 | debug_print exit "Script error: The download_select_install function expects a numerical argument. Aborting." 1259 | fi 1260 | 1261 | # Sanity checks 1262 | if [ "${#download_sources[@]}" -eq 0 ]; then 1263 | debug_print exit "Script error: The array 'download_sources' was not set before calling the download_select_install function. Aborting." 1264 | elif [ -z "$download_type" ]; then 1265 | debug_print exit "Script error: The string 'download_type' was not set before calling the download_select_install function. Aborting." 1266 | elif [ -z "$download_dir" ]; then 1267 | debug_print exit "Script error: The string 'download_dir' was not set before calling the download_select_install function. Aborting." 1268 | fi 1269 | 1270 | # Store info from the selected contributor 1271 | contributor_name="${download_sources[$1]}" 1272 | contributor_url="${download_sources[$1+1]}" 1273 | 1274 | # For runners, check GlibC version against runner requirements 1275 | if [ "$download_type" = "runner" ] && { [ "$contributor_name" = "TKG" ] || [ "$contributor_name" = "RawFox" ] || [ "$contributor_name" = "Mactan" ]; }; then 1276 | glibc_fail="false" 1277 | required_glibc="2.38" 1278 | 1279 | # Check the system glibc 1280 | if [ -x "$(command -v ldd)" ]; then 1281 | system_glibc="$(ldd --version | awk '/ldd/{print $NF}')" 1282 | else 1283 | system_glibc="0 (Not installed)" 1284 | fi 1285 | 1286 | # Sort the versions and check if the installed glibc is smaller 1287 | if [ "$required_glibc" != "$system_glibc" ] && 1288 | [ "$system_glibc" = "$(printf "%s\n%s" "$system_glibc" "$required_glibc" | sort -V | head -n1)" ]; then 1289 | glibc_fail="true" 1290 | fi 1291 | 1292 | # Display a warning message 1293 | if [ "$glibc_fail" = "true" ]; then 1294 | message warning "Your glibc version is incompatible with the selected runner\n\nSystem glibc: ${system_glibc}\nMinimum required glibc: $required_glibc" 1295 | return 1 1296 | fi 1297 | fi 1298 | 1299 | # Check the provided contributor url to make sure we know how to handle it 1300 | # To add new sources, add them here and handle in the if statement 1301 | # just below and in the download_install function 1302 | case "$contributor_url" in 1303 | https://api.github.com/*) 1304 | download_url_type="github" 1305 | ;; 1306 | https://gitlab.com/api/v4/projects/*) 1307 | download_url_type="gitlab" 1308 | ;; 1309 | *) 1310 | debug_print exit "Script error: Unknown api/url format in ${download_type}_sources array. Aborting." 1311 | ;; 1312 | esac 1313 | 1314 | # Set the search keys we'll use to parse the api for the download url 1315 | # To add new sources, handle them here, in the if statement 1316 | # just above, and in the download_install function 1317 | if [ "$download_url_type" = "github" ]; then 1318 | # Which json key are we looking for? 1319 | search_key="browser_download_url" 1320 | # Optional: Only match urls containing a keyword 1321 | match_url_keyword="" 1322 | # Optional: Filter out game-specific builds by keyword 1323 | # Format for grep extended regex (ie: "word1|word2|word3") 1324 | if [ "$download_type" = "runner" ] && [ "$contributor_name" = "GloriousEggroll" ]; then 1325 | filter_keywords="lol|diablo" 1326 | elif [ "$download_type" = "runner" ] && [ "$contributor_name" = "Kron4ek" ]; then 1327 | filter_keywords="x86|wow64" 1328 | else 1329 | filter_keywords="oh hi there. this is just placeholder text. how are you today?" 1330 | fi 1331 | # Add a query string to the url 1332 | query_string="?per_page=$max_download_items" 1333 | elif [ "$download_url_type" = "gitlab" ]; then 1334 | # Which json key are we looking for? 1335 | search_key="direct_asset_url" 1336 | # Only match urls containing a keyword 1337 | match_url_keyword="releases" 1338 | # Optional: Filter out game-specific builds by keyword 1339 | # Format for grep extended regex (ie: "word1|word2|word3") 1340 | filter_keywords="oh hi there. this is just placeholder text. how are you today?" 1341 | # Add a query string to the url 1342 | query_string="?per_page=$max_download_items" 1343 | else 1344 | debug_print exit "Script error: Unknown api/url format in ${download_type}_sources array. Aborting." 1345 | fi 1346 | 1347 | # Fetch a list of versions from the selected contributor 1348 | unset download_versions 1349 | while IFS='' read -r line; do 1350 | download_versions+=("$line") 1351 | done < <(curl -s "$contributor_url$query_string" | grep -Eo "\"$search_key\": ?\"[^\"]+\"" | grep "$match_url_keyword" | cut -d '"' -f4 | cut -d '?' -f1 | xargs basename -a | grep -viE "$filter_keywords") 1352 | # Note: match from search_key until " or EOL (Handles embedded commas and escaped quotes). Cut out quotes and gitlab's extraneous query strings. 1353 | 1354 | # Sanity check 1355 | if [ "${#download_versions[@]}" -eq 0 ]; then 1356 | message warning "No $download_type versions were found. The $download_url_type API may be down or rate limited." 1357 | return 1 1358 | fi 1359 | 1360 | # Configure the menu 1361 | menu_text_zenity="Select the $download_type you want to install:" 1362 | menu_text_terminal="Select the $download_type you want to install:" 1363 | menu_text_height="320" 1364 | menu_type="radiolist" 1365 | goback="Return to the $download_type management menu" 1366 | unset menu_options 1367 | unset menu_actions 1368 | 1369 | # Iterate through the versions, check if they are installed, 1370 | # and add them to the menu options 1371 | # To add new file extensions, handle them here and in 1372 | # the download_install function 1373 | for (( i=0,num_download_items=0; i<"${#download_versions[@]}" && "$num_download_items"<"$max_download_items"; i++ )); do 1374 | 1375 | # Get the file name minus the extension 1376 | case "${download_versions[i]}" in 1377 | *.sha*sum | *.ini | proton* | *.txt) 1378 | # Ignore hashes, configs, and proton downloads 1379 | continue 1380 | ;; 1381 | *.tar.gz) 1382 | download_basename="$(basename "${download_versions[i]}" .tar.gz)" 1383 | ;; 1384 | *.tgz) 1385 | download_basename="$(basename "${download_versions[i]}" .tgz)" 1386 | ;; 1387 | *.tar.xz) 1388 | download_basename="$(basename "${download_versions[i]}" .tar.xz)" 1389 | ;; 1390 | *.tar.zst) 1391 | download_basename="$(basename "${download_versions[i]}" .tar.zst)" 1392 | ;; 1393 | *) 1394 | # Print a warning and move on to the next item 1395 | debug_print continue "Warning: Unknown archive filetype in download_select_install() function. Offending String: ${download_versions[i]}" 1396 | continue 1397 | ;; 1398 | esac 1399 | 1400 | # Build the menu item 1401 | unset menu_option_text 1402 | if [ -d "${download_dir}/${download_basename}" ] && [ "$download_type" = "runner" ] && [ "$current_runner_basename" = "$download_basename" ]; then 1403 | menu_option_text="$download_basename [in-use]" 1404 | elif [ -d "${download_dir}/${download_basename}" ]; then 1405 | menu_option_text="$download_basename [installed]" 1406 | else 1407 | # The file is not installed 1408 | menu_option_text="$download_basename" 1409 | fi 1410 | 1411 | # Add the file names to the menu 1412 | menu_options+=("$menu_option_text") 1413 | menu_actions+=("download_install $i") 1414 | 1415 | # Increment the added items counter 1416 | num_download_items="$(($num_download_items+1))" 1417 | done 1418 | 1419 | # Complete the menu by adding the option to go back to the previous menu 1420 | menu_options+=("$goback") 1421 | menu_actions+=(":") # no-op 1422 | 1423 | # Calculate the total height the menu should be 1424 | # menu_option_height = pixels per menu option 1425 | # #menu_options[@] = number of menu options 1426 | # menu_text_height = height of the title/description text 1427 | # menu_text_height_zenity4 = added title/description height for libadwaita bigness 1428 | menu_height="$(($menu_option_height * ${#menu_options[@]} + $menu_text_height + $menu_text_height_zenity4))" 1429 | # Cap menu height 1430 | if [ "$menu_height" -gt "$menu_height_max" ]; then 1431 | menu_height="$menu_height_max" 1432 | fi 1433 | 1434 | # Set the label for the cancel button 1435 | cancel_label="Go Back" 1436 | 1437 | # Call the menu function. It will use the options as configured above 1438 | menu 1439 | } 1440 | 1441 | # MARK: download_install() 1442 | # Download and install the selected item. Called by download_select_install() 1443 | # 1444 | # Expects one numerical argument, an index number for the array "download_versions" 1445 | # 1446 | # The following variables are expected to be set before calling this function: 1447 | # - download_versions (array) 1448 | # - contributor_url (string) 1449 | # - download_url_type (string) 1450 | # - download_type (string) 1451 | # - download_dir (string) 1452 | download_install() { 1453 | # This function expects an index number for the array 1454 | # download_versions to be passed in as an argument 1455 | if [ -z "$1" ]; then 1456 | debug_print exit "Script error: The download_install function expects a numerical argument. Aborting." 1457 | fi 1458 | 1459 | # Sanity checks 1460 | if [ "${#download_versions[@]}" -eq 0 ]; then 1461 | debug_print exit "Script error: The array 'download_versions' was not set before calling the download_install function. Aborting." 1462 | elif [ -z "$contributor_url" ]; then 1463 | debug_print exit "Script error: The string 'contributor_url' was not set before calling the download_install function. Aborting." 1464 | elif [ -z "$download_url_type" ]; then 1465 | debug_print exit "Script error: The string 'download_url_type' was not set before calling the download_install function. Aborting." 1466 | elif [ -z "$download_type" ]; then 1467 | debug_print exit "Script error: The string 'download_type' was not set before calling the download_install function. Aborting." 1468 | elif [ -z "$download_dir" ]; then 1469 | debug_print exit "Script error: The string 'download_dir' was not set before calling the download_install function. Aborting." 1470 | fi 1471 | 1472 | # Get the filename including file extension 1473 | download_filename="${download_versions[$1]}" 1474 | 1475 | # Get the selected item name minus the file extension 1476 | # To add new file extensions, handle them here and in 1477 | # the download_select_install function 1478 | case "$download_filename" in 1479 | *.tar.gz) 1480 | if [ ! -x "$(command -v gzip)" ]; then 1481 | message error "gzip does not appear to be installed. Unable to extract the requested archive." 1482 | return 1 1483 | fi 1484 | download_basename="$(basename "$download_filename" .tar.gz)" 1485 | ;; 1486 | *.tgz) 1487 | if [ ! -x "$(command -v gzip)" ]; then 1488 | message error "gzip does not appear to be installed. Unable to extract the requested archive." 1489 | return 1 1490 | fi 1491 | download_basename="$(basename "$download_filename" .tgz)" 1492 | ;; 1493 | *.tar.xz) 1494 | if [ ! -x "$(command -v xz)" ]; then 1495 | message error "xz does not appear to be installed. Unable to extract the requested archive." 1496 | return 1 1497 | fi 1498 | download_basename="$(basename "$download_filename" .tar.xz)" 1499 | ;; 1500 | *.tar.zst) 1501 | if [ ! -x "$(command -v zstd)" ]; then 1502 | message error "zstd does not appear to be installed. Unable to extract the requested archive." 1503 | return 1 1504 | fi 1505 | download_basename="$(basename "$download_filename" .tar.zst)" 1506 | ;; 1507 | *) 1508 | debug_print exit "Script error: Unknown archive filetype in download_install function. Aborting." 1509 | ;; 1510 | esac 1511 | 1512 | # Check if the item is already installed 1513 | # Trigger post-download actions to reconfigure the launch script then skip the rest of the redownload process 1514 | if [ -d "${download_dir}/${download_basename}" ]; then 1515 | debug_print continue "The selected $download_type is already installed. Skipping download." 1516 | 1517 | # Store the final name of the downloaded item 1518 | downloaded_item_name="$download_basename" 1519 | # Mark success for triggering post-download actions 1520 | post_download_required="installed" 1521 | 1522 | return 0 1523 | fi 1524 | 1525 | # Set the search keys we'll use to parse the api for the download url 1526 | # To add new sources, handle them here and in the 1527 | # download_select_install function 1528 | if [ "$download_url_type" = "github" ]; then 1529 | # Which json key are we looking for? 1530 | search_key="browser_download_url" 1531 | # Add a query string to the url 1532 | query_string="?per_page=$max_download_items" 1533 | elif [ "$download_url_type" = "gitlab" ]; then 1534 | # Which json key are we looking for? 1535 | search_key="direct_asset_url" 1536 | # Add a query string to the url 1537 | query_string="?per_page=$max_download_items" 1538 | else 1539 | debug_print exit "Script error: Unknown api/url format in ${download_type}_sources array. Aborting." 1540 | fi 1541 | 1542 | # Get the selected download url 1543 | download_url="$(curl -s "$contributor_url$query_string" | grep -Eo "\"$search_key\": ?\"[^\"]+\"" | grep "$download_filename" | cut -d '"' -f4 | cut -d '?' -f1 | sed 's|/-/blob/|/-/raw/|')" 1544 | 1545 | # Sanity check 1546 | if [ -z "$download_url" ]; then 1547 | message warning "Could not find the requested ${download_type}. The $download_url_type API may be down or rate limited." 1548 | return 1 1549 | fi 1550 | 1551 | # Download the item to the tmp directory 1552 | download_file "$download_url" "$download_filename" "$download_type" 1553 | 1554 | # Sanity check 1555 | if [ ! -f "$tmp_dir/$download_filename" ]; then 1556 | # Something went wrong with the download and the file doesn't exist 1557 | message error "Something went wrong and the requested $download_type file could not be downloaded!" 1558 | debug_print continue "Download failed! File not found: $tmp_dir/$download_filename" 1559 | return 1 1560 | fi 1561 | 1562 | # Show a zenity pulsating progress bar 1563 | progress_bar start "Installing ${download_type}. Please wait..." 1564 | 1565 | # Extract the archive to the tmp directory 1566 | debug_print continue "Extracting $download_type into $tmp_dir/$download_basename..." 1567 | mkdir "$tmp_dir/$download_basename" && tar -xf "$tmp_dir/$download_filename" -C "$tmp_dir/$download_basename" 1568 | 1569 | # Check the contents of the extracted archive to determine the 1570 | # directory structure we must create upon installation 1571 | num_dirs=0 1572 | num_files=0 1573 | for extracted_item in "$tmp_dir/$download_basename"/*; do 1574 | if [ -d "$extracted_item" ]; then 1575 | num_dirs="$(($num_dirs+1))" 1576 | extracted_dir="$(basename "$extracted_item")" 1577 | elif [ -f "$extracted_item" ]; then 1578 | num_files="$(($num_files+1))" 1579 | fi 1580 | done 1581 | 1582 | # Create the correct directory structure and install the item 1583 | if [ "$num_dirs" -eq 0 ] && [ "$num_files" -eq 0 ]; then 1584 | # Sanity check 1585 | message warning "The downloaded archive is empty. There is nothing to do." 1586 | elif [ "$num_dirs" -eq 1 ] && [ "$num_files" -eq 0 ]; then 1587 | # If the archive contains only one directory, install that directory 1588 | # We rename it to the name of the archive in case it is different 1589 | # so we can easily detect installed items in download_select_install() 1590 | debug_print continue "Installing $download_type into ${download_dir}/${download_basename}..." 1591 | 1592 | # Copy the directory to the destination 1593 | mkdir -p "$download_dir" && cp -r "${tmp_dir}/${download_basename}/${extracted_dir}" "${download_dir}/${download_basename}" 1594 | 1595 | # Store the final name of the downloaded item 1596 | downloaded_item_name="$download_basename" 1597 | # Mark success for triggering post-download actions 1598 | post_download_required="installed" 1599 | elif [ "$num_dirs" -gt 1 ] || [ "$num_files" -gt 0 ]; then 1600 | # If the archive contains more than one directory or 1601 | # one or more files, we must create a subdirectory 1602 | debug_print continue "Installing $download_type into ${download_dir}/${download_basename}..." 1603 | 1604 | # Copy the directory to the destination 1605 | mkdir -p "${download_dir}/${download_basename}" && cp -r "$tmp_dir"/"$download_basename"/* "$download_dir"/"$download_basename" 1606 | 1607 | # Store the final name of the downloaded item 1608 | downloaded_item_name="$download_basename" 1609 | # Mark success for triggering post-download actions 1610 | post_download_required="installed" 1611 | else 1612 | # Some unexpected combination of directories and files 1613 | debug_print exit "Script error: Unexpected archive contents in download_install function. Aborting" 1614 | fi 1615 | 1616 | progress_bar stop # Stop the zenity progress window 1617 | 1618 | # Cleanup tmp download 1619 | debug_print continue "Cleaning up ${tmp_dir}/${download_filename}..." 1620 | rm --interactive=never "${tmp_dir:?}/${download_filename}" 1621 | rm -r --interactive=never "${tmp_dir:?}/${download_basename}" 1622 | 1623 | return 0 1624 | } 1625 | 1626 | # MARK: download_select_delete() 1627 | # List installed items for deletion. Called by download_manage() 1628 | # 1629 | # The following variables are expected to be set before calling this function: 1630 | # - download_type (string) 1631 | # - download_dir (string) 1632 | download_select_delete() { 1633 | # Sanity checks 1634 | if [ -z "$download_type" ]; then 1635 | debug_print exit "Script error: The string 'download_type' was not set before calling the download_select_delete function. Aborting." 1636 | elif [ -z "$download_dir" ]; then 1637 | debug_print exit "Script error: The string 'download_dir' was not set before calling the download_select_delete function. Aborting." 1638 | fi 1639 | 1640 | # Configure the menu 1641 | menu_text_zenity="Select the $download_type(s) you want to remove:" 1642 | menu_text_terminal="Select the $download_type you want to remove:" 1643 | menu_text_height="320" 1644 | menu_type="checklist" 1645 | goback="Return to the $download_type management menu" 1646 | unset installed_items 1647 | unset installed_item_names 1648 | unset menu_options 1649 | unset menu_actions 1650 | 1651 | # Find all installed items in the download destination 1652 | if [ -d "$download_dir" ]; then 1653 | for item in "$download_dir"/*; do 1654 | if [ -d "$item" ]; then 1655 | installed_item_names+=("$(basename "$item")") 1656 | installed_items+=("$item") 1657 | fi 1658 | done 1659 | fi 1660 | 1661 | # Create menu options for the installed items 1662 | for (( i=0; i<"${#installed_items[@]}"; i++ )); do 1663 | # Build the menu item 1664 | unset menu_option_text 1665 | # Special handling for runners currently in use 1666 | if [ "$download_type" = "runner" ] && [ "$current_runner_basename" = "${installed_item_names[i]}" ]; then 1667 | menu_option_text="${installed_item_names[i]} [in-use]" 1668 | else 1669 | # Everything else 1670 | menu_option_text="${installed_item_names[i]}" 1671 | fi 1672 | 1673 | 1674 | menu_options+=("$menu_option_text") 1675 | menu_actions+=("download_delete $i") 1676 | done 1677 | 1678 | # Print a message and return if no installed items were found 1679 | if [ "${#menu_options[@]}" -eq 0 ]; then 1680 | message info "No installed ${download_type}s found." 1681 | return 0 1682 | fi 1683 | 1684 | # Complete the menu by adding the option to go back to the previous menu 1685 | menu_options+=("$goback") 1686 | menu_actions+=(":") # no-op 1687 | 1688 | # Calculate the total height the menu should be 1689 | # menu_option_height = pixels per menu option 1690 | # #menu_options[@] = number of menu options 1691 | # menu_text_height = height of the title/description text 1692 | # menu_text_height_zenity4 = added title/description height for libadwaita bigness 1693 | menu_height="$(($menu_option_height * ${#menu_options[@]} + $menu_text_height + $menu_text_height_zenity4))" 1694 | # Cap menu height 1695 | if [ "$menu_height" -gt "$menu_height_max" ]; then 1696 | menu_height="$menu_height_max" 1697 | fi 1698 | 1699 | # Set the label for the cancel button 1700 | cancel_label="Go Back" 1701 | 1702 | # Call the menu function. It will use the options as configured above 1703 | menu 1704 | } 1705 | 1706 | # MARK: download_delete() 1707 | # Uninstall the selected item(s). Called by download_select_install() 1708 | # Accepts array index numbers as an argument 1709 | # 1710 | # The following variables are expected to be set before calling this function: 1711 | # - download_type (string) 1712 | # - installed_items (array) 1713 | # - installed_item_names (array) 1714 | download_delete() { 1715 | # This function expects at least one index number for the array installed_items to be passed in as an argument 1716 | if [ -z "$1" ]; then 1717 | debug_print exit "Script error: The download_delete function expects an argument. Aborting." 1718 | fi 1719 | 1720 | # Sanity checks 1721 | if [ -z "$download_type" ]; then 1722 | debug_print exit "Script error: The string 'download_type' was not set before calling the download_delete function. Aborting." 1723 | elif [ "${#installed_items[@]}" -eq 0 ]; then 1724 | debug_print exit "Script error: The array 'installed_items' was not set before calling the download_delete function. Aborting." 1725 | elif [ "${#installed_item_names[@]}" -eq 0 ]; then 1726 | debug_print exit "Script error: The array 'installed_item_names' was not set before calling the download_delete function. Aborting." 1727 | fi 1728 | 1729 | # Capture arguments and format a list of items 1730 | item_to_delete=("$@") 1731 | unset list_to_delete 1732 | unset deleted_item_names 1733 | for (( i=0; i<"${#item_to_delete[@]}"; i++ )); do 1734 | list_to_delete+="\n${installed_items[${item_to_delete[i]}]}" 1735 | done 1736 | 1737 | if message question "Are you sure you want to delete the following ${download_type}(s)?\n$list_to_delete"; then 1738 | 1739 | unset post_delete_required 1740 | 1741 | # Loop through the arguments 1742 | for (( i=0; i<"${#item_to_delete[@]}"; i++ )); do 1743 | rm -r --interactive=never "${installed_items[${item_to_delete[i]}]}" 1744 | debug_print continue "Deleted ${installed_items[${item_to_delete[i]}]}" 1745 | 1746 | # If we just deleted the currently used runner, we need to trigger post-delete to update the launch script 1747 | if [ "$download_type" = "runner" ] && [ "${installed_items[${item_to_delete[i]}]}" = "$current_runner_path" ]; then 1748 | post_delete_required="true" 1749 | fi 1750 | 1751 | # Store the names of deleted items for post_download() processing 1752 | deleted_item_names+=("${installed_item_names[${item_to_delete[i]}]}") 1753 | done 1754 | # Mark success for triggering post-deletion actions 1755 | if [ "$post_delete_required" = "true" ]; then 1756 | post_download_required="deleted" 1757 | fi 1758 | fi 1759 | } 1760 | 1761 | # MARK: post_download() 1762 | # Perform post-download actions or display a message/instructions 1763 | # 1764 | # The following variables are expected to be set before calling this function: 1765 | # - post_download_type (string. "none", "configure-wine") 1766 | # - post_download_sed_string (string. For type configure-wine) 1767 | # - post_delete_restore_value (string. For type configure-wine) 1768 | # - post_download_required (string. Set automatically in install/delete functions) 1769 | # - downloaded_item_name (string. For installs only. Set automatically in download_install function) 1770 | # - deleted_item_names (array. For deletions only. Set automatically in download_delete function) 1771 | # - download_dir (string) 1772 | # 1773 | # Details for post_download_sed_string: 1774 | # This is the string sed will match against when editing configs or files 1775 | # For the wine install, it replaces values in the default launch script 1776 | # with the appropriate paths and values after installation. 1777 | post_download() { 1778 | # Sanity checks 1779 | if [ -z "$post_download_type" ]; then 1780 | debug_print exit "Script error: The string 'post_download_type' was not set before calling the post_download function. Aborting." 1781 | elif [ -z "$post_download_sed_string" ] && [ "$post_download_type" = "configure-wine" ]; then 1782 | debug_print exit "Script error: The string 'post_download_sed_string' was not set before calling the post_download function. Aborting." 1783 | elif [ -z "$post_delete_restore_value" ] && [ "$post_download_type" = "configure-wine" ]; then 1784 | debug_print exit "Script error: The string 'post_delete_restore_value' was not set before calling the post_download function. Aborting." 1785 | elif [ -z "$download_dir" ]; then 1786 | debug_print exit "Script error: The string 'download_dir' was not set before calling the post_download function. Aborting." 1787 | fi 1788 | 1789 | # Return if we don't have anything to do 1790 | if [ "$post_download_type" = "none" ]; then 1791 | return 0 1792 | fi 1793 | 1794 | # Handle the appropriate post-download actions 1795 | if [ "$post_download_type" = "configure-wine" ]; then 1796 | # Make sure we can locate the launch script 1797 | if [ ! -f "$wine_prefix/$wine_launch_script_name" ]; then 1798 | message warning "Unable to find launch script!\n$wine_prefix/$wine_launch_script_name\n\nYou will need to edit your launch script's \"${post_download_sed_string}\" variable manually." 1799 | return 1 1800 | fi 1801 | 1802 | # Make sure the launch script has the appropriate string to be replaced 1803 | if ! grep -q "^${post_download_sed_string}" "$wine_prefix/$wine_launch_script_name"; then 1804 | if message question "Unable to find a required variable in your launch script! It may be out of date.\n\nWould you like to try updating your launch script?"; then 1805 | # Try updating the launch script 1806 | update_launch_script 1807 | 1808 | # Check if the update was successful and we now have the required string 1809 | if ! grep -q "^${post_download_sed_string}" "$wine_prefix/$wine_launch_script_name"; then 1810 | message warning "Unable to find a required variable in your launch script! The update may have failed.\n\nYou will need to edit your launch script's \"${post_download_sed_string}\" variable manually." 1811 | return 1 1812 | fi 1813 | else 1814 | message warning "You will need to edit your launch script's \"${post_download_sed_string}\" variable manually." 1815 | return 1 1816 | fi 1817 | fi 1818 | 1819 | # We handle installs and deletions differently 1820 | if [ "$post_download_required" = "installed" ] && [ "$download_type" = "runner" ]; then 1821 | # We are installing a wine version and updating the launch script to use it 1822 | 1823 | # Replace the specified variable in the launch script 1824 | debug_print continue "Updating \"${post_download_sed_string}\" variable in launch script ${wine_prefix}/${wine_launch_script_name}..." 1825 | sed -i "s|^${post_download_sed_string}.*|${post_download_sed_string}\"${wine_prefix}/runners/${downloaded_item_name}/bin\"|" "$wine_prefix/$wine_launch_script_name" 1826 | 1827 | # Display a confirmation message 1828 | message info "Wine Runner installation complete!" 1829 | elif [ "$post_download_required" = "deleted" ] && [ "$download_type" = "runner" ]; then 1830 | # We deleted a custom wine version and need to revert the launch script to use the default wine runner 1831 | 1832 | # Check if the default wine runner is installed 1833 | if [ ! -d "${download_dir}/${default_runner}" ]; then 1834 | message info "The Wine runner currently used by your launch script has been deleted!\n\nThe default Wine runner will now be downloaded and installed." 1835 | # Install the default wine runner into the prefix 1836 | download_wine 1837 | # Make sure the wine download worked 1838 | if [ "$?" -eq 1 ]; then 1839 | message warning "Something went wrong while installing ${default_runner}!\n\nYou will need to edit your launch script's \"${post_download_sed_string}\" variable manually." 1840 | return 1 1841 | fi 1842 | else 1843 | message info "The Wine runner currently used by your launch script has been deleted!\n\nYour launch script will be updated to use the default Wine runner." 1844 | fi 1845 | 1846 | # Replace the specified variable in the launch script 1847 | debug_print continue "Updating \"${post_download_sed_string}\" variable in launch script ${wine_prefix}/${wine_launch_script_name}..." 1848 | sed -i "s#^${post_download_sed_string}.*#${post_download_sed_string}\"${post_delete_restore_value}\"#" "$wine_prefix/$wine_launch_script_name" 1849 | 1850 | # Display a confirmation message 1851 | message info "Your launch script has been updated!" 1852 | else 1853 | debug_print exit "Script error: Unknown post_download_required value in post_download function. Aborting." 1854 | fi 1855 | else 1856 | debug_print exit "Script error: Unknown post_download_type value in post_download function. Aborting." 1857 | fi 1858 | } 1859 | 1860 | # MARK: download_file() 1861 | # Download a file to the tmp directory 1862 | # Expects three arguments: The download URL, file name, and download type 1863 | download_file() { 1864 | # This function expects three string arguments 1865 | if [ "$#" -lt 3 ]; then 1866 | printf "\nScript error: The download_file function expects three arguments. Aborting.\n" 1867 | read -n 1 -s -p "Press any key..." 1868 | exit 0 1869 | fi 1870 | 1871 | # Capture the arguments and encode spaces in urls 1872 | download_url="${1// /%20}" 1873 | download_filename="$2" 1874 | download_type="$3" 1875 | 1876 | # Download the item to the tmp directory 1877 | debug_print continue "Downloading $download_url into $tmp_dir/$download_filename..." 1878 | if [ "$use_zenity" -eq 1 ]; then 1879 | # Format the curl progress bar for zenity 1880 | mkfifo "$tmp_dir/lugpipe" 1881 | cd "$tmp_dir" && curl -#L "$download_url" -o "$download_filename" > "$tmp_dir/lugpipe" 2>&1 & curlpid="$!" 1882 | stdbuf -oL tr '\r' '\n' < "$tmp_dir/lugpipe" | \ 1883 | grep --line-buffered -ve "100" | grep --line-buffered -o "[0-9]*\.[0-9]" | \ 1884 | ( 1885 | trap 'kill "$curlpid"; trap - ERR' ERR 1886 | zenity --progress --auto-close --title="Star Citizen LUG Helper" --text="Downloading ${download_type}. This might take a moment.\n" 2>/dev/null 1887 | ) 1888 | 1889 | if [ "$?" -eq 1 ]; then 1890 | # User clicked cancel 1891 | debug_print continue "Download aborted. Removing $tmp_dir/$download_filename..." 1892 | rm --interactive=never "${tmp_dir:?}/$download_filename" 1893 | rm --interactive=never "${tmp_dir:?}/lugpipe" 1894 | return 1 1895 | fi 1896 | rm --interactive=never "${tmp_dir:?}/lugpipe" 1897 | else 1898 | # Standard curl progress bar 1899 | (cd "$tmp_dir" && curl -#L "$download_url" -o "$download_filename") 1900 | fi 1901 | } 1902 | 1903 | ############################################################################ 1904 | ######## end download functions ############################################ 1905 | ############################################################################ 1906 | 1907 | ############################################################################ 1908 | ######## begin maintenance functions ####################################### 1909 | ############################################################################ 1910 | 1911 | # MARK: maintenance_menu() 1912 | # Show maintenance/troubleshooting options 1913 | maintenance_menu() { 1914 | # Loop the menu until the user selects quit 1915 | looping_menu="true" 1916 | while [ "$looping_menu" = "true" ]; do 1917 | # Fetch wine prefix 1918 | if [ -f "$conf_dir/$conf_subdir/$wine_conf" ]; then 1919 | game_prefix="$(cat "$conf_dir/$conf_subdir/$wine_conf")" 1920 | else 1921 | game_prefix="Not configured" 1922 | fi 1923 | 1924 | # Configure the menu 1925 | menu_text_zenity="Game Maintenance and Troubleshooting\n\nLUG Wiki: $lug_wiki\n\nWine prefix: $game_prefix" 1926 | menu_text_terminal="Game Maintenance and Troubleshooting\n\nLUG Wiki: $lug_wiki\n\nWine prefix: $game_prefix" 1927 | menu_text_height="320" 1928 | menu_type="radiolist" 1929 | 1930 | # Configure the menu options 1931 | prefix_msg="Target a different Star Citizen installation" 1932 | launcher_msg="Update/Repair launch script" 1933 | launchscript_msg="Edit launch script" 1934 | config_msg="Open Wine prefix configuration" 1935 | controllers_msg="Open Wine controller configuration" 1936 | powershell_msg="Install PowerShell into Wine prefix" 1937 | rsi_launcher_msg="Update/Re-install RSI Launcher" 1938 | dirs_msg="Display Helper and Star Citizen directories" 1939 | reset_msg="Reset Helper configs" 1940 | quit_msg="Return to the main menu" 1941 | 1942 | # Set the options to be displayed in the menu 1943 | menu_options=("$prefix_msg" "$launcher_msg" "$launchscript_msg" "$config_msg" "$controllers_msg" "$powershell_msg" "$rsi_launcher_msg" "$dirs_msg" "$reset_msg" "$quit_msg") 1944 | # Set the corresponding functions to be called for each of the options 1945 | menu_actions=("switch_prefix" "update_launch_script" "edit_launch_script" "call_launch_script config" "call_launch_script controllers" "install_powershell" "reinstall_rsi_launcher" "display_dirs" "reset_helper" "menu_loop_done") 1946 | 1947 | # Calculate the total height the menu should be 1948 | # menu_option_height = pixels per menu option 1949 | # #menu_options[@] = number of menu options 1950 | # menu_text_height = height of the title/description text 1951 | # menu_text_height_zenity4 = added title/description height for libadwaita bigness 1952 | menu_height="$(($menu_option_height * ${#menu_options[@]} + $menu_text_height + $menu_text_height_zenity4))" 1953 | 1954 | # Set the label for the cancel button 1955 | cancel_label="Go Back" 1956 | 1957 | # Call the menu function. It will use the options as configured above 1958 | menu 1959 | done 1960 | } 1961 | 1962 | # MARK: switch_prefix() 1963 | # Target the Helper at a different Star Citizen prefix/installation 1964 | switch_prefix() { 1965 | # Check if the config file exists 1966 | if [ -f "$conf_dir/$conf_subdir/$wine_conf" ] && [ -f "$conf_dir/$conf_subdir/$game_conf" ]; then 1967 | getdirs 1968 | # Above will return code 3 if the user had to select new directories. This can happen if the stored directories are now invalid. 1969 | # We check this so we don't prompt the user to set directories twice here. 1970 | if [ "$?" -ne 3 ] && message question "The Helper is currently targeting this Star Citizen install\nWould you like to change it?\n\n$wine_prefix"; then 1971 | reset_helper "switchprefix" 1972 | # Prompt the user for a new set of game paths 1973 | getdirs 1974 | fi 1975 | else 1976 | # Prompt the user for game paths 1977 | getdirs 1978 | fi 1979 | } 1980 | 1981 | # MARK: update_launch_script() 1982 | # Update the game launch script if necessary 1983 | update_launch_script() { 1984 | getdirs 1985 | 1986 | if [ "$?" -eq 1 ]; then 1987 | # User cancelled getdirs or there was an error 1988 | message warning "Unable to update launch script." 1989 | return 0 1990 | fi 1991 | 1992 | if [ ! -f "$wine_prefix/$wine_launch_script_name" ]; then 1993 | message warning "Game launch script not found!\n\n$wine_prefix/$wine_launch_script_name" 1994 | return 1 1995 | fi 1996 | 1997 | # Get launch script version info 1998 | current_launcher_ver="$(grep "^# version:" "$wine_prefix/$wine_launch_script_name" | awk '{print $3}')" 1999 | latest_launcher_ver="$(grep "^# version:" "$wine_launch_script" | awk '{print $3}')" 2000 | 2001 | # Get some path variables from the existing launch script 2002 | launcher_wineprefix="$(grep "^export WINEPREFIX=" "$wine_prefix/$wine_launch_script_name" | awk -F '=' '{print $2}' | tr -d '"')" 2003 | launcher_winepath="$(grep -e "^export wine_path=" -e "^wine_path=" "$wine_prefix/$wine_launch_script_name" | awk -F '=' '{print $2}' | tr -d '"')" 2004 | 2005 | if [ "$latest_launcher_ver" != "$current_launcher_ver" ] && 2006 | [ "$current_launcher_ver" = "$(printf "%s\n%s" "$current_launcher_ver" "$latest_launcher_ver" | sort -V | head -n1)" ]; then 2007 | # The launch script is out of date and needs to be updated 2008 | 2009 | # Backup the file 2010 | cp "$wine_prefix/$wine_launch_script_name" "$wine_prefix/$(basename "$wine_launch_script_name" .sh).bak" 2011 | 2012 | # If wineprefix isn't found in the file, something is wrong and we shouldn't proceed 2013 | if [ -z "$launcher_wineprefix" ]; then 2014 | message error "The WINEPREFIX env var was not found in your launch script. Unable to proceed!\n\n$wine_prefix/$wine_launch_script_name" 2015 | return 1 2016 | fi 2017 | 2018 | # If wine_path is empty, it may be an older version of the launch script. Default to system wine 2019 | if [ -z "$launcher_winepath" ]; then 2020 | launcher_winepath="$(command -v wine | xargs dirname)" 2021 | launcher_winepath="${launcher_winepath:-/usr/bin}" # default to /usr/bin if still empty 2022 | fi 2023 | 2024 | # Copy in the new launch script 2025 | cp "$wine_launch_script" "$wine_prefix" 2026 | 2027 | # Restore the wine prefix variable 2028 | if [ "$launcher_wineprefix" != "$wine_prefix" ]; then 2029 | # Offer to fix an incorrectly referenced wine prefix 2030 | if message question "Your launch script is pointing to the wrong Wine prefix.\nWould you like to update it to use the correct prefix?\n\nCurrent prefix in launch script:\n${launcher_wineprefix}\n\nCorrect prefix:\n${wine_prefix}"; then 2031 | sed -i "s|^export WINEPREFIX=.*|export WINEPREFIX=\"$wine_prefix\"|" "$wine_prefix/$wine_launch_script_name" 2032 | fi 2033 | else 2034 | # Restore the backed up prefix variable if the user doesn't want to change it 2035 | sed -i "s|^export WINEPREFIX=.*|export WINEPREFIX=\"$launcher_wineprefix\"|" "$wine_prefix/$wine_launch_script_name" 2036 | fi 2037 | 2038 | # Restore the wine path variable 2039 | sed -i "s#^export wine_path=.*#export wine_path=\"$launcher_winepath\"#" "$wine_prefix/$wine_launch_script_name" 2040 | 2041 | # Create .desktop files if needed 2042 | create_desktop_files needed 2043 | 2044 | message info "Your game launch script has been updated!\n\nIf you had customized your script, you'll need to re-add your changes.\nA backup was created at:\n\n$wine_prefix/$(basename "$wine_launch_script_name" .sh).bak" 2045 | elif [ "$launcher_wineprefix" != "$wine_prefix" ]; then 2046 | # The launch script is the correct version, but the current prefix is pointing to the wrong location 2047 | 2048 | # Create .desktop files if needed 2049 | create_desktop_files needed 2050 | 2051 | if message question "Your launch script is pointing to the wrong Wine prefix.\nWould you like to update it to use the correct prefix?\n\nCurrent prefix in launch script:\n${launcher_wineprefix}\n\nCorrect prefix:\n${wine_prefix}"; then 2052 | sed -i "s|^export WINEPREFIX=.*|export WINEPREFIX=\"$wine_prefix\"|" "$wine_prefix/$wine_launch_script_name" 2053 | message info "Your game launch script has been repaired!" 2054 | fi 2055 | else 2056 | # Create .desktop files if needed 2057 | create_desktop_files needed 2058 | 2059 | message info "Your game launch script is already up to date!" 2060 | fi 2061 | } 2062 | 2063 | # MARK: edit_launch_script() 2064 | # Edit the launch script 2065 | edit_launch_script() { 2066 | # Get/Set directory paths 2067 | getdirs 2068 | if [ "$?" -eq 1 ]; then 2069 | # User cancelled and wants to return to the main menu 2070 | # or there was an error 2071 | return 0 2072 | fi 2073 | 2074 | # Make sure the launch script exists 2075 | if [ ! -f "$wine_prefix/$wine_launch_script_name" ]; then 2076 | message error "Unable to find $wine_prefix/$wine_launch_script_name" 2077 | return 1 2078 | fi 2079 | 2080 | # Open the launch script in the user's preferred editor 2081 | if [ -x "$(command -v xdg-open)" ]; then 2082 | xdg-open "$wine_prefix/$wine_launch_script_name" 2083 | else 2084 | message error "xdg-open is not installed.\nYou may open the launch script manually:\n\n$wine_prefix/$wine_launch_script_name" 2085 | fi 2086 | } 2087 | 2088 | # MARK: call_launch_script() 2089 | # Call our launch script and pass it the given command line argument 2090 | call_launch_script() { 2091 | # This function expects a string to be passed in as an argument 2092 | if [ -z "$1" ]; then 2093 | debug_print exit "Script error: The call_launch_script function expects an argument. Aborting." 2094 | fi 2095 | 2096 | launch_arg="$1" 2097 | 2098 | # Get/Set directory paths 2099 | getdirs 2100 | if [ "$?" -eq 1 ]; then 2101 | # User cancelled and wants to return to the main menu 2102 | # or there was an error 2103 | return 0 2104 | fi 2105 | 2106 | # Make sure the launch script exists 2107 | if [ ! -f "$wine_prefix/$wine_launch_script_name" ]; then 2108 | message error "Unable to find $wine_prefix/$wine_launch_script_name" 2109 | return 1 2110 | fi 2111 | 2112 | # Check if the launch script is the correct version 2113 | current_launcher_ver="$(grep "^# version:" "$wine_prefix/$wine_launch_script_name" | awk '{print $3}')" 2114 | req_launcher_ver="1.5" 2115 | 2116 | if [ "$req_launcher_ver" != "$current_launcher_ver" ] && 2117 | [ "$current_launcher_ver" = "$(printf "%s\n%s" "$current_launcher_ver" "$req_launcher_ver" | sort -V | head -n1)" ]; then 2118 | message error "Your launch script is out of date!\nPlease update your launch script before proceeding." 2119 | return 1 2120 | fi 2121 | 2122 | # Launch a wine shell using the launch script 2123 | "$wine_prefix/$wine_launch_script_name" "$launch_arg" 2124 | } 2125 | 2126 | # MARK: install_powershell() 2127 | # Install powershell verb into the game's wine prefix 2128 | install_powershell() { 2129 | # Download winetricks 2130 | download_winetricks 2131 | 2132 | # Abort if the winetricks download failed 2133 | if [ "$?" -eq 1 ]; then 2134 | message error "Unable to install powershell without winetricks. Aborting." 2135 | return 1 2136 | fi 2137 | 2138 | # Update directories 2139 | getdirs 2140 | 2141 | if [ "$?" -eq 1 ]; then 2142 | # User cancelled getdirs or there was an error 2143 | message warning "Unable to install powershell." 2144 | return 1 2145 | fi 2146 | 2147 | # Get the current wine runner from the launch script 2148 | get_current_runner 2149 | if [ "$?" -ne 1 ]; then 2150 | export WINE="$launcher_winepath/wine" 2151 | export WINESERVER="$launcher_winepath/wineserver" 2152 | fi 2153 | # Set the correct wine prefix 2154 | export WINEPREFIX="$wine_prefix" 2155 | 2156 | # Show a zenity pulsating progress bar 2157 | progress_bar start "Installing PowerShell. Please wait..." 2158 | 2159 | # Install powershell 2160 | debug_print continue "Installing PowerShell into ${wine_prefix}..." 2161 | "$winetricks_bin" -q powershell 2162 | 2163 | exit_code="$?" 2164 | if [ "$exit_code" -eq 1 ] || [ "$exit_code" -eq 130 ] || [ "$exit_code" -eq 126 ]; then 2165 | progress_bar stop # Stop the zenity progress window 2166 | message warning "PowerShell could not be installed. See terminal output for details." 2167 | else 2168 | progress_bar stop # Stop the zenity progress window 2169 | message info "PowerShell operation complete. See terminal output for details." 2170 | fi 2171 | } 2172 | 2173 | # MARK: reinstall_rsi_launcher() 2174 | # Download and re-install the latest RSI Launcher into the wine prefix 2175 | reinstall_rsi_launcher() { 2176 | # Update directories 2177 | getdirs 2178 | 2179 | if [ "$?" -eq 1 ]; then 2180 | # User cancelled getdirs or there was an error 2181 | message error "Unable to install or update the RSI Launcher." 2182 | return 1 2183 | fi 2184 | 2185 | download_rsi_installer 2186 | # Abort if the download failed 2187 | if [ "$?" -eq 1 ]; then 2188 | message error "Unable to install or update the RSI Launcher." 2189 | return 1 2190 | fi 2191 | 2192 | # Get the current wine runner from the launch script 2193 | get_current_runner 2194 | if [ "$?" -ne 1 ]; then 2195 | export WINE="$launcher_winepath/wine" 2196 | export WINESERVER="$launcher_winepath/wineserver" 2197 | else 2198 | # Default to system wine 2199 | launcher_winepath="$(command -v wine | xargs dirname)" 2200 | launcher_winepath="${launcher_winepath:-/usr/bin}" # default to /usr/bin if still empty 2201 | fi 2202 | 2203 | # Set the correct wine prefix 2204 | export WINEPREFIX="$wine_prefix" 2205 | export WINEDLLOVERRIDES="dxwebsetup.exe,dotNetFx45_Full_setup.exe,winemenubuilder.exe=d" 2206 | 2207 | # Show a zenity pulsating progress bar 2208 | progress_bar start "Installing RSI Launcher. Please wait..." 2209 | 2210 | # Run the installer 2211 | debug_print continue "Installing RSI Launcher. Please wait; this will take a moment..." 2212 | "$launcher_winepath"/wine "$tmp_dir/$rsi_installer" /S 2213 | 2214 | exit_code="$?" 2215 | if [ "$exit_code" -eq 1 ] || [ "$exit_code" -eq 58 ]; then 2216 | # User cancelled or there was an error 2217 | "$launcher_winepath"/wineserver -k # Kill all wine processes 2218 | progress_bar stop # Stop the zenity progress window 2219 | message error "Installation aborted. See terminal output for details." 2220 | return 1 2221 | fi 2222 | 2223 | # Stop the zenity progress window 2224 | progress_bar stop 2225 | 2226 | # Kill the wine process after installation 2227 | "$launcher_winepath"/wineserver -k 2228 | 2229 | message info "RSI Launcher installation complete!" 2230 | } 2231 | 2232 | # MARK: display_dirs() 2233 | # Display all directories currently used by this helper and Star Citizen 2234 | display_dirs() { 2235 | dirs_list="\n" 2236 | 2237 | # Helper configs and keybinds 2238 | if [ -d "$conf_dir/$conf_subdir" ]; then 2239 | dir_path="$conf_dir/$conf_subdir" 2240 | if [ "$use_zenity" -eq 1 ]; then 2241 | dirs_list+="Helper configuration:\n$dir_path\n\n" 2242 | else 2243 | dirs_list+="Helper configuration:\n$dir_path\n\n" 2244 | fi 2245 | fi 2246 | 2247 | # Wine prefix 2248 | if [ -f "$conf_dir/$conf_subdir/$wine_conf" ]; then 2249 | dir_path="$(cat "$conf_dir/$conf_subdir/$wine_conf")" 2250 | if [ "$use_zenity" -eq 1 ]; then 2251 | dirs_list+="Wine prefix:\n$dir_path\n\n" 2252 | else 2253 | dirs_list+="Wine prefix:\n$dir_path\n\n" 2254 | fi 2255 | fi 2256 | 2257 | # Star Citizen installation 2258 | if [ -f "$conf_dir/$conf_subdir/$game_conf" ]; then 2259 | dir_path="$(cat "$conf_dir/$conf_subdir/$game_conf")" 2260 | if [ "$use_zenity" -eq 1 ]; then 2261 | dirs_list+="Star Citizen game directory:\n$dir_path\n\n" 2262 | else 2263 | dirs_list+="Star Citizen game directory:\n$dir_path\n\n" 2264 | fi 2265 | fi 2266 | 2267 | # Format the info header 2268 | message_heading="These directories are currently being used by this Helper and Star Citizen" 2269 | if [ "$use_zenity" -eq 1 ]; then 2270 | message_heading="$message_heading" 2271 | fi 2272 | 2273 | message info "$message_heading\n$dirs_list" 2274 | } 2275 | 2276 | # MARK: display_wiki() 2277 | # Display the LUG Wiki 2278 | display_wiki() { 2279 | # Display a message containing the URL 2280 | message info "See the Wiki for our Quick-Start Guide, Troubleshooting,\nand Performance Tuning Recommendations:\n\n$lug_wiki" 2281 | } 2282 | 2283 | # MARK: reset_helper() 2284 | # Delete the helper's config directory 2285 | reset_helper() { 2286 | if [ "$1" = "switchprefix" ]; then 2287 | # This gets called by the switch_prefix and install_game functions 2288 | # We only want to delete configs related to the game path in order to target a different game install 2289 | debug_print continue "Deleting $conf_dir/$conf_subdir/{$wine_conf,$game_conf}..." 2290 | rm --interactive=never "${conf_dir:?}/$conf_subdir/"{"$wine_conf","$game_conf"} 2291 | elif message question "All config files will be deleted from:\n\n$conf_dir/$conf_subdir\n\nDo you want to proceed?"; then 2292 | # Called normally by the user, wipe all the things! 2293 | debug_print continue "Deleting $conf_dir/$conf_subdir/*.conf..." 2294 | rm --interactive=never "${conf_dir:?}/$conf_subdir/"*.conf 2295 | message info "The Helper has been reset!" 2296 | fi 2297 | # Also wipe path variables so the reset takes immediate effect 2298 | wine_prefix="" 2299 | game_path="" 2300 | } 2301 | 2302 | ############################################################################ 2303 | ######## end maintenance functions ######################################### 2304 | ############################################################################ 2305 | 2306 | ############################################################################ 2307 | ######## begin dxvk functions ############################################## 2308 | ############################################################################ 2309 | 2310 | # MARK: dxvk_menu() 2311 | # Menu to select and install a dxvk into the wine prefix 2312 | dxvk_menu() { 2313 | # Fetch wine prefix 2314 | if [ -f "$conf_dir/$conf_subdir/$wine_conf" ]; then 2315 | game_prefix="$(cat "$conf_dir/$conf_subdir/$wine_conf")" 2316 | else 2317 | game_prefix="Not configured" 2318 | fi 2319 | 2320 | # Configure the menu 2321 | menu_text_zenity="Manage Your DXVK Version\n\nSelect which DXVK you'd like to update or install\n\nWine prefix: $game_prefix" 2322 | menu_text_terminal="Manage Your DXVK Version\n\nSelect which DXVK you'd like to update or install\nWine prefix: $game_prefix" 2323 | menu_text_height="300" 2324 | menu_type="radiolist" 2325 | 2326 | # Configure the menu options 2327 | standard_msg="Update or Switch to Standard DXVK" 2328 | async_msg="Update or Switch to Async DXVK" 2329 | nvapi_msg="Add or Update DXVK-NVAPI" 2330 | quit_msg="Return to the main menu" 2331 | 2332 | # Set the options to be displayed in the menu 2333 | menu_options=("$standard_msg" "$async_msg" "$nvapi_msg" "$quit_msg") 2334 | # Set the corresponding functions to be called for each of the options 2335 | menu_actions=("install_dxvk standard" "install_dxvk async" "install_dxvk nvapi" "menu_loop_done") 2336 | 2337 | # Calculate the total height the menu should be 2338 | # menu_option_height = pixels per menu option 2339 | # #menu_options[@] = number of menu options 2340 | # menu_text_height = height of the title/description text 2341 | # menu_text_height_zenity4 = added title/description height for libadwaita bigness 2342 | menu_height="$(($menu_option_height * ${#menu_options[@]} + $menu_text_height + $menu_text_height_zenity4))" 2343 | 2344 | # Set the label for the cancel button 2345 | cancel_label="Go Back" 2346 | 2347 | # Call the menu function. It will use the options as configured above 2348 | menu 2349 | } 2350 | 2351 | # MARK: install_dxvk() 2352 | # Entry function to install or update DXVK in the wine prefix 2353 | # 2354 | # Requires one argument to specify which type of dxvk to install 2355 | # Supports "standard", "async", "nvapi" 2356 | install_dxvk() { 2357 | # Sanity checks 2358 | if [ "$#" -lt 1 ]; then 2359 | debug_print exit "Script error: The install_dxvk function expects one argument. Aborting." 2360 | fi 2361 | 2362 | # Update directories 2363 | getdirs 2364 | 2365 | if [ "$?" -eq 1 ]; then 2366 | # User cancelled getdirs or there was an error 2367 | message warning "Unable to update dxvk." 2368 | return 1 2369 | fi 2370 | 2371 | # Get the current wine runner from the launch script 2372 | get_current_runner 2373 | if [ "$?" -ne 1 ]; then 2374 | export WINE="$launcher_winepath/wine" 2375 | export WINESERVER="$launcher_winepath/wineserver" 2376 | fi 2377 | # Set the correct wine prefix 2378 | export WINEPREFIX="$wine_prefix" 2379 | 2380 | if [ "$1" = "standard" ]; then 2381 | install_standard_dxvk 2382 | elif [ "$1" = "async" ]; then 2383 | install_async_dxvk 2384 | elif [ "$1" = "nvapi" ]; then 2385 | install_dxvk_nvapi 2386 | else 2387 | debug_print exit "Script error: Unknown argument in install_dxvk function: $1. Aborting." 2388 | fi 2389 | } 2390 | 2391 | # MARK: install_standard_dxvk() 2392 | # Install or update standard dxvk in the wine prefix 2393 | # 2394 | # Expects that getdirs has already been called 2395 | # Expects that the env vars WINE, WINESERVER, and WINEPREFIX are already set 2396 | install_standard_dxvk() { 2397 | # Download winetricks 2398 | download_winetricks 2399 | 2400 | # Abort if the winetricks download failed 2401 | if [ "$?" -eq 1 ]; then 2402 | message error "Unable to update dxvk without winetricks. Aborting." 2403 | return 1 2404 | fi 2405 | 2406 | # Show a zenity pulsating progress bar 2407 | progress_bar start "Updating DXVK. Please wait..." 2408 | debug_print continue "Updating DXVK in ${wine_prefix}..." 2409 | 2410 | # Update dxvk 2411 | "$winetricks_bin" -f dxvk 2412 | 2413 | exit_code="$?" 2414 | if [ "$exit_code" -eq 1 ] || [ "$exit_code" -eq 130 ] || [ "$exit_code" -eq 126 ]; then 2415 | progress_bar stop # Stop the zenity progress window 2416 | message warning "DXVK could not be installed. See terminal output for details." 2417 | else 2418 | progress_bar stop # Stop the zenity progress window 2419 | message info "DXVK update complete. See terminal output for details." 2420 | fi 2421 | } 2422 | 2423 | # MARK: install_async_dxvk() 2424 | # Install or update async dxvk in the wine prefix 2425 | # 2426 | # Expects that getdirs has already been called 2427 | # Expects that the env vars WINE, WINESERVER, and WINEPREFIX are already set 2428 | install_async_dxvk() { 2429 | # Sanity checks 2430 | if [ ! -d "$wine_prefix/drive_c/windows/system32" ]; then 2431 | message error "Unable to find the system32 directory in your Wine prefix! Your prefix may be broken.\n\n$wine_prefix/drive_c/windows/system32" 2432 | return 1 2433 | fi 2434 | if [ ! -d "$wine_prefix/drive_c/windows/syswow64" ]; then 2435 | message error "Unable to find the syswow64 directory in your Wine prefix! Your prefix may be broken.\n\n$wine_prefix/drive_c/windows/syswow64" 2436 | return 1 2437 | fi 2438 | 2439 | # Get the file download url 2440 | # Assume the first item returned by the API is the latest version 2441 | download_url="$(curl -sL "${dxvk_async_source}" | grep -Eo "\"direct_asset_url\": ?\"[^\"]+\"" | grep "releases" | grep -F ".tar.gz" | cut -d '"' -f4 | cut -d '?' -f1)" 2442 | 2443 | # Sanity check 2444 | if [ -z "$download_url" ]; then 2445 | message warning "Could not find the requested dxvk file. The GitLab API may be down or rate limited." 2446 | return 1 2447 | fi 2448 | 2449 | # Get file name info 2450 | download_filename="$(basename "$download_url")" 2451 | download_basename="$(basename "$download_filename" .tar.gz)" 2452 | 2453 | # Download the item to the tmp directory 2454 | download_file "$download_url" "$download_filename" "DXVK" 2455 | 2456 | # Sanity check 2457 | if [ ! -f "$tmp_dir/$download_filename" ]; then 2458 | # Something went wrong with the download and the file doesn't exist 2459 | message error "Something went wrong and the requested DXVK file could not be downloaded!" 2460 | debug_print continue "Download failed! File not found: $tmp_dir/$download_filename" 2461 | return 1 2462 | fi 2463 | 2464 | # Show a zenity pulsating progress bar 2465 | progress_bar start "Updating DXVK. Please wait..." 2466 | 2467 | # Extract the archive to the tmp directory 2468 | debug_print continue "Extracting DXVK into $tmp_dir/$download_basename..." 2469 | tar -xf "$tmp_dir/$download_filename" -C "$tmp_dir" 2470 | 2471 | # Make sure the expected directories exist 2472 | if [ ! -d "$tmp_dir/$download_basename/x64" ] || [ ! -d "$tmp_dir/$download_basename/x32" ]; then 2473 | progress_bar stop # Stop the zenity progress window 2474 | message warning "Unexpected file structure in the extracted DXVK. The file may be corrupt." 2475 | return 1 2476 | fi 2477 | 2478 | # Install the dxvk into the wine prefix 2479 | debug_print continue "Copying DXVK dlls into ${wine_prefix}..." 2480 | cp "$tmp_dir"/"$download_basename"/x64/*.dll "$wine_prefix/drive_c/windows/system32" 2481 | cp "$tmp_dir"/"$download_basename"/x32/*.dll "$wine_prefix/drive_c/windows/syswow64" 2482 | 2483 | 2484 | # Make sure we can locate the launch script 2485 | if [ ! -f "$wine_prefix/$wine_launch_script_name" ]; then 2486 | progress_bar stop # Stop the zenity progress window 2487 | message warning "Unable to find launch script!\n$wine_prefix/$wine_launch_script_name\n\nTo enable async, set the environment variable: DXVK_ASYNC=1" 2488 | return 0 2489 | fi 2490 | # Check if the DXVK_ASYNC variable is commented out in the launch script 2491 | if ! grep -q "^#export DXVK_ASYNC=" "$wine_prefix/$wine_launch_script_name" && ! grep -q "^export DXVK_ASYNC=1" "$wine_prefix/$wine_launch_script_name"; then 2492 | progress_bar stop # Stop the zenity progress window 2493 | if message question "Could not find the DXVK_ASYNC environment variable in your launch script! It may be out of date.\n\nWould you like to try updating your launch script?"; then 2494 | # Try updating the launch script 2495 | update_launch_script 2496 | 2497 | # Check if the update was successful and we now have the env var 2498 | if ! grep -q "^#export DXVK_ASYNC=" "$wine_prefix/$wine_launch_script_name"; then 2499 | message warning "Could not find the DXVK_ASYNC environment variable in your launch script! The update may have failed.\n\nTo enable async, set the environment variable: DXVK_ASYNC=1" 2500 | return 0 2501 | fi 2502 | else 2503 | message warning "To enable async, set the environment variable: DXVK_ASYNC=1" 2504 | return 0 2505 | fi 2506 | fi 2507 | 2508 | # Modify the launch script to uncomment the DXVK_ASYNC variable unless it's already uncommented 2509 | if ! grep -q "^export DXVK_ASYNC=1" "$wine_prefix/$wine_launch_script_name"; then 2510 | debug_print continue "Updating DXVK_ASYNC env var in launch script ${wine_prefix}/${wine_launch_script_name}..." 2511 | sed -i "s|^#export DXVK_ASYNC=.*|export DXVK_ASYNC=1|" "$wine_prefix/$wine_launch_script_name" 2512 | fi 2513 | 2514 | progress_bar stop # Stop the zenity progress window 2515 | message info "DXVK update complete." 2516 | } 2517 | 2518 | # MARK: install_dxvk_nvapi() 2519 | # Install or update dxvk-nvapi in the wine prefix 2520 | # 2521 | # Expects that getdirs has already been called 2522 | # Expects that the env vars WINE, WINESERVER, and WINEPREFIX are already set 2523 | install_dxvk_nvapi() { 2524 | # Download winetricks 2525 | download_winetricks next 2526 | 2527 | # Abort if the winetricks download failed 2528 | if [ "$?" -eq 1 ]; then 2529 | message error "Unable to install dxvk_nvapi without winetricks. Aborting." 2530 | return 1 2531 | fi 2532 | 2533 | # Show a zenity pulsating progress bar 2534 | progress_bar start "Installing DXVK-NVAPI. Please wait..." 2535 | debug_print continue "Installing DXVK-NVAPI in ${wine_prefix}..." 2536 | 2537 | # Update dxvk 2538 | "$winetricks_bin" -f dxvk_nvapi 2539 | 2540 | exit_code="$?" 2541 | if [ "$exit_code" -eq 1 ] || [ "$exit_code" -eq 130 ] || [ "$exit_code" -eq 126 ]; then 2542 | progress_bar stop # Stop the zenity progress window 2543 | message warning "DXVK-NVAPI could not be installed. See terminal output for details." 2544 | else 2545 | progress_bar stop # Stop the zenity progress window 2546 | message info "DXVK-NVAPI update complete. See terminal output for details." 2547 | fi 2548 | } 2549 | 2550 | ############################################################################ 2551 | ######## end dxvk functions ################################################ 2552 | ############################################################################ 2553 | 2554 | # MARK: install_game() 2555 | # Install the game with Wine 2556 | install_game() { 2557 | # Check if the install script exists 2558 | if [ ! -f "$wine_launch_script" ]; then 2559 | message error "Game launch script not found! Unable to proceed.\n\n$wine_launch_script\n\nIt is included in our official releases here:\n$releases_url" 2560 | return 1 2561 | fi 2562 | 2563 | # Call the preflight check and confirm the user is ready to proceed 2564 | preflight_check "wine" 2565 | if [ "$?" -eq 1 ]; then 2566 | # There were errors 2567 | install_question="Before proceeding, be sure all Preflight Checks have passed!\n\nPlease refer to our Quick Start Guide:\n$lug_wiki\n\nAre you ready to continue?" 2568 | else 2569 | # No errors 2570 | install_question="Before proceeding, please refer to our Quick Start Guide:\n$lug_wiki\n\nAll Preflight Checks have passed\nAre you ready to continue?" 2571 | fi 2572 | if ! message question "$install_question"; then 2573 | return 1 2574 | fi 2575 | 2576 | # Get the install path from the user 2577 | if message question "Would you like to use the default install path?\n\n$HOME/Games/star-citizen"; then 2578 | # Set the default install path 2579 | install_dir="$HOME/Games/star-citizen" 2580 | 2581 | # Make sure we're not installing over an existing prefix 2582 | if [ -d "$install_dir" ]; then 2583 | message warning "A directory named \"star-citizen\" already exists!\n\n$install_dir\n\nInstalling over an existing prefix is not recommended.\nIf you need to update or re-install the RSI Launcher, use the option in the Maintenance and Troubleshooting menu." 2584 | return 0 2585 | fi 2586 | else 2587 | if [ "$use_zenity" -eq 1 ]; then 2588 | message info "On the next screen, select your Star Citizen install location" 2589 | 2590 | # Get the install path from the user 2591 | while true; do 2592 | install_dir="$(zenity --file-selection --directory --title="Choose your Star Citizen install directory" --filename="$HOME/" 2>/dev/null)" 2593 | 2594 | if [ "$?" -eq -1 ]; then 2595 | message error "An unexpected error has occurred. The Helper is unable to proceed." 2596 | return 1 2597 | elif [ -z "$install_dir" ]; then 2598 | # User clicked cancel or something else went wrong 2599 | message warning "Installation cancelled." 2600 | return 1 2601 | fi 2602 | 2603 | # Make sure we're not installing over an existing prefix 2604 | if [ -d "$install_dir/star-citizen" ]; then 2605 | message warning "A directory named \"star-citizen\" already exists!\nPlease choose a different install location.\n\n$install_dir" 2606 | continue 2607 | fi 2608 | 2609 | # Add the wine prefix subdirectory to the install path 2610 | install_dir="$install_dir/star-citizen" 2611 | 2612 | break 2613 | done 2614 | else 2615 | # No Zenity, use terminal-based menus 2616 | clear 2617 | # Get the install path from the user 2618 | printf "Enter the desired Star Citizen install path (case sensitive)\nie. /home/USER/Games/star-citizen\n\n" 2619 | while read -rp "Install path: " install_dir; do 2620 | if [ -z "$install_dir" ]; then 2621 | printf "Invalid directory. Please try again.\n\n" 2622 | elif [ ! -d "$install_dir" ]; then 2623 | if message question "That directory does not exist.\nWould you like it to be created for you?\n"; then 2624 | break 2625 | fi 2626 | else 2627 | break 2628 | fi 2629 | done 2630 | fi 2631 | fi 2632 | 2633 | # Create the game path 2634 | mkdir -p "$install_dir" 2635 | 2636 | # EAC doesn't like >10.0 wine or wow64 wine (all new wines are wow64) 2637 | # Until EAC fixes itself, we need to force a working runner for everyone 2638 | # The below is commented out in hopes that it can be restored in the future 2639 | 2640 | # If we can't use the system wine, we'll need to have the user select a custom wine runner to use 2641 | #wine_path="$(command -v wine | xargs dirname)" 2642 | 2643 | #if [ "$system_wine_ok" = "false" ]; then 2644 | #debug_print continue "Your system Wine does not meet the minimum requirements for Star Citizen!" 2645 | #debug_print continue "A custom wine runner will be automatically downloaded and used." 2646 | 2647 | debug_print continue "Installing a custom wine runner..." 2648 | 2649 | download_dir="$install_dir/runners" 2650 | 2651 | # Install the default wine runner into the prefix 2652 | download_wine 2653 | # Make sure the wine download worked 2654 | if [ "$?" -eq 1 ]; then 2655 | message error "Something went wrong while installing ${default_runner}!\nGame installation cannot proceed." 2656 | return 1 2657 | fi 2658 | 2659 | wine_path="$install_dir/runners/$downloaded_item_name/bin" 2660 | #fi #### Note: End of previous if statement commented out due to new EAC requirements 2661 | 2662 | # Download winetricks 2663 | download_winetricks 2664 | # Abort if the winetricks download failed 2665 | if [ "$?" -eq 1 ]; then 2666 | message error "Unable to install Star Citizen without winetricks. Aborting." 2667 | return 1 2668 | fi 2669 | 2670 | download_rsi_installer 2671 | # Abort if the download failed 2672 | if [ "$?" -eq 1 ]; then 2673 | message error "Unable to install Star Citizen. Aborting." 2674 | return 1 2675 | fi 2676 | 2677 | # Create a temporary log file 2678 | tmp_install_log="$(mktemp --suffix=".log" -t "lughelper-install-XXX")" 2679 | debug_print continue "Installation log file created at $tmp_install_log" 2680 | 2681 | # Configure the wine prefix environment 2682 | export WINE="$wine_path/wine" 2683 | export WINESERVER="$wine_path/wineserver" 2684 | export WINEPREFIX="$install_dir" 2685 | export WINEDLLOVERRIDES="dxwebsetup.exe,dotNetFx45_Full_setup.exe,winemenubuilder.exe=d" 2686 | 2687 | # Show a zenity pulsating progress bar 2688 | progress_bar start "Preparing Wine prefix and installing RSI Launcher. Please wait..." 2689 | 2690 | # Create the new prefix and install powershell 2691 | debug_print continue "Preparing Wine prefix. Please wait; this will take a moment..." 2692 | "$winetricks_bin" -q arial tahoma dxvk powershell win11 >"$tmp_install_log" 2>&1 2693 | 2694 | exit_code="$?" 2695 | if [ "$exit_code" -eq 1 ] || [ "$exit_code" -eq 130 ] || [ "$exit_code" -eq 126 ]; then 2696 | # 126 = permission denied (ie. noexec on /tmp) 2697 | "$wine_path"/wineserver -k # Kill all wine processes 2698 | progress_bar stop # Stop the zenity progress window 2699 | if message question "Wine prefix creation failed. Aborting installation.\nThe install log was written to\n$tmp_install_log\n\nDo you want to delete\n${install_dir}?"; then 2700 | debug_print continue "Deleting $install_dir..." 2701 | rm -r --interactive=never "$install_dir" 2702 | fi 2703 | return 1 2704 | fi 2705 | 2706 | # Add registry key that prevents wine from creating unnecessary file type associations 2707 | "$wine_path"/wine reg add "HKEY_CURRENT_USER\Software\Wine\FileOpenAssociations" /v Enable /d N /f >>"$tmp_install_log" 2>&1 2708 | 2709 | # Run the installer 2710 | debug_print continue "Installing RSI Launcher. Please wait; this will take a moment..." 2711 | "$wine_path"/wine "$tmp_dir/$rsi_installer" /S >>"$tmp_install_log" 2>&1 2712 | 2713 | exit_code="$?" 2714 | if [ "$exit_code" -eq 1 ] || [ "$exit_code" -eq 58 ]; then 2715 | # User cancelled or there was an error 2716 | "$wine_path"/wineserver -k # Kill all wine processes 2717 | progress_bar stop # Stop the zenity progress window 2718 | if message question "Installation aborted. The install log was written to\n$tmp_install_log\n\nDo you want to delete\n${install_dir}?"; then 2719 | debug_print continue "Deleting $install_dir..." 2720 | rm -r --interactive=never "$install_dir" 2721 | fi 2722 | return 0 2723 | fi 2724 | 2725 | # Stop the zenity progress window 2726 | progress_bar stop 2727 | 2728 | # Kill the wine process after installation 2729 | "$wine_path"/wineserver -k 2730 | 2731 | # Save the install location to the Helper's config files 2732 | reset_helper "switchprefix" 2733 | wine_prefix="$install_dir" 2734 | if [ -d "$wine_prefix/$default_install_path" ]; then 2735 | game_path="$wine_prefix/$default_install_path/$sc_base_dir" 2736 | fi 2737 | getdirs 2738 | 2739 | # Verify that we have an installed game path 2740 | if [ -z "$game_path" ]; then 2741 | message error "Something went wrong during installation. Unable to locate the expected game path. Aborting." 2742 | return 1 2743 | fi 2744 | 2745 | # Copy game launch script to the wine prefix root directory 2746 | debug_print continue "Copying game launch script to ${install_dir}..." 2747 | if [ -f "$install_dir/$wine_launch_script_name" ]; then 2748 | # Back it up if it already exists 2749 | cp "$install_dir/$wine_launch_script_name" "$install_dir/$(basename "$wine_launch_script_name" .sh).bak" 2750 | fi 2751 | cp "$wine_launch_script" "$install_dir" 2752 | installed_launch_script="$install_dir/$wine_launch_script_name" 2753 | 2754 | # Update WINEPREFIX in game launch script 2755 | sed -i "s|^export WINEPREFIX=.*|export WINEPREFIX=\"$install_dir\"|" "$installed_launch_script" 2756 | 2757 | # Update Wine binary in game launch script 2758 | post_download_sed_string="export wine_path=" 2759 | sed -i "s|^${post_download_sed_string}.*|${post_download_sed_string}\"${wine_path}\"|" "$installed_launch_script" 2760 | 2761 | # Copy the bundled RSI Launcher icon to the .local icons directory 2762 | if [ -f "$rsi_icon" ]; then 2763 | mkdir -p "$data_dir/icons/hicolor/256x256/apps" && 2764 | cp "$rsi_icon" "$data_dir/icons/hicolor/256x256/apps" 2765 | fi 2766 | 2767 | # Create a "no_win64_warnings" file in the prefix to supress Wine64 warnings 2768 | touch "${install_dir}/no_win64_warnings" 2769 | 2770 | # Create .desktop files 2771 | create_desktop_files 2772 | 2773 | debug_print continue "Installation finished" 2774 | message info "Installation has finished. The install log was written to $tmp_install_log\n\nTo start the RSI Launcher, use the following .desktop files:\n $home_desktop_file\n $localshare_desktop_file\n\nOr run the following launch script:\n $installed_launch_script\n\nIMPORTANT!\nThe RSI Launcher will offer to install the game into C:\\\Program Files\\\...\nDo not change the default path!" 2775 | } 2776 | 2777 | # MARK: create_desktop_files() 2778 | # Create .desktop files for the RSI Launcher 2779 | # The default behavior is to overwite any existing .desktop files 2780 | # 2781 | # This function takes one optional string argument: 2782 | # "needed" will only create necessary desktop files that don't exist 2783 | create_desktop_files() { 2784 | # Sanity checks 2785 | if [ -z "$wine_prefix" ]; then 2786 | debug_print exit "Script error: The string 'wine_prefix' was not set before calling the create_desktop_files function. Aborting." 2787 | fi 2788 | 2789 | # $HOME/Games/star-citizen/RSI Launcher.desktop 2790 | prefix_desktop_file="${wine_prefix}/RSI Launcher.desktop" 2791 | # $HOME/.local/share/applications/RSI Launcher.desktop 2792 | localshare_desktop_file="${data_dir}/applications/RSI Launcher.desktop" 2793 | # $HOME/Desktop/RSI Launcher.desktop 2794 | home_desktop_file="${XDG_DESKTOP_DIR:-$HOME/Desktop}/RSI Launcher.desktop" 2795 | 2796 | create_desktop_files="true" 2797 | # If the "needed" argument is passed, determine if we need to create system desktop files 2798 | if [ "$1" = "needed" ]; then 2799 | if [ -f "$localshare_desktop_file" ] || [ -f "$home_desktop_file" ]; then 2800 | # If either desktop file already exists, don't overwrite/replace them 2801 | create_desktop_files="false" 2802 | fi 2803 | fi 2804 | 2805 | debug_print continue "Creating ${prefix_desktop_file}..." 2806 | # The backup .desktop file in the prefix directory will always be created so it's up to date 2807 | echo "[Desktop Entry] 2808 | Name=RSI Launcher 2809 | Type=Application 2810 | Comment=RSI Launcher 2811 | Keywords=Star Citizen;StarCitizen 2812 | StartupNotify=true 2813 | StartupWMClass=rsi launcher.exe 2814 | Icon=rsi-launcher 2815 | Exec=\"${wine_prefix}/${wine_launch_script_name}\"" > "$prefix_desktop_file" 2816 | 2817 | if [ "$create_desktop_files" = "true" ]; then 2818 | debug_print continue "Creating system .desktop files...\n${localshare_desktop_file}\n${home_desktop_file}" 2819 | 2820 | # Copy the new desktop file to ~/.local/share/applications 2821 | mkdir -p "${data_dir}/applications" 2822 | cp "$prefix_desktop_file" "$localshare_desktop_file" 2823 | # Copy the new desktop file to the user's desktop directory 2824 | if [ -d "$(dirname "$home_desktop_file")" ]; then 2825 | cp "$prefix_desktop_file" "$home_desktop_file" 2826 | fi 2827 | 2828 | # Update the .desktop file database if the command is available 2829 | if [ -x "$(command -v update-desktop-database)" ]; then 2830 | debug_print continue "Running update-desktop-database..." 2831 | update-desktop-database "${data_dir}/applications" 2832 | fi 2833 | 2834 | # Check if the desktop files were created successfully 2835 | if [ ! -f "$home_desktop_file" ]; then 2836 | # Desktop file couldn't be created 2837 | message warning "Warning: The .desktop file could not be created!\n\n${home_desktop_file}" 2838 | fi 2839 | if [ ! -f "$localshare_desktop_file" ]; then 2840 | # Desktop file couldn't be created 2841 | message warning "Warning: The .desktop file could not be created!\n\n${localshare_desktop_file}" 2842 | fi 2843 | fi 2844 | } 2845 | 2846 | # MARK: download_wine() 2847 | # Download a default wine runner for use by the installer 2848 | # Expects download_dir to be set before calling 2849 | download_wine() { 2850 | if [ -z "$download_dir" ]; then 2851 | debug_print exit "Script error: The string 'download_dir' was not set before calling the download_wine function. Aborting." 2852 | fi 2853 | 2854 | # Set variables for the latest default runner 2855 | set_latest_default_runner 2856 | # Sanity check 2857 | if [ "$?" -eq 1 ]; then 2858 | message error "Could not fetch the latest default wine runner. The Github API may be down or rate limited." 2859 | return 1 2860 | fi 2861 | 2862 | # Set up variables needed for the download functions, quick and dirty 2863 | # For more details, see their usage in the download_select_install and download_install functions 2864 | declare -n download_sources=runner_sources 2865 | download_type="runner" 2866 | download_versions=("$default_runner_file") 2867 | contributor_name="${download_sources[$default_runner_source]}" 2868 | contributor_url="${download_sources[$default_runner_source+1]}" 2869 | case "$contributor_url" in 2870 | https://api.github.com/*) 2871 | download_url_type="github" 2872 | ;; 2873 | https://gitlab.com/api/v4/projects/*) 2874 | download_url_type="gitlab" 2875 | ;; 2876 | *) 2877 | debug_print exit "Script error: Unknown api/url format in ${download_type}_sources array. Aborting." 2878 | ;; 2879 | esac 2880 | 2881 | # Call the download_install function with the above options to install the default wine runner 2882 | download_install 0 2883 | 2884 | if [ "$?" -eq 1 ]; then 2885 | return 1 2886 | fi 2887 | } 2888 | 2889 | # MARK: download_winetricks() 2890 | # Download winetricks to a temporary file 2891 | # Accepts an optional string argument "next" to download the latest -next version instead of the stable release 2892 | download_winetricks() { 2893 | # Set variables for the latest winetricks urls 2894 | set_latest_winetricks 2895 | 2896 | if [ "$1" = "next" ]; then 2897 | # Download the -next version 2898 | download_file "$winetricks_next_url" "winetricks" "winetricks" 2899 | else 2900 | # Download the stable version 2901 | download_file "$winetricks_url" "winetricks" "winetricks" 2902 | fi 2903 | 2904 | # Sanity check 2905 | if [ ! -f "$tmp_dir/winetricks" ]; then 2906 | # Something went wrong with the download and the file doesn't exist 2907 | message error "Something went wrong; winetricks could not be downloaded!" 2908 | return 1 2909 | fi 2910 | 2911 | # Save the path to the downloaded binary 2912 | winetricks_bin="$tmp_dir/winetricks" 2913 | 2914 | # Make it executable 2915 | chmod +x "$winetricks_bin" 2916 | } 2917 | 2918 | # MARK: download_rsi_installer() 2919 | # Downloads the latest RSI setup installer to a temporary file 2920 | download_rsi_installer() { 2921 | # Fetch the latest RSI installer url 2922 | set_latest_rsi_installer 2923 | # Sanity check 2924 | if [ "$?" -eq 1 ]; then 2925 | message error "Could not fetch the latest RSI installer! The latest.yml format may have changed or the site is down." 2926 | return 1 2927 | fi 2928 | 2929 | # Download RSI installer to tmp 2930 | download_file "$rsi_installer_url" "$rsi_installer" "installer" 2931 | # Sanity check 2932 | if [ ! -f "$tmp_dir/$rsi_installer" ]; then 2933 | # Something went wrong with the download and the file doesn't exist 2934 | message error "Something went wrong; the installer could not be downloaded!" 2935 | return 1 2936 | fi 2937 | } 2938 | 2939 | # MARK: get_current_runner() 2940 | # Get the wine runner path from the sc-launch.sh script 2941 | # It's expected that getdirs has already been called by the calling function to populate directory variables 2942 | get_current_runner() { 2943 | # Make sure we can find the launch script 2944 | if [ ! -f "$wine_prefix/$wine_launch_script_name" ]; then 2945 | message warning "Unable to find launch script!\n$wine_prefix/$wine_launch_script_name" 2946 | return 1 2947 | fi 2948 | 2949 | # Get the current wine runner path from the launch script 2950 | launcher_winepath="$(grep -e "^export wine_path=" -e "^wine_path=" "$wine_prefix/$wine_launch_script_name" | awk -F '=' '{print $2}' | tr -d '"')" 2951 | 2952 | # Double check that we found a path in the launch script 2953 | if [ -z "$launcher_winepath" ]; then 2954 | message warning "Unable to find the current wine runner in your launch script!\n$wine_prefix/$wine_launch_script_name" 2955 | return 1 2956 | fi 2957 | 2958 | # Remove the last /bin directory from the path to get the runner directory 2959 | current_runner_path="$(dirname "$launcher_winepath")" 2960 | # Get the runner filename, not including its file extension 2961 | current_runner_basename="$(basename "$current_runner_path")" 2962 | } 2963 | 2964 | # MARK: set_latest_rsi_installer() 2965 | # Fetch and store variables for the latest RSI installer filename and url 2966 | set_latest_rsi_installer() { 2967 | # Fetch the yml and parse it for the latest filename 2968 | # ie. RSI Launcher-Setup-2.9.0.exe 2969 | rsi_installer="$(curl -s "$rsi_installer_latest_yml" | grep -Eo "url:.+" | sed 's/url:[[:space:]]*//')" 2970 | 2971 | if [ -z "$rsi_installer" ]; then 2972 | return 1 2973 | fi 2974 | 2975 | rsi_installer_url="${rsi_installer_base_url}/${rsi_installer}" 2976 | } 2977 | 2978 | # MARK: set_latest_default_runner() 2979 | # Fetch and store variables for the latest default wine runner filename 2980 | set_latest_default_runner() { 2981 | default_runner_file="$(curl -s https://api.github.com/repos/starcitizen-lug/lug-wine/releases/latest | grep -Eo "\"browser_download_url\": ?\"[^\"]+\"" | grep -vie "staging" | cut -d '"' -f4 | xargs basename)" 2982 | 2983 | if [ -z "$default_runner_file" ]; then 2984 | return 1 2985 | fi 2986 | 2987 | # Store the filename without the file extension 2988 | default_runner="$(basename "$default_runner_file" .tar.gz)" 2989 | 2990 | # Set the runner_sources array index which points to the default runner source api url (must be an even number in the array, arrays start with 0) 2991 | default_runner_source=0 2992 | } 2993 | 2994 | # MARK: set_latest_winetricks() 2995 | # Fetch and store variables for the latest winetricks download urls 2996 | set_latest_winetricks() { 2997 | # Winetricks download url 2998 | winetricks_version="$(get_latest_release Winetricks/winetricks)" 2999 | winetricks_url="https://raw.githubusercontent.com/Winetricks/winetricks/refs/tags/${winetricks_version}/src/winetricks" 3000 | winetricks_next_url="https://raw.githubusercontent.com/Winetricks/winetricks/master/src/winetricks" 3001 | } 3002 | 3003 | # MARK: get_latest_release() 3004 | # Get the latest release version of a repo. Expects "user/repo_name" as input 3005 | # Credits for this go to https://gist.github.com/lukechilds/a83e1d7127b78fef38c2914c4ececc3c 3006 | get_latest_release() { 3007 | # Sanity check 3008 | if [ "$#" -lt 1 ]; then 3009 | debug_print exit "Script error: The get_latest_release function expects one argument. Aborting." 3010 | fi 3011 | 3012 | curl --silent "https://api.github.com/repos/$1/releases/latest" | # Get latest release from GitHub api 3013 | grep '"tag_name":' | # Get tag line 3014 | sed -E 's/.*"([^"]+)".*/\1/' # Pluck JSON value 3015 | } 3016 | 3017 | # MARK: format_urls() 3018 | # Format some URLs for Zenity 3019 | format_urls() { 3020 | if [ "$use_zenity" -eq 1 ]; then 3021 | releases_url="$releases_url" 3022 | lug_wiki="$lug_wiki" 3023 | lug_wiki_nixos="$lug_wiki_nixos" 3024 | fi 3025 | } 3026 | 3027 | # MARK: referral_randomizer() 3028 | # Get a random Penguin's Star Citizen referral code 3029 | referral_randomizer() { 3030 | # Populate the referral codes array 3031 | referral_codes=("STAR-4TZD-6KMM" "STAR-4XM2-VM99" "STAR-2NPY-FCR2" "STAR-T9Z9-7W6P" "STAR-VLBF-W2QR" "STAR-BYR6-YHMF" "STAR-3X2H-VZMX" "STAR-BRWN-FB9T" "STAR-FG6Y-N4Q4" "STAR-VLD6-VZRG" "STAR-T9KF-LV77" "STAR-4XHB-R7RF" "STAR-9NVF-MRN7" "STAR-3Q4W-9TC3" "STAR-3SBK-7QTT" "STAR-XFBT-9TTK" "STAR-F3H9-YPHN" "STAR-BYK6-RCCL" "STAR-XCKH-W6T7" "STAR-H292-39WK" "STAR-ZRT5-PJB7" "STAR-GMBP-SH9Y" "STAR-PLWB-LMFY" "STAR-TNZN-H4ZT" "STAR-T5G5-L2GJ" "STAR-6TPV-7QH2" "STAR-7ZFS-PK2L" "STAR-SRQN-43TB" "STAR-9TDG-D4H9" "STAR-BPH3-THJC" "STAR-HL3M-R5KC" "STAR-GBS5-LTVB" "STAR-CJ3Y-KZZ4" "STAR-5GRM-7HBY" "STAR-G2GX-Y2QJ" "STAR-YWY3-H4XX" "STAR-6VGM-PTKC" "STAR-T6MZ-QFHX" "STAR-T2K6-LXFW" "STAR-XN25-9CJJ" "STAR-47V3-4QGB" "STAR-YD4Z-TQZV" "STAR-XLN7-9XNJ" "STAR-N62T-2R39" "STAR-3S3D-9HXQ" "STAR-TRZF-NMCV" "STAR-TLLJ-SMG4" "STAR-MFT6-Q44H" "STAR-TZX2-TPWF" "STAR-WCHN-4ZMX" "STAR-2GHY-WB4F" "STAR-KLM2-R4SX" "STAR-RYXQ-PBZB" "STAR-BSTC-NQPW" "STAR-X32P-J2NS" "STAR-9DMZ-CXWW" "STAR-ZDC2-TDP9" "STAR-J3PJ-RH2K" "STAR-Q6QW-5CC4" "STAR-FLVX-2KGT" "STAR-FTFR-JN47") 3032 | # Pick a random array element. Scale a floating point number for 3033 | # a more random distribution than simply calling RANDOM 3034 | random_code="${referral_codes[$(awk '{srand($2); print int(rand()*$1)}' <<< "${#referral_codes[@]} $RANDOM")]}" 3035 | 3036 | message info "Your random Penguin's referral code is:\n\n$random_code\n\nThank you!" 3037 | } 3038 | 3039 | # MARK: quit() 3040 | quit() { 3041 | exit 0 3042 | } 3043 | 3044 | 3045 | ############################################################################ 3046 | ######## MAIN ############################################################## 3047 | ############################################################################ 3048 | 3049 | # MARK: MAIN 3050 | # Zenity availability/version check 3051 | use_zenity=0 3052 | # Initialize some variables 3053 | menu_option_height="0" 3054 | menu_text_height_zenity4="0" 3055 | menu_height_max="0" 3056 | if [ -x "$(command -v zenity)" ]; then 3057 | if zenity --version >/dev/null; then 3058 | use_zenity=1 3059 | zenity_version="$(zenity --version)" 3060 | 3061 | # Zenity 4.0.0 uses libadwaita, which changes fonts/sizing 3062 | # Add pixels to each menu option depending on the version of zenity in use 3063 | # used to dynamically determine the height of menus 3064 | # menu_text_height_zenity4 = Add extra pixels to the menu title/description height for libadwaita bigness 3065 | if [ "$zenity_version" != "4.0.0" ] && 3066 | [ "$zenity_version" = "$(printf "%s\n%s" "$zenity_version" "4.0.0" | sort -V | head -n1)" ]; then 3067 | # zenity 3.x menu sizing 3068 | menu_option_height="26" 3069 | menu_text_height_zenity4="0" 3070 | menu_height_max="400" 3071 | else 3072 | # zenity 4.x+ menu sizing 3073 | menu_option_height="26" 3074 | menu_text_height_zenity4="0" 3075 | menu_height_max="800" 3076 | fi 3077 | else 3078 | # Zenity is broken 3079 | debug_print continue "Zenity failed to start. Falling back to terminal menus" 3080 | fi 3081 | fi 3082 | 3083 | # Check if this is the user's first run of the Helper 3084 | if [ -f "$conf_dir/$conf_subdir/$firstrun_conf" ]; then 3085 | is_firstrun="$(cat "$conf_dir/$conf_subdir/$firstrun_conf")" 3086 | fi 3087 | if [ "$is_firstrun" != "false" ]; then 3088 | is_firstrun="true" 3089 | fi 3090 | 3091 | # Format some URLs for Zenity if the Helper was not invoked with command-line arguments (handle those separately below) 3092 | if [ "$#" -eq 0 ]; then 3093 | format_urls 3094 | fi 3095 | 3096 | # Check if a newer verison of the script is available 3097 | latest_version="$(get_latest_release "$repo")" 3098 | 3099 | # Sort the versions and check if the installed Helper is smaller 3100 | if [ "$latest_version" != "$current_version" ] && 3101 | [ "$current_version" = "$(printf "%s\n%s" "$current_version" "$latest_version" | sort -V | head -n1)" ]; then 3102 | 3103 | message info "The latest version of the LUG Helper is $latest_version\nYou are using $current_version\n\nYou can download new releases here:\n$releases_url" 3104 | fi 3105 | 3106 | # MARK: Cmdline arguments 3107 | # If invoked with command line arguments, process them and exit 3108 | if [ "$#" -gt 0 ]; then 3109 | while [ "$#" -gt 0 ] 3110 | do 3111 | # Victor_Tramp expects the spanish inquisition. 3112 | case "$1" in 3113 | --help | -h ) 3114 | printf "Star Citizen Linux Users Group Helper Script 3115 | Usage: lug-helper 3116 | -p, --preflight-check Run system optimization checks 3117 | -i, --install Install Star Citizen 3118 | -m, --manage-runners Install or remove Wine runners 3119 | -k, --manage-dxvk Manage DXVK in the Wine prefix 3120 | -u, --update-launch-script Update/Repair the game launch script 3121 | -e, --edit-launch-script Edit the game launch script 3122 | -c, --wine-config Launch winecfg for the game's prefix 3123 | -j, --wine-controllers Launch Wine controllers configuration 3124 | -l, --update-rsi-launcher Update/Re-install RSI Launcher 3125 | -r, --get-referral Get a random LUG member's referral code 3126 | -d, --show-directories Show all Star Citizen and Helper directories 3127 | -w, --show-wiki Show the LUG Wiki 3128 | -x, --reset-helper Delete saved lug-helper configs 3129 | -g, --no-gui Use terminal menus instead of a Zenity GUI 3130 | -v, --version Display version info and exit 3131 | " 3132 | exit 0 3133 | ;; 3134 | --preflight-check | -p ) 3135 | cargs+=("preflight_check") 3136 | ;; 3137 | --install | -i ) 3138 | cargs+=("install_game") 3139 | ;; 3140 | --manage-runners | -m ) 3141 | cargs+=("runner_manage") 3142 | ;; 3143 | --manage-dxvk | -k ) 3144 | cargs+=("dxvk_menu") 3145 | ;; 3146 | --update-launch-script | -u ) 3147 | cargs+=("update_launch_script") 3148 | ;; 3149 | --edit-launch-script | -e ) 3150 | cargs+=("edit_launch_script") 3151 | ;; 3152 | --wine-config | -c ) 3153 | cargs+=("call_launch_script config") 3154 | ;; 3155 | --wine-controllers | -j ) 3156 | cargs+=("call_launch_script controllers") 3157 | ;; 3158 | --update-rsi-launcher | -l ) 3159 | cargs+=("reinstall_rsi_launcher") 3160 | ;; 3161 | --get-referral | -r ) 3162 | cargs+=("referral_randomizer") 3163 | ;; 3164 | --show-directories | -d ) 3165 | cargs+=("display_dirs") 3166 | ;; 3167 | --show-wiki | -w ) 3168 | cargs+=("display_wiki") 3169 | ;; 3170 | --reset-helper | -x ) 3171 | cargs+=("reset_helper") 3172 | ;; 3173 | --no-gui | -g ) 3174 | # If zenity is unavailable, it has already been set to 0 3175 | # and this setting has no effect 3176 | use_zenity=0 3177 | ;; 3178 | --version | -v ) 3179 | printf "LUG Helper %s\n" "$current_version" 3180 | exit 0 3181 | ;; 3182 | * ) 3183 | printf "%s: Invalid option '%s'\n" "$0" "$1" 3184 | exit 0 3185 | ;; 3186 | esac 3187 | # Shift forward to the next argument and loop again 3188 | shift 3189 | done 3190 | 3191 | # Format some URLs for Zenity 3192 | format_urls 3193 | 3194 | # Call the requested functions and exit 3195 | if [ "${#cargs[@]}" -gt 0 ]; then 3196 | cmd_line="true" 3197 | for (( x=0; x<"${#cargs[@]}"; x++ )); do 3198 | ${cargs[x]} 3199 | done 3200 | exit 0 3201 | fi 3202 | fi 3203 | 3204 | # Detect if NixOS is being used and direct user to wiki 3205 | if (grep -q '^NAME=NixOS' /etc/os-release 2> /dev/null ); then 3206 | message info "It looks like you're using NixOS\nPlease see our wiki for NixOS-specific configuration requirements:\n\n$lug_wiki_nixos" 3207 | fi 3208 | 3209 | # Set up the main menu heading 3210 | menu_heading_zenity="Greetings, Space Penguin!\n\nThis tool is provided by the Star Citizen Linux Users Group\nFor help, see our wiki: $lug_wiki" 3211 | menu_heading_terminal="Greetings, Space Penguin!\n\nThis tool is provided by the Star Citizen Linux Users Group\nFor help, see our wiki: $lug_wiki" 3212 | 3213 | # MARK: First Run 3214 | # First run 3215 | firstrun_message="It looks like this is your first time running the Helper\n\nWould you like to run the Preflight Check and install Star Citizen?" 3216 | if [ "$use_zenity" -eq 1 ]; then 3217 | firstrun_message="$menu_heading_zenity\n\n$firstrun_message" 3218 | else 3219 | firstrun_message="$menu_heading_terminal\n\n$firstrun_message" 3220 | fi 3221 | if [ "$is_firstrun" = "true" ]; then 3222 | if message question "$firstrun_message"; then 3223 | install_game 3224 | fi 3225 | # Store the first run state for subsequent launches 3226 | if [ ! -d "$conf_dir/$conf_subdir" ]; then 3227 | mkdir -p "$conf_dir/$conf_subdir" 3228 | fi 3229 | echo "false" > "$conf_dir/$conf_subdir/$firstrun_conf" 3230 | fi 3231 | 3232 | # MARK: Main Menu 3233 | # Loop the main menu until the user selects quit 3234 | while true; do 3235 | # Configure the menu 3236 | menu_text_zenity="$menu_heading_zenity" 3237 | menu_text_terminal="$menu_heading_terminal" 3238 | menu_text_height="300" 3239 | menu_type="radiolist" 3240 | 3241 | # Configure the menu options 3242 | preflight_msg="Preflight Check (System Optimization)" 3243 | install_msg_wine="Install Star Citizen" 3244 | runners_msg_wine="Manage Wine Runners" 3245 | dxvk_msg_wine="Manage DXVK" 3246 | maintenance_msg="Maintenance and Troubleshooting" 3247 | randomizer_msg="Get a random Penguin's Star Citizen referral code" 3248 | quit_msg="Quit" 3249 | 3250 | # Set the options to be displayed in the menu 3251 | menu_options=("$preflight_msg" "$install_msg_wine" "$runners_msg_wine" "$dxvk_msg_wine" "$maintenance_msg" "$randomizer_msg" "$quit_msg") 3252 | # Set the corresponding functions to be called for each of the options 3253 | menu_actions=("preflight_check" "install_game" "runner_manage" "dxvk_menu" "maintenance_menu" "referral_randomizer" "quit") 3254 | 3255 | # Calculate the total height the menu should be 3256 | # menu_option_height = pixels per menu option 3257 | # #menu_options[@] = number of menu options 3258 | # menu_text_height = height of the title/description text 3259 | # menu_text_height_zenity4 = added title/description height for libadwaita bigness 3260 | menu_height="$(($menu_option_height * ${#menu_options[@]} + $menu_text_height + $menu_text_height_zenity4))" 3261 | 3262 | # Set the label for the cancel button 3263 | cancel_label="Quit" 3264 | 3265 | # Call the menu function. It will use the options as configured above 3266 | menu 3267 | done 3268 | --------------------------------------------------------------------------------