first commit

This commit is contained in:
caca 2025-12-18 00:03:18 +01:00
commit 0b149cec60
7 changed files with 261 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
discord/
settings.json
*.jar
*.apk
!uber-apk-signer.jar

54
README.md Normal file
View File

@ -0,0 +1,54 @@
# discord-patcher
This patches the discord.apk for use with different backends/implementations. Edit settings.json to customise options.
# How to use this tool
- copy `example.settings.json` to `settings.json`
- edit `settings.json` as needed
- run `pip install -r requirements.txt` to install python dependencies
- download uber apk signer and place it in the folder, rename it to `uber-apk-signer.jar`
- download apktool and place it in the folder, rename it to `apktool.jar`
- run `python patcher.py` and wait for it to complete. it can take a while.
The URL for the discord APK is set as the last discord stable version using
Java source code.
This tool does not work with the React Native APK, likely because it uses Hermes
bytecode, whereas we're patching smali code (decompiled .dex files).
Icon replacement does not work currently.
Package renaming also does not work.
## Config
`secure` - if `https`/`wss` is used instead of `http`/`ws`.
`base_url` - The base URL, this is used for API routes like `/api/`.
`gateway_url` - The gateway URL, this is used for the gateway connection (via websockets).
`cdn_url` - The CDN url, for uploaded files, images, etc.
`invite_url` - The invite URL, for guild invites.
`package_name` - The package name you want the app to be under, so you can install it alongside your existing discord installation. This may take longer to compile and decompile, but it's recommended.
NOTE: This is broken and will be installed under `com.discord` no matter what the config is set to.
`original_package_name` - The original package name for the APK you're decompiling. dont touch this.
`new_package_name` - the new package name for replacement.
`old_app_name` - the old app name string, dont touch this.
`new_app_name` - the new app name
`download_url` - The download url of the apk to decompile, it's recommended to leave this alone.
`debug` - The option to enable or disable debugging options built into the discord apk. It's recommended to leave this alone.
# Contributions
Thanks to `Puyodead1` for fixing the script after months of neglect :p

BIN
assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

13
example.settings.json Normal file
View File

@ -0,0 +1,13 @@
{
"secure": true,
"base_url": "staging.fosscord.com",
"gateway_url": "staging.fosscord.com",
"cdn_url": "staging.fosscord.com",
"invite_url": "staging.fosscord.com/invite",
"original_package_name": "com.discord",
"new_package_name": "com.fosscord",
"old_app_name": "@string/discord",
"new_app_name": "Fosscord",
"download_url": "https://aliucord.com/download/discord?v=126021",
"debug": true
}

154
patcher.py Normal file
View File

