Update On Mon Sep 2 20:34:11 CEST 2024

This commit is contained in:
github-action[bot]
2024-09-02 20:34:11 +02:00
parent e2e637c916
commit 144e46a9ef
121 changed files with 2249 additions and 1278 deletions

1
.github/update.log vendored
View File

@@ -751,3 +751,4 @@ Update On Wed Aug 28 20:33:23 CEST 2024
Update On Fri Aug 30 20:32:59 CEST 2024
Update On Sat Aug 31 20:34:23 CEST 2024
Update On Sun Sep 1 20:30:49 CEST 2024
Update On Mon Sep 2 20:34:00 CEST 2024

View File

@@ -40,8 +40,8 @@ subprojects {
minSdk = 21
targetSdk = 31
versionName = "2.10.3"
versionCode = 210003
versionName = "2.10.4"
versionCode = 210004
resValue("string", "release_name", "v$versionName")
resValue("integer", "release_code", "$versionCode")

View File

@@ -387,18 +387,18 @@ jobs:
git fetch --tags
echo "PREVERSION=$(git describe --tags --abbrev=0 HEAD)" >> $GITHUB_ENV
- name: Merge Alpha branch into Meta
- name: Force push Alpha branch to Meta
run: |
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git config --global user.name "github-actions[bot]"
git fetch origin Alpha:Alpha
git merge Alpha
git push origin Meta
git push origin Alpha:Meta --force
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Tag the commit
- name: Tag the commit on Alpha
run: |
git checkout Alpha
git tag ${{ github.event.inputs.version }}
git push origin ${{ github.event.inputs.version }}
env:

View File

@@ -2,7 +2,6 @@ package updater
import (
"archive/zip"
"errors"
"fmt"
"io"
"os"
@@ -12,23 +11,26 @@ import (
"sync"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
)
var (
ExternalUIURL string
ExternalUIPath string
ExternalUIFolder string
ExternalUIName string
)
var (
ErrIncompleteConf = errors.New("ExternalUI configure incomplete")
AutoUpdateUI bool
)
var xdMutex sync.Mutex
func UpdateUI() error {
xdMutex.Lock()
defer xdMutex.Unlock()
err := prepareUIPath()
if err != nil {
return fmt.Errorf("prepare UI path failed: %w", err)
}
data, err := downloadForBytes(ExternalUIURL)
if err != nil {
return fmt.Errorf("can't download file: %w", err)
@@ -40,7 +42,7 @@ func UpdateUI() error {
}
defer os.Remove(saved)
err = cleanup(ExternalUIFolder)
err = cleanup(ExternalUIPath)
if err != nil {
if !os.IsNotExist(err) {
return fmt.Errorf("cleanup exist file error: %w", err)
@@ -52,28 +54,20 @@ func UpdateUI() error {
return fmt.Errorf("can't extract zip file: %w", err)
}
err = os.Rename(unzipFolder, ExternalUIFolder)
err = os.Rename(unzipFolder, ExternalUIPath)
if err != nil {
return fmt.Errorf("can't rename folder: %w", err)
return fmt.Errorf("rename UI folder failed: %w", err)
}
return nil
}
func PrepareUIPath() error {
if ExternalUIPath == "" || ExternalUIURL == "" {
return ErrIncompleteConf
}
if ExternalUIName != "" {
ExternalUIFolder = filepath.Clean(path.Join(ExternalUIPath, ExternalUIName))
func prepareUIPath() error {
if _, err := os.Stat(ExternalUIPath); os.IsNotExist(err) {
log.Infoln("dir %s does not exist, creating", ExternalUIPath)
if err := os.MkdirAll(ExternalUIPath, os.ModePerm); err != nil {
return err
log.Warnln("create dir %s error: %s", ExternalUIPath, err)
}
}
} else {
ExternalUIFolder = ExternalUIPath
}
return nil
}

View File

@@ -7,7 +7,6 @@ import (
"net"
"net/netip"
"net/url"
"os"
"path"
"regexp"
"strings"
@@ -704,33 +703,23 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
}
N.DisableKeepAlive = cfg.DisableKeepAlive
updater.ExternalUIPath = cfg.ExternalUI
// checkout externalUI exist
if updater.ExternalUIPath != "" {
updater.ExternalUIPath = C.Path.Resolve(updater.ExternalUIPath)
if _, err := os.Stat(updater.ExternalUIPath); os.IsNotExist(err) {
defaultUIpath := path.Join(C.Path.HomeDir(), "ui")
log.Warnln("external-ui: %s does not exist, creating folder in %s", updater.ExternalUIPath, defaultUIpath)
if err := os.MkdirAll(defaultUIpath, os.ModePerm); err != nil {
return nil, err
}
updater.ExternalUIPath = defaultUIpath
cfg.ExternalUI = defaultUIpath
}
}
// checkout UIpath/name exist
if cfg.ExternalUIName != "" {
updater.ExternalUIName = cfg.ExternalUIName
if cfg.ExternalUI != "" {
updater.AutoUpdateUI = true
updater.ExternalUIPath = C.Path.Resolve(cfg.ExternalUI)
} else {
updater.ExternalUIFolder = updater.ExternalUIPath
}
if cfg.ExternalUIURL != "" {
updater.ExternalUIURL = cfg.ExternalUIURL
// default externalUI path
updater.ExternalUIPath = path.Join(C.Path.HomeDir(), "ui")
}
err := updater.PrepareUIPath()
if err != nil {
log.Errorln("PrepareUIPath error: %s", err)
// checkout UIpath/name exist
if cfg.ExternalUIName != "" {
updater.AutoUpdateUI = true
updater.ExternalUIPath = path.Join(updater.ExternalUIPath, cfg.ExternalUIName)
}
if cfg.ExternalUIURL != "" {
updater.ExternalUIURL = cfg.ExternalUIURL
}
return &General{

View File

@@ -381,12 +381,12 @@ func updateTunnels(tunnels []LC.Tunnel) {
}
func initExternalUI() {
if updater.ExternalUIFolder != "" {
dirEntries, _ := os.ReadDir(updater.ExternalUIFolder)
if updater.AutoUpdateUI {
dirEntries, _ := os.ReadDir(updater.ExternalUIPath)
if len(dirEntries) > 0 {
log.Infoln("UI already exists")
log.Infoln("UI already exists, skip downloading")
} else {
log.Infoln("UI not exists, downloading")
log.Infoln("External UI downloading ...")
updater.UpdateUI()
}
}

View File

@@ -60,9 +60,15 @@ func getGroupDelay(w http.ResponseWriter, r *http.Request) {
return
}
if proxy.(*adapter.Proxy).Type() == C.URLTest {
URLTestGroup := proxy.(*adapter.Proxy).ProxyAdapter.(*outboundgroup.URLTest)
URLTestGroup.ForceSet("")
switch proxy.(*adapter.Proxy).Type() {
case C.URLTest:
if urlTestGroup, ok := proxy.(*adapter.Proxy).ProxyAdapter.(*outboundgroup.URLTest); ok {
urlTestGroup.ForceSet("")
}
case C.Fallback:
if fallbackGroup, ok := proxy.(*adapter.Proxy).ProxyAdapter.(*outboundgroup.Fallback); ok {
fallbackGroup.ForceSet("")
}
}
if proxy.(*adapter.Proxy).Type() != C.Selector {

View File

@@ -1,7 +1,6 @@
package route
import (
"errors"
"fmt"
"net/http"
"os"
@@ -50,15 +49,9 @@ func upgradeCore(w http.ResponseWriter, r *http.Request) {
func updateUI(w http.ResponseWriter, r *http.Request) {
err := updater.UpdateUI()
if err != nil {
if errors.Is(err, updater.ErrIncompleteConf) {
log.Warnln("%s", err)
render.Status(r, http.StatusNotImplemented)
render.JSON(w, r, newError(fmt.Sprintf("%s", err)))
} else {
log.Warnln("%s", err)
render.Status(r, http.StatusInternalServerError)
render.JSON(w, r, newError(fmt.Sprintf("%s", err)))
}
return
}

View File

@@ -107,7 +107,6 @@ require (
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.24.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.3.0 // indirect
)

View File

@@ -267,8 +267,6 @@ google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6h
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -2,7 +2,6 @@ package updater
import (
"archive/zip"
"errors"
"fmt"
"io"
"os"
@@ -12,23 +11,26 @@ import (
"sync"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
)
var (
ExternalUIURL string
ExternalUIPath string
ExternalUIFolder string
ExternalUIName string
)
var (
ErrIncompleteConf = errors.New("ExternalUI configure incomplete")
AutoUpdateUI bool
)
var xdMutex sync.Mutex
func UpdateUI() error {
xdMutex.Lock()
defer xdMutex.Unlock()
err := prepareUIPath()
if err != nil {
return fmt.Errorf("prepare UI path failed: %w", err)
}
data, err := downloadForBytes(ExternalUIURL)
if err != nil {
return fmt.Errorf("can't download file: %w", err)
@@ -40,7 +42,7 @@ func UpdateUI() error {
}
defer os.Remove(saved)
err = cleanup(ExternalUIFolder)
err = cleanup(ExternalUIPath)
if err != nil {
if !os.IsNotExist(err) {
return fmt.Errorf("cleanup exist file error: %w", err)
@@ -52,28 +54,20 @@ func UpdateUI() error {
return fmt.Errorf("can't extract zip file: %w", err)
}
err = os.Rename(unzipFolder, ExternalUIFolder)
err = os.Rename(unzipFolder, ExternalUIPath)
if err != nil {
return fmt.Errorf("can't rename folder: %w", err)
return fmt.Errorf("rename UI folder failed: %w", err)
}
return nil
}
func PrepareUIPath() error {
if ExternalUIPath == "" || ExternalUIURL == "" {
return ErrIncompleteConf
}
if ExternalUIName != "" {
ExternalUIFolder = filepath.Clean(path.Join(ExternalUIPath, ExternalUIName))
func prepareUIPath() error {
if _, err := os.Stat(ExternalUIPath); os.IsNotExist(err) {
log.Infoln("dir %s does not exist, creating", ExternalUIPath)
if err := os.MkdirAll(ExternalUIPath, os.ModePerm); err != nil {
return err
log.Warnln("create dir %s error: %s", ExternalUIPath, err)
}
}
} else {
ExternalUIFolder = ExternalUIPath
}
return nil
}

View File

@@ -7,7 +7,6 @@ import (
"net"
"net/netip"
"net/url"
"os"
"path"
"regexp"
"strings"
@@ -704,33 +703,23 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
}
N.DisableKeepAlive = cfg.DisableKeepAlive
updater.ExternalUIPath = cfg.ExternalUI
// checkout externalUI exist
if updater.ExternalUIPath != "" {
updater.ExternalUIPath = C.Path.Resolve(updater.ExternalUIPath)
if _, err := os.Stat(updater.ExternalUIPath); os.IsNotExist(err) {
defaultUIpath := path.Join(C.Path.HomeDir(), "ui")
log.Warnln("external-ui: %s does not exist, creating folder in %s", updater.ExternalUIPath, defaultUIpath)
if err := os.MkdirAll(defaultUIpath, os.ModePerm); err != nil {
return nil, err
}
updater.ExternalUIPath = defaultUIpath
cfg.ExternalUI = defaultUIpath
}
}
// checkout UIpath/name exist
if cfg.ExternalUIName != "" {
updater.ExternalUIName = cfg.ExternalUIName
if cfg.ExternalUI != "" {
updater.AutoUpdateUI = true
updater.ExternalUIPath = C.Path.Resolve(cfg.ExternalUI)
} else {
updater.ExternalUIFolder = updater.ExternalUIPath
}
if cfg.ExternalUIURL != "" {
updater.ExternalUIURL = cfg.ExternalUIURL
// default externalUI path
updater.ExternalUIPath = path.Join(C.Path.HomeDir(), "ui")
}
err := updater.PrepareUIPath()
if err != nil {
log.Errorln("PrepareUIPath error: %s", err)
// checkout UIpath/name exist
if cfg.ExternalUIName != "" {
updater.AutoUpdateUI = true
updater.ExternalUIPath = path.Join(updater.ExternalUIPath, cfg.ExternalUIName)
}
if cfg.ExternalUIURL != "" {
updater.ExternalUIURL = cfg.ExternalUIURL
}
return &General{

View File

@@ -381,12 +381,12 @@ func updateTunnels(tunnels []LC.Tunnel) {
}
func initExternalUI() {
if updater.ExternalUIFolder != "" {
dirEntries, _ := os.ReadDir(updater.ExternalUIFolder)
if updater.AutoUpdateUI {
dirEntries, _ := os.ReadDir(updater.ExternalUIPath)
if len(dirEntries) > 0 {
log.Infoln("UI already exists")
log.Infoln("UI already exists, skip downloading")
} else {
log.Infoln("UI not exists, downloading")
log.Infoln("External UI downloading ...")
updater.UpdateUI()
}
}

View File

@@ -60,9 +60,15 @@ func getGroupDelay(w http.ResponseWriter, r *http.Request) {
return
}
if proxy.(*adapter.Proxy).Type() == C.URLTest {
URLTestGroup := proxy.(*adapter.Proxy).ProxyAdapter.(*outboundgroup.URLTest)
URLTestGroup.ForceSet("")
switch proxy.(*adapter.Proxy).Type() {
case C.URLTest:
if urlTestGroup, ok := proxy.(*adapter.Proxy).ProxyAdapter.(*outboundgroup.URLTest); ok {
urlTestGroup.ForceSet("")
}
case C.Fallback:
if fallbackGroup, ok := proxy.(*adapter.Proxy).ProxyAdapter.(*outboundgroup.Fallback); ok {
fallbackGroup.ForceSet("")
}
}
if proxy.(*adapter.Proxy).Type() != C.Selector {

View File

@@ -1,7 +1,6 @@
package route
import (
"errors"
"fmt"
"net/http"
"os"
@@ -50,15 +49,9 @@ func upgradeCore(w http.ResponseWriter, r *http.Request) {
func updateUI(w http.ResponseWriter, r *http.Request) {
err := updater.UpdateUI()
if err != nil {
if errors.Is(err, updater.ErrIncompleteConf) {
log.Warnln("%s", err)
render.Status(r, http.StatusNotImplemented)
render.JSON(w, r, newError(fmt.Sprintf("%s", err)))
} else {
log.Warnln("%s", err)
render.Status(r, http.StatusInternalServerError)
render.JSON(w, r, newError(fmt.Sprintf("%s", err)))
}
return
}

View File

@@ -1,2 +0,0 @@
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"

View File

@@ -75,7 +75,7 @@ jobs:
run: pnpm install --no-frozen-lockfile
- name: Prepare fronend
run: pnpm -r build # Build frontend
run: pnpm -r build && mkdir -p ./backend/tauri/.tmp/dist # Build frontend
- name: Prepare sidecar and resources
run: pnpm check
- name: Lint

View File

@@ -51,31 +51,15 @@ jobs:
run: |
rustup install stable --profile minimal --no-self-update
rustup default stable
- name: Setup aarch64 Toolchain
- name: Setup Cargo binstall
if: ${{ inputs.aarch64 == true }}
uses: cargo-bins/cargo-binstall@main
- name: Setup Cross Toolchain
if: ${{ inputs.aarch64 == true }}
run: |
rustup target add aarch64-unknown-linux-gnu
sudo apt install gcc-aarch64-linux-gnu -y
sudo dpkg --add-architecture arm64
cat <<EOF
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy main restricted
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy universe
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates universe
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy multiverse
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates multiverse
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-backports main restricted universe multiverse
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security main restricted
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security universe
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security multiverse
EOF | sudo tee /etc/apt/sources.list.d/arm64.list
sudo apt update -y && sudo apt upgrade -y
apt-get install -y libncurses6:arm64 libtinfo6:arm64 linux-libc-dev:arm64 libncursesw6:arm64 libssl3:arm64 libcups2:arm64
apt-get install -y --no-install-recommends g++-aarch64-linux-gnu libc6-dev-arm64-cross libwebkit2gtk-4.0-dev:arm64 libgtk-3-dev:arm64 patchelf:arm64 librsvg2-dev:arm64 libayatana-appindicator3-dev:arm64
- name: Setup x86_64 Toolchain
if: ${{ inputs.aarch64 == false }}
cargo binstall -y cross
- name: Setup Toolchain
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf openssl
@@ -137,17 +121,18 @@ jobs:
CXX_aarch64_unknown_linux_gnu: aarch64-linux-gnu-g++
PKG_CONFIG_PATH: /usr/lib/aarch64-linux-gnu/pkgconfig
PKG_CONFIG_ALLOW_CROSS: 1
run: |
${{ inputs.nightly == true && 'pnpm build:nightly --target aarch64-unknown-linux-gnu -b "rpm,deb,updater"' || 'pnpm build --target aarch64-unknown-linux-gnu -b "rpm,deb,updater"' }}
gh release upload ${{ inputs.tag }} ./backend/target/release/bundle/*.deb ./backend/target/release/bundle/*.rpm --clobber
${{ inputs.nightly == true && 'pnpm build:nightly -r cross --target aarch64-unknown-linux-gnu -b "rpm,deb,updater"' || 'pnpm build -r cross --target aarch64-unknown-linux-gnu -b "rpm,deb,updater"' }}
find ./backend/target \( -name "*.deb" -o -name "*.rpm" \) | while read file; do
gh release upload ${{ inputs.tag }} "$file" --clobber
done
- name: Calc the archive signature
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG_NAME=${{ inputs.tag }}
find ./backend/target/release/bundle \( -name "*.deb" -o -name "*.rpm" \) | while read file; do
find ./backend/target \( -name "*.deb" -o -name "*.rpm" \) | while read file; do
sha_file="$file.sha256"
if [[ ! -f "$sha_file" ]]; then

View File

@@ -0,0 +1,23 @@
name: "Close stale issues and PRs"
on:
schedule:
- cron: "30 1 * * *"
workflow_dispatch:
permissions:
contents: write # only for delete-branch option
issues: write
pull-requests: write
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
stale-issue-message: "This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days."
close-issue-message: "This issue is closed because it has been stale for 5 days with no activity."
days-before-stale: 30
days-before-close: 5
stale-issue-label: "S: Stale"
only-issue-labels: "S: Untriaged"

View File

@@ -27,8 +27,8 @@ jobs:
tag: "pre-release"
secrets: inherit
linux_build:
name: Linux Build
linux_amd64_build:
name: Linux amd64 Build
uses: ./.github/workflows/deps-build-linux.yaml
needs: [delete_current_releases]
with:
@@ -36,6 +36,16 @@ jobs:
tag: "pre-release"
secrets: inherit
linux_aarch64_build:
name: Linux aarch64 Build
uses: ./.github/workflows/deps-build-linux.yaml
needs: [delete_current_releases]
with:
nightly: true
tag: "pre-release"
aarch64: true
secrets: inherit
macos_amd64_build:
name: macOS amd64 Build
uses: ./.github/workflows/deps-build-macos.yaml
@@ -58,7 +68,14 @@ jobs:
update_tag:
name: Update tag
needs: [windows_build, linux_build, macos_amd64_build, macos_aarch64_build]
needs:
[
windows_build,
linux_amd64_build,
linux_aarch64_build,
macos_amd64_build,
macos_aarch64_build,
]
uses: ./.github/workflows/deps-update-tag.yaml
with:
tag: "pre-release"

View File

@@ -16,3 +16,5 @@ tauri.preview.conf.json
.idea
*.tsbuildinfo
**/.tmp

View File

@@ -1273,6 +1273,7 @@ dependencies = [
"objc",
"once_cell",
"open 5.3.0",
"openssl",
"os_pipe",
"oxc_allocator",
"oxc_ast",

View File

@@ -0,0 +1,16 @@
[target.aarch64-unknown-linux-gnu]
image = "ghcr.io/cross-rs/aarch64-unknown-linux-gnu:edge"
pre-build = [
"dpkg --add-architecture $CROSS_DEB_ARCH",
"""apt-get update && apt-get -y install \
libwebkit2gtk-4.0-dev:$CROSS_DEB_ARCH \
libgtk-3-dev:$CROSS_DEB_ARCH \
libayatana-appindicator3-dev:$CROSS_DEB_ARCH \
librsvg2-dev:$CROSS_DEB_ARCH \
libpango1.0-dev:$CROSS_DEB_ARCH \
libcairo2-dev:$CROSS_DEB_ARCH \
libatk1.0-dev:$CROSS_DEB_ARCH \
libsoup2.4-dev:$CROSS_DEB_ARCH \
libssl-dev:$CROSS_DEB_ARCH
""",
]

View File

@@ -137,6 +137,10 @@ mlua = { version = "0.9", features = [
] }
enumflags2 = "0.7"
sha2 = "0.10"
[target.'cfg(all(target_os = "linux", target_arch = "aarch64"))'.dependencies]
openssl = { version = "0.10", features = ["vendored"] }
[target.'cfg(target_os = "macos")'.dependencies]
cocoa = "0.25.0"
objc = "0.2.7"
@@ -185,3 +189,4 @@ verge-dev = []
default-meta = []
devtools = ["tauri/devtools"]
deadlock-detection = ["parking_lot/deadlock_detection"]
openssl_vendored = ["openssl/vendored"]

View File

@@ -1,19 +1,48 @@
use chrono::{DateTime, SecondsFormat, Utc};
use rustc_version::version_meta;
use serde::Deserialize;
use std::{env, fs::read, process::Command};
use std::{
env,
fs::{exists, read},
process::Command,
};
#[derive(Deserialize)]
struct PackageJson {
version: String, // we only need the version
}
#[derive(Deserialize)]
struct TauriJson {
package: PackageJson,
}
#[derive(Deserialize)]
struct GitInfo {
hash: String,
author: String,
time: String,
}
fn main() {
let mut pkg_json = read("../../package.json").unwrap();
let pkg_json: PackageJson = simd_json::from_slice(&mut pkg_json).unwrap();
let version = semver::Version::parse(pkg_json.version.as_str()).unwrap();
let version: String = if let Ok(true) = exists("../../package.json") {
let mut raw = read("../../package.json").unwrap();
let pkg_json: PackageJson = simd_json::from_slice(&mut raw).unwrap();
pkg_json.version
} else {
let mut raw = read("./tauri.conf.json").unwrap(); // TODO: fix it when windows arm64 need it
let tauri_json: TauriJson = simd_json::from_slice(&mut raw).unwrap();
tauri_json.package.version
};
let version = semver::Version::parse(&version).unwrap();
let is_prerelase = !version.pre.is_empty();
println!("cargo:rustc-env=NYANPASU_VERSION={}", pkg_json.version);
println!("cargo:rustc-env=NYANPASU_VERSION={}", version);
// Git Information
let (commit_hash, commit_author, commit_date) = if let Ok(true) = exists("./.tmp/git-info.json")
{
let mut git_info = read("./.tmp/git-info.json").unwrap();
let git_info: GitInfo = simd_json::from_slice(&mut git_info).unwrap();
(git_info.hash, git_info.author, git_info.time)
} else {
let output = Command::new("git")
.args([
"show",
@@ -22,16 +51,23 @@ fn main() {
"--no-notes",
])
.output()
.unwrap();
.expect("Failed to execute git command");
// println!("{}", String::from_utf8(output.stderr.clone()).unwrap());
let command_args: Vec<String> = String::from_utf8(output.stdout)
.unwrap()
.replace('\'', "")
.split(',')
.map(String::from)
.collect();
println!("cargo:rustc-env=COMMIT_HASH={}", command_args[0]);
println!("cargo:rustc-env=COMMIT_AUTHOR={}", command_args[1]);
let commit_date = DateTime::parse_from_rfc3339(command_args[2].as_str())
(
command_args[0].clone(),
command_args[1].clone(),
command_args[2].clone(),
)
};
println!("cargo:rustc-env=COMMIT_HASH={}", commit_hash);
println!("cargo:rustc-env=COMMIT_AUTHOR={}", commit_author);
let commit_date = DateTime::parse_from_rfc3339(&commit_date)
.unwrap()
.with_timezone(&Utc)
.to_rfc3339_opts(SecondsFormat::Millis, true);

View File

@@ -4,10 +4,10 @@
"version": "1.6.0"
},
"build": {
"distDir": "../../frontend/nyanpasu/dist",
"distDir": "./.tmp/dist",
"devPath": "http://localhost:3000/",
"beforeDevCommand": "pnpm run web:dev",
"beforeBuildCommand": "pnpm run web:build"
"beforeBuildCommand": "pnpm run-p web:build generate:git-info && echo $(pwd) && rm -rf ./tauri/.tmp/dist && mv ../frontend/nyanpasu/dist ./tauri/.tmp/dist"
},
"tauri": {
"systemTray": {

View File

@@ -56,7 +56,7 @@
"@vitejs/plugin-react-swc": "3.7.0",
"clsx": "2.1.1",
"sass": "1.77.8",
"shiki": "1.15.2",
"shiki": "1.16.1",
"tailwindcss-textshadow": "2.1.3",
"unplugin-auto-import": "0.18.2",
"unplugin-icons": "0.19.2",

View File

@@ -44,6 +44,6 @@
"sass": "1.77.8",
"tailwind-merge": "2.5.2",
"typescript-plugin-css-modules": "5.1.0",
"vite-plugin-dts": "4.0.3"
"vite-plugin-dts": "4.1.0"
}
}

View File

@@ -43,6 +43,7 @@
"publish": "tsx scripts/publish.ts",
"portable": "tsx scripts/portable.ts",
"upload:osx-aarch64": "tsx scripts/osx-aarch64-upload.ts",
"generate:git-info": "tsx scripts/generate-git-info.ts",
"generate:manifest": "run-p generate:manifest:*",
"generate:manifest:latest-version": "tsx scripts/generate-latest-version.ts",
"prepare": "husky",
@@ -61,7 +62,7 @@
"@tauri-apps/cli": "1.6.1",
"@types/fs-extra": "11.0.4",
"@types/lodash-es": "4.17.12",
"@types/node": "22.5.1",
"@types/node": "22.5.2",
"@typescript-eslint/eslint-plugin": "8.3.0",
"@typescript-eslint/parser": "8.3.0",
"autoprefixer": "10.4.20",
@@ -80,10 +81,10 @@
"eslint-plugin-react": "7.35.0",
"eslint-plugin-react-compiler": "0.0.0-experimental-f8a5409-20240829",
"eslint-plugin-react-hooks": "4.6.2",
"knip": "5.28.0",
"knip": "5.29.1",
"lint-staged": "15.2.9",
"npm-run-all2": "6.2.2",
"postcss": "8.4.42",
"postcss": "8.4.44",
"postcss-html": "1.7.0",
"postcss-import": "16.1.0",
"postcss-scss": "4.0.9",

View File

@@ -24,7 +24,7 @@ importers:
devDependencies:
'@commitlint/cli':
specifier: 19.4.1
version: 19.4.1(@types/node@22.5.1)(typescript@5.5.4)
version: 19.4.1(@types/node@22.5.2)(typescript@5.5.4)
'@commitlint/config-conventional':
specifier: 19.4.1
version: 19.4.1
@@ -41,8 +41,8 @@ importers:
specifier: 4.17.12
version: 4.17.12
'@types/node':
specifier: 22.5.1
version: 22.5.1
specifier: 22.5.2
version: 22.5.2
'@typescript-eslint/eslint-plugin':
specifier: 8.3.0
version: 8.3.0(@typescript-eslint/parser@8.3.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4)
@@ -51,7 +51,7 @@ importers:
version: 8.3.0(eslint@8.57.0)(typescript@5.5.4)
autoprefixer:
specifier: 10.4.20
version: 10.4.20(postcss@8.4.42)
version: 10.4.20(postcss@8.4.44)
conventional-changelog-conventionalcommits:
specifier: 8.0.0
version: 8.0.0
@@ -98,8 +98,8 @@ importers:
specifier: 4.6.2
version: 4.6.2(eslint@8.57.0)
knip:
specifier: 5.28.0
version: 5.28.0(@types/node@22.5.1)(typescript@5.5.4)
specifier: 5.29.1
version: 5.29.1(@types/node@22.5.2)(typescript@5.5.4)
lint-staged:
specifier: 15.2.9
version: 15.2.9
@@ -107,17 +107,17 @@ importers:
specifier: 6.2.2
version: 6.2.2
postcss:
specifier: 8.4.42
version: 8.4.42
specifier: 8.4.44
version: 8.4.44
postcss-html:
specifier: 1.7.0
version: 1.7.0
postcss-import:
specifier: 16.1.0
version: 16.1.0(postcss@8.4.42)
version: 16.1.0(postcss@8.4.44)
postcss-scss:
specifier: 4.0.9
version: 4.0.9(postcss@8.4.42)
version: 4.0.9(postcss@8.4.44)
prettier:
specifier: 3.3.3
version: 3.3.3
@@ -199,7 +199,7 @@ importers:
version: 11.13.0(@emotion/react@11.13.3(react@19.0.0-rc-e948a5ac-20240807)(types-react@19.0.0-rc.1))(react@19.0.0-rc-e948a5ac-20240807)(types-react@19.0.0-rc.1)
'@generouted/react-router':
specifier: 1.19.6
version: 1.19.6(react-router-dom@6.26.1(react-dom@19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807)(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
version: 1.19.6(react-router-dom@6.26.1(react-dom@19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807)(vite@5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
'@juggle/resize-observer':
specifier: 3.4.0
version: 3.4.0
@@ -311,10 +311,10 @@ importers:
version: types-react-dom@19.0.0-rc.1
'@vitejs/plugin-react':
specifier: 4.3.1
version: 4.3.1(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
version: 4.3.1(vite@5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
'@vitejs/plugin-react-swc':
specifier: 3.7.0
version: 3.7.0(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
version: 3.7.0(vite@5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
clsx:
specifier: 2.1.1
version: 2.1.1
@@ -322,8 +322,8 @@ importers:
specifier: 1.77.8
version: 1.77.8
shiki:
specifier: 1.15.2
version: 1.15.2
specifier: 1.16.1
version: 1.16.1
tailwindcss-textshadow:
specifier: 2.1.3
version: 2.1.3
@@ -335,19 +335,19 @@ importers:
version: 0.19.2(@svgr/core@8.1.0(typescript@5.5.4))
vite:
specifier: 5.4.2
version: 5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
version: 5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
vite-plugin-monaco-editor:
specifier: npm:vite-plugin-monaco-editor-new@1.1.3
version: vite-plugin-monaco-editor-new@1.1.3(monaco-editor@0.51.0)
vite-plugin-sass-dts:
specifier: 1.3.25
version: 1.3.25(postcss@8.4.42)(prettier@3.3.3)(sass@1.77.8)(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
version: 1.3.25(postcss@8.4.44)(prettier@3.3.3)(sass@1.77.8)(vite@5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
vite-plugin-svgr:
specifier: 4.2.0
version: 4.2.0(rollup@4.21.0)(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
version: 4.2.0(rollup@4.21.0)(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
vite-tsconfig-paths:
specifier: 5.0.1
version: 5.0.1(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
version: 5.0.1(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
frontend/ui:
dependencies:
@@ -380,7 +380,7 @@ importers:
version: types-react@19.0.0-rc.1
'@vitejs/plugin-react':
specifier: 4.3.1
version: 4.3.1(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
version: 4.3.1(vite@5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
ahooks:
specifier: 3.8.1
version: 3.8.1(react@19.0.0-rc-e948a5ac-20240807)
@@ -404,10 +404,10 @@ importers:
version: 17.5.1(react-dom@19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807)
vite:
specifier: 5.4.2
version: 5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
version: 5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
vite-tsconfig-paths:
specifier: 5.0.1
version: 5.0.1(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
version: 5.0.1(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
devDependencies:
'@emotion/react':
specifier: 11.13.3
@@ -431,8 +431,8 @@ importers:
specifier: 5.1.0
version: 5.1.0(typescript@5.5.4)
vite-plugin-dts:
specifier: 4.0.3
version: 4.0.3(@types/node@22.5.1)(rollup@4.21.0)(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
specifier: 4.1.0
version: 4.1.0(@types/node@22.5.2)(rollup@4.21.0)(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
scripts:
dependencies:
@@ -2048,8 +2048,11 @@ packages:
'@rushstack/ts-command-line@4.22.3':
resolution: {integrity: sha512-edMpWB3QhFFZ4KtSzS8WNjBgR4PXPPOVrOHMbb7kNpmQ1UFS9HdVtjCXg1H5fG+xYAbeE+TMPcVPUyX2p84STA==}
'@shikijs/core@1.15.2':
resolution: {integrity: sha512-hi6XZuwHYn6bU4wtXZxST8ynM55aiU2+rVU9aPIrSxqKmEKl4d65puwGsggwcZWTET+7zGXKe7AUj46iQ8Aq8w==}
'@shikijs/core@1.16.1':
resolution: {integrity: sha512-aI0hBtw+a6KsJp2jcD4YuQqKpeCbURMZbhHVozDknJpm+KJqeMRkEnfBC8BaKE/5XC+uofPgCLsa/TkTk0Ba0w==}
'@shikijs/vscode-textmate@9.2.0':
resolution: {integrity: sha512-5FinaOp6Vdh/dl4/yaOTh0ZeKch+rYS8DUb38V3GMKYVkdqzxw53lViRKUYkVILRiVQT7dcPC7VvAKOR73zVtQ==}
'@sindresorhus/is@4.6.0':
resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
@@ -2492,8 +2495,8 @@ packages:
'@types/node@22.5.0':
resolution: {integrity: sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg==}
'@types/node@22.5.1':
resolution: {integrity: sha512-KkHsxej0j9IW1KKOOAA/XBA0z08UFSrRQHErzEfA3Vgq57eXIMYboIlHJuYIfd+lwCQjtKqUu3UnmKbtUc9yRw==}
'@types/node@22.5.2':
resolution: {integrity: sha512-acJsPTEqYqulZS/Yp/S3GgeE6GZ0qYODUR8aVr/DkhHQ8l9nd4j5x1/ZJy9/gHrRlFMqkO6i0I3E27Alu4jjPg==}
'@types/parse-json@4.0.2':
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
@@ -4623,8 +4626,8 @@ packages:
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
engines: {node: '>=0.10.0'}
knip@5.28.0:
resolution: {integrity: sha512-3nlqKCHFCfXp4VDP570ly7HLCyIM3JyLM+msr2l3HtQJ1NeraBrj6AQE80SFIyu8nOJZZpiZQWmiXq5RSczqsQ==}
knip@5.29.1:
resolution: {integrity: sha512-l8qFtRqNpCk8xf46VOwhBUva7LBwanoGPJ4KQNwVRl6hmEXStf1BJlfbYRZ+yQpbilbIV6LN+ztX6LaGtyd4TQ==}
engines: {node: '>=18.6.0'}
hasBin: true
peerDependencies:
@@ -5465,8 +5468,8 @@ packages:
resolution: {integrity: sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==}
engines: {node: ^10 || ^12 || >=14}
postcss@8.4.42:
resolution: {integrity: sha512-hywKUQB9Ra4dR1mGhldy5Aj1X3MWDSIA1cEi+Uy0CjheLvP6Ual5RlwMCh8i/X121yEDLDIKBsrCQ8ba3FDMfQ==}
postcss@8.4.44:
resolution: {integrity: sha512-Aweb9unOEpQ3ezu4Q00DPvvM2ZTUitJdNKeP/+uQgr1IBIqu574IaZoURId7BKtWMREwzKa9OgzPzezWGPWFQw==}
engines: {node: ^10 || ^12 || >=14}
prelude-ls@1.2.1:
@@ -5931,8 +5934,8 @@ packages:
shell-quote@1.8.1:
resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==}
shiki@1.15.2:
resolution: {integrity: sha512-M+7QZQZiZw/cZeizrC/yryG3eeG8pTUhu7ZaHxVyzPNFIRIlN46YBciquoNPCiXiwLnx6JB62f3lSuSYQrus1w==}
shiki@1.16.1:
resolution: {integrity: sha512-tCJIMaxDVB1mEIJ5TvfZU7kCPB5eo9fli5+21Olc/bmyv+w8kye3JOp+LZRmGkAyT71hrkefQhTiY+o9mBikRQ==}
side-channel@1.0.6:
resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==}
@@ -6581,8 +6584,8 @@ packages:
vue:
optional: true
vite-plugin-dts@4.0.3:
resolution: {integrity: sha512-+xnTsaONwU2kV6zhRjtbRJSGN41uFR/whqmcb4k4fftLFDJElxthp0PP5Fq8gMeM9ytWMt1yk5gGgekLREWYQQ==}
vite-plugin-dts@4.1.0:
resolution: {integrity: sha512-sRlmt9k2q8MrX4F2058N3KmB6WyJ3Ao6QaExOv1X99F3j0GhPziEz1zscWQ1q2r1PeFc96L7GIUu8Pl2DPr2Hg==}
engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies:
typescript: '*'
@@ -7130,11 +7133,11 @@ snapshots:
'@babel/helper-validator-identifier': 7.24.7
to-fast-properties: 2.0.0
'@commitlint/cli@19.4.1(@types/node@22.5.1)(typescript@5.5.4)':
'@commitlint/cli@19.4.1(@types/node@22.5.2)(typescript@5.5.4)':
dependencies:
'@commitlint/format': 19.3.0
'@commitlint/lint': 19.4.1
'@commitlint/load': 19.4.0(@types/node@22.5.1)(typescript@5.5.4)
'@commitlint/load': 19.4.0(@types/node@22.5.2)(typescript@5.5.4)
'@commitlint/read': 19.4.0
'@commitlint/types': 19.0.3
execa: 8.0.1
@@ -7181,7 +7184,7 @@ snapshots:
'@commitlint/rules': 19.4.1
'@commitlint/types': 19.0.3
'@commitlint/load@19.4.0(@types/node@22.5.1)(typescript@5.5.4)':
'@commitlint/load@19.4.0(@types/node@22.5.2)(typescript@5.5.4)':
dependencies:
'@commitlint/config-validator': 19.0.3
'@commitlint/execute-rule': 19.0.0
@@ -7189,7 +7192,7 @@ snapshots:
'@commitlint/types': 19.0.3
chalk: 5.3.0
cosmiconfig: 9.0.0(typescript@5.5.4)
cosmiconfig-typescript-loader: 5.0.0(@types/node@22.5.1)(cosmiconfig@9.0.0(typescript@5.5.4))(typescript@5.5.4)
cosmiconfig-typescript-loader: 5.0.0(@types/node@22.5.2)(cosmiconfig@9.0.0(typescript@5.5.4))(typescript@5.5.4)
lodash.isplainobject: 4.0.6
lodash.merge: 4.6.2
lodash.uniq: 4.5.0
@@ -7647,13 +7650,13 @@ snapshots:
postcss: 7.0.32
purgecss: 2.3.0
'@generouted/react-router@1.19.6(react-router-dom@6.26.1(react-dom@19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807)(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))':
'@generouted/react-router@1.19.6(react-router-dom@6.26.1(react-dom@19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807)(vite@5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))':
dependencies:
fast-glob: 3.3.2
generouted: 1.19.6(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
generouted: 1.19.6(vite@5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
react: 19.0.0-rc-e948a5ac-20240807
react-router-dom: 6.26.1(react-dom@19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807)
vite: 5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
vite: 5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
'@humanwhocodes/config-array@0.11.14':
dependencies:
@@ -7739,23 +7742,23 @@ snapshots:
'@material/material-color-utilities@0.3.0': {}
'@microsoft/api-extractor-model@7.29.4(@types/node@22.5.1)':
'@microsoft/api-extractor-model@7.29.4(@types/node@22.5.2)':
dependencies:
'@microsoft/tsdoc': 0.15.0
'@microsoft/tsdoc-config': 0.17.0
'@rushstack/node-core-library': 5.5.1(@types/node@22.5.1)
'@rushstack/node-core-library': 5.5.1(@types/node@22.5.2)
transitivePeerDependencies:
- '@types/node'
'@microsoft/api-extractor@7.47.4(@types/node@22.5.1)':
'@microsoft/api-extractor@7.47.4(@types/node@22.5.2)':
dependencies:
'@microsoft/api-extractor-model': 7.29.4(@types/node@22.5.1)
'@microsoft/api-extractor-model': 7.29.4(@types/node@22.5.2)
'@microsoft/tsdoc': 0.15.0
'@microsoft/tsdoc-config': 0.17.0
'@rushstack/node-core-library': 5.5.1(@types/node@22.5.1)
'@rushstack/node-core-library': 5.5.1(@types/node@22.5.2)
'@rushstack/rig-package': 0.5.3
'@rushstack/terminal': 0.13.3(@types/node@22.5.1)
'@rushstack/ts-command-line': 4.22.3(@types/node@22.5.1)
'@rushstack/terminal': 0.13.3(@types/node@22.5.2)
'@rushstack/ts-command-line': 4.22.3(@types/node@22.5.2)
lodash: 4.17.21
minimatch: 3.0.8
resolve: 1.22.8
@@ -8311,7 +8314,7 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.21.0':
optional: true
'@rushstack/node-core-library@5.5.1(@types/node@22.5.1)':
'@rushstack/node-core-library@5.5.1(@types/node@22.5.2)':
dependencies:
ajv: 8.13.0
ajv-draft-04: 1.0.0(ajv@8.13.0)
@@ -8322,33 +8325,36 @@ snapshots:
resolve: 1.22.8
semver: 7.5.4
optionalDependencies:
'@types/node': 22.5.1
'@types/node': 22.5.2
'@rushstack/rig-package@0.5.3':
dependencies:
resolve: 1.22.8
strip-json-comments: 3.1.1
'@rushstack/terminal@0.13.3(@types/node@22.5.1)':
'@rushstack/terminal@0.13.3(@types/node@22.5.2)':
dependencies:
'@rushstack/node-core-library': 5.5.1(@types/node@22.5.1)
'@rushstack/node-core-library': 5.5.1(@types/node@22.5.2)
supports-color: 8.1.1
optionalDependencies:
'@types/node': 22.5.1
'@types/node': 22.5.2
'@rushstack/ts-command-line@4.22.3(@types/node@22.5.1)':
'@rushstack/ts-command-line@4.22.3(@types/node@22.5.2)':
dependencies:
'@rushstack/terminal': 0.13.3(@types/node@22.5.1)
'@rushstack/terminal': 0.13.3(@types/node@22.5.2)
'@types/argparse': 1.0.38
argparse: 1.0.10
string-argv: 0.3.2
transitivePeerDependencies:
- '@types/node'
'@shikijs/core@1.15.2':
'@shikijs/core@1.16.1':
dependencies:
'@shikijs/vscode-textmate': 9.2.0
'@types/hast': 3.0.4
'@shikijs/vscode-textmate@9.2.0': {}
'@sindresorhus/is@4.6.0': {}
'@snyk/github-codeowners@1.1.0':
@@ -8600,12 +8606,12 @@ snapshots:
dependencies:
'@types/http-cache-semantics': 4.0.4
'@types/keyv': 3.1.4
'@types/node': 22.5.1
'@types/node': 22.5.2
'@types/responselike': 1.0.3
'@types/conventional-commits-parser@5.0.0':
dependencies:
'@types/node': 22.5.1
'@types/node': 22.5.2
'@types/d3-array@3.2.1': {}
@@ -8741,7 +8747,7 @@ snapshots:
'@types/fs-extra@11.0.4':
dependencies:
'@types/jsonfile': 6.1.4
'@types/node': 22.5.1
'@types/node': 22.5.2
'@types/geojson@7946.0.14': {}
@@ -8757,11 +8763,11 @@ snapshots:
'@types/jsonfile@6.1.4':
dependencies:
'@types/node': 22.5.1
'@types/node': 22.5.2
'@types/keyv@3.1.4':
dependencies:
'@types/node': 22.5.1
'@types/node': 22.5.2
'@types/lodash-es@4.17.12':
dependencies:
@@ -8781,7 +8787,7 @@ snapshots:
dependencies:
undici-types: 6.19.6
'@types/node@22.5.1':
'@types/node@22.5.2':
dependencies:
undici-types: 6.19.6
@@ -8789,11 +8795,11 @@ snapshots:
'@types/postcss-modules-local-by-default@4.0.2':
dependencies:
postcss: 8.4.42
postcss: 8.4.44
'@types/postcss-modules-scope@3.0.4':
dependencies:
postcss: 8.4.42
postcss: 8.4.44
'@types/prop-types@15.7.12': {}
@@ -8803,7 +8809,7 @@ snapshots:
'@types/responselike@1.0.3':
dependencies:
'@types/node': 22.5.1
'@types/node': 22.5.2
'@types/unist@2.0.10': {}
@@ -8811,7 +8817,7 @@ snapshots:
'@types/yauzl@2.10.3':
dependencies:
'@types/node': 22.5.1
'@types/node': 22.5.2
optional: true
'@typescript-eslint/eslint-plugin@8.3.0(@typescript-eslint/parser@8.3.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4)':
@@ -8897,21 +8903,21 @@ snapshots:
'@ungap/structured-clone@1.2.0': {}
'@vitejs/plugin-react-swc@3.7.0(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))':
'@vitejs/plugin-react-swc@3.7.0(vite@5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))':
dependencies:
'@swc/core': 1.6.1
vite: 5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
vite: 5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
transitivePeerDependencies:
- '@swc/helpers'
'@vitejs/plugin-react@4.3.1(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))':
'@vitejs/plugin-react@4.3.1(vite@5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))':
dependencies:
'@babel/core': 7.24.5
'@babel/plugin-transform-react-jsx-self': 7.24.5(@babel/core@7.24.5)
'@babel/plugin-transform-react-jsx-source': 7.24.1(@babel/core@7.24.5)
'@types/babel__core': 7.20.5
react-refresh: 0.14.2
vite: 5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
vite: 5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
transitivePeerDependencies:
- supports-color
@@ -9170,14 +9176,14 @@ snapshots:
dependencies:
tslib: 2.6.2
autoprefixer@10.4.20(postcss@8.4.42):
autoprefixer@10.4.20(postcss@8.4.44):
dependencies:
browserslist: 4.23.3
caniuse-lite: 1.0.30001646
fraction.js: 4.3.7
normalize-range: 0.1.2
picocolors: 1.0.1
postcss: 8.4.42
postcss: 8.4.44
postcss-value-parser: 4.2.0
autoprefixer@9.8.8:
@@ -9475,9 +9481,9 @@ snapshots:
dependencies:
toggle-selection: 1.0.6
cosmiconfig-typescript-loader@5.0.0(@types/node@22.5.1)(cosmiconfig@9.0.0(typescript@5.5.4))(typescript@5.5.4):
cosmiconfig-typescript-loader@5.0.0(@types/node@22.5.2)(cosmiconfig@9.0.0(typescript@5.5.4))(typescript@5.5.4):
dependencies:
'@types/node': 22.5.1
'@types/node': 22.5.2
cosmiconfig: 9.0.0(typescript@5.5.4)
jiti: 1.21.6
typescript: 5.5.4
@@ -10560,9 +10566,9 @@ snapshots:
functions-have-names@1.2.3: {}
generouted@1.19.6(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)):
generouted@1.19.6(vite@5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)):
dependencies:
vite: 5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
vite: 5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
gensync@1.0.0-beta.2: {}
@@ -11173,11 +11179,11 @@ snapshots:
kind-of@6.0.3: {}
knip@5.28.0(@types/node@22.5.1)(typescript@5.5.4):
knip@5.29.1(@types/node@22.5.2)(typescript@5.5.4):
dependencies:
'@nodelib/fs.walk': 1.2.8
'@snyk/github-codeowners': 1.1.0
'@types/node': 22.5.1
'@types/node': 22.5.2
easy-table: 1.2.0
enhanced-resolve: 5.17.1
fast-glob: 3.3.2
@@ -11996,19 +12002,19 @@ snapshots:
dependencies:
htmlparser2: 8.0.2
js-tokens: 9.0.0
postcss: 8.4.42
postcss-safe-parser: 6.0.0(postcss@8.4.42)
postcss: 8.4.44
postcss-safe-parser: 6.0.0(postcss@8.4.44)
postcss-import@15.1.0(postcss@8.4.42):
postcss-import@15.1.0(postcss@8.4.44):
dependencies:
postcss: 8.4.42
postcss: 8.4.44
postcss-value-parser: 4.2.0
read-cache: 1.0.0
resolve: 1.22.8
postcss-import@16.1.0(postcss@8.4.42):
postcss-import@16.1.0(postcss@8.4.44):
dependencies:
postcss: 8.4.42
postcss: 8.4.44
postcss-value-parser: 4.2.0
read-cache: 1.0.0
resolve: 1.22.8
@@ -12018,10 +12024,10 @@ snapshots:
camelcase-css: 2.0.1
postcss: 7.0.39
postcss-js@4.0.1(postcss@8.4.42):
postcss-js@4.0.1(postcss@8.4.44):
dependencies:
camelcase-css: 2.0.1
postcss: 8.4.42
postcss: 8.4.44
postcss-load-config@3.1.4(postcss@8.4.38):
dependencies:
@@ -12030,12 +12036,12 @@ snapshots:
optionalDependencies:
postcss: 8.4.38
postcss-load-config@4.0.2(postcss@8.4.42):
postcss-load-config@4.0.2(postcss@8.4.44):
dependencies:
lilconfig: 3.1.2
yaml: 2.5.0
optionalDependencies:
postcss: 8.4.42
postcss: 8.4.44
postcss-media-query-parser@0.2.3: {}
@@ -12060,24 +12066,24 @@ snapshots:
postcss: 7.0.39
postcss-selector-parser: 6.1.2
postcss-nested@6.0.1(postcss@8.4.42):
postcss-nested@6.0.1(postcss@8.4.44):
dependencies:
postcss: 8.4.42
postcss: 8.4.44
postcss-selector-parser: 6.1.2
postcss-resolve-nested-selector@0.1.6: {}
postcss-safe-parser@6.0.0(postcss@8.4.42):
postcss-safe-parser@6.0.0(postcss@8.4.44):
dependencies:
postcss: 8.4.42
postcss: 8.4.44
postcss-safe-parser@7.0.0(postcss@8.4.42):
postcss-safe-parser@7.0.0(postcss@8.4.44):
dependencies:
postcss: 8.4.42
postcss: 8.4.44
postcss-scss@4.0.9(postcss@8.4.42):
postcss-scss@4.0.9(postcss@8.4.44):
dependencies:
postcss: 8.4.42
postcss: 8.4.44
postcss-selector-parser@6.0.16:
dependencies:
@@ -12099,9 +12105,9 @@ snapshots:
cssesc: 3.0.0
util-deprecate: 1.0.2
postcss-sorting@8.0.2(postcss@8.4.42):
postcss-sorting@8.0.2(postcss@8.4.44):
dependencies:
postcss: 8.4.42
postcss: 8.4.44
postcss-value-parser@3.3.1: {}
@@ -12136,7 +12142,7 @@ snapshots:
picocolors: 1.0.1
source-map-js: 1.2.0
postcss@8.4.42:
postcss@8.4.44:
dependencies:
nanoid: 3.3.7
picocolors: 1.0.1
@@ -12593,9 +12599,10 @@ snapshots:
shell-quote@1.8.1: {}
shiki@1.15.2:
shiki@1.16.1:
dependencies:
'@shikijs/core': 1.15.2
'@shikijs/core': 1.16.1
'@shikijs/vscode-textmate': 9.2.0
'@types/hast': 3.0.4
side-channel@1.0.6:
@@ -12814,8 +12821,8 @@ snapshots:
stylelint-order@6.0.4(stylelint@16.9.0(typescript@5.5.4)):
dependencies:
postcss: 8.4.42
postcss-sorting: 8.0.2(postcss@8.4.42)
postcss: 8.4.44
postcss-sorting: 8.0.2(postcss@8.4.44)
stylelint: 16.9.0(typescript@5.5.4)
stylelint-scss@6.5.1(stylelint@16.9.0(typescript@5.5.4)):
@@ -12858,9 +12865,9 @@ snapshots:
micromatch: 4.0.8
normalize-path: 3.0.0
picocolors: 1.0.1
postcss: 8.4.42
postcss: 8.4.44
postcss-resolve-nested-selector: 0.1.6
postcss-safe-parser: 7.0.0(postcss@8.4.42)
postcss-safe-parser: 7.0.0(postcss@8.4.44)
postcss-selector-parser: 6.1.2
postcss-value-parser: 4.2.0
resolve-from: 5.0.0
@@ -12999,11 +13006,11 @@ snapshots:
normalize-path: 3.0.0
object-hash: 3.0.0
picocolors: 1.0.1
postcss: 8.4.42
postcss-import: 15.1.0(postcss@8.4.42)
postcss-js: 4.0.1(postcss@8.4.42)
postcss-load-config: 4.0.2(postcss@8.4.42)
postcss-nested: 6.0.1(postcss@8.4.42)
postcss: 8.4.44
postcss-import: 15.1.0(postcss@8.4.44)
postcss-js: 4.0.1(postcss@8.4.44)
postcss-load-config: 4.0.2(postcss@8.4.44)
postcss-nested: 6.0.1(postcss@8.4.44)
postcss-selector-parser: 6.1.1
resolve: 1.22.8
sucrase: 3.35.0
@@ -13389,9 +13396,9 @@ snapshots:
react: 19.0.0-rc-e948a5ac-20240807
react-dom: 19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807)
vite-plugin-dts@4.0.3(@types/node@22.5.1)(rollup@4.21.0)(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)):
vite-plugin-dts@4.1.0(@types/node@22.5.2)(rollup@4.21.0)(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)):
dependencies:
'@microsoft/api-extractor': 7.47.4(@types/node@22.5.1)
'@microsoft/api-extractor': 7.47.4(@types/node@22.5.2)
'@rollup/pluginutils': 5.1.0(rollup@4.21.0)
'@volar/typescript': 2.4.0
'@vue/language-core': 2.0.29(typescript@5.5.4)
@@ -13403,7 +13410,7 @@ snapshots:
typescript: 5.5.4
vue-tsc: 2.0.29(typescript@5.5.4)
optionalDependencies:
vite: 5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
vite: 5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
transitivePeerDependencies:
- '@types/node'
- rollup
@@ -13414,43 +13421,43 @@ snapshots:
esbuild: 0.19.12
monaco-editor: 0.51.0
vite-plugin-sass-dts@1.3.25(postcss@8.4.42)(prettier@3.3.3)(sass@1.77.8)(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)):
vite-plugin-sass-dts@1.3.25(postcss@8.4.44)(prettier@3.3.3)(sass@1.77.8)(vite@5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)):
dependencies:
postcss: 8.4.42
postcss-js: 4.0.1(postcss@8.4.42)
postcss: 8.4.44
postcss-js: 4.0.1(postcss@8.4.44)
prettier: 3.3.3
sass: 1.77.8
vite: 5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
vite: 5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
vite-plugin-svgr@4.2.0(rollup@4.21.0)(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)):
vite-plugin-svgr@4.2.0(rollup@4.21.0)(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)):
dependencies:
'@rollup/pluginutils': 5.1.0(rollup@4.21.0)
'@svgr/core': 8.1.0(typescript@5.5.4)
'@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.5.4))
vite: 5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
vite: 5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
transitivePeerDependencies:
- rollup
- supports-color
- typescript
vite-tsconfig-paths@5.0.1(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)):
vite-tsconfig-paths@5.0.1(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)):
dependencies:
debug: 4.3.6
globrex: 0.1.2
tsconfck: 3.0.3(typescript@5.5.4)
optionalDependencies:
vite: 5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
vite: 5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
transitivePeerDependencies:
- supports-color
- typescript
vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0):
vite@5.4.2(@types/node@22.5.2)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0):
dependencies:
esbuild: 0.21.5
postcss: 8.4.41
rollup: 4.21.0
optionalDependencies:
'@types/node': 22.5.1
'@types/node': 22.5.2
fsevents: 2.3.3
less: 4.2.0
sass: 1.77.8

View File

@@ -0,0 +1,31 @@
import { execSync } from "node:child_process";
import fs from "fs-extra";
import { GIT_SUMMARY_INFO_PATH, TAURI_APP_TEMP_DIR } from "./utils/env";
import { consola } from "./utils/logger";
async function main() {
const [hash, author, time] = execSync(
"git show --pretty=format:'%H,%cn,%cI' --no-patch --no-notes",
{
cwd: process.cwd(),
},
)
.toString()
.replace(/'/g, "")
.split(",");
const summary = {
hash,
author,
time,
};
consola.info(summary);
if (!(await fs.exists(TAURI_APP_TEMP_DIR))) {
await fs.mkdir(TAURI_APP_TEMP_DIR);
}
await fs.writeJSON(GIT_SUMMARY_INFO_PATH, summary, { spaces: 2 });
consola.success("Git summary info generated");
}
main().catch(consola.error);

View File

@@ -44,6 +44,8 @@ const resourceFormats = [
"amd64.deb",
"x64.dmg",
"aarch64.dmg",
"aarch64.rpm",
"arm64.deb",
];
const isValidFormat = (fileName: string): boolean => {

View File

@@ -7,3 +7,8 @@ export const GITHUB_PROXY = "https://mirror.ghproxy.com/";
export const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
export const TEMP_DIR = path.join(cwd, "node_modules/.verge");
export const MANIFEST_VERSION_PATH = path.join(MANIFEST_DIR, "version.json");
export const TAURI_APP_TEMP_DIR = path.join(TAURI_APP_DIR, ".tmp");
export const GIT_SUMMARY_INFO_PATH = path.join(
TAURI_APP_TEMP_DIR,
"git-info.json",
);

View File

@@ -66,7 +66,6 @@ type relayConnImpl struct {
// options set those fields
l *zap.SugaredLogger
remote *lb.Node
HandshakeDuration time.Duration
RelayLabel string `json:"relay_label"`
ConnType string `json:"conn_type"`
Options *conf.Options
@@ -78,12 +77,6 @@ func WithRelayLabel(relayLabel string) RelayConnOption {
}
}
func WithHandshakeDuration(duration time.Duration) RelayConnOption {
return func(rci *relayConnImpl) {
rci.HandshakeDuration = duration
}
}
func WithConnType(connType string) RelayConnOption {
return func(rci *relayConnImpl) {
rci.ConnType = connType
@@ -93,6 +86,7 @@ func WithConnType(connType string) RelayConnOption {
func WithRemote(remote *lb.Node) RelayConnOption {
return func(rci *relayConnImpl) {
rci.remote = remote
rci.Stats.HandShakeLatency = remote.HandShakeDuration
}
}

View File

@@ -44,9 +44,6 @@ func newBaseRelayServer(cfg *conf.Config, cmgr cmgr.Cmgr) (*BaseRelayServer, err
}
func (b *BaseRelayServer) RelayTCPConn(ctx context.Context, c net.Conn, remote *lb.Node) error {
if remote == nil {
remote = b.remotes.Next().Clone()
}
metrics.CurConnectionCount.WithLabelValues(remote.Label, metrics.METRIC_CONN_TYPE_TCP).Inc()
defer metrics.CurConnectionCount.WithLabelValues(remote.Label, metrics.METRIC_CONN_TYPE_TCP).Dec()
@@ -66,15 +63,11 @@ func (b *BaseRelayServer) RelayTCPConn(ctx context.Context, c net.Conn, remote *
return fmt.Errorf("handshake error: %w", err)
}
defer rc.Close()
b.l.Infof("RelayTCPConn from %s to %s", c.LocalAddr(), remote.Address)
return b.handleRelayConn(c, rc, remote, metrics.METRIC_CONN_TYPE_TCP)
}
func (b *BaseRelayServer) RelayUDPConn(ctx context.Context, c net.Conn, remote *lb.Node) error {
if remote == nil {
remote = b.remotes.Next().Clone()
}
metrics.CurConnectionCount.WithLabelValues(remote.Label, metrics.METRIC_CONN_TYPE_UDP).Inc()
defer metrics.CurConnectionCount.WithLabelValues(remote.Label, metrics.METRIC_CONN_TYPE_UDP).Dec()
@@ -144,7 +137,6 @@ func (b *BaseRelayServer) handleRelayConn(c, rc net.Conn, remote *lb.Node, connT
conn.WithConnType(connType),
conn.WithRelayLabel(b.cfg.Label),
conn.WithRelayOptions(b.cfg.Options),
conn.WithHandshakeDuration(remote.HandShakeDuration),
}
relayConn := conn.NewRelayConn(c, rc, opts...)
if b.cmgr != nil {

View File

@@ -99,7 +99,7 @@ func (s *RawServer) ListenAndServe(ctx context.Context) error {
}
go func(c net.Conn) {
defer c.Close()
if err := s.RelayTCPConn(ctx, c, nil); err != nil {
if err := s.RelayTCPConn(ctx, c, s.remotes.Next()); err != nil {
s.l.Errorf("RelayTCPConn meet error: %s", err.Error())
}
}(c)
@@ -118,7 +118,7 @@ func (s *RawServer) listenUDP(ctx context.Context) error {
return err
}
go func() {
if err := s.RelayUDPConn(ctx, c, nil); err != nil {
if err := s.RelayUDPConn(ctx, c, s.remotes.Next()); err != nil {
s.l.Errorf("RelayUDPConn meet error: %s", err.Error())
}
}()

View File

@@ -98,6 +98,8 @@ func (s *WsServer) handleRequest(w http.ResponseWriter, req *http.Request) {
var remote *lb.Node
if addr := req.URL.Query().Get(conf.WS_QUERY_REMOTE_ADDR); addr != "" {
remote = &lb.Node{Address: addr, Label: addr}
} else {
remote = s.remotes.Next()
}
if req.URL.Query().Get("type") == "udp" {

View File

@@ -0,0 +1,393 @@
const MetricsModule = (function () {
// Constants
const API_BASE_URL = '/api/v1';
const NODE_METRICS_PATH = '/node_metrics/';
const BYTE_TO_MB = 1024 * 1024;
const handleError = (error) => {
console.error('Error:', error);
};
// API functions
const fetchData = async (path, params = {}) => {
const url = new URL(API_BASE_URL + path, window.location.origin);
Object.entries(params).forEach(([key, value]) => url.searchParams.append(key, value));
try {
const response = await fetch(url.toString());
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
handleError(error);
return null;
}
};
const fetchLatestMetric = () => fetchData(NODE_METRICS_PATH, { latest: true }).then((data) => data?.data[0]);
const fetchMetrics = (startTs, endTs) => fetchData(NODE_METRICS_PATH, { start_ts: startTs, end_ts: endTs }).then((data) => data?.data);
// Chart functions
const initChart = (canvasId, type, datasets, legendPosition = '', yDisplayText = '', title = '', unit = '') => {
const ctx = $(`#${canvasId}`)[0].getContext('2d');
const colors = {
cpu: 'rgba(255, 99, 132, 1)',
memory: 'rgba(54, 162, 235, 1)',
disk: 'rgba(255, 206, 86, 1)',
receive: 'rgba(0, 150, 255, 1)',
transmit: 'rgba(255, 140, 0, 1)',
};
const getDatasetConfig = (label) => {
const color = colors[label.toLowerCase()] || 'rgba(0, 0, 0, 1)';
return {
label,
borderColor: color,
backgroundColor: color.replace('1)', '0.2)'),
borderWidth: 2,
pointRadius: 2,
pointHoverRadius: 2,
fill: true,
data: [],
};
};
const data = {
labels: [],
datasets: $.isArray(datasets) ? datasets.map((dataset) => getDatasetConfig(dataset.label)) : [getDatasetConfig(datasets.label)],
};
return new Chart(ctx, {
type,
data,
options: {
line: {
spanGaps: false, // 设置为 false不连接空值
},
responsive: true,
plugins: {
legend: { position: legendPosition },
title: {
display: !!title,
text: title,
position: 'bottom',
font: { size: 14, weight: 'bold' },
},
tooltip: {
callbacks: {
title: function (tooltipItems) {
return new Date(tooltipItems[0].label).toLocaleString();
},
label: function (context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.parsed.y !== null) {
label += context.parsed.y.toFixed(2) + ' ' + unit;
}
return label;
},
},
},
},
scales: {
x: {
type: 'time',
time: {
unit: 'minute',
displayFormats: {
minute: 'HH:mm',
},
},
ticks: {
maxRotation: 0,
autoSkip: true,
maxTicksLimit: 10,
},
adapters: {
date: {
locale: 'en',
},
},
},
y: {
beginAtZero: true,
title: { display: true, text: yDisplayText, font: { weight: 'bold' } },
},
},
elements: { line: { tension: 0.4 } },
downsample: {
enabled: true,
threshold: 200,
},
},
});
};
const updateChart = (chart, newData, labels) => {
if (!newData || !labels) {
console.error('Invalid data or labels provided');
return;
}
if ($.isArray(newData) && $.isArray(newData[0])) {
$.each(chart.data.datasets, (index, dataset) => {
if (newData[index]) {
dataset.data = newData[index].map((value, i) => ({ x: moment(labels[i]), y: value }));
}
});
} else {
chart.data.datasets[0].data = newData.map((value, i) => ({ x: moment(labels[i]), y: value }));
}
chart.options.scales.x.min = moment(labels[0]);
chart.options.scales.x.max = moment(labels[labels.length - 1]);
chart.update();
};
const updateCharts = (charts, metrics, startTs, endTs) => {
console.log('Raw metrics data:', metrics);
const generateTimestamps = (start, end) => {
const timestamps = [];
let current = moment.unix(start);
const endMoment = moment.unix(end);
while (current.isSameOrBefore(endMoment)) {
timestamps.push(current.toISOString());
current.add(1, 'minute');
}
return timestamps;
};
const timestamps = generateTimestamps(startTs, endTs);
const processData = (dataKey) => {
const data = new Array(timestamps.length).fill(null);
metrics.forEach((metric) => {
const index = Math.floor((metric.timestamp - startTs) / 60);
if (index >= 0 && index < data.length) {
data[index] = metric[dataKey];
}
});
return data;
};
updateChart(charts.cpu, processData('cpu_usage'), timestamps);
updateChart(charts.memory, processData('memory_usage'), timestamps);
updateChart(charts.disk, processData('disk_usage'), timestamps);
updateChart(
charts.network,
[
processData('network_in').map((v) => (v === null ? null : v / BYTE_TO_MB)),
processData('network_out').map((v) => (v === null ? null : v / BYTE_TO_MB)),
],
timestamps
);
};
const addLatestDataToCharts = (charts, latestMetric) => {
console.log('Raw latestMetric data:', latestMetric);
const timestamp = moment.unix(latestMetric.timestamp);
$.each(charts, (key, chart) => {
// 检查是否已经有这个时间戳的数据
const existingDataIndex = chart.data.labels.findIndex((label) => label.isSame(timestamp));
if (existingDataIndex === -1) {
// 如果是新数据,添加到末尾
chart.data.labels.push(timestamp);
if (key === 'network') {
chart.data.datasets[0].data.push({ x: timestamp, y: latestMetric.network_in / BYTE_TO_MB });
chart.data.datasets[1].data.push({ x: timestamp, y: latestMetric.network_out / BYTE_TO_MB });
} else {
chart.data.datasets[0].data.push({ x: timestamp, y: latestMetric[`${key}_usage`] });
}
// 更新x轴范围但保持一定的时间窗口
const timeWindow = moment.duration(30, 'minutes'); // 设置显示的时间窗口例如30分钟
const oldestAllowedTime = moment(timestamp).subtract(timeWindow);
chart.options.scales.x.min = oldestAllowedTime;
chart.options.scales.x.max = timestamp;
// 开启图表的平移和缩放功能
chart.options.plugins.zoom = {
pan: {
enabled: true,
mode: 'x',
},
zoom: {
wheel: {
enabled: true,
},
pinch: {
enabled: true,
},
mode: 'x',
},
};
chart.update();
}
// 如果数据已存在,我们不做任何操作,保持现有数据
});
};
// Chart initialization
const initializeCharts = async () => {
const metric = await fetchLatestMetric();
if (!metric) return null;
return {
cpu: initChart('cpuChart', 'line', { label: 'CPU' }, 'top', 'Usage (%)', `CPU`, '%'),
memory: initChart('memoryChart', 'line', { label: 'Memory' }, 'top', 'Usage (%)', `Memory`, '%'),
disk: initChart('diskChart', 'line', { label: 'Disk' }, 'top', 'Usage (%)', `Disk`, '%'),
network: initChart(
'networkChart',
'line',
[{ label: 'Receive' }, { label: 'Transmit' }],
'top',
'Rate (MB/s)',
'Network Rate',
'MB/s'
),
};
};
// Date range functions
const setupDateRangeDropdown = (charts) => {
const $dateRangeDropdown = $('#dateRangeDropdown');
const $dateRangeButton = $('#dateRangeButton');
const $dateRangeText = $('#dateRangeText');
const $dateRangeInput = $('#dateRangeInput');
$dateRangeDropdown.find('.dropdown-item[data-range]').on('click', function (e) {
e.preventDefault();
const range = $(this).data('range');
const now = new Date();
let start, end;
switch (range) {
case '30m':
start = new Date(now - 30 * 60 * 1000);
break;
case '1h':
start = new Date(now - 60 * 60 * 1000);
break;
case '3h':
start = new Date(now - 3 * 60 * 60 * 1000);
break;
case '6h':
start = new Date(now - 6 * 60 * 60 * 1000);
break;
case '12h':
start = new Date(now - 12 * 60 * 60 * 1000);
break;
case '24h':
start = new Date(now - 24 * 60 * 60 * 1000);
break;
case '7d':
start = new Date(now - 7 * 24 * 60 * 60 * 1000);
break;
}
end = now;
const startTs = Math.floor(start.getTime() / 1000);
const endTs = Math.floor(end.getTime() / 1000);
fetchDataForRange(charts, startTs, endTs);
$dateRangeText.text($(this).text());
$dateRangeDropdown.removeClass('is-active');
});
$dateRangeButton.on('click', (event) => {
event.stopPropagation();
$dateRangeDropdown.toggleClass('is-active');
});
$(document).on('click', (event) => {
if (!$dateRangeDropdown.has(event.target).length) {
$dateRangeDropdown.removeClass('is-active');
}
});
const picker = flatpickr($dateRangeInput[0], {
mode: 'range',
enableTime: true,
dateFormat: 'Y-m-d H:i',
onChange: function (selectedDates) {
if (selectedDates.length === 2) {
const startTs = Math.floor(selectedDates[0].getTime() / 1000);
const endTs = Math.floor(selectedDates[1].getTime() / 1000);
fetchDataForRange(charts, startTs, endTs);
const formattedStart = selectedDates[0].toLocaleString();
const formattedEnd = selectedDates[1].toLocaleString();
$dateRangeText.text(`${formattedStart} - ${formattedEnd}`);
// 关闭下拉菜单
$dateRangeDropdown.removeClass('is-active');
}
},
onClose: function () {
// 确保在日期选择器关闭时也关闭下拉菜单
$dateRangeDropdown.removeClass('is-active');
},
});
// 防止点击日期选择器时关闭下拉菜单
$dateRangeInput.on('click', (event) => {
event.stopPropagation();
});
};
const fetchDataForRange = async (charts, startTs, endTs) => {
const metrics = await fetchMetrics(startTs, endTs);
if (metrics) {
console.log('Raw metrics data:', metrics);
updateCharts(charts, metrics, startTs, endTs);
}
};
// Auto refresh functions
const setupAutoRefresh = (charts) => {
let autoRefreshInterval;
let isAutoRefreshing = false;
$('#refreshButton').click(function () {
if (isAutoRefreshing) {
clearInterval(autoRefreshInterval);
$(this).removeClass('is-info');
$(this).find('span:last').text('Auto Refresh');
isAutoRefreshing = false;
} else {
$(this).addClass('is-info');
$(this).find('span:last').text('Stop Refresh');
isAutoRefreshing = true;
refreshData(charts);
autoRefreshInterval = setInterval(() => refreshData(charts), 5000);
}
});
};
const refreshData = async (charts) => {
const latestMetric = await fetchLatestMetric();
if (latestMetric) {
addLatestDataToCharts(charts, latestMetric);
}
};
// Main initialization function
const init = async () => {
const charts = await initializeCharts();
if (charts) {
setupDateRangeDropdown(charts);
setupAutoRefresh(charts);
}
};
// Public API
return {
init: init,
};
})();
// Initialize when the DOM is ready
document.addEventListener('DOMContentLoaded', MetricsModule.init);

View File

@@ -22,7 +22,7 @@ import (
"github.com/Ehco1996/ehco/internal/metrics"
)
//go:embed templates/*.html
//go:embed templates/*.html js/*.js
var templatesFS embed.FS
const (
@@ -70,7 +70,7 @@ func NewServer(
return nil, errors.Wrap(err, "failed to setup middleware")
}
if err := setupTemplates(e, l); err != nil {
if err := setupTemplates(e, l, cfg); err != nil {
return nil, errors.Wrap(err, "failed to setup templates")
}
@@ -128,10 +128,13 @@ func setupMiddleware(e *echo.Echo, cfg *config.Config, l *zap.SugaredLogger) err
return nil
}
func setupTemplates(e *echo.Echo, l *zap.SugaredLogger) error {
func setupTemplates(e *echo.Echo, l *zap.SugaredLogger, cfg *config.Config) error {
funcMap := template.FuncMap{
"sub": func(a, b int) int { return a - b },
"add": func(a, b int) int { return a + b },
"CurrentCfg": func() *config.Config {
return cfg
},
}
tmpl, err := template.New("").Funcs(funcMap).ParseFS(templatesFS, "templates/*.html")
if err != nil {
@@ -158,6 +161,7 @@ func setupMetrics(cfg *config.Config) error {
func setupRoutes(s *Server) {
e := s.e
e.StaticFS("/js", echo.MustSubFS(templatesFS, "js"))
e.GET(metricsPath, echo.WrapHandler(promhttp.Handler()))
e.GET("/debug/pprof/*", echo.WrapHandler(http.DefaultServeMux))

View File

@@ -1,5 +1,5 @@
<head>
<title>Ehco Web</title>
<title>Ehco Web({{ (CurrentCfg).NodeLabel}})</title>
<meta charset="UTF-8" />
<meta name="description" content="ehco web" />
<meta name="keywords" content="ehco-relay" />
@@ -7,7 +7,9 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/1.0.1/css/bulma.min.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/flatpickr/4.6.13/flatpickr.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/flatpickr/4.6.13/flatpickr.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-adapter-moment/1.0.1/chartjs-adapter-moment.min.js"></script>
</head>

View File

@@ -52,323 +52,6 @@
</div>
</div>
</div>
<script>
$(document).ready(async function () {
// Constants
const API_BASE_URL = '/api/v1';
const NODE_METRICS_PATH = '/node_metrics/';
const BYTE_TO_MB = 1024 * 1024;
const BYTE_TO_GB = BYTE_TO_MB * 1024;
// Utility functions
const handleError = (error) => {
console.error('Error:', error);
// You can add user notifications here
};
const formatDate = (timeStamp) => {
const date = new Date(timeStamp * 1000);
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
};
const formatBytes = (bytes, decimals = 2) => {
return (bytes / BYTE_TO_GB).toFixed(decimals);
};
// API functions
const fetchData = async (path, params = {}) => {
const url = new URL(API_BASE_URL + path, window.location.origin);
$.each(params, (key, value) => url.searchParams.append(key, value));
try {
const response = await $.ajax({
url: url.toString(),
method: 'GET',
dataType: 'json',
});
return response;
} catch (error) {
handleError(error);
return null;
}
};
const fetchLatestMetric = () => fetchData(NODE_METRICS_PATH, { latest: true }).then((data) => data?.data[0]);
const fetchMetrics = (startTs, endTs) =>
fetchData(NODE_METRICS_PATH, { start_ts: startTs, end_ts: endTs }).then((data) => data?.data);
// Chart functions
const initChart = (canvasId, type, datasets, legendPosition = '', yDisplayText = '', title = '', unit = '') => {
const ctx = $(`#${canvasId}`)[0].getContext('2d');
const colors = {
cpu: 'rgba(255, 99, 132, 1)',
memory: 'rgba(54, 162, 235, 1)',
disk: 'rgba(255, 206, 86, 1)',
receive: 'rgba(0, 150, 255, 1)',
transmit: 'rgba(255, 140, 0, 1)',
};
const getDatasetConfig = (label) => {
const color = colors[label.toLowerCase()] || 'rgba(0, 0, 0, 1)'; // 默认颜色
console.log(label, color);
return {
label,
borderColor: color,
backgroundColor: color.replace('1)', '0.2)'),
borderWidth: 2,
pointRadius: 0,
fill: true,
data: [],
};
};
const data = {
labels: [],
datasets: $.isArray(datasets)
? datasets.map((dataset) => getDatasetConfig(dataset.label))
: [getDatasetConfig(datasets.label)],
};
return new Chart(ctx, {
type,
data,
options: {
responsive: true,
plugins: {
legend: { position: legendPosition },
title: {
display: !!title,
text: title,
position: 'bottom',
font: { size: 14, weight: 'bold' },
},
tooltip: {
callbacks: {
label: function (context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.parsed.y !== null) {
label += context.parsed.y.toFixed(2) + ' ' + unit;
}
return label;
},
},
},
},
scales: {
y: {
beginAtZero: true,
title: { display: true, text: yDisplayText, font: { weight: 'bold' } },
},
x: {
ticks: { maxRotation: 0, autoSkip: true, maxTicksLimit: 10 },
},
},
elements: { line: { tension: 0.4 } },
downsample: {
enabled: true,
threshold: 200,
},
},
});
};
const updateChart = (chart, newData, labels) => {
if (!newData || !labels) {
console.error('Invalid data or labels provided');
return;
}
const formattedLabels = labels.map(formatDate);
if ($.isArray(newData) && $.isArray(newData[0])) {
$.each(chart.data.datasets, (index, dataset) => {
if (newData[index]) {
dataset.data = newData[index];
}
});
} else {
chart.data.datasets[0].data = newData;
}
chart.data.labels = formattedLabels;
chart.update();
};
const updateCharts = (charts, metrics) => {
console.log('Raw metrics data:', metrics);
const timestamps = metrics.map((data) => data.timestamp);
updateChart(
charts.cpu,
metrics.map((data) => data.cpu_usage),
timestamps,
);
updateChart(
charts.memory,
metrics.map((data) => data.memory_usage),
timestamps,
);
updateChart(
charts.disk,
metrics.map((data) => data.disk_usage),
timestamps,
);
updateChart(
charts.network,
[metrics.map((data) => data.network_in / BYTE_TO_MB), metrics.map((data) => data.network_out / BYTE_TO_MB)],
timestamps,
);
};
const addLatestDataToCharts = (charts, latestMetric) => {
console.log('Raw latestMetric data:', latestMetric);
const timestamp = formatDate(latestMetric.timestamp);
$.each(charts, (key, chart) => {
chart.data.labels.push(timestamp);
if (key === 'network') {
chart.data.datasets[0].data.push(latestMetric.network_in / BYTE_TO_MB);
chart.data.datasets[1].data.push(latestMetric.network_out / BYTE_TO_MB);
} else {
chart.data.datasets[0].data.push(latestMetric[`${key}_usage`]);
}
chart.update();
});
};
// Chart initialization
const initializeCharts = async () => {
const metric = await fetchLatestMetric();
if (!metric) return null;
return {
cpu: initChart('cpuChart', 'line', { label: 'CPU' }, 'top', 'Usage (%)', `CPU`, '%'),
memory: initChart('memoryChart', 'line', { label: 'Memory' }, 'top', 'Usage (%)', `Memory`, '%'),
disk: initChart('diskChart', 'line', { label: 'Disk' }, 'top', 'Usage (%)', `Disk`, '%'),
network: initChart(
'networkChart',
'line',
[{ label: 'Receive' }, { label: 'Transmit' }],
'top',
'Rate (MB/s)',
'Network Rate',
'MB/s',
),
};
};
// Date range functions
const setupDateRangeDropdown = (charts) => {
const $dateRangeDropdown = $('#dateRangeDropdown');
const $dateRangeButton = $('#dateRangeButton');
const $dateRangeText = $('#dateRangeText');
const $dateRangeInput = $('#dateRangeInput');
$dateRangeDropdown.find('.dropdown-item[data-range]').on('click', function (e) {
e.preventDefault();
const range = $(this).data('range');
const now = new Date();
let start, end;
switch (range) {
case '30m':
start = new Date(now - 30 * 60 * 1000);
break;
case '1h':
start = new Date(now - 60 * 60 * 1000);
break;
case '3h':
start = new Date(now - 3 * 60 * 60 * 1000);
break;
case '6h':
start = new Date(now - 6 * 60 * 60 * 1000);
break;
case '12h':
start = new Date(now - 12 * 60 * 60 * 1000);
break;
case '24h':
start = new Date(now - 24 * 60 * 60 * 1000);
break;
case '7d':
start = new Date(now - 7 * 24 * 60 * 60 * 1000);
break;
}
end = now;
const startTs = Math.floor(start.getTime() / 1000);
const endTs = Math.floor(end.getTime() / 1000);
fetchDataForRange(charts, startTs, endTs);
$dateRangeText.text($(this).text());
$dateRangeDropdown.removeClass('is-active');
});
$dateRangeButton.on('click', (event) => {
event.stopPropagation();
$dateRangeDropdown.toggleClass('is-active');
});
$(document).on('click', (event) => {
if (!$dateRangeDropdown.has(event.target).length) {
$dateRangeDropdown.removeClass('is-active');
}
});
const picker = flatpickr($dateRangeInput[0], {
mode: 'range',
enableTime: true,
dateFormat: 'Y-m-d H:i',
onChange: function (selectedDates) {
if (selectedDates.length === 2) {
const startTs = Math.floor(selectedDates[0].getTime() / 1000);
const endTs = Math.floor(selectedDates[1].getTime() / 1000);
fetchDataForRange(charts, startTs, endTs);
const formattedStart = selectedDates[0].toLocaleString();
const formattedEnd = selectedDates[1].toLocaleString();
$dateRangeText.text(`${formattedStart} - ${formattedEnd}`);
}
},
});
};
const fetchDataForRange = async (charts, startTs, endTs) => {
const metrics = await fetchMetrics(startTs, endTs);
if (metrics) {
console.log('Raw metrics data:', metrics);
updateCharts(charts, metrics);
}
};
// Auto refresh functions
const setupAutoRefresh = (charts) => {
let autoRefreshInterval;
let isAutoRefreshing = false;
$('#refreshButton').click(function () {
if (isAutoRefreshing) {
clearInterval(autoRefreshInterval);
$(this).removeClass('is-info');
$(this).find('span:last').text('Auto Refresh');
isAutoRefreshing = false;
} else {
$(this).addClass('is-info');
$(this).find('span:last').text('Stop Refresh');
isAutoRefreshing = true;
refreshData(charts);
autoRefreshInterval = setInterval(() => refreshData(charts), 5000);
}
});
};
const refreshData = async (charts) => {
const latestMetric = await fetchLatestMetric();
if (latestMetric) {
addLatestDataToCharts(charts, latestMetric);
}
};
// Main execution
const charts = await initializeCharts();
if (charts) {
setupDateRangeDropdown(charts);
setupAutoRefresh(charts);
}
});
</script>
<!-- </div> -->
<script src="js/metrics.js"></script>
</div>

View File

@@ -4,7 +4,7 @@
<a class="navbar-item" href="/">
<h1 class="title is-4">Ehco Relay</h1>
</a>
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false">
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="navbarMenu">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
@@ -12,7 +12,7 @@
</a>
</div>
<div class="navbar-menu">
<div id="navbarMenu" class="navbar-menu">
<div class="navbar-start">
<a href="/rules/" class="navbar-item">
<span class="icon"><i class="fas fa-list"></i></span>
@@ -56,3 +56,28 @@
</div>
</nav>
<hr />
<script>
document.addEventListener('DOMContentLoaded', () => {
// Get all "navbar-burger" elements
const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
// Add a click event on each of them
$navbarBurgers.forEach((el) => {
el.addEventListener('click', () => {
// Get the target from the "data-target" attribute
const target = el.dataset.target;
const $target = document.getElementById(target);
// Check if the target element exists
if ($target) {
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
el.classList.toggle('is-active');
$target.classList.toggle('is-active');
} else {
console.error(`Target element with id "${target}" not found`);
}
});
});
});
</script>

View File

@@ -10,7 +10,7 @@
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3328-roc-cc.dtb
--- /dev/null
+++ b/arch/arm64/boot/dts/rockchip/rk3328-orangepi-r1-plus-lts.dts
@@ -0,0 +1,71 @@
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Copyright (c) 2016 Xunlong Software. Co., Ltd.
@@ -54,9 +54,11 @@
+ "ethernet-phy-ieee802.3-c22";
+ reg = <0>;
+
+ motorcomm,auto-sleep-disabled;
+ motorcomm,clk-out-frequency-hz = <125000000>;
+ motorcomm,keep-pll-enabled;
+ motorcomm,auto-sleep-disabled;
+ motorcomm,rx-clk-drv-microamp = <5020>;
+ motorcomm,rx-data-drv-microamp = <5020>;
+
+ pinctrl-0 = <&eth_phy_reset_pin>;
+ pinctrl-names = "default";

View File

@@ -2,7 +2,6 @@ package updater
import (
"archive/zip"
"errors"
"fmt"
"io"
"os"
@@ -12,23 +11,26 @@ import (
"sync"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
)
var (
ExternalUIURL string
ExternalUIPath string
ExternalUIFolder string
ExternalUIName string
)
var (
ErrIncompleteConf = errors.New("ExternalUI configure incomplete")
AutoUpdateUI bool
)
var xdMutex sync.Mutex
func UpdateUI() error {
xdMutex.Lock()
defer xdMutex.Unlock()
err := prepareUIPath()
if err != nil {
return fmt.Errorf("prepare UI path failed: %w", err)
}
data, err := downloadForBytes(ExternalUIURL)
if err != nil {
return fmt.Errorf("can't download file: %w", err)
@@ -40,7 +42,7 @@ func UpdateUI() error {
}
defer os.Remove(saved)
err = cleanup(ExternalUIFolder)
err = cleanup(ExternalUIPath)
if err != nil {
if !os.IsNotExist(err) {
return fmt.Errorf("cleanup exist file error: %w", err)
@@ -52,28 +54,20 @@ func UpdateUI() error {
return fmt.Errorf("can't extract zip file: %w", err)
}
err = os.Rename(unzipFolder, ExternalUIFolder)
err = os.Rename(unzipFolder, ExternalUIPath)
if err != nil {
return fmt.Errorf("can't rename folder: %w", err)
return fmt.Errorf("rename UI folder failed: %w", err)
}
return nil
}
func PrepareUIPath() error {
if ExternalUIPath == "" || ExternalUIURL == "" {
return ErrIncompleteConf
}
if ExternalUIName != "" {
ExternalUIFolder = filepath.Clean(path.Join(ExternalUIPath, ExternalUIName))
func prepareUIPath() error {
if _, err := os.Stat(ExternalUIPath); os.IsNotExist(err) {
log.Infoln("dir %s does not exist, creating", ExternalUIPath)
if err := os.MkdirAll(ExternalUIPath, os.ModePerm); err != nil {
return err
log.Warnln("create dir %s error: %s", ExternalUIPath, err)
}
}
} else {
ExternalUIFolder = ExternalUIPath
}
return nil
}

View File

@@ -7,7 +7,6 @@ import (
"net"
"net/netip"
"net/url"
"os"
"path"
"regexp"
"strings"
@@ -704,33 +703,23 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
}
N.DisableKeepAlive = cfg.DisableKeepAlive
updater.ExternalUIPath = cfg.ExternalUI
// checkout externalUI exist
if updater.ExternalUIPath != "" {
updater.ExternalUIPath = C.Path.Resolve(updater.ExternalUIPath)
if _, err := os.Stat(updater.ExternalUIPath); os.IsNotExist(err) {
defaultUIpath := path.Join(C.Path.HomeDir(), "ui")
log.Warnln("external-ui: %s does not exist, creating folder in %s", updater.ExternalUIPath, defaultUIpath)
if err := os.MkdirAll(defaultUIpath, os.ModePerm); err != nil {
return nil, err
}
updater.ExternalUIPath = defaultUIpath
cfg.ExternalUI = defaultUIpath
}
}
// checkout UIpath/name exist
if cfg.ExternalUIName != "" {
updater.ExternalUIName = cfg.ExternalUIName
if cfg.ExternalUI != "" {
updater.AutoUpdateUI = true
updater.ExternalUIPath = C.Path.Resolve(cfg.ExternalUI)
} else {
updater.ExternalUIFolder = updater.ExternalUIPath
}
if cfg.ExternalUIURL != "" {
updater.ExternalUIURL = cfg.ExternalUIURL
// default externalUI path
updater.ExternalUIPath = path.Join(C.Path.HomeDir(), "ui")
}
err := updater.PrepareUIPath()
if err != nil {
log.Errorln("PrepareUIPath error: %s", err)
// checkout UIpath/name exist
if cfg.ExternalUIName != "" {
updater.AutoUpdateUI = true
updater.ExternalUIPath = path.Join(updater.ExternalUIPath, cfg.ExternalUIName)
}
if cfg.ExternalUIURL != "" {
updater.ExternalUIURL = cfg.ExternalUIURL
}
return &General{

View File

@@ -381,12 +381,12 @@ func updateTunnels(tunnels []LC.Tunnel) {
}
func initExternalUI() {
if updater.ExternalUIFolder != "" {
dirEntries, _ := os.ReadDir(updater.ExternalUIFolder)
if updater.AutoUpdateUI {
dirEntries, _ := os.ReadDir(updater.ExternalUIPath)
if len(dirEntries) > 0 {
log.Infoln("UI already exists")
log.Infoln("UI already exists, skip downloading")
} else {
log.Infoln("UI not exists, downloading")
log.Infoln("External UI downloading ...")
updater.UpdateUI()
}
}

View File

@@ -60,9 +60,15 @@ func getGroupDelay(w http.ResponseWriter, r *http.Request) {
return
}
if proxy.(*adapter.Proxy).Type() == C.URLTest {
URLTestGroup := proxy.(*adapter.Proxy).ProxyAdapter.(*outboundgroup.URLTest)
URLTestGroup.ForceSet("")
switch proxy.(*adapter.Proxy).Type() {
case C.URLTest:
if urlTestGroup, ok := proxy.(*adapter.Proxy).ProxyAdapter.(*outboundgroup.URLTest); ok {
urlTestGroup.ForceSet("")
}
case C.Fallback:
if fallbackGroup, ok := proxy.(*adapter.Proxy).ProxyAdapter.(*outboundgroup.Fallback); ok {
fallbackGroup.ForceSet("")
}
}
if proxy.(*adapter.Proxy).Type() != C.Selector {

View File

@@ -1,7 +1,6 @@
package route
import (
"errors"
"fmt"
"net/http"
"os"
@@ -50,15 +49,9 @@ func upgradeCore(w http.ResponseWriter, r *http.Request) {
func updateUI(w http.ResponseWriter, r *http.Request) {
err := updater.UpdateUI()
if err != nil {
if errors.Is(err, updater.ErrIncompleteConf) {
log.Warnln("%s", err)
render.Status(r, http.StatusNotImplemented)
render.JSON(w, r, newError(fmt.Sprintf("%s", err)))
} else {
log.Warnln("%s", err)
render.Status(r, http.StatusInternalServerError)
render.JSON(w, r, newError(fmt.Sprintf("%s", err)))
}
return
}

View File

@@ -328,6 +328,33 @@ lua_api() {
echo $(lua -e "local api = require 'luci.passwall.api' print(api.${func})")
}
parse_doh() {
local __doh=$1 __url_var=$2 __host_var=$3 __port_var=$4 __bootstrap_var=$5
__doh=$(echo -e "$__doh" | tr -d ' \t\n')
local __url=${__doh%%,*}
local __bootstrap=${__doh#*,}
local __host_port=$(lua_api "get_domain_from_url(\"${__url}\")")
local __host __port
if echo "${__host_port}" | grep -q '^\[.*\]:[0-9]\+$'; then
__host=${__host_port%%]:*}]
__port=${__host_port##*:}
elif echo "${__host_port}" | grep -q ':[0-9]\+$'; then
__host=${__host_port%:*}
__port=${__host_port##*:}
else
__host=${__host_port}
__port=443
fi
__host=${__host#[}
__host=${__host%]}
if [ "$(lua_api "is_ip(\"${__host}\")")" = "true" ]; then
__bootstrap=${__host}
fi
__bootstrap=${__bootstrap#[}
__bootstrap=${__bootstrap%]}
eval "${__url_var}='${__url}' ${__host_var}='${__host}' ${__port_var}='${__port}' ${__bootstrap_var}='${__bootstrap}'"
}
run_ipt2socks() {
local flag proto tcp_tproxy local_port socks_address socks_port socks_username socks_password log_file
local _extra_param=""
@@ -422,15 +449,8 @@ run_singbox() {
_extra_param="${_extra_param} -remote_dns_server ${_dns_address} -remote_dns_port ${_dns_port} -remote_dns_tcp_server tcp://${_dns}"
;;
doh)
local _doh_url=$(echo $remote_dns_doh | awk -F ',' '{print $1}')
local _doh_host_port=$(lua_api "get_domain_from_url(\"${_doh_url}\")")
#local _doh_host_port=$(echo $_doh_url | sed "s/https:\/\///g" | awk -F '/' '{print $1}')
local _doh_host=$(echo $_doh_host_port | awk -F ':' '{print $1}')
local is_ip=$(lua_api "is_ip(\"${_doh_host}\")")
local _doh_port=$(echo $_doh_host_port | awk -F ':' '{print $2}')
[ -z "${_doh_port}" ] && _doh_port=443
local _doh_bootstrap=$(echo $remote_dns_doh | cut -d ',' -sf 2-)
[ "${is_ip}" = "true" ] && _doh_bootstrap=${_doh_host}
local _doh_url _doh_host _doh_port _doh_bootstrap
parse_doh "$remote_dns_doh" _doh_url _doh_host _doh_port _doh_bootstrap
[ -n "$_doh_bootstrap" ] && _extra_param="${_extra_param} -remote_dns_server ${_doh_bootstrap}"
_extra_param="${_extra_param} -remote_dns_port ${_doh_port} -remote_dns_doh_url ${_doh_url} -remote_dns_doh_host ${_doh_host}"
;;
@@ -481,15 +501,8 @@ run_xray() {
_extra_param="${_extra_param} -remote_dns_tcp_server ${_dns_address} -remote_dns_tcp_port ${_dns_port}"
}
[ -n "${remote_dns_doh}" ] && {
local _doh_url=$(echo $remote_dns_doh | awk -F ',' '{print $1}')
local _doh_host_port=$(lua_api "get_domain_from_url(\"${_doh_url}\")")
#local _doh_host_port=$(echo $_doh_url | sed "s/https:\/\///g" | awk -F '/' '{print $1}')
local _doh_host=$(echo $_doh_host_port | awk -F ':' '{print $1}')
local is_ip=$(lua_api "is_ip(\"${_doh_host}\")")
local _doh_port=$(echo $_doh_host_port | awk -F ':' '{print $2}')
[ -z "${_doh_port}" ] && _doh_port=443
local _doh_bootstrap=$(echo $remote_dns_doh | cut -d ',' -sf 2-)
[ "${is_ip}" = "true" ] && _doh_bootstrap=${_doh_host}
local _doh_url _doh_host _doh_port _doh_bootstrap
parse_doh "$remote_dns_doh" _doh_url _doh_host _doh_port _doh_bootstrap
[ -n "$_doh_bootstrap" ] && _extra_param="${_extra_param} -remote_dns_doh_ip ${_doh_bootstrap}"
_extra_param="${_extra_param} -remote_dns_doh_port ${_doh_port} -remote_dns_doh_url ${_doh_url} -remote_dns_doh_host ${_doh_host}"
}
@@ -1436,16 +1449,9 @@ start_dns() {
_args="${_args} remote_dns_doh=${remote_dns_doh}"
echolog " - Sing-Box DNS(${TUN_DNS}) -> ${remote_dns_doh}"
local _doh_url=$(echo $remote_dns_doh | awk -F ',' '{print $1}')
local _doh_host_port=$(lua_api "get_domain_from_url(\"${_doh_url}\")")
local _doh_host=$(echo $_doh_host_port | awk -F ':' '{print $1}')
local _is_ip=$(lua_api "is_ip(\"${_doh_host}\")")
local _doh_port=$(echo $_doh_host_port | awk -F ':' '{print $2}')
[ -z "${_doh_port}" ] && _doh_port=443
local _doh_bootstrap=$(echo $remote_dns_doh | cut -d ',' -sf 2-)
[ "${_is_ip}" = "true" ] && _doh_bootstrap=${_doh_host}
[ -n "${_doh_bootstrap}" ] && REMOTE_DNS=${_doh_bootstrap}:${_doh_port}
unset _doh_url _doh_host_port _doh_host _is_ip _doh_port _doh_bootstrap
local _doh_url _doh_host _doh_port _doh_bootstrap
parse_doh "$remote_dns_doh" _doh_url _doh_host _doh_port _doh_bootstrap
[ -n "${_doh_bootstrap}" ] && REMOTE_DNS="${_doh_bootstrap}#${_doh_port}"
;;
esac
_args="${_args} dns_socks_address=127.0.0.1 dns_socks_port=${tcp_node_socks_port}"
@@ -1473,16 +1479,9 @@ start_dns() {
_args="${_args} remote_dns_doh=${remote_dns_doh}"
echolog " - Xray DNS(${TUN_DNS}) -> (${remote_dns_doh})(A/AAAA) + tcp://${REMOTE_DNS}"
local _doh_url=$(echo $remote_dns_doh | awk -F ',' '{print $1}')
local _doh_host_port=$(lua_api "get_domain_from_url(\"${_doh_url}\")")
local _doh_host=$(echo $_doh_host_port | awk -F ':' '{print $1}')
local _is_ip=$(lua_api "is_ip(\"${_doh_host}\")")
local _doh_port=$(echo $_doh_host_port | awk -F ':' '{print $2}')
[ -z "${_doh_port}" ] && _doh_port=443
local _doh_bootstrap=$(echo $remote_dns_doh | cut -d ',' -sf 2-)
[ "${_is_ip}" = "true" ] && _doh_bootstrap=${_doh_host}
[ -n "${_doh_bootstrap}" ] && REMOTE_DNS=${REMOTE_DNS},${_doh_bootstrap}:${_doh_port}
unset _doh_url _doh_host_port _doh_host _is_ip _doh_port _doh_bootstrap
local _doh_url _doh_host _doh_port _doh_bootstrap
parse_doh "$remote_dns_doh" _doh_url _doh_host _doh_port _doh_bootstrap
[ -n "${_doh_bootstrap}" ] && REMOTE_DNS="${REMOTE_DNS},${_doh_bootstrap}#${_doh_port}"
else
echolog " - Xray DNS(${TUN_DNS}) -> tcp://${REMOTE_DNS}"
fi

View File

@@ -1011,6 +1011,7 @@ add_firewall_rule() {
if [ "$TCP_NODE" != "nil" ]; then
_proxy_tcp_access() {
[ -n "${2}" ] || return 0
if echo "${2}" | grep -q -v ':'; then
ipset -q test $IPSET_LANLIST ${2}
[ $? -eq 0 ] && {
echolog " - 上游 DNS 服务器 ${2} 已在直接访问的列表中,不强制向 TCP 代理转发对该服务器 TCP/${3} 端口的访问"
@@ -1023,6 +1024,16 @@ add_firewall_rule() {
$ipt_m -I PSW $(comment "本机") -p tcp -i lo -d ${2} --dport ${3} $(REDIRECT $TCP_REDIR_PORT TPROXY)
fi
echolog " - [$?]将上游 DNS 服务器 ${2}:${3} 加入到路由器自身代理的 TCP 转发链"
else
ipset -q test $IPSET_LANLIST6 ${2}
[ $? -eq 0 ] && {
echolog " - 上游 DNS 服务器 ${2} 已在直接访问的列表中,不强制向 TCP 代理转发对该服务器 TCP/${3} 端口的访问"
return 0
}
$ip6t_m -I PSW_OUTPUT -p tcp -d ${2} --dport ${3} -j PSW_RULE
$ip6t_m -I PSW $(comment "本机") -p tcp -i lo -d ${2} --dport ${3} $(REDIRECT $TCP_REDIR_PORT TPROXY)
echolog " - [$?]将上游 DNS 服务器 [${2}]:${3} 加入到路由器自身代理的 TCP 转发链请确保您的节点支持IPv6并开启IPv6透明代理"
fi
}
[ "$use_tcp_node_resolve_dns" == 1 ] && hosts_foreach REMOTE_DNS _proxy_tcp_access 53
@@ -1087,6 +1098,7 @@ add_firewall_rule() {
if [ "$UDP_NODE" != "nil" -o "$TCP_UDP" = "1" ]; then
_proxy_udp_access() {
[ -n "${2}" ] || return 0
if echo "${2}" | grep -q -v ':'; then
ipset -q test $IPSET_LANLIST ${2}
[ $? == 0 ] && {
echolog " - 上游 DNS 服务器 ${2} 已在直接访问的列表中,不强制向 UDP 代理转发对该服务器 UDP/${3} 端口的访问"
@@ -1095,6 +1107,16 @@ add_firewall_rule() {
$ipt_m -I PSW_OUTPUT -p udp -d ${2} --dport ${3} -j PSW_RULE
$ipt_m -I PSW $(comment "本机") -p udp -i lo -d ${2} --dport ${3} $(REDIRECT $UDP_REDIR_PORT TPROXY)
echolog " - [$?]将上游 DNS 服务器 ${2}:${3} 加入到路由器自身代理的 UDP 转发链"
else
ipset -q test $IPSET_LANLIST6 ${2}
[ $? == 0 ] && {
echolog " - 上游 DNS 服务器 ${2} 已在直接访问的列表中,不强制向 UDP 代理转发对该服务器 UDP/${3} 端口的访问"
return 0
}
$ip6t_m -I PSW_OUTPUT -p udp -d ${2} --dport ${3} -j PSW_RULE
$ip6t_m -I PSW $(comment "本机") -p udp -i lo -d ${2} --dport ${3} $(REDIRECT $UDP_REDIR_PORT TPROXY)
echolog " - [$?]将上游 DNS 服务器 [${2}]:${3} 加入到路由器自身代理的 UDP 转发链请确保您的节点支持IPv6并开启IPv6透明代理"
fi
}
[ "$use_udp_node_resolve_dns" == 1 ] && hosts_foreach REMOTE_DNS _proxy_udp_access 53

View File

@@ -1088,6 +1088,7 @@ add_firewall_rule() {
if [ "$TCP_NODE" != "nil" ]; then
_proxy_tcp_access() {
[ -n "${2}" ] || return 0
if echo "${2}" | grep -q -v ':'; then
nft "get element $NFTABLE_NAME $NFTSET_LANLIST {${2}}" &>/dev/null
[ $? -eq 0 ] && {
echolog " - 上游 DNS 服务器 ${2} 已在直接访问的列表中,不强制向 TCP 代理转发对该服务器 TCP/${3} 端口的访问"
@@ -1100,6 +1101,16 @@ add_firewall_rule() {
nft insert rule $NFTABLE_NAME PSW_MANGLE ip protocol tcp iif lo tcp dport ${3} ip daddr ${2} $(REDIRECT $TCP_REDIR_PORT TPROXY4) comment \"本机\"
fi
echolog " - [$?]将上游 DNS 服务器 ${2}:${3} 加入到路由器自身代理的 TCP 转发链"
else
nft "get element $NFTABLE_NAME $NFTSET_LANLIST6 {${2}}" &>/dev/null
[ $? -eq 0 ] && {
echolog " - 上游 DNS 服务器 ${2} 已在直接访问的列表中,不强制向 TCP 代理转发对该服务器 TCP/${3} 端口的访问"
return 0
}
nft "insert rule $NFTABLE_NAME PSW_OUTPUT_MANGLE_V6 meta l4proto tcp ip6 daddr ${2} tcp dport ${3} counter jump PSW_RULE"
nft "insert rule $NFTABLE_NAME PSW_MANGLE_V6 meta l4proto tcp iif lo tcp dport ${3} ip6 daddr ${2} $(REDIRECT $TCP_REDIR_PORT TPROXY6) comment \"本机\""
echolog " - [$?]将上游 DNS 服务器 [${2}]:${3} 加入到路由器自身代理的 TCP 转发链请确保您的节点支持IPv6并开启IPv6透明代理"
fi
}
[ "$use_tcp_node_resolve_dns" == 1 ] && hosts_foreach REMOTE_DNS _proxy_tcp_access 53
@@ -1163,6 +1174,7 @@ add_firewall_rule() {
if [ "$UDP_NODE" != "nil" -o "$TCP_UDP" = "1" ]; then
_proxy_udp_access() {
[ -n "${2}" ] || return 0
if echo "${2}" | grep -q -v ':'; then
nft "get element $NFTABLE_NAME $NFTSET_LANLIST {${2}}" &>/dev/null
[ $? == 0 ] && {
echolog " - 上游 DNS 服务器 ${2} 已在直接访问的列表中,不强制向 UDP 代理转发对该服务器 UDP/${3} 端口的访问"
@@ -1171,6 +1183,16 @@ add_firewall_rule() {
nft "insert rule $NFTABLE_NAME PSW_OUTPUT_MANGLE ip protocol udp ip daddr ${2} udp dport ${3} counter jump PSW_RULE"
nft "insert rule $NFTABLE_NAME PSW_MANGLE ip protocol udp iif lo ip daddr ${2} $(REDIRECT $UDP_REDIR_PORT TPROXY4) comment \"本机\""
echolog " - [$?]将上游 DNS 服务器 ${2}:${3} 加入到路由器自身代理的 UDP 转发链"
else
nft "get element $NFTABLE_NAME $NFTSET_LANLIST6 {${2}}" &>/dev/null
[ $? == 0 ] && {
echolog " - 上游 DNS 服务器 ${2} 已在直接访问的列表中,不强制向 UDP 代理转发对该服务器 UDP/${3} 端口的访问"
return 0
}
nft "insert rule $NFTABLE_NAME PSW_OUTPUT_MANGLE_V6 meta l4proto udp ip6 daddr ${2} udp dport ${3} counter jump PSW_RULE"
nft "insert rule $NFTABLE_NAME PSW_MANGLE_V6 meta l4proto tcp iif lo ip6 daddr ${2} $(REDIRECT $UDP_REDIR_PORT TPROXY6) comment \"本机\""
echolog " - [$?]将上游 DNS 服务器 [${2}]:${3} 加入到路由器自身代理的 UDP 转发链请确保您的节点支持IPv6并开启IPv6透明代理"
fi
}
[ "$use_udp_node_resolve_dns" == 1 ] && hosts_foreach REMOTE_DNS _proxy_udp_access 53
[ -n "${LOCALHOST_UDP_PROXY_MODE}" ] && {

View File

@@ -39,9 +39,9 @@
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="shaderc.net" Version="0.1.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
<PackageVersion Include="Silk.NET.Vulkan" Version="2.21.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.21.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.21.0" />
<PackageVersion Include="SkiaSharp" Version="2.88.7" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.7" />
<PackageVersion Include="SPB" Version="0.0.4-build32" />

View File

@@ -1,13 +1,33 @@
using Ryujinx.Common.Utilities;
using System;
namespace Ryujinx.Common.GraphicsDriver
{
public static class DriverUtilities
{
private static void AddMesaFlags(string envVar, string newFlags)
{
string existingFlags = Environment.GetEnvironmentVariable(envVar);
string flags = existingFlags == null ? newFlags : $"{existingFlags},{newFlags}";
OsUtils.SetEnvironmentVariableNoCaching(envVar, flags);
}
public static void InitDriverConfig(bool oglThreading)
{
if (OperatingSystem.IsLinux())
{
AddMesaFlags("RADV_DEBUG", "nodcc");
}
ToggleOGLThreading(oglThreading);
}
public static void ToggleOGLThreading(bool enabled)
{
Environment.SetEnvironmentVariable("mesa_glthread", enabled.ToString().ToLower());
Environment.SetEnvironmentVariable("__GL_THREADED_OPTIMIZATIONS", enabled ? "1" : "0");
OsUtils.SetEnvironmentVariableNoCaching("mesa_glthread", enabled.ToString().ToLower());
OsUtils.SetEnvironmentVariableNoCaching("__GL_THREADED_OPTIMIZATIONS", enabled ? "1" : "0");
try
{

View File

@@ -0,0 +1,24 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Ryujinx.Common.Utilities
{
public partial class OsUtils
{
[LibraryImport("libc", SetLastError = true)]
private static partial int setenv([MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string value, int overwrite);
public static void SetEnvironmentVariableNoCaching(string key, string value)
{
// Set the value in the cached environment variables, too.
Environment.SetEnvironmentVariable(key, value);
if (!OperatingSystem.IsWindows())
{
int res = setenv(key, value, 1);
Debug.Assert(res != -1);
}
}
}
}

View File

@@ -415,7 +415,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
#pragma warning disable CS0649 // Field is never assigned to
public int Width;
public int Height;
public int Depth;
public ushort Depth;
public ushort Flags;
public readonly bool UnpackIsLayered()
{
return (Flags & 1) == 0;
}
#pragma warning restore CS0649
}

View File

@@ -468,13 +468,11 @@ namespace Ryujinx.Graphics.Gpu.Image
int gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY();
int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ();
layered &= size.UnpackIsLayered();
Target target;
if (dsState.MemoryLayout.UnpackIsTarget3D())
{
target = Target.Texture3D;
}
else if ((samplesInX | samplesInY) != 1)
if ((samplesInX | samplesInY) != 1)
{
target = size.Depth > 1 && layered
? Target.Texture2DMultisampleArray

View File

@@ -32,10 +32,12 @@ namespace Ryujinx.Graphics.Vulkan
CommandBuffer
}
private bool _feedbackLoopActive;
private PipelineStageFlags _incoherentBufferWriteStages;
private PipelineStageFlags _incoherentTextureWriteStages;
private PipelineStageFlags _extraStages;
private IncoherentBarrierType _queuedIncoherentBarrier;
private bool _queuedFeedbackLoopBarrier;
public BarrierBatch(VulkanRenderer gd)
{
@@ -53,17 +55,6 @@ namespace Ryujinx.Graphics.Vulkan
stages |= PipelineStageFlags.TransformFeedbackBitExt;
}
if (!gd.IsTBDR)
{
// Desktop GPUs can transform image barriers into memory barriers.
access |= AccessFlags.DepthStencilAttachmentWriteBit | AccessFlags.ColorAttachmentWriteBit;
access |= AccessFlags.DepthStencilAttachmentReadBit | AccessFlags.ColorAttachmentReadBit;
stages |= PipelineStageFlags.EarlyFragmentTestsBit | PipelineStageFlags.LateFragmentTestsBit;
stages |= PipelineStageFlags.ColorAttachmentOutputBit;
}
return (access, stages);
}
@@ -178,16 +169,34 @@ namespace Ryujinx.Graphics.Vulkan
}
_queuedIncoherentBarrier = IncoherentBarrierType.None;
_queuedFeedbackLoopBarrier = false;
}
else if (_feedbackLoopActive && _queuedFeedbackLoopBarrier)
{
// Feedback loop barrier.
MemoryBarrier barrier = new MemoryBarrier()
{
SType = StructureType.MemoryBarrier,
SrcAccessMask = AccessFlags.ShaderWriteBit,
DstAccessMask = AccessFlags.ShaderReadBit
};
QueueBarrier(barrier, PipelineStageFlags.FragmentShaderBit, PipelineStageFlags.AllGraphicsBit);
_queuedFeedbackLoopBarrier = false;
}
_feedbackLoopActive = false;
}
}
public unsafe void Flush(CommandBufferScoped cbs, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass)
{
Flush(cbs, null, inRenderPass, rpHolder, endRenderPass);
Flush(cbs, null, false, inRenderPass, rpHolder, endRenderPass);
}
public unsafe void Flush(CommandBufferScoped cbs, ShaderCollection program, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass)
public unsafe void Flush(CommandBufferScoped cbs, ShaderCollection program, bool feedbackLoopActive, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass)
{
if (program != null)
{
@@ -195,6 +204,8 @@ namespace Ryujinx.Graphics.Vulkan
_incoherentTextureWriteStages |= program.IncoherentTextureWriteStages;
}
_feedbackLoopActive |= feedbackLoopActive;
FlushMemoryBarrier(program, inRenderPass);
if (!inRenderPass && rpHolder != null)
@@ -406,6 +417,8 @@ namespace Ryujinx.Graphics.Vulkan
{
_queuedIncoherentBarrier = type;
}
_queuedFeedbackLoopBarrier = true;
}
public void QueueTextureBarrier()

View File

@@ -122,7 +122,7 @@ namespace Ryujinx.Graphics.Vulkan
Range = (uint)size,
};
_gd.Api.CreateBufferView(_device, bufferViewCreateInfo, null, out var bufferView).ThrowOnError();
_gd.Api.CreateBufferView(_device, in bufferViewCreateInfo, null, out var bufferView).ThrowOnError();
return new Auto<DisposableBufferView>(new DisposableBufferView(_gd.Api, _device, bufferView), this, _waitable, _buffer);
}
@@ -153,7 +153,7 @@ namespace Ryujinx.Graphics.Vulkan
PipelineStageFlags.AllCommandsBit,
DependencyFlags.DeviceGroupBit,
1,
memoryBarrier,
in memoryBarrier,
0,
null,
0,
@@ -770,7 +770,7 @@ namespace Ryujinx.Graphics.Vulkan
0,
null,
1,
memoryBarrier,
in memoryBarrier,
0,
null);
}

View File

@@ -221,7 +221,7 @@ namespace Ryujinx.Graphics.Vulkan
PBufferBinds = &bufferBind
};
gd.Api.QueueBindSparse(gd.Queue, 1, bindSparseInfo, default).ThrowOnError();
gd.Api.QueueBindSparse(gd.Queue, 1, in bindSparseInfo, default).ThrowOnError();
}
var holder = new BufferHolder(gd, _device, buffer, (int)size, storageAllocations);

View File

@@ -25,7 +25,10 @@ namespace Ryujinx.Graphics.Vulkan
{
var buffer = _buffer.Get(cbs, _offset, _size, true).Value;
gd.TransformFeedbackApi.CmdBindTransformFeedbackBuffers(cbs.CommandBuffer, binding, 1, buffer, (ulong)_offset, (ulong)_size);
ulong offset = (ulong)_offset;
ulong size = (ulong)_size;
gd.TransformFeedbackApi.CmdBindTransformFeedbackBuffers(cbs.CommandBuffer, binding, 1, in buffer, in offset, in size);
}
}

View File

@@ -45,7 +45,7 @@ namespace Ryujinx.Graphics.Vulkan
Level = CommandBufferLevel.Primary,
};
api.AllocateCommandBuffers(device, allocateInfo, out CommandBuffer);
api.AllocateCommandBuffers(device, in allocateInfo, out CommandBuffer);
Dependants = new List<IAuto>();
Waitables = new List<MultiFenceHolder>();
@@ -83,7 +83,7 @@ namespace Ryujinx.Graphics.Vulkan
CommandPoolCreateFlags.ResetCommandBufferBit,
};
api.CreateCommandPool(device, commandPoolCreateInfo, null, out _pool).ThrowOnError();
api.CreateCommandPool(device, in commandPoolCreateInfo, null, out _pool).ThrowOnError();
// We need at least 2 command buffers to get texture data in some cases.
_totalCommandBuffers = isLight ? 2 : MaxCommandBuffers;
@@ -253,7 +253,7 @@ namespace Ryujinx.Graphics.Vulkan
SType = StructureType.CommandBufferBeginInfo,
};
_api.BeginCommandBuffer(entry.CommandBuffer, commandBufferBeginInfo).ThrowOnError();
_api.BeginCommandBuffer(entry.CommandBuffer, in commandBufferBeginInfo).ThrowOnError();
return new CommandBufferScoped(this, entry.CommandBuffer, cursor);
}
@@ -311,7 +311,7 @@ namespace Ryujinx.Graphics.Vulkan
lock (_queueLock)
{
_api.QueueSubmit(_queue, 1, sInfo, entry.Fence.GetUnsafe()).ThrowOnError();
_api.QueueSubmit(_queue, 1, in sInfo, entry.Fence.GetUnsafe()).ThrowOnError();
}
}
}

View File

@@ -43,7 +43,7 @@ namespace Ryujinx.Graphics.Vulkan
PBufferInfo = &bufferInfo,
};
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null);
}
}
@@ -66,7 +66,7 @@ namespace Ryujinx.Graphics.Vulkan
PBufferInfo = pBufferInfo,
};
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null);
}
}
@@ -84,7 +84,7 @@ namespace Ryujinx.Graphics.Vulkan
PImageInfo = &imageInfo,
};
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null);
}
}
@@ -107,7 +107,7 @@ namespace Ryujinx.Graphics.Vulkan
PImageInfo = pImageInfo,
};
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null);
}
}
@@ -144,7 +144,7 @@ namespace Ryujinx.Graphics.Vulkan
PImageInfo = pImageInfo,
};
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null);
i += count - 1;
}
@@ -166,7 +166,7 @@ namespace Ryujinx.Graphics.Vulkan
PTexelBufferView = &texelBufferView,
};
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null);
}
}
@@ -200,7 +200,7 @@ namespace Ryujinx.Graphics.Vulkan
PTexelBufferView = pTexelBufferView + i,
};
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null);
}
i += count;

View File

@@ -40,7 +40,7 @@ namespace Ryujinx.Graphics.Vulkan
PPoolSizes = pPoolsSize,
};
Api.CreateDescriptorPool(device, descriptorPoolCreateInfo, null, out _pool).ThrowOnError();
Api.CreateDescriptorPool(device, in descriptorPoolCreateInfo, null, out _pool).ThrowOnError();
}
}

View File

@@ -4,6 +4,7 @@ using Ryujinx.Graphics.Shader;
using Silk.NET.Vulkan;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using CompareOp = Ryujinx.Graphics.GAL.CompareOp;
@@ -42,15 +43,15 @@ namespace Ryujinx.Graphics.Vulkan
private record struct TextureRef
{
public ShaderStage Stage;
public TextureStorage Storage;
public Auto<DisposableImageView> View;
public TextureView View;
public Auto<DisposableImageView> ImageView;
public Auto<DisposableSampler> Sampler;
public TextureRef(ShaderStage stage, TextureStorage storage, Auto<DisposableImageView> view, Auto<DisposableSampler> sampler)
public TextureRef(ShaderStage stage, TextureView view, Auto<DisposableImageView> imageView, Auto<DisposableSampler> sampler)
{
Stage = stage;
Storage = storage;
View = view;
ImageView = imageView;
Sampler = sampler;
}
}
@@ -58,14 +59,14 @@ namespace Ryujinx.Graphics.Vulkan
private record struct ImageRef
{
public ShaderStage Stage;
public TextureStorage Storage;
public Auto<DisposableImageView> View;
public TextureView View;
public Auto<DisposableImageView> ImageView;
public ImageRef(ShaderStage stage, TextureStorage storage, Auto<DisposableImageView> view)
public ImageRef(ShaderStage stage, TextureView view, Auto<DisposableImageView> imageView)
{
Stage = stage;
Storage = storage;
View = view;
ImageView = imageView;
}
}
@@ -124,6 +125,8 @@ namespace Ryujinx.Graphics.Vulkan
private readonly TextureView _dummyTexture;
private readonly SamplerHolder _dummySampler;
public List<TextureView> FeedbackLoopHazards { get; private set; }
public DescriptorSetUpdater(VulkanRenderer gd, Device device)
{
_gd = gd;
@@ -209,10 +212,15 @@ namespace Ryujinx.Graphics.Vulkan
_templateUpdater = new();
}
public void Initialize()
public void Initialize(bool isMainPipeline)
{
MemoryOwner<byte> dummyTextureData = MemoryOwner<byte>.RentCleared(4);
_dummyTexture.SetData(dummyTextureData);
if (isMainPipeline)
{
FeedbackLoopHazards = new();
}
}
private static bool BindingOverlaps(ref DescriptorBufferInfo info, int bindingOffset, int offset, int size)
@@ -275,6 +283,18 @@ namespace Ryujinx.Graphics.Vulkan
public void InsertBindingBarriers(CommandBufferScoped cbs)
{
if ((FeedbackLoopHazards?.Count ?? 0) > 0)
{
// Clear existing hazards - they will be rebuilt.
foreach (TextureView hazard in FeedbackLoopHazards)
{
hazard.DecrementHazardUses();
}
FeedbackLoopHazards.Clear();
}
foreach (ResourceBindingSegment segment in _program.BindingSegments[PipelineBase.TextureSetIndex])
{
if (segment.Type == ResourceType.TextureAndSampler)
@@ -284,7 +304,7 @@ namespace Ryujinx.Graphics.Vulkan
for (int i = 0; i < segment.Count; i++)
{
ref var texture = ref _textureRefs[segment.Binding + i];
texture.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, texture.Stage.ConvertToPipelineStageFlags());
texture.View?.PrepareForUsage(cbs, texture.Stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards);
}
}
else
@@ -305,7 +325,7 @@ namespace Ryujinx.Graphics.Vulkan
for (int i = 0; i < segment.Count; i++)
{
ref var image = ref _imageRefs[segment.Binding + i];
image.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, image.Stage.ConvertToPipelineStageFlags());
image.View?.PrepareForUsage(cbs, image.Stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards);
}
}
else
@@ -385,9 +405,12 @@ namespace Ryujinx.Graphics.Vulkan
}
else if (image is TextureView view)
{
view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
ref ImageRef iRef = ref _imageRefs[binding];
_imageRefs[binding] = new(stage, view.Storage, view.GetView(imageFormat).GetIdentityImageView());
iRef.View?.ClearUsage(FeedbackLoopHazards);
view?.PrepareForUsage(cbs, stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards);
iRef = new(stage, view, view.GetView(imageFormat).GetIdentityImageView());
}
else
{
@@ -486,9 +509,12 @@ namespace Ryujinx.Graphics.Vulkan
}
else if (texture is TextureView view)
{
view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
ref TextureRef iRef = ref _textureRefs[binding];
_textureRefs[binding] = new(stage, view.Storage, view.GetImageView(), ((SamplerHolder)sampler)?.GetSampler());
iRef.View?.ClearUsage(FeedbackLoopHazards);
view?.PrepareForUsage(cbs, stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards);
iRef = new(stage, view, view.GetImageView(), ((SamplerHolder)sampler)?.GetSampler());
}
else
{
@@ -510,7 +536,7 @@ namespace Ryujinx.Graphics.Vulkan
{
view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
_textureRefs[binding] = new(stage, view.Storage, view.GetIdentityImageView(), ((SamplerHolder)sampler)?.GetSampler());
_textureRefs[binding] = new(stage, view, view.GetIdentityImageView(), ((SamplerHolder)sampler)?.GetSampler());
SignalDirty(DirtyFlags.Texture);
}
@@ -836,7 +862,7 @@ namespace Ryujinx.Graphics.Vulkan
ref var texture = ref textures[i];
ref var refs = ref _textureRefs[binding + i];
texture.ImageView = refs.View?.Get(cbs).Value ?? default;
texture.ImageView = refs.ImageView?.Get(cbs).Value ?? default;
texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default;
if (texture.ImageView.Handle == 0)
@@ -886,7 +912,7 @@ namespace Ryujinx.Graphics.Vulkan
for (int i = 0; i < count; i++)
{
images[i].ImageView = _imageRefs[binding + i].View?.Get(cbs).Value ?? default;
images[i].ImageView = _imageRefs[binding + i].ImageView?.Get(cbs).Value ?? default;
}
tu.Push<DescriptorImageInfo>(images[..count]);
@@ -957,7 +983,7 @@ namespace Ryujinx.Graphics.Vulkan
ref var texture = ref textures[i];
ref var refs = ref _textureRefs[binding + i];
texture.ImageView = refs.View?.Get(cbs).Value ?? default;
texture.ImageView = refs.ImageView?.Get(cbs).Value ?? default;
texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default;
if (texture.ImageView.Handle == 0)

View File

@@ -0,0 +1,12 @@
using System;
namespace Ryujinx.Graphics.Vulkan
{
[Flags]
internal enum FeedbackLoopAspects
{
None = 0,
Color = 1 << 0,
Depth = 1 << 1,
}
}

View File

@@ -250,7 +250,7 @@ namespace Ryujinx.Graphics.Vulkan
Layers = Layers,
};
api.CreateFramebuffer(_device, framebufferCreateInfo, null, out var framebuffer).ThrowOnError();
api.CreateFramebuffer(_device, in framebufferCreateInfo, null, out var framebuffer).ThrowOnError();
return new Auto<DisposableFramebuffer>(new DisposableFramebuffer(api, _device, framebuffer), null, _attachments);
}
@@ -302,6 +302,27 @@ namespace Ryujinx.Graphics.Vulkan
_depthStencil?.Storage?.AddStoreOpUsage(true);
}
public void ClearBindings()
{
_depthStencil?.Storage.ClearBindings();
for (int i = 0; i < _colorsCanonical.Length; i++)
{
_colorsCanonical[i]?.Storage.ClearBindings();
}
}
public void AddBindings()
{
_depthStencil?.Storage.AddBinding(_depthStencil);
for (int i = 0; i < _colorsCanonical.Length; i++)
{
TextureView color = _colorsCanonical[i];
color?.Storage.AddBinding(color);
}
}
public (RenderPassHolder rpHolder, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
VulkanRenderer gd,
Device device,

View File

@@ -46,6 +46,8 @@ namespace Ryujinx.Graphics.Vulkan
public readonly bool SupportsViewportArray2;
public readonly bool SupportsHostImportedMemory;
public readonly bool SupportsDepthClipControl;
public readonly bool SupportsAttachmentFeedbackLoop;
public readonly bool SupportsDynamicAttachmentFeedbackLoop;
public readonly uint SubgroupSize;
public readonly SampleCountFlags SupportedSampleCounts;
public readonly PortabilitySubsetFlags PortabilitySubset;
@@ -84,6 +86,8 @@ namespace Ryujinx.Graphics.Vulkan
bool supportsViewportArray2,
bool supportsHostImportedMemory,
bool supportsDepthClipControl,
bool supportsAttachmentFeedbackLoop,
bool supportsDynamicAttachmentFeedbackLoop,
uint subgroupSize,
SampleCountFlags supportedSampleCounts,
PortabilitySubsetFlags portabilitySubset,
@@ -121,6 +125,8 @@ namespace Ryujinx.Graphics.Vulkan
SupportsViewportArray2 = supportsViewportArray2;
SupportsHostImportedMemory = supportsHostImportedMemory;
SupportsDepthClipControl = supportsDepthClipControl;
SupportsAttachmentFeedbackLoop = supportsAttachmentFeedbackLoop;
SupportsDynamicAttachmentFeedbackLoop = supportsDynamicAttachmentFeedbackLoop;
SubgroupSize = subgroupSize;
SupportedSampleCounts = supportedSampleCounts;
PortabilitySubset = portabilitySubset;

View File

@@ -115,7 +115,7 @@ namespace Ryujinx.Graphics.Vulkan
PNext = &importInfo,
};
Result result = _api.AllocateMemory(_device, memoryAllocateInfo, null, out var deviceMemory);
Result result = _api.AllocateMemory(_device, in memoryAllocateInfo, null, out var deviceMemory);
if (result < Result.Success)
{

View File

@@ -220,7 +220,7 @@ namespace Ryujinx.Graphics.Vulkan
MemoryTypeIndex = (uint)MemoryTypeIndex,
};
_api.AllocateMemory(_device, memoryAllocateInfo, null, out var deviceMemory).ThrowOnError();
_api.AllocateMemory(_device, in memoryAllocateInfo, null, out var deviceMemory).ThrowOnError();
IntPtr hostPointer = IntPtr.Zero;

View File

@@ -1,3 +1,4 @@
using Silk.NET.Core.Loader;
using Silk.NET.Vulkan;
using System;
using System.Runtime.InteropServices;
@@ -8,6 +9,8 @@ namespace Ryujinx.Graphics.Vulkan.MoltenVK
[SupportedOSPlatform("macos")]
public static partial class MVKInitialization
{
private const string VulkanLib = "libvulkan.dylib";
[LibraryImport("libMoltenVK.dylib")]
private static partial Result vkGetMoltenVKConfigurationMVK(IntPtr unusedInstance, out MVKConfiguration config, in IntPtr configSize);
@@ -29,5 +32,20 @@ namespace Ryujinx.Graphics.Vulkan.MoltenVK
vkSetMoltenVKConfigurationMVK(IntPtr.Zero, config, configSize);
}
private static string[] Resolver(string path)
{
if (path.EndsWith(VulkanLib))
{
path = path[..^VulkanLib.Length] + "libMoltenVK.dylib";
return [path];
}
return Array.Empty<string>();
}
public static void InitializeResolver()
{
((DefaultPathResolver)PathResolver.Default).Resolvers.Insert(0, Resolver);
}
}
}

View File

@@ -2,6 +2,7 @@ using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
using Silk.NET.Vulkan;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
@@ -33,6 +34,7 @@ namespace Ryujinx.Graphics.Vulkan
public readonly Action EndRenderPassDelegate;
protected PipelineDynamicState DynamicState;
protected bool IsMainPipeline;
private PipelineState _newState;
private bool _graphicsStateDirty;
private bool _computeStateDirty;
@@ -85,6 +87,9 @@ namespace Ryujinx.Graphics.Vulkan
private bool _tfEnabled;
private bool _tfActive;
private FeedbackLoopAspects _feedbackLoop;
private bool _passWritesDepthStencil;
private readonly PipelineColorBlendAttachmentState[] _storedBlend;
public ulong DrawCount { get; private set; }
public bool RenderPassActive { get; private set; }
@@ -102,7 +107,7 @@ namespace Ryujinx.Graphics.Vulkan
SType = StructureType.PipelineCacheCreateInfo,
};
gd.Api.CreatePipelineCache(device, pipelineCacheCreateInfo, null, out PipelineCache).ThrowOnError();
gd.Api.CreatePipelineCache(device, in pipelineCacheCreateInfo, null, out PipelineCache).ThrowOnError();
_descriptorSetUpdater = new DescriptorSetUpdater(gd, device);
_vertexBufferUpdater = new VertexBufferUpdater(gd);
@@ -126,7 +131,7 @@ namespace Ryujinx.Graphics.Vulkan
public void Initialize()
{
_descriptorSetUpdater.Initialize();
_descriptorSetUpdater.Initialize(IsMainPipeline);
QuadsToTrisPattern = new IndexBufferPattern(Gd, 4, 6, 0, new[] { 0, 1, 2, 0, 2, 3 }, 4, false);
TriFanToTrisPattern = new IndexBufferPattern(Gd, 3, 3, 2, new[] { int.MinValue, -1, 0 }, 1, true);
@@ -814,6 +819,8 @@ namespace Ryujinx.Graphics.Vulkan
_newState.DepthTestEnable = depthTest.TestEnable;
_newState.DepthWriteEnable = depthTest.WriteEnable;
_newState.DepthCompareOp = depthTest.Func.Convert();
UpdatePassDepthStencil();
SignalStateChange();
}
@@ -1079,6 +1086,8 @@ namespace Ryujinx.Graphics.Vulkan
_newState.StencilFrontPassOp = stencilTest.FrontDpPass.Convert();
_newState.StencilFrontDepthFailOp = stencilTest.FrontDpFail.Convert();
_newState.StencilFrontCompareOp = stencilTest.FrontFunc.Convert();
UpdatePassDepthStencil();
SignalStateChange();
}
@@ -1426,7 +1435,23 @@ namespace Ryujinx.Graphics.Vulkan
}
}
if (IsMainPipeline)
{
FramebufferParams?.ClearBindings();
}
FramebufferParams = new FramebufferParams(Device, colors, depthStencil);
if (IsMainPipeline)
{
FramebufferParams.AddBindings();
_newState.FeedbackLoopAspects = FeedbackLoopAspects.None;
_bindingBarriersDirty = true;
}
_passWritesDepthStencil = false;
UpdatePassDepthStencil();
UpdatePipelineAttachmentFormats();
}
@@ -1493,11 +1518,82 @@ namespace Ryujinx.Graphics.Vulkan
}
}
Gd.Barriers.Flush(Cbs, _program, RenderPassActive, _rpHolder, EndRenderPassDelegate);
Gd.Barriers.Flush(Cbs, _program, _feedbackLoop != 0, RenderPassActive, _rpHolder, EndRenderPassDelegate);
_descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Compute);
}
private bool ChangeFeedbackLoop(FeedbackLoopAspects aspects)
{
if (_feedbackLoop != aspects)
{
if (Gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop)
{
DynamicState.SetFeedbackLoop(aspects);
}
else
{
_newState.FeedbackLoopAspects = aspects;
}
_feedbackLoop = aspects;
return true;
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool UpdateFeedbackLoop()
{
List<TextureView> hazards = _descriptorSetUpdater.FeedbackLoopHazards;
if ((hazards?.Count ?? 0) > 0)
{
FeedbackLoopAspects aspects = 0;
foreach (TextureView view in hazards)
{
// May need to enforce feedback loop layout here in the future.
// Though technically, it should always work with the general layout.
if (view.Info.Format.IsDepthOrStencil())
{
if (_passWritesDepthStencil)
{
// If depth/stencil isn't written in the pass, it doesn't count as a feedback loop.
aspects |= FeedbackLoopAspects.Depth;
}
}
else
{
aspects |= FeedbackLoopAspects.Color;
}
}
return ChangeFeedbackLoop(aspects);
}
else if (_feedbackLoop != 0)
{
return ChangeFeedbackLoop(FeedbackLoopAspects.None);
}
return false;
}
private void UpdatePassDepthStencil()
{
if (!RenderPassActive)
{
_passWritesDepthStencil = false;
}
// Stencil test being enabled doesn't necessarily mean a write, but it's not critical to check.
_passWritesDepthStencil |= (_newState.DepthTestEnable && _newState.DepthWriteEnable) || _newState.StencilTestEnable;
}
private bool RecreateGraphicsPipelineIfNeeded()
{
if (AutoFlush.ShouldFlushDraw(DrawCount))
@@ -1505,7 +1601,7 @@ namespace Ryujinx.Graphics.Vulkan
Gd.FlushAllCommands();
}
DynamicState.ReplayIfDirty(Gd.Api, CommandBuffer);
DynamicState.ReplayIfDirty(Gd, CommandBuffer);
if (_needsIndexBufferRebind && _indexBufferPattern == null)
{
@@ -1539,7 +1635,15 @@ namespace Ryujinx.Graphics.Vulkan
_vertexBufferUpdater.Commit(Cbs);
}
if (_graphicsStateDirty || Pbp != PipelineBindPoint.Graphics)
if (_bindingBarriersDirty)
{
// Stale barriers may have been activated by switching program. Emit any that are relevant.
_descriptorSetUpdater.InsertBindingBarriers(Cbs);
_bindingBarriersDirty = false;
}
if (UpdateFeedbackLoop() || _graphicsStateDirty || Pbp != PipelineBindPoint.Graphics)
{
if (!CreatePipeline(PipelineBindPoint.Graphics))
{
@@ -1548,17 +1652,9 @@ namespace Ryujinx.Graphics.Vulkan
_graphicsStateDirty = false;
Pbp = PipelineBindPoint.Graphics;
if (_bindingBarriersDirty)
{
// Stale barriers may have been activated by switching program. Emit any that are relevant.
_descriptorSetUpdater.InsertBindingBarriers(Cbs);
_bindingBarriersDirty = false;
}
}
Gd.Barriers.Flush(Cbs, _program, RenderPassActive, _rpHolder, EndRenderPassDelegate);
Gd.Barriers.Flush(Cbs, _program, _feedbackLoop != 0, RenderPassActive, _rpHolder, EndRenderPassDelegate);
_descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Graphics);
@@ -1628,7 +1724,7 @@ namespace Ryujinx.Graphics.Vulkan
ClearValueCount = 1,
};
Gd.Api.CmdBeginRenderPass(CommandBuffer, renderPassBeginInfo, SubpassContents.Inline);
Gd.Api.CmdBeginRenderPass(CommandBuffer, in renderPassBeginInfo, SubpassContents.Inline);
RenderPassActive = true;
}
}

View File

@@ -116,7 +116,7 @@ namespace Ryujinx.Graphics.Vulkan
DependencyCount = 1,
};
gd.Api.CreateRenderPass(device, renderPassCreateInfo, null, out var renderPass).ThrowOnError();
gd.Api.CreateRenderPass(device, in renderPassCreateInfo, null, out var renderPass).ThrowOnError();
return new DisposableRenderPass(gd.Api, device, renderPass);
}

View File

@@ -1,5 +1,6 @@
using Ryujinx.Common.Memory;
using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.EXT;
namespace Ryujinx.Graphics.Vulkan
{
@@ -21,6 +22,8 @@ namespace Ryujinx.Graphics.Vulkan
private Array4<float> _blendConstants;
private FeedbackLoopAspects _feedbackLoopAspects;
public uint ViewportsCount;
public Array16<Viewport> Viewports;
@@ -32,7 +35,8 @@ namespace Ryujinx.Graphics.Vulkan
Scissor = 1 << 2,
Stencil = 1 << 3,
Viewport = 1 << 4,
All = Blend | DepthBias | Scissor | Stencil | Viewport,
FeedbackLoop = 1 << 5,
All = Blend | DepthBias | Scissor | Stencil | Viewport | FeedbackLoop,
}
private DirtyFlags _dirty;
@@ -99,13 +103,22 @@ namespace Ryujinx.Graphics.Vulkan
}
}
public void SetFeedbackLoop(FeedbackLoopAspects aspects)
{
_feedbackLoopAspects = aspects;
_dirty |= DirtyFlags.FeedbackLoop;
}
public void ForceAllDirty()
{
_dirty = DirtyFlags.All;
}
public void ReplayIfDirty(Vk api, CommandBuffer commandBuffer)
public void ReplayIfDirty(VulkanRenderer gd, CommandBuffer commandBuffer)
{
Vk api = gd.Api;
if (_dirty.HasFlag(DirtyFlags.Blend))
{
RecordBlend(api, commandBuffer);
@@ -131,6 +144,11 @@ namespace Ryujinx.Graphics.Vulkan
RecordViewport(api, commandBuffer);
}
if (_dirty.HasFlag(DirtyFlags.FeedbackLoop) && gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop)
{
RecordFeedbackLoop(gd.DynamicFeedbackLoopApi, commandBuffer);
}
_dirty = DirtyFlags.None;
}
@@ -169,5 +187,17 @@ namespace Ryujinx.Graphics.Vulkan
api.CmdSetViewport(commandBuffer, 0, ViewportsCount, Viewports.AsSpan());
}
}
private readonly void RecordFeedbackLoop(ExtAttachmentFeedbackLoopDynamicState api, CommandBuffer commandBuffer)
{
ImageAspectFlags aspects = (_feedbackLoopAspects & FeedbackLoopAspects.Color) != 0 ? ImageAspectFlags.ColorBit : 0;
if ((_feedbackLoopAspects & FeedbackLoopAspects.Depth) != 0)
{
aspects |= ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit;
}
api.CmdSetAttachmentFeedbackLoopEnable(commandBuffer, aspects);
}
}
}

View File

@@ -28,6 +28,8 @@ namespace Ryujinx.Graphics.Vulkan
_activeBufferMirrors = new();
CommandBuffer = (Cbs = gd.CommandBufferPool.Rent()).CommandBuffer;
IsMainPipeline = true;
}
private void CopyPendingQuery()
@@ -235,7 +237,7 @@ namespace Ryujinx.Graphics.Vulkan
if (Pipeline != null && Pbp == PipelineBindPoint.Graphics)
{
DynamicState.ReplayIfDirty(Gd.Api, CommandBuffer);
DynamicState.ReplayIfDirty(Gd, CommandBuffer);
}
}

View File

@@ -91,7 +91,7 @@ namespace Ryujinx.Graphics.Vulkan
Flags = flags,
};
gd.Api.CreateDescriptorSetLayout(device, descriptorSetLayoutCreateInfo, null, out layouts[setIndex]).ThrowOnError();
gd.Api.CreateDescriptorSetLayout(device, in descriptorSetLayoutCreateInfo, null, out layouts[setIndex]).ThrowOnError();
}
}

View File

@@ -8,6 +8,7 @@ namespace Ryujinx.Graphics.Vulkan
struct PipelineState : IDisposable
{
private const int RequiredSubgroupSize = 32;
private const int MaxDynamicStatesCount = 9;
public PipelineUid Internal;
@@ -299,6 +300,12 @@ namespace Ryujinx.Graphics.Vulkan
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFBF) | ((value ? 1UL : 0UL) << 6);
}
public FeedbackLoopAspects FeedbackLoopAspects
{
readonly get => (FeedbackLoopAspects)((Internal.Id8 >> 7) & 0x3);
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFE7F) | (((ulong)value) << 7);
}
public bool HasTessellationControlShader;
public NativeArray<PipelineShaderStageCreateInfo> Stages;
public PipelineLayout PipelineLayout;
@@ -564,9 +571,11 @@ namespace Ryujinx.Graphics.Vulkan
}
bool supportsExtDynamicState = gd.Capabilities.SupportsExtendedDynamicState;
int dynamicStatesCount = supportsExtDynamicState ? 8 : 7;
bool supportsFeedbackLoopDynamicState = gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop;
DynamicState* dynamicStates = stackalloc DynamicState[dynamicStatesCount];
DynamicState* dynamicStates = stackalloc DynamicState[MaxDynamicStatesCount];
int dynamicStatesCount = 7;
dynamicStates[0] = DynamicState.Viewport;
dynamicStates[1] = DynamicState.Scissor;
@@ -578,7 +587,12 @@ namespace Ryujinx.Graphics.Vulkan
if (supportsExtDynamicState)
{
dynamicStates[7] = DynamicState.VertexInputBindingStrideExt;
dynamicStates[dynamicStatesCount++] = DynamicState.VertexInputBindingStrideExt;
}
if (supportsFeedbackLoopDynamicState)
{
dynamicStates[dynamicStatesCount++] = DynamicState.AttachmentFeedbackLoopEnableExt;
}
var pipelineDynamicStateCreateInfo = new PipelineDynamicStateCreateInfo
@@ -588,9 +602,27 @@ namespace Ryujinx.Graphics.Vulkan
PDynamicStates = dynamicStates,
};
PipelineCreateFlags flags = 0;
if (gd.Capabilities.SupportsAttachmentFeedbackLoop)
{
FeedbackLoopAspects aspects = FeedbackLoopAspects;
if ((aspects & FeedbackLoopAspects.Color) != 0)
{
flags |= PipelineCreateFlags.CreateColorAttachmentFeedbackLoopBitExt;
}
if ((aspects & FeedbackLoopAspects.Depth) != 0)
{
flags |= PipelineCreateFlags.CreateDepthStencilAttachmentFeedbackLoopBitExt;
}
}
var pipelineCreateInfo = new GraphicsPipelineCreateInfo
{
SType = StructureType.GraphicsPipelineCreateInfo,
Flags = flags,
StageCount = StagesCount,
PStages = Stages.Pointer,
PVertexInputState = &vertexInputState,

View File

@@ -52,7 +52,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries
PipelineStatistics = flags,
};
gd.Api.CreateQueryPool(device, queryPoolCreateInfo, null, out _queryPool).ThrowOnError();
gd.Api.CreateQueryPool(device, in queryPoolCreateInfo, null, out _queryPool).ThrowOnError();
}
var buffer = gd.BufferManager.Create(gd, sizeof(long), forConditionalRendering: true);

View File

@@ -125,7 +125,7 @@ namespace Ryujinx.Graphics.Vulkan
DependencyCount = 1,
};
gd.Api.CreateRenderPass(device, renderPassCreateInfo, null, out var renderPass).ThrowOnError();
gd.Api.CreateRenderPass(device, in renderPassCreateInfo, null, out var renderPass).ThrowOnError();
_renderPass = new Auto<DisposableRenderPass>(new DisposableRenderPass(gd.Api, device, renderPass));
}

View File

@@ -68,7 +68,7 @@ namespace Ryujinx.Graphics.Vulkan
samplerCreateInfo.BorderColor = BorderColor.FloatCustomExt;
}
gd.Api.CreateSampler(device, samplerCreateInfo, null, out var sampler).ThrowOnError();
gd.Api.CreateSampler(device, in samplerCreateInfo, null, out var sampler).ThrowOnError();
_sampler = new Auto<DisposableSampler>(new DisposableSampler(gd.Api, device, sampler));
}

View File

@@ -64,7 +64,7 @@ namespace Ryujinx.Graphics.Vulkan
PCode = (uint*)pCode,
};
api.CreateShaderModule(device, shaderModuleCreateInfo, null, out _module).ThrowOnError();
api.CreateShaderModule(device, in shaderModuleCreateInfo, null, out _module).ThrowOnError();
}
CompileStatus = ProgramLinkStatus.Success;

View File

@@ -88,7 +88,7 @@ namespace Ryujinx.Graphics.Vulkan
DstOffsets = dstOffsets,
};
api.CmdBlitImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, region, filter);
api.CmdBlitImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, in region, filter);
copySrcLevel++;
copyDstLevel++;
@@ -320,13 +320,13 @@ namespace Ryujinx.Graphics.Vulkan
{
var region = new ImageResolve(srcSl, new Offset3D(0, 0, srcZ), dstSl, new Offset3D(0, 0, dstZ), extent);
api.CmdResolveImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, region);
api.CmdResolveImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, in region);
}
else
{
var region = new ImageCopy(srcSl, new Offset3D(0, 0, srcZ), dstSl, new Offset3D(0, 0, dstZ), extent);
api.CmdCopyImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, region);
api.CmdCopyImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, in region);
}
width = Math.Max(1, width >> 1);
@@ -422,7 +422,7 @@ namespace Ryujinx.Graphics.Vulkan
DependencyCount = 1,
};
gd.Api.CreateRenderPass2(device, renderPassCreateInfo, null, out var renderPass).ThrowOnError();
gd.Api.CreateRenderPass2(device, in renderPassCreateInfo, null, out var renderPass).ThrowOnError();
using var rp = new Auto<DisposableRenderPass>(new DisposableRenderPass(gd.Api, device, renderPass));
@@ -445,7 +445,7 @@ namespace Ryujinx.Graphics.Vulkan
Layers = (uint)src.Layers,
};
gd.Api.CreateFramebuffer(device, framebufferCreateInfo, null, out var framebuffer).ThrowOnError();
gd.Api.CreateFramebuffer(device, in framebufferCreateInfo, null, out var framebuffer).ThrowOnError();
using var fb = new Auto<DisposableFramebuffer>(new DisposableFramebuffer(gd.Api, device, framebuffer), null, srcView, dstView);
var renderArea = new Rect2D(null, new Extent2D((uint)src.Info.Width, (uint)src.Info.Height));
@@ -465,7 +465,7 @@ namespace Ryujinx.Graphics.Vulkan
// to resolve the depth-stencil texture.
// TODO: Do speculative resolve and part of the same render pass as the draw to avoid
// ending the current render pass?
gd.Api.CmdBeginRenderPass(cbs.CommandBuffer, renderPassBeginInfo, SubpassContents.Inline);
gd.Api.CmdBeginRenderPass(cbs.CommandBuffer, in renderPassBeginInfo, SubpassContents.Inline);
gd.Api.CmdEndRenderPass(cbs.CommandBuffer);
}
}

