-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathinstaller.go
304 lines (237 loc) · 8.58 KB
/
installer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
package main
import (
"errors"
"os"
"os/user"
"path/filepath"
"runtime"
"strings"
"encoding/json"
gopsutil "github.com/shirou/gopsutil/process"
"github.com/sqweek/dialog"
)
type AsarInstallState int
const (
AsarInstallStateNone AsarInstallState = iota
AsarInstallStateShelter
AsarInstallStateShelterArchLinux
AsarInstallStateKernel
AsarInstallStateOtherMod
)
func isDiscordRunning() bool {
procs, err := gopsutil.Processes()
if err != nil { return false }
for _, p := range procs {
name, err := p.Name()
if err == nil && strings.Contains(strings.ToLower(name), "discord") {
return true
}
}
return false
}
func mayNeedElevation() bool {
user, err := user.Current()
if err != nil {
panic(err)
}
return user.Username != "root" && runtime.GOOS != "windows" && runtime.GOOS != "darwin";
}
/* func canElevateWithPolkit() bool {
return runtime.GOOS != "windows" && runtime.GOOS != "darwin"
} */
// checks if the traditional shelter installer exists
func checkTraditionalInstall(instance DiscordInstance) AsarInstallState {
// see if app/package.json exists and read it
pkgJson, err := os.ReadFile(filepath.Join(instance.PathRes, "app/package.json"))
if err != nil {
// there's no app folder.
// Either your mod messed with the asar vencord style, or you're stock
// Hopefully asar client mods don't cause issues!!!!! I sure hope they don't!!! that'd be bad!!!!
return AsarInstallStateNone
}
// first, check for the arch shelter package
pacmanProc, err := os.StartProcess("/usr/bin/pacman", []string{ "pacman", "-Q", "shelter" }, &os.ProcAttr{})
if err == nil {
// we are on arch!
state, err := pacmanProc.Wait()
// idk what would error here but im just gonna silently continue
if err == nil {
if state.Success() {
// we have the shelter arch package!
return AsarInstallStateShelterArchLinux
}
}
}
_, err = os.Stat(filepath.Join(instance.PathRes, "original.asar"));
originalAsarExists := err == nil
// assume we have shelter installed iff package.json has { name: "shelter" },
// and original.asar exists
if originalAsarExists && strings.Contains(string(pkgJson), "\"name\": \"shelter\"") {
return AsarInstallStateShelter
}
// test for kernel
if strings.Contains(string(pkgJson), "\"name\": \"kernel\"") {
return AsarInstallStateKernel
}
return AsarInstallStateOtherMod
}
// the removing flag does not change behaviour, but changes the error reporting used.
func uninstallTraditionalShelter(instance DiscordInstance, removing bool) bool {
// if we actually, don't want to uninstall shelter, just quietly report success
switch checkTraditionalInstall(instance) {
case AsarInstallStateNone:
return true
case AsarInstallStateOtherMod:
if !removing {
dialog.Message("%s", "You appear to have another client mod installed. This may cause issues with shelter.").Info()
}
return true
case AsarInstallStateKernel:
if !removing {
dialog.Message("%s", "You have Kernel installed. Please install the Kernel shelter injector, or uninstall Kernel and try again.").Error()
}
return false
case AsarInstallStateShelterArchLinux:
dialog.Message("%s", "You appear to have the Arch Linux shelter package installed. Please remove it (sudo pacman -R shelter) and try again.").Error();
return false
case AsarInstallStateShelter:
removingOrUpgrading := "upgrading"
if removing {
removingOrUpgrading = "removing"
}
// remove the app folder, put original.asar back
// we are guaranteed that these two things exist by the fact we got AsarInstallStateShelter back
// move the asar first as that way around is less likely to break stuff if the first succeeds and second fails.
var err error
// note: on macos, we need a special permission that can only be granted in system settings.
// when we attempt to perform a file operation, macos will show an instruction to the user, and fail
if mayNeedElevation() {
dialog.Message("%s %s %s", "You have an old-style shelter install, and", removingOrUpgrading, "it requires root permissions. We will now ask for your password.").Info()
cmd := "mv " + filepath.Join(instance.PathRes, "original.asar") +
" " + filepath.Join(instance.PathRes, "app.asar") +
" && rm -rf " + filepath.Join(instance.PathRes, "app")
// bad but using polkit directly is horrible so pick your poison
proc, err2 := os.StartProcess("/usr/bin/pkexec", []string{"pkexec", "sh", "-c", cmd}, &os.ProcAttr{})
err = err2
if err == nil {
state, err2 := proc.Wait()
err = err2
if err != nil && !state.Success() {
err = errors.New("pkexec process exited with failure")
}
}
} else {
err = os.Rename(filepath.Join(instance.PathRes, "original.asar"), filepath.Join(instance.PathRes, "app.asar"))
// macos sucks, so we need to double-check that this actually goddamn worked. great.
_, serr := os.Stat(filepath.Join(instance.PathRes, "app.asar"))
if serr != nil {
dialog.Message("%s", "Removing your old-style shelter installation requires the App Management permission. Please allow this in System Settings > Security & Privacy > Privacy and try again, or manually uninstall shelter.").Error()
return false
}
if err == nil {
err = os.RemoveAll(filepath.Join(instance.PathRes, "app"))
}
}
if err != nil {
dialog.Message("%s", "Failed to remove your old-style shelter installation.").Error()
return false
}
return true
default:
panic("Invalid return type from checkTraditionalInstall()")
}
}
func isUpdateUrlInJson(jsonStr []byte) bool {
var deserialized map[string] any
err := json.Unmarshal(jsonStr, &deserialized)
if err != nil {
panic(err)
}
ue1 := deserialized["UPDATE_ENDPOINT"]
ue2 := deserialized["NEW_UPDATE_ENDPOINT"]
// i will have rob pike's decapitated head on a spike for the mess that is this language
switch ue1 := ue1.(type) {
case string:
if strings.Contains(ue1, "inject.shelter.uwu.network") {
return true
}
}
switch ue2 := ue2.(type) {
case string:
if strings.Contains(ue2, "inject.shelter.uwu.network") {
return true
}
}
return false
}
func setUpdateUrlInJson(jsonStr []byte, branch string) []byte {
var deserialized map[string] any
err := json.Unmarshal(jsonStr, &deserialized)
if err != nil {
panic(err)
}
deserialized["UPDATE_ENDPOINT"] = "https://inject.shelter.uwu.network/" + branch
deserialized["NEW_UPDATE_ENDPOINT"] = "https://inject.shelter.uwu.network/" + branch + "/"
sered, err := json.MarshalIndent(deserialized, "", " ")
if err != nil {
panic(err)
}
return sered
}
func removeUpdateUrlFromJson(jsonStr []byte) []byte {
var deserialized map[string] any
err := json.Unmarshal(jsonStr, &deserialized)
if err != nil {
panic(err)
}
delete(deserialized, "UPDATE_ENDPOINT")
delete(deserialized, "NEW_UPDATE_ENDPOINT")
sered, err := json.MarshalIndent(deserialized, "", " ")
if err != nil {
panic(err)
}
return sered
}
func installShelter(instance DiscordInstance) {
// step one: uninstall traditional shelter if necessary
// this will also prompt users with errors and warnings about existing installs, and returns if we can proceed
if !uninstallTraditionalShelter(instance, false) {
return
}
// read the settings json
// note if we fail here we remove the old shelter and don't replace it, lol oopsies.
content, err := os.ReadFile(instance.PathCfg)
if err == nil {
// inject
// TODO: other branches? not sure what I want to do with respect to that.
newContent := setUpdateUrlInJson(content, "shelter")
// write
err = os.WriteFile(instance.PathCfg, newContent, 0644)
}
if err != nil {
dialog.Message("%s", "Failed to install shelter. *If* you had an old-style injector present, it has been removed.").Error()
} else {
dialog.Message("%s", "shelter has been installed successfully 🎉. Please restart Discord if it is open.").Info()
}
}
func isShelterNewInstalled(instance DiscordInstance) bool {
content, err := os.ReadFile(instance.PathCfg)
if err != nil { return false }
return isUpdateUrlInJson(content)
}
func uninstallShelter(instance DiscordInstance) {
// first, uninstall it traditionally
// will tell the user if theres an issue itself
_ = uninstallTraditionalShelter(instance, true)
// next, remove it from settings.json
content, err := os.ReadFile(instance.PathCfg)
if err == nil {
newContent := removeUpdateUrlFromJson(content)
err = os.WriteFile(instance.PathCfg, newContent, 0644)
}
if err != nil {
dialog.Message("%s", "Encountered an error while uninstalling shelter. shelter may or may not remain installed.")
} else {
dialog.Message("%s", "shelter has been uninstalled successfully.").Info()
}
}