@ -0,0 +1,154 @@
#!/usr/bin/python3
import typing
import os
import shutil
import subprocess
from json import load as jload
from os import chdir, path, system, unlink, walk
import urllib.request
verbose = True
stdout = subprocess.PIPE if verbose else subprocess.DEVNULL
stderr = subprocess.STDOUT if verbose else subprocess.DEVNULL
with open('settings.json') as f:
config = jload(f)
if not os.path.isfile("discord.apk"):
print("[Download] Downloading Discord APK...")
code = system(f'curl -L {config["download_url"]} -o discord.apk')
if code != 0:
print("[Download] Failed to download APK!")
raise SystemExit(code)
'''
apk_url = config["download_url"]
while True:
try:
req = urllib.request.urlopen(apk_url)
print(req.read())
req.close()
except Exception as e:
if e.code == 403:
apk_url = e.geturl()
continue
print(e)
raise SystemExit("[Download] Failed to download APK!")
'''
print("[Download] APK Downloaded")
else:
print("[Download] APK already downloaded")
print("[Decompile] Decompiling APK, this may take a minute...")
r = subprocess.Popen('java -jar apktool.jar d -f discord.apk', shell=True, text=True, stdin=subprocess.PIPE, stdout=stdout, stderr=stderr)
r.stdin.write('\r\n' if os.name=='nt' else '\n')
r.communicate()
chdir('discord')
# Things that need some renaming to actually work correctly should be edited and reviewed here every update
bugfixes = []
protocol = ('http://', 'ws://')
if config['secure']:
protocol = ('https://', 'wss://')
# Basic replacements throughout the code to replace discord routes with fosscord routes
# NOTE: Order of replacements is VERY important and will probably have to do stupid stuff to make it customisable
replacements = [
('https://cdn.discordapp.com', protocol[0]+config['cdn_url']), # cdn.discord.com to cdn url
('https://media.discordapp.net', protocol[0]+config['cdn_url']), # discord media proxy to cdn url
('https://gateway.discord.gg', protocol[1]+config['gateway_url']), # gateway.discord.com to gateway url
('https://discord.com', protocol[0]+config['base_url']), # discord.com to the base url
('https://discordapp.com', protocol[0]+config['base_url']), # Extra change just in case discordapp is still used in the code somewhere
('https://discord.gg', protocol[0]+config['invite_url']), # discord.gg to the invite url
('https://discord.new/', protocol[0]+config['base_url']+'/template') # discord.new to template url
]
if config.get('debug'):
replacements.append(("DEBUG:Z = false", "DEBUG:Z = true")) # Enables debug if it's true in the config
def patchfile(file):
code = system("patch -p1 --no-backup-if-mismatch -i ../patches/"+file)
if code != 0:
print(f"[PatchFile] Failed to apply patchfile {file}")
return
if verbose:
print(f"[PatchFile] Applied patchfile {file}")
def patch(folder):
for root, _, files in walk(path.join('.', folder)):
for file in files:
fpath = path.join(root, file)
try:
with open(fpath) as f:
data = tmp = f.read()
for bugfix in bugfixes:
data = data.replace(*bugfix)
for replacement in replacements:
data = data.replace(*replacement)
if tmp != data:
with open(fpath, 'w+') as f:
f.write(data)
if verbose:
print(f"[Patch] Applied patches to `{fpath}`")
except UnicodeDecodeError:
pass
print("[Patcher] Patching...")
patch('smali')
patch('smali_classes2')
patch('smali_classes3')
print("[Patcher] Patching AndroidManifest.xml...")
with open('AndroidManifest.xml') as f:
manifest = f.read()
# manifest = manifest.replace(config['original_package_name'], config['new_package_name'])
if config["old_app_name"] != config["new_app_name"]:
manifest = manifest.replace(config['old_app_name'], config['new_app_name'])
if verbose:
print("[Patcher] Patched AndroidManifest.xml")
with open('AndroidManifest.xml', 'w') as f:
f.write(manifest)
print("[Patcher] Patching complete")
chdir('..')
'''
print("[Icon] Replacing icon...")
shutil.copyfile("assets/icon.png", "discord/res/mipmap-hdpi/ic_logo_round.png")
shutil.copyfile("assets/icon.png", "discord/res/mipmap-hdpi/ic_logo_square.png")
shutil.copyfile("assets/icon.png", "discord/res/mipmap-xhdpi/ic_logo_round.png")
shutil.copyfile("assets/icon.png", "discord/res/mipmap-xhdpi/ic_logo_square.png")
shutil.copyfile("assets/icon.png", "discord/res/mipmap-xxhdpi/ic_logo_round.png")
shutil.copyfile("assets/icon.png", "discord/res/mipmap-xxhdpi/ic_logo_square.png")
shutil.copyfile("assets/icon.png", "discord/res/mipmap-xxhdpi/logo.png")
shutil.copyfile("assets/icon.png", "discord/res/mipmap-xxxhdpi/ic_logo_round.png")
shutil.copyfile("assets/icon.png", "discord/res/mipmap-xxxhdpi/ic_logo_square.png")
shutil.copyfile("assets/icon.png", "discord/res/mipmap-xxxhdpi/logo.png")
'''
print("[Build] Rebuilding APK...")
r = subprocess.Popen('apktool b discord/ -o fosscord.unsigned.apk', shell=True, text=True, stdin=subprocess.PIPE, stdout=stdout, stderr=stderr)
r.stdin.write('\r\n' if os.name=='nt' else '\n')
r.communicate()
print("[Build] APK rebuilt")
print("[Sign] Signing APK...")
r = subprocess.Popen('java -jar uber-apk-signer.jar --apks fosscord.unsigned.apk -o .', shell=True, text=True, stdout=stdout, stderr=stderr)
r.communicate()
print("[Sign] APK signed")
'''
print("[Clean] Cleaning up...")
shutil.rmtree("discord")
unlink("fosscord.unsigned.apk")
'''
print("All done!")

34
patches/nozlib.patch Normal file
View File

@ -0,0 +1,34 @@
diff -crB from/smali/com/discord/gateway/GatewaySocket.smali to/smali/com/discord/gateway/GatewaySocket.smali
*** from/smali/com/discord/gateway/GatewaySocket.smali 2021-08-04 19:45:39.263375300 +0100
--- to/smali/com/discord/gateway/GatewaySocket.smali 2021-08-04 23:26:38.221788200 +0100
***************
*** 1410,1416 ****
invoke-virtual {v1, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
! const-string p1, "/?encoding=json&v=9&compress=zlib-stream"
invoke-virtual {v1, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
--- 1410,1416 ----
invoke-virtual {v1, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
! const-string p1, "/?encoding=json&v=9"
invoke-virtual {v1, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
diff -crB from/smali/com/discord/gateway/io/OutgoingPayload$Identify.smali to/smali/com/discord/gateway/io/OutgoingPayload$Identify.smali
*** from/smali/com/discord/gateway/io/OutgoingPayload$Identify.smali 2021-08-04 19:45:39.360374800 +0100
--- to/smali/com/discord/gateway/io/OutgoingPayload$Identify.smali 2021-08-04 23:28:01.288223200 +0100
***************
*** 115,120 ****
--- 115,122 ----
iput p2, p0, Lcom/discord/gateway/io/OutgoingPayload$Identify;->largeThreshold:I
+ const/4 p3, 0x0
+
iput-boolean p3, p0, Lcom/discord/gateway/io/OutgoingPayload$Identify;->compress:Z
iput-wide p4, p0, Lcom/discord/gateway/io/OutgoingPayload$Identify;->capabilities:J

BIN
uber-apk-signer.jar Normal file

Binary file not shown.