View File

@@ -4,6 +4,7 @@ using Silk.NET.Vulkan;
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using Format = Ryujinx.Graphics.GAL.Format;
using VkBuffer = Silk.NET.Vulkan.Buffer;
using VkFormat = Silk.NET.Vulkan.Format;
@@ -12,6 +13,11 @@ namespace Ryujinx.Graphics.Vulkan
{
class TextureStorage : IDisposable
{
private struct TextureSliceInfo
{
public int BindCount;
}
private const MemoryPropertyFlags DefaultImageMemoryFlags =
MemoryPropertyFlags.DeviceLocalBit;
@@ -43,6 +49,7 @@ namespace Ryujinx.Graphics.Vulkan
private readonly Image _image;
private readonly Auto<DisposableImage> _imageAuto;
private readonly Auto<MemoryAllocation> _allocationAuto;
private readonly int _depthOrLayers;
private Auto<MemoryAllocation> _foreignAllocationAuto;
private Dictionary<Format, TextureStorage> _aliasedStorages;
@@ -55,6 +62,9 @@ namespace Ryujinx.Graphics.Vulkan
private int _viewsCount;
private readonly ulong _size;
private int _bindCount;
private readonly TextureSliceInfo[] _slices;
public VkFormat VkFormat { get; }
public unsafe TextureStorage(
@@ -73,6 +83,7 @@ namespace Ryujinx.Graphics.Vulkan
var depth = (uint)(info.Target == Target.Texture3D ? info.Depth : 1);
VkFormat = format;
_depthOrLayers = info.GetDepthOrLayers();
var type = info.Target.Convert();
@@ -80,7 +91,7 @@ namespace Ryujinx.Graphics.Vulkan
var sampleCountFlags = ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)info.Samples);
var usage = GetImageUsage(info.Format, info.Target, gd.Capabilities.SupportsShaderStorageImageMultisample);
var usage = GetImageUsage(info.Format, info.Target, gd.Capabilities);
var flags = ImageCreateFlags.CreateMutableFormatBit | ImageCreateFlags.CreateExtendedUsageBit;
@@ -114,7 +125,7 @@ namespace Ryujinx.Graphics.Vulkan
Flags = flags,
};
gd.Api.CreateImage(device, imageCreateInfo, null, out _image).ThrowOnError();
gd.Api.CreateImage(device, in imageCreateInfo, null, out _image).ThrowOnError();
if (foreignAllocation == null)
{
@@ -148,6 +159,8 @@ namespace Ryujinx.Graphics.Vulkan
InitialTransition(ImageLayout.Preinitialized, ImageLayout.General);
}
_slices = new TextureSliceInfo[levels * _depthOrLayers];
}
public TextureStorage CreateAliasedColorForDepthStorageUnsafe(Format format)
@@ -284,7 +297,7 @@ namespace Ryujinx.Graphics.Vulkan
0,
null,
1,
barrier);
in barrier);
if (useTempCbs)
{
@@ -292,7 +305,7 @@ namespace Ryujinx.Graphics.Vulkan
}
}
public static ImageUsageFlags GetImageUsage(Format format, Target target, bool supportsMsStorage)
public static ImageUsageFlags GetImageUsage(Format format, Target target, in HardwareCapabilities capabilities)
{
var usage = DefaultUsageFlags;
@@ -305,11 +318,19 @@ namespace Ryujinx.Graphics.Vulkan
usage |= ImageUsageFlags.ColorAttachmentBit;
}
bool supportsMsStorage = capabilities.SupportsShaderStorageImageMultisample;
if (format.IsImageCompatible() && (supportsMsStorage || !target.IsMultisample()))
{
usage |= ImageUsageFlags.StorageBit;
}
if (capabilities.SupportsAttachmentFeedbackLoop &&
(usage & (ImageUsageFlags.DepthStencilAttachmentBit | ImageUsageFlags.ColorAttachmentBit)) != 0)
{
usage |= ImageUsageFlags.AttachmentFeedbackLoopBitExt;
}
return usage;
}
@@ -401,11 +422,11 @@ namespace Ryujinx.Graphics.Vulkan
if (to)
{
_gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, region);
_gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, in region);
}
else
{
_gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, region);
_gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, in region);
}
offset += mipSize;
@@ -510,6 +531,55 @@ namespace Ryujinx.Graphics.Vulkan
}
}
public void AddBinding(TextureView view)
{
// Assumes a view only has a first level.
int index = view.FirstLevel * _depthOrLayers + view.FirstLayer;
int layers = view.Layers;
for (int i = 0; i < layers; i++)
{
ref TextureSliceInfo info = ref _slices[index++];
info.BindCount++;
}
_bindCount++;
}
public void ClearBindings()
{
if (_bindCount != 0)
{
Array.Clear(_slices, 0, _slices.Length);
_bindCount = 0;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsBound(TextureView view)
{
if (_bindCount != 0)
{
int index = view.FirstLevel * _depthOrLayers + view.FirstLayer;
int layers = view.Layers;
for (int i = 0; i < layers; i++)
{
ref TextureSliceInfo info = ref _slices[index++];
if (info.BindCount != 0)
{
return true;
}
}
}
return false;
}
public void IncrementViewsCount()
{
_viewsCount++;

View File

@@ -23,6 +23,8 @@ namespace Ryujinx.Graphics.Vulkan
private readonly Auto<DisposableImageView> _imageView2dArray;
private Dictionary<Format, TextureView> _selfManagedViews;
private int _hazardUses;
private readonly TextureCreateInfo _info;
private HashTableSlim<RenderPassCacheKey, RenderPassHolder> _renderPasses;
@@ -60,7 +62,7 @@ namespace Ryujinx.Graphics.Vulkan
gd.Textures.Add(this);
var format = _gd.FormatCapabilities.ConvertToVkFormat(info.Format);
var usage = TextureStorage.GetImageUsage(info.Format, info.Target, gd.Capabilities.SupportsShaderStorageImageMultisample);
var usage = TextureStorage.GetImageUsage(info.Format, info.Target, gd.Capabilities);
var levels = (uint)info.Levels;
var layers = (uint)info.GetLayers();
@@ -117,7 +119,7 @@ namespace Ryujinx.Graphics.Vulkan
PNext = &imageViewUsage,
};
gd.Api.CreateImageView(device, imageCreateInfo, null, out var imageView).ThrowOnError();
gd.Api.CreateImageView(device, in imageCreateInfo, null, out var imageView).ThrowOnError();
return new Auto<DisposableImageView>(new DisposableImageView(gd.Api, device, imageView), null, storage.GetImage());
}
@@ -492,7 +494,7 @@ namespace Ryujinx.Graphics.Vulkan
dstStageMask,
DependencyFlags.None,
1,
memoryBarrier,
in memoryBarrier,
0,
null,
0,
@@ -557,7 +559,7 @@ namespace Ryujinx.Graphics.Vulkan
0,
null,
1,
memoryBarrier);
in memoryBarrier);
}
public TextureView GetView(Format format)
@@ -949,11 +951,11 @@ namespace Ryujinx.Graphics.Vulkan
if (to)
{
_gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, region);
_gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, in region);
}
else
{
_gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, region);
_gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, in region);
}
offset += mipSize;
@@ -1010,11 +1012,11 @@ namespace Ryujinx.Graphics.Vulkan
if (to)
{
_gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, region);
_gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, in region);
}
else
{
_gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, region);
_gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, in region);
}
}
@@ -1034,6 +1036,34 @@ namespace Ryujinx.Graphics.Vulkan
throw new NotImplementedException();
}
public void PrepareForUsage(CommandBufferScoped cbs, PipelineStageFlags flags, List<TextureView> feedbackLoopHazards)
{
Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, flags);
if (feedbackLoopHazards != null && Storage.IsBound(this))
{
feedbackLoopHazards.Add(this);
_hazardUses++;
}
}
public void ClearUsage(List<TextureView> feedbackLoopHazards)
{
if (_hazardUses != 0 && feedbackLoopHazards != null)
{
feedbackLoopHazards.Remove(this);
_hazardUses--;
}
}
public void DecrementHazardUses()
{
if (_hazardUses != 0)
{
_hazardUses--;
}
}
public (RenderPassHolder rpHolder, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
VulkanRenderer gd,
Device device,

View File

@@ -90,11 +90,9 @@ namespace Ryujinx.Graphics.Vulkan
DriverId.SamsungProprietary => "Samsung",
DriverId.MesaVenus => "Venus",
DriverId.MesaDozen => "Dozen",
// TODO: Use real enum when we have an up to date Silk.NET.
(DriverId)24 => "NVK",
(DriverId)25 => "Imagination (Open)",
(DriverId)26 => "Honeykrisp",
DriverId.MesaNvk => "NVK",
DriverId.ImaginationOpenSourceMesa => "Imagination (Open)",
DriverId.MesaAgxv => "Honeykrisp",
_ => id.ToString(),
};
}

