forked from acquitelol/rosiecord
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
308 lines (308 loc) · 19.6 KB
/
index.js
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
304
305
306
307
308
/**
* -~-~-~ Main Rosiecord Patch Script -~-~-~
* Build to Patch Enmity, Icons, Fonts, and Other Tweaks into the Base Discord IPA.
* Created by Rosie "Acquite" on Thursday 22nd December 2022.
* Last updated by Rosie "Acquite" on Thursday 6th July 2023.
* Required Dependencies: plutil, local-dirs[Fonts, Packs, Icons, Patches{Required, Optional}], Azule, Theos, NodeJS (run `npm i`)
*/
import fs from 'fs';
import { Colors, Shell, States, Constants, Divider } from './constants.js';
class Main extends Colors {
constructor(type, outputType) {
super();
this.type = type;
this.outputType = outputType;
}
format(item, type) {
return `${this.PINK}[${this.CYAN}${item.state === 'pending'
? '*'
: item.state === 'success'
? '+'
: '-'}${this.PINK}]${item.state === 'pending'
? this.CYAN
: item.state === 'success'
? this.GREEN
: this.RED} ${item.name} ${type}`;
}
load(path) {
return JSON.parse(fs.readFileSync(path).toString());
}
async get(item) {
const ipaArray = [];
await Shell.run(item, (_, stdout) => {
stdout.split('\n').filter(ipa => ipa !== "").forEach(ipa => ipaArray.push(ipa));
});
return ipaArray;
}
async logCurrentState(ipaStates, type) {
const defaultStates = ipaStates.map(ipaItem => this.format(ipaItem, this.type));
const stdout = `${this.BLUE}${type}: [${this.PINK}${defaultStates.join(`${this.CYAN}, ${this.PINK}`)}${this.BLUE}]${this.ENDC}\r`;
await Shell.write('\r'.repeat(stdout.length));
await Shell.write(stdout);
}
async Main(callable) {
await callable();
await Shell.write(`\n${this.GREEN}All ${this.PINK}Base IPAs${this.GREEN} have been successfully packaged with ${this.PINK}${this.outputType}${this.GREEN}. ${this.CYAN}Continuing to the next step...${this.ENDC}\n`);
}
}
class State {
constructor(state, name) {
this.state = state;
this.name = name;
}
}
class Inject extends Colors {
constructor(type, outputType, hasClean, getParam) {
super();
this.type = type;
this.outputType = outputType;
this.hasClean = hasClean;
this.getParam = getParam;
}
async run(M, callable) {
const requiredPatches = (await M.get(this.getParam)).filter(item => {
if (!this.hasClean)
return true;
return process.argv[2] == "k2genmity"
? (item !== (item.includes("Development")
? "Enmity.Development.Official.deb"
: "Enmity.deb"))
: (item !== "K2genmity.Development.deb");
});
const stdoutIpas = await M.get(`ls Dist`);
const tweakStates = requiredPatches.map(ipa => new State('pending', ipa));
const S = new States();
await Shell.write(`${this.CYAN}Injecting ${this.PINK}${this.outputType}${this.CYAN} into ${this.PINK}Base IPAs${this.CYAN}. If ${this.hasClean ? "a " : ""}${this.PINK}${this.type}${this.CYAN} has been ${this.GREEN}successfully${this.CYAN} injected in all IPAs, it will look like this: ${this.BLUE}"${S.SUCCESS} ${this.hasClean ? `Example ` : ""}${this.type}${this.BLUE}"\n`);
await M.logCurrentState(tweakStates, `Injected ${this.type}${this.hasClean ? "s" : ""}`);
for (const i in requiredPatches) {
let patched = 0;
for (const j in stdoutIpas) {
const ipaName = stdoutIpas[j].split('.')[0];
const patchName = requiredPatches[i];
await callable(ipaName, patchName);
patched++;
const isComplete = patched === stdoutIpas.length;
// @ts-ignore
isComplete ? tweakStates.find(patch => patch.name === patchName).state = 'success' : null;
isComplete ? await M.logCurrentState(tweakStates, `Injected ${this.type}s`) : null;
}
}
}
}
const EntryPoint = async (index, ipaName) => {
switch (index) {
case 0: {
const M = new Main('IPA', "Different Fonts");
await M.Main(async () => {
var _a;
const ipaList = ['GGSans', ...await M.get('ls Fonts/ttf')];
const ipaStates = ipaList.map(ipa => new State('pending', ipa));
await Shell.write(`${M.CYAN}Packaging the ${M.PINK}Base IPAs${M.CYAN}. If an ${M.PINK}IPA${M.CYAN} has been ${M.GREEN}successfully${M.CYAN} packaged, it will look like this: ${M.BLUE}"${M.PINK}[${M.CYAN}+${M.PINK}]${M.GREEN} Example IPA${M.BLUE}"\n`);
await M.logCurrentState(ipaStates, "Base Font IPAs");
await Shell.runSilently(`zip -q -r Dist/Rosiecord-${ipaName.split("_")[1]}_GGSans-Font.ipa Payload & wait $!`, async (stderr, _) => {
ipaStates[0].state = stderr ? 'failure' : 'success';
await M.logCurrentState(ipaStates, 'Base Font IPAs');
});
await Shell.runSilently(`rm -rf Payload & wait $!`);
for (const Font of ipaList.filter(ipa => ipa !== 'GGSans')) {
await Shell.runSilently(`unzip -qq -o Dist/Rosiecord-${ipaName.split("_")[1]}_GGSans-Font.ipa`);
await Shell.runSilently(`cp -rf Fonts/ttf/${Font}/* Payload/Discord.app/`);
await Shell.runSilently(`zip -q -r Dist/Rosiecord-${ipaName.split("_")[1]}_${Font}-Font.ipa Payload & wait $!`);
await Shell.runSilently(`rm -rf Payload & wait $!`);
((_a = ipaStates.find(ipa => ipa.name === Font)) !== null && _a !== void 0 ? _a : { state: null }).state = 'success';
await M.logCurrentState(ipaStates, "Base Font IPAs");
}
});
break;
}
case 1: {
const M = new Main('Tweak', 'Required Tweaks');
await M.Main(async () => {
await new Inject("Tweak", "all Required Tweaks", true, 'ls Patches/Required').run(M, async (ipaName, patchName) => {
await Shell.run(`Azule/azule -U -i Dist/${ipaName}.ipa -o Dist -f $PWD/Patches/Required/${patchName} -v & wait $!`);
await Shell.run(`mv Dist/${ipaName}+${patchName}.ipa Dist/${ipaName}.ipa`);
});
});
break;
}
case 2: {
const M = new Main('Pack', 'Icon Packs');
await M.Main(async () => {
await new Inject("Pack", "all Icon Packs", true, 'ls Packs').run(M, async (ipaName, patchName) => {
await Shell.run(`unzip -qq -o Dist/${ipaName}.ipa`);
await Shell.runSilently(`cp -rf Packs/${patchName}/Assets.car Payload/Discord.app/`);
await Shell.runSilently(`cp -rf Packs/${patchName}/* Payload/Discord.app/assets/`);
await Shell.runSilently(`rm -f Payload/Discord.app/assets/Assets.car`);
await Shell.runSilently(`zip -q -r Dist/${ipaName}+${patchName}_Icons.ipa Payload`);
await Shell.runSilently(`rm -rf Payload`);
});
});
break;
}
case 3: {
const M = new Main('Tweak', "Optional Variations");
await M.Main(async () => {
await new Inject("Flowercord", 'Flowercord', false, "ls Patches/Optional").run(M, async (ipaName, patchName) => {
await Shell.run(`Azule/azule -U -i Dist/${ipaName}.ipa -o Dist -f $PWD/Patches/Optional/${patchName} -v & wait $!`);
await Shell.run(`mv Dist/${ipaName}+${patchName}.ipa Dist/${ipaName}+Flowercord.ipa`);
});
});
break;
}
default:
break;
}
};
class Initialiser extends States {
async PackageTweak(tweakName, cmd, permanentability) {
await Shell.write(`${this.PENDING}${this.PINK} Packaging ${this.CYAN}"${this.PINK}${tweakName}${this.CYAN}"${this.PINK}. ${this.BLUE}This may take a while...${this.ENDC}\r`);
process.chdir(`Tweaks/${tweakName}`);
let errored;
await Shell.runSilently(`rm -rf packages`);
await Shell.run(cmd, async (stderr, stdout) => {
await Shell.write(stderr
? `${this.FAILURE} An error occured while packaging ${this.PINK}"${this.CYAN}${tweakName}${this.PINK}"${this.RED}.${this.ENDC}\n${stderr}`
: `${this.SUCCESS} Successfully packaged ${this.PINK}"${this.CYAN}${tweakName}${this.PINK}".${this.GREEN} Moving into ${this.PINK}"${this.CYAN}./Patches/${permanentability}/${this.PINK}"${this.GREEN}...${this.ENDC}\n`);
errored = stderr;
});
if (errored)
return process.chdir("../..");
process.chdir("packages");
await Shell.write(`${this.PENDING}${this.PINK} Moving ${this.CYAN}"${this.PINK}${tweakName}${this.CYAN}"${this.PINK} into ${this.PINK}"${this.CYAN}./Patches/${permanentability}/${this.PINK}"${this.PINK}...${this.ENDC}\r`);
await Shell.run(`mv $(find . -name "*.deb") ../../../Patches/${permanentability}/${tweakName}.deb`, async (stderr, stdout) => {
await Shell.write(stderr
? `${this.FAILURE} An error occured while moving ${this.PINK}"${this.CYAN}${tweakName}${this.PINK}"${this.RED} into ${this.PINK}"${this.CYAN}./Patches/${permanentability}/${this.PINK}"${this.RED}.${this.ENDC}\n`
: `${this.SUCCESS} Successfully moved ${this.PINK}"${this.CYAN}${tweakName}${this.PINK}"${this.GREEN} into ${this.PINK}"${this.CYAN}./Patches/${permanentability}/${this.PINK}"${this.GREEN}.${this.ENDC}\n`);
});
process.chdir('../../..');
}
async InitializeAzule() {
fs.existsSync('Azule')
? await Shell.write(`${this.SUCCESS}${this.PINK} Azule${this.GREEN} already exists in ${this.PINK}"${this.CYAN}./${this.PINK}"${this.GREEN}...${this.ENDC}\n`)
: await (async () => {
await Shell.write(`${this.PENDING}${this.PINK} Installing ${this.CYAN}"Azule"${this.PINK}. ${this.BLUE}This may take a while...${this.ENDC}\r`);
await Shell.run(`git clone https://github.com/Al4ise/Azule/ & wait $!`, async (stderr, stdout) => {
await Shell.write(stderr
? `${this.FAILURE} An error occured while installing ${this.PINK}"${this.CYAN}Azule${this.PINK}"${this.RED}.${this.ENDC}\n`
: `${this.SUCCESS} Successfully installed ${this.PINK}"${this.CYAN}Azule${this.PINK}"${this.GREEN} into ${this.PINK}"${this.CYAN}./${this.PINK}"${this.GREEN}.${this.ENDC}\n`);
await Shell.write(stdout);
});
})();
}
}
const main = async () => {
const START_TIME = Date.now();
const M = new Main("Entry", "Entry in file");
const S = new States();
const D = new Divider(25);
const Init = new Initialiser();
const { version } = M.load('./package.json');
const IPA_LINK = Constants.IPA_FETCH_LINK;
// Gets just the IPA Name, "Discord_158" or whatever
const [, IPA_VERSION] = IPA_LINK.match(/.*Discord(.*)\..*\.ipa/);
const IPA_NAME = `Discord${IPA_VERSION.startsWith('_') ? IPA_VERSION : `_${IPA_VERSION}`}`;
await D.logDivider();
await Shell.write(`${M.PINK} █▀█ █▀█ █▀ █ █▀▀ █▀▀ █▀█ █▀█ █▀▄\n${M.CYAN} █▀▄ █▄█ ▄█ █ ██▄ █▄▄ █▄█ █▀▄ █▄▀${M.ENDC}\n`);
await Shell.write(`${M.PINK}A project written by ${M.CYAN}Rosie${M.BLUE}/${M.CYAN}Acquite${M.ENDC}\n`);
await Shell.write(`${M.BLUE}This patcher is on version ${M.PINK}"${M.CYAN}${version}${M.PINK}"${M.ENDC}\n`);
await Shell.write(`${M.BLUE}Patching Discord Version ${M.PINK}"${M.CYAN}${IPA_NAME}${M.PINK}"${M.ENDC}\n`);
await Shell.write(`${M.BLUE}Patching Link ${M.PINK}"${M.CYAN}${IPA_LINK}${M.PINK}"${M.ENDC}\n`);
await Shell.write(`${S.PENDING}${M.RED} Running ${M.PINK}Legacy${M.RED} Patcher.${M.ENDC}\n`);
await D.logDivider();
await Shell.write(`${S.PENDING}${M.CYAN} Clearing existing ${M.PINK}\"IPAs\"${M.CYAN} in ${M.PINK}\"./Dist\".${M.ENDC}\r`);
await Shell.runSilently(`mkdir -p Dist/ & wait $!; rm -rf Dist/* & wait $!; rm -rf Payload & wait $!`, (stderr) => {
Shell.write(stderr
? `${S.FAILURE} An error occurred while clearing existing ${M.PINK}\"IPAs\" in ${M.PINK}\"./Dist\".${M.ENDC}\n`
: `${S.SUCCESS} Successfully cleared existing ${M.PINK}\"IPAs\"${M.GREEN} in ${M.PINK}\"./Dist\".${M.ENDC}\n`);
});
await Shell.write(`${S.PENDING}${M.CYAN} Downloading ${M.PINK}\"${IPA_NAME}.ipa\"${M.CYAN} into ${M.PINK}\"./Ipas\".${M.ENDC}\r`);
await Shell.runSilently(`mkdir Ipas; rm -rf Ipas/* & wait $!;`);
await Shell.runSilently(`curl ${IPA_LINK} -o Ipas/${IPA_NAME}.ipa`, (stderr) => {
Shell.write(stderr
? `${S.FAILURE} An error occurred while downloading ${M.PINK}\"${IPA_NAME}.ipa\" into ${M.PINK}\"./Ipas\".${M.ENDC}\n`
: `${S.SUCCESS} Successfully downloaded ${M.PINK}\"${IPA_NAME}.ipa\"${M.GREEN} into ${M.PINK}\"./Ipas\".${M.ENDC}\n`);
});
const IPA_DIR = `Ipas/${IPA_NAME}.ipa`;
await Shell.write(`${S.SUCCESS} Directory of IPA: ${M.PINK}${IPA_DIR}${M.ENDC}\n`);
await Shell.write(`${S.PENDING}${M.CYAN} Unzipping ${M.PINK}\"${IPA_DIR}\"${M.CYAN} into ${M.PINK}\"./Payload\".${M.ENDC}\r`);
await Shell.runSilently(`unzip -o ${IPA_DIR} & wait $!`, (stderr) => {
Shell.write(stderr
? `${S.FAILURE} An error occurred while unzipping ${M.PINK}\"${IPA_DIR}\".${M.ENDC}\n`
: `${S.SUCCESS} Successfully unzipped ${M.PINK}\"${IPA_DIR}\"${M.GREEN} into ${M.PINK}\"./Payload\".${M.ENDC}\n`);
});
await D.logDivider();
const MAIN_PLIST = `Payload/Discord.app/Info.plist`;
const name = "Rosiecord";
await Shell.write(`${S.PENDING}${M.CYAN} Replacing Discord's Name To ${M.PINK}\"${name}\".${M.ENDC}\r`);
await Shell.runSilently(`plutil -replace CFBundleName -string "${name}" ${MAIN_PLIST} & wait $!`);
await Shell.runSilently(`plutil -replace CFBundleDisplayName -string "Rosiecord" ${MAIN_PLIST} & wait $!`, (stderr) => {
Shell.write(stderr
? `${S.FAILURE} An error occurred while Replacing ${M.PINK}\"Discord's Name\".${M.ENDC}\n`
: `${S.SUCCESS} Successfully Replaced ${M.PINK}\"Discord's Name\"${M.GREEN} to ${M.PINK}\"${name}\".${M.ENDC}\n`);
});
await Shell.write(`${S.PENDING}${M.CYAN} Patching Discord's URL Scheme To ${M.PINK}\"Add Enmity's URL Handler\".${M.ENDC}\r`);
await Shell.runSilently(`plutil -insert CFBundleURLTypes.1 -xml "<dict><key>CFBundleURLName</key><string>Enmity</string><key>CFBundleURLSchemes</key><array><string>enmity</string></array></dict>" ${MAIN_PLIST} & wait $!`, (stderr) => {
Shell.write(stderr
? `${S.FAILURE} An error occurred while Patching ${M.PINK}\"Discord's URL Scheme\".${M.ENDC}\n`
: `${S.SUCCESS} Successfully Patched ${M.PINK}\"Discord's URL Scheme\"${M.GREEN} to ${M.PINK}\./Add Enmity's URL Handler\".${M.ENDC}\n`);
});
await Shell.write(`${S.PENDING}${M.CYAN} Removing Discord's ${M.PINK}\"Supported Device Limits\"${M.CYAN}.${M.ENDC}\r`);
await Shell.runSilently(`plutil -remove UISupportedDevices ${MAIN_PLIST} & wait $!`, (stderr) => {
Shell.write(stderr
? `${S.FAILURE} An error occurred while removing Discord's ${M.PINK}\"Supported Device Limits\"${M.RED}.${M.ENDC}\n`
: `${S.SUCCESS} Successfully Removed Discord's ${M.PINK}\"Supported Device Limits\"${M.GREEN}.${M.ENDC}\n`);
});
// await Shell.write(`${S.PENDING}${M.CYAN} Patching ${M.PINK}\"Discord's Icons\" ${M.CYAN} to ${M.PINK}\"Enmity's Icons\"${M.CYAN}.${M.ENDC}\r`);
// await Shell.runSilently(`cp -rf Icons/* Payload/Discord.app/`)
// await Shell.runSilently(`plutil -replace CFBundleIcons -xml "<dict><key>CFBundlePrimaryIcon</key><dict><key>CFBundleIconFiles</key><array><string>EnmityIcon60x60</string></array><key>CFBundleIconName</key><string>EnmityIcon</string></dict></dict>" ${MAIN_PLIST} & wait $!`)
// await Shell.runSilently(`plutil -replace CFBundleIcons~ipad -xml "<dict><key>CFBundlePrimaryIcon</key><dict><key>CFBundleIconFiles</key><array><string>EnmityIcon60x60</string><string>EnmityIcon76x76</string></array><key>CFBundleIconName</key><string>EnmityIcon</string></dict></dict>" ${MAIN_PLIST} & wait $!`, (stderr) => {
// Shell.write(stderr
// ? `${S.FAILURE} An error occurred while removing Discord's ${M.PINK}\"Supported Device Limits\"${M.RED}.${M.ENDC}\n`
// : `${S.SUCCESS} Successfully Patched ${M.PINK}\"Discord's Icons\"${M.GREEN} to ${M.PINK}\"Enmity's Icons\"${M.GREEN}.${M.ENDC}\n`
// )
// })
await Shell.write(`${S.PENDING}${M.CYAN} Enabling ${M.PINK}\"UISupportsDocumentBrowser\"${M.CYAN} and ${M.PINK}\"UIFileSharingEnabled\"${M.CYAN}.${M.ENDC}\r`);
await Shell.run(`plutil -replace UISupportsDocumentBrowser -bool true ${MAIN_PLIST} & wait $!`);
await Shell.run(`plutil -replace UIFileSharingEnabled -bool true ${MAIN_PLIST} & wait $!`, (stderr) => {
Shell.write(stderr
? `${S.FAILURE} An error occurred while Enabling ${M.PINK}\"UISupportsDocumentBrowser\"${M.RED} and ${M.PINK}\"UIFileSharingEnabled\"${M.RED}.${M.ENDC}\n`
: `${S.SUCCESS} Successfully Enabled ${M.PINK}\"UISupportsDocumentBrowser\"${M.GREEN} and ${M.PINK}\"UIFileSharingEnabled\"${M.GREEN}.${M.ENDC}\n`);
});
if (process.argv[2] == "custom") {
await Shell.write(`${S.PENDING}${M.CYAN} Setting bundle ID to ${M.PINK}\"com.rosie.rosiecord\"${M.CYAN}...${M.ENDC}\r`);
await Shell.run(`plutil -replace CFBundleIdentifier -string "com.rosie.rosiecord" ${MAIN_PLIST} & wait $!`, (stderr) => {
Shell.write(stderr
? `${S.FAILURE} An error occurred while settting bundle ID to ${M.PINK}\"com.rosie.rosiecord\"${M.RED}.${M.ENDC}\n`
: `${S.SUCCESS} Successfully set bundle ID to ${M.PINK}\"com.rosie.rosiecord\"${M.CYAN}.${M.ENDC}\n`);
});
}
await D.logDivider();
await Init.PackageTweak("Flowercord", "gmake package", "Optional");
await Init.PackageTweak("SideloadFix", "gmake package", "Required");
await Init.InitializeAzule();
await D.logDivider();
for (let i = 0; i <= 3; i++) {
await EntryPoint(i, IPA_NAME);
await D.logDivider();
// await new Promise((resolve) => setTimeout(() => resolve(), 2000));
}
// await Shell.write(`${S.PENDING}${M.CYAN} Signing ${M.PINK}\"Discord\"${M.CYAN} and signing ${M.PINK}\"Frameworks\"${M.CYAN}.${M.ENDC}\r`);
// const errors: any[] = [];
// for (const Ipa of await M.get(`ls Dist`)) {
// await Shell.run(`unzip -qq -o Dist/${Ipa}`, (stderr) => stderr && errors.push(stderr));
// await Shell.run(`ldid -S Payload/Discord.app/Discord`, (stderr) => stderr && errors.push(stderr))
// for (const Framework of await M.get('ls Payload/Discord.app/Frameworks/*.dylib')) {
// await Shell.run(`ldid -S ${Framework}`, (stderr) => stderr && errors.push(stderr))
// }
// await Shell.runSilently(`zip -q -r Dist/${Ipa} Payload & wait $!`)
// await Shell.runSilently(`rm -rf Payload & wait $!`)
// }
// Shell.write(errors.length > 0
// ? `${S.FAILURE} An error occurred while signing ${M.PINK}\"Discord and Frameworks\"${M.RED}.${M.ENDC}\n`
// : `${S.SUCCESS} Successfully signed ${M.PINK}\"Discord\"${M.GREEN} and signed ${M.PINK}\"Frameworks\"${M.GREEN}.${M.ENDC}\n`
// )
// errors.length > 0 && Shell.write(errors);
const END_TIME = Date.now();
await Shell.write(`${S.SUCCESS} Successfully built ${M.PINK}Rosiecord${M.GREEN} in ${M.CYAN}${(END_TIME - START_TIME) / 1000} seconds${M.GREEN}.`);
};
await main();