Thanks to the following contributors: - @snowykami - @frg2089 - Nya_Twisuki
71
.github/workflows/deploy-docs.yml
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
name: 部署文档
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- .github/**
|
||||
- src/**
|
||||
- docs/**
|
||||
- public/**
|
||||
- package.json
|
||||
- vuepress.*.ts
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
statuses: write
|
||||
|
||||
env:
|
||||
MELI_SITE: b9bc0b87-f062-4724-b2f5-55866ff12736
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: [ubuntu-latest, public]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# lfs: true
|
||||
# 如果你文档需要 Git 子模块,取消注释下一行
|
||||
# submodules: true
|
||||
|
||||
- name: 设置 pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
# with:
|
||||
# version: 8
|
||||
|
||||
|
||||
- name: 设置 Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 23
|
||||
cache: pnpm
|
||||
|
||||
- name: 安装依赖
|
||||
run: |
|
||||
corepack enable
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
- name: 构建文档
|
||||
env:
|
||||
NODE_OPTIONS: --max_old_space_size=8192
|
||||
run: |-
|
||||
pnpm run docs:build
|
||||
> dist/.nojekyll
|
||||
|
||||
- name: 推送 liteyuki pages
|
||||
run: |
|
||||
pnpx @getmeli/cli upload \
|
||||
--branch main \
|
||||
--url "https://dash.apage.dev" \
|
||||
--site "$MELI_SITE" \
|
||||
--token "$MELI_TOKEN" \
|
||||
--release "$GITHUB_SHA" \
|
||||
./dist
|
||||
env:
|
||||
MELI_TOKEN: ${{ secrets.MELI_TOKEN }}
|
||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
node_modules/
|
||||
.cache/
|
||||
.temp/
|
||||
dist/
|
15
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
// 使用 IntelliSense 了解相关属性。
|
||||
// 悬停以查看现有属性的描述。
|
||||
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "针对 localhost 启动 Chrome",
|
||||
"url": "http://localhost:8080",
|
||||
"webRoot": "${workspaceFolder}",
|
||||
}
|
||||
]
|
||||
}
|
6
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"files.exclude": {
|
||||
"**/*.png": true,
|
||||
"**/*.webp": true
|
||||
}
|
||||
}
|
17
.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "docs:dev",
|
||||
"problemMatcher": [],
|
||||
"label": "npm: docs:dev",
|
||||
"detail": "vuepress-vite dev src",
|
||||
"isBackground": true,
|
||||
"runOptions": {
|
||||
"runOn": "folderOpen",
|
||||
"instanceLimit": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
438
LICENSE-CC
Normal file
@ -0,0 +1,438 @@
|
||||
Attribution-NonCommercial-ShareAlike 4.0 International
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
||||
does not provide legal services or legal advice. Distribution of
|
||||
Creative Commons public licenses does not create a lawyer-client or
|
||||
other relationship. Creative Commons makes its licenses and related
|
||||
information available on an "as-is" basis. Creative Commons gives no
|
||||
warranties regarding its licenses, any material licensed under their
|
||||
terms and conditions, or any related information. Creative Commons
|
||||
disclaims all liability for damages resulting from their use to the
|
||||
fullest extent possible.
|
||||
|
||||
Using Creative Commons Public Licenses
|
||||
|
||||
Creative Commons public licenses provide a standard set of terms and
|
||||
conditions that creators and other rights holders may use to share
|
||||
original works of authorship and other material subject to copyright
|
||||
and certain other rights specified in the public license below. The
|
||||
following considerations are for informational purposes only, are not
|
||||
exhaustive, and do not form part of our licenses.
|
||||
|
||||
Considerations for licensors: Our public licenses are
|
||||
intended for use by those authorized to give the public
|
||||
permission to use material in ways otherwise restricted by
|
||||
copyright and certain other rights. Our licenses are
|
||||
irrevocable. Licensors should read and understand the terms
|
||||
and conditions of the license they choose before applying it.
|
||||
Licensors should also secure all rights necessary before
|
||||
applying our licenses so that the public can reuse the
|
||||
material as expected. Licensors should clearly mark any
|
||||
material not subject to the license. This includes other CC-
|
||||
licensed material, or material used under an exception or
|
||||
limitation to copyright. More considerations for licensors:
|
||||
wiki.creativecommons.org/Considerations_for_licensors
|
||||
|
||||
Considerations for the public: By using one of our public
|
||||
licenses, a licensor grants the public permission to use the
|
||||
licensed material under specified terms and conditions. If
|
||||
the licensor's permission is not necessary for any reason--for
|
||||
example, because of any applicable exception or limitation to
|
||||
copyright--then that use is not regulated by the license. Our
|
||||
licenses grant only permissions under copyright and certain
|
||||
other rights that a licensor has authority to grant. Use of
|
||||
the licensed material may still be restricted for other
|
||||
reasons, including because others have copyright or other
|
||||
rights in the material. A licensor may make special requests,
|
||||
such as asking that all changes be marked or described.
|
||||
Although not required by our licenses, you are encouraged to
|
||||
respect those requests where reasonable. More considerations
|
||||
for the public:
|
||||
wiki.creativecommons.org/Considerations_for_licensees
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
|
||||
Public License
|
||||
|
||||
By exercising the Licensed Rights (defined below), You accept and agree
|
||||
to be bound by the terms and conditions of this Creative Commons
|
||||
Attribution-NonCommercial-ShareAlike 4.0 International Public License
|
||||
("Public License"). To the extent this Public License may be
|
||||
interpreted as a contract, You are granted the Licensed Rights in
|
||||
consideration of Your acceptance of these terms and conditions, and the
|
||||
Licensor grants You such rights in consideration of benefits the
|
||||
Licensor receives from making the Licensed Material available under
|
||||
these terms and conditions.
|
||||
|
||||
|
||||
Section 1 -- Definitions.
|
||||
|
||||
a. Adapted Material means material subject to Copyright and Similar
|
||||
Rights that is derived from or based upon the Licensed Material
|
||||
and in which the Licensed Material is translated, altered,
|
||||
arranged, transformed, or otherwise modified in a manner requiring
|
||||
permission under the Copyright and Similar Rights held by the
|
||||
Licensor. For purposes of this Public License, where the Licensed
|
||||
Material is a musical work, performance, or sound recording,
|
||||
Adapted Material is always produced where the Licensed Material is
|
||||
synched in timed relation with a moving image.
|
||||
|
||||
b. Adapter's License means the license You apply to Your Copyright
|
||||
and Similar Rights in Your contributions to Adapted Material in
|
||||
accordance with the terms and conditions of this Public License.
|
||||
|
||||
c. BY-NC-SA Compatible License means a license listed at
|
||||
creativecommons.org/compatiblelicenses, approved by Creative
|
||||
Commons as essentially the equivalent of this Public License.
|
||||
|
||||
d. Copyright and Similar Rights means copyright and/or similar rights
|
||||
closely related to copyright including, without limitation,
|
||||
performance, broadcast, sound recording, and Sui Generis Database
|
||||
Rights, without regard to how the rights are labeled or
|
||||
categorized. For purposes of this Public License, the rights
|
||||
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
||||
Rights.
|
||||
|
||||
e. Effective Technological Measures means those measures that, in the
|
||||
absence of proper authority, may not be circumvented under laws
|
||||
fulfilling obligations under Article 11 of the WIPO Copyright
|
||||
Treaty adopted on December 20, 1996, and/or similar international
|
||||
agreements.
|
||||
|
||||
f. Exceptions and Limitations means fair use, fair dealing, and/or
|
||||
any other exception or limitation to Copyright and Similar Rights
|
||||
that applies to Your use of the Licensed Material.
|
||||
|
||||
g. License Elements means the license attributes listed in the name
|
||||
of a Creative Commons Public License. The License Elements of this
|
||||
Public License are Attribution, NonCommercial, and ShareAlike.
|
||||
|
||||
h. Licensed Material means the artistic or literary work, database,
|
||||
or other material to which the Licensor applied this Public
|
||||
License.
|
||||
|
||||
i. Licensed Rights means the rights granted to You subject to the
|
||||
terms and conditions of this Public License, which are limited to
|
||||
all Copyright and Similar Rights that apply to Your use of the
|
||||
Licensed Material and that the Licensor has authority to license.
|
||||
|
||||
j. Licensor means the individual(s) or entity(ies) granting rights
|
||||
under this Public License.
|
||||
|
||||
k. NonCommercial means not primarily intended for or directed towards
|
||||
commercial advantage or monetary compensation. For purposes of
|
||||
this Public License, the exchange of the Licensed Material for
|
||||
other material subject to Copyright and Similar Rights by digital
|
||||
file-sharing or similar means is NonCommercial provided there is
|
||||
no payment of monetary compensation in connection with the
|
||||
exchange.
|
||||
|
||||
l. Share means to provide material to the public by any means or
|
||||
process that requires permission under the Licensed Rights, such
|
||||
as reproduction, public display, public performance, distribution,
|
||||
dissemination, communication, or importation, and to make material
|
||||
available to the public including in ways that members of the
|
||||
public may access the material from a place and at a time
|
||||
individually chosen by them.
|
||||
|
||||
m. Sui Generis Database Rights means rights other than copyright
|
||||
resulting from Directive 96/9/EC of the European Parliament and of
|
||||
the Council of 11 March 1996 on the legal protection of databases,
|
||||
as amended and/or succeeded, as well as other essentially
|
||||
equivalent rights anywhere in the world.
|
||||
|
||||
n. You means the individual or entity exercising the Licensed Rights
|
||||
under this Public License. Your has a corresponding meaning.
|
||||
|
||||
|
||||
Section 2 -- Scope.
|
||||
|
||||
a. License grant.
|
||||
|
||||
1. Subject to the terms and conditions of this Public License,
|
||||
the Licensor hereby grants You a worldwide, royalty-free,
|
||||
non-sublicensable, non-exclusive, irrevocable license to
|
||||
exercise the Licensed Rights in the Licensed Material to:
|
||||
|
||||
a. reproduce and Share the Licensed Material, in whole or
|
||||
in part, for NonCommercial purposes only; and
|
||||
|
||||
b. produce, reproduce, and Share Adapted Material for
|
||||
NonCommercial purposes only.
|
||||
|
||||
2. Exceptions and Limitations. For the avoidance of doubt, where
|
||||
Exceptions and Limitations apply to Your use, this Public
|
||||
License does not apply, and You do not need to comply with
|
||||
its terms and conditions.
|
||||
|
||||
3. Term. The term of this Public License is specified in Section
|
||||
6(a).
|
||||
|
||||
4. Media and formats; technical modifications allowed. The
|
||||
Licensor authorizes You to exercise the Licensed Rights in
|
||||
all media and formats whether now known or hereafter created,
|
||||
and to make technical modifications necessary to do so. The
|
||||
Licensor waives and/or agrees not to assert any right or
|
||||
authority to forbid You from making technical modifications
|
||||
necessary to exercise the Licensed Rights, including
|
||||
technical modifications necessary to circumvent Effective
|
||||
Technological Measures. For purposes of this Public License,
|
||||
simply making modifications authorized by this Section 2(a)
|
||||
(4) never produces Adapted Material.
|
||||
|
||||
5. Downstream recipients.
|
||||
|
||||
a. Offer from the Licensor -- Licensed Material. Every
|
||||
recipient of the Licensed Material automatically
|
||||
receives an offer from the Licensor to exercise the
|
||||
Licensed Rights under the terms and conditions of this
|
||||
Public License.
|
||||
|
||||
b. Additional offer from the Licensor -- Adapted Material.
|
||||
Every recipient of Adapted Material from You
|
||||
automatically receives an offer from the Licensor to
|
||||
exercise the Licensed Rights in the Adapted Material
|
||||
under the conditions of the Adapter's License You apply.
|
||||
|
||||
c. No downstream restrictions. You may not offer or impose
|
||||
any additional or different terms or conditions on, or
|
||||
apply any Effective Technological Measures to, the
|
||||
Licensed Material if doing so restricts exercise of the
|
||||
Licensed Rights by any recipient of the Licensed
|
||||
Material.
|
||||
|
||||
6. No endorsement. Nothing in this Public License constitutes or
|
||||
may be construed as permission to assert or imply that You
|
||||
are, or that Your use of the Licensed Material is, connected
|
||||
with, or sponsored, endorsed, or granted official status by,
|
||||
the Licensor or others designated to receive attribution as
|
||||
provided in Section 3(a)(1)(A)(i).
|
||||
|
||||
b. Other rights.
|
||||
|
||||
1. Moral rights, such as the right of integrity, are not
|
||||
licensed under this Public License, nor are publicity,
|
||||
privacy, and/or other similar personality rights; however, to
|
||||
the extent possible, the Licensor waives and/or agrees not to
|
||||
assert any such rights held by the Licensor to the limited
|
||||
extent necessary to allow You to exercise the Licensed
|
||||
Rights, but not otherwise.
|
||||
|
||||
2. Patent and trademark rights are not licensed under this
|
||||
Public License.
|
||||
|
||||
3. To the extent possible, the Licensor waives any right to
|
||||
collect royalties from You for the exercise of the Licensed
|
||||
Rights, whether directly or through a collecting society
|
||||
under any voluntary or waivable statutory or compulsory
|
||||
licensing scheme. In all other cases the Licensor expressly
|
||||
reserves any right to collect such royalties, including when
|
||||
the Licensed Material is used other than for NonCommercial
|
||||
purposes.
|
||||
|
||||
|
||||
Section 3 -- License Conditions.
|
||||
|
||||
Your exercise of the Licensed Rights is expressly made subject to the
|
||||
following conditions.
|
||||
|
||||
a. Attribution.
|
||||
|
||||
1. If You Share the Licensed Material (including in modified
|
||||
form), You must:
|
||||
|
||||
a. retain the following if it is supplied by the Licensor
|
||||
with the Licensed Material:
|
||||
|
||||
i. identification of the creator(s) of the Licensed
|
||||
Material and any others designated to receive
|
||||
attribution, in any reasonable manner requested by
|
||||
the Licensor (including by pseudonym if
|
||||
designated);
|
||||
|
||||
ii. a copyright notice;
|
||||
|
||||
iii. a notice that refers to this Public License;
|
||||
|
||||
iv. a notice that refers to the disclaimer of
|
||||
warranties;
|
||||
|
||||
v. a URI or hyperlink to the Licensed Material to the
|
||||
extent reasonably practicable;
|
||||
|
||||
b. indicate if You modified the Licensed Material and
|
||||
retain an indication of any previous modifications; and
|
||||
|
||||
c. indicate the Licensed Material is licensed under this
|
||||
Public License, and include the text of, or the URI or
|
||||
hyperlink to, this Public License.
|
||||
|
||||
2. You may satisfy the conditions in Section 3(a)(1) in any
|
||||
reasonable manner based on the medium, means, and context in
|
||||
which You Share the Licensed Material. For example, it may be
|
||||
reasonable to satisfy the conditions by providing a URI or
|
||||
hyperlink to a resource that includes the required
|
||||
information.
|
||||
3. If requested by the Licensor, You must remove any of the
|
||||
information required by Section 3(a)(1)(A) to the extent
|
||||
reasonably practicable.
|
||||
|
||||
b. ShareAlike.
|
||||
|
||||
In addition to the conditions in Section 3(a), if You Share
|
||||
Adapted Material You produce, the following conditions also apply.
|
||||
|
||||
1. The Adapter's License You apply must be a Creative Commons
|
||||
license with the same License Elements, this version or
|
||||
later, or a BY-NC-SA Compatible License.
|
||||
|
||||
2. You must include the text of, or the URI or hyperlink to, the
|
||||
Adapter's License You apply. You may satisfy this condition
|
||||
in any reasonable manner based on the medium, means, and
|
||||
context in which You Share Adapted Material.
|
||||
|
||||
3. You may not offer or impose any additional or different terms
|
||||
or conditions on, or apply any Effective Technological
|
||||
Measures to, Adapted Material that restrict exercise of the
|
||||
rights granted under the Adapter's License You apply.
|
||||
|
||||
|
||||
Section 4 -- Sui Generis Database Rights.
|
||||
|
||||
Where the Licensed Rights include Sui Generis Database Rights that
|
||||
apply to Your use of the Licensed Material:
|
||||
|
||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
||||
to extract, reuse, reproduce, and Share all or a substantial
|
||||
portion of the contents of the database for NonCommercial purposes
|
||||
only;
|
||||
|
||||
b. if You include all or a substantial portion of the database
|
||||
contents in a database in which You have Sui Generis Database
|
||||
Rights, then the database in which You have Sui Generis Database
|
||||
Rights (but not its individual contents) is Adapted Material,
|
||||
including for purposes of Section 3(b); and
|
||||
|
||||
c. You must comply with the conditions in Section 3(a) if You Share
|
||||
all or a substantial portion of the contents of the database.
|
||||
|
||||
For the avoidance of doubt, this Section 4 supplements and does not
|
||||
replace Your obligations under this Public License where the Licensed
|
||||
Rights include other Copyright and Similar Rights.
|
||||
|
||||
|
||||
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
||||
|
||||
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
||||
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
||||
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
||||
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
||||
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
||||
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
||||
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
||||
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
||||
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
||||
|
||||
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
||||
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
||||
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
||||
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
||||
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
||||
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
||||
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
||||
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
||||
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
||||
|
||||
c. The disclaimer of warranties and limitation of liability provided
|
||||
above shall be interpreted in a manner that, to the extent
|
||||
possible, most closely approximates an absolute disclaimer and
|
||||
waiver of all liability.
|
||||
|
||||
|
||||
Section 6 -- Term and Termination.
|
||||
|
||||
a. This Public License applies for the term of the Copyright and
|
||||
Similar Rights licensed here. However, if You fail to comply with
|
||||
this Public License, then Your rights under this Public License
|
||||
terminate automatically.
|
||||
|
||||
b. Where Your right to use the Licensed Material has terminated under
|
||||
Section 6(a), it reinstates:
|
||||
|
||||
1. automatically as of the date the violation is cured, provided
|
||||
it is cured within 30 days of Your discovery of the
|
||||
violation; or
|
||||
|
||||
2. upon express reinstatement by the Licensor.
|
||||
|
||||
For the avoidance of doubt, this Section 6(b) does not affect any
|
||||
right the Licensor may have to seek remedies for Your violations
|
||||
of this Public License.
|
||||
|
||||
c. For the avoidance of doubt, the Licensor may also offer the
|
||||
Licensed Material under separate terms or conditions or stop
|
||||
distributing the Licensed Material at any time; however, doing so
|
||||
will not terminate this Public License.
|
||||
|
||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
||||
License.
|
||||
|
||||
|
||||
Section 7 -- Other Terms and Conditions.
|
||||
|
||||
a. The Licensor shall not be bound by any additional or different
|
||||
terms or conditions communicated by You unless expressly agreed.
|
||||
|
||||
b. Any arrangements, understandings, or agreements regarding the
|
||||
Licensed Material not stated herein are separate from and
|
||||
independent of the terms and conditions of this Public License.
|
||||
|
||||
|
||||
Section 8 -- Interpretation.
|
||||
|
||||
a. For the avoidance of doubt, this Public License does not, and
|
||||
shall not be interpreted to, reduce, limit, restrict, or impose
|
||||
conditions on any use of the Licensed Material that could lawfully
|
||||
be made without permission under this Public License.
|
||||
|
||||
b. To the extent possible, if any provision of this Public License is
|
||||
deemed unenforceable, it shall be automatically reformed to the
|
||||
minimum extent necessary to make it enforceable. If the provision
|
||||
cannot be reformed, it shall be severed from this Public License
|
||||
without affecting the enforceability of the remaining terms and
|
||||
conditions.
|
||||
|
||||
c. No term or condition of this Public License will be waived and no
|
||||
failure to comply consented to unless expressly agreed to by the
|
||||
Licensor.
|
||||
|
||||
d. Nothing in this Public License constitutes or may be interpreted
|
||||
as a limitation upon, or waiver of, any privileges and immunities
|
||||
that apply to the Licensor or You, including from the legal
|
||||
processes of any jurisdiction or authority.
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons is not a party to its public
|
||||
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
||||
its public licenses to material it publishes and in those instances
|
||||
will be considered the “Licensor.” The text of the Creative Commons
|
||||
public licenses is dedicated to the public domain under the CC0 Public
|
||||
Domain Dedication. Except for the limited purpose of indicating that
|
||||
material is shared under a Creative Commons public license or as
|
||||
otherwise permitted by the Creative Commons policies published at
|
||||
creativecommons.org/policies, Creative Commons does not authorize the
|
||||
use of the trademark "Creative Commons" or any other trademark or logo
|
||||
of Creative Commons without its prior written consent including,
|
||||
without limitation, in connection with any unauthorized modifications
|
||||
to any of its public licenses or any other arrangements,
|
||||
understandings, or agreements concerning use of licensed material. For
|
||||
the avoidance of doubt, this paragraph does not form part of the
|
||||
public licenses.
|
||||
|
||||
Creative Commons may be contacted at creativecommons.org.
|
||||
|
21
LICENSE-MIT
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Kariko Lin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
17
README.md
Normal file
@ -0,0 +1,17 @@
|
||||
## SilverAg.L 的玩具城
|
||||
说人话就是咱的个人博客。不考虑英翻,NO ENGLISH VERSION!
|
||||
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/license-MIT-blue" alt="license - MIT">
|
||||
<img src="https://img.shields.io/badge/license-CC--BY--NC--SA--4.0-lightgrey" alt="license - CC-BY-NC-SA-4.0">
|
||||
</p>
|
||||
|
||||
本博客收纳的文章统一采用 CC BY-NC-SA 4.0 协议共享。
|
||||
|
||||
**欢迎就本博客上传的「随记」「综述」中的错误、不足之处提出 Issue 批评指正;或者看不顺眼了,丢个 Pull Request,也是极好的。**
|
||||
|
||||
### 特别鸣谢
|
||||
- [神羽 @SnowyKami](https://github.com/snowykami)
|
||||
- [岛风 @frg2089](https://github.com/frg2089)
|
5
docs/.vuepress/styles/config.scss
Normal file
@ -0,0 +1,5 @@
|
||||
// you can change config here
|
||||
$colors: #c0392b, #d35400, #f39c12, #27ae60, #16a085, #2980b9, #8e44ad, #2c3e50,
|
||||
#7f8c8d !default;
|
||||
|
||||
$theme-color: #66ccff;
|
66
docs/.vuepress/styles/index.scss
Normal file
@ -0,0 +1,66 @@
|
||||
// place your custom styles here
|
||||
.vp-blog-hero-description {
|
||||
text-align: center;
|
||||
margin: 0.75rem auto 0;
|
||||
}
|
||||
|
||||
.vp-nav-logo {
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.vp-blog-hero-image {
|
||||
border-radius: 2rem;
|
||||
}
|
||||
|
||||
.vp-blogger-avatar {
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
$width: 48rem;
|
||||
$height: 25rem;
|
||||
|
||||
.vp-page.vp-blog-home {
|
||||
& .vp-blog-mask:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: min($width, calc(100vw - 2rem));
|
||||
height: min($height, 100vh);
|
||||
z-index: 2;
|
||||
display: block;
|
||||
transform: translate(max(calc(min($width, calc(100vw - 2rem)) / -2), -50vw),
|
||||
calc(min($height, 100vh) / -2));
|
||||
backdrop-filter: blur(1rem);
|
||||
/* 边框模糊效果 */
|
||||
/* filter: blur(.25rem); */
|
||||
border-radius: 1rem;
|
||||
|
||||
html[data-theme='dark'] & {
|
||||
background: #3333337f;
|
||||
box-shadow: 0.25rem 0.25rem #0000007f;
|
||||
}
|
||||
|
||||
html[data-theme='light'] & {
|
||||
background: #cccccc7f;
|
||||
box-shadow: 0.25rem 0.25rem #6666667f;
|
||||
}
|
||||
}
|
||||
|
||||
& .vp-blog-mask::after {
|
||||
opacity: unset;
|
||||
background: unset;
|
||||
|
||||
html[data-theme='dark'] & {
|
||||
background: #0000007f;
|
||||
}
|
||||
}
|
||||
|
||||
& .vp-blog-hero {
|
||||
padding: 2rem;
|
||||
|
||||
html[data-theme='light'] & {
|
||||
color: var(--grey-darker);
|
||||
}
|
||||
}
|
||||
}
|
3
docs/.vuepress/styles/palette.scss
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
// force Noto Serif.
|
||||
$vp-font: '"Noto Serif SC", serif';
|
16
docs/README.md
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
home: true
|
||||
layout: BlogHome
|
||||
icon: home
|
||||
title: 主页
|
||||
heroImage: /assets/images/avatar.webp
|
||||
heroText: SilverAg.L
|
||||
heroFullScreen: true
|
||||
bgImage: https://www.loliapi.com/acg/
|
||||
tagline: 欢迎光临我的玩具城……♡
|
||||
---
|
||||
|
||||
::: center
|
||||
Powered by [VuePress Theme Hope](https://theme-hope.vuejs.press/zh/),
|
||||
Blogs Issued with [CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/).
|
||||
:::
|
476
docs/archives/RA2TriggerLogics.md
Normal file
@ -0,0 +1,476 @@
|
||||
---
|
||||
category:
|
||||
- RA2
|
||||
- 地图
|
||||
tag:
|
||||
- 地图编辑
|
||||
- 触发
|
||||
- 局部变量
|
||||
star: true
|
||||
---
|
||||
|
||||
# 浅谈红警 2 触发组件的运行逻辑
|
||||
|
||||
::: warning 观前注意
|
||||
由于涉及一些编程知识点,本综述可能存在亿些阅读困难。
|
||||
虽然经过与 Zero Fanker 等人的讨论后决定做些~~修缮~~重写,但难免仍有需要改进之处。
|
||||
:::
|
||||
|
||||
## 绪论
|
||||
|
||||
### 研究背景
|
||||
红警 2 的地图创作中,流程设计是战役最重要的一环,其中触发发挥着举足轻重的作用。好的剧情流程能够让人印象深刻,如何用触发设计出好的流程,就得凭借地图师们的逻辑智慧了。但长久以来,红红的地图教程偏实用性居多,大多数地图师对触发并没有明晰的认识,他们或许在脑内对任务流程有着天马行空的设想,但落到触发实现上往往束手无策。
|
||||
|
||||
本文基于已有的触发和编程实践,尝试阐明触发组件的运作**逻辑**,并就这套系统的一些缺陷提出个人的见解,希望能够对各位读者有所启发。如果能稍稍地推进触发设计的简化事业,那就再好不过了。
|
||||
|
||||
### 研究目的与意义
|
||||
本文旨在用程序化的思想阐述触发的运作**逻辑**(而非原理,更不是底层原理),指出这套系统存在的**逻辑**缺陷,并试着给出可行的解决思路。通过对触发**逻辑**的分析,用不同的视角去看待触发,或许可以**一定程度上**简化触发的表达,增强地图师们的逻辑思维,启发他们对优秀设计模型(如状态机)的借鉴、化用,提升剧情的观赏性,为观众们带来更多精彩的“剧目”吧。
|
||||
|
||||
::: note 逻辑与实现
|
||||
必须指出的一点是:逻辑与实现并不等同。逻辑关心客观事物的**本质、规律**,实现则关心符合这个规律的**解决方案**。
|
||||
:::
|
||||
|
||||
## 一、触发组件相关概念
|
||||
|
||||
### 1.1 触发
|
||||
|
||||
地图触发是早在《命运与征服:泰伯利亚黎明》就引入的系统,负责处理地图当中的“事件”^1^。
|
||||
|
||||
一局游戏瞬息万变,其中总有一些**既成的、游戏引擎能感知的事,叫做事件**。比如什么关键建筑被打爆了啊,哪家缺电缺钱了,等等。
|
||||
这些事件会被触发捕捉到,并驱动后者去执行相应的**行为,也就是游戏引擎能做到的各种效果**:可以是刷兵,改变光照,炸个桥,平地起心灵信标……诸如此类。
|
||||
|
||||
再次强调,捕获到的事件、要执行的行为,都仅限**引擎能做到的**范围之内。
|
||||
|
||||
::: note 事件和行为
|
||||
有一些地编调整过用词,事件改称“条件”,行为改叫“结果”。你可以简单这么理解。
|
||||
:::
|
||||
|
||||
<!-- 在红警 2,触发更类似于高中数学中的“命题”:若 p 则 q。其中事件 p 和行为 q 都可以不止一条,并且 p1 p2 p... 之间、q1 q2 q... 之间有一定的连接关系。 -->
|
||||
|
||||
### 1.2 局部变量
|
||||
|
||||
变量系统则在《命运与征服:泰伯利亚之日》才开始出现,根据`[VariableNames]`小节所处的位置不同,
|
||||
分为 Rules 里的**全局变量**,和地图里的**局部变量**^2^。本文主要讨论局部变量。
|
||||
|
||||
在地图创作中,局部变量就是**对设计者有具体意义的**,**会因触发**(和动作脚本)**做出改变的**,**临时在某一局游戏起作用的**数值。
|
||||
|
||||
值得一提的是,从原版一直到 Ares 扩展平台,局部变量均有如下限制:
|
||||
|
||||
- **存在数目上限**:至多能设置 100 个;
|
||||
- **取值范围有限**:变量的值只能为`0`(清除)和`1`(设置)两种。
|
||||
|
||||
一直到 [@secsome](https://github.com/secsome) 在 Phobos 平台扩展了变量系统之后,上述限制才被打破。
|
||||
|
||||
> 参考链接:
|
||||
> [pr#321](https://github.com/Phobos-developers/Phobos/pull/321),
|
||||
> [pr#424](https://github.com/Phobos-developers/Phobos/pull/424),
|
||||
> [pr#425](https://github.com/Phobos-developers/Phobos/pull/425).
|
||||
|
||||
::: info 扩展变量系统
|
||||
船新的变量系统大大扩充了局部变量池,几乎可以认为是“无限量”使用。~~真的有人会堆料到要用 2^31^ - 1 个以上的变量吗?~~
|
||||
除此之外,变量取值范围也极大地延展了。但官方文档指出,这种延展并不对等:
|
||||
|
||||
- **纯触发组件中**,范围可达 $[-2^{31}, 2^{31} - 1]$(即上至`2147483647`);
|
||||
- **动作脚本中**参数位有限,范围**缩减**为 $[-2^{15}, 2^{15} - 1]$(上至`32767`)。出界不保证预期效果。
|
||||
|
||||
至于为何会有这种缩减,还请移步《计算机组成原理》第二章:数据的表示与运算。
|
||||
:::
|
||||
|
||||
## 二、触发组件的程序逻辑分析
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 由于本博客使用的`katex`插件不支持编写 LaTeX 伪代码,因此本文的伪代码仍然基于 Python 语法。
|
||||
> 但考虑到 Python 的很多特性存在理解门槛,本文将只着眼于`if`等通用表达。
|
||||
|
||||
### 2.1 触发的逻辑本质
|
||||
提及触发,各位地图师看过教程、初步上手后应该会建立这样的基本印象:
|
||||
|
||||
- 触发有一定**前置事件**。
|
||||
- 触发指明了**执行行为**。
|
||||
- 触发要等**前置事件(条件)完成**,才会**执行相应行为**,表现出各种结果。
|
||||
|
||||
> 比如简单的延时字幕,你确实需要等上那一段时间,然后才会冒出来一句“必须重新集结部队”。
|
||||
|
||||
这种“等待条件”的现象与高中数学讨论的“若 p 则 q”命题(即条件命题)非常类似。一个条件命题要能判断其真伪,条件就必不可少;同样的,一个触发要能执行,首先要等待前置事件完成。在程序框图中,像这种具有前置条件的语义用**条件分支**表示(如下图),又称**选择结构**。
|
||||
|
||||

|
||||
|
||||
从游戏实际运行的现象来看,触发会在条件满足(为真)后执行相应的“处理程序”(也就是行为),这一点符合基本印象。而当条件**尚未满足**时,触发则阻塞等待。
|
||||
假如**逻辑上**就是希望判断条件不满足(为假),由于触发一直等不到条件满足,则直到游戏结束为止这个条件都得不到任何处理。如此看来,触发的逻辑本质就是如上左图的**条件单分支结构**。
|
||||
|
||||
而在程序语言里,这种条件单分支常用`if`表示:
|
||||
|
||||
```python
|
||||
if condition: # 条件
|
||||
do_sth() # 结果
|
||||
```
|
||||
|
||||
在这段代码中,只有当条件`condition`满足(为真),才会去走`do_sth()`那一步,执行出结果来。
|
||||
而触发也是等到条件满足,才会执行结果。既然如此,抛开触发的所属、难度开关等等其他属性,不妨就把一个触发当成这种`if`结构。
|
||||
|
||||
> [!note]
|
||||
> 1. 为了方便讨论,本章 *Q53/Q54 允许/禁止触发* 不会直接使用`do_sth()`这种函数表示。
|
||||
> 2. 如没有文字说明,文中使用到的触发条件、结果会在号码前面标明 P、Q 加以区分。
|
||||
|
||||
至于事件`condition`和行为`do_sth()`,事实上它们可以不止一条。多条件姑且不论,有些朋友可能很喜欢在单一触发里塞十几条结果。
|
||||
那么多个条件、多个结果之间是如何串起来的呢?你可以自行翻阅各扩展平台的 YRpp,或是在 FA2 等地图编辑器中实验一下,这边就直接说结论了:
|
||||
|
||||
- 多个条件之间**以逻辑且(AND)连接**,所有条件**必须全部满足**,才可以执行对应的结果;
|
||||
- 多个结果之间形成队列**按序执行**,但因为每一条结果执行耗时很短,表面上看似乎是同时完成。
|
||||
|
||||
至此,可以用这样的伪代码描述一个触发的 $p_1 \land \ldots \land p_n \to Q$ 逻辑了:
|
||||
```python
|
||||
if event1 and event2() and ...:
|
||||
action1()
|
||||
action2()
|
||||
...
|
||||
```
|
||||
|
||||
::: details 参考资料:触发行为在 INI 和引擎中的实际表示
|
||||
|
||||
以触发行为为例,触发行为在地图里是这种 INI 表示:
|
||||
```ini
|
||||
01019810 = len_actions, a1_type, a1p1, a1p2, ..., a1p6, a1_wp, a2_type, ...
|
||||
```
|
||||
在游戏引擎中,一条 Action 由`TActionClass`管理(下列声明有所省略,详见 [YRpp](https://github.com/Phobos-developers/YRpp/blob/c8d4da4f57a80a3cc2b9ecbde56c335e082c8335/TActionClass.h)):
|
||||
```cpp
|
||||
class TActionClass : public AbstractClass {
|
||||
public:
|
||||
TriggerAction ActionKind; // aX_type
|
||||
union {
|
||||
RectangleStruct Bounds; // map bounds for use with action 40
|
||||
struct {
|
||||
int Param3;
|
||||
int Param4;
|
||||
int Param5;
|
||||
int Param6;
|
||||
};
|
||||
}; // It's enough for calling Bounds.X, just use a union here now. - secsome
|
||||
int Waypoint; // aX_wp
|
||||
int Value2; // multipurpose // aXp2
|
||||
int Value; // multipurpose // aXp1
|
||||
};
|
||||
```
|
||||
其中 P1 决定了 P2 参数的类型。由于 P3-P6 均为`int`类型,无法满足文本、数值、触发等多种类型需求,所以由 Value (P1) 采取类似`enum`的设计,Value2 则记录真实参数 P2 的指针地址。
|
||||
:::
|
||||
|
||||
::: details 参考资料:触发行为的具体实现
|
||||
游戏引擎仍然用`TActionClass`声明和实现原版的行为(也就是地编靠前的 100 多号),扩展平台则用`TActionExt`实现扩展。
|
||||
以 Phobos 的 *编辑变量* 这一结果为例:
|
||||
```cpp
|
||||
bool TActionExt::EditVariable(
|
||||
TActionClass* pThis, HouseClass* pHouse, ObjectClass* pObject,
|
||||
TriggerClass* pTrigger, CellStruct const& location)
|
||||
{
|
||||
// blabla
|
||||
return true;
|
||||
}
|
||||
```
|
||||
- `pThis`为`TActionClass`的指针,在参考资料「触发行为在 INI 和引擎中的实际表示」中已经介绍过,它可以记录行为参数;
|
||||
- `pHouse`系触发所属方,比如行为 *36 全部更改所属* 要变走\*触发所属方\*的全部东西。
|
||||
|
||||
其余的函数形参本人没有具体研究过,恕不做介绍。
|
||||
:::
|
||||
|
||||
### 2.2 顺序结构
|
||||
|
||||
顺序结构是最简单、最基本、最符合思维直觉的结构。大部分战役任务的流程也都是线性、有序的流程。以被广为改编的《脑死》为例,它的任务流程具有很典型的顺序性:
|
||||
|
||||
1. 建立一座~~锅盖~~苏军雷达
|
||||
2. 超时空援军就位后,摧毁最后一座心灵控制器
|
||||
3. 秋风扫落叶,清除残余敌军。
|
||||
|
||||
在线性系统中,**按时间先后执行的流程**我们认为就是顺序结构^3^。
|
||||
|
||||
然而两个触发之间常常在宏观上表现为异步:同样都是延时 10s,A 触发和 B 触发看起来好像是同时执行、互不相干的。那要如何保证顺序呢?
|
||||
一个简单的办法是用行为 *53 允许触发*。假定有两个触发 $t_0$ 和 $t_1$,要求先触发 $t_0$ 后触发 $t_1$。只需在触发编辑器里完成如下步骤:
|
||||
- 为 $t_1$ 触发勾上“禁止”选项;($t_0$ 无需再做处理)
|
||||
- 在 $t_0$ 触发的行为(或者结果)页面中,添加一项(行为类型选中 53,参数选择 $t_1$ 触发)。
|
||||
|
||||
如需链式允许一系列触发,比如 $t_1 \to t_2 \to \ldots \to t_n$ ,也是如法炮制。以此类推,就形成触发链:
|
||||
|
||||

|
||||
|
||||
而在程序代码当中,顺序是通过代码行的先后次序来体现的:
|
||||
```python
|
||||
x = input("输入 x:")
|
||||
y = input("输入 y:")
|
||||
print(x + y)
|
||||
```
|
||||
可以很清楚地看出,这段程序先读入`x`,后读入`y`,最后输出它们两相加 ~~(实际上是拼接)~~ 的结果。**自上而下、逐行执行**,这就是程序代码的顺序结构。
|
||||
|
||||
从 [2.1 一节](#_2-1-触发的逻辑本质)的讨论可知,触发在逻辑上可以表达成`if`单分支的伪代码。那么不妨将这些`if`也按照地图师期望的顺序,自上而下地排列起来:
|
||||
```python
|
||||
# t0. start
|
||||
if anyEvent: # P8 任何事件(当*单独使用*时,它会令触发*立即执行*)
|
||||
lock_input() # Q46 禁止用户输入
|
||||
play_speech("EVA_EstablishBattlefieldControl") # Q21 播放 EVA 语音
|
||||
|
||||
# t1. intro.brief.A
|
||||
if elapsedTime(6): # Q13 流逝时间
|
||||
text_trigger("mission:naosi_A") # Q11 文本触发
|
||||
```
|
||||
其中,$t_0$ 触发的条件是`anyEvent`。结合右边的注解和前面对于`if`执行过程的讨论,这个条件肯定是恒满足的,也就是为真(`True`)。
|
||||
|
||||
上述代码段的排布与前面的案例一致,$t_1$ 是排在 $t_0$ 后面的,这样 $t_1$ 必定会等 $t_0$ 先触发;除此之外,$t_1$ 还往右缩进,与 $t_0$ 的那两条结果对齐,意味着 $t_0$ 不触发时,绝对不会触发 $t_1$,以此避免独立触发和触发链的歧义。
|
||||
|
||||
但随着触发链越来越长,这种层层缩进的形式也会因为不同结果的穿插变得混乱不堪。除此之外,这种表示也很难解释行为*54 禁止触发*。由于本人水平有限,如何解决这些缺陷、让逻辑表达更贴近 Q53、Q54 的表述,就权当是留给有兴趣的读者一道思考题吧。
|
||||
|
||||
### 2.3 循环结构
|
||||
早期扩展平台对于屏幕界面的利用并不充分,地图师只能每隔一段时间在屏幕左上方提示玩家当前的任务目标。那么像这种需要**重复执行同一个流程**的结构,就叫循环结构。
|
||||
|
||||
对于循环结构,[已知的触发教程](https://www.bilibili.com/video/BV1Zw411y7DZ?spm_id_from=333.1387.collection.video_card.click)大概会让你关注触发的「重复类型」:
|
||||
|
||||

|
||||
|
||||
通常来说,选择 *2 - Repeating OR* 那一项便足以满足很多简单的重复需求。
|
||||
|
||||
::: info 重复类型
|
||||
事实上,用重复这个概念描述触发(实际上是标签)的类型并不准确。由于本文内容不允许做过多展开,有机会再单独做个“实验”。
|
||||
:::
|
||||
|
||||
而在程序代码中,`while`循环则取代`if`扮演这个执行重复主体的角色:
|
||||
```python
|
||||
while True:
|
||||
print("Hello World")
|
||||
```
|
||||
将上述代码粘贴进 Python IDLE 回车运行,你也能看到终端里打出来一行行`Hello World`,除非用任务管理器干掉这个进程,否则它会无休止地输出下去。
|
||||
|
||||
分析这个运行表现不难发现,它是类似下图图二的流程:首先它走到`while`处执行判断,由于条件恒满足,往下执行`print`;然后循环并没有结束,它重新回到`while`重复执行前面说的流程,将坏掉的乐土打字机事业推进下去~~爱莉希雅死辣~~。
|
||||
|
||||

|
||||
|
||||
上面的`Hello World`案例也是类似图二的流程。于是,重复触发可以用`while`循环表示:
|
||||
```python
|
||||
while elapsedTime(6): # 每隔 6 秒
|
||||
text_trigger("mission:naosi_enemyarmada") # 输出文本
|
||||
reinforcement("01014514") # Q7 援军(小队)
|
||||
```
|
||||
|
||||
### 2.4 线性多分支选择结构
|
||||
在触发设计中,有的时候还会希望在某一个节点,根据不同的情况分别做出不同的反应,这就涉及到了选择。选择结构是创建分支流程的重要组成部分,可用于实现**条件发生变化时流程也随之发生改变**的效果^3^。
|
||||
|
||||
选择结构其实前面 [2.1 一节](#_2-1-触发的逻辑本质)已经介绍过,并且基于触发运行的现象指出触发本质是“条件单分支结构”。由于存在阻塞等待,触发并不会主动判断条件不满足(为假)的情形,所以在实际应用中比起去实现双分支,地图师更倾向于关注**多个条件分支**之间如何组织。
|
||||
|
||||
> [!tip]
|
||||
> 实际上借助局部变量也可以实现双分支选择,但并不是本章的重点。详见 [3.3.1 小节](#_3-3-1-门电路的搭建)。
|
||||
|
||||
当大量的选择结构简单串联之后,实际上就构成了线性多分支结构^3^。而在涉及到多个条件时,不同条件之间的关系也会影响选择结构的实际效果。具体来说,是互斥与否的关系。
|
||||
|
||||
**互斥**是说,这组条件分支**不可能**同时满足,因此必然只会运行**其中一个**处理程序。用程序语言来说,就是`if-elif-else`。比如用公式法求一元二次方程,不可能 $\Delta > 0$ 还说方程找不到实根。
|
||||
**非互斥**是指,多条件中**至少有两条**同时满足,有可能**同时经过若干个**处理程序。比如说重叠区间:
|
||||
|
||||
- 若 $x \in (0, 233]$,则令 $x = -x$;
|
||||
- (反之)若 $x \in (220, 512]$,则又令 $x = x^3$。
|
||||
|
||||
如果没有加上那个“反之”,考虑输入一个特殊值 $x = 230$:首先 $230 < 233$,$x$ 变换成 $-230$;随后 $230 > 220$,对 $-x$ 做幂运算得出 $-12,167,000$。
|
||||
而一旦加上“反之”,上述处理就是互斥的:**“反之”要求 $0 < x \le 233$ 这一条件首先不满足**。对于特殊值 $230$,显然 $230 < 233$ 满足条件,$x$ 变成 $-230$ 就直接结束了。
|
||||
|
||||
由于触发的 $p \to q$ 语义无法通过逻辑上互斥实现选择,因此实践中更倾向物理实现这个互斥:一旦某一分支成功触发,就需要**尽快阻止触发其他分支**。实践中通常会考虑 *Q54 禁止触发*、摧毁选择器等方式,让用户**来不及**进入另外的分支。
|
||||
|
||||
比如近年来的子阵营指挥:设有三支部队分别从三个方向开进,你需要**选择**指挥其中一支。这种桥段通常会刷三个假单位充当选择信标,玩家选中一个就把那一路部队更改所属。那么这种情况会在玩家选择之后*立刻摧毁其他选择信标*(*Q119 摧毁所属方*)。
|
||||
|
||||
```python
|
||||
if objSelected(BeaconA):
|
||||
del BeaconA, BeaconB, BeaconC
|
||||
...
|
||||
# B、C 信标也是一样,就不再展开了。
|
||||
```
|
||||
|
||||
非互斥的多分支实际上就是**顺序地经过这些条件分支**,如此便又回归顺序结构的处理方式。其中比较典型的设计类似数学上的“大、小前提”。
|
||||
考虑一个争夺战:玩家与敌军互相争夺油田,但狡猾的敌军可能直接摧毁油田。假如有两个 Phobos 扩展局部变量充当计数器:
|
||||
- `remaining`计算地图上还有多少油田。若余量小于玩家应占数目,任务失败;
|
||||
- `player_owns`计算玩家占了多少油田。大于等于应占数目,任务完成。
|
||||
|
||||
那么当一个油田 *P48 被摧毁* 时,首先想到余量`remaining`减一。*如果这个油田先前是玩家持有*,那就再对`player_owns`减一。简单的允许触发即可:
|
||||
|
||||
```python
|
||||
if objDestroyed(OilA):
|
||||
remaining -= 1 # 编辑变量 (Phobos)
|
||||
if oil_A_captured and objDestroyed(OilA):
|
||||
player_owns -= 1
|
||||
```
|
||||
|
||||
## 三、局部变量的程序逻辑分析
|
||||
前面的分析都是基于触发系统本身,贴的代码也只是对假想条件和结果的调用。而在地图创作中,除了直接利用已有的条件库,还会用局部变量间接地做条件判断。为了讲清楚它如何参与到触发逻辑当中,不妨引入经典案例“运输船找妈妈”。
|
||||
|
||||
### 3.1 案例实现思路
|
||||
“运输船找妈妈”简单来说,就是在**乘客还有事要做**的情况下,让运载乘客的**载具打哪来,回哪去**。观察一下 YR S01:时空转移的相关演出,易得流程如下:
|
||||
|
||||
- **满载**的运输船从地图外刷出,移动到定点;
|
||||
- 等待所有乘客下船。然后乘客有可能还会打光几秒、更改所属方……;
|
||||
- **空载**的运输船从定点返回地图外。
|
||||
|
||||
难点就在于,如何得知**卸出乘客后船是空的**。翻看条件库,好像也没有类似的选项。
|
||||
于是考虑设`apc unload`变量初始为 0,然后在运输船卸出乘客之后*Q56 设置局部变量*,触发得知 *P36 局部变量被设置* 了,就建一个空船小队把它拉走。
|
||||
|
||||
> [!note]
|
||||
> 篇幅原因,具体实现步骤我就不贴出来了。这种实用向教程应该也很容易找到。
|
||||
|
||||
### 3.2 案例逻辑分析
|
||||
已知这一经典例子中运用了局部变量,那么引入局部变量后触发逻辑有什么变化呢?
|
||||
|
||||
首先考虑局部变量的位置。从语义、代码语法上说,不可能未经定义就使用一个变量——好比解应用题,只设了未知数`x`却凭空跑出个`y`来。
|
||||
|
||||
在 FA2 的局部变量窗口中,你需要为局部变量起名(声明),然后 FA2 默认会令它等于 0(初始化,当然你可以改这个初始值)。那么这些局部变量保存到地图里肯定也是带着初始值的。
|
||||
这些局部变量最终又被引擎读进内存,组成数列(或者说数组)。所以,**在游戏开始之前,这些变量就已经就位了**。
|
||||
|
||||
那么不妨把局部变量放在程序代码的开头:
|
||||
```python
|
||||
apc_unload = 0 # 多数编程语言并不允许变量名带空格
|
||||
if ...:
|
||||
...
|
||||
```
|
||||
既然声明了局部变量,那么就要用起来。触发里有一组事件和一组结果分别读写局部变量(设待操作的局部变量为 $x$):
|
||||
|
||||
- P36:局部变量被设定(为 1),即当 $x = 1$ 时
|
||||
- P37:局部变量被清除(0),即当 $x = 0$ 时
|
||||
|
||||
* Q56:设置局部变量(值为 1),即令 $x = 1$
|
||||
* Q57:清除局部变量(值),即令 $x = 0$
|
||||
|
||||
::: info 赋值与相等
|
||||
在数学中描述等量关系用等号:`a = 0`时,……。同时,赋值也用等号:令`x = 1`。
|
||||
而在编程中二者是不同的运算。为了避免混淆,你经常会看到用`==`指代相等,用`=`来赋值。
|
||||
:::
|
||||
|
||||
那么上述案例便可以用如下伪代码表示:
|
||||
```python
|
||||
apc_unload = 0
|
||||
|
||||
if elapsedTime(20):
|
||||
play_speech("EVA_ReinforcementsHaveArrived")
|
||||
reinforcement("LCRFCome")
|
||||
|
||||
if apc_unload == 1:
|
||||
create_team("LCRFBack") # Q4 建立小队
|
||||
```
|
||||
|
||||
触发就是通过对局部变量值的变动,来实现一些较为复杂的逻辑判断。并且随着 Phobos 扩展了变量系统,触发对变量的判断也不再局限于 0 和 1 的“左手倒右手”,而是与科技类型、超级武器、随机数等联系了起来,实现更加精细的随机机制和流程控制。
|
||||
|
||||
### 3.3 高阶用法:逻辑门电路
|
||||
|
||||
触发条件仅以**逻辑“且”** 相连。其阻塞等待决定了它没有类似“否则”的设计,也就莫得直给的逻辑“非”;已知的触发实践也表明,多条件不可能在仅满足其中一个的情况下执行触发,无法直接判断逻辑“或”。那是否说明,触发就是做不到逻辑完备,如同早期面对平台限制一样无解呢?非也。
|
||||
|
||||
事实上,依据软硬件的逻辑等价性原理,触发确实可以做到或、非逻辑的判定。但显然,用累加去实现相乘,比起直接列个竖式配合九九乘法表,总是麻烦得多。自行实现的或、非逻辑也一样。
|
||||
|
||||
#### 3.3.1 门电路的搭建
|
||||
|
||||
> “逻辑智能所有的基础,都不过是这小小的开关。”
|
||||
> ::: right
|
||||
> ——ElectroBOOM
|
||||
> :::
|
||||
|
||||
在 [1.2 一节](#_1-2-局部变量)中已知,原版的局部变量只有`0`和`1`两种取值,恰如一个“开关”。前面两个小节的实例分析也展示了局部变量作为“开关”,在触发系统中的**辅助判断**作用。有了开关,就可以人为建立起“或”、“非”,乃至更为复杂的逻辑通路。
|
||||
|
||||
我们不妨从先前一直遗漏的“条件双分支结构”入手。很显然,双分支摘去一支就退化为了单分支;反过来,双分支的真、假两路恰好是一个开关的两极。那么有没有可能,可以通过局部变量连结两个单分支,组合成一个“条件双分支”呢?这就是接下来要讨论的单刀双掷开关(SPDT)设计。
|
||||
|
||||
从 [3.2 一节](#_3-2-案例逻辑分析)的介绍可以看出,触发是取局部变量的**瞬时值**做的判断,变量值为`0`还是为`1`是两个独立事件。既然**逻辑上**通过局部变量反映了条件的真假,不妨假设经过这个双分支时,该局部变量的值“保持恒定”,于是得出以下的实现思路:
|
||||
```python
|
||||
# t0: if-else 双分支:条件判断器
|
||||
if elapsedTime(114514) and ...:
|
||||
your_local_var = 1
|
||||
# t1: 条件为真时的处理
|
||||
if your_local_var == 1:
|
||||
...
|
||||
# t2: 条件为假时的处理
|
||||
if your_local_var == 0:
|
||||
...
|
||||
```
|
||||
注意前面 2.4 讨论多分支时,还提到要“尽快阻止触发其他分支”。在上面这个简单模型中,令 $t_1$ 第一时间“禁止”$t_2$,反过来也一样,就可以了。
|
||||
|
||||
事实上,不单是局部变量,像 YR A03:集中攻击那关争抢电厂的桥段,“电厂是否属于玩家”也可以采用 SPDT 设计,直接判断关联电厂的归属是敌是友,并相应地允许(或禁止)敌军反占电厂的演出。
|
||||
|
||||
像这样将条件映射到局部变量,用变量代为影响执行流的“逻辑电路”思想,接下来会多次体现。
|
||||
|
||||
#### 3.3.2 或运算——殊途同归
|
||||
如今的任务有一个利好玩家的设计:如果你实在没钱(假定低于 $100),**或者**你的矿车被打没了,就给你派送几个钱箱子救急,所谓“战争援助”。不妨以这个设计为例。
|
||||
|
||||
那首先判断“缺钱”是有现成事件*52 金钱低于* 的;至于判断“缺少矿车”,在原版中可反向考虑用事件*20 生产特定类型的载具* 判断玩家生产了矿车。如果任务流程足够简单,玩家和其他 AI 阵营*绝不可能生产出相同种类的矿车*,也可以用 YR 的事件*61 科技类型不存在*。
|
||||
|
||||
条件输入、结果输出两端都 OK,就可以用局部变量搭建“或门”电路了:
|
||||
- 设一局部变量`player low funds`,**初值为 0**;
|
||||
- 触发 $I_1$:若玩家\*金钱低于\* $100,则令该变量值为 1;
|
||||
- 触发 $I_2$:若玩家补牛(没有牛车),也令该变量值为 1;
|
||||
- 触发 $O$:若该变量**值为 1**,则在指定路径点刷出奖励箱子。
|
||||
|
||||
对应的伪代码如下:
|
||||
```python
|
||||
player_low_funds = 0
|
||||
if creditsBelow(100):
|
||||
player_low_funds = 1
|
||||
if noMiner: # 取决于你怎么判断玩家缺牛车
|
||||
player_low_funds = 1
|
||||
if player_low_funds == 1:
|
||||
create_crate(0, waypoint=81) # 参数写 0 表示*大量金钱*
|
||||
```
|
||||
|
||||
“一真皆真”,这就是或门的精髓。当然 Python 里真正的`or`有短路特性,这里就不再展开了。
|
||||
|
||||
#### 3.3.3 非运算——逆向思维
|
||||
原版 RA2 A08 有这样一个任务流程:在心灵信标影响到谭雅之前摧毁心灵信标。换言之,要求指定时间内摧毁目标(也就是计时器**没有超时**,并且目标被摧毁)。
|
||||
|
||||
在开始之前,不妨先捋一捋这个条件组的逻辑。这实际上是个必要不充分条件:要保证*完成流程*,必需满足以下两个分条件:(一)没有超时,计时器没有走完;(二)目标被摧毁。但反过来,单纯“没有超时”推不出任务完成——目标可能还在。
|
||||
|
||||
由于*目标被歼灭* 这个分条件不需要非运算,因此重点关注 *流程能完成 $\Rightarrow$ 没超时* 这一分路。这一路条件按照设计得是真命题,原命题为真,其逆否命题也为真:*超时了 $\Rightarrow$ 流程完不成*。这样就得到非运算的关键实现了。
|
||||
|
||||
有思路之后打开触发编辑器,现有这些条件与计时器有关:流逝时间、计时器时间到、流逝游戏时间……无不判断一个计时器*超时*。根据刚刚得到的逆否命题,用局部变量实现“非门”电路:
|
||||
|
||||
- 设一局部变量`obj1 reachable`,**初始为 1**;
|
||||
- 触发 $I$:若计时器超时,则令该变量值为 0;
|
||||
- 触发 $O$:若变量值**仍为 1**(即未超时),且目标不再存在,则宣布任务完成。
|
||||
|
||||
对应的伪代码如下:
|
||||
```python
|
||||
obj1_reachable = 1
|
||||
if timeout: # 取决于你用 P13 P14 还是 P47
|
||||
obj1_reachable = 0
|
||||
if obj1_reachable == 1 and techTypeNotExist("NAPSYB"):
|
||||
play_speech("EVA_ObjectiveComplete")
|
||||
...
|
||||
```
|
||||
---
|
||||
|
||||
当然,虽然理想很美好,但从原版一直到纯 Ares(特别是 Hares 平台),100 个局部变量的限制依然不容忽视。对于有限的资源,还是需要做出合理的分配。
|
||||
|
||||
<!-- ## 四、触发时序与标签 -->
|
||||
|
||||
## 结论
|
||||
|
||||
综上所述,红警 2 的触发组件在任务设计当中举足轻重,把握其运行的逻辑,有利于互相交流(至少不需要甩一堆截图)、有利于把更精妙的脑洞付诸实践、有利于优秀触发设计的提炼与发掘,对于触发编写乃至地图创作都有重要意义。
|
||||
|
||||
触发系统在逻辑层面上类似`if`单分支语句的设计,使其就入门而言并无太大门槛;支持顺序、选择、循环三种结构,可以实现大部分任务所需的线性叙事。然而其在逻辑运算上又有所欠缺,稍微复合一点的或、非逻辑判断必须通过局部变量绕路实现,对于地图师的逻辑思维能力是一大考验。
|
||||
|
||||
## 参考文献
|
||||
|
||||
1. ModEnc. [Triggers](https://modenc.renegadeprojects.com/Triggers) \[EB/OL\], 1.31.2024, 6.17.2024.
|
||||
2. ModEnc. [VariableNames](https://modenc.renegadeprojects.com/VariableNames) \[EB/OL\], 5.16.2024, 6.17.2024.
|
||||
3. RN Studio. [map_tutorial](https://github.com/revengenowstudio/map_tutorial) \[EB/OL\], 4.29.2024, 5.6.2024.
|
||||
|
||||
> [!NOTE]
|
||||
> 便利起见,文献附注格式基于 GB/T 7714 规范作了简化。
|
||||
|
||||
## 致谢
|
||||
|
||||
时光荏苒,距离最开始做大白板雪原、遭遇战一样的战役地图应该也有九个年头了。能够写到这里,全拜各位大佬、同行、玩家朋友所赐。Lin 在此谢过诸位。
|
||||
|
||||
首先,感谢 RN Studio 指导本次“实验”。感谢制作组的地图教程,为“选题”指引方向;感谢 Zero Fanker 等人提出的指导和改进意见,以及触发实际执行的补充、佐证和更正。
|
||||
|
||||
其次,感谢曾经与仍在为红警 2 模组创作贡献智慧的各路人才,为论证提供各种帮助。特别感谢地图师同行们对触发系统所作的各种实地测试和表述修订,同时感谢 Phobos 团队开源触发组件的扩展实现,以及 Heli 等人为简化地图开发所做的各种尝试。
|
||||
|
||||
最后,感谢各位喜爱战役的红红玩家,特别是《星辰之光》的测试员和玩家朋友们拨冗测试和体验。
|
||||
|
||||
## 后记
|
||||
比起“逻辑”,modder 们始终更在乎切实的、能实装进游戏和编辑器的改善。除此之外,红警 2 的触发逻辑本身就与“引擎实现”密不可分。比如:
|
||||
|
||||
- 越靠近`[Triggers]`小节头的触发越容易触发;
|
||||
- *通过所属方的启动是经由所属方的某个函数执行的,此函数调用为**每 8 帧一次**,根据所属方列表从上而下依次执行。*([FA2spHDM](https://github.com/handama/FA2sp/releases) 附文档)
|
||||
- *包含两个**非持续伴随事件**的触发永远不会启动,因为这两个事件是相继发生的,而不是同时发生的,因此永远不可能同时满足条件。*(FA2spHDM 附文档,有关概念参见 [Ares 文档](https://ares-developers.github.io/Ares-docs/new/triggerevents.html))
|
||||
- ……
|
||||
|
||||
凡此种种,都加深了我个人的自我怀疑——我写这些真的有意义吗?
|
||||
但至少我是很想找到“存在的意义”的。所以哪怕本文多么“空中楼阁”,至少就让它烂在这里吧。
|
||||
|
||||
::: right
|
||||
04.19.2025
|
||||
:::
|
12
docs/archives/README.md
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
title: 综述
|
||||
index: false
|
||||
article: false
|
||||
timeline: false
|
||||
icon: folder-open
|
||||
category: ''
|
||||
---
|
||||
|
||||
“综述”这边的博文通常比较正式一些,编排上会参考大学论文、实验报告,当然也经常会有迭代(综述嘛,追求严谨)。
|
||||
|
||||
<Catalog />
|
BIN
docs/archives/fa2_trigger_ui.webp
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
docs/archives/trigger_chain.webp
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
docs/diaries/Ag.webp
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
docs/diaries/Ca.webp
Normal file
After Width: | Height: | Size: 86 KiB |
BIN
docs/diaries/Cl.webp
Normal file
After Width: | Height: | Size: 53 KiB |
88
docs/diaries/about-me.md
Normal file
@ -0,0 +1,88 @@
|
||||
# 关于我的三个拟设
|
||||
早在 2015 年的时候,我就开始亦步亦趋地制作 ~~(仿造)~~ 一些东西。早期说实话也没想过把这些东西发布出去,顶多在初中同学之间那里分享着玩。直到两年后的一节化学课上,我才突发奇想地决定挪用三个常见的元素——Ca(钙)、Cl(氯)、Ag(银)——作为我昵称的基底,一直沿用至今。
|
||||
|
||||
最近也是经历了许多烂事,我也好久没有机会去维护自己的博客了。既然 Ag 这个网名也终于启用,我想是时候补全一下那三个基于化学元素拟定的网络形象了。
|
||||
|
||||
> [!note]
|
||||
> 由于一些意外,原有的所有拟设图均已佚失,只能从用过的 QQ 头像里找到裁剪版。
|
||||
>
|
||||
> 尽管如此,还是非常感谢当年无偿为我绘制立绘的亲友们。
|
||||
|
||||
## 钙
|
||||
- 生命周期:2017-2021
|
||||
- 种属:兽娘·猫郎[^moewiki_catboy]
|
||||
- 瞳:青绿,猫瞳
|
||||
- 代表色:白
|
||||
- 性格:内向
|
||||
- 配偶:Cl(氯)
|
||||
- 喜欢的:苹果;尝试、摸索、折腾
|
||||
- 讨厌的:阅读理解(
|
||||
|
||||
[^moewiki_catboy]: 这种分类取自[萌娘百科](https://zh.moegirl.org.cn/%E7%8C%AB%E9%83%8E)。简而言之就是“猫少年”,或者说“具有猫部分特征的男孩子”。
|
||||
|
||||

|
||||
|
||||
最早启用的设定。虽然理论上任何钙离子组成的盐都可以指代我,但中学阶段最常见的沉淀果然还是 CaCO~3~ 吧。然后“碳酸钙摘掉两个氧”,最初的名字——Caco 就确定下来了。后来又衍生出音译“卡扣”、Casheen 和相应音译“卡伸”。
|
||||
老朋友们大抵还是愿意叫我“卡”这组名字,特别是接触过的红警 2 modder 和地图师。
|
||||
|
||||
猫少年啊……说实话现在回头想想,可能是受到初中同好的影响也说不定。不过印象中被说“可爱”确实是很早就开始了。(话说真的可爱吗?)
|
||||
总之上图已经是我能找到尽可能完整的立绘了,看上去不需要额外添几笔细致的外貌描写。
|
||||
|
||||
> “他呀……是个很可爱的家伙呢。初见看着很腼腆,相谈甚欢了又开始滔滔不绝,和我独处又脸红得像熟透的苹果一样。
|
||||
> “hmm?当初怎么认识的啊……说来有点搞笑。他一开始的个人简介整了个莫名其妙的反应方程,然后我看着好玩就和他聊上咯。再然后?嘻嘻,自己猜去。
|
||||
> “搞起熟悉的东西来能心无旁骛搞个通宵……啧,有时候还挺羡慕他的。但…再怎么说也稍微陪陪我啊。明明他自己也很喜欢抱抱的说。
|
||||
> “唉,明明说好了做我的专属的……到头来还是把我抛下了嘛……”
|
||||
> ::: right
|
||||
> ——ChlorideP
|
||||
> :::
|
||||
|
||||
## 氯
|
||||
- 生命周期:2022-2025.3
|
||||
- 种属:幽灵(♀)
|
||||
- 瞳:黄绿,类人瞳孔
|
||||
- 代表色:黄绿
|
||||
- 性格:寡言、孤僻
|
||||
- 配偶:Ca(钙)
|
||||
- *伴侣:Ag(银)
|
||||
- 喜欢的:听故事和讲故事
|
||||
- 讨厌的:毫无营养的信源
|
||||
|
||||

|
||||
|
||||
在 Caco 因故被一撮原神同人女攻击之后不久,钙的形象弃用,咱也随即改名为 Chloride Pussemi,即 ChlorideP 了。而后有人因为末尾这个 P 以为我是 VOCALOID 曲师(P 主),加上这个昵称全小写起来并不方便手写,遂又更名为 NyaCl.
|
||||
|
||||
氯的种属启发自氯单质(氯气)的物理性质。作为气体,它是有在空中的那种飘飞感的,不难想到幽灵也是在空中飘飞的存在。于是就决定是“黄绿色的幽灵”这样的形态。上图是 Q 版氯,其实还有一版正式立绘,是个大姐姐的形象。~~可惜弄丢了。~~
|
||||
|
||||
后来经历的一些事情让我的精神状态逐渐贴合氯气的化学性质:有毒、刺鼻。有时候的确觉得自己到处倒垃圾很困扰人;应激起来说话很大声,不也挺“刺”耳的。(苦笑)
|
||||
|
||||
> “你说她?我的食物罢了。知不知道一颗电子对阳离子来说有多么诱人?
|
||||
> “刚找上她的时候她还在郁郁寡欢呢。结果呢?还不是顺从本能乖乖交粮。(舔唇)
|
||||
> “不过还别说,强氧化的元素就是不一样,比什么碳酸根的美味多了。听说她已故的另一半也和碳酸根有点关系?乐。
|
||||
> “后来一来二去的,她似乎也走出来了,愿意和我一起贴贴……嗯哼,就这点来说我还是很喜欢她的。
|
||||
> “最后倒是和她贴了个痛快。不知道她觉得如何,我是觉得她这结局挺好的。好歹舒服地享受完最后一刻。
|
||||
> “就是可惜……再也找不到这么好的食物咯……(叹气)”
|
||||
> ::: right
|
||||
> ——Ag.L
|
||||
> :::
|
||||
|
||||
## 银
|
||||
- 生命周期:2025.4-
|
||||
- 种属:兽娘·猫娘(亚种,魅魔混血)
|
||||
- 瞳色:粉红,爱心瞳
|
||||
- 代表色:银白(亮白)
|
||||
- 性格:对外满不在乎,私底下很细腻
|
||||
- 喜欢的:涩涩(无论主动被动)
|
||||
- 讨厌的:烦心事
|
||||
|
||||

|
||||
|
||||
> [!note]
|
||||
> 将来有时间和闲钱的话,再为 Ag 这个形象重新约张稿吧。
|
||||
|
||||
银这个设定实际上直到去年才给她勾个轮廓——**白丝魅魔猫娘少女**。乍一听这四个词组合在一起很违和。说实话我也觉得(
|
||||
|
||||
首先说说“魅魔”吧。既然银这个字有着一层谐音关系(所谓“银梦”嘛),那么我就往涩涩的方向去考虑了。所以给她的定型是魅魔元素。
|
||||
但一来我说话做不到大多数黄油的魅魔那么“诱人”,二来我养成的猫娘口癖根深蒂固,这就决定了**立绘主体仍然是猫娘**。那么怎么办呢?那就把猫尾巴替换成魅魔尾巴罢。“猫娘身上”的“魅魔尾巴”,很奇特的组合对吧(
|
||||
剩下来的要素就很简单了:白丝一般显得清纯(相对地黑丝会显得诱惑一些),穿在“摇着魅魔尾巴的猫娘”身上增添些反差感;少女嘛……可以保留一些卡哇伊的感觉。(但实际想象起来好像更偏雌小鬼一点)
|
||||
|
||||
设定上魅魔尾巴非常敏感,只是碰触就会浑身战栗的程度。若是摸上那么一两下说不定就开始发情了吧。
|
16
docs/diaries/keqing-stocking.md
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
date: 2024-12-31
|
||||
article: false
|
||||
---
|
||||
|
||||
# 炒冷饭:刻晴裤袜
|
||||
第一张公开放出的女装(?)图。
|
||||
|
||||
> 感觉还是黑巧克力适合我(?)
|
||||
> 白丝一是太胖了驾驭不住,二是腿毛没怎么修剪,容易露馅。
|
||||
|
||||
袜子淘宝随便买的,我也没什么钱和经验去仔细挑。
|
||||
|
||||
拍的时候主包可能已经 85+kg 了。最近有在减肥喵,但说实话对于女装,我不抱希望。
|
||||
|
||||

|
12
docs/diaries/readme.md
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
title: 随想
|
||||
index: false
|
||||
article: false
|
||||
timeline: false
|
||||
icon: paper-plane
|
||||
category: ''
|
||||
---
|
||||
|
||||
可能是小作文(或者说牢骚),可能是女装(不过主包太肥了,大概只能拍些并不好看的腿子),也可能是乱七八糟。
|
||||
|
||||
<Catalog />
|
15
docs/friends/index.md
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
layout: FriendsHome
|
||||
icon: link
|
||||
index: false
|
||||
title: 友链
|
||||
heroText: SilverAg.L
|
||||
heroFullScreen: true
|
||||
article: false
|
||||
---
|
||||
|
||||
> [!note]
|
||||
> ::: center
|
||||
> 因服务迁移,现需要移步 Profile Repo [提交友链申请](https://github.com/AgxCOy/AgxCOy/issues),并待我核验添加。
|
||||
> 非常抱歉给你带来不便。
|
||||
> :::
|
382
docs/notes/OS/ArchInstall.md
Normal file
@ -0,0 +1,382 @@
|
||||
---
|
||||
category:
|
||||
- 操作系统
|
||||
- Linux
|
||||
tag:
|
||||
- Arch Linux
|
||||
- KDE
|
||||
- Wayland
|
||||
- X11
|
||||
star: true
|
||||
---
|
||||
|
||||
# Arch Linux 个人安(折)装(腾)流程
|
||||
<!-- https://www.glowmem.com/archives/archlinux-note -->
|
||||
<!-- https://arch.icekylin.online/guide/-->
|
||||
|
||||
::: details 我选择 Arch 的理由
|
||||
1. 比起“服务”,我还是更倾向于把操作系统当作纯正的工具。可能这就是旧信息时代遗老吧。
|
||||
2. “缘,妙不可言”。
|
||||
3. 具体工作具体分析吧。跨平台开发 Arch 也挺舒服的。但 Adobe 全家桶就显然不适合了。
|
||||
:::
|
||||
|
||||
其实这篇笔记说是“流程”,更像是“避坑指南”。因为文中大部分实际操作步骤都是直接贴的参考外链。
|
||||
我只是为了补充些注意事项,免得以后重装起来忘掉而已。
|
||||
|
||||
> [!important]
|
||||
> 由于 Arch 更迭速度比较快,下面的参考链接以及这篇笔记本身的内容可能随时失效。
|
||||
> 在安装、使用过程中遇到的,这里没有提及的问题,还请自行 Google、Bing 或 Baidu。
|
||||
>
|
||||
> 如果你觉得 Arch 滚动更新很累、玩不太明白,不妨还是先上手`Pop!_OS`或者`Ubuntu`。
|
||||
>
|
||||
> 此外,也可以多多留意其他人总结的 Arch 折腾小技巧,说不定会有意外收获。
|
||||
|
||||
## 参考链接
|
||||
|
||||
本文有参考以下两篇安装教程:
|
||||
|
||||
1. [律回彼境:Arch Linux 折腾指南&记录](https://www.glowmem.com/archives/archlinux-note)(以下简称「律回指南」)
|
||||
2. [Nakano Miku:Arch 简明指南](https://arch.icekylin.online/guide/)(以下简称「Miku 指南」)
|
||||
|
||||
我的笔记**以律回 MelodyEcho 的流程为主**,偶尔穿插一下 Miku 指南。
|
||||
|
||||
## 零、前期准备
|
||||
|
||||
这部分无需赘述,主要工作就是**下载 Arch 安装镜像**,并把它**刻录到 U 盘里**。
|
||||
网上对此已有很多成熟的教程,恕不在这里浪费时间了。但有两件事稍微还是要注意一下。
|
||||
|
||||
首先是主板设置^1^。如今的主板应该都允许使用 UEFI 了,故本篇笔记也不会考虑传统 BIOS 引导,你需要**确保主板是 UEFI 启动**;
|
||||
除此之外,**需要关闭“安全启动”**(Secure Boot)。UEFI 的这一安全机制会拒绝执行未签名的启动文件,而不幸的是,Arch 食用的 Linux 内核以及 Grub 等加载器的确没有签名,并且签名的“手续”很麻烦。
|
||||
|
||||
其次是你的 WiFi 名字^2^。在 Arch 的 LiveCD(维护环境,下同)里,大部分安装步骤都需要手敲命令来完成,并且**无法输入、显示中文**。
|
||||
如果你打算用 WiFi 连接,不妨提前更一下名。
|
||||
|
||||
> [!tip]
|
||||
> 如果只是迁移系统,那么进入维护环境之后只需`rsync`做全盘搬运即可(当然前提是目标**盘**要比原**系统**的实际占用空间要大)。可参见 [lin.moe](https://lin.moe/tutorial/2020/04/arch_migrate/)。
|
||||
|
||||
## 一、联网并设置时区
|
||||
|
||||
U 盘启动 PE 相信很多人都操作过,或者看过教程。LiveCD 也是这么启动的。但与 Ubuntu 可选联网不同,Arch 的安装**必须联网**。
|
||||
本章可直接阅读律回指南[第一章「连接网络和时区配置」](https://www.glowmem.com/archives/archlinux-note#toc-head-2)。直至 6 月初此法仍然可用(
|
||||
|
||||
## 二、硬盘分区、格式化和挂载
|
||||
|
||||
由于`ext4`调整分区大小并不是很方便,我改用了`Btrfs`文件系统,备份则采用`RSYNC`异盘同步和`Btrfs`同盘快照。你仍可以参照律回指南[第二章「分区、格式化与挂载」](https://www.glowmem.com/archives/archlinux-note#toc-head-3)去操作。
|
||||
|
||||
::: details 补充一下律回的 fdisk 操作流程
|
||||
> 注:下列内容只考虑全新安装。
|
||||
|
||||
首先需要知道你要操作哪块硬盘:`lsblk`命令可以以树形结构呈现出当前都连着什么硬盘,都分了什么区。
|
||||
|
||||
然后`fdisk /dev/<disk_id>`进入操作控制台,比如`fdisk /dev/nvme0n1`。
|
||||
在控制台里可以敲`m`看帮助。清空重新分区大致的操作如下(是的,都是单字母喔):
|
||||
|
||||
- `g`:新建 GPT 分区表。
|
||||
- `d`:如果你不想动分区表,那么就用 d 一个一个删除分区吧。
|
||||
- `n`:用 n 逐个添加新分区。
|
||||
|
||||
> [!tip]
|
||||
> 其中,`n`之后需要指定这是第几个分区,以及分区多大。
|
||||
> 分区的大小默认用扇区表示,你可以改用`+120G` `+256M` `+512K`这种我们更熟知的`GB` `MB` `KB`单位。
|
||||
> 如果你原先的硬盘不是“未分配”状态(也就是使用过,分过区),那么新建时可能会提示你删除文件系统标识。`y`允许即可。
|
||||
|
||||
- `t`:对于 EFI 引导分区,需要借此更改分区类型为 EFI System(即 ESP)。
|
||||
|
||||
> [!info]
|
||||
> 此举是出于兼容考虑。诚然较新的主板可以搜索任意 FAT 分区中的 EFI 文件,但旧主板未必,Windows 的`bootmgr`未必。
|
||||
> 当然也有些固件可能只认 Windows 的`bootmgfw.efi`,如此不妨试试 [UKI](./ArchUEFI.md#统一内核映像-uki)。
|
||||
|
||||
- `w`:保存并应用分区表更改。DiskGenius 改分区也不是设置完立马生效的嘛。
|
||||
:::
|
||||
|
||||
Miku 指南则由于假定保留 Windows 系统分区,对分区方案的介绍实际上拆成了两部分:
|
||||
|
||||
- :new: [全新安装](https://arch.icekylin.online/guide/rookie/basic-install-detail#%F0%9F%86%95-%E5%85%A8%E6%96%B0%E5%AE%89%E8%A3%85)
|
||||
- [7. 分区和格式化(使用 Btrfs 文件系统)](https://arch.icekylin.online/guide/rookie/basic-install.html#_7-%E5%88%86%E5%8C%BA%E5%92%8C%E6%A0%BC%E5%BC%8F%E5%8C%96-%E4%BD%BF%E7%94%A8-btrfs-%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F)
|
||||
|
||||
> [!tip]
|
||||
> 我个人的分区方案是:
|
||||
> - ESP:挂载`/boot`
|
||||
> - 系统分区:挂载`/`;若是 Btrfs,另开子卷挂载`/home`
|
||||
> - 交换分区
|
||||
>
|
||||
> 这么分可以相对灵活地调整交换分区,因为总体来说调整分区右端比调整左端容易。
|
||||
|
||||
分区用`fdisk`和`cfdisk`都可以。完事了格式化、挂载文件系统即可。两篇指南对此都有叙述。
|
||||
|
||||
## 三、安装系统
|
||||
|
||||
我个人偏向于律回指南。但在正式安装之前,还有一些事要做。
|
||||
|
||||
### 3.1 pacman 镜像源配置
|
||||
|
||||
Linux 的包管理器默认用的国外的软件源,`pacman`也一样。因此,非常建议先换用国内镜像,加快包下载速度。
|
||||
|
||||
编辑`/etc/pacman.d/mirrorlist`(`vim`还是`nano`请自便):
|
||||
```ini
|
||||
# 在文件开头起一空行,复制下列镜像源:
|
||||
Server = https://mirrors.tuna.tsinghua.edu.cn/archlinux/$repo/os/$arch
|
||||
Server = https://mirrors.ustc.edu.cn/archlinux/$repo/os/$arch
|
||||
Server = https://mirrors.aliyun.com/archlinux/$repo/os/$arch
|
||||
```
|
||||
> [!tip]
|
||||
> 变更后的`mirrorlist`会在 Arch 安装过程中被复制过去。这样后续就不需要再做一遍换源了。
|
||||
|
||||
### 3.2 包管理器配置
|
||||
默认`pacman`是逐个下载软件的。但哪怕是 1MB/s 小水管,并行下载四、五个软件包也绰绰有余了。
|
||||
|
||||
编辑`/etc/pacman.conf`:
|
||||
|
||||
1. 找到`# Misc options`,删掉`Color` `ParallelDownloads = 5`前面的注释`#`:
|
||||
```ini
|
||||
# Misc options
|
||||
#UseSyslog
|
||||
Color # 输出彩色日志
|
||||
#NoProgressBar
|
||||
CheckSpace
|
||||
#VerbosePkgLists
|
||||
ParallelDownloads = 5 # 最大并行下载数(根据你的网速自行斟酌,不建议写太大)
|
||||
```
|
||||
|
||||
2. 翻页到文件末尾,删掉`[multilib]`和底下`Include =`这两行的注释`#`。
|
||||
> `multilib`是 32 位软件源。默认下载的包都是`x86_64`的,而有一些程序仍需要 32 位的库。
|
||||
|
||||
> [!note]
|
||||
> 很遗憾,经实测 pacman 配置并不会复制过去。在安装完系统`arch-chroot`进去进一步配置时,你需要重复做一遍上述操作。
|
||||
|
||||
### 3.3 正式部署
|
||||
|
||||
在换源、调整设置之后,`pacman -Sy`更新软件库,然后你就可以安装最新版的 Arch Linux 了。
|
||||
具体的安装步骤参见律回指南的[第三章「安装系统」](https://www.glowmem.com/archives/archlinux-note#toc-head-4)
|
||||
和[第四章「系统基本配置」](https://www.glowmem.com/archives/archlinux-note#toc-head-5)两章。以下仅作补充。
|
||||
|
||||
> [!note]
|
||||
> 确保`pacstrap`只执行一次——我不清楚重复刷入系统会不会误覆盖些啥东西。
|
||||
|
||||
::: warning AUR 助手
|
||||
Miku 指南强调过“不要(在 LiveCD 里)提前配置`archlinuxcn`源”,而`yay` `paru`等 AUR 助手恰好是 CN 源提供的软件包。
|
||||
所以直到重启进入新系统之前,也千万不要试图安装 AUR 助手喔。
|
||||
:::
|
||||
|
||||
## 四、系统基本配置
|
||||
跟着律回指南的三、四章装好系统之后,重启登入新系统的终端。你现在应能通过`nmtui`连上 WiFi 了。
|
||||
|
||||
### 4.1 CN 源和 AUR 助手
|
||||
在**联好网的新系统**里配置`archlinuxcn`源:`sudo nano /etc/pacman.conf`
|
||||
```ini
|
||||
# 末行添加
|
||||
[archlinuxcn]
|
||||
Server = https://mirrors.tuna.tsinghua.edu.cn/archlinuxcn/$arch
|
||||
```
|
||||
并安装 CN 源的签名密钥和 AUR 助手:
|
||||
```bash
|
||||
sudo pacman-key --lsign-key "farseerfc@archlinux.org" # 为密钥环添加本地信任
|
||||
sudo pacman -S archlinuxcn-keyring # 安装密钥环
|
||||
sudo pacman -S yay paru # 安装 AUR 助手
|
||||
```
|
||||
::: info 关于本地信任 Key
|
||||
简单来说就是给 CN 源密钥环签名的`farseerfc`他的 Key 掉信任了,包管理器“不敢”安装这个密钥环^2^。
|
||||
:::
|
||||
|
||||
### 4.2 硬件(一)音频安装
|
||||
|
||||
音频分为固件(或者说驱动)和管理套件两部分:
|
||||
```bash
|
||||
# 音频固件
|
||||
sudo pacman -S sof-firmware alsa-firmware alsa-ucm-conf
|
||||
# pipewire 及其音频管理套件
|
||||
sudo pacman -S pipewire gst-plugin-pipewire pipewire-alsa pipewire-jack pipewire-pulse wireplumber
|
||||
```
|
||||
::: tip pulseaudio
|
||||
除了 pipewire 音频方案之外另有`pulseaudio`可供选择。但务必注意:音频管理套件**只能二选一,不可以混装**。
|
||||
|
||||
另外,由于 pipewire 本身不单只负责音频管理的工作,如需装 pulseaudio 仍需安装`pipewire` `gst-plugin-pipewire`两个包。
|
||||
相应地,其余的包可换用如下平替:
|
||||
|
||||
- `pipewire-alsa` → `pulseaudio-alsa`
|
||||
- `pipewire-jack` → `jack2`
|
||||
- `pipewire-pulse` → `pulseaudio`
|
||||
- `wireplumber` → `pipewire-media-session`(pipewire 弃用)
|
||||
|
||||
> 由于 pipewire 那边有 wireplumber 代替,所以这个包被他们自行标记为“过时”。
|
||||
> 但 pulseaudio 仍需要这个包。
|
||||
:::
|
||||
|
||||
显卡、蓝牙等其他硬件设施需要在装好桌面环境后再考虑。至少**到本小节为止你的系统里并没有蓝牙服务**,无法启用。
|
||||
|
||||
## 五、KDE 桌面环境
|
||||
|
||||
跟完我的第三章,律回指南的三、四章,还有我的第四章之后,你便拥有了一个无 GUI 的终端 Arch 系统。
|
||||
但作为日常使用的话,图形桌面肯定必不可少。
|
||||
|
||||
本文与那两篇参考外链一样**采用 KDE 桌面环境**。当然除了 KDE 之外,你也可以考虑 GNOME 桌面环境 ~~(只是我用腻了)~~;
|
||||
也可以考虑散装方案(比如`hyprland`~~,只是我没装成功~~)。
|
||||
|
||||
::: info KDE 6 vs KDE 5?
|
||||
目前最新版本为 KDE 6。但律回指南发布于 23 年 11 月,介绍的是 KDE 5。
|
||||
|
||||
话虽如此,倒也不必惊慌。`pacman`以及`yay` `paru`之流均**默认安装最新版**,以下**安装 KDE 5 的步骤仍可用于安装 KDE 6**:
|
||||
```bash
|
||||
# 分别安装 xorg 套件、sddm 登录管理器、KDE 桌面环境,以及配套软件
|
||||
sudo pacman -S xorg
|
||||
sudo pacman -S plasma sddm konsole dolphin kate okular spectacle partitionmanager ark filelight gwenview
|
||||
# 启用 sddm 服务,重启进 SDDM 用户登录
|
||||
sudo systemctl enable sddm
|
||||
sudo reboot
|
||||
```
|
||||
:::
|
||||
|
||||
重启后在用户登录界面输入密码回车,恭喜你,距离投入日常使用只剩几步之遥了。之后对 KDE
|
||||
和系统的配置**大部分**仍可参考律回指南的[第六章「桌面环境配置」](https://www.glowmem.com/archives/archlinux-note#toc-head-7)。
|
||||
|
||||
### 5.1 关于 Wayland 和 X
|
||||
KDE 的图形实现默认已经是 Wayland 了。在开机后输入用户密码的界面处,找找屏幕边角,你可以看到默认选用`Plasma (Wayland)`。
|
||||
点击它,你可以选择换用`Plasma (X11)`。
|
||||
尽管 X11 有个「锁屏黑屏」的问题,但目前来说我还是推荐换回 X11。
|
||||
|
||||
::: note X11 锁屏黑屏
|
||||
KDE 的默认 Breeze 主题锁屏时大概率会出现黑屏、惟有鼠标的现象。在 7 月中旬时已经发现该现象已经蔓延到自定义主题了。查了下 Google 以及 Arch、Manjaro、KDE 的一些讨论帖,尚没有有效的解决方案。
|
||||
|
||||
当然有一些主题可能能够解除这个“病征”,像我之前装的 Nordic Dark,以及现在的 Lavanda。这种 work around 可能还是因人而异。
|
||||
:::
|
||||
|
||||
### 5.2 硬件(二)显卡驱动与蓝牙
|
||||
|
||||
> “so NVIDIA, F**K YOU! ”——Linus Torvalds
|
||||
|
||||
AMD 或 NVIDIA 显卡可参见律回指南[6.4 小节「显卡驱动安装」](https://www.glowmem.com/archives/archlinux-note#toc-head-11)
|
||||
和 Miku 版指南的[新手进阶—显卡驱动](https://arch.icekylin.online/guide/rookie/graphic-driver.html)篇。
|
||||
但我是锐炬核显捏,只需要在 Konsole 终端里`sudo pacman -S`安装图形 API:
|
||||
|
||||
- `mesa` `lib32-mesa`(OpenGL)
|
||||
- `vulkan-intel` `lib32-vulkan-intel`(Vulkan)
|
||||
- `intel-media-driver`(VAAPI 解码器,OBS 需要)
|
||||
|
||||
如果有蓝牙的话,在 Konsole 里启用(并立即启动)蓝牙服务:
|
||||
```bash
|
||||
sudo systemctl enable --now bluetooth
|
||||
```
|
||||
> 之前误以为`bluetooth`是 Arch 本身就有的服务,结果发现是桌面环境依赖了蓝牙组件包。
|
||||
|
||||
### 5.3 额外中文字体和输入法
|
||||
|
||||
律回指南安装的字体分别是 Noto 系列(Linux 常用的 Unicode 字体)和思源系列(也算是 Noto 系列的子集)。
|
||||
其中 Noto 系列的汉字部分由于一些神秘的原因,不做额外配置的话,渲染出来只能说……能用。
|
||||
|
||||
::: info 参考资料
|
||||
- [Arch Wiki:关于中文字被异常渲染成日文异体字的说明](https://wiki.archlinux.org/title/Localization/Simplified_Chinese#Chinese_characters_displayed_as_variant_(Japanese)_glyphs)^2^
|
||||
- [Arch 中文论坛:noto-fonts-cjk 打包变化可能导致的回落字体选取问题](https://bbs.archlinuxcn.org/viewtopic.php?pid=60100)^2^
|
||||
:::
|
||||
|
||||
::: note fontconfig
|
||||
`wqy-zenhei`^extra^(文泉驿)和`misans`^aur^会在安装过程中自动帮你配置 fontconfig,因此安装完这两款字体之后系统默认用这些字体显示。
|
||||
如果你希望使用未经适配的字体,那么需要在 KDE 设置里装好字体后,额外做字体配置。
|
||||
|
||||
示例:[思源系列字体配置](../../shared/01-Prefer.conf) By [@Vescrity](https://github.com/Vescrity)
|
||||
注意:用户级字体配置需放在`~/.config/fontconfig/conf.d`目录中。
|
||||
另注:使用`fc-cache -vf`刷新字体缓存。
|
||||
:::
|
||||
|
||||
至于输入法,律回指南推荐安装搜狗拼音`fcitx-sogoupinyin`^aur^,
|
||||
但它在星火 Wine 版网易云里似乎无法调出来,最近也无法安装(会卡在`build()`开头):
|
||||
```bash
|
||||
tar (child): data.tar.gz: Cannot open: No such file or directory
|
||||
```
|
||||
Miku 指南的[输入法介绍](https://arch.icekylin.online/guide/rookie/desktop-env-and-app.html#_10-%E5%AE%89%E8%A3%85%E8%BE%93%E5%85%A5%E6%B3%95)则推荐直接安装`fcitx5`。
|
||||
|
||||
::: warning 可能的版本冲突
|
||||
若先前配置搜狗拼音失败,你需要排查并移除已经安装的 Fcitx 4 组件,它与`fcitx5`冲突:
|
||||
```bash
|
||||
# 查询本地(local)已安装的软件库
|
||||
sudo pacman -Qs fcitx
|
||||
# 逐个移除,以 fcitx 为例
|
||||
sudo pacman -R fcitx
|
||||
```
|
||||
不用递归移除`-Rs`的原因是,递归可能移除掉你不希望干掉的包依赖。
|
||||
:::
|
||||
|
||||
至此,Arch 的安装告一段落,你可以像捣腾 Windows 那样玩转 Arch 了。
|
||||
|
||||
---
|
||||
|
||||
## 附录:系统美化
|
||||
|
||||
> “爱美之心,人皆有之。”
|
||||
|
||||
> [!tip]
|
||||
> - **风格统一**是美观的必要条件。
|
||||
> - 少搞“侵入性”美化。或者说,需要**修改系统文件、注入系统进程、破坏系统稳定的美化尽量少做**。
|
||||
> - **谨遵发布页面附送的安装指引**(KDE、GNOME 主题可以参考项目 GitHub),否则可能安装不全。
|
||||
|
||||
### I. 主题
|
||||
主题这边我也没啥好推荐的,虽然 KDE 6 现在也出现了一些比较好看的主题,但终究是因人而异吧。
|
||||
|
||||
我想说明的是,KDE 商店的多数主题在 **X11 会话、125% 甚至更高缩放率**下会出现“非常粗窗口边框,使我的窗口肥胖”的现象(至少我的笔记本如此)。
|
||||
我个人目前是直接修改主题 Aurorae 配置文件,利用二分法逐步找到四条边的最适 Padding。
|
||||
网上貌似也有“把缩放调回 100%,但是更改字体 DPI”的做法,但个人觉得显示效果应该好不到哪去(
|
||||
|
||||
### II. 仿 Mac 上下双栏布局
|
||||
KDE 原生的桌面 UI 就挺 Windows 的,但胜在自由度足够高。
|
||||
我**个人觉得** Mac OS 那种双栏比较好看、比较方便,所以稍微按照如下配置调整了面板布局。
|
||||
|
||||
仅供参考咯。
|
||||
|
||||
::: details Dock 栏
|
||||
即原本的任务栏。
|
||||
- 位于底部、居中、适宜宽度、取消悬浮、避开窗口
|
||||
- 除“图标任务管理器”外,其余组件全部移除。
|
||||
:::
|
||||
|
||||
::: details Finder 栏
|
||||
即“应用程序菜单栏”(可在 *编辑模式—添加面板* 处找到)
|
||||
- 位于顶部、居中、填满宽度、取消悬浮、常驻显示
|
||||
- 自左到右依次为:
|
||||
- 应用程序启动器(类比开始菜单)
|
||||
- 窗口列表
|
||||
- 全局菜单(默认提供)
|
||||
- “面板间距”留白
|
||||
- 数字时钟
|
||||
- 日期保持在时间旁边,而不是上下两行
|
||||
- 字号略小于菜单栏高度,凭感觉捏
|
||||
- “面板间距”留白
|
||||
- 系统监视传感器
|
||||
- 横向柱状图(平均 CPU 温度、最高 CPU 温度)
|
||||
- 仅文字(网络上行、下行速度;网络上传、下载的总流量)
|
||||
- 系统托盘
|
||||
:::
|
||||
|
||||
除了 Finder 栏外,可以在系统设置里更改屏幕四周的鼠标表现。
|
||||
比如,鼠标移动到左上角可以自动弹出“应用程序启动器”,移到右上角可以切换你的桌面,等等。
|
||||
|
||||
## 附录:GPG 密钥配置
|
||||
主要讨论配置提交签名(Commit Signing)时遇到的问题。
|
||||
|
||||
### I. VSCode 提交签名
|
||||
大体上跟着 [Commit Signing - VSCode Wiki](https://github.com/microsoft/vscode/wiki/Commit-Signing) 就可以了。唯一需要留意的是`pinentry`。
|
||||
|
||||
VSCode 的主侧栏“源代码管理”页提交时并不会走终端,也就莫得 pinentry 的 CUI;莫得 pinentry 输密码验证,提交就签不了名。
|
||||
虽然有人好像搞了个`pinentry-extension`出来,但 6 月初我去看的时候它连说明书都莫得,也没有上架,那用集贸。
|
||||
|
||||
所以我选择编辑`~/.gnupg/gpg-agent.conf`:
|
||||
```properties
|
||||
default-cache-ttl 28800
|
||||
pinentry-program /usr/bin/pinentry-qt
|
||||
```
|
||||
保存后重启`gpg-agent`:`gpg-connect-agent reloadagent /bye`。
|
||||
|
||||
经测试,大部分终端均能在 SSH 连接中调出 CUI;VSCode Remote-SSH 打开的终端可能比较特殊,仍然无法签名。个人还是建议单独开个终端作为 workaround。
|
||||
|
||||
### II. GPG 密钥备份(导出导入)
|
||||
之前并没有意识到备份 key 的重要性,结果重装 Arch 重新配置提交签名时,
|
||||
我发现 GitHub 和腾讯 Coding 会重置提交验证(同一个邮箱只能上传一个公钥),届时就是我痛苦的 rebase 重签了。
|
||||
~~不过好在受影响的多数只是我的个人项目,变基无伤大雅。~~
|
||||
```bash
|
||||
gpg --list-secret-keys --keyid-format LONG
|
||||
# export
|
||||
gpg -a -o public-file.key --export <keyid>
|
||||
gpg -a -o private-file.key --export-secret-keys <keyid>
|
||||
# import
|
||||
gpg --import ~/public-file.key
|
||||
gpg --allow-secret-key-import --import ~/private-file.key
|
||||
```
|
||||
重新导入 Key 之后,可能还需要`gpg --edit-key`更改密码(`passwd`)、重设信任(`trust`)。
|
242
docs/notes/OS/ArchUEFI.md
Normal file
@ -0,0 +1,242 @@
|
||||
---
|
||||
category:
|
||||
- 操作系统
|
||||
- Linux
|
||||
tag:
|
||||
- Arch Linux
|
||||
- Grub
|
||||
- UEFI
|
||||
- UKI
|
||||
---
|
||||
|
||||
# Arch Linux UEFI 启动二三事
|
||||
|
||||
由于我对 Linux 乃至整个 UEFI 的启动机制尚且“浅尝辄止”,本文并不会展开很多硬核内容,只是对我个人使用过的启动方案做个总结。
|
||||
|
||||
::: tip 引导和启动
|
||||
在维基百科中二者似乎是同一概念,搜索“启动程序”会跳转到[“引导程序”](https://zh.wikipedia.org/wiki/%E5%95%9F%E5%8B%95%E7%A8%8B%E5%BC%8F)的介绍。
|
||||
|
||||
> 另见英文维基:[Booting](https://en.wikipedia.org/wiki/Booting)
|
||||
|
||||
国内很多折腾 WinPE 的人(包括我)对此也并没有很明确的区分;当然有些博客则对开机装载 Linux 的过程拆分成引导、启动两个阶段。本文为了方便起见,用词不作区分。
|
||||
:::
|
||||
|
||||
在此感谢岛风 [@frg2089](https://github.com/frg2089) 指路。
|
||||
|
||||
## UEFI 启动简述:启动项管理
|
||||
|
||||
> UEFI 规范定义了名为“UEFI 启动管理器”的一项功能 …… 是一种固件策略引擎,可通过修改固件架构中定义的全局 NVRAM 变量来进行配置。启动管理器将尝试按全局 NVRAM 变量定义的顺序依次加载 UEFI 驱动和 UEFI 应用程序(包括 UEFI 操作系统启动装载程序)。……
|
||||
> ::: right
|
||||
> ——[(译)UEFI 启动:实际工作原理](https://www.cnblogs.com/mahocon/p/5691348.html)
|
||||
> :::
|
||||
|
||||
本“议题”只讨论 UEFI 原生启动项和回退路径启动项。恕不对 BIOS 兼容的部分作详细展开。
|
||||
|
||||
### i. 原生启动项
|
||||
用 Windows 7 及更高版本系统的朋友肯定知道这个东西:Windows Boot Manager。`bootmgr`它代替了`ntldr`,从此便沿用至今。
|
||||
|
||||
事实上,Windows Boot Manager 是系统安装完成后,初次加载系统时为其创建的**原生启动项**。它明确指出需要启动**指定设备中**的**指定引导文件**(即`bootmgfw.efi`)。
|
||||
|
||||
即便 WinToGo 也是如此——在初次以 U 盘身份进入 WTG 系统时,Windows 也会为该设备作配置——所谓“正在准备设备”。在此过程中,顺带把原生启动项建立好。然后重启之后再按快捷键进入启动菜单,你**可能**会在**部分主板上**发现有两个启动项,指向同一个设备:
|
||||
```
|
||||
Windows Boot Manager ( Koi Series Pro ...)
|
||||
USB HDD: Koi Series Pro ...
|
||||
```
|
||||
需要注意的是,原生启动项是**存储在主板里的**(更准确的说,是全局 NVRAM 变量)。这多少可以解释为什么 Grub 引导那么脆弱(
|
||||
|
||||
### ii. 回退路径启动项
|
||||
对于 WinPE、Windows 安装镜像而言,它们并非用于长线运行,往往没有“准备设备”的步骤,那么 UEFI 如何认出它们捏?
|
||||
还记得上面提到的同一设备双启动项吗?UEFI 固件是能够找到可启动设备,并且尝试启动的。但它是依据什么去找的捏?
|
||||
|
||||
UEFI 固件首先会**遍历各硬盘的 ESP 分区**,并在其中查找`\EFI\BOOT\boot{cpu_arch}.efi`。前面的这一固定路径就称为**回退路径**,通过查找回退路径建立的启动项就称作**回退路径启动项**。其中,`cpu_arch`即 CPU 架构,已知的有:
|
||||
- `x64`:x86-64
|
||||
- `ia32`:x86-32
|
||||
- `ia64`:Itanium
|
||||
- `arm`:AArch32,即 arm32 ~~(胳膊 32)~~
|
||||
- `aa64`:AArch64,即 arm64 ~~(64 条胳膊)~~
|
||||
|
||||
> [!note]
|
||||
> UEFI 的路径系统与 Windows 类似:以`\`分隔,不区分大小写。
|
||||
|
||||
如果同一硬盘、同一 CPU 架构存在多个匹配的 EFI 文件(比如,可能有两个 ESP 分区,分开装不同系统的 EFI),那么**只会选第一个有效的**去执行。
|
||||
|
||||
对于 WinPE U 盘,通常它们是 MBR 分区表,那么会考虑更泛用的搜索:采用 **FAT** 文件系统的**活动分区**;
|
||||
对于 GPT 分区表,可以直接搜索 ESP 分区。当然如今的主板并不会卡那么死,哪怕只是普通的 FAT 分区,也会尝试搜索、执行。
|
||||
|
||||
也就是说,哪怕原生启动项意外被固件扬了,只要还有回退启动项,便仍可从同一个硬盘启动系统。
|
||||
|
||||
> [!info]
|
||||
> 实际上`bcdboot`工具会在 ESP 分区里同时写入`bootx64.efi`和`bootmgfw.efi`,这两个 EFI 除了文件名以外并无区别。
|
||||
> 前者即回退路径启动项,作为启动 Windows 的后备方案。
|
||||
|
||||
## 启动加载器(以 Grub 为主)
|
||||
这也是最广泛使用的启动方式 ~~,Windows 也干了~~。在 Linux 当中,最常用的加载器是 Grub。当然,也有使用 rEFInd 的。
|
||||
|
||||
启动加载器(bootloader)本身作为跳板,被 UEFI 固件加载后,需要根据配置找到真正的 Linux 内核,并经由内核引导用户硬盘上的 Arch 系统。而在 Windows 中,`bootmgfw.efi`会根据`BCD`配置文件,执行硬盘其中一个 Windows 副本中的`winload.exe`,并将该副本的其余加载流程交给它完成。
|
||||
|
||||
正常使用 Windows 单系统的用户可能对启动过程并无察觉,因为 Windows 为了确保能够启动,会时不时刷新启动项。但一旦与 Linux 混用,你就需要**留意 Linux 的加载器会不会被 Windows 刷下去(甚至被覆盖)**。除此之外,尽管因“机”而异,但 UEFI 固件**有可能会自动清理不再可用的启动项**。比如重新插拔固态,有可能会出现掉引导的情况。因此就个人来说,我不会再考虑 bootloader 了。
|
||||
|
||||
### i. 修复 Grub 引导
|
||||
Windows 启不动我们会尝试修复引导,Arch 亦然。修复 Grub 引导实际上就是**重走 Grub 安装流程**:
|
||||
|
||||
- `mount`挂载相应分区;
|
||||
- `genfstab`重建挂载表(如有必要);
|
||||
> 个人建议无论如何都重建一遍`fstab`。反正刷完绝对是最新的。
|
||||
- `arch-chroot`切换进硬盘上的系统;
|
||||
- `grub-install`重建 grub 引导。
|
||||
|
||||
### ii. 补充回退启动项
|
||||
事实上,需要反复重建 Grub 引导的一大原因就在于,Grub 只会写入它自己的`grubx64.efi`,以及原生启动项:
|
||||
|
||||

|
||||
|
||||
那么办法也很简单:像 Windows 那样也建一个回退路径启动项。具体来说,在 ESP 分区里建立`EFI\BOOT`目录,复制`grubx64.efi`重命名成`bootx64.efi`嘛。~~Windows 不也干了(~~
|
||||
当然如果是像图中那样不止一个 Grub,甚至同盘 Windows 和 Arch 双系统,那我不推荐你这么做。
|
||||
|
||||
> [!warning]
|
||||
> 不要在这边试图软链接节省空间!
|
||||
|
||||
## 固件直接引导(EFIStub)
|
||||
Grub 本身写入 ESP 的内容不多,配置啊、Linux 内核啊都在`/boot`。有人便主张把`/boot`还给`/`,ESP 分区实际挂载`/efi`。
|
||||
而岛风则提出了更激进的主张:让固件直接引导内核。
|
||||
|
||||
> An EFI boot stub (aka EFI stub) is **a kernel that is an EFI executable**,
|
||||
> i.e. that can directly be booted from the UEFI.
|
||||
> ::: right
|
||||
> ——[Arch Wiki: EFIStub](https://wiki.archlinux.org/title/EFISTUB)
|
||||
> :::
|
||||
|
||||
根据 Wiki,**默认情况下** Arch Linux 的内核本身就是可启动 EFI,只是需要附加[**内核参数**](https://wiki.archlinux.org/title/Kernel_parameters#Parameter_list):
|
||||
```
|
||||
# 为便于阅读,这里分了三行。
|
||||
root=UUID=7a6afcd0-a25a-4a6c-bf7b-920b53097eae
|
||||
resume=UUID=b84ae173-edbc-442c-b00b-5c47eef203f1
|
||||
rw loglevel=3 quiet initrd=\intel-ucode.img initrd=\initramfs-linux.img
|
||||
```
|
||||
::: details 内核参数详解
|
||||
Grub 等启动加载器的本职工作就是帮你引导内核,因此它们的配置文件已经包含完整的内核参数了。
|
||||
我上面列的内核参数是参照 Wiki 自行搭配,确认可行的参数。你也可以查 Wiki 自行组合。
|
||||
- `root`:`/`分区。目前只见到 UUID 填法。
|
||||
- `rw rootflags=subvol=@`:对`/`分区挂载的附加属性,比如可读写、指定 Btrfs 子卷。
|
||||
- `resume`:休眠使用的交换分区,同样只见到 UUID 填法。休眠时会在指定 Swap 里创建内存映像。
|
||||
- `loglevel=3 quiet`:内核加载时的附加属性,如日志等级之类。
|
||||
- `initrd=\intel-ucode.img`:加载的初始化内存盘 (Init RAM Disk)。
|
||||
一个`.img`一条`initrd=`,路径用`\`分隔,顺序自左向右(可以参见 grub 的配置文件)
|
||||
|
||||
> [!note]
|
||||
> 个人觉得这里 initrd 称作“初始化映像”更合适,毕竟需要填`.img`嘛。
|
||||
:::
|
||||
|
||||
LiveCD 里的`efibootmgr`工具可以直接操作固件的启动项。当然若是遵照律回指南和 Miku 指南,那么`efibootmgr`业已安装到你的系统中,你可以在运行中的本机 Arch 系统中折腾:
|
||||
```bash
|
||||
# 首先确定你要操作的硬盘和分区,不要搞错。UUID 马上就会用到。
|
||||
lsblk -o name,mountpoint,uuid
|
||||
# 参见 Wiki,以 Btrfs 为例,仅供参考
|
||||
sudo efibootmgr --create --disk /dev/nvme0n1 --part 1 \
|
||||
--label "Arch Linux" --loader /vmlinuz-linux \
|
||||
--unicode 'root=UUID=f6419b76-c55b-4d7b-92f7-99c3b04a2a6f rw rootflags=subvol=@ loglevel=3 quiet initrd=\intel-ucode.img initrd=\initramfs-linux.img'
|
||||
```
|
||||
::: note 创建启动项命令详解
|
||||
- `--part 1`:你的 ESP 分区序号。根据`lsblk`的树状图顺序判别。
|
||||
- `--label "Arch Linux"`:启动项名称。大多数固件并不支持中文。
|
||||
- `--unicode`后面跟内核参数。
|
||||
:::
|
||||
|
||||
归根结底,EFIStub 代替了启动加载器,由我们用户手动建立 UEFI 原生启动项。但这种方式硬要说优点吧……可能也就比 Grub 快那么几秒而已。维护起来并不比 Grub 轻松多少。
|
||||
|
||||
## 统一内核映像(UKI)
|
||||
在应用 EFIStub 的时候我就在想,有没有可能写一个`bootx64.efi`,直接带内核参数启动`vmlinuz-linux`呢。后面偶然找到了“统一内核映像”的介绍,豁然开朗。
|
||||
|
||||
> A unified kernel image (UKI) is a **single executable** which can be **booted directly from UEFI firmware**, or automatically sourced by boot loaders with little or no configuration.
|
||||
> ::: right
|
||||
> ——[Arch Wiki: Unified Kernel Image](https://wiki.archlinux.org/title/Unified_kernel_image)
|
||||
> :::
|
||||
|
||||
根据介绍,UKI 实际上就是将内核引导的资源整合起来,打包而成的 EFI 可执行文件。某种意义上这也算是一种「固件直接引导」,只不过 EFIStub 只创建原生启动项,而它两种启动项都可以做。
|
||||
|
||||
::: info UKI 通常包含……
|
||||
> 摘自 [UAPI Group Specifications](https://uapi-group.org/specifications/specs/unified_kernel_image/)。
|
||||
|
||||
- EFI 执行代码(决定它“可执行 EFI”的本质)
|
||||
- Linux 内核
|
||||
- 【可选】内核参数
|
||||
- 【可选】初始化内存盘
|
||||
- 【可选】CPU 微码
|
||||
- 【可选】描述信息、启动屏幕图、设备树……(不重要)
|
||||
|
||||
只要集成了 EFI 执行代码和 Linux 内核,就可以称作统一内核映像了。
|
||||
:::
|
||||
|
||||
接下来以`mkinitcpio`为例,但是不走寻常路。
|
||||
|
||||
### i. 内核参数
|
||||
Wiki 中介绍了两种方法:
|
||||
- 动`/etc/cmdline.d/`里的`.conf`配置。像`root.conf`决定`/`如何挂载,等等。
|
||||
- 直接把所有参数搓成一行 echo 给`/etc/kernel/cmdline`文件。
|
||||
|
||||
于我而言,显然第二种更方便。
|
||||
```bash
|
||||
# 我在 LiveCD 里 arch-chroot 进去做的。别问我为什么没权限。
|
||||
echo 'root=UUID=... resume=UUID=... rw loglevel=3 quiet' > /etc/kernel/cmdline
|
||||
```
|
||||
与 EFIStub 不同,这里不需要指定`initrd=`——工具会自己打包。
|
||||
|
||||
> [!warning]
|
||||
> 若启用“安全启动”,且 UKI 封装了内核参数,则 UEFI 固件会无视外部传入的其余参数。
|
||||
|
||||
### ii. 预设文件
|
||||
编辑`/etc/mkinitcpio.d/linux.preset`。我们前面说过“不走寻常路”,关键就在这里。
|
||||
|
||||
下面是我自用的`linux.preset`。~~想走寻常路的话还是抄别人的预设吧。~~
|
||||
```properties
|
||||
# mkinitcpio preset file for the 'linux' package
|
||||
|
||||
ALL_config="/etc/mkinitcpio.conf"
|
||||
ALL_kver="/boot/vmlinuz-linux"
|
||||
|
||||
PRESETS=('default' 'fallback')
|
||||
|
||||
#default_config="/etc/mkinitcpio.conf"
|
||||
#default_image="/boot/initramfs-linux.img"
|
||||
|
||||
default_uki="/efi/EFI/BOOT/bootx64.efi"
|
||||
#default_uki="/efi/EFI/Linux/arch-linux.efi"
|
||||
#default_options="--splash /usr/share/systemd/bootctl/splash-arch.bmp"
|
||||
|
||||
#fallback_config="/etc/mkinitcpio.conf"
|
||||
#fallback_image="/boot/initramfs-linux-fallback.img"
|
||||
|
||||
fallback_uki="/boot/arch-linux-fallback.efi"
|
||||
#fallback_uki="/efi/EFI/Linux/arch-linux-fallback.efi"
|
||||
fallback_options="-S autodetect"
|
||||
```
|
||||
> [!warning]
|
||||
> 有的教程会出现`ALL_microcode=(/boot/*-ucode.img)`这一句。
|
||||
> 这种指定微码的方法已经废弃,`/etc/mkinitcpio.conf`已经有针对微码的 Hook 了,无需手动指定。
|
||||
|
||||
正常来说 UKI 均会输出到`/efi/EFI/Linux/arch-linux*.efi`,据称`systemd-boot`可以扫到并引导 UKI。
|
||||
但生成`bootx64.efi`让固件去加载不更直接?所以我选择直接导出到回退路径,如此连`efibootmgr`都不需要了。
|
||||
|
||||
另一方面,`fallback`也会生成 UKI,但`bootx64.efi`只有一个。注意到之前 EFIStub 一节我只给`initramfs-linux`构建了原生启动项,那么`fallback`自然丢回`/boot`里不管它。
|
||||
|
||||
> [!note]
|
||||
> 把`fallback`移回`/boot`还有一重原因:尽量缩减 ESP 分区体积——本来`/efi`设计上也有减小 ESP 分区大小的意图。
|
||||
> 我试跑完发现`fallback.efi`高达 140+MB,而`arch-linux.efi`不过 30MB。
|
||||
>
|
||||
> 当然,保险起见,我并没有尝试`default`生成 UKI、`fallback`仍生成内存盘的做法,也没有试图把`fallback`预设直接干掉。
|
||||
> 如有勇士试验过一切正常,欢迎 Issues 反馈(
|
||||
|
||||
### iii. 创建映像
|
||||
按需建立路径,并跑一遍生成:
|
||||
```bash
|
||||
mkdir -p /efi/EFI/BOOT/
|
||||
mkinitcpio -p linux
|
||||
```
|
||||
还是那句话,想循规蹈矩的不要学我。
|
||||
|
||||
---
|
||||
|
||||
建完之后退出系统,重启按快捷键进入启动菜单,这下该有你硬盘的 UEFI 回退路径启动项了:
|
||||
```
|
||||
HDD: PM8512GPKTCB4BACE-E162
|
||||
```
|
BIN
docs/notes/OS/esp_without_bootARCH.png
Normal file
After Width: | Height: | Size: 68 KiB |
220
docs/notes/RA2/ExtremeStarryInLinux.md
Normal file
@ -0,0 +1,220 @@
|
||||
---
|
||||
category:
|
||||
- Linux
|
||||
- RA2
|
||||
tag:
|
||||
- Wine
|
||||
- Bottles
|
||||
- 虚拟环境
|
||||
---
|
||||
|
||||
# 在 Linux 中游玩《星辰之光》
|
||||
因为一些机缘巧合,现在我改用 Linux 作为主力系统了。然鹅地图我还是得做的,我又没有两台电脑搞远程,那如何在 Linux 里游玩红红、制作地图首先就成了问题。
|
||||
|
||||
当然有人马上会想到虚拟机,比如熟悉的 VirtualBox、VMWare,比如`winapps`。但虚拟机也好,双系统也罢,都太重量级了,我 128GB 的二手盘不堪重负。
|
||||
|
||||
那都玩 Linux 了,Wine 兼容层怎么样呢?不也有过 Wine 跑《原神》的成功案例了嘛。但遗憾的是,自 9.17-1 版本开始,**原生 Wine** 的 DDraw 兼容都做得十分甚至九分的抽象,无论是红警 2 本体还是 FA2 地图编辑器都无法正常显示,原生 Wine 也就不再适用了。
|
||||
|
||||
> 现阶段也不再建议用 Wine 玩原神,容易被封号。可以试试云原神,但首先需要解决“鼠标无法转动视角”的问题。
|
||||
|
||||
所以,兜兜转转,还是回到了 Bottles。
|
||||
|
||||
::: important
|
||||
本篇笔记仅以《星辰之光》这个红警 2 模组作为范例,因为它是我这里最早成功跑起来的红警 2 mod。
|
||||
对于其他 mod,乃至其他游戏和 Windows 程序,本篇笔记的方案可能有一定参考价值,**但不保证能够成功运行**。
|
||||
:::
|
||||
|
||||
::: note 图片大小
|
||||
本篇笔记的插图原图对于电脑端来说会偏大一些,因此我基本上都做了缩小处理——你可以点击图片查看原图。
|
||||
如果您在用移动设备阅读,则这种“缩小”效果可能更明显些。还请见谅。
|
||||
:::
|
||||
|
||||
那么正式开始之前,我有必要先说一下我的 Linux 环境。由于 Linux 发行版众多,我**无法保证别的包源、别的发行版能否这么操作**。
|
||||
|
||||
- 操作系统:Arch Linux
|
||||
- 桌面环境:KDE 6
|
||||
|
||||
## 一、Bottles
|
||||
|
||||
Bottles 是由 [bottlesdevs](https://github.com/bottlesdevs) 开发的可视化 Wine 配置工具,旨在“让用户便利地在喜欢的发行版里运行 Windows 程序”。
|
||||
|
||||
> 参考链接:[GitHub](https://github.com/bottlesdevs/Bottles) [官网](https://usebottles.com/)
|
||||
|
||||
官方推荐通过 Flatpak 安装,在沙箱里运行。但由于「懒加载」问题,游戏本体和地编都无法正常启动。因此还是改用`pacman`吧。
|
||||
|
||||
::: info 懒加载
|
||||
经实测发现,单文件 exe 才可以在这种情况下直接在 Bottles 里启动。
|
||||
但凡需要读同级文件、子文件夹的,都需要在 Bottle 里添加快捷方式,并在快捷方式的设置里手动指明工作目录。
|
||||
:::
|
||||
|
||||
首先需要引入`archlinuxcn`源。具体步骤参见[《Arch 安装流程》](../OS/ArchInstall.md#_4-1-cn-源和-aur-助手),这里不再重复。
|
||||
接着`sudo pacman -Sy bottles`安装。等待进度跑完,就可以从“应用程序菜单栏”运行了。
|
||||
|
||||
初次运行 Bottles 会弹出一个向导跟你 blabla,无脑下一步即可。
|
||||
到最后一步时 Bottles 会下载一些组件包。由于众所周知的原因,可能会花费比较长的时间。
|
||||
|
||||

|
||||
|
||||
## 二、部署 Bottle
|
||||
|
||||
### 2.1 新建
|
||||
进入 Bottles 的主界面,点击“新建 Bottle……”(或者窗口左上角的加号),填些基本信息:
|
||||
|
||||
> [!note]
|
||||
> 我的系统语言是英文,中文翻译仅供参考。
|
||||
|
||||
- 名称`Name`:自拟(为便于说明,后面用`$venv`表示);
|
||||
- 环境`Environment`:建议选自定义`Custom`。
|
||||
|
||||
> 应用程序`Application`和游戏`Gaming`这两个预设,初次新建 Bottle 时会下载巨量的依赖。
|
||||
> 如果你网不是特别好,也没走代理,直接“自定义”就可以了。
|
||||
|
||||
- 打开共享用户目录`Share User Directory`选项
|
||||
- 兼容层,或者说运行器`Runner`选择 soda-9.0-1(以最新版为准)
|
||||
|
||||
> 如果你选了预设,这里是改不了兼容层的,得等创建好 Bottle 之后进设置再改。
|
||||
|
||||
> 此外,cn 源的 Bottles 使用系统中装的原生 Wine。在文章开头我就强调过原生 Wine 已不适用红红。所以务必换用别的。
|
||||
|
||||
- Bottle 目录`Bottle Directory`可改可不改(为便于说明,后面用`$bottles`表示)。
|
||||
|
||||
> 默认你的环境位于`~/.local/share/bottles/bottles`目录下。
|
||||
|
||||
> [!warning]
|
||||
> 如果你在全局设置里改过默认目录,千万不要在新建这里又改到同一个位置,否则会报**符号占用,创建失败**。
|
||||
|
||||

|
||||
|
||||
然后在右上角点击“创建”即可。
|
||||
|
||||
> [!note]
|
||||
> 在 Linux 中,`~`和`$HOME`通常指代`/home/<user_id>` [^home_dir],比如`/home/nyacl`。
|
||||
> 类比下 Win7 的`%UserProfile%`和`C:\Users\nyacl`就知道了。
|
||||
>
|
||||
> [^home_dir]: Linux 的路径是**区分大小写**的,终端里的环境变量(通常全大写)也是。
|
||||
> 即`YURI.exe` ≠ `yuri.EXE`;`$HOME` ≠ `$home`。
|
||||
|
||||
### 2.2 Bottle 选项
|
||||
|
||||
点击刚建好的 Bottle 进入详情页,点开设置`Settings`:
|
||||
|
||||
1. 需要开启 DirectX 翻译——将组件`Components`部分的 DXVK 和 VKD3D 打开;
|
||||
2. 可以考虑在显示`Display`部分启用独立显卡`Discrete Graphics`(我的笔记本没有捏);
|
||||
3. 性能`Performance`部分的同步`Synchronization`可以考虑 Fsync,除此之外的选项建议不动;
|
||||
|
||||
做完设置,退回上一页把依赖`Dependencies`装上:
|
||||
|
||||
::: tip 红警 2 推荐依赖
|
||||
|
||||
- 客户端需要:`mono` (Wine mono .NET 依赖) (耗时较长,建议最后安装)
|
||||
- 中日韩字体:`cjkfonts`(避免“口口文学”)
|
||||
|
||||
> 你也可以手动下载(或复制 C:\Windows\Fonts 里的)msyh.ttc 和 simsun.ttc,
|
||||
> 并复制到`$bottles/$venv/drive_c/windows/Fonts`里。
|
||||
|
||||
- 游戏本体需要:`cnc-ddraw`
|
||||
- Reshade 特效层需要:`d3dcompiler_*.dll` `d3dx*`
|
||||
|
||||
> 这里的 * 代表全都要,比如 d3dx11 和 d3dx9。
|
||||
<!-- - gdiplus -->
|
||||
:::
|
||||
|
||||
## 三、部署游戏环境
|
||||
|
||||
下载好《星辰之光》大版本包体(如有必要,额外再下载小更新包),用`unzip`解压:
|
||||
```bash
|
||||
sudo pacman -S unzip
|
||||
# 请根据实际情况替换压缩包路径
|
||||
unzip -O GBK -o '~/Documents/Extreme Starry v0.6.zip'
|
||||
# 如果网络不好,不方便更新,并且群里恰有离线更新包,也可以直接下载、覆盖更新
|
||||
unzip -O GBK -o '~/Documents/0.6.2 离线更新包.zip' -d '~/Documents/Extreme Starry'
|
||||
```
|
||||
|
||||
::: details unzip 命令行解释
|
||||
`unzip [opt] </path/to/zip> [-d extract_dir]`
|
||||
|
||||
- `-O encoding`:指定在 Windows 里打包的 ZIP 采用什么编码打开。
|
||||
- `-o`(注意大小写不一样):有相同文件名的,一律覆盖。
|
||||
- `/path/to/zip`:zip 路径。
|
||||
> 遇到空格需要加反斜杠转义,或者像我那样直接打引号。
|
||||
- `-d extract_dir`:解压到单独的文件夹。
|
||||
> 像上面离线包直接解压出来是散装跟`Extreme Starry`并列放的。而`~/Documents`可能不止放《星辰之光》。
|
||||
|
||||
更多细节还请自行`unzip -h`。虽然解说都是英文。
|
||||
:::
|
||||
|
||||
然后点开你的 Bottle 进入详情页,为客户端`Extreme Starry.exe`添加快捷方式,这样就不需要每次都点“运行可执行程序”找半天了。
|
||||
|
||||

|
||||
|
||||
> [!tip]
|
||||
> 在“选择可执行文件”对话框中,若找不到 exe,请在“过滤”那里改为`Supported Executables`。
|
||||
|
||||
## 四、渲染补丁
|
||||
我们知道,红警 2 是个 Windows 游戏,但众所周知,由于系统调用的不同,Windows 程序无法直接在 Linux 上跑,这点对于“渲染补丁”也是一样。
|
||||
所以客户端设置也好,玩家自备`ddraw.dll`也罢,**均无法在 Wine 里使用**。
|
||||
|
||||
### 4.1 游戏本体
|
||||
可能你会有疑问:前面不是让装`cnc-ddraw`了吗?怎么又有问题捏?因为文中的 Bottles 以及用于原生 Wine 的 Winetricks 均只提供这个。换言之,你基本上**只有`cnc-ddraw`类补丁可以选**。
|
||||
|
||||
除此之外,Bottle 容器与 Windows 类似,**默认从游戏目录(即“内建`Builtin`”)加载 DLL**。所以,还需要调整`ddraw.dll`加载次序。
|
||||
- 找到 Bottle 详情的“工具”一栏;
|
||||
- 点开`Legacy Wine Tools`找到`Configuration`,打开`winecfg`。
|
||||
- 选中函数库`Libraries`页面,在列表中选中`ddraw`,点击编辑`Edit`;
|
||||
若找不到,**先**在上面的输入框里手打`ddraw.dll`,点击添加`Add`。
|
||||
- 在弹出的 5 个选项中,选择**原装`Native` (Windows)**。
|
||||
|
||||
而对于 Reshade,国内有一些 Reshade 会伪装成`d3d*.dll`。由于上面提到的默认规则,这种 Reshade 实际仍能配合`ddraw.dll`运作,在游戏中显示出 Reshade 版本提示。当然具体特效显示成什么样就未经细致测试了。
|
||||
|
||||
::: details Wine 的 DLL 查找
|
||||
经查证,前面说的`soda`、`proton`均为 Wine 的变种。所以只需讨论 Wine 的做法即可。
|
||||
总的来说,Wine 的查找与 Windows 的 KnownDlls 机制类似,但做了简化[^wine_forum_dll]:
|
||||
|
||||
- 内建(Builtin):(默认优先)在程序的**当前目录**(或者叫**工作目录**,在本文中又称**游戏目录**)下查找、加载。
|
||||
- 原装(Native):(默认备选)在 Wine 容器(即`$venv`虚拟 C 盘的`System32`,可能还有`SysWOW64`)中查找。
|
||||
|
||||
[^wine_forum_dll]: 参见帖子 _[Wine can't find/load DLLs in the same dir](https://forum.winehq.org/viewtopic.php?t=36023)_。
|
||||
:::
|
||||
|
||||
### 4.2 FA2 及其扩展(FA2sp 等)
|
||||
开篇提到,我还有做地图的需求。
|
||||
|
||||
目前圈子里所谓“FA2 防卡补丁”实际是 DxWnd,它仍会加载系统目录的`ddraw.dll`。那么对本随记而言,便只需讨论“原装”的 DDraw。经过测试,刚建好的 Wine 环境其`ddraw.dll`恰可以为 FA2 所用。
|
||||
|
||||
> 原生 9.16-1 那版对我来说刚好,但是无视缩放比;
|
||||
> 9.17-1 及往后的新版本则会因屏幕缩放有一些拉扯感,不知高分屏用户觉得如何。
|
||||
> Proton 等 Wine 改版的表现与 9.16-1 一致。推测是并未跟进最新版本。
|
||||
|
||||
那需要做的就很简单了:**另起一个 Bottle 跑地编**。或者,在`cnc-ddraw`安装之前先提取出`$venv/drive_c/windows/System32`(也可能是`SysWOW64`,如果有的话)里面的`ddraw.dll`,**覆盖 DxWnd**。
|
||||
|
||||
## 五、开玩
|
||||
|
||||
在做完全部配置之后,点击你建过的快捷方式右边的`▶`图标,开耍。……虽然,读条可能会比较慢。
|
||||
|
||||

|
||||
|
||||
::: info 再次启动客户端没有反应
|
||||
可能是因为进程还驻留在 Wine 环境当中,需要“强制停止所有进程”手动干掉:
|
||||
|
||||

|
||||
:::
|
||||
|
||||
## 附录:关于 Syringe 命令行
|
||||
> [!tip]
|
||||
> 像《星辰之光》这种有独立客户端的 mod 无需查阅此附录,客户端本身就负责了命令行解析。
|
||||
|
||||
Linux 的文件名允许英文引号(如`"game"md.exe`),在终端里,这会给 Syringe 带来歧义:
|
||||
```log
|
||||
Syringe.exe "\"gamemd.exe\"" -SPAWN ...
|
||||
```
|
||||
解法也很简单,把它扔进批处理即可:
|
||||
```cmd
|
||||
PUSHD %~dp0
|
||||
Syringe.exe "gamemd.exe" -SPAWN -log -cd -speedcontrol
|
||||
```
|
||||
然后把批处理扔进游戏目录(或者说和`gamemd.exe`放在一起),让 Wine 去启动批处理即可:
|
||||
```bash
|
||||
# wine 运行时会把 Linux 根目录挂载到 Z 盘。
|
||||
wine cmd /c "Z:/home/agxcoy/Documents/ES-FA2/launch.cmd"
|
||||
```
|
||||
|
301
docs/notes/RA2/ReverseEngineering.md
Executable file
@ -0,0 +1,301 @@
|
||||
---
|
||||
category:
|
||||
- 逆向工程
|
||||
- RA2
|
||||
tag:
|
||||
- Syringe
|
||||
- 进程注入
|
||||
---
|
||||
|
||||
# 基于 FA2sp 的逆向小记
|
||||
|
||||
参考资料:
|
||||
- Zero-Fanker:[Ares Wiki](https://gitee.com/Zero_Fanker/Ares/wikis)
|
||||
- 王道论坛:[2023 考研 408 学习资料](https://github.com/ddy-ddy/cs-408)
|
||||
|
||||
## 背景
|
||||
|
||||
FA2sp 是为了改善红警 2 地图编辑器 FinalAlert2(下面简称 FA2)的使用体验而开发的扩展库。它通过 Syringe 注入 Hook 的方式,无需修改 FA2 本体便能享受到扩展的功能和修复。
|
||||
|
||||
2024年3月8日,EA 在发布 Steam 版红警 2 时,终于把 FA2 的源码放了出来,自此 FA2sp 完成了历史使命。
|
||||
然鹅,FA2sp 的主要开发者 [@secsome](https://github.com/secsome)(书伸)虽然承诺把扩展功能和 bug 修复移植到官方源码中,但由于三次元原因该计划迟迟未能推进。
|
||||
考虑到《星辰之光》这边仍有对 FA2 的扩展需求,我便当起了接盘侠,继续 [FA2sp](https://github.com/ClLab-YR/FA2sp) 的维护。
|
||||
|
||||
那既然接了盘,总该干点事不是。于是才疏学浅的我结合自己备考 408 的粗略理解,尝试研究书伸留下来的逆向成果——`finalalert2yr.exe.idb`。
|
||||
|
||||
## 复习一下寄组
|
||||
|
||||
> [!note]
|
||||
> 我事非科班生,对汇编的认识仅限考研 408 计算机组成原理对“指令系统”的考察。
|
||||
> > 其实我参加的是 24 考研(2023.12.23-24),但回去翻考研群已经只剩 23 考研的资料了。
|
||||
>
|
||||
> FA2 显然是 Intel x86 架构的程序,恰好 24 考研主要考 x86 汇编。
|
||||
|
||||
### 寄存器
|
||||
|
||||
除了考研常考的通用寄存器`e[abcd]x`、帧指针`ebp`栈指针`esp`外,
|
||||
在遇到 Fatal Error 时,我们还重点关注`except.txt`里的`eip`寄存器:
|
||||
```
|
||||
EIP: 00534096 ESP: 013A89D4 EBP: 013A89FC
|
||||
EAX: 00000000 EBX: 00886240 ECX: 00886240
|
||||
EDX: 003F5000 ESI: 00886230 EDI: 2A3C0000
|
||||
```
|
||||
在 FA2sp 里,这些寄存器可以通过 [Syringe](https://github.com/Ares-Developers/YRpp/blob/master/Syringe.h) Hook 定义里的`REGISTERS *R`指针参数存取。
|
||||
|
||||
### 跳转汇编指令
|
||||
|
||||
> [!important]
|
||||
> 考虑到王道书里介绍的多数基本运算指令在分析 FA2 中意义并不大,这里就直接跳过了。
|
||||
> 完整版的 x86 汇编指令介绍还请移步《汇编语言程序设计》或者《汇编原理》之类的课程,恕不浪费太多时间咯。
|
||||
|
||||
#### 常规:Jump 系列
|
||||
|
||||
分为 jmp 无条件跳转,和 j*condition* 有条件跳转两种。其中条件跳转可以**部分**参考 pwsh 的比较:
|
||||
|条件跳转指令字|PowerShell 比较
|
||||
|-|-|
|
||||
|`je` (Equal `==`)|`-eq`|
|
||||
|`jne` (Not Equal `!=`)|`-ne`|
|
||||
|`jz` (Zero `== 0`)|`-eq 0`|
|
||||
|`jg` (Greater than `>`)|`-gt`|
|
||||
|`jge` (Greater than or Equal to `>=`)|`-ge`|
|
||||
|...|
|
||||
|
||||
在 IDA 中,`jmp` `j...`通常跟的是标签(如`LABEL_20`),标签用于指代某一个虚拟地址(32 位程序基址`0x400000`)。
|
||||
跳转指令认出标签指代的地址后,将 EIP 寄存器设为该地址,CPU 从那里继续取指、间址(可能跳过)、执行、中断(可能跳过)四部曲。
|
||||
|
||||
#### 特殊:函数调用
|
||||
|
||||
主要是`call`和`ret`这一对。
|
||||
|
||||
`call lbl`是父级函数去“调用”。它会把函数参数、下一指令地址压入栈,然后无条件跳转到`lbl`标签指代的地址(同时改变 EBP 的值,以便建立新的栈帧);
|
||||
|
||||
相对的,`ret`是子函数要“返回”。在回收子函数栈帧、还原 EBP 之后,`ret`指令会无条件跳转回先前执行到的位置。
|
||||
|
||||
> [!info]
|
||||
> 回收栈帧、还原回父级函数的 EBP 这两步由`leave`指令完成,
|
||||
> 相当于`mov esp, ebp`再`pop ebp`,详见「栈帧」。
|
||||
|
||||
::: details 栈帧
|
||||
|
||||
函数的执行是由进程的栈空间管理的(相应的,`malloc` `new`之类则从堆空间申请内存),正所谓“函数调用栈”。
|
||||
栈帧通常会记录局部变量等临时用到的数据,同时也是实现函数调用的重要跳板。
|
||||
|
||||
设有这么两段代码:
|
||||
```c
|
||||
int eg_sub(int x, int y) { return x * y; }
|
||||
int example() {
|
||||
int a = 10;
|
||||
int b = eg_sub(a, 1024);
|
||||
return b - a;
|
||||
}
|
||||
```
|
||||
又假设`example()`被 main 函数调用,那么栈帧可能会是这种分布:
|
||||
|地址|...|备注|
|
||||
|-|:--:|-|
|
||||
|0x520|(`main()`的 EBP)|`example()`栈帧从这里开始|
|
||||
|0x51C|int a = 10|
|
||||
|0x518|int b|
|
||||
|0x514|(空余 8B)|`gcc`编译器要求栈帧大小为 16B 的整数倍|
|
||||
|0x50C|1024|参数 y|
|
||||
|0x508|10|参数 x,即复制 a 的值|
|
||||
|0x504|调用`eg_sub()`时 EIP 指向的下一指令地址|亦即被`call`压栈、`ret`返回的地址<br>`example()`栈帧到此结束|
|
||||
|0x500|(`example()`函数的 EBP)|这里是`eg_sub()`的栈帧了|
|
||||
|...|...||
|
||||
|
||||
调用`eg_sub()`前,首先把参数`y` `x`压栈(`cdecl`约定采取反向入栈)。
|
||||
对于`int b`那一行语句,我们不妨拆成这样的汇编指令:
|
||||
```
|
||||
push ecx # 设 a=10 位于 ecx
|
||||
call eg_sub # 函数调用,返回值在 eax
|
||||
mov ebx, eax # 假设 b 在 ebx,把返回值赋给 b
|
||||
```
|
||||
那么执行到`call`指令时,EIP 指向下一条`mov`指令,于是`call`指令保存(入栈)EIP 的值,放心地跳转到`eg_sub`的指令地址去了。
|
||||
|
||||
在进入`eg_sub`那里之后,首先建立它自己的栈帧:
|
||||
```
|
||||
push ebp
|
||||
...
|
||||
mov ecx, [ebp + 12] # 假设 ecx 存 y
|
||||
mov edx, [ebp + 8] # 假设 edx 存 x
|
||||
...
|
||||
```
|
||||
执行完之后保存返回值`mov eax, ...`,回收栈帧、还原现场`leave`,然后`ret`指令跳转回`example()`。
|
||||
`ret`指令把执行`call`指令时的“下一指令地址”弹回 EIP 寄存器,然后 CPU 就若无其事地继续跑 example 函数了。
|
||||
:::
|
||||
|
||||
> [!note]
|
||||
> 王道计组书和视频课「过程调用的机器级表示」那一节对于`call` `ret`指令以及栈帧的介绍可能更清楚一点。
|
||||
> 24 考研距今也有半年余了,恕我没有办法准确地复述出来。
|
||||
|
||||
### 寻址方式
|
||||
上面讲栈帧出现了个`[ebp + 12]`,涉及到两种寻址:寄存器间接寻址和 EBP“相对寻址”。
|
||||
|
||||
> [!important]
|
||||
> 注意我这“相对寻址”是打了引号的,因为并不是以 PC(或者说 IP、EIP 寄存器)为基准的相对,而是 EBP。
|
||||
|
||||
首先是 EBP 寻址。进程由操作系统管理,其堆栈空间在内存中开辟。既然如此,EBP 和 ESP 的值实际上就是指向内存中栈空间的地址。
|
||||
比如在上面「栈帧」里举的例子,执行`example()`函数主体时,\[EBP\]=0x520,\[ESP\]=0x504;
|
||||
进入`eg_sub()`函数调用后,\[EBP\] 则变为 0x500。
|
||||
|
||||
于是,我们可以对栈指针 ESP 和帧指针 EBP 做加减运算,找出函数参数、局部变量等信息。
|
||||
例如上面建立`eg_sub`的栈帧时把函数参数从栈里读出来(不是`pop`出栈),就用`eg_sub`的 EBP 往上加。
|
||||
由于两个栈帧之间总隔着一个“返回地址”,所以第一个参数并不是`+4`,而是`+8`。
|
||||
而相对的,访问局部变量可以用 EBP 往下减,`EBP - 4`,`EBP - 8`,之类的。
|
||||
|
||||
> 通常来说,ESP 容易受`pop` `push`指令的影响,比较“多动”;而 EBP 相比起来更“安稳”一些。
|
||||
> 当然 ESP 寻址肯定是有的,Syringe 里的`XXX_STACK`就是 ESP 寻址。只是 EBP 寻址我讨论起来方便。
|
||||
|
||||
其次是寄存器间接寻址。对 EBP 指针做加减运算,找到参数、局部的地址之后,还需要做一次间接寻址,去内存里把真正的数据抓出来。
|
||||
间接寻址不需要你操心,我只是让你注意寄存器旁边的中括号而已:
|
||||
```
|
||||
mov eax, ebx # 把 EBX 寄存器里的值直接传给 EAX
|
||||
mov eax, [ebx] # 把 EBX 里的内存地址取出来,再读那个内存地址,把数据传进 EAX。
|
||||
```
|
||||
|
||||
## 初探 IDA
|
||||
|
||||
### 案例
|
||||
|
||||
FA2 的“国家”和“所属”是靠后缀区分的,国家直接取自 Rules*.ini,所属则是在国家基础上添加了` House`后缀,比如国家`YuriCountry`和所属`YuriCountry House`。
|
||||
默认在触发编辑器属性页里,触发所属方会截断空格,只许你选“国家”。现要求把这个碍事的截断给干掉,方便我们实现多人合作地图的“所属”关联。
|
||||
|
||||
### 逆向分析
|
||||
|
||||
::: details 有源码做题就是快
|
||||
注意到`TriggerOptionsDlg.cpp`里关于“触发所属方”的事件定义:
|
||||
```cpp {14,}
|
||||
void CTriggerOptionsDlg::OnEditchangeHouse()
|
||||
{
|
||||
// ... 前面忘了
|
||||
|
||||
CString newHouse;
|
||||
m_House.GetWindowText(newHouse); // 实际是 GetWindowTextA
|
||||
|
||||
// FA2 读完所属会用 CSF 本地化这些窗口控件的所属名字(但是非常鸡肋)
|
||||
// 这一步又把本地化的所属翻译回 INI 的所属 ID
|
||||
newHouse=TranslateHouse(newHouse);
|
||||
|
||||
newHouse.TrimLeft();
|
||||
// 如果你英语好一点,空格 => space,你便已经找到要淦的位置了:
|
||||
TruncSpace(newHouse);
|
||||
|
||||
// ... 后面忘了
|
||||
}
|
||||
```
|
||||
右键对`TruncSpace`转到定义,可以在`functions.cpp`发现:
|
||||
```cpp
|
||||
void TruncSpace(CString& str)
|
||||
{
|
||||
str.TrimLeft();
|
||||
str.TrimRight();
|
||||
if(str.Find(" ")>=0) str.Delete(str.Find(" "), str.GetLength()-str.Find(" "));
|
||||
}
|
||||
```
|
||||
于是确定我们要干掉的就是这个`TruncSpace`。
|
||||
|
||||
当然了前面说过,书伸还没搬运完 FA2sp 的功能修复,改源码暂时没什么意义。
|
||||
:::
|
||||
|
||||
> 开始之前赞美一下书伸,书门!(
|
||||
> 没有书伸的成果,我不可能很快找出待修改函数的虚拟地址。
|
||||
|
||||
在 32 位 IDA 里新建一个反编译项目,打开 FA2 的主程序。我们案例要淦的函(方)数(法)位于`0x501D90`,在菜单栏`Jump`里找到`Jump to address`,把这个地址复制进去确认。
|
||||
默认它会切换为 Graph View,你需要右键改为 Text View:
|
||||
|
||||

|
||||
|
||||
往下翻到`.text:00501E58`,注意到`GetWindowTextA`这个 WinAPI。如果你翻看了上面的源码,就会发现我们离目标不远了。
|
||||
|
||||
> [!tip]
|
||||
> 引用的 API,比如说 WinAPI 或者 CString 类的 API,地址通常都比较靠后。
|
||||
> 在 Text View 里双击那个`GetWindowTextA`,可以发现地址跑到`0x553134`去力(瞄完可以用工具栏上的左箭头返回我们正文看的位置)。
|
||||
> 所以接下来不要认错函数调用咯。
|
||||
|
||||
借着上面的提示,同屏`GetWindowTextA`后面只剩两个怀疑对象:`sub_43C3C0`和`sub_43EA90`。
|
||||
|
||||

|
||||
|
||||
接下来看看这两个嫌疑函数的特征。直接菜单栏`View`,`Open subviews`,`Generate pseudocode (F5)`生成反汇编代码,于是我们得到案例方法的 C 式伪代码:
|
||||
|
||||

|
||||
|
||||
由上面的源码可得,截断空格的函数`TruncSpace`只有一个参数,至此我们确定是`sub_43EA90`背锅。
|
||||
|
||||
## 编写 Hook
|
||||
|
||||
目前我已知两种 Hook 用法,我们这里写的 Hook 是第二种用途:
|
||||
|
||||
- 在原函数里新增内容实现扩展(`return 0`)
|
||||
- 绕过(或覆盖)原函数的执行流程(`return`到目标地址)
|
||||
|
||||
### 背景芝士:Syringe
|
||||
|
||||
`Syringe.h`提供了定义 Hook 的宏:
|
||||
```cpp
|
||||
#define EXPORT_FUNC(name) extern "C" __declspec(dllexport) DWORD __cdecl name (REGISTERS *R)
|
||||
|
||||
#define DEFINE_HOOK(hook, funcname, size) \
|
||||
declhook(hook, funcname, size) \
|
||||
EXPORT_FUNC(funcname)
|
||||
```
|
||||
|
||||
更详细的介绍可以翻 Zero Fanker 的 Ares Wiki。这里只需要知道,写 Hook 靠`DEFINE_HOOK`准没错。
|
||||
然后解释一下`DEFINE_HOOK`这个宏要补的三个参数:
|
||||
|
||||
- `hook`:即你要灌注(覆盖)的地址。
|
||||
|
||||
> 毕竟你外部定义的 Hook 不可能凭空插入原程序里,肯定需要遮掉原有的一部分指令机器码,才有机会跳转到你的 Hook。
|
||||
|
||||
- `funcname`:即你的 Hook 名字。
|
||||
|
||||
> [!warning]
|
||||
> 虽然 Hook 名字实际上就是 DLL 导出的函数名字,但并不推荐随性的命名。最好还是讲清楚你淦的原函数叫什么,或者你写这个 Hook 要做什么。
|
||||
|
||||
- `size`:即 Hook 覆盖多少字节的原函数指令码(bixv >= 5B)
|
||||
|
||||
::: info 简单提一嘴 Syringe 如何“灌注”Hook:
|
||||
完整版可以参考 Thomas 写的[高阶知识:Syringe 的工作原理](https://gitee.com/Zero_Fanker/Ares/wikis/%E9%AB%98%E9%98%B6%E7%9F%A5%E8%AF%86/Syringe%E7%9A%84%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86)。
|
||||
|
||||
浓缩版就是,向`hook`的地址那里写入`jmp`无条件跳转指令。由于`jmp`指令码本身占 1B,后面跟的虚拟地址总是占 4B,故`size`至少得是 5。
|
||||
那倘若要覆盖超过 5B 的机器代码呢?答案是多余部分用`nop`(空指令,什么也不做)填充。
|
||||
|
||||

|
||||
:::
|
||||
|
||||
### 注意事项
|
||||
|
||||
我们这里针对的是函数调用,需要注意 C++ 的函数执行完成后**会触发栈区局部变量的析构函数**(通常是空间回收),因此并不建议把传参的汇编指令也给覆盖掉。
|
||||
|
||||
就这个例子而言,只需覆盖`call`指令:
|
||||
|
||||
> [!tip]
|
||||
> 在 IDA 选项(`Options` > `General`)里,右上角勾选`Stack Pointer`,把`Number of opcode bytes`改为 8,确认即可看到机器码视图。
|
||||
> 然后你就会发现`call`指令刚好 5 个字节。
|
||||
>
|
||||
> 
|
||||
|
||||
### 实战
|
||||
|
||||
> 都有现成项目 FA2sp 了,你不会想着要白手起家吧?
|
||||
|
||||
在 FA2sp 项目里依次打开`FA2sp\Ext\CTriggerOption`,在`Hooks.cpp`里添一个 Hook:
|
||||
```cpp
|
||||
DEFINE_HOOK(501EAD, CTriggerOption_OnCBHouseChanged, 5)
|
||||
{
|
||||
// 这里什么都不用做,我们只是跳过 FA2 截断空格那一步而已。
|
||||
return 0x501EB2;
|
||||
}
|
||||
```
|
||||
由于`declhook`宏设置 Hook 位置时已经标了`0x`(可以在 Visual Studio 里把鼠标移到宏上面预览展开的代码),
|
||||
这里`DEFINE_HOOK`后面设置的地址就不需要再补`0x`了。
|
||||
|
||||
## 补充
|
||||
|
||||
### REGISTERS 寄存器类
|
||||
在上面的「背景芝士」中,注意到导出的 Hook 函数只有一个`REGISTERS`类的指针参数 R。
|
||||
有时我们会需要获取原函数的实参、局部变量等信息,并加以修改,这时就要靠 R 指针获取了:
|
||||
|
||||
[进阶知识:Hook 函数的用法](https://gitee.com/Zero_Fanker/Ares/wikis/%E8%BF%9B%E9%98%B6%E7%9F%A5%E8%AF%86/HOOK%E5%87%BD%E6%95%B0%E7%9A%84%E7%94%A8%E6%B3%95)
|
||||
|
||||
具体的例子还要结合已有的`idb`逆向成果自行意会。虽然函数调用的基本原理在计组那一块已有涉及,
|
||||
但一个函数叫什么名字、里面什么寄存器对应什么变量,这些都是前辈们自行逆向出来的结论。对此,咱还是保留点最起码的尊重罢。
|
BIN
docs/notes/RA2/bottle_kill_proc.webp
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
docs/notes/RA2/bottle_preferences.webp
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
docs/notes/RA2/bottles_main.webp
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
docs/notes/RA2/bottles_new_venv.webp
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
docs/notes/RA2/ida_call_opcode.webp
Executable file
After Width: | Height: | Size: 26 KiB |
BIN
docs/notes/RA2/ida_find_calls.webp
Executable file
After Width: | Height: | Size: 80 KiB |
BIN
docs/notes/RA2/ida_graph_view.webp
Executable file
After Width: | Height: | Size: 112 KiB |
BIN
docs/notes/RA2/ida_recog_func_calls.webp
Executable file
After Width: | Height: | Size: 42 KiB |
BIN
docs/notes/RA2/linux_bottles_ES.webp
Normal file
After Width: | Height: | Size: 101 KiB |
12
docs/notes/README.md
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
title: 笔记
|
||||
index: false
|
||||
article: false
|
||||
timeline: false
|
||||
icon: book
|
||||
category: ''
|
||||
---
|
||||
|
||||
笔记这边类似 B 站、知乎的“专栏”,有能成篇的发现就会考虑写写。
|
||||
|
||||
<Catalog />
|
23
package.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "chloridep-blog",
|
||||
"version": "1.0.0",
|
||||
"description": "ChlorideP Personal Blogs",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"docs:build": "vuepress-vite build docs",
|
||||
"docs:clean-dev": "vuepress-vite dev docs --clean-cache",
|
||||
"docs:dev": "vuepress-vite dev docs",
|
||||
"docs:update-package": "pnpm dlx vp-update"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vuepress/bundler-vite": "2.0.0-rc.19",
|
||||
"@vuepress/plugin-docsearch": "2.0.0-rc.76",
|
||||
"@vuepress/plugin-pwa": "2.0.0-rc.76",
|
||||
"katex": "^0.16.21",
|
||||
"vue": "^3.5.13",
|
||||
"vuepress": "2.0.0-rc.19",
|
||||
"vuepress-theme-hope": "2.0.0-rc.71"
|
||||
},
|
||||
"packageManager": "pnpm@9.15.3+sha512.1f79bc245a66eb0b07c5d4d83131240774642caaa86ef7d0434ab47c0d16f66b04e21e0c086eb61e62c77efc4d7f7ec071afad3796af64892fae66509173893a"
|
||||
}
|
7752
pnpm-lock.yaml
generated
Normal file
BIN
public/assets/icon/apple-icon-152.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
public/assets/icon/chrome-192.png
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
public/assets/icon/chrome-512.png
Normal file
After Width: | Height: | Size: 158 KiB |
BIN
public/assets/icon/chrome-mask-192.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
public/assets/icon/chrome-mask-512.png
Normal file
After Width: | Height: | Size: 151 KiB |
BIN
public/assets/icon/guide-maskable.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
public/assets/icon/ms-icon-144.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
public/assets/images/avatar.webp
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
public/dress/keqing-202501.webp
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
public/favicon.ico
Executable file
After Width: | Height: | Size: 264 KiB |
BIN
public/logo.png
Normal file
After Width: | Height: | Size: 92 KiB |
1
public/logo.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" class="icon" viewBox="0 0 3280.944 2800"><path fill="#41b883" d="M1645.332 601.004h375.675L1081.82 2238.478 142.636 601.004h718.477l220.708 379.704 216.013-379.704z"/><path fill="#41b883" d="M142.636 601.004l939.185 1637.474 939.186-1637.474h-375.675l-563.51 982.484-568.208-982.484z"/><path fill="#35495e" d="M513.188 601.004l568.207 987.23 563.511-987.23h-347.498l-216.013 379.704-220.708-379.704zM1607.792 1311.83l594.678 2.293 187.353-316.325-598.662 2.292zM2198.506 1909.57C2867.436 732.7 2939.502 605.426 2937.874 603.78c-.715-.723 45.303-1.314 102.262-1.314s103.562.428 103.562.951c0 .523-208.57 367.978-463.491 816.567L2216.715 2235.6l-102.1.596-102.102.596z"/><path fill="#41b883" d="M1680.563 2233.328c0-1.34 168.208-298.145 440.375-777.048a4135645.775 4135645.775 0 00337.619-594.19l146.13-257.25 170.746-.04 170.747-.04-5.536 9.741c-3.044 5.358-43.727 77.302-90.407 159.875-85.356 150.992-337.562 595.163-656.602 1156.373l-172 302.559-170.536.588c-93.795.322-170.536.069-170.536-.567z"/><path fill="#35495e" d="M1429.783 1625.351l594.679 2.292 187.353-316.324-598.662 2.292z"/><path fill="#41b883" d="M1524.207 1464.903l608.285 6.877 173.746-320.909h-619.072z"/></svg>
|
After Width: | Height: | Size: 1.2 KiB |
16
public/shared/01-Prefer.conf
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<alias>
|
||||
<family>sans-serif</family>
|
||||
<prefer><family>Noto Sans CJK SC</family></prefer>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>serif</family>
|
||||
<prefer><family>Noto Serif CJK SC</family></prefer>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>monospace</family>
|
||||
<prefer><family>Noto Sans Mono CJK SC</family></prefer>
|
||||
</alias>
|
||||
</fontconfig>
|
26
src/friends.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"#5": {
|
||||
"link": "https://blog.shimakaze.dev",
|
||||
"icon": "https://blog.shimakaze.dev/avatar.webp",
|
||||
"desc": ".NET 壬,也会一些前端。比我厉害。",
|
||||
"name": "frg2089 岛风"
|
||||
},
|
||||
"#6": {
|
||||
"icon": "https://twis.uk/avatar.png",
|
||||
"name": "Twisuki 苏阳",
|
||||
"link": "https://twis.uk",
|
||||
"desc": "一只喜欢到处贴贴的猫。"
|
||||
},
|
||||
"#7": {
|
||||
"link": "https://sfkm.me",
|
||||
"desc": "轻雪工作室主要负责人。非常厉害。",
|
||||
"name": "SnowyKami 神羽",
|
||||
"icon": "https://q.qlogo.cn/g?b=qq&nk=2751454815&s=640"
|
||||
},
|
||||
"#8": {
|
||||
"desc": "可爱的 Python Dev 和音乐人律姐姐~",
|
||||
"name": "MelodyEcho 律回",
|
||||
"link": "https://glowmem.com/",
|
||||
"icon": "https://github.com/AiCorein.png"
|
||||
}
|
||||
}
|
6
src/friends.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { ThemeBlogHomeProjectOptions } from 'vuepress-theme-hope'
|
||||
import friendsJson from './friends.json' with { type: 'json' }
|
||||
|
||||
const friends: ThemeBlogHomeProjectOptions[] = Object.values(friendsJson)
|
||||
|
||||
export default friends
|
60
src/layouts/FriendsHome.vue
Normal file
@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<BlogWrapper>
|
||||
<div class="vp-page vp-blog">
|
||||
<BlogHero>
|
||||
<template #info="{ tagline, isFullScreen, text, alt }">
|
||||
<HitokotoBlogHero v-if="text" :text="text" :alt="alt" />
|
||||
</template>
|
||||
<template #bg>
|
||||
<BingHeroBackground />
|
||||
</template>
|
||||
</BlogHero>
|
||||
<div class="blog-page-wrapper">
|
||||
<main id="main-content" class="vp-blog-main friends round-avatar">
|
||||
<DropTransition appear :delay="0.16">
|
||||
<div class="theme-hope-content">
|
||||
<ProjectPanel :items="friends" />
|
||||
</div>
|
||||
</DropTransition>
|
||||
<DropTransition appear :delay="0.28">
|
||||
<MarkdownContent />
|
||||
</DropTransition>
|
||||
</main>
|
||||
|
||||
<DropTransition appear :delay="0.4">
|
||||
<InfoPanel />
|
||||
</DropTransition>
|
||||
</div>
|
||||
</div>
|
||||
</BlogWrapper>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import BlogHero from 'vuepress-theme-hope/blog/components/BlogHero.js'
|
||||
import BlogWrapper from 'vuepress-theme-hope/blog/components/BlogWrapper.js'
|
||||
import InfoPanel from 'vuepress-theme-hope/blog/components/InfoPanel.js'
|
||||
import ProjectPanel from 'vuepress-theme-hope/blog/components/ProjectPanel.js'
|
||||
import MarkdownContent from 'vuepress-theme-hope/components/MarkdownContent.js'
|
||||
import { DropTransition } from 'vuepress-theme-hope/components/transitions/DropTransition.js'
|
||||
import BingHeroBackground from 'vuepress-theme-hope/presets/BingHeroBackground.js'
|
||||
import HitokotoBlogHero from 'vuepress-theme-hope/presets/HitokotoBlogHero.js'
|
||||
|
||||
import 'vuepress-theme-hope/modules/blog/styles/home.scss'
|
||||
import friends from "../friends"
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
main.friends.round-avatar {
|
||||
.vp-project-card {
|
||||
img.vp-project-icon {
|
||||
border-radius: 9999px;
|
||||
height: 2em;
|
||||
}
|
||||
width: calc(50% - 40px);
|
||||
|
||||
@media (max-width: 959px) {
|
||||
width: 100%
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
13
src/navbar.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { navbar } from "vuepress-theme-hope";
|
||||
|
||||
export default navbar([
|
||||
"/",
|
||||
"/archives/",
|
||||
"/notes/",
|
||||
"/diaries/",
|
||||
{
|
||||
text: "友链",
|
||||
icon: "heart",
|
||||
link: "/friends/",
|
||||
},
|
||||
]);
|
27
src/sidebar.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { sidebar } from "vuepress-theme-hope";
|
||||
|
||||
export default sidebar({
|
||||
"/": [
|
||||
// "",
|
||||
{
|
||||
text: "综述",
|
||||
icon: "folder-open",
|
||||
prefix: "archives/",
|
||||
children: "structure",
|
||||
},
|
||||
{
|
||||
text: "笔记",
|
||||
icon: "book",
|
||||
prefix: "notes/",
|
||||
children: "structure",
|
||||
},
|
||||
{
|
||||
text: "随想",
|
||||
icon: "paper-plane",
|
||||
prefix: "diaries/",
|
||||
children: "structure",
|
||||
},
|
||||
// "intro",
|
||||
// "friends/"
|
||||
],
|
||||
});
|
168
src/theme.hope.ts
Normal file
@ -0,0 +1,168 @@
|
||||
import { hopeTheme } from "vuepress-theme-hope";
|
||||
import navbar from "./navbar.js";
|
||||
import sidebar from "./sidebar.js";
|
||||
|
||||
export default hopeTheme(
|
||||
{
|
||||
hostname: "https://agxcoy.shimakaze.org",
|
||||
|
||||
author: {
|
||||
name: "SilverAg.L",
|
||||
url: "https://github.com/AgxCOy",
|
||||
},
|
||||
logo: "/assets/images/avatar.webp",
|
||||
|
||||
// repo: "AgxCOy/blogs", // now becomes private.
|
||||
|
||||
docsDir: "docs",
|
||||
|
||||
// 导航栏
|
||||
navbar,
|
||||
// 侧边栏
|
||||
sidebar,
|
||||
// 页脚
|
||||
footer: "喵?……嗯哼♡",
|
||||
displayFooter: true,
|
||||
|
||||
// 博客相关
|
||||
blog: {
|
||||
description: "希望你我都能……“玩”得尽兴♡",
|
||||
// intro: "/intro.html",
|
||||
medias: {
|
||||
BiliBili: "https://space.bilibili.com/301413212",
|
||||
Email: "mailto:caclx@outlook.com",
|
||||
GitHub: "https://github.com/AgxCOy",
|
||||
Steam: "https://steamcommunity.com/id/silveraglin/",
|
||||
},
|
||||
},
|
||||
|
||||
// 加密配置
|
||||
// encrypt: {
|
||||
// config: {
|
||||
// "/demo/encrypt.html": ["1234"],
|
||||
// },
|
||||
// },
|
||||
|
||||
markdown: {
|
||||
align: true,
|
||||
alert: true,
|
||||
attrs: true,
|
||||
codeTabs: true,
|
||||
component: true,
|
||||
demo: true,
|
||||
figure: true,
|
||||
footnote: true,
|
||||
imgLazyload: true,
|
||||
imgSize: true,
|
||||
include: true,
|
||||
mark: true,
|
||||
plantuml: true,
|
||||
spoiler: true,
|
||||
stylize: [
|
||||
{
|
||||
matcher: "Recommended",
|
||||
replacer: ({ tag }) => {
|
||||
if (tag === "em")
|
||||
return {
|
||||
tag: "Badge",
|
||||
attrs: { type: "tip" },
|
||||
content: "Recommended",
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
sub: true,
|
||||
sup: true,
|
||||
tabs: true,
|
||||
tasklist: true,
|
||||
vPre: true,
|
||||
// katex
|
||||
math: true,
|
||||
},
|
||||
|
||||
// 多语言配置
|
||||
metaLocales: {
|
||||
editLink: "在 GitHub 上编辑此页",
|
||||
},
|
||||
|
||||
// 如果想要实时查看任何改变,启用它。注: 这对更新性能有很大负面影响
|
||||
// hotReload: true,
|
||||
|
||||
// 在这里配置主题提供的插件
|
||||
plugins: {
|
||||
blog: {
|
||||
excerptLength: 0,
|
||||
},
|
||||
|
||||
icon: {
|
||||
assets: "fontawesome-with-brands"
|
||||
},
|
||||
|
||||
docsearch: {
|
||||
apiKey: '665b95419abc56fb78211160bc331a81',
|
||||
appId: 'JDO4FJXBV7',
|
||||
indexName: 'agxcoy-shimakaze'
|
||||
},
|
||||
|
||||
components: {
|
||||
components: ["Badge", "VPCard"],
|
||||
},
|
||||
|
||||
// 如果你需要 PWA。安装 @vuepress/plugin-pwa 并取消下方注释
|
||||
pwa: {
|
||||
favicon: "/favicon.ico",
|
||||
cacheHTML: false,
|
||||
cacheImage: false,
|
||||
appendBase: true,
|
||||
apple: {
|
||||
icon: "/assets/icon/apple-icon-152.png",
|
||||
statusBarColor: "black",
|
||||
},
|
||||
manifest: {
|
||||
icons: [
|
||||
{
|
||||
src: "/assets/icon/chrome-mask-512.png",
|
||||
sizes: "512x512",
|
||||
purpose: "maskable",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "/assets/icon/chrome-mask-192.png",
|
||||
sizes: "192x192",
|
||||
purpose: "maskable",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "/assets/icon/chrome-512.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "/assets/icon/chrome-192.png",
|
||||
sizes: "192x192",
|
||||
type: "image/png",
|
||||
},
|
||||
],
|
||||
// shortcuts: [
|
||||
// {
|
||||
// name: "Demo",
|
||||
// short_name: "Demo",
|
||||
// url: "/demo/",
|
||||
// icons: [
|
||||
// {
|
||||
// src: "/assets/icon/guide-maskable.png",
|
||||
// sizes: "192x192",
|
||||
// purpose: "maskable",
|
||||
// type: "image/png",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// ],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"custom": true
|
||||
}
|
||||
);
|
16
tsconfig.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"target": "ES2022",
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": [
|
||||
"vuepress.*.ts",
|
||||
"src/**/*.ts",
|
||||
"src/**/*.vue"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
6
vuepress.client.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { defineClientConfig } from 'vuepress/client'
|
||||
import FriendsHome from './src/layouts/FriendsHome.vue'
|
||||
|
||||
export default defineClientConfig({
|
||||
layouts: { FriendsHome },
|
||||
})
|
48
vuepress.config.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { defineUserConfig } from "vuepress";
|
||||
import theme from "./src/theme.hope.js";
|
||||
|
||||
export default defineUserConfig({
|
||||
base: "/",
|
||||
|
||||
lang: "zh-CN",
|
||||
title: "SilverAg.L",
|
||||
description: "his personal blogs",
|
||||
|
||||
temp: ".temp",
|
||||
cache: ".cache",
|
||||
public: "public",
|
||||
dest: "dist",
|
||||
|
||||
theme,
|
||||
|
||||
// 和 PWA 一起启用
|
||||
shouldPrefetch: false,
|
||||
|
||||
head: [
|
||||
// 导入相应链接
|
||||
["link", { rel: "preconnect", href: "https://fonts.googleapis.com" }],
|
||||
[
|
||||
"link",
|
||||
{ rel: "preconnect", href: "https://fonts.gstatic.com", crossorigin: "" },
|
||||
],
|
||||
[
|
||||
"link",
|
||||
{
|
||||
href: "https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400..700&display=swap",
|
||||
as: "font",
|
||||
crossorigin: "",
|
||||
},
|
||||
],
|
||||
[
|
||||
"link",
|
||||
{
|
||||
href: "https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400..700&display=swap",
|
||||
rel: "stylesheet",
|
||||
as: "font",
|
||||
crossorigin: "",
|
||||
media: "print",
|
||||
onload: "this.media='all'",
|
||||
},
|
||||
],
|
||||
],
|
||||
});
|