View File

@@ -44,6 +44,8 @@ namespace Ryujinx.Graphics.Vulkan
"VK_EXT_4444_formats",
"VK_KHR_8bit_storage",
"VK_KHR_maintenance2",
"VK_EXT_attachment_feedback_loop_layout",
"VK_EXT_attachment_feedback_loop_dynamic_state",
};
private static readonly string[] _requiredExtensions = {
@@ -357,6 +359,28 @@ namespace Ryujinx.Graphics.Vulkan
features2.PNext = &supportedFeaturesDepthClipControl;
}
PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT supportedFeaturesAttachmentFeedbackLoopLayout = new()
{
SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesExt,
PNext = features2.PNext,
};
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_layout"))
{
features2.PNext = &supportedFeaturesAttachmentFeedbackLoopLayout;
}
PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT supportedFeaturesDynamicAttachmentFeedbackLoopLayout = new()
{
SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesExt,
PNext = features2.PNext,
};
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_dynamic_state"))
{
features2.PNext = &supportedFeaturesDynamicAttachmentFeedbackLoopLayout;
}
PhysicalDeviceVulkan12Features supportedPhysicalDeviceVulkan12Features = new()
{
SType = StructureType.PhysicalDeviceVulkan12Features,
@@ -531,6 +555,36 @@ namespace Ryujinx.Graphics.Vulkan
pExtendedFeatures = &featuresDepthClipControl;
}
PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT featuresAttachmentFeedbackLoopLayout;
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_layout") &&
supportedFeaturesAttachmentFeedbackLoopLayout.AttachmentFeedbackLoopLayout)
{
featuresAttachmentFeedbackLoopLayout = new()
{
SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesExt,
PNext = pExtendedFeatures,
AttachmentFeedbackLoopLayout = true,
};
pExtendedFeatures = &featuresAttachmentFeedbackLoopLayout;
}
PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT featuresDynamicAttachmentFeedbackLoopLayout;
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_dynamic_state") &&
supportedFeaturesDynamicAttachmentFeedbackLoopLayout.AttachmentFeedbackLoopDynamicState)
{
featuresDynamicAttachmentFeedbackLoopLayout = new()
{
SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesExt,
PNext = pExtendedFeatures,
AttachmentFeedbackLoopDynamicState = true,
};
pExtendedFeatures = &featuresDynamicAttachmentFeedbackLoopLayout;
}
var enabledExtensions = _requiredExtensions.Union(_desirableExtensions.Intersect(physicalDevice.DeviceExtensions)).ToArray();
IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length];

View File

@@ -38,6 +38,7 @@ namespace Ryujinx.Graphics.Vulkan
internal KhrPushDescriptor PushDescriptorApi { get; private set; }
internal ExtTransformFeedback TransformFeedbackApi { get; private set; }
internal KhrDrawIndirectCount DrawIndirectCountApi { get; private set; }
internal ExtAttachmentFeedbackLoopDynamicState DynamicFeedbackLoopApi { get; private set; }
internal uint QueueFamilyIndex { get; private set; }
internal Queue Queue { get; private set; }
@@ -149,6 +150,11 @@ namespace Ryujinx.Graphics.Vulkan
DrawIndirectCountApi = drawIndirectCountApi;
}
if (Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtAttachmentFeedbackLoopDynamicState dynamicFeedbackLoopApi))
{
DynamicFeedbackLoopApi = dynamicFeedbackLoopApi;
}
if (maxQueueCount >= 2)
{
Api.GetDeviceQueue(_device, queueFamilyIndex, 1, out var backgroundQueue);
@@ -243,6 +249,16 @@ namespace Ryujinx.Graphics.Vulkan
SType = StructureType.PhysicalDeviceDepthClipControlFeaturesExt,
};
PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT featuresAttachmentFeedbackLoop = new()
{
SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesExt,
};
PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT featuresDynamicAttachmentFeedbackLoop = new()
{
SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesExt,
};
PhysicalDevicePortabilitySubsetFeaturesKHR featuresPortabilitySubset = new()
{
SType = StructureType.PhysicalDevicePortabilitySubsetFeaturesKhr,
@@ -279,6 +295,22 @@ namespace Ryujinx.Graphics.Vulkan
features2.PNext = &featuresDepthClipControl;
}
bool supportsAttachmentFeedbackLoop = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_layout");
if (supportsAttachmentFeedbackLoop)
{
featuresAttachmentFeedbackLoop.PNext = features2.PNext;
features2.PNext = &featuresAttachmentFeedbackLoop;
}
bool supportsDynamicAttachmentFeedbackLoop = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_dynamic_state");
if (supportsDynamicAttachmentFeedbackLoop)
{
featuresDynamicAttachmentFeedbackLoop.PNext = features2.PNext;
features2.PNext = &featuresDynamicAttachmentFeedbackLoop;
}
bool usePortability = _physicalDevice.IsDeviceExtensionPresent("VK_KHR_portability_subset");
if (usePortability)
@@ -401,6 +433,8 @@ namespace Ryujinx.Graphics.Vulkan
_physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"),
_physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName),
supportsDepthClipControl && featuresDepthClipControl.DepthClipControl,
supportsAttachmentFeedbackLoop && featuresAttachmentFeedbackLoop.AttachmentFeedbackLoopLayout,
supportsDynamicAttachmentFeedbackLoop && featuresDynamicAttachmentFeedbackLoop.AttachmentFeedbackLoopDynamicState,
propertiesSubgroup.SubgroupSize,
supportedSampleCounts,
portabilityFlags,

