diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..2866ade --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,24 @@ +name: tests + +on: + push: + pull_request: + +jobs: + test: + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install PyQt5 pytest pytest-cov + - name: Run tests + run: python -m pytest tests/ -q --cov=apluse.core --cov=apluse.mcpk --cov=apluse.engine_window --cov=apluse.restore_template --cov-report=term diff --git a/.gitignore b/.gitignore index 0dd92f6..a977b37 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,14 @@ -__pycache__/ +__pycache__/ *.pyc *.pyo *.log *.bak tests/__pycache__/ README_backup.md +apluse_config.json +apluse_restore_android/ +.zcode/ +ui_preview/ +.coverage +old/ +capsule.mcpk diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..5ff6210 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,6 @@ +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.8.4 + hooks: + - id: ruff + args: [--check] diff --git a/DEVELOPER.md b/DEVELOPER.md deleted file mode 100644 index 6e3b744..0000000 --- a/DEVELOPER.md +++ /dev/null @@ -1,121 +0,0 @@ -- # APLUSE ENGINE v3.4 - - 基于 [apate](https://github.com/rippod/apate) 思路,用 Python 重新实现的文件伪装/还原工具。通过替换文件头部字节并追加加密元数据,将任意文件伪装为另一种格式(如将 `.rar` 伪装为 `.mp4`),同时支持一键还原,现在支持windows系统和安卓系统的还原。 - - ## 功能特性 - - **核心能力** - - 一键伪装/还原:自动识别文件当前状态,原始文件执行伪装,伪装文件执行还原 - - 批量处理:支持同时操作多个文件或整个文件夹 - - 支持使用面具池 - - 自定义魔术字:支持 HEX 和文本两种输入,可随机生成,用于标记伪装文件 - - **恢复工具生成** - - Windows 恢复程序:打包为独立 `.exe`,无需 Python 环境即可在任意电脑上批量还原 - - Android 恢复包:生成 Android 项目,编译为 `.apk` 安装到手机端还原伪装文件 - - 恢复程序支持手动输入魔术字,同一工具可适配不同密钥 - - **界面与体验** - - 7 套主题:暗色极客、亮色极简、渐变幽蓝、暗金奢华、猛男猛粉、辐射废土、低调暗紫 - - 拖拽添加:支持将文件/文件夹直接拖入列表 - - 文件大小显示:列表项和状态栏实时显示文件数量与总大小 - - 键盘快捷键:`Ctrl+O` 添加文件、`Delete` 删除选中、`Ctrl+D` 扫描、`Ctrl+Enter` 启动 - - 操作日志持久化:所有操作记录同步写入 `apluse.log` - - **安全与健壮** - - 配置文件自动迁移:从旧版 `mask_config.json` 无缝升级到新版配置 - - 跨分区兼容:文件移动使用 `shutil.move`,支持不同盘符 - - 文件占用检测:被其他程序锁定时给出明确提示 - - 批量操作确认:执行前弹出二次确认,防止误操作 - - ## 截图 - - ### v3.1 - - ![v3.1](README.assets/image-20260409010208641.png) - - ### v3.3 - - ![v3.3-1](README.assets/image-20260409010324267.png) - - ![v3.3-2](README.assets/image-20260409010347858.png) - - ### v3.4 - - ![image-20260507173405311](README.assets/image-20260507173405311.png) - - #### 手机恢复: - - ![Screenshot_20260507_174010](README.assets/mobile.jpg) - - ## 使用方式 - - ### 基本操作 - - 1. 启动程序后,在「核心密钥」区域确认或修改魔术字 - 2. 将目标文件拖入「目标执行队列」,或点击按钮选择 - 3. 将伪装用的媒体文件拖入「面具文件库」 - 4. 点击「引擎启动」即可自动完成伪装或还原 - - ### 生成恢复工具 - - 伪装完成后,点击「生成恢复程序」按钮,选择: - - **Windows (.exe)**:生成 `{魔术字}_restore.exe`,放到伪装文件所在目录双击运行即可批量还原 - - **Android (.apk)**:生成 Android 项目,用 Android Studio 编译后安装到手机 - - ### 键盘快捷键 - - | 快捷键 | 功能 | - | -------------- | ------------ | - | `Ctrl+O` | 添加目标文件 | - | `Ctrl+Shift+O` | 添加面具文件 | - | `Delete` | 删除选中项 | - | `Ctrl+D` | 扫描分析队列 | - | `Ctrl+Enter` | 启动引擎 | - - ## 环境要求 - - - Python 3.8+ - - PyQt5 - - ```bash - pip install pyqt5 - ``` - - ## 构建 - - ### PyInstaller(推荐) - - ```bash - pip install pyinstaller - pyinstaller --onefile --windowed --icon=icon.ico -n apluse --add-data "icon.ico;." --clean main.py - ``` - - ### Nuitka - - ```bash - nuitka --standalone --onefile --windows-console-mode=disable --enable-plugin=pyqt5 --windows-icon-from-ico=icon.ico --output-filename=apluse --include-data-files=icon.ico=icon.ico --clean-cache=all main.py - ``` - - ### 生成恢复工具的额外依赖 - - 生成 Windows 恢复程序需要系统中安装 Python 和 PyInstaller(程序会自动检测并安装)。 - - 生成 Android 恢复包需要 [Android Studio](https://developer.android.com/studio),程序会自动创建完整项目,用 Android Studio 打开后一键编译即可。如需自动转换图标,需安装 `pip install Pillow`。 - - ## 项目结构 - - ``` - apluse/ - ├── main.py # 程序入口 - ├── core.py # 核心引擎:伪装/还原逻辑、配置管理、恢复工具生成 - ├── ui.py # PyQt5 界面 - ├── themes.py # 主题配色方案 - ├── android_templates.py # Android 项目模板 - ├── icon.ico # 应用图标 - └── apluse_config.json # 配置文件(自动生成) - ``` - - ## 致谢 - - 思路来源:[apate](https://github.com/rippod/apate) diff --git a/README.md b/README.md index 49c0f04..0b9d8d3 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,9 @@ 推广页请见[index](./html/index.html) -基于 [apate](https://github.com/rippod/apate) 思路,用 Python 重新实现的文件伪装/还原工具。通过替换文件头部字节并追加加密元数据,将任意文件伪装为另一种格式(如将 `.rar` 伪装为 `.mp4`),同时支持一键还原,现在支持windows系统和安卓系统的还原。(当前仓库里具有前瞻性组件,具体详情请查看[beta](./beta.md)) +基于 [apate](https://github.com/rippod/apate) 思路,用 Python 重新实现的文件伪装/还原工具。通过替换文件头部字节并追加加密元数据,将任意文件伪装为另一种格式(如将 `.rar` 伪装为 `.mp4`),同时支持一键还原,现在支持windows系统和安卓系统的还原。 + +> **Beta 功能**:集成 [MCPK (MeCapsule Package)](https://github.com/tripodxu/MeCapsule) 打包格式,支持将文件打包为 `.mcpk` 归档。详见下方 [MCPK 实验性功能](#mcpk-实验性功能-beta) 章节。 ## 功能特性 @@ -76,6 +78,88 @@ | `Ctrl+D` | 扫描分析队列 | | `Ctrl+Enter` | 启动引擎 | +## MCPK 实验性功能 (Beta) + +> **Beta 声明**:MCPK 打包功能目前为实验性功能,API 可能在后续版本中调整。 + +### 什么是 MCPK? + +MCPK(MeCapsule Package)是一种自定义二进制容器格式,专为个人知识管理设计。它将文档、图片、音频、视频及其元数据打包为单一的 `.mcpk` 文件,支持智能分组、灵活加密和独立压缩。 + +完整项目与格式规范见上游仓库:**[tripodxu/MeCapsule](https://github.com/tripodxu/MeCapsule)** + +**核心特性:** +- 单一文件归档:内建元数据、标签和条目关系 +- 智能分组:相关文件(如视频与字幕)物理相邻存储 +- 灵活加密:XOR 流加密(零依赖)+ AES-256-GCM(高强度) +- 独立压缩:每条目自动选择算法(文本用 zlib,已压缩格式跳过) +- 完整校验:CRC32 完整性校验 + 完整时间戳 + +### 通过主界面使用 MCPK + +1. 启动 APLUSE ENGINE 主程序(`python main.py`) +2. 在主界面找到 MCPK 功能区域 +3. 将文件/文件夹拖入 MCPK 打包队列 +4. 点击「打包」按钮生成 `.mcpk` 文件 +5. 点击「打开 MCPK 文件」或拖入 `.mcpk` 文件浏览内容 + +MCPK 浏览器支持预览图片、GIF、视频、文本文件,并可将 `.mcpk` 文件发送到伪装引擎进行二次保护。 + +### 通过命令行使用 MCPK + +```bash +# 打包文件/目录 +python -m mcpk pack ./notes/ -o archive.mcpk + +# 带分组打包 +python -m mcpk pack ./project/ -o project.mcpk --group + +# 加密打包(XOR,零依赖) +python -m mcpk pack ./secret/ -o secret.mcpk --password mypassword + +# 加密打包(AES-256-GCM,需 pip install cryptography) +python -m mcpk pack ./secret/ -o secret.mcpk --password mypassword --encryption aes + +# 列出条目 +python -m mcpk list archive.mcpk + +# 提取全部 +python -m mcpk extract archive.mcpk -o ./output/ + +# 验证完整性 +python -m mcpk verify archive.mcpk +``` + +### Python API + +```python +from apluse.mcpk import MCPKWriter, MCPKReader + +# 打包 +with MCPKWriter("archive.mcpk") as w: + w.add_file("report.pdf") + w.add_file("photo.jpg") + w.import_folder("./my_project/") # 按文件夹打包 + +# 读取 +with MCPKReader("archive.mcpk") as r: + for entry in r.list_entries(): + print(entry.name) + data = r.extract("report.pdf") + r.extract_all("./output/") +``` + +### 伪装 + MCPK 双重保护 + +MCPK 文件可以作为伪装引擎的目标文件,实现「内容加密打包 → 格式伪装」的双重保护: + +1. 先将敏感文件打包为 `.mcpk`(可选择加密) +2. 在 MCPK 浏览器中点击「发送到伪装引擎」 +3. 选择面具文件(如 `.mp4`),启动伪装引擎 +4. 最终文件外观为普通视频,实际为加密的知识归档 + +详细使用指南见 [doc/mcpk-usage.md](./doc/mcpk-usage.md) + ## 环境要求 - Python 3.8+ @@ -85,6 +169,14 @@ pip install pyqt5 ``` +**可选依赖(MCPK 增强功能):** + +```bash +pip install cryptography # AES-256-GCM 高强度加密 +pip install zstd # ZSTD 高压缩比 +pip install lz4 # LZ4 极速压缩 +``` + ## 构建 ### PyInstaller(推荐) @@ -109,156 +201,111 @@ nuitka --standalone --onefile --windows-console-mode=disable --enable-plugin=pyq ## 项目结构 ``` -apluse/ -├── main.py # 程序入口 -├── core.py # 核心引擎:伪装/还原逻辑、配置管理、恢复工具生成 -├── ui.py # PyQt5 界面 -├── themes.py # 主题配色方案 -├── android_templates.py # Android 项目模板 -├── icon.ico # 应用图标 -└── apluse_config.json # 配置文件(自动生成) +. +├── main.py # 程序入口 +├── admin_main.py # 管理员模式入口 +├── apluse/ # 核心包 +│ ├── __init__.py # 公共 API 导出 +│ ├── core.py # 核心引擎:伪装/还原逻辑、配置管理、恢复工具生成 +│ ├── ui.py # PyQt5 主界面 +│ ├── ui_dev.py # 开发者窗口 +│ ├── admin_ui.py # 管理员窗口 +│ ├── themes.py # 7 套主题配色方案 +│ ├── android_templates.py # Android 项目模板 +│ ├── restore_template.py # 恢复脚本模板 +│ ├── app_bootstrap.py # 入口引导(自检、QApplication、图标) +│ ├── engine_window.py # 开发者/管理员窗口公共基类 +│ └── mcpk/ # MCPK 文件格式子包 +│ ├── __init__.py +│ ├── __main__.py +│ ├── cli.py # CLI 命令 +│ ├── constants.py # 常量与枚举 +│ ├── reader.py # MCPK 读取器 +│ ├── types.py # 数据类型 +│ └── writer.py # MCPK 写入器 +├── tests/ # 测试套件(90 条测试) +├── tools/ +│ └── render_ui_preview.py # UI 截图对比工具 +├── doc/ # 项目文档 +│ ├── dev-log.md # 开发日志 +│ ├── architecture.md # 软件架构文档 +│ ├── mcpk-technical.md # MCPK 技术文档 +│ ├── mcpk-usage.md # MCPK Beta 使用指南 +│ └── config-params.md # 配置参数参考 +├── html/ # 推广页面 +├── README.assets/ # 截图资源 +├── icon.ico # 应用图标 +├── apluse.spec # PyInstaller 打包配置 +├── pyproject.toml # 项目配置(pytest、ruff) +└── requirements.txt # 运行依赖 ``` -## 致谢 +## 开发环境与测试 -思路来源:[apate](https://github.com/rippod/apate) +- 依赖:`pip install -r requirements-dev.txt`(含 PyQt5、pytest、pytest-cov、pyinstaller、Pillow、ruff) +- 运行测试:`python -m pytest tests/ -q`(测试通过 conftest 自动隔离配置,不会改写真实 `apluse_config.json`) +- 覆盖率:`python -m pytest tests/ -q --cov=apluse.core --cov=apluse.mcpk --cov=apluse.engine_window --cov=apluse.restore_template --cov-report=term` +- Lint:`python -m ruff check .`(仅错误级规则,配置见 `pyproject.toml`) +- UI 截图对比:`python tools/render_ui_preview.py `,输出到 `ui_preview/`(离屏渲染,不弹窗、不碰真实配置) +- CI:`.github/workflows/tests.yml` 在 Windows 上跑 Python 3.8–3.13 测试矩阵 +- 打包:`pyinstaller apluse.spec --noconfirm`(等价于 README 中的 PyInstaller 命令) +## 文档索引 +| 文档 | 说明 | +|------|------| +| [开发日志](./doc/dev-log.md) | 版本迭代记录与每阶段优化详情 | +| [软件架构](./doc/architecture.md) | 分层架构、模块职责、数据流、测试架构 | +| [MCPK 技术文档](./doc/mcpk-technical.md) | MCPK 文件格式规范、加密架构、压缩算法 | +| [MCPK 使用指南](./doc/mcpk-usage.md) | MCPK Beta 功能的完整使用说明 | +| [配置参数参考](./doc/config-params.md) | 所有可配置参数的详细说明 | +## 工程优化历史 +本项目经过四个阶段的系统性工程优化,所有优化均保持功能零变更、回归测试全绿。 -**p.s:恢复源码见此** +### Phase 1+2:工程架构优化 -```python -import sys -import os -import struct -from pathlib import Path -MAGIC = bytes.fromhex("878afe7e") -CHUNK_SIZE = 4 * 1024 * 1024 - -def xor_bytes(data: bytes, key: bytes) -> bytes: - if not key: return data - return bytes([b ^ key[i % len(key)] for i, b in enumerate(data)]) - -def get_base_dir() -> Path: - if getattr(sys, "frozen", False) or "__compiled__" in globals(): - return Path(sys.argv[0]).resolve().parent - return Path(__file__).resolve().parent - -def is_disguised_file(p: Path) -> bool: - try: - if p.stat().st_size < (1+4+len(MAGIC)): return False - with open(p, "rb") as f: - f.seek(-len(MAGIC), os.SEEK_END) - return f.read(len(MAGIC)) == MAGIC - except Exception: return False - -def parse_metadata(f, sz): - f.seek(-len(MAGIC), os.SEEK_END) - if f.read(len(MAGIC)) != MAGIC: raise Exception("标记无效") - - try: - if sz >= len(MAGIC)+13: - f.seek(-(len(MAGIC)+13), os.SEEK_END) - dec = xor_bytes(f.read(13), MAGIC) - nlen, hlen, osize = dec[0], struct.unpack("= len(MAGIC)+13: - f.seek(-(len(MAGIC)+8), os.SEEK_END) - osize = struct.unpack(" 0: - read_size = min(CHUNK_SIZE, bytes_left) - f.seek(read_offset) - chunk = f.read(read_size) - write_offset = bytes_left - read_size - f.seek(write_offset) - f.write(chunk[::-1]) - read_offset += read_size - bytes_left -= read_size - f.truncate(meta["osize"]) - - dp = fp.parent / meta["name"] - rest = dp - if str(dp.resolve()) in reserved: - idx = 1 - while True: - c = dp.with_name(f"{dp.stem}_restored_{idx}{dp.suffix}") - if str(c.resolve()) not in reserved and not c.exists(): - rest = c; break - idx += 1 - fp.replace(rest) - return rest - -def main(): - bd = get_base_dir() - print(f"扫描: {bd}\n魔术字: {MAGIC.hex()}\n"+"-"*40) - res, fail = 0, 0 - reserved = {str(p.resolve()) for p in bd.rglob("*") if p.is_file()} - for p in bd.rglob("*"): - if p.is_file() and p.name.lower() not in {"restore_all_disguised.exe", "restore_all_disguised.py"}: - try: - reserved.discard(str(p.resolve())) - if is_disguised_file(p): - np = reveal_file(p, reserved) - reserved.add(str(np.resolve())) - res += 1 - print(f"[恢复] {p.name} -> {np.name}") - except Exception as e: fail += 1; print(f"[失败] {p.name} -> {e}") - print(f"\n完成: 成功 {res}, 失败 {fail}") - input("按回车退出...") - -if __name__ == "__main__": main() -``` +| 优化项 | 说明 | +|--------|------| +| 公共基类抽取 | 新增 `engine_window.py`,`ui_dev.py` / `admin_ui` 收敛约 150 行重复逻辑 | +| 模板独立化 | 新增 `restore_template.py`,将 180 行恢复脚本模板从 `core.py` 中抽出 | +| 入口引导抽象 | 新增 `app_bootstrap.py`,统一自检、QApplication 创建、图标加载流程 | +| 版本号单一来源 | 统一为 `core.APP_VERSION`,消除多处硬编码 | +| 代码质量工具链 | 新增 `pyproject.toml`、ruff、pre-commit、GitHub Actions CI | + +### Phase 2.5:测试覆盖率提升 + +| 优化项 | 说明 | +|--------|------| +| 测试数量 | 从 45 条增至 90 条 | +| 覆盖率 | 从 50% 提升至 65% | + +### Phase 3:行为级修复 + +| 修复项 | 说明 | +|--------|------| +| `reveal_file` 防覆盖 | 还原时自动追加 `_restored_N` 后缀 | +| 平台守卫 | `self_check` 仅在 Windows 下启用调试器检测 | +| 日志轮转 | 日志文件超过 5MB 时自动轮转 | +| 可配置密码 | 开发者窗口密码支持配置文件自定义 | + +### Phase 4:文件夹结构优化 + +| 优化项 | 说明 | +|--------|------| +| 包结构重组 | 根目录 10 个模块收拢到 `apluse/` 包 | +| 相对导入 | 包内所有 import 改为相对导入 | +| 死代码清理 | 删除 `old/` 目录和过期的 `beta.md` | + +## 致谢 + +思路来源:[apate](https://github.com/rippod/apate) + +MCPK 格式:[MeCapsule](https://github.com/tripodxu/MeCapsule) + +**p.s:恢复源码见此** -只需要将 MAGIC.hex() 修改为对应的魔术字字符即可 +恢复脚本的核心逻辑内嵌在生成的 `.exe` / `.py` 中,源码模板见 [`apluse/restore_template.py`](./apluse/restore_template.py)。只需将 `MAGIC.hex()` 修改为对应的魔术字字符即可。 **When generating the recovery exe file, you need to 'pip install pyinstaller'** diff --git a/admin_main.py b/admin_main.py index b23dfe5..3d0d30b 100644 --- a/admin_main.py +++ b/admin_main.py @@ -1,33 +1,5 @@ -import sys -import os -import ctypes -from PyQt5.QtWidgets import QApplication -from PyQt5.QtGui import QIcon -from core import PathManager -from admin_ui import AdminWindow - - -def self_check(): - if sys.gettrace() is not None: - ctypes.windll.user32.MessageBoxW( - 0, - "安全自检未通过:检测到非法调试环境!\n程序即将退出。", - "安全拦截", - 0x10, - ) - sys.exit(1) - +from apluse.app_bootstrap import run_app +from apluse.admin_ui import AdminWindow if __name__ == "__main__": - self_check() - - app = QApplication(sys.argv) - app.setStyle("Fusion") - - icon_path = os.path.join(PathManager.get_resource_dir(), "icon.ico") - if os.path.exists(icon_path): - app.setWindowIcon(QIcon(icon_path)) - - win = AdminWindow() - win.show() - sys.exit(app.exec_()) + run_app(AdminWindow) diff --git a/apluse.spec b/apluse.spec new file mode 100644 index 0000000..7ad9c1a --- /dev/null +++ b/apluse.spec @@ -0,0 +1,39 @@ +# -*- mode: python ; coding: utf-8 -*- + + +a = Analysis( + ['main.py'], + pathex=[], + binaries=[], + datas=[('icon.ico', '.')], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='apluse', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + icon=['icon.ico'], +) diff --git a/apluse/__init__.py b/apluse/__init__.py new file mode 100644 index 0000000..07f5210 --- /dev/null +++ b/apluse/__init__.py @@ -0,0 +1,18 @@ +"""APLUSE ENGINE - 文件伪装/还原工具包。 + +公共 API 快捷导入: + from apluse import DisguiseEngine, PathManager +""" + +from .core import ( + APP_VERSION, + DisguiseEngine, + DisguiseError, + PathManager, + collect_files_from_paths, + disguise_file, + format_file_size, + is_disguised_file, + magic_to_display_text, + reveal_file, +) diff --git a/admin_ui.py b/apluse/admin_ui.py similarity index 69% rename from admin_ui.py rename to apluse/admin_ui.py index 2d36cc3..dbe5b39 100644 --- a/admin_ui.py +++ b/apluse/admin_ui.py @@ -1,34 +1,39 @@ -from datetime import datetime +from datetime import datetime from pathlib import Path from PyQt5.QtCore import Qt from PyQt5.QtGui import QColor, QKeySequence from PyQt5.QtWidgets import ( - QWidget, QLabel, QPushButton, QFileDialog, QShortcut, - QTextEdit, QVBoxLayout, QHBoxLayout, QMessageBox, QLineEdit, + QShortcut, QTextEdit, QVBoxLayout, QHBoxLayout, QMessageBox, QLineEdit, QFrame, QProgressBar, QGraphicsDropShadowEffect, QCheckBox, ) -from core import ( - DisguiseEngine, DisguiseError, collect_files_from_paths, - PathManager, magic_to_display_text, format_file_size, +from .core import ( + DisguiseEngine, DisguiseError, + PathManager, magic_to_display_text, ) -from themes import PALETTES, THEME_NAMES, build_qss, parse_shadow_color -from ui import EngineWorker, CustomTitleBar, CustomDropList +from .themes import PALETTES, build_qss, parse_shadow_color +from .ui import CustomTitleBar, CustomDropList +from .engine_window import EngineWindowBase + + +class AdminWindow(EngineWindowBase): + # ── 与开发者窗口的差异文案(基类默认值被此处覆盖)── + ITEM_TEXT_TMPL = "{name} ({size})" + TARGET_FILE_FILTER = "所有文件 (*)" + TARGET_DIR_DIALOG_TITLE = "选择目标目录" + MASK_DIR_DIALOG_TITLE = "选择面具目录" + MASK_FILE_FILTER = "视频/图片 (*.mp4 *.jpg *.png *.bmp *.mkv *.avi *.mov);;所有文件 (*)" + MSG_TARGET_ADDED = "目标队列新增 {added} 个文件" + MSG_MASK_ADDED = "面具库新增 {added} 个文件" + MSG_EMPTY_QUEUE = "中断:目标队列为空" + MSG_ERROR = "任务异常: {err}" + TOGGLE_TEXT = "即将对 {count} 个文件执行自动伪装/还原。\n\n确定继续吗?" - -class AdminWindow(QWidget): def __init__(self): - super().__init__() - self.engine = DisguiseEngine() - self.current_worker = None - + super().__init__(DisguiseEngine()) self.log_file_path = PathManager.get_persist_dir() / "apluse.log" - self.setWindowFlags(Qt.FramelessWindowHint) - self.setAttribute(Qt.WA_TranslucentBackground) - self.resize(1280, 860) - self._init_ui() self._setup_shortcuts() @@ -207,82 +212,43 @@ def ui_reset_magic(self): self.magic_edit.setText(magic.hex()) self.cb_log(f"已恢复默认钥匙: {magic_to_display_text(magic)}") - def ui_add_target_paths(self, paths): - before = len(self.engine.target_files) - self.engine.target_files.extend(collect_files_from_paths(paths)) - self.engine.target_files = list(dict.fromkeys(self.engine.target_files)) - added = len(self.engine.target_files) - before - self.cb_log(f"目标队列新增 {added} 个文件") - self._refresh_targets() - - def ui_add_mask_paths(self, paths): - before = set(self.engine.mask_library) - for p in collect_files_from_paths(paths): - if p not in self.engine.mask_library: - self.engine.mask_library.append(p) - added = len(set(self.engine.mask_library) - before) - self.engine.save_config() - self.cb_log(f"面具库新增 {added} 个文件") - self._refresh_masks() - - def ui_select_targets(self): - paths, _ = QFileDialog.getOpenFileNames(self, "选择目标文件", "", "所有文件 (*)") - if paths: - self.ui_add_target_paths(paths) - - def ui_select_target_folder(self): - d = QFileDialog.getExistingDirectory(self, "选择目标目录") - if d: - self.ui_add_target_paths([d]) - def ui_clear_targets(self): self.engine.target_files.clear() - self._refresh_targets() - - def ui_select_masks(self): - paths, _ = QFileDialog.getOpenFileNames(self, "选择面具文件", "", "视频/图片 (*.mp4 *.jpg *.png *.bmp *.mkv *.avi *.mov);;所有文件 (*)") - if paths: - self.ui_add_mask_paths(paths) - - def ui_select_mask_folder(self): - d = QFileDialog.getExistingDirectory(self, "选择面具目录") - if d: - self.ui_add_mask_paths([d]) + self.refresh_target_list() def ui_clear_masks(self): self.engine.mask_library.clear() self.engine.save_config() - self._refresh_masks() + self.refresh_mask_list() - def _refresh_targets(self): + def refresh_target_list(self): self.target_list.clear() for p in self.engine.target_files: - self.target_list.addItem(f"{Path(p).name} ({format_file_size(Path(p).stat().st_size)})") + self.target_list.addItem(self._make_file_item(p)) - def _refresh_masks(self): + def refresh_mask_list(self): self.mask_list.clear() for p in self.engine.mask_library: - self.mask_list.addItem(f"{Path(p).name} ({format_file_size(Path(p).stat().st_size)})") + self.mask_list.addItem(self._make_file_item(p)) + + def _selected_paths(self, lst): + return {lst.item(i).data(Qt.UserRole) for i in range(lst.count()) if lst.item(i).isSelected()} - {None} def _delete_selected(self): - sel = [self.target_list.item(i).text().split(" (")[0] for i in range(self.target_list.count()) if self.target_list.item(i).isSelected()] + sel = self._selected_paths(self.target_list) if sel: - self.engine.target_files = [p for p in self.engine.target_files if Path(p).name not in sel] - self._refresh_targets() + self.engine.target_files = [p for p in self.engine.target_files if p not in sel] + self.refresh_target_list() return - sel = [self.mask_list.item(i).text().split(" (")[0] for i in range(self.mask_list.count()) if self.mask_list.item(i).isSelected()] + sel = self._selected_paths(self.mask_list) if sel: - keep = [] - for p in self.engine.mask_library: - if Path(p).name not in sel: - keep.append(p) - self.engine.mask_library = keep + self.engine.mask_library = [p for p in self.engine.mask_library if p not in sel] self.engine.save_config() - self._refresh_masks() + self.refresh_mask_list() def ui_detect(self): if not self.engine.target_files: - return self.cb_log("中断:目标队列为空") + return self.cb_log(self.MSG_EMPTY_QUEUE) try: def done(res): self.set_ui_busy(False) @@ -293,43 +259,25 @@ def done(res): except Exception as e: self.cb_log(f"检测失败: {e}") + def rename_checkbox(self): + return self.chk_rename + + def mapping_checkbox(self): + return self.chk_mapping + def ui_toggle(self): - if not self.engine.target_files: - return self.cb_log("中断:目标队列为空") - reply = QMessageBox.question( - self, "确认执行", - f"即将对 {len(self.engine.target_files)} 个文件执行自动伪装/还原。\n\n确定继续吗?", - QMessageBox.Yes | QMessageBox.No, QMessageBox.No, - ) - if reply != QMessageBox.Yes: + if not self.prepare_toggle(): return - self.engine.rename_mapping = self.chk_rename.isChecked() - self.engine.disguise_mapping_txt = self.chk_mapping.isChecked() - if self.engine.rename_mapping: - mapping_dir = self.engine.get_common_target_parent_dir() - self.engine.mapping_output_path = str(mapping_dir / "mapping.txt") - else: - self.engine.mapping_output_path = None - def done(res): self.set_ui_busy(False) s, f = res - self._refresh_targets() + self.refresh_target_list() self.cb_progress(1, 1, "引擎挂起", f"成功:{s} | 失败:{len(f)}") self.cb_log(f"执行周期结束。成功 {s} 失败 {len(f)}") self._run_task(self.engine.handle_toggle, done) - def _run_task(self, task_fn, done_cb): - self.set_ui_busy(True) - self.current_worker = EngineWorker(task_fn) - self.current_worker.log_sig.connect(self.cb_log) - self.current_worker.prog_sig.connect(self.cb_progress) - self.current_worker.done_sig.connect(done_cb) - self.current_worker.err_sig.connect(lambda e: (self.set_ui_busy(False), self.cb_log(f"任务异常: {e}"))) - self.current_worker.start() - def set_ui_busy(self, busy): for w in [ self.btn_detect, self.btn_start, @@ -339,7 +287,7 @@ def set_ui_busy(self, busy): self.btn_start.setText("🚀 引擎高速处理中..." if busy else "🚀 启动引擎") def _label(self, text, obj_name="subText"): - lb = QLabel(text); lb.setObjectName(obj_name); return lb + return self.make_label(text, obj_name) def _btn(self, text, role="secondary"): - b = QPushButton(text); b.setProperty("role", role); return b + return self.make_btn(text, role) diff --git a/android_templates.py b/apluse/android_templates.py similarity index 99% rename from android_templates.py rename to apluse/android_templates.py index 0ac7775..858cc09 100644 --- a/android_templates.py +++ b/apluse/android_templates.py @@ -69,7 +69,7 @@ GRADLE_WRAPPER_PROPERTIES = """\ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.5-bin.zip +distributionUrl=https\\://mirrors.cloud.tencent.com/gradle/gradle-8.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists """ diff --git a/apluse/app_bootstrap.py b/apluse/app_bootstrap.py new file mode 100644 index 0000000..da7a016 --- /dev/null +++ b/apluse/app_bootstrap.py @@ -0,0 +1,43 @@ +"""GUI 入口公共引导:自检、QApplication、图标与主循环,供 main.py / admin_main.py 复用。""" +import sys +import ctypes + +from PyQt5.QtWidgets import QApplication +from PyQt5.QtGui import QIcon + +from .core import PathManager + + +def self_check(): + """ + 基础的程序自检与环境校验。 + 防止程序被逆向人员使用调试器(如 PySnooper, PDB 等)动态调试分析。 + 仅在 Windows 下启用(ctypes.windll 仅 Windows 可用)。 + """ + if sys.platform != "win32": + return + if sys.gettrace() is not None: + ctypes.windll.user32.MessageBoxW( + 0, + "安全自检未通过:检测到非法调试环境!\n程序即将退出。", + "安全拦截", + 0x10, + ) + sys.exit(1) + + +def run_app(window_cls): + """启动指定主窗口类(在创建 QApplication 前先执行自检,与原入口行为一致)。""" + self_check() + + app = QApplication(sys.argv) + app.setStyle("Fusion") + + # 使用 PathManager.get_resource_dir() 精准寻找 Nuitka/PyInstaller 的资源解压目录 + icon_path = PathManager.get_resource_dir() / "icon.ico" + if icon_path.exists(): + app.setWindowIcon(QIcon(str(icon_path))) + + win = window_cls() + win.show() + sys.exit(app.exec_()) diff --git a/core.py b/apluse/core.py similarity index 86% rename from core.py rename to apluse/core.py index 5e267ae..9bf132f 100644 --- a/core.py +++ b/apluse/core.py @@ -9,6 +9,8 @@ from pathlib import Path DEFAULT_MAGIC = b"DGSK" +# 应用版本号(标题栏、生成的 Android 恢复包等处的单一来源) +APP_VERSION = "3.4" # 全局新版配置文件 CONFIG_FILE_NAME = "apluse_config.json" # 兼容旧版的配置文件名 @@ -71,7 +73,8 @@ def normalize_config(data) -> dict: return { "mask_library": mask_library, "magic_hex": magic_hex, - "theme_index": theme_index + "theme_index": theme_index, + "dev_password": str(data.get("dev_password", "")), } def load_config() -> dict: @@ -159,13 +162,15 @@ def collect_files_from_paths(paths): if path.is_file(): s = str(path.resolve()) if s not in seen: - seen.add(s), results.append(s) + seen.add(s) + results.append(s) elif path.is_dir(): for sub in path.rglob("*"): if sub.is_file(): s = str(sub.resolve()) if s not in seen: - seen.add(s), results.append(s) + seen.add(s) + results.append(s) return results # =================== 反侦察: 字节异或加密 =================== @@ -371,7 +376,9 @@ def reveal_file(file_path: str, magic: bytes, reserved_output_paths=None) -> str desired_path = file_path.parent / meta["original_name"] restored_path = desired_path - if str(desired_path.resolve()) in {str(Path(p).resolve()) for p in (reserved_output_paths or [])}: + reserved_set = {str(Path(p).resolve()) for p in (reserved_output_paths or [])} + # 目标已存在且不在本次保留列表 → 自动加后缀,防止覆盖已有文件 + if str(desired_path.resolve()) in reserved_set or desired_path.exists(): restored_path = build_non_conflicting_path(desired_path, "restored", reserved_output_paths) try: shutil.move(str(file_path), str(restored_path)) @@ -383,196 +390,18 @@ def reveal_file(file_path: str, magic: bytes, reserved_output_paths=None) -> str # =================== 恢复脚本模板 =================== # 解析逻辑与 parse_disguised_metadata 保持一致,避免维护两份代码。 -_RESTORE_SCRIPT_TEMPLATE = '''import sys -import os -import struct -from pathlib import Path - -MAGIC = bytes.fromhex("__MAGIC_HEX__") -CHUNK_SIZE = 4 * 1024 * 1024 -SELF_NAMES = {__SELF_NAMES_SET__} - - -def xor_bytes(data: bytes, key: bytes) -> bytes: - if not key: - return data - return bytes([b ^ key[i % len(key)] for i, b in enumerate(data)]) - - -def get_base_dir() -> Path: - if getattr(sys, "frozen", False) or "__compiled__" in globals(): - return Path(sys.argv[0]).resolve().parent - return Path(__file__).resolve().parent - - -def is_disguised_file(p: Path) -> bool: - try: - if p.stat().st_size < (1 + 4 + len(MAGIC)): - return False - with open(p, "rb") as f: - f.seek(-len(MAGIC), os.SEEK_END) - return f.read(len(MAGIC)) == MAGIC - except Exception: - return False - - -def _try_parse_name(raw_bytes, magic): - """尝试从原始字节中解析文件名,优先 XOR 解密,回退明文。""" - try: - decoded = xor_bytes(raw_bytes, magic).decode("utf-8") - if decoded and decoded == Path(decoded).name and decoded not in (".", ".."): - return decoded - except Exception: - pass - try: - decoded = raw_bytes.decode("utf-8") - if decoded and decoded == Path(decoded).name and decoded not in (".", ".."): - return decoded - except Exception: - pass - return None - - -def parse_metadata(f, sz): - f.seek(-len(MAGIC), os.SEEK_END) - if f.read(len(MAGIC)) != MAGIC: - raise Exception("标记无效") - - # v4: 加密元数据 name_len(1B XOR) + head_len(4B XOR) + original_size(8B XOR) + magic - try: - if sz >= len(MAGIC) + 13: - f.seek(-(len(MAGIC) + 13), os.SEEK_END) - dec_meta = xor_bytes(f.read(13), MAGIC) - nlen = dec_meta[0] - hlen = struct.unpack("= len(MAGIC) + 13: - f.seek(-(len(MAGIC) + 8), os.SEEK_END) - osize = struct.unpack(" sz: - raise Exception("名称长度非法") - npos = sz - len(MAGIC) - 5 - nlen - if npos < 0: - raise Exception("name_pos 非法") - hpos = npos - hlen - if hpos < 0: - raise Exception("head_pos 非法") - f.seek(npos) - raw_name = f.read(nlen) - if len(raw_name) != nlen: - raise Exception("名称长度不足") - # 一次性读取完毕,后续不再 seek/read,避免文件指针问题 - name = _try_parse_name(raw_name, MAGIC) - if not name: - # 回退:当作后缀拼接 - try: - suffix = raw_name.decode("utf-8") - except Exception as e: - raise Exception(f"无法解析后缀: {e}") - name = Path(fp_name).stem + suffix - return {"hlen": hlen, "hpos": hpos, "name": name, "osize": hpos} - - -def reveal_file(fp: Path, reserved): - global fp_name - fp_name = fp.name - with open(fp, "r+b") as f: - meta = parse_metadata(f, fp.stat().st_size) - bytes_left, read_offset = meta["hlen"], meta["hpos"] - while bytes_left > 0: - read_size = min(CHUNK_SIZE, bytes_left) - f.seek(read_offset) - chunk = f.read(read_size) - write_offset = bytes_left - read_size - f.seek(write_offset) - f.write(chunk[::-1]) - read_offset += read_size - bytes_left -= read_size - f.truncate(meta["osize"]) - - dp = fp.parent / meta["name"] - rest = dp - if str(dp.resolve()) in reserved: - idx = 1 - while True: - c = dp.with_name(f"{dp.stem}_restored_{idx}{dp.suffix}") - if str(c.resolve()) not in reserved and not c.exists(): - rest = c - break - idx += 1 - fp.replace(rest) - return rest - - -fp_name = "" - - -def main(): - global fp_name - bd = get_base_dir() - print(f"扫描: {bd}\\n魔术字: {MAGIC.hex()}\\n" + "-" * 40) - res, fail = 0, 0 - reserved = {str(p.resolve()) for p in bd.rglob("*") if p.is_file()} - for p in bd.rglob("*"): - if p.is_file() and p.name.lower() not in SELF_NAMES: - try: - reserved.discard(str(p.resolve())) - if is_disguised_file(p): - fp_name = p.name - np = reveal_file(p, reserved) - reserved.add(str(np.resolve())) - res += 1 - print(f"[恢复] {p.name} -> {np.name}") - except Exception as e: - fail += 1 - print(f"[失败] {p.name} -> {e}") - print(f"\\n完成: 成功 {res}, 失败 {fail}") - input("按回车退出...") -if __name__ == "__main__": - main() -''' +# 恢复脚本模板独立成模块(restore_template.py),避免 180 行字符串占据主引擎文件。 +# 模板以 .replace() 填充占位符,因此这里不能用 format。 +from .restore_template import RESTORE_SCRIPT_TEMPLATE def _build_restore_script(magic_hex: str, script_filename: str) -> str: """根据模板生成恢复脚本源码。""" self_names = f'"{script_filename}", "{script_filename.replace(".py", ".exe")}"' return ( - _RESTORE_SCRIPT_TEMPLATE + RESTORE_SCRIPT_TEMPLATE .replace("__MAGIC_HEX__", magic_hex) .replace("__SELF_NAMES_SET__", self_names) ) @@ -580,7 +409,7 @@ def _build_restore_script(magic_hex: str, script_filename: str) -> str: def _build_android_project(magic_hex: str, project_dir: Path, script_name: str): """将 Android 项目模板写入指定目录。""" - from android_templates import ( + from .android_templates import ( GRADLE_SETTINGS, PROJECT_BUILD_GRADLE, APP_BUILD_GRADLE, GRADLE_PROPERTIES, GRADLE_WRAPPER_PROPERTIES, ANDROID_MANIFEST, ACTIVITY_MAIN_XML, STRINGS_XML, COLORS_XML, STYLES_XML, @@ -588,7 +417,7 @@ def _build_android_project(magic_hex: str, project_dir: Path, script_name: str): ) pkg = "com.apluse.restore" - app_name = "APLUSE 3.4" + app_name = f"APLUSE {APP_VERSION}" self_names_set = f'"{script_name}", "{script_name.replace(".py", ".exe")}"' def apply(text): @@ -1035,7 +864,7 @@ def generate_mcpk(self, output_path, log_cb=None, progress_cb=None, Returns: 输出文件路径 (str) """ - from mcpk import MCPKWriter + from .mcpk import MCPKWriter if not self.target_files: raise DisguiseError("目标队列为空,无法打包") @@ -1096,7 +925,7 @@ def inspect_mcpk(mcpk_path, password=None): dict: 包含 entries, file_size, entry_count, total_original_size, encrypted, groups, relations, packed_at 等 """ - from mcpk import MCPKReader, MCPKError as _MCPKError + from .mcpk import MCPKReader, MCPKError as _MCPKError mcpk_path = Path(mcpk_path) if not mcpk_path.is_file(): @@ -1143,7 +972,7 @@ def extract_mcpk_entry(mcpk_path, entry_name, output_dir, password=None): Returns: 提取文件的路径 (str) """ - from mcpk import MCPKReader, MCPKError as _MCPKError + from .mcpk import MCPKReader, MCPKError as _MCPKError try: with MCPKReader(mcpk_path, password=password) as reader: @@ -1167,7 +996,7 @@ def extract_mcpk_all(mcpk_path, output_dir, password=None): Returns: 提取文件路径列表 (list[str]) """ - from mcpk import MCPKReader, MCPKError as _MCPKError + from .mcpk import MCPKReader, MCPKError as _MCPKError try: with MCPKReader(mcpk_path, password=password) as reader: diff --git a/apluse/engine_window.py b/apluse/engine_window.py new file mode 100644 index 0000000..376543a --- /dev/null +++ b/apluse/engine_window.py @@ -0,0 +1,163 @@ +"""DeveloperWindow(ui_dev)与 AdminWindow(admin_ui)的公共基类。 + +只收敛两窗口完全一致的机械逻辑(队列装载、文件对话框、异步任务、启动确认); +文案与控件差异通过类属性 / 钩子方法注入,保证重构前后每个用户可见字符串 +与交互行为逐字不变。子类必须实现:cb_log / cb_progress / set_ui_busy / +refresh_target_list / refresh_mask_list / rename_checkbox / mapping_checkbox。 +""" +from pathlib import Path + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import ( + QWidget, QLabel, QPushButton, QFileDialog, QMessageBox, QListWidgetItem, +) + +from .core import collect_files_from_paths, format_file_size +from .ui import EngineWorker + + +class EngineWindowBase(QWidget): + # ── 子类可覆盖的差异项(默认值为开发者窗口的文案)── + WINDOW_FLAGS = Qt.FramelessWindowHint + ITEM_TEXT_TMPL = "{name} [{size}]" + TARGET_DIALOG_TITLE = "选择目标文件" + TARGET_DIR_DIALOG_TITLE = "选择目标文件夹" + TARGET_FILE_FILTER = "" + MASK_DIALOG_TITLE = "选择面具文件" + MASK_DIR_DIALOG_TITLE = "选择面具文件夹" + MASK_FILE_FILTER = "" + MSG_TARGET_ADDED = "目标装载: +{added} 项" + MSG_MASK_ADDED = "面具库扩充: +{added} 项" + MSG_EMPTY_QUEUE = "中断: 目标队列为空" + MSG_ERROR = "[ERROR] 内核异常: {err}" + TOGGLE_TEXT = "即将对 {count} 个文件执行自动伪装/还原操作。\n\n确定继续吗?" + + def __init__(self, engine): + super().__init__() + self.engine = engine + self.current_worker = None + self.setWindowFlags(self.WINDOW_FLAGS) + self.setAttribute(Qt.WA_TranslucentBackground) + self.resize(1280, 860) + + # ── 控件工厂 ── + @staticmethod + def make_label(text, obj_name="subText"): + lbl = QLabel(text) + lbl.setObjectName(obj_name) + return lbl + + @staticmethod + def make_btn(text, role="default"): + btn = QPushButton(text) + btn.setProperty("role", role) + return btn + + # ── 列表条目:显示「文件名 + 大小」,真实路径存 UserRole,悬停显示完整路径 ── + def _make_file_item(self, filepath): + try: + size_str = format_file_size(Path(filepath).stat().st_size) + except Exception: + size_str = "?" + item = QListWidgetItem(self.ITEM_TEXT_TMPL.format(name=Path(filepath).name, size=size_str)) + item.setData(Qt.UserRole, filepath) + item.setToolTip(str(filepath)) + return item + + # ── 队列装载(结果与旧实现等价:按 collect 顺序去重追加)── + def ui_add_target_paths(self, paths): + added = 0 + for f in collect_files_from_paths(paths): + if f not in self.engine.target_files: + self.engine.target_files.append(f) + added += 1 + self.refresh_target_list() + self.cb_log(self.MSG_TARGET_ADDED.format(added=added)) + + def ui_add_mask_paths(self, paths): + added = 0 + for f in collect_files_from_paths(paths): + if f not in self.engine.mask_library: + self.engine.mask_library.append(f) + added += 1 + self.engine.save_config() + self.refresh_mask_list() + self.cb_log(self.MSG_MASK_ADDED.format(added=added)) + + # ── 文件对话框 ── + def ui_select_targets(self): + paths, _ = QFileDialog.getOpenFileNames(self, self.TARGET_DIALOG_TITLE, "", self.TARGET_FILE_FILTER) + if paths: + self.ui_add_target_paths(paths) + + def ui_select_target_folder(self): + folder = QFileDialog.getExistingDirectory(self, self.TARGET_DIR_DIALOG_TITLE) + if folder: + self.ui_add_target_paths([folder]) + + def ui_select_masks(self): + paths, _ = QFileDialog.getOpenFileNames(self, self.MASK_DIALOG_TITLE, "", self.MASK_FILE_FILTER) + if paths: + self.ui_add_mask_paths(paths) + + def ui_select_mask_folder(self): + folder = QFileDialog.getExistingDirectory(self, self.MASK_DIR_DIALOG_TITLE) + if folder: + self.ui_add_mask_paths([folder]) + + # ── 异步任务 ── + def _run_task(self, task_fn, done_cb): + self.set_ui_busy(True) + self.current_worker = EngineWorker(task_fn) + self.current_worker.log_sig.connect(self.cb_log) + self.current_worker.prog_sig.connect(self.cb_progress) + self.current_worker.done_sig.connect(done_cb) + self.current_worker.err_sig.connect(self._on_task_error) + self.current_worker.start() + + def _on_task_error(self, err_msg): + self.set_ui_busy(False) + self.cb_log(self.MSG_ERROR.format(err=err_msg)) + + # ── 引擎启动前的确认与映射准备 ── + def prepare_toggle(self): + if not self.engine.target_files: + self.cb_log(self.MSG_EMPTY_QUEUE) + return False + reply = QMessageBox.question( + self, "确认执行", + self.TOGGLE_TEXT.format(count=len(self.engine.target_files)), + QMessageBox.Yes | QMessageBox.No, QMessageBox.No, + ) + if reply != QMessageBox.Yes: + return False + self.engine.rename_mapping = self.rename_checkbox().isChecked() + self.engine.disguise_mapping_txt = self.mapping_checkbox().isChecked() + if self.engine.rename_mapping: + mapping_dir = self.engine.get_common_target_parent_dir() + self.engine.mapping_output_path = str(mapping_dir / "mapping.txt") + else: + self.engine.mapping_output_path = None + return True + + def rename_checkbox(self): + raise NotImplementedError + + def mapping_checkbox(self): + raise NotImplementedError + + # ── 子类必须实现的接口 ── + def cb_log(self, text): + raise NotImplementedError + + def cb_progress(self, curr, total, title, detail): + raise NotImplementedError + + def set_ui_busy(self, busy): + raise NotImplementedError + + def refresh_target_list(self): + raise NotImplementedError + + def refresh_mask_list(self): + raise NotImplementedError diff --git a/mcpk/__init__.py b/apluse/mcpk/__init__.py similarity index 100% rename from mcpk/__init__.py rename to apluse/mcpk/__init__.py diff --git a/mcpk/__main__.py b/apluse/mcpk/__main__.py similarity index 100% rename from mcpk/__main__.py rename to apluse/mcpk/__main__.py diff --git a/mcpk/cli.py b/apluse/mcpk/cli.py similarity index 100% rename from mcpk/cli.py rename to apluse/mcpk/cli.py diff --git a/mcpk/constants.py b/apluse/mcpk/constants.py similarity index 100% rename from mcpk/constants.py rename to apluse/mcpk/constants.py diff --git a/mcpk/reader.py b/apluse/mcpk/reader.py similarity index 100% rename from mcpk/reader.py rename to apluse/mcpk/reader.py diff --git a/mcpk/types.py b/apluse/mcpk/types.py similarity index 100% rename from mcpk/types.py rename to apluse/mcpk/types.py diff --git a/apluse/mcpk/writer.py b/apluse/mcpk/writer.py new file mode 100644 index 0000000..1511148 --- /dev/null +++ b/apluse/mcpk/writer.py @@ -0,0 +1,1093 @@ +"""MCPK v2 文件写入工具。 + +支持: +- Magic Index Table(文件签名聚合) +- 分组存储(相关文件物理相邻) +- Group Index(分组元数据 + 组间关系 + 组内关系 + 标签) +- VIDEO 条目类型 +- 完整时间戳(created_at / modified_at / packed_at) +- 可选 XOR 流加密(v2.1 兼容) +- 可选 AES-256-GCM 认证加密(v2.2 高强度) +- 按文件夹打包(import_folder) +- JSON 索引打包(load_index) +""" + +from __future__ import annotations + +import binascii +import hashlib +import json +import os +import struct +import time +import warnings +import zlib +from pathlib import Path +from typing import Optional, Union + +from .constants import ( + MAGIC, VERSION, HEADER_SIZE, FOOTER_SIZE, MAGIC_ENTRY_SIZE, + MAGIC_INDEX_MAGIC, GROUP_INDEX_MAGIC, ENCRYPTION_PARAMS_MAGIC, + NO_GROUP, ENCRYPTION_PARAMS_SIZE_LEGACY, ENCRYPTION_PARAMS_SIZE_V2, + ENCRYPTION_PARAMS_SIZE, AES_GCM_NONCE_SIZE, AES_GCM_TAG_SIZE, + PBKDF2_DEFAULT_ITERATIONS, + HEADER_FMT, FOOTER_FMT, TOC_ENTRY_FIXED_FMT, + MAGIC_INDEX_HEADER_FMT, MAGIC_INDEX_HEADER_SIZE, + MAGIC_ENTRY_FMT, GROUP_INDEX_HEADER_FMT, GROUP_INDEX_HEADER_SIZE, + ENCRYPTION_PARAMS_FMT_LEGACY, ENCRYPTION_PARAMS_FMT_V2, + EntryType, Compression, GroupType, RelationType, IntraRelationType, + EncryptionMode, KdfType, FLAG_ENCRYPTED, + EXTENSION_MAP, FILE_MAGICS, +) +from .types import ( + FileHeader, TocEntry, MagicEntry, GroupEntry, GroupRelation, + IntraRelation, EncryptionParams, +) + +# ── 可选依赖:cryptography(AES-GCM)────────────────────── +try: + from cryptography.hazmat.primitives.ciphers.aead import AESGCM + from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC + from cryptography.hazmat.primitives.kdf.hkdf import HKDF + from cryptography.hazmat.primitives import hashes + HAS_CRYPTO = True +except ImportError: + HAS_CRYPTO = False + + +# ── 加密工具函数 ──────────────────────────────────────────── + +def xor_bytes(data: bytes, key: bytes) -> bytes: + """XOR 流加密/解密(与 1apluse xor_bytes 一致)。""" + if not key: + return data + key_len = len(key) + result = bytearray(data) + for i in range(len(result)): + result[i] ^= key[i % key_len] + return bytes(result) + + +# ── 可选压缩模块缓存 ────────────────────────────────────── +_zstd_mod = None +_lz4_mod = None +_zstd_checked = False +_lz4_checked = False + +def _get_zstd(): + global _zstd_mod, _zstd_checked + if not _zstd_checked: + _zstd_checked = True + try: + import zstd as _m + _zstd_mod = _m + except ImportError: + pass + return _zstd_mod + +def _get_lz4(): + global _lz4_mod, _lz4_checked + if not _lz4_checked: + _lz4_checked = True + try: + import lz4.frame as _m + _lz4_mod = _m + except ImportError: + pass + return _lz4_mod + + +# ── XOR 模式密钥派生(v2.1 兼容)──────────────────────── + +def _derive_key(password: str, salt: bytes) -> bytes: + """从密码 + salt 派生 32 字节主密钥(XOR 模式)。""" + pwd_bytes = password.encode("utf-8") + seed = bytes( + pwd_bytes[i % len(pwd_bytes)] ^ salt[i % len(salt)] + for i in range(32) + ) + return hashlib.sha256(seed + salt).digest() + + +def _derive_control_key(master_key: bytes) -> bytes: + """派生控制区加密密钥(XOR 模式)。""" + return hashlib.sha256(master_key + b"ctrl").digest() + + +def _derive_blob_key(master_key: bytes, entry_id: int, entry_salt: bytes) -> bytes: + """派生单条目 blob 加密密钥(XOR 模式)。""" + id_bytes = struct.pack(" bytes: + """PBKDF2-SHA256 派生 256-bit 主密钥。""" + if not HAS_CRYPTO: + raise ImportError("AES-GCM 加密需要 cryptography 库: pip install cryptography") + kdf = PBKDF2HMAC( + algorithm=hashes.SHA256(), + length=32, + salt=salt, + iterations=iterations, + ) + return kdf.derive(password.encode("utf-8")) + + +def _derive_subkeys_aes(master_key: bytes) -> tuple[bytes, bytes]: + """从主密钥派生控制区密钥和数据区密钥基(HKDF-SHA256)。""" + control_key = HKDF( + algorithm=hashes.SHA256(), length=32, + salt=None, info=b"mcpk-ctrl", + ).derive(master_key) + data_key_base = HKDF( + algorithm=hashes.SHA256(), length=32, + salt=None, info=b"mcpk-data", + ).derive(master_key) + return control_key, data_key_base + + +def _derive_blob_key_aes(master_key: bytes, entry_id: int, entry_salt: bytes) -> bytes: + """派生单条目 blob 加密密钥(AES 模式,HKDF)。""" + id_bytes = struct.pack(" bytes: + """AES-256-GCM 加密。返回 nonce(12B) + ciphertext + tag(16B)。""" + nonce = os.urandom(AES_GCM_NONCE_SIZE) + aesgcm = AESGCM(key) + ct = aesgcm.encrypt(nonce, plaintext, aad) + return nonce + ct + + +def aes_gcm_decrypt(key: bytes, data: bytes, aad: bytes = b"") -> bytes: + """AES-256-GCM 解密。输入 nonce(12B) + ciphertext + tag(16B)。""" + nonce = data[:AES_GCM_NONCE_SIZE] + ct = data[AES_GCM_NONCE_SIZE:] + aesgcm = AESGCM(key) + return aesgcm.decrypt(nonce, ct, aad) + + +# ── Writer ────────────────────────────────────────────────── + +class MCPKWriter: + """ + MCPK v2 文件写入器。 + + 用法: + # 不加密(默认) + with MCPKWriter("output.mcpk") as writer: + writer.add_file("report.pdf") + + # AES-256-GCM 加密(需 cryptography) + with MCPKWriter("secret.mcpk", password="mypass") as writer: + writer.add_file("private.md") + + # XOR 加密(兼容模式,零依赖) + with MCPKWriter("compat.mcpk", password="mypass", encryption="xor") as writer: + writer.add_file("video.mp4") + + # 按文件夹打包 + with MCPKWriter("folders.mcpk") as writer: + writer.import_folder("path/to/folder_A") + writer.import_folder("path/to/folder_B", tags=["项目B"]) + + # 从 JSON 索引打包 + with MCPKWriter("indexed.mcpk") as writer: + writer.load_index("index.json", base_dir="./project/") + """ + + def __init__( + self, + output_path: Union[str, Path], + *, + password: Optional[str] = None, + encrypt_mode: Union[str, int] = EncryptionMode.FULL, + encryption: str = "xor", # "aes" | "xor" (默认 xor,兼容无 cryptography 环境) + ): + self.output_path = Path(output_path) + self._file = None + self._closed = False + + # 条目和 blob 缓存 + self._entries: list[TocEntry] = [] + self._blobs: list[bytes] = [] + + # 分组管理 + self._groups: dict[str, GroupEntry] = {} + self._group_id_counter: int = 0 + self._relations: list[GroupRelation] = [] + + # 加密状态 + self._password = password + self._encrypted = password is not None + self._master_key: Optional[bytes] = None + self._control_key: Optional[bytes] = None + self._data_key_base: Optional[bytes] = None # AES 模式专用 + self._salt: bytes = b"" + self._encrypt_mode = EncryptionMode.NONE + self._kdf_type = KdfType.SHA256_XOR + self._kdf_iterations = 0 + + if self._encrypted: + # 解析加密模式 + if isinstance(encrypt_mode, str): + mode_map = { + "full": EncryptionMode.FULL, + "metadata_only": EncryptionMode.METADATA_ONLY, + "data_only": EncryptionMode.DATA_ONLY, + "none": EncryptionMode.NONE, + } + self._encrypt_mode = mode_map.get(encrypt_mode.lower(), EncryptionMode.FULL) + else: + self._encrypt_mode = EncryptionMode(encrypt_mode) + + if self._encrypt_mode == EncryptionMode.NONE: + self._encrypted = False + self._password = None + else: + # 解析加密算法 + encryption = encryption.lower().strip() + if encryption == "aes": + if HAS_CRYPTO: + self._kdf_type = KdfType.PBKDF2_AES + self._kdf_iterations = PBKDF2_DEFAULT_ITERATIONS + self._salt = os.urandom(32) + self._master_key = _derive_key_pbkdf2( + password, self._salt, self._kdf_iterations + ) + self._control_key, self._data_key_base = _derive_subkeys_aes( + self._master_key + ) + else: + raise ImportError( + "AES-256-GCM 加密需要 cryptography 库。" + "请安装: pip install cryptography," + "或使用 encryption='xor' 回退到 XOR 模式。" + ) + + if encryption == "xor": + self._kdf_type = KdfType.SHA256_XOR + self._kdf_iterations = 0 + self._salt = os.urandom(16) + self._master_key = _derive_key(password, self._salt) + self._control_key = _derive_control_key(self._master_key) + + def __enter__(self): + self._file = open(self.output_path, "wb") + self._file.write(b"\x00" * HEADER_SIZE) + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if not self._closed: + self.finalize() + + # ── 分组 API ──────────────────────────────────────────── + + def create_group( + self, name: str, group_type: int = GroupType.GENERIC, + *, metadata: Optional[dict] = None, + tags: Optional[list[str]] = None, + ) -> GroupEntry: + if name in self._groups: + raise ValueError(f"分组已存在: {name}") + group_id = self._group_id_counter + self._group_id_counter += 1 + meta_json = None + if metadata: + meta_json = json.dumps(metadata, ensure_ascii=False, separators=(",", ":")) + group = GroupEntry( + group_id=group_id, group_type=group_type, + name=name, metadata=meta_json, + tags=list(tags) if tags else [], + ) + self._groups[name] = group + return group + + def add_relation( + self, source_group: str, target_group: str, + relation_type: int = RelationType.RELATED, *, description: str = "", + ) -> GroupRelation: + if source_group not in self._groups: + raise ValueError(f"源分组不存在: {source_group}") + if target_group not in self._groups: + raise ValueError(f"目标分组不存在: {target_group}") + relation = GroupRelation( + source_group=self._groups[source_group].group_id, + target_group=self._groups[target_group].group_id, + relation_type=relation_type, description=description, + ) + self._relations.append(relation) + return relation + + def add_tag(self, group: Union[str, GroupEntry], tag: str): + """为已有分组添加标签。""" + if isinstance(group, str): + if group not in self._groups: + raise ValueError(f"分组不存在: {group}") + group = self._groups[group] + if tag not in group.tags: + group.tags.append(tag) + + def add_intra_relation( + self, group_name: str, *, + source: str, target: str, + relation_type: int = IntraRelationType.CUSTOM, + description: str = "", + ) -> IntraRelation: + """添加组内条目间关系。source/target 为文件名。""" + if group_name not in self._groups: + raise ValueError(f"分组不存在: {group_name}") + group = self._groups[group_name] + + # 查找条目 ID + src_id = None + tgt_id = None + for i, entry in enumerate(self._entries): + if entry.name == source and entry.group_id == group.group_id: + src_id = i + if entry.name == target and entry.group_id == group.group_id: + tgt_id = i + + if src_id is None: + raise ValueError(f"组内未找到源文件: {source}") + if tgt_id is None: + raise ValueError(f"组内未找到目标文件: {target}") + + rel = IntraRelation( + source_entry=src_id, target_entry=tgt_id, + relation_type=relation_type, description=description, + ) + group.intra_relations.append(rel) + return rel + + # ── 文件添加 API ──────────────────────────────────────── + + def add_file( + self, file_path: Union[str, Path], *, + arcname: Optional[str] = None, entry_type: Optional[int] = None, + mime_type: Optional[str] = None, compression: Optional[int] = None, + metadata: Optional[dict] = None, created_at: Optional[int] = None, + modified_at: Optional[int] = None, + group: Optional[Union[GroupEntry, str]] = None, + group_name: Optional[str] = None, + ) -> TocEntry: + file_path = Path(file_path) + if not file_path.is_file(): + raise FileNotFoundError(f"文件不存在: {file_path}") + + ext = file_path.suffix.lower() + inferred = EXTENSION_MAP.get(ext) + if entry_type is None: + entry_type = inferred[0] if inferred else EntryType.DOCUMENT + if mime_type is None: + mime_type = inferred[1] if inferred else "application/octet-stream" + if compression is None: + compression = inferred[2] if inferred else Compression.ZLIB + + name = arcname or file_path.name + if ".." in name or name.startswith("/") or name.startswith("\\"): + raise ValueError(f"不安全的文件名: {name}") + + # 读取源文件时间戳 + stat = file_path.stat() + if created_at is None: + ct = stat.st_ctime + created_at = int(ct * 1000) + if modified_at is None: + mt = stat.st_mtime + modified_at = int(mt * 1000) + + original_data = file_path.read_bytes() + return self._add_entry( + original_data, name, entry_type, mime_type, compression, + metadata=metadata, created_at=created_at, modified_at=modified_at, + group=group, group_name=group_name, + ) + + def add_data( + self, data: bytes, name: str, *, + entry_type: int = EntryType.DOCUMENT, + mime_type: str = "application/octet-stream", + compression: int = Compression.ZLIB, + metadata: Optional[dict] = None, + created_at: Optional[int] = None, modified_at: Optional[int] = None, + group: Optional[Union[GroupEntry, str]] = None, + group_name: Optional[str] = None, + ) -> TocEntry: + if ".." in name or name.startswith("/") or name.startswith("\\"): + raise ValueError(f"不安全的文件名: {name}") + return self._add_entry( + data, name, entry_type, mime_type, compression, + metadata=metadata, created_at=created_at, modified_at=modified_at, + group=group, group_name=group_name, + ) + + def add_directory( + self, dir_path: Union[str, Path], *, + recursive: bool = True, prefix: str = "", + metadata_fn=None, group_name: Optional[str] = None, + ) -> list[TocEntry]: + dir_path = Path(dir_path) + if not dir_path.is_dir(): + raise NotADirectoryError(f"不是目录: {dir_path}") + group = None + if group_name: + if group_name not in self._groups: + group = self.create_group(group_name) + else: + group = self._groups[group_name] + entries = [] + pattern = "**/*" if recursive else "*" + for f in sorted(dir_path.glob(pattern)): + if not f.is_file(): + continue + relative = f.relative_to(dir_path) + arcname = f"{prefix}{relative}" if prefix else str(relative) + arcname = arcname.replace("\\", "/") + meta = metadata_fn(f) if metadata_fn else None + entry = self.add_file(f, arcname=arcname, metadata=meta, group=group) + entries.append(entry) + return entries + + def import_folder( + self, folder_path: Union[str, Path], *, + group_name: Optional[str] = None, + recursive: bool = True, + tags: Optional[list[str]] = None, + group_type: int = GroupType.GENERIC, + metadata: Optional[dict] = None, + metadata_fn=None, + ) -> GroupEntry: + """ + 导入文件夹,自动创建同名分组。 + + Args: + folder_path: 文件夹路径 + group_name: 分组名(默认=文件夹名) + recursive: 是否递归子目录 + tags: 分组标签 + group_type: 分组类型 + metadata: 分组元数据 + metadata_fn: 条目级元数据回调 + + Returns: + 创建的 GroupEntry + """ + folder_path = Path(folder_path) + if not folder_path.is_dir(): + raise NotADirectoryError(f"不是目录: {folder_path}") + + if group_name is None: + group_name = folder_path.name + + if group_name not in self._groups: + group = self.create_group( + group_name, group_type, metadata=metadata, tags=tags, + ) + else: + group = self._groups[group_name] + if tags: + for t in tags: + if t not in group.tags: + group.tags.append(t) + + pattern = "**/*" if recursive else "*" + for f in sorted(folder_path.glob(pattern)): + if not f.is_file(): + continue + relative = f.relative_to(folder_path) + arcname = str(relative).replace("\\", "/") + meta = metadata_fn(f) if metadata_fn else None + self.add_file(f, arcname=arcname, metadata=meta, group=group) + + return group + + def load_index( + self, index_path: Union[str, Path], *, + base_dir: Union[str, Path] = ".", + ) -> dict: + """ + 从 JSON 索引文件加载打包配置。 + + JSON 格式: + { + "name": "包名称", + "description": "描述", + "groups": [ + { + "name": "组名", + "type": "COURSE", + "tags": ["tag1"], + "metadata": {}, + "files": [ + {"path": "relative/path.ext", "title": "显示名"}, + "another/file.ext" + ] + } + ], + "standalone_files": [ + {"path": "file.ext", "tags": ["optional"]}, + "simple_file.txt" + ], + "relations": [ + {"source": "组A", "target": "组B", "type": "SEQUEL", "desc": "..."} + ], + "intra_relations": [ + {"group": "组名", "source": "a.mp4", "target": "a.srt", + "type": "SUBTITLE_OF", "desc": "..."} + ] + } + + Returns: + {"loaded": int, "skipped": list, "groups_created": int, "relations_created": int} + """ + index_path = Path(index_path) + base_dir = Path(base_dir) + + if not index_path.is_file(): + raise FileNotFoundError(f"索引文件不存在: {index_path}") + + with open(index_path, "r", encoding="utf-8") as f: + index = json.load(f) + + result = { + "loaded": 0, + "skipped": [], + "groups_created": 0, + "relations_created": 0, + } + + # GroupType 名称映射 + gt_map = {name.lower(): val for name, val in GroupType.__members__.items()} + # RelationType 名称映射 + rt_map = {name.lower(): val for name, val in RelationType.__members__.items()} + # IntraRelationType 名称映射 + irt_map = {name.lower(): val for name, val in IntraRelationType.__members__.items()} + + # ── 处理 groups ── + for gspec in index.get("groups", []): + gname = gspec["name"] + gtype_str = gspec.get("type", "GENERIC").upper() + gtype = gt_map.get(gtype_str.lower(), GroupType.GENERIC) + gtags = gspec.get("tags", []) + gmeta = gspec.get("metadata") + + if gname not in self._groups: + self.create_group(gname, gtype, metadata=gmeta, tags=gtags) + result["groups_created"] += 1 + else: + # 已存在则补充 tags + group = self._groups[gname] + for t in gtags: + if t not in group.tags: + group.tags.append(t) + + group = self._groups[gname] + for filespec in gspec.get("files", []): + if isinstance(filespec, str): + fpath = filespec + fmeta = None + else: + fpath = filespec["path"] + fmeta = {"title": filespec["title"]} if "title" in filespec else None + + full_path = base_dir / fpath + if not full_path.is_file(): + result["skipped"].append((fpath, "文件不存在")) + warnings.warn(f"跳过不存在的文件: {fpath}", stacklevel=2) + continue + + arcname = Path(fpath).name + try: + self.add_file(full_path, arcname=arcname, + metadata=fmeta, group=group) + result["loaded"] += 1 + except Exception as e: + result["skipped"].append((fpath, str(e))) + warnings.warn(f"跳过文件 {fpath}: {e}", stacklevel=2) + + # ── 处理 standalone_files ── + for filespec in index.get("standalone_files", []): + if isinstance(filespec, str): + fpath = filespec + ftags = [] + fmeta = None + else: + fpath = filespec["path"] + ftags = filespec.get("tags", []) + fmeta = {"title": filespec["title"]} if "title" in filespec else None + + full_path = base_dir / fpath + if not full_path.is_file(): + result["skipped"].append((fpath, "文件不存在")) + warnings.warn(f"跳过不存在的文件: {fpath}", stacklevel=2) + continue + + arcname = Path(fpath).name + try: + self.add_file(full_path, arcname=arcname, metadata=fmeta) + result["loaded"] += 1 + except Exception as e: + result["skipped"].append((fpath, str(e))) + warnings.warn(f"跳过文件 {fpath}: {e}", stacklevel=2) + + # ── 处理 relations ── + for rspec in index.get("relations", []): + src = rspec["source"] + tgt = rspec["target"] + rtype_str = rspec.get("type", "RELATED").upper() + rtype = rt_map.get(rtype_str.lower(), RelationType.RELATED) + rdesc = rspec.get("desc", "") + + if src in self._groups and tgt in self._groups: + self.add_relation(src, tgt, rtype, description=rdesc) + result["relations_created"] += 1 + else: + missing = [] + if src not in self._groups: + missing.append(src) + if tgt not in self._groups: + missing.append(tgt) + warnings.warn( + f"跳过关系 (分组不存在: {', '.join(missing)})", stacklevel=2 + ) + + # ── 处理 intra_relations ── + for rspec in index.get("intra_relations", []): + gname = rspec["group"] + src = rspec["source"] + tgt = rspec["target"] + rtype_str = rspec.get("type", "CUSTOM").upper() + rtype = irt_map.get(rtype_str.lower(), IntraRelationType.CUSTOM) + rdesc = rspec.get("desc", "") + + if gname not in self._groups: + warnings.warn( + f"跳过组内关系 (分组不存在: {gname})", stacklevel=2 + ) + continue + + try: + self.add_intra_relation( + gname, source=src, target=tgt, + relation_type=rtype, description=rdesc, + ) + except ValueError as e: + warnings.warn(f"跳过组内关系: {e}", stacklevel=2) + + return result + + # ── 完成写入 ──────────────────────────────────────────── + + def finalize(self): + if self._closed: + return + self._closed = True + + if not self._entries: + self._write_empty_file() + return + + packed_at = int(time.time() * 1000) + cursor = HEADER_SIZE + + # ── Step 0: 写入 Encryption Params (如果加密) ── + ep_offset = 0 + ep_size = 0 + if self._encrypted: + ep_offset = cursor + ep_bytes = self._build_encryption_params() + ep_size = len(ep_bytes) + self._file.write(ep_bytes) + cursor += ep_size + + # ── Step 1: 写入 Magic Index ── + magic_index_offset = cursor + mi_bytes = self._build_magic_index() + if self._encrypted and self._encrypt_mode in ( + EncryptionMode.FULL, EncryptionMode.METADATA_ONLY + ): + mi_bytes = self._encrypt_control(mi_bytes) + mi_encrypted_size = len(mi_bytes) # 存入 header,供 reader 定位 MI + self._file.write(mi_bytes) + cursor += len(mi_bytes) + + # ── Step 2: 按分组顺序写入 Blob ── + indexed = list(enumerate(self._entries)) + def sort_key(item): + idx, e = item + return (0 if e.group_id != NO_GROUP else 1, e.group_id, idx) + sorted_entries = sorted(indexed, key=sort_key) + + encrypt_blobs = self._encrypted and self._encrypt_mode in ( + EncryptionMode.FULL, EncryptionMode.DATA_ONLY + ) + for original_idx, entry in sorted_entries: + entry.blob_offset = cursor + blob = self._blobs[original_idx] + if encrypt_blobs: + blob = self._encrypt_blob(blob, original_idx) + entry.stored_size = len(blob) + self._file.write(blob) + cursor += len(blob) + + # ── Step 3: 写入 Group Index ── + group_index_offset = cursor + gi_bytes = self._build_group_index() + gi_original_size = len(gi_bytes) # 保存原始大小 + if self._encrypted and self._encrypt_mode in ( + EncryptionMode.FULL, EncryptionMode.METADATA_ONLY + ): + gi_bytes = self._encrypt_control(gi_bytes) + self._file.write(gi_bytes) + cursor += len(gi_bytes) + + # ── Step 4: 写入 TOC ── + toc_offset = cursor + toc_bytes = self._build_toc() + if self._encrypted and self._encrypt_mode in ( + EncryptionMode.FULL, EncryptionMode.METADATA_ONLY + ): + toc_bytes = self._encrypt_control(toc_bytes) + self._file.write(toc_bytes) + cursor += len(toc_bytes) + + # ── Step 5: 写入 Footer ── + footer_raw = struct.pack(FOOTER_FMT, MAGIC, toc_offset, 0) + footer_crc = binascii.crc32(footer_raw[:12]) & 0xFFFFFFFF + self._file.write(struct.pack(FOOTER_FMT, MAGIC, toc_offset, footer_crc)) + + # ── Step 6: 回写 Header ── + flags = FLAG_ENCRYPTED if self._encrypted else 0 + # header 第 10 个字段(group_index_size 位置)存储 mi_encrypted_size + # Reader 用它精确读取 MI,GI 大小通过 toc_offset - gi_offset 计算 + header = struct.pack( + HEADER_FMT, + MAGIC, VERSION, flags, packed_at, + ep_offset, ep_size, + len(self._entries), len(self._groups), + group_index_offset, mi_encrypted_size, + toc_offset, + ) + self._file.seek(0) + self._file.write(header) + self._file.close() + + # ── 加密/解密内部方法 ─────────────────────────────────── + + def _encrypt_control(self, data: bytes) -> bytes: + """加密控制区数据。""" + if self._kdf_type == KdfType.PBKDF2_AES: + return aes_gcm_encrypt(self._control_key, data, aad=b"mcpk-ctrl") + else: + return xor_bytes(data, self._control_key) + + def _encrypt_blob(self, blob: bytes, entry_id: int) -> bytes: + """加密数据区 blob,返回 [salt(16B)] + [加密数据]。""" + entry_salt = os.urandom(16) + if self._kdf_type == KdfType.PBKDF2_AES: + blob_key = _derive_blob_key_aes(self._data_key_base, entry_id, entry_salt) + encrypted = aes_gcm_encrypt(blob_key, blob, aad=struct.pack(" TocEntry: + stored_data = self._compress(original_data, compression) + original_size = len(original_data) + stored_size = len(stored_data) + crc32_val = binascii.crc32(original_data) & 0xFFFFFFFF + now_ms = int(time.time() * 1000) + ts_created = created_at if created_at is not None else now_ms + ts_modified = modified_at if modified_at is not None else now_ms + + # 分组归属 + gid = NO_GROUP + if group is not None: + if isinstance(group, GroupEntry): + gid = group.group_id + elif isinstance(group, str): + if group in self._groups: + gid = self._groups[group].group_id + else: + gid = self.create_group(group).group_id + elif group_name is not None: + if group_name in self._groups: + gid = self._groups[group_name].group_id + else: + gid = self.create_group(group_name).group_id + + if gid != NO_GROUP: + for g in self._groups.values(): + if g.group_id == gid: + g.entry_ids.append(len(self._entries)) + break + + meta_json = None + if metadata: + if "title" not in metadata: + metadata["title"] = Path(name).stem + meta_json = json.dumps(metadata, ensure_ascii=False, separators=(",", ":")) + + entry = TocEntry( + entry_type=entry_type, compression=compression, + crc32=crc32_val, created_at=ts_created, modified_at=ts_modified, + original_size=original_size, stored_size=stored_size, + blob_offset=0, name=name, mime_type=mime_type, + metadata=meta_json, group_id=gid, + ) + self._entries.append(entry) + self._blobs.append(stored_data) + return entry + + def _compress(self, data: bytes, compression: int) -> bytes: + if compression == Compression.NONE: + return data + elif compression == Compression.ZLIB: + return zlib.compress(data, level=6) + elif compression == Compression.ZSTD: + m = _get_zstd() + if m is not None: + return m.compress(data, 3) + return zlib.compress(data, level=6) + elif compression == Compression.LZ4: + m = _get_lz4() + if m is not None: + return m.compress(data) + return zlib.compress(data, level=6) + else: + raise ValueError(f"未知压缩算法: {compression}") + + def _build_encryption_params(self) -> bytes: + """构建 Encryption Params 区。""" + if self._kdf_type == KdfType.PBKDF2_AES: + key_verify = hashlib.sha256( + self._master_key + b"verify" + ).digest() + return struct.pack( + ENCRYPTION_PARAMS_FMT_V2, + ENCRYPTION_PARAMS_MAGIC, + KdfType.PBKDF2_AES, + self._encrypt_mode, + b"\x00" * 2, + self._kdf_iterations, + self._salt, + key_verify, + ) + else: + key_hash = hashlib.sha256(self._control_key).digest() + return struct.pack( + ENCRYPTION_PARAMS_FMT_LEGACY, + ENCRYPTION_PARAMS_MAGIC, + KdfType.SHA256_XOR, + self._encrypt_mode, + b"\x00" * 2, + self._salt, + key_hash, + ) + + def _build_magic_index(self) -> bytes: + parts = [] + header = struct.pack( + MAGIC_INDEX_HEADER_FMT, + MAGIC_INDEX_MAGIC, len(self._entries), 0, + ) + parts.append(header) + for i, entry in enumerate(self._entries): + # 用字符串操作替代 Path() 构造 + dot = entry.name.rfind(".") + ext = entry.name[dot:].lower() if dot >= 0 else "" + magic_bytes = FILE_MAGICS.get(ext, b"") + if len(magic_bytes) > 32: + magic_bytes = magic_bytes[:32] + else: + magic_bytes = magic_bytes.ljust(32, b"\x00") + name_bytes = entry.name.encode("utf-8") + me = struct.pack( + MAGIC_ENTRY_FMT, + i, entry.entry_type, entry.group_id, + len(magic_bytes), magic_bytes, + len(name_bytes), b"\x00" * 6, + ) + parts.append(me) + result = b"".join(parts) + index_size = len(result) + result = result[:8] + struct.pack(" bytes: + parts = [] + header = struct.pack( + GROUP_INDEX_HEADER_FMT, + GROUP_INDEX_MAGIC, len(self._groups), len(self._relations), 0, + ) + parts.append(header) + for group in self._groups.values(): + name_bytes = group.name.encode("utf-8") + meta_bytes = group.metadata.encode("utf-8") if group.metadata else b"" + + # tags 序列化 + tag_parts = [] + for tag in group.tags: + tag_bytes = tag.encode("utf-8") + tag_parts.append(struct.pack(" bytes: + parts = [] + for entry in self._entries: + name_bytes = entry.name.encode("utf-8") + mime_bytes = entry.mime_type.encode("utf-8") + meta_bytes = entry.metadata.encode("utf-8") if entry.metadata else b"" + reserved = bytes([entry.group_id, 0x00]) + fixed = struct.pack( + TOC_ENTRY_FIXED_FMT, + entry.entry_type, entry.compression, reserved, + entry.crc32, entry.created_at, entry.modified_at, + entry.original_size, entry.stored_size, entry.blob_offset, + len(name_bytes), + ) + parts.append(fixed) + parts.append(name_bytes) + parts.append(struct.pack(" list[TocEntry]: + return list(self._entries) + + @property + def entry_count(self) -> int: + return len(self._entries) + + @property + def groups(self) -> list[GroupEntry]: + return list(self._groups.values()) + + @property + def is_encrypted(self) -> bool: + return self._encrypted diff --git a/apluse/restore_template.py b/apluse/restore_template.py new file mode 100644 index 0000000..2cd045f --- /dev/null +++ b/apluse/restore_template.py @@ -0,0 +1,188 @@ +"""恢复脚本模板:core._build_restore_script 以 .replace() 填充占位符后写出。 + +此文件内容会被原样写入生成的 {魔术字}_restore.py,修改需同步 tests/test_restore_script.py 的回归。 +""" + +RESTORE_SCRIPT_TEMPLATE = '''import sys +import os +import struct +from pathlib import Path + +MAGIC = bytes.fromhex("__MAGIC_HEX__") +CHUNK_SIZE = 4 * 1024 * 1024 +SELF_NAMES = {__SELF_NAMES_SET__} + + +def xor_bytes(data: bytes, key: bytes) -> bytes: + if not key: + return data + return bytes([b ^ key[i % len(key)] for i, b in enumerate(data)]) + + +def get_base_dir() -> Path: + if getattr(sys, "frozen", False) or "__compiled__" in globals(): + return Path(sys.argv[0]).resolve().parent + return Path(__file__).resolve().parent + + +def is_disguised_file(p: Path) -> bool: + try: + if p.stat().st_size < (1 + 4 + len(MAGIC)): + return False + with open(p, "rb") as f: + f.seek(-len(MAGIC), os.SEEK_END) + return f.read(len(MAGIC)) == MAGIC + except Exception: + return False + + +def _try_parse_name(raw_bytes, magic): + """尝试从原始字节中解析文件名,优先 XOR 解密,回退明文。""" + try: + decoded = xor_bytes(raw_bytes, magic).decode("utf-8") + if decoded and decoded == Path(decoded).name and decoded not in (".", ".."): + return decoded + except Exception: + pass + try: + decoded = raw_bytes.decode("utf-8") + if decoded and decoded == Path(decoded).name and decoded not in (".", ".."): + return decoded + except Exception: + pass + return None + + +def parse_metadata(f, sz): + f.seek(-len(MAGIC), os.SEEK_END) + if f.read(len(MAGIC)) != MAGIC: + raise Exception("标记无效") + + # v4: 加密元数据 name_len(1B XOR) + head_len(4B XOR) + original_size(8B XOR) + magic + try: + if sz >= len(MAGIC) + 13: + f.seek(-(len(MAGIC) + 13), os.SEEK_END) + dec_meta = xor_bytes(f.read(13), MAGIC) + nlen = dec_meta[0] + hlen = struct.unpack("= len(MAGIC) + 13: + f.seek(-(len(MAGIC) + 8), os.SEEK_END) + osize = struct.unpack(" sz: + raise Exception("名称长度非法") + npos = sz - len(MAGIC) - 5 - nlen + if npos < 0: + raise Exception("name_pos 非法") + hpos = npos - hlen + if hpos < 0: + raise Exception("head_pos 非法") + f.seek(npos) + raw_name = f.read(nlen) + if len(raw_name) != nlen: + raise Exception("名称长度不足") + # 一次性读取完毕,后续不再 seek/read,避免文件指针问题 + name = _try_parse_name(raw_name, MAGIC) + if not name: + # 回退:当作后缀拼接 + try: + suffix = raw_name.decode("utf-8") + except Exception as e: + raise Exception(f"无法解析后缀: {e}") + name = Path(fp_name).stem + suffix + return {"hlen": hlen, "hpos": hpos, "name": name, "osize": hpos} + + +def reveal_file(fp: Path, reserved): + global fp_name + fp_name = fp.name + with open(fp, "r+b") as f: + meta = parse_metadata(f, fp.stat().st_size) + bytes_left, read_offset = meta["hlen"], meta["hpos"] + while bytes_left > 0: + read_size = min(CHUNK_SIZE, bytes_left) + f.seek(read_offset) + chunk = f.read(read_size) + write_offset = bytes_left - read_size + f.seek(write_offset) + f.write(chunk[::-1]) + read_offset += read_size + bytes_left -= read_size + f.truncate(meta["osize"]) + + dp = fp.parent / meta["name"] + rest = dp + if str(dp.resolve()) in reserved: + idx = 1 + while True: + c = dp.with_name(f"{dp.stem}_restored_{idx}{dp.suffix}") + if str(c.resolve()) not in reserved and not c.exists(): + rest = c + break + idx += 1 + fp.replace(rest) + return rest + + +fp_name = "" + + +def main(): + global fp_name + bd = get_base_dir() + print(f"扫描: {bd}\\n魔术字: {MAGIC.hex()}\\n" + "-" * 40) + res, fail = 0, 0 + reserved = {str(p.resolve()) for p in bd.rglob("*") if p.is_file()} + for p in bd.rglob("*"): + if p.is_file() and p.name.lower() not in SELF_NAMES: + try: + reserved.discard(str(p.resolve())) + if is_disguised_file(p): + fp_name = p.name + np = reveal_file(p, reserved) + reserved.add(str(np.resolve())) + res += 1 + print(f"[恢复] {p.name} -> {np.name}") + except Exception as e: + fail += 1 + print(f"[失败] {p.name} -> {e}") + print(f"\\n完成: 成功 {res}, 失败 {fail}") + input("按回车退出...") + + +if __name__ == "__main__": + main() +''' diff --git a/themes.py b/apluse/themes.py similarity index 54% rename from themes.py rename to apluse/themes.py index ba5d82e..de87708 100644 --- a/themes.py +++ b/apluse/themes.py @@ -1,10 +1,17 @@ """ APLUSE ENGINE - 主题配置模块 将主题配色数据与 QSS 模板分离,采用现代化的 UI 色彩系统设计。 + +对比度设计原则: +- TEXT_MAIN / BG_MAIN ≥ 10:1(正文、文件名、数字) +- TEXT_SUB / BG_CARD ≥ 4.5:1(次级文字:大小、时间、分组名) +- TEXT_TITLE / BG_MAIN ≥ 7:1(标题) +- BTN_SEC_TEXT / BTN_SEC ≥ 4.5:1(按钮文字) +- PRI_H_START / BG_MAIN ≥ 4.5:1(强调色文字,如 neonInput) """ # 主题名称列表(与 palettes 列表索引一一对应) -THEME_NAMES =[ +THEME_NAMES = [ "暗色极客 (Geek Zinc)", "亮色极简 (Clean Light)", "渐变幽蓝 (Deep Ocean)", @@ -15,83 +22,119 @@ ] # 主题配色方案列表 -PALETTES =[ +PALETTES = [ { # 0: 暗色极客 - 采用深灰(Zinc)和低饱和度靛蓝,适合长期凝视的护眼黑客主题 "BG_MAIN": "#09090B", "BG_CARD": "#18181B", "BORDER": "#27272A", - "TEXT_MAIN": "#F4F4F5", "TEXT_SUB": "#A1A1AA", "TEXT_TITLE": "#FAFAFA", + "TEXT_MAIN": "#F4F4F5", "TEXT_SUB": "#D4D4D8", "TEXT_TITLE": "#FAFAFA", "BTN_SEC": "#27272A", "BTN_SEC_HOVER": "#3F3F46", "BTN_SEC_TEXT": "#E4E4E7", - "PRI_START": "#4F46E5", "PRI_END": "#7C3AED", "PRI_H_START": "#6366F1", "PRI_H_END": "#8B5CF6", + "PRI_START": "#6D67E8", "PRI_END": "#8B5CF6", "PRI_H_START": "#818CF8", "PRI_H_END": "#A78BFA", "LIST_BG": "#09090B", "LIST_ITEM": "#18181B", "LIST_HOVER": "#27272A", "LIST_SEL": "#4F46E5", "TERM_BG": "#050505", "TERM_TEXT": "#34D399", "SHADOW": "rgba(0,0,0,80)", - "DIS_BG": "rgba(255, 255, 255, 0.03)", "DIS_TEXT": "rgba(255, 255, 255, 0.2)", - "DIS_BORDER": "rgba(255, 255, 255, 0.08)", "DIS_PRI_BG": "rgba(255, 255, 255, 0.05)", - "INPUT_DIS_BG": "rgba(255, 255, 255, 0.02)", + "DIS_BG": "rgba(255, 255, 255, 0.05)", "DIS_TEXT": "rgba(255, 255, 255, 0.45)", + "DIS_BORDER": "rgba(255, 255, 255, 0.1)", "DIS_PRI_BG": "rgba(255, 255, 255, 0.06)", + "INPUT_DIS_BG": "rgba(255, 255, 255, 0.03)", + # 预览区域专用色(跟随主题,不再硬编码) + "PREVIEW_BG": "rgba(255, 255, 255, 0.04)", "PREVIEW_TEXT": "#D4D4D8", + "PREVIEW_PLACEHOLDER": "#A1A1AA", + "VIDEO_CTRL": "#A1A1AA", "VIDEO_CTRL_HOVER": "#D4D4D8", + "VIDEO_SLIDER_GROOVE": "rgba(255, 255, 255, 0.15)", "VIDEO_SLIDER_HANDLE": "#818CF8", + "MCPK_META": "#A1A1AA", }, { # 1: 亮色极简 - 类似 macOS 的冷白色调,阴影柔和,UI元素边缘清爽 "BG_MAIN": "#F8FAFC", "BG_CARD": "#FFFFFF", "BORDER": "#E2E8F0", - "TEXT_MAIN": "#0F172A", "TEXT_SUB": "#64748B", "TEXT_TITLE": "#020617", + "TEXT_MAIN": "#0F172A", "TEXT_SUB": "#475569", "TEXT_TITLE": "#020617", "BTN_SEC": "#F1F5F9", "BTN_SEC_HOVER": "#E2E8F0", "BTN_SEC_TEXT": "#334155", - "PRI_START": "#3B82F6", "PRI_END": "#6366F1", "PRI_H_START": "#60A5FA", "PRI_H_END": "#818CF8", + "PRI_START": "#2563EB", "PRI_END": "#6366F1", "PRI_H_START": "#1D4ED8", "PRI_H_END": "#7C3AED", "LIST_BG": "#F8FAFC", "LIST_ITEM": "#FFFFFF", "LIST_HOVER": "#F1F5F9", "LIST_SEL": "#3B82F6", "TERM_BG": "#F1F5F9", "TERM_TEXT": "#0369A1", "SHADOW": "rgba(0,0,0,20)", - "DIS_BG": "rgba(0, 0, 0, 0.03)", "DIS_TEXT": "rgba(0, 0, 0, 0.3)", - "DIS_BORDER": "rgba(0, 0, 0, 0.1)", "DIS_PRI_BG": "rgba(0, 0, 0, 0.06)", - "INPUT_DIS_BG": "rgba(0, 0, 0, 0.02)", + "DIS_BG": "rgba(0, 0, 0, 0.04)", "DIS_TEXT": "rgba(0, 0, 0, 0.45)", + "DIS_BORDER": "rgba(0, 0, 0, 0.12)", "DIS_PRI_BG": "rgba(0, 0, 0, 0.06)", + "INPUT_DIS_BG": "rgba(0, 0, 0, 0.03)", + "PREVIEW_BG": "rgba(0, 0, 0, 0.03)", "PREVIEW_TEXT": "#334155", + "PREVIEW_PLACEHOLDER": "#64748B", + "VIDEO_CTRL": "#475569", "VIDEO_CTRL_HOVER": "#0F172A", + "VIDEO_SLIDER_GROOVE": "rgba(0, 0, 0, 0.12)", "VIDEO_SLIDER_HANDLE": "#2563EB", + "MCPK_META": "#475569", }, { # 2: 渐变幽蓝 - 午夜深蓝底色搭配青蓝(Cyan)色渐变发光,富有科幻质感 "BG_MAIN": "#020617", "BG_CARD": "#0F172A", "BORDER": "#1E293B", - "TEXT_MAIN": "#F8FAFC", "TEXT_SUB": "#94A3B8", "TEXT_TITLE": "#38BDF8", + "TEXT_MAIN": "#F8FAFC", "TEXT_SUB": "#CBD5E1", "TEXT_TITLE": "#38BDF8", "BTN_SEC": "#1E293B", "BTN_SEC_HOVER": "#334155", "BTN_SEC_TEXT": "#E2E8F0", "PRI_START": "#0EA5E9", "PRI_END": "#2563EB", "PRI_H_START": "#38BDF8", "PRI_H_END": "#3B82F6", "LIST_BG": "#020617", "LIST_ITEM": "#0F172A", "LIST_HOVER": "#1E293B", "LIST_SEL": "#0284C7", "TERM_BG": "#000000", "TERM_TEXT": "#38BDF8", "SHADOW": "rgba(14,165,233,40)", - "DIS_BG": "rgba(255, 255, 255, 0.04)", "DIS_TEXT": "rgba(255, 255, 255, 0.3)", - "DIS_BORDER": "rgba(255, 255, 255, 0.1)", "DIS_PRI_BG": "rgba(255, 255, 255, 0.08)", - "INPUT_DIS_BG": "rgba(255, 255, 255, 0.02)", + "DIS_BG": "rgba(255, 255, 255, 0.06)", "DIS_TEXT": "rgba(255, 255, 255, 0.5)", + "DIS_BORDER": "rgba(255, 255, 255, 0.12)", "DIS_PRI_BG": "rgba(255, 255, 255, 0.08)", + "INPUT_DIS_BG": "rgba(255, 255, 255, 0.03)", + "PREVIEW_BG": "rgba(255, 255, 255, 0.04)", "PREVIEW_TEXT": "#CBD5E1", + "PREVIEW_PLACEHOLDER": "#94A3B8", + "VIDEO_CTRL": "#94A3B8", "VIDEO_CTRL_HOVER": "#E2E8F0", + "VIDEO_SLIDER_GROOVE": "rgba(255, 255, 255, 0.15)", "VIDEO_SLIDER_HANDLE": "#38BDF8", + "MCPK_META": "#94A3B8", }, { # 3: 暗金奢华 - 黑曜石底色与拉丝香槟金搭配,高贵内敛 "BG_MAIN": "#0E0E0E", "BG_CARD": "#171717", "BORDER": "#2D281F", - "TEXT_MAIN": "#EAE6E1", "TEXT_SUB": "#A39A88", "TEXT_TITLE": "#E2C881", + "TEXT_MAIN": "#EAE6E1", "TEXT_SUB": "#C4B9A8", "TEXT_TITLE": "#E2C881", "BTN_SEC": "#24221E", "BTN_SEC_HOVER": "#36332C", "BTN_SEC_TEXT": "#E2C881", "PRI_START": "#CBA358", "PRI_END": "#A67C2E", "PRI_H_START": "#E5C27C", "PRI_H_END": "#B88B35", "LIST_BG": "#0E0E0E", "LIST_ITEM": "#171717", "LIST_HOVER": "#24221E", "LIST_SEL": "#4A3A1C", "TERM_BG": "#080808", "TERM_TEXT": "#D1B570", "SHADOW": "rgba(203,163,88,25)", - "DIS_BG": "rgba(255, 255, 255, 0.03)", "DIS_TEXT": "rgba(226, 200, 129, 0.3)", - "DIS_BORDER": "rgba(203, 163, 88, 0.15)", "DIS_PRI_BG": "rgba(203, 163, 88, 0.1)", - "INPUT_DIS_BG": "rgba(255, 255, 255, 0.02)", + "DIS_BG": "rgba(255, 255, 255, 0.05)", "DIS_TEXT": "rgba(226, 200, 129, 0.5)", + "DIS_BORDER": "rgba(203, 163, 88, 0.2)", "DIS_PRI_BG": "rgba(203, 163, 88, 0.12)", + "INPUT_DIS_BG": "rgba(255, 255, 255, 0.03)", + "PREVIEW_BG": "rgba(255, 255, 255, 0.04)", "PREVIEW_TEXT": "#C4B9A8", + "PREVIEW_PLACEHOLDER": "#A39A88", + "VIDEO_CTRL": "#A39A88", "VIDEO_CTRL_HOVER": "#EAE6E1", + "VIDEO_SLIDER_GROOVE": "rgba(203, 163, 88, 0.2)", "VIDEO_SLIDER_HANDLE": "#CBA358", + "MCPK_META": "#A39A88", }, { # 4: 猛男猛粉 - 纯粹的 Outrun 赛博黑粉系,极高的对比与视觉冲击力 "BG_MAIN": "#0D0208", "BG_CARD": "#1A0B16", "BORDER": "#4A1535", - "TEXT_MAIN": "#FCE7F3", "TEXT_SUB": "#F472B6", "TEXT_TITLE": "#F9A8D4", + "TEXT_MAIN": "#FCE7F3", "TEXT_SUB": "#F9A8D4", "TEXT_TITLE": "#F9A8D4", "BTN_SEC": "#331024", "BTN_SEC_HOVER": "#4D1B37", "BTN_SEC_TEXT": "#FBCFE8", "PRI_START": "#D946EF", "PRI_END": "#BE185D", "PRI_H_START": "#E879F9", "PRI_H_END": "#E11D48", "LIST_BG": "#0D0208", "LIST_ITEM": "#1A0B16", "LIST_HOVER": "#331024", "LIST_SEL": "#9D174D", "TERM_BG": "#050003", "TERM_TEXT": "#F472B6", "SHADOW": "rgba(217,70,239,35)", - "DIS_BG": "rgba(255, 255, 255, 0.04)", "DIS_TEXT": "rgba(244, 114, 182, 0.4)", - "DIS_BORDER": "rgba(217, 70, 239, 0.2)", "DIS_PRI_BG": "rgba(217, 70, 239, 0.12)", - "INPUT_DIS_BG": "rgba(255, 255, 255, 0.02)", + "DIS_BG": "rgba(255, 255, 255, 0.06)", "DIS_TEXT": "rgba(244, 114, 182, 0.55)", + "DIS_BORDER": "rgba(217, 70, 239, 0.25)", "DIS_PRI_BG": "rgba(217, 70, 239, 0.15)", + "INPUT_DIS_BG": "rgba(255, 255, 255, 0.03)", + "PREVIEW_BG": "rgba(255, 255, 255, 0.04)", "PREVIEW_TEXT": "#F9A8D4", + "PREVIEW_PLACEHOLDER": "#F472B6", + "VIDEO_CTRL": "#F472B6", "VIDEO_CTRL_HOVER": "#FCE7F3", + "VIDEO_SLIDER_GROOVE": "rgba(217, 70, 239, 0.2)", "VIDEO_SLIDER_HANDLE": "#E879F9", + "MCPK_META": "#F472B6", }, { # 5: 辐射废土 - 废土灰褐色 + 经典 PIP-Boy 绿色荧光 "BG_MAIN": "#1A1C19", "BG_CARD": "#242722", "BORDER": "#3B4236", - "TEXT_MAIN": "#D1D6CD", "TEXT_SUB": "#9AA096", "TEXT_TITLE": "#A3E635", + "TEXT_MAIN": "#D1D6CD", "TEXT_SUB": "#B5BCB0", "TEXT_TITLE": "#A3E635", "BTN_SEC": "#32382F", "BTN_SEC_HOVER": "#454C41", "BTN_SEC_TEXT": "#D1D6CD", "PRI_START": "#65A30D", "PRI_END": "#4D7C0F", "PRI_H_START": "#84CC16", "PRI_H_END": "#65A30D", "LIST_BG": "#1A1C19", "LIST_ITEM": "#242722", "LIST_HOVER": "#32382F", "LIST_SEL": "#4D7C0F", "TERM_BG": "#0D0E0C", "TERM_TEXT": "#84CC16", "SHADOW": "rgba(101,163,13,30)", - "DIS_BG": "rgba(255, 255, 255, 0.03)", "DIS_TEXT": "rgba(163, 230, 53, 0.3)", - "DIS_BORDER": "rgba(101, 163, 13, 0.2)", "DIS_PRI_BG": "rgba(101, 163, 13, 0.1)", - "INPUT_DIS_BG": "rgba(255, 255, 255, 0.02)", + "DIS_BG": "rgba(255, 255, 255, 0.05)", "DIS_TEXT": "rgba(163, 230, 53, 0.5)", + "DIS_BORDER": "rgba(101, 163, 13, 0.25)", "DIS_PRI_BG": "rgba(101, 163, 13, 0.12)", + "INPUT_DIS_BG": "rgba(255, 255, 255, 0.03)", + "PREVIEW_BG": "rgba(255, 255, 255, 0.04)", "PREVIEW_TEXT": "#B5BCB0", + "PREVIEW_PLACEHOLDER": "#9AA096", + "VIDEO_CTRL": "#9AA096", "VIDEO_CTRL_HOVER": "#D1D6CD", + "VIDEO_SLIDER_GROOVE": "rgba(101, 163, 13, 0.2)", "VIDEO_SLIDER_HANDLE": "#84CC16", + "MCPK_META": "#9AA096", }, { # 6: 低调暗紫 - Discord / Vercel 风格的优雅紫黑色体系 "BG_MAIN": "#0A0118", "BG_CARD": "#130927", "BORDER": "#2A1654", - "TEXT_MAIN": "#F5F3FF", "TEXT_SUB": "#A78BFA", "TEXT_TITLE": "#C4B5FD", + "TEXT_MAIN": "#F5F3FF", "TEXT_SUB": "#C4B5FD", "TEXT_TITLE": "#C4B5FD", "BTN_SEC": "#20103E", "BTN_SEC_HOVER": "#311A5C", "BTN_SEC_TEXT": "#EDE9FE", "PRI_START": "#8B5CF6", "PRI_END": "#6D28D9", "PRI_H_START": "#A78BFA", "PRI_H_END": "#7C3AED", "LIST_BG": "#0A0118", "LIST_ITEM": "#130927", "LIST_HOVER": "#20103E", "LIST_SEL": "#5B21B6", "TERM_BG": "#05000A", "TERM_TEXT": "#D8B4FE", "SHADOW": "rgba(139,92,246,30)", - "DIS_BG": "rgba(255, 255, 255, 0.03)", "DIS_TEXT": "rgba(167, 139, 250, 0.3)", - "DIS_BORDER": "rgba(139, 92, 246, 0.2)", "DIS_PRI_BG": "rgba(139, 92, 246, 0.1)", - "INPUT_DIS_BG": "rgba(255, 255, 255, 0.02)", + "DIS_BG": "rgba(255, 255, 255, 0.05)", "DIS_TEXT": "rgba(167, 139, 250, 0.5)", + "DIS_BORDER": "rgba(139, 92, 246, 0.25)", "DIS_PRI_BG": "rgba(139, 92, 246, 0.12)", + "INPUT_DIS_BG": "rgba(255, 255, 255, 0.03)", + "PREVIEW_BG": "rgba(255, 255, 255, 0.04)", "PREVIEW_TEXT": "#C4B5FD", + "PREVIEW_PLACEHOLDER": "#A78BFA", + "VIDEO_CTRL": "#A78BFA", "VIDEO_CTRL_HOVER": "#EDE9FE", + "VIDEO_SLIDER_GROOVE": "rgba(139, 92, 246, 0.2)", "VIDEO_SLIDER_HANDLE": "#A78BFA", + "MCPK_META": "#A78BFA", }, ] @@ -101,10 +144,18 @@ def build_qss(p: dict) -> str: return f""" QWidget {{ font-family: "Segoe UI", "Microsoft YaHei", sans-serif; font-size: 13px; color: {p['TEXT_MAIN']}; }} - QMessageBox {{ background-color: {p['BG_CARD']}; }} + QMessageBox {{ background-color: {p['BG_CARD']}; min-width: 480px; }} QMessageBox QLabel {{ color: {p['TEXT_MAIN']}; background: transparent; font-size: 13px; }} QMessageBox QPushButton {{ min-width: 72px; padding: 6px 16px; }} + QInputDialog {{ background-color: {p['BG_CARD']}; min-width: 480px; }} + QInputDialog QLabel {{ color: {p['TEXT_MAIN']}; background: transparent; }} + QInputDialog QLineEdit {{ + background: {p['BG_CARD']}; color: {p['TEXT_MAIN']}; + border: 1px solid {p['BORDER']}; border-radius: 6px; padding: 6px 10px; + }} + QInputDialog QLineEdit:focus {{ border: 1px solid {p['PRI_H_START']}; }} + ::selection {{ background-color: {p['PRI_H_START']}; color: white; }} QFrame#mainContainer {{ background-color: {p['BG_MAIN']}; border: 1px solid {p['BORDER']}; border-radius: 12px; }} @@ -130,16 +181,38 @@ def build_qss(p: dict) -> str: QPushButton#macClose {{ background-color: #EF4444; border: 1px solid #DC2626; }} QPushButton#macClose:hover {{ background-color: #F87171; }} - QComboBox#themeCombo {{ background-color: {p['BTN_SEC']}; color: {p['TEXT_MAIN']}; border: 1px solid {p['BORDER']}; border-radius: 6px; padding: 4px 10px; font-weight: bold; }} - QComboBox#themeCombo:hover {{ background-color: {p['BTN_SEC_HOVER']}; }} - QComboBox#themeCombo::drop-down {{ border: none; }} - QComboBox#themeCombo QAbstractItemView {{ background-color: {p['BG_CARD']}; color: {p['TEXT_MAIN']}; border: 1px solid {p['BORDER']}; selection-background-color: {p['PRI_START']}; border-radius: 6px; }} + QComboBox {{ background-color: {p['BTN_SEC']}; color: {p['TEXT_MAIN']}; border: 1px solid {p['BORDER']}; border-radius: 6px; padding: 4px 10px; }} + QComboBox:hover {{ background-color: {p['BTN_SEC_HOVER']}; }} + QComboBox::drop-down {{ border: none; }} + QComboBox QAbstractItemView {{ + background-color: {p['BG_CARD']}; color: {p['TEXT_MAIN']}; + border: 1px solid {p['BORDER']}; selection-background-color: {p['PRI_START']}; + selection-color: white; outline: none; border-radius: 6px; + }} + QComboBox QAbstractItemView::item {{ padding: 4px 8px; min-height: 22px; }} + + QCheckBox {{ color: {p['TEXT_MAIN']}; spacing: 6px; background: transparent; }} + QCheckBox::indicator {{ + width: 16px; height: 16px; border-radius: 4px; + border: 1px solid {p['BORDER']}; background: {p['BG_CARD']}; + }} + QCheckBox::indicator:checked {{ background: {p['PRI_START']}; border-color: {p['PRI_START']}; }} + QCheckBox::indicator:hover {{ border-color: {p['PRI_H_START']}; }} + QFrame#card QCheckBox {{ color: {p['TEXT_MAIN']}; background: transparent; }} QFrame#card {{ background-color: {p['BG_CARD']}; border: 1px solid {p['BORDER']}; border-radius: 10px; }} QLabel#cardTitle {{ color: {p['TEXT_MAIN']}; font-size: 15px; font-weight: bold; }} QLabel#subText {{ color: {p['TEXT_SUB']}; font-size: 12px; }} QLabel#badge {{ background: {p['BORDER']}; color: {p['TEXT_MAIN']}; padding: 4px 8px; border-radius: 6px; font-size: 11px; font-weight: 800; }} + QMenu {{ + background-color: {p['BG_CARD']}; color: {p['TEXT_MAIN']}; + border: 1px solid {p['BORDER']}; border-radius: 8px; padding: 4px; + }} + QMenu::item {{ padding: 6px 28px 6px 12px; border-radius: 4px; }} + QMenu::item:selected {{ background: {p['PRI_START']}; color: white; }} + QMenu::separator {{ height: 1px; background: {p['BORDER']}; margin: 4px 8px; }} + QListWidget#darkList {{ background: {p['LIST_BG']}; border: 1px dashed {p['BORDER']}; border-radius: 8px; outline: none; padding: 6px; color: {p['TEXT_MAIN']}; font-size: 13px; }} @@ -148,6 +221,26 @@ def build_qss(p: dict) -> str: QListWidget#darkList::item:hover {{ background: {p['LIST_HOVER']}; border-color: {p['BORDER']}; }} QListWidget#darkList::item:selected {{ background: {p['LIST_SEL']}; color: white; border-color: {p['PRI_H_START']}; }} + QTreeWidget#darkList {{ + background: {p['LIST_BG']}; border: 1px dashed {p['BORDER']}; border-radius: 8px; outline: none; + color: {p['TEXT_MAIN']}; font-size: 12px; + alternate-background-color: {p['LIST_ITEM']}; + }} + QTreeWidget#darkList::item {{ padding: 4px 6px; background: transparent; }} + QTreeWidget#darkList::item:alternate {{ background: {p['LIST_ITEM']}; }} + QTreeWidget#darkList::item:hover {{ background: {p['LIST_HOVER']}; }} + QTreeWidget#darkList::item:selected {{ background: {p['LIST_SEL']}; color: white; }} + QTreeWidget#darkList QHeaderView::section {{ + background: {p['BG_CARD']}; color: {p['TEXT_SUB']}; border: none; + border-bottom: 1px solid {p['BORDER']}; padding: 6px 8px; font-weight: 600; + }} + + QLineEdit {{ + background: {p['BG_CARD']}; color: {p['TEXT_MAIN']}; border: 1px solid {p['BORDER']}; + border-radius: 6px; padding: 6px 10px; + }} + QLineEdit:focus {{ border: 1px solid {p['PRI_H_START']}; background: {p['LIST_ITEM']}; }} + QLineEdit#neonInput {{ background: {p['LIST_BG']}; border: 1px solid {p['BORDER']}; border-radius: 6px; padding: 8px 12px; color: {p['PRI_H_START']}; font-family: "Consolas", monospace; font-weight: bold; }} @@ -200,6 +293,19 @@ def build_qss(p: dict) -> str: QScrollBar::handle:vertical {{ background: {p['BORDER']}; border-radius: 3px; }} QScrollBar::handle:vertical:hover {{ background: {p['TEXT_SUB']}; }} QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {{ height: 0px; }} + + /* MCPK 预览区域 */ + QLabel#previewTitle {{ font-weight: bold; font-size: 13px; color: {p['TEXT_SUB']}; }} + QLabel#previewPlaceholder {{ color: {p['PREVIEW_PLACEHOLDER']}; font-size: 13px; background: {p['PREVIEW_BG']}; border-radius: 8px; }} + QLabel#previewImage {{ background: {p['PREVIEW_BG']}; border-radius: 8px; color: {p['PREVIEW_TEXT']}; }} + QLabel#previewGif {{ background: {p['PREVIEW_BG']}; border-radius: 8px; }} + QWidget#previewVideo {{ background: {p['BG_MAIN']}; border-radius: 8px; }} + QPushButton#videoPlayBtn {{ background: rgba(255,255,255,0.1); border-radius: 16px; font-size: 14px; color: {p['VIDEO_CTRL']}; }} + QPushButton#videoPlayBtn:hover {{ background: rgba(255,255,255,0.2); color: {p['VIDEO_CTRL_HOVER']}; }} + QLabel#videoTime {{ color: {p['VIDEO_CTRL']}; font-size: 11px; }} + QSlider#videoSlider::groove:horizontal {{ height: 4px; background: {p['VIDEO_SLIDER_GROOVE']}; border-radius: 2px; }} + QSlider#videoSlider::handle:horizontal {{ width: 12px; margin: -4px 0; background: {p['VIDEO_SLIDER_HANDLE']}; border-radius: 6px; }} + QLabel#mcpkMeta {{ color: {p['MCPK_META']}; font-size: 11px; padding: 4px 8px; }} """ diff --git a/ui.py b/apluse/ui.py similarity index 87% rename from ui.py rename to apluse/ui.py index a81b74d..a628ba7 100644 --- a/ui.py +++ b/apluse/ui.py @@ -16,11 +16,11 @@ QTreeWidget, QTreeWidgetItem, QInputDialog, QSplitter, QScrollArea, QSlider, ) -from core import ( +from .core import ( DisguiseEngine, DisguiseError, disguise_file, collect_files_from_paths, - PathManager, magic_to_display_text, format_file_size, + PathManager, magic_to_display_text, format_file_size, APP_VERSION, ) -from themes import PALETTES, THEME_NAMES, build_qss, parse_shadow_color +from .themes import PALETTES, THEME_NAMES, build_qss, parse_shadow_color # =================== 通用异步工作线程 =================== @@ -52,7 +52,7 @@ def run(self): # =================== 现代化 UI 组件 =================== class CustomTitleBar(QFrame): - def __init__(self, parent=None, title="✨ APLUSE ENGINE v3.4", show_dev_btn=False, show_theme=True): + def __init__(self, parent=None, title=f"✨ APLUSE ENGINE v{APP_VERSION}", show_dev_btn=False, show_theme=True): super().__init__(parent) self.parent = parent self.setFixedHeight(48) @@ -464,6 +464,29 @@ def dropEvent(self, event): event.acceptProposedAction() +# =================== MCPK 预览加载工作线程 =================== + +class _PreviewWorker(QThread): + """后台加载 MCPK 条目数据,避免阻塞 UI 线程。""" + finished = pyqtSignal(object, str) # (data: bytes | None, name: str) + error = pyqtSignal(str, str) # (error_msg, name) + + def __init__(self, mcpk_path, name, password=None): + super().__init__() + self._mcpk_path = mcpk_path + self._name = name + self._password = password + + def run(self): + try: + from .mcpk import MCPKReader + with MCPKReader(self._mcpk_path, password=self._password) as reader: + data = reader.extract(self._name) + self.finished.emit(data, self._name) + except Exception as e: + self.error.emit(str(e), self._name) + + # =================== MCPK 内容查看对话框 =================== class MCPKViewerDialog(QWidget): @@ -492,10 +515,17 @@ def _load_data(self): # 如果是加密文件,提示输入密码 if "已加密" in err_msg or "密码" in err_msg: from PyQt5.QtWidgets import QInputDialog - pwd, ok = QInputDialog.getText( - self, "需要密码", "此 MCPK 文件已加密,请输入密码:", - QLineEdit.Password - ) + dlg = QInputDialog(self) + dlg.setWindowTitle("需要密码") + dlg.setLabelText("此 MCPK 文件已加密,请输入密码:") + dlg.setTextEchoMode(QLineEdit.Password) + # 强制主题样式与宽度(与 MainWindow._themed_dialog 同样的修复) + if self.parent_window is not None: + dlg.setStyleSheet(self.parent_window.styleSheet()) + dlg.setMinimumWidth(480) + dlg.resize(480, max(dlg.sizeHint().height(), 160)) + ok = dlg.exec_() == QInputDialog.Accepted + pwd = dlg.textValue() if ok else "" if ok and pwd: self._password = pwd try: @@ -554,7 +584,8 @@ def _init_ui(self): for entry in self._info.get("entries", []): item = QTreeWidgetItem() tn = entry.get("type", "?") - item.setText(0, f"{icons.get(tn, '\U0001f4ce')} {tn}") + fallback_icon = "\U0001f4ce" + item.setText(0, f"{icons.get(tn, fallback_icon)} {tn}") item.setText(1, entry.get("name", "")) item.setText(2, entry.get("mime", "")) item.setText(3, format_file_size(entry.get("original_size", 0))) @@ -591,7 +622,7 @@ def _init_ui(self): pv_layout.setSpacing(8) preview_title = QLabel("\U0001f441️ 预览") - preview_title.setStyleSheet("font-weight: bold; font-size: 13px; color: #ccc;") + preview_title.setObjectName("previewTitle") pv_layout.addWidget(preview_title) # ── 预览切换器 ── @@ -600,26 +631,26 @@ def _init_ui(self): # Page 0: 占位(未选择) self._preview_placeholder = QLabel("选择文件以预览") self._preview_placeholder.setAlignment(Qt.AlignCenter) - self._preview_placeholder.setStyleSheet("color: #666; font-size: 13px; background: rgba(255,255,255,0.03); border-radius: 8px;") + self._preview_placeholder.setObjectName("previewPlaceholder") self._preview_stack.addWidget(self._preview_placeholder) # Page 1: 静态图片 self._preview_image = QLabel() self._preview_image.setAlignment(Qt.AlignCenter) - self._preview_image.setStyleSheet("background: rgba(255,255,255,0.03); border-radius: 8px;") + self._preview_image.setObjectName("previewImage") self._preview_stack.addWidget(self._preview_image) # Page 2: GIF 动画 self._preview_gif = QLabel() self._preview_gif.setAlignment(Qt.AlignCenter) - self._preview_gif.setStyleSheet("background: rgba(255,255,255,0.03); border-radius: 8px;") + self._preview_gif.setObjectName("previewGif") self._preview_gif_movie = None self._preview_gif_buffer = None self._preview_stack.addWidget(self._preview_gif) # Page 3: 视频播放 self._preview_video_widget = QVideoWidget() - self._preview_video_widget.setStyleSheet("background: #111; border-radius: 8px;") + self._preview_video_widget.setObjectName("previewVideo") self._preview_player = QMediaPlayer() self._preview_player.setVideoOutput(self._preview_video_widget) self._preview_stack.addWidget(self._preview_video_widget) @@ -628,24 +659,24 @@ def _init_ui(self): video_ctrl = QHBoxLayout() self._btn_play = QPushButton("▶") self._btn_play.setFixedSize(32, 32) - self._btn_play.setStyleSheet("QPushButton { background: rgba(255,255,255,0.1); border-radius: 16px; font-size: 14px; } QPushButton:hover { background: rgba(255,255,255,0.2); }") + self._btn_play.setObjectName("videoPlayBtn") self._btn_play.clicked.connect(self._toggle_video_play) self._btn_play.hide() video_ctrl.addWidget(self._btn_play) self._video_position = QLabel("00:00") - self._video_position.setStyleSheet("color: #999; font-size: 11px;") + self._video_position.setObjectName("videoTime") self._video_position.hide() video_ctrl.addWidget(self._video_position) self._video_slider = QSlider(Qt.Horizontal) - self._video_slider.setStyleSheet("QSlider::groove:horizontal { height: 4px; background: rgba(255,255,255,0.15); border-radius: 2px; } QSlider::handle:horizontal { width: 12px; margin: -4px 0; background: #4a9eff; border-radius: 6px; }") + self._video_slider.setObjectName("videoSlider") self._video_slider.hide() self._video_slider.sliderMoved.connect(self._on_video_slider_moved) video_ctrl.addWidget(self._video_slider, 1) self._video_duration = QLabel("00:00") - self._video_duration.setStyleSheet("color: #999; font-size: 11px;") + self._video_duration.setObjectName("videoTime") self._video_duration.hide() video_ctrl.addWidget(self._video_duration) @@ -687,6 +718,11 @@ def _on_entry_selected(self, current, previous): self._preview_gif_movie.stop() self._hide_video_controls() + # 停止之前的后台加载 + if hasattr(self, '_preview_worker') and self._preview_worker and self._preview_worker.isRunning(): + self._preview_worker.terminate() + self._preview_worker.wait(500) + if not current: self._preview_stack.setCurrentIndex(0) self._preview_info.setText("") @@ -726,34 +762,62 @@ def _on_entry_selected(self, current, previous): info_lines.append(f"标签: {', '.join(meta['tags'])}") self._preview_info.setText("\n".join(info_lines)) - # 根据类型路由预览 + # 记录当前预览类型,供 worker 回调使用 TEXT_EXTS = {".md", ".txt", ".json", ".csv", ".srt", ".vtt", ".ass", ".xml", ".yaml", ".yml", ".html", ".htm"} IMAGE_EXTS = {".jpg", ".jpeg", ".png", ".bmp", ".ico"} GIF_EXTS = {".gif"} VIDEO_EXTS = {".mp4", ".mkv", ".avi", ".mov", ".webm", ".flv", ".wmv", ".ts", ".m4v"} + if ext in GIF_EXTS or (entry_type == "IMAGE" and ext == ".gif"): + preview_type = "gif" + elif entry_type == "VIDEO" or ext in VIDEO_EXTS: + preview_type = "video" + elif entry_type == "IMAGE" or ext in IMAGE_EXTS: + preview_type = "image" + elif entry_type == "DOCUMENT" or ext in TEXT_EXTS: + preview_type = "text" + else: + self._show_placeholder(entry_type, name) + return + + # 显示加载提示 + self._preview_stack.setCurrentIndex(1) + self._preview_image.setPixmap(QPixmap()) + self._preview_image.setText(f"⏳ 加载中: {name}") + self._preview_image.setAlignment(Qt.AlignCenter) + + # 启动后台线程加载数据 + self._preview_pending_type = preview_type + self._preview_worker = _PreviewWorker(self.mcpk_path, name, self._password) + self._preview_worker.finished.connect(self._on_preview_data_ready) + self._preview_worker.error.connect(self._on_preview_error) + self._preview_worker.start() + + def _on_preview_data_ready(self, data, name): + """后台加载完成,切换到 UI 线程渲染预览。""" + preview_type = getattr(self, '_preview_pending_type', None) try: - if ext in GIF_EXTS or (entry_type == "IMAGE" and ext == ".gif"): - self._show_gif_preview(name) - elif entry_type == "VIDEO" or ext in VIDEO_EXTS: - self._show_video_preview(name) - elif entry_type == "IMAGE" or ext in IMAGE_EXTS: - self._show_image_preview(name) - elif entry_type == "DOCUMENT" or ext in TEXT_EXTS: - self._show_text_preview(name) - else: - self._show_placeholder(entry_type, name) + if preview_type == "gif": + self._render_gif_preview(data) + elif preview_type == "video": + self._render_video_preview(data, name) + elif preview_type == "image": + self._render_image_preview(data) + elif preview_type == "text": + self._render_text_preview(data) except Exception as e: self._preview_stack.setCurrentIndex(1) self._preview_image.setPixmap(QPixmap()) self._preview_image.setText(f"预览失败:\n{e}") - def _show_image_preview(self, name): - """静态图片预览。""" - from mcpk import MCPKReader - with MCPKReader(self.mcpk_path, password=self._password) as reader: - data = reader.extract(name) + def _on_preview_error(self, error_msg, name): + """后台加载失败。""" + self._preview_stack.setCurrentIndex(1) + self._preview_image.setPixmap(QPixmap()) + self._preview_image.setText(f"加载失败:\n{error_msg}") + def _render_image_preview(self, data): + """静态图片预览(在 UI 线程调用,data 已加载完毕)。""" img = QImage() img.loadFromData(data) if img.isNull(): @@ -767,12 +831,8 @@ def _show_image_preview(self, name): self._preview_image.setText("") self._preview_stack.setCurrentIndex(1) - def _show_gif_preview(self, name): - """GIF 动画预览。""" - from mcpk import MCPKReader - with MCPKReader(self.mcpk_path, password=self._password) as reader: - data = reader.extract(name) - + def _render_gif_preview(self, data): + """GIF 动画预览(在 UI 线程调用,data 已加载完毕)。""" # 停止旧动画 if self._preview_gif_movie: self._preview_gif_movie.stop() @@ -794,13 +854,9 @@ def _show_gif_preview(self, name): movie.start() self._preview_stack.setCurrentIndex(2) - def _show_video_preview(self, name): - """视频预览(提取到临时文件后播放)。""" + def _render_video_preview(self, data, name): + """视频预览(在 UI 线程调用,data 已加载完毕,写入临时文件后播放)。""" import tempfile - from mcpk import MCPKReader - - with MCPKReader(self.mcpk_path, password=self._password) as reader: - data = reader.extract(name) # 写入临时文件 ext = Path(name).suffix.lower() @@ -814,12 +870,8 @@ def _show_video_preview(self, name): self._show_video_controls() self._preview_player.play() - def _show_text_preview(self, name, max_chars=8000): - """文本预览。""" - from mcpk import MCPKReader - with MCPKReader(self.mcpk_path, password=self._password) as reader: - data = reader.extract(name) - + def _render_text_preview(self, data, max_chars=8000): + """文本预览(在 UI 线程调用,data 已加载完毕)。""" for encoding in ("utf-8", "gbk", "latin-1"): try: text = data.decode(encoding) @@ -848,7 +900,7 @@ def _show_placeholder(self, entry_type, name): icon = type_icons.get(entry_type, "\U0001f4ce") self._preview_image.setPixmap(QPixmap()) self._preview_image.setText(f"{icon}\n{entry_type}\n{name}") - self._preview_image.setStyleSheet("background: rgba(255,255,255,0.03); border-radius: 8px; color: #999;") + # 颜色由主题 QSS 的 QLabel#previewImage 控制 self._preview_stack.setCurrentIndex(1) # ── 视频控制 ── @@ -900,7 +952,7 @@ def _extract_selected(self): output_dir = QFileDialog.getExistingDirectory(self, "选择输出目录") if not output_dir: return try: - from mcpk import MCPKReader + from .mcpk import MCPKReader with MCPKReader(self.mcpk_path, password=self._password) as reader: for name in names: reader.extract_to(name, output_dir) @@ -944,7 +996,43 @@ def _disguise_mcpk(self): # =================== 主窗口(MCPK Manager) =================== class MainWindow(QWidget): - DEV_PASSWORD = "1080" + DEV_PASSWORD_DEFAULT = "1080" + + def _themed_dialog(self, dlg): + """给弹窗强制应用主题样式并加宽,修复 QSS 对 QInputDialog 无效的问题。""" + qss = self.styleSheet() + if qss: + dlg.setStyleSheet(qss) + # QInputDialog 内部布局会忽略 QSS min-width,必须在控件层面强制 + dlg.setMinimumWidth(480) + dlg.resize(480, max(dlg.sizeHint().height(), 160)) + return dlg + + def _themed_get_text(self, title, label, echo=QLineEdit.Normal, text=""): + """主题化文本输入弹窗,返回 (text, ok)。""" + dlg = self._themed_dialog(QInputDialog(self)) + dlg.setWindowTitle(title) + dlg.setLabelText(label) + dlg.setTextEchoMode(echo) + dlg.setTextValue(text) + ok = dlg.exec_() == QInputDialog.Accepted + return dlg.textValue(), ok + + def _themed_get_item(self, title, label, items, current=0, editable=True): + """主题化下拉选择弹窗,返回 (text, ok)。""" + dlg = self._themed_dialog(QInputDialog(self)) + dlg.setWindowTitle(title) + dlg.setLabelText(label) + dlg.setComboBoxItems(items) + dlg.setComboBoxEditable(editable) + if items: + dlg.setComboBoxCurrentIndex(min(current, len(items) - 1)) + ok = dlg.exec_() == QInputDialog.Accepted + return dlg.textValue(), ok + + def _ask_password(self, title, label): + """弹出密码输入框,强制应用主题到弹窗内部控件。""" + return self._themed_get_text(title, label, echo=QLineEdit.Password) def __init__(self): super().__init__() @@ -956,6 +1044,7 @@ def __init__(self): self.mcpk_group_map = {} # {file_path: group_name} self.log_file_path = PathManager.get_persist_dir() / "apluse.log" + self.dev_password = self.engine.config.get("dev_password") or self.DEV_PASSWORD_DEFAULT self.setWindowFlags(Qt.FramelessWindowHint) self.setAttribute(Qt.WA_TranslucentBackground) @@ -1047,7 +1136,7 @@ def init_ui(self): # 当前分组指示器 self.mcpk_group_indicator = QLabel("未选择分组") - self.mcpk_group_indicator.setStyleSheet("color: #888; font-size: 11px; padding: 4px 8px;") + self.mcpk_group_indicator.setObjectName("mcpkMeta") group_bar.addWidget(self.mcpk_group_indicator) group_bar.addStretch() @@ -1154,8 +1243,9 @@ def _make_file_item(self, filepath): size_str = format_file_size(Path(filepath).stat().st_size) except Exception: size_str = "?" - item = QListWidgetItem(f"{filepath} [{size_str}]") + item = QListWidgetItem(f"{Path(filepath).name} [{size_str}]") item.setData(Qt.UserRole, filepath) + item.setToolTip(filepath) return item def _setup_shortcuts(self): @@ -1169,10 +1259,10 @@ def _toggle_developer_mode(self): self.dev_window.activateWindow() return - pwd, ok = QInputDialog.getText(self, "身份验证", "输入开发者密码:", QLineEdit.Password) - if ok and pwd == self.DEV_PASSWORD: + pwd, ok = self._ask_password("开发者身份验证", "请输入开发者密码:") + if ok and pwd == self.dev_password: self.title_bar.set_dev_active(True) - from ui_dev import DeveloperWindow + from .ui_dev import DeveloperWindow self.dev_window = DeveloperWindow(self.engine, self) self.dev_window.show() self.cb_log("已开启开发者独立窗口") @@ -1213,6 +1303,10 @@ def cb_log(self, text): timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") with open(self.log_file_path, "a", encoding="utf-8") as f: f.write(f"[{timestamp}] {text}\n") + # 日志轮转:超过 5MB 截断备份 + if self.log_file_path.stat().st_size > 5 * 1024 * 1024: + backup = self.log_file_path.with_suffix(".log.1") + self.log_file_path.replace(backup) except Exception: pass @@ -1268,7 +1362,7 @@ def ui_mcpk_remove_selected(self): def ui_mcpk_create_group(self): """弹出输入框创建新分组。""" - name, ok = QInputDialog.getText(self, "新建分组", "分组名称:") + name, ok = self._themed_get_text("新建分组", "分组名称:") if not ok or not name.strip(): return name = name.strip() @@ -1289,11 +1383,12 @@ def _on_group_combo_changed(self, text): self.mcpk_selected_group = text border, bg, txt_color = self.mcpk_group_colors.get(text, ("#888", "#88812", "#aaa")) self.mcpk_group_indicator.setText(f"● {text}") + # 分组选中时用强调色,由主题 QSS 的 QLabel#mcpkMeta 控制基础样式 self.mcpk_group_indicator.setStyleSheet(f"color: {txt_color}; font-size: 11px; padding: 4px 8px; font-weight: bold;") else: self.mcpk_selected_group = None self.mcpk_group_indicator.setText("未选择分组") - self.mcpk_group_indicator.setStyleSheet("color: #888; font-size: 11px; padding: 4px 8px;") + self.mcpk_group_indicator.setStyleSheet("") # 恢复主题默认 def ui_mcpk_assign_to_group(self): """将选中文件归入当前分组。""" @@ -1406,10 +1501,7 @@ def ui_mcpk_pack(self): if reply == QMessageBox.Cancel: return if reply == QMessageBox.Yes: - from PyQt5.QtWidgets import QInputDialog - pwd, ok = QInputDialog.getText( - self, "设置密码", "请输入加密密码:", QLineEdit.Password - ) + pwd, ok = self._themed_get_text("设置密码", "请输入加密密码:", echo=QLineEdit.Password) if not ok or not pwd: return password = pwd @@ -1467,14 +1559,13 @@ def _carry_memory_after_pack(self, mcpk_path): """打包后承载记忆流程:铸钥匙 → 选外壳 → 承载。""" # Step 1: 铸造钥匙 - magic_input, ok = QInputDialog.getText( - self, "铸造你的钥匙", + magic_input, ok = self._themed_get_text( + "铸造你的钥匙", "为这段记忆铸造一把钥匙:\n\n" "它可以是一句暗语、一个日期、一个名字——\n" "任何对你而言有特殊意义的字符。\n" "也可以是十六进制(如 DEADBEEF)。\n\n" "请铭记它,遗忘即永失。", - QLineEdit.Normal, ) if not ok or not magic_input.strip(): return @@ -1547,9 +1638,7 @@ def _pick_memory_shell(self): if choice_reply == QMessageBox.Yes: items = [f"{Path(p).name} ({format_file_size(Path(p).stat().st_size)})" for p in valid_masks] - item, ok = QInputDialog.getItem( - self, "选择外壳", "外壳文件:", items, 0, False, - ) + item, ok = self._themed_get_item("选择外壳", "外壳文件:", items, editable=False) if ok and item: idx = items.index(item) return valid_masks[idx] diff --git a/ui_dev.py b/apluse/ui_dev.py similarity index 80% rename from ui_dev.py rename to apluse/ui_dev.py index a3e9625..d67e659 100644 --- a/ui_dev.py +++ b/apluse/ui_dev.py @@ -5,32 +5,23 @@ from PyQt5.QtCore import Qt from PyQt5.QtGui import QColor, QKeySequence from PyQt5.QtWidgets import ( - QWidget, QLabel, QPushButton, QFileDialog, QShortcut, - QTextEdit, QVBoxLayout, QHBoxLayout, QMessageBox, QLineEdit, - QListWidget, QListWidgetItem, QFrame, QGridLayout, QProgressBar, - QGraphicsDropShadowEffect, QComboBox, QMenu, QCheckBox, + QShortcut, QTextEdit, QVBoxLayout, QHBoxLayout, QMessageBox, QLineEdit, + QFrame, QGridLayout, QProgressBar, QGraphicsDropShadowEffect, QMenu, QCheckBox, ) -from core import ( - DisguiseEngine, collect_files_from_paths, - PathManager, magic_to_display_text, format_file_size, -) -from themes import PALETTES, THEME_NAMES, build_qss, parse_shadow_color -from ui import EngineWorker, CustomTitleBar, CustomDropList +from .core import PathManager, magic_to_display_text, format_file_size, APP_VERSION +from .ui import CustomTitleBar, CustomDropList +from .engine_window import EngineWindowBase -class DeveloperWindow(QWidget): +class DeveloperWindow(EngineWindowBase): """独立的伪装引擎窗口,通过密码从主窗口打开。""" + WINDOW_FLAGS = Qt.FramelessWindowHint | Qt.Window + def __init__(self, engine, main_window): - super().__init__() - self.engine = engine + super().__init__(engine) self.main_window = main_window - self.current_worker = None - - self.setWindowFlags(Qt.FramelessWindowHint | Qt.Window) - self.setAttribute(Qt.WA_TranslucentBackground) - self.resize(1280, 860) self.init_ui() self._setup_shortcuts() @@ -61,7 +52,7 @@ def init_ui(self): container_layout.setContentsMargins(0, 0, 0, 0) container_layout.setSpacing(0) - self.title_bar = CustomTitleBar(self, title="\U0001f527 APLUSE ENGINE v3.4 - 开发者模式", show_theme=False) + self.title_bar = CustomTitleBar(self, title=f"\U0001f527 APLUSE ENGINE v{APP_VERSION} - 开发者模式", show_theme=False) container_layout.addWidget(self.title_bar) dashboard_layout = QVBoxLayout() @@ -278,25 +269,6 @@ def init_ui(self): # ── 工具方法 ────────────────────────────────────────── - def make_label(self, text, obj_name): - lbl = QLabel(text) - lbl.setObjectName(obj_name) - return lbl - - def make_btn(self, text, role="default"): - btn = QPushButton(text) - btn.setProperty("role", role) - return btn - - def _make_file_item(self, filepath): - try: - size_str = format_file_size(Path(filepath).stat().st_size) - except Exception: - size_str = "?" - item = QListWidgetItem(f"{filepath} [{size_str}]") - item.setData(Qt.UserRole, filepath) - return item - def _setup_shortcuts(self): QShortcut(QKeySequence("Esc"), self).activated.connect(self.close) QShortcut(QKeySequence("Ctrl+O"), self).activated.connect(self.ui_select_targets) @@ -346,7 +318,15 @@ def set_ui_busy(self, busy): def refresh_magic_ui(self): m = self.engine.get_magic_bytes() self.magic_edit.setText(m.hex().upper()) - self.magic_info_label.setText(f"生效指令:{magic_to_display_text(m)}") + try: + text_part = m.decode("utf-8") + except UnicodeDecodeError: + text_part = "" + if text_part and text_part.isprintable(): + self.magic_info_label.setText(f"生效指令:HEX={m.hex().upper()} | 文本={text_part}") + else: + self.magic_info_label.setText(f"生效指令:HEX={m.hex().upper()}") + self.magic_info_label.setToolTip(magic_to_display_text(m)) def ui_apply_magic(self): try: @@ -380,25 +360,6 @@ def refresh_target_list(self): ts = format_file_size(t_total) if t_total else "" self.t_count_label.setText(f"{t_count} 项" + (f" {ts}" if ts else "")) - def ui_add_target_paths(self, paths): - added = 0 - for f in collect_files_from_paths(paths): - if f not in self.engine.target_files: - self.engine.target_files.append(f) - added += 1 - self.refresh_target_list() - self.cb_log(f"目标装载: +{added} 项") - - def ui_select_targets(self): - paths, _ = QFileDialog.getOpenFileNames(self, "选择目标文件") - if paths: - self.ui_add_target_paths(paths) - - def ui_select_target_folder(self): - folder = QFileDialog.getExistingDirectory(self, "选择目标文件夹") - if folder: - self.ui_add_target_paths([folder]) - def ui_rm_targets(self): selected = self.target_list.selectedItems() if not selected: @@ -421,26 +382,6 @@ def refresh_mask_list(self): self.mask_list.addItem(self._make_file_item(f)) self.m_count_label.setText(f"{len(self.engine.mask_library)} 项") - def ui_add_mask_paths(self, paths): - added = 0 - for f in collect_files_from_paths(paths): - if f not in self.engine.mask_library: - self.engine.mask_library.append(f) - added += 1 - self.engine.save_config() - self.refresh_mask_list() - self.cb_log(f"面具库扩充: +{added} 项") - - def ui_select_masks(self): - paths, _ = QFileDialog.getOpenFileNames(self, "选择面具文件") - if paths: - self.ui_add_mask_paths(paths) - - def ui_select_mask_folder(self): - folder = QFileDialog.getExistingDirectory(self, "选择面具文件夹") - if folder: - self.ui_add_mask_paths([folder]) - def ui_rm_masks(self): selected = self.mask_list.selectedItems() if not selected: @@ -459,19 +400,10 @@ def ui_clr_masks(self): # ── 异步任务 ────────────────────────────────────────── - def _run_task(self, task_fn, done_cb): - self.set_ui_busy(True) - self.current_worker = EngineWorker(task_fn) - self.current_worker.log_sig.connect(self.cb_log) - self.current_worker.prog_sig.connect(self.cb_progress) - self.current_worker.done_sig.connect(done_cb) - self.current_worker.err_sig.connect(self._on_err) - self.current_worker.start() - - def _on_err(self, err_msg): - self.set_ui_busy(False) + def _on_task_error(self, err_msg): + # 开发者窗口在内核异常时额外推进度条(管理员窗口无此行为) self.cb_progress(0, 1, "系统崩溃", "ERR_FATAL") - self.cb_log(f"[ERROR] 内核异常: {err_msg}") + super()._on_task_error(err_msg) def ui_detect(self): if not self.engine.target_files: @@ -487,24 +419,15 @@ def done(result): self._run_task(self.engine.detect_status, done) + def rename_checkbox(self): + return self.chk_rename_seq + + def mapping_checkbox(self): + return self.chk_disguise_mapping + def ui_toggle(self): - if not self.engine.target_files: - return self.cb_log("中断: 目标队列为空") - count = len(self.engine.target_files) - reply = QMessageBox.question( - self, "确认执行", - f"即将对 {count} 个文件执行自动伪装/还原操作。\n\n确定继续吗?", - QMessageBox.Yes | QMessageBox.No, QMessageBox.No, - ) - if reply != QMessageBox.Yes: + if not self.prepare_toggle(): return - self.engine.rename_mapping = self.chk_rename_seq.isChecked() - self.engine.disguise_mapping_txt = self.chk_disguise_mapping.isChecked() - if self.engine.rename_mapping: - mapping_dir = self.engine.get_common_target_parent_dir() - self.engine.mapping_output_path = str(mapping_dir / "mapping.txt") - else: - self.engine.mapping_output_path = None def done(result): self.set_ui_busy(False) diff --git a/apluse_config.json b/apluse_config.json deleted file mode 100644 index c6badbb..0000000 --- a/apluse_config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "mask_library": [], - "magic_hex": "54455354", - "theme_index": 1 -} \ No newline at end of file diff --git a/apluse_restore_android/.gradle/8.5/checksums/checksums.lock b/apluse_restore_android/.gradle/8.5/checksums/checksums.lock deleted file mode 100644 index 5771f3b..0000000 Binary files a/apluse_restore_android/.gradle/8.5/checksums/checksums.lock and /dev/null differ diff --git a/apluse_restore_android/.gradle/8.5/checksums/md5-checksums.bin b/apluse_restore_android/.gradle/8.5/checksums/md5-checksums.bin deleted file mode 100644 index f503626..0000000 Binary files a/apluse_restore_android/.gradle/8.5/checksums/md5-checksums.bin and /dev/null differ diff --git a/apluse_restore_android/.gradle/8.5/checksums/sha1-checksums.bin b/apluse_restore_android/.gradle/8.5/checksums/sha1-checksums.bin deleted file mode 100644 index 5e762c8..0000000 Binary files a/apluse_restore_android/.gradle/8.5/checksums/sha1-checksums.bin and /dev/null differ diff --git a/apluse_restore_android/.gradle/8.5/dependencies-accessors/dependencies-accessors.lock b/apluse_restore_android/.gradle/8.5/dependencies-accessors/dependencies-accessors.lock deleted file mode 100644 index 5ad6bf7..0000000 Binary files a/apluse_restore_android/.gradle/8.5/dependencies-accessors/dependencies-accessors.lock and /dev/null differ diff --git a/apluse_restore_android/.gradle/8.5/dependencies-accessors/gc.properties b/apluse_restore_android/.gradle/8.5/dependencies-accessors/gc.properties deleted file mode 100644 index e69de29..0000000 diff --git a/apluse_restore_android/.gradle/8.5/executionHistory/executionHistory.bin b/apluse_restore_android/.gradle/8.5/executionHistory/executionHistory.bin deleted file mode 100644 index 64f13d4..0000000 Binary files a/apluse_restore_android/.gradle/8.5/executionHistory/executionHistory.bin and /dev/null differ diff --git a/apluse_restore_android/.gradle/8.5/executionHistory/executionHistory.lock b/apluse_restore_android/.gradle/8.5/executionHistory/executionHistory.lock deleted file mode 100644 index 235b374..0000000 Binary files a/apluse_restore_android/.gradle/8.5/executionHistory/executionHistory.lock and /dev/null differ diff --git a/apluse_restore_android/.gradle/8.5/fileChanges/last-build.bin b/apluse_restore_android/.gradle/8.5/fileChanges/last-build.bin deleted file mode 100644 index f76dd23..0000000 Binary files a/apluse_restore_android/.gradle/8.5/fileChanges/last-build.bin and /dev/null differ diff --git a/apluse_restore_android/.gradle/8.5/fileHashes/fileHashes.bin b/apluse_restore_android/.gradle/8.5/fileHashes/fileHashes.bin deleted file mode 100644 index a1b6f9a..0000000 Binary files a/apluse_restore_android/.gradle/8.5/fileHashes/fileHashes.bin and /dev/null differ diff --git a/apluse_restore_android/.gradle/8.5/fileHashes/fileHashes.lock b/apluse_restore_android/.gradle/8.5/fileHashes/fileHashes.lock deleted file mode 100644 index d03a00b..0000000 Binary files a/apluse_restore_android/.gradle/8.5/fileHashes/fileHashes.lock and /dev/null differ diff --git a/apluse_restore_android/.gradle/8.5/fileHashes/resourceHashesCache.bin b/apluse_restore_android/.gradle/8.5/fileHashes/resourceHashesCache.bin deleted file mode 100644 index 36a37ab..0000000 Binary files a/apluse_restore_android/.gradle/8.5/fileHashes/resourceHashesCache.bin and /dev/null differ diff --git a/apluse_restore_android/.gradle/8.5/gc.properties b/apluse_restore_android/.gradle/8.5/gc.properties deleted file mode 100644 index e69de29..0000000 diff --git a/apluse_restore_android/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/apluse_restore_android/.gradle/buildOutputCleanup/buildOutputCleanup.lock deleted file mode 100644 index 2de2eda..0000000 Binary files a/apluse_restore_android/.gradle/buildOutputCleanup/buildOutputCleanup.lock and /dev/null differ diff --git a/apluse_restore_android/.gradle/buildOutputCleanup/cache.properties b/apluse_restore_android/.gradle/buildOutputCleanup/cache.properties deleted file mode 100644 index 1c254e3..0000000 --- a/apluse_restore_android/.gradle/buildOutputCleanup/cache.properties +++ /dev/null @@ -1,2 +0,0 @@ -#Thu May 07 16:05:34 CST 2026 -gradle.version=8.5 diff --git a/apluse_restore_android/.gradle/buildOutputCleanup/outputFiles.bin b/apluse_restore_android/.gradle/buildOutputCleanup/outputFiles.bin deleted file mode 100644 index 4db11db..0000000 Binary files a/apluse_restore_android/.gradle/buildOutputCleanup/outputFiles.bin and /dev/null differ diff --git a/apluse_restore_android/.gradle/config.properties b/apluse_restore_android/.gradle/config.properties deleted file mode 100644 index 69abb21..0000000 --- a/apluse_restore_android/.gradle/config.properties +++ /dev/null @@ -1,2 +0,0 @@ -#Thu May 07 16:05:11 CST 2026 -java.home=C\:\\Program Files\\Android\\Android Studio\\jbr diff --git a/apluse_restore_android/.gradle/file-system.probe b/apluse_restore_android/.gradle/file-system.probe deleted file mode 100644 index 5af0a45..0000000 Binary files a/apluse_restore_android/.gradle/file-system.probe and /dev/null differ diff --git a/apluse_restore_android/.gradle/vcs-1/gc.properties b/apluse_restore_android/.gradle/vcs-1/gc.properties deleted file mode 100644 index e69de29..0000000 diff --git a/apluse_restore_android/.idea/.gitignore b/apluse_restore_android/.idea/.gitignore deleted file mode 100644 index 26d3352..0000000 --- a/apluse_restore_android/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/apluse_restore_android/.idea/.name b/apluse_restore_android/.idea/.name deleted file mode 100644 index 6740c89..0000000 --- a/apluse_restore_android/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -APLUSE 3.4 \ No newline at end of file diff --git a/apluse_restore_android/.idea/AndroidProjectSystem.xml b/apluse_restore_android/.idea/AndroidProjectSystem.xml deleted file mode 100644 index 4a53bee..0000000 --- a/apluse_restore_android/.idea/AndroidProjectSystem.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apluse_restore_android/.idea/caches/deviceStreaming.xml b/apluse_restore_android/.idea/caches/deviceStreaming.xml deleted file mode 100644 index 8efc3b6..0000000 --- a/apluse_restore_android/.idea/caches/deviceStreaming.xml +++ /dev/null @@ -1,1760 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/apluse_restore_android/.idea/compiler.xml b/apluse_restore_android/.idea/compiler.xml deleted file mode 100644 index b86273d..0000000 --- a/apluse_restore_android/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/apluse_restore_android/.idea/deploymentTargetSelector.xml b/apluse_restore_android/.idea/deploymentTargetSelector.xml deleted file mode 100644 index ca16a99..0000000 --- a/apluse_restore_android/.idea/deploymentTargetSelector.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/apluse_restore_android/.idea/gradle.xml b/apluse_restore_android/.idea/gradle.xml deleted file mode 100644 index 97f0a8e..0000000 --- a/apluse_restore_android/.idea/gradle.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/apluse_restore_android/.idea/migrations.xml b/apluse_restore_android/.idea/migrations.xml deleted file mode 100644 index f8051a6..0000000 --- a/apluse_restore_android/.idea/migrations.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/apluse_restore_android/.idea/misc.xml b/apluse_restore_android/.idea/misc.xml deleted file mode 100644 index e984dde..0000000 --- a/apluse_restore_android/.idea/misc.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/apluse_restore_android/.idea/runConfigurations.xml b/apluse_restore_android/.idea/runConfigurations.xml deleted file mode 100644 index 16660f1..0000000 --- a/apluse_restore_android/.idea/runConfigurations.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/apluse_restore_android/.idea/workspace.xml b/apluse_restore_android/.idea/workspace.xml deleted file mode 100644 index c7adf63..0000000 --- a/apluse_restore_android/.idea/workspace.xml +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1778141110197 - - - - \ No newline at end of file diff --git a/apluse_restore_android/app/build.gradle b/apluse_restore_android/app/build.gradle deleted file mode 100644 index e9cdc73..0000000 --- a/apluse_restore_android/app/build.gradle +++ /dev/null @@ -1,27 +0,0 @@ -plugins { - id 'com.android.application' -} - -android { - namespace 'com.apluse.restore' - compileSdk 34 - - defaultConfig { - applicationId "com.apluse.restore" - minSdk 21 - targetSdk 34 - versionCode 1 - versionName "1.0" - } - - buildTypes { - release { - minifyEnabled false - } - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } -} diff --git a/apluse_restore_android/app/build/intermediates/annotation_processor_list/debug/annotationProcessors.json b/apluse_restore_android/app/build/intermediates/annotation_processor_list/debug/annotationProcessors.json deleted file mode 100644 index 9e26dfe..0000000 --- a/apluse_restore_android/app/build/intermediates/annotation_processor_list/debug/annotationProcessors.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/apluse_restore_android/app/build/intermediates/annotation_processor_list/debugAndroidTest/annotationProcessors.json b/apluse_restore_android/app/build/intermediates/annotation_processor_list/debugAndroidTest/annotationProcessors.json deleted file mode 100644 index 9e26dfe..0000000 --- a/apluse_restore_android/app/build/intermediates/annotation_processor_list/debugAndroidTest/annotationProcessors.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/apluse_restore_android/app/build/intermediates/apk_ide_redirect_file/debug/redirect.txt b/apluse_restore_android/app/build/intermediates/apk_ide_redirect_file/debug/redirect.txt deleted file mode 100644 index 66652a5..0000000 --- a/apluse_restore_android/app/build/intermediates/apk_ide_redirect_file/debug/redirect.txt +++ /dev/null @@ -1,2 +0,0 @@ -#- File Locator - -listingFile=../../../outputs/apk/debug/output-metadata.json diff --git a/apluse_restore_android/app/build/intermediates/apk_ide_redirect_file/debugAndroidTest/redirect.txt b/apluse_restore_android/app/build/intermediates/apk_ide_redirect_file/debugAndroidTest/redirect.txt deleted file mode 100644 index b7d5868..0000000 --- a/apluse_restore_android/app/build/intermediates/apk_ide_redirect_file/debugAndroidTest/redirect.txt +++ /dev/null @@ -1,2 +0,0 @@ -#- File Locator - -listingFile=../../../outputs/apk/androidTest/debug/output-metadata.json diff --git a/apluse_restore_android/app/build/intermediates/app_metadata/debug/app-metadata.properties b/apluse_restore_android/app/build/intermediates/app_metadata/debug/app-metadata.properties deleted file mode 100644 index d8c27a4..0000000 --- a/apluse_restore_android/app/build/intermediates/app_metadata/debug/app-metadata.properties +++ /dev/null @@ -1,2 +0,0 @@ -appMetadataVersion=1.1 -androidGradlePluginVersion=8.2.0 diff --git a/apluse_restore_android/app/build/intermediates/compatible_screen_manifest/debug/output-metadata.json b/apluse_restore_android/app/build/intermediates/compatible_screen_manifest/debug/output-metadata.json deleted file mode 100644 index 63ba825..0000000 --- a/apluse_restore_android/app/build/intermediates/compatible_screen_manifest/debug/output-metadata.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "version": 3, - "artifactType": { - "type": "COMPATIBLE_SCREEN_MANIFEST", - "kind": "Directory" - }, - "applicationId": "com.apluse.restore", - "variantName": "debug", - "elements": [] -} \ No newline at end of file diff --git a/apluse_restore_android/app/build/intermediates/compile_and_runtime_not_namespaced_r_class_jar/debug/R.jar b/apluse_restore_android/app/build/intermediates/compile_and_runtime_not_namespaced_r_class_jar/debug/R.jar deleted file mode 100644 index 61f1be9..0000000 Binary files a/apluse_restore_android/app/build/intermediates/compile_and_runtime_not_namespaced_r_class_jar/debug/R.jar and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/compile_and_runtime_not_namespaced_r_class_jar/debugAndroidTest/R.jar b/apluse_restore_android/app/build/intermediates/compile_and_runtime_not_namespaced_r_class_jar/debugAndroidTest/R.jar deleted file mode 100644 index 2779210..0000000 Binary files a/apluse_restore_android/app/build/intermediates/compile_and_runtime_not_namespaced_r_class_jar/debugAndroidTest/R.jar and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/compile_app_classes_jar/debug/classes.jar b/apluse_restore_android/app/build/intermediates/compile_app_classes_jar/debug/classes.jar deleted file mode 100644 index e0fb43a..0000000 Binary files a/apluse_restore_android/app/build/intermediates/compile_app_classes_jar/debug/classes.jar and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_0/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_0/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_0/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_1/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_1/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_1/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_2/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_2/graph.bin deleted file mode 100644 index a47d61e..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_2/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_3/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_3/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_3/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_4/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_4/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_4/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_5/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_5/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_5/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_6/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_6/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_6/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_7/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_7/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_7/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_8/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_8/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_8/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_9/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_9/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_9/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_0/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_0/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_0/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_1/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_1/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_1/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_2/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_2/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_2/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_3/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_3/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_3/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_4/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_4/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_4/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_5/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_5/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_5/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_6/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_6/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_6/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_7/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_7/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_7/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_8/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_8/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_8/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_9/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_9/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_b75bf6a5ab9fbd599e359504f43780e324ff7588a64c3db3f70c8b9d3cb569da_bucket_9/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_0/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_0/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_0/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_1/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_1/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_1/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_2/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_2/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_2/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_3/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_3/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_3/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_4/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_4/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_4/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_5/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_5/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_5/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_6/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_6/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_6/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_7/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_7/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_7/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_8/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_8/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_8/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_9/graph.bin b/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_9/graph.bin deleted file mode 100644 index 601f245..0000000 Binary files a/apluse_restore_android/app/build/intermediates/desugar_graph/debugAndroidTest/out/currentProject/jar_e9d05bce8fa830c3594ba3d38d4a1885dd6ba361b536075bf393f9083596b395_bucket_9/graph.bin and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/dex/debug/mergeProjectDexDebug/0/classes.dex b/apluse_restore_android/app/build/intermediates/dex/debug/mergeProjectDexDebug/0/classes.dex deleted file mode 100644 index 423e1f8..0000000 Binary files a/apluse_restore_android/app/build/intermediates/dex/debug/mergeProjectDexDebug/0/classes.dex and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/dex/debug/mergeProjectDexDebug/9/classes.dex b/apluse_restore_android/app/build/intermediates/dex/debug/mergeProjectDexDebug/9/classes.dex deleted file mode 100644 index 8fa7d01..0000000 Binary files a/apluse_restore_android/app/build/intermediates/dex/debug/mergeProjectDexDebug/9/classes.dex and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/dex/debugAndroidTest/mergeProjectDexDebugAndroidTest/0/classes.dex b/apluse_restore_android/app/build/intermediates/dex/debugAndroidTest/mergeProjectDexDebugAndroidTest/0/classes.dex deleted file mode 100644 index 12ecb9d..0000000 Binary files a/apluse_restore_android/app/build/intermediates/dex/debugAndroidTest/mergeProjectDexDebugAndroidTest/0/classes.dex and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/dex_archive_input_jar_hashes/debug/out b/apluse_restore_android/app/build/intermediates/dex_archive_input_jar_hashes/debug/out deleted file mode 100644 index 5caa094..0000000 Binary files a/apluse_restore_android/app/build/intermediates/dex_archive_input_jar_hashes/debug/out and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/dex_archive_input_jar_hashes/debugAndroidTest/out b/apluse_restore_android/app/build/intermediates/dex_archive_input_jar_hashes/debugAndroidTest/out deleted file mode 100644 index df7e0d1..0000000 Binary files a/apluse_restore_android/app/build/intermediates/dex_archive_input_jar_hashes/debugAndroidTest/out and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/dex_number_of_buckets_file/debug/out b/apluse_restore_android/app/build/intermediates/dex_number_of_buckets_file/debug/out deleted file mode 100644 index 9a03714..0000000 --- a/apluse_restore_android/app/build/intermediates/dex_number_of_buckets_file/debug/out +++ /dev/null @@ -1 +0,0 @@ -10 \ No newline at end of file diff --git a/apluse_restore_android/app/build/intermediates/dex_number_of_buckets_file/debugAndroidTest/out b/apluse_restore_android/app/build/intermediates/dex_number_of_buckets_file/debugAndroidTest/out deleted file mode 100644 index 9a03714..0000000 --- a/apluse_restore_android/app/build/intermediates/dex_number_of_buckets_file/debugAndroidTest/out +++ /dev/null @@ -1 +0,0 @@ -10 \ No newline at end of file diff --git a/apluse_restore_android/app/build/intermediates/incremental/debug-mergeJavaRes/merge-state b/apluse_restore_android/app/build/intermediates/incremental/debug-mergeJavaRes/merge-state deleted file mode 100644 index 1c983fc..0000000 Binary files a/apluse_restore_android/app/build/intermediates/incremental/debug-mergeJavaRes/merge-state and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/incremental/debug/mergeDebugResources/compile-file-map.properties b/apluse_restore_android/app/build/intermediates/incremental/debug/mergeDebugResources/compile-file-map.properties deleted file mode 100644 index e329e6b..0000000 --- a/apluse_restore_android/app/build/intermediates/incremental/debug/mergeDebugResources/compile-file-map.properties +++ /dev/null @@ -1,7 +0,0 @@ -#Thu May 07 16:12:09 CST 2026 -com.apluse.restore.app-main-6\:/layout/activity_main.xml=E\:\\mimo\\deemo\\apluse_restore_android\\app\\build\\intermediates\\merged_res\\debug\\layout_activity_main.xml.flat -com.apluse.restore.app-main-6\:/mipmap-hdpi/ic_launcher.png=E\:\\mimo\\deemo\\apluse_restore_android\\app\\build\\intermediates\\merged_res\\debug\\mipmap-hdpi_ic_launcher.png.flat -com.apluse.restore.app-main-6\:/mipmap-mdpi/ic_launcher.png=E\:\\mimo\\deemo\\apluse_restore_android\\app\\build\\intermediates\\merged_res\\debug\\mipmap-mdpi_ic_launcher.png.flat -com.apluse.restore.app-main-6\:/mipmap-xhdpi/ic_launcher.png=E\:\\mimo\\deemo\\apluse_restore_android\\app\\build\\intermediates\\merged_res\\debug\\mipmap-xhdpi_ic_launcher.png.flat -com.apluse.restore.app-main-6\:/mipmap-xxhdpi/ic_launcher.png=E\:\\mimo\\deemo\\apluse_restore_android\\app\\build\\intermediates\\merged_res\\debug\\mipmap-xxhdpi_ic_launcher.png.flat -com.apluse.restore.app-main-6\:/mipmap-xxxhdpi/ic_launcher.png=E\:\\mimo\\deemo\\apluse_restore_android\\app\\build\\intermediates\\merged_res\\debug\\mipmap-xxxhdpi_ic_launcher.png.flat diff --git a/apluse_restore_android/app/build/intermediates/incremental/debug/mergeDebugResources/merged.dir/values/values.xml b/apluse_restore_android/app/build/intermediates/incremental/debug/mergeDebugResources/merged.dir/values/values.xml deleted file mode 100644 index 86eb72e..0000000 --- a/apluse_restore_android/app/build/intermediates/incremental/debug/mergeDebugResources/merged.dir/values/values.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - #FF000000 - #FF6200EE - #FF3700B3 - #FF03DAC5 - #FF018786 - #FFFFFFFF - APLUSE 3.4 - - \ No newline at end of file diff --git a/apluse_restore_android/app/build/intermediates/incremental/debug/mergeDebugResources/merger.xml b/apluse_restore_android/app/build/intermediates/incremental/debug/mergeDebugResources/merger.xml deleted file mode 100644 index c108cc3..0000000 --- a/apluse_restore_android/app/build/intermediates/incremental/debug/mergeDebugResources/merger.xml +++ /dev/null @@ -1,6 +0,0 @@ - -#FF6200EE#FF3700B3#FF03DAC5#FF018786#FF000000#FFFFFFFFAPLUSE 3.4 \ No newline at end of file diff --git a/apluse_restore_android/app/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties b/apluse_restore_android/app/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties deleted file mode 100644 index b583edf..0000000 --- a/apluse_restore_android/app/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +++ /dev/null @@ -1,7 +0,0 @@ -#Thu May 07 16:12:09 CST 2026 -com.apluse.restore.app-main-5\:/layout/activity_main.xml=E\:\\mimo\\deemo\\apluse_restore_android\\app\\build\\intermediates\\packaged_res\\debug\\layout\\activity_main.xml -com.apluse.restore.app-main-5\:/mipmap-hdpi/ic_launcher.png=E\:\\mimo\\deemo\\apluse_restore_android\\app\\build\\intermediates\\packaged_res\\debug\\mipmap-hdpi-v4\\ic_launcher.png -com.apluse.restore.app-main-5\:/mipmap-mdpi/ic_launcher.png=E\:\\mimo\\deemo\\apluse_restore_android\\app\\build\\intermediates\\packaged_res\\debug\\mipmap-mdpi-v4\\ic_launcher.png -com.apluse.restore.app-main-5\:/mipmap-xhdpi/ic_launcher.png=E\:\\mimo\\deemo\\apluse_restore_android\\app\\build\\intermediates\\packaged_res\\debug\\mipmap-xhdpi-v4\\ic_launcher.png -com.apluse.restore.app-main-5\:/mipmap-xxhdpi/ic_launcher.png=E\:\\mimo\\deemo\\apluse_restore_android\\app\\build\\intermediates\\packaged_res\\debug\\mipmap-xxhdpi-v4\\ic_launcher.png -com.apluse.restore.app-main-5\:/mipmap-xxxhdpi/ic_launcher.png=E\:\\mimo\\deemo\\apluse_restore_android\\app\\build\\intermediates\\packaged_res\\debug\\mipmap-xxxhdpi-v4\\ic_launcher.png diff --git a/apluse_restore_android/app/build/intermediates/incremental/debug/packageDebugResources/merged.dir/values/values.xml b/apluse_restore_android/app/build/intermediates/incremental/debug/packageDebugResources/merged.dir/values/values.xml deleted file mode 100644 index 86eb72e..0000000 --- a/apluse_restore_android/app/build/intermediates/incremental/debug/packageDebugResources/merged.dir/values/values.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - #FF000000 - #FF6200EE - #FF3700B3 - #FF03DAC5 - #FF018786 - #FFFFFFFF - APLUSE 3.4 - - \ No newline at end of file diff --git a/apluse_restore_android/app/build/intermediates/incremental/debug/packageDebugResources/merger.xml b/apluse_restore_android/app/build/intermediates/incremental/debug/packageDebugResources/merger.xml deleted file mode 100644 index c108cc3..0000000 --- a/apluse_restore_android/app/build/intermediates/incremental/debug/packageDebugResources/merger.xml +++ /dev/null @@ -1,6 +0,0 @@ - -#FF6200EE#FF3700B3#FF03DAC5#FF018786#FF000000#FFFFFFFFAPLUSE 3.4 \ No newline at end of file diff --git a/apluse_restore_android/app/build/intermediates/incremental/debugAndroidTest-mergeJavaRes/merge-state b/apluse_restore_android/app/build/intermediates/incremental/debugAndroidTest-mergeJavaRes/merge-state deleted file mode 100644 index 1c983fc..0000000 Binary files a/apluse_restore_android/app/build/intermediates/incremental/debugAndroidTest-mergeJavaRes/merge-state and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/incremental/debugAndroidTest/mergeDebugAndroidTestResources/compile-file-map.properties b/apluse_restore_android/app/build/intermediates/incremental/debugAndroidTest/mergeDebugAndroidTestResources/compile-file-map.properties deleted file mode 100644 index bb04a34..0000000 --- a/apluse_restore_android/app/build/intermediates/incremental/debugAndroidTest/mergeDebugAndroidTestResources/compile-file-map.properties +++ /dev/null @@ -1 +0,0 @@ -#Thu May 07 16:12:10 CST 2026 diff --git a/apluse_restore_android/app/build/intermediates/incremental/debugAndroidTest/mergeDebugAndroidTestResources/merger.xml b/apluse_restore_android/app/build/intermediates/incremental/debugAndroidTest/mergeDebugAndroidTestResources/merger.xml deleted file mode 100644 index 4bcade3..0000000 --- a/apluse_restore_android/app/build/intermediates/incremental/debugAndroidTest/mergeDebugAndroidTestResources/merger.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/apluse_restore_android/app/build/intermediates/incremental/mergeDebugAndroidTestAssets/merger.xml b/apluse_restore_android/app/build/intermediates/incremental/mergeDebugAndroidTestAssets/merger.xml deleted file mode 100644 index 99408c0..0000000 --- a/apluse_restore_android/app/build/intermediates/incremental/mergeDebugAndroidTestAssets/merger.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/apluse_restore_android/app/build/intermediates/incremental/mergeDebugAndroidTestJniLibFolders/merger.xml b/apluse_restore_android/app/build/intermediates/incremental/mergeDebugAndroidTestJniLibFolders/merger.xml deleted file mode 100644 index 295a6e7..0000000 --- a/apluse_restore_android/app/build/intermediates/incremental/mergeDebugAndroidTestJniLibFolders/merger.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/apluse_restore_android/app/build/intermediates/incremental/mergeDebugAndroidTestShaders/merger.xml b/apluse_restore_android/app/build/intermediates/incremental/mergeDebugAndroidTestShaders/merger.xml deleted file mode 100644 index 219a98a..0000000 --- a/apluse_restore_android/app/build/intermediates/incremental/mergeDebugAndroidTestShaders/merger.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/apluse_restore_android/app/build/intermediates/incremental/mergeDebugAssets/merger.xml b/apluse_restore_android/app/build/intermediates/incremental/mergeDebugAssets/merger.xml deleted file mode 100644 index 4f71857..0000000 --- a/apluse_restore_android/app/build/intermediates/incremental/mergeDebugAssets/merger.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/apluse_restore_android/app/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml b/apluse_restore_android/app/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml deleted file mode 100644 index f04b6aa..0000000 --- a/apluse_restore_android/app/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/apluse_restore_android/app/build/intermediates/incremental/mergeDebugShaders/merger.xml b/apluse_restore_android/app/build/intermediates/incremental/mergeDebugShaders/merger.xml deleted file mode 100644 index 67dbd7b..0000000 --- a/apluse_restore_android/app/build/intermediates/incremental/mergeDebugShaders/merger.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/apluse_restore_android/app/build/intermediates/incremental/packageDebug/tmp/debug/dex-renamer-state.txt b/apluse_restore_android/app/build/intermediates/incremental/packageDebug/tmp/debug/dex-renamer-state.txt deleted file mode 100644 index dd39b0b..0000000 --- a/apluse_restore_android/app/build/intermediates/incremental/packageDebug/tmp/debug/dex-renamer-state.txt +++ /dev/null @@ -1,7 +0,0 @@ -#Thu May 07 16:12:13 CST 2026 -base.0=E\:\\mimo\\deemo\\apluse_restore_android\\app\\build\\intermediates\\dex\\debug\\mergeProjectDexDebug\\0\\classes.dex -base.1=E\:\\mimo\\deemo\\apluse_restore_android\\app\\build\\intermediates\\dex\\debug\\mergeProjectDexDebug\\9\\classes.dex -path.0=0/classes.dex -path.1=9/classes.dex -renamed.0=classes.dex -renamed.1=classes2.dex diff --git a/apluse_restore_android/app/build/intermediates/incremental/packageDebug/tmp/debug/zip-cache/androidResources b/apluse_restore_android/app/build/intermediates/incremental/packageDebug/tmp/debug/zip-cache/androidResources deleted file mode 100644 index 9b14dd1..0000000 Binary files a/apluse_restore_android/app/build/intermediates/incremental/packageDebug/tmp/debug/zip-cache/androidResources and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/incremental/packageDebug/tmp/debug/zip-cache/javaResources0 b/apluse_restore_android/app/build/intermediates/incremental/packageDebug/tmp/debug/zip-cache/javaResources0 deleted file mode 100644 index 945a7c5..0000000 Binary files a/apluse_restore_android/app/build/intermediates/incremental/packageDebug/tmp/debug/zip-cache/javaResources0 and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/incremental/packageDebugAndroidTest/tmp/debugAndroidTest/dex-renamer-state.txt b/apluse_restore_android/app/build/intermediates/incremental/packageDebugAndroidTest/tmp/debugAndroidTest/dex-renamer-state.txt deleted file mode 100644 index 6b9bd7e..0000000 --- a/apluse_restore_android/app/build/intermediates/incremental/packageDebugAndroidTest/tmp/debugAndroidTest/dex-renamer-state.txt +++ /dev/null @@ -1,4 +0,0 @@ -#Thu May 07 16:12:13 CST 2026 -base.0=E\:\\mimo\\deemo\\apluse_restore_android\\app\\build\\intermediates\\dex\\debugAndroidTest\\mergeProjectDexDebugAndroidTest\\0\\classes.dex -path.0=0/classes.dex -renamed.0=classes.dex diff --git a/apluse_restore_android/app/build/intermediates/incremental/packageDebugAndroidTest/tmp/debugAndroidTest/zip-cache/androidResources b/apluse_restore_android/app/build/intermediates/incremental/packageDebugAndroidTest/tmp/debugAndroidTest/zip-cache/androidResources deleted file mode 100644 index cbbd08c..0000000 Binary files a/apluse_restore_android/app/build/intermediates/incremental/packageDebugAndroidTest/tmp/debugAndroidTest/zip-cache/androidResources and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/incremental/packageDebugAndroidTest/tmp/debugAndroidTest/zip-cache/javaResources0 b/apluse_restore_android/app/build/intermediates/incremental/packageDebugAndroidTest/tmp/debugAndroidTest/zip-cache/javaResources0 deleted file mode 100644 index 945a7c5..0000000 Binary files a/apluse_restore_android/app/build/intermediates/incremental/packageDebugAndroidTest/tmp/debugAndroidTest/zip-cache/javaResources0 and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/javac/debug/classes/com/apluse/restore/MainActivity.class b/apluse_restore_android/app/build/intermediates/javac/debug/classes/com/apluse/restore/MainActivity.class deleted file mode 100644 index 07c067a..0000000 Binary files a/apluse_restore_android/app/build/intermediates/javac/debug/classes/com/apluse/restore/MainActivity.class and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/javac/debug/classes/com/apluse/restore/RestoreEngine$Metadata.class b/apluse_restore_android/app/build/intermediates/javac/debug/classes/com/apluse/restore/RestoreEngine$Metadata.class deleted file mode 100644 index 9744cbc..0000000 Binary files a/apluse_restore_android/app/build/intermediates/javac/debug/classes/com/apluse/restore/RestoreEngine$Metadata.class and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/javac/debug/classes/com/apluse/restore/RestoreEngine$ScanCallback.class b/apluse_restore_android/app/build/intermediates/javac/debug/classes/com/apluse/restore/RestoreEngine$ScanCallback.class deleted file mode 100644 index 07466e4..0000000 Binary files a/apluse_restore_android/app/build/intermediates/javac/debug/classes/com/apluse/restore/RestoreEngine$ScanCallback.class and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/javac/debug/classes/com/apluse/restore/RestoreEngine.class b/apluse_restore_android/app/build/intermediates/javac/debug/classes/com/apluse/restore/RestoreEngine.class deleted file mode 100644 index 7ad855e..0000000 Binary files a/apluse_restore_android/app/build/intermediates/javac/debug/classes/com/apluse/restore/RestoreEngine.class and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/local_only_symbol_list/debug/R-def.txt b/apluse_restore_android/app/build/intermediates/local_only_symbol_list/debug/R-def.txt deleted file mode 100644 index 34747f7..0000000 --- a/apluse_restore_android/app/build/intermediates/local_only_symbol_list/debug/R-def.txt +++ /dev/null @@ -1,22 +0,0 @@ -R_DEF: Internal format may change without notice -local -color black -color purple_500 -color purple_700 -color teal_200 -color teal_700 -color white -id btnApplyMagic -id btnResetMagic -id btnRestore -id btnScan -id btnSelectFolder -id etMagic -id progressBar -id tvLog -id tvMagic -id tvStatus -layout activity_main -mipmap ic_launcher -string app_name -style AppTheme diff --git a/apluse_restore_android/app/build/intermediates/manifest_merge_blame_file/debug/manifest-merger-blame-debug-report.txt b/apluse_restore_android/app/build/intermediates/manifest_merge_blame_file/debug/manifest-merger-blame-debug-report.txt deleted file mode 100644 index e24fe35..0000000 --- a/apluse_restore_android/app/build/intermediates/manifest_merge_blame_file/debug/manifest-merger-blame-debug-report.txt +++ /dev/null @@ -1,52 +0,0 @@ -1 -2 -6 -7 -10 -11 -11-->E:\mimo\deemo\apluse_restore_android\app\src\main\AndroidManifest.xml:4:5-80 -11-->E:\mimo\deemo\apluse_restore_android\app\src\main\AndroidManifest.xml:4:22-77 -12 -12-->E:\mimo\deemo\apluse_restore_android\app\src\main\AndroidManifest.xml:5:5-81 -12-->E:\mimo\deemo\apluse_restore_android\app\src\main\AndroidManifest.xml:5:22-78 -13 -13-->E:\mimo\deemo\apluse_restore_android\app\src\main\AndroidManifest.xml:6:5-82 -13-->E:\mimo\deemo\apluse_restore_android\app\src\main\AndroidManifest.xml:6:22-79 -14 -15 E:\mimo\deemo\apluse_restore_android\app\src\main\AndroidManifest.xml:8:5-23:19 -16 android:debuggable="true" -17 android:extractNativeLibs="true" -18 android:icon="@mipmap/ic_launcher" -18-->E:\mimo\deemo\apluse_restore_android\app\src\main\AndroidManifest.xml:10:9-43 -19 android:label="APLUSE 3.4" -19-->E:\mimo\deemo\apluse_restore_android\app\src\main\AndroidManifest.xml:9:9-35 -20 android:requestLegacyExternalStorage="true" -20-->E:\mimo\deemo\apluse_restore_android\app\src\main\AndroidManifest.xml:11:9-52 -21 android:theme="@style/AppTheme" > -21-->E:\mimo\deemo\apluse_restore_android\app\src\main\AndroidManifest.xml:12:9-40 -22 E:\mimo\deemo\apluse_restore_android\app\src\main\AndroidManifest.xml:14:9-21:20 -23 android:name="com.apluse.restore.MainActivity" -23-->E:\mimo\deemo\apluse_restore_android\app\src\main\AndroidManifest.xml:15:13-41 -24 android:exported="true" > -24-->E:\mimo\deemo\apluse_restore_android\app\src\main\AndroidManifest.xml:16:13-36 -25 -25-->E:\mimo\deemo\apluse_restore_android\app\src\main\AndroidManifest.xml:17:13-20:29 -26 -26-->E:\mimo\deemo\apluse_restore_android\app\src\main\AndroidManifest.xml:18:17-69 -26-->E:\mimo\deemo\apluse_restore_android\app\src\main\AndroidManifest.xml:18:25-66 -27 -28 -28-->E:\mimo\deemo\apluse_restore_android\app\src\main\AndroidManifest.xml:19:17-77 -28-->E:\mimo\deemo\apluse_restore_android\app\src\main\AndroidManifest.xml:19:27-74 -29 -30 -31 -32 -33 diff --git a/apluse_restore_android/app/build/intermediates/manifest_merge_blame_file/debugAndroidTest/manifest-merger-blame-debug-androidTest-report.txt b/apluse_restore_android/app/build/intermediates/manifest_merge_blame_file/debugAndroidTest/manifest-merger-blame-debug-androidTest-report.txt deleted file mode 100644 index 9e44923..0000000 --- a/apluse_restore_android/app/build/intermediates/manifest_merge_blame_file/debugAndroidTest/manifest-merger-blame-debug-androidTest-report.txt +++ /dev/null @@ -1,34 +0,0 @@ -1 -2 -4 -5 E:\mimo\deemo\apluse_restore_android\app\build\intermediates\tmp\manifest\androidTest\debug\tempFile1ProcessTestManifest241253139500513197.xml:5:5-74 -6 android:minSdkVersion="21" -6-->E:\mimo\deemo\apluse_restore_android\app\build\intermediates\tmp\manifest\androidTest\debug\tempFile1ProcessTestManifest241253139500513197.xml:5:15-41 -7 android:targetSdkVersion="34" /> -7-->E:\mimo\deemo\apluse_restore_android\app\build\intermediates\tmp\manifest\androidTest\debug\tempFile1ProcessTestManifest241253139500513197.xml:5:42-71 -8 -9 E:\mimo\deemo\apluse_restore_android\app\build\intermediates\tmp\manifest\androidTest\debug\tempFile1ProcessTestManifest241253139500513197.xml:11:5-15:68 -10 android:name="android.test.InstrumentationTestRunner" -10-->E:\mimo\deemo\apluse_restore_android\app\build\intermediates\tmp\manifest\androidTest\debug\tempFile1ProcessTestManifest241253139500513197.xml:11:22-75 -11 android:functionalTest="false" -11-->E:\mimo\deemo\apluse_restore_android\app\build\intermediates\tmp\manifest\androidTest\debug\tempFile1ProcessTestManifest241253139500513197.xml:14:22-52 -12 android:handleProfiling="false" -12-->E:\mimo\deemo\apluse_restore_android\app\build\intermediates\tmp\manifest\androidTest\debug\tempFile1ProcessTestManifest241253139500513197.xml:13:22-53 -13 android:label="Tests for com.apluse.restore" -13-->E:\mimo\deemo\apluse_restore_android\app\build\intermediates\tmp\manifest\androidTest\debug\tempFile1ProcessTestManifest241253139500513197.xml:15:22-66 -14 android:targetPackage="com.apluse.restore" /> -14-->E:\mimo\deemo\apluse_restore_android\app\build\intermediates\tmp\manifest\androidTest\debug\tempFile1ProcessTestManifest241253139500513197.xml:12:22-64 -15 -16 E:\mimo\deemo\apluse_restore_android\app\build\intermediates\tmp\manifest\androidTest\debug\tempFile1ProcessTestManifest241253139500513197.xml:7:5-9:19 -17 android:debuggable="true" -18 android:extractNativeLibs="true" > -19 -19-->E:\mimo\deemo\apluse_restore_android\app\build\intermediates\tmp\manifest\androidTest\debug\tempFile1ProcessTestManifest241253139500513197.xml:8:9-60 -19-->E:\mimo\deemo\apluse_restore_android\app\build\intermediates\tmp\manifest\androidTest\debug\tempFile1ProcessTestManifest241253139500513197.xml:8:23-57 -20 -21 -22 diff --git a/apluse_restore_android/app/build/intermediates/merged_java_res/debug/base.jar b/apluse_restore_android/app/build/intermediates/merged_java_res/debug/base.jar deleted file mode 100644 index 15cb0ec..0000000 Binary files a/apluse_restore_android/app/build/intermediates/merged_java_res/debug/base.jar and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/merged_java_res/debugAndroidTest/feature-app.jar b/apluse_restore_android/app/build/intermediates/merged_java_res/debugAndroidTest/feature-app.jar deleted file mode 100644 index 15cb0ec..0000000 Binary files a/apluse_restore_android/app/build/intermediates/merged_java_res/debugAndroidTest/feature-app.jar and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/merged_manifest/debug/AndroidManifest.xml b/apluse_restore_android/app/build/intermediates/merged_manifest/debug/AndroidManifest.xml deleted file mode 100644 index bc5b802..0000000 --- a/apluse_restore_android/app/build/intermediates/merged_manifest/debug/AndroidManifest.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/apluse_restore_android/app/build/intermediates/merged_manifests/debug/AndroidManifest.xml b/apluse_restore_android/app/build/intermediates/merged_manifests/debug/AndroidManifest.xml deleted file mode 100644 index bc5b802..0000000 --- a/apluse_restore_android/app/build/intermediates/merged_manifests/debug/AndroidManifest.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/apluse_restore_android/app/build/intermediates/merged_manifests/debug/output-metadata.json b/apluse_restore_android/app/build/intermediates/merged_manifests/debug/output-metadata.json deleted file mode 100644 index 79a4885..0000000 --- a/apluse_restore_android/app/build/intermediates/merged_manifests/debug/output-metadata.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "version": 3, - "artifactType": { - "type": "MERGED_MANIFESTS", - "kind": "Directory" - }, - "applicationId": "com.apluse.restore", - "variantName": "debug", - "elements": [ - { - "type": "SINGLE", - "filters": [], - "attributes": [], - "versionCode": 1, - "versionName": "1.0", - "outputFile": "AndroidManifest.xml" - } - ], - "elementType": "File" -} \ No newline at end of file diff --git a/apluse_restore_android/app/build/intermediates/merged_res/debug/layout_activity_main.xml.flat b/apluse_restore_android/app/build/intermediates/merged_res/debug/layout_activity_main.xml.flat deleted file mode 100644 index c1391cd..0000000 Binary files a/apluse_restore_android/app/build/intermediates/merged_res/debug/layout_activity_main.xml.flat and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/merged_res/debug/mipmap-hdpi_ic_launcher.png.flat b/apluse_restore_android/app/build/intermediates/merged_res/debug/mipmap-hdpi_ic_launcher.png.flat deleted file mode 100644 index 9aad5f9..0000000 Binary files a/apluse_restore_android/app/build/intermediates/merged_res/debug/mipmap-hdpi_ic_launcher.png.flat and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/merged_res/debug/mipmap-mdpi_ic_launcher.png.flat b/apluse_restore_android/app/build/intermediates/merged_res/debug/mipmap-mdpi_ic_launcher.png.flat deleted file mode 100644 index 30afada..0000000 Binary files a/apluse_restore_android/app/build/intermediates/merged_res/debug/mipmap-mdpi_ic_launcher.png.flat and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/merged_res/debug/mipmap-xhdpi_ic_launcher.png.flat b/apluse_restore_android/app/build/intermediates/merged_res/debug/mipmap-xhdpi_ic_launcher.png.flat deleted file mode 100644 index e91a96d..0000000 Binary files a/apluse_restore_android/app/build/intermediates/merged_res/debug/mipmap-xhdpi_ic_launcher.png.flat and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/merged_res/debug/mipmap-xxhdpi_ic_launcher.png.flat b/apluse_restore_android/app/build/intermediates/merged_res/debug/mipmap-xxhdpi_ic_launcher.png.flat deleted file mode 100644 index 41798f5..0000000 Binary files a/apluse_restore_android/app/build/intermediates/merged_res/debug/mipmap-xxhdpi_ic_launcher.png.flat and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/merged_res/debug/mipmap-xxxhdpi_ic_launcher.png.flat b/apluse_restore_android/app/build/intermediates/merged_res/debug/mipmap-xxxhdpi_ic_launcher.png.flat deleted file mode 100644 index 9bffe57..0000000 Binary files a/apluse_restore_android/app/build/intermediates/merged_res/debug/mipmap-xxxhdpi_ic_launcher.png.flat and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/merged_res/debug/values_values.arsc.flat b/apluse_restore_android/app/build/intermediates/merged_res/debug/values_values.arsc.flat deleted file mode 100644 index a29a786..0000000 Binary files a/apluse_restore_android/app/build/intermediates/merged_res/debug/values_values.arsc.flat and /dev/null differ diff --git a/apluse_restore_android/app/build/intermediates/merged_res_blame_folder/debug/out/multi-v2/debug.json b/apluse_restore_android/app/build/intermediates/merged_res_blame_folder/debug/out/multi-v2/debug.json deleted file mode 100644 index 0e515b5..0000000 --- a/apluse_restore_android/app/build/intermediates/merged_res_blame_folder/debug/out/multi-v2/debug.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "logs": [ - { - "outputFile": "com.apluse.restore.app-merged_res-4:/values_values.arsc.flat", - "map": [ - { - "source": "E:\\mimo\\deemo\\apluse_restore_android\\app\\src\\main\\res\\values\\strings.xml", - "from": { - "startLines": "2", - "startColumns": "4", - "startOffsets": "57", - "endColumns": "47", - "endOffsets": "100" - }, - "to": { - "startLines": "8", - "startColumns": "4", - "startOffsets": "323", - "endColumns": "47", - "endOffsets": "366" - } - }, - { - "source": "E:\\mimo\\deemo\\apluse_restore_android\\app\\src\\main\\res\\values\\styles.xml", - "from": { - "startLines": "2", - "startColumns": "4", - "startOffsets": "57", - "endLines": "6", - "endColumns": "12", - "endOffsets": "323" - }, - "to": { - "startLines": "9", - "startColumns": "4", - "startOffsets": "371", - "endLines": "13", - "endColumns": "12", - "endOffsets": "633" - } - }, - { - "source": "E:\\mimo\\deemo\\apluse_restore_android\\app\\src\\main\\res\\values\\colors.xml", - "from": { - "startLines": "6,2,3,4,5,7", - "startColumns": "4,4,4,4,4,4", - "startOffsets": "245,57,105,153,199,288", - "endColumns": "41,46,46,44,44,41", - "endOffsets": "282,99,147,193,239,325" - }, - "to": { - "startLines": "2,3,4,5,6,7", - "startColumns": "4,4,4,4,4,4", - "startOffsets": "55,97,144,191,236,281", - "endColumns": "41,46,46,44,44,41", - "endOffsets": "92,139,186,231,276,318" - } - } - ] - } - ] -} \ No newline at end of file diff --git a/apluse_restore_android/app/build/intermediates/merged_res_blame_folder/debug/out/multi-v2/values.json b/apluse_restore_android/app/build/intermediates/merged_res_blame_folder/debug/out/multi-v2/values.json deleted file mode 100644 index 0c780a3..0000000 --- a/apluse_restore_android/app/build/intermediates/merged_res_blame_folder/debug/out/multi-v2/values.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "logs": [ - { - "outputFile": "com.apluse.restore.app-mergeDebugResources-2:/values/values.xml", - "map": [ - { - "source": "E:\\mimo\\deemo\\apluse_restore_android\\app\\src\\main\\res\\values\\strings.xml", - "from": { - "startLines": "2", - "startColumns": "4", - "startOffsets": "57", - "endColumns": "47", - "endOffsets": "100" - }, - "to": { - "startLines": "8", - "startColumns": "4", - "startOffsets": "323", - "endColumns": "47", - "endOffsets": "366" - } - }, - { - "source": "E:\\mimo\\deemo\\apluse_restore_android\\app\\src\\main\\res\\values\\styles.xml", - "from": { - "startLines": "2", - "startColumns": "4", - "startOffsets": "57", - "endLines": "6", - "endColumns": "12", - "endOffsets": "323" - }, - "to": { - "startLines": "9", - "startColumns": "4", - "startOffsets": "371", - "endLines": "13", - "endColumns": "12", - "endOffsets": "633" - } - }, - { - "source": "E:\\mimo\\deemo\\apluse_restore_android\\app\\src\\main\\res\\values\\colors.xml", - "from": { - "startLines": "6,2,3,4,5,7", - "startColumns": "4,4,4,4,4,4", - "startOffsets": "245,57,105,153,199,288", - "endColumns": "41,46,46,44,44,41", - "endOffsets": "282,99,147,193,239,325" - }, - "to": { - "startLines": "2,3,4,5,6,7", - "startColumns": "4,4,4,4,4,4", - "startOffsets": "55,97,144,191,236,281", - "endColumns": "41,46,46,44,44,41", - "endOffsets": "92,139,186,231,276,318" - } - } - ] - } - ] -} \ No newline at end of file diff --git a/apluse_restore_android/app/build/intermediates/merged_res_blame_folder/debug/out/single/debug.json b/apluse_restore_android/app/build/intermediates/merged_res_blame_folder/debug/out/single/debug.json deleted file mode 100644 index 663eb61..0000000 --- a/apluse_restore_android/app/build/intermediates/merged_res_blame_folder/debug/out/single/debug.json +++ /dev/null @@ -1,26 +0,0 @@ -[ - { - "merged": "com.apluse.restore.app-merged_res-4:/mipmap-mdpi_ic_launcher.png.flat", - "source": "com.apluse.restore.app-main-6:/mipmap-mdpi/ic_launcher.png" - }, - { - "merged": "com.apluse.restore.app-merged_res-4:/mipmap-xxxhdpi_ic_launcher.png.flat", - "source": "com.apluse.restore.app-main-6:/mipmap-xxxhdpi/ic_launcher.png" - }, - { - "merged": "com.apluse.restore.app-merged_res-4:/layout_activity_main.xml.flat", - "source": "com.apluse.restore.app-main-6:/layout/activity_main.xml" - }, - { - "merged": "com.apluse.restore.app-merged_res-4:/mipmap-xxhdpi_ic_launcher.png.flat", - "source": "com.apluse.restore.app-main-6:/mipmap-xxhdpi/ic_launcher.png" - }, - { - "merged": "com.apluse.restore.app-merged_res-4:/mipmap-hdpi_ic_launcher.png.flat", - "source": "com.apluse.restore.app-main-6:/mipmap-hdpi/ic_launcher.png" - }, - { - "merged": "com.apluse.restore.app-merged_res-4:/mipmap-xhdpi_ic_launcher.png.flat", - "source": "com.apluse.restore.app-main-6:/mipmap-xhdpi/ic_launcher.png" - } -] \ No newline at end of file diff --git a/apluse_restore_android/app/build/intermediates/navigation_json/debug/navigation.json b/apluse_restore_android/app/build/intermediates/navigation_json/debug/navigation.json deleted file mode 100644 index 0637a08..0000000 --- a/apluse_restore_android/app/build/intermediates/navigation_json/debug/navigation.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/apluse_restore_android/app/build/intermediates/packaged_manifests/debug/AndroidManifest.xml b/apluse_restore_android/app/build/intermediates/packaged_manifests/debug/AndroidManifest.xml deleted file mode 100644 index bc5b802..0000000 --- a/apluse_restore_android/app/build/intermediates/packaged_manifests/debug/AndroidManifest.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/apluse_restore_android/app/build/intermediates/packaged_manifests/debug/output-metadata.json b/apluse_restore_android/app/build/intermediates/packaged_manifests/debug/output-metadata.json deleted file mode 100644 index c0fad8d..0000000 --- a/apluse_restore_android/app/build/intermediates/packaged_manifests/debug/output-metadata.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "version": 3, - "artifactType": { - "type": "PACKAGED_MANIFESTS", - "kind": "Directory" - }, - "applicationId": "com.apluse.restore", - "variantName": "debug", - "elements": [ - { - "type": "SINGLE", - "filters": [], - "attributes": [], - "versionCode": 1, - "versionName": "1.0", - "outputFile": "AndroidManifest.xml" - } - ], - "elementType": "File" -} \ No newline at end of file diff --git a/apluse_restore_android/app/build/intermediates/packaged_manifests/debugAndroidTest/AndroidManifest.xml b/apluse_restore_android/app/build/intermediates/packaged_manifests/debugAndroidTest/AndroidManifest.xml deleted file mode 100644 index f90fa3e..0000000 --- a/apluse_restore_android/app/build/intermediates/packaged_manifests/debugAndroidTest/AndroidManifest.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/apluse_restore_android/app/build/intermediates/packaged_manifests/debugAndroidTest/output-metadata.json b/apluse_restore_android/app/build/intermediates/packaged_manifests/debugAndroidTest/output-metadata.json deleted file mode 100644 index 795b101..0000000 --- a/apluse_restore_android/app/build/intermediates/packaged_manifests/debugAndroidTest/output-metadata.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "version": 3, - "artifactType": { - "type": "PACKAGED_MANIFESTS", - "kind": "Directory" - }, - "applicationId": "com.apluse.restore.test", - "variantName": "debugAndroidTest", - "elements": [ - { - "type": "SINGLE", - "filters": [], - "attributes": [], - "outputFile": "AndroidManifest.xml" - } - ], - "elementType": "File" -} \ No newline at end of file diff --git a/apluse_restore_android/app/build/intermediates/packaged_res/debug/layout/activity_main.xml b/apluse_restore_android/app/build/intermediates/packaged_res/debug/layout/activity_main.xml deleted file mode 100644 index dc6a0c9..0000000 --- a/apluse_restore_android/app/build/intermediates/packaged_res/debug/layout/activity_main.xml +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - - - - -