ww-manager 2.3.2__tar.gz → 2.3.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ww_manager-2.3.2 → ww_manager-2.3.4}/.github/generate-pkgbuild.sh +9 -4
- {ww_manager-2.3.2 → ww_manager-2.3.4}/.github/workflows/aur-publish.yml +2 -1
- ww_manager-2.3.4/.github/workflows/aur-republish.yml +38 -0
- {ww_manager-2.3.2 → ww_manager-2.3.4}/.github/workflows/ci.yml +3 -1
- {ww_manager-2.3.2 → ww_manager-2.3.4}/PKG-INFO +3 -3
- {ww_manager-2.3.2 → ww_manager-2.3.4}/README.md +2 -2
- {ww_manager-2.3.2 → ww_manager-2.3.4}/docs/README.en.md +2 -2
- {ww_manager-2.3.2 → ww_manager-2.3.4}/docs/linux.md +31 -3
- {ww_manager-2.3.2 → ww_manager-2.3.4}/pyproject.toml +1 -1
- {ww_manager-2.3.2 → ww_manager-2.3.4}/scripts/demo_cli_menus.py +1 -1
- {ww_manager-2.3.2 → ww_manager-2.3.4}/scripts/demo_resource_menu.py +1 -1
- ww_manager-2.3.4/scripts/extract-icon.py +146 -0
- ww_manager-2.3.4/scripts/setup-linux.sh +986 -0
- {ww_manager-2.3.2 → ww_manager-2.3.4}/src/ww_manager/cli.py +2 -2
- {ww_manager-2.3.2 → ww_manager-2.3.4}/src/ww_manager/core.py +12 -5
- {ww_manager-2.3.2 → ww_manager-2.3.4}/src/ww_manager/incremental.py +27 -12
- {ww_manager-2.3.2 → ww_manager-2.3.4}/src/ww_manager/resource_updates.py +68 -5
- {ww_manager-2.3.2 → ww_manager-2.3.4}/tests/test_demo_cli_menus.py +1 -1
- {ww_manager-2.3.2 → ww_manager-2.3.4}/tests/test_menu.py +3 -3
- {ww_manager-2.3.2 → ww_manager-2.3.4}/tests/test_resource_tier.py +15 -1
- {ww_manager-2.3.2 → ww_manager-2.3.4}/tests/test_resource_updates.py +122 -0
- {ww_manager-2.3.2 → ww_manager-2.3.4}/uv.lock +2 -2
- ww_manager-2.3.4/vendor/riched20-utf8-patch/SOURCE.json +12 -0
- ww_manager-2.3.4/vendor/riched20-utf8-patch/riched20-utf8-rtf.patch +90 -0
- {ww_manager-2.3.2 → ww_manager-2.3.4}/.github/workflows/publish.yml +0 -0
- {ww_manager-2.3.2 → ww_manager-2.3.4}/.github/workflows/release-hpatchz.yml +0 -0
- {ww_manager-2.3.2 → ww_manager-2.3.4}/.gitignore +0 -0
- {ww_manager-2.3.2 → ww_manager-2.3.4}/.pre-commit-config.yaml +0 -0
- {ww_manager-2.3.2 → ww_manager-2.3.4}/LICENSE +0 -0
- {ww_manager-2.3.2 → ww_manager-2.3.4}/Makefile +0 -0
- {ww_manager-2.3.2 → ww_manager-2.3.4}/docs/images/cli-preview.webp +0 -0
- {ww_manager-2.3.2 → ww_manager-2.3.4}/docs/images/download-preview.webp +0 -0
- {ww_manager-2.3.2 → ww_manager-2.3.4}/docs/wuwa-launcher.md +0 -0
- {ww_manager-2.3.2 → ww_manager-2.3.4}/run_ww.sh +0 -0
- {ww_manager-2.3.2 → ww_manager-2.3.4}/scripts/demo_disk_space_errors.py +0 -0
- {ww_manager-2.3.2 → ww_manager-2.3.4}/scripts/sync_version.py +0 -0
- {ww_manager-2.3.2 → ww_manager-2.3.4}/src/ww_manager/config.py +0 -0
- {ww_manager-2.3.2 → ww_manager-2.3.4}/src/ww_manager/download.py +0 -0
- {ww_manager-2.3.2 → ww_manager-2.3.4}/src/ww_manager/menu.py +0 -0
- {ww_manager-2.3.2 → ww_manager-2.3.4}/tests/test_cli_help.py +0 -0
- {ww_manager-2.3.2 → ww_manager-2.3.4}/tests/test_cli_menus.py +0 -0
- {ww_manager-2.3.2 → ww_manager-2.3.4}/tests/test_demo_disk_space_errors.py +0 -0
|
@@ -10,11 +10,15 @@ URL="https://github.com/$REPO/archive/refs/tags/v$VERSION.tar.gz"
|
|
|
10
10
|
curl --fail --location "$URL" -o source.tar.gz
|
|
11
11
|
SHA256=$(sha256sum source.tar.gz | cut -d ' ' -f 1)
|
|
12
12
|
|
|
13
|
+
# pkgrel:新上游版本发 tag 时固定为 1;纯打包修正(版本号不变)由
|
|
14
|
+
# aur-republish 工作流传入 2、3…,让已装旧 pkgrel 的用户能被 pacman 升级
|
|
15
|
+
PKGREL="${PKGREL:-1}"
|
|
16
|
+
|
|
13
17
|
# 生成 PKGBUILD
|
|
14
18
|
cat <<EOF >PKGBUILD
|
|
15
19
|
pkgname=ww-manager
|
|
16
20
|
pkgver=$VERSION
|
|
17
|
-
pkgrel
|
|
21
|
+
pkgrel=$PKGREL
|
|
18
22
|
pkgdesc="ww-manager (A Wuthering Waves CLI Manager)"
|
|
19
23
|
arch=('any')
|
|
20
24
|
url="https://github.com/$REPO"
|
|
@@ -22,10 +26,11 @@ license=('MIT')
|
|
|
22
26
|
depends=(
|
|
23
27
|
'python'
|
|
24
28
|
'python-typer'
|
|
25
|
-
'python-rich'
|
|
26
|
-
'python-requests'
|
|
27
|
-
'python-certifi'
|
|
29
|
+
'python-rich'
|
|
30
|
+
'python-requests'
|
|
31
|
+
'python-certifi'
|
|
28
32
|
'python-typing_extensions'
|
|
33
|
+
'python-prompt_toolkit'
|
|
29
34
|
)
|
|
30
35
|
makedepends=('python-build' 'python-installer' 'python-hatchling')
|
|
31
36
|
source=("\$pkgname-\$pkgver.tar.gz::$URL")
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: Republish to AUR (pkgrel bump)
|
|
2
|
+
|
|
3
|
+
# 仅手动触发:版本号不变、只改打包内容(依赖、安装路径、启动参数等)时用。
|
|
4
|
+
# pkgrel 递增后 AUR 上的版本从 X.Y.Z-1 变成 X.Y.Z-2,已安装旧包的用户
|
|
5
|
+
# 才能通过 pacman/yay 升级拿到修正。
|
|
6
|
+
on:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
inputs:
|
|
9
|
+
pkgrel:
|
|
10
|
+
description: 'pkgrel(纯打包修正请递增,例如 2、3…)'
|
|
11
|
+
required: true
|
|
12
|
+
default: '2'
|
|
13
|
+
reason:
|
|
14
|
+
description: '本次重新打包的原因(写入 commit message)'
|
|
15
|
+
required: false
|
|
16
|
+
default: ''
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
aur-republish:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
|
|
24
|
+
- name: Generate PKGBUILD
|
|
25
|
+
env:
|
|
26
|
+
PKGREL: ${{ inputs.pkgrel }}
|
|
27
|
+
run: bash .github/generate-pkgbuild.sh
|
|
28
|
+
|
|
29
|
+
- name: Publish AUR package
|
|
30
|
+
uses: KSXGitHub/github-actions-deploy-aur@master
|
|
31
|
+
with:
|
|
32
|
+
pkgname: ww-manager
|
|
33
|
+
pkgbuild: ./PKGBUILD
|
|
34
|
+
commit_username: ${{ github.repository_owner }}
|
|
35
|
+
commit_email: ${{ github.repository_owner }}@users.noreply.github.com
|
|
36
|
+
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
|
37
|
+
commit_message: "Repackage ${{ github.ref_name }}: pkgrel ${{ inputs.pkgrel }}${{ inputs.reason && format(' - {0}', inputs.reason) || '' }}"
|
|
38
|
+
ssh_keyscan_types: rsa,ecdsa,ed25519
|
|
@@ -19,7 +19,9 @@ jobs:
|
|
|
19
19
|
- name: Set up Python
|
|
20
20
|
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
|
21
21
|
with:
|
|
22
|
-
|
|
22
|
+
# ubuntu-latest 已切到 26.04,其预构建版本里没有 3.9;
|
|
23
|
+
# 项目仍声明 >=3.9,这里只需一个能装上的版本跑检查。
|
|
24
|
+
python-version: "3.13"
|
|
23
25
|
|
|
24
26
|
- name: Install dependencies
|
|
25
27
|
run: pip install ruff -e .
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ww-manager
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.4
|
|
4
4
|
Summary: 鸣潮命令行管理器
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.9
|
|
@@ -15,7 +15,7 @@ Description-Content-Type: text/markdown
|
|
|
15
15
|
<div align="center">
|
|
16
16
|
<h1>WutheringWaves CLI Manager</h1>
|
|
17
17
|
<h3>鸣潮命令行管理器</h3>
|
|
18
|
-
<a href="https://python.org"><img src="https://img.shields.io/badge/Python-3.9%2B-blue.svg" alt="Python 3.9+"></a> <a href="https://github.com/astral-sh/uv"><img src="https://img.shields.io/badge/Tool-uv-purple.svg" alt="uv"></a> <img src="https://img.shields.io/badge/Version-2.3.
|
|
18
|
+
<a href="https://python.org"><img src="https://img.shields.io/badge/Python-3.9%2B-blue.svg" alt="Python 3.9+"></a> <a href="https://github.com/astral-sh/uv"><img src="https://img.shields.io/badge/Tool-uv-purple.svg" alt="uv"></a> <img src="https://img.shields.io/badge/Version-2.3.4-brightgreen.svg" alt="version 2.3.4">
|
|
19
19
|
</div>
|
|
20
20
|
|
|
21
21
|
<p align="center">
|
|
@@ -117,7 +117,7 @@ ww status
|
|
|
117
117
|
|
|
118
118
|
| 档位 | 启动参数 |
|
|
119
119
|
|---|---|
|
|
120
|
-
| SD ·
|
|
120
|
+
| SD · 流畅 | `-krqlv=sd` |
|
|
121
121
|
| HD · 高清 | `-krqlv=hd` |
|
|
122
122
|
| UHD · 极致 | `-krqlv=uhd` |
|
|
123
123
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
<h1>WutheringWaves CLI Manager</h1>
|
|
3
3
|
<h3>鸣潮命令行管理器</h3>
|
|
4
|
-
<a href="https://python.org"><img src="https://img.shields.io/badge/Python-3.9%2B-blue.svg" alt="Python 3.9+"></a> <a href="https://github.com/astral-sh/uv"><img src="https://img.shields.io/badge/Tool-uv-purple.svg" alt="uv"></a> <img src="https://img.shields.io/badge/Version-2.3.
|
|
4
|
+
<a href="https://python.org"><img src="https://img.shields.io/badge/Python-3.9%2B-blue.svg" alt="Python 3.9+"></a> <a href="https://github.com/astral-sh/uv"><img src="https://img.shields.io/badge/Tool-uv-purple.svg" alt="uv"></a> <img src="https://img.shields.io/badge/Version-2.3.4-brightgreen.svg" alt="version 2.3.4">
|
|
5
5
|
</div>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
@@ -103,7 +103,7 @@ ww status
|
|
|
103
103
|
|
|
104
104
|
| 档位 | 启动参数 |
|
|
105
105
|
|---|---|
|
|
106
|
-
| SD ·
|
|
106
|
+
| SD · 流畅 | `-krqlv=sd` |
|
|
107
107
|
| HD · 高清 | `-krqlv=hd` |
|
|
108
108
|
| UHD · 极致 | `-krqlv=uhd` |
|
|
109
109
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
<h1>WutheringWaves CLI Manager</h1>
|
|
3
3
|
<h3>Command-line manager for Wuthering Waves</h3>
|
|
4
|
-
<a href="https://python.org"><img src="https://img.shields.io/badge/Python-3.9%2B-blue.svg" alt="Python 3.9+"></a> <a href="https://github.com/astral-sh/uv"><img src="https://img.shields.io/badge/Tool-uv-purple.svg" alt="uv"></a> <img src="https://img.shields.io/badge/Version-2.3.
|
|
4
|
+
<a href="https://python.org"><img src="https://img.shields.io/badge/Python-3.9%2B-blue.svg" alt="Python 3.9+"></a> <a href="https://github.com/astral-sh/uv"><img src="https://img.shields.io/badge/Tool-uv-purple.svg" alt="uv"></a> <img src="https://img.shields.io/badge/Version-2.3.4-brightgreen.svg" alt="version 2.3.4">
|
|
5
5
|
</div>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
@@ -103,7 +103,7 @@ Pass the launch argument for an installed quality tier:
|
|
|
103
103
|
|
|
104
104
|
| Tier | Launch argument |
|
|
105
105
|
|---|---|
|
|
106
|
-
| SD ·
|
|
106
|
+
| SD · Smooth | `-krqlv=sd` |
|
|
107
107
|
| HD · High | `-krqlv=hd` |
|
|
108
108
|
| UHD · Ultra | `-krqlv=uhd` |
|
|
109
109
|
|
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
|
|
11
11
|
| 档位 | 参数 |
|
|
12
12
|
|---|---|
|
|
13
|
-
| SD ·
|
|
13
|
+
| SD · 流畅 | `-krqlv=sd` |
|
|
14
14
|
| HD · 高清 | `-krqlv=hd` |
|
|
15
15
|
| UHD · 极致 | `-krqlv=uhd` |
|
|
16
16
|
|
|
17
|
-
只指定一个档位。可以用 `ww status` 查看工具当前默认档位,但工具不会自动修改 Steam
|
|
17
|
+
只指定一个档位。可以用 `ww status` 查看工具当前默认档位,但工具不会自动修改 Steam 或其他启动器中的启动参数。下面是一些启动游戏的参考:
|
|
18
18
|
|
|
19
19
|
## Steam + Proton
|
|
20
20
|
|
|
@@ -29,7 +29,35 @@
|
|
|
29
29
|
-krqlv=hd
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
也可以使用 Lutris
|
|
32
|
+
也可以使用 Lutris,或直接调用 DWProton 自带 Wine(见下节)。
|
|
33
|
+
|
|
34
|
+
## 一键安装(DWProton + 独立 prefix)
|
|
35
|
+
|
|
36
|
+
不想经过 Steam 或 Lutris 时,用仓库里的一键部署环境脚本(实验性,仅在 Archlinux + niri 图形环境下测试可用):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
// 不带参数时默认读 ww-cli 的安装目录
|
|
40
|
+
scripts/setup-linux.sh --game-dir ~/Games/WutheringWaves
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
脚本依次完成:下载并校验 DWProton、准备 riched20 UTF-8 补丁组件(修复登录协议正文乱码)、建立独立 prefix(DXVK、vkd3d-proton、dxvk-nvapi、NVIDIA 的 NGX 运行时、ICU、字体、DLL 覆盖),初始化后逐项自检并补装缺项,最后把启动器装到 `~/.local/bin/run_ww`,并写入桌面启动器入口 `ww.desktop`(图标从游戏自带启动器里提取)。
|
|
44
|
+
|
|
45
|
+
常用开关:
|
|
46
|
+
|
|
47
|
+
| 开关 | 作用 |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `-y` | 不询问,缺失的系统包直接装 |
|
|
50
|
+
| `--no-install` | 不装任何系统包,只提示 |
|
|
51
|
+
| `--check` | 只检查路径与档位,不启动游戏 |
|
|
52
|
+
| `--rebuild` | 忽略仓库内的预编译组件,从源码重编 riched20 |
|
|
53
|
+
|
|
54
|
+
安装时检查 gamemode(启动时默认启用,实测对帧率有提升)与 PE 交叉工具链(仅重编组件时需要,mingw-w64 或 clang+lld 任一即可),缺失则询问是否安装;选择跳过则不打补丁,登录协议等文本可能乱码,但游戏仍可正常启动。MangoHud 属可选项,默认不装。脚本支持断点续传,并会复用其它安装根里已下载的归档;下载长期失败时可设 `https_proxy` 重试,或用 `WW_WINE_SOURCE_URL`、`WW_DWPROTON_URL` 换镜像源。
|
|
55
|
+
|
|
56
|
+
已存在的 DWProton(`$DWPROTON`、`~/.local/share/dwproton/`、Lutris/Steam 目录等)和已有 prefix 会被直接复用,不重复下载也不移动。
|
|
57
|
+
|
|
58
|
+
安装后用 `run_ww` 启动(或点桌面图标),默认经 gamemoderun 启动(`WW_GAMEMODE=0` 关闭)。脚本的画质档位自动读取游戏目录的 `wwm_resource_state.json`,`run_ww --check` 只检查路径和档位。日志在 `~/.local/share/ww-linux/logs/game.log`。
|
|
59
|
+
|
|
60
|
+
已知问题:`bilibili` 服在登录的 webview 框会固定弹出**网络请求失败,请重试**,但是不影响验证码登录,忽略即可。
|
|
33
61
|
|
|
34
62
|
### 安装额外的 Proton 版本
|
|
35
63
|
- [dwproton](https://dawn.wine/dawn-winery/dwproton/releases)
|
|
@@ -24,7 +24,7 @@ from ww_manager.download import format_bytes, space_shortage
|
|
|
24
24
|
|
|
25
25
|
GIB = 1024**3
|
|
26
26
|
SIZES = {"sd": 12 * GIB, "hd": 24 * GIB, "uhd": 48 * GIB}
|
|
27
|
-
NAMES = {"sd": "
|
|
27
|
+
NAMES = {"sd": "流畅", "hd": "高清", "uhd": "极致"}
|
|
28
28
|
VERSION = "3.7.1"
|
|
29
29
|
|
|
30
30
|
|
|
@@ -14,7 +14,7 @@ from ww_manager.menu import MenuOption, build_selection_menu
|
|
|
14
14
|
GIB = 1024**3
|
|
15
15
|
# 所有体积均为展示交互而虚构,不代表真实客户端下载大小。
|
|
16
16
|
DEMO_CHOICES = (
|
|
17
|
-
("sd", "
|
|
17
|
+
("sd", "流畅", 12 * GIB, 12 * GIB),
|
|
18
18
|
("hd", "高清", 24 * GIB, 0),
|
|
19
19
|
("uhd", "极致", 48 * GIB, 48 * GIB),
|
|
20
20
|
)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""从 Windows PE 可执行文件提取最大尺寸的图标。
|
|
3
|
+
|
|
4
|
+
用法:extract-icon.py <exe> <输出目录> [尺寸...]
|
|
5
|
+
|
|
6
|
+
把图标写成 <输出目录>/<尺寸>/apps/ww.png。没有 ImageMagick 时只写 256x256。
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import pathlib
|
|
11
|
+
import shutil
|
|
12
|
+
import struct
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def u16(buf, off):
|
|
18
|
+
return struct.unpack_from("<H", buf, off)[0]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def u32(buf, off):
|
|
22
|
+
return struct.unpack_from("<I", buf, off)[0]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def read_pe_resources(path):
|
|
26
|
+
data = pathlib.Path(path).read_bytes()
|
|
27
|
+
pe = u32(data, 0x3C)
|
|
28
|
+
if data[pe : pe + 4] != b"PE\0\0":
|
|
29
|
+
raise SystemExit("不是 PE 文件")
|
|
30
|
+
coff = pe + 4
|
|
31
|
+
opt = coff + 20
|
|
32
|
+
ndd = opt + (96 if u16(data, opt) == 0x10B else 112)
|
|
33
|
+
rsrc_rva = u32(data, ndd + 16)
|
|
34
|
+
|
|
35
|
+
sections = []
|
|
36
|
+
for i in range(u16(data, coff + 2)):
|
|
37
|
+
base = opt + u16(data, coff + 16) + i * 40
|
|
38
|
+
vaddr = u32(data, base + 12)
|
|
39
|
+
vsize = max(u32(data, base + 8), u32(data, base + 16))
|
|
40
|
+
sections.append((vaddr, vsize, u32(data, base + 20)))
|
|
41
|
+
|
|
42
|
+
def rva2off(rva):
|
|
43
|
+
for vaddr, vsize, raw in sections:
|
|
44
|
+
if vaddr <= rva < vaddr + vsize:
|
|
45
|
+
return raw + rva - vaddr
|
|
46
|
+
raise SystemExit("资源段地址映射失败")
|
|
47
|
+
|
|
48
|
+
def entries(dir_off):
|
|
49
|
+
named, ids = u16(data, dir_off + 12), u16(data, dir_off + 14)
|
|
50
|
+
out = []
|
|
51
|
+
for i in range(named + ids):
|
|
52
|
+
e = dir_off + 16 + i * 8
|
|
53
|
+
out.append((u32(data, e), u32(data, e + 4)))
|
|
54
|
+
return out
|
|
55
|
+
|
|
56
|
+
rsrc = rva2off(rsrc_rva)
|
|
57
|
+
|
|
58
|
+
def leaf(off):
|
|
59
|
+
"""目录下第一个语言条目的数据位置与长度。"""
|
|
60
|
+
_name, target = entries(off)[0]
|
|
61
|
+
pos = rsrc + (target & 0x7FFFFFFF)
|
|
62
|
+
return rva2off(u32(data, pos)), u32(data, pos + 4)
|
|
63
|
+
|
|
64
|
+
return data, rsrc, entries, leaf
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def largest_icon(path):
|
|
68
|
+
data, rsrc, entries, leaf = read_pe_resources(path)
|
|
69
|
+
types = {name: i for i, (name, _t) in enumerate(entries(rsrc))}
|
|
70
|
+
if 3 not in types or 14 not in types:
|
|
71
|
+
raise SystemExit("文件里没有图标资源")
|
|
72
|
+
|
|
73
|
+
images = {}
|
|
74
|
+
_name, target = entries(rsrc)[types[3]]
|
|
75
|
+
for name, sub in entries(rsrc + (target & 0x7FFFFFFF)):
|
|
76
|
+
off, size = leaf(rsrc + (sub & 0x7FFFFFFF))
|
|
77
|
+
images[name] = data[off : off + size]
|
|
78
|
+
|
|
79
|
+
best, best_width = None, -1
|
|
80
|
+
_name, target = entries(rsrc)[types[14]]
|
|
81
|
+
for _gname, sub in entries(rsrc + (target & 0x7FFFFFFF)):
|
|
82
|
+
off, size = leaf(rsrc + (sub & 0x7FFFFFFF))
|
|
83
|
+
group = data[off : off + size]
|
|
84
|
+
count = u16(group, 4)
|
|
85
|
+
items = [struct.unpack_from("<BBBBHHIH", group, 6 + i * 14) for i in range(count)]
|
|
86
|
+
width = max(int(item[0]) or 256 for item in items)
|
|
87
|
+
if width > best_width:
|
|
88
|
+
best, best_width = items, width
|
|
89
|
+
|
|
90
|
+
# 256x256 条目在 ICO 内以 PNG 存储,可直接作为图片使用。
|
|
91
|
+
if not best:
|
|
92
|
+
raise SystemExit("找不到图标条目")
|
|
93
|
+
for item in best:
|
|
94
|
+
width, _height, _colors, _res, _planes, _bpp, _size, image_id = item
|
|
95
|
+
if (int(width) or 256) >= 256 and image_id in images:
|
|
96
|
+
return images[image_id]
|
|
97
|
+
raise SystemExit("找不到 256x256 图标")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def write_ico(png, path):
|
|
101
|
+
header = struct.pack("<HHH", 0, 1, 1)
|
|
102
|
+
entry = struct.pack("<BBBBHHII", 0, 0, 0, 0, 1, 32, len(png), 6 + 16)
|
|
103
|
+
path.write_bytes(header + entry + png)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def write_png(data, dest):
|
|
107
|
+
tmp = dest.with_name(f".{dest.name}.tmp")
|
|
108
|
+
tmp.write_bytes(data)
|
|
109
|
+
os.replace(tmp, dest)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def main():
|
|
113
|
+
exe, out_dir, *sizes = sys.argv[1:]
|
|
114
|
+
sizes = sizes or ["256x256"]
|
|
115
|
+
png = largest_icon(exe)
|
|
116
|
+
have_magick = shutil.which("magick") is not None
|
|
117
|
+
|
|
118
|
+
icon_file = pathlib.Path("/tmp") / f"ww-icon-{os.getpid()}.ico"
|
|
119
|
+
write_ico(png, icon_file)
|
|
120
|
+
try:
|
|
121
|
+
for size in sizes:
|
|
122
|
+
if size != "256x256" and not have_magick:
|
|
123
|
+
continue
|
|
124
|
+
dest = pathlib.Path(out_dir) / size / "apps" / "ww.png"
|
|
125
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
126
|
+
if have_magick:
|
|
127
|
+
tmp = dest.with_name(f".{dest.name}.tmp.png")
|
|
128
|
+
try:
|
|
129
|
+
subprocess.run(
|
|
130
|
+
["magick", str(icon_file), "-resize", size, f"PNG:{tmp}"],
|
|
131
|
+
check=True,
|
|
132
|
+
capture_output=True,
|
|
133
|
+
)
|
|
134
|
+
os.replace(tmp, dest)
|
|
135
|
+
except (subprocess.CalledProcessError, OSError) as exc:
|
|
136
|
+
tmp.unlink(missing_ok=True)
|
|
137
|
+
print(f"警告:ImageMagick 缩放 {size} 失败({exc}),改用原图。", file=sys.stderr)
|
|
138
|
+
write_png(png, dest)
|
|
139
|
+
else:
|
|
140
|
+
write_png(png, dest)
|
|
141
|
+
finally:
|
|
142
|
+
icon_file.unlink(missing_ok=True)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
if __name__ == "__main__":
|
|
146
|
+
main()
|