View File

@@ -160,7 +160,7 @@ namespace Ryujinx.Graphics.Vulkan
SwizzleComponent.Blue,
SwizzleComponent.Alpha);
_gd.SwapchainApi.CreateSwapchain(_device, swapchainCreateInfo, null, out _swapchain).ThrowOnError();
_gd.SwapchainApi.CreateSwapchain(_device, in swapchainCreateInfo, null, out _swapchain).ThrowOnError();
_gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, null);
@@ -187,14 +187,14 @@ namespace Ryujinx.Graphics.Vulkan
for (int i = 0; i < _imageAvailableSemaphores.Length; i++)
{
_gd.Api.CreateSemaphore(_device, semaphoreCreateInfo, null, out _imageAvailableSemaphores[i]).ThrowOnError();
_gd.Api.CreateSemaphore(_device, in semaphoreCreateInfo, null, out _imageAvailableSemaphores[i]).ThrowOnError();
}
_renderFinishedSemaphores = new Semaphore[imageCount];
for (int i = 0; i < _renderFinishedSemaphores.Length; i++)
{
_gd.Api.CreateSemaphore(_device, semaphoreCreateInfo, null, out _renderFinishedSemaphores[i]).ThrowOnError();
_gd.Api.CreateSemaphore(_device, in semaphoreCreateInfo, null, out _renderFinishedSemaphores[i]).ThrowOnError();
}
}
@@ -220,7 +220,7 @@ namespace Ryujinx.Graphics.Vulkan
SubresourceRange = subresourceRange,
};
_gd.Api.CreateImageView(_device, imageCreateInfo, null, out var imageView).ThrowOnError();
_gd.Api.CreateImageView(_device, in imageCreateInfo, null, out var imageView).ThrowOnError();
return new TextureView(_gd, _device, new DisposableImageView(_gd.Api, _device, imageView), info, format);
}
@@ -479,7 +479,7 @@ namespace Ryujinx.Graphics.Vulkan
lock (_gd.QueueLock)
{
_gd.SwapchainApi.QueuePresent(_gd.Queue, presentInfo);
_gd.SwapchainApi.QueuePresent(_gd.Queue, in presentInfo);
}
}
@@ -611,7 +611,7 @@ namespace Ryujinx.Graphics.Vulkan
0,
null,
1,
barrier);
in barrier);
}
private void CaptureFrame(TextureView texture, int x, int y, int width, int height, bool isBgra, bool flipX, bool flipY)

