mirror of
https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT.git
synced 2025-12-16 03:40:27 +00:00
docs(FAQ): 更新常见问题文档并优化首页布局
- 更新提交错误报告的说明 - 优化 FAQ 文档结构 - 删除旧的主窗口代码文件 - 清理不必要的 IP 优化代码
This commit is contained in:
236
FAQ-en.md
Normal file
236
FAQ-en.md
Normal file
@@ -0,0 +1,236 @@
|
||||
<div align="center" style="margin-top: 20px; margin-bottom: 20px;">
|
||||
<img src="./introduction_imgs/main.png" alt="FRAISEMOE Logo" />
|
||||
<h2 style="margin: 10px 0 5px 0; font-weight: bold; color: #e75480;">🍓 FRAISEMOE NEKOPARA Addons Installer NEXT🍓</h2>
|
||||
<p style="font-size: 1.1em; color: #555;">An application for installing patches for the Nekopara series games.</p>
|
||||
|
||||
<p>
|
||||
<a href="./FAQ.md">简体中文</a> |
|
||||
<a href="./FAQ-en.md">English</a>
|
||||
</p>
|
||||
<blockquote style="color: #c00; font-weight: bold; border-left: 4px solid #e75480; background: #fff0f5; padding: 10px;">
|
||||
The English version is not updated in real-time! Please check the Simplified Chinese version for more updates! Thank you for your support!
|
||||
</blockquote>
|
||||
<blockquote style="color: #c00; font-weight: bold; border-left: 4px solid #e75480; background: #fff0f5; padding: 10px;">
|
||||
Please strictly follow all the rules in the <a href="https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT/blob/master/FAQ.md">User Guide</a>. The developers are not responsible for any violations.<br>
|
||||
This tool is for educational and communication purposes only. Do not use it for commercial purposes.
|
||||
</blockquote>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## 🎮 Applicable Games:
|
||||
|
||||
- **NEKOPARA Vol. 1**
|
||||
- **NEKOPARA Vol. 2**
|
||||
- **NEKOPARA Vol. 3**
|
||||
- **NEKOPARA Vol. 4**
|
||||
- **NEKOPARA After**
|
||||
|
||||
---
|
||||
|
||||
⛔ **Special Reminder:**
|
||||
|
||||
> **1. Patches cannot be installed for NEKOPARA Vol. 0 & NEKOPARA Extra ❗**
|
||||
|
||||
> **2. Patches will not be installed for games you do not own ❗**
|
||||
|
||||
> **3. This tool is only for installing patches, not for installing games ❗ It only runs on Windows x64 systems ❗**
|
||||
|
||||
> **4. The tool requires administrator privileges to run ❗
|
||||
> Reason: To prevent installation issues caused by the game running, the tool will get game process information to close the game before starting ❗**
|
||||
|
||||
> **5. Before using this tool, you need to understand the basics of patch installation:**
|
||||
>
|
||||
> **5-1. Why install the patch? What's in the patch?**
|
||||
>
|
||||
> **5-2. Based on the documentation, why do errors occur? Or why does the installation fail?**
|
||||
>
|
||||
> **5-3. After a successful installation, how to check the patch settings to confirm it was installed correctly?**
|
||||
>
|
||||
> ***If you are completely unfamiliar with the above, please do not use this tool or watch the tutorial video. If you have already downloaded the tool, it is recommended to move it to the Recycle Bin and delete it.***
|
||||
|
||||
> **6. Make sure you are using the latest version of the application (please regularly check for updates on the mirror site or GitHub and download them) ❗**
|
||||
|
||||
---
|
||||
|
||||
## **🔄 Usage/Flow:**
|
||||
|
||||
1. Download "FRAISEMOE Addons Installer NEXT.exe" from the repository.
|
||||
2. **Close any running games from the ["Applicable Games"](#-applicable-games) list.**
|
||||
3. If the application asks for administrator privileges, grant them. **If administrator privileges cannot be obtained, the application will not run and will exit automatically.**
|
||||
4. If the application asks to close a running game, select "Yes". **If the running game cannot be closed, the application will not run and will exit automatically.**
|
||||
5. After launching the application, select "Start Install" and choose the **parent directory of the game directory.**<br />
|
||||
|
||||
> **Important Note 1** ❓ What is the "parent directory of the game directory"? How to get it?
|
||||
> Taking Steam as an example, find the "Library" tab at the top, then find a [patchable game](#-applicable-games) in the game list on the left;<br />Right-click the game in the list, select "Manage" -> "Browse local files" to get the game directory. Then, click the "←" button in the address bar. If you see the [game's folder, e.g., "NEKOPARA Vol. 1"](#-applicable-games) in the file explorer, then this is the parent directory of the game directory. Select and copy the full path from the address bar.
|
||||
|
||||
> **Important Note 2** ❓ How to use this tool for games from third-party installers?
|
||||
> Since the installation path for third-party games is not fixed, please copy the parent directory path of the game directory yourself, similar to the method for Steam.
|
||||
|
||||
> Example (for illustration only, do not copy directly):
|
||||
> Game folder: C: (drive letter may vary)\Steam\steamapps\common\NEKOPARA Vol. 1
|
||||
> Parent directory of game directory: C: (drive letter may vary)\Steam\steamapps\common
|
||||
|
||||
6. In the folder selection dialog, **paste the copied path into the address bar** and click "Select Folder".<br />**(Note whether the text next to the "Select Folder" button is the last folder name in the path. If not, you may need to re-select).**
|
||||
|
||||
> √ Correct Example (for illustration only, do not copy directly):
|
||||
> Path entered in the address bar above: C: (drive letter may vary)\Steam\steamapps\common
|
||||
> Folder name below: common
|
||||
|
||||
7. After selecting the folder, you may encounter the following situations:
|
||||
<table>
|
||||
<tr>
|
||||
<td><h5>Status</h5></td>
|
||||
<td><h5>Action</h5></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Game exists, but patch is not installed</td>
|
||||
<td>Proceeds directly to download task</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Game exists,<br />but patch is installed from another source or patch file is corrupted</td>
|
||||
<td>Asks whether to reinstall the patch from this tool. If the patch from another source is usable, you can choose not to reinstall</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Game does not exist</td>
|
||||
<td>Skips the patch installation step</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Game exists,<br />but the corresponding patch version cannot be installed with this tool</td>
|
||||
<td>Repeat the previous installation steps</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
8. Confirm the final installation result, then select "Exit".
|
||||
9. Enter the game and check if there are more options in "Settings". Or if you have previously entered the extra story and it appears in the "EXTRA" option, it means the patch was installed successfully. If none of the above is observed, repeat the installation steps.
|
||||
|
||||
---
|
||||
|
||||
## 🔰 Software Features:
|
||||
|
||||
- Detects installed patch files for ["Applicable Games"](#-applicable-games) and compares their [Hash (SHA-256)](#-hashsha-256-checksums) to verify integrity. If normal, it skips installation for that version;<br />If patches from other sources are used or patch files are corrupted, it asks whether to reinstall. If you choose to reinstall, it will automatically delete old patch files, download the patch package, and reinstall it.
|
||||
- Detects all unpatched versions and performs installation tasks.
|
||||
|
||||
---
|
||||
|
||||
## ❓ FAQ & User Guide
|
||||
|
||||
---
|
||||
|
||||
<h4><u>【Important】Why did the download fail?</u></h4>
|
||||
|
||||
1. Please check if the "final folder" in the address bar of the "Folder Selector" matches the "Folder Name" below (above the "Select Folder" button). If they do not match, it will not work correctly. If this is not the issue, proceed to the next step.
|
||||
2. Please check if the selected folder contains the [game folder](#-usageflow) (refer to step 5 in the usage flow). If the game folder does not exist, it will not work correctly. If this is not the issue, proceed to the next step.
|
||||
3. Please check if your network environment is normal and the connection is stable. If this is not the issue, proceed to the next step.
|
||||
4. If the installation result is displayed directly (i.e., the installation step was skipped), it means the path is incorrect and the game could not be identified. Please check the path and try again. <b>If you are using a non-Steam version, please find the relevant resources to install it yourself.</b>
|
||||
5. Please go to [GitHub](https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT) or the [domestic mirror site blog](https://blog.ovofish.com/posts/c54d3755.html) to <b>check if you are using the latest version. The application will not work correctly if it is not the latest version.</b>
|
||||
|
||||
---
|
||||
|
||||
<h4><u>【Important】Encountered an error and need to report it? How to submit a bug report?</u></h4>
|
||||
|
||||
1. First, please rule out if it is a local network problem.
|
||||
2. Second, please enable debug mode, run the program again, and <b>save a screenshot of the error along with the log.txt file from the same directory.</b>
|
||||
3. Finally, <b>please [submit an Issue on GitHub](https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT/issues).</b>
|
||||

|
||||
|
||||
---
|
||||
|
||||
<h4><u>During Use</u></h4>
|
||||
|
||||
- The application is open but loading slowly.
|
||||
- Please wait patiently for a while; the program is not unresponsive.
|
||||
- Do not open the application multiple times during loading to avoid unnecessary issues.
|
||||
|
||||
<h4><u>"Application is already running" / "In use" prompt when opening</u></h4>
|
||||
|
||||
- This is caused by opening the application too frequently, causing Task Manager to fail to refresh. Please manually open Task Manager, find "FRAISEMOE Addons Installer NEXT", end its process, and then restart.
|
||||
|
||||
<h4><u>1. Download Errors</u></h4>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><h5>Common Error Types</h5></td>
|
||||
<td><h5>Error Information</h5></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Contains "403" / "Access denied by server"</td>
|
||||
<td>Access denied by the server. Check if you are using a network proxy (VPN), reset the network proxy (or exit the VPN program), then "Restart the application" and try again.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Contains "port=443" / "An existing connection was forcibly closed by the remote host"</td>
|
||||
<td>Download interrupted. After other tasks have verified file integrity / download tasks are complete, use "Start Install" again and select the previously entered "parent directory of the game" to install.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Contains other messages</td>
|
||||
<td>1. Mostly user network status is abnormal. Check and fix your network status before trying again.<br />2. In some cases, it may be a server failure. Please report the problem via GitHub Issues.</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
<h4><u>2. Problems During Download and Installation</u></h4>
|
||||
<table>
|
||||
<tr>
|
||||
<td><h5>Common Problem Types</h5></td>
|
||||
<td><h5>Solution</h5></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Download progress is slow or seems to have stalled</td>
|
||||
<td>If the progress has stalled but no error is reported, wait a moment.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Window flickers when verifying file integrity</td>
|
||||
<td>This is normal, no action is needed.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>The download progress window pops up and is covered by the hash check window / the window close button turns red</td>
|
||||
<td>Some patch files are large, and calculating the hash value takes longer. Please wait a moment. If the wait is too long, you can manually click the main window/download progress pop-up/hash check window to refresh the status.</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h4><u>3. Cannot exit the program during download</u></h4>
|
||||
|
||||
- To ensure the effectiveness of the patch, do not exit the program during download and installation. The user is responsible for any negative consequences of violating this notice.
|
||||
|
||||
<h4><u>4. Forcibly terminating the program during download</u></h4>
|
||||
|
||||
- This may cause patch file corruption. Restarting the application will automatically overwrite downloaded files. The user is responsible for any negative consequences of violating this notice.
|
||||
|
||||
<h4><u>5. Network speed significantly decreases after multiple downloads and installations, despite a normal network status</u></h4>
|
||||
|
||||
- To ensure server stability and resource security, download sources are divided into domestic and international. To guarantee download quality for more users, domestic sources have a download limit. Tasks exceeding the limit are forwarded to international sources for download.
|
||||
|
||||
<h4><u>6. Found an identical/similar repository/application outside of this repository</u></h4>
|
||||
|
||||
- It may be modified by other developers or use patch files from unknown sources. Do not download/use such repositories/applications.
|
||||
|
||||
<h4><u>7. User obtained this application through non-free means before using it</u></h4>
|
||||
|
||||
- This application is free and open-source. If you obtained it through a paid channel, please request a refund immediately and take action to protect your rights.
|
||||
|
||||
---
|
||||
|
||||
## 💫 HASH(SHA-256) Checksums
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><h5>Game Patch</h5></td>
|
||||
<td><h5>SHA-256 (Hash creation date: 2024/07-2024-08)</h5></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Vol.1</td>
|
||||
<td>04b48b231a7f34431431e5027fcc7b27affaa951b8169c541709156acf754f3e</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Vol.2</td>
|
||||
<td>b9c00a2b113a1e768bf78400e4f9075ceb7b35349cdeca09be62eb014f0d4b42</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Vol.3</td>
|
||||
<td>2ce7b223c84592e1ebc3b72079dee1e5e8d064ade15723328a64dee58833b9d5</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Vol.4</td>
|
||||
<td>4a4a9ae5a75a18aacbe3ab0774d7f93f99c046afe3a777ee0363e8932b90f36a</td>
|
||||
</tr>
|
||||
</table>
|
||||
28
FAQ.md
28
FAQ.md
@@ -2,19 +2,10 @@
|
||||
<img src="./introduction_imgs/main.png" alt="FRAISEMOE Logo" />
|
||||
<h2 style="margin: 10px 0 5px 0; font-weight: bold; color: #e75480;">🍓 FRAISEMOE NEKOPARA Addons Installer NEXT🍓</h2>
|
||||
<p style="font-size: 1.1em; color: #555;">一个为 Nekopara 系列游戏安装补丁的应用。</p>
|
||||
|
||||
<p>
|
||||
<a href="https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT/" style="margin-right: 15px;">
|
||||
<img src="https://img.shields.io/github/stars/Yanam1Anna/FRAISEMOE-Addons-Installer?style=social" alt="GitHub stars" />
|
||||
GitHub
|
||||
</a>
|
||||
<a href="https://www.bilibili.com/video/BV1hn9UYwE6p/" style="margin-right: 15px;">
|
||||
<img src="https://img.shields.io/badge/Bilibili-视频讲解-00A1D6?logo=bilibili&logoColor=white" alt="Bilibili" />
|
||||
Bilibili
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT/blob/master/FAQ.md">中文</a> |
|
||||
<a href="https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT/blob/master/FAQ-en.md">English</a>
|
||||
<a href="./FAQ.md">中文</a> |
|
||||
<a href="./FAQ-en.md">English</a>
|
||||
</p>
|
||||
<blockquote style="color: #c00; font-weight: bold; border-left: 4px solid #e75480; background: #fff0f5; padding: 10px;">
|
||||
请严格遵守 <a href="https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT/blob/master/FAQ.md">使用须知文档</a> 的所有条例,如有违反,全体开发人员不承担任何责任。<br>
|
||||
@@ -133,13 +124,12 @@
|
||||
|
||||
---
|
||||
|
||||
<h4><u>【重要】为什么开发者无视我的问题?如何提交错误报告?</u></h4>
|
||||
<h4><u>【重要】遇到错误需要反馈?如何提交错误报告?</u></h4>
|
||||
|
||||
1. 首先,每个人都会有没空的时候,请耐心等待回复或问题处理。
|
||||
2. 其次,文档和视频中已详细介绍了使用方法和常见问题解决方式,请检查你遇到的问题,或相似类别的问题是否存在于文档中,如果存在,一般不回复处理。
|
||||
3. 最后,如果遇到了未提及的问题,<b>请勿在视频站内或博客站内以评论,私信等方式报告你的问题,请到[GitHub中提交Issues](https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT/issues)。</b>
|
||||
4. 提交问题报告时,<b>请附上下载报错窗口的报错信息,而不是安装最终结果显示,</b>安装结果显示是给用户看的,不是给开发者看的。
|
||||

|
||||
1. 首先,请排除是否计算机本机网络问题
|
||||
2. 其次,请打开debug模式,再次运行程序,<b>将报错截图与同目录下的log.txt文件一并保存。</b>
|
||||
3. 最后,<b>请到[GitHub中提交Issues](https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT/issues)。</b>
|
||||

|
||||
|
||||
---
|
||||
|
||||
@@ -151,7 +141,7 @@
|
||||
|
||||
<h4><u>打开应用时提示应用正在运行 / 被占用的情况</u></h4>
|
||||
|
||||
- 由于开启应用动作过于频繁,造成任务管理器刷新失败,请手动进入任务管理器中找到"FRAISEMOE-Addons-Installer",结束其程序进程后再次重启。
|
||||
- 由于开启应用动作过于频繁,造成任务管理器刷新失败,请手动进入任务管理器中找到"FRAISEMOE Addons Installer NEXT",结束其程序进程后再次重启。
|
||||
|
||||
<h4><u>1. 下载报错</u></h4>
|
||||
|
||||
|
||||
93
README-en.md
Normal file
93
README-en.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# 🍓FRAISEMOE-Addons-Installer-NEXT🍓
|
||||
|
||||
```
|
||||
🔊 Note: This repository is still under active development, and most of the documentation is not yet available. We appreciate your understanding.
|
||||
The English version is not updated in real-time! Please check the Simplified Chinese version for more updates! Thank you for your support!
|
||||
```
|
||||
|
||||
<!-- PROJECT SHIELDS -->
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT">
|
||||
<img src="./introduction_imgs/main.png" alt="Logo">
|
||||
</a>
|
||||
<br />
|
||||
<br />
|
||||
If you find this tool helpful, please give it a Star⭐~
|
||||
<br />
|
||||
<br />
|
||||
⚠️ This is an unofficial tool and does not represent any official stance. ⚠️
|
||||
<br />
|
||||
<br />
|
||||
⚠️ This NEXT version is currently under active development, and stability is not guaranteed. ⚠️
|
||||
<br />
|
||||
<br />
|
||||
<a href="https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT/issues">Report a Bug</a>
|
||||
·
|
||||
<a href="https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT/issues">Request a Feature</a>
|
||||
·
|
||||
<a href="https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT/blob/master/FAQ.md">【Must Read Before Use】User Guide</a>
|
||||
<br />
|
||||
</p>
|
||||
|
||||
<!-- LANGUAGE -->
|
||||
<p align="center">
|
||||
<a href="README.md">简体中文</a> |
|
||||
<a href="README-en.md">English</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## 📕 Table of Contents
|
||||
|
||||
- [Getting Started](#getting-started)
|
||||
- [Installation](#installation)
|
||||
- [Usage Steps](#usage-steps)
|
||||
- [Versioning](#versioning)
|
||||
- [Authors](#authors)
|
||||
- [Important Notes](#important-notes)
|
||||
- [Special Thanks](#special-thanks)
|
||||
- [License](#license)
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
### 📥 Installation
|
||||
|
||||
Please download the latest version of the application from the [Releases Page](https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT/releases).
|
||||
|
||||

|
||||
|
||||
### ❗ Usage Steps
|
||||
|
||||
1. **Important**: Please be sure to read the [User Guide](https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT/blob/master/FAQ.md) before use.
|
||||
2. **Detailed Tutorial**: Refer to the [Video Tutorial](https://www.bilibili.com/video/BV1hn9UYwE6p/).
|
||||
|
||||
### ⭕ Versioning
|
||||
|
||||
This project uses Git for version control. You can view the currently available versions on the [Releases Page](https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT/releases).
|
||||
|
||||
---
|
||||
|
||||
## 💡 Important Notes
|
||||
|
||||
1. **Do not use modified applications**: The authors and developers are not responsible for any personal loss resulting from the use of applications from unknown or modified sources.
|
||||
2. **Follow all rules**: Please strictly adhere to the rules in the [User Guide](https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT/blob/master/FAQ.md) and this document. The authors and developers are not liable for any violations.
|
||||
3. **Free and Open Source**: This application is free and open-source. If you obtained it through a paid channel, please request a refund immediately and take action to protect your rights.
|
||||
|
||||
---
|
||||
|
||||
## 👨💻 Authors
|
||||
|
||||
- [ouyangqiqi](https://github.com/hyb-oyqq): Current maintainer of this repository.
|
||||
|
||||
## 🎉 Special Thanks
|
||||
- [Yanam1Anna](https://github.com/Yanam1Anna): The original author of this project, who provided extensive code and resources.
|
||||
- [HTony03](https://github.com/HTony03): Provided support for refactoring, logic optimization, and feature implementation for parts of the original source code.
|
||||
- [钨鸮](https://github.com/ABSIDIA): Provided support for cloud resource storage.
|
||||
- [XIU2/CloudflareSpeedTest](https://github.com/XIU2/CloudflareSpeedTest): Provided core support for the IP optimization feature of this project.
|
||||
|
||||
## 📖 License
|
||||
|
||||
This application is licensed under the [GPL-3.0](https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT/blob/master/LICENSE) license. Please see the [LICENSE](https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT/blob/master/LICENSE) file for more information.
|
||||
@@ -32,7 +32,7 @@
|
||||
<!-- LANGUAGE -->
|
||||
<p align="center">
|
||||
<a href="https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT">中文</a> |
|
||||
<a href="#">English</a>
|
||||
<a href="README-en.md">English</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
IP 地址,已发送,已接收,丢包率,平均延迟,下载速度(MB/s),地区码
|
||||
104.21.237.213,4,4,0.00,173.18,86.14,LAX
|
||||
141.101.120.105,4,4,0.00,173.02,40.72,SJC
|
||||
162.159.251.244,4,4,0.00,171.02,8.59,LAX
|
||||
104.18.19.226,4,4,0.00,172.26,5.27,LAX
|
||||
104.19.32.102,4,4,0.00,173.01,4.52,SJC
|
||||
141.101.121.113,4,4,0.00,169.51,3.69,SJC
|
||||
162.159.207.149,4,4,0.00,169.94,3.24,SJC
|
||||
104.19.21.114,4,4,0.00,171.00,0.38,SJC
|
||||
104.19.127.210,4,4,0.00,170.53,0.22,SJC
|
||||
104.19.60.94,4,4,0.00,168.33,0.00,N/A
|
||||
|
@@ -1,656 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import webbrowser
|
||||
import requests
|
||||
import json
|
||||
from urllib.parse import urlparse
|
||||
from collections import deque
|
||||
|
||||
from PySide6 import QtWidgets
|
||||
from PySide6.QtCore import QTimer, Qt
|
||||
from PySide6.QtGui import QIcon, QAction
|
||||
from PySide6.QtWidgets import QMainWindow, QFileDialog, QApplication, QMessageBox
|
||||
|
||||
from ui.Ui_install import Ui_MainWindows
|
||||
from core.animations import MultiStageAnimations
|
||||
from data.config import (
|
||||
APP_NAME, APP_VERSION, PLUGIN, GAME_INFO, BLOCK_SIZE,
|
||||
PLUGIN_HASH, UA, CONFIG_URL, LOG_FILE
|
||||
)
|
||||
from utils import (
|
||||
load_base64_image, HashManager, AdminPrivileges, msgbox_frame,
|
||||
load_config, save_config, HostsManager, Logger
|
||||
)
|
||||
from workers.download import DownloadThread, ProgressWindow
|
||||
from data.pic_data import img_data
|
||||
from workers import (
|
||||
IpOptimizerThread, HashThread, ExtractionThread, ConfigFetchThread
|
||||
)
|
||||
from core import (
|
||||
MultiStageAnimations, UIManager, DownloadManager, DebugManager
|
||||
)
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
# 初始化UI (从Ui_install.py导入)
|
||||
self.ui = Ui_MainWindows()
|
||||
self.ui.setupUi(self)
|
||||
|
||||
icon_data = img_data.get("icon")
|
||||
if icon_data:
|
||||
pixmap = load_base64_image(icon_data)
|
||||
self.setWindowIcon(QIcon(pixmap))
|
||||
|
||||
# 设置窗口标题为APP_NAME加版本号
|
||||
self.setWindowTitle(f"{APP_NAME} v{APP_VERSION}")
|
||||
|
||||
# 初始化动画系统 (从animations.py导入)
|
||||
self.animator = MultiStageAnimations(self.ui, self)
|
||||
|
||||
# 初始化功能变量
|
||||
self.selected_folder = ""
|
||||
self.installed_status = {f"NEKOPARA Vol.{i}": False for i in range(1, 5)}
|
||||
self.installed_status["NEKOPARA After"] = False # 添加After的状态
|
||||
self.download_queue = deque()
|
||||
self.current_download_thread = None
|
||||
self.hash_manager = HashManager(BLOCK_SIZE)
|
||||
self.hash_thread = None
|
||||
self.extraction_thread = None
|
||||
self.hash_msg_box = None
|
||||
self.optimized_ip = None
|
||||
self.optimization_done = False # 标记是否已执行过优选
|
||||
self.logger = None
|
||||
self.hosts_manager = HostsManager() # 实例化HostsManager
|
||||
self.cloud_config = None
|
||||
self.config_fetch_thread = None
|
||||
|
||||
# 加载配置
|
||||
self.config = load_config()
|
||||
|
||||
# 检查管理员权限和进程
|
||||
self.admin_privileges = AdminPrivileges()
|
||||
self.admin_privileges.request_admin_privileges()
|
||||
self.admin_privileges.check_and_terminate_processes()
|
||||
|
||||
# 备份hosts文件
|
||||
self.hosts_manager.backup()
|
||||
|
||||
# 创建缓存目录
|
||||
if not os.path.exists(PLUGIN):
|
||||
try:
|
||||
os.makedirs(PLUGIN)
|
||||
except OSError as e:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self,
|
||||
f"错误 - {APP_NAME}",
|
||||
f"\n无法创建缓存位置\n\n使用管理员身份运行或检查文件读写权限\n\n【错误信息】:{e}\n",
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# 连接信号 (使用Ui_install.py中的组件名称)
|
||||
self.ui.start_install_btn.clicked.connect(self.download_manager.file_dialog)
|
||||
self.ui.exit_btn.clicked.connect(self.shutdown_app)
|
||||
|
||||
# “帮助”菜单
|
||||
project_home_action = QAction("项目主页", self)
|
||||
project_home_action.triggered.connect(self.open_project_home_page)
|
||||
about_action = QAction("关于", self)
|
||||
about_action.triggered.connect(self.show_about_dialog)
|
||||
self.ui.menu_2.addAction(project_home_action)
|
||||
self.ui.menu_2.addAction(about_action)
|
||||
|
||||
# “设置”菜单
|
||||
self.debug_action = QAction("Debug模式", self, checkable=True)
|
||||
self.debug_action.setChecked(self.config.get("debug_mode", False))
|
||||
self.debug_action.triggered.connect(self.toggle_debug_mode)
|
||||
self.ui.menu.addAction(self.debug_action)
|
||||
|
||||
# 为未来功能预留的“切换下载源”按钮
|
||||
self.switch_source_action = QAction("切换下载源", self)
|
||||
self.switch_source_action.setEnabled(False) # 暂时禁用
|
||||
self.ui.menu.addAction(self.switch_source_action)
|
||||
|
||||
# 根据初始配置决定是否开启Debug模式
|
||||
if self.debug_action.isChecked():
|
||||
self.start_logging()
|
||||
|
||||
# 在窗口显示前设置初始状态
|
||||
self.animator.initialize()
|
||||
|
||||
# 窗口显示后延迟100ms启动动画
|
||||
QTimer.singleShot(100, self.start_animations)
|
||||
|
||||
def start_animations(self):
|
||||
self.ui.exit_btn.setEnabled(False)
|
||||
self.animator.animation_finished.connect(self.on_animations_finished)
|
||||
self.animator.start_animations()
|
||||
self.fetch_cloud_config()
|
||||
|
||||
def on_animations_finished(self):
|
||||
self.ui.exit_btn.setEnabled(True)
|
||||
|
||||
def fetch_cloud_config(self):
|
||||
headers = {"User-Agent": UA}
|
||||
debug_mode = self.debug_action.isChecked()
|
||||
self.config_fetch_thread = ConfigFetchThread(CONFIG_URL, headers, debug_mode, self)
|
||||
self.config_fetch_thread.finished.connect(self.on_config_fetched)
|
||||
self.config_fetch_thread.start()
|
||||
|
||||
def on_config_fetched(self, data, error_message):
|
||||
if error_message:
|
||||
if error_message == "update_required":
|
||||
msg_box = msgbox_frame(
|
||||
f"更新提示 - {APP_NAME}",
|
||||
"\n当前版本过低,请及时更新。\n",
|
||||
QtWidgets.QMessageBox.StandardButton.Ok,
|
||||
)
|
||||
msg_box.exec()
|
||||
self.open_project_home_page()
|
||||
self.shutdown_app(force_exit=True)
|
||||
elif "missing_keys" in error_message:
|
||||
missing_versions = error_message.split(":")[1]
|
||||
msg_box = msgbox_frame(
|
||||
f"配置缺失 - {APP_NAME}",
|
||||
f'\n云端缺失下载链接,可能云服务器正在维护,不影响其他版本下载。\n当前缺失版本:"{missing_versions}"\n',
|
||||
QtWidgets.QMessageBox.StandardButton.Ok,
|
||||
)
|
||||
msg_box.exec()
|
||||
else:
|
||||
# 其他错误暂时只在debug模式下打印
|
||||
if self.debug_action.isChecked():
|
||||
print(f"获取云端配置失败: {error_message}")
|
||||
else:
|
||||
self.cloud_config = data
|
||||
if self.debug_action.isChecked():
|
||||
print("--- Cloud config fetched successfully ---")
|
||||
print(json.dumps(data, indent=2))
|
||||
|
||||
def toggle_debug_mode(self, checked):
|
||||
self.config["debug_mode"] = checked
|
||||
save_config(self.config)
|
||||
if checked:
|
||||
self.start_logging()
|
||||
else:
|
||||
self.stop_logging()
|
||||
|
||||
def start_logging(self):
|
||||
if self.logger is None:
|
||||
try:
|
||||
if os.path.exists(LOG_FILE):
|
||||
os.remove(LOG_FILE)
|
||||
# 保存原始的 stdout 和 stderr
|
||||
self.original_stdout = sys.stdout
|
||||
self.original_stderr = sys.stderr
|
||||
# 创建 Logger 实例
|
||||
self.logger = Logger(LOG_FILE, self.original_stdout)
|
||||
sys.stdout = self.logger
|
||||
sys.stderr = self.logger
|
||||
print("--- Debug mode enabled ---")
|
||||
except (IOError, OSError) as e:
|
||||
QtWidgets.QMessageBox.critical(self, "错误", f"无法创建日志文件: {e}")
|
||||
self.logger = None
|
||||
|
||||
def stop_logging(self):
|
||||
if self.logger:
|
||||
print("--- Debug mode disabled ---")
|
||||
sys.stdout = self.original_stdout
|
||||
sys.stderr = self.original_stderr
|
||||
self.logger.close()
|
||||
self.logger = None
|
||||
|
||||
def get_install_paths(self):
|
||||
return {
|
||||
game: os.path.join(self.selected_folder, info["install_path"])
|
||||
for game, info in GAME_INFO.items()
|
||||
}
|
||||
|
||||
def file_dialog(self):
|
||||
self.selected_folder = QFileDialog.getExistingDirectory(
|
||||
self, f"选择游戏所在【上级目录】 {APP_NAME}"
|
||||
)
|
||||
if not self.selected_folder:
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self, f"通知 - {APP_NAME}", "\n未选择任何目录,请重新选择\n"
|
||||
)
|
||||
return
|
||||
self.download_action()
|
||||
|
||||
def get_download_url(self) -> dict:
|
||||
try:
|
||||
if self.cloud_config:
|
||||
if self.debug_action.isChecked():
|
||||
print("--- Using pre-fetched cloud config ---")
|
||||
config_data = self.cloud_config
|
||||
else:
|
||||
# 如果没有预加载的配置,则同步获取
|
||||
headers = {"User-Agent": UA}
|
||||
response = requests.get(CONFIG_URL, headers=headers, timeout=10)
|
||||
response.raise_for_status()
|
||||
config_data = response.json()
|
||||
|
||||
if not config_data:
|
||||
raise ValueError("未能获取或解析配置数据")
|
||||
|
||||
if self.debug_action.isChecked():
|
||||
print(f"DEBUG: Parsed JSON data: {json.dumps(config_data, indent=2)}")
|
||||
|
||||
# 统一处理URL提取,确保返回扁平化的字典
|
||||
urls = {}
|
||||
for i in range(4):
|
||||
key = f"vol.{i+1}.data"
|
||||
if key in config_data and "url" in config_data[key]:
|
||||
urls[f"vol{i+1}"] = config_data[key]["url"]
|
||||
|
||||
if "after.data" in config_data and "url" in config_data["after.data"]:
|
||||
urls["after"] = config_data["after.data"]["url"]
|
||||
|
||||
# 检查是否成功提取了所有URL
|
||||
if len(urls) != 5:
|
||||
missing_keys_map = {
|
||||
f"vol{i+1}": f"vol.{i+1}.data" for i in range(4)
|
||||
}
|
||||
missing_keys_map["after"] = "after.data"
|
||||
|
||||
extracted_keys = set(urls.keys())
|
||||
all_keys = set(missing_keys_map.keys())
|
||||
missing_simple_keys = all_keys - extracted_keys
|
||||
|
||||
missing_original_keys = [missing_keys_map[k] for k in missing_simple_keys]
|
||||
raise ValueError(f"配置文件缺少必要的键: {', '.join(missing_original_keys)}")
|
||||
|
||||
if self.debug_action.isChecked():
|
||||
print(f"DEBUG: Extracted URLs: {urls}")
|
||||
print("--- Finished getting download URL successfully ---")
|
||||
return urls
|
||||
if self.debug_action.isChecked():
|
||||
print(f"DEBUG: Extracted URLs: {urls}")
|
||||
print("--- Finished getting download URL successfully ---")
|
||||
return urls
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
status_code = e.response.status_code if e.response is not None else "未知"
|
||||
try:
|
||||
error_response = e.response.json() if e.response else {}
|
||||
json_title = error_response.get("title", "无错误类型")
|
||||
json_message = error_response.get("message", "无附加错误信息")
|
||||
except (ValueError, AttributeError):
|
||||
json_title = "配置文件异常,无法解析错误类型"
|
||||
json_message = "配置文件异常,无法解析错误信息"
|
||||
|
||||
if self.debug_action.isChecked():
|
||||
print(f"ERROR: Failed to get download config due to RequestException: {e}")
|
||||
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self,
|
||||
f"错误 - {APP_NAME}",
|
||||
f"\n下载配置获取失败\n\n【HTTP状态】:{status_code}\n【错误类型】:{json_title}\n【错误信息】:{json_message}\n",
|
||||
)
|
||||
return {}
|
||||
except ValueError as e:
|
||||
if self.debug_action.isChecked():
|
||||
print(f"ERROR: Failed to parse download config due to ValueError: {e}")
|
||||
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self,
|
||||
f"错误 - {APP_NAME}",
|
||||
f"\n配置文件格式异常\n\n【错误信息】:{e}\n",
|
||||
)
|
||||
return {}
|
||||
|
||||
def download_setting(self, url, game_folder, game_version, _7z_path, plugin_path):
|
||||
game_exe = {
|
||||
game: os.path.join(
|
||||
self.selected_folder, info["install_path"].split("/")[0], info["exe"]
|
||||
)
|
||||
for game, info in GAME_INFO.items()
|
||||
}
|
||||
|
||||
if (
|
||||
game_version not in game_exe
|
||||
or not os.path.exists(game_exe[game_version])
|
||||
or self.installed_status[game_version]
|
||||
):
|
||||
self.installed_status[game_version] = False
|
||||
self.show_result()
|
||||
return
|
||||
|
||||
self.progress_window = ProgressWindow(self)
|
||||
self.start_download_with_ip(self.optimized_ip, url, _7z_path, game_version, game_folder, plugin_path)
|
||||
|
||||
|
||||
def on_optimization_and_hosts_finished(self, ip):
|
||||
self.optimized_ip = ip
|
||||
self.optimization_done = True
|
||||
if hasattr(self, 'optimizing_msg_box') and self.optimizing_msg_box:
|
||||
if self.optimizing_msg_box.isVisible():
|
||||
self.optimizing_msg_box.accept()
|
||||
self.optimizing_msg_box = None
|
||||
|
||||
if not ip:
|
||||
QtWidgets.QMessageBox.warning(self, f"优选失败 - {APP_NAME}", "\n未能找到合适的Cloudflare IP,将使用默认网络进行下载。\n")
|
||||
else:
|
||||
if self.download_queue:
|
||||
first_url = self.download_queue[0][0]
|
||||
hostname = urlparse(first_url).hostname
|
||||
if self.hosts_manager.apply_ip(hostname, ip):
|
||||
QtWidgets.QMessageBox.information(self, f"成功 - {APP_NAME}", f"\n已将优选IP ({ip}) 应用到hosts文件。\n")
|
||||
else:
|
||||
QtWidgets.QMessageBox.critical(self, f"错误 - {APP_NAME}", "\n修改hosts文件失败,请检查程序是否以管理员权限运行。\n")
|
||||
|
||||
self.next_download_task()
|
||||
|
||||
def start_download_with_ip(self, preferred_ip, url, _7z_path, game_version, game_folder, plugin_path):
|
||||
if preferred_ip:
|
||||
print(f"已为 {game_version} 获取到优选IP: {preferred_ip}")
|
||||
else:
|
||||
print(f"未能为 {game_version} 获取优选IP,将使用默认线路。")
|
||||
|
||||
self.current_download_thread = DownloadThread(url, _7z_path, game_version, self)
|
||||
self.current_download_thread.progress.connect(self.progress_window.update_progress)
|
||||
self.current_download_thread.finished.connect(
|
||||
lambda success, error: self.install_setting(
|
||||
success,
|
||||
error,
|
||||
self.progress_window,
|
||||
url,
|
||||
game_folder,
|
||||
game_version,
|
||||
_7z_path,
|
||||
plugin_path,
|
||||
)
|
||||
)
|
||||
|
||||
self.progress_window.stop_button.clicked.connect(self.current_download_thread.stop)
|
||||
self.current_download_thread.start()
|
||||
self.progress_window.exec()
|
||||
|
||||
def install_setting(
|
||||
self,
|
||||
success,
|
||||
error,
|
||||
progress_window,
|
||||
url,
|
||||
game_folder,
|
||||
game_version,
|
||||
_7z_path,
|
||||
plugin_path,
|
||||
):
|
||||
if progress_window.isVisible():
|
||||
progress_window.reject()
|
||||
|
||||
if not success:
|
||||
print(f"--- Download Failed: {game_version} ---")
|
||||
print(error)
|
||||
print("------------------------------------")
|
||||
msg_box = QtWidgets.QMessageBox(self)
|
||||
msg_box.setWindowTitle(f"下载失败 - {APP_NAME}")
|
||||
msg_box.setText(f"\n文件获取失败: {game_version}\n错误: {error}\n\n是否重试?")
|
||||
|
||||
retry_button = msg_box.addButton("重试", QtWidgets.QMessageBox.ButtonRole.YesRole)
|
||||
next_button = msg_box.addButton("下一个", QtWidgets.QMessageBox.ButtonRole.NoRole)
|
||||
end_button = msg_box.addButton("结束", QtWidgets.QMessageBox.ButtonRole.RejectRole)
|
||||
|
||||
msg_box.exec()
|
||||
clicked_button = msg_box.clickedButton()
|
||||
|
||||
if clicked_button == retry_button:
|
||||
self.download_setting(url, game_folder, game_version, _7z_path, plugin_path)
|
||||
elif clicked_button == next_button:
|
||||
self.next_download_task()
|
||||
else:
|
||||
self.on_download_stopped()
|
||||
return
|
||||
|
||||
# --- Start Extraction in a new thread ---
|
||||
self.hash_msg_box = self.hash_manager.hash_pop_window()
|
||||
|
||||
self.extraction_thread = ExtractionThread(_7z_path, game_folder, plugin_path, game_version, self)
|
||||
self.extraction_thread.finished.connect(self.on_extraction_finished)
|
||||
self.extraction_thread.start()
|
||||
|
||||
def on_extraction_finished(self, success, error_message, game_version):
|
||||
if self.hash_msg_box and self.hash_msg_box.isVisible():
|
||||
self.hash_msg_box.close()
|
||||
|
||||
if not success:
|
||||
QtWidgets.QMessageBox.critical(self, f"错误 - {APP_NAME}", error_message)
|
||||
self.installed_status[game_version] = False
|
||||
else:
|
||||
self.installed_status[game_version] = True
|
||||
|
||||
self.next_download_task()
|
||||
|
||||
def download_action(self):
|
||||
# 询问用户是否使用Cloudflare加速
|
||||
msg_box = QMessageBox(self)
|
||||
msg_box.setWindowTitle(f"下载优化 - {APP_NAME}")
|
||||
msg_box.setText("是否愿意通过Cloudflare加速来优化下载速度?\n\n这将临时修改系统的hosts文件,并需要管理员权限。")
|
||||
msg_box.setIcon(QMessageBox.Icon.Question)
|
||||
|
||||
yes_button = msg_box.addButton("是,开启加速", QMessageBox.ButtonRole.YesRole)
|
||||
no_button = msg_box.addButton("否,直接下载", QMessageBox.ButtonRole.NoRole)
|
||||
|
||||
msg_box.exec()
|
||||
|
||||
use_optimization = msg_box.clickedButton() == yes_button
|
||||
|
||||
self.hash_msg_box = self.hash_manager.hash_pop_window()
|
||||
|
||||
install_paths = self.get_install_paths()
|
||||
|
||||
self.hash_thread = HashThread("pre", install_paths, PLUGIN_HASH, self.installed_status, self)
|
||||
# 将用户选择传递给哈希完成后的处理函数
|
||||
self.hash_thread.pre_finished.connect(lambda status: self.on_pre_hash_finished(status, use_optimization))
|
||||
self.hash_thread.start()
|
||||
|
||||
def on_pre_hash_finished(self, updated_status, use_optimization):
|
||||
self.installed_status = updated_status
|
||||
if self.hash_msg_box and self.hash_msg_box.isVisible():
|
||||
self.hash_msg_box.accept()
|
||||
self.hash_msg_box = None
|
||||
|
||||
config = self.get_download_url()
|
||||
if not config:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self, f"错误 - {APP_NAME}", "\n网络状态异常或服务器故障,请重试\n"
|
||||
)
|
||||
return
|
||||
|
||||
# --- 填充下载队列 ---
|
||||
for i in range(1, 5):
|
||||
game_version = f"NEKOPARA Vol.{i}"
|
||||
if not self.installed_status.get(game_version, False):
|
||||
url = config.get(f"vol{i}")
|
||||
if not url: continue
|
||||
game_folder = os.path.join(self.selected_folder, f"NEKOPARA Vol. {i}")
|
||||
_7z_path = os.path.join(PLUGIN, f"vol.{i}.7z")
|
||||
plugin_path = os.path.join(PLUGIN, GAME_INFO[game_version]["plugin_path"])
|
||||
self.download_queue.append((url, game_folder, game_version, _7z_path, plugin_path))
|
||||
|
||||
game_version = "NEKOPARA After"
|
||||
if not self.installed_status.get(game_version, False):
|
||||
url = config.get("after")
|
||||
if url:
|
||||
game_folder = os.path.join(self.selected_folder, "NEKOPARA After")
|
||||
_7z_path = os.path.join(PLUGIN, "after.7z")
|
||||
plugin_path = os.path.join(PLUGIN, GAME_INFO[game_version]["plugin_path"])
|
||||
self.download_queue.append((url, game_folder, game_version, _7z_path, plugin_path))
|
||||
|
||||
if not self.download_queue:
|
||||
self.after_hash_compare(PLUGIN_HASH)
|
||||
return
|
||||
|
||||
if use_optimization and not self.optimization_done:
|
||||
first_url = self.download_queue[0][0]
|
||||
self.optimizing_msg_box = msgbox_frame(
|
||||
f"通知 - {APP_NAME}",
|
||||
"\n正在优选Cloudflare IP,请稍候...\n\n这可能需要5-10分钟,请耐心等待喵~"
|
||||
)
|
||||
# 我们不再提供“跳过”按钮,因为用户已经做出了选择
|
||||
self.optimizing_msg_box.setStandardButtons(QMessageBox.StandardButton.NoButton)
|
||||
self.optimizing_msg_box.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.optimizing_msg_box.open()
|
||||
|
||||
self.ip_optimizer_thread = IpOptimizerThread(first_url)
|
||||
# 优选完成后,需要修改hosts并开始下载
|
||||
self.ip_optimizer_thread.finished.connect(self.on_optimization_and_hosts_finished)
|
||||
self.ip_optimizer_thread.start()
|
||||
else:
|
||||
# 如果用户选择不优化,或已经优化过,直接开始下载
|
||||
self.next_download_task()
|
||||
|
||||
def next_download_task(self):
|
||||
if not self.download_queue:
|
||||
self.after_hash_compare(PLUGIN_HASH)
|
||||
return
|
||||
# 检查下载线程是否仍在运行,以避免在手动停止后立即开始下一个任务
|
||||
if self.current_download_thread and self.current_download_thread.isRunning():
|
||||
return
|
||||
|
||||
# 在开始下载前,确保hosts文件已修改(如果需要)
|
||||
# 这里的逻辑保持不变,因为hosts文件应该在队列开始前就被修改了
|
||||
url, game_folder, game_version, _7z_path, plugin_path = self.download_queue.popleft()
|
||||
self.download_setting(url, game_folder, game_version, _7z_path, plugin_path)
|
||||
|
||||
def on_download_stopped(self):
|
||||
"""当用户点击停止按钮或选择结束时调用的槽函数"""
|
||||
# 停止IP优选线程
|
||||
if hasattr(self, 'ip_optimizer_thread') and self.ip_optimizer_thread and self.ip_optimizer_thread.isRunning():
|
||||
self.ip_optimizer_thread.stop()
|
||||
self.ip_optimizer_thread.wait()
|
||||
if hasattr(self, 'optimizing_msg_box') and self.optimizing_msg_box:
|
||||
if self.optimizing_msg_box.isVisible():
|
||||
self.optimizing_msg_box.accept()
|
||||
self.optimizing_msg_box = None
|
||||
|
||||
# 停止当前可能仍在运行的下载线程
|
||||
if self.current_download_thread and self.current_download_thread.isRunning():
|
||||
self.current_download_thread.stop()
|
||||
self.current_download_thread.wait() # 等待线程完全终止
|
||||
|
||||
# 清空下载队列,因为用户决定停止
|
||||
self.download_queue.clear()
|
||||
|
||||
# 确保进度窗口已关闭
|
||||
if hasattr(self, 'progress_window') and self.progress_window.isVisible():
|
||||
self.progress_window.reject()
|
||||
|
||||
# 可以在这里决定是否立即进行哈希比较或显示结果
|
||||
print("下载已全部停止。")
|
||||
self.setEnabled(True) # 恢复主窗口交互
|
||||
self.show_result()
|
||||
|
||||
def after_hash_compare(self, plugin_hash):
|
||||
self.hash_msg_box = self.hash_manager.hash_pop_window()
|
||||
|
||||
install_paths = self.get_install_paths()
|
||||
|
||||
self.hash_thread = HashThread("after", install_paths, plugin_hash, self.installed_status, self)
|
||||
self.hash_thread.after_finished.connect(self.on_after_hash_finished)
|
||||
self.hash_thread.start()
|
||||
|
||||
def on_after_hash_finished(self, result):
|
||||
if self.hash_msg_box and self.hash_msg_box.isVisible():
|
||||
self.hash_msg_box.close()
|
||||
|
||||
if not result["passed"]:
|
||||
game = result.get("game", "未知游戏")
|
||||
message = result.get("message", "发生未知错误。")
|
||||
msg_box = msgbox_frame(
|
||||
f"文件校验失败 - {APP_NAME}",
|
||||
message,
|
||||
QtWidgets.QMessageBox.StandardButton.Ok,
|
||||
)
|
||||
msg_box.exec()
|
||||
|
||||
self.show_result()
|
||||
|
||||
def show_result(self):
|
||||
installed_version = "\n".join(
|
||||
[i for i in self.installed_status if self.installed_status[i]]
|
||||
)
|
||||
failed_ver = "\n".join(
|
||||
[i for i in self.installed_status if not self.installed_status[i]]
|
||||
)
|
||||
QtWidgets.QMessageBox.information(
|
||||
self,
|
||||
f"完成 - {APP_NAME}",
|
||||
f"\n安装结果:\n安装成功数:{len(installed_version.splitlines())} 安装失败数:{len(failed_ver.splitlines())}\n"
|
||||
f"安装成功的版本:\n{installed_version}\n尚未持有或未使用本工具安装补丁的版本:\n{failed_ver}\n",
|
||||
)
|
||||
|
||||
def show_about_dialog(self):
|
||||
"""显示关于对话框"""
|
||||
about_text = f"""
|
||||
<p><b>{APP_NAME} v{APP_VERSION}</b></p>
|
||||
<p>原作: <a href="https://github.com/Yanam1Anna">Yanam1Anna</a></p>
|
||||
<p>此应用根据 <a href="https://github.com/hyb-oyqq/FRAISEMOE2-Installer/blob/master/LICENSE">GPL-3.0 许可证</a> 授权。</p>
|
||||
"""
|
||||
msg_box = msgbox_frame(
|
||||
f"关于 - {APP_NAME}",
|
||||
about_text,
|
||||
QtWidgets.QMessageBox.StandardButton.Ok,
|
||||
)
|
||||
msg_box.setTextFormat(Qt.TextFormat.RichText) # 启用富文本
|
||||
msg_box.exec()
|
||||
|
||||
def open_project_home_page(self):
|
||||
"""打开项目主页"""
|
||||
webbrowser.open("https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT")
|
||||
|
||||
def closeEvent(self, event):
|
||||
self.shutdown_app(event)
|
||||
|
||||
def shutdown_app(self, event=None, force_exit=False):
|
||||
self.hosts_manager.restore() # 恢复hosts文件
|
||||
self.stop_logging() # 确保在退出时停止日志记录
|
||||
|
||||
if not force_exit:
|
||||
reply = QtWidgets.QMessageBox.question(
|
||||
self,
|
||||
"退出程序",
|
||||
"\n是否确定退出?\n",
|
||||
QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No,
|
||||
QtWidgets.QMessageBox.StandardButton.No,
|
||||
)
|
||||
if reply != QtWidgets.QMessageBox.StandardButton.Yes:
|
||||
if event:
|
||||
event.ignore()
|
||||
return
|
||||
|
||||
if (
|
||||
self.current_download_thread
|
||||
and self.current_download_thread.isRunning()
|
||||
):
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self,
|
||||
f"错误 - {APP_NAME}",
|
||||
"\n当前有下载任务正在进行,完成后再试\n",
|
||||
)
|
||||
if event:
|
||||
event.ignore()
|
||||
return
|
||||
|
||||
if os.path.exists(PLUGIN):
|
||||
for attempt in range(3):
|
||||
try:
|
||||
shutil.rmtree(PLUGIN)
|
||||
break
|
||||
except Exception as e:
|
||||
if attempt == 2:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self,
|
||||
f"错误 - {APP_NAME}",
|
||||
f"\n清理缓存失败\n\n【错误信息】:{e}\n",
|
||||
)
|
||||
if event:
|
||||
event.accept()
|
||||
sys.exit(1)
|
||||
if event:
|
||||
event.accept()
|
||||
else:
|
||||
sys.exit(0)
|
||||
@@ -182,5 +182,4 @@ class ProgressWindow(QDialog):
|
||||
|
||||
def closeEvent(self, event):
|
||||
# 覆盖默认的关闭事件,防止用户通过其他方式关闭窗口
|
||||
# 如果需要,可以在这里添加逻辑,例如询问用户是否要停止下载
|
||||
event.ignore()
|
||||
@@ -38,6 +38,7 @@ class IpOptimizer:
|
||||
"-url", url, # 指定测速地址
|
||||
"-f", ip_txt_path, # IP文件
|
||||
"-dd", # 禁用下载测速,按延迟排序
|
||||
"-o",
|
||||
]
|
||||
|
||||
creation_flags = subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0
|
||||
|
||||
Reference in New Issue
Block a user