Compare commits
9 Commits
alpha-v0.0.1
...
v0.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 62e23a2eb0 | |||
| 5986d8b069 | |||
| cc49aad2aa | |||
| d6f616da7a | |||
| 3b2e5bee18 | |||
| 8a74245c9c | |||
| 0f53c836ae | |||
| d0744050d2 | |||
| 8f30061d44 |
@@ -0,0 +1,65 @@
|
|||||||
|
name: Build mod
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
#schedule: # раз в 36 часов
|
||||||
|
# - cron: "0 0 */3 * *" # каждые 3 дня в 00:00
|
||||||
|
# - cron: "0 12 */3 * *" # каждые 3 дня в 12:00
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Download APK
|
||||||
|
run: |
|
||||||
|
curl -L -o app.apk "https://mirror-dl.anixart-app.com/anixart-beta.apk"
|
||||||
|
|
||||||
|
- name: Ensure aapt is installed
|
||||||
|
run: |
|
||||||
|
if ! command -v aapt &> /dev/null; then
|
||||||
|
echo "aapt не найден, устанавливаем..."
|
||||||
|
sudo apt-get update && sudo apt-get install -y --no-install-recommends android-sdk-build-tools
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Export secrets
|
||||||
|
env:
|
||||||
|
KEYSTORE: ${{ secrets.KEYSTORE }}
|
||||||
|
KEYSTORE_PASS: ${{ secrets.KEYSTORE_PASS }}
|
||||||
|
run: |
|
||||||
|
# Export so later steps can reference them
|
||||||
|
echo "$KEYSTORE" | base64 -d > keystore.jks
|
||||||
|
echo "$KEYSTORE_PASS" > keystore.pass
|
||||||
|
|
||||||
|
- name: Build APK
|
||||||
|
id: build
|
||||||
|
run: |
|
||||||
|
mkdir original
|
||||||
|
mv app.apk original/
|
||||||
|
python ./main.py -f
|
||||||
|
|
||||||
|
- name: Read title from report.log
|
||||||
|
id: get_title
|
||||||
|
run: |
|
||||||
|
TITLE=$(head -n 1 modified/report.log)
|
||||||
|
echo "title=${TITLE}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Setup go
|
||||||
|
if: steps.build.outputs.BUILD_EXIT == '0'
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: '>=1.20'
|
||||||
|
|
||||||
|
- name: Make release
|
||||||
|
if: steps.build.outputs.BUILD_EXIT == '0'
|
||||||
|
uses: https://gitea.com/actions/release-action@main
|
||||||
|
with:
|
||||||
|
title: ${{ steps.get_title.outputs.title }}
|
||||||
|
body_path: modified/report.log
|
||||||
|
draft: true
|
||||||
|
api_key: '${{secrets.RELEASE_TOKEN}}'
|
||||||
|
files: |-
|
||||||
|
modified/**-mod.apk
|
||||||
|
modified/report.log
|
||||||
Vendored
+3
-1
@@ -4,4 +4,6 @@ original
|
|||||||
tools
|
tools
|
||||||
|
|
||||||
__pycache__
|
__pycache__
|
||||||
.venv
|
.venv
|
||||||
|
*.jks
|
||||||
|
*.pass
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
### Структура проекта:
|
### Структура проекта:
|
||||||
- `main.py` Главный файл
|
- `main.py` Главный файл
|
||||||
- `patches` Модули патчей
|
- `patches` Модули патчей
|
||||||
@@ -13,6 +14,37 @@
|
|||||||
- `patches/resources` Ресурсы, используемые патчами
|
- `patches/resources` Ресурсы, используемые патчами
|
||||||
- `todo_drafts` Заметки для новых патчей(можно в любом формате)
|
- `todo_drafts` Заметки для новых патчей(можно в любом формате)
|
||||||
|
|
||||||
|
### Схема
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
---
|
||||||
|
title: Процесс модифицирования приложения
|
||||||
|
---
|
||||||
|
|
||||||
|
flowchart TD
|
||||||
|
A([Оригинальный apk]) f1@==> B[поиск и выбор apk]
|
||||||
|
|
||||||
|
B f2@==> p[Декомпиляция]
|
||||||
|
|
||||||
|
subgraph p["Применение патчей по возрастанию приоритета"]
|
||||||
|
C[Патч 1] --> D
|
||||||
|
D[Патч 2] --...--> E[Патч n]
|
||||||
|
end
|
||||||
|
|
||||||
|
p f3@==> F[Сборка apk обратно]
|
||||||
|
F f4@==> G[Выравнивание zipaling]
|
||||||
|
G f5@==> H[Подпись V2+V3]
|
||||||
|
|
||||||
|
H f6@==> I([Модифицированый apk])
|
||||||
|
|
||||||
|
f1@{ animate: true }
|
||||||
|
f2@{ animate: true }
|
||||||
|
f3@{ animate: true }
|
||||||
|
f4@{ animate: true }
|
||||||
|
f5@{ animate: true }
|
||||||
|
f6@{ animate: true }
|
||||||
|
```
|
||||||
|
|
||||||
### Установка и использование:
|
### Установка и использование:
|
||||||
|
|
||||||
1. Клонируйте репозиторий:
|
1. Клонируйте репозиторий:
|
||||||
|
|||||||
@@ -3,16 +3,17 @@
|
|||||||
"apktool_jar_url": "https://bitbucket.org/iBotPeaches/apktool/downloads/apktool_2.12.0.jar",
|
"apktool_jar_url": "https://bitbucket.org/iBotPeaches/apktool/downloads/apktool_2.12.0.jar",
|
||||||
"apktool_wrapper_url": "https://raw.githubusercontent.com/iBotPeaches/Apktool/master/scripts/linux/apktool"
|
"apktool_wrapper_url": "https://raw.githubusercontent.com/iBotPeaches/Apktool/master/scripts/linux/apktool"
|
||||||
},
|
},
|
||||||
"new_package_name": "com.wowlikon.anixart",
|
"new_package_name": "com.wowlikon.anixart2",
|
||||||
"server": "https://anixarty.wowlikon.tech/modding",
|
"server": "https://anixarty.wowlikon.tech/modding",
|
||||||
"theme": {
|
"theme": {
|
||||||
"colors": {
|
"colors": {
|
||||||
"primary": "#ccff00",
|
"primary": "#ccff00",
|
||||||
"secondary": "#ffffd700",
|
"secondary": "#ffffd700",
|
||||||
"background": "#FFFFFF",
|
"background": "#ffffff",
|
||||||
"text": "#000000"
|
"text": "#000000"
|
||||||
},
|
},
|
||||||
"gradient": {
|
"gradient": {
|
||||||
|
"angle": "135.0",
|
||||||
"from": "#ffff6060",
|
"from": "#ffff6060",
|
||||||
"to": "#ffccff00"
|
"to": "#ffccff00"
|
||||||
}
|
}
|
||||||
@@ -29,10 +30,17 @@
|
|||||||
"icon": "@drawable/ic_custom_telegram",
|
"icon": "@drawable/ic_custom_telegram",
|
||||||
"icon_space_reserved": "false"
|
"icon_space_reserved": "false"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "Kentai Radiquum",
|
||||||
|
"description": "Разработчик",
|
||||||
|
"url": "https://t.me/radiquum",
|
||||||
|
"icon": "@drawable/ic_custom_telegram",
|
||||||
|
"icon_space_reserved": "false"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "Мы в Telegram",
|
"title": "Мы в Telegram",
|
||||||
"description": "Подпишитесь на канал, чтобы быть в курсе последних новостей.",
|
"description": "Подпишитесь на канал, чтобы быть в курсе последних новостей.",
|
||||||
"url": "https://t.me/wowlikon",
|
"url": "https://t.me/http_teapod",
|
||||||
"icon": "@drawable/ic_custom_telegram",
|
"icon": "@drawable/ic_custom_telegram",
|
||||||
"icon_space_reserved": "false"
|
"icon_space_reserved": "false"
|
||||||
}
|
}
|
||||||
@@ -51,5 +59,5 @@
|
|||||||
"android": "http://schemas.android.com/apk/res/android",
|
"android": "http://schemas.android.com/apk/res/android",
|
||||||
"app": "http://schemas.android.com/apk/res-auto"
|
"app": "http://schemas.android.com/apk/res-auto"
|
||||||
},
|
},
|
||||||
"speeds": [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0, 2.5, 3.0, 9.0]
|
"speeds": [9.0]
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
|
import yaml
|
||||||
import requests
|
import requests
|
||||||
|
import argparse
|
||||||
import colorama
|
import colorama
|
||||||
import importlib
|
import importlib
|
||||||
|
import traceback
|
||||||
import subprocess
|
import subprocess
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
@@ -12,7 +15,7 @@ def init() -> dict:
|
|||||||
if not os.path.exists(directory):
|
if not os.path.exists(directory):
|
||||||
os.makedirs(directory)
|
os.makedirs(directory)
|
||||||
|
|
||||||
with open("./patches/config.json", "r") as config_file:
|
with open("./config.json", "r") as config_file:
|
||||||
conf = json.load(config_file)
|
conf = json.load(config_file)
|
||||||
|
|
||||||
if not os.path.exists("./tools/apktool.jar"):
|
if not os.path.exists("./tools/apktool.jar"):
|
||||||
@@ -61,6 +64,11 @@ def select_apk() -> str:
|
|||||||
print("Нет файлов .apk в текущей директории")
|
print("Нет файлов .apk в текущей директории")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
if len(apks) == 1:
|
||||||
|
apk = apks[0]
|
||||||
|
print(f"Выбран файл {apk}")
|
||||||
|
return apk
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
print("Выберете файл для модификации")
|
print("Выберете файл для модификации")
|
||||||
for index, apk in enumerate(apks):
|
for index, apk in enumerate(apks):
|
||||||
@@ -101,6 +109,53 @@ def decompile_apk(apk: str):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def compile_apk(apk: str):
|
||||||
|
print("Компилируем apk...")
|
||||||
|
try:
|
||||||
|
subprocess.run(
|
||||||
|
"tools/apktool b decompiled -o " + os.path.join("modified", apk),
|
||||||
|
shell=True,
|
||||||
|
check=True,
|
||||||
|
text=True,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
)
|
||||||
|
subprocess.run(
|
||||||
|
"zipalign -v 4 " + os.path.join("modified", apk) + " " + os.path.join("modified", apk.replace(".apk", "-aligned.apk")),
|
||||||
|
shell=True,
|
||||||
|
check=True,
|
||||||
|
text=True,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
)
|
||||||
|
subprocess.run(
|
||||||
|
"apksigner sign " +
|
||||||
|
"--v1-signing-enabled false " +
|
||||||
|
"--v2-signing-enabled true " +
|
||||||
|
"--v3-signing-enabled true " +
|
||||||
|
"--ks keystore.jks " +
|
||||||
|
"--ks-pass file:keystore.pass " +
|
||||||
|
"--out " + os.path.join("modified", apk.replace(".apk", "-mod.apk")) +
|
||||||
|
" " + os.path.join("modified", apk.replace(".apk", "-aligned.apk")),
|
||||||
|
shell=True,
|
||||||
|
check=True,
|
||||||
|
text=True,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
)
|
||||||
|
title = "anixart mod "
|
||||||
|
with open('./decompiled/apktool.yml') as f:
|
||||||
|
package = yaml.safe_load(f)
|
||||||
|
title += ' '.join([f'{k}: {v}' for k, v in package['versionInfo'].items()])
|
||||||
|
with open("./modified/report.log", "w") as log_file:
|
||||||
|
log_file.write(title+'\n')
|
||||||
|
log_file.write("\n".join([f"{patch.name}: {'applied' if patch.applied else 'failed'}" for patch in patches]))
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print("Ошибка при выполнении команды:")
|
||||||
|
print(e.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
class Patch:
|
class Patch:
|
||||||
def __init__(self, name, pkg):
|
def __init__(self, name, pkg):
|
||||||
self.name = name
|
self.name = name
|
||||||
@@ -117,36 +172,59 @@ class Patch:
|
|||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Ошибка при применении патча {self.name}: {e}")
|
print(f"Ошибка при применении патча {self.name}: {e}")
|
||||||
print(e.args)
|
print(type(e), e.args)
|
||||||
|
traceback.print_exc()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Автоматический патчер anixart"
|
||||||
|
)
|
||||||
|
|
||||||
conf = init()
|
parser.add_argument("-v", "--verbose",
|
||||||
apk = select_apk()
|
action="store_true",
|
||||||
patch = decompile_apk(apk)
|
help="Выводить подробные сообщения")
|
||||||
|
|
||||||
patches = []
|
parser.add_argument("-f", "--force",
|
||||||
for filename in os.listdir("patches/"):
|
action="store_true",
|
||||||
if filename.endswith(".py") and filename != "__init__.py":
|
help="Принудительно собрать APK")
|
||||||
module_name = filename[:-3]
|
|
||||||
module = importlib.import_module(f"patches.{module_name}")
|
|
||||||
patches.append(Patch(module_name, module))
|
|
||||||
|
|
||||||
patches.sort(key=lambda x: x.package.priority, reverse=True)
|
args = parser.parse_args()
|
||||||
|
|
||||||
for patch in tqdm(patches, colour="green", desc="Применение патчей"):
|
conf = init()
|
||||||
tqdm.write(f"Применение патча: {patch.name}")
|
apk = select_apk()
|
||||||
patch.apply(conf)
|
patch = decompile_apk(apk)
|
||||||
|
|
||||||
statuses = {}
|
if args.verbose: conf["verbose"] = True
|
||||||
for patch in patches:
|
|
||||||
statuses[patch.name] = patch.applied
|
|
||||||
marker = colorama.Fore.GREEN + "✔" if patch.applied else colorama.Fore.RED + "✘"
|
|
||||||
print(f"{marker}{colorama.Style.RESET_ALL} {patch.name}")
|
|
||||||
|
|
||||||
if all(statuses.values()):
|
patches = []
|
||||||
print("Все патчи успешно применены")
|
for filename in os.listdir("patches/"):
|
||||||
elif any(statuses.values()):
|
if filename.endswith(".py") and filename != "__init__.py" and not filename.startswith("todo_"):
|
||||||
print("Некоторые патчи не были успешно применены")
|
module_name = filename[:-3]
|
||||||
else:
|
module = importlib.import_module(f"patches.{module_name}")
|
||||||
print("Ни один патч не был успешно применен")
|
patches.append(Patch(module_name, module))
|
||||||
|
|
||||||
|
patches.sort(key=lambda x: x.package.priority, reverse=True)
|
||||||
|
|
||||||
|
for patch in tqdm(patches, colour="green", desc="Применение патчей"):
|
||||||
|
tqdm.write(f"Применение патча: {patch.name}")
|
||||||
|
patch.apply(conf)
|
||||||
|
|
||||||
|
statuses = {}
|
||||||
|
for patch in patches:
|
||||||
|
statuses[patch.name] = patch.applied
|
||||||
|
marker = colorama.Fore.GREEN + "✔" if patch.applied else colorama.Fore.RED + "✘"
|
||||||
|
print(f"{marker}{colorama.Style.RESET_ALL} {patch.name}")
|
||||||
|
|
||||||
|
if all(statuses.values()):
|
||||||
|
print(f"{colorama.Fore.GREEN}Все патчи успешно применены{colorama.Style.RESET_ALL}")
|
||||||
|
compile_apk(apk)
|
||||||
|
elif any(statuses.values()):
|
||||||
|
print(f"{colorama.Fore.YELLOW}⚠{colorama.Style.RESET_ALL} Некоторые патчи не были успешно применены")
|
||||||
|
if args.force or input("Продолжить? (y/n): ").lower() == "y":
|
||||||
|
compile_apk(apk)
|
||||||
|
else:
|
||||||
|
print(colorama.Fore.RED + "Операция отменена" + colorama.Style.RESET_ALL)
|
||||||
|
else:
|
||||||
|
print(f"{colorama.Fore.RED}Ни один патч не был успешно применен{colorama.Style.RESET_ALL}")
|
||||||
|
sys.exit(1)
|
||||||
|
|||||||
@@ -5,11 +5,36 @@ from tqdm import tqdm
|
|||||||
import json
|
import json
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
|
||||||
def apply(config: dict) -> bool:
|
def apply(config: dict) -> bool:
|
||||||
response = requests.get(config['server'])
|
response = requests.get(config['server'])
|
||||||
if response.status_code == 200:
|
assert response.status_code == 200, f"Failed to fetch data {response.status_code} {response.text}"
|
||||||
for item in json.loads(response.text)["modding"]:
|
new_api = json.loads(response.text)
|
||||||
tqdm.write(item)
|
for item in new_api['modifications']:
|
||||||
return True
|
tqdm.write(f"Изменение {item['file']}")
|
||||||
tqdm.write(f"Failed to fetch data {response.status_code} {response.text}")
|
filepath = './decompiled/smali_classes2/com/swiftsoft/anixartd/network/api/'+item['file']
|
||||||
return False
|
with open(filepath, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
with open(filepath, 'w') as f:
|
||||||
|
if content.count(item['src']) == 0:
|
||||||
|
tqdm.write(f"⚠ Не найдено {item['src']}")
|
||||||
|
f.write(content.replace(item['src'], item['dst']))
|
||||||
|
|
||||||
|
tqdm.write(f"Изменение Github ссылки")
|
||||||
|
filepath = './decompiled/smali_classes2/com/swiftsoft/anixartd/utils/anixnet/GithubPagesNetFetcher.smali'
|
||||||
|
with open(filepath, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
with open(filepath, 'w') as f:
|
||||||
|
f.write(content.replace('const-string v1, "https://anixhelper.github.io/pages/urls.json"', f'const-string v1, "{new_api["gh"]}"'))
|
||||||
|
|
||||||
|
content = ""
|
||||||
|
tqdm.write("Удаление динамического выбора сервера")
|
||||||
|
filepath = './decompiled/smali_classes2/com/swiftsoft/anixartd/DaggerApp_HiltComponents_SingletonC$SingletonCImpl$SwitchingProvider.smali'
|
||||||
|
with open(filepath, 'r') as f:
|
||||||
|
for line in f.readlines():
|
||||||
|
if "addInterceptor" in line: continue
|
||||||
|
content += line
|
||||||
|
with open(filepath, 'w') as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|||||||
+5
-2
@@ -5,15 +5,18 @@ from tqdm import tqdm
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
|
|
||||||
def apply(config: dict) -> bool:
|
def apply(config: dict) -> bool:
|
||||||
for item in os.listdir("./decompiled/unknown/"):
|
for item in os.listdir("./decompiled/unknown/"):
|
||||||
item_path = os.path.join("./decompiled/unknown/", item)
|
item_path = os.path.join("./decompiled/unknown/", item)
|
||||||
|
|
||||||
if os.path.isfile(item_path):
|
if os.path.isfile(item_path):
|
||||||
os.remove(item_path)
|
os.remove(item_path)
|
||||||
tqdm.write(f'Удалён файл: {item_path}')
|
if config.get("verbose", False):
|
||||||
|
tqdm.write(f'Удалён файл: {item_path}')
|
||||||
elif os.path.isdir(item_path):
|
elif os.path.isdir(item_path):
|
||||||
if item not in config["cleanup"]["keep_dirs"]:
|
if item not in config["cleanup"]["keep_dirs"]:
|
||||||
shutil.rmtree(item_path)
|
shutil.rmtree(item_path)
|
||||||
tqdm.write(f'Удалена папка: {item_path}')
|
if config.get("verbose", False):
|
||||||
|
tqdm.write(f'Удалена папка: {item_path}')
|
||||||
return True
|
return True
|
||||||
|
|||||||
+57
-2
@@ -1,12 +1,19 @@
|
|||||||
"""Change application theme"""
|
"""Change application theme"""
|
||||||
priority = 0
|
priority = 0
|
||||||
from tqdm import tqdm
|
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
from utils.public import (
|
||||||
|
insert_after_public,
|
||||||
|
insert_after_color,
|
||||||
|
insert_after_id,
|
||||||
|
change_color,
|
||||||
|
)
|
||||||
|
|
||||||
def apply(config: dict) -> bool:
|
def apply(config: dict) -> bool:
|
||||||
main_color = config["theme"]["colors"]["primary"]
|
main_color = config["theme"]["colors"]["primary"]
|
||||||
splash_color = config["theme"]["colors"]["secondary"]
|
splash_color = config["theme"]["colors"]["secondary"]
|
||||||
|
gradient_angle = config["theme"]["gradient"]["angle"]
|
||||||
gradient_from = config["theme"]["gradient"]["from"]
|
gradient_from = config["theme"]["gradient"]["from"]
|
||||||
gradient_to = config["theme"]["gradient"]["to"]
|
gradient_to = config["theme"]["gradient"]["to"]
|
||||||
|
|
||||||
@@ -31,6 +38,7 @@ def apply(config: dict) -> bool:
|
|||||||
root = tree.getroot()
|
root = tree.getroot()
|
||||||
|
|
||||||
# Change attributes with namespace
|
# Change attributes with namespace
|
||||||
|
root.set(f"{{{config['xml_ns']['android']}}}angle", gradient_angle)
|
||||||
root.set(f"{{{config['xml_ns']['android']}}}startColor", gradient_from)
|
root.set(f"{{{config['xml_ns']['android']}}}startColor", gradient_from)
|
||||||
root.set(f"{{{config['xml_ns']['android']}}}endColor", gradient_to)
|
root.set(f"{{{config['xml_ns']['android']}}}endColor", gradient_to)
|
||||||
|
|
||||||
@@ -45,7 +53,7 @@ def apply(config: dict) -> bool:
|
|||||||
root = tree.getroot()
|
root = tree.getroot()
|
||||||
|
|
||||||
# Finding "path"
|
# Finding "path"
|
||||||
for el in root.findall("path", namespaces=config['xml_ns']):
|
for el in root.findall("path", namespaces=config["xml_ns"]):
|
||||||
name = el.get(f"{{{config['xml_ns']['android']}}}name")
|
name = el.get(f"{{{config['xml_ns']['android']}}}name")
|
||||||
if name == "path":
|
if name == "path":
|
||||||
el.set(f"{{{config['xml_ns']['android']}}}fillColor", splash_color)
|
el.set(f"{{{config['xml_ns']['android']}}}fillColor", splash_color)
|
||||||
@@ -53,4 +61,51 @@ def apply(config: dict) -> bool:
|
|||||||
# Save back
|
# Save back
|
||||||
tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8")
|
tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8")
|
||||||
|
|
||||||
|
for filename in ["$ic_launcher_foreground__0", "$ic_banner_foreground__0"]:
|
||||||
|
file_path = f"./decompiled/res/drawable-v24/{filename}.xml"
|
||||||
|
|
||||||
|
parser = etree.XMLParser(remove_blank_text=True)
|
||||||
|
tree = etree.parse(file_path, parser)
|
||||||
|
root = tree.getroot()
|
||||||
|
|
||||||
|
# Change attributes with namespace
|
||||||
|
root.set(f"{{{config['xml_ns']['android']}}}angle", gradient_angle)
|
||||||
|
items = root.findall("item", namespaces=config['xml_ns'])
|
||||||
|
assert len(items) == 2
|
||||||
|
items[0].set(f"{{{config['xml_ns']['android']}}}color", gradient_from)
|
||||||
|
items[1].set(f"{{{config['xml_ns']['android']}}}color", gradient_to)
|
||||||
|
|
||||||
|
# Save back
|
||||||
|
tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8")
|
||||||
|
|
||||||
|
insert_after_public("carmine", "custom_color")
|
||||||
|
insert_after_public("carmine_alpha_10", "custom_color_alpha_10")
|
||||||
|
insert_after_color("carmine", "custom_color", main_color[0]+'ff'+main_color[1:])
|
||||||
|
insert_after_color("carmine_alpha_10", "custom_color_alpha_10", main_color[0]+'1a'+main_color[1:])
|
||||||
|
|
||||||
|
change_color("accent_alpha_10", main_color[0]+'1a'+main_color[1:])
|
||||||
|
change_color("accent_alpha_20", main_color[0]+'33'+main_color[1:])
|
||||||
|
change_color("accent_alpha_50", main_color[0]+'80'+main_color[1:])
|
||||||
|
change_color("accent_alpha_70", main_color[0]+'b3'+main_color[1:])
|
||||||
|
change_color("colorAccent", main_color[0]+'ff'+main_color[1:])
|
||||||
|
change_color("link_color", main_color[0]+'ff'+main_color[1:])
|
||||||
|
change_color("link_color_alpha_70", main_color[0]+'b3'+main_color[1:])
|
||||||
|
change_color("refresh_progress", main_color[0]+'ff'+main_color[1:])
|
||||||
|
|
||||||
|
change_color("ic_launcher_background", "#ff000000")
|
||||||
|
change_color("bottom_nav_indicator_active", "#ffffffff")
|
||||||
|
change_color("bottom_nav_indicator_icon_checked", main_color[0]+'ff'+main_color[1:])
|
||||||
|
change_color("bottom_nav_indicator_label_checked", main_color[0]+'ff'+main_color[1:])
|
||||||
|
|
||||||
|
insert_after_public("warning_error_counter_background", "ic_custom_telegram")
|
||||||
|
insert_after_public("warning_error_counter_background", "ic_custom_crown")
|
||||||
|
|
||||||
|
try:
|
||||||
|
last = "speed75"
|
||||||
|
for speed in config.get("speeds", []):
|
||||||
|
insert_after_public(last, f"speed{int(float(speed)*10)}")
|
||||||
|
insert_after_id(last, f"speed{int(float(speed)*10)}")
|
||||||
|
last = f"speed{int(float(speed)*10)}"
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error occurred while processing speeds: {e}")
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
"""Compress PNGs"""
|
"""Compress PNGs"""
|
||||||
|
|
||||||
priority = -1
|
priority = -1
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
@@ -7,15 +6,15 @@ import os
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
def compress_pngs(root_dir):
|
def compress_pngs(root_dir: str, verbose: bool = False):
|
||||||
compressed_files = []
|
compressed_files = []
|
||||||
for dirpath, _, filenames in os.walk(root_dir):
|
for dirpath, _, filenames in os.walk(root_dir):
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
if filename.lower().endswith(".png"):
|
if filename.lower().endswith(".png"):
|
||||||
filepath = os.path.join(dirpath, filename)
|
filepath = os.path.join(dirpath, filename)
|
||||||
tqdm.write(f"Сжимаю: {filepath}")
|
if verbose: tqdm.write(f"Сжимаю: {filepath}")
|
||||||
try:
|
try:
|
||||||
subprocess.run(
|
assert subprocess.run(
|
||||||
[
|
[
|
||||||
"pngquant",
|
"pngquant",
|
||||||
"--force",
|
"--force",
|
||||||
@@ -24,9 +23,8 @@ def compress_pngs(root_dir):
|
|||||||
"--quality=65-90",
|
"--quality=65-90",
|
||||||
filepath,
|
filepath,
|
||||||
],
|
],
|
||||||
check=True,
|
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
)
|
).returncode in [0, 99]
|
||||||
compressed_files.append(filepath)
|
compressed_files.append(filepath)
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
tqdm.write(f"Ошибка при сжатии {filepath}: {e}")
|
tqdm.write(f"Ошибка при сжатии {filepath}: {e}")
|
||||||
@@ -34,5 +32,5 @@ def compress_pngs(root_dir):
|
|||||||
|
|
||||||
|
|
||||||
def apply(config: dict) -> bool:
|
def apply(config: dict) -> bool:
|
||||||
files = compress_pngs("./decompiled")
|
files = compress_pngs("./decompiled", config.get("verbose", False))
|
||||||
return len(files) > 0 and any(files)
|
return len(files) > 0 and any(files)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
"""Disable ad banners"""
|
"""Disable ad banners"""
|
||||||
|
|
||||||
priority = 0
|
priority = 0
|
||||||
|
|
||||||
from utils.smali_parser import (
|
from utils.smali_parser import (
|
||||||
@@ -9,6 +8,7 @@ from utils.smali_parser import (
|
|||||||
replace_smali_method_body,
|
replace_smali_method_body,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
replace = """ .locals 0
|
replace = """ .locals 0
|
||||||
|
|
||||||
const/4 p0, 0x1
|
const/4 p0, 0x1
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
"""Remove beta banner"""
|
"""Remove beta banner"""
|
||||||
|
|
||||||
priority = 0
|
priority = 0
|
||||||
import os
|
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
from lxml import etree
|
|
||||||
|
|
||||||
from typing import TypedDict
|
import os
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
|
||||||
def apply(config) -> bool:
|
def apply(config) -> bool:
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
"""Insert new files"""
|
"""Insert new files"""
|
||||||
|
|
||||||
priority = 0
|
priority = 0
|
||||||
from tqdm import tqdm
|
|
||||||
|
|
||||||
import shutil
|
import shutil
|
||||||
import os
|
import os
|
||||||
|
|||||||
+11
-2
@@ -1,9 +1,8 @@
|
|||||||
"""Change package name of apk"""
|
"""Change package name of apk"""
|
||||||
|
|
||||||
priority = -1
|
priority = -1
|
||||||
from tqdm import tqdm
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
|
||||||
def rename_dir(src, dst):
|
def rename_dir(src, dst):
|
||||||
@@ -88,6 +87,16 @@ def apply(config: dict) -> bool:
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
file_path = "./decompiled/res/layout/fragment_sign_in.xml"
|
||||||
|
parser = etree.XMLParser(remove_blank_text=True)
|
||||||
|
tree = etree.parse(file_path, parser)
|
||||||
|
root = tree.getroot()
|
||||||
|
|
||||||
|
last_linear = root.xpath("//LinearLayout/LinearLayout[4]")[0]
|
||||||
|
last_linear.set(f"{{{config['xml_ns']['android']}}}visibility", "gone")
|
||||||
|
|
||||||
|
tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
"""Add new settings"""
|
"""Add new settings"""
|
||||||
priority = 0
|
priority = 0
|
||||||
from tqdm import tqdm
|
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
# Generate PreferenceCategory
|
|
||||||
def make_category(ns, name, items):
|
def make_category(ns, name, items):
|
||||||
cat = etree.Element("PreferenceCategory", nsmap=ns)
|
cat = etree.Element("PreferenceCategory", nsmap=ns)
|
||||||
cat.set(f"{{{ns['android']}}}title", name)
|
cat.set(f"{{{ns['android']}}}title", name)
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
"""Change application icon"""
|
"""Change application icon"""
|
||||||
priority = 0
|
priority = 0
|
||||||
from tqdm import tqdm
|
|
||||||
|
|
||||||
import time
|
|
||||||
|
|
||||||
def apply(config: dict) -> bool:
|
def apply(config: dict) -> bool:
|
||||||
time.sleep(0.2)
|
|
||||||
return False
|
return False
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
"""Change application icon"""
|
"""Change application icon"""
|
||||||
|
|
||||||
priority = 0
|
priority = 0
|
||||||
from tqdm import tqdm
|
|
||||||
|
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
+65
-7
@@ -2,15 +2,15 @@ from lxml import etree
|
|||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
|
|
||||||
def insert_after(anchor_name: str, elem_name: str):
|
def insert_after_public(anchor_name: str, elem_name: str):
|
||||||
file_path = "../decompiled/res/values/public.xml"
|
file_path = "./decompiled/res/values/public.xml"
|
||||||
parser = etree.XMLParser(remove_blank_text=True)
|
parser = etree.XMLParser(remove_blank_text=True)
|
||||||
tree = etree.parse(file_path, parser)
|
tree = etree.parse(file_path, parser)
|
||||||
root = tree.getroot()
|
root = tree.getroot()
|
||||||
anchor = None
|
anchor = None
|
||||||
types = {}
|
types = {}
|
||||||
|
|
||||||
for idx, elem in enumerate(root):
|
for elem in root:
|
||||||
assert elem.tag == "public"
|
assert elem.tag == "public"
|
||||||
assert elem.keys() == ["type", "name", "id"]
|
assert elem.keys() == ["type", "name", "id"]
|
||||||
attrs = dict(zip(elem.keys(), elem.values()))
|
attrs = dict(zip(elem.keys(), elem.values()))
|
||||||
@@ -25,7 +25,6 @@ def insert_after(anchor_name: str, elem_name: str):
|
|||||||
if i not in group:
|
if i not in group:
|
||||||
free_ids.add(i)
|
free_ids.add(i)
|
||||||
|
|
||||||
assert len(free_ids) > 0
|
|
||||||
new_id = None
|
new_id = None
|
||||||
for i in free_ids:
|
for i in free_ids:
|
||||||
if i > int(anchor[1]["id"], 16):
|
if i > int(anchor[1]["id"], 16):
|
||||||
@@ -38,12 +37,71 @@ def insert_after(anchor_name: str, elem_name: str):
|
|||||||
if name == anchor[1]["type"]:
|
if name == anchor[1]["type"]:
|
||||||
continue
|
continue
|
||||||
if new_id in group:
|
if new_id in group:
|
||||||
new_id = max(group)
|
assert False, f"ID {new_id} already exists in group {name}"
|
||||||
|
|
||||||
new_elem = deepcopy(anchor[0])
|
new_elem = deepcopy(anchor[0])
|
||||||
new_elem.set("id", new_id)
|
new_elem.set("id", hex(new_id))
|
||||||
new_elem.set("name", elem_name)
|
new_elem.set("name", elem_name)
|
||||||
anchor[0].addnext(new_elem)
|
anchor[0].addnext(new_elem)
|
||||||
|
|
||||||
tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8")
|
tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8")
|
||||||
return new_id
|
return new_id
|
||||||
|
|
||||||
|
|
||||||
|
def insert_after_id(anchor_name: str, elem_name: str):
|
||||||
|
file_path = "./decompiled/res/values/ids.xml"
|
||||||
|
parser = etree.XMLParser(remove_blank_text=True)
|
||||||
|
tree = etree.parse(file_path, parser)
|
||||||
|
root = tree.getroot()
|
||||||
|
anchor = None
|
||||||
|
|
||||||
|
for elem in root:
|
||||||
|
assert elem.tag == "item"
|
||||||
|
assert elem.keys() == ["type", "name"]
|
||||||
|
attrs = dict(zip(elem.keys(), elem.values()))
|
||||||
|
if attrs["name"] == anchor_name:
|
||||||
|
assert anchor == None
|
||||||
|
anchor = (elem, attrs)
|
||||||
|
|
||||||
|
new_elem = deepcopy(anchor[0])
|
||||||
|
new_elem.set("name", elem_name)
|
||||||
|
anchor[0].addnext(new_elem)
|
||||||
|
tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def change_color(name: str, value: str):
|
||||||
|
file_path = "./decompiled/res/values/colors.xml"
|
||||||
|
parser = etree.XMLParser(remove_blank_text=True)
|
||||||
|
tree = etree.parse(file_path, parser)
|
||||||
|
root = tree.getroot()
|
||||||
|
replacements = 0
|
||||||
|
|
||||||
|
for elem in root:
|
||||||
|
assert elem.tag == "color"
|
||||||
|
assert elem.keys() == ["name"]
|
||||||
|
attrs = dict(zip(elem.keys(), elem.values()))
|
||||||
|
if attrs["name"] == name:
|
||||||
|
elem.set("name", name)
|
||||||
|
elem.text = value
|
||||||
|
replacements += 1
|
||||||
|
assert replacements >= 1
|
||||||
|
tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8")
|
||||||
|
|
||||||
|
def insert_after_color(anchor_name: str, elem_name: str, elem_value: str):
|
||||||
|
file_path = "./decompiled/res/values/colors.xml"
|
||||||
|
parser = etree.XMLParser(remove_blank_text=True)
|
||||||
|
tree = etree.parse(file_path, parser)
|
||||||
|
root = tree.getroot()
|
||||||
|
anchor = None
|
||||||
|
|
||||||
|
for elem in root:
|
||||||
|
assert elem.tag == "color"
|
||||||
|
assert elem.keys() == ["name"]
|
||||||
|
attrs = dict(zip(elem.keys(), elem.values()))
|
||||||
|
if attrs["name"] == anchor_name:
|
||||||
|
assert anchor == None
|
||||||
|
anchor = (elem, attrs)
|
||||||
|
|
||||||
|
new_elem = deepcopy(anchor[0])
|
||||||
|
new_elem.set("name", elem_name)
|
||||||
|
anchor[0].addnext(new_elem)
|
||||||
|
tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8")
|
||||||
|
|||||||
Reference in New Issue
Block a user