View File

@@ -4,6 +4,8 @@ using Ryujinx.Common.Configuration;
using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging;
using Ryujinx.Common.SystemInterop;
using Ryujinx.Common.Utilities;
using Ryujinx.Graphics.Vulkan.MoltenVK;
using Ryujinx.Modules;
using Ryujinx.SDL2.Common;
using Ryujinx.UI;
@@ -40,9 +42,6 @@ namespace Ryujinx
[LibraryImport("user32.dll", SetLastError = true)]
public static partial int MessageBoxA(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type);
[LibraryImport("libc", SetLastError = true)]
private static partial int setenv([MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string value, int overwrite);
private const uint MbIconWarning = 0x30;
static Program()
@@ -104,12 +103,13 @@ namespace Ryujinx
throw new NotSupportedException("Failed to initialize multi-threading support.");
}
Environment.SetEnvironmentVariable("GDK_BACKEND", "x11");
setenv("GDK_BACKEND", "x11", 1);
OsUtils.SetEnvironmentVariableNoCaching("GDK_BACKEND", "x11");
}
if (OperatingSystem.IsMacOS())
{
MVKInitialization.InitializeResolver();
string baseDirectory = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);
string resourcesDataDir;
@@ -122,19 +122,13 @@ namespace Ryujinx
resourcesDataDir = baseDirectory;
}
static void SetEnvironmentVariableNoCaching(string key, string value)
{
int res = setenv(key, value, 1);
Debug.Assert(res != -1);
}
// On macOS, GTK3 needs XDG_DATA_DIRS to be set, otherwise it will try searching for "gschemas.compiled" in system directories.
SetEnvironmentVariableNoCaching("XDG_DATA_DIRS", Path.Combine(resourcesDataDir, "share"));
OsUtils.SetEnvironmentVariableNoCaching("XDG_DATA_DIRS", Path.Combine(resourcesDataDir, "share"));
// On macOS, GTK3 needs GDK_PIXBUF_MODULE_FILE to be set, otherwise it will try searching for "loaders.cache" in system directories.
SetEnvironmentVariableNoCaching("GDK_PIXBUF_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gdk-pixbuf-2.0", "2.10.0", "loaders.cache"));
OsUtils.SetEnvironmentVariableNoCaching("GDK_PIXBUF_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gdk-pixbuf-2.0", "2.10.0", "loaders.cache"));
SetEnvironmentVariableNoCaching("GTK_IM_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gtk-3.0", "3.0.0", "immodules.cache"));
OsUtils.SetEnvironmentVariableNoCaching("GTK_IM_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gtk-3.0", "3.0.0", "immodules.cache"));
}
string systemPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine);
@@ -230,9 +224,9 @@ namespace Ryujinx
// Logging system information.
PrintSystemInfo();
// Enable OGL multithreading on the driver, when available.
// Enable OGL multithreading on the driver, and some other flags.
BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading;
DriverUtilities.ToggleOGLThreading(threadingMode == BackendThreading.Off);
DriverUtilities.InitDriverConfig(threadingMode == BackendThreading.Off);
// Initialize Gtk.
Application.Init();

