├── 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 |
--------------------------------------------------------------------------------