View File

@@ -7,6 +7,7 @@ using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Logging.Targets;
using Ryujinx.Common.SystemInterop;
@@ -18,6 +19,7 @@ using Ryujinx.Graphics.Gpu;
using Ryujinx.Graphics.Gpu.Shader;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.Graphics.Vulkan;
using Ryujinx.Graphics.Vulkan.MoltenVK;
using Ryujinx.Headless.SDL2.OpenGL;
using Ryujinx.Headless.SDL2.Vulkan;
using Ryujinx.HLE;
@@ -88,6 +90,11 @@ namespace Ryujinx.Headless.SDL2
};
}
if (OperatingSystem.IsMacOS())
{
MVKInitialization.InitializeResolver();
}
Parser.Default.ParseArguments<Options>(args)
.WithParsed(Load)
.WithNotParsed(errors => errors.Output());
@@ -457,6 +464,8 @@ namespace Ryujinx.Headless.SDL2
GraphicsConfig.ShadersDumpPath = option.GraphicsShadersDumpPath;
GraphicsConfig.EnableMacroHLE = !option.DisableMacroHLE;
DriverUtilities.InitDriverConfig(option.BackendThreading == BackendThreading.Off);
while (true)
{
LoadApplication(option);

View File

@@ -7,6 +7,7 @@ using Ryujinx.Common.Configuration;
using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging;
using Ryujinx.Common.SystemInterop;
using Ryujinx.Graphics.Vulkan.MoltenVK;
using Ryujinx.Modules;
using Ryujinx.SDL2.Common;
using Ryujinx.UI.Common;
@@ -80,6 +81,11 @@ namespace Ryujinx.Ava
// Parse arguments
CommandLineState.ParseArguments(args);
if (OperatingSystem.IsMacOS())
{
MVKInitialization.InitializeResolver();
}
// Delete backup files after updating.
Task.Run(Updater.CleanupUpdate);
@@ -111,8 +117,8 @@ namespace Ryujinx.Ava
// Logging system information.
PrintSystemInfo();
// Enable OGL multithreading on the driver, when available.
DriverUtilities.ToggleOGLThreading(ConfigurationState.Instance.Graphics.BackendThreading == BackendThreading.Off);
// Enable OGL multithreading on the driver, and some other flags.
DriverUtilities.InitDriverConfig(ConfigurationState.Instance.Graphics.BackendThreading == BackendThreading.Off);
// Check if keys exists.
if (!File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")))

View File

@@ -164,7 +164,7 @@ return view.extend({
o.value('', '---');
o.value('223.5.5.5', _('Aliyun Public DNS (223.5.5.5)'));
o.value('119.29.29.29', _('Tencent Public DNS (119.29.29.29)'));
o.value('114.114.114.114', _('Xinfeng Public DNS (114.114.114.114)'));
o.value('117.50.10.10', _('ThreatBook Public DNS (117.50.10.10)'));
o.default = '8.8.8.8';
o.rmempty = false;
o.depends({'routing_mode': 'custom', '!reverse': true});
@@ -187,7 +187,7 @@ return view.extend({
o.value('223.5.5.5', _('Aliyun Public DNS (223.5.5.5)'));
o.value('210.2.4.8', _('CNNIC Public DNS (210.2.4.8)'));
o.value('119.29.29.29', _('Tencent Public DNS (119.29.29.29)'));
o.value('114.114.114.114', _('Xinfeng Public DNS (114.114.114.114)'));
o.value('117.50.10.10', _('ThreatBook Public DNS (117.50.10.10)'));
o.depends('routing_mode', 'bypass_mainland_china');
o.validate = function(section_id) {
if (section_id) {

View File

@@ -1152,6 +1152,15 @@ return view.extend({
var routing_mode = uci.get(data[0], 'config', 'routing_mode');
var features = data[1];
/* Cache subscription information, it will be called multiple times */
var subinfo = [];
for (var suburl of (uci.get(data[0], 'subscription', 'subscription_url') || [])) {
const url = new URL(suburl);
const urlhash = hp.calcStringMD5(suburl.replace(/#.*$/, ''));
const title = url.hash ? decodeURIComponent(url.hash.slice(1)) : url.hostname;
subinfo.push({ 'hash': urlhash, 'title': title });
}
m = new form.Map('homeproxy', _('Edit nodes'));
s = m.section(form.NamedSection, 'subscription', 'homeproxy');
@@ -1163,7 +1172,11 @@ return view.extend({
ss = renderNodeSettings(o.subsection, data, features, main_node, routing_mode);
ss.addremove = true;
ss.filter = function(section_id) {
return uci.get(data[0], section_id, 'grouphash') ? false : true;
for (var info of subinfo)
if (info.hash === uci.get(data[0], section_id, 'grouphash'))
return false;
return true;
}
/* Import subscription links start */
/* Thanks to luci-app-shadowsocks-libev */
@@ -1226,7 +1239,7 @@ return view.extend({
])
])
}
ss.renderSectionAdd = function(extra_class) {
ss.renderSectionAdd = function(/* ... */) {
var el = form.GridSection.prototype.renderSectionAdd.apply(this, arguments),
nameEl = el.querySelector('.cbi-section-create-name');
@@ -1258,16 +1271,12 @@ return view.extend({
/* User nodes end */
/* Subscription nodes start */
for (var suburl of (uci.get(data[0], 'subscription', 'subscription_url') || [])) {
const url = new URL(suburl);
const urlhash = hp.calcStringMD5(suburl.replace(/#.*$/, ''));
const title = url.hash ? decodeURIComponent(url.hash.slice(1)) : url.hostname;
s.tab('sub_' + urlhash, _('Sub (%s)').format(title));
o = s.taboption('sub_' + urlhash, form.SectionValue, '_sub_' + urlhash, form.GridSection, 'node');
for (const info of subinfo) {
s.tab('sub_' + info.hash, _('Sub (%s)').format(info.title));
o = s.taboption('sub_' + info.hash, form.SectionValue, '_sub_' + info.hash, form.GridSection, 'node');
ss = renderNodeSettings(o.subsection, data, features, main_node, routing_mode);
ss.filter = function(section_id) {
return (uci.get(data[0], section_id, 'grouphash') === urlhash);
return (uci.get(data[0], section_id, 'grouphash') === info.hash);
}
}
/* Subscription nodes end */

View File

@@ -5,7 +5,7 @@ msgstr "Content-Type: text/plain; charset=UTF-8"
msgid "%s log"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1399
#: htdocs/luci-static/resources/view/homeproxy/node.js:1408
msgid "%s nodes removed"
msgstr ""
@@ -99,7 +99,7 @@ msgid "All ports"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:982
#: htdocs/luci-static/resources/view/homeproxy/node.js:1325
#: htdocs/luci-static/resources/view/homeproxy/node.js:1334
msgid "Allow insecure"
msgstr ""
@@ -107,7 +107,7 @@ msgstr ""
msgid "Allow insecure connection at TLS client."
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1326
#: htdocs/luci-static/resources/view/homeproxy/node.js:1335
msgid "Allow insecure connection by default when add nodes from subscriptions."
msgstr ""
@@ -137,7 +137,7 @@ msgstr ""
msgid "Alternative TLS port"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1362
#: htdocs/luci-static/resources/view/homeproxy/node.js:1371
msgid "An error occurred during updating subscriptions: %s"
msgstr ""
@@ -193,11 +193,11 @@ msgstr ""
msgid "Auto configure firewall"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1279
#: htdocs/luci-static/resources/view/homeproxy/node.js:1288
msgid "Auto update"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1280
#: htdocs/luci-static/resources/view/homeproxy/node.js:1289
msgid "Auto update subscriptions."
msgstr ""
@@ -232,7 +232,7 @@ msgid ""
"Bind outbound traffic to specific interface. Leave empty to auto detect."
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1315
#: htdocs/luci-static/resources/view/homeproxy/node.js:1324
msgid "Blacklist mode"
msgstr ""
@@ -279,7 +279,7 @@ msgstr ""
msgid "CUBIC"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1179
#: htdocs/luci-static/resources/view/homeproxy/node.js:1192
msgid "Cancel"
msgstr ""
@@ -426,7 +426,7 @@ msgstr ""
msgid "Default outbound"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1333
#: htdocs/luci-static/resources/view/homeproxy/node.js:1342
msgid "Default packet encoding"
msgstr ""
@@ -470,7 +470,7 @@ msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:492
#: htdocs/luci-static/resources/view/homeproxy/node.js:504
#: htdocs/luci-static/resources/view/homeproxy/node.js:1061
#: htdocs/luci-static/resources/view/homeproxy/node.js:1314
#: htdocs/luci-static/resources/view/homeproxy/node.js:1323
#: htdocs/luci-static/resources/view/homeproxy/server.js:246
#: htdocs/luci-static/resources/view/homeproxy/server.js:258
msgid "Disable"
@@ -555,14 +555,14 @@ msgstr ""
msgid "Download bandwidth in Mbps."
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1321
#: htdocs/luci-static/resources/view/homeproxy/node.js:1330
msgid ""
"Drop/keep nodes that contain the specific keywords. <a target=\"_blank\" "
"href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/"
"Regular_Expressions\">Regex</a> is supported."
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1313
#: htdocs/luci-static/resources/view/homeproxy/node.js:1322
msgid "Drop/keep specific nodes from subscriptions."
msgstr ""
@@ -604,7 +604,7 @@ msgstr ""
msgid "Early data is sent in path instead of header by default."
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1155
#: htdocs/luci-static/resources/view/homeproxy/node.js:1164
msgid "Edit nodes"
msgstr ""
@@ -705,9 +705,9 @@ msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/client.js:1200
#: htdocs/luci-static/resources/view/homeproxy/node.js:452
#: htdocs/luci-static/resources/view/homeproxy/node.js:1082
#: htdocs/luci-static/resources/view/homeproxy/node.js:1242
#: htdocs/luci-static/resources/view/homeproxy/node.js:1302
#: htdocs/luci-static/resources/view/homeproxy/node.js:1305
#: htdocs/luci-static/resources/view/homeproxy/node.js:1255
#: htdocs/luci-static/resources/view/homeproxy/node.js:1311
#: htdocs/luci-static/resources/view/homeproxy/node.js:1314
#: htdocs/luci-static/resources/view/homeproxy/server.js:211
#: htdocs/luci-static/resources/view/homeproxy/server.js:602
#: htdocs/luci-static/resources/view/homeproxy/server.js:604
@@ -730,11 +730,11 @@ msgstr ""
msgid "Failed to upload %s, error: %s."
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1320
#: htdocs/luci-static/resources/view/homeproxy/node.js:1329
msgid "Filter keywords"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1312
#: htdocs/luci-static/resources/view/homeproxy/node.js:1321
msgid "Filter nodes"
msgstr ""
@@ -975,13 +975,13 @@ msgstr ""
msgid "Ignore client bandwidth"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1225
#: htdocs/luci-static/resources/view/homeproxy/node.js:1238
msgid "Import"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1172
#: htdocs/luci-static/resources/view/homeproxy/node.js:1251
#: htdocs/luci-static/resources/view/homeproxy/node.js:1253
#: htdocs/luci-static/resources/view/homeproxy/node.js:1185
#: htdocs/luci-static/resources/view/homeproxy/node.js:1264
#: htdocs/luci-static/resources/view/homeproxy/node.js:1266
msgid "Import share links"
msgstr ""
@@ -1317,7 +1317,7 @@ msgstr ""
msgid "NOT RUNNING"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1339
#: htdocs/luci-static/resources/view/homeproxy/node.js:1348
msgid "NOTE: Save current settings before updating subscriptions."
msgstr ""
@@ -1351,15 +1351,15 @@ msgstr ""
msgid "No additional encryption support: It's basically duplicate encryption."
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1355
#: htdocs/luci-static/resources/view/homeproxy/node.js:1364
msgid "No subscription available"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1380
#: htdocs/luci-static/resources/view/homeproxy/node.js:1389
msgid "No subscription node"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1211
#: htdocs/luci-static/resources/view/homeproxy/node.js:1224
msgid "No valid share link found."
msgstr ""
@@ -1372,7 +1372,7 @@ msgstr ""
msgid "Node Settings"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1161
#: htdocs/luci-static/resources/view/homeproxy/node.js:1170
msgid "Nodes"
msgstr ""
@@ -1700,11 +1700,11 @@ msgstr ""
msgid "Remote"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1377
#: htdocs/luci-static/resources/view/homeproxy/node.js:1386
msgid "Remove %s nodes"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1367
#: htdocs/luci-static/resources/view/homeproxy/node.js:1376
msgid "Remove all nodes from subscriptions"
msgstr ""
@@ -1803,11 +1803,11 @@ msgstr ""
msgid "Same as main node"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1341
#: htdocs/luci-static/resources/view/homeproxy/node.js:1350
msgid "Save current settings"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1338
#: htdocs/luci-static/resources/view/homeproxy/node.js:1347
msgid "Save subscriptions settings"
msgstr ""
@@ -1941,19 +1941,19 @@ msgstr ""
msgid "String"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1266
#: htdocs/luci-static/resources/view/homeproxy/node.js:1275
msgid "Sub (%s)"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1295
#: htdocs/luci-static/resources/view/homeproxy/node.js:1304
msgid "Subscription URL-s"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1277
#: htdocs/luci-static/resources/view/homeproxy/node.js:1286
msgid "Subscriptions"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1213
#: htdocs/luci-static/resources/view/homeproxy/node.js:1226
msgid "Successfully imported %s nodes of total %s."
msgstr ""
@@ -1961,8 +1961,8 @@ msgstr ""
msgid "Successfully updated."
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1173
#: htdocs/luci-static/resources/view/homeproxy/node.js:1296
#: htdocs/luci-static/resources/view/homeproxy/node.js:1186
#: htdocs/luci-static/resources/view/homeproxy/node.js:1305
msgid ""
"Support Hysteria, Shadowsocks, Trojan, v2rayN (VMess), and XTLS (VLESS) "
"online configuration delivery standard."
@@ -2185,7 +2185,7 @@ msgid ""
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:985
#: htdocs/luci-static/resources/view/homeproxy/node.js:1328
#: htdocs/luci-static/resources/view/homeproxy/node.js:1337
msgid ""
"This is <strong>DANGEROUS</strong>, your traffic is almost like "
"<strong>PLAIN TEXT</strong>! Use at your own risk!"
@@ -2197,6 +2197,11 @@ msgid ""
"QUIC stream based UDP relay mode that TUIC does not provide."
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/client.js:167
#: htdocs/luci-static/resources/view/homeproxy/client.js:190
msgid "ThreatBook Public DNS (117.50.10.10)"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/client.js:662
msgid ""
"Timeout of rejected DNS response cache. <code>7d</code> is used by default."
@@ -2288,7 +2293,7 @@ msgstr ""
msgid "Unsupported fingerprint!"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1352
#: htdocs/luci-static/resources/view/homeproxy/node.js:1361
msgid "Update %s subscriptions"
msgstr ""
@@ -2304,19 +2309,19 @@ msgstr ""
msgid "Update interval of rule set.<br/><code>1d</code> will be used if empty."
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1347
#: htdocs/luci-static/resources/view/homeproxy/node.js:1356
msgid "Update nodes from subscriptions"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1291
#: htdocs/luci-static/resources/view/homeproxy/node.js:1300
msgid "Update subscriptions via proxy."
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1284
#: htdocs/luci-static/resources/view/homeproxy/node.js:1293
msgid "Update time"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1290
#: htdocs/luci-static/resources/view/homeproxy/node.js:1299
msgid "Update via proxy"
msgstr ""
@@ -2390,7 +2395,7 @@ msgstr ""
msgid "WebSocket"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:1316
#: htdocs/luci-static/resources/view/homeproxy/node.js:1325
msgid "Whitelist mode"
msgstr ""
@@ -2414,13 +2419,8 @@ msgstr ""
msgid "Write proxy protocol in the connection header."
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/client.js:167
#: htdocs/luci-static/resources/view/homeproxy/client.js:190
msgid "Xinfeng Public DNS (114.114.114.114)"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:828
#: htdocs/luci-static/resources/view/homeproxy/node.js:1336
#: htdocs/luci-static/resources/view/homeproxy/node.js:1345
msgid "Xudp (Xray-core)"
msgstr ""
@@ -2482,12 +2482,12 @@ msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:567
#: htdocs/luci-static/resources/view/homeproxy/node.js:826
#: htdocs/luci-static/resources/view/homeproxy/node.js:1334
#: htdocs/luci-static/resources/view/homeproxy/node.js:1343
msgid "none"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/node.js:827
#: htdocs/luci-static/resources/view/homeproxy/node.js:1335
#: htdocs/luci-static/resources/view/homeproxy/node.js:1344
msgid "packet addr (v2ray-core v5+)"
msgstr ""
@@ -2522,7 +2522,7 @@ msgid "unchecked"
msgstr ""
#: htdocs/luci-static/resources/homeproxy.js:221
#: htdocs/luci-static/resources/view/homeproxy/node.js:1242
#: htdocs/luci-static/resources/view/homeproxy/node.js:1255
msgid "unique UCI identifier"
msgstr ""
@@ -2552,8 +2552,8 @@ msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/client.js:1029
#: htdocs/luci-static/resources/view/homeproxy/client.js:1032
#: htdocs/luci-static/resources/view/homeproxy/node.js:1302
#: htdocs/luci-static/resources/view/homeproxy/node.js:1305
#: htdocs/luci-static/resources/view/homeproxy/node.js:1311
#: htdocs/luci-static/resources/view/homeproxy/node.js:1314
msgid "valid URL"
msgstr ""

View File

@@ -12,7 +12,7 @@ msgstr ""
msgid "%s log"
msgstr "%s 日志"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1399
#: htdocs/luci-static/resources/view/homeproxy/node.js:1408
msgid "%s nodes removed"
msgstr "移除了 %s 个节点"
@@ -106,7 +106,7 @@ msgid "All ports"
msgstr "所有端口"
#: htdocs/luci-static/resources/view/homeproxy/node.js:982
#: htdocs/luci-static/resources/view/homeproxy/node.js:1325
#: htdocs/luci-static/resources/view/homeproxy/node.js:1334
msgid "Allow insecure"
msgstr "允许不安全连接"
@@ -114,7 +114,7 @@ msgstr "允许不安全连接"
msgid "Allow insecure connection at TLS client."
msgstr "允许 TLS 客户端侧的不安全连接。"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1326
#: htdocs/luci-static/resources/view/homeproxy/node.js:1335
msgid "Allow insecure connection by default when add nodes from subscriptions."
msgstr "从订阅获取节点时,默认允许不安全连接。"
@@ -144,7 +144,7 @@ msgstr "替代 HTTP 端口"
msgid "Alternative TLS port"
msgstr "替代 HTTPS 端口"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1362
#: htdocs/luci-static/resources/view/homeproxy/node.js:1371
msgid "An error occurred during updating subscriptions: %s"
msgstr "更新订阅时发生错误:%s"
@@ -202,11 +202,11 @@ msgstr "认证类型"
msgid "Auto configure firewall"
msgstr "自动配置防火墙"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1279
#: htdocs/luci-static/resources/view/homeproxy/node.js:1288
msgid "Auto update"
msgstr "自动更新"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1280
#: htdocs/luci-static/resources/view/homeproxy/node.js:1289
msgid "Auto update subscriptions."
msgstr "自动更新订阅。"
@@ -241,7 +241,7 @@ msgid ""
"Bind outbound traffic to specific interface. Leave empty to auto detect."
msgstr "绑定出站流量至指定端口。留空自动检测。"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1315
#: htdocs/luci-static/resources/view/homeproxy/node.js:1324
msgid "Blacklist mode"
msgstr "黑名单模式"
@@ -288,7 +288,7 @@ msgstr "CNNIC 公共 DNS210.2.4.8"
msgid "CUBIC"
msgstr "CUBIC"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1179
#: htdocs/luci-static/resources/view/homeproxy/node.js:1192
msgid "Cancel"
msgstr "取消"
@@ -435,7 +435,7 @@ msgstr "默认域名解析策略。"
msgid "Default outbound"
msgstr "默认出站"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1333
#: htdocs/luci-static/resources/view/homeproxy/node.js:1342
msgid "Default packet encoding"
msgstr "默认包封装格式"
@@ -479,7 +479,7 @@ msgstr "直连 MAC 地址"
#: htdocs/luci-static/resources/view/homeproxy/node.js:492
#: htdocs/luci-static/resources/view/homeproxy/node.js:504
#: htdocs/luci-static/resources/view/homeproxy/node.js:1061
#: htdocs/luci-static/resources/view/homeproxy/node.js:1314
#: htdocs/luci-static/resources/view/homeproxy/node.js:1323
#: htdocs/luci-static/resources/view/homeproxy/server.js:246
#: htdocs/luci-static/resources/view/homeproxy/server.js:258
msgid "Disable"
@@ -566,7 +566,7 @@ msgstr "下载带宽"
msgid "Download bandwidth in Mbps."
msgstr "下载带宽单位Mbps。"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1321
#: htdocs/luci-static/resources/view/homeproxy/node.js:1330
msgid ""
"Drop/keep nodes that contain the specific keywords. <a target=\"_blank\" "
"href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/"
@@ -576,7 +576,7 @@ msgstr ""
"developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions\">"
"正则表达式</a>。"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1313
#: htdocs/luci-static/resources/view/homeproxy/node.js:1322
msgid "Drop/keep specific nodes from subscriptions."
msgstr "从订阅中 丢弃/保留 指定节点"
@@ -623,7 +623,7 @@ msgstr "前置数据标头"
msgid "Early data is sent in path instead of header by default."
msgstr "前置数据默认发送在路径而不是标头中。"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1155
#: htdocs/luci-static/resources/view/homeproxy/node.js:1164
msgid "Edit nodes"
msgstr "修改节点"
@@ -726,9 +726,9 @@ msgstr "加密方式"
#: htdocs/luci-static/resources/view/homeproxy/client.js:1200
#: htdocs/luci-static/resources/view/homeproxy/node.js:452
#: htdocs/luci-static/resources/view/homeproxy/node.js:1082
#: htdocs/luci-static/resources/view/homeproxy/node.js:1242
#: htdocs/luci-static/resources/view/homeproxy/node.js:1302
#: htdocs/luci-static/resources/view/homeproxy/node.js:1305
#: htdocs/luci-static/resources/view/homeproxy/node.js:1255
#: htdocs/luci-static/resources/view/homeproxy/node.js:1311
#: htdocs/luci-static/resources/view/homeproxy/node.js:1314
#: htdocs/luci-static/resources/view/homeproxy/server.js:211
#: htdocs/luci-static/resources/view/homeproxy/server.js:602
#: htdocs/luci-static/resources/view/homeproxy/server.js:604
@@ -751,11 +751,11 @@ msgstr "外部账户密钥标识符"
msgid "Failed to upload %s, error: %s."
msgstr "上传 %s 失败,错误:%s。"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1320
#: htdocs/luci-static/resources/view/homeproxy/node.js:1329
msgid "Filter keywords"
msgstr "过滤关键词"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1312
#: htdocs/luci-static/resources/view/homeproxy/node.js:1321
msgid "Filter nodes"
msgstr "过滤节点"
@@ -999,13 +999,13 @@ msgstr "如果你拥有根证书,使用此选项而不是允许不安全连接
msgid "Ignore client bandwidth"
msgstr "忽略客户端带宽"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1225
#: htdocs/luci-static/resources/view/homeproxy/node.js:1238
msgid "Import"
msgstr "导入"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1172
#: htdocs/luci-static/resources/view/homeproxy/node.js:1251
#: htdocs/luci-static/resources/view/homeproxy/node.js:1253
#: htdocs/luci-static/resources/view/homeproxy/node.js:1185
#: htdocs/luci-static/resources/view/homeproxy/node.js:1264
#: htdocs/luci-static/resources/view/homeproxy/node.js:1266
msgid "Import share links"
msgstr "导入分享链接"
@@ -1345,7 +1345,7 @@ msgstr "多路复用协议。"
msgid "NOT RUNNING"
msgstr "未运行"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1339
#: htdocs/luci-static/resources/view/homeproxy/node.js:1348
msgid "NOTE: Save current settings before updating subscriptions."
msgstr "注意:更新订阅前先保存当前配置。"
@@ -1379,15 +1379,15 @@ msgstr "无 TCP 传输层, 纯 HTTP 已合并到 HTTP 传输层。"
msgid "No additional encryption support: It's basically duplicate encryption."
msgstr "无额外加密支持:它基本上是重复加密。"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1355
#: htdocs/luci-static/resources/view/homeproxy/node.js:1364
msgid "No subscription available"
msgstr "无可用订阅"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1380
#: htdocs/luci-static/resources/view/homeproxy/node.js:1389
msgid "No subscription node"
msgstr "无订阅节点"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1211
#: htdocs/luci-static/resources/view/homeproxy/node.js:1224
msgid "No valid share link found."
msgstr "找不到有效分享链接。"
@@ -1400,7 +1400,7 @@ msgstr "节点"
msgid "Node Settings"
msgstr "节点设置"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1161
#: htdocs/luci-static/resources/view/homeproxy/node.js:1170
msgid "Nodes"
msgstr "节点"
@@ -1728,11 +1728,11 @@ msgstr "区域 ID"
msgid "Remote"
msgstr "远程"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1377
#: htdocs/luci-static/resources/view/homeproxy/node.js:1386
msgid "Remove %s nodes"
msgstr "移除 %s 个节点"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1367
#: htdocs/luci-static/resources/view/homeproxy/node.js:1376
msgid "Remove all nodes from subscriptions"
msgstr "移除所有订阅节点"
@@ -1831,11 +1831,11 @@ msgstr "Salamander"
msgid "Same as main node"
msgstr "保持与主节点一致"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1341
#: htdocs/luci-static/resources/view/homeproxy/node.js:1350
msgid "Save current settings"
msgstr "保存当前设置"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1338
#: htdocs/luci-static/resources/view/homeproxy/node.js:1347
msgid "Save subscriptions settings"
msgstr "保存订阅设置"
@@ -1980,19 +1980,19 @@ msgstr ""
msgid "String"
msgstr "字符串"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1266
#: htdocs/luci-static/resources/view/homeproxy/node.js:1275
msgid "Sub (%s)"
msgstr "订阅(%s"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1295
#: htdocs/luci-static/resources/view/homeproxy/node.js:1304
msgid "Subscription URL-s"
msgstr "订阅地址"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1277
#: htdocs/luci-static/resources/view/homeproxy/node.js:1286
msgid "Subscriptions"
msgstr "订阅"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1213
#: htdocs/luci-static/resources/view/homeproxy/node.js:1226
msgid "Successfully imported %s nodes of total %s."
msgstr "成功导入 %s 个节点,共 %s 个。"
@@ -2000,8 +2000,8 @@ msgstr "成功导入 %s 个节点,共 %s 个。"
msgid "Successfully updated."
msgstr "更新成功。"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1173
#: htdocs/luci-static/resources/view/homeproxy/node.js:1296
#: htdocs/luci-static/resources/view/homeproxy/node.js:1186
#: htdocs/luci-static/resources/view/homeproxy/node.js:1305
msgid ""
"Support Hysteria, Shadowsocks, Trojan, v2rayN (VMess), and XTLS (VLESS) "
"online configuration delivery standard."
@@ -2244,7 +2244,7 @@ msgstr ""
"检测到任何活动,则会关闭连接。"
#: htdocs/luci-static/resources/view/homeproxy/node.js:985
#: htdocs/luci-static/resources/view/homeproxy/node.js:1328
#: htdocs/luci-static/resources/view/homeproxy/node.js:1337
msgid ""
"This is <strong>DANGEROUS</strong>, your traffic is almost like "
"<strong>PLAIN TEXT</strong>! Use at your own risk!"
@@ -2259,6 +2259,11 @@ msgstr ""
"这是 TUIC 的 UDP over TCP 协议移植, 旨在提供 TUIC 不提供的基于 QUIC 流的 "
"UDP 中继模式。"
#: htdocs/luci-static/resources/view/homeproxy/client.js:167
#: htdocs/luci-static/resources/view/homeproxy/client.js:190
msgid "ThreatBook Public DNS (117.50.10.10)"
msgstr "微步在线公共 DNS117.50.10.10"
#: htdocs/luci-static/resources/view/homeproxy/client.js:662
msgid ""
"Timeout of rejected DNS response cache. <code>7d</code> is used by default."
@@ -2352,7 +2357,7 @@ msgstr "未知错误:%s"
msgid "Unsupported fingerprint!"
msgstr "不支持的指纹!"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1352
#: htdocs/luci-static/resources/view/homeproxy/node.js:1361
msgid "Update %s subscriptions"
msgstr "更新 %s 个订阅"
@@ -2368,19 +2373,19 @@ msgstr "更新间隔"
msgid "Update interval of rule set.<br/><code>1d</code> will be used if empty."
msgstr "规则集更新间隔。<br/>留空使用 <code>1d</code>。"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1347
#: htdocs/luci-static/resources/view/homeproxy/node.js:1356
msgid "Update nodes from subscriptions"
msgstr "从订阅更新节点"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1291
#: htdocs/luci-static/resources/view/homeproxy/node.js:1300
msgid "Update subscriptions via proxy."
msgstr "使用代理更新订阅。"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1284
#: htdocs/luci-static/resources/view/homeproxy/node.js:1293
msgid "Update time"
msgstr "更新时间"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1290
#: htdocs/luci-static/resources/view/homeproxy/node.js:1299
msgid "Update via proxy"
msgstr "使用代理更新"
@@ -2454,7 +2459,7 @@ msgstr "WAN IP 策略"
msgid "WebSocket"
msgstr "WebSocket"
#: htdocs/luci-static/resources/view/homeproxy/node.js:1316
#: htdocs/luci-static/resources/view/homeproxy/node.js:1325
msgid "Whitelist mode"
msgstr "白名单模式"
@@ -2478,13 +2483,8 @@ msgstr "WireGuard 要求 base64 编码的私钥。"
msgid "Write proxy protocol in the connection header."
msgstr "在连接头中写入代理协议。"
#: htdocs/luci-static/resources/view/homeproxy/client.js:167
#: htdocs/luci-static/resources/view/homeproxy/client.js:190
msgid "Xinfeng Public DNS (114.114.114.114)"
msgstr "信风公共 DNS114.114.114.114"
#: htdocs/luci-static/resources/view/homeproxy/node.js:828
#: htdocs/luci-static/resources/view/homeproxy/node.js:1336
#: htdocs/luci-static/resources/view/homeproxy/node.js:1345
msgid "Xudp (Xray-core)"
msgstr "Xudp (Xray-core)"
@@ -2546,12 +2546,12 @@ msgstr "非空值"
#: htdocs/luci-static/resources/view/homeproxy/node.js:567
#: htdocs/luci-static/resources/view/homeproxy/node.js:826
#: htdocs/luci-static/resources/view/homeproxy/node.js:1334
#: htdocs/luci-static/resources/view/homeproxy/node.js:1343
msgid "none"
msgstr "无"
#: htdocs/luci-static/resources/view/homeproxy/node.js:827
#: htdocs/luci-static/resources/view/homeproxy/node.js:1335
#: htdocs/luci-static/resources/view/homeproxy/node.js:1344
msgid "packet addr (v2ray-core v5+)"
msgstr "packet addr (v2ray-core v5+)"
@@ -2587,7 +2587,7 @@ msgid "unchecked"
msgstr "未检查"
#: htdocs/luci-static/resources/homeproxy.js:221
#: htdocs/luci-static/resources/view/homeproxy/node.js:1242
#: htdocs/luci-static/resources/view/homeproxy/node.js:1255
msgid "unique UCI identifier"
msgstr "独立 UCI 标识"
@@ -2617,8 +2617,8 @@ msgstr "有效 IP 地址"
#: htdocs/luci-static/resources/view/homeproxy/client.js:1029
#: htdocs/luci-static/resources/view/homeproxy/client.js:1032
#: htdocs/luci-static/resources/view/homeproxy/node.js:1302
#: htdocs/luci-static/resources/view/homeproxy/node.js:1305
#: htdocs/luci-static/resources/view/homeproxy/node.js:1311
#: htdocs/luci-static/resources/view/homeproxy/node.js:1314
msgid "valid URL"
msgstr "有效网址"

View File

@@ -88,6 +88,9 @@ const control_info = {};
for (let i in control_options)
control_info[i] = uci.get(cfgname, 'control', i);
const dns_hijacked = uci.get('dhcp', '@dnsmasq[0]', 'dns_redirect') || '0',
dns_port = uci.get('dhcp', '@dnsmasq[0]', 'port') || '53';
/* UCI config end */
-%}
@@ -228,6 +231,16 @@ set homeproxy_routing_port {
}
{% endif %}
{# DNS hijack & TCP redirect #}
chain dstnat {
{% if (dns_hijacked !== '1'): %}
meta nfproto { ipv4, ipv6 } udp dport 53 counter redirect to :{{ dns_port }} comment "!{{ cfgname }}: DNS hijack"
{% endif /* dns_hijacked */ %}
{% if (match(proxy_mode, /redirect/)): %}
meta nfproto { {{ (ipv6_support === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto tcp jump homeproxy_redirect_lanac
{% endif /* proxy_mode */ %}
}
{# TCP redirect #}
{% if (match(proxy_mode, /redirect/)): %}
chain homeproxy_redirect_proxy {
@@ -338,10 +351,6 @@ chain homeproxy_output_redir {
type nat hook output priority filter -105; policy accept
meta nfproto { {{ (ipv6_support === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto tcp jump homeproxy_redirect
}
chain dstnat {
meta nfproto { {{ (ipv6_support === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto tcp jump homeproxy_redirect_lanac
}
{% endif %}
{# UDP tproxy #}
@@ -371,6 +380,7 @@ chain homeproxy_mangle_lanac {
{% if (control_info.listen_interfaces): %}
meta iifname != {{ array_to_nftarr(split(join(' ', control_info.listen_interfaces) + ' lo', ' ')) }} counter return
{% endif %}
meta iifname != io udp dport 53 counter return
meta mark {{ self_mark }} counter return
{% if (control_info.lan_proxy_mode === 'listed_only'): %}
@@ -513,6 +523,7 @@ chain mangle_output {
{% if (match(proxy_mode, /tun/)): %}
chain homeproxy_mangle_lanac {
iifname {{ tun_name }} counter return
udp dport 53 counter return
{% if (control_info.listen_interfaces): %}
meta iifname != {{ array_to_nftarr(control_info.listen_interfaces) }} counter return

View File

@@ -30,13 +30,13 @@ define Download/geosite
HASH:=8fba86084a952fd635f5a4728c14f7e5eafd225132013163072272859dbbd8cb
endef
GEOSITE_IRAN_VER:=202408260030
GEOSITE_IRAN_VER:=202409020032
GEOSITE_IRAN_FILE:=iran.dat.$(GEOSITE_IRAN_VER)
define Download/geosite-ir
URL:=https://github.com/bootmortis/iran-hosted-domains/releases/download/$(GEOSITE_IRAN_VER)/
URL_FILE:=iran.dat
FILE:=$(GEOSITE_IRAN_FILE)
HASH:=d95bd88c33b41514400ced2ec117834dd325c24a46c04e82f8c04ef040648f14
HASH:=ec146949ced882d20eb1dd2ada61e00d67bef758bd3772c312e952ccd3693d74
endef
define Package/v2ray-geodata/template

View File

@@ -1097,7 +1097,7 @@ namespace ServiceLib.Handler.CoreConfig
address = Utils.IsNullOrEmpty(dNSItem?.domainDNSAddress) ? Global.DomainDNSAddress.FirstOrDefault() : dNSItem?.domainDNSAddress,
domains = [node.address]
};
servers.AsArray().Insert(0, JsonUtils.SerializeToNode(dnsServer));
servers.AsArray().Add(JsonUtils.SerializeToNode(dnsServer));
}
}
return 0;

View File

@@ -66,14 +66,12 @@
<TextBlock
Margin="8,0"
VerticalAlignment="Center"
IsVisible="False"
Text="{x:Static resx:ResUI.TbAutoScrollToEnd}" />
<ToggleSwitch
x:Name="togScrollToEnd"
Margin="8,0"
HorizontalAlignment="Left"
IsChecked="True"
IsVisible="False" />
IsChecked="True" />
</WrapPanel>
<TextBox
Name="txtMsg"

View File

@@ -11,10 +11,11 @@ namespace v2rayN.Desktop.Views
public partial class MsgView : UserControl
{
private static Config? _config;
private ConcurrentQueue<string> _queueMsg = new();
private int _numMaxMsg = 500;
private string lastMsgFilter = string.Empty;
private bool lastMsgFilterNotAvailable;
private ConcurrentBag<string> _lstMsg = [];
public MsgView()
{
@@ -74,30 +75,30 @@ namespace v2rayN.Desktop.Views
if (togScrollToEnd.IsChecked ?? true)
{
txtMsg.CaretIndex = int.MaxValue;
}
}
private void ShowMsg(string msg)
{
if (_lstMsg.Count > 999)
if (_queueMsg.Count > _numMaxMsg)
{
ClearMsg();
for (int k = 0; k < _queueMsg.Count - _numMaxMsg; k++)
{
_queueMsg.TryDequeue(out _);
}
}
_queueMsg.Enqueue(msg);
if (!msg.EndsWith(Environment.NewLine))
{
_lstMsg.Add(Environment.NewLine);
_queueMsg.Enqueue(Environment.NewLine);
}
_lstMsg.Add(msg);
// if (!msg.EndsWith(Environment.NewLine))
// {
// _lstMsg.Add(Environment.NewLine);
// }
this.txtMsg.Text = string.Join("", _lstMsg);
txtMsg.Text = string.Join("", _queueMsg.ToArray());
}
public void ClearMsg()
{
_lstMsg.Clear();
_queueMsg.Clear();
txtMsg.Clear();
}

View File

@@ -1,4 +1,5 @@
using ReactiveUI;
using System.Collections.Concurrent;
using System.Reactive.Linq;
using System.Text.RegularExpressions;
using System.Windows.Threading;
@@ -8,6 +9,8 @@ namespace v2rayN.Views
public partial class MsgView
{
private static Config? _config;
private ConcurrentQueue<string> _queueMsg = new();
private int _numMaxMsg = 500;
private string lastMsgFilter = string.Empty;
private bool lastMsgFilterNotAvailable;
@@ -80,19 +83,24 @@ namespace v2rayN.Views
private void ShowMsg(string msg)
{
if (txtMsg.LineCount > 999)
if (_queueMsg.Count > _numMaxMsg)
{
ClearMsg();
for (int k = 0; k < _queueMsg.Count - _numMaxMsg; k++)
{
_queueMsg.TryDequeue(out _);
}
this.txtMsg.AppendText(msg);
}
_queueMsg.Enqueue(msg);
if (!msg.EndsWith(Environment.NewLine))
{
this.txtMsg.AppendText(Environment.NewLine);
_queueMsg.Enqueue(Environment.NewLine);
}
txtMsg.Text = string.Join("", _queueMsg.ToArray());
}
public void ClearMsg()
{
_queueMsg.Clear();
txtMsg.Clear();
}

Some files were not shown because too many files have changed in this diff Show More