aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--COPYING2
-rw-r--r--Makefile84
-rw-r--r--README.md92
-rw-r--r--build.bat45
-rw-r--r--conf/admin_windows.go36
-rw-r--r--conf/config.go170
-rw-r--r--conf/dnsresolver_windows.go71
-rw-r--r--conf/dpapi/dpapi_windows.go73
-rw-r--r--conf/dpapi/dpapi_windows_test.go8
-rw-r--r--conf/dpapi/mksyscall.go8
-rw-r--r--conf/dpapi/zdpapi_windows.go68
-rw-r--r--conf/filewriter_windows.go90
-rw-r--r--conf/migration_windows.go108
-rw-r--r--conf/mksyscall.go8
-rw-r--r--conf/name.go33
-rw-r--r--conf/parser.go318
-rw-r--r--conf/parser_test.go17
-rw-r--r--conf/path_windows.go106
-rw-r--r--conf/store.go112
-rw-r--r--conf/store_test.go10
-rw-r--r--conf/storewatcher.go2
-rw-r--r--conf/storewatcher_windows.go26
-rw-r--r--conf/writer.go104
-rw-r--r--conf/zsyscall_windows.go83
-rw-r--r--docs/adminregistry.md39
-rw-r--r--docs/attacksurface.md (renamed from attacksurface.md)23
-rw-r--r--docs/buildrun.md98
-rw-r--r--docs/enterprise.md113
-rw-r--r--docs/netquirk.md33
-rw-r--r--driver/configuration_windows.go183
-rw-r--r--driver/dll_fromfile_windows.go56
-rw-r--r--driver/dll_fromrsrc_windows.go62
-rw-r--r--driver/dll_windows.go91
-rw-r--r--driver/driver_windows.go170
-rw-r--r--driver/memmod/memmod_windows.go698
-rw-r--r--driver/memmod/memmod_windows_32.go16
-rw-r--r--driver/memmod/memmod_windows_386.go8
-rw-r--r--driver/memmod/memmod_windows_64.go36
-rw-r--r--driver/memmod/memmod_windows_amd64.go8
-rw-r--r--driver/memmod/memmod_windows_arm.go8
-rw-r--r--driver/memmod/memmod_windows_arm64.go8
-rw-r--r--driver/memmod/syscall_windows.go392
-rw-r--r--driver/memmod/syscall_windows_32.go96
-rw-r--r--driver/memmod/syscall_windows_64.go95
-rw-r--r--driver/wintunremoval_windows.go50
-rw-r--r--elevate/doas.go41
-rw-r--r--elevate/loader.go8
-rw-r--r--elevate/membership.go6
-rw-r--r--elevate/privileges.go4
-rw-r--r--elevate/shellexecute.go26
-rw-r--r--elevate/syscall_windows.go89
-rw-r--r--elevate/zsyscall_windows.go102
-rw-r--r--embeddable-dll-service/.gitignore4
-rw-r--r--embeddable-dll-service/README.md47
-rw-r--r--embeddable-dll-service/build.bat16
-rw-r--r--embeddable-dll-service/csharp/.gitignore3
-rw-r--r--embeddable-dll-service/csharp/DemoUI/MainWindow.Designer.cs87
-rw-r--r--embeddable-dll-service/csharp/DemoUI/MainWindow.cs215
-rw-r--r--embeddable-dll-service/csharp/DemoUI/MainWindow.resx60
-rw-r--r--embeddable-dll-service/csharp/DemoUI/Program.cs44
-rw-r--r--embeddable-dll-service/csharp/DemoUI/app.manifest48
-rw-r--r--embeddable-dll-service/csharp/Program.cs92
-rw-r--r--embeddable-dll-service/csharp/README.md7
-rw-r--r--embeddable-dll-service/csharp/TunnelDll/Driver.cs234
-rw-r--r--embeddable-dll-service/csharp/TunnelDll/Keypair.cs (renamed from embeddable-dll-service/csharp/Keypair.cs)4
-rw-r--r--embeddable-dll-service/csharp/TunnelDll/Ringlogger.cs (renamed from embeddable-dll-service/csharp/Ringlogger.cs)2
-rw-r--r--embeddable-dll-service/csharp/TunnelDll/Service.cs (renamed from embeddable-dll-service/csharp/Service.cs)36
-rw-r--r--embeddable-dll-service/csharp/TunnelDll/Win32.cs (renamed from embeddable-dll-service/csharp/Win32.cs)54
-rw-r--r--embeddable-dll-service/csharp/demo-client.csproj14
-rw-r--r--embeddable-dll-service/csharp/demo-client.csproj.user8
-rw-r--r--embeddable-dll-service/csharp/demo-client.sln25
-rw-r--r--embeddable-dll-service/main.go15
-rw-r--r--go.mod27
-rw-r--r--go.mod.master5
-rw-r--r--go.sum43
-rw-r--r--gotext.go75
-rw-r--r--installer/.gitignore2
-rw-r--r--installer/build.bat26
-rw-r--r--installer/customactions.c357
-rw-r--r--installer/fetcher/.gitignore6
-rw-r--r--installer/fetcher/Makefile42
-rw-r--r--installer/fetcher/constants.h17
-rw-r--r--installer/fetcher/crypto.c2252
-rw-r--r--installer/fetcher/crypto.h29
-rw-r--r--installer/fetcher/fetcher.c340
-rw-r--r--installer/fetcher/filelist.c168
-rw-r--r--installer/fetcher/filelist.h17
-rw-r--r--installer/fetcher/icon.svg92
-rw-r--r--installer/fetcher/manifest.xml34
-rw-r--r--installer/fetcher/resources.rc41
-rw-r--r--installer/fetcher/systeminfo.c74
-rw-r--r--installer/fetcher/systeminfo.h16
-rw-r--r--installer/fetcher/version.h12
-rw-r--r--installer/wireguard.wxs83
-rw-r--r--l18n/l18n.go78
-rw-r--r--locales/ca/messages.gotext.json751
-rw-r--r--locales/cs/messages.gotext.json1907
-rw-r--r--locales/de/messages.gotext.json1847
-rw-r--r--locales/en/messages.gotext.json1955
-rw-r--r--locales/es-ES/messages.gotext.json1077
-rw-r--r--locales/fa/messages.gotext.json841
-rw-r--r--locales/fi/messages.gotext.json445
-rw-r--r--locales/fr/messages.gotext.json1847
-rw-r--r--locales/id/messages.gotext.json820
-rw-r--r--locales/it/messages.gotext.json1847
-rw-r--r--locales/ja/messages.gotext.json1817
-rw-r--r--locales/ko/messages.gotext.json11
-rw-r--r--locales/pa-IN/messages.gotext.json1467
-rw-r--r--locales/pl/messages.gotext.json1907
-rw-r--r--locales/ro/messages.gotext.json1877
-rw-r--r--locales/ru/messages.gotext.json1907
-rw-r--r--locales/si-LK/messages.gotext.json715
-rw-r--r--locales/sk/messages.gotext.json1827
-rw-r--r--locales/sl/messages.gotext.json1907
-rw-r--r--locales/tr/messages.gotext.json1847
-rw-r--r--locales/uk/messages.gotext.json933
-rw-r--r--locales/vi/messages.gotext.json334
-rw-r--r--locales/zh-CN/messages.gotext.json1817
-rw-r--r--locales/zh-TW/messages.gotext.json1805
-rw-r--r--main.go164
-rw-r--r--manager/install.go59
-rw-r--r--manager/interfacecleanup.go58
-rw-r--r--manager/ipc_client.go17
-rw-r--r--manager/ipc_driver.go61
-rw-r--r--manager/ipc_pipe.go55
-rw-r--r--manager/ipc_server.go134
-rw-r--r--manager/service.go157
-rw-r--r--manager/tunneltracker.go357
-rw-r--r--manager/uiprocess.go103
-rw-r--r--manager/updatestate.go36
-rw-r--r--quickinstall.bat4
-rw-r--r--resources.rc110
-rw-r--r--ringlogger/cli_test.go2
-rw-r--r--ringlogger/dump.go62
-rw-r--r--ringlogger/global.go51
-rw-r--r--ringlogger/ringlogger.go61
-rw-r--r--services/boot.go47
-rw-r--r--services/errors.go30
-rw-r--r--services/names.go26
-rw-r--r--tunnel/addressconfig.go186
-rw-r--r--tunnel/deterministicguid.go16
-rw-r--r--tunnel/firewall/blocker.go30
-rw-r--r--tunnel/firewall/helpers.go23
-rw-r--r--tunnel/firewall/mksyscall.go2
-rw-r--r--tunnel/firewall/rules.go17
-rw-r--r--tunnel/firewall/syscall_windows.go2
-rw-r--r--tunnel/firewall/types_windows.go8
-rw-r--r--tunnel/firewall/types_windows_32.go (renamed from tunnel/firewall/types_windows_386.go)4
-rw-r--r--tunnel/firewall/types_windows_64.go (renamed from tunnel/firewall/types_windows_amd64.go)4
-rw-r--r--tunnel/firewall/types_windows_test.go29
-rw-r--r--tunnel/firewall/zsyscall_windows.go107
-rw-r--r--tunnel/interfacewatcher.go119
-rw-r--r--tunnel/ipcpermissions.go63
-rw-r--r--tunnel/mtumonitor.go (renamed from tunnel/defaultroutemonitor.go)69
-rw-r--r--tunnel/pitfalls.go177
-rw-r--r--tunnel/scriptrunner.go77
-rw-r--r--tunnel/service.go182
-rw-r--r--tunnel/winipcfg/interface_change_handler.go2
-rw-r--r--tunnel/winipcfg/luid.go221
-rw-r--r--tunnel/winipcfg/mksyscall.go2
-rw-r--r--tunnel/winipcfg/netsh.go85
-rw-r--r--tunnel/winipcfg/route_change_handler.go2
-rw-r--r--tunnel/winipcfg/types.go202
-rw-r--r--tunnel/winipcfg/types_32.go (renamed from tunnel/winipcfg/types_386.go)4
-rw-r--r--tunnel/winipcfg/types_64.go (renamed from tunnel/winipcfg/types_amd64.go)4
-rw-r--r--tunnel/winipcfg/types_test.go3
-rw-r--r--tunnel/winipcfg/types_test_32.go (renamed from tunnel/winipcfg/types_test_386.go)4
-rw-r--r--tunnel/winipcfg/types_test_64.go (renamed from tunnel/winipcfg/types_test_amd64.go)4
-rw-r--r--tunnel/winipcfg/unicast_address_change_handler.go2
-rw-r--r--tunnel/winipcfg/winipcfg.go30
-rw-r--r--tunnel/winipcfg/winipcfg_test.go247
-rw-r--r--tunnel/winipcfg/zwinipcfg_windows.go227
-rw-r--r--tunnel/wintun_test.go202
-rw-r--r--ui/aboutdialog.go25
-rw-r--r--ui/confview.go116
-rw-r--r--ui/editdialog.go97
-rw-r--r--ui/filesave.go11
-rw-r--r--ui/icon/dot.svg (renamed from ui/icon/dot-gray.svg)0
-rw-r--r--ui/iconprovider.go29
-rw-r--r--ui/listview.go25
-rw-r--r--ui/logpage.go25
-rw-r--r--ui/managewindow.go13
-rw-r--r--ui/raise.go9
-rw-r--r--ui/syntax/highlighter.c620
-rw-r--r--ui/syntax/highlighter.go631
-rw-r--r--ui/syntax/highlighter.h39
-rw-r--r--ui/syntax/syntaxedit.c411
-rw-r--r--ui/syntax/syntaxedit.go434
-rw-r--r--ui/syntax/syntaxedit.h31
-rw-r--r--ui/tray.go212
-rw-r--r--ui/tunnelspage.go128
-rw-r--r--ui/ui.go20
-rw-r--r--ui/updatepage.go35
-rw-r--r--updater/authenticode.go34
-rw-r--r--updater/constants.go9
-rw-r--r--updater/downloader.go119
-rw-r--r--updater/msirunner.go (renamed from updater/msirunner_windows.go)51
-rw-r--r--updater/msirunner_linux.go23
-rw-r--r--updater/signify.go10
-rw-r--r--updater/updater_test.go4
-rw-r--r--updater/versions.go15
-rw-r--r--updater/winhttp/mksyscall.go (renamed from elevate/mksyscall.go)4
-rw-r--r--updater/winhttp/syscall_windows.go355
-rw-r--r--updater/winhttp/winhttp.go230
-rw-r--r--updater/winhttp/winhttp_test.go71
-rw-r--r--updater/winhttp/zsyscall_windows.go155
-rw-r--r--version/certificate_test.go42
-rw-r--r--version/debugging_linux.go35
-rw-r--r--version/official.go156
-rw-r--r--version/official_windows.go105
-rw-r--r--version/os.go (renamed from version/os_windows.go)2
-rw-r--r--version/useragent.go18
-rw-r--r--version/version.go10
-rw-r--r--version/version.h2
-rw-r--r--version/wintrust/certificate_windows.go59
-rw-r--r--version/wintrust/mksyscall.go8
-rw-r--r--version/wintrust/wintrust_windows.go116
-rw-r--r--version/wintrust/zsyscall_windows.go69
-rw-r--r--zgotext.go3850
220 files changed, 49925 insertions, 5084 deletions
diff --git a/.gitignore b/.gitignore
index ad8d0739..152ec5d1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,11 +1,15 @@
# Dependencies
/.deps
+/.distfiles
# Build Output
/x86
/amd64
+/arm
+/arm64
# Misc
+/locales/*/out.gotext.json
/sign.bat
*.swp
*.bak
diff --git a/COPYING b/COPYING
index dd8dd389..916eef78 100644
--- a/COPYING
+++ b/COPYING
@@ -1,4 +1,4 @@
-Copyright (C) 2018-2019 WireGuard LLC
+Copyright (C) 2018-2022 WireGuard LLC. All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
diff --git a/Makefile b/Makefile
index 1fee2326..02127e1f 100644
--- a/Makefile
+++ b/Makefile
@@ -1,56 +1,100 @@
-GOFLAGS := -ldflags="-H windowsgui -s -w" -v -tags walk_use_cgo -trimpath
-export CGO_ENABLED := 1
-export CGO_CFLAGS := -O3 -Wall -Wno-unused-function -Wno-switch -std=gnu11 -DWINVER=0x0601
-export CGO_LDFLAGS := -Wl,--dynamicbase -Wl,--nxcompat -Wl,--export-all-symbols
+GOFLAGS := -tags load_wgnt_from_rsrc -ldflags="-H windowsgui -s -w" -trimpath -buildvcs=false -v
export GOOS := windows
+export PATH := $(CURDIR)/.deps/go/bin:$(PATH)
-rwildcard=$(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst *,%,$2),$d))
-SOURCE_FILES := $(call rwildcard,,*.go *.c *.h) go.mod go.sum
-RESOURCE_FILES := resources.rc version/version.h manifest.xml $(patsubst %.svg,%.ico,$(wildcard ui/icon/*.svg))
+VERSION := $(shell sed -n 's/^\s*Number\s*=\s*"\([0-9.]\+\)"$$/\1/p' version/version.go)
+empty :=
+space := $(empty) $(empty)
+comma := ,
+RCFLAGS := -DWIREGUARD_VERSION_ARRAY=$(subst $(space),$(comma),$(wordlist 1,4,$(subst .,$(space),$(VERSION)) 0 0 0 0)) -DWIREGUARD_VERSION_STR=$(VERSION) -O coff -c 65001
+
+rwildcard=$(foreach d,$(filter-out .deps,$(wildcard $1*)),$(call rwildcard,$d/,$2) $(filter $(subst *,%,$2),$d))
+SOURCE_FILES := $(call rwildcard,,*.go) .deps/go/prepared go.mod go.sum
+RESOURCE_FILES := resources.rc version/version.go manifest.xml $(patsubst %.svg,%.ico,$(wildcard ui/icon/*.svg)) .deps/wireguard-nt/prepared
DEPLOYMENT_HOST ?= winvm
DEPLOYMENT_PATH ?= Desktop
-all: amd64/wireguard.exe x86/wireguard.exe
+all: amd64/wireguard.exe x86/wireguard.exe arm64/wireguard.exe
+
+define download =
+.distfiles/$(1):
+ mkdir -p .distfiles
+ if ! curl -L#o $$@.unverified $(2); then rm -f $$@.unverified; exit 1; fi
+ if ! echo "$(3) $$@.unverified" | sha256sum -c; then rm -f $$@.unverified; exit 1; fi
+ if ! mv $$@.unverified $$@; then rm -f $$@.unverified; exit 1; fi
+endef
+
+$(eval $(call download,go.tar.gz,https://go.dev/dl/go1.18.linux-amd64.tar.gz,e85278e98f57cdb150fe8409e6e5df5343ecb13cebf03a5d5ff12bd55a80264f))
+$(eval $(call download,wireguard-nt.zip,https://download.wireguard.com/wireguard-nt/wireguard-nt-0.10.1.zip,772c0b1463d8d2212716f43f06f4594d880dea4f735165bd68e388fc41b81605))
+
+.deps/go/prepared: .distfiles/go.tar.gz
+ mkdir -p .deps
+ rm -rf .deps/go
+ bsdtar -C .deps -xf .distfiles/go.tar.gz
+ chmod -R +w .deps/go
+ touch $@
+
+.deps/wireguard-nt/prepared: .distfiles/wireguard-nt.zip
+ mkdir -p .deps
+ rm -rf .deps/wireguard-nt
+ bsdtar -C .deps -xf .distfiles/wireguard-nt.zip
+ touch $@
%.ico: %.svg
- convert -background none $< -define icon:auto-resize="256,192,128,96,64,48,32,24,16" $@
+ convert -background none $< -define icon:auto-resize="256,192,128,96,64,48,40,32,24,20,16" -compress zip $@
resources_amd64.syso: $(RESOURCE_FILES)
- x86_64-w64-mingw32-windres -i $< -o $@ -O coff
+ x86_64-w64-mingw32-windres $(RCFLAGS) -I .deps/wireguard-nt/bin/amd64 -i $< -o $@
resources_386.syso: $(RESOURCE_FILES)
- i686-w64-mingw32-windres -i $< -o $@ -O coff
+ i686-w64-mingw32-windres $(RCFLAGS) -I .deps/wireguard-nt/bin/x86 -i $< -o $@
+
+resources_arm64.syso: $(RESOURCE_FILES)
+ aarch64-w64-mingw32-windres $(RCFLAGS) -I .deps/wireguard-nt/bin/arm64 -i $< -o $@
-amd64/wireguard.exe: export CC := x86_64-w64-mingw32-gcc
amd64/wireguard.exe: export GOARCH := amd64
-amd64/wireguard.exe: CGO_LDFLAGS += -Wl,--high-entropy-va
amd64/wireguard.exe: resources_amd64.syso $(SOURCE_FILES)
go build $(GOFLAGS) -o $@
-x86/wireguard.exe: export CC := i686-w64-mingw32-gcc
x86/wireguard.exe: export GOARCH := 386
x86/wireguard.exe: resources_386.syso $(SOURCE_FILES)
go build $(GOFLAGS) -o $@
-remaster: export CC := x86_64-w64-mingw32-gcc
+arm64/wireguard.exe: export GOARCH := arm64
+arm64/wireguard.exe: resources_arm64.syso $(SOURCE_FILES)
+ go build $(GOFLAGS) -o $@
+
remaster: export GOARCH := amd64
remaster: export GOPROXY := direct
-remaster:
+remaster: .deps/go/prepared
rm -f go.sum go.mod
cp go.mod.master go.mod
go get -d
+ sed -i $(shell curl -L 'https://go.dev/dl/?mode=json&include=all' | jq -r '(".windows-amd64.zip",".linux-amd64.tar.gz") as $$suffix | .[0].files[] | select(.filename|endswith($$suffix)) | ("-e", "s/go[0-9][^ ]*\\\($$suffix)\\([ ,]\\)[a-f0-9]\\+/\(.filename)\\1\(.sha256)/") | @sh') Makefile build.bat
-fmt: export CC := x86_64-w64-mingw32-gcc
fmt: export GOARCH := amd64
-fmt:
+fmt: .deps/go/prepared
go fmt ./...
+generate: export GOOS :=
+generate: .deps/go/prepared
+ go generate -mod=mod ./...
+
+crowdin:
+ find locales -maxdepth 1 -mindepth 1 -type d \! -name en -exec rm -rf {} +
+ curl -Lo - https://crowdin.com/backend/download/project/wireguard.zip | bsdtar -C locales -x -f - --strip-components 2 wireguard-windows
+ find locales -name messages.gotext.json -exec bash -c '[[ $$(jq ".messages | length" {}) -ne 0 ]] || rm -rf "$$(dirname {})"' \;
+ @$(MAKE) --no-print-directory generate
+
deploy: amd64/wireguard.exe
-ssh $(DEPLOYMENT_HOST) -- 'taskkill /im wireguard.exe /f'
scp $< $(DEPLOYMENT_HOST):$(DEPLOYMENT_PATH)
clean:
- rm -rf *.syso ui/icon/*.ico x86/ amd64/
+ rm -rf *.syso ui/icon/*.ico x86/ amd64/ arm64/ .deps
+
+distclean: clean
+ rm -rf .distfiles
-.PHONY: deploy clean fmt remaster all
+.PHONY: deploy clean distclean fmt remaster generate all
diff --git a/README.md b/README.md
index 940f4b33..4ae2f267 100644
--- a/README.md
+++ b/README.md
@@ -1,76 +1,46 @@
# [WireGuard](https://www.wireguard.com/) for Windows
-***If you've come here looking to simply run WireGuard for Windows, [you may download it here](https://www.wireguard.com/install/).***
+This is a fully-featured WireGuard client for Windows that uses [WireGuardNT](https://git.zx2c4.com/wireguard-nt/about/). It is the only official and recommended way of using WireGuard on Windows.
-This is a fully-featured WireGuard client for Windows that uses [Wintun](https://www.wintun.net/).
+## Download &amp; Install
-### Building
+If you've come here looking to simply run WireGuard for Windows, [the main download page has links](https://www.wireguard.com/install/). There you will find two things:
-Windows 10 64-bit or Windows Server 2019, and Git for Windows is required. The build script will take care of downloading, verifying, and extracting the right versions of the various dependencies:
+- [The WireGuard Installer](https://download.wireguard.com/windows-client/wireguard-installer.exe) &ndash; This selects the most recent version for your architecture, downloads it, checks signatures and hashes, and installs it.
+- [Standalone MSIs](https://download.wireguard.com/windows-client/) &ndash; These are for system admins who wish to deploy the MSIs directly. For most end users, the ordinary installer takes care of downloading these automatically.
-```
-C:\Projects> git clone https://git.zx2c4.com/wireguard-windows
-C:\Projects> cd wireguard-windows
-C:\Projects\wireguard-windows> build
-```
-
-### Running
-
-After you've built the application, run `amd64\wireguard.exe` or `x86\wireguard.exe` to install the manager service and show the UI.
-
-```
-C:\Projects\wireguard-windows> amd64\wireguard.exe
-```
-
-Since WireGuard requires the Wintun driver to be installed, and this generally requires a valid Microsoft signature, you may benefit from first installing a release of WireGuard for Windows from the official [wireguard.com](https://www.wireguard.com/install/) builds, which bundles a Microsoft-signed Wintun, and then subsequently run your own wireguard.exe. Alternatively, you can craft your own installer using the `quickinstall.bat` script.
-
-### Optional: Creating the Installer
-
-The installer build script will take care of downloading, verifying, and extracting the right versions of the various dependencies:
-
-```
-C:\Projects\wireguard-windows> cd installer
-C:\Projects\wireguard-windows\installer> build
-```
+## Documentation
-### Optional: Signing Binaries
+In addition to this [`README.md`](README.md), the following documents are also available:
-Add a file called `sign.bat` in the root of this repository with these contents, or similar:
+- [`adminregistry.md`](docs/adminregistry.md) &ndash; A list of registry keys settable by the system administrator for changing the behavior of the application.
+- [`attacksurface.md`](docs/attacksurface.md) &ndash; A discussion of the various components from a security perspective, so that future auditors of this code have a head start in assessing its security design.
+- [`buildrun.md`](docs/buildrun.md) &ndash; Instructions on building, localizing, running, and developing for this repository.
+- [`enterprise.md`](docs/enterprise.md) &ndash; A summary of various features and tips for making the application usable in enterprise settings.
+- [`netquirk.md`](docs/netquirk.md) &ndash; A description of various networking quirks and "kill-switch" semantics.
-```
-set SigningCertificate=DF98E075A012ED8C86FBCF14854B8F9555CB3D45
-set TimestampServer=http://timestamp.digicert.com
-```
+## License
-After, run the above `build` commands as usual, from a shell that has [`signtool.exe`](https://docs.microsoft.com/en-us/windows/desktop/SecCrypto/signtool) in its `PATH`, such as the Visual Studio 2017 command prompt.
+This repository is MIT-licensed.
-### Alternative: Building from Linux
+```text
+Copyright (C) 2018-2022 WireGuard LLC. All Rights Reserved.
-You must first have Go ≥1.12, Mingw, and ImageMagick installed.
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
-```
-$ sudo apt install mingw-w64 golang-go imagemagick
-$ git clone https://git.zx2c4.com/wireguard-windows
-$ cd wireguard-windows
-$ make
-```
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
-You can deploy the 64-bit build to an SSH host specified by the `DEPLOYMENT_HOST` environment variable (default "winvm") to the remote directory specified by the `DEPLOYMENT_PATH` environment variable (default "Desktop") by using the `deploy` target:
-
-```
-$ make deploy
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
```
-
-### [`wg(8)`](https://git.zx2c4.com/wireguard-tools/about/src/man/wg.8) Support for Windows
-
-The command line utility [`wg(8)`](https://git.zx2c4.com/wireguard-tools/about/src/man/wg.8) works well on Windows. Being a Unix-centric project, it compiles with a Makefile and MingW:
-
-```
-$ git clone https://git.zx2c4.com/wireguard-tools
-$ PLATFORM=windows make -C wireguard-tools/src
-$ stat wireguard-tools/src/wg.exe
-```
-
-It interacts with WireGuard instances run by the main WireGuard for Windows program.
-
-When building on Windows, the aforementioned `build.bat` script takes care of building this.
diff --git a/build.bat b/build.bat
index d2b52e49..afb171ef 100644
--- a/build.bat
+++ b/build.bat
@@ -1,10 +1,10 @@
@echo off
rem SPDX-License-Identifier: MIT
-rem Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+rem Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
-setlocal
+setlocal enabledelayedexpansion
set BUILDDIR=%~dp0
-set PATH=%BUILDDIR%.deps\go\bin;%BUILDDIR%.deps;%PATH%
+set PATH=%BUILDDIR%.deps\llvm-mingw\bin;%BUILDDIR%.deps\go\bin;%BUILDDIR%.deps;%PATH%
set PATHEXT=.exe
cd /d %BUILDDIR% || exit /b 1
@@ -13,40 +13,44 @@ if exist .deps\prepared goto :render
rmdir /s /q .deps 2> NUL
mkdir .deps || goto :error
cd .deps || goto :error
- call :download go.zip https://dl.google.com/go/go1.13.3.windows-amd64.zip 9585efeab37783152c81c6ce373b22e68f45c6801dc2c208bfd1e47b646efbef || goto :error
- rem Mirror of https://musl.cc/i686-w64-mingw32-native.zip
- call :download mingw-x86.zip https://download.wireguard.com/windows-toolchain/distfiles/i686-w64-mingw32-native-20190903.zip dfb297cc86c4a4c12eedaeb0a89dff2e1cfa9afacfb9c32690dd23ca7726560a || goto :error
- rem Mirror of https://musl.cc/x86_64-w64-mingw32-native.zip
- call :download mingw-amd64.zip https://download.wireguard.com/windows-toolchain/distfiles/x86_64-w64-mingw32-native-20190903.zip 15cf5596ece5394be0d71c22f586ef252e0390689ef6526f990a262f772aecf8 || goto :error
+ call :download go.zip https://go.dev/dl/go1.18.windows-amd64.zip 65c5c0c709a7ca1b357091b10b795b439d8b50e579d3893edab4c7e9b384f435 || goto :error
+ rem Mirror of https://github.com/mstorsjo/llvm-mingw/releases/download/20201020/llvm-mingw-20201020-msvcrt-x86_64.zip
+ call :download llvm-mingw-msvcrt.zip https://download.wireguard.com/windows-toolchain/distfiles/llvm-mingw-20201020-msvcrt-x86_64.zip 2e46593245090df96d15e360e092f0b62b97e93866e0162dca7f93b16722b844 || goto :error
rem Mirror of https://imagemagick.org/download/binaries/ImageMagick-7.0.8-42-portable-Q16-x64.zip
call :download imagemagick.zip https://download.wireguard.com/windows-toolchain/distfiles/ImageMagick-7.0.8-42-portable-Q16-x64.zip 584e069f56456ce7dde40220948ff9568ac810688c892c5dfb7f6db902aa05aa "convert.exe colors.xml delegates.xml" || goto :error
rem Mirror of https://sourceforge.net/projects/ezwinports/files/make-4.2.1-without-guile-w32-bin.zip
call :download make.zip https://download.wireguard.com/windows-toolchain/distfiles/make-4.2.1-without-guile-w32-bin.zip 30641be9602712be76212b99df7209f4f8f518ba764cf564262bc9d6e4047cc7 "--strip-components 1 bin" || goto :error
- call :download wireguard-tools.zip https://git.zx2c4.com/wireguard-tools/snapshot/wireguard-tools-1.0.20200102.zip fffee62b8c5520658895d9f94b420fd62204d2f8b223fa683416906600f62517 "--exclude wg-quick --strip-components 1" || goto :error
+ call :download wireguard-tools.zip https://git.zx2c4.com/wireguard-tools/snapshot/wireguard-tools-1ee37b8e4833a25efe6f1fc0d5bdcb476148f4ba.zip ed0739bc3e5a7021a59d4cc4fc63e5fb60a0cb8628d30515a747bfbdcf1fdb0a "--exclude wg-quick --strip-components 1" || goto :error
+ call :download wireguard-nt.zip https://download.wireguard.com/wireguard-nt/wireguard-nt-0.10.1.zip 772c0b1463d8d2212716f43f06f4594d880dea4f735165bd68e388fc41b81605 || goto :error
copy /y NUL prepared > NUL || goto :error
cd .. || goto :error
:render
echo [+] Rendering icons
- for %%a in ("ui\icon\*.svg") do convert -background none "%%~fa" -define icon:auto-resize="256,192,128,96,64,48,32,24,16" "%%~dpna.ico" || goto :error
+ for %%a in ("ui\icon\*.svg") do convert -background none "%%~fa" -define icon:auto-resize="256,192,128,96,64,48,40,32,24,20,16" -compress zip "%%~dpna.ico" || goto :error
:build
+ for /f "tokens=3" %%a in ('findstr /r "Number.*=.*[0-9.]*" .\version\version.go') do set WIREGUARD_VERSION=%%a
+ set WIREGUARD_VERSION=%WIREGUARD_VERSION:"=%
+ for /f "tokens=1-4" %%a in ("%WIREGUARD_VERSION:.= % 0 0 0") do set WIREGUARD_VERSION_ARRAY=%%a,%%b,%%c,%%d
set GOOS=windows
+ set GOARM=7
set GOPATH=%BUILDDIR%.deps\gopath
set GOROOT=%BUILDDIR%.deps\go
- set CGO_ENABLED=1
- set CGO_CFLAGS=-O3 -Wall -Wno-unused-function -Wno-switch -std=gnu11 -DWINVER=0x0601
- set CGO_LDFLAGS=-Wl,--dynamicbase -Wl,--nxcompat -Wl,--export-all-symbols
+ if "%GoGenerate%"=="yes" (
+ echo [+] Regenerating files
+ go generate ./... || exit /b 1
+ )
call :build_plat x86 i686 386 || goto :error
- set CGO_LDFLAGS=%CGO_LDFLAGS% -Wl,--high-entropy-va
call :build_plat amd64 x86_64 amd64 || goto :error
+ call :build_plat arm64 aarch64 arm64 || goto :error
:sign
if exist .\sign.bat call .\sign.bat
if "%SigningCertificate%"=="" goto :success
if "%TimestampServer%"=="" goto :success
echo [+] Signing
- signtool sign /sha1 "%SigningCertificate%" /fd sha256 /tr "%TimestampServer%" /td sha256 /d WireGuard x86\wireguard.exe x86\wg.exe amd64\wireguard.exe amd64\wg.exe || goto :error
+ signtool sign /sha1 "%SigningCertificate%" /fd sha256 /tr "%TimestampServer%" /td sha256 /d WireGuard x86\wireguard.exe x86\wg.exe amd64\wireguard.exe amd64\wg.exe arm64\wireguard.exe arm64\wg.exe || goto :error
:success
echo [+] Success. Launch wireguard.exe.
@@ -64,18 +68,17 @@ if exist .deps\prepared goto :render
goto :eof
:build_plat
- set PATH=%BUILDDIR%.deps\%~2-w64-mingw32-native\bin;%PATH%
- set CC=%~2-w64-mingw32-gcc
set GOARCH=%~3
mkdir %1 >NUL 2>&1
echo [+] Assembling resources %1
- windres -i resources.rc -o resources.syso -O coff || exit /b %errorlevel%
+ %~2-w64-mingw32-windres -I ".deps\wireguard-nt\bin\%~1" -DWIREGUARD_VERSION_ARRAY=%WIREGUARD_VERSION_ARRAY% -DWIREGUARD_VERSION_STR=%WIREGUARD_VERSION% -i resources.rc -o "resources_%~3.syso" -O coff -c 65001 || exit /b %errorlevel%
echo [+] Building program %1
- go build -ldflags="-H windowsgui -s -w" -tags walk_use_cgo -trimpath -v -o "%~1\wireguard.exe" || exit /b 1
+ go build -tags load_wgnt_from_rsrc -ldflags="-H windowsgui -s -w" -trimpath -buildvcs=false -v -o "%~1\wireguard.exe" || exit /b 1
if not exist "%~1\wg.exe" (
echo [+] Building command line tools %1
- del .deps\src\*.exe .deps\src\*.o .deps\src\wincompat\*.o 2> NUL
- make --no-print-directory -C .deps\src PLATFORM=windows CC=%CC% V=1 LDFLAGS=-s RUNSTATEDIR= SYSTEMDUNITDIR= -j%NUMBER_OF_PROCESSORS% || exit /b 1
+ del .deps\src\*.exe .deps\src\*.o .deps\src\wincompat\*.o .deps\src\wincompat\*.lib 2> NUL
+ set LDFLAGS=-s
+ make --no-print-directory -C .deps\src PLATFORM=windows CC=%~2-w64-mingw32-gcc WINDRES=%~2-w64-mingw32-windres V=1 RUNSTATEDIR= SYSTEMDUNITDIR= -j%NUMBER_OF_PROCESSORS% || exit /b 1
move /Y .deps\src\wg.exe "%~1\wg.exe" > NUL || exit /b 1
)
goto :eof
diff --git a/conf/admin_windows.go b/conf/admin_windows.go
new file mode 100644
index 00000000..91d4bdc9
--- /dev/null
+++ b/conf/admin_windows.go
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package conf
+
+import "golang.org/x/sys/windows/registry"
+
+const adminRegKey = `Software\WireGuard`
+
+var adminKey registry.Key
+
+func openAdminKey() (registry.Key, error) {
+ if adminKey != 0 {
+ return adminKey, nil
+ }
+ var err error
+ adminKey, err = registry.OpenKey(registry.LOCAL_MACHINE, adminRegKey, registry.QUERY_VALUE|registry.WOW64_64KEY)
+ if err != nil {
+ return 0, err
+ }
+ return adminKey, nil
+}
+
+func AdminBool(name string) bool {
+ key, err := openAdminKey()
+ if err != nil {
+ return false
+ }
+ val, _, err := key.GetIntegerValue(name)
+ if err != nil {
+ return false
+ }
+ return val != 0
+}
diff --git a/conf/config.go b/conf/config.go
index 5b3496b6..74ffacf6 100644
--- a/conf/config.go
+++ b/conf/config.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package conf
@@ -9,30 +9,28 @@ import (
"crypto/rand"
"crypto/subtle"
"encoding/base64"
- "encoding/hex"
"fmt"
- "net"
+ "net/netip"
"strings"
"time"
"golang.org/x/crypto/curve25519"
+
+ "golang.zx2c4.com/wireguard/windows/l18n"
)
const KeyLength = 32
-type IPCidr struct {
- IP net.IP
- Cidr uint8
-}
-
type Endpoint struct {
Host string
Port uint16
}
-type Key [KeyLength]byte
-type HandshakeTime time.Duration
-type Bytes uint64
+type (
+ Key [KeyLength]byte
+ HandshakeTime time.Duration
+ Bytes uint64
+)
type Config struct {
Name string
@@ -42,16 +40,22 @@ type Config struct {
type Interface struct {
PrivateKey Key
- Addresses []IPCidr
+ Addresses []netip.Prefix
ListenPort uint16
MTU uint16
- DNS []net.IP
+ DNS []netip.Addr
+ DNSSearch []string
+ PreUp string
+ PostUp string
+ PreDown string
+ PostDown string
+ TableOff bool
}
type Peer struct {
PublicKey Key
PresharedKey Key
- AllowedIPs []IPCidr
+ AllowedIPs []netip.Prefix
Endpoint Endpoint
PersistentKeepalive uint16
@@ -60,26 +64,37 @@ type Peer struct {
LastHandshakeTime HandshakeTime
}
-func (r *IPCidr) String() string {
- return fmt.Sprintf("%s/%d", r.IP.String(), r.Cidr)
-}
-
-func (r *IPCidr) Bits() uint8 {
- if r.IP.To4() != nil {
- return 32
+func (conf *Config) IntersectsWith(other *Config) bool {
+ allRoutes := make(map[netip.Prefix]bool, len(conf.Interface.Addresses)*2+len(conf.Peers)*3)
+ for _, a := range conf.Interface.Addresses {
+ allRoutes[netip.PrefixFrom(a.Addr(), a.Addr().BitLen())] = true
+ allRoutes[a.Masked()] = true
}
- return 128
-}
-
-func (r *IPCidr) IPNet() net.IPNet {
- return net.IPNet{
- IP: r.IP,
- Mask: net.CIDRMask(int(r.Cidr), int(r.Bits())),
+ for i := range conf.Peers {
+ for _, a := range conf.Peers[i].AllowedIPs {
+ allRoutes[a.Masked()] = true
+ }
+ }
+ for _, a := range other.Interface.Addresses {
+ if allRoutes[netip.PrefixFrom(a.Addr(), a.Addr().BitLen())] {
+ return true
+ }
+ if allRoutes[a.Masked()] {
+ return true
+ }
}
+ for i := range other.Peers {
+ for _, a := range other.Peers[i].AllowedIPs {
+ if allRoutes[a.Masked()] {
+ return true
+ }
+ }
+ }
+ return false
}
func (e *Endpoint) String() string {
- if strings.IndexByte(e.Host, ':') > 0 {
+ if strings.IndexByte(e.Host, ':') != -1 {
return fmt.Sprintf("[%s]:%d", e.Host, e.Port)
}
return fmt.Sprintf("%s:%d", e.Host, e.Port)
@@ -93,10 +108,6 @@ func (k *Key) String() string {
return base64.StdEncoding.EncodeToString(k[:])
}
-func (k *Key) HexString() string {
- return hex.EncodeToString(k[:])
-}
-
func (k *Key) IsZero() bool {
var zeros Key
return subtle.ConstantTimeCompare(zeros[:], k[:]) == 1
@@ -131,18 +142,6 @@ func NewPrivateKeyFromString(b64 string) (*Key, error) {
return parseKeyBase64(b64)
}
-func formatInterval(i int64, n string, l int) string {
- r := ""
- if l > 0 {
- r += ", "
- }
- r += fmt.Sprintf("%d %s", i, n)
- if i != 1 {
- r += "s"
- }
- return r
-}
-
func (t HandshakeTime) IsEmpty() bool {
return t == HandshakeTime(0)
}
@@ -151,9 +150,9 @@ func (t HandshakeTime) String() string {
u := time.Unix(0, 0).Add(time.Duration(t)).Unix()
n := time.Now().Unix()
if u == n {
- return "Now"
+ return l18n.Sprintf("Now")
} else if u > n {
- return "System clock wound backward!"
+ return l18n.Sprintf("System clock wound backward!")
}
left := n - u
years := left / (365 * 24 * 60 * 60)
@@ -164,35 +163,86 @@ func (t HandshakeTime) String() string {
left = left % (60 * 60)
minutes := left / 60
seconds := left % 60
- s := ""
+ s := make([]string, 0, 5)
if years > 0 {
- s += formatInterval(years, "year", len(s))
+ s = append(s, l18n.Sprintf("%d year(s)", years))
}
if days > 0 {
- s += formatInterval(days, "day", len(s))
+ s = append(s, l18n.Sprintf("%d day(s)", days))
}
if hours > 0 {
- s += formatInterval(hours, "hour", len(s))
+ s = append(s, l18n.Sprintf("%d hour(s)", hours))
}
if minutes > 0 {
- s += formatInterval(minutes, "minute", len(s))
+ s = append(s, l18n.Sprintf("%d minute(s)", minutes))
}
if seconds > 0 {
- s += formatInterval(seconds, "second", len(s))
+ s = append(s, l18n.Sprintf("%d second(s)", seconds))
}
- s += " ago"
- return s
+ timestamp := strings.Join(s, l18n.UnitSeparator())
+ return l18n.Sprintf("%s ago", timestamp)
}
func (b Bytes) String() string {
if b < 1024 {
- return fmt.Sprintf("%d B", b)
+ return l18n.Sprintf("%d\u00a0B", b)
} else if b < 1024*1024 {
- return fmt.Sprintf("%.2f KiB", float64(b)/1024)
+ return l18n.Sprintf("%.2f\u00a0KiB", float64(b)/1024)
} else if b < 1024*1024*1024 {
- return fmt.Sprintf("%.2f MiB", float64(b)/(1024*1024))
+ return l18n.Sprintf("%.2f\u00a0MiB", float64(b)/(1024*1024))
} else if b < 1024*1024*1024*1024 {
- return fmt.Sprintf("%.2f GiB", float64(b)/(1024*1024*1024))
+ return l18n.Sprintf("%.2f\u00a0GiB", float64(b)/(1024*1024*1024))
+ }
+ return l18n.Sprintf("%.2f\u00a0TiB", float64(b)/(1024*1024*1024)/1024)
+}
+
+func (conf *Config) DeduplicateNetworkEntries() {
+ m := make(map[string]bool, len(conf.Interface.Addresses))
+ i := 0
+ for _, addr := range conf.Interface.Addresses {
+ s := addr.String()
+ if m[s] {
+ continue
+ }
+ m[s] = true
+ conf.Interface.Addresses[i] = addr
+ i++
+ }
+ conf.Interface.Addresses = conf.Interface.Addresses[:i]
+
+ m = make(map[string]bool, len(conf.Interface.DNS))
+ i = 0
+ for _, addr := range conf.Interface.DNS {
+ s := addr.String()
+ if m[s] {
+ continue
+ }
+ m[s] = true
+ conf.Interface.DNS[i] = addr
+ i++
+ }
+ conf.Interface.DNS = conf.Interface.DNS[:i]
+
+ for _, peer := range conf.Peers {
+ m = make(map[string]bool, len(peer.AllowedIPs))
+ i = 0
+ for _, addr := range peer.AllowedIPs {
+ s := addr.String()
+ if m[s] {
+ continue
+ }
+ m[s] = true
+ peer.AllowedIPs[i] = addr
+ i++
+ }
+ peer.AllowedIPs = peer.AllowedIPs[:i]
+ }
+}
+
+func (conf *Config) Redact() {
+ conf.Interface.PrivateKey = Key{}
+ for i := range conf.Peers {
+ conf.Peers[i].PublicKey = Key{}
+ conf.Peers[i].PresharedKey = Key{}
}
- return fmt.Sprintf("%.2f TiB", float64(b)/(1024*1024*1024)/1024)
}
diff --git a/conf/dnsresolver_windows.go b/conf/dnsresolver_windows.go
index d6c2f1c7..a299c475 100644
--- a/conf/dnsresolver_windows.go
+++ b/conf/dnsresolver_windows.go
@@ -1,43 +1,41 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package conf
import (
- "fmt"
"log"
- "net"
- "syscall"
+ "net/netip"
"time"
"unsafe"
+ "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
+
"golang.org/x/sys/windows"
+ "golang.zx2c4.com/wireguard/windows/services"
)
-//sys internetGetConnectedState(flags *uint32, reserved uint32) (connected bool) = wininet.InternetGetConnectedState
-
func resolveHostname(name string) (resolvedIPString string, err error) {
maxTries := 10
- systemJustBooted := windows.DurationSinceBoot() <= time.Minute*4
- if systemJustBooted {
- maxTries *= 4
+ if services.StartedAtBoot() {
+ maxTries *= 3
}
for i := 0; i < maxTries; i++ {
+ if i > 0 {
+ time.Sleep(time.Second * 4)
+ }
resolvedIPString, err = resolveHostnameOnce(name)
if err == nil {
return
}
if err == windows.WSATRY_AGAIN {
- log.Printf("Temporary DNS error when resolving %s, sleeping for 4 seconds", name)
- time.Sleep(time.Second * 4)
+ log.Printf("Temporary DNS error when resolving %s, so sleeping for 4 seconds", name)
continue
}
- var state uint32
- if err == windows.WSAHOST_NOT_FOUND && systemJustBooted && !internetGetConnectedState(&state, 0) {
- log.Printf("Host not found when resolving %s, but no Internet connection available, sleeping for 4 seconds", name)
- time.Sleep(time.Second * 4)
+ if err == windows.WSAHOST_NOT_FOUND && services.StartedAtBoot() {
+ log.Printf("Host not found when resolving %s at boot time, so sleeping for 4 seconds", name)
continue
}
return
@@ -65,28 +63,35 @@ func resolveHostnameOnce(name string) (resolvedIPString string, err error) {
return
}
defer windows.FreeAddrInfoW(result)
- ipv6 := ""
+ var v6 netip.Addr
for ; result != nil; result = result.Next {
- addr := unsafe.Pointer(result.Addr)
- switch result.Family {
- case windows.AF_INET:
- a := (*syscall.RawSockaddrInet4)(addr).Addr
- return net.IP{a[0], a[1], a[2], a[3]}.String(), nil
- case windows.AF_INET6:
- if len(ipv6) != 0 {
- continue
- }
- a := (*syscall.RawSockaddrInet6)(addr).Addr
- ipv6 = net.IP{a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]}.String()
- scope := uint32((*syscall.RawSockaddrInet6)(addr).Scope_id)
- if scope != 0 {
- ipv6 += fmt.Sprintf("%%%d", scope)
- }
+ if result.Family != windows.AF_INET && result.Family != windows.AF_INET6 {
+ continue
+ }
+ addr := (*winipcfg.RawSockaddrInet)(unsafe.Pointer(result.Addr)).Addr()
+ if addr.Is4() {
+ return addr.String(), nil
+ } else if !v6.IsValid() && addr.Is6() {
+ v6 = addr
}
}
- if len(ipv6) != 0 {
- return ipv6, nil
+ if v6.IsValid() {
+ return v6.String(), nil
}
err = windows.WSAHOST_NOT_FOUND
return
}
+
+func (config *Config) ResolveEndpoints() error {
+ for i := range config.Peers {
+ if config.Peers[i].Endpoint.IsEmpty() {
+ continue
+ }
+ var err error
+ config.Peers[i].Endpoint.Host, err = resolveHostname(config.Peers[i].Endpoint.Host)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/conf/dpapi/dpapi_windows.go b/conf/dpapi/dpapi_windows.go
index 851ec1ee..49a32915 100644
--- a/conf/dpapi/dpapi_windows.go
+++ b/conf/dpapi/dpapi_windows.go
@@ -1,86 +1,53 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package dpapi
import (
"errors"
+ "fmt"
"runtime"
"unsafe"
"golang.org/x/sys/windows"
)
-const (
- dpCRYPTPROTECT_UI_FORBIDDEN uint32 = 0x1
- dpCRYPTPROTECT_LOCAL_MACHINE uint32 = 0x4
- dpCRYPTPROTECT_CRED_SYNC uint32 = 0x8
- dpCRYPTPROTECT_AUDIT uint32 = 0x10
- dpCRYPTPROTECT_NO_RECOVERY uint32 = 0x20
- dpCRYPTPROTECT_VERIFY_PROTECTION uint32 = 0x40
- dpCRYPTPROTECT_CRED_REGENERATE uint32 = 0x80
-)
-
-type dpBlob struct {
- len uint32
- data uintptr
-}
-
-func bytesToBlob(bytes []byte) *dpBlob {
- blob := &dpBlob{}
- blob.len = uint32(len(bytes))
+func bytesToBlob(bytes []byte) *windows.DataBlob {
+ blob := &windows.DataBlob{Size: uint32(len(bytes))}
if len(bytes) > 0 {
- blob.data = uintptr(unsafe.Pointer(&bytes[0]))
+ blob.Data = &bytes[0]
}
return blob
}
-//sys cryptProtectData(dataIn *dpBlob, name *uint16, optionalEntropy *dpBlob, reserved uintptr, promptStruct uintptr, flags uint32, dataOut *dpBlob) (err error) = crypt32.CryptProtectData
-
func Encrypt(data []byte, name string) ([]byte, error) {
- out := dpBlob{}
- err := cryptProtectData(bytesToBlob(data), windows.StringToUTF16Ptr(name), nil, 0, 0, dpCRYPTPROTECT_UI_FORBIDDEN, &out)
+ out := windows.DataBlob{}
+ err := windows.CryptProtectData(bytesToBlob(data), windows.StringToUTF16Ptr(name), nil, 0, nil, windows.CRYPTPROTECT_UI_FORBIDDEN, &out)
if err != nil {
- return nil, errors.New("Unable to encrypt DPAPI protected data: " + err.Error())
+ return nil, fmt.Errorf("unable to encrypt DPAPI protected data: %w", err)
}
-
- outSlice := *(*[]byte)(unsafe.Pointer(&(struct {
- addr uintptr
- len int
- cap int
- }{out.data, int(out.len), int(out.len)})))
- ret := make([]byte, len(outSlice))
- copy(ret, outSlice)
- windows.LocalFree(windows.Handle(out.data))
-
+ ret := make([]byte, out.Size)
+ copy(ret, unsafe.Slice(out.Data, out.Size))
+ windows.LocalFree(windows.Handle(unsafe.Pointer(out.Data)))
return ret, nil
}
-//sys cryptUnprotectData(dataIn *dpBlob, name **uint16, optionalEntropy *dpBlob, reserved uintptr, promptStruct uintptr, flags uint32, dataOut *dpBlob) (err error) = crypt32.CryptUnprotectData
-
func Decrypt(data []byte, name string) ([]byte, error) {
- out := dpBlob{}
+ out := windows.DataBlob{}
var outName *uint16
utf16Name, err := windows.UTF16PtrFromString(name)
if err != nil {
return nil, err
}
-
- err = cryptUnprotectData(bytesToBlob(data), &outName, nil, 0, 0, dpCRYPTPROTECT_UI_FORBIDDEN, &out)
+ err = windows.CryptUnprotectData(bytesToBlob(data), &outName, nil, 0, nil, windows.CRYPTPROTECT_UI_FORBIDDEN, &out)
if err != nil {
- return nil, errors.New("Unable to decrypt DPAPI protected data: " + err.Error())
+ return nil, fmt.Errorf("unable to decrypt DPAPI protected data: %w", err)
}
-
- outSlice := *(*[]byte)(unsafe.Pointer(&(struct {
- addr uintptr
- len int
- cap int
- }{out.data, int(out.len), int(out.len)})))
- ret := make([]byte, len(outSlice))
- copy(ret, outSlice)
- windows.LocalFree(windows.Handle(out.data))
+ ret := make([]byte, out.Size)
+ copy(ret, unsafe.Slice(out.Data, out.Size))
+ windows.LocalFree(windows.Handle(unsafe.Pointer(out.Data)))
// Note: this ridiculous open-coded strcmp is not constant time.
different := false
@@ -94,14 +61,14 @@ func Decrypt(data []byte, name string) ([]byte, error) {
if *a == 0 || *b == 0 {
break
}
- a = (*uint16)(unsafe.Pointer(uintptr(unsafe.Pointer(a)) + 2))
- b = (*uint16)(unsafe.Pointer(uintptr(unsafe.Pointer(b)) + 2))
+ a = (*uint16)(unsafe.Add(unsafe.Pointer(a), 2))
+ b = (*uint16)(unsafe.Add(unsafe.Pointer(b), 2))
}
runtime.KeepAlive(utf16Name)
windows.LocalFree(windows.Handle(unsafe.Pointer(outName)))
if different {
- return nil, errors.New("The input name does not match the stored name")
+ return nil, errors.New("input name does not match the stored name")
}
return ret, nil
diff --git a/conf/dpapi/dpapi_windows_test.go b/conf/dpapi/dpapi_windows_test.go
index 8356f2d4..fd7307e6 100644
--- a/conf/dpapi/dpapi_windows_test.go
+++ b/conf/dpapi/dpapi_windows_test.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package dpapi
@@ -53,11 +53,7 @@ func TestRoundTrip(t *testing.T) {
if err != nil {
t.Errorf("Unable to get utf16 chars for name: %s", err)
}
- nameUtf16Bytes := *(*[]byte)(unsafe.Pointer(&struct {
- addr *byte
- len int
- cap int
- }{(*byte)(unsafe.Pointer(&nameUtf16[0])), len(nameUtf16) * 2, cap(nameUtf16) * 2}))
+ nameUtf16Bytes := unsafe.Slice((*byte)(unsafe.Pointer(&nameUtf16[0])), len(nameUtf16)*2)
i := bytes.Index(eCorrupt, nameUtf16Bytes)
if i == -1 {
t.Error("Unable to find ad in blob")
diff --git a/conf/dpapi/mksyscall.go b/conf/dpapi/mksyscall.go
deleted file mode 100644
index 3d467f76..00000000
--- a/conf/dpapi/mksyscall.go
+++ /dev/null
@@ -1,8 +0,0 @@
-/* SPDX-License-Identifier: MIT
- *
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
- */
-
-package dpapi
-
-//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zdpapi_windows.go dpapi_windows.go
diff --git a/conf/dpapi/zdpapi_windows.go b/conf/dpapi/zdpapi_windows.go
deleted file mode 100644
index e48d36b2..00000000
--- a/conf/dpapi/zdpapi_windows.go
+++ /dev/null
@@ -1,68 +0,0 @@
-// Code generated by 'go generate'; DO NOT EDIT.
-
-package dpapi
-
-import (
- "syscall"
- "unsafe"
-
- "golang.org/x/sys/windows"
-)
-
-var _ unsafe.Pointer
-
-// Do the interface allocations only once for common
-// Errno values.
-const (
- errnoERROR_IO_PENDING = 997
-)
-
-var (
- errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
-)
-
-// errnoErr returns common boxed Errno values, to prevent
-// allocations at runtime.
-func errnoErr(e syscall.Errno) error {
- switch e {
- case 0:
- return nil
- case errnoERROR_IO_PENDING:
- return errERROR_IO_PENDING
- }
- // TODO: add more here, after collecting data on the common
- // error values see on Windows. (perhaps when running
- // all.bat?)
- return e
-}
-
-var (
- modcrypt32 = windows.NewLazySystemDLL("crypt32.dll")
-
- procCryptProtectData = modcrypt32.NewProc("CryptProtectData")
- procCryptUnprotectData = modcrypt32.NewProc("CryptUnprotectData")
-)
-
-func cryptProtectData(dataIn *dpBlob, name *uint16, optionalEntropy *dpBlob, reserved uintptr, promptStruct uintptr, flags uint32, dataOut *dpBlob) (err error) {
- r1, _, e1 := syscall.Syscall9(procCryptProtectData.Addr(), 7, uintptr(unsafe.Pointer(dataIn)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(optionalEntropy)), uintptr(reserved), uintptr(promptStruct), uintptr(flags), uintptr(unsafe.Pointer(dataOut)), 0, 0)
- if r1 == 0 {
- if e1 != 0 {
- err = errnoErr(e1)
- } else {
- err = syscall.EINVAL
- }
- }
- return
-}
-
-func cryptUnprotectData(dataIn *dpBlob, name **uint16, optionalEntropy *dpBlob, reserved uintptr, promptStruct uintptr, flags uint32, dataOut *dpBlob) (err error) {
- r1, _, e1 := syscall.Syscall9(procCryptUnprotectData.Addr(), 7, uintptr(unsafe.Pointer(dataIn)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(optionalEntropy)), uintptr(reserved), uintptr(promptStruct), uintptr(flags), uintptr(unsafe.Pointer(dataOut)), 0, 0)
- if r1 == 0 {
- if e1 != 0 {
- err = errnoErr(e1)
- } else {
- err = syscall.EINVAL
- }
- }
- return
-}
diff --git a/conf/filewriter_windows.go b/conf/filewriter_windows.go
new file mode 100644
index 00000000..c6bb2b45
--- /dev/null
+++ b/conf/filewriter_windows.go
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package conf
+
+import (
+ "crypto/rand"
+ "encoding/hex"
+ "path/filepath"
+ "sync/atomic"
+ "unsafe"
+
+ "golang.org/x/sys/windows"
+)
+
+var encryptedFileSd unsafe.Pointer
+
+func randomFileName() string {
+ var randBytes [32]byte
+ _, err := rand.Read(randBytes[:])
+ if err != nil {
+ panic(err)
+ }
+ return hex.EncodeToString(randBytes[:]) + ".tmp"
+}
+
+func writeLockedDownFile(destination string, overwrite bool, contents []byte) error {
+ var err error
+ sa := &windows.SecurityAttributes{Length: uint32(unsafe.Sizeof(windows.SecurityAttributes{}))}
+ sa.SecurityDescriptor = (*windows.SECURITY_DESCRIPTOR)(atomic.LoadPointer(&encryptedFileSd))
+ if sa.SecurityDescriptor == nil {
+ sa.SecurityDescriptor, err = windows.SecurityDescriptorFromString("O:SYG:SYD:PAI(A;;FA;;;SY)(A;;SD;;;BA)")
+ if err != nil {
+ return err
+ }
+ atomic.StorePointer(&encryptedFileSd, unsafe.Pointer(sa.SecurityDescriptor))
+ }
+ destination16, err := windows.UTF16FromString(destination)
+ if err != nil {
+ return err
+ }
+ tmpDestination := filepath.Join(filepath.Dir(destination), randomFileName())
+ tmpDestination16, err := windows.UTF16PtrFromString(tmpDestination)
+ if err != nil {
+ return err
+ }
+ handle, err := windows.CreateFile(tmpDestination16, windows.GENERIC_WRITE|windows.DELETE, windows.FILE_SHARE_READ, sa, windows.CREATE_ALWAYS, windows.FILE_ATTRIBUTE_NORMAL, 0)
+ if err != nil {
+ return err
+ }
+ defer windows.CloseHandle(handle)
+ deleteIt := func() {
+ yes := byte(1)
+ windows.SetFileInformationByHandle(handle, windows.FileDispositionInfo, &yes, 1)
+ }
+ n, err := windows.Write(handle, contents)
+ if err != nil {
+ deleteIt()
+ return err
+ }
+ if n != len(contents) {
+ deleteIt()
+ return windows.ERROR_IO_INCOMPLETE
+ }
+ fileRenameInfo := &struct {
+ replaceIfExists byte
+ rootDirectory windows.Handle
+ fileNameLength uint32
+ fileName [windows.MAX_PATH]uint16
+ }{replaceIfExists: func() byte {
+ if overwrite {
+ return 1
+ } else {
+ return 0
+ }
+ }(), fileNameLength: uint32(len(destination16) - 1)}
+ if len(destination16) > len(fileRenameInfo.fileName) {
+ deleteIt()
+ return windows.ERROR_BUFFER_OVERFLOW
+ }
+ copy(fileRenameInfo.fileName[:], destination16[:])
+ err = windows.SetFileInformationByHandle(handle, windows.FileRenameInfo, (*byte)(unsafe.Pointer(fileRenameInfo)), uint32(unsafe.Sizeof(*fileRenameInfo)))
+ if err != nil {
+ deleteIt()
+ return err
+ }
+ return nil
+}
diff --git a/conf/migration_windows.go b/conf/migration_windows.go
index 72b298b6..ed288f3c 100644
--- a/conf/migration_windows.go
+++ b/conf/migration_windows.go
@@ -1,51 +1,109 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package conf
import (
+ "errors"
+ "io"
"log"
+ "os"
"path/filepath"
"strings"
+ "sync"
+ "time"
"golang.org/x/sys/windows"
)
-func maybeMigrate(c string) {
- if disableAutoMigration {
- return
- }
+var (
+ migrating sync.Mutex
+ lastMigrationTimer *time.Timer
+)
- vol := filepath.VolumeName(c)
- withoutVol := strings.TrimPrefix(c, vol)
- oldRoot := filepath.Join(vol, "\\windows.old")
- oldC := filepath.Join(oldRoot, withoutVol)
+type MigrationCallback func(name, oldPath, newPath string)
- sd, err := windows.GetNamedSecurityInfo(oldRoot, windows.SE_FILE_OBJECT, windows.OWNER_SECURITY_INFORMATION)
- if err == windows.ERROR_PATH_NOT_FOUND || err == windows.ERROR_FILE_NOT_FOUND {
- return
+func MigrateUnencryptedConfigs(migrated MigrationCallback) { migrateUnencryptedConfigs(3, migrated) }
+
+func migrateUnencryptedConfigs(sharingBase int, migrated MigrationCallback) {
+ if migrated == nil {
+ migrated = func(_, _, _ string) {}
}
+ migrating.Lock()
+ defer migrating.Unlock()
+ configFileDir, err := tunnelConfigurationsDirectory()
if err != nil {
- log.Printf("Not migrating configuration from ‘%s’ due to GetNamedSecurityInfo error: %v", oldRoot, err)
return
}
- owner, defaulted, err := sd.Owner()
+ files, err := os.ReadDir(configFileDir)
if err != nil {
- log.Printf("Not migrating configuration from ‘%s’ due to GetSecurityDescriptorOwner error: %v", oldRoot, err)
return
}
- if defaulted || (!owner.IsWellKnown(windows.WinLocalSystemSid) && !owner.IsWellKnown(windows.WinBuiltinAdministratorsSid)) {
- log.Printf("Not migrating configuration from ‘%s’, as it is not explicitly owned by SYSTEM or Built-in Administrators, but rather ‘%v’", oldRoot, owner)
- return
- }
- err = windows.MoveFileEx(windows.StringToUTF16Ptr(oldC), windows.StringToUTF16Ptr(c), windows.MOVEFILE_COPY_ALLOWED)
- if err != nil {
- if err != windows.ERROR_FILE_NOT_FOUND && err != windows.ERROR_ALREADY_EXISTS {
- log.Printf("Not migrating configuration from ‘%s’ due to error when moving files: %v", oldRoot, err)
+ ignoreSharingViolations := false
+ for _, file := range files {
+ path := filepath.Join(configFileDir, file.Name())
+ name := filepath.Base(file.Name())
+ if len(name) <= len(configFileUnencryptedSuffix) || !strings.HasSuffix(name, configFileUnencryptedSuffix) {
+ continue
}
- return
+ if !file.Type().IsRegular() {
+ continue
+ }
+ info, err := file.Info()
+ if err != nil {
+ continue
+ }
+ if info.Mode().Perm()&0o444 == 0 {
+ continue
+ }
+
+ var bytes []byte
+ var config *Config
+ var newPath string
+ // We don't use os.ReadFile, because we actually want RDWR, so that we can take advantage
+ // of Windows file locking for ensuring the file is finished being written.
+ f, err := os.OpenFile(path, os.O_RDWR, 0)
+ if err != nil {
+ if errors.Is(err, windows.ERROR_SHARING_VIOLATION) {
+ if ignoreSharingViolations {
+ continue
+ } else if sharingBase > 0 {
+ if lastMigrationTimer != nil {
+ lastMigrationTimer.Stop()
+ }
+ lastMigrationTimer = time.AfterFunc(time.Second/time.Duration(sharingBase*sharingBase), func() { migrateUnencryptedConfigs(sharingBase-1, migrated) })
+ ignoreSharingViolations = true
+ continue
+ }
+ }
+ goto error
+ }
+ bytes, err = io.ReadAll(f)
+ f.Close()
+ if err != nil {
+ goto error
+ }
+ config, err = FromWgQuickWithUnknownEncoding(string(bytes), strings.TrimSuffix(name, configFileUnencryptedSuffix))
+ if err != nil {
+ goto error
+ }
+ err = config.Save(false)
+ if err != nil {
+ goto error
+ }
+ err = os.Remove(path)
+ if err != nil {
+ goto error
+ }
+ newPath, err = config.Path()
+ if err != nil {
+ goto error
+ }
+ migrated(config.Name, path, newPath)
+ continue
+ error:
+ log.Printf("Unable to ingest and encrypt %#q: %v", path, err)
}
- log.Printf("Migrated configuration from ‘%s’", oldRoot)
}
diff --git a/conf/mksyscall.go b/conf/mksyscall.go
deleted file mode 100644
index 2706c304..00000000
--- a/conf/mksyscall.go
+++ /dev/null
@@ -1,8 +0,0 @@
-/* SPDX-License-Identifier: MIT
- *
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
- */
-
-package conf
-
-//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go dnsresolver_windows.go migration_windows.go storewatcher_windows.go
diff --git a/conf/name.go b/conf/name.go
index 87c463af..0d084070 100644
--- a/conf/name.go
+++ b/conf/name.go
@@ -1,11 +1,12 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package conf
import (
+ "errors"
"regexp"
"strconv"
"strings"
@@ -17,15 +18,13 @@ var reservedNames = []string{
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
}
-const serviceNameForbidden = "$"
-const netshellDllForbidden = "\\/:*?\"<>|\t"
-const specialChars = "/\\<>:\"|?*\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x00"
-
-var allowedNameFormat *regexp.Regexp
+const (
+ serviceNameForbidden = "$"
+ netshellDllForbidden = "\\/:*?\"<>|\t"
+ specialChars = "/\\<>:\"|?*\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x00"
+)
-func init() {
- allowedNameFormat = regexp.MustCompile("^[a-zA-Z0-9_=+.-]{1,32}$")
-}
+var allowedNameFormat = regexp.MustCompile("^[a-zA-Z0-9_=+.-]{1,32}$")
func isReserved(name string) bool {
if len(name) == 0 {
@@ -35,6 +34,14 @@ func isReserved(name string) bool {
if strings.EqualFold(name, reserved) {
return true
}
+ for i := len(name) - 1; i >= 0; i-- {
+ if name[i] == '.' {
+ if strings.EqualFold(name[:i], reserved) {
+ return true
+ }
+ break
+ }
+ }
}
return false
}
@@ -55,6 +62,7 @@ type naturalSortToken struct {
maybeString string
maybeNumber int
}
+
type naturalSortString struct {
originalString string
tokens []naturalSortToken
@@ -110,3 +118,10 @@ func TunnelNameIsLess(a, b string) bool {
}
return false
}
+
+func ServiceNameOfTunnel(tunnelName string) (string, error) {
+ if !TunnelNameIsValid(tunnelName) {
+ return "", errors.New("Tunnel name is not valid")
+ }
+ return "WireGuardTunnel$" + tunnelName, nil
+}
diff --git a/conf/parser.go b/conf/parser.go
index 3f64677b..b1da9816 100644
--- a/conf/parser.go
+++ b/conf/parser.go
@@ -1,20 +1,21 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package conf
import (
"encoding/base64"
- "encoding/hex"
- "fmt"
- "net"
+ "net/netip"
"strconv"
"strings"
- "time"
+ "golang.org/x/sys/windows"
"golang.org/x/text/encoding/unicode"
+
+ "golang.zx2c4.com/wireguard/windows/driver"
+ "golang.zx2c4.com/wireguard/windows/l18n"
)
type ParseError struct {
@@ -23,56 +24,29 @@ type ParseError struct {
}
func (e *ParseError) Error() string {
- return fmt.Sprintf("%s: %q", e.why, e.offender)
+ return l18n.Sprintf("%s: %q", e.why, e.offender)
}
-func parseIPCidr(s string) (ipcidr *IPCidr, err error) {
- var addrStr, cidrStr string
- var cidr int
-
- i := strings.IndexByte(s, '/')
- if i < 0 {
- addrStr = s
- } else {
- addrStr, cidrStr = s[:i], s[i+1:]
+func parseIPCidr(s string) (netip.Prefix, error) {
+ ipcidr, err := netip.ParsePrefix(s)
+ if err == nil {
+ return ipcidr, nil
}
-
- err = &ParseError{"Invalid IP address", s}
- addr := net.ParseIP(addrStr)
- if addr == nil {
- return
- }
- maybeV4 := addr.To4()
- if maybeV4 != nil {
- addr = maybeV4
- }
- if len(cidrStr) > 0 {
- err = &ParseError{"Invalid network prefix length", s}
- cidr, err = strconv.Atoi(cidrStr)
- if err != nil || cidr < 0 || cidr > 128 {
- return
- }
- if cidr > 32 && maybeV4 != nil {
- return
- }
- } else {
- if maybeV4 != nil {
- cidr = 32
- } else {
- cidr = 128
- }
+ addr, err := netip.ParseAddr(s)
+ if err != nil {
+ return netip.Prefix{}, &ParseError{l18n.Sprintf("Invalid IP address: "), s}
}
- return &IPCidr{addr, uint8(cidr)}, nil
+ return netip.PrefixFrom(addr, addr.BitLen()), nil
}
func parseEndpoint(s string) (*Endpoint, error) {
i := strings.LastIndexByte(s, ':')
if i < 0 {
- return nil, &ParseError{"Missing port from endpoint", s}
+ return nil, &ParseError{l18n.Sprintf("Missing port from endpoint"), s}
}
host, portStr := s[:i], s[i+1:]
if len(host) < 1 {
- return nil, &ParseError{"Invalid endpoint host", host}
+ return nil, &ParseError{l18n.Sprintf("Invalid endpoint host"), host}
}
port, err := parsePort(portStr)
if err != nil {
@@ -80,14 +54,14 @@ func parseEndpoint(s string) (*Endpoint, error) {
}
hostColon := strings.IndexByte(host, ':')
if host[0] == '[' || host[len(host)-1] == ']' || hostColon > 0 {
- err := &ParseError{"Brackets must contain an IPv6 address", host}
+ err := &ParseError{l18n.Sprintf("Brackets must contain an IPv6 address"), host}
if len(host) > 3 && host[0] == '[' && host[len(host)-1] == ']' && hostColon > 0 {
end := len(host) - 1
if i := strings.LastIndexByte(host, '%'); i > 1 {
end = i
}
- maybeV6 := net.ParseIP(host[1:end])
- if maybeV6 == nil || len(maybeV6) != net.IPv6len {
+ maybeV6, err2 := netip.ParseAddr(host[1:end])
+ if err2 != nil || !maybeV6.Is6() {
return nil, err
}
} else {
@@ -95,7 +69,7 @@ func parseEndpoint(s string) (*Endpoint, error) {
}
host = host[1 : len(host)-1]
}
- return &Endpoint{host, uint16(port)}, nil
+ return &Endpoint{host, port}, nil
}
func parseMTU(s string) (uint16, error) {
@@ -104,7 +78,7 @@ func parseMTU(s string) (uint16, error) {
return 0, err
}
if m < 576 || m > 65535 {
- return 0, &ParseError{"Invalid MTU", s}
+ return 0, &ParseError{l18n.Sprintf("Invalid MTU"), s}
}
return uint16(m), nil
}
@@ -115,7 +89,7 @@ func parsePort(s string) (uint16, error) {
return 0, err
}
if m < 0 || m > 65535 {
- return 0, &ParseError{"Invalid port", s}
+ return 0, &ParseError{l18n.Sprintf("Invalid port"), s}
}
return uint16(m), nil
}
@@ -129,51 +103,40 @@ func parsePersistentKeepalive(s string) (uint16, error) {
return 0, err
}
if m < 0 || m > 65535 {
- return 0, &ParseError{"Invalid persistent keepalive", s}
+ return 0, &ParseError{l18n.Sprintf("Invalid persistent keepalive"), s}
}
return uint16(m), nil
}
-func parseKeyBase64(s string) (*Key, error) {
- k, err := base64.StdEncoding.DecodeString(s)
- if err != nil {
- return nil, &ParseError{"Invalid key: " + err.Error(), s}
- }
- if len(k) != KeyLength {
- return nil, &ParseError{"Keys must decode to exactly 32 bytes", s}
+func parseTableOff(s string) (bool, error) {
+ if s == "off" {
+ return true, nil
+ } else if s == "auto" || s == "main" {
+ return false, nil
}
- var key Key
- copy(key[:], k)
- return &key, nil
+ _, err := strconv.ParseUint(s, 10, 32)
+ return false, err
}
-func parseKeyHex(s string) (*Key, error) {
- k, err := hex.DecodeString(s)
+func parseKeyBase64(s string) (*Key, error) {
+ k, err := base64.StdEncoding.DecodeString(s)
if err != nil {
- return nil, &ParseError{"Invalid key: " + err.Error(), s}
+ return nil, &ParseError{l18n.Sprintf("Invalid key: %v", err), s}
}
if len(k) != KeyLength {
- return nil, &ParseError{"Keys must decode to exactly 32 bytes", s}
+ return nil, &ParseError{l18n.Sprintf("Keys must decode to exactly 32 bytes"), s}
}
var key Key
copy(key[:], k)
return &key, nil
}
-func parseBytesOrStamp(s string) (uint64, error) {
- b, err := strconv.ParseUint(s, 10, 64)
- if err != nil {
- return 0, &ParseError{"Number must be a number between 0 and 2^64-1: " + err.Error(), s}
- }
- return b, nil
-}
-
func splitList(s string) ([]string, error) {
var out []string
for _, split := range strings.Split(s, ",") {
trim := strings.TrimSpace(split)
if len(trim) == 0 {
- return nil, &ParseError{"Two commas in a row", s}
+ return nil, &ParseError{l18n.Sprintf("Two commas in a row"), s}
}
out = append(out, trim)
}
@@ -194,9 +157,9 @@ func (c *Config) maybeAddPeer(p *Peer) {
}
}
-func FromWgQuick(s string, name string) (*Config, error) {
+func FromWgQuick(s, name string) (*Config, error) {
if !TunnelNameIsValid(name) {
- return nil, &ParseError{"Tunnel name is not valid", name}
+ return nil, &ParseError{l18n.Sprintf("Tunnel name is not valid"), name}
}
lines := strings.Split(s, "\n")
parserState := notInASection
@@ -204,10 +167,7 @@ func FromWgQuick(s string, name string) (*Config, error) {
sawPrivateKey := false
var peer *Peer
for _, line := range lines {
- pound := strings.IndexByte(line, '#')
- if pound >= 0 {
- line = line[:pound]
- }
+ line, _, _ = strings.Cut(line, "#")
line = strings.TrimSpace(line)
lineLower := strings.ToLower(line)
if len(line) == 0 {
@@ -225,15 +185,15 @@ func FromWgQuick(s string, name string) (*Config, error) {
continue
}
if parserState == notInASection {
- return nil, &ParseError{"Line must occur in a section", line}
+ return nil, &ParseError{l18n.Sprintf("Line must occur in a section"), line}
}
equals := strings.IndexByte(line, '=')
if equals < 0 {
- return nil, &ParseError{"Invalid config key is missing an equals separator", line}
+ return nil, &ParseError{l18n.Sprintf("Config key is missing an equals separator"), line}
}
key, val := strings.TrimSpace(lineLower[:equals]), strings.TrimSpace(line[equals+1:])
if len(val) == 0 {
- return nil, &ParseError{"Key must have a value", line}
+ return nil, &ParseError{l18n.Sprintf("Key must have a value"), line}
}
if parserState == inInterfaceSection {
switch key {
@@ -266,7 +226,7 @@ func FromWgQuick(s string, name string) (*Config, error) {
if err != nil {
return nil, err
}
- conf.Interface.Addresses = append(conf.Interface.Addresses, *a)
+ conf.Interface.Addresses = append(conf.Interface.Addresses, a)
}
case "dns":
addresses, err := splitList(val)
@@ -274,14 +234,29 @@ func FromWgQuick(s string, name string) (*Config, error) {
return nil, err
}
for _, address := range addresses {
- a := net.ParseIP(address)
- if a == nil {
- return nil, &ParseError{"Invalid IP address", address}
+ a, err := netip.ParseAddr(address)
+ if err != nil {
+ conf.Interface.DNSSearch = append(conf.Interface.DNSSearch, address)
+ } else {
+ conf.Interface.DNS = append(conf.Interface.DNS, a)
}
- conf.Interface.DNS = append(conf.Interface.DNS, a)
}
+ case "preup":
+ conf.Interface.PreUp = val
+ case "postup":
+ conf.Interface.PostUp = val
+ case "predown":
+ conf.Interface.PreDown = val
+ case "postdown":
+ conf.Interface.PostDown = val
+ case "table":
+ tableOff, err := parseTableOff(val)
+ if err != nil {
+ return nil, err
+ }
+ conf.Interface.TableOff = tableOff
default:
- return nil, &ParseError{"Invalid key for [Interface] section", key}
+ return nil, &ParseError{l18n.Sprintf("Invalid key for [Interface] section"), key}
}
} else if parserState == inPeerSection {
switch key {
@@ -307,7 +282,7 @@ func FromWgQuick(s string, name string) (*Config, error) {
if err != nil {
return nil, err
}
- peer.AllowedIPs = append(peer.AllowedIPs, *a)
+ peer.AllowedIPs = append(peer.AllowedIPs, a)
}
case "persistentkeepalive":
p, err := parsePersistentKeepalive(val)
@@ -322,25 +297,25 @@ func FromWgQuick(s string, name string) (*Config, error) {
}
peer.Endpoint = *e
default:
- return nil, &ParseError{"Invalid key for [Peer] section", key}
+ return nil, &ParseError{l18n.Sprintf("Invalid key for [Peer] section"), key}
}
}
}
conf.maybeAddPeer(peer)
if !sawPrivateKey {
- return nil, &ParseError{"An interface must have a private key", "[none specified]"}
+ return nil, &ParseError{l18n.Sprintf("An interface must have a private key"), l18n.Sprintf("[none specified]")}
}
for _, p := range conf.Peers {
if p.PublicKey.IsZero() {
- return nil, &ParseError{"All peers must have public keys", "[none specified]"}
+ return nil, &ParseError{l18n.Sprintf("All peers must have public keys"), l18n.Sprintf("[none specified]")}
}
}
return &conf, nil
}
-func FromWgQuickWithUnknownEncoding(s string, name string) (*Config, error) {
+func FromWgQuickWithUnknownEncoding(s, name string) (*Config, error) {
c, firstErr := FromWgQuick(s, name)
if firstErr == nil {
return c, nil
@@ -357,128 +332,69 @@ func FromWgQuickWithUnknownEncoding(s string, name string) (*Config, error) {
return nil, firstErr
}
-func FromUAPI(s string, existingConfig *Config) (*Config, error) {
- lines := strings.Split(s, "\n")
- parserState := inInterfaceSection
+func FromDriverConfiguration(interfaze *driver.Interface, existingConfig *Config) *Config {
conf := Config{
Name: existingConfig.Name,
Interface: Interface{
Addresses: existingConfig.Interface.Addresses,
DNS: existingConfig.Interface.DNS,
+ DNSSearch: existingConfig.Interface.DNSSearch,
MTU: existingConfig.Interface.MTU,
+ PreUp: existingConfig.Interface.PreUp,
+ PostUp: existingConfig.Interface.PostUp,
+ PreDown: existingConfig.Interface.PreDown,
+ PostDown: existingConfig.Interface.PostDown,
+ TableOff: existingConfig.Interface.TableOff,
},
}
- var peer *Peer
- for _, line := range lines {
- if len(line) == 0 {
- continue
+ if interfaze.Flags&driver.InterfaceHasPrivateKey != 0 {
+ conf.Interface.PrivateKey = interfaze.PrivateKey
+ }
+ if interfaze.Flags&driver.InterfaceHasListenPort != 0 {
+ conf.Interface.ListenPort = interfaze.ListenPort
+ }
+ var p *driver.Peer
+ for i := uint32(0); i < interfaze.PeerCount; i++ {
+ if p == nil {
+ p = interfaze.FirstPeer()
+ } else {
+ p = p.NextPeer()
}
- equals := strings.IndexByte(line, '=')
- if equals < 0 {
- return nil, &ParseError{"Invalid config key is missing an equals separator", line}
+ peer := Peer{}
+ if p.Flags&driver.PeerHasPublicKey != 0 {
+ peer.PublicKey = p.PublicKey
}
- key, val := line[:equals], line[equals+1:]
- if len(val) == 0 {
- return nil, &ParseError{"Key must have a value", line}
+ if p.Flags&driver.PeerHasPresharedKey != 0 {
+ peer.PresharedKey = p.PresharedKey
}
- switch key {
- case "public_key":
- conf.maybeAddPeer(peer)
- peer = &Peer{}
- parserState = inPeerSection
- case "errno":
- if val == "0" {
- continue
- } else {
- return nil, &ParseError{"Error in getting configuration", val}
- }
+ if p.Flags&driver.PeerHasEndpoint != 0 {
+ peer.Endpoint.Port = p.Endpoint.Port()
+ peer.Endpoint.Host = p.Endpoint.Addr().String()
}
- if parserState == inInterfaceSection {
- switch key {
- case "private_key":
- k, err := parseKeyHex(val)
- if err != nil {
- return nil, err
- }
- conf.Interface.PrivateKey = *k
- case "listen_port":
- p, err := parsePort(val)
- if err != nil {
- return nil, err
- }
- conf.Interface.ListenPort = p
- case "fwmark":
- // Ignored for now.
-
- default:
- return nil, &ParseError{"Invalid key for interface section", key}
+ if p.Flags&driver.PeerHasPersistentKeepalive != 0 {
+ peer.PersistentKeepalive = p.PersistentKeepalive
+ }
+ peer.TxBytes = Bytes(p.TxBytes)
+ peer.RxBytes = Bytes(p.RxBytes)
+ if p.LastHandshake != 0 {
+ peer.LastHandshakeTime = HandshakeTime((p.LastHandshake - 116444736000000000) * 100)
+ }
+ var a *driver.AllowedIP
+ for j := uint32(0); j < p.AllowedIPsCount; j++ {
+ if a == nil {
+ a = p.FirstAllowedIP()
+ } else {
+ a = a.NextAllowedIP()
}
- } else if parserState == inPeerSection {
- switch key {
- case "public_key":
- k, err := parseKeyHex(val)
- if err != nil {
- return nil, err
- }
- peer.PublicKey = *k
- case "preshared_key":
- k, err := parseKeyHex(val)
- if err != nil {
- return nil, err
- }
- peer.PresharedKey = *k
- case "protocol_version":
- if val != "1" {
- return nil, &ParseError{"Protocol version must be 1", val}
- }
- case "allowed_ip":
- a, err := parseIPCidr(val)
- if err != nil {
- return nil, err
- }
- peer.AllowedIPs = append(peer.AllowedIPs, *a)
- case "persistent_keepalive_interval":
- p, err := parsePersistentKeepalive(val)
- if err != nil {
- return nil, err
- }
- peer.PersistentKeepalive = p
- case "endpoint":
- e, err := parseEndpoint(val)
- if err != nil {
- return nil, err
- }
- peer.Endpoint = *e
- case "tx_bytes":
- b, err := parseBytesOrStamp(val)
- if err != nil {
- return nil, err
- }
- peer.TxBytes = Bytes(b)
- case "rx_bytes":
- b, err := parseBytesOrStamp(val)
- if err != nil {
- return nil, err
- }
- peer.RxBytes = Bytes(b)
- case "last_handshake_time_sec":
- t, err := parseBytesOrStamp(val)
- if err != nil {
- return nil, err
- }
- peer.LastHandshakeTime += HandshakeTime(time.Duration(t) * time.Second)
- case "last_handshake_time_nsec":
- t, err := parseBytesOrStamp(val)
- if err != nil {
- return nil, err
- }
- peer.LastHandshakeTime += HandshakeTime(time.Duration(t) * time.Nanosecond)
- default:
- return nil, &ParseError{"Invalid key for peer section", key}
+ var ip netip.Addr
+ if a.AddressFamily == windows.AF_INET {
+ ip = netip.AddrFrom4(*(*[4]byte)(a.Address[:4]))
+ } else if a.AddressFamily == windows.AF_INET6 {
+ ip = netip.AddrFrom16(*(*[16]byte)(a.Address[:16]))
}
+ peer.AllowedIPs = append(peer.AllowedIPs, netip.PrefixFrom(ip, int(a.Cidr)))
}
+ conf.Peers = append(conf.Peers, peer)
}
- conf.maybeAddPeer(peer)
-
- return &conf, nil
+ return &conf
}
diff --git a/conf/parser_test.go b/conf/parser_test.go
index a6afbf53..25d906fd 100644
--- a/conf/parser_test.go
+++ b/conf/parser_test.go
@@ -1,12 +1,12 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package conf
import (
- "net"
+ "net/netip"
"reflect"
"runtime"
"testing"
@@ -45,7 +45,7 @@ func noError(t *testing.T, err error) bool {
return false
}
-func equal(t *testing.T, expected, actual interface{}) bool {
+func equal(t *testing.T, expected, actual any) bool {
if reflect.DeepEqual(expected, actual) {
return true
}
@@ -53,7 +53,8 @@ func equal(t *testing.T, expected, actual interface{}) bool {
t.Errorf("Failed equals at %s:%d\nactual %#v\nexpected %#v", fn, line, actual, expected)
return false
}
-func lenTest(t *testing.T, actualO interface{}, expected int) bool {
+
+func lenTest(t *testing.T, actualO any, expected int) bool {
actual := reflect.ValueOf(actualO).Len()
if reflect.DeepEqual(expected, actual) {
return true
@@ -62,7 +63,8 @@ func lenTest(t *testing.T, actualO interface{}, expected int) bool {
t.Errorf("Wrong length at %s:%d\nactual %#v\nexpected %#v", fn, line, actual, expected)
return false
}
-func contains(t *testing.T, list, element interface{}) bool {
+
+func contains(t *testing.T, list, element any) bool {
listValue := reflect.ValueOf(list)
for i := 0; i < listValue.Len(); i++ {
if reflect.DeepEqual(listValue.Index(i).Interface(), element) {
@@ -77,10 +79,9 @@ func contains(t *testing.T, list, element interface{}) bool {
func TestFromWgQuick(t *testing.T) {
conf, err := FromWgQuick(testInput, "test")
if noError(t, err) {
-
lenTest(t, conf.Interface.Addresses, 2)
- contains(t, conf.Interface.Addresses, IPCidr{net.IPv4(10, 10, 0, 1), uint8(16)})
- contains(t, conf.Interface.Addresses, IPCidr{net.IPv4(10, 192, 122, 1), uint8(24)})
+ contains(t, conf.Interface.Addresses, netip.PrefixFrom(netip.AddrFrom4([4]byte{0, 10, 0, 1}), 16))
+ contains(t, conf.Interface.Addresses, netip.PrefixFrom(netip.AddrFrom4([4]byte{10, 192, 122, 1}), 24))
equal(t, "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=", conf.Interface.PrivateKey.String())
equal(t, uint16(51820), conf.Interface.ListenPort)
diff --git a/conf/path_windows.go b/conf/path_windows.go
index a53968c5..0ff0a057 100644
--- a/conf/path_windows.go
+++ b/conf/path_windows.go
@@ -1,33 +1,36 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package conf
import (
+ "errors"
"os"
"path/filepath"
+ "strings"
+ "unsafe"
"golang.org/x/sys/windows"
)
-var cachedConfigFileDir string
-var cachedRootDir string
-var disableAutoMigration bool
+var (
+ cachedConfigFileDir string
+ cachedRootDir string
+)
func tunnelConfigurationsDirectory() (string, error) {
if cachedConfigFileDir != "" {
return cachedConfigFileDir, nil
}
- root, err := RootDirectory()
+ root, err := RootDirectory(true)
if err != nil {
return "", err
}
c := filepath.Join(root, "Configurations")
- maybeMigrate(c)
- err = os.MkdirAll(c, os.ModeDir|0700)
- if err != nil {
+ err = os.Mkdir(c, os.ModeDir|0o700)
+ if err != nil && !os.IsExist(err) {
return "", err
}
cachedConfigFileDir = c
@@ -39,22 +42,97 @@ func tunnelConfigurationsDirectory() (string, error) {
// consumers of our libraries who might want to do strange things.
func PresetRootDirectory(root string) {
cachedRootDir = root
- disableAutoMigration = true
}
-func RootDirectory() (string, error) {
+func RootDirectory(create bool) (string, error) {
if cachedRootDir != "" {
return cachedRootDir, nil
}
- root, err := windows.KnownFolderPath(windows.FOLDERID_LocalAppData, windows.KF_FLAG_CREATE)
+ root, err := windows.KnownFolderPath(windows.FOLDERID_ProgramFiles, windows.KF_FLAG_DEFAULT)
+ if err != nil {
+ return "", err
+ }
+ root = filepath.Join(root, "WireGuard")
+ if !create {
+ return filepath.Join(root, "Data"), nil
+ }
+ root16, err := windows.UTF16PtrFromString(root)
+ if err != nil {
+ return "", err
+ }
+
+ // The root directory inherits its ACL from Program Files; we don't want to mess with that
+ err = windows.CreateDirectory(root16, nil)
+ if err != nil && err != windows.ERROR_ALREADY_EXISTS {
+ return "", err
+ }
+
+ dataDirectorySd, err := windows.SecurityDescriptorFromString("O:SYG:SYD:PAI(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)")
if err != nil {
return "", err
}
- c := filepath.Join(root, "WireGuard")
- err = os.MkdirAll(c, os.ModeDir|0700)
+ dataDirectorySa := &windows.SecurityAttributes{
+ Length: uint32(unsafe.Sizeof(windows.SecurityAttributes{})),
+ SecurityDescriptor: dataDirectorySd,
+ }
+
+ data := filepath.Join(root, "Data")
+ data16, err := windows.UTF16PtrFromString(data)
if err != nil {
return "", err
}
- cachedRootDir = c
+ var dataHandle windows.Handle
+ for {
+ err = windows.CreateDirectory(data16, dataDirectorySa)
+ if err != nil && err != windows.ERROR_ALREADY_EXISTS {
+ return "", err
+ }
+ dataHandle, err = windows.CreateFile(data16, windows.READ_CONTROL|windows.WRITE_OWNER|windows.WRITE_DAC, windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE, nil, windows.OPEN_EXISTING, windows.FILE_FLAG_BACKUP_SEMANTICS|windows.FILE_FLAG_OPEN_REPARSE_POINT|windows.FILE_ATTRIBUTE_DIRECTORY, 0)
+ if err != nil && err != windows.ERROR_FILE_NOT_FOUND {
+ return "", err
+ }
+ if err == nil {
+ break
+ }
+ }
+ defer windows.CloseHandle(dataHandle)
+ var fileInfo windows.ByHandleFileInformation
+ err = windows.GetFileInformationByHandle(dataHandle, &fileInfo)
+ if err != nil {
+ return "", err
+ }
+ if fileInfo.FileAttributes&windows.FILE_ATTRIBUTE_DIRECTORY == 0 {
+ return "", errors.New("Data directory is actually a file")
+ }
+ if fileInfo.FileAttributes&windows.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
+ return "", errors.New("Data directory is reparse point")
+ }
+ buf := make([]uint16, windows.MAX_PATH+4)
+ for {
+ bufLen, err := windows.GetFinalPathNameByHandle(dataHandle, &buf[0], uint32(len(buf)), 0)
+ if err != nil {
+ return "", err
+ }
+ if bufLen < uint32(len(buf)) {
+ break
+ }
+ buf = make([]uint16, bufLen)
+ }
+ if !strings.EqualFold(`\\?\`+data, windows.UTF16ToString(buf[:])) {
+ return "", errors.New("Data directory jumped to unexpected location")
+ }
+ err = windows.SetKernelObjectSecurity(dataHandle, windows.DACL_SECURITY_INFORMATION|windows.GROUP_SECURITY_INFORMATION|windows.OWNER_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION, dataDirectorySd)
+ if err != nil {
+ return "", err
+ }
+ cachedRootDir = data
return cachedRootDir, nil
}
+
+func LogFile(createRoot bool) (string, error) {
+ root, err := RootDirectory(createRoot)
+ if err != nil {
+ return "", err
+ }
+ return filepath.Join(root, "log.bin"), nil
+}
diff --git a/conf/store.go b/conf/store.go
index 21bd3a22..02807b77 100644
--- a/conf/store.go
+++ b/conf/store.go
@@ -1,13 +1,12 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package conf
import (
"errors"
- "io/ioutil"
"os"
"path/filepath"
"strings"
@@ -15,109 +14,41 @@ import (
"golang.zx2c4.com/wireguard/windows/conf/dpapi"
)
-const configFileSuffix = ".conf.dpapi"
-const configFileUnencryptedSuffix = ".conf"
+const (
+ configFileSuffix = ".conf.dpapi"
+ configFileUnencryptedSuffix = ".conf"
+)
func ListConfigNames() ([]string, error) {
configFileDir, err := tunnelConfigurationsDirectory()
if err != nil {
return nil, err
}
- files, err := ioutil.ReadDir(configFileDir)
+ files, err := os.ReadDir(configFileDir)
if err != nil {
return nil, err
}
configs := make([]string, len(files))
i := 0
for _, file := range files {
- name := filepath.Base(file.Name())
- if len(name) <= len(configFileSuffix) || !strings.HasSuffix(name, configFileSuffix) {
- continue
- }
- if !file.Mode().IsRegular() || file.Mode().Perm()&0444 == 0 {
- continue
- }
- name = strings.TrimSuffix(name, configFileSuffix)
- if !TunnelNameIsValid(name) {
- continue
- }
- configs[i] = name
- i++
- }
- return configs[:i], nil
-}
-
-func MigrateUnencryptedConfigs() (int, []error) {
- configFileDir, err := tunnelConfigurationsDirectory()
- if err != nil {
- return 0, []error{err}
- }
- files, err := ioutil.ReadDir(configFileDir)
- if err != nil {
- return 0, []error{err}
- }
- errs := make([]error, len(files))
- i := 0
- e := 0
- for _, file := range files {
- path := filepath.Join(configFileDir, file.Name())
- name := filepath.Base(file.Name())
- if len(name) <= len(configFileUnencryptedSuffix) || !strings.HasSuffix(name, configFileUnencryptedSuffix) {
- continue
- }
- if !file.Mode().IsRegular() || file.Mode().Perm()&0444 == 0 {
- continue
- }
-
- // We don't use ioutil's ReadFile, because we actually want RDWR, so that we can take advantage
- // of Windows file locking for ensuring the file is finished being written.
- f, err := os.OpenFile(path, os.O_RDWR, 0)
- if err != nil {
- errs[e] = err
- e++
- continue
- }
- bytes, err := ioutil.ReadAll(f)
- f.Close()
+ name, err := NameFromPath(file.Name())
if err != nil {
- errs[e] = err
- e++
continue
}
- _, err = FromWgQuickWithUnknownEncoding(string(bytes), "input")
- if err != nil {
- errs[e] = err
- e++
+ if !file.Type().IsRegular() {
continue
}
-
- bytes, err = dpapi.Encrypt(bytes, strings.TrimSuffix(name, configFileUnencryptedSuffix))
+ info, err := file.Info()
if err != nil {
- errs[e] = err
- e++
- continue
- }
- dstFile := strings.TrimSuffix(path, configFileUnencryptedSuffix) + configFileSuffix
- if _, err = os.Stat(dstFile); err != nil && !os.IsNotExist(err) {
- errs[e] = errors.New("Unable to migrate to " + dstFile + " as it already exists")
- e++
continue
}
- err = ioutil.WriteFile(dstFile, bytes, 0600)
- if err != nil {
- errs[e] = err
- e++
- continue
- }
- err = os.Remove(path)
- if err != nil && os.Remove(dstFile) == nil {
- errs[e] = err
- e++
+ if info.Mode().Perm()&0o444 == 0 {
continue
}
+ configs[i] = name
i++
}
- return i, errs[:e]
+ return configs[:i], nil
}
func LoadFromName(name string) (*Config, error) {
@@ -129,15 +60,11 @@ func LoadFromName(name string) (*Config, error) {
}
func LoadFromPath(path string) (*Config, error) {
- if !disableAutoMigration {
- tunnelConfigurationsDirectory() // Provoke migrations, if needed.
- }
-
name, err := NameFromPath(path)
if err != nil {
return nil, err
}
- bytes, err := ioutil.ReadFile(path)
+ bytes, err := os.ReadFile(path)
if err != nil {
return nil, err
}
@@ -171,7 +98,7 @@ func NameFromPath(path string) (string, error) {
return name, nil
}
-func (config *Config) Save() error {
+func (config *Config) Save(overwrite bool) error {
if !TunnelNameIsValid(config.Name) {
return errors.New("Tunnel name is not valid")
}
@@ -185,16 +112,7 @@ func (config *Config) Save() error {
if err != nil {
return err
}
- err = ioutil.WriteFile(filename+".tmp", bytes, 0600)
- if err != nil {
- return err
- }
- err = os.Rename(filename+".tmp", filename)
- if err != nil {
- os.Remove(filename + ".tmp")
- return err
- }
- return nil
+ return writeLockedDownFile(filename, overwrite, bytes)
}
func (config *Config) Path() (string, error) {
diff --git a/conf/store_test.go b/conf/store_test.go
index fdef7ea7..3427a2b1 100644
--- a/conf/store_test.go
+++ b/conf/store_test.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package conf
@@ -17,7 +17,7 @@ func TestStorage(t *testing.T) {
return
}
- err = c.Save()
+ err = c.Save(true)
if err != nil {
t.Errorf("Unable to save config: %s", err.Error())
}
@@ -54,7 +54,11 @@ func TestStorage(t *testing.T) {
}
c.Interface.PrivateKey = *k
- err = c.Save()
+ err = c.Save(false)
+ if err == nil {
+ t.Error("Config disappeared or was unexpectedly overwritten")
+ }
+ err = c.Save(true)
if err != nil {
t.Errorf("Unable to save config a second time: %s", err.Error())
}
diff --git a/conf/storewatcher.go b/conf/storewatcher.go
index ffd20ee0..70a44add 100644
--- a/conf/storewatcher.go
+++ b/conf/storewatcher.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package conf
diff --git a/conf/storewatcher_windows.go b/conf/storewatcher_windows.go
index 19956263..0c4b74e7 100644
--- a/conf/storewatcher_windows.go
+++ b/conf/storewatcher_windows.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package conf
@@ -11,20 +11,6 @@ import (
"golang.org/x/sys/windows"
)
-const (
- fncFILE_NAME uint32 = 0x00000001
- fncDIR_NAME uint32 = 0x00000002
- fncATTRIBUTES uint32 = 0x00000004
- fncSIZE uint32 = 0x00000008
- fncLAST_WRITE uint32 = 0x00000010
- fncLAST_ACCESS uint32 = 0x00000020
- fncCREATION uint32 = 0x00000040
- fncSECURITY uint32 = 0x00000100
-)
-
-//sys findFirstChangeNotification(path *uint16, watchSubtree bool, filter uint32) (handle windows.Handle, err error) [failretval==windows.InvalidHandle] = kernel32.FindFirstChangeNotificationW
-//sys findNextChangeNotification(handle windows.Handle) (err error) = kernel32.FindNextChangeNotification
-
var haveStartedWatchingConfigDir bool
func startWatchingConfigDir() {
@@ -36,7 +22,7 @@ func startWatchingConfigDir() {
h := windows.InvalidHandle
defer func() {
if h != windows.InvalidHandle {
- windows.CloseHandle(h)
+ windows.FindCloseChangeNotification(h)
}
haveStartedWatchingConfigDir = false
}()
@@ -45,7 +31,7 @@ func startWatchingConfigDir() {
if err != nil {
return
}
- h, err = findFirstChangeNotification(windows.StringToUTF16Ptr(configFileDir), true, fncFILE_NAME|fncDIR_NAME|fncATTRIBUTES|fncSIZE|fncLAST_WRITE|fncLAST_ACCESS|fncCREATION|fncSECURITY)
+ h, err = windows.FindFirstChangeNotification(configFileDir, true, windows.FILE_NOTIFY_CHANGE_FILE_NAME|windows.FILE_NOTIFY_CHANGE_DIR_NAME|windows.FILE_NOTIFY_CHANGE_ATTRIBUTES|windows.FILE_NOTIFY_CHANGE_SIZE|windows.FILE_NOTIFY_CHANGE_LAST_WRITE|windows.FILE_NOTIFY_CHANGE_LAST_ACCESS|windows.FILE_NOTIFY_CHANGE_CREATION|windows.FILE_NOTIFY_CHANGE_SECURITY)
if err != nil {
log.Printf("Unable to monitor config directory: %v", err)
return
@@ -54,7 +40,7 @@ func startWatchingConfigDir() {
s, err := windows.WaitForSingleObject(h, windows.INFINITE)
if err != nil || s == windows.WAIT_FAILED {
log.Printf("Unable to wait on config directory watcher: %v", err)
- windows.CloseHandle(h)
+ windows.FindCloseChangeNotification(h)
h = windows.InvalidHandle
goto startover
}
@@ -63,10 +49,10 @@ func startWatchingConfigDir() {
cb.cb()
}
- err = findNextChangeNotification(h)
+ err = windows.FindNextChangeNotification(h)
if err != nil {
log.Printf("Unable to monitor config directory again: %v", err)
- windows.CloseHandle(h)
+ windows.FindCloseChangeNotification(h)
h = windows.InvalidHandle
goto startover
}
diff --git a/conf/writer.go b/conf/writer.go
index 748c1d61..162962b5 100644
--- a/conf/writer.go
+++ b/conf/writer.go
@@ -1,13 +1,19 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package conf
import (
"fmt"
+ "net/netip"
"strings"
+ "unsafe"
+
+ "golang.org/x/sys/windows"
+ "golang.zx2c4.com/wireguard/windows/driver"
+ "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
)
func (conf *Config) ToWgQuick() string {
@@ -28,11 +34,12 @@ func (conf *Config) ToWgQuick() string {
output.WriteString(fmt.Sprintf("Address = %s\n", strings.Join(addrStrings[:], ", ")))
}
- if len(conf.Interface.DNS) > 0 {
- addrStrings := make([]string, len(conf.Interface.DNS))
- for i, address := range conf.Interface.DNS {
- addrStrings[i] = address.String()
+ if len(conf.Interface.DNS)+len(conf.Interface.DNSSearch) > 0 {
+ addrStrings := make([]string, 0, len(conf.Interface.DNS)+len(conf.Interface.DNSSearch))
+ for _, address := range conf.Interface.DNS {
+ addrStrings = append(addrStrings, address.String())
}
+ addrStrings = append(addrStrings, conf.Interface.DNSSearch...)
output.WriteString(fmt.Sprintf("DNS = %s\n", strings.Join(addrStrings[:], ", ")))
}
@@ -40,6 +47,22 @@ func (conf *Config) ToWgQuick() string {
output.WriteString(fmt.Sprintf("MTU = %d\n", conf.Interface.MTU))
}
+ if len(conf.Interface.PreUp) > 0 {
+ output.WriteString(fmt.Sprintf("PreUp = %s\n", conf.Interface.PreUp))
+ }
+ if len(conf.Interface.PostUp) > 0 {
+ output.WriteString(fmt.Sprintf("PostUp = %s\n", conf.Interface.PostUp))
+ }
+ if len(conf.Interface.PreDown) > 0 {
+ output.WriteString(fmt.Sprintf("PreDown = %s\n", conf.Interface.PreDown))
+ }
+ if len(conf.Interface.PostDown) > 0 {
+ output.WriteString(fmt.Sprintf("PostDown = %s\n", conf.Interface.PostDown))
+ }
+ if conf.Interface.TableOff {
+ output.WriteString("Table = off\n")
+ }
+
for _, peer := range conf.Peers {
output.WriteString("\n[Peer]\n")
@@ -68,43 +91,50 @@ func (conf *Config) ToWgQuick() string {
return output.String()
}
-func (conf *Config) ToUAPI() (uapi string, dnsErr error) {
- var output strings.Builder
- output.WriteString(fmt.Sprintf("private_key=%s\n", conf.Interface.PrivateKey.HexString()))
-
- if conf.Interface.ListenPort > 0 {
- output.WriteString(fmt.Sprintf("listen_port=%d\n", conf.Interface.ListenPort))
- }
-
- if len(conf.Peers) > 0 {
- output.WriteString("replace_peers=true\n")
+func (config *Config) ToDriverConfiguration() (*driver.Interface, uint32) {
+ preallocation := unsafe.Sizeof(driver.Interface{}) + uintptr(len(config.Peers))*unsafe.Sizeof(driver.Peer{})
+ for i := range config.Peers {
+ preallocation += uintptr(len(config.Peers[i].AllowedIPs)) * unsafe.Sizeof(driver.AllowedIP{})
}
-
- for _, peer := range conf.Peers {
- output.WriteString(fmt.Sprintf("public_key=%s\n", peer.PublicKey.HexString()))
-
- if !peer.PresharedKey.IsZero() {
- output.WriteString(fmt.Sprintf("preshared_key=%s\n", peer.PresharedKey.HexString()))
+ var c driver.ConfigBuilder
+ c.Preallocate(uint32(preallocation))
+ c.AppendInterface(&driver.Interface{
+ Flags: driver.InterfaceHasPrivateKey | driver.InterfaceHasListenPort,
+ ListenPort: config.Interface.ListenPort,
+ PrivateKey: config.Interface.PrivateKey,
+ PeerCount: uint32(len(config.Peers)),
+ })
+ for i := range config.Peers {
+ flags := driver.PeerHasPublicKey | driver.PeerHasPersistentKeepalive
+ if !config.Peers[i].PresharedKey.IsZero() {
+ flags |= driver.PeerHasPresharedKey
}
-
- if !peer.Endpoint.IsEmpty() {
- var resolvedIP string
- resolvedIP, dnsErr = resolveHostname(peer.Endpoint.Host)
- if dnsErr != nil {
- return
+ var endpoint winipcfg.RawSockaddrInet
+ if !config.Peers[i].Endpoint.IsEmpty() {
+ addr, err := netip.ParseAddr(config.Peers[i].Endpoint.Host)
+ if err == nil {
+ flags |= driver.PeerHasEndpoint
+ endpoint.SetAddrPort(netip.AddrPortFrom(addr, config.Peers[i].Endpoint.Port))
}
- resolvedEndpoint := Endpoint{resolvedIP, peer.Endpoint.Port}
- output.WriteString(fmt.Sprintf("endpoint=%s\n", resolvedEndpoint.String()))
}
-
- output.WriteString(fmt.Sprintf("persistent_keepalive_interval=%d\n", peer.PersistentKeepalive))
-
- if len(peer.AllowedIPs) > 0 {
- output.WriteString("replace_allowed_ips=true\n")
- for _, address := range peer.AllowedIPs {
- output.WriteString(fmt.Sprintf("allowed_ip=%s\n", address.String()))
+ c.AppendPeer(&driver.Peer{
+ Flags: flags,
+ PublicKey: config.Peers[i].PublicKey,
+ PresharedKey: config.Peers[i].PresharedKey,
+ PersistentKeepalive: config.Peers[i].PersistentKeepalive,
+ Endpoint: endpoint,
+ AllowedIPsCount: uint32(len(config.Peers[i].AllowedIPs)),
+ })
+ for j := range config.Peers[i].AllowedIPs {
+ a := &driver.AllowedIP{Cidr: uint8(config.Peers[i].AllowedIPs[j].Bits())}
+ copy(a.Address[:], config.Peers[i].AllowedIPs[j].Addr().AsSlice())
+ if config.Peers[i].AllowedIPs[j].Addr().Is4() {
+ a.AddressFamily = windows.AF_INET
+ } else if config.Peers[i].AllowedIPs[j].Addr().Is6() {
+ a.AddressFamily = windows.AF_INET6
}
+ c.AppendAllowedIP(a)
}
}
- return output.String(), nil
+ return c.Interface()
}
diff --git a/conf/zsyscall_windows.go b/conf/zsyscall_windows.go
deleted file mode 100644
index 9dcf68fe..00000000
--- a/conf/zsyscall_windows.go
+++ /dev/null
@@ -1,83 +0,0 @@
-// Code generated by 'go generate'; DO NOT EDIT.
-
-package conf
-
-import (
- "syscall"
- "unsafe"
-
- "golang.org/x/sys/windows"
-)
-
-var _ unsafe.Pointer
-
-// Do the interface allocations only once for common
-// Errno values.
-const (
- errnoERROR_IO_PENDING = 997
-)
-
-var (
- errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
-)
-
-// errnoErr returns common boxed Errno values, to prevent
-// allocations at runtime.
-func errnoErr(e syscall.Errno) error {
- switch e {
- case 0:
- return nil
- case errnoERROR_IO_PENDING:
- return errERROR_IO_PENDING
- }
- // TODO: add more here, after collecting data on the common
- // error values see on Windows. (perhaps when running
- // all.bat?)
- return e
-}
-
-var (
- modwininet = windows.NewLazySystemDLL("wininet.dll")
- modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
-
- procInternetGetConnectedState = modwininet.NewProc("InternetGetConnectedState")
- procFindFirstChangeNotificationW = modkernel32.NewProc("FindFirstChangeNotificationW")
- procFindNextChangeNotification = modkernel32.NewProc("FindNextChangeNotification")
-)
-
-func internetGetConnectedState(flags *uint32, reserved uint32) (connected bool) {
- r0, _, _ := syscall.Syscall(procInternetGetConnectedState.Addr(), 2, uintptr(unsafe.Pointer(flags)), uintptr(reserved), 0)
- connected = r0 != 0
- return
-}
-
-func findFirstChangeNotification(path *uint16, watchSubtree bool, filter uint32) (handle windows.Handle, err error) {
- var _p0 uint32
- if watchSubtree {
- _p0 = 1
- } else {
- _p0 = 0
- }
- r0, _, e1 := syscall.Syscall(procFindFirstChangeNotificationW.Addr(), 3, uintptr(unsafe.Pointer(path)), uintptr(_p0), uintptr(filter))
- handle = windows.Handle(r0)
- if handle == windows.InvalidHandle {
- if e1 != 0 {
- err = errnoErr(e1)
- } else {
- err = syscall.EINVAL
- }
- }
- return
-}
-
-func findNextChangeNotification(handle windows.Handle) (err error) {
- r1, _, e1 := syscall.Syscall(procFindNextChangeNotification.Addr(), 1, uintptr(handle), 0, 0)
- if r1 == 0 {
- if e1 != 0 {
- err = errnoErr(e1)
- } else {
- err = syscall.EINVAL
- }
- }
- return
-}
diff --git a/docs/adminregistry.md b/docs/adminregistry.md
new file mode 100644
index 00000000..4289fb38
--- /dev/null
+++ b/docs/adminregistry.md
@@ -0,0 +1,39 @@
+# Registry Keys for Admins
+
+These are advanced configuration knobs that admins can set to do unusual things
+that are not recommended. There is no UI to enable these, and no such thing is
+planned. These registry keys may also be removed at some point in the future.
+The uninstaller will clean up the entirety of `HKLM\Software\WireGuard`. Use
+at your own risk, and please make sure you know what you're doing.
+
+#### `HKLM\Software\WireGuard\LimitedOperatorUI`
+
+When this key is set to `DWORD(1)`, the UI will be launched on desktops of
+users belonging to the Network Configuration Operators builtin group
+(S-1-5-32-556), with the following limitations for members of that group:
+
+ - Configurations are stripped of all public, private, and pre-shared keys;
+ - No version update popup notifications are shown, and updates are not permitted, though a tab still indicates the availability;
+ - Adding, removing, editing, importing, or exporting configurations is forbidden; and
+ - Quitting the manager is forbidden.
+
+However, basic functionality such as starting and stopping tunnels remains intact.
+
+```
+> reg add HKLM\Software\WireGuard /v LimitedOperatorUI /t REG_DWORD /d 1 /f
+```
+
+#### `HKLM\Software\WireGuard\DangerousScriptExecution`
+
+When this key is set to `DWORD(1)`, the tunnel service will execute the commands
+specified in the `PreUp`, `PostUp`, `PreDown`, and `PostDown` options of a
+tunnel configuration. Note that this execution is done as the Local System user,
+which runs with the highest permissions on the operating system, and is therefore
+a real target of malware. Therefore, you should enable this option only with the
+utmost trepidation. Rather than use `%i`, WireGuard for Windows instead sets the
+environment variable `WIREGUARD_TUNNEL_NAME` to the name of the tunnel when
+executing these scripts.
+
+```
+> reg add HKLM\Software\WireGuard /v DangerousScriptExecution /t REG_DWORD /d 1 /f
+```
diff --git a/attacksurface.md b/docs/attacksurface.md
index 24fdbe02..a495ba41 100644
--- a/attacksurface.md
+++ b/docs/attacksurface.md
@@ -1,25 +1,24 @@
-### WireGuard for Windows Attack Surface
+# Attack Surface
_This is an evolving document, describing currently known attack surface, a few mitigations, and several open questions. This is a work in progress. We document our current understanding with the intent of improving both our understanding and our security posture over time._
WireGuard for Windows consists of four components: a kernel driver, and three separate interacting userspace parts.
-#### Wintun
+### WireGuardNT
-Wintun is a kernel driver. It exposes:
+WireGuardNT is a kernel driver. It exposes:
- A miniport driver to the ndis stack, meaning any process on the system that can access the network stack in a reasonable way can send and receive packets, hitting those related ndis handlers.
+ - A UDP port parsing WireGuard packets.
- There are also various ndis OID calls, accessible to certain users, which hit further code.
- - IOCTLs are added to the NDIS device file, and those IOCTLs are restricted to `SDDL_DEVOBJ_SYS_ALL`. The IOCTL allows userspace to register a pair of rings and event objects, which Wintun then locks the pages of with a double mapping and takes a reference to the event object. It parses the contents of the ring to send and receive layer 3 packets, each of which it minimally parses to determine IP family.
+ - A PNP and Close notifier added to the NDIS device file.
+ - IOCTLs are added to the NDIS device file, and those IOCTLs are restricted to `O:SYD:P(A;;FA;;;SY)(A;;FA;;;BA)S:(ML;;NWNRNX;;;HI)`. The IOCTL allows userspace to get and set configuration, adapter state, and read log messages from a ring buffer.
### Tunnel Service
-The tunnel service is a userspace service running as Local System, responsible for creating UDP sockets, creating Wintun adapters, and speaking the WireGuard protocol between the two. It exposes:
+The tunnel service is a userspace service running as Local System, responsible for creating WireGuardNT adapters and configuring them. It exposes:
- - A listening pipe in `\\.\pipe\ProtectedPrefix\Administrators\WireGuard\%s`, where `%s` is some basename of an already valid filename. Its DACL is set to `O:SYD:(A;;GA;;;SY)`. If the config file used by the tunnel service is not DPAPI-encrypted and it is owned by a SID other than "Local System" then an additional ACE is added giving that file owner SID access to the named pipe. This pipe gives access to private keys and allows for reconfiguration of the interface, as well as rebinding to different ports (below 1024, even). Clients who connect to the pipe run `GetSecurityInfo` to verify that it is owned by "Local System".
- - A global mutex is used for Wintun interface creation, with the same DACL as the pipe, but first CreatePrivateNamespace is called with a "Local System" SID.
- - It handles data from its two UDP sockets, accessible to the public Internet.
- - It handles data from Wintun, accessible to all users who can do anything with the network stack.
+ - A global mutex is used for WireGuardNT interface creation, with the same DACL as the pipe, but first CreatePrivateNamespace is called with a "Local System" SID.
- After some initial setup, it uses `AdjustTokenPrivileges` to remove all privileges, except for `SeLoadDriverPrivilege`, so that it can remove the interface when shutting down. This latter point is rather unfortunate, as `SeLoadDriverPrivilege` can be used for all sorts of interesting escalation. Future work includes forking an additional process or the like so that we can drop this from the main tunnel process.
### Manager Service
@@ -29,8 +28,10 @@ The manager service is a userspace service running as Local System, responsible
- Extensive IPC using unnamed pipes, inherited by the UI process.
- A readable `CreateFileMapping` handle to a binary ringlog shared by all services, inherited by the UI process.
- It listens for service changes in tunnel services according to the string prefix "WireGuardTunnel$".
- - It manages DPAPI-encrypted configuration files in Local System's local appdata directory, and makes some effort to enforce good configuration filenames.
- - It uses `WTSEnumerateSessions` and `WTSSESSION_NOTIFICATION` to walk through each available session. It then uses `WTSQueryUserToken`, and then calls `GetTokenInformation(TokenGroups)` on it. If one of the returned group's SIDs matches `IsWellKnownSid(WinBuiltinAdministratorsSid)`, and has attributes of either `SE_GROUP_ENABLED` or `SE_GROUP_USE_FOR_DENY_ONLY` and calling `GetTokenInformation(TokenElevation)` on it or its `TokenLinkedToken` indicates that either is elevated, then it spawns the UI process as that the elevated user token, passing it three unnamed pipe handles for IPC and the log mapping handle, as described above.
+ - It manages DPAPI-encrypted configuration files in `C:\Program Files\WireGuard\Data`, which is created with `O:SYG:SYD:PAI(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)`, and makes some effort to enforce good configuration filenames.
+ - The actual DPAPI-encrypted configuration files are created with `O:SYG:SYD:PAI(A;;FA;;;SY)(A;;SD;;;BA)`.
+ - It uses `WTSEnumerateSessions` and `WTSSESSION_NOTIFICATION` to walk through each available session. It then uses `WTSQueryUserToken` to get the token belonging to each session and then determines whether or not it is an administrator token. To determine that, it calls `CheckTokenMembership(CreateWellKnownSid(WinBuiltinAdministratorsSid))` on a duplicated impersonation token, as well as and calling `GetTokenInformation(TokenElevation)` on it. If either of these are false, then it fetched the linked token using `GetTokenInformation(TokenLinkedToken)` and queries the same. Only then does it spawn the UI process as that the elevated user token, passing it three unnamed pipe handles for IPC and the log mapping handle, as described above.
+ - In the event that the administrator has set `HKLM\Software\WireGuard\LimitedOperatorUI` to 1, sessions are started for users that are a member of group S-1-5-32-556 (determined sing `CheckTokenMembership(CreateWellKnownSid(WinBuiltinNetworkConfigurationOperatorsSid))` on it and its linked token), with a more limited IPC interface, in which these non-admin users are denied private keys and tunnel editing rights. (This means users can potentially DoS the IPC server by draining notifications too slowly, or exhausting memory of the manager by spawning too many watcher go routines, or by sending garbage data that Go's `gob` decoder isn't expecting.)
### UI
diff --git a/docs/buildrun.md b/docs/buildrun.md
new file mode 100644
index 00000000..3d356f2a
--- /dev/null
+++ b/docs/buildrun.md
@@ -0,0 +1,98 @@
+# Building, Running, and Developing
+
+### Building
+
+Windows 10 64-bit or Windows Server 2019, and Git for Windows is required. The build script will take care of downloading, verifying, and extracting the right versions of the various dependencies:
+
+```text
+C:\Projects> git clone https://git.zx2c4.com/wireguard-windows
+C:\Projects> cd wireguard-windows
+C:\Projects\wireguard-windows> build
+```
+
+### Running
+
+After you've built the application, run `amd64\wireguard.exe` or `x86\wireguard.exe` to install the manager service and show the UI.
+
+```text
+C:\Projects\wireguard-windows> amd64\wireguard.exe
+```
+
+Since WireGuard requires a driver to be installed, and this generally requires a valid Microsoft signature, you may benefit from first installing a release of WireGuard for Windows from the official [wireguard.com](https://www.wireguard.com/install/) builds, which bundles a Microsoft-signed driver, and then subsequently run your own wireguard.exe. Alternatively, you can craft your own installer using the `quickinstall.bat` script.
+
+### Optional: Localizing
+
+To translate WireGuard UI to your language:
+
+1. Upgrade `resources.rc` accordingly. Follow the pattern.
+
+2. Make a new directory in `locales\` containing the language ID:
+
+ ```text
+ C:\Projects\wireguard-windows> mkdir locales\<langID>
+ ```
+
+3. Configure and run `build` to prepare initial `locales\<langID>\messages.gotext.json` file:
+
+ ```text
+ C:\Projects\wireguard-windows> set GoGenerate=yes
+ C:\Projects\wireguard-windows> build
+ C:\Projects\wireguard-windows> copy locales\<langID>\out.gotext.json locales\<langID>\messages.gotext.json
+ ```
+
+4. Translate `locales\<langID>\messages.gotext.json`. See other language message files how to translate messages and how to tackle plural. For this step, the project is currently using [CrowdIn](https://crowdin.com/translate/WireGuard); please make sure your translations make it there in order to be added here.
+
+5. Run `build` from the step 3 again, and test.
+
+6. Repeat from step 4.
+
+### Optional: Creating the Installer
+
+The installer build script will take care of downloading, verifying, and extracting the right versions of the various dependencies:
+
+```text
+C:\Projects\wireguard-windows> cd installer
+C:\Projects\wireguard-windows\installer> build
+```
+
+### Optional: Signing Binaries
+
+Add a file called `sign.bat` in the root of this repository with these contents, or similar:
+
+```text
+set SigningCertificate=8BC932FDFF15B892E8364C49B383210810E4709D
+set TimestampServer=http://timestamp.entrust.net/rfc3161ts2
+```
+
+After, run the above `build` commands as usual, from a shell that has [`signtool.exe`](https://docs.microsoft.com/en-us/windows/desktop/SecCrypto/signtool) in its `PATH`, such as the Visual Studio 2017 command prompt.
+
+### Alternative: Building from Linux
+
+You must first have Mingw and ImageMagick installed.
+
+```text
+$ sudo apt install mingw-w64 imagemagick
+$ git clone https://git.zx2c4.com/wireguard-windows
+$ cd wireguard-windows
+$ make
+```
+
+You can deploy the 64-bit build to an SSH host specified by the `DEPLOYMENT_HOST` environment variable (default "winvm") to the remote directory specified by the `DEPLOYMENT_PATH` environment variable (default "Desktop") by using the `deploy` target:
+
+```text
+$ make deploy
+```
+
+### [`wg(8)`](https://git.zx2c4.com/wireguard-tools/about/src/man/wg.8) Support for Windows
+
+The command line utility [`wg(8)`](https://git.zx2c4.com/wireguard-tools/about/src/man/wg.8) works well on Windows. Being a Unix-centric project, it compiles with a Makefile and MingW:
+
+```text
+$ git clone https://git.zx2c4.com/wireguard-tools
+$ PLATFORM=windows make -C wireguard-tools/src
+$ stat wireguard-tools/src/wg.exe
+```
+
+It interacts with WireGuard instances run by the main WireGuard for Windows program.
+
+When building on Windows, the aforementioned `build.bat` script takes care of building this.
diff --git a/docs/enterprise.md b/docs/enterprise.md
new file mode 100644
index 00000000..8430cc99
--- /dev/null
+++ b/docs/enterprise.md
@@ -0,0 +1,113 @@
+# Enterprise Usage
+
+WireGuard for Windows has been designed from the ground-up to make full use of standard Windows service, ACL, and CLI capabilities, making WireGuard deployable in enterprise scenarios or as part of Active Directory domains.
+
+### Installation
+
+While consumer users are generally directed toward [wireguard-installer.exe](https://download.wireguard.com/windows-client/wireguard-installer.exe), this installer simply takes care of selecting the correct MSI for the architecture, validating signatures, and executing it. Enterprise admins can instead [download MSIs directly](https://download.wireguard.com/windows-client/) and deploy these using [Group Policy Objects](https://docs.microsoft.com/en-us/troubleshoot/windows-server/group-policy/use-group-policy-to-install-software). The installer makes use of standard MSI features and should be easily automatable. The additional MSI property of `DO_NOT_LAUNCH` suppresses launching WireGuard after its installation, should that be required.
+
+### Tunnel Service versus Manager Service and UI
+
+The "manager service" is responsible for displaying a UI on select users' desktops (in the system tray), and responding to requests from the UI to do things like add, remove, start, or stop tunnels. The "tunnel service" is a separate Windows service for each tunnel. These two services may be used together, or separately, as described below. The various commands below will log errors and status to standard error, or, if standard error does not exist, to standard output.
+
+### Tunnel Service
+
+A tunnel service may be installed or uninstalled using the commands:
+
+```text
+> wireguard /installtunnelservice C:\path\to\some\myconfname.conf
+> wireguard /uninstalltunnelservice myconfname
+```
+
+This creates a service called `WireGuardTunnel$myconfname`, which can be controlled using standard Windows service management utilites, such as `services.msc` or [`sc`](https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/sc-query).
+
+If the configuration filename ends in `.conf`, it is interpreted as a normal [`wg-quick(8)`](https://git.zx2c4.com/wireguard-tools/about/src/man/wg-quick.8) configuration file. If it ends in `.conf.dpapi`, it is considered to be that same configuration file, but encrypted using [`CryptProtectData(bytes, "myconfname")`](https://docs.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptprotectdata).
+
+The tunnel service may be queried and modified at runtime using the standard [`wg(8)`](https://git.zx2c4.com/wireguard-tools/about/src/man/wg.8) command line utility. If the configuration file is a `.conf.dpapi` one, then Local System or Administrator permissions is required to interact with it using `wg(8)`; otherwise users of `wg(8)` must have Local System or Administrator permissions, or permissions the same as the owner of the `.conf` file. Invocation of `wg(8)` follows usual patterns on other platforms. For example:
+
+```text
+> wg show myconfname
+interface: myconfname
+ public key: lfTRXEWxt8mZc8cjSvOWN3tqnTpWw4v2Eg3qF6WTklw=
+ private key: (hidden)
+ listening port: 53488
+
+peer: JRI8Xc0zKP9kXk8qP84NdUQA04h6DLfFbwJn4g+/PFs=
+ endpoint: 163.172.161.0:12912
+ allowed ips: 0.0.0.0/0
+ latest handshake: 3 seconds ago
+ transfer: 6.55 KiB received, 4.13 KiB sent
+```
+
+The `PreUp`, `PostUp`, `PreDown`, and `PostDown` configuration options may be specified to run custom commands at various points in the lifetime of a tunnel service, but only if the correct registry key is set. [See `adminregistry.md` for information.](adminregistry.md)
+
+### Manager Service
+
+The manager service may be installed or uninstalled using the commands:
+
+```text
+> wireguard /installmanagerservice
+> wireguard /uninstallmanagerservice
+```
+
+This creates a service called `WireGuardManager`, which can be controlled using standard Windows service management utilites, such as `services.msc` or [`sc`](https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/sc-query).
+
+When executing `wireguard` with no arguments, the command first attempts to show the UI if the manager service is already running; otherwise it starts the manager service, waits for it to create a UI in the system tray, and then shows the main manager window. Therefore, `wireguard /installmanagerservice` is suitable for silent installation, whereas `wireguard` alone is suitable for interactive startup.
+
+The manager service monitors `%ProgramFiles%\WireGuard\Data\Configurations\` for the addition of new `.conf` files. Upon seeing one, it encrypts the file to a `.conf.dpapi` file, makes it unreadable to users other than Local System, confers the administrator only the ability to remove it, and then deletes the original unencrypted file. (Configurations can always be _exported_ later using the export feature of the UI.) Using this, configurations can programmatically be added to the secure store of the manager service simply by copying them into that directory.
+
+The UI is started in the system tray of all builtin Administrators when the manager service is running. A limited UI may also be started in the system tray of all builtin Network Configuration Operators, if the correct registry key is set. [See `adminregistry.md` for information.](adminregistry.md)
+
+### Diagnostic Logs
+
+The manager and all tunnel services produce diagnostic logs in a shared ringbuffer-based log. This is shown in the UI, and also can be dumped to standard out using the command:
+
+```text
+> wireguard /dumplog > C:\path\to\diagnostic\log.txt
+```
+
+Alternatively, the log can be tailed continuously, for passing it to logging services:
+
+```text
+> wireguard /dumplog /tail | log-ingest
+```
+
+Or it can be monitored in PowerShell by piping to `select`:
+
+```text
+PS> wireguard /dumplog /tail | select
+```
+
+### Updates
+
+Administrators are notified of updates within the UI and can update from within the UI, but updates can also be invoked at the command line using the command:
+
+```text
+> wireguard /update
+```
+
+Or, to log the status of that command:
+
+```text
+> wireguard /update 2> C:\path\to\update\log.txt
+```
+
+One could have Task Scheduler run it daily at 3am:
+
+```text
+> schtasks /create /f /ru SYSTEM /sc daily /tn "WireGuard Update" /tr "%PROGRAMFILES%\WireGuard\wireguard.exe /update" /st 03:00
+```
+
+### Driver Removal
+
+The tunnel service creates a network adapter at startup and destroys it at shutdown. If there are no more network adapters, the driver may be removed with:
+
+```text
+> wireguard /removedriver
+```
+
+Or, to log the status of that command:
+
+```text
+> wireguard /removedriver 2> C:\path\to\removal\log.txt
+```
diff --git a/docs/netquirk.md b/docs/netquirk.md
new file mode 100644
index 00000000..d53c8cdb
--- /dev/null
+++ b/docs/netquirk.md
@@ -0,0 +1,33 @@
+# Network Configuration Quirks
+
+As part of setting up a WireGuard tunnel, the tunnel service also sets up various network configuration parameters that are in one way or another related to the original configuration.
+
+### Routing
+
+The tunnel service takes all the allowed IPs from each peer, deduplicates them, and adds them to the routes for the WireGuard interface. The service then monitors which interface on the system has a default route (a route with a `/0` CIDR) that is not the WireGuard interface itself, and, if no MTU has been specified in the configuration, it sets the MTU of the WireGuard interface to be 80 less than the MTU of that default route interface. WireGuardNT also monitors the routing table and determines the outgoing route that does not loopback to itself, and then sends each packet using `IP_PKTINFO`/`IPV6_PKTINFO`. It keeps track of the incoming interface and source address for received packets, and always replies to the sender in that way.
+
+### Firewall Considerations for `/0` Allowed IPs
+
+If an interface has only one peer, and that peer contains an Allowed IP in `/0`, then WireGuard enables a so-called "kill-switch", which adds firewall rules to do the following:
+
+- Packets from the tunnel service itself are permitted, so that WireGuard packets can flow successfully.
+- If the configuration specifies DNS servers, then packets sent to port `53` are only permitted if they are to one of those DNS servers. This is to prevent Windows' [ordinary multihomed DNS resolution behavior](https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29), so that DNS queries only go to the DNS server specified, rather than multiple DNS servers.
+- Loopback packets are permitted, and packets actually going through the WireGuard tunnel are permitted.
+- DHCP for IPv4 and IPv6 and NDP for IPv6 are permitted.
+- All other packets are blocked.
+
+This prevents traffic from leaking outside the tunnel.
+
+If you'd like to use a default route _without_ having these restrictive kill-switch semantics, one may use the routes `0.0.0.0/1` and `128.0.0.0/1` in place of `0.0.0.0/0`, as well as `::/1` and `8000::/1` in place of `::/0`. This achieves nearly the same thing, but does not activate the above firewalling semantics. (The UI's editor has a checkbox that toggles this.) And users without the need for a `/0` route at all do not have to worry about this, and instead fall back to ordinary Windows routing and DNS behavior.
+
+### Considerations for non-`/0` Allowed IPs
+
+When the above conditions do not apply, routing and DNS information is handed to Windows in the typical way for Windows to manage. This includes its [ordinary multihomed DNS resolution behavior](https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd197552%28v%3Dws.10%29) as well as its ordinary routing table resolution. Users may make use of the normal Windows firewalling and network configuration capabilities to firewall this as needed. One firewall rule is added, however, which allows the tunnel service to send and receive WireGuard packets.
+
+### Network List Manager
+
+Windows assigns a unique GUID to each new WireGuard adapter. The application takes pains to make this GUID deterministic, so that firewall policy (such as "public" vs "private" network categorization) can be consistently applied to the tunnel's network. This determinism is based on the configuration of the tunnel. Therefore, if the WireGuard configuration changes, so too will the unique GUID. Technical details are described in [a mailing list post](https://lists.zx2c4.com/pipermail/wireguard/2019-June/004259.html).
+
+### Adapter Lifetime
+
+WireGuard's network adapter is created dynamically when a tunnel is started and destroyed when a tunnel is stopped. This means that additional filters, address families, or protocols should be bound to the adapter programmatically, possibly through use of dangerous script execution in the configuration file or by way of automatic NDIS layer binding.
diff --git a/driver/configuration_windows.go b/driver/configuration_windows.go
new file mode 100644
index 00000000..d838dcf2
--- /dev/null
+++ b/driver/configuration_windows.go
@@ -0,0 +1,183 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2017-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package driver
+
+import (
+ "syscall"
+ "unsafe"
+
+ "golang.org/x/sys/windows"
+ "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
+)
+
+type AdapterState uint32
+
+const (
+ AdapterStateDown AdapterState = 0
+ AdapterStateUp AdapterState = 1
+)
+
+type AllowedIP struct {
+ Address [16]byte
+ AddressFamily winipcfg.AddressFamily
+ Cidr uint8
+ _ [4]byte
+}
+
+type PeerFlag uint32
+
+const (
+ PeerHasPublicKey PeerFlag = 1 << 0
+ PeerHasPresharedKey PeerFlag = 1 << 1
+ PeerHasPersistentKeepalive PeerFlag = 1 << 2
+ PeerHasEndpoint PeerFlag = 1 << 3
+ PeerReplaceAllowedIPs PeerFlag = 1 << 5
+ PeerRemove PeerFlag = 1 << 6
+ PeerUpdateOnly PeerFlag = 1 << 7
+)
+
+type Peer struct {
+ Flags PeerFlag
+ _ uint32
+ PublicKey [32]byte
+ PresharedKey [32]byte
+ PersistentKeepalive uint16
+ _ uint16
+ Endpoint winipcfg.RawSockaddrInet
+ TxBytes uint64
+ RxBytes uint64
+ LastHandshake uint64
+ AllowedIPsCount uint32
+ _ [4]byte
+}
+
+type InterfaceFlag uint32
+
+const (
+ InterfaceHasPublicKey InterfaceFlag = 1 << 0
+ InterfaceHasPrivateKey InterfaceFlag = 1 << 1
+ InterfaceHasListenPort InterfaceFlag = 1 << 2
+ InterfaceReplacePeers InterfaceFlag = 1 << 3
+)
+
+type Interface struct {
+ Flags InterfaceFlag
+ ListenPort uint16
+ PrivateKey [32]byte
+ PublicKey [32]byte
+ PeerCount uint32
+ _ [4]byte
+}
+
+var (
+ procWireGuardSetAdapterState = modwireguard.NewProc("WireGuardSetAdapterState")
+ procWireGuardGetAdapterState = modwireguard.NewProc("WireGuardGetAdapterState")
+ procWireGuardSetConfiguration = modwireguard.NewProc("WireGuardSetConfiguration")
+ procWireGuardGetConfiguration = modwireguard.NewProc("WireGuardGetConfiguration")
+)
+
+// SetAdapterState sets the adapter either Up or Down.
+func (wireguard *Adapter) SetAdapterState(adapterState AdapterState) (err error) {
+ r0, _, e1 := syscall.SyscallN(procWireGuardSetAdapterState.Addr(), wireguard.handle, uintptr(adapterState))
+ if r0 == 0 {
+ err = e1
+ }
+ return
+}
+
+// AdapterState returns the current state of the adapter.
+func (wireguard *Adapter) AdapterState() (adapterState AdapterState, err error) {
+ r0, _, e1 := syscall.SyscallN(procWireGuardGetAdapterState.Addr(), wireguard.handle, uintptr(unsafe.Pointer(&adapterState)))
+ if r0 == 0 {
+ err = e1
+ }
+ return
+}
+
+// SetConfiguration sets the adapter configuration.
+func (wireguard *Adapter) SetConfiguration(interfaze *Interface, size uint32) (err error) {
+ r0, _, e1 := syscall.SyscallN(procWireGuardSetConfiguration.Addr(), wireguard.handle, uintptr(unsafe.Pointer(interfaze)), uintptr(size))
+ if r0 == 0 {
+ err = e1
+ }
+ return
+}
+
+// Configuration gets the adapter configuration.
+func (wireguard *Adapter) Configuration() (interfaze *Interface, err error) {
+ size := wireguard.lastGetGuessSize
+ if size == 0 {
+ size = 512
+ }
+ for {
+ buf := make([]byte, size)
+ r0, _, e1 := syscall.SyscallN(procWireGuardGetConfiguration.Addr(), wireguard.handle, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&size)))
+ if r0 != 0 {
+ wireguard.lastGetGuessSize = size
+ return (*Interface)(unsafe.Pointer(&buf[0])), nil
+ }
+ if e1 != windows.ERROR_MORE_DATA {
+ return nil, e1
+ }
+ }
+}
+
+// FirstPeer returns the first peer attached to the interface.
+func (interfaze *Interface) FirstPeer() *Peer {
+ return (*Peer)(unsafe.Add(unsafe.Pointer(interfaze), unsafe.Sizeof(*interfaze)))
+}
+
+// NextPeer returns the subsequent peer of the current one.
+func (peer *Peer) NextPeer() *Peer {
+ return (*Peer)(unsafe.Pointer(uintptr(unsafe.Pointer(peer)) + unsafe.Sizeof(*peer) + uintptr(peer.AllowedIPsCount)*unsafe.Sizeof(AllowedIP{})))
+}
+
+// FirstAllowedIP returns the first allowed IP attached to the peer.
+func (peer *Peer) FirstAllowedIP() *AllowedIP {
+ return (*AllowedIP)(unsafe.Add(unsafe.Pointer(peer), unsafe.Sizeof(*peer)))
+}
+
+// NextAllowedIP returns the subsequent allowed IP of the current one.
+func (allowedIP *AllowedIP) NextAllowedIP() *AllowedIP {
+ return (*AllowedIP)(unsafe.Add(unsafe.Pointer(allowedIP), unsafe.Sizeof(*allowedIP)))
+}
+
+type ConfigBuilder struct {
+ buffer []byte
+}
+
+// Preallocate reserves memory in the config builder to reduce allocations of append operations.
+func (builder *ConfigBuilder) Preallocate(size uint32) {
+ if builder.buffer == nil {
+ builder.buffer = make([]byte, 0, size)
+ }
+}
+
+// AppendInterface appends an interface to the building configuration. This should be called first.
+func (builder *ConfigBuilder) AppendInterface(interfaze *Interface) {
+ newBytes := unsafe.Slice((*byte)(unsafe.Pointer(interfaze)), unsafe.Sizeof(*interfaze))
+ builder.buffer = append(builder.buffer, newBytes...)
+}
+
+// AppendPeer appends a peer to the building configuration. This should be called after an interface has been added.
+func (builder *ConfigBuilder) AppendPeer(peer *Peer) {
+ newBytes := unsafe.Slice((*byte)(unsafe.Pointer(peer)), unsafe.Sizeof(*peer))
+ builder.buffer = append(builder.buffer, newBytes...)
+}
+
+// AppendAllowedIP appends an allowed IP to the building configuration. This should be called after a peer has been added.
+func (builder *ConfigBuilder) AppendAllowedIP(allowedIP *AllowedIP) {
+ newBytes := unsafe.Slice((*byte)(unsafe.Pointer(allowedIP)), unsafe.Sizeof(*allowedIP))
+ builder.buffer = append(builder.buffer, newBytes...)
+}
+
+// Interface assembles the configuration and returns the interface and length to be passed to SetConfiguration.
+func (builder *ConfigBuilder) Interface() (*Interface, uint32) {
+ if builder.buffer == nil {
+ return nil, 0
+ }
+ return (*Interface)(unsafe.Pointer(&builder.buffer[0])), uint32(len(builder.buffer))
+}
diff --git a/driver/dll_fromfile_windows.go b/driver/dll_fromfile_windows.go
new file mode 100644
index 00000000..b815b4c0
--- /dev/null
+++ b/driver/dll_fromfile_windows.go
@@ -0,0 +1,56 @@
+//go:build !load_wgnt_from_rsrc
+
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ */
+
+package driver
+
+import (
+ "fmt"
+ "sync"
+ "sync/atomic"
+ "unsafe"
+
+ "golang.org/x/sys/windows"
+)
+
+type lazyDLL struct {
+ Name string
+ Base windows.Handle
+ mu sync.Mutex
+ module windows.Handle
+ onLoad func(d *lazyDLL)
+}
+
+func (d *lazyDLL) Load() error {
+ if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&d.module))) != nil {
+ return nil
+ }
+ d.mu.Lock()
+ defer d.mu.Unlock()
+ if d.module != 0 {
+ return nil
+ }
+
+ const (
+ LOAD_LIBRARY_SEARCH_APPLICATION_DIR = 0x00000200
+ LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800
+ )
+ module, err := windows.LoadLibraryEx(d.Name, 0, LOAD_LIBRARY_SEARCH_APPLICATION_DIR|LOAD_LIBRARY_SEARCH_SYSTEM32)
+ if err != nil {
+ return fmt.Errorf("Unable to load library: %w", err)
+ }
+ d.Base = module
+
+ atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&d.module)), unsafe.Pointer(module))
+ if d.onLoad != nil {
+ d.onLoad(d)
+ }
+ return nil
+}
+
+func (p *lazyProc) nameToAddr() (uintptr, error) {
+ return windows.GetProcAddress(p.dll.module, p.Name)
+}
diff --git a/driver/dll_fromrsrc_windows.go b/driver/dll_fromrsrc_windows.go
new file mode 100644
index 00000000..65b1cfce
--- /dev/null
+++ b/driver/dll_fromrsrc_windows.go
@@ -0,0 +1,62 @@
+//go:build load_wgnt_from_rsrc
+
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ */
+
+package driver
+
+import (
+ "fmt"
+ "sync"
+ "sync/atomic"
+ "unsafe"
+
+ "golang.org/x/sys/windows"
+ "golang.zx2c4.com/wireguard/windows/driver/memmod"
+)
+
+type lazyDLL struct {
+ Name string
+ Base windows.Handle
+ mu sync.Mutex
+ module *memmod.Module
+ onLoad func(d *lazyDLL)
+}
+
+func (d *lazyDLL) Load() error {
+ if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&d.module))) != nil {
+ return nil
+ }
+ d.mu.Lock()
+ defer d.mu.Unlock()
+ if d.module != nil {
+ return nil
+ }
+
+ const ourModule windows.Handle = 0
+ resInfo, err := windows.FindResource(ourModule, d.Name, windows.RT_RCDATA)
+ if err != nil {
+ return fmt.Errorf("Unable to find \"%v\" RCDATA resource: %w", d.Name, err)
+ }
+ data, err := windows.LoadResourceData(ourModule, resInfo)
+ if err != nil {
+ return fmt.Errorf("Unable to load resource: %w", err)
+ }
+ module, err := memmod.LoadLibrary(data)
+ if err != nil {
+ return fmt.Errorf("Unable to load library: %w", err)
+ }
+ d.Base = windows.Handle(module.BaseAddr())
+
+ atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&d.module)), unsafe.Pointer(module))
+ if d.onLoad != nil {
+ d.onLoad(d)
+ }
+ return nil
+}
+
+func (p *lazyProc) nameToAddr() (uintptr, error) {
+ return p.dll.module.ProcAddressByName(p.Name)
+}
diff --git a/driver/dll_windows.go b/driver/dll_windows.go
new file mode 100644
index 00000000..5dcb849e
--- /dev/null
+++ b/driver/dll_windows.go
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2017-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package driver
+
+import (
+ "fmt"
+ "sync"
+ "sync/atomic"
+ "unsafe"
+
+ "golang.org/x/sys/windows"
+)
+
+func newLazyDLL(name string, onLoad func(d *lazyDLL)) *lazyDLL {
+ return &lazyDLL{Name: name, onLoad: onLoad}
+}
+
+func (d *lazyDLL) NewProc(name string) *lazyProc {
+ return &lazyProc{dll: d, Name: name}
+}
+
+type lazyProc struct {
+ Name string
+ mu sync.Mutex
+ dll *lazyDLL
+ addr uintptr
+}
+
+func (p *lazyProc) Find() error {
+ if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&p.addr))) != nil {
+ return nil
+ }
+ p.mu.Lock()
+ defer p.mu.Unlock()
+ if p.addr != 0 {
+ return nil
+ }
+
+ err := p.dll.Load()
+ if err != nil {
+ return fmt.Errorf("Error loading %v DLL: %w", p.dll.Name, err)
+ }
+ addr, err := p.nameToAddr()
+ if err != nil {
+ return fmt.Errorf("Error getting %v address: %w", p.Name, err)
+ }
+
+ atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&p.addr)), unsafe.Pointer(addr))
+ return nil
+}
+
+func (p *lazyProc) Addr() uintptr {
+ err := p.Find()
+ if err != nil {
+ panic(err)
+ }
+ return p.addr
+}
+
+// Version returns the version of the driver DLL.
+func Version() string {
+ if modwireguard.Load() != nil {
+ return "unknown"
+ }
+ resInfo, err := windows.FindResource(modwireguard.Base, windows.ResourceID(1), windows.RT_VERSION)
+ if err != nil {
+ return "unknown"
+ }
+ data, err := windows.LoadResourceData(modwireguard.Base, resInfo)
+ if err != nil {
+ return "unknown"
+ }
+
+ var fixedInfo *windows.VS_FIXEDFILEINFO
+ fixedInfoLen := uint32(unsafe.Sizeof(*fixedInfo))
+ err = windows.VerQueryValue(unsafe.Pointer(&data[0]), `\`, unsafe.Pointer(&fixedInfo), &fixedInfoLen)
+ if err != nil {
+ return "unknown"
+ }
+ version := fmt.Sprintf("%d.%d", (fixedInfo.FileVersionMS>>16)&0xff, (fixedInfo.FileVersionMS>>0)&0xff)
+ if nextNibble := (fixedInfo.FileVersionLS >> 16) & 0xff; nextNibble != 0 {
+ version += fmt.Sprintf(".%d", nextNibble)
+ }
+ if nextNibble := (fixedInfo.FileVersionLS >> 0) & 0xff; nextNibble != 0 {
+ version += fmt.Sprintf(".%d", nextNibble)
+ }
+ return version
+}
diff --git a/driver/driver_windows.go b/driver/driver_windows.go
new file mode 100644
index 00000000..462c3a30
--- /dev/null
+++ b/driver/driver_windows.go
@@ -0,0 +1,170 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2017-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package driver
+
+import (
+ "log"
+ "runtime"
+ "syscall"
+ "unsafe"
+
+ "golang.org/x/sys/windows"
+ "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
+)
+
+type loggerLevel int
+
+const (
+ logInfo loggerLevel = iota
+ logWarn
+ logErr
+)
+
+const AdapterNameMax = 128
+
+type Adapter struct {
+ handle uintptr
+ lastGetGuessSize uint32
+}
+
+var (
+ modwireguard = newLazyDLL("wireguard.dll", setupLogger)
+ procWireGuardCreateAdapter = modwireguard.NewProc("WireGuardCreateAdapter")
+ procWireGuardOpenAdapter = modwireguard.NewProc("WireGuardOpenAdapter")
+ procWireGuardCloseAdapter = modwireguard.NewProc("WireGuardCloseAdapter")
+ procWireGuardDeleteDriver = modwireguard.NewProc("WireGuardDeleteDriver")
+ procWireGuardGetAdapterLUID = modwireguard.NewProc("WireGuardGetAdapterLUID")
+ procWireGuardGetRunningDriverVersion = modwireguard.NewProc("WireGuardGetRunningDriverVersion")
+ procWireGuardSetAdapterLogging = modwireguard.NewProc("WireGuardSetAdapterLogging")
+)
+
+type TimestampedWriter interface {
+ WriteWithTimestamp(p []byte, ts int64) (n int, err error)
+}
+
+func logMessage(level loggerLevel, timestamp uint64, msg *uint16) int {
+ if tw, ok := log.Default().Writer().(TimestampedWriter); ok {
+ tw.WriteWithTimestamp([]byte(log.Default().Prefix()+windows.UTF16PtrToString(msg)), (int64(timestamp)-116444736000000000)*100)
+ } else {
+ log.Println(windows.UTF16PtrToString(msg))
+ }
+ return 0
+}
+
+func setupLogger(dll *lazyDLL) {
+ var callback uintptr
+ if runtime.GOARCH == "386" {
+ callback = windows.NewCallback(func(level loggerLevel, timestampLow, timestampHigh uint32, msg *uint16) int {
+ return logMessage(level, uint64(timestampHigh)<<32|uint64(timestampLow), msg)
+ })
+ } else if runtime.GOARCH == "arm" {
+ callback = windows.NewCallback(func(level loggerLevel, _, timestampLow, timestampHigh uint32, msg *uint16) int {
+ return logMessage(level, uint64(timestampHigh)<<32|uint64(timestampLow), msg)
+ })
+ } else if runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" {
+ callback = windows.NewCallback(logMessage)
+ }
+ syscall.SyscallN(dll.NewProc("WireGuardSetLogger").Addr(), callback)
+}
+
+func closeAdapter(wireguard *Adapter) {
+ syscall.SyscallN(procWireGuardCloseAdapter.Addr(), wireguard.handle)
+}
+
+// CreateAdapter creates a WireGuard adapter. name is the cosmetic name of the adapter.
+// tunnelType represents the type of adapter and should be "WireGuard". requestedGUID is
+// the GUID of the created network adapter, which then influences NLA generation
+// deterministically. If it is set to nil, the GUID is chosen by the system at random,
+// and hence a new NLA entry is created for each new adapter.
+func CreateAdapter(name, tunnelType string, requestedGUID *windows.GUID) (wireguard *Adapter, err error) {
+ var name16 *uint16
+ name16, err = windows.UTF16PtrFromString(name)
+ if err != nil {
+ return
+ }
+ var tunnelType16 *uint16
+ tunnelType16, err = windows.UTF16PtrFromString(tunnelType)
+ if err != nil {
+ return
+ }
+ r0, _, e1 := syscall.SyscallN(procWireGuardCreateAdapter.Addr(), uintptr(unsafe.Pointer(name16)), uintptr(unsafe.Pointer(tunnelType16)), uintptr(unsafe.Pointer(requestedGUID)))
+ if r0 == 0 {
+ err = e1
+ return
+ }
+ wireguard = &Adapter{handle: r0}
+ runtime.SetFinalizer(wireguard, closeAdapter)
+ return
+}
+
+// OpenAdapter opens an existing WireGuard adapter by name.
+func OpenAdapter(name string) (wireguard *Adapter, err error) {
+ var name16 *uint16
+ name16, err = windows.UTF16PtrFromString(name)
+ if err != nil {
+ return
+ }
+ r0, _, e1 := syscall.SyscallN(procWireGuardOpenAdapter.Addr(), uintptr(unsafe.Pointer(name16)))
+ if r0 == 0 {
+ err = e1
+ return
+ }
+ wireguard = &Adapter{handle: r0}
+ runtime.SetFinalizer(wireguard, closeAdapter)
+ return
+}
+
+// Close closes a WireGuard adapter.
+func (wireguard *Adapter) Close() (err error) {
+ runtime.SetFinalizer(wireguard, nil)
+ r1, _, e1 := syscall.SyscallN(procWireGuardCloseAdapter.Addr(), wireguard.handle)
+ if r1 == 0 {
+ err = e1
+ }
+ return
+}
+
+// Uninstall removes the driver from the system if no drivers are currently in use.
+func Uninstall() (err error) {
+ r1, _, e1 := syscall.SyscallN(procWireGuardDeleteDriver.Addr())
+ if r1 == 0 {
+ err = e1
+ }
+ return
+}
+
+type AdapterLogState uint32
+
+const (
+ AdapterLogOff AdapterLogState = 0
+ AdapterLogOn AdapterLogState = 1
+ AdapterLogOnWithPrefix AdapterLogState = 2
+)
+
+// SetLogging enables or disables logging on the WireGuard adapter.
+func (wireguard *Adapter) SetLogging(logState AdapterLogState) (err error) {
+ r1, _, e1 := syscall.SyscallN(procWireGuardSetAdapterLogging.Addr(), wireguard.handle, uintptr(logState))
+ if r1 == 0 {
+ err = e1
+ }
+ return
+}
+
+// RunningVersion returns the version of the loaded driver.
+func RunningVersion() (version uint32, err error) {
+ r0, _, e1 := syscall.SyscallN(procWireGuardGetRunningDriverVersion.Addr())
+ version = uint32(r0)
+ if version == 0 {
+ err = e1
+ }
+ return
+}
+
+// LUID returns the LUID of the adapter.
+func (wireguard *Adapter) LUID() (luid winipcfg.LUID) {
+ syscall.SyscallN(procWireGuardGetAdapterLUID.Addr(), wireguard.handle, uintptr(unsafe.Pointer(&luid)))
+ return
+}
diff --git a/driver/memmod/memmod_windows.go b/driver/memmod/memmod_windows.go
new file mode 100644
index 00000000..7b54282a
--- /dev/null
+++ b/driver/memmod/memmod_windows.go
@@ -0,0 +1,698 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2017-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package memmod
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+ "sync"
+ "syscall"
+ "unsafe"
+
+ "golang.org/x/sys/windows"
+)
+
+type addressList struct {
+ next *addressList
+ address uintptr
+}
+
+func (head *addressList) free() {
+ for node := head; node != nil; node = node.next {
+ windows.VirtualFree(node.address, 0, windows.MEM_RELEASE)
+ }
+}
+
+type Module struct {
+ headers *IMAGE_NT_HEADERS
+ codeBase uintptr
+ modules []windows.Handle
+ initialized bool
+ isDLL bool
+ isRelocated bool
+ nameExports map[string]uint16
+ entry uintptr
+ blockedMemory *addressList
+}
+
+func (module *Module) BaseAddr() uintptr {
+ return module.codeBase
+}
+
+func (module *Module) headerDirectory(idx int) *IMAGE_DATA_DIRECTORY {
+ return &module.headers.OptionalHeader.DataDirectory[idx]
+}
+
+func (module *Module) copySections(address, size uintptr, oldHeaders *IMAGE_NT_HEADERS) error {
+ sections := module.headers.Sections()
+ for i := range sections {
+ if sections[i].SizeOfRawData == 0 {
+ // Section doesn't contain data in the dll itself, but may define uninitialized data.
+ sectionSize := oldHeaders.OptionalHeader.SectionAlignment
+ if sectionSize == 0 {
+ continue
+ }
+ dest, err := windows.VirtualAlloc(module.codeBase+uintptr(sections[i].VirtualAddress),
+ uintptr(sectionSize),
+ windows.MEM_COMMIT,
+ windows.PAGE_READWRITE)
+ if err != nil {
+ return fmt.Errorf("Error allocating section: %w", err)
+ }
+
+ // Always use position from file to support alignments smaller than page size (allocation above will align to page size).
+ dest = module.codeBase + uintptr(sections[i].VirtualAddress)
+ // NOTE: On 64bit systems we truncate to 32bit here but expand again later when "PhysicalAddress" is used.
+ sections[i].SetPhysicalAddress((uint32)(dest & 0xffffffff))
+ dst := unsafe.Slice((*byte)(a2p(dest)), sectionSize)
+ for j := range dst {
+ dst[j] = 0
+ }
+ continue
+ }
+
+ if size < uintptr(sections[i].PointerToRawData+sections[i].SizeOfRawData) {
+ return errors.New("Incomplete section")
+ }
+
+ // Commit memory block and copy data from dll.
+ dest, err := windows.VirtualAlloc(module.codeBase+uintptr(sections[i].VirtualAddress),
+ uintptr(sections[i].SizeOfRawData),
+ windows.MEM_COMMIT,
+ windows.PAGE_READWRITE)
+ if err != nil {
+ return fmt.Errorf("Error allocating memory block: %w", err)
+ }
+
+ // Always use position from file to support alignments smaller than page size (allocation above will align to page size).
+ memcpy(
+ module.codeBase+uintptr(sections[i].VirtualAddress),
+ address+uintptr(sections[i].PointerToRawData),
+ uintptr(sections[i].SizeOfRawData))
+ // NOTE: On 64bit systems we truncate to 32bit here but expand again later when "PhysicalAddress" is used.
+ sections[i].SetPhysicalAddress((uint32)(dest & 0xffffffff))
+ }
+
+ return nil
+}
+
+func (module *Module) realSectionSize(section *IMAGE_SECTION_HEADER) uintptr {
+ size := section.SizeOfRawData
+ if size != 0 {
+ return uintptr(size)
+ }
+ if (section.Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA) != 0 {
+ return uintptr(module.headers.OptionalHeader.SizeOfInitializedData)
+ }
+ if (section.Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA) != 0 {
+ return uintptr(module.headers.OptionalHeader.SizeOfUninitializedData)
+ }
+ return 0
+}
+
+type sectionFinalizeData struct {
+ address uintptr
+ alignedAddress uintptr
+ size uintptr
+ characteristics uint32
+ last bool
+}
+
+func (module *Module) finalizeSection(sectionData *sectionFinalizeData) error {
+ if sectionData.size == 0 {
+ return nil
+ }
+
+ if (sectionData.characteristics & IMAGE_SCN_MEM_DISCARDABLE) != 0 {
+ // Section is not needed any more and can safely be freed.
+ if sectionData.address == sectionData.alignedAddress &&
+ (sectionData.last ||
+ (sectionData.size%uintptr(module.headers.OptionalHeader.SectionAlignment)) == 0) {
+ // Only allowed to decommit whole pages.
+ windows.VirtualFree(sectionData.address, sectionData.size, windows.MEM_DECOMMIT)
+ }
+ return nil
+ }
+
+ // determine protection flags based on characteristics
+ ProtectionFlags := [8]uint32{
+ windows.PAGE_NOACCESS, // not writeable, not readable, not executable
+ windows.PAGE_EXECUTE, // not writeable, not readable, executable
+ windows.PAGE_READONLY, // not writeable, readable, not executable
+ windows.PAGE_EXECUTE_READ, // not writeable, readable, executable
+ windows.PAGE_WRITECOPY, // writeable, not readable, not executable
+ windows.PAGE_EXECUTE_WRITECOPY, // writeable, not readable, executable
+ windows.PAGE_READWRITE, // writeable, readable, not executable
+ windows.PAGE_EXECUTE_READWRITE, // writeable, readable, executable
+ }
+ protect := ProtectionFlags[sectionData.characteristics>>29]
+ if (sectionData.characteristics & IMAGE_SCN_MEM_NOT_CACHED) != 0 {
+ protect |= windows.PAGE_NOCACHE
+ }
+
+ // Change memory access flags.
+ var oldProtect uint32
+ err := windows.VirtualProtect(sectionData.address, sectionData.size, protect, &oldProtect)
+ if err != nil {
+ return fmt.Errorf("Error protecting memory page: %w", err)
+ }
+
+ return nil
+}
+
+func (module *Module) registerExceptionHandlers() {
+ directory := module.headerDirectory(IMAGE_DIRECTORY_ENTRY_EXCEPTION)
+ if directory.Size == 0 || directory.VirtualAddress == 0 {
+ return
+ }
+ runtimeFuncs := (*windows.RUNTIME_FUNCTION)(unsafe.Pointer(module.codeBase + uintptr(directory.VirtualAddress)))
+ windows.RtlAddFunctionTable(runtimeFuncs, uint32(uintptr(directory.Size)/unsafe.Sizeof(*runtimeFuncs)), module.codeBase)
+}
+
+func (module *Module) finalizeSections() error {
+ sections := module.headers.Sections()
+ imageOffset := module.headers.OptionalHeader.imageOffset()
+ sectionData := sectionFinalizeData{}
+ sectionData.address = uintptr(sections[0].PhysicalAddress()) | imageOffset
+ sectionData.alignedAddress = alignDown(sectionData.address, uintptr(module.headers.OptionalHeader.SectionAlignment))
+ sectionData.size = module.realSectionSize(&sections[0])
+ sections[0].SetVirtualSize(uint32(sectionData.size))
+ sectionData.characteristics = sections[0].Characteristics
+
+ // Loop through all sections and change access flags.
+ for i := uint16(1); i < module.headers.FileHeader.NumberOfSections; i++ {
+ sectionAddress := uintptr(sections[i].PhysicalAddress()) | imageOffset
+ alignedAddress := alignDown(sectionAddress, uintptr(module.headers.OptionalHeader.SectionAlignment))
+ sectionSize := module.realSectionSize(&sections[i])
+ sections[i].SetVirtualSize(uint32(sectionSize))
+ // Combine access flags of all sections that share a page.
+ // TODO: We currently share flags of a trailing large section with the page of a first small section. This should be optimized.
+ if sectionData.alignedAddress == alignedAddress || sectionData.address+sectionData.size > alignedAddress {
+ // Section shares page with previous.
+ if (sections[i].Characteristics&IMAGE_SCN_MEM_DISCARDABLE) == 0 || (sectionData.characteristics&IMAGE_SCN_MEM_DISCARDABLE) == 0 {
+ sectionData.characteristics = (sectionData.characteristics | sections[i].Characteristics) &^ IMAGE_SCN_MEM_DISCARDABLE
+ } else {
+ sectionData.characteristics |= sections[i].Characteristics
+ }
+ sectionData.size = sectionAddress + sectionSize - sectionData.address
+ continue
+ }
+
+ err := module.finalizeSection(&sectionData)
+ if err != nil {
+ return fmt.Errorf("Error finalizing section: %w", err)
+ }
+ sectionData.address = sectionAddress
+ sectionData.alignedAddress = alignedAddress
+ sectionData.size = sectionSize
+ sectionData.characteristics = sections[i].Characteristics
+ }
+ sectionData.last = true
+ err := module.finalizeSection(&sectionData)
+ if err != nil {
+ return fmt.Errorf("Error finalizing section: %w", err)
+ }
+ return nil
+}
+
+func (module *Module) executeTLS() {
+ directory := module.headerDirectory(IMAGE_DIRECTORY_ENTRY_TLS)
+ if directory.VirtualAddress == 0 {
+ return
+ }
+
+ tls := (*IMAGE_TLS_DIRECTORY)(a2p(module.codeBase + uintptr(directory.VirtualAddress)))
+ callback := tls.AddressOfCallbacks
+ if callback != 0 {
+ for {
+ f := *(*uintptr)(a2p(callback))
+ if f == 0 {
+ break
+ }
+ syscall.SyscallN(f, module.codeBase, DLL_PROCESS_ATTACH, 0)
+ callback += unsafe.Sizeof(f)
+ }
+ }
+}
+
+func (module *Module) performBaseRelocation(delta uintptr) (relocated bool, err error) {
+ directory := module.headerDirectory(IMAGE_DIRECTORY_ENTRY_BASERELOC)
+ if directory.Size == 0 {
+ return delta == 0, nil
+ }
+
+ relocationHdr := (*IMAGE_BASE_RELOCATION)(a2p(module.codeBase + uintptr(directory.VirtualAddress)))
+ for relocationHdr.VirtualAddress > 0 {
+ dest := module.codeBase + uintptr(relocationHdr.VirtualAddress)
+
+ relInfos := unsafe.Slice(
+ (*uint16)(a2p(uintptr(unsafe.Pointer(relocationHdr))+unsafe.Sizeof(*relocationHdr))),
+ (uintptr(relocationHdr.SizeOfBlock)-unsafe.Sizeof(*relocationHdr))/unsafe.Sizeof(uint16(0)))
+ for _, relInfo := range relInfos {
+ // The upper 4 bits define the type of relocation.
+ relType := relInfo >> 12
+ // The lower 12 bits define the offset.
+ relOffset := uintptr(relInfo & 0xfff)
+
+ switch relType {
+ case IMAGE_REL_BASED_ABSOLUTE:
+ // Skip relocation.
+
+ case IMAGE_REL_BASED_LOW:
+ *(*uint16)(a2p(dest + relOffset)) += uint16(delta & 0xffff)
+ break
+
+ case IMAGE_REL_BASED_HIGH:
+ *(*uint16)(a2p(dest + relOffset)) += uint16(uint32(delta) >> 16)
+ break
+
+ case IMAGE_REL_BASED_HIGHLOW:
+ *(*uint32)(a2p(dest + relOffset)) += uint32(delta)
+
+ case IMAGE_REL_BASED_DIR64:
+ *(*uint64)(a2p(dest + relOffset)) += uint64(delta)
+
+ case IMAGE_REL_BASED_THUMB_MOV32:
+ inst := *(*uint32)(a2p(dest + relOffset))
+ imm16 := ((inst << 1) & 0x0800) + ((inst << 12) & 0xf000) +
+ ((inst >> 20) & 0x0700) + ((inst >> 16) & 0x00ff)
+ if (inst & 0x8000fbf0) != 0x0000f240 {
+ return false, fmt.Errorf("Wrong Thumb2 instruction %08x, expected MOVW", inst)
+ }
+ imm16 += uint32(delta) & 0xffff
+ hiDelta := (uint32(delta&0xffff0000) >> 16) + ((imm16 & 0xffff0000) >> 16)
+ *(*uint32)(a2p(dest + relOffset)) = (inst & 0x8f00fbf0) + ((imm16 >> 1) & 0x0400) +
+ ((imm16 >> 12) & 0x000f) +
+ ((imm16 << 20) & 0x70000000) +
+ ((imm16 << 16) & 0xff0000)
+ if hiDelta != 0 {
+ inst = *(*uint32)(a2p(dest + relOffset + 4))
+ imm16 = ((inst << 1) & 0x0800) + ((inst << 12) & 0xf000) +
+ ((inst >> 20) & 0x0700) + ((inst >> 16) & 0x00ff)
+ if (inst & 0x8000fbf0) != 0x0000f2c0 {
+ return false, fmt.Errorf("Wrong Thumb2 instruction %08x, expected MOVT", inst)
+ }
+ imm16 += hiDelta
+ if imm16 > 0xffff {
+ return false, fmt.Errorf("Resulting immediate value won't fit: %08x", imm16)
+ }
+ *(*uint32)(a2p(dest + relOffset + 4)) = (inst & 0x8f00fbf0) +
+ ((imm16 >> 1) & 0x0400) +
+ ((imm16 >> 12) & 0x000f) +
+ ((imm16 << 20) & 0x70000000) +
+ ((imm16 << 16) & 0xff0000)
+ }
+
+ default:
+ return false, fmt.Errorf("Unsupported relocation: %v", relType)
+ }
+ }
+
+ // Advance to next relocation block.
+ relocationHdr = (*IMAGE_BASE_RELOCATION)(a2p(uintptr(unsafe.Pointer(relocationHdr)) + uintptr(relocationHdr.SizeOfBlock)))
+ }
+ return true, nil
+}
+
+func (module *Module) buildImportTable() error {
+ directory := module.headerDirectory(IMAGE_DIRECTORY_ENTRY_IMPORT)
+ if directory.Size == 0 {
+ return nil
+ }
+
+ module.modules = make([]windows.Handle, 0, 16)
+ importDesc := (*IMAGE_IMPORT_DESCRIPTOR)(a2p(module.codeBase + uintptr(directory.VirtualAddress)))
+ for importDesc.Name != 0 {
+ handle, err := windows.LoadLibraryEx(windows.BytePtrToString((*byte)(a2p(module.codeBase+uintptr(importDesc.Name)))), 0, windows.LOAD_LIBRARY_SEARCH_SYSTEM32)
+ if err != nil {
+ return fmt.Errorf("Error loading module: %w", err)
+ }
+ var thunkRef, funcRef *uintptr
+ if importDesc.OriginalFirstThunk() != 0 {
+ thunkRef = (*uintptr)(a2p(module.codeBase + uintptr(importDesc.OriginalFirstThunk())))
+ funcRef = (*uintptr)(a2p(module.codeBase + uintptr(importDesc.FirstThunk)))
+ } else {
+ // No hint table.
+ thunkRef = (*uintptr)(a2p(module.codeBase + uintptr(importDesc.FirstThunk)))
+ funcRef = (*uintptr)(a2p(module.codeBase + uintptr(importDesc.FirstThunk)))
+ }
+ for *thunkRef != 0 {
+ if IMAGE_SNAP_BY_ORDINAL(*thunkRef) {
+ *funcRef, err = windows.GetProcAddressByOrdinal(handle, IMAGE_ORDINAL(*thunkRef))
+ } else {
+ thunkData := (*IMAGE_IMPORT_BY_NAME)(a2p(module.codeBase + *thunkRef))
+ *funcRef, err = windows.GetProcAddress(handle, windows.BytePtrToString(&thunkData.Name[0]))
+ }
+ if err != nil {
+ windows.FreeLibrary(handle)
+ return fmt.Errorf("Error getting function address: %w", err)
+ }
+ thunkRef = (*uintptr)(a2p(uintptr(unsafe.Pointer(thunkRef)) + unsafe.Sizeof(*thunkRef)))
+ funcRef = (*uintptr)(a2p(uintptr(unsafe.Pointer(funcRef)) + unsafe.Sizeof(*funcRef)))
+ }
+ module.modules = append(module.modules, handle)
+ importDesc = (*IMAGE_IMPORT_DESCRIPTOR)(a2p(uintptr(unsafe.Pointer(importDesc)) + unsafe.Sizeof(*importDesc)))
+ }
+ return nil
+}
+
+func (module *Module) buildNameExports() error {
+ directory := module.headerDirectory(IMAGE_DIRECTORY_ENTRY_EXPORT)
+ if directory.Size == 0 {
+ return errors.New("No export table found")
+ }
+ exports := (*IMAGE_EXPORT_DIRECTORY)(a2p(module.codeBase + uintptr(directory.VirtualAddress)))
+ if exports.NumberOfNames == 0 || exports.NumberOfFunctions == 0 {
+ return errors.New("No functions exported")
+ }
+ if exports.NumberOfNames == 0 {
+ return errors.New("No functions exported by name")
+ }
+ nameRefs := unsafe.Slice((*uint32)(a2p(module.codeBase+uintptr(exports.AddressOfNames))), exports.NumberOfNames)
+ ordinals := unsafe.Slice((*uint16)(a2p(module.codeBase+uintptr(exports.AddressOfNameOrdinals))), exports.NumberOfNames)
+ module.nameExports = make(map[string]uint16)
+ for i := range nameRefs {
+ nameArray := windows.BytePtrToString((*byte)(a2p(module.codeBase + uintptr(nameRefs[i]))))
+ module.nameExports[nameArray] = ordinals[i]
+ }
+ return nil
+}
+
+type addressRange struct {
+ start uintptr
+ end uintptr
+}
+
+var (
+ loadedAddressRanges []addressRange
+ loadedAddressRangesMu sync.RWMutex
+ haveHookedRtlPcToFileHeader sync.Once
+ hookRtlPcToFileHeaderResult error
+)
+
+func hookRtlPcToFileHeader() error {
+ var kernelBase windows.Handle
+ err := windows.GetModuleHandleEx(windows.GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, windows.StringToUTF16Ptr("kernelbase.dll"), &kernelBase)
+ if err != nil {
+ return err
+ }
+ imageBase := unsafe.Pointer(kernelBase)
+ dosHeader := (*IMAGE_DOS_HEADER)(imageBase)
+ ntHeaders := (*IMAGE_NT_HEADERS)(unsafe.Add(imageBase, dosHeader.E_lfanew))
+ importsDirectory := ntHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
+ importDescriptor := (*IMAGE_IMPORT_DESCRIPTOR)(unsafe.Add(imageBase, importsDirectory.VirtualAddress))
+ for ; importDescriptor.Name != 0; importDescriptor = (*IMAGE_IMPORT_DESCRIPTOR)(unsafe.Add(unsafe.Pointer(importDescriptor), unsafe.Sizeof(*importDescriptor))) {
+ libraryName := windows.BytePtrToString((*byte)(unsafe.Add(imageBase, importDescriptor.Name)))
+ if strings.EqualFold(libraryName, "ntdll.dll") {
+ break
+ }
+ }
+ if importDescriptor.Name == 0 {
+ return errors.New("ntdll.dll not found")
+ }
+ originalThunk := (*uintptr)(unsafe.Add(imageBase, importDescriptor.OriginalFirstThunk()))
+ thunk := (*uintptr)(unsafe.Add(imageBase, importDescriptor.FirstThunk))
+ for ; *originalThunk != 0; originalThunk = (*uintptr)(unsafe.Add(unsafe.Pointer(originalThunk), unsafe.Sizeof(*originalThunk))) {
+ if *originalThunk&IMAGE_ORDINAL_FLAG == 0 {
+ function := (*IMAGE_IMPORT_BY_NAME)(unsafe.Add(imageBase, *originalThunk))
+ name := windows.BytePtrToString(&function.Name[0])
+ if name == "RtlPcToFileHeader" {
+ break
+ }
+ }
+ thunk = (*uintptr)(unsafe.Add(unsafe.Pointer(thunk), unsafe.Sizeof(*thunk)))
+ }
+ if *originalThunk == 0 {
+ return errors.New("RtlPcToFileHeader not found")
+ }
+ var oldProtect uint32
+ err = windows.VirtualProtect(uintptr(unsafe.Pointer(thunk)), unsafe.Sizeof(*thunk), windows.PAGE_READWRITE, &oldProtect)
+ if err != nil {
+ return err
+ }
+ originalRtlPcToFileHeader := *thunk
+ *thunk = windows.NewCallback(func(pcValue uintptr, baseOfImage *uintptr) uintptr {
+ loadedAddressRangesMu.RLock()
+ for i := range loadedAddressRanges {
+ if pcValue >= loadedAddressRanges[i].start && pcValue < loadedAddressRanges[i].end {
+ pcValue = *thunk
+ break
+ }
+ }
+ loadedAddressRangesMu.RUnlock()
+ ret, _, _ := syscall.SyscallN(originalRtlPcToFileHeader, pcValue, uintptr(unsafe.Pointer(baseOfImage)))
+ return ret
+ })
+ err = windows.VirtualProtect(uintptr(unsafe.Pointer(thunk)), unsafe.Sizeof(*thunk), oldProtect, &oldProtect)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// LoadLibrary loads module image to memory.
+func LoadLibrary(data []byte) (module *Module, err error) {
+ addr := uintptr(unsafe.Pointer(&data[0]))
+ size := uintptr(len(data))
+ if size < unsafe.Sizeof(IMAGE_DOS_HEADER{}) {
+ return nil, errors.New("Incomplete IMAGE_DOS_HEADER")
+ }
+ dosHeader := (*IMAGE_DOS_HEADER)(a2p(addr))
+ if dosHeader.E_magic != IMAGE_DOS_SIGNATURE {
+ return nil, fmt.Errorf("Not an MS-DOS binary (provided: %x, expected: %x)", dosHeader.E_magic, IMAGE_DOS_SIGNATURE)
+ }
+ if (size < uintptr(dosHeader.E_lfanew)+unsafe.Sizeof(IMAGE_NT_HEADERS{})) {
+ return nil, errors.New("Incomplete IMAGE_NT_HEADERS")
+ }
+ oldHeader := (*IMAGE_NT_HEADERS)(a2p(addr + uintptr(dosHeader.E_lfanew)))
+ if oldHeader.Signature != IMAGE_NT_SIGNATURE {
+ return nil, fmt.Errorf("Not an NT binary (provided: %x, expected: %x)", oldHeader.Signature, IMAGE_NT_SIGNATURE)
+ }
+ if oldHeader.FileHeader.Machine != imageFileProcess {
+ return nil, fmt.Errorf("Foreign platform (provided: %x, expected: %x)", oldHeader.FileHeader.Machine, imageFileProcess)
+ }
+ if (oldHeader.OptionalHeader.SectionAlignment & 1) != 0 {
+ return nil, errors.New("Unaligned section")
+ }
+ lastSectionEnd := uintptr(0)
+ sections := oldHeader.Sections()
+ optionalSectionSize := oldHeader.OptionalHeader.SectionAlignment
+ for i := range sections {
+ var endOfSection uintptr
+ if sections[i].SizeOfRawData == 0 {
+ // Section without data in the DLL
+ endOfSection = uintptr(sections[i].VirtualAddress) + uintptr(optionalSectionSize)
+ } else {
+ endOfSection = uintptr(sections[i].VirtualAddress) + uintptr(sections[i].SizeOfRawData)
+ }
+ if endOfSection > lastSectionEnd {
+ lastSectionEnd = endOfSection
+ }
+ }
+ alignedImageSize := alignUp(uintptr(oldHeader.OptionalHeader.SizeOfImage), uintptr(oldHeader.OptionalHeader.SectionAlignment))
+ if alignedImageSize != alignUp(lastSectionEnd, uintptr(oldHeader.OptionalHeader.SectionAlignment)) {
+ return nil, errors.New("Section is not page-aligned")
+ }
+
+ module = &Module{isDLL: (oldHeader.FileHeader.Characteristics & IMAGE_FILE_DLL) != 0}
+ defer func() {
+ if err != nil {
+ module.Free()
+ module = nil
+ }
+ }()
+
+ // Reserve memory for image of library.
+ // TODO: Is it correct to commit the complete memory region at once? Calling DllEntry raises an exception if we don't.
+ module.codeBase, err = windows.VirtualAlloc(oldHeader.OptionalHeader.ImageBase,
+ alignedImageSize,
+ windows.MEM_RESERVE|windows.MEM_COMMIT,
+ windows.PAGE_READWRITE)
+ if err != nil {
+ // Try to allocate memory at arbitrary position.
+ module.codeBase, err = windows.VirtualAlloc(0,
+ alignedImageSize,
+ windows.MEM_RESERVE|windows.MEM_COMMIT,
+ windows.PAGE_READWRITE)
+ if err != nil {
+ err = fmt.Errorf("Error allocating code: %w", err)
+ return
+ }
+ }
+ err = module.check4GBBoundaries(alignedImageSize)
+ if err != nil {
+ err = fmt.Errorf("Error reallocating code: %w", err)
+ return
+ }
+
+ if size < uintptr(oldHeader.OptionalHeader.SizeOfHeaders) {
+ err = errors.New("Incomplete headers")
+ return
+ }
+ // Commit memory for headers.
+ headers, err := windows.VirtualAlloc(module.codeBase,
+ uintptr(oldHeader.OptionalHeader.SizeOfHeaders),
+ windows.MEM_COMMIT,
+ windows.PAGE_READWRITE)
+ if err != nil {
+ err = fmt.Errorf("Error allocating headers: %w", err)
+ return
+ }
+ // Copy PE header to code.
+ memcpy(headers, addr, uintptr(oldHeader.OptionalHeader.SizeOfHeaders))
+ module.headers = (*IMAGE_NT_HEADERS)(a2p(headers + uintptr(dosHeader.E_lfanew)))
+
+ // Update position.
+ module.headers.OptionalHeader.ImageBase = module.codeBase
+
+ // Copy sections from DLL file block to new memory location.
+ err = module.copySections(addr, size, oldHeader)
+ if err != nil {
+ err = fmt.Errorf("Error copying sections: %w", err)
+ return
+ }
+
+ // Adjust base address of imported data.
+ locationDelta := module.headers.OptionalHeader.ImageBase - oldHeader.OptionalHeader.ImageBase
+ if locationDelta != 0 {
+ module.isRelocated, err = module.performBaseRelocation(locationDelta)
+ if err != nil {
+ err = fmt.Errorf("Error relocating module: %w", err)
+ return
+ }
+ } else {
+ module.isRelocated = true
+ }
+
+ // Load required dlls and adjust function table of imports.
+ err = module.buildImportTable()
+ if err != nil {
+ err = fmt.Errorf("Error building import table: %w", err)
+ return
+ }
+
+ // Mark memory pages depending on section headers and release sections that are marked as "discardable".
+ err = module.finalizeSections()
+ if err != nil {
+ err = fmt.Errorf("Error finalizing sections: %w", err)
+ return
+ }
+
+ // Register exception tables, if they exist.
+ module.registerExceptionHandlers()
+
+ // Register function PCs.
+ loadedAddressRangesMu.Lock()
+ loadedAddressRanges = append(loadedAddressRanges, addressRange{module.codeBase, module.codeBase + alignedImageSize})
+ loadedAddressRangesMu.Unlock()
+ haveHookedRtlPcToFileHeader.Do(func() {
+ hookRtlPcToFileHeaderResult = hookRtlPcToFileHeader()
+ })
+ err = hookRtlPcToFileHeaderResult
+ if err != nil {
+ return
+ }
+
+ // TLS callbacks are executed BEFORE the main loading.
+ module.executeTLS()
+
+ // Get entry point of loaded module.
+ if module.headers.OptionalHeader.AddressOfEntryPoint != 0 {
+ module.entry = module.codeBase + uintptr(module.headers.OptionalHeader.AddressOfEntryPoint)
+ if module.isDLL {
+ // Notify library about attaching to process.
+ r0, _, _ := syscall.SyscallN(module.entry, module.codeBase, DLL_PROCESS_ATTACH, 0)
+ successful := r0 != 0
+ if !successful {
+ err = windows.ERROR_DLL_INIT_FAILED
+ return
+ }
+ module.initialized = true
+ }
+ }
+
+ module.buildNameExports()
+ return
+}
+
+// Free releases module resources and unloads it.
+func (module *Module) Free() {
+ if module.initialized {
+ // Notify library about detaching from process.
+ syscall.SyscallN(module.entry, module.codeBase, DLL_PROCESS_DETACH, 0)
+ module.initialized = false
+ }
+ if module.modules != nil {
+ // Free previously opened libraries.
+ for _, handle := range module.modules {
+ windows.FreeLibrary(handle)
+ }
+ module.modules = nil
+ }
+ if module.codeBase != 0 {
+ windows.VirtualFree(module.codeBase, 0, windows.MEM_RELEASE)
+ module.codeBase = 0
+ }
+ if module.blockedMemory != nil {
+ module.blockedMemory.free()
+ module.blockedMemory = nil
+ }
+}
+
+// ProcAddressByName returns function address by exported name.
+func (module *Module) ProcAddressByName(name string) (uintptr, error) {
+ directory := module.headerDirectory(IMAGE_DIRECTORY_ENTRY_EXPORT)
+ if directory.Size == 0 {
+ return 0, errors.New("No export table found")
+ }
+ exports := (*IMAGE_EXPORT_DIRECTORY)(a2p(module.codeBase + uintptr(directory.VirtualAddress)))
+ if module.nameExports == nil {
+ return 0, errors.New("No functions exported by name")
+ }
+ if idx, ok := module.nameExports[name]; ok {
+ if uint32(idx) > exports.NumberOfFunctions {
+ return 0, errors.New("Ordinal number too high")
+ }
+ // AddressOfFunctions contains the RVAs to the "real" functions.
+ return module.codeBase + uintptr(*(*uint32)(a2p(module.codeBase + uintptr(exports.AddressOfFunctions) + uintptr(idx)*4))), nil
+ }
+ return 0, errors.New("Function not found by name")
+}
+
+// ProcAddressByOrdinal returns function address by exported ordinal.
+func (module *Module) ProcAddressByOrdinal(ordinal uint16) (uintptr, error) {
+ directory := module.headerDirectory(IMAGE_DIRECTORY_ENTRY_EXPORT)
+ if directory.Size == 0 {
+ return 0, errors.New("No export table found")
+ }
+ exports := (*IMAGE_EXPORT_DIRECTORY)(a2p(module.codeBase + uintptr(directory.VirtualAddress)))
+ if uint32(ordinal) < exports.Base {
+ return 0, errors.New("Ordinal number too low")
+ }
+ idx := ordinal - uint16(exports.Base)
+ if uint32(idx) > exports.NumberOfFunctions {
+ return 0, errors.New("Ordinal number too high")
+ }
+ // AddressOfFunctions contains the RVAs to the "real" functions.
+ return module.codeBase + uintptr(*(*uint32)(a2p(module.codeBase + uintptr(exports.AddressOfFunctions) + uintptr(idx)*4))), nil
+}
+
+func alignDown(value, alignment uintptr) uintptr {
+ return value & ^(alignment - 1)
+}
+
+func alignUp(value, alignment uintptr) uintptr {
+ return (value + alignment - 1) & ^(alignment - 1)
+}
+
+func a2p(addr uintptr) unsafe.Pointer {
+ return unsafe.Pointer(addr)
+}
+
+func memcpy(dst, src, size uintptr) {
+ copy(unsafe.Slice((*byte)(a2p(dst)), size), unsafe.Slice((*byte)(a2p(src)), size))
+}
diff --git a/driver/memmod/memmod_windows_32.go b/driver/memmod/memmod_windows_32.go
new file mode 100644
index 00000000..611fbe53
--- /dev/null
+++ b/driver/memmod/memmod_windows_32.go
@@ -0,0 +1,16 @@
+//go:build (windows && 386) || (windows && arm)
+
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2017-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package memmod
+
+func (opthdr *IMAGE_OPTIONAL_HEADER) imageOffset() uintptr {
+ return 0
+}
+
+func (module *Module) check4GBBoundaries(alignedImageSize uintptr) (err error) {
+ return
+}
diff --git a/driver/memmod/memmod_windows_386.go b/driver/memmod/memmod_windows_386.go
new file mode 100644
index 00000000..71d6c93e
--- /dev/null
+++ b/driver/memmod/memmod_windows_386.go
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2017-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package memmod
+
+const imageFileProcess = IMAGE_FILE_MACHINE_I386
diff --git a/driver/memmod/memmod_windows_64.go b/driver/memmod/memmod_windows_64.go
new file mode 100644
index 00000000..28ada96e
--- /dev/null
+++ b/driver/memmod/memmod_windows_64.go
@@ -0,0 +1,36 @@
+//go:build (windows && amd64) || (windows && arm64)
+
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2017-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package memmod
+
+import (
+ "fmt"
+
+ "golang.org/x/sys/windows"
+)
+
+func (opthdr *IMAGE_OPTIONAL_HEADER) imageOffset() uintptr {
+ return uintptr(opthdr.ImageBase & 0xffffffff00000000)
+}
+
+func (module *Module) check4GBBoundaries(alignedImageSize uintptr) (err error) {
+ for (module.codeBase >> 32) < ((module.codeBase + alignedImageSize) >> 32) {
+ node := &addressList{
+ next: module.blockedMemory,
+ address: module.codeBase,
+ }
+ module.blockedMemory = node
+ module.codeBase, err = windows.VirtualAlloc(0,
+ alignedImageSize,
+ windows.MEM_RESERVE|windows.MEM_COMMIT,
+ windows.PAGE_READWRITE)
+ if err != nil {
+ return fmt.Errorf("Error allocating memory block: %w", err)
+ }
+ }
+ return
+}
diff --git a/driver/memmod/memmod_windows_amd64.go b/driver/memmod/memmod_windows_amd64.go
new file mode 100644
index 00000000..2459b2d2
--- /dev/null
+++ b/driver/memmod/memmod_windows_amd64.go
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2017-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package memmod
+
+const imageFileProcess = IMAGE_FILE_MACHINE_AMD64
diff --git a/driver/memmod/memmod_windows_arm.go b/driver/memmod/memmod_windows_arm.go
new file mode 100644
index 00000000..a644cee6
--- /dev/null
+++ b/driver/memmod/memmod_windows_arm.go
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2017-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package memmod
+
+const imageFileProcess = IMAGE_FILE_MACHINE_ARMNT
diff --git a/driver/memmod/memmod_windows_arm64.go b/driver/memmod/memmod_windows_arm64.go
new file mode 100644
index 00000000..09e24639
--- /dev/null
+++ b/driver/memmod/memmod_windows_arm64.go
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2017-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package memmod
+
+const imageFileProcess = IMAGE_FILE_MACHINE_ARM64
diff --git a/driver/memmod/syscall_windows.go b/driver/memmod/syscall_windows.go
new file mode 100644
index 00000000..cdc6ef67
--- /dev/null
+++ b/driver/memmod/syscall_windows.go
@@ -0,0 +1,392 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2017-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package memmod
+
+import "unsafe"
+
+const (
+ IMAGE_DOS_SIGNATURE = 0x5A4D // MZ
+ IMAGE_OS2_SIGNATURE = 0x454E // NE
+ IMAGE_OS2_SIGNATURE_LE = 0x454C // LE
+ IMAGE_VXD_SIGNATURE = 0x454C // LE
+ IMAGE_NT_SIGNATURE = 0x00004550 // PE00
+)
+
+// DOS .EXE header
+type IMAGE_DOS_HEADER struct {
+ E_magic uint16 // Magic number
+ E_cblp uint16 // Bytes on last page of file
+ E_cp uint16 // Pages in file
+ E_crlc uint16 // Relocations
+ E_cparhdr uint16 // Size of header in paragraphs
+ E_minalloc uint16 // Minimum extra paragraphs needed
+ E_maxalloc uint16 // Maximum extra paragraphs needed
+ E_ss uint16 // Initial (relative) SS value
+ E_sp uint16 // Initial SP value
+ E_csum uint16 // Checksum
+ E_ip uint16 // Initial IP value
+ E_cs uint16 // Initial (relative) CS value
+ E_lfarlc uint16 // File address of relocation table
+ E_ovno uint16 // Overlay number
+ E_res [4]uint16 // Reserved words
+ E_oemid uint16 // OEM identifier (for e_oeminfo)
+ E_oeminfo uint16 // OEM information; e_oemid specific
+ E_res2 [10]uint16 // Reserved words
+ E_lfanew int32 // File address of new exe header
+}
+
+// File header format
+type IMAGE_FILE_HEADER struct {
+ Machine uint16
+ NumberOfSections uint16
+ TimeDateStamp uint32
+ PointerToSymbolTable uint32
+ NumberOfSymbols uint32
+ SizeOfOptionalHeader uint16
+ Characteristics uint16
+}
+
+const (
+ IMAGE_SIZEOF_FILE_HEADER = 20
+
+ IMAGE_FILE_RELOCS_STRIPPED = 0x0001 // Relocation info stripped from file.
+ IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002 // File is executable (i.e. no unresolved external references).
+ IMAGE_FILE_LINE_NUMS_STRIPPED = 0x0004 // Line nunbers stripped from file.
+ IMAGE_FILE_LOCAL_SYMS_STRIPPED = 0x0008 // Local symbols stripped from file.
+ IMAGE_FILE_AGGRESIVE_WS_TRIM = 0x0010 // Aggressively trim working set
+ IMAGE_FILE_LARGE_ADDRESS_AWARE = 0x0020 // App can handle >2gb addresses
+ IMAGE_FILE_BYTES_REVERSED_LO = 0x0080 // Bytes of machine word are reversed.
+ IMAGE_FILE_32BIT_MACHINE = 0x0100 // 32 bit word machine.
+ IMAGE_FILE_DEBUG_STRIPPED = 0x0200 // Debugging info stripped from file in .DBG file
+ IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP = 0x0400 // If Image is on removable media, copy and run from the swap file.
+ IMAGE_FILE_NET_RUN_FROM_SWAP = 0x0800 // If Image is on Net, copy and run from the swap file.
+ IMAGE_FILE_SYSTEM = 0x1000 // System File.
+ IMAGE_FILE_DLL = 0x2000 // File is a DLL.
+ IMAGE_FILE_UP_SYSTEM_ONLY = 0x4000 // File should only be run on a UP machine
+ IMAGE_FILE_BYTES_REVERSED_HI = 0x8000 // Bytes of machine word are reversed.
+
+ IMAGE_FILE_MACHINE_UNKNOWN = 0
+ IMAGE_FILE_MACHINE_TARGET_HOST = 0x0001 // Useful for indicating we want to interact with the host and not a WoW guest.
+ IMAGE_FILE_MACHINE_I386 = 0x014c // Intel 386.
+ IMAGE_FILE_MACHINE_R3000 = 0x0162 // MIPS little-endian, 0x160 big-endian
+ IMAGE_FILE_MACHINE_R4000 = 0x0166 // MIPS little-endian
+ IMAGE_FILE_MACHINE_R10000 = 0x0168 // MIPS little-endian
+ IMAGE_FILE_MACHINE_WCEMIPSV2 = 0x0169 // MIPS little-endian WCE v2
+ IMAGE_FILE_MACHINE_ALPHA = 0x0184 // Alpha_AXP
+ IMAGE_FILE_MACHINE_SH3 = 0x01a2 // SH3 little-endian
+ IMAGE_FILE_MACHINE_SH3DSP = 0x01a3
+ IMAGE_FILE_MACHINE_SH3E = 0x01a4 // SH3E little-endian
+ IMAGE_FILE_MACHINE_SH4 = 0x01a6 // SH4 little-endian
+ IMAGE_FILE_MACHINE_SH5 = 0x01a8 // SH5
+ IMAGE_FILE_MACHINE_ARM = 0x01c0 // ARM Little-Endian
+ IMAGE_FILE_MACHINE_THUMB = 0x01c2 // ARM Thumb/Thumb-2 Little-Endian
+ IMAGE_FILE_MACHINE_ARMNT = 0x01c4 // ARM Thumb-2 Little-Endian
+ IMAGE_FILE_MACHINE_AM33 = 0x01d3
+ IMAGE_FILE_MACHINE_POWERPC = 0x01F0 // IBM PowerPC Little-Endian
+ IMAGE_FILE_MACHINE_POWERPCFP = 0x01f1
+ IMAGE_FILE_MACHINE_IA64 = 0x0200 // Intel 64
+ IMAGE_FILE_MACHINE_MIPS16 = 0x0266 // MIPS
+ IMAGE_FILE_MACHINE_ALPHA64 = 0x0284 // ALPHA64
+ IMAGE_FILE_MACHINE_MIPSFPU = 0x0366 // MIPS
+ IMAGE_FILE_MACHINE_MIPSFPU16 = 0x0466 // MIPS
+ IMAGE_FILE_MACHINE_AXP64 = IMAGE_FILE_MACHINE_ALPHA64
+ IMAGE_FILE_MACHINE_TRICORE = 0x0520 // Infineon
+ IMAGE_FILE_MACHINE_CEF = 0x0CEF
+ IMAGE_FILE_MACHINE_EBC = 0x0EBC // EFI Byte Code
+ IMAGE_FILE_MACHINE_AMD64 = 0x8664 // AMD64 (K8)
+ IMAGE_FILE_MACHINE_M32R = 0x9041 // M32R little-endian
+ IMAGE_FILE_MACHINE_ARM64 = 0xAA64 // ARM64 Little-Endian
+ IMAGE_FILE_MACHINE_CEE = 0xC0EE
+)
+
+// Directory format
+type IMAGE_DATA_DIRECTORY struct {
+ VirtualAddress uint32
+ Size uint32
+}
+
+const IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16
+
+type IMAGE_NT_HEADERS struct {
+ Signature uint32
+ FileHeader IMAGE_FILE_HEADER
+ OptionalHeader IMAGE_OPTIONAL_HEADER
+}
+
+func (ntheader *IMAGE_NT_HEADERS) Sections() []IMAGE_SECTION_HEADER {
+ return (*[0xffff]IMAGE_SECTION_HEADER)(unsafe.Pointer(
+ (uintptr)(unsafe.Pointer(ntheader)) +
+ unsafe.Offsetof(ntheader.OptionalHeader) +
+ uintptr(ntheader.FileHeader.SizeOfOptionalHeader)))[:ntheader.FileHeader.NumberOfSections]
+}
+
+const (
+ IMAGE_DIRECTORY_ENTRY_EXPORT = 0 // Export Directory
+ IMAGE_DIRECTORY_ENTRY_IMPORT = 1 // Import Directory
+ IMAGE_DIRECTORY_ENTRY_RESOURCE = 2 // Resource Directory
+ IMAGE_DIRECTORY_ENTRY_EXCEPTION = 3 // Exception Directory
+ IMAGE_DIRECTORY_ENTRY_SECURITY = 4 // Security Directory
+ IMAGE_DIRECTORY_ENTRY_BASERELOC = 5 // Base Relocation Table
+ IMAGE_DIRECTORY_ENTRY_DEBUG = 6 // Debug Directory
+ IMAGE_DIRECTORY_ENTRY_COPYRIGHT = 7 // (X86 usage)
+ IMAGE_DIRECTORY_ENTRY_ARCHITECTURE = 7 // Architecture Specific Data
+ IMAGE_DIRECTORY_ENTRY_GLOBALPTR = 8 // RVA of GP
+ IMAGE_DIRECTORY_ENTRY_TLS = 9 // TLS Directory
+ IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG = 10 // Load Configuration Directory
+ IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT = 11 // Bound Import Directory in headers
+ IMAGE_DIRECTORY_ENTRY_IAT = 12 // Import Address Table
+ IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT = 13 // Delay Load Import Descriptors
+ IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR = 14 // COM Runtime descriptor
+)
+
+const IMAGE_SIZEOF_SHORT_NAME = 8
+
+// Section header format
+type IMAGE_SECTION_HEADER struct {
+ Name [IMAGE_SIZEOF_SHORT_NAME]byte
+ physicalAddressOrVirtualSize uint32
+ VirtualAddress uint32
+ SizeOfRawData uint32
+ PointerToRawData uint32
+ PointerToRelocations uint32
+ PointerToLinenumbers uint32
+ NumberOfRelocations uint16
+ NumberOfLinenumbers uint16
+ Characteristics uint32
+}
+
+func (ishdr *IMAGE_SECTION_HEADER) PhysicalAddress() uint32 {
+ return ishdr.physicalAddressOrVirtualSize
+}
+
+func (ishdr *IMAGE_SECTION_HEADER) SetPhysicalAddress(addr uint32) {
+ ishdr.physicalAddressOrVirtualSize = addr
+}
+
+func (ishdr *IMAGE_SECTION_HEADER) VirtualSize() uint32 {
+ return ishdr.physicalAddressOrVirtualSize
+}
+
+func (ishdr *IMAGE_SECTION_HEADER) SetVirtualSize(addr uint32) {
+ ishdr.physicalAddressOrVirtualSize = addr
+}
+
+const (
+ // Dll characteristics.
+ IMAGE_DLL_CHARACTERISTICS_HIGH_ENTROPY_VA = 0x0020
+ IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE = 0x0040
+ IMAGE_DLL_CHARACTERISTICS_FORCE_INTEGRITY = 0x0080
+ IMAGE_DLL_CHARACTERISTICS_NX_COMPAT = 0x0100
+ IMAGE_DLL_CHARACTERISTICS_NO_ISOLATION = 0x0200
+ IMAGE_DLL_CHARACTERISTICS_NO_SEH = 0x0400
+ IMAGE_DLL_CHARACTERISTICS_NO_BIND = 0x0800
+ IMAGE_DLL_CHARACTERISTICS_APPCONTAINER = 0x1000
+ IMAGE_DLL_CHARACTERISTICS_WDM_DRIVER = 0x2000
+ IMAGE_DLL_CHARACTERISTICS_GUARD_CF = 0x4000
+ IMAGE_DLL_CHARACTERISTICS_TERMINAL_SERVER_AWARE = 0x8000
+)
+
+const (
+ // Section characteristics.
+ IMAGE_SCN_TYPE_REG = 0x00000000 // Reserved.
+ IMAGE_SCN_TYPE_DSECT = 0x00000001 // Reserved.
+ IMAGE_SCN_TYPE_NOLOAD = 0x00000002 // Reserved.
+ IMAGE_SCN_TYPE_GROUP = 0x00000004 // Reserved.
+ IMAGE_SCN_TYPE_NO_PAD = 0x00000008 // Reserved.
+ IMAGE_SCN_TYPE_COPY = 0x00000010 // Reserved.
+
+ IMAGE_SCN_CNT_CODE = 0x00000020 // Section contains code.
+ IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040 // Section contains initialized data.
+ IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080 // Section contains uninitialized data.
+
+ IMAGE_SCN_LNK_OTHER = 0x00000100 // Reserved.
+ IMAGE_SCN_LNK_INFO = 0x00000200 // Section contains comments or some other type of information.
+ IMAGE_SCN_TYPE_OVER = 0x00000400 // Reserved.
+ IMAGE_SCN_LNK_REMOVE = 0x00000800 // Section contents will not become part of image.
+ IMAGE_SCN_LNK_COMDAT = 0x00001000 // Section contents comdat.
+ IMAGE_SCN_MEM_PROTECTED = 0x00004000 // Obsolete.
+ IMAGE_SCN_NO_DEFER_SPEC_EXC = 0x00004000 // Reset speculative exceptions handling bits in the TLB entries for this section.
+ IMAGE_SCN_GPREL = 0x00008000 // Section content can be accessed relative to GP
+ IMAGE_SCN_MEM_FARDATA = 0x00008000
+ IMAGE_SCN_MEM_SYSHEAP = 0x00010000 // Obsolete.
+ IMAGE_SCN_MEM_PURGEABLE = 0x00020000
+ IMAGE_SCN_MEM_16BIT = 0x00020000
+ IMAGE_SCN_MEM_LOCKED = 0x00040000
+ IMAGE_SCN_MEM_PRELOAD = 0x00080000
+
+ IMAGE_SCN_ALIGN_1BYTES = 0x00100000 //
+ IMAGE_SCN_ALIGN_2BYTES = 0x00200000 //
+ IMAGE_SCN_ALIGN_4BYTES = 0x00300000 //
+ IMAGE_SCN_ALIGN_8BYTES = 0x00400000 //
+ IMAGE_SCN_ALIGN_16BYTES = 0x00500000 // Default alignment if no others are specified.
+ IMAGE_SCN_ALIGN_32BYTES = 0x00600000 //
+ IMAGE_SCN_ALIGN_64BYTES = 0x00700000 //
+ IMAGE_SCN_ALIGN_128BYTES = 0x00800000 //
+ IMAGE_SCN_ALIGN_256BYTES = 0x00900000 //
+ IMAGE_SCN_ALIGN_512BYTES = 0x00A00000 //
+ IMAGE_SCN_ALIGN_1024BYTES = 0x00B00000 //
+ IMAGE_SCN_ALIGN_2048BYTES = 0x00C00000 //
+ IMAGE_SCN_ALIGN_4096BYTES = 0x00D00000 //
+ IMAGE_SCN_ALIGN_8192BYTES = 0x00E00000 //
+ IMAGE_SCN_ALIGN_MASK = 0x00F00000
+
+ IMAGE_SCN_LNK_NRELOC_OVFL = 0x01000000 // Section contains extended relocations.
+ IMAGE_SCN_MEM_DISCARDABLE = 0x02000000 // Section can be discarded.
+ IMAGE_SCN_MEM_NOT_CACHED = 0x04000000 // Section is not cachable.
+ IMAGE_SCN_MEM_NOT_PAGED = 0x08000000 // Section is not pageable.
+ IMAGE_SCN_MEM_SHARED = 0x10000000 // Section is shareable.
+ IMAGE_SCN_MEM_EXECUTE = 0x20000000 // Section is executable.
+ IMAGE_SCN_MEM_READ = 0x40000000 // Section is readable.
+ IMAGE_SCN_MEM_WRITE = 0x80000000 // Section is writeable.
+
+ // TLS Characteristic Flags
+ IMAGE_SCN_SCALE_INDEX = 0x00000001 // Tls index is scaled.
+)
+
+// Based relocation format
+type IMAGE_BASE_RELOCATION struct {
+ VirtualAddress uint32
+ SizeOfBlock uint32
+}
+
+const (
+ IMAGE_REL_BASED_ABSOLUTE = 0
+ IMAGE_REL_BASED_HIGH = 1
+ IMAGE_REL_BASED_LOW = 2
+ IMAGE_REL_BASED_HIGHLOW = 3
+ IMAGE_REL_BASED_HIGHADJ = 4
+ IMAGE_REL_BASED_MACHINE_SPECIFIC_5 = 5
+ IMAGE_REL_BASED_RESERVED = 6
+ IMAGE_REL_BASED_MACHINE_SPECIFIC_7 = 7
+ IMAGE_REL_BASED_MACHINE_SPECIFIC_8 = 8
+ IMAGE_REL_BASED_MACHINE_SPECIFIC_9 = 9
+ IMAGE_REL_BASED_DIR64 = 10
+
+ IMAGE_REL_BASED_IA64_IMM64 = 9
+
+ IMAGE_REL_BASED_MIPS_JMPADDR = 5
+ IMAGE_REL_BASED_MIPS_JMPADDR16 = 9
+
+ IMAGE_REL_BASED_ARM_MOV32 = 5
+ IMAGE_REL_BASED_THUMB_MOV32 = 7
+)
+
+// Export Format
+type IMAGE_EXPORT_DIRECTORY struct {
+ Characteristics uint32
+ TimeDateStamp uint32
+ MajorVersion uint16
+ MinorVersion uint16
+ Name uint32
+ Base uint32
+ NumberOfFunctions uint32
+ NumberOfNames uint32
+ AddressOfFunctions uint32 // RVA from base of image
+ AddressOfNames uint32 // RVA from base of image
+ AddressOfNameOrdinals uint32 // RVA from base of image
+}
+
+type IMAGE_IMPORT_BY_NAME struct {
+ Hint uint16
+ Name [1]byte
+}
+
+func IMAGE_ORDINAL(ordinal uintptr) uintptr {
+ return ordinal & 0xffff
+}
+
+func IMAGE_SNAP_BY_ORDINAL(ordinal uintptr) bool {
+ return (ordinal & IMAGE_ORDINAL_FLAG) != 0
+}
+
+// Thread Local Storage
+type IMAGE_TLS_DIRECTORY struct {
+ StartAddressOfRawData uintptr
+ EndAddressOfRawData uintptr
+ AddressOfIndex uintptr // PDWORD
+ AddressOfCallbacks uintptr // PIMAGE_TLS_CALLBACK *;
+ SizeOfZeroFill uint32
+ Characteristics uint32
+}
+
+type IMAGE_IMPORT_DESCRIPTOR struct {
+ characteristicsOrOriginalFirstThunk uint32 // 0 for terminating null import descriptor
+ // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
+ TimeDateStamp uint32 // 0 if not bound,
+ // -1 if bound, and real date\time stamp
+ // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
+ // O.W. date/time stamp of DLL bound to (Old BIND)
+ ForwarderChain uint32 // -1 if no forwarders
+ Name uint32
+ FirstThunk uint32 // RVA to IAT (if bound this IAT has actual addresses)
+}
+
+func (imgimpdesc *IMAGE_IMPORT_DESCRIPTOR) Characteristics() uint32 {
+ return imgimpdesc.characteristicsOrOriginalFirstThunk
+}
+
+func (imgimpdesc *IMAGE_IMPORT_DESCRIPTOR) OriginalFirstThunk() uint32 {
+ return imgimpdesc.characteristicsOrOriginalFirstThunk
+}
+
+type IMAGE_DELAYLOAD_DESCRIPTOR struct {
+ Attributes uint32
+ DllNameRVA uint32
+ ModuleHandleRVA uint32
+ ImportAddressTableRVA uint32
+ ImportNameTableRVA uint32
+ BoundImportAddressTableRVA uint32
+ UnloadInformationTableRVA uint32
+ TimeDateStamp uint32
+}
+
+type IMAGE_LOAD_CONFIG_CODE_INTEGRITY struct {
+ Flags uint16
+ Catalog uint16
+ CatalogOffset uint32
+ Reserved uint32
+}
+
+const (
+ IMAGE_GUARD_CF_INSTRUMENTED = 0x00000100
+ IMAGE_GUARD_CFW_INSTRUMENTED = 0x00000200
+ IMAGE_GUARD_CF_FUNCTION_TABLE_PRESENT = 0x00000400
+ IMAGE_GUARD_SECURITY_COOKIE_UNUSED = 0x00000800
+ IMAGE_GUARD_PROTECT_DELAYLOAD_IAT = 0x00001000
+ IMAGE_GUARD_DELAYLOAD_IAT_IN_ITS_OWN_SECTION = 0x00002000
+ IMAGE_GUARD_CF_EXPORT_SUPPRESSION_INFO_PRESENT = 0x00004000
+ IMAGE_GUARD_CF_ENABLE_EXPORT_SUPPRESSION = 0x00008000
+ IMAGE_GUARD_CF_LONGJUMP_TABLE_PRESENT = 0x00010000
+ IMAGE_GUARD_RF_INSTRUMENTED = 0x00020000
+ IMAGE_GUARD_RF_ENABLE = 0x00040000
+ IMAGE_GUARD_RF_STRICT = 0x00080000
+ IMAGE_GUARD_RETPOLINE_PRESENT = 0x00100000
+ IMAGE_GUARD_EH_CONTINUATION_TABLE_PRESENT = 0x00400000
+ IMAGE_GUARD_XFG_ENABLED = 0x00800000
+ IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK = 0xF0000000
+ IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_SHIFT = 28
+)
+
+const (
+ DLL_PROCESS_ATTACH = 1
+ DLL_THREAD_ATTACH = 2
+ DLL_THREAD_DETACH = 3
+ DLL_PROCESS_DETACH = 0
+)
+
+type SYSTEM_INFO struct {
+ ProcessorArchitecture uint16
+ Reserved uint16
+ PageSize uint32
+ MinimumApplicationAddress uintptr
+ MaximumApplicationAddress uintptr
+ ActiveProcessorMask uintptr
+ NumberOfProcessors uint32
+ ProcessorType uint32
+ AllocationGranularity uint32
+ ProcessorLevel uint16
+ ProcessorRevision uint16
+}
diff --git a/driver/memmod/syscall_windows_32.go b/driver/memmod/syscall_windows_32.go
new file mode 100644
index 00000000..dde8b360
--- /dev/null
+++ b/driver/memmod/syscall_windows_32.go
@@ -0,0 +1,96 @@
+//go:build (windows && 386) || (windows && arm)
+
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2017-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package memmod
+
+// Optional header format
+type IMAGE_OPTIONAL_HEADER struct {
+ Magic uint16
+ MajorLinkerVersion uint8
+ MinorLinkerVersion uint8
+ SizeOfCode uint32
+ SizeOfInitializedData uint32
+ SizeOfUninitializedData uint32
+ AddressOfEntryPoint uint32
+ BaseOfCode uint32
+ BaseOfData uint32
+ ImageBase uintptr
+ SectionAlignment uint32
+ FileAlignment uint32
+ MajorOperatingSystemVersion uint16
+ MinorOperatingSystemVersion uint16
+ MajorImageVersion uint16
+ MinorImageVersion uint16
+ MajorSubsystemVersion uint16
+ MinorSubsystemVersion uint16
+ Win32VersionValue uint32
+ SizeOfImage uint32
+ SizeOfHeaders uint32
+ CheckSum uint32
+ Subsystem uint16
+ DllCharacteristics uint16
+ SizeOfStackReserve uintptr
+ SizeOfStackCommit uintptr
+ SizeOfHeapReserve uintptr
+ SizeOfHeapCommit uintptr
+ LoaderFlags uint32
+ NumberOfRvaAndSizes uint32
+ DataDirectory [IMAGE_NUMBEROF_DIRECTORY_ENTRIES]IMAGE_DATA_DIRECTORY
+}
+
+const IMAGE_ORDINAL_FLAG uintptr = 0x80000000
+
+type IMAGE_LOAD_CONFIG_DIRECTORY struct {
+ Size uint32
+ TimeDateStamp uint32
+ MajorVersion uint16
+ MinorVersion uint16
+ GlobalFlagsClear uint32
+ GlobalFlagsSet uint32
+ CriticalSectionDefaultTimeout uint32
+ DeCommitFreeBlockThreshold uint32
+ DeCommitTotalFreeThreshold uint32
+ LockPrefixTable uint32
+ MaximumAllocationSize uint32
+ VirtualMemoryThreshold uint32
+ ProcessHeapFlags uint32
+ ProcessAffinityMask uint32
+ CSDVersion uint16
+ DependentLoadFlags uint16
+ EditList uint32
+ SecurityCookie uint32
+ SEHandlerTable uint32
+ SEHandlerCount uint32
+ GuardCFCheckFunctionPointer uint32
+ GuardCFDispatchFunctionPointer uint32
+ GuardCFFunctionTable uint32
+ GuardCFFunctionCount uint32
+ GuardFlags uint32
+ CodeIntegrity IMAGE_LOAD_CONFIG_CODE_INTEGRITY
+ GuardAddressTakenIatEntryTable uint32
+ GuardAddressTakenIatEntryCount uint32
+ GuardLongJumpTargetTable uint32
+ GuardLongJumpTargetCount uint32
+ DynamicValueRelocTable uint32
+ CHPEMetadataPointer uint32
+ GuardRFFailureRoutine uint32
+ GuardRFFailureRoutineFunctionPointer uint32
+ DynamicValueRelocTableOffset uint32
+ DynamicValueRelocTableSection uint16
+ Reserved2 uint16
+ GuardRFVerifyStackPointerFunctionPointer uint32
+ HotPatchTableOffset uint32
+ Reserved3 uint32
+ EnclaveConfigurationPointer uint32
+ VolatileMetadataPointer uint32
+ GuardEHContinuationTable uint32
+ GuardEHContinuationCount uint32
+ GuardXFGCheckFunctionPointer uint32
+ GuardXFGDispatchFunctionPointer uint32
+ GuardXFGTableDispatchFunctionPointer uint32
+ CastGuardOsDeterminedFailureMode uint32
+}
diff --git a/driver/memmod/syscall_windows_64.go b/driver/memmod/syscall_windows_64.go
new file mode 100644
index 00000000..f23524fb
--- /dev/null
+++ b/driver/memmod/syscall_windows_64.go
@@ -0,0 +1,95 @@
+//go:build (windows && amd64) || (windows && arm64)
+
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2017-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package memmod
+
+// Optional header format
+type IMAGE_OPTIONAL_HEADER struct {
+ Magic uint16
+ MajorLinkerVersion uint8
+ MinorLinkerVersion uint8
+ SizeOfCode uint32
+ SizeOfInitializedData uint32
+ SizeOfUninitializedData uint32
+ AddressOfEntryPoint uint32
+ BaseOfCode uint32
+ ImageBase uintptr
+ SectionAlignment uint32
+ FileAlignment uint32
+ MajorOperatingSystemVersion uint16
+ MinorOperatingSystemVersion uint16
+ MajorImageVersion uint16
+ MinorImageVersion uint16
+ MajorSubsystemVersion uint16
+ MinorSubsystemVersion uint16
+ Win32VersionValue uint32
+ SizeOfImage uint32
+ SizeOfHeaders uint32
+ CheckSum uint32
+ Subsystem uint16
+ DllCharacteristics uint16
+ SizeOfStackReserve uintptr
+ SizeOfStackCommit uintptr
+ SizeOfHeapReserve uintptr
+ SizeOfHeapCommit uintptr
+ LoaderFlags uint32
+ NumberOfRvaAndSizes uint32
+ DataDirectory [IMAGE_NUMBEROF_DIRECTORY_ENTRIES]IMAGE_DATA_DIRECTORY
+}
+
+const IMAGE_ORDINAL_FLAG uintptr = 0x8000000000000000
+
+type IMAGE_LOAD_CONFIG_DIRECTORY struct {
+ Size uint32
+ TimeDateStamp uint32
+ MajorVersion uint16
+ MinorVersion uint16
+ GlobalFlagsClear uint32
+ GlobalFlagsSet uint32
+ CriticalSectionDefaultTimeout uint32
+ DeCommitFreeBlockThreshold uint64
+ DeCommitTotalFreeThreshold uint64
+ LockPrefixTable uint64
+ MaximumAllocationSize uint64
+ VirtualMemoryThreshold uint64
+ ProcessAffinityMask uint64
+ ProcessHeapFlags uint32
+ CSDVersion uint16
+ DependentLoadFlags uint16
+ EditList uint64
+ SecurityCookie uint64
+ SEHandlerTable uint64
+ SEHandlerCount uint64
+ GuardCFCheckFunctionPointer uint64
+ GuardCFDispatchFunctionPointer uint64
+ GuardCFFunctionTable uint64
+ GuardCFFunctionCount uint64
+ GuardFlags uint32
+ CodeIntegrity IMAGE_LOAD_CONFIG_CODE_INTEGRITY
+ GuardAddressTakenIatEntryTable uint64
+ GuardAddressTakenIatEntryCount uint64
+ GuardLongJumpTargetTable uint64
+ GuardLongJumpTargetCount uint64
+ DynamicValueRelocTable uint64
+ CHPEMetadataPointer uint64
+ GuardRFFailureRoutine uint64
+ GuardRFFailureRoutineFunctionPointer uint64
+ DynamicValueRelocTableOffset uint32
+ DynamicValueRelocTableSection uint16
+ Reserved2 uint16
+ GuardRFVerifyStackPointerFunctionPointer uint64
+ HotPatchTableOffset uint32
+ Reserved3 uint32
+ EnclaveConfigurationPointer uint64
+ VolatileMetadataPointer uint64
+ GuardEHContinuationTable uint64
+ GuardEHContinuationCount uint64
+ GuardXFGCheckFunctionPointer uint64
+ GuardXFGDispatchFunctionPointer uint64
+ GuardXFGTableDispatchFunctionPointer uint64
+ CastGuardOsDeterminedFailureMode uint64
+}
diff --git a/driver/wintunremoval_windows.go b/driver/wintunremoval_windows.go
new file mode 100644
index 00000000..ab51d989
--- /dev/null
+++ b/driver/wintunremoval_windows.go
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package driver
+
+import (
+ "path/filepath"
+
+ "golang.org/x/sys/windows"
+)
+
+func UninstallLegacyWintun() error {
+ deviceClassNetGUID := &windows.GUID{0x4d36e972, 0xe325, 0x11ce, [8]byte{0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18}}
+ devInfo, err := windows.SetupDiCreateDeviceInfoListEx(deviceClassNetGUID, 0, "")
+ if err != nil {
+ return err
+ }
+ defer devInfo.Close()
+ devInfoData, err := devInfo.CreateDeviceInfo("Wintun", deviceClassNetGUID, "", 0, windows.DICD_GENERATE_ID)
+ if err != nil {
+ return err
+ }
+ err = devInfo.SetDeviceRegistryProperty(devInfoData, windows.SPDRP_HARDWAREID, []byte("W\x00i\x00n\x00t\x00u\x00n\x00\x00\x00\x00\x00"))
+ if err != nil {
+ return err
+ }
+ err = devInfo.BuildDriverInfoList(devInfoData, windows.SPDIT_COMPATDRIVER)
+ if err != nil {
+ return err
+ }
+ defer devInfo.DestroyDriverInfoList(devInfoData, windows.SPDIT_COMPATDRIVER)
+ var lastError error
+ for i := 0; ; i++ {
+ drvInfoData, err := devInfo.EnumDriverInfo(devInfoData, windows.SPDIT_COMPATDRIVER, i)
+ if err != nil {
+ if err == windows.ERROR_NO_MORE_ITEMS {
+ break
+ }
+ continue
+ }
+ drvInfoDetailData, err := devInfo.DriverInfoDetail(devInfoData, drvInfoData)
+ if err != nil {
+ continue
+ }
+ lastError = windows.SetupUninstallOEMInf(filepath.Base(drvInfoDetailData.InfFileName()), 0)
+ }
+ return lastError
+}
diff --git a/elevate/doas.go b/elevate/doas.go
index ceedb78b..b67ee88c 100644
--- a/elevate/doas.go
+++ b/elevate/doas.go
@@ -1,12 +1,13 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package elevate
import (
"errors"
+ "os"
"runtime"
"strings"
"unsafe"
@@ -15,6 +16,17 @@ import (
"golang.org/x/sys/windows/svc/mgr"
)
+func setAllEnv(env []string) {
+ windows.Clearenv()
+ for _, e := range env {
+ k, v, ok := strings.Cut(e, "=")
+ if !ok {
+ continue
+ }
+ windows.Setenv(k, v)
+ }
+}
+
func DoAsSystem(f func() error) error {
runtime.LockOSThread()
defer func() {
@@ -58,12 +70,14 @@ func DoAsSystem(f func() error) error {
return err
}
processEntry := windows.ProcessEntry32{Size: uint32(unsafe.Sizeof(windows.ProcessEntry32{}))}
+ var impersonationError error
for err = windows.Process32First(processes, &processEntry); err == nil; err = windows.Process32Next(processes, &processEntry) {
if strings.ToLower(windows.UTF16ToString(processEntry.ExeFile[:])) != "winlogon.exe" {
continue
}
winlogonProcess, err := windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION, false, processEntry.ProcessID)
if err != nil {
+ impersonationError = err
continue
}
var winlogonToken windows.Token
@@ -85,14 +99,26 @@ func DoAsSystem(f func() error) error {
if err != nil {
return err
}
+ newEnv, err := duplicatedToken.Environ(false)
+ if err != nil {
+ duplicatedToken.Close()
+ return err
+ }
+ currentEnv := os.Environ()
err = windows.SetThreadToken(nil, duplicatedToken)
duplicatedToken.Close()
if err != nil {
return err
}
- return f()
+ setAllEnv(newEnv)
+ err = f()
+ setAllEnv(currentEnv)
+ return err
}
windows.CloseHandle(processes)
+ if impersonationError != nil {
+ return impersonationError
+ }
return errors.New("unable to find winlogon.exe process")
}
@@ -131,11 +157,20 @@ func DoAsService(serviceName string, f func() error) error {
if err != nil {
return err
}
+ newEnv, err := duplicatedToken.Environ(false)
+ if err != nil {
+ duplicatedToken.Close()
+ return err
+ }
+ currentEnv := os.Environ()
err = windows.SetThreadToken(nil, duplicatedToken)
duplicatedToken.Close()
if err != nil {
return err
}
- return f()
+ setAllEnv(newEnv)
+ err = f()
+ setAllEnv(currentEnv)
+ return err
})
}
diff --git a/elevate/loader.go b/elevate/loader.go
index 0bb275da..7d15f8a4 100644
--- a/elevate/loader.go
+++ b/elevate/loader.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package elevate
@@ -13,14 +13,14 @@ import (
/* We could use the undocumented LdrFindEntryForAddress function instead, but that's undocumented, and we're trying
* to be as rock-solid as possible here. */
-func findCurrentDataTableEntry() (entry *cLDR_DATA_TABLE_ENTRY, err error) {
- peb := rtlGetCurrentPeb()
+func findCurrentDataTableEntry() (entry *windows.LDR_DATA_TABLE_ENTRY, err error) {
+ peb := windows.RtlGetCurrentPeb()
if peb == nil || peb.Ldr == nil {
err = windows.ERROR_INVALID_ADDRESS
return
}
for cur := peb.Ldr.InMemoryOrderModuleList.Flink; cur != &peb.Ldr.InMemoryOrderModuleList; cur = cur.Flink {
- entry = (*cLDR_DATA_TABLE_ENTRY)(unsafe.Pointer(uintptr(unsafe.Pointer(cur)) - unsafe.Offsetof(cLDR_DATA_TABLE_ENTRY{}.InMemoryOrderLinks)))
+ entry = (*windows.LDR_DATA_TABLE_ENTRY)(unsafe.Pointer(uintptr(unsafe.Pointer(cur)) - unsafe.Offsetof(windows.LDR_DATA_TABLE_ENTRY{}.InMemoryOrderLinks)))
if entry.DllBase == peb.ImageBaseAddress {
return
}
diff --git a/elevate/membership.go b/elevate/membership.go
index 07c2ef69..383a10ba 100644
--- a/elevate/membership.go
+++ b/elevate/membership.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package elevate
@@ -37,12 +37,12 @@ func TokenIsElevatedOrElevatable(token windows.Token) bool {
}
func IsAdminDesktop() (bool, error) {
- hwnd := getShellWindow()
+ hwnd := windows.GetShellWindow()
if hwnd == 0 {
return false, windows.ERROR_INVALID_WINDOW_HANDLE
}
var pid uint32
- _, err := getWindowThreadProcessId(hwnd, &pid)
+ _, err := windows.GetWindowThreadProcessId(hwnd, &pid)
if err != nil {
return false, err
}
diff --git a/elevate/privileges.go b/elevate/privileges.go
index 84fdb2bd..3d51640f 100644
--- a/elevate/privileges.go
+++ b/elevate/privileges.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package elevate
@@ -44,7 +44,7 @@ func DropAllPrivileges(retainDriverLoading bool) error {
}
tokenPrivileges := (*windows.Tokenprivileges)(unsafe.Pointer(&buffer[0]))
for i := uint32(0); i < tokenPrivileges.PrivilegeCount; i++ {
- item := (*windows.LUIDAndAttributes)(unsafe.Pointer(uintptr(unsafe.Pointer(&tokenPrivileges.Privileges[0])) + unsafe.Sizeof(tokenPrivileges.Privileges[0])*uintptr(i)))
+ item := (*windows.LUIDAndAttributes)(unsafe.Add(unsafe.Pointer(&tokenPrivileges.Privileges[0]), unsafe.Sizeof(tokenPrivileges.Privileges[0])*uintptr(i)))
if retainDriverLoading && item.Luid == luid {
continue
}
diff --git a/elevate/shellexecute.go b/elevate/shellexecute.go
index 6290b4f8..e0be9d98 100644
--- a/elevate/shellexecute.go
+++ b/elevate/shellexecute.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package elevate
@@ -24,7 +24,7 @@ const (
cSEE_MASK_DEFAULT = 0
)
-func ShellExecute(program string, arguments string, directory string, show int32) (err error) {
+func ShellExecute(program, arguments, directory string, show int32) (err error) {
var (
program16 *uint16
arguments16 *uint16
@@ -107,36 +107,36 @@ func ShellExecute(program string, arguments string, directory string, show int32
}
originalPath := dataTableEntry.FullDllName.Buffer
explorerPath := windows.StringToUTF16Ptr(filepath.Join(windowsDirectory, "explorer.exe"))
- rtlInitUnicodeString(&dataTableEntry.FullDllName, explorerPath)
+ windows.RtlInitUnicodeString(&dataTableEntry.FullDllName, explorerPath)
defer func() {
- rtlInitUnicodeString(&dataTableEntry.FullDllName, originalPath)
+ windows.RtlInitUnicodeString(&dataTableEntry.FullDllName, originalPath)
runtime.KeepAlive(explorerPath)
}()
- if err = coInitializeEx(0, cCOINIT_APARTMENTTHREADED); err == nil {
- defer coUninitialize()
+ if err = windows.CoInitializeEx(0, windows.COINIT_APARTMENTTHREADED); err == nil {
+ defer windows.CoUninitialize()
}
var interfacePointer **[0xffff]uintptr
- if err = coGetObject(
+ if err = windows.CoGetObject(
windows.StringToUTF16Ptr("Elevation:Administrator!new:{3E5FC7F9-9A51-4367-9063-A120244FBEC7}"),
- &cBIND_OPTS3{
- cbStruct: uint32(unsafe.Sizeof(cBIND_OPTS3{})),
- dwClassContext: cCLSCTX_LOCAL_SERVER,
+ &windows.BIND_OPTS3{
+ CbStruct: uint32(unsafe.Sizeof(windows.BIND_OPTS3{})),
+ ClassContext: windows.CLSCTX_LOCAL_SERVER,
},
&windows.GUID{0x6EDD6D74, 0xC007, 0x4E75, [8]byte{0xB7, 0x6A, 0xE5, 0x74, 0x09, 0x95, 0xE2, 0x4C}},
- &interfacePointer,
+ (**uintptr)(unsafe.Pointer(&interfacePointer)),
); err != nil {
return
}
- defer syscall.Syscall((*interfacePointer)[releaseOffset], 1, uintptr(unsafe.Pointer(interfacePointer)), 0, 0)
+ defer syscall.SyscallN((*interfacePointer)[releaseOffset], uintptr(unsafe.Pointer(interfacePointer)))
if program16 == nil {
return
}
- if ret, _, _ := syscall.Syscall6((*interfacePointer)[shellExecuteOffset], 6,
+ if ret, _, _ := syscall.SyscallN((*interfacePointer)[shellExecuteOffset],
uintptr(unsafe.Pointer(interfacePointer)),
uintptr(unsafe.Pointer(program16)),
uintptr(unsafe.Pointer(arguments16)),
diff --git a/elevate/syscall_windows.go b/elevate/syscall_windows.go
deleted file mode 100644
index 1957dea2..00000000
--- a/elevate/syscall_windows.go
+++ /dev/null
@@ -1,89 +0,0 @@
-/* SPDX-License-Identifier: MIT
- *
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
- */
-
-package elevate
-
-type cBIND_OPTS3 struct {
- cbStruct uint32
- grfFlags uint32
- grfMode uint32
- dwTickCountDeadline uint32
- dwTrackFlags uint32
- dwClassContext uint32
- locale uint32
- pServerInfo *uintptr
- hwnd *uintptr
-}
-
-type cUNICODE_STRING struct {
- Length uint16
- MaximumLength uint16
- Buffer *uint16
-}
-
-type cLIST_ENTRY struct {
- Flink *cLIST_ENTRY
- Blink *cLIST_ENTRY
-}
-
-/* The below three structs have several "reserved" members. These are of course well-known and extensively reverse-
- * engineered, but the below shows only the documented and therefore stable fields from Microsoft's winternl.h header */
-
-type cLDR_DATA_TABLE_ENTRY struct {
- Reserved1 [2]uintptr
- InMemoryOrderLinks cLIST_ENTRY
- Reserved2 [2]uintptr
- DllBase uintptr
- Reserved3 [2]uintptr
- FullDllName cUNICODE_STRING
- Reserved4 [8]byte
- Reserved5 [3]uintptr
- Reserved6 uintptr
- TimeDateStamp uint32
-}
-
-type cPEB_LDR_DATA struct {
- Reserved1 [8]byte
- Reserved2 [3]uintptr
- InMemoryOrderModuleList cLIST_ENTRY
-}
-
-type cPEB struct {
- Reserved1 [2]byte
- BeingDebugged byte
- Reserved2 [1]byte
- Reserved3 uintptr
- ImageBaseAddress uintptr
- Ldr *cPEB_LDR_DATA
- ProcessParameters uintptr
- Reserved4 [3]uintptr
- AtlThunkSListPtr uintptr
- Reserved5 uintptr
- Reserved6 uint32
- Reserved7 uintptr
- Reserved8 uint32
- AtlThunkSListPtr32 uint32
- Reserved9 [45]uintptr
- Reserved10 [96]byte
- PostProcessInitRoutine uintptr
- Reserved11 [128]byte
- Reserved12 [1]uintptr
- SessionId uint32
-}
-
-const (
- cCLSCTX_LOCAL_SERVER = 4
- cCOINIT_APARTMENTTHREADED = 2
-)
-
-//sys rtlInitUnicodeString(destinationString *cUNICODE_STRING, sourceString *uint16) = ntdll.RtlInitUnicodeString
-//sys rtlGetCurrentPeb() (peb *cPEB) = ntdll.RtlGetCurrentPeb
-
-//sys coInitializeEx(reserved uintptr, coInit uint32) (ret error) = ole32.CoInitializeEx
-//sys coUninitialize() = ole32.CoUninitialize
-//sys coGetObject(name *uint16, bindOpts *cBIND_OPTS3, guid *windows.GUID, functionTable ***[0xffff]uintptr) (ret error) = ole32.CoGetObject
-
-//sys getWindowThreadProcessId(hwnd uintptr, pid *uint32) (tid uint32, err error) = user32.GetWindowThreadProcessId
-//sys getShellWindow() (hwnd uintptr) = user32.GetShellWindow
diff --git a/elevate/zsyscall_windows.go b/elevate/zsyscall_windows.go
deleted file mode 100644
index afff428f..00000000
--- a/elevate/zsyscall_windows.go
+++ /dev/null
@@ -1,102 +0,0 @@
-// Code generated by 'go generate'; DO NOT EDIT.
-
-package elevate
-
-import (
- "syscall"
- "unsafe"
-
- "golang.org/x/sys/windows"
-)
-
-var _ unsafe.Pointer
-
-// Do the interface allocations only once for common
-// Errno values.
-const (
- errnoERROR_IO_PENDING = 997
-)
-
-var (
- errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
-)
-
-// errnoErr returns common boxed Errno values, to prevent
-// allocations at runtime.
-func errnoErr(e syscall.Errno) error {
- switch e {
- case 0:
- return nil
- case errnoERROR_IO_PENDING:
- return errERROR_IO_PENDING
- }
- // TODO: add more here, after collecting data on the common
- // error values see on Windows. (perhaps when running
- // all.bat?)
- return e
-}
-
-var (
- modntdll = windows.NewLazySystemDLL("ntdll.dll")
- modole32 = windows.NewLazySystemDLL("ole32.dll")
- moduser32 = windows.NewLazySystemDLL("user32.dll")
-
- procRtlInitUnicodeString = modntdll.NewProc("RtlInitUnicodeString")
- procRtlGetCurrentPeb = modntdll.NewProc("RtlGetCurrentPeb")
- procCoInitializeEx = modole32.NewProc("CoInitializeEx")
- procCoUninitialize = modole32.NewProc("CoUninitialize")
- procCoGetObject = modole32.NewProc("CoGetObject")
- procGetWindowThreadProcessId = moduser32.NewProc("GetWindowThreadProcessId")
- procGetShellWindow = moduser32.NewProc("GetShellWindow")
-)
-
-func rtlInitUnicodeString(destinationString *cUNICODE_STRING, sourceString *uint16) {
- syscall.Syscall(procRtlInitUnicodeString.Addr(), 2, uintptr(unsafe.Pointer(destinationString)), uintptr(unsafe.Pointer(sourceString)), 0)
- return
-}
-
-func rtlGetCurrentPeb() (peb *cPEB) {
- r0, _, _ := syscall.Syscall(procRtlGetCurrentPeb.Addr(), 0, 0, 0, 0)
- peb = (*cPEB)(unsafe.Pointer(r0))
- return
-}
-
-func coInitializeEx(reserved uintptr, coInit uint32) (ret error) {
- r0, _, _ := syscall.Syscall(procCoInitializeEx.Addr(), 2, uintptr(reserved), uintptr(coInit), 0)
- if r0 != 0 {
- ret = syscall.Errno(r0)
- }
- return
-}
-
-func coUninitialize() {
- syscall.Syscall(procCoUninitialize.Addr(), 0, 0, 0, 0)
- return
-}
-
-func coGetObject(name *uint16, bindOpts *cBIND_OPTS3, guid *windows.GUID, functionTable ***[0xffff]uintptr) (ret error) {
- r0, _, _ := syscall.Syscall6(procCoGetObject.Addr(), 4, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(bindOpts)), uintptr(unsafe.Pointer(guid)), uintptr(unsafe.Pointer(functionTable)), 0, 0)
- if r0 != 0 {
- ret = syscall.Errno(r0)
- }
- return
-}
-
-func getWindowThreadProcessId(hwnd uintptr, pid *uint32) (tid uint32, err error) {
- r0, _, e1 := syscall.Syscall(procGetWindowThreadProcessId.Addr(), 2, uintptr(hwnd), uintptr(unsafe.Pointer(pid)), 0)
- tid = uint32(r0)
- if tid == 0 {
- if e1 != 0 {
- err = errnoErr(e1)
- } else {
- err = syscall.EINVAL
- }
- }
- return
-}
-
-func getShellWindow() (hwnd uintptr) {
- r0, _, _ := syscall.Syscall(procGetShellWindow.Addr(), 0, 0, 0, 0)
- hwnd = uintptr(r0)
- return
-}
diff --git a/embeddable-dll-service/.gitignore b/embeddable-dll-service/.gitignore
new file mode 100644
index 00000000..9e3c52ab
--- /dev/null
+++ b/embeddable-dll-service/.gitignore
@@ -0,0 +1,4 @@
+# Build Output
+/x86
+/amd64
+/arm64
diff --git a/embeddable-dll-service/README.md b/embeddable-dll-service/README.md
index 3ac0dbae..a326dc70 100644
--- a/embeddable-dll-service/README.md
+++ b/embeddable-dll-service/README.md
@@ -1,19 +1,21 @@
## Embeddable WireGuard Tunnel Library
-This allows embedding WireGuard as a service inside of another application. Build `tunnel.dll` by running `./build.bat` in this folder. The first time you run it, it will invoke `..\build.bat` simply for downloading dependencies. After, you should have `amd64/tunnel.dll` and `x86/tunnel.dll`.
+This allows embedding WireGuard as a service inside of another application. Build `tunnel.dll` by running `./build.bat` in this folder. The first time you run it, it will invoke `..\build.bat` simply for downloading dependencies. After, you should have `amd64/tunnel.dll`, `x86/tunnel.dll`, and `arm64/tunnel.dll`. In addition, `tunnel.dll` requires `wireguard.dll`, which can be downloaded from [the wireguard-nt download server](https://download.wireguard.com/wireguard-nt/).
The basic setup to use `tunnel.dll` is:
##### 1. Install a service with these parameters:
- Service Name: "SomeServiceName"
- Display Name: "Some Service Name"
- Service Type: SERVICE_WIN32_OWN_PROCESS
- Start Type: StartAutomatic
- Error Control: ErrorNormal,
- Dependencies: [ "Nsi" ]
- Sid Type: SERVICE_SID_TYPE_UNRESTRICTED
- Executable: "C:\path\to\example\vpnclient.exe /service configfile.conf"
+```text
+Service Name: "WireGuardTunnel$SomeTunnelName"
+Display Name: "Some Service Name"
+Service Type: SERVICE_WIN32_OWN_PROCESS
+Start Type: StartAutomatic
+Error Control: ErrorNormal,
+Dependencies: [ "Nsi", "TcpIp" ]
+Sid Type: SERVICE_SID_TYPE_UNRESTRICTED
+Executable: "C:\path\to\example\vpnclient.exe /service configfile.conf"
+```
Some of these may have to be changed with `ChangeServiceConfig2` after the
initial call to `CreateService` The `SERVICE_SID_TYPE_UNRESTRICTED` parameter
@@ -21,22 +23,19 @@ is absolutely essential; do not forget it.
##### 2. Have your program's main function handle the `/service` switch:
- if (!strcmp(argv[1], "/service") && argc == 3) {
- HMODULE tunnel_lib = LoadLibrary("tunnel.dll");
- if (!tunnel_lib)
- abort();
- tunnel_proc_t tunnel_proc = (tunnel_proc_t)GetProcAddress(tunnel_lib, "WireGuardTunnelService");
- if (!tunnel_proc)
- abort();
- struct go_string conf_file = {
- .str = argv[2],
- .n = strlen(argv[2])
- };
- return tunnel_proc(conf_file);
- }
+```c
+if (wargc == 3 && !wcscmp(wargv[1], L"/service")) {
+ HMODULE tunnel_lib = LoadLibrary("tunnel.dll");
+ if (!tunnel_lib)
+ abort();
+ BOOL (_cdecl *tunnel_proc)(_In_ LPCWSTR conf_file);
+ *(FARPROC*)&tunnel_proc = GetProcAddress(tunnel_lib, "WireGuardTunnelService");
+ if (!tunnel_proc)
+ abort();
+ return tunnel_proc(wargv[2]);
+}
+```
##### 3. Scoop up logs by implementing a ringlogger format reader.
-##### 4. Talk to the service over its named pipe.
-
There is a sample implementation of bits and pieces of this inside of the `csharp\` directory.
diff --git a/embeddable-dll-service/build.bat b/embeddable-dll-service/build.bat
index cae35f70..b4c29000 100644
--- a/embeddable-dll-service/build.bat
+++ b/embeddable-dll-service/build.bat
@@ -1,10 +1,10 @@
@echo off
rem SPDX-License-Identifier: MIT
-rem Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+rem Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
setlocal
set BUILDDIR=%~dp0
-set PATH=%BUILDDIR%..\.deps\go\bin;%BUILDDIR%..\.deps;%PATH%
+set PATH=%BUILDDIR%..\.deps\llvm-mingw\bin;%BUILDDIR%..\.deps\go\bin;%PATH%
set PATHEXT=.exe
cd /d %BUILDDIR% || exit /b 1
@@ -14,21 +14,27 @@ if exist ..\.deps\prepared goto :build
:build
set GOOS=windows
+ set GOARM=7
set GOPATH=%BUILDDIR%..\.deps\gopath
set GOROOT=%BUILDDIR%..\.deps\go
set CGO_ENABLED=1
set CGO_CFLAGS=-O3 -Wall -Wno-unused-function -Wno-switch -std=gnu11 -DWINVER=0x0601
- set CGO_LDFLAGS=-Wl,--dynamicbase -Wl,--nxcompat -Wl,--export-all-symbols
call :build_plat x86 i686 386 || goto :error
- set CGO_LDFLAGS=%CGO_LDFLAGS% -Wl,--high-entropy-va
call :build_plat amd64 x86_64 amd64 || goto :error
+ call :build_plat arm64 aarch64 arm64 || goto :error
+
+:sign
+ if exist ..\sign.bat call ..\sign.bat
+ if "%SigningCertificate%"=="" goto :success
+ if "%TimestampServer%"=="" goto :success
+ echo [+] Signing
+ signtool sign /sha1 "%SigningCertificate%" /fd sha256 /tr "%TimestampServer%" /td sha256 /d "WireGuard Tunnel" x86\tunnel.dll amd64\tunnel.dll arm64\tunnel.dll || goto :error
:success
echo [+] Success
exit /b 0
:build_plat
- set PATH=%BUILDDIR%..\.deps\%~2-w64-mingw32-native\bin;%PATH%
set CC=%~2-w64-mingw32-gcc
set GOARCH=%~3
mkdir %1 >NUL 2>&1
diff --git a/embeddable-dll-service/csharp/.gitignore b/embeddable-dll-service/csharp/.gitignore
new file mode 100644
index 00000000..28bcb4ab
--- /dev/null
+++ b/embeddable-dll-service/csharp/.gitignore
@@ -0,0 +1,3 @@
+/.vs
+/bin
+/obj
diff --git a/embeddable-dll-service/csharp/DemoUI/MainWindow.Designer.cs b/embeddable-dll-service/csharp/DemoUI/MainWindow.Designer.cs
new file mode 100644
index 00000000..5aad1b97
--- /dev/null
+++ b/embeddable-dll-service/csharp/DemoUI/MainWindow.Designer.cs
@@ -0,0 +1,87 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+namespace DemoUI
+{
+ partial class MainWindow
+ {
+ /// <summary>
+ /// Required designer variable.
+ /// </summary>
+ private System.ComponentModel.IContainer components = null;
+
+ /// <summary>
+ /// Clean up any resources being used.
+ /// </summary>
+ /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ /// <summary>
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ /// </summary>
+ private void InitializeComponent()
+ {
+ this.connectButton = new System.Windows.Forms.Button();
+ this.logBox = new System.Windows.Forms.TextBox();
+ this.SuspendLayout();
+ //
+ // connectButton
+ //
+ this.connectButton.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
+ | System.Windows.Forms.AnchorStyles.Right)));
+ this.connectButton.Location = new System.Drawing.Point(12, 12);
+ this.connectButton.Name = "connectButton";
+ this.connectButton.Size = new System.Drawing.Size(1137, 46);
+ this.connectButton.TabIndex = 5;
+ this.connectButton.Text = "&Connect";
+ this.connectButton.UseVisualStyleBackColor = true;
+ this.connectButton.Click += new System.EventHandler(this.connectButton_Click);
+ //
+ // logBox
+ //
+ this.logBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
+ | System.Windows.Forms.AnchorStyles.Left)
+ | System.Windows.Forms.AnchorStyles.Right)));
+ this.logBox.Location = new System.Drawing.Point(12, 64);
+ this.logBox.Multiline = true;
+ this.logBox.Name = "logBox";
+ this.logBox.ReadOnly = true;
+ this.logBox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
+ this.logBox.Size = new System.Drawing.Size(1137, 561);
+ this.logBox.TabIndex = 4;
+ this.logBox.TabStop = false;
+ //
+ // MainWindow
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(13F, 32F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(1161, 637);
+ this.Controls.Add(this.logBox);
+ this.Controls.Add(this.connectButton);
+ this.Name = "MainWindow";
+ this.Text = "WireGuard Demo Client";
+ this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MainWindow_FormClosing);
+ this.Load += new System.EventHandler(this.MainWindow_Load);
+ this.ResumeLayout(false);
+ this.PerformLayout();
+
+ }
+
+ #endregion
+ private System.Windows.Forms.Button connectButton;
+ private System.Windows.Forms.TextBox logBox;
+ }
+}
+
diff --git a/embeddable-dll-service/csharp/DemoUI/MainWindow.cs b/embeddable-dll-service/csharp/DemoUI/MainWindow.cs
new file mode 100644
index 00000000..1df17181
--- /dev/null
+++ b/embeddable-dll-service/csharp/DemoUI/MainWindow.cs
@@ -0,0 +1,215 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+using System;
+using System.Text;
+using System.Threading.Tasks;
+using System.IO;
+using System.Windows.Forms;
+using System.Threading;
+using System.IO.Pipes;
+using System.Diagnostics;
+using System.Net.Sockets;
+using System.Security.AccessControl;
+
+namespace DemoUI
+{
+ public partial class MainWindow : Form
+ {
+ private static readonly string userDirectory = Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), "Config"); //TODO: put in Program Files in real code.
+ private static readonly string configFile = Path.Combine(userDirectory, "demobox.conf");
+ private static readonly string logFile = Path.Combine(userDirectory, "log.bin");
+
+ private Tunnel.Ringlogger log;
+ private Thread logPrintingThread, transferUpdateThread;
+ private volatile bool threadsRunning;
+ private bool connected;
+
+ public MainWindow()
+ {
+ makeConfigDirectory();
+ InitializeComponent();
+ Application.ApplicationExit += Application_ApplicationExit;
+ try { File.Delete(logFile); } catch { }
+ log = new Tunnel.Ringlogger(logFile, "GUI");
+ logPrintingThread = new Thread(new ThreadStart(tailLog));
+ transferUpdateThread = new Thread(new ThreadStart(tailTransfer));
+ }
+
+ private void makeConfigDirectory()
+ {
+ var ds = new DirectorySecurity();
+ ds.SetSecurityDescriptorSddlForm("O:BAG:BAD:PAI(A;OICI;FA;;;BA)(A;OICI;FA;;;SY)");
+ FileSystemAclExtensions.CreateDirectory(ds, userDirectory);
+ }
+
+ private void tailLog()
+ {
+ var cursor = Tunnel.Ringlogger.CursorAll;
+ while (threadsRunning)
+ {
+ var lines = log.FollowFromCursor(ref cursor);
+ foreach (var line in lines)
+ logBox.Invoke(new Action<string>(logBox.AppendText), new object[] { line + "\r\n" });
+ try
+ {
+ Thread.Sleep(300);
+ }
+ catch
+ {
+ break;
+ }
+ }
+ }
+
+ private void tailTransfer()
+ {
+ Tunnel.Driver.Adapter adapter = null;
+ while (threadsRunning)
+ {
+ if (adapter == null)
+ {
+ while (threadsRunning)
+ {
+ try
+ {
+ adapter = Tunnel.Service.GetAdapter(configFile);
+ break;
+ }
+ catch
+ {
+ try
+ {
+ Thread.Sleep(1000);
+ }
+ catch { }
+ }
+ }
+ }
+ if (adapter == null)
+ continue;
+ try
+ {
+ ulong rx = 0, tx = 0;
+ var config = adapter.GetConfiguration();
+ foreach (var peer in config.Peers)
+ {
+ rx += peer.RxBytes;
+ tx += peer.TxBytes;
+ }
+ Invoke(new Action<ulong, ulong>(updateTransferTitle), new object[] { rx, tx });
+ Thread.Sleep(1000);
+ }
+ catch { adapter = null; }
+ }
+ }
+
+ private void Application_ApplicationExit(object sender, EventArgs e)
+ {
+ Tunnel.Service.Remove(configFile, true);
+ try { File.Delete(logFile); } catch { }
+ try { File.Delete(configFile); } catch { }
+ }
+
+ private void MainWindow_Load(object sender, EventArgs e)
+ {
+ threadsRunning = true;
+ logPrintingThread.Start();
+ transferUpdateThread.Start();
+ }
+
+ private void MainWindow_FormClosing(object sender, FormClosingEventArgs e)
+ {
+ threadsRunning = false;
+ logPrintingThread.Interrupt();
+ transferUpdateThread.Interrupt();
+ try { logPrintingThread.Join(); } catch { }
+ try { transferUpdateThread.Join(); } catch { }
+ }
+
+ private static string formatBytes(ulong bytes)
+ {
+ decimal d = bytes;
+ string selectedUnit = null;
+ foreach (string unit in new string[] { "B", "KiB", "MiB", "GiB", "TiB" })
+ {
+ selectedUnit = unit;
+ if (d < 1024)
+ break;
+ d /= 1024;
+ }
+ return string.Format("{0:0.##} {1}", d, selectedUnit);
+ }
+
+ private void updateTransferTitle(ulong rx, ulong tx)
+ {
+ var titleBase = Text;
+ var idx = titleBase.IndexOf(" - ");
+ if (idx != -1)
+ titleBase = titleBase.Substring(0, idx);
+ if (rx == 0 && tx == 0)
+ Text = titleBase;
+ else
+ Text = string.Format("{0} - rx: {1}, tx: {2}", titleBase, formatBytes(rx), formatBytes(tx));
+ }
+
+ private async Task<string> generateNewConfig()
+ {
+ log.Write("Generating keys");
+ var keys = Tunnel.Keypair.Generate();
+ log.Write("Exchanging keys with demo server");
+ var client = new TcpClient();
+ await client.ConnectAsync("demo.wireguard.com", 42912);
+ var stream = client.GetStream();
+ var reader = new StreamReader(stream, Encoding.UTF8);
+ var pubKeyBytes = Encoding.UTF8.GetBytes(keys.Public + "\n");
+ await stream.WriteAsync(pubKeyBytes, 0, pubKeyBytes.Length);
+ await stream.FlushAsync();
+ var ret = (await reader.ReadLineAsync()).Split(':');
+ client.Close();
+ var status = ret.Length >= 1 ? ret[0] : "";
+ var serverPubkey = ret.Length >= 2 ? ret[1] : "";
+ var serverPort = ret.Length >= 3 ? ret[2] : "";
+ var internalIP = ret.Length >= 4 ? ret[3] : "";
+ if (status != "OK")
+ throw new InvalidOperationException(string.Format("Server status is {0}", status));
+ return string.Format("[Interface]\nPrivateKey = {0}\nAddress = {1}/24\nDNS = 8.8.8.8, 8.8.4.4\n\n[Peer]\nPublicKey = {2}\nEndpoint = demo.wireguard.com:{3}\nAllowedIPs = 0.0.0.0/0\n", keys.Private, internalIP, serverPubkey, serverPort);
+ }
+
+ private async void connectButton_Click(object sender, EventArgs e)
+ {
+ if (connected)
+ {
+ connectButton.Enabled = false;
+ await Task.Run(() =>
+ {
+ Tunnel.Service.Remove(configFile, true);
+ try { File.Delete(configFile); } catch { }
+ });
+ updateTransferTitle(0, 0);
+ connectButton.Text = "&Connect";
+ connectButton.Enabled = true;
+ connected = false;
+ return;
+ }
+
+ connectButton.Enabled = false;
+ try
+ {
+ var config = await generateNewConfig();
+ await File.WriteAllBytesAsync(configFile, Encoding.UTF8.GetBytes(config));
+ await Task.Run(() => Tunnel.Service.Add(configFile, true));
+ connected = true;
+ connectButton.Text = "&Disconnect";
+ }
+ catch (Exception ex)
+ {
+ log.Write(ex.Message);
+ try { File.Delete(configFile); } catch { }
+ }
+ connectButton.Enabled = true;
+ }
+ }
+}
diff --git a/embeddable-dll-service/csharp/DemoUI/MainWindow.resx b/embeddable-dll-service/csharp/DemoUI/MainWindow.resx
new file mode 100644
index 00000000..f298a7be
--- /dev/null
+++ b/embeddable-dll-service/csharp/DemoUI/MainWindow.resx
@@ -0,0 +1,60 @@
+<root>
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+</root> \ No newline at end of file
diff --git a/embeddable-dll-service/csharp/DemoUI/Program.cs b/embeddable-dll-service/csharp/DemoUI/Program.cs
new file mode 100644
index 00000000..3649ab93
--- /dev/null
+++ b/embeddable-dll-service/csharp/DemoUI/Program.cs
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+using System;
+using System.Threading;
+using System.Diagnostics;
+using System.Windows.Forms;
+
+namespace DemoUI
+{
+ static class Program
+ {
+ [STAThread]
+ static void Main(string[] args)
+ {
+ if (args.Length == 3 && args[0] == "/service")
+ {
+ var t = new Thread(() =>
+ {
+ try
+ {
+ var currentProcess = Process.GetCurrentProcess();
+ var uiProcess = Process.GetProcessById(int.Parse(args[2]));
+ if (uiProcess.MainModule.FileName != currentProcess.MainModule.FileName)
+ return;
+ uiProcess.WaitForExit();
+ Tunnel.Service.Remove(args[1], false);
+ }
+ catch { }
+ });
+ t.Start();
+ Tunnel.Service.Run(args[1]);
+ t.Interrupt();
+ return;
+ }
+ Application.SetHighDpiMode(HighDpiMode.SystemAware);
+ Application.EnableVisualStyles();
+ Application.SetCompatibleTextRenderingDefault(false);
+ Application.Run(new MainWindow());
+ }
+ }
+}
diff --git a/embeddable-dll-service/csharp/DemoUI/app.manifest b/embeddable-dll-service/csharp/DemoUI/app.manifest
new file mode 100644
index 00000000..616ab5b9
--- /dev/null
+++ b/embeddable-dll-service/csharp/DemoUI/app.manifest
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+ <assemblyIdentity version="1.0.0.0" name="demo-client"/>
+ <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+ <security>
+ <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
+ <requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
+ </requestedPrivileges>
+ </security>
+ </trustInfo>
+
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <!-- Windows 7 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
+
+ <!-- Windows 8 -->
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
+
+ <!-- Windows 8.1 -->
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
+
+ <!-- Windows 10 -->
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
+
+ </application>
+ </compatibility>
+
+ <application xmlns="urn:schemas-microsoft-com:asm.v3">
+ <windowsSettings>
+ <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
+ </windowsSettings>
+ </application>
+
+ <dependency>
+ <dependentAssembly>
+ <assemblyIdentity
+ type="win32"
+ name="Microsoft.Windows.Common-Controls"
+ version="6.0.0.0"
+ processorArchitecture="*"
+ publicKeyToken="6595b64144ccf1df"
+ language="*"
+ />
+ </dependentAssembly>
+ </dependency>
+
+</assembly>
diff --git a/embeddable-dll-service/csharp/Program.cs b/embeddable-dll-service/csharp/Program.cs
deleted file mode 100644
index d99e66fd..00000000
--- a/embeddable-dll-service/csharp/Program.cs
+++ /dev/null
@@ -1,92 +0,0 @@
-/* SPDX-License-Identifier: MIT
- *
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
- */
-
-using System;
-using System.Net.Sockets;
-using System.IO;
-using System.Text;
-using System.Diagnostics;
-using System.Threading;
-using System.Runtime.InteropServices;
-
-namespace Tunnel
-{
- class Program
- {
-
- [DllImport("kernel32.dll")]
- private static extern bool SetConsoleCtrlHandler(SetConsoleCtrlEventHandler handler, bool add);
- private delegate bool SetConsoleCtrlEventHandler(UInt32 signal);
-
- public static void Main(string[] args)
- {
- if (args.Length == 2 && args[0] == "/service")
- {
- Service.Run(args[1]);
- return;
- }
-
- var baseDirectory = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
- var configFile = Path.Combine(baseDirectory, "demobox.conf");
- var logFile = Path.Combine(baseDirectory, "log.bin");
-
- try { File.Delete(logFile); } catch { }
- Ringlogger log = new Ringlogger(logFile, "GUI");
-
- var logPrintingThread = new Thread(() =>
- {
- var cursor = Ringlogger.CursorAll;
- while (Thread.CurrentThread.IsAlive)
- {
- var lines = log.FollowFromCursor(ref cursor);
- foreach (var line in lines)
- Console.WriteLine(line);
- Thread.Sleep(300);
- }
- });
- logPrintingThread.Start();
-
- log.Write("Generating keys");
- var keys = Keypair.Generate();
- log.Write("Exchanging keys with demo server");
- var client = new TcpClient("demo.wireguard.com", 42912);
- var stream = client.GetStream();
- var reader = new StreamReader(stream, Encoding.UTF8);
- var pubKeyBytes = Encoding.UTF8.GetBytes(keys.Public + "\n");
- stream.Write(pubKeyBytes, 0, pubKeyBytes.Length);
- stream.Flush();
- var ret = reader.ReadLine().Split(':');
- client.Close();
- var status = ret.Length >= 1 ? ret[0] : "";
- var serverPubkey = ret.Length >= 2 ? ret[1] : "";
- var serverPort = ret.Length >= 3 ? ret[2] : "";
- var internalIP = ret.Length >= 4 ? ret[3] : "";
-
- if (status != "OK")
- throw new InvalidOperationException(String.Format("Server status is {0}", status));
-
- SetConsoleCtrlHandler(delegate
- {
- Service.Remove(configFile);
- Environment.Exit(0);
- return true;
- }, true);
-
- log.Write("Writing config file to disk");
- var configFileContents = String.Format("[Interface]\nPrivateKey = {0}\nAddress = {1}/24\nDNS = 8.8.8.8, 8.8.4.4\n\n[Peer]\nPublicKey = {2}\nEndpoint = demo.wireguard.com:{3}\nAllowedIPs = 0.0.0.0/0\n", keys.Private, internalIP, serverPubkey, serverPort);
- File.WriteAllText(configFile, configFileContents);
-
- try
- {
- Service.Add(configFile);
- logPrintingThread.Join();
- }
- finally
- {
- Service.Remove(configFile);
- }
- }
- }
-}
diff --git a/embeddable-dll-service/csharp/README.md b/embeddable-dll-service/csharp/README.md
new file mode 100644
index 00000000..24b36563
--- /dev/null
+++ b/embeddable-dll-service/csharp/README.md
@@ -0,0 +1,7 @@
+# Example WireGuard Demo Client for Windows
+
+This is a simple client for demo.wireguard.com, which brings up WireGuard tunnels using the [embeddable-dll-service](https://git.zx2c4.com/wireguard-windows/about/embeddable-dll-service/README.md).
+
+## Building
+
+The code in this repository can be built in Visual Studio 2019 by opening the .sln and pressing build. However, it requires [`tunnel.dll` and `wireguard.dll`](../README.md).
diff --git a/embeddable-dll-service/csharp/TunnelDll/Driver.cs b/embeddable-dll-service/csharp/TunnelDll/Driver.cs
new file mode 100644
index 00000000..69911ec8
--- /dev/null
+++ b/embeddable-dll-service/csharp/TunnelDll/Driver.cs
@@ -0,0 +1,234 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+using System;
+using System.ComponentModel;
+using System.Net;
+using System.Runtime.InteropServices;
+
+namespace Tunnel
+{
+ public class Driver
+ {
+ [DllImport("wireguard.dll", EntryPoint = "WireGuardOpenAdapter", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
+ private static extern IntPtr openAdapter([MarshalAs(UnmanagedType.LPWStr)] string name);
+ [DllImport("wireguard.dll", EntryPoint = "WireGuardCloseAdapter", CallingConvention = CallingConvention.StdCall)]
+ private static extern void freeAdapter(IntPtr adapter);
+ [DllImport("wireguard.dll", EntryPoint = "WireGuardGetConfiguration", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
+ private static extern bool getConfiguration(IntPtr adapter, byte[] iface, ref UInt32 bytes);
+
+ public class Adapter
+ {
+ private IntPtr _handle;
+ private UInt32 _lastGetGuess;
+ public Adapter(string name)
+ {
+ _lastGetGuess = 1024;
+ _handle = openAdapter(name);
+ if (_handle == IntPtr.Zero)
+ throw new Win32Exception();
+ }
+ ~Adapter()
+ {
+ freeAdapter(_handle);
+ }
+ public unsafe Interface GetConfiguration()
+ {
+ var iface = new Interface();
+ byte[] bytes;
+ for (; ; )
+ {
+ bytes = new byte[_lastGetGuess];
+ if (getConfiguration(_handle, bytes, ref _lastGetGuess))
+ break;
+ if (Marshal.GetLastWin32Error() != 234 /* ERROR_MORE_DATA */)
+ throw new Win32Exception();
+ }
+ fixed (void* start = bytes)
+ {
+ var ioctlIface = (IoctlInterface*)start;
+ if ((ioctlIface->Flags & IoctlInterfaceFlags.HasPublicKey) != 0)
+ iface.PublicKey = new Key(ioctlIface->PublicKey);
+ if ((ioctlIface->Flags & IoctlInterfaceFlags.HasPrivateKey) != 0)
+ iface.PrivateKey = new Key(ioctlIface->PrivateKey);
+ if ((ioctlIface->Flags & IoctlInterfaceFlags.HasListenPort) != 0)
+ iface.ListenPort = ioctlIface->ListenPort;
+ var peers = new Peer[ioctlIface->PeersCount];
+ var ioctlPeer = (IoctlPeer*)((byte*)ioctlIface + sizeof(IoctlInterface));
+ for (UInt32 i = 0; i < peers.Length; ++i)
+ {
+ var peer = new Peer();
+ if ((ioctlPeer->Flags & IoctlPeerFlags.HasPublicKey) != 0)
+ peer.PublicKey = new Key(ioctlPeer->PublicKey);
+ if ((ioctlPeer->Flags & IoctlPeerFlags.HasPresharedKey) != 0)
+ peer.PresharedKey = new Key(ioctlPeer->PresharedKey);
+ if ((ioctlPeer->Flags & IoctlPeerFlags.HasPersistentKeepalive) != 0)
+ peer.PersistentKeepalive = ioctlPeer->PersistentKeepalive;
+ if ((ioctlPeer->Flags & IoctlPeerFlags.HasEndpoint) != 0)
+ {
+ if (ioctlPeer->Endpoint.si_family == Win32.ADDRESS_FAMILY.AF_INET)
+ {
+ var ip = new byte[4];
+ Marshal.Copy((IntPtr)ioctlPeer->Endpoint.Ipv4.sin_addr.bytes, ip, 0, 4);
+ peer.Endpoint = new IPEndPoint(new IPAddress(ip), (ushort)IPAddress.NetworkToHostOrder((short)ioctlPeer->Endpoint.Ipv4.sin_port));
+ }
+ else if (ioctlPeer->Endpoint.si_family == Win32.ADDRESS_FAMILY.AF_INET6)
+ {
+ var ip = new byte[16];
+ Marshal.Copy((IntPtr)ioctlPeer->Endpoint.Ipv6.sin6_addr.bytes, ip, 0, 16);
+ peer.Endpoint = new IPEndPoint(new IPAddress(ip), (ushort)IPAddress.NetworkToHostOrder((short)ioctlPeer->Endpoint.Ipv6.sin6_port));
+ }
+ }
+ peer.TxBytes = ioctlPeer->TxBytes;
+ peer.RxBytes = ioctlPeer->RxBytes;
+ if (ioctlPeer->LastHandshake != 0)
+ peer.LastHandshake = DateTime.FromFileTimeUtc((long)ioctlPeer->LastHandshake);
+ var allowedIPs = new AllowedIP[ioctlPeer->AllowedIPsCount];
+ var ioctlAllowedIP = (IoctlAllowedIP*)((byte*)ioctlPeer + sizeof(IoctlPeer));
+ for (UInt32 j = 0; j < allowedIPs.Length; ++j)
+ {
+ var allowedIP = new AllowedIP();
+ if (ioctlAllowedIP->AddressFamily == Win32.ADDRESS_FAMILY.AF_INET)
+ {
+ var ip = new byte[4];
+ Marshal.Copy((IntPtr)ioctlAllowedIP->V4.bytes, ip, 0, 4);
+ allowedIP.Address = new IPAddress(ip);
+ }
+ else if (ioctlAllowedIP->AddressFamily == Win32.ADDRESS_FAMILY.AF_INET6)
+ {
+ var ip = new byte[16];
+ Marshal.Copy((IntPtr)ioctlAllowedIP->V6.bytes, ip, 0, 16);
+ allowedIP.Address = new IPAddress(ip);
+ }
+ allowedIP.Cidr = ioctlAllowedIP->Cidr;
+ allowedIPs[j] = allowedIP;
+ ioctlAllowedIP = (IoctlAllowedIP*)((byte*)ioctlAllowedIP + sizeof(IoctlAllowedIP));
+ }
+ peer.AllowedIPs = allowedIPs;
+ peers[i] = peer;
+ ioctlPeer = (IoctlPeer*)ioctlAllowedIP;
+ }
+ iface.Peers = peers;
+ }
+ return iface;
+ }
+
+ public class Key
+ {
+ private byte[] _bytes;
+ public byte[] Bytes
+ {
+ get
+ {
+ return _bytes;
+ }
+ set
+ {
+ if (value == null || value.Length != 32)
+ throw new ArgumentException("Keys must be 32 bytes");
+ _bytes = value;
+ }
+ }
+ public Key(byte[] bytes)
+ {
+ Bytes = bytes;
+ }
+ public unsafe Key(byte* bytes)
+ {
+ _bytes = new byte[32];
+ Marshal.Copy((IntPtr)bytes, _bytes, 0, 32);
+ }
+ public override String ToString()
+ {
+ return Convert.ToBase64String(_bytes);
+ }
+ }
+
+ public class Interface
+ {
+ public UInt16 ListenPort { get; set; }
+ public Key PrivateKey { get; set; }
+ public Key PublicKey { get; set; }
+ public Peer[] Peers { get; set; }
+ }
+
+ public class Peer
+ {
+ public Key PublicKey { get; set; }
+ public Key PresharedKey { get; set; }
+ public UInt16 PersistentKeepalive { get; set; }
+ public IPEndPoint Endpoint { get; set; }
+ public UInt64 TxBytes { get; set; }
+ public UInt64 RxBytes { get; set; }
+ public DateTime LastHandshake { get; set; }
+ public AllowedIP[] AllowedIPs { get; set; }
+ }
+
+ public class AllowedIP
+ {
+ public IPAddress Address { get; set; }
+ public byte Cidr { get; set; }
+ }
+
+ private enum IoctlInterfaceFlags : UInt32
+ {
+ HasPublicKey = 1 << 0,
+ HasPrivateKey = 1 << 1,
+ HasListenPort = 1 << 2,
+ ReplacePeers = 1 << 3
+ };
+
+ [StructLayout(LayoutKind.Sequential, Pack = 8, Size = 80)]
+ private unsafe struct IoctlInterface
+ {
+ public IoctlInterfaceFlags Flags;
+ public UInt16 ListenPort;
+ public fixed byte PrivateKey[32];
+ public fixed byte PublicKey[32];
+ public UInt32 PeersCount;
+ };
+
+ private enum IoctlPeerFlags : UInt32
+ {
+ HasPublicKey = 1 << 0,
+ HasPresharedKey = 1 << 1,
+ HasPersistentKeepalive = 1 << 2,
+ HasEndpoint = 1 << 3,
+ ReplaceAllowedIPs = 1 << 5,
+ Remove = 1 << 6,
+ UpdateOnly = 1 << 7
+ };
+
+ [StructLayout(LayoutKind.Sequential, Pack = 8, Size = 136)]
+ private unsafe struct IoctlPeer
+ {
+ public IoctlPeerFlags Flags;
+ public UInt32 Reserved;
+ public fixed byte PublicKey[32];
+ public fixed byte PresharedKey[32];
+ public UInt16 PersistentKeepalive;
+ public Win32.SOCKADDR_INET Endpoint;
+ public UInt64 TxBytes, RxBytes;
+ public UInt64 LastHandshake;
+ public UInt32 AllowedIPsCount;
+ };
+
+ [StructLayout(LayoutKind.Explicit, Pack = 8, Size = 24)]
+ private unsafe struct IoctlAllowedIP
+ {
+ [FieldOffset(0)]
+ [MarshalAs(UnmanagedType.Struct)]
+ public Win32.IN_ADDR V4;
+ [FieldOffset(0)]
+ [MarshalAs(UnmanagedType.Struct)]
+ public Win32.IN6_ADDR V6;
+ [FieldOffset(16)]
+ public Win32.ADDRESS_FAMILY AddressFamily;
+ [FieldOffset(20)]
+ public byte Cidr;
+ }
+ }
+ }
+}
diff --git a/embeddable-dll-service/csharp/Keypair.cs b/embeddable-dll-service/csharp/TunnelDll/Keypair.cs
index e5764fbd..59847b98 100644
--- a/embeddable-dll-service/csharp/Keypair.cs
+++ b/embeddable-dll-service/csharp/TunnelDll/Keypair.cs
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
using System;
@@ -13,7 +13,7 @@ namespace Tunnel
public readonly string Public;
public readonly string Private;
- private Keypair(string pub, string priv)
+ public Keypair(string pub, string priv)
{
Public = pub;
Private = priv;
diff --git a/embeddable-dll-service/csharp/Ringlogger.cs b/embeddable-dll-service/csharp/TunnelDll/Ringlogger.cs
index d0957926..9db01fc8 100644
--- a/embeddable-dll-service/csharp/Ringlogger.cs
+++ b/embeddable-dll-service/csharp/TunnelDll/Ringlogger.cs
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
using System;
diff --git a/embeddable-dll-service/csharp/Service.cs b/embeddable-dll-service/csharp/TunnelDll/Service.cs
index db600819..74e1a888 100644
--- a/embeddable-dll-service/csharp/Service.cs
+++ b/embeddable-dll-service/csharp/TunnelDll/Service.cs
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
using System;
@@ -9,36 +9,30 @@ using System.IO.Pipes;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Diagnostics;
-using System.Security.Principal;
using System.Threading;
namespace Tunnel
{
public class Service
{
- private const string LongName = "Example WireGuard Tunnel Client";
- private const string Description = "A WireGuard tunnel created by example code.";
+ private const string LongName = "WireGuard Demo Box";
+ private const string Description = "Demonstration tunnel for testing WireGuard";
[DllImport("tunnel.dll", EntryPoint = "WireGuardTunnelService", CallingConvention = CallingConvention.Cdecl)]
public static extern bool Run([MarshalAs(UnmanagedType.LPWStr)] string configFile);
- public static NamedPipeClientStream GetPipe(string configFile)
+ public static Driver.Adapter GetAdapter(string configFile)
{
- var pipepath = "ProtectedPrefix\\Administrators\\WireGuard\\" + Path.GetFileNameWithoutExtension(configFile);
- return new NamedPipeClientStream(pipepath);
+ return new Driver.Adapter(Path.GetFileNameWithoutExtension(configFile));
}
- public static void Add(string configFile)
+ public static void Add(string configFile, bool ephemeral)
{
var tunnelName = Path.GetFileNameWithoutExtension(configFile);
var shortName = String.Format("WireGuardTunnel${0}", tunnelName);
var longName = String.Format("{0}: {1}", LongName, tunnelName);
var exeName = Process.GetCurrentProcess().MainModule.FileName;
- var pathAndArgs = String.Format("\"{0}\" /service \"{1}\"", exeName, configFile); //TODO: This is not the proper way to escape file args.
-
- var accessControl = File.GetAccessControl(configFile); //TODO: TOCTOU!
- accessControl.SetOwner(new NTAccount(Environment.UserDomainName, Environment.UserName));
- File.SetAccessControl(configFile, accessControl);
+ var pathAndArgs = String.Format("\"{0}\" /service \"{1}\" {2}", exeName, configFile, Process.GetCurrentProcess().Id); //TODO: This is not the proper way to escape file args.
var scm = Win32.OpenSCManager(null, null, Win32.ScmAccessRights.AllAccess);
if (scm == IntPtr.Zero)
@@ -49,9 +43,9 @@ namespace Tunnel
if (service != IntPtr.Zero)
{
Win32.CloseServiceHandle(service);
- Remove(configFile);
+ Remove(configFile, true);
}
- service = Win32.CreateService(scm, shortName, longName, Win32.ServiceAccessRights.AllAccess, Win32.ServiceType.Win32OwnProcess, Win32.ServiceStartType.Demand, Win32.ServiceError.Normal, pathAndArgs, null, IntPtr.Zero, "Nsi", null, null);
+ service = Win32.CreateService(scm, shortName, longName, Win32.ServiceAccessRights.AllAccess, Win32.ServiceType.Win32OwnProcess, Win32.ServiceStartType.Demand, Win32.ServiceError.Normal, pathAndArgs, null, IntPtr.Zero, "Nsi\0TcpIp\0", null, null);
if (service == IntPtr.Zero)
throw new Win32Exception(Marshal.GetLastWin32Error());
try
@@ -66,6 +60,9 @@ namespace Tunnel
if (!Win32.StartService(service, 0, null))
throw new Win32Exception(Marshal.GetLastWin32Error());
+
+ if (ephemeral && !Win32.DeleteService(service))
+ throw new Win32Exception(Marshal.GetLastWin32Error());
}
finally
{
@@ -78,7 +75,7 @@ namespace Tunnel
}
}
- public static void Remove(string configFile)
+ public static void Remove(string configFile, bool waitForStop)
{
var tunnelName = Path.GetFileNameWithoutExtension(configFile);
var shortName = String.Format("WireGuardTunnel${0}", tunnelName);
@@ -90,19 +87,16 @@ namespace Tunnel
{
var service = Win32.OpenService(scm, shortName, Win32.ServiceAccessRights.AllAccess);
if (service == IntPtr.Zero)
- {
- Win32.CloseServiceHandle(service);
return;
- }
try
{
var serviceStatus = new Win32.ServiceStatus();
Win32.ControlService(service, Win32.ServiceControl.Stop, serviceStatus);
- for (int i = 0; i < 180 && Win32.QueryServiceStatus(service, serviceStatus) && serviceStatus.dwCurrentState != Win32.ServiceState.Stopped; ++i)
+ for (int i = 0; waitForStop && i < 180 && Win32.QueryServiceStatus(service, serviceStatus) && serviceStatus.dwCurrentState != Win32.ServiceState.Stopped; ++i)
Thread.Sleep(1000);
- if (!Win32.DeleteService(service))
+ if (!Win32.DeleteService(service) && Marshal.GetLastWin32Error() != 0x00000430)
throw new Win32Exception(Marshal.GetLastWin32Error());
}
finally
diff --git a/embeddable-dll-service/csharp/Win32.cs b/embeddable-dll-service/csharp/TunnelDll/Win32.cs
index 76395f7e..8e7f986d 100644
--- a/embeddable-dll-service/csharp/Win32.cs
+++ b/embeddable-dll-service/csharp/TunnelDll/Win32.cs
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
using System;
@@ -135,6 +135,58 @@ namespace Tunnel
SidInfo = 5
}
+ [StructLayout(LayoutKind.Sequential)]
+ public unsafe struct IN_ADDR
+ {
+ public fixed byte bytes[4];
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public unsafe struct IN6_ADDR
+ {
+ public fixed byte bytes[16];
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct SOCKADDR_IN
+ {
+ public ushort sin_family;
+ public ushort sin_port;
+ public IN_ADDR sin_addr;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct SOCKADDR_IN6
+ {
+ public ushort sin6_family;
+ public ushort sin6_port;
+ public uint sin6_flowinfo;
+ public IN6_ADDR sin6_addr;
+ public uint sin6_scope_id;
+ }
+
+ [StructLayout(LayoutKind.Explicit)]
+ public struct SOCKADDR_INET
+ {
+ [FieldOffset(0)]
+ [MarshalAs(UnmanagedType.Struct)]
+ public SOCKADDR_IN Ipv4;
+
+ [FieldOffset(0)]
+ [MarshalAs(UnmanagedType.Struct)]
+ public SOCKADDR_IN6 Ipv6;
+
+ [FieldOffset(0)]
+ public ADDRESS_FAMILY si_family;
+ }
+
+ public enum ADDRESS_FAMILY : UInt16
+ {
+ AF_UNSPEC = 0,
+ AF_INET = 2,
+ AF_INET6 = 23
+ }
+
[DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr OpenSCManager(string machineName, string databaseName, ScmAccessRights dwDesiredAccess);
diff --git a/embeddable-dll-service/csharp/demo-client.csproj b/embeddable-dll-service/csharp/demo-client.csproj
new file mode 100644
index 00000000..00339ee2
--- /dev/null
+++ b/embeddable-dll-service/csharp/demo-client.csproj
@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
+
+ <PropertyGroup>
+ <OutputType>WinExe</OutputType>
+ <TargetFramework>net5.0-windows</TargetFramework>
+ <RootNamespace>DemoUI</RootNamespace>
+ <UseWindowsForms>true</UseWindowsForms>
+ <AssemblyName>demo-client</AssemblyName>
+ <Platforms>x64</Platforms>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <ApplicationManifest>DemoUI\app.manifest</ApplicationManifest>
+ </PropertyGroup>
+
+</Project>
diff --git a/embeddable-dll-service/csharp/demo-client.csproj.user b/embeddable-dll-service/csharp/demo-client.csproj.user
new file mode 100644
index 00000000..27d1a581
--- /dev/null
+++ b/embeddable-dll-service/csharp/demo-client.csproj.user
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Compile Update="DemoUI\MainWindow.cs">
+ <SubType>Form</SubType>
+ </Compile>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/embeddable-dll-service/csharp/demo-client.sln b/embeddable-dll-service/csharp/demo-client.sln
new file mode 100644
index 00000000..a7d8b0ee
--- /dev/null
+++ b/embeddable-dll-service/csharp/demo-client.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30804.86
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "demo-client", "demo-client.csproj", "{AADC81E1-0294-483B-ABAE-63DBE82436E9}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Release|x64 = Release|x64
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {AADC81E1-0294-483B-ABAE-63DBE82436E9}.Debug|x64.ActiveCfg = Debug|x64
+ {AADC81E1-0294-483B-ABAE-63DBE82436E9}.Debug|x64.Build.0 = Debug|x64
+ {AADC81E1-0294-483B-ABAE-63DBE82436E9}.Release|x64.ActiveCfg = Release|x64
+ {AADC81E1-0294-483B-ABAE-63DBE82436E9}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {E410FD53-4E4A-4299-B6BD-CE91685DF7BE}
+ EndGlobalSection
+EndGlobal
diff --git a/embeddable-dll-service/main.go b/embeddable-dll-service/main.go
index 27fca4ee..f313ae7f 100644
--- a/embeddable-dll-service/main.go
+++ b/embeddable-dll-service/main.go
@@ -1,28 +1,27 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package main
import (
"C"
+ "crypto/rand"
+ "log"
+ "path/filepath"
+ "unsafe"
"golang.org/x/crypto/curve25519"
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/windows/conf"
"golang.zx2c4.com/wireguard/windows/tunnel"
-
- "crypto/rand"
- "log"
- "path/filepath"
- "unsafe"
)
//export WireGuardTunnelService
func WireGuardTunnelService(confFile16 *uint16) bool {
- confFile := windows.UTF16ToString((*[(1 << 30) - 1]uint16)(unsafe.Pointer(confFile16))[:])
+ confFile := windows.UTF16PtrToString(confFile16)
conf.PresetRootDirectory(filepath.Dir(confFile))
tunnel.UseFixedGUIDInsteadOfDeterministic = true
err := tunnel.Run(confFile)
@@ -33,7 +32,7 @@ func WireGuardTunnelService(confFile16 *uint16) bool {
}
//export WireGuardGenerateKeypair
-func WireGuardGenerateKeypair(publicKey *byte, privateKey *byte) {
+func WireGuardGenerateKeypair(publicKey, privateKey *byte) {
publicKeyArray := (*[32]byte)(unsafe.Pointer(publicKey))
privateKeyArray := (*[32]byte)(unsafe.Pointer(privateKey))
n, err := rand.Read(privateKeyArray[:])
diff --git a/go.mod b/go.mod
index 25b3e04e..84a49f75 100644
--- a/go.mod
+++ b/go.mod
@@ -1,18 +1,23 @@
module golang.zx2c4.com/wireguard/windows
+go 1.18
+
require (
- github.com/lxn/walk v0.0.0-20191128110447-55ccb3a9f5c1
- github.com/lxn/win v0.0.0-20191128105842-2da648fda5b4
- golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876
- golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553
- golang.org/x/sys v0.0.0-20200107162124-548cf772de50
- golang.org/x/text v0.3.2
- golang.zx2c4.com/wireguard v0.0.20191013-0.20200107164045-4fa2ea6a2dab
+ github.com/lxn/walk v0.0.0-20210112085537-c389da54e794
+ github.com/lxn/win v0.0.0-20210218163916-a377121e959e
+ golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd
+ golang.org/x/net v0.0.0-20220225172249-27dd8689420f
+ golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86
+ golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab
)
-replace (
- github.com/lxn/walk => golang.zx2c4.com/wireguard/windows v0.0.0-20191128151049-87f28cc339ec
- github.com/lxn/win => golang.zx2c4.com/wireguard/windows v0.0.0-20191128151145-b4e4933852d5
+require (
+ golang.org/x/mod v0.4.2 // indirect
+ golang.org/x/tools v0.1.7 // indirect
+ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
)
-go 1.13
+replace (
+ github.com/lxn/walk => golang.zx2c4.com/wireguard/windows v0.0.0-20210121140954-e7fc19d483bd
+ github.com/lxn/win => golang.zx2c4.com/wireguard/windows v0.0.0-20210224134948-620c54ef6199
+)
diff --git a/go.mod.master b/go.mod.master
index e24ca108..ca40b2f0 100644
--- a/go.mod.master
+++ b/go.mod.master
@@ -1,13 +1,14 @@
module golang.zx2c4.com/wireguard/windows
+go 1.18
+
require (
github.com/lxn/walk latest
github.com/lxn/win latest
golang.org/x/crypto latest
golang.org/x/net latest
golang.org/x/sys latest
- golang.org/x/text latest
- golang.zx2c4.com/wireguard master
+ golang.org/x/text master
)
replace (
diff --git a/go.sum b/go.sum
index 1ff88e9a..c402ce70 100644
--- a/go.sum
+++ b/go.sum
@@ -1,24 +1,29 @@
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 h1:sKJQZMuxjOAR/Uo2LBfU90onWEf1dF4C+0hPJCc9Mpc=
-golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38=
+golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
-golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200107162124-548cf772de50 h1:YvQ10rzcqWXLlJZ3XCUoO25savxmscf4+SC+ZqiCHhA=
-golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86 h1:A9i04dxx7Cribqbs8jf3FQLogkL/CV2YN7hj9KWJCkc=
+golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.zx2c4.com/wireguard v0.0.20191013-0.20200107164045-4fa2ea6a2dab h1:HVdRO4CGjul3Pj1BD1K5uThcFtrwXEF+F5qI+//V0mw=
-golang.zx2c4.com/wireguard v0.0.20191013-0.20200107164045-4fa2ea6a2dab/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4=
-golang.zx2c4.com/wireguard/windows v0.0.0-20191128151049-87f28cc339ec h1:pBaaA+qpAHWEAT6QOfR/FIhQADFBtRgQVhj7wQicA0g=
-golang.zx2c4.com/wireguard/windows v0.0.0-20191128151049-87f28cc339ec/go.mod h1:Y+FYqVFaQO6a+1uigm0N0GiuaZrLEaBxEiJ8tfH9sMQ=
-golang.zx2c4.com/wireguard/windows v0.0.0-20191128151145-b4e4933852d5 h1:tkrF3cHqbnFQ068q0cUbu9nJMXKChFT2rGL9sSAMlrI=
-golang.zx2c4.com/wireguard/windows v0.0.0-20191128151145-b4e4933852d5/go.mod h1:ouWl4wViUNh8tPSIwxTVMuS014WakR1hqvBc2I0bMoA=
+golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab h1:eHo2TTVBaAPw9lDGK2Gb9GyPMXT6g7O63W6sx3ylbzU=
+golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ=
+golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.zx2c4.com/wireguard/windows v0.0.0-20210121140954-e7fc19d483bd h1:kAUzMAITME2MCtrXBaUa9P4tndiXGWO674k9gn6ZR28=
+golang.zx2c4.com/wireguard/windows v0.0.0-20210121140954-e7fc19d483bd/go.mod h1:Y+FYqVFaQO6a+1uigm0N0GiuaZrLEaBxEiJ8tfH9sMQ=
+golang.zx2c4.com/wireguard/windows v0.0.0-20210224134948-620c54ef6199 h1:ogXKLng/Myrt2odYTkleySGzQj/GWg9GV1AQ8P9NnU4=
+golang.zx2c4.com/wireguard/windows v0.0.0-20210224134948-620c54ef6199/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
diff --git a/gotext.go b/gotext.go
new file mode 100644
index 00000000..db39bdd3
--- /dev/null
+++ b/gotext.go
@@ -0,0 +1,75 @@
+//go:build generate
+
+//go:generate go run gotext.go
+
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package main
+
+import (
+ "encoding/json"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+
+ "golang.org/x/text/message/pipeline"
+)
+
+func main() {
+ langDirs, err := os.ReadDir("locales")
+ if err != nil {
+ panic(err)
+ }
+ var langs []string
+ for _, dir := range langDirs {
+ if !dir.IsDir() {
+ continue
+ }
+ lang := dir.Name()
+ if jsonData, err := os.ReadFile(filepath.Join("locales", lang, "messages.gotext.json")); err == nil {
+ var translations pipeline.Messages
+ if err := json.Unmarshal(jsonData, &translations); err != nil {
+ panic(err)
+ }
+ lang = translations.Language.String()
+ if lang != dir.Name() {
+ err = os.Rename(filepath.Join("locales", dir.Name()), filepath.Join("locales", lang))
+ if err != nil {
+ panic(err)
+ }
+ }
+ } else if os.IsNotExist(err) {
+ panic(err)
+ }
+ langs = append(langs, lang)
+ }
+ if len(langs) == 0 {
+ panic("no locales found")
+ }
+ gotext, err := os.CreateTemp("", "gotext*.exe")
+ if err != nil {
+ panic(err)
+ }
+ gotextFilename := gotext.Name()
+ gotext.Close()
+ defer os.Remove(gotextFilename)
+ cmd := exec.Command("go", "build", "-o", gotextFilename, "golang.org/x/text/cmd/gotext")
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ err = cmd.Run()
+ if err != nil {
+ panic(err)
+ }
+ cmd = exec.Command(gotextFilename, "-srclang=en", "update", "-out=zgotext.go", "-lang="+strings.Join(langs, ","))
+ cmd.Env = append(os.Environ(), "GOOS=windows", "GOARCH=amd64")
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ err = cmd.Run()
+ if err != nil {
+ panic(err)
+ }
+}
diff --git a/installer/.gitignore b/installer/.gitignore
index 2933d030..fc4d2127 100644
--- a/installer/.gitignore
+++ b/installer/.gitignore
@@ -5,3 +5,5 @@
/dist
/x86
/amd64
+/arm
+/arm64
diff --git a/installer/build.bat b/installer/build.bat
index 74cea439..60d2558c 100644
--- a/installer/build.bat
+++ b/installer/build.bat
@@ -1,30 +1,27 @@
@echo off
rem SPDX-License-Identifier: MIT
-rem Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+rem Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
setlocal
set PATHEXT=.exe
set BUILDDIR=%~dp0
cd /d %BUILDDIR% || exit /b 1
-for /f "tokens=3" %%a in ('findstr /r "WIREGUARD_WINDOWS_VERSION_STRING.*[0-9.]*" ..\version\version.h') do set WIREGUARD_VERSION=%%a
+for /f "tokens=3" %%a in ('findstr /r "Number.*=.*[0-9.]*" ..\version\version.go') do set WIREGUARD_VERSION=%%a
set WIREGUARD_VERSION=%WIREGUARD_VERSION:"=%
set WIX_CANDLE_FLAGS=-nologo -dWIREGUARD_VERSION="%WIREGUARD_VERSION%"
set WIX_LIGHT_FLAGS=-nologo -spdb
-set WIX_LIGHT_FLAGS=%WIX_LIGHT_FLAGS% -sw1056
-set WIX_LIGHT_FLAGS=%WIX_LIGHT_FLAGS% -sice:ICE30
+set WIX_LIGHT_FLAGS=%WIX_LIGHT_FLAGS% -sice:ICE39
set WIX_LIGHT_FLAGS=%WIX_LIGHT_FLAGS% -sice:ICE61
-set WIX_LIGHT_FLAGS=%WIX_LIGHT_FLAGS% -sice:ICE09
+set WIX_LIGHT_FLAGS=%WIX_LIGHT_FLAGS% -sice:ICE03
if exist .deps\prepared goto :build
:installdeps
rmdir /s /q .deps 2> NUL
mkdir .deps || goto :error
cd .deps || goto :error
- call :download wintun-x86.msm https://www.wintun.net/builds/wintun-x86-0.8.msm 7ff5fcca21be75584fea830a4624ff52305ebb6982c3ec1b294a22b20ee5c1fc || goto :error
- call :download wintun-amd64.msm https://www.wintun.net/builds/wintun-amd64-0.8.msm 14e94f3151e425d80fc262b4bb3f351df9d3b3dde5d9cf39aad2e94c39944435 || goto :error
- call :download wix-binaries.zip https://github.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip 2c1888d5d1dba377fc7fa14444cf556963747ff9a0a289a3599cf09da03b9e2e || goto :error
+ call :download wix-binaries.zip https://wixtoolset.org/downloads/v3.14.0.4118/wix314-binaries.zip 34dcbba9952902bfb710161bd45ee2e721ffa878db99f738285a21c9b09c6edb || goto :error
echo [+] Extracting wix-binaries.zip
mkdir wix\bin || goto :error
tar -xf wix-binaries.zip -C wix\bin || goto :error
@@ -34,14 +31,15 @@ if exist .deps\prepared goto :build
cd .. || goto :error
:build
+ if exist ..\sign.bat call ..\sign.bat
+ set PATH=%BUILDDIR%..\.deps\llvm-mingw\bin;%PATH%
set WIX=%BUILDDIR%.deps\wix\
set CFLAGS=-O3 -Wall -std=gnu11 -DWINVER=0x0601 -D_WIN32_WINNT=0x0601 -municode -DUNICODE -D_UNICODE -DNDEBUG
set LDFLAGS=-shared -s -Wl,--kill-at -Wl,--major-os-version=6 -Wl,--minor-os-version=1 -Wl,--major-subsystem-version=6 -Wl,--minor-subsystem-version=1 -Wl,--tsaware -Wl,--dynamicbase -Wl,--nxcompat -Wl,--export-all-symbols
- set LDLIBS=-lmsi -lole32 -lshlwapi -lshell32 -luuid
+ set LDLIBS=-lmsi -lole32 -lshlwapi -lshell32 -luuid -lntdll
call :msi x86 i686 x86 || goto :error
- set CGO_LDFLAGS=%CGO_LDFLAGS% -Wl,--high-entropy-va
call :msi amd64 x86_64 x64 || goto :error
- if exist ..\sign.bat call ..\sign.bat
+ call :msi arm64 aarch64 arm64 || goto :error
if "%SigningCertificate%"=="" goto :success
if "%TimestampServer%"=="" goto :success
echo [+] Signing
@@ -59,11 +57,15 @@ if exist .deps\prepared goto :build
goto :eof
:msi
- set PATH=%BUILDDIR%..\.deps\%~2-w64-mingw32-native\bin;%PATH%
set CC=%~2-w64-mingw32-gcc
if not exist "%~1" mkdir "%~1"
echo [+] Compiling %1
%CC% %CFLAGS% %LDFLAGS% -o "%~1\customactions.dll" customactions.c %LDLIBS% || exit /b 1
+ if "%SigningCertificate%"=="" goto :skipsign
+ if "%TimestampServer%"=="" goto :skipsign
+ echo [+] Signing %1
+ signtool sign /sha1 "%SigningCertificate%" /fd sha256 /tr "%TimestampServer%" /td sha256 /d "WireGuard Setup Custom Actions" "%~1\customactions.dll" || exit /b 1
+:skipsign
"%WIX%bin\candle" %WIX_CANDLE_FLAGS% -dWIREGUARD_PLATFORM="%~1" -out "%~1\wireguard.wixobj" -arch %3 wireguard.wxs || exit /b %errorlevel%
echo [+] Linking %1
"%WIX%bin\light" %WIX_LIGHT_FLAGS% -out "dist\wireguard-%~1-%WIREGUARD_VERSION%.msi" "%~1\wireguard.wixobj" || exit /b %errorlevel%
diff --git a/installer/customactions.c b/installer/customactions.c
index 496d80b1..d3078a31 100644
--- a/installer/customactions.c
+++ b/installer/customactions.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT
/*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
#include <windows.h>
@@ -17,7 +17,7 @@
#define MANAGER_SERVICE_NAME TEXT("WireGuardManager")
#define TUNNEL_SERVICE_PREFIX TEXT("WireGuardTunnel$")
-enum log_level { LOG_LEVEL_INFO, LOG_LEVEL_WARN, LOG_LEVEL_ERR };
+enum log_level { LOG_LEVEL_INFO, LOG_LEVEL_WARN, LOG_LEVEL_ERR, LOG_LEVEL_MSIERR };
static void log_messagef(MSIHANDLE installer, enum log_level level, const TCHAR *format, ...)
{
@@ -49,6 +49,10 @@ static void log_messagef(MSIHANDLE installer, enum log_level level, const TCHAR
template = TEXT("WireGuard error: [1]");
type = INSTALLMESSAGE_ERROR;
break;
+ case LOG_LEVEL_MSIERR:
+ template = TEXT("[1]");
+ type = INSTALLMESSAGE_ERROR;
+ break;
default:
goto out;
}
@@ -78,6 +82,46 @@ static void log_errorf(MSIHANDLE installer, enum log_level level, DWORD error_co
LocalFree(system_message);
}
+__declspec(dllexport) UINT __stdcall CheckWow64(MSIHANDLE installer)
+{
+ UINT ret = ERROR_SUCCESS;
+ bool is_com_initialized = SUCCEEDED(CoInitialize(NULL));
+ HMODULE kernel32 = GetModuleHandle(TEXT("kernel32.dll"));
+ BOOL(WINAPI *IsWow64Process2)(HANDLE hProcess, USHORT *pProcessMachine, USHORT *pNativeMachine);
+ USHORT process_machine, native_machine;
+ BOOL is_wow64_process;
+
+ if (!kernel32) {
+ ret = GetLastError();
+ log_errorf(installer, LOG_LEVEL_ERR, ret, TEXT("Failed to get kernel32.dll handle"));
+ goto out;
+ }
+ *(FARPROC *)&IsWow64Process2 = GetProcAddress(kernel32, "IsWow64Process2");
+ if (IsWow64Process2) {
+ if (!IsWow64Process2(GetCurrentProcess(), &process_machine, &native_machine)) {
+ ret = GetLastError();
+ log_errorf(installer, LOG_LEVEL_ERR, ret, TEXT("Failed to determine Wow64 status from IsWow64Process2"));
+ goto out;
+ }
+ if (process_machine == IMAGE_FILE_MACHINE_UNKNOWN)
+ goto out;
+ } else {
+ if (!IsWow64Process(GetCurrentProcess(), &is_wow64_process)) {
+ ret = GetLastError();
+ log_errorf(installer, LOG_LEVEL_ERR, ret, TEXT("Failed to determine Wow64 status from IsWow64Process"));
+ goto out;
+ }
+ if (!is_wow64_process)
+ goto out;
+ }
+ log_messagef(installer, LOG_LEVEL_MSIERR, TEXT("You must use the native version of WireGuard on this computer."));
+ ret = ERROR_INSTALL_FAILURE;
+out:
+ if (is_com_initialized)
+ CoUninitialize();
+ return ret;
+}
+
static UINT insert_service_control(MSIHANDLE installer, MSIHANDLE view, const TCHAR *service_name, bool start)
{
static unsigned int index = 0;
@@ -126,62 +170,6 @@ out:
return ret;
}
-static bool remove_directory_recursive(MSIHANDLE installer, TCHAR path[MAX_PATH], unsigned int max_depth)
-{
- HANDLE find_handle;
- WIN32_FIND_DATA find_data;
- TCHAR *path_end;
-
- if (!max_depth) {
- log_messagef(installer, LOG_LEVEL_WARN, TEXT("Too many levels of nesting at \"%1\""), path);
- return false;
- }
-
- path_end = path + _tcsnlen(path, MAX_PATH);
- if (!PathAppend(path, TEXT("*.*"))) {
- log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("PathAppend(\"%1\", \"*.*\") failed"), path);
- return false;
- }
- find_handle = FindFirstFileEx(path, FindExInfoBasic, &find_data, FindExSearchNameMatch, NULL, 0);
- if (find_handle == INVALID_HANDLE_VALUE) {
- log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("FindFirstFileEx(\"%1\") failed"), path);
- return false;
- }
- do {
- if (find_data.cFileName[0] == TEXT('.') && (find_data.cFileName[1] == TEXT('\0') || (find_data.cFileName[1] == TEXT('.') && find_data.cFileName[2] == TEXT('\0'))))
- continue;
-
- path_end[0] = TEXT('\0');
- if (!PathAppend(path, find_data.cFileName)) {
- log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("PathAppend(\"%1\", \"%2\") failed"), path, find_data.cFileName);
- continue;
- }
-
- if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
- remove_directory_recursive(installer, path, max_depth - 1);
- continue;
- }
-
- if ((find_data.dwFileAttributes & FILE_ATTRIBUTE_READONLY) && !SetFileAttributes(path, find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY))
- log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("SetFileAttributes(\"%1\") failed"), path);
-
- if (DeleteFile(path))
- log_messagef(installer, LOG_LEVEL_INFO, TEXT("Deleted \"%1\""), path);
- else
- log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("DeleteFile(\"%1\") failed"), path);
- } while (FindNextFile(find_handle, &find_data));
- FindClose(find_handle);
-
- path_end[0] = TEXT('\0');
- if (RemoveDirectory(path)) {
- log_messagef(installer, LOG_LEVEL_INFO, TEXT("Removed \"%1\""), path);
- return true;
- } else {
- log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("RemoveDirectory(\"%1\") failed"), path);
- return false;
- }
-}
-
__declspec(dllexport) UINT __stdcall EvaluateWireGuardServices(MSIHANDLE installer)
{
UINT ret = ERROR_INSTALL_FAILURE;
@@ -255,27 +243,82 @@ out:
return ret == ERROR_SUCCESS ? ret : ERROR_INSTALL_FAILURE;
}
-__declspec(dllexport) UINT __stdcall RemoveConfigFolder(MSIHANDLE installer)
+__declspec(dllexport) UINT __stdcall LaunchApplicationAndAbort(MSIHANDLE installer)
{
- LSTATUS ret;
+ UINT ret = ERROR_INSTALL_FAILURE;
TCHAR path[MAX_PATH];
+ DWORD path_len = _countof(path);
+ PROCESS_INFORMATION pi;
+ STARTUPINFO si = { .cb = sizeof(STARTUPINFO) };
+
+ ret = MsiGetProperty(installer, TEXT("WireGuardFolder"), path, &path_len);
+ if (ret != ERROR_SUCCESS) {
+ log_errorf(installer, LOG_LEVEL_WARN, ret, TEXT("MsiGetProperty(\"WireGuardFolder\") failed"));
+ goto out;
+ }
+ if (!path[0] || !PathAppend(path, TEXT("wireguard.exe")))
+ goto out;
+ log_messagef(installer, LOG_LEVEL_INFO, TEXT("Launching %1"), path);
+ if (!CreateProcess(path, TEXT("wireguard"), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
+ log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("Failed to create \"%1\" process"), path);
+ goto out;
+ }
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+out:
+ return ERROR_INSTALL_USEREXIT;
+}
+
+__declspec(dllexport) UINT __stdcall EvaluateWireGuardComponents(MSIHANDLE installer)
+{
+ UINT ret = ERROR_INSTALL_FAILURE;
bool is_com_initialized = SUCCEEDED(CoInitialize(NULL));
+ INSTALLSTATE component_installed, component_action;
+ TCHAR path[MAX_PATH];
+ DWORD path_len = _countof(path);
- ret = SHRegGetPath(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\S-1-5-18"),
- TEXT("ProfileImagePath"), path, 0);
+ ret = MsiGetComponentState(installer, TEXT("WireGuardExecutable"), &component_installed, &component_action);
if (ret != ERROR_SUCCESS) {
- log_errorf(installer, LOG_LEVEL_WARN, ret, TEXT("SHRegGetPath failed"));
+ log_errorf(installer, LOG_LEVEL_ERR, ret, TEXT("MsiGetComponentState(\"WireGuardExecutable\") failed"));
goto out;
}
- if (!PathAppend(path, TEXT("AppData\\Local\\WireGuard"))) {
- log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("PathAppend(\"%1\", \"AppData\\Local\\WireGuard\") failed"), path);
+ ret = MsiGetProperty(installer, TEXT("WireGuardFolder"), path, &path_len);
+ if (ret != ERROR_SUCCESS) {
+ log_errorf(installer, LOG_LEVEL_ERR, ret, TEXT("MsiGetProperty(\"WireGuardFolder\") failed"));
goto out;
}
- remove_directory_recursive(installer, path, 10);
+
+ if (component_action >= INSTALLSTATE_LOCAL) {
+ /* WireGuardExecutable component shall be installed. */
+ ret = MsiSetProperty(installer, TEXT("KillWireGuardProcesses"), path);
+ if (ret != ERROR_SUCCESS) {
+ log_errorf(installer, LOG_LEVEL_ERR, ret, TEXT("MsiSetProperty(\"KillWireGuardProcesses\") failed"));
+ goto out;
+ }
+ } else if (component_action >= INSTALLSTATE_REMOVED) {
+ /* WireGuardExecutable component shall be uninstalled. */
+ ret = MsiSetProperty(installer, TEXT("KillWireGuardProcesses"), path);
+ if (ret != ERROR_SUCCESS) {
+ log_errorf(installer, LOG_LEVEL_ERR, ret, TEXT("MsiSetProperty(\"KillWireGuardProcesses\") failed"));
+ goto out;
+ }
+ ret = MsiSetProperty(installer, TEXT("RemoveConfigFolder"), path);
+ if (ret != ERROR_SUCCESS) {
+ log_errorf(installer, LOG_LEVEL_ERR, ret, TEXT("MsiSetProperty(\"RemoveConfigFolder\") failed"));
+ goto out;
+ }
+ ret = MsiSetProperty(installer, TEXT("RemoveAdapters"), path);
+ if (ret != ERROR_SUCCESS) {
+ log_errorf(installer, LOG_LEVEL_ERR, ret, TEXT("MsiSetProperty(\"RemoveAdapters\") failed"));
+ goto out;
+ }
+ }
+ ret = ERROR_SUCCESS;
+
out:
if (is_com_initialized)
CoUninitialize();
- return ERROR_SUCCESS;
+ return ret == ERROR_SUCCESS ? ret : ERROR_INSTALL_FAILURE;
}
struct file_id { DWORD volume, index_high, index_low; };
@@ -299,36 +342,30 @@ static bool calculate_file_id(const TCHAR *path, struct file_id *id)
return true;
}
-static bool calculate_known_file_id(const KNOWNFOLDERID *known_folder, const TCHAR *file, struct file_id *id)
-{
- TCHAR *folder_path, process_path[MAX_PATH + 1];
- bool ret = false;
-
- if (SHGetKnownFolderPath(known_folder, KF_FLAG_DEFAULT, NULL, &folder_path) == S_OK) {
- if (PathCombine(process_path, folder_path, file)) {
- if (calculate_file_id(process_path, id))
- ret = true;
- }
- CoTaskMemFree(folder_path);
- }
- return ret;
-}
-
__declspec(dllexport) UINT __stdcall KillWireGuardProcesses(MSIHANDLE installer)
{
HANDLE snapshot, process;
PROCESSENTRY32 entry = { .dwSize = sizeof(PROCESSENTRY32) };
- TCHAR process_path[MAX_PATH + 1];
- DWORD process_path_len;
+ TCHAR process_path[MAX_PATH], executable[MAX_PATH];
+ DWORD process_path_len = _countof(process_path);
struct file_id file_ids[3], file_id;
size_t file_ids_len = 0;
bool is_com_initialized = SUCCEEDED(CoInitialize(NULL));
+ LSTATUS mret;
- if (calculate_known_file_id(&FOLDERID_System, TEXT("wg.exe"), &file_ids[file_ids_len]))
- ++file_ids_len;
- if (calculate_known_file_id(&FOLDERID_SystemX86, TEXT("wg.exe"), &file_ids[file_ids_len]))
+ mret = MsiGetProperty(installer, TEXT("CustomActionData"), process_path, &process_path_len);
+ if (mret != ERROR_SUCCESS) {
+ log_errorf(installer, LOG_LEVEL_WARN, mret, TEXT("MsiGetProperty(\"CustomActionData\") failed"));
+ goto out;
+ }
+ if (!process_path[0])
+ goto out;
+
+ log_messagef(installer, LOG_LEVEL_INFO, TEXT("Detecting running processes"));
+
+ if (PathCombine(executable, process_path, TEXT("wg.exe")) && calculate_file_id(executable, &file_ids[file_ids_len]))
++file_ids_len;
- if (calculate_known_file_id(&FOLDERID_ProgramFiles, TEXT("WireGuard\\wireguard.exe"), &file_ids[file_ids_len]))
+ if (PathCombine(executable, process_path, TEXT("wireguard.exe")) && calculate_file_id(executable, &file_ids[file_ids_len]))
++file_ids_len;
if (!file_ids_len)
goto out;
@@ -371,3 +408,147 @@ out:
CoUninitialize();
return ERROR_SUCCESS;
}
+
+static bool remove_directory_recursive(MSIHANDLE installer, TCHAR path[MAX_PATH], unsigned int max_depth)
+{
+ HANDLE find_handle;
+ WIN32_FIND_DATA find_data;
+ TCHAR *path_end;
+
+ if (!max_depth) {
+ log_messagef(installer, LOG_LEVEL_WARN, TEXT("Too many levels of nesting at \"%1\""), path);
+ return false;
+ }
+
+ path_end = path + _tcsnlen(path, MAX_PATH);
+ if (!PathAppend(path, TEXT("*.*"))) {
+ log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("PathAppend(\"%1\", \"*.*\") failed"), path);
+ return false;
+ }
+ find_handle = FindFirstFileEx(path, FindExInfoBasic, &find_data, FindExSearchNameMatch, NULL, 0);
+ if (find_handle == INVALID_HANDLE_VALUE) {
+ log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("FindFirstFileEx(\"%1\") failed"), path);
+ return false;
+ }
+ do {
+ if (find_data.cFileName[0] == TEXT('.') && (find_data.cFileName[1] == TEXT('\0') || (find_data.cFileName[1] == TEXT('.') && find_data.cFileName[2] == TEXT('\0'))))
+ continue;
+
+ path_end[0] = TEXT('\0');
+ if (!PathAppend(path, find_data.cFileName)) {
+ log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("PathAppend(\"%1\", \"%2\") failed"), path, find_data.cFileName);
+ continue;
+ }
+
+ if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
+ remove_directory_recursive(installer, path, max_depth - 1);
+ continue;
+ }
+
+ if ((find_data.dwFileAttributes & FILE_ATTRIBUTE_READONLY) && !SetFileAttributes(path, find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY))
+ log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("SetFileAttributes(\"%1\") failed"), path);
+
+ if (DeleteFile(path))
+ log_messagef(installer, LOG_LEVEL_INFO, TEXT("Deleted \"%1\""), path);
+ else
+ log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("DeleteFile(\"%1\") failed"), path);
+ } while (FindNextFile(find_handle, &find_data));
+ FindClose(find_handle);
+
+ path_end[0] = TEXT('\0');
+ if (RemoveDirectory(path)) {
+ log_messagef(installer, LOG_LEVEL_INFO, TEXT("Removed \"%1\""), path);
+ return true;
+ } else {
+ log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("RemoveDirectory(\"%1\") failed"), path);
+ return false;
+ }
+}
+
+__declspec(dllexport) UINT __stdcall RemoveConfigFolder(MSIHANDLE installer)
+{
+ LSTATUS ret;
+ TCHAR path[MAX_PATH];
+ DWORD path_len = _countof(path);
+ bool is_com_initialized = SUCCEEDED(CoInitialize(NULL));
+
+ ret = MsiGetProperty(installer, TEXT("CustomActionData"), path, &path_len);
+ if (ret != ERROR_SUCCESS) {
+ log_errorf(installer, LOG_LEVEL_WARN, ret, TEXT("MsiGetProperty(\"CustomActionData\") failed"));
+ goto out;
+ }
+ if (!path[0] || !PathAppend(path, TEXT("Data")))
+ goto out;
+ remove_directory_recursive(installer, path, 10);
+ RegDeleteKey(HKEY_LOCAL_MACHINE, TEXT("Software\\WireGuard")); // Assumes no WOW.
+out:
+ if (is_com_initialized)
+ CoUninitialize();
+ return ERROR_SUCCESS;
+}
+
+__declspec(dllexport) UINT __stdcall RemoveAdapters(MSIHANDLE installer)
+{
+ UINT ret;
+ bool is_com_initialized = SUCCEEDED(CoInitialize(NULL));
+ TCHAR path[MAX_PATH];
+ DWORD path_len = _countof(path);
+ HANDLE pipe;
+ char buf[0x200];
+ DWORD offset = 0, size_read;
+ PROCESS_INFORMATION pi;
+ STARTUPINFO si = {
+ .cb = sizeof(STARTUPINFO),
+ .dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES,
+ .wShowWindow = SW_HIDE
+ };
+
+ ret = MsiGetProperty(installer, TEXT("CustomActionData"), path, &path_len);
+ if (ret != ERROR_SUCCESS) {
+ log_errorf(installer, LOG_LEVEL_WARN, ret, TEXT("MsiGetProperty(\"CustomActionData\") failed"));
+ goto out;
+ }
+ if (!path[0] || !PathAppend(path, TEXT("wireguard.exe")))
+ goto out;
+
+ if (!CreatePipe(&pipe, &si.hStdOutput, NULL, 0)) {
+ log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("CreatePipe failed"));
+ goto out;
+ }
+ if (!SetHandleInformation(si.hStdOutput, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) {
+ log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("SetHandleInformation failed"));
+ goto cleanup_pipe_w;
+ }
+ if (!CreateProcess(path, TEXT("wireguard /removedriver"), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
+ log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("Failed to create \"%1\" process"), path);
+ goto cleanup_pipe_w;
+ }
+ CloseHandle(si.hStdOutput);
+ buf[sizeof(buf) - 1] = '\0';
+ while (ReadFile(pipe, buf + offset, sizeof(buf) - offset - 1, &size_read, NULL)) {
+ char *nl;
+ buf[offset + size_read] = '\0';
+ nl = strchr(buf, '\n');
+ if (!nl) {
+ offset = size_read;
+ continue;
+ }
+ nl[0] = '\0';
+ log_messagef(installer, LOG_LEVEL_INFO, TEXT("%1!hs!"), buf);
+ offset = strlen(&nl[1]);
+ memmove(buf, &nl[1], offset);
+ }
+ WaitForSingleObject(pi.hProcess, INFINITE);
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ goto cleanup_pipe_r;
+
+cleanup_pipe_w:
+ CloseHandle(si.hStdOutput);
+cleanup_pipe_r:
+ CloseHandle(pipe);
+out:
+ if (is_com_initialized)
+ CoUninitialize();
+ return ERROR_SUCCESS;
+}
diff --git a/installer/fetcher/.gitignore b/installer/fetcher/.gitignore
new file mode 100644
index 00000000..9e4c427d
--- /dev/null
+++ b/installer/fetcher/.gitignore
@@ -0,0 +1,6 @@
+*.ico
+*.o
+*.d
+*.exe
+*.pro
+*.pro.user
diff --git a/installer/fetcher/Makefile b/installer/fetcher/Makefile
new file mode 100644
index 00000000..a247adfe
--- /dev/null
+++ b/installer/fetcher/Makefile
@@ -0,0 +1,42 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) 2015-2022 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+
+CFLAGS ?= -Os
+DEPLOYMENT_HOST ?= winvm
+DEPLOYMENT_PATH ?= Desktop
+
+CFLAGS += -std=gnu11 -DWINVER=0x0601 -D_WIN32_WINNT=0x0601 -flto
+CFLAGS += -Wall -Wextra
+CFLAGS += -MMD -MP
+LDLIBS += -lkernel32 -lwinhttp -lntdll -lshlwapi -lmsi -lcomctl32 -luser32 -lshell32 -lwintrust -lbcrypt
+LDFLAGS += -s -flto -Wl,--dynamicbase -Wl,--nxcompat -Wl,--tsaware -mwindows
+LDFLAGS += -Wl,--major-os-version=6 -Wl,--minor-os-version=1 -Wl,--major-subsystem-version=6 -Wl,--minor-subsystem-version=1
+# The use of -Wl,/delayload: here implies we're using llvm-mingw
+LDFLAGS += -Wl,/delayload:winhttp.dll -Wl,/delayload:msi.dll -Wl,/delayload:wintrust.dll -Wl,/delayload:advapi32.dll -Wl,/delayload:shell32.dll -Wl,/delayload:shlwapi.dll -Wl,/delayload:gdi32.dll -Wl,/delayload:user32.dll -Wl,/delayload:comctl32.dll -Wl,/delayload:bcrypt.dll
+TARGET := wireguard-installer.exe
+CC := i686-w64-mingw32-clang
+WINDRES := i686-w64-mingw32-windres
+
+$(TARGET): $(sort $(patsubst %.c,%.o,$(wildcard *.c))) resources.o
+ $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
+
+%.ico: %.svg
+ convert -background none $< -define icon:auto-resize="64,32,16" -compress zip $@
+
+resources.o: resources.rc icon.ico manifest.xml
+ $(WINDRES) -O coff -c 65001 -i $< -o $@
+
+clean:
+ $(RM) $(TARGET) *.o *.d *.ico
+
+deploy: $(TARGET)
+ scp $< $(DEPLOYMENT_HOST):$(DEPLOYMENT_PATH)
+
+sign: deploy
+ ssh $(DEPLOYMENT_HOST) '"C:\Program Files (x86)\Windows Kits\10\App Certification Kit\signtool.exe"' sign /sha1 $(SIGNING_CERTIFICATE) /fd sha256 /tr $(TIMESTAMP_SERVER) /td sha256 /d '"WireGuard Installer"' '$(DEPLOYMENT_PATH)\$(TARGET)'
+ scp -T '$(DEPLOYMENT_HOST):$(DEPLOYMENT_PATH)\$(TARGET)' .
+
+.PHONY: clean deploy sign
+
+-include *.d
diff --git a/installer/fetcher/constants.h b/installer/fetcher/constants.h
new file mode 100644
index 00000000..96d88d0e
--- /dev/null
+++ b/installer/fetcher/constants.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright (C) 2020-2022 Jason A. Donenfeld. All Rights Reserved.
+ */
+
+#ifndef _CONSTANTS_H
+#define _CONSTANTS_H
+
+#define release_public_key_base64 "RWRNqGKtBXftKTKPpBPGDMe8jHLnFQ0EdRy8Wg0apV6vTDFLAODD83G4"
+#define msi_arch_prefix "wireguard-%s-"
+#define msi_suffix ".msi"
+#define server "download.wireguard.com"
+#define port (443)
+#define latest_version_file "latest.sig"
+#define msi_path "/windows-client/"
+
+#endif
diff --git a/installer/fetcher/crypto.c b/installer/fetcher/crypto.c
new file mode 100644
index 00000000..33154efb
--- /dev/null
+++ b/installer/fetcher/crypto.c
@@ -0,0 +1,2252 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020-2022 Jason A. Donenfeld. All Rights Reserved.
+ * Copyright (c) 2020, Google Inc.
+ */
+
+#include "crypto.h"
+#include <stdint.h>
+#include <string.h>
+#include <winternl.h>
+#include <bcrypt.h>
+
+#if REG_DWORD == REG_DWORD_LITTLE_ENDIAN
+#define swap_le64(x) (x)
+#define swap_le32(x) (x)
+#elif REG_DWORD == REG_DWORD_BIG_ENDIAN
+#define swap_le64(x) __builtin_bswap64(x)
+#define swap_le32(x) __builtin_bswap32(x)
+#endif
+
+static void store_le64(uint8_t *dst, uint64_t src)
+{
+ src = swap_le64(src);
+ __builtin_memcpy(dst, &src, sizeof(src));
+}
+
+static uint64_t load_le64(const uint8_t *src)
+{
+ uint64_t dst;
+ __builtin_memcpy(&dst, src, sizeof(dst));
+ return swap_le64(dst);
+}
+
+static uint32_t load_le24(const uint8_t *in)
+{
+ uint32_t dst;
+ dst = (uint32_t)in[0];
+ dst |= ((uint32_t)in[1]) << 8;
+ dst |= ((uint32_t)in[2]) << 16;
+ return dst;
+}
+
+static uint32_t load_le32(const uint8_t *src)
+{
+ uint32_t dst;
+ __builtin_memcpy(&dst, src, sizeof(dst));
+ return swap_le32(dst);
+}
+
+static uint64_t ror64(uint64_t i, unsigned int s)
+{
+ return (i >> (s & 63)) | (i << ((-s) & 63));
+}
+
+static inline uint32_t value_barrier_u32(uint32_t a)
+{
+ __asm__("" : "+r"(a) : /* no inputs */);
+ return a;
+}
+
+static int memcmp_ct(const void *first, const void *second, size_t len)
+{
+ const uint8_t *a = first;
+ const uint8_t *b = second;
+ uint8_t diff = 0;
+
+ for (size_t i = 0; i < len; ++i) {
+ diff |= a[i] ^ b[i];
+ __asm__("" : "+r"(diff) : /* no inputs */);
+ }
+
+ return diff;
+}
+
+/*
+ * The function fiat_25519_addcarryx_u26 is an addition with carry.
+ * Postconditions:
+ * out1 = (arg1 + arg2 + arg3) mod 2^26
+ * out2 = ⌊(arg1 + arg2 + arg3) / 2^26⌋
+ *
+ * Input Bounds:
+ * arg1: [0x0 ~> 0x1]
+ * arg2: [0x0 ~> 0x3ffffff]
+ * arg3: [0x0 ~> 0x3ffffff]
+ * Output Bounds:
+ * out1: [0x0 ~> 0x3ffffff]
+ * out2: [0x0 ~> 0x1]
+ */
+static void fiat_25519_addcarryx_u26(uint32_t *out1, uint8_t *out2,
+ uint8_t arg1, uint32_t arg2, uint32_t arg3)
+{
+ uint32_t x1 = ((arg1 + arg2) + arg3);
+ uint32_t x2 = (x1 & UINT32_C(0x3ffffff));
+ uint8_t x3 = (uint8_t)(x1 >> 26);
+ *out1 = x2;
+ *out2 = x3;
+}
+
+/*
+ * The function fiat_25519_subborrowx_u26 is a subtraction with borrow.
+ * Postconditions:
+ * out1 = (-arg1 + arg2 + -arg3) mod 2^26
+ * out2 = -⌊(-arg1 + arg2 + -arg3) / 2^26⌋
+ *
+ * Input Bounds:
+ * arg1: [0x0 ~> 0x1]
+ * arg2: [0x0 ~> 0x3ffffff]
+ * arg3: [0x0 ~> 0x3ffffff]
+ * Output Bounds:
+ * out1: [0x0 ~> 0x3ffffff]
+ * out2: [0x0 ~> 0x1]
+ */
+static void fiat_25519_subborrowx_u26(uint32_t *out1, uint8_t *out2,
+ uint8_t arg1, uint32_t arg2,
+ uint32_t arg3)
+{
+ int32_t x1 = ((int32_t)(arg2 - arg1) - (int32_t)arg3);
+ int8_t x2 = (int8_t)(x1 >> 26);
+ uint32_t x3 = (x1 & UINT32_C(0x3ffffff));
+ *out1 = x3;
+ *out2 = (uint8_t)(0x0 - x2);
+}
+
+/*
+ * The function fiat_25519_addcarryx_u25 is an addition with carry.
+ * Postconditions:
+ * out1 = (arg1 + arg2 + arg3) mod 2^25
+ * out2 = ⌊(arg1 + arg2 + arg3) / 2^25⌋
+ *
+ * Input Bounds:
+ * arg1: [0x0 ~> 0x1]
+ * arg2: [0x0 ~> 0x1ffffff]
+ * arg3: [0x0 ~> 0x1ffffff]
+ * Output Bounds:
+ * out1: [0x0 ~> 0x1ffffff]
+ * out2: [0x0 ~> 0x1]
+ */
+static void fiat_25519_addcarryx_u25(uint32_t *out1, uint8_t *out2,
+ uint8_t arg1, uint32_t arg2, uint32_t arg3)
+{
+ uint32_t x1 = ((arg1 + arg2) + arg3);
+ uint32_t x2 = (x1 & UINT32_C(0x1ffffff));
+ uint8_t x3 = (uint8_t)(x1 >> 25);
+ *out1 = x2;
+ *out2 = x3;
+}
+
+/*
+ * The function fiat_25519_subborrowx_u25 is a subtraction with borrow.
+ * Postconditions:
+ * out1 = (-arg1 + arg2 + -arg3) mod 2^25
+ * out2 = -⌊(-arg1 + arg2 + -arg3) / 2^25⌋
+ *
+ * Input Bounds:
+ * arg1: [0x0 ~> 0x1]
+ * arg2: [0x0 ~> 0x1ffffff]
+ * arg3: [0x0 ~> 0x1ffffff]
+ * Output Bounds:
+ * out1: [0x0 ~> 0x1ffffff]
+ * out2: [0x0 ~> 0x1]
+ */
+static void fiat_25519_subborrowx_u25(uint32_t *out1, uint8_t *out2,
+ uint8_t arg1, uint32_t arg2,
+ uint32_t arg3)
+{
+ int32_t x1 = ((int32_t)(arg2 - arg1) - (int32_t)arg3);
+ int8_t x2 = (int8_t)(x1 >> 25);
+ uint32_t x3 = (x1 & UINT32_C(0x1ffffff));
+ *out1 = x3;
+ *out2 = (uint8_t)(0x0 - x2);
+}
+
+/*
+ * The function fiat_25519_cmovznz_u32 is a single-word conditional move.
+ * Postconditions:
+ * out1 = (if arg1 = 0 then arg2 else arg3)
+ *
+ * Input Bounds:
+ * arg1: [0x0 ~> 0x1]
+ * arg2: [0x0 ~> 0xffffffff]
+ * arg3: [0x0 ~> 0xffffffff]
+ * Output Bounds:
+ * out1: [0x0 ~> 0xffffffff]
+ */
+static void fiat_25519_cmovznz_u32(uint32_t *out1, uint8_t arg1, uint32_t arg2,
+ uint32_t arg3)
+{
+ uint8_t x1 = (!(!arg1));
+ uint32_t x2 = ((int8_t)(0x0 - x1) & UINT32_C(0xffffffff));
+ // Note this line has been patched from the synthesized code to add value
+ // barriers.
+ //
+ // Clang recognizes this pattern as a select. While it usually transforms it
+ // to a cmov, it sometimes further transforms it into a branch, which we do
+ // not want.
+ uint32_t x3 = ((value_barrier_u32(x2) & arg3) |
+ (value_barrier_u32(~x2) & arg2));
+ *out1 = x3;
+}
+
+/*
+ * The function fiat_25519_carry_mul multiplies two field elements and reduces the result.
+ * Postconditions:
+ * eval out1 mod m = (eval arg1 * eval arg2) mod m
+ *
+ * Input Bounds:
+ * arg1: [[0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999]]
+ * arg2: [[0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999]]
+ * Output Bounds:
+ * out1: [[0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333]]
+ */
+static void fiat_25519_carry_mul(uint32_t out1[10], const uint32_t arg1[10],
+ const uint32_t arg2[10])
+{
+ uint64_t x1 = ((uint64_t)(arg1[9]) * ((arg2[9]) * UINT8_C(0x26)));
+ uint64_t x2 = ((uint64_t)(arg1[9]) * ((arg2[8]) * UINT8_C(0x13)));
+ uint64_t x3 = ((uint64_t)(arg1[9]) * ((arg2[7]) * UINT8_C(0x26)));
+ uint64_t x4 = ((uint64_t)(arg1[9]) * ((arg2[6]) * UINT8_C(0x13)));
+ uint64_t x5 = ((uint64_t)(arg1[9]) * ((arg2[5]) * UINT8_C(0x26)));
+ uint64_t x6 = ((uint64_t)(arg1[9]) * ((arg2[4]) * UINT8_C(0x13)));
+ uint64_t x7 = ((uint64_t)(arg1[9]) * ((arg2[3]) * UINT8_C(0x26)));
+ uint64_t x8 = ((uint64_t)(arg1[9]) * ((arg2[2]) * UINT8_C(0x13)));
+ uint64_t x9 = ((uint64_t)(arg1[9]) * ((arg2[1]) * UINT8_C(0x26)));
+ uint64_t x10 = ((uint64_t)(arg1[8]) * ((arg2[9]) * UINT8_C(0x13)));
+ uint64_t x11 = ((uint64_t)(arg1[8]) * ((arg2[8]) * UINT8_C(0x13)));
+ uint64_t x12 = ((uint64_t)(arg1[8]) * ((arg2[7]) * UINT8_C(0x13)));
+ uint64_t x13 = ((uint64_t)(arg1[8]) * ((arg2[6]) * UINT8_C(0x13)));
+ uint64_t x14 = ((uint64_t)(arg1[8]) * ((arg2[5]) * UINT8_C(0x13)));
+ uint64_t x15 = ((uint64_t)(arg1[8]) * ((arg2[4]) * UINT8_C(0x13)));
+ uint64_t x16 = ((uint64_t)(arg1[8]) * ((arg2[3]) * UINT8_C(0x13)));
+ uint64_t x17 = ((uint64_t)(arg1[8]) * ((arg2[2]) * UINT8_C(0x13)));
+ uint64_t x18 = ((uint64_t)(arg1[7]) * ((arg2[9]) * UINT8_C(0x26)));
+ uint64_t x19 = ((uint64_t)(arg1[7]) * ((arg2[8]) * UINT8_C(0x13)));
+ uint64_t x20 = ((uint64_t)(arg1[7]) * ((arg2[7]) * UINT8_C(0x26)));
+ uint64_t x21 = ((uint64_t)(arg1[7]) * ((arg2[6]) * UINT8_C(0x13)));
+ uint64_t x22 = ((uint64_t)(arg1[7]) * ((arg2[5]) * UINT8_C(0x26)));
+ uint64_t x23 = ((uint64_t)(arg1[7]) * ((arg2[4]) * UINT8_C(0x13)));
+ uint64_t x24 = ((uint64_t)(arg1[7]) * ((arg2[3]) * UINT8_C(0x26)));
+ uint64_t x25 = ((uint64_t)(arg1[6]) * ((arg2[9]) * UINT8_C(0x13)));
+ uint64_t x26 = ((uint64_t)(arg1[6]) * ((arg2[8]) * UINT8_C(0x13)));
+ uint64_t x27 = ((uint64_t)(arg1[6]) * ((arg2[7]) * UINT8_C(0x13)));
+ uint64_t x28 = ((uint64_t)(arg1[6]) * ((arg2[6]) * UINT8_C(0x13)));
+ uint64_t x29 = ((uint64_t)(arg1[6]) * ((arg2[5]) * UINT8_C(0x13)));
+ uint64_t x30 = ((uint64_t)(arg1[6]) * ((arg2[4]) * UINT8_C(0x13)));
+ uint64_t x31 = ((uint64_t)(arg1[5]) * ((arg2[9]) * UINT8_C(0x26)));
+ uint64_t x32 = ((uint64_t)(arg1[5]) * ((arg2[8]) * UINT8_C(0x13)));
+ uint64_t x33 = ((uint64_t)(arg1[5]) * ((arg2[7]) * UINT8_C(0x26)));
+ uint64_t x34 = ((uint64_t)(arg1[5]) * ((arg2[6]) * UINT8_C(0x13)));
+ uint64_t x35 = ((uint64_t)(arg1[5]) * ((arg2[5]) * UINT8_C(0x26)));
+ uint64_t x36 = ((uint64_t)(arg1[4]) * ((arg2[9]) * UINT8_C(0x13)));
+ uint64_t x37 = ((uint64_t)(arg1[4]) * ((arg2[8]) * UINT8_C(0x13)));
+ uint64_t x38 = ((uint64_t)(arg1[4]) * ((arg2[7]) * UINT8_C(0x13)));
+ uint64_t x39 = ((uint64_t)(arg1[4]) * ((arg2[6]) * UINT8_C(0x13)));
+ uint64_t x40 = ((uint64_t)(arg1[3]) * ((arg2[9]) * UINT8_C(0x26)));
+ uint64_t x41 = ((uint64_t)(arg1[3]) * ((arg2[8]) * UINT8_C(0x13)));
+ uint64_t x42 = ((uint64_t)(arg1[3]) * ((arg2[7]) * UINT8_C(0x26)));
+ uint64_t x43 = ((uint64_t)(arg1[2]) * ((arg2[9]) * UINT8_C(0x13)));
+ uint64_t x44 = ((uint64_t)(arg1[2]) * ((arg2[8]) * UINT8_C(0x13)));
+ uint64_t x45 = ((uint64_t)(arg1[1]) * ((arg2[9]) * UINT8_C(0x26)));
+ uint64_t x46 = ((uint64_t)(arg1[9]) * (arg2[0]));
+ uint64_t x47 = ((uint64_t)(arg1[8]) * (arg2[1]));
+ uint64_t x48 = ((uint64_t)(arg1[8]) * (arg2[0]));
+ uint64_t x49 = ((uint64_t)(arg1[7]) * (arg2[2]));
+ uint64_t x50 = ((uint64_t)(arg1[7]) * ((arg2[1]) * 0x2));
+ uint64_t x51 = ((uint64_t)(arg1[7]) * (arg2[0]));
+ uint64_t x52 = ((uint64_t)(arg1[6]) * (arg2[3]));
+ uint64_t x53 = ((uint64_t)(arg1[6]) * (arg2[2]));
+ uint64_t x54 = ((uint64_t)(arg1[6]) * (arg2[1]));
+ uint64_t x55 = ((uint64_t)(arg1[6]) * (arg2[0]));
+ uint64_t x56 = ((uint64_t)(arg1[5]) * (arg2[4]));
+ uint64_t x57 = ((uint64_t)(arg1[5]) * ((arg2[3]) * 0x2));
+ uint64_t x58 = ((uint64_t)(arg1[5]) * (arg2[2]));
+ uint64_t x59 = ((uint64_t)(arg1[5]) * ((arg2[1]) * 0x2));
+ uint64_t x60 = ((uint64_t)(arg1[5]) * (arg2[0]));
+ uint64_t x61 = ((uint64_t)(arg1[4]) * (arg2[5]));
+ uint64_t x62 = ((uint64_t)(arg1[4]) * (arg2[4]));
+ uint64_t x63 = ((uint64_t)(arg1[4]) * (arg2[3]));
+ uint64_t x64 = ((uint64_t)(arg1[4]) * (arg2[2]));
+ uint64_t x65 = ((uint64_t)(arg1[4]) * (arg2[1]));
+ uint64_t x66 = ((uint64_t)(arg1[4]) * (arg2[0]));
+ uint64_t x67 = ((uint64_t)(arg1[3]) * (arg2[6]));
+ uint64_t x68 = ((uint64_t)(arg1[3]) * ((arg2[5]) * 0x2));
+ uint64_t x69 = ((uint64_t)(arg1[3]) * (arg2[4]));
+ uint64_t x70 = ((uint64_t)(arg1[3]) * ((arg2[3]) * 0x2));
+ uint64_t x71 = ((uint64_t)(arg1[3]) * (arg2[2]));
+ uint64_t x72 = ((uint64_t)(arg1[3]) * ((arg2[1]) * 0x2));
+ uint64_t x73 = ((uint64_t)(arg1[3]) * (arg2[0]));
+ uint64_t x74 = ((uint64_t)(arg1[2]) * (arg2[7]));
+ uint64_t x75 = ((uint64_t)(arg1[2]) * (arg2[6]));
+ uint64_t x76 = ((uint64_t)(arg1[2]) * (arg2[5]));
+ uint64_t x77 = ((uint64_t)(arg1[2]) * (arg2[4]));
+ uint64_t x78 = ((uint64_t)(arg1[2]) * (arg2[3]));
+ uint64_t x79 = ((uint64_t)(arg1[2]) * (arg2[2]));
+ uint64_t x80 = ((uint64_t)(arg1[2]) * (arg2[1]));
+ uint64_t x81 = ((uint64_t)(arg1[2]) * (arg2[0]));
+ uint64_t x82 = ((uint64_t)(arg1[1]) * (arg2[8]));
+ uint64_t x83 = ((uint64_t)(arg1[1]) * ((arg2[7]) * 0x2));
+ uint64_t x84 = ((uint64_t)(arg1[1]) * (arg2[6]));
+ uint64_t x85 = ((uint64_t)(arg1[1]) * ((arg2[5]) * 0x2));
+ uint64_t x86 = ((uint64_t)(arg1[1]) * (arg2[4]));
+ uint64_t x87 = ((uint64_t)(arg1[1]) * ((arg2[3]) * 0x2));
+ uint64_t x88 = ((uint64_t)(arg1[1]) * (arg2[2]));
+ uint64_t x89 = ((uint64_t)(arg1[1]) * ((arg2[1]) * 0x2));
+ uint64_t x90 = ((uint64_t)(arg1[1]) * (arg2[0]));
+ uint64_t x91 = ((uint64_t)(arg1[0]) * (arg2[9]));
+ uint64_t x92 = ((uint64_t)(arg1[0]) * (arg2[8]));
+ uint64_t x93 = ((uint64_t)(arg1[0]) * (arg2[7]));
+ uint64_t x94 = ((uint64_t)(arg1[0]) * (arg2[6]));
+ uint64_t x95 = ((uint64_t)(arg1[0]) * (arg2[5]));
+ uint64_t x96 = ((uint64_t)(arg1[0]) * (arg2[4]));
+ uint64_t x97 = ((uint64_t)(arg1[0]) * (arg2[3]));
+ uint64_t x98 = ((uint64_t)(arg1[0]) * (arg2[2]));
+ uint64_t x99 = ((uint64_t)(arg1[0]) * (arg2[1]));
+ uint64_t x100 = ((uint64_t)(arg1[0]) * (arg2[0]));
+ uint64_t x101 =
+ (x100 +
+ (x45 +
+ (x44 + (x42 + (x39 + (x35 + (x30 + (x24 + (x17 + x9)))))))));
+ uint64_t x102 = (x101 >> 26);
+ uint32_t x103 = (uint32_t)(x101 & UINT32_C(0x3ffffff));
+ uint64_t x104 =
+ (x91 +
+ (x82 +
+ (x74 + (x67 + (x61 + (x56 + (x52 + (x49 + (x47 + x46)))))))));
+ uint64_t x105 =
+ (x92 +
+ (x83 +
+ (x75 + (x68 + (x62 + (x57 + (x53 + (x50 + (x48 + x1)))))))));
+ uint64_t x106 =
+ (x93 +
+ (x84 +
+ (x76 + (x69 + (x63 + (x58 + (x54 + (x51 + (x10 + x2)))))))));
+ uint64_t x107 =
+ (x94 +
+ (x85 +
+ (x77 + (x70 + (x64 + (x59 + (x55 + (x18 + (x11 + x3)))))))));
+ uint64_t x108 =
+ (x95 +
+ (x86 +
+ (x78 + (x71 + (x65 + (x60 + (x25 + (x19 + (x12 + x4)))))))));
+ uint64_t x109 =
+ (x96 +
+ (x87 +
+ (x79 + (x72 + (x66 + (x31 + (x26 + (x20 + (x13 + x5)))))))));
+ uint64_t x110 =
+ (x97 +
+ (x88 +
+ (x80 + (x73 + (x36 + (x32 + (x27 + (x21 + (x14 + x6)))))))));
+ uint64_t x111 =
+ (x98 +
+ (x89 +
+ (x81 + (x40 + (x37 + (x33 + (x28 + (x22 + (x15 + x7)))))))));
+ uint64_t x112 =
+ (x99 +
+ (x90 +
+ (x43 + (x41 + (x38 + (x34 + (x29 + (x23 + (x16 + x8)))))))));
+ uint64_t x113 = (x102 + x112);
+ uint64_t x114 = (x113 >> 25);
+ uint32_t x115 = (uint32_t)(x113 & UINT32_C(0x1ffffff));
+ uint64_t x116 = (x114 + x111);
+ uint64_t x117 = (x116 >> 26);
+ uint32_t x118 = (uint32_t)(x116 & UINT32_C(0x3ffffff));
+ uint64_t x119 = (x117 + x110);
+ uint64_t x120 = (x119 >> 25);
+ uint32_t x121 = (uint32_t)(x119 & UINT32_C(0x1ffffff));
+ uint64_t x122 = (x120 + x109);
+ uint64_t x123 = (x122 >> 26);
+ uint32_t x124 = (uint32_t)(x122 & UINT32_C(0x3ffffff));
+ uint64_t x125 = (x123 + x108);
+ uint64_t x126 = (x125 >> 25);
+ uint32_t x127 = (uint32_t)(x125 & UINT32_C(0x1ffffff));
+ uint64_t x128 = (x126 + x107);
+ uint64_t x129 = (x128 >> 26);
+ uint32_t x130 = (uint32_t)(x128 & UINT32_C(0x3ffffff));
+ uint64_t x131 = (x129 + x106);
+ uint64_t x132 = (x131 >> 25);
+ uint32_t x133 = (uint32_t)(x131 & UINT32_C(0x1ffffff));
+ uint64_t x134 = (x132 + x105);
+ uint64_t x135 = (x134 >> 26);
+ uint32_t x136 = (uint32_t)(x134 & UINT32_C(0x3ffffff));
+ uint64_t x137 = (x135 + x104);
+ uint64_t x138 = (x137 >> 25);
+ uint32_t x139 = (uint32_t)(x137 & UINT32_C(0x1ffffff));
+ uint64_t x140 = (x138 * UINT8_C(0x13));
+ uint64_t x141 = (x103 + x140);
+ uint32_t x142 = (uint32_t)(x141 >> 26);
+ uint32_t x143 = (uint32_t)(x141 & UINT32_C(0x3ffffff));
+ uint32_t x144 = (x142 + x115);
+ uint8_t x145 = (uint8_t)(x144 >> 25);
+ uint32_t x146 = (x144 & UINT32_C(0x1ffffff));
+ uint32_t x147 = (x145 + x118);
+ out1[0] = x143;
+ out1[1] = x146;
+ out1[2] = x147;
+ out1[3] = x121;
+ out1[4] = x124;
+ out1[5] = x127;
+ out1[6] = x130;
+ out1[7] = x133;
+ out1[8] = x136;
+ out1[9] = x139;
+}
+
+/*
+ * The function fiat_25519_carry_square squares a field element and reduces the result.
+ * Postconditions:
+ * eval out1 mod m = (eval arg1 * eval arg1) mod m
+ *
+ * Input Bounds:
+ * arg1: [[0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999]]
+ * Output Bounds:
+ * out1: [[0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333]]
+ */
+static void fiat_25519_carry_square(uint32_t out1[10], const uint32_t arg1[10])
+{
+ uint32_t x1 = ((arg1[9]) * UINT8_C(0x13));
+ uint32_t x2 = (x1 * 0x2);
+ uint32_t x3 = ((arg1[9]) * 0x2);
+ uint32_t x4 = ((arg1[8]) * UINT8_C(0x13));
+ uint64_t x5 = ((uint64_t)x4 * 0x2);
+ uint32_t x6 = ((arg1[8]) * 0x2);
+ uint32_t x7 = ((arg1[7]) * UINT8_C(0x13));
+ uint32_t x8 = (x7 * 0x2);
+ uint32_t x9 = ((arg1[7]) * 0x2);
+ uint32_t x10 = ((arg1[6]) * UINT8_C(0x13));
+ uint64_t x11 = ((uint64_t)x10 * 0x2);
+ uint32_t x12 = ((arg1[6]) * 0x2);
+ uint32_t x13 = ((arg1[5]) * UINT8_C(0x13));
+ uint32_t x14 = ((arg1[5]) * 0x2);
+ uint32_t x15 = ((arg1[4]) * 0x2);
+ uint32_t x16 = ((arg1[3]) * 0x2);
+ uint32_t x17 = ((arg1[2]) * 0x2);
+ uint32_t x18 = ((arg1[1]) * 0x2);
+ uint64_t x19 = ((uint64_t)(arg1[9]) * (x1 * 0x2));
+ uint64_t x20 = ((uint64_t)(arg1[8]) * x2);
+ uint64_t x21 = ((uint64_t)(arg1[8]) * x4);
+ uint64_t x22 = ((arg1[7]) * ((uint64_t)x2 * 0x2));
+ uint64_t x23 = ((arg1[7]) * x5);
+ uint64_t x24 = ((uint64_t)(arg1[7]) * (x7 * 0x2));
+ uint64_t x25 = ((uint64_t)(arg1[6]) * x2);
+ uint64_t x26 = ((arg1[6]) * x5);
+ uint64_t x27 = ((uint64_t)(arg1[6]) * x8);
+ uint64_t x28 = ((uint64_t)(arg1[6]) * x10);
+ uint64_t x29 = ((arg1[5]) * ((uint64_t)x2 * 0x2));
+ uint64_t x30 = ((arg1[5]) * x5);
+ uint64_t x31 = ((arg1[5]) * ((uint64_t)x8 * 0x2));
+ uint64_t x32 = ((arg1[5]) * x11);
+ uint64_t x33 = ((uint64_t)(arg1[5]) * (x13 * 0x2));
+ uint64_t x34 = ((uint64_t)(arg1[4]) * x2);
+ uint64_t x35 = ((arg1[4]) * x5);
+ uint64_t x36 = ((uint64_t)(arg1[4]) * x8);
+ uint64_t x37 = ((arg1[4]) * x11);
+ uint64_t x38 = ((uint64_t)(arg1[4]) * x14);
+ uint64_t x39 = ((uint64_t)(arg1[4]) * (arg1[4]));
+ uint64_t x40 = ((arg1[3]) * ((uint64_t)x2 * 0x2));
+ uint64_t x41 = ((arg1[3]) * x5);
+ uint64_t x42 = ((arg1[3]) * ((uint64_t)x8 * 0x2));
+ uint64_t x43 = ((uint64_t)(arg1[3]) * x12);
+ uint64_t x44 = ((uint64_t)(arg1[3]) * (x14 * 0x2));
+ uint64_t x45 = ((uint64_t)(arg1[3]) * x15);
+ uint64_t x46 = ((uint64_t)(arg1[3]) * ((arg1[3]) * 0x2));
+ uint64_t x47 = ((uint64_t)(arg1[2]) * x2);
+ uint64_t x48 = ((arg1[2]) * x5);
+ uint64_t x49 = ((uint64_t)(arg1[2]) * x9);
+ uint64_t x50 = ((uint64_t)(arg1[2]) * x12);
+ uint64_t x51 = ((uint64_t)(arg1[2]) * x14);
+ uint64_t x52 = ((uint64_t)(arg1[2]) * x15);
+ uint64_t x53 = ((uint64_t)(arg1[2]) * x16);
+ uint64_t x54 = ((uint64_t)(arg1[2]) * (arg1[2]));
+ uint64_t x55 = ((arg1[1]) * ((uint64_t)x2 * 0x2));
+ uint64_t x56 = ((uint64_t)(arg1[1]) * x6);
+ uint64_t x57 = ((uint64_t)(arg1[1]) * (x9 * 0x2));
+ uint64_t x58 = ((uint64_t)(arg1[1]) * x12);
+ uint64_t x59 = ((uint64_t)(arg1[1]) * (x14 * 0x2));
+ uint64_t x60 = ((uint64_t)(arg1[1]) * x15);
+ uint64_t x61 = ((uint64_t)(arg1[1]) * (x16 * 0x2));
+ uint64_t x62 = ((uint64_t)(arg1[1]) * x17);
+ uint64_t x63 = ((uint64_t)(arg1[1]) * ((arg1[1]) * 0x2));
+ uint64_t x64 = ((uint64_t)(arg1[0]) * x3);
+ uint64_t x65 = ((uint64_t)(arg1[0]) * x6);
+ uint64_t x66 = ((uint64_t)(arg1[0]) * x9);
+ uint64_t x67 = ((uint64_t)(arg1[0]) * x12);
+ uint64_t x68 = ((uint64_t)(arg1[0]) * x14);
+ uint64_t x69 = ((uint64_t)(arg1[0]) * x15);
+ uint64_t x70 = ((uint64_t)(arg1[0]) * x16);
+ uint64_t x71 = ((uint64_t)(arg1[0]) * x17);
+ uint64_t x72 = ((uint64_t)(arg1[0]) * x18);
+ uint64_t x73 = ((uint64_t)(arg1[0]) * (arg1[0]));
+ uint64_t x74 = (x73 + (x55 + (x48 + (x42 + (x37 + x33)))));
+ uint64_t x75 = (x74 >> 26);
+ uint32_t x76 = (uint32_t)(x74 & UINT32_C(0x3ffffff));
+ uint64_t x77 = (x64 + (x56 + (x49 + (x43 + x38))));
+ uint64_t x78 = (x65 + (x57 + (x50 + (x44 + (x39 + x19)))));
+ uint64_t x79 = (x66 + (x58 + (x51 + (x45 + x20))));
+ uint64_t x80 = (x67 + (x59 + (x52 + (x46 + (x22 + x21)))));
+ uint64_t x81 = (x68 + (x60 + (x53 + (x25 + x23))));
+ uint64_t x82 = (x69 + (x61 + (x54 + (x29 + (x26 + x24)))));
+ uint64_t x83 = (x70 + (x62 + (x34 + (x30 + x27))));
+ uint64_t x84 = (x71 + (x63 + (x40 + (x35 + (x31 + x28)))));
+ uint64_t x85 = (x72 + (x47 + (x41 + (x36 + x32))));
+ uint64_t x86 = (x75 + x85);
+ uint64_t x87 = (x86 >> 25);
+ uint32_t x88 = (uint32_t)(x86 & UINT32_C(0x1ffffff));
+ uint64_t x89 = (x87 + x84);
+ uint64_t x90 = (x89 >> 26);
+ uint32_t x91 = (uint32_t)(x89 & UINT32_C(0x3ffffff));
+ uint64_t x92 = (x90 + x83);
+ uint64_t x93 = (x92 >> 25);
+ uint32_t x94 = (uint32_t)(x92 & UINT32_C(0x1ffffff));
+ uint64_t x95 = (x93 + x82);
+ uint64_t x96 = (x95 >> 26);
+ uint32_t x97 = (uint32_t)(x95 & UINT32_C(0x3ffffff));
+ uint64_t x98 = (x96 + x81);
+ uint64_t x99 = (x98 >> 25);
+ uint32_t x100 = (uint32_t)(x98 & UINT32_C(0x1ffffff));
+ uint64_t x101 = (x99 + x80);
+ uint64_t x102 = (x101 >> 26);
+ uint32_t x103 = (uint32_t)(x101 & UINT32_C(0x3ffffff));
+ uint64_t x104 = (x102 + x79);
+ uint64_t x105 = (x104 >> 25);
+ uint32_t x106 = (uint32_t)(x104 & UINT32_C(0x1ffffff));
+ uint64_t x107 = (x105 + x78);
+ uint64_t x108 = (x107 >> 26);
+ uint32_t x109 = (uint32_t)(x107 & UINT32_C(0x3ffffff));
+ uint64_t x110 = (x108 + x77);
+ uint64_t x111 = (x110 >> 25);
+ uint32_t x112 = (uint32_t)(x110 & UINT32_C(0x1ffffff));
+ uint64_t x113 = (x111 * UINT8_C(0x13));
+ uint64_t x114 = (x76 + x113);
+ uint32_t x115 = (uint32_t)(x114 >> 26);
+ uint32_t x116 = (uint32_t)(x114 & UINT32_C(0x3ffffff));
+ uint32_t x117 = (x115 + x88);
+ uint8_t x118 = (uint8_t)(x117 >> 25);
+ uint32_t x119 = (x117 & UINT32_C(0x1ffffff));
+ uint32_t x120 = (x118 + x91);
+ out1[0] = x116;
+ out1[1] = x119;
+ out1[2] = x120;
+ out1[3] = x94;
+ out1[4] = x97;
+ out1[5] = x100;
+ out1[6] = x103;
+ out1[7] = x106;
+ out1[8] = x109;
+ out1[9] = x112;
+}
+
+/*
+ * The function fiat_25519_carry reduces a field element.
+ * Postconditions:
+ * eval out1 mod m = eval arg1 mod m
+ *
+ * Input Bounds:
+ * arg1: [[0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999]]
+ * Output Bounds:
+ * out1: [[0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333]]
+ */
+static void fiat_25519_carry(uint32_t out1[10], const uint32_t arg1[10])
+{
+ uint32_t x1 = (arg1[0]);
+ uint32_t x2 = ((x1 >> 26) + (arg1[1]));
+ uint32_t x3 = ((x2 >> 25) + (arg1[2]));
+ uint32_t x4 = ((x3 >> 26) + (arg1[3]));
+ uint32_t x5 = ((x4 >> 25) + (arg1[4]));
+ uint32_t x6 = ((x5 >> 26) + (arg1[5]));
+ uint32_t x7 = ((x6 >> 25) + (arg1[6]));
+ uint32_t x8 = ((x7 >> 26) + (arg1[7]));
+ uint32_t x9 = ((x8 >> 25) + (arg1[8]));
+ uint32_t x10 = ((x9 >> 26) + (arg1[9]));
+ uint32_t x11 =
+ ((x1 & UINT32_C(0x3ffffff)) + ((x10 >> 25) * UINT8_C(0x13)));
+ uint32_t x12 = ((uint8_t)(x11 >> 26) + (x2 & UINT32_C(0x1ffffff)));
+ uint32_t x13 = (x11 & UINT32_C(0x3ffffff));
+ uint32_t x14 = (x12 & UINT32_C(0x1ffffff));
+ uint32_t x15 = ((uint8_t)(x12 >> 25) + (x3 & UINT32_C(0x3ffffff)));
+ uint32_t x16 = (x4 & UINT32_C(0x1ffffff));
+ uint32_t x17 = (x5 & UINT32_C(0x3ffffff));
+ uint32_t x18 = (x6 & UINT32_C(0x1ffffff));
+ uint32_t x19 = (x7 & UINT32_C(0x3ffffff));
+ uint32_t x20 = (x8 & UINT32_C(0x1ffffff));
+ uint32_t x21 = (x9 & UINT32_C(0x3ffffff));
+ uint32_t x22 = (x10 & UINT32_C(0x1ffffff));
+ out1[0] = x13;
+ out1[1] = x14;
+ out1[2] = x15;
+ out1[3] = x16;
+ out1[4] = x17;
+ out1[5] = x18;
+ out1[6] = x19;
+ out1[7] = x20;
+ out1[8] = x21;
+ out1[9] = x22;
+}
+
+/*
+ * The function fiat_25519_add adds two field elements.
+ * Postconditions:
+ * eval out1 mod m = (eval arg1 + eval arg2) mod m
+ *
+ * Input Bounds:
+ * arg1: [[0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333]]
+ * arg2: [[0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333]]
+ * Output Bounds:
+ * out1: [[0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999]]
+ */
+static void fiat_25519_add(uint32_t out1[10], const uint32_t arg1[10],
+ const uint32_t arg2[10])
+{
+ uint32_t x1 = ((arg1[0]) + (arg2[0]));
+ uint32_t x2 = ((arg1[1]) + (arg2[1]));
+ uint32_t x3 = ((arg1[2]) + (arg2[2]));
+ uint32_t x4 = ((arg1[3]) + (arg2[3]));
+ uint32_t x5 = ((arg1[4]) + (arg2[4]));
+ uint32_t x6 = ((arg1[5]) + (arg2[5]));
+ uint32_t x7 = ((arg1[6]) + (arg2[6]));
+ uint32_t x8 = ((arg1[7]) + (arg2[7]));
+ uint32_t x9 = ((arg1[8]) + (arg2[8]));
+ uint32_t x10 = ((arg1[9]) + (arg2[9]));
+ out1[0] = x1;
+ out1[1] = x2;
+ out1[2] = x3;
+ out1[3] = x4;
+ out1[4] = x5;
+ out1[5] = x6;
+ out1[6] = x7;
+ out1[7] = x8;
+ out1[8] = x9;
+ out1[9] = x10;
+}
+
+/*
+ * The function fiat_25519_sub subtracts two field elements.
+ * Postconditions:
+ * eval out1 mod m = (eval arg1 - eval arg2) mod m
+ *
+ * Input Bounds:
+ * arg1: [[0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333]]
+ * arg2: [[0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333]]
+ * Output Bounds:
+ * out1: [[0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999]]
+ */
+static void fiat_25519_sub(uint32_t out1[10], const uint32_t arg1[10],
+ const uint32_t arg2[10])
+{
+ uint32_t x1 = ((UINT32_C(0x7ffffda) + (arg1[0])) - (arg2[0]));
+ uint32_t x2 = ((UINT32_C(0x3fffffe) + (arg1[1])) - (arg2[1]));
+ uint32_t x3 = ((UINT32_C(0x7fffffe) + (arg1[2])) - (arg2[2]));
+ uint32_t x4 = ((UINT32_C(0x3fffffe) + (arg1[3])) - (arg2[3]));
+ uint32_t x5 = ((UINT32_C(0x7fffffe) + (arg1[4])) - (arg2[4]));
+ uint32_t x6 = ((UINT32_C(0x3fffffe) + (arg1[5])) - (arg2[5]));
+ uint32_t x7 = ((UINT32_C(0x7fffffe) + (arg1[6])) - (arg2[6]));
+ uint32_t x8 = ((UINT32_C(0x3fffffe) + (arg1[7])) - (arg2[7]));
+ uint32_t x9 = ((UINT32_C(0x7fffffe) + (arg1[8])) - (arg2[8]));
+ uint32_t x10 = ((UINT32_C(0x3fffffe) + (arg1[9])) - (arg2[9]));
+ out1[0] = x1;
+ out1[1] = x2;
+ out1[2] = x3;
+ out1[3] = x4;
+ out1[4] = x5;
+ out1[5] = x6;
+ out1[6] = x7;
+ out1[7] = x8;
+ out1[8] = x9;
+ out1[9] = x10;
+}
+
+/*
+ * The function fiat_25519_opp negates a field element.
+ * Postconditions:
+ * eval out1 mod m = -eval arg1 mod m
+ *
+ * Input Bounds:
+ * arg1: [[0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333]]
+ * Output Bounds:
+ * out1: [[0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999]]
+ */
+static void fiat_25519_opp(uint32_t out1[10], const uint32_t arg1[10])
+{
+ uint32_t x1 = (UINT32_C(0x7ffffda) - (arg1[0]));
+ uint32_t x2 = (UINT32_C(0x3fffffe) - (arg1[1]));
+ uint32_t x3 = (UINT32_C(0x7fffffe) - (arg1[2]));
+ uint32_t x4 = (UINT32_C(0x3fffffe) - (arg1[3]));
+ uint32_t x5 = (UINT32_C(0x7fffffe) - (arg1[4]));
+ uint32_t x6 = (UINT32_C(0x3fffffe) - (arg1[5]));
+ uint32_t x7 = (UINT32_C(0x7fffffe) - (arg1[6]));
+ uint32_t x8 = (UINT32_C(0x3fffffe) - (arg1[7]));
+ uint32_t x9 = (UINT32_C(0x7fffffe) - (arg1[8]));
+ uint32_t x10 = (UINT32_C(0x3fffffe) - (arg1[9]));
+ out1[0] = x1;
+ out1[1] = x2;
+ out1[2] = x3;
+ out1[3] = x4;
+ out1[4] = x5;
+ out1[5] = x6;
+ out1[6] = x7;
+ out1[7] = x8;
+ out1[8] = x9;
+ out1[9] = x10;
+}
+
+/*
+ * The function fiat_25519_to_bytes serializes a field element to bytes in little-endian order.
+ * Postconditions:
+ * out1 = map (λ x, ⌊((eval arg1 mod m) mod 2^(8 * (x + 1))) / 2^(8 * x)⌋) [0..31]
+ *
+ * Input Bounds:
+ * arg1: [[0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333]]
+ * Output Bounds:
+ * out1: [[0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0x7f]]
+ */
+static void fiat_25519_to_bytes(uint8_t out1[32], const uint32_t arg1[10])
+{
+ uint32_t x1;
+ uint8_t x2;
+ fiat_25519_subborrowx_u26(&x1, &x2, 0x0, (arg1[0]),
+ UINT32_C(0x3ffffed));
+ uint32_t x3;
+ uint8_t x4;
+ fiat_25519_subborrowx_u25(&x3, &x4, x2, (arg1[1]), UINT32_C(0x1ffffff));
+ uint32_t x5;
+ uint8_t x6;
+ fiat_25519_subborrowx_u26(&x5, &x6, x4, (arg1[2]), UINT32_C(0x3ffffff));
+ uint32_t x7;
+ uint8_t x8;
+ fiat_25519_subborrowx_u25(&x7, &x8, x6, (arg1[3]), UINT32_C(0x1ffffff));
+ uint32_t x9;
+ uint8_t x10;
+ fiat_25519_subborrowx_u26(&x9, &x10, x8, (arg1[4]),
+ UINT32_C(0x3ffffff));
+ uint32_t x11;
+ uint8_t x12;
+ fiat_25519_subborrowx_u25(&x11, &x12, x10, (arg1[5]),
+ UINT32_C(0x1ffffff));
+ uint32_t x13;
+ uint8_t x14;
+ fiat_25519_subborrowx_u26(&x13, &x14, x12, (arg1[6]),
+ UINT32_C(0x3ffffff));
+ uint32_t x15;
+ uint8_t x16;
+ fiat_25519_subborrowx_u25(&x15, &x16, x14, (arg1[7]),
+ UINT32_C(0x1ffffff));
+ uint32_t x17;
+ uint8_t x18;
+ fiat_25519_subborrowx_u26(&x17, &x18, x16, (arg1[8]),
+ UINT32_C(0x3ffffff));
+ uint32_t x19;
+ uint8_t x20;
+ fiat_25519_subborrowx_u25(&x19, &x20, x18, (arg1[9]),
+ UINT32_C(0x1ffffff));
+ uint32_t x21;
+ fiat_25519_cmovznz_u32(&x21, x20, 0x0, UINT32_C(0xffffffff));
+ uint32_t x22;
+ uint8_t x23;
+ fiat_25519_addcarryx_u26(&x22, &x23, 0x0, x1,
+ (x21 & UINT32_C(0x3ffffed)));
+ uint32_t x24;
+ uint8_t x25;
+ fiat_25519_addcarryx_u25(&x24, &x25, x23, x3,
+ (x21 & UINT32_C(0x1ffffff)));
+ uint32_t x26;
+ uint8_t x27;
+ fiat_25519_addcarryx_u26(&x26, &x27, x25, x5,
+ (x21 & UINT32_C(0x3ffffff)));
+ uint32_t x28;
+ uint8_t x29;
+ fiat_25519_addcarryx_u25(&x28, &x29, x27, x7,
+ (x21 & UINT32_C(0x1ffffff)));
+ uint32_t x30;
+ uint8_t x31;
+ fiat_25519_addcarryx_u26(&x30, &x31, x29, x9,
+ (x21 & UINT32_C(0x3ffffff)));
+ uint32_t x32;
+ uint8_t x33;
+ fiat_25519_addcarryx_u25(&x32, &x33, x31, x11,
+ (x21 & UINT32_C(0x1ffffff)));
+ uint32_t x34;
+ uint8_t x35;
+ fiat_25519_addcarryx_u26(&x34, &x35, x33, x13,
+ (x21 & UINT32_C(0x3ffffff)));
+ uint32_t x36;
+ uint8_t x37;
+ fiat_25519_addcarryx_u25(&x36, &x37, x35, x15,
+ (x21 & UINT32_C(0x1ffffff)));
+ uint32_t x38;
+ uint8_t x39;
+ fiat_25519_addcarryx_u26(&x38, &x39, x37, x17,
+ (x21 & UINT32_C(0x3ffffff)));
+ uint32_t x40;
+ uint8_t x41;
+ fiat_25519_addcarryx_u25(&x40, &x41, x39, x19,
+ (x21 & UINT32_C(0x1ffffff)));
+ uint32_t x42 = (x40 << 6);
+ uint32_t x43 = (x38 << 4);
+ uint32_t x44 = (x36 << 3);
+ uint32_t x45 = (x34 * (uint32_t)0x2);
+ uint32_t x46 = (x30 << 6);
+ uint32_t x47 = (x28 << 5);
+ uint32_t x48 = (x26 << 3);
+ uint32_t x49 = (x24 << 2);
+ uint32_t x50 = (x22 >> 8);
+ uint8_t x51 = (uint8_t)(x22 & UINT8_C(0xff));
+ uint32_t x52 = (x50 >> 8);
+ uint8_t x53 = (uint8_t)(x50 & UINT8_C(0xff));
+ uint8_t x54 = (uint8_t)(x52 >> 8);
+ uint8_t x55 = (uint8_t)(x52 & UINT8_C(0xff));
+ uint32_t x56 = (x54 + x49);
+ uint32_t x57 = (x56 >> 8);
+ uint8_t x58 = (uint8_t)(x56 & UINT8_C(0xff));
+ uint32_t x59 = (x57 >> 8);
+ uint8_t x60 = (uint8_t)(x57 & UINT8_C(0xff));
+ uint8_t x61 = (uint8_t)(x59 >> 8);
+ uint8_t x62 = (uint8_t)(x59 & UINT8_C(0xff));
+ uint32_t x63 = (x61 + x48);
+ uint32_t x64 = (x63 >> 8);
+ uint8_t x65 = (uint8_t)(x63 & UINT8_C(0xff));
+ uint32_t x66 = (x64 >> 8);
+ uint8_t x67 = (uint8_t)(x64 & UINT8_C(0xff));
+ uint8_t x68 = (uint8_t)(x66 >> 8);
+ uint8_t x69 = (uint8_t)(x66 & UINT8_C(0xff));
+ uint32_t x70 = (x68 + x47);
+ uint32_t x71 = (x70 >> 8);
+ uint8_t x72 = (uint8_t)(x70 & UINT8_C(0xff));
+ uint32_t x73 = (x71 >> 8);
+ uint8_t x74 = (uint8_t)(x71 & UINT8_C(0xff));
+ uint8_t x75 = (uint8_t)(x73 >> 8);
+ uint8_t x76 = (uint8_t)(x73 & UINT8_C(0xff));
+ uint32_t x77 = (x75 + x46);
+ uint32_t x78 = (x77 >> 8);
+ uint8_t x79 = (uint8_t)(x77 & UINT8_C(0xff));
+ uint32_t x80 = (x78 >> 8);
+ uint8_t x81 = (uint8_t)(x78 & UINT8_C(0xff));
+ uint8_t x82 = (uint8_t)(x80 >> 8);
+ uint8_t x83 = (uint8_t)(x80 & UINT8_C(0xff));
+ uint8_t x84 = (uint8_t)(x82 & UINT8_C(0xff));
+ uint32_t x85 = (x32 >> 8);
+ uint8_t x86 = (uint8_t)(x32 & UINT8_C(0xff));
+ uint32_t x87 = (x85 >> 8);
+ uint8_t x88 = (uint8_t)(x85 & UINT8_C(0xff));
+ uint8_t x89 = (uint8_t)(x87 >> 8);
+ uint8_t x90 = (uint8_t)(x87 & UINT8_C(0xff));
+ uint32_t x91 = (x89 + x45);
+ uint32_t x92 = (x91 >> 8);
+ uint8_t x93 = (uint8_t)(x91 & UINT8_C(0xff));
+ uint32_t x94 = (x92 >> 8);
+ uint8_t x95 = (uint8_t)(x92 & UINT8_C(0xff));
+ uint8_t x96 = (uint8_t)(x94 >> 8);
+ uint8_t x97 = (uint8_t)(x94 & UINT8_C(0xff));
+ uint32_t x98 = (x96 + x44);
+ uint32_t x99 = (x98 >> 8);
+ uint8_t x100 = (uint8_t)(x98 & UINT8_C(0xff));
+ uint32_t x101 = (x99 >> 8);
+ uint8_t x102 = (uint8_t)(x99 & UINT8_C(0xff));
+ uint8_t x103 = (uint8_t)(x101 >> 8);
+ uint8_t x104 = (uint8_t)(x101 & UINT8_C(0xff));
+ uint32_t x105 = (x103 + x43);
+ uint32_t x106 = (x105 >> 8);
+ uint8_t x107 = (uint8_t)(x105 & UINT8_C(0xff));
+ uint32_t x108 = (x106 >> 8);
+ uint8_t x109 = (uint8_t)(x106 & UINT8_C(0xff));
+ uint8_t x110 = (uint8_t)(x108 >> 8);
+ uint8_t x111 = (uint8_t)(x108 & UINT8_C(0xff));
+ uint32_t x112 = (x110 + x42);
+ uint32_t x113 = (x112 >> 8);
+ uint8_t x114 = (uint8_t)(x112 & UINT8_C(0xff));
+ uint32_t x115 = (x113 >> 8);
+ uint8_t x116 = (uint8_t)(x113 & UINT8_C(0xff));
+ uint8_t x117 = (uint8_t)(x115 >> 8);
+ uint8_t x118 = (uint8_t)(x115 & UINT8_C(0xff));
+ out1[0] = x51;
+ out1[1] = x53;
+ out1[2] = x55;
+ out1[3] = x58;
+ out1[4] = x60;
+ out1[5] = x62;
+ out1[6] = x65;
+ out1[7] = x67;
+ out1[8] = x69;
+ out1[9] = x72;
+ out1[10] = x74;
+ out1[11] = x76;
+ out1[12] = x79;
+ out1[13] = x81;
+ out1[14] = x83;
+ out1[15] = x84;
+ out1[16] = x86;
+ out1[17] = x88;
+ out1[18] = x90;
+ out1[19] = x93;
+ out1[20] = x95;
+ out1[21] = x97;
+ out1[22] = x100;
+ out1[23] = x102;
+ out1[24] = x104;
+ out1[25] = x107;
+ out1[26] = x109;
+ out1[27] = x111;
+ out1[28] = x114;
+ out1[29] = x116;
+ out1[30] = x118;
+ out1[31] = x117;
+}
+
+/*
+ * The function fiat_25519_from_bytes deserializes a field element from bytes in little-endian order.
+ * Postconditions:
+ * eval out1 mod m = bytes_eval arg1 mod m
+ *
+ * Input Bounds:
+ * arg1: [[0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0x7f]]
+ * Output Bounds:
+ * out1: [[0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333]]
+ */
+static void fiat_25519_from_bytes(uint32_t out1[10], const uint8_t arg1[32])
+{
+ uint32_t x1 = ((uint32_t)(arg1[31]) << 18);
+ uint32_t x2 = ((uint32_t)(arg1[30]) << 10);
+ uint32_t x3 = ((uint32_t)(arg1[29]) << 2);
+ uint32_t x4 = ((uint32_t)(arg1[28]) << 20);
+ uint32_t x5 = ((uint32_t)(arg1[27]) << 12);
+ uint32_t x6 = ((uint32_t)(arg1[26]) << 4);
+ uint32_t x7 = ((uint32_t)(arg1[25]) << 21);
+ uint32_t x8 = ((uint32_t)(arg1[24]) << 13);
+ uint32_t x9 = ((uint32_t)(arg1[23]) << 5);
+ uint32_t x10 = ((uint32_t)(arg1[22]) << 23);
+ uint32_t x11 = ((uint32_t)(arg1[21]) << 15);
+ uint32_t x12 = ((uint32_t)(arg1[20]) << 7);
+ uint32_t x13 = ((uint32_t)(arg1[19]) << 24);
+ uint32_t x14 = ((uint32_t)(arg1[18]) << 16);
+ uint32_t x15 = ((uint32_t)(arg1[17]) << 8);
+ uint8_t x16 = (arg1[16]);
+ uint32_t x17 = ((uint32_t)(arg1[15]) << 18);
+ uint32_t x18 = ((uint32_t)(arg1[14]) << 10);
+ uint32_t x19 = ((uint32_t)(arg1[13]) << 2);
+ uint32_t x20 = ((uint32_t)(arg1[12]) << 19);
+ uint32_t x21 = ((uint32_t)(arg1[11]) << 11);
+ uint32_t x22 = ((uint32_t)(arg1[10]) << 3);
+ uint32_t x23 = ((uint32_t)(arg1[9]) << 21);
+ uint32_t x24 = ((uint32_t)(arg1[8]) << 13);
+ uint32_t x25 = ((uint32_t)(arg1[7]) << 5);
+ uint32_t x26 = ((uint32_t)(arg1[6]) << 22);
+ uint32_t x27 = ((uint32_t)(arg1[5]) << 14);
+ uint32_t x28 = ((uint32_t)(arg1[4]) << 6);
+ uint32_t x29 = ((uint32_t)(arg1[3]) << 24);
+ uint32_t x30 = ((uint32_t)(arg1[2]) << 16);
+ uint32_t x31 = ((uint32_t)(arg1[1]) << 8);
+ uint8_t x32 = (arg1[0]);
+ uint32_t x33 = (x32 + (x31 + (x30 + x29)));
+ uint8_t x34 = (uint8_t)(x33 >> 26);
+ uint32_t x35 = (x33 & UINT32_C(0x3ffffff));
+ uint32_t x36 = (x3 + (x2 + x1));
+ uint32_t x37 = (x6 + (x5 + x4));
+ uint32_t x38 = (x9 + (x8 + x7));
+ uint32_t x39 = (x12 + (x11 + x10));
+ uint32_t x40 = (x16 + (x15 + (x14 + x13)));
+ uint32_t x41 = (x19 + (x18 + x17));
+ uint32_t x42 = (x22 + (x21 + x20));
+ uint32_t x43 = (x25 + (x24 + x23));
+ uint32_t x44 = (x28 + (x27 + x26));
+ uint32_t x45 = (x34 + x44);
+ uint8_t x46 = (uint8_t)(x45 >> 25);
+ uint32_t x47 = (x45 & UINT32_C(0x1ffffff));
+ uint32_t x48 = (x46 + x43);
+ uint8_t x49 = (uint8_t)(x48 >> 26);
+ uint32_t x50 = (x48 & UINT32_C(0x3ffffff));
+ uint32_t x51 = (x49 + x42);
+ uint8_t x52 = (uint8_t)(x51 >> 25);
+ uint32_t x53 = (x51 & UINT32_C(0x1ffffff));
+ uint32_t x54 = (x52 + x41);
+ uint32_t x55 = (x54 & UINT32_C(0x3ffffff));
+ uint8_t x56 = (uint8_t)(x40 >> 25);
+ uint32_t x57 = (x40 & UINT32_C(0x1ffffff));
+ uint32_t x58 = (x56 + x39);
+ uint8_t x59 = (uint8_t)(x58 >> 26);
+ uint32_t x60 = (x58 & UINT32_C(0x3ffffff));
+ uint32_t x61 = (x59 + x38);
+ uint8_t x62 = (uint8_t)(x61 >> 25);
+ uint32_t x63 = (x61 & UINT32_C(0x1ffffff));
+ uint32_t x64 = (x62 + x37);
+ uint8_t x65 = (uint8_t)(x64 >> 26);
+ uint32_t x66 = (x64 & UINT32_C(0x3ffffff));
+ uint32_t x67 = (x65 + x36);
+ out1[0] = x35;
+ out1[1] = x47;
+ out1[2] = x50;
+ out1[3] = x53;
+ out1[4] = x55;
+ out1[5] = x57;
+ out1[6] = x60;
+ out1[7] = x63;
+ out1[8] = x66;
+ out1[9] = x67;
+}
+
+// Definitions
+
+// fe means field element. Here the field is \Z/(2^255-19). An element t,
+// entries t[0]...t[9], represents the integer t[0]+2^26 t[1]+2^51 t[2]+2^77
+// t[3]+2^102 t[4]+...+2^230 t[9].
+// fe limbs are bounded by 1.125*2^26,1.125*2^25,1.125*2^26,1.125*2^25,etc.
+// Multiplication and carrying produce fe from fe_loose.
+typedef struct fe {
+ uint32_t v[10];
+} fe;
+
+// fe_loose limbs are bounded by 3.375*2^26,3.375*2^25,3.375*2^26,3.375*2^25,etc.
+// Addition and subtraction produce fe_loose from (fe, fe).
+typedef struct fe_loose {
+ uint32_t v[10];
+} fe_loose;
+
+// ge means group element.
+//
+// Here the group is the set of pairs (x,y) of field elements (see fe.h)
+// satisfying -x^2 + y^2 = 1 + d x^2y^2
+// where d = -121665/121666.
+//
+// Representations:
+// ge_p2 (projective): (X:Y:Z) satisfying x=X/Z, y=Y/Z
+// ge_p3 (extended): (X:Y:Z:T) satisfying x=X/Z, y=Y/Z, XY=ZT
+// ge_p1p1 (completed): ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T
+// ge_precomp (Duif): (y+x,y-x,2dxy)
+
+typedef struct {
+ fe X;
+ fe Y;
+ fe Z;
+} ge_p2;
+
+typedef struct {
+ fe X;
+ fe Y;
+ fe Z;
+ fe T;
+} ge_p3;
+
+typedef struct {
+ fe_loose X;
+ fe_loose Y;
+ fe_loose Z;
+ fe_loose T;
+} ge_p1p1;
+
+typedef struct {
+ fe_loose yplusx;
+ fe_loose yminusx;
+ fe_loose xy2d;
+} ge_precomp;
+
+typedef struct {
+ fe_loose YplusX;
+ fe_loose YminusX;
+ fe_loose Z;
+ fe_loose T2d;
+} ge_cached;
+
+// Constants.
+
+static const fe d = { { 56195235, 13857412, 51736253, 6949390, 114729, 24766616,
+ 60832955, 30306712, 48412415, 21499315 } };
+
+static const fe sqrtm1 = { { 34513072, 25610706, 9377949, 3500415, 12389472,
+ 33281959, 41962654, 31548777, 326685, 11406482 } };
+
+static const fe d2 = { { 45281625, 27714825, 36363642, 13898781, 229458,
+ 15978800, 54557047, 27058993, 29715967, 9444199 } };
+
+// Bi[i] = (2*i+1)*B
+static const ge_precomp Bi[8] = {
+ {
+ { { 25967493, 19198397, 29566455, 3660896, 54414519, 4014786,
+ 27544626, 21800161, 61029707, 2047604
+
+ } },
+ { { 54563134, 934261, 64385954, 3049989, 66381436, 9406985,
+ 12720692, 5043384, 19500929, 18085054
+
+ } },
+ { { 58370664, 4489569, 9688441, 18769238, 10184608, 21191052,
+ 29287918, 11864899, 42594502, 29115885 } },
+ },
+ {
+ { { 15636272, 23865875, 24204772, 25642034, 616976, 16869170,
+ 27787599, 18782243, 28944399, 32004408 } },
+ { { 16568933, 4717097, 55552716, 32452109, 15682895, 21747389,
+ 16354576, 21778470, 7689661, 11199574 } },
+ { { 30464137, 27578307, 55329429, 17883566, 23220364, 15915852,
+ 7512774, 10017326, 49359771, 23634074 } },
+ },
+ {
+ { { 10861363, 11473154, 27284546, 1981175, 37044515, 12577860,
+ 32867885, 14515107, 51670560, 10819379 } },
+ { { 4708026, 6336745, 20377586, 9066809, 55836755, 6594695,
+ 41455196, 12483687, 54440373, 5581305 } },
+ { { 19563141, 16186464, 37722007, 4097518, 10237984, 29206317,
+ 28542349, 13850243, 43430843, 17738489 } },
+ },
+ {
+ { { 5153727, 9909285, 1723747, 30776558, 30523604, 5516873,
+ 19480852, 5230134, 43156425, 18378665 } },
+ { { 36839857, 30090922, 7665485, 10083793, 28475525, 1649722,
+ 20654025, 16520125, 30598449, 7715701 } },
+ { { 28881826, 14381568, 9657904, 3680757, 46927229, 7843315,
+ 35708204, 1370707, 29794553, 32145132 } },
+ },
+ {
+ { { 44589871, 26862249, 14201701, 24808930, 43598457, 8844725,
+ 18474211, 32192982, 54046167, 13821876 } },
+ { { 60653668, 25714560, 3374701, 28813570, 40010246, 22982724,
+ 31655027, 26342105, 18853321, 19333481 } },
+ { { 4566811, 20590564, 38133974, 21313742, 59506191, 30723862,
+ 58594505, 23123294, 2207752, 30344648 } },
+ },
+ {
+ { { 41954014, 29368610, 29681143, 7868801, 60254203, 24130566,
+ 54671499, 32891431, 35997400, 17421995 } },
+ { { 25576264, 30851218, 7349803, 21739588, 16472781, 9300885,
+ 3844789, 15725684, 171356, 6466918 } },
+ { { 23103977, 13316479, 9739013, 17404951, 817874, 18515490,
+ 8965338, 19466374, 36393951, 16193876 } },
+ },
+ {
+ { { 33587053, 3180712, 64714734, 14003686, 50205390, 17283591,
+ 17238397, 4729455, 49034351, 9256799 } },
+ { { 41926547, 29380300, 32336397, 5036987, 45872047, 11360616,
+ 22616405, 9761698, 47281666, 630304 } },
+ { { 53388152, 2639452, 42871404, 26147950, 9494426, 27780403,
+ 60554312, 17593437, 64659607, 19263131 } },
+ },
+ {
+ { { 63957664, 28508356, 9282713, 6866145, 35201802, 32691408,
+ 48168288, 15033783, 25105118, 25659556 } },
+ { { 42782475, 15950225, 35307649, 18961608, 55446126, 28463506,
+ 1573891, 30928545, 2198789, 17749813 } },
+ { { 64009494, 10324966, 64867251, 7453182, 61661885, 30818928,
+ 53296841, 17317989, 34647629, 21263748 } },
+ },
+};
+
+static void fe_frombytes_strict(fe *h, const uint8_t s[32])
+{
+ // |fiat_25519_from_bytes| requires the top-most bit be clear.
+ fiat_25519_from_bytes(h->v, s);
+}
+
+static void fe_frombytes(fe *h, const uint8_t s[32])
+{
+ uint8_t s_copy[32];
+ memcpy(s_copy, s, 32);
+ s_copy[31] &= 0x7f;
+ fe_frombytes_strict(h, s_copy);
+}
+
+static void fe_tobytes(uint8_t s[32], const fe *f)
+{
+ fiat_25519_to_bytes(s, f->v);
+}
+
+// h = 0
+static void fe_0(fe *h)
+{
+ memset(h, 0, sizeof(fe));
+}
+
+// h = 1
+static void fe_1(fe *h)
+{
+ memset(h, 0, sizeof(fe));
+ h->v[0] = 1;
+}
+
+// h = f + g
+// Can overlap h with f or g.
+static void fe_add(fe_loose *h, const fe *f, const fe *g)
+{
+ fiat_25519_add(h->v, f->v, g->v);
+}
+
+// h = f - g
+// Can overlap h with f or g.
+static void fe_sub(fe_loose *h, const fe *f, const fe *g)
+{
+ fiat_25519_sub(h->v, f->v, g->v);
+}
+
+static void fe_carry(fe *h, const fe_loose *f)
+{
+ fiat_25519_carry(h->v, f->v);
+}
+
+static void fe_mul_impl(uint32_t out[10], const uint32_t in1[10],
+ const uint32_t in2[10])
+{
+ fiat_25519_carry_mul(out, in1, in2);
+}
+
+static void fe_mul_ltt(fe_loose *h, const fe *f, const fe *g)
+{
+ fe_mul_impl(h->v, f->v, g->v);
+}
+
+static void fe_mul_ttt(fe *h, const fe *f, const fe *g)
+{
+ fe_mul_impl(h->v, f->v, g->v);
+}
+
+static void fe_mul_tlt(fe *h, const fe_loose *f, const fe *g)
+{
+ fe_mul_impl(h->v, f->v, g->v);
+}
+
+static void fe_mul_ttl(fe *h, const fe *f, const fe_loose *g)
+{
+ fe_mul_impl(h->v, f->v, g->v);
+}
+
+static void fe_mul_tll(fe *h, const fe_loose *f, const fe_loose *g)
+{
+ fe_mul_impl(h->v, f->v, g->v);
+}
+
+static void fe_sq_tl(fe *h, const fe_loose *f)
+{
+ fiat_25519_carry_square(h->v, f->v);
+}
+
+static void fe_sq_tt(fe *h, const fe *f)
+{
+ fiat_25519_carry_square(h->v, f->v);
+}
+
+// h = -f
+static void fe_neg(fe_loose *h, const fe *f)
+{
+ fiat_25519_opp(h->v, f->v);
+}
+
+// h = f
+static void fe_copy(fe *h, const fe *f)
+{
+ memmove(h, f, sizeof(fe));
+}
+
+static void fe_copy_lt(fe_loose *h, const fe *f)
+{
+ memmove(h, f, sizeof(fe));
+}
+
+static void fe_loose_invert(fe *out, const fe_loose *z)
+{
+ fe t0;
+ fe t1;
+ fe t2;
+ fe t3;
+ int i;
+
+ fe_sq_tl(&t0, z);
+ fe_sq_tt(&t1, &t0);
+ for (i = 1; i < 2; ++i) {
+ fe_sq_tt(&t1, &t1);
+ }
+ fe_mul_tlt(&t1, z, &t1);
+ fe_mul_ttt(&t0, &t0, &t1);
+ fe_sq_tt(&t2, &t0);
+ fe_mul_ttt(&t1, &t1, &t2);
+ fe_sq_tt(&t2, &t1);
+ for (i = 1; i < 5; ++i) {
+ fe_sq_tt(&t2, &t2);
+ }
+ fe_mul_ttt(&t1, &t2, &t1);
+ fe_sq_tt(&t2, &t1);
+ for (i = 1; i < 10; ++i) {
+ fe_sq_tt(&t2, &t2);
+ }
+ fe_mul_ttt(&t2, &t2, &t1);
+ fe_sq_tt(&t3, &t2);
+ for (i = 1; i < 20; ++i) {
+ fe_sq_tt(&t3, &t3);
+ }
+ fe_mul_ttt(&t2, &t3, &t2);
+ fe_sq_tt(&t2, &t2);
+ for (i = 1; i < 10; ++i) {
+ fe_sq_tt(&t2, &t2);
+ }
+ fe_mul_ttt(&t1, &t2, &t1);
+ fe_sq_tt(&t2, &t1);
+ for (i = 1; i < 50; ++i) {
+ fe_sq_tt(&t2, &t2);
+ }
+ fe_mul_ttt(&t2, &t2, &t1);
+ fe_sq_tt(&t3, &t2);
+ for (i = 1; i < 100; ++i) {
+ fe_sq_tt(&t3, &t3);
+ }
+ fe_mul_ttt(&t2, &t3, &t2);
+ fe_sq_tt(&t2, &t2);
+ for (i = 1; i < 50; ++i) {
+ fe_sq_tt(&t2, &t2);
+ }
+ fe_mul_ttt(&t1, &t2, &t1);
+ fe_sq_tt(&t1, &t1);
+ for (i = 1; i < 5; ++i) {
+ fe_sq_tt(&t1, &t1);
+ }
+ fe_mul_ttt(out, &t1, &t0);
+}
+
+static void fe_invert(fe *out, const fe *z)
+{
+ fe_loose l;
+ fe_copy_lt(&l, z);
+ fe_loose_invert(out, &l);
+}
+
+// return 0 if f == 0
+// return 1 if f != 0
+static int fe_isnonzero(const fe_loose *f)
+{
+ fe tight;
+ fe_carry(&tight, f);
+ uint8_t s[32];
+ fe_tobytes(s, &tight);
+
+ static const uint8_t zero[32] = { 0 };
+ return memcmp_ct(s, zero, sizeof(zero)) != 0;
+}
+
+// return 1 if f is in {1,3,5,...,q-2}
+// return 0 if f is in {0,2,4,...,q-1}
+static int fe_isnegative(const fe *f)
+{
+ uint8_t s[32];
+ fe_tobytes(s, f);
+ return s[0] & 1;
+}
+
+static void fe_sq2_tt(fe *h, const fe *f)
+{
+ // h = f^2
+ fe_sq_tt(h, f);
+
+ // h = h + h
+ fe_loose tmp;
+ fe_add(&tmp, h, h);
+ fe_carry(h, &tmp);
+}
+
+static void fe_pow22523(fe *out, const fe *z)
+{
+ fe t0;
+ fe t1;
+ fe t2;
+ int i;
+
+ fe_sq_tt(&t0, z);
+ fe_sq_tt(&t1, &t0);
+ for (i = 1; i < 2; ++i) {
+ fe_sq_tt(&t1, &t1);
+ }
+ fe_mul_ttt(&t1, z, &t1);
+ fe_mul_ttt(&t0, &t0, &t1);
+ fe_sq_tt(&t0, &t0);
+ fe_mul_ttt(&t0, &t1, &t0);
+ fe_sq_tt(&t1, &t0);
+ for (i = 1; i < 5; ++i) {
+ fe_sq_tt(&t1, &t1);
+ }
+ fe_mul_ttt(&t0, &t1, &t0);
+ fe_sq_tt(&t1, &t0);
+ for (i = 1; i < 10; ++i) {
+ fe_sq_tt(&t1, &t1);
+ }
+ fe_mul_ttt(&t1, &t1, &t0);
+ fe_sq_tt(&t2, &t1);
+ for (i = 1; i < 20; ++i) {
+ fe_sq_tt(&t2, &t2);
+ }
+ fe_mul_ttt(&t1, &t2, &t1);
+ fe_sq_tt(&t1, &t1);
+ for (i = 1; i < 10; ++i) {
+ fe_sq_tt(&t1, &t1);
+ }
+ fe_mul_ttt(&t0, &t1, &t0);
+ fe_sq_tt(&t1, &t0);
+ for (i = 1; i < 50; ++i) {
+ fe_sq_tt(&t1, &t1);
+ }
+ fe_mul_ttt(&t1, &t1, &t0);
+ fe_sq_tt(&t2, &t1);
+ for (i = 1; i < 100; ++i) {
+ fe_sq_tt(&t2, &t2);
+ }
+ fe_mul_ttt(&t1, &t2, &t1);
+ fe_sq_tt(&t1, &t1);
+ for (i = 1; i < 50; ++i) {
+ fe_sq_tt(&t1, &t1);
+ }
+ fe_mul_ttt(&t0, &t1, &t0);
+ fe_sq_tt(&t0, &t0);
+ for (i = 1; i < 2; ++i) {
+ fe_sq_tt(&t0, &t0);
+ }
+ fe_mul_ttt(out, &t0, z);
+}
+
+static void x25519_ge_tobytes(uint8_t s[32], const ge_p2 *h)
+{
+ fe recip;
+ fe x;
+ fe y;
+
+ fe_invert(&recip, &h->Z);
+ fe_mul_ttt(&x, &h->X, &recip);
+ fe_mul_ttt(&y, &h->Y, &recip);
+ fe_tobytes(s, &y);
+ s[31] ^= fe_isnegative(&x) << 7;
+}
+
+static int x25519_ge_frombytes_vartime(ge_p3 *h, const uint8_t s[32])
+{
+ fe u;
+ fe_loose v;
+ fe v3;
+ fe vxx;
+ fe_loose check;
+
+ fe_frombytes(&h->Y, s);
+ fe_1(&h->Z);
+ fe_sq_tt(&v3, &h->Y);
+ fe_mul_ttt(&vxx, &v3, &d);
+ fe_sub(&v, &v3, &h->Z); // u = y^2-1
+ fe_carry(&u, &v);
+ fe_add(&v, &vxx, &h->Z); // v = dy^2+1
+
+ fe_sq_tl(&v3, &v);
+ fe_mul_ttl(&v3, &v3, &v); // v3 = v^3
+ fe_sq_tt(&h->X, &v3);
+ fe_mul_ttl(&h->X, &h->X, &v);
+ fe_mul_ttt(&h->X, &h->X, &u); // x = uv^7
+
+ fe_pow22523(&h->X, &h->X); // x = (uv^7)^((q-5)/8)
+ fe_mul_ttt(&h->X, &h->X, &v3);
+ fe_mul_ttt(&h->X, &h->X, &u); // x = uv^3(uv^7)^((q-5)/8)
+
+ fe_sq_tt(&vxx, &h->X);
+ fe_mul_ttl(&vxx, &vxx, &v);
+ fe_sub(&check, &vxx, &u);
+ if (fe_isnonzero(&check)) {
+ fe_add(&check, &vxx, &u);
+ if (fe_isnonzero(&check)) {
+ return 0;
+ }
+ fe_mul_ttt(&h->X, &h->X, &sqrtm1);
+ }
+
+ if (fe_isnegative(&h->X) != (s[31] >> 7)) {
+ fe_loose t;
+ fe_neg(&t, &h->X);
+ fe_carry(&h->X, &t);
+ }
+
+ fe_mul_ttt(&h->T, &h->X, &h->Y);
+ return 1;
+}
+
+static void ge_p2_0(ge_p2 *h)
+{
+ fe_0(&h->X);
+ fe_1(&h->Y);
+ fe_1(&h->Z);
+}
+
+// r = p
+static void ge_p3_to_p2(ge_p2 *r, const ge_p3 *p)
+{
+ fe_copy(&r->X, &p->X);
+ fe_copy(&r->Y, &p->Y);
+ fe_copy(&r->Z, &p->Z);
+}
+
+// r = p
+static void x25519_ge_p3_to_cached(ge_cached *r, const ge_p3 *p)
+{
+ fe_add(&r->YplusX, &p->Y, &p->X);
+ fe_sub(&r->YminusX, &p->Y, &p->X);
+ fe_copy_lt(&r->Z, &p->Z);
+ fe_mul_ltt(&r->T2d, &p->T, &d2);
+}
+
+// r = p
+static void x25519_ge_p1p1_to_p2(ge_p2 *r, const ge_p1p1 *p)
+{
+ fe_mul_tll(&r->X, &p->X, &p->T);
+ fe_mul_tll(&r->Y, &p->Y, &p->Z);
+ fe_mul_tll(&r->Z, &p->Z, &p->T);
+}
+
+// r = p
+static void x25519_ge_p1p1_to_p3(ge_p3 *r, const ge_p1p1 *p)
+{
+ fe_mul_tll(&r->X, &p->X, &p->T);
+ fe_mul_tll(&r->Y, &p->Y, &p->Z);
+ fe_mul_tll(&r->Z, &p->Z, &p->T);
+ fe_mul_tll(&r->T, &p->X, &p->Y);
+}
+
+// r = 2 * p
+static void ge_p2_dbl(ge_p1p1 *r, const ge_p2 *p)
+{
+ fe trX, trZ, trT;
+ fe t0;
+
+ fe_sq_tt(&trX, &p->X);
+ fe_sq_tt(&trZ, &p->Y);
+ fe_sq2_tt(&trT, &p->Z);
+ fe_add(&r->Y, &p->X, &p->Y);
+ fe_sq_tl(&t0, &r->Y);
+
+ fe_add(&r->Y, &trZ, &trX);
+ fe_sub(&r->Z, &trZ, &trX);
+ fe_carry(&trZ, &r->Y);
+ fe_sub(&r->X, &t0, &trZ);
+ fe_carry(&trZ, &r->Z);
+ fe_sub(&r->T, &trT, &trZ);
+}
+
+// r = 2 * p
+static void ge_p3_dbl(ge_p1p1 *r, const ge_p3 *p)
+{
+ ge_p2 q;
+ ge_p3_to_p2(&q, p);
+ ge_p2_dbl(r, &q);
+}
+
+// r = p + q
+static void ge_madd(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q)
+{
+ fe trY, trZ, trT;
+
+ fe_add(&r->X, &p->Y, &p->X);
+ fe_sub(&r->Y, &p->Y, &p->X);
+ fe_mul_tll(&trZ, &r->X, &q->yplusx);
+ fe_mul_tll(&trY, &r->Y, &q->yminusx);
+ fe_mul_tlt(&trT, &q->xy2d, &p->T);
+ fe_add(&r->T, &p->Z, &p->Z);
+ fe_sub(&r->X, &trZ, &trY);
+ fe_add(&r->Y, &trZ, &trY);
+ fe_carry(&trZ, &r->T);
+ fe_add(&r->Z, &trZ, &trT);
+ fe_sub(&r->T, &trZ, &trT);
+}
+
+// r = p - q
+static void ge_msub(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q)
+{
+ fe trY, trZ, trT;
+
+ fe_add(&r->X, &p->Y, &p->X);
+ fe_sub(&r->Y, &p->Y, &p->X);
+ fe_mul_tll(&trZ, &r->X, &q->yminusx);
+ fe_mul_tll(&trY, &r->Y, &q->yplusx);
+ fe_mul_tlt(&trT, &q->xy2d, &p->T);
+ fe_add(&r->T, &p->Z, &p->Z);
+ fe_sub(&r->X, &trZ, &trY);
+ fe_add(&r->Y, &trZ, &trY);
+ fe_carry(&trZ, &r->T);
+ fe_sub(&r->Z, &trZ, &trT);
+ fe_add(&r->T, &trZ, &trT);
+}
+
+// r = p + q
+static void x25519_ge_add(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q)
+{
+ fe trX, trY, trZ, trT;
+
+ fe_add(&r->X, &p->Y, &p->X);
+ fe_sub(&r->Y, &p->Y, &p->X);
+ fe_mul_tll(&trZ, &r->X, &q->YplusX);
+ fe_mul_tll(&trY, &r->Y, &q->YminusX);
+ fe_mul_tlt(&trT, &q->T2d, &p->T);
+ fe_mul_ttl(&trX, &p->Z, &q->Z);
+ fe_add(&r->T, &trX, &trX);
+ fe_sub(&r->X, &trZ, &trY);
+ fe_add(&r->Y, &trZ, &trY);
+ fe_carry(&trZ, &r->T);
+ fe_add(&r->Z, &trZ, &trT);
+ fe_sub(&r->T, &trZ, &trT);
+}
+
+// r = p - q
+static void x25519_ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q)
+{
+ fe trX, trY, trZ, trT;
+
+ fe_add(&r->X, &p->Y, &p->X);
+ fe_sub(&r->Y, &p->Y, &p->X);
+ fe_mul_tll(&trZ, &r->X, &q->YminusX);
+ fe_mul_tll(&trY, &r->Y, &q->YplusX);
+ fe_mul_tlt(&trT, &q->T2d, &p->T);
+ fe_mul_ttl(&trX, &p->Z, &q->Z);
+ fe_add(&r->T, &trX, &trX);
+ fe_sub(&r->X, &trZ, &trY);
+ fe_add(&r->Y, &trZ, &trY);
+ fe_carry(&trZ, &r->T);
+ fe_sub(&r->Z, &trZ, &trT);
+ fe_add(&r->T, &trZ, &trT);
+}
+
+static void slide(signed char *r, const uint8_t *a)
+{
+ int i;
+ int b;
+ int k;
+
+ for (i = 0; i < 256; ++i) {
+ r[i] = 1 & (a[i >> 3] >> (i & 7));
+ }
+
+ for (i = 0; i < 256; ++i) {
+ if (r[i]) {
+ for (b = 1; b <= 6 && i + b < 256; ++b) {
+ if (r[i + b]) {
+ if (r[i] + (r[i + b] << b) <= 15) {
+ r[i] += r[i + b] << b;
+ r[i + b] = 0;
+ } else if (r[i] - (r[i + b] << b) >=
+ -15) {
+ r[i] -= r[i + b] << b;
+ for (k = i + b; k < 256; ++k) {
+ if (!r[k]) {
+ r[k] = 1;
+ break;
+ }
+ r[k] = 0;
+ }
+ } else {
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+// r = a * A + b * B
+// where a = a[0]+256*a[1]+...+256^31 a[31].
+// and b = b[0]+256*b[1]+...+256^31 b[31].
+// B is the Ed25519 base point (x,4/5) with x positive.
+static void ge_double_scalarmult_vartime(ge_p2 *r, const uint8_t *a,
+ const ge_p3 *A, const uint8_t *b)
+{
+ signed char aslide[256];
+ signed char bslide[256];
+ ge_cached Ai[8]; // A,3A,5A,7A,9A,11A,13A,15A
+ ge_p1p1 t;
+ ge_p3 u;
+ ge_p3 A2;
+ int i;
+
+ slide(aslide, a);
+ slide(bslide, b);
+
+ x25519_ge_p3_to_cached(&Ai[0], A);
+ ge_p3_dbl(&t, A);
+ x25519_ge_p1p1_to_p3(&A2, &t);
+ x25519_ge_add(&t, &A2, &Ai[0]);
+ x25519_ge_p1p1_to_p3(&u, &t);
+ x25519_ge_p3_to_cached(&Ai[1], &u);
+ x25519_ge_add(&t, &A2, &Ai[1]);
+ x25519_ge_p1p1_to_p3(&u, &t);
+ x25519_ge_p3_to_cached(&Ai[2], &u);
+ x25519_ge_add(&t, &A2, &Ai[2]);
+ x25519_ge_p1p1_to_p3(&u, &t);
+ x25519_ge_p3_to_cached(&Ai[3], &u);
+ x25519_ge_add(&t, &A2, &Ai[3]);
+ x25519_ge_p1p1_to_p3(&u, &t);
+ x25519_ge_p3_to_cached(&Ai[4], &u);
+ x25519_ge_add(&t, &A2, &Ai[4]);
+ x25519_ge_p1p1_to_p3(&u, &t);
+ x25519_ge_p3_to_cached(&Ai[5], &u);
+ x25519_ge_add(&t, &A2, &Ai[5]);
+ x25519_ge_p1p1_to_p3(&u, &t);
+ x25519_ge_p3_to_cached(&Ai[6], &u);
+ x25519_ge_add(&t, &A2, &Ai[6]);
+ x25519_ge_p1p1_to_p3(&u, &t);
+ x25519_ge_p3_to_cached(&Ai[7], &u);
+
+ ge_p2_0(r);
+
+ for (i = 255; i >= 0; --i) {
+ if (aslide[i] || bslide[i]) {
+ break;
+ }
+ }
+
+ for (; i >= 0; --i) {
+ ge_p2_dbl(&t, r);
+
+ if (aslide[i] > 0) {
+ x25519_ge_p1p1_to_p3(&u, &t);
+ x25519_ge_add(&t, &u, &Ai[aslide[i] / 2]);
+ } else if (aslide[i] < 0) {
+ x25519_ge_p1p1_to_p3(&u, &t);
+ x25519_ge_sub(&t, &u, &Ai[(-aslide[i]) / 2]);
+ }
+
+ if (bslide[i] > 0) {
+ x25519_ge_p1p1_to_p3(&u, &t);
+ ge_madd(&t, &u, &Bi[bslide[i] / 2]);
+ } else if (bslide[i] < 0) {
+ x25519_ge_p1p1_to_p3(&u, &t);
+ ge_msub(&t, &u, &Bi[(-bslide[i]) / 2]);
+ }
+
+ x25519_ge_p1p1_to_p2(r, &t);
+ }
+}
+
+// int64_lshift21 returns |a << 21| but is defined when shifting bits into the
+// sign bit. This works around a language flaw in C.
+static inline int64_t int64_lshift21(int64_t a)
+{
+ return (int64_t)((uint64_t)a << 21);
+}
+
+// The set of scalars is \Z/l
+// where l = 2^252 + 27742317777372353535851937790883648493.
+
+// Input:
+// s[0]+256*s[1]+...+256^63*s[63] = s
+//
+// Output:
+// s[0]+256*s[1]+...+256^31*s[31] = s mod l
+// where l = 2^252 + 27742317777372353535851937790883648493.
+// Overwrites s in place.
+static void x25519_sc_reduce(uint8_t s[64])
+{
+ int64_t s0 = 2097151 & load_le24(s);
+ int64_t s1 = 2097151 & (load_le32(s + 2) >> 5);
+ int64_t s2 = 2097151 & (load_le24(s + 5) >> 2);
+ int64_t s3 = 2097151 & (load_le32(s + 7) >> 7);
+ int64_t s4 = 2097151 & (load_le32(s + 10) >> 4);
+ int64_t s5 = 2097151 & (load_le24(s + 13) >> 1);
+ int64_t s6 = 2097151 & (load_le32(s + 15) >> 6);
+ int64_t s7 = 2097151 & (load_le24(s + 18) >> 3);
+ int64_t s8 = 2097151 & load_le24(s + 21);
+ int64_t s9 = 2097151 & (load_le32(s + 23) >> 5);
+ int64_t s10 = 2097151 & (load_le24(s + 26) >> 2);
+ int64_t s11 = 2097151 & (load_le32(s + 28) >> 7);
+ int64_t s12 = 2097151 & (load_le32(s + 31) >> 4);
+ int64_t s13 = 2097151 & (load_le24(s + 34) >> 1);
+ int64_t s14 = 2097151 & (load_le32(s + 36) >> 6);
+ int64_t s15 = 2097151 & (load_le24(s + 39) >> 3);
+ int64_t s16 = 2097151 & load_le24(s + 42);
+ int64_t s17 = 2097151 & (load_le32(s + 44) >> 5);
+ int64_t s18 = 2097151 & (load_le24(s + 47) >> 2);
+ int64_t s19 = 2097151 & (load_le32(s + 49) >> 7);
+ int64_t s20 = 2097151 & (load_le32(s + 52) >> 4);
+ int64_t s21 = 2097151 & (load_le24(s + 55) >> 1);
+ int64_t s22 = 2097151 & (load_le32(s + 57) >> 6);
+ int64_t s23 = (load_le32(s + 60) >> 3);
+ int64_t carry0;
+ int64_t carry1;
+ int64_t carry2;
+ int64_t carry3;
+ int64_t carry4;
+ int64_t carry5;
+ int64_t carry6;
+ int64_t carry7;
+ int64_t carry8;
+ int64_t carry9;
+ int64_t carry10;
+ int64_t carry11;
+ int64_t carry12;
+ int64_t carry13;
+ int64_t carry14;
+ int64_t carry15;
+ int64_t carry16;
+
+ s11 += s23 * 666643;
+ s12 += s23 * 470296;
+ s13 += s23 * 654183;
+ s14 -= s23 * 997805;
+ s15 += s23 * 136657;
+ s16 -= s23 * 683901;
+ s23 = 0;
+
+ s10 += s22 * 666643;
+ s11 += s22 * 470296;
+ s12 += s22 * 654183;
+ s13 -= s22 * 997805;
+ s14 += s22 * 136657;
+ s15 -= s22 * 683901;
+ s22 = 0;
+
+ s9 += s21 * 666643;
+ s10 += s21 * 470296;
+ s11 += s21 * 654183;
+ s12 -= s21 * 997805;
+ s13 += s21 * 136657;
+ s14 -= s21 * 683901;
+ s21 = 0;
+
+ s8 += s20 * 666643;
+ s9 += s20 * 470296;
+ s10 += s20 * 654183;
+ s11 -= s20 * 997805;
+ s12 += s20 * 136657;
+ s13 -= s20 * 683901;
+ s20 = 0;
+
+ s7 += s19 * 666643;
+ s8 += s19 * 470296;
+ s9 += s19 * 654183;
+ s10 -= s19 * 997805;
+ s11 += s19 * 136657;
+ s12 -= s19 * 683901;
+ s19 = 0;
+
+ s6 += s18 * 666643;
+ s7 += s18 * 470296;
+ s8 += s18 * 654183;
+ s9 -= s18 * 997805;
+ s10 += s18 * 136657;
+ s11 -= s18 * 683901;
+ s18 = 0;
+
+ carry6 = (s6 + (1 << 20)) >> 21;
+ s7 += carry6;
+ s6 -= int64_lshift21(carry6);
+ carry8 = (s8 + (1 << 20)) >> 21;
+ s9 += carry8;
+ s8 -= int64_lshift21(carry8);
+ carry10 = (s10 + (1 << 20)) >> 21;
+ s11 += carry10;
+ s10 -= int64_lshift21(carry10);
+ carry12 = (s12 + (1 << 20)) >> 21;
+ s13 += carry12;
+ s12 -= int64_lshift21(carry12);
+ carry14 = (s14 + (1 << 20)) >> 21;
+ s15 += carry14;
+ s14 -= int64_lshift21(carry14);
+ carry16 = (s16 + (1 << 20)) >> 21;
+ s17 += carry16;
+ s16 -= int64_lshift21(carry16);
+
+ carry7 = (s7 + (1 << 20)) >> 21;
+ s8 += carry7;
+ s7 -= int64_lshift21(carry7);
+ carry9 = (s9 + (1 << 20)) >> 21;
+ s10 += carry9;
+ s9 -= int64_lshift21(carry9);
+ carry11 = (s11 + (1 << 20)) >> 21;
+ s12 += carry11;
+ s11 -= int64_lshift21(carry11);
+ carry13 = (s13 + (1 << 20)) >> 21;
+ s14 += carry13;
+ s13 -= int64_lshift21(carry13);
+ carry15 = (s15 + (1 << 20)) >> 21;
+ s16 += carry15;
+ s15 -= int64_lshift21(carry15);
+
+ s5 += s17 * 666643;
+ s6 += s17 * 470296;
+ s7 += s17 * 654183;
+ s8 -= s17 * 997805;
+ s9 += s17 * 136657;
+ s10 -= s17 * 683901;
+ s17 = 0;
+
+ s4 += s16 * 666643;
+ s5 += s16 * 470296;
+ s6 += s16 * 654183;
+ s7 -= s16 * 997805;
+ s8 += s16 * 136657;
+ s9 -= s16 * 683901;
+ s16 = 0;
+
+ s3 += s15 * 666643;
+ s4 += s15 * 470296;
+ s5 += s15 * 654183;
+ s6 -= s15 * 997805;
+ s7 += s15 * 136657;
+ s8 -= s15 * 683901;
+ s15 = 0;
+
+ s2 += s14 * 666643;
+ s3 += s14 * 470296;
+ s4 += s14 * 654183;
+ s5 -= s14 * 997805;
+ s6 += s14 * 136657;
+ s7 -= s14 * 683901;
+ s14 = 0;
+
+ s1 += s13 * 666643;
+ s2 += s13 * 470296;
+ s3 += s13 * 654183;
+ s4 -= s13 * 997805;
+ s5 += s13 * 136657;
+ s6 -= s13 * 683901;
+ s13 = 0;
+
+ s0 += s12 * 666643;
+ s1 += s12 * 470296;
+ s2 += s12 * 654183;
+ s3 -= s12 * 997805;
+ s4 += s12 * 136657;
+ s5 -= s12 * 683901;
+ s12 = 0;
+
+ carry0 = (s0 + (1 << 20)) >> 21;
+ s1 += carry0;
+ s0 -= int64_lshift21(carry0);
+ carry2 = (s2 + (1 << 20)) >> 21;
+ s3 += carry2;
+ s2 -= int64_lshift21(carry2);
+ carry4 = (s4 + (1 << 20)) >> 21;
+ s5 += carry4;
+ s4 -= int64_lshift21(carry4);
+ carry6 = (s6 + (1 << 20)) >> 21;
+ s7 += carry6;
+ s6 -= int64_lshift21(carry6);
+ carry8 = (s8 + (1 << 20)) >> 21;
+ s9 += carry8;
+ s8 -= int64_lshift21(carry8);
+ carry10 = (s10 + (1 << 20)) >> 21;
+ s11 += carry10;
+ s10 -= int64_lshift21(carry10);
+
+ carry1 = (s1 + (1 << 20)) >> 21;
+ s2 += carry1;
+ s1 -= int64_lshift21(carry1);
+ carry3 = (s3 + (1 << 20)) >> 21;
+ s4 += carry3;
+ s3 -= int64_lshift21(carry3);
+ carry5 = (s5 + (1 << 20)) >> 21;
+ s6 += carry5;
+ s5 -= int64_lshift21(carry5);
+ carry7 = (s7 + (1 << 20)) >> 21;
+ s8 += carry7;
+ s7 -= int64_lshift21(carry7);
+ carry9 = (s9 + (1 << 20)) >> 21;
+ s10 += carry9;
+ s9 -= int64_lshift21(carry9);
+ carry11 = (s11 + (1 << 20)) >> 21;
+ s12 += carry11;
+ s11 -= int64_lshift21(carry11);
+
+ s0 += s12 * 666643;
+ s1 += s12 * 470296;
+ s2 += s12 * 654183;
+ s3 -= s12 * 997805;
+ s4 += s12 * 136657;
+ s5 -= s12 * 683901;
+ s12 = 0;
+
+ carry0 = s0 >> 21;
+ s1 += carry0;
+ s0 -= int64_lshift21(carry0);
+ carry1 = s1 >> 21;
+ s2 += carry1;
+ s1 -= int64_lshift21(carry1);
+ carry2 = s2 >> 21;
+ s3 += carry2;
+ s2 -= int64_lshift21(carry2);
+ carry3 = s3 >> 21;
+ s4 += carry3;
+ s3 -= int64_lshift21(carry3);
+ carry4 = s4 >> 21;
+ s5 += carry4;
+ s4 -= int64_lshift21(carry4);
+ carry5 = s5 >> 21;
+ s6 += carry5;
+ s5 -= int64_lshift21(carry5);
+ carry6 = s6 >> 21;
+ s7 += carry6;
+ s6 -= int64_lshift21(carry6);
+ carry7 = s7 >> 21;
+ s8 += carry7;
+ s7 -= int64_lshift21(carry7);
+ carry8 = s8 >> 21;
+ s9 += carry8;
+ s8 -= int64_lshift21(carry8);
+ carry9 = s9 >> 21;
+ s10 += carry9;
+ s9 -= int64_lshift21(carry9);
+ carry10 = s10 >> 21;
+ s11 += carry10;
+ s10 -= int64_lshift21(carry10);
+ carry11 = s11 >> 21;
+ s12 += carry11;
+ s11 -= int64_lshift21(carry11);
+
+ s0 += s12 * 666643;
+ s1 += s12 * 470296;
+ s2 += s12 * 654183;
+ s3 -= s12 * 997805;
+ s4 += s12 * 136657;
+ s5 -= s12 * 683901;
+ s12 = 0;
+
+ carry0 = s0 >> 21;
+ s1 += carry0;
+ s0 -= int64_lshift21(carry0);
+ carry1 = s1 >> 21;
+ s2 += carry1;
+ s1 -= int64_lshift21(carry1);
+ carry2 = s2 >> 21;
+ s3 += carry2;
+ s2 -= int64_lshift21(carry2);
+ carry3 = s3 >> 21;
+ s4 += carry3;
+ s3 -= int64_lshift21(carry3);
+ carry4 = s4 >> 21;
+ s5 += carry4;
+ s4 -= int64_lshift21(carry4);
+ carry5 = s5 >> 21;
+ s6 += carry5;
+ s5 -= int64_lshift21(carry5);
+ carry6 = s6 >> 21;
+ s7 += carry6;
+ s6 -= int64_lshift21(carry6);
+ carry7 = s7 >> 21;
+ s8 += carry7;
+ s7 -= int64_lshift21(carry7);
+ carry8 = s8 >> 21;
+ s9 += carry8;
+ s8 -= int64_lshift21(carry8);
+ carry9 = s9 >> 21;
+ s10 += carry9;
+ s9 -= int64_lshift21(carry9);
+ carry10 = s10 >> 21;
+ s11 += carry10;
+ s10 -= int64_lshift21(carry10);
+
+ s[0] = s0 >> 0;
+ s[1] = s0 >> 8;
+ s[2] = (s0 >> 16) | (s1 << 5);
+ s[3] = s1 >> 3;
+ s[4] = s1 >> 11;
+ s[5] = (s1 >> 19) | (s2 << 2);
+ s[6] = s2 >> 6;
+ s[7] = (s2 >> 14) | (s3 << 7);
+ s[8] = s3 >> 1;
+ s[9] = s3 >> 9;
+ s[10] = (s3 >> 17) | (s4 << 4);
+ s[11] = s4 >> 4;
+ s[12] = s4 >> 12;
+ s[13] = (s4 >> 20) | (s5 << 1);
+ s[14] = s5 >> 7;
+ s[15] = (s5 >> 15) | (s6 << 6);
+ s[16] = s6 >> 2;
+ s[17] = s6 >> 10;
+ s[18] = (s6 >> 18) | (s7 << 3);
+ s[19] = s7 >> 5;
+ s[20] = s7 >> 13;
+ s[21] = s8 >> 0;
+ s[22] = s8 >> 8;
+ s[23] = (s8 >> 16) | (s9 << 5);
+ s[24] = s9 >> 3;
+ s[25] = s9 >> 11;
+ s[26] = (s9 >> 19) | (s10 << 2);
+ s[27] = s10 >> 6;
+ s[28] = (s10 >> 14) | (s11 << 7);
+ s[29] = s11 >> 1;
+ s[30] = s11 >> 9;
+ s[31] = s11 >> 17;
+}
+
+bool ed25519_verify(const uint8_t signature[64], const uint8_t public_key[32],
+ const void *message, size_t message_size)
+{
+ ge_p3 A;
+ if ((signature[63] & 224) != 0 ||
+ !x25519_ge_frombytes_vartime(&A, public_key))
+ return false;
+
+ fe_loose t;
+ fe_neg(&t, &A.X);
+ fe_carry(&A.X, &t);
+ fe_neg(&t, &A.T);
+ fe_carry(&A.T, &t);
+
+ uint8_t pkcopy[32];
+ memcpy(pkcopy, public_key, 32);
+ uint8_t rcopy[32];
+ memcpy(rcopy, signature, 32);
+ union {
+ uint64_t u64[4];
+ uint8_t u8[32];
+ } scopy;
+ memcpy(&scopy.u8[0], signature + 32, 32);
+
+ // https://tools.ietf.org/html/rfc8032#section-5.1.7 requires that s be in
+ // the range [0, order) in order to prevent signature malleability.
+
+ // kOrder is the order of Curve25519 in little-endian form.
+ static const uint64_t kOrder[4] = {
+ UINT64_C(0x5812631a5cf5d3ed),
+ UINT64_C(0x14def9dea2f79cd6),
+ 0,
+ UINT64_C(0x1000000000000000),
+ };
+ for (size_t i = 3;; --i) {
+ uint64_t le = swap_le64(scopy.u64[i]);
+ if (le > kOrder[i]) {
+ return false;
+ } else if (le < kOrder[i]) {
+ break;
+ } else if (i == 0) {
+ return false;
+ }
+ }
+
+ uint8_t h[64];
+ BCRYPT_ALG_HANDLE alg, hash;
+ if (!NT_SUCCESS(BCryptOpenAlgorithmProvider(&alg, BCRYPT_SHA512_ALGORITHM, NULL, 0)) ||
+ !NT_SUCCESS(BCryptCreateHash(alg, &hash, NULL, 0, NULL, 0, 0)) ||
+ !NT_SUCCESS(BCryptHashData(hash, (PUCHAR)signature, 32, 0)) ||
+ !NT_SUCCESS(BCryptHashData(hash, (PUCHAR)public_key, 32, 0)) ||
+ !NT_SUCCESS(BCryptHashData(hash, (PUCHAR)message, message_size, 0)) ||
+ !NT_SUCCESS(BCryptFinishHash(hash, h, 64, 0)) ||
+ !NT_SUCCESS(BCryptDestroyHash(hash)) ||
+ !NT_SUCCESS(BCryptCloseAlgorithmProvider(alg, 0)))
+ return false;
+
+ x25519_sc_reduce(h);
+
+ ge_p2 R;
+ ge_double_scalarmult_vartime(&R, h, &A, scopy.u8);
+
+ uint8_t rcheck[32];
+ x25519_ge_tobytes(rcheck, &R);
+
+ return memcmp_ct(rcheck, rcopy, sizeof(rcheck)) == 0;
+}
+
+static const uint64_t blake2b_iv[8] = {
+ 0x6a09e667f3bcc908ULL, 0xbb67ae8584caa73bULL, 0x3c6ef372fe94f82bULL,
+ 0xa54ff53a5f1d36f1ULL, 0x510e527fade682d1ULL, 0x9b05688c2b3e6c1fULL,
+ 0x1f83d9abfb41bd6bULL, 0x5be0cd19137e2179ULL
+};
+
+static const uint8_t blake2b_sigma[12][16] = {
+ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
+ { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 },
+ { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 },
+ { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 },
+ { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 },
+ { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 },
+ { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 },
+ { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 },
+ { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 },
+ { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 },
+ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
+ { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 }
+};
+
+#define G(r, i, a, b, c, d) \
+ do { \
+ a = a + b + m[blake2b_sigma[r][2 * i + 0]]; \
+ d = ror64(d ^ a, 32); \
+ c = c + d; \
+ b = ror64(b ^ c, 24); \
+ a = a + b + m[blake2b_sigma[r][2 * i + 1]]; \
+ d = ror64(d ^ a, 16); \
+ c = c + d; \
+ b = ror64(b ^ c, 63); \
+ } while (0)
+
+#define ROUND(r) \
+ do { \
+ G(r, 0, v[0], v[4], v[8], v[12]); \
+ G(r, 1, v[1], v[5], v[9], v[13]); \
+ G(r, 2, v[2], v[6], v[10], v[14]); \
+ G(r, 3, v[3], v[7], v[11], v[15]); \
+ G(r, 4, v[0], v[5], v[10], v[15]); \
+ G(r, 5, v[1], v[6], v[11], v[12]); \
+ G(r, 6, v[2], v[7], v[8], v[13]); \
+ G(r, 7, v[3], v[4], v[9], v[14]); \
+ } while (0)
+
+static void blake2b256_compress(struct blake2b256_state *state,
+ const uint8_t block[128])
+{
+ uint64_t m[16];
+ uint64_t v[16];
+
+ for (int i = 0; i < 16; ++i)
+ m[i] = load_le64(block + i * sizeof(m[i]));
+
+ for (int i = 0; i < 8; ++i)
+ v[i] = state->h[i];
+
+ memcpy(v + 8, blake2b_iv, sizeof(blake2b_iv));
+ v[12] ^= state->t[0];
+ v[13] ^= state->t[1];
+ v[14] ^= state->f[0];
+ v[15] ^= state->f[1];
+
+ for (int i = 0; i < 12; ++i)
+ ROUND(i);
+ for (int i = 0; i < 8; ++i)
+ state->h[i] = state->h[i] ^ v[i] ^ v[i + 8];
+}
+
+void blake2b256_init(struct blake2b256_state *state)
+{
+ memset(state, 0, sizeof(*state));
+ memcpy(state->h, blake2b_iv, sizeof(state->h));
+ state->h[0] ^= 0x01010000 | 32;
+}
+
+void blake2b256_update(struct blake2b256_state *state, const uint8_t *in,
+ unsigned int inlen)
+{
+ const size_t left = state->buflen;
+ const size_t fill = 128 - left;
+
+ if (!inlen)
+ return;
+
+ if (inlen > fill) {
+ state->buflen = 0;
+ memcpy(state->buf + left, in, fill);
+ state->t[0] += 128;
+ state->t[1] += (state->t[0] < 128);
+ blake2b256_compress(state, state->buf);
+ in += fill;
+ inlen -= fill;
+ while (inlen > 128) {
+ state->t[0] += 128;
+ state->t[1] += (state->t[0] < 128);
+ blake2b256_compress(state, in);
+ in += 128;
+ inlen -= 128;
+ }
+ }
+ memcpy(state->buf + state->buflen, in, inlen);
+ state->buflen += inlen;
+}
+
+void blake2b256_final(struct blake2b256_state *state, uint8_t out[32])
+{
+ state->t[0] += state->buflen;
+ state->t[1] += (state->t[0] < state->buflen);
+ state->f[0] = (uint64_t)-1;
+ memset(state->buf + state->buflen, 0, 128 - state->buflen);
+ blake2b256_compress(state, state->buf);
+
+ for (int i = 0; i < 4; ++i)
+ store_le64(out + i * sizeof(state->h[i]), state->h[i]);
+}
diff --git a/installer/fetcher/crypto.h b/installer/fetcher/crypto.h
new file mode 100644
index 00000000..dc234ec7
--- /dev/null
+++ b/installer/fetcher/crypto.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright (C) 2020-2022 Jason A. Donenfeld. All Rights Reserved.
+ */
+
+#ifndef _CRYPTO_H
+#define _CRYPTO_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include <stdbool.h>
+
+struct blake2b256_state {
+ uint64_t h[8];
+ uint64_t t[2];
+ uint64_t f[2];
+ uint8_t buf[128];
+ size_t buflen;
+};
+
+void blake2b256_init(struct blake2b256_state *state);
+void blake2b256_update(struct blake2b256_state *state, const uint8_t *in,
+ unsigned int inlen);
+void blake2b256_final(struct blake2b256_state *state, uint8_t out[32]);
+
+bool ed25519_verify(const uint8_t signature[64], const uint8_t public_key[32],
+ const void *message, size_t message_size);
+
+#endif
diff --git a/installer/fetcher/fetcher.c b/installer/fetcher/fetcher.c
new file mode 100644
index 00000000..5c688997
--- /dev/null
+++ b/installer/fetcher/fetcher.c
@@ -0,0 +1,340 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020-2022 Jason A. Donenfeld. All Rights Reserved.
+ */
+
+#include <windows.h>
+#include <delayimp.h>
+#include <commctrl.h>
+#include <shlwapi.h>
+#include <ntsecapi.h>
+#include <sddl.h>
+#include <winhttp.h>
+#include <wintrust.h>
+#include <softpub.h>
+#include <msi.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <wchar.h>
+#include "filelist.h"
+#include "crypto.h"
+#include "systeminfo.h"
+#include "constants.h"
+
+static char msi_filename[MAX_PATH];
+static volatile bool msi_filename_is_set, prompts = true;
+static volatile size_t g_current, g_total;
+static HWND progress;
+static HANDLE filehandle = INVALID_HANDLE_VALUE;
+
+static wchar_t *L(const char *a)
+{
+ static wchar_t w[0x2000];
+ if (!MultiByteToWideChar(CP_UTF8, 0, a, -1, w, sizeof(w)))
+ abort();
+ return w;
+}
+
+static bool random_string(char hex[static 65])
+{
+ uint8_t bytes[32];
+ if (!RtlGenRandom(bytes, sizeof(bytes)))
+ return false;
+ for (int i = 0; i < 32; ++i) {
+ hex[i * 2] = 87U + (bytes[i] >> 4) + ((((bytes[i] >> 4) - 10U) >> 8) & ~38U);
+ hex[i * 2 + 1] = 87U + (bytes[i] & 0xf) + ((((bytes[i] & 0xf) - 10U) >> 8) & ~38U);
+ }
+ hex[64] = '\0';
+ return true;
+}
+
+static void set_status(HWND progress, const char *status)
+{
+ LONG_PTR current_style = GetWindowLongPtrA(progress, GWL_STYLE);
+ char buf[0x1000];
+ g_total = 0;
+ _snprintf_s(buf, sizeof(buf), _TRUNCATE, "WireGuard: %s...", status);
+ SetWindowTextA(progress, buf);
+ if (!(current_style & PBS_MARQUEE)) {
+ SendMessageA(progress, PBM_SETRANGE32, 0, 100);
+ SendMessageA(progress, PBM_SETPOS, 0, 0);
+ SetWindowLongPtrA(progress, GWL_STYLE, current_style | PBS_MARQUEE);
+ SendMessageA(progress, PBM_SETMARQUEE, TRUE, 0);
+ }
+}
+
+static void set_progress(HWND progress, size_t current, size_t total)
+{
+ g_current = current;
+ g_total = total;
+ PostMessageA(progress, WM_APP, 0, 0);
+}
+
+static DWORD __stdcall download_thread(void *param)
+{
+ DWORD ret = 1, bytes_read, bytes_written, enable_http2 = WINHTTP_PROTOCOL_FLAG_HTTP2;
+ HINTERNET session = NULL, connection = NULL, request = NULL;
+ uint8_t hash[32], computed_hash[32], buf[512 * 1024];
+ char download_path[MAX_FILENAME_LEN + sizeof(msi_path)], random_filename[65];
+ wchar_t total_bytes_str[22];
+ size_t total_bytes, current_bytes;
+ const char *arch;
+ struct blake2b256_state hasher;
+ SECURITY_ATTRIBUTES security_attributes = { .nLength = sizeof(security_attributes) };
+ WINTRUST_FILE_INFO wintrust_fileinfo = { .cbStruct = sizeof(wintrust_fileinfo) };
+ WINTRUST_DATA wintrust_data = {
+ .cbStruct = sizeof(wintrust_data),
+ .dwUIChoice = WTD_UI_NONE,
+ .fdwRevocationChecks = WTD_REVOKE_WHOLECHAIN,
+ .dwUnionChoice = WTD_CHOICE_FILE,
+ .dwStateAction = WTD_STATEACTION_VERIFY,
+ .pFile = &wintrust_fileinfo
+ };
+
+ (void)param;
+
+ set_status(progress, "determining paths");
+ if (!ConvertStringSecurityDescriptorToSecurityDescriptorA("O:BAD:PAI(A;;FA;;;BA)", SDDL_REVISION_1, &security_attributes.lpSecurityDescriptor, NULL))
+ goto out;
+ if (!GetWindowsDirectoryA(msi_filename, sizeof(msi_filename)) || !PathAppendA(msi_filename, "Temp"))
+ goto out;
+ if (!random_string(random_filename))
+ goto out;
+ if (!PathAppendA(msi_filename, random_filename))
+ goto out;
+
+ set_status(progress, "determining architecture");
+ arch = architecture();
+ if (!arch)
+ goto out;
+
+ set_status(progress, "connecting to server");
+ session = WinHttpOpen(L(useragent()), is_win7() ? WINHTTP_ACCESS_TYPE_DEFAULT_PROXY : WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, NULL, NULL, 0);
+ if (!session)
+ goto out;
+ WinHttpSetOption(session, WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL, &enable_http2, sizeof(enable_http2)); // Don't check return value, in case of old Windows
+ if (is_win8dotzero_or_below()) {
+ DWORD enable_tls12 = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2;
+ if (!WinHttpSetOption(session, WINHTTP_OPTION_SECURE_PROTOCOLS, &enable_tls12, sizeof(enable_tls12)))
+ goto out;
+ }
+
+ connection = WinHttpConnect(session, L(server), port, 0);
+ if (!connection)
+ goto out;
+
+ set_status(progress, "downloading installer list");
+ request = WinHttpOpenRequest(connection, L"GET", L(msi_path latest_version_file), NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_REFRESH | WINHTTP_FLAG_SECURE);
+ if (!request)
+ goto out;
+ if (!WinHttpSendRequest(request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0))
+ goto out;
+ if (!WinHttpReceiveResponse(request, NULL))
+ goto out;
+ if (!WinHttpReadData(request, buf, sizeof(buf), &bytes_read))
+ goto out;
+ WinHttpCloseHandle(request);
+ request = NULL;
+ if (bytes_read <= 0 || bytes_read >= sizeof(buf))
+ goto out;
+
+ set_status(progress, "verifying installer list");
+ memcpy(download_path, msi_path, strlen(msi_path));
+ if (!extract_newest_file(download_path + strlen(msi_path), hash, (const char *)buf, bytes_read, arch))
+ goto out;
+
+ set_status(progress, "creating temporary file");
+ filehandle = CreateFileA(msi_filename, GENERIC_WRITE | DELETE, 0, &security_attributes, CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY, NULL);
+ if (filehandle == INVALID_HANDLE_VALUE)
+ goto out;
+ msi_filename_is_set = true;
+
+ set_status(progress, "downloading installer");
+ request = WinHttpOpenRequest(connection, L"GET", L(download_path), NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE);
+ if (!request)
+ goto out;
+ if (!WinHttpSendRequest(request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0))
+ goto out;
+ if (!WinHttpReceiveResponse(request, NULL))
+ goto out;
+ bytes_read = sizeof(total_bytes_str);
+ if (!WinHttpQueryHeaders(request, WINHTTP_QUERY_CONTENT_LENGTH, WINHTTP_HEADER_NAME_BY_INDEX, total_bytes_str, &bytes_read, WINHTTP_NO_HEADER_INDEX))
+ goto out;
+ total_bytes = wcstoul(total_bytes_str, NULL, 10);
+ if (total_bytes > 100 * 1024 * 1024)
+ goto out;
+ blake2b256_init(&hasher);
+ set_progress(progress, 0, total_bytes);
+ for (current_bytes = 0;;) {
+ if (!WinHttpReadData(request, buf, 8192, &bytes_read))
+ goto out;
+ if (!bytes_read)
+ break;
+ current_bytes += bytes_read;
+ if (current_bytes > 100 * 1024 * 1024)
+ goto out;
+ blake2b256_update(&hasher, buf, bytes_read);
+ if (!WriteFile(filehandle, buf, bytes_read, &bytes_written, NULL) || bytes_read != bytes_written)
+ goto out;
+ set_progress(progress, current_bytes, total_bytes);
+ }
+
+ set_status(progress, "verifying installer");
+ blake2b256_final(&hasher, computed_hash);
+ if (memcmp(hash, computed_hash, sizeof(hash)))
+ goto out;
+ CloseHandle(filehandle); //TODO: I wish this wasn't required.
+ filehandle = INVALID_HANDLE_VALUE;
+ wintrust_fileinfo.pcwszFilePath = L(msi_filename);
+ ret = WinVerifyTrustEx(INVALID_HANDLE_VALUE, &(GUID)WINTRUST_ACTION_GENERIC_VERIFY_V2, &wintrust_data);
+ wintrust_data.dwStateAction = WTD_STATEACTION_CLOSE;
+ WinVerifyTrustEx(INVALID_HANDLE_VALUE, &(GUID)WINTRUST_ACTION_GENERIC_VERIFY_V2, &wintrust_data);
+ if (ret)
+ goto out;
+
+ set_status(progress, "launching installer");
+ ShowWindow(progress, SW_HIDE);
+ ret = MsiInstallProductA(msi_filename, NULL);
+ ret = ret == ERROR_INSTALL_USEREXIT ? ERROR_SUCCESS : ret;
+
+out:
+ if (request)
+ WinHttpCloseHandle(request);
+ if (connection)
+ WinHttpCloseHandle(connection);
+ if (session)
+ WinHttpCloseHandle(session);
+ if (security_attributes.lpSecurityDescriptor)
+ LocalFree(security_attributes.lpSecurityDescriptor);
+
+ if (ret && prompts) {
+ ShowWindow(progress, SW_SHOWDEFAULT);
+ if (MessageBoxA(progress, "Something went wrong when downloading the WireGuard installer. Would you like to open your web browser to the MSI download page?", "Download Error", MB_YESNO | MB_ICONWARNING) == IDYES)
+ ShellExecuteA(progress, NULL, "https://" server msi_path, NULL, NULL, SW_SHOWNORMAL);
+ }
+ exit(ret);
+ return ret;
+}
+
+static int cleanup(void)
+{
+ BOOL did_delete_via_handle = FALSE;
+ FILE_DISPOSITION_INFO disposition = { TRUE };
+ if (filehandle != INVALID_HANDLE_VALUE) {
+ did_delete_via_handle = SetFileInformationByHandle(filehandle, FileDispositionInfo, &disposition, sizeof(disposition));
+ CloseHandle(filehandle);
+ filehandle = INVALID_HANDLE_VALUE;
+ }
+ if (msi_filename_is_set && !did_delete_via_handle) {
+ //TODO: how does DeleteFile deal with reparse points?
+ for (int i = 0; i < 200 && !DeleteFileA(msi_filename) && GetLastError() != ERROR_FILE_NOT_FOUND; ++i)
+ Sleep(200);
+ }
+ return 0;
+}
+
+static FARPROC WINAPI delayed_load_library_hook(unsigned dliNotify, PDelayLoadInfo pdli)
+{
+ HMODULE library;
+ if (dliNotify != dliNotePreLoadLibrary)
+ return NULL;
+ library = LoadLibraryExA(pdli->szDll, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
+ if (!library)
+ abort();
+ return (FARPROC)library;
+}
+
+PfnDliHook __pfnDliNotifyHook2 = delayed_load_library_hook;
+
+static LRESULT CALLBACK wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
+{
+ (void)uIdSubclass; (void)dwRefData;
+
+ switch (uMsg) {
+ case WM_CLOSE:
+ case WM_DESTROY: {
+ LRESULT ret = DefSubclassProc(hWnd, uMsg, wParam, lParam);
+ exit(0);
+ return ret;
+ }
+ case WM_APP: if (g_total) {
+ char buf[0x1000], *start, *paren;
+ LONG_PTR current_style;
+ int chars = GetWindowTextA(progress, buf, sizeof(buf));
+ if (chars) {
+ start = buf + chars;
+ if (start[-1] == '.' && start[-2] == '.' && start[-3] == '.')
+ start -= 3;
+ else if ((paren = memchr(buf, '(', chars)) && paren > buf)
+ start = paren - 1;
+ *start = '\0';
+ _snprintf_s(start, sizeof(buf) - (start - buf), _TRUNCATE, " (%.2f%%)", g_current * 100.0f / g_total);
+ SetWindowTextA(progress, buf);
+ }
+ current_style = GetWindowLongPtrA(progress, GWL_STYLE);
+ if (current_style & PBS_MARQUEE) {
+ SetWindowLongPtrA(progress, GWL_STYLE, current_style & ~PBS_MARQUEE);
+ SendMessageA(progress, PBM_SETMARQUEE, FALSE, 0);
+ }
+ SendMessageA(progress, PBM_SETRANGE32, 0, (LPARAM)g_total);
+ SendMessageA(progress, PBM_SETPOS, (WPARAM)g_current, 0);
+ break;
+ }
+ }
+ return DefSubclassProc(hWnd, uMsg, wParam, lParam);
+}
+
+static void parse_command_line(void)
+{
+ LPWSTR *argv;
+ int argc;
+ argv = CommandLineToArgvW(GetCommandLineW(), &argc);
+ if (!argv)
+ return;
+ for (int i = 1; i < argc; ++i) {
+ if (wcsicmp(argv[i], L"/noprompt") == 0)
+ prompts = false;
+ }
+ LocalFree(argv);
+}
+
+int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pCmdLine, int nCmdShow)
+{
+ MSG msg;
+ HICON icon;
+ HDC dc;
+ float scale;
+
+ (void)hPrevInstance; (void)pCmdLine; (void)nCmdShow;
+
+ if (!SetDllDirectoryA("") || !SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32))
+ return 1;
+
+ parse_command_line();
+
+ InitCommonControlsEx(&(INITCOMMONCONTROLSEX){ .dwSize = sizeof(INITCOMMONCONTROLSEX), .dwICC = ICC_PROGRESS_CLASS });
+
+ progress = CreateWindowExA(0, PROGRESS_CLASS, "WireGuard Installer",
+ (WS_OVERLAPPEDWINDOW & ~(WS_BORDER | WS_THICKFRAME | WS_MAXIMIZEBOX)) | PBS_MARQUEE | PBS_SMOOTH,
+ CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
+ NULL, NULL, hInstance, NULL);
+ SetWindowSubclass(progress, wndproc, 0, 0);
+ dc = GetDC(progress);
+ scale = GetDeviceCaps(dc, LOGPIXELSY) / 96.0f;
+ ReleaseDC(progress, dc);
+ icon = LoadIconA(hInstance, MAKEINTRESOURCE(7));
+ SendMessageA(progress, WM_SETICON, ICON_BIG, (LPARAM)icon);
+ SendMessageA(progress, WM_SETICON, ICON_SMALL, (LPARAM)icon);
+ SendMessageA(progress, PBM_SETMARQUEE, TRUE, 0);
+ SetWindowPos(progress, HWND_TOPMOST, -1, -1, 500 * scale, 80 * scale, SWP_NOMOVE | SWP_SHOWWINDOW);
+
+ _onexit(cleanup);
+ CreateThread(NULL, 0, download_thread, NULL, 0, NULL);
+
+ while (GetMessage(&msg, NULL, 0, 0)) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ return 0;
+}
diff --git a/installer/fetcher/filelist.c b/installer/fetcher/filelist.c
new file mode 100644
index 00000000..f938e324
--- /dev/null
+++ b/installer/fetcher/filelist.c
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020-2022 Jason A. Donenfeld. All Rights Reserved.
+ */
+
+#include "constants.h"
+#include "crypto.h"
+#include "filelist.h"
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static inline int decode_base64(const char src[static 4])
+{
+ int val = 0;
+
+ for (unsigned int i = 0; i < 4; ++i)
+ val |= (-1
+ + ((((('A' - 1) - src[i]) & (src[i] - ('Z' + 1))) >> 8) & (src[i] - 64))
+ + ((((('a' - 1) - src[i]) & (src[i] - ('z' + 1))) >> 8) & (src[i] - 70))
+ + ((((('0' - 1) - src[i]) & (src[i] - ('9' + 1))) >> 8) & (src[i] + 5))
+ + ((((('+' - 1) - src[i]) & (src[i] - ('+' + 1))) >> 8) & 63)
+ + ((((('/' - 1) - src[i]) & (src[i] - ('/' + 1))) >> 8) & 64)
+ ) << (18 - 6 * i);
+ return val;
+}
+
+bool signify_pubkey_from_base64(uint8_t key[static 42], const char base64[static 56])
+{
+ unsigned int i;
+ volatile uint8_t ret = 0;
+ int val;
+
+ for (i = 0; i < 42 / 3; ++i) {
+ val = decode_base64(&base64[i * 4]);
+ ret |= (uint32_t)val >> 31;
+ key[i * 3 + 0] = (val >> 16) & 0xff;
+ key[i * 3 + 1] = (val >> 8) & 0xff;
+ key[i * 3 + 2] = val & 0xff;
+ }
+
+ return 1 & ((ret - 1) >> 8);
+}
+
+bool signify_signature_from_base64(uint8_t sig[static 74], const char base64[static 100])
+{
+ unsigned int i;
+ volatile uint8_t ret = 0;
+ int val;
+
+ if (base64[99] != '=')
+ return false;
+
+ for (i = 0; i < 74 / 3; ++i) {
+ val = decode_base64(&base64[i * 4]);
+ ret |= (uint32_t)val >> 31;
+ sig[i * 3 + 0] = (val >> 16) & 0xff;
+ sig[i * 3 + 1] = (val >> 8) & 0xff;
+ sig[i * 3 + 2] = val & 0xff;
+ }
+ val = decode_base64((const char[]){ base64[i * 4 + 0], base64[i * 4 + 1], base64[i * 4 + 2], 'A' });
+ ret |= ((uint32_t)val >> 31) | (val & 0xff);
+ sig[i * 3 + 0] = (val >> 16) & 0xff;
+ sig[i * 3 + 1] = (val >> 8) & 0xff;
+
+ return 1 & ((ret - 1) >> 8);
+}
+
+bool hash_from_hex(uint8_t hash[static 32], const char hex[static 64])
+{
+ uint8_t c, c_acc, c_alpha0, c_alpha, c_num0, c_num, c_val;
+ volatile uint8_t ret = 0;
+
+ for (unsigned int i = 0; i < 64; i += 2) {
+ c = (uint8_t)hex[i];
+ c_num = c ^ 48U;
+ c_num0 = (c_num - 10U) >> 8;
+ c_alpha = (c & ~32U) - 55U;
+ c_alpha0 = ((c_alpha - 10U) ^ (c_alpha - 16U)) >> 8;
+ ret |= ((c_num0 | c_alpha0) - 1) >> 8;
+ c_val = (c_num0 & c_num) | (c_alpha0 & c_alpha);
+ c_acc = c_val * 16U;
+
+ c = (uint8_t)hex[i + 1];
+ c_num = c ^ 48U;
+ c_num0 = (c_num - 10U) >> 8;
+ c_alpha = (c & ~32U) - 55U;
+ c_alpha0 = ((c_alpha - 10U) ^ (c_alpha - 16U)) >> 8;
+ ret |= ((c_num0 | c_alpha0) - 1) >> 8;
+ c_val = (c_num0 & c_num) | (c_alpha0 & c_alpha);
+ hash[i / 2] = c_acc | c_val;
+ }
+
+ return 1 & ((ret - 1) >> 8);
+}
+
+static uint64_t parse_version(const char *str, size_t len)
+{
+ uint64_t version = 0;
+ unsigned long nibble;
+ const char *limit = str + len;
+ char *end;
+
+ for (int shift = 64 - 16; shift >= 0; shift -= 16, str = end + 1) {
+ if (str[0] == '-' || str[0] == '+')
+ return 0;
+ nibble = strtoul(str, &end, 10);
+ if (nibble > UINT16_MAX)
+ return 0;
+ version |= (uint64_t)nibble << shift;
+ if (end >= limit)
+ break;
+ if (end[0] != '.')
+ return 0;
+ }
+ return version;
+}
+
+bool extract_newest_file(char filename[static MAX_FILENAME_LEN], uint8_t hash[static 32], const char *list, size_t len, const char *arch)
+{
+ const char *first_nl, *second_nl, *line_start, *line_end;
+ char msi_prefix[sizeof(msi_arch_prefix) + 10];
+ int msi_prefix_len;
+ uint8_t pubkey[42], signature[74];
+ uint64_t biggest_version = 0, version;
+
+ if ((msi_prefix_len = _snprintf_s(msi_prefix, sizeof(msi_prefix), _TRUNCATE, msi_arch_prefix, arch)) < 0)
+ return false;
+ if (!signify_pubkey_from_base64(pubkey, release_public_key_base64))
+ return false;
+ first_nl = memchr(list, '\n', len);
+ if (!first_nl)
+ return false;
+ second_nl = memchr(first_nl + 1, '\n', len - (first_nl + 1 - list));
+ if (!second_nl)
+ return false;
+ if (len < 19 || memcmp(list, "untrusted comment: ", 19))
+ return false;
+ if (second_nl - first_nl != 101)
+ return false;
+ if (!signify_signature_from_base64(signature, first_nl + 1))
+ return false;
+ if (memcmp(pubkey, signature, 10))
+ return false;
+ if (!ed25519_verify(signature + 10, pubkey + 10, second_nl + 1, len - (second_nl + 1 - list)))
+ return false;
+ for (line_start = second_nl + 1; line_start < list + len; line_start = line_end + 1) {
+ line_end = memchr(line_start + 1, '\n', len - (line_start + 1 - list));
+ if (!line_end)
+ break;
+ if ((size_t)(line_end - line_start) < (64 + 2 + msi_prefix_len + strlen(msi_suffix) + 1) || line_start[64] != ' ' || line_start[65] != ' ')
+ continue;
+ if (memcmp(msi_prefix, line_start + 66, msi_prefix_len) || memcmp(msi_suffix, line_end - strlen(msi_suffix), strlen(msi_suffix)))
+ continue;
+ if (line_end - line_start - 66 > MAX_FILENAME_LEN - 1)
+ continue;
+ version = parse_version(line_start + 66 + msi_prefix_len, line_end - strlen(msi_suffix) - line_start - 66 - msi_prefix_len);
+ if (version < biggest_version)
+ continue;
+ if (!hash_from_hex(hash, line_start))
+ continue;
+ memcpy(filename, line_start + 66, line_end - line_start - 66);
+ filename[line_end - line_start - 66] = '\0';
+ biggest_version = version;
+ }
+ return biggest_version > 0;
+}
diff --git a/installer/fetcher/filelist.h b/installer/fetcher/filelist.h
new file mode 100644
index 00000000..7da57470
--- /dev/null
+++ b/installer/fetcher/filelist.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright (C) 2020-2022 Jason A. Donenfeld. All Rights Reserved.
+ */
+
+#ifndef _FILELIST_H
+#define _FILELIST_H
+
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+enum { MAX_FILENAME_LEN = 0x400 };
+
+bool extract_newest_file(char filename[static MAX_FILENAME_LEN], uint8_t hash[static 32], const char *list, size_t len, const char *arch);
+
+#endif
diff --git a/installer/fetcher/icon.svg b/installer/fetcher/icon.svg
new file mode 100644
index 00000000..06a2db5c
--- /dev/null
+++ b/installer/fetcher/icon.svg
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="478.99808"
+ height="478.99808"
+ version="1.1"
+ viewBox="0 0 478.99808 478.99808"
+ xml:space="preserve"
+ id="svg35"
+ sodipodi:docname="wireguard-install.svg"
+ inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)"><metadata
+ id="metadata39"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1023"
+ id="namedview37"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="1.2639534"
+ inkscape:cx="246.80998"
+ inkscape:cy="256.18157"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg35" /><defs
+ id="defs5"><clipPath
+ id="a"><path
+ d="M 0,300 H 300 V 0 H 0 Z"
+ id="path2" /></clipPath></defs><g
+ id="g2416"
+ transform="scale(1.3897777)"><g
+ transform="matrix(1.1488658,0,0,-1.1488658,0,344.65974)"
+ id="g33"><g
+ clip-path="url(#a)"
+ id="g31"><g
+ transform="translate(177.57,268.56)"
+ id="g9"><path
+ d="M 0,0 C 0.969,-0.066 2.097,0.81 3.987,1.635 2.125,2.437 1.014,3.29 0.048,3.228 -0.948,3.165 -2.544,2.197 -2.519,1.65 -2.492,1.047 -0.987,0.067 0,0"
+ fill="#871719"
+ id="path7" /></g><g
+ transform="translate(179.32,268.11)"
+ id="g13"><path
+ d="M 0,0 C 0.969,-0.066 2.097,0.81 3.987,1.635 2.125,2.437 1.014,3.29 0.048,3.228 -0.948,3.165 -2.544,2.197 -2.519,1.65 -2.492,1.047 -0.987,0.067 0,0"
+ fill="#871719"
+ id="path11" /></g><g
+ transform="translate(299.74,154.44)"
+ id="g17"><path
+ d="m 0,0 c 0,0 6.94,145.56 -153.04,145.56 -141.48,0 -145.9,-139.63 -145.9,-139.63 0,0 -20.811,-160.37 149.16,-160.37 C 13.24,-154.44 0,0 0,0"
+ fill="#871719"
+ id="path15" /></g><g
+ transform="translate(133.86,128.17)"
+ id="g21"><path
+ d="m 0,0 c -2.627,-1.39 -4.65,-2.414 -6.63,-3.517 -8.1,-4.512 -15.026,-10.419 -20.544,-17.868 -1.784,-2.409 -3.01,-2.603 -5.727,-0.941 -35.338,21.61 -37.609,75.843 0.983,99.453 C -1.901,95.491 36.447,84.267 50.817,56.65 53.54,51.416 53.886,43.359 52.162,37.868 46.207,18.913 32.147,8.282 12.849,3.766 18.538,8.636 23.067,14.159 24.508,21.791 25.96,29.478 24.424,36.429 19.966,42.747 13.193,52.343 0.098,56.291 -10.845,52.136 -22.726,47.625 -29.235,36.782 -28.061,23.453 -26.971,11.072 -17.577,3.048 0,0"
+ fill="#ffffff"
+ id="path19" /></g><g
+ transform="translate(58.513,66.293)"
+ id="g25"><path
+ d="M 0,0 C 2.838,19.152 25.265,36.788 44.23,34.776 38.356,26.832 35.643,17.846 34.988,8.883 28.686,7.722 22.747,6.941 16.981,5.478 11.304,4.037 5.803,1.903 0,0"
+ fill="#ffffff"
+ id="path23" /></g><g
+ transform="translate(183.79,273.09)"
+ id="g29"><path
+ d="M 0,0 C 1.061,0.812 2.155,1.494 3.472,0.408 4.222,-0.209 4.95,-0.849 5.858,-1.624 4.731,-2.219 3.816,-2.72 2.883,-3.191 1.577,-3.849 0.601,-3.409 -0.189,-2.369 -0.831,-1.525 -0.946,-0.724 0,0 m 15.447,-157.8 c -1.598,1.382 -2.611,1.381 -4.485,0.182 -6.359,-4.068 -12.867,-7.922 -19.481,-11.562 -3.792,-2.086 -7.898,-3.599 -12.653,-5.724 1.633,-0.421 2.418,-0.619 3.201,-0.827 17.776,-4.73 27.272,-20.335 23.065,-37.813 -3.741,-15.544 -19.52,-25.482 -34.812,-22.86 -12.748,2.186 -23.877,12.772 -25.735,25.456 -2.026,13.824 4.859,27.119 17.108,32.689 6.794,3.089 13.771,5.778 20.549,8.9 7.706,3.551 16.038,6.355 22.766,11.296 16.7,12.262 27.012,29.145 31.033,49.523 2.408,12.207 2.245,24.36 -3.339,35.95 -4.286,8.895 -11.319,15.357 -18.875,21.253 -7.775,6.068 -16.007,11.554 -23.747,17.664 -2.095,1.653 -3.509,4.505 -4.478,7.09 -0.411,1.095 0.925,4.066 1.819,4.227 4.746,0.852 9.596,1.29 14.425,1.473 5.574,0.21 11.164,0.032 16.746,-0.042 1.21,-0.015 2.853,0.141 3.549,-0.542 2.891,-2.843 5.159,-1.014 7.166,0.856 1.689,1.573 2.893,3.668 4.236,5.433 -0.815,0.12 -2.487,0.541 -4.168,0.581 -5.613,0.133 -11.233,0.047 -16.843,0.253 -1,0.037 -1.963,1.066 -2.942,1.637 1.031,0.409 2.058,1.165 3.093,1.175 9.682,0.091 19.366,0.054 29.057,0.054 C 41.713,-6.44 34.98,0.458 28.998,2.328 28.953,1.646 28.911,1.011 28.867,0.334 22.923,0.193 17.089,0.304 11.789,3.122 10.393,3.865 9.48,5.516 8.343,6.749 6.912,8.3 5.738,10.296 3.994,11.308 c -3.576,2.076 -7.48,3.58 -11.211,5.397 -13.259,6.458 -27.262,6.231 -42.302,4.854 8.991,-2.092 17.11,-3.982 25.23,-5.872 -0.093,-0.494 -0.185,-0.987 -0.278,-1.481 -10.86,-1.455 -21.134,2.528 -31.756,4.003 3.849,-2.254 7.749,-4.35 11.778,-6.158 4.095,-1.837 8.316,-3.39 12.538,-5.091 -5.364,-4.583 -10.746,-5.588 -17.488,-4.048 -3.686,0.842 -7.585,1.29 -11.348,1.106 -3.887,-0.19 -7.802,-1.147 -11.332,-3.506 3.78,-1.916 7.263,-3.506 10.549,-5.432 1.355,-0.795 2.909,-2.144 3.287,-3.536 0.904,-3.333 1.166,-6.841 1.687,-10.281 -6.188,-0.701 -17.071,-6.994 -19.27,-11.09 9.512,-1.831 19.868,0.383 28.942,-5.746 -2.989,-2.262 -9.949,-5.075 -12.502,-7.007 3.156,-0.827 10.469,-0.423 13.33,-0.229 2.409,0.164 3.521,0.223 4.508,-0.59 l 28.001,-21.921 c 2.944,-2.374 14.835,-13.629 17.939,-20.704 2.643,-6.023 2.966,-11.148 2.965,-12.398 -0.002,-3.355 -0.413,-8.609 -2.721,-14.469 -0.969,-2.461 -3.812,-7.912 -9.677,-14.267 -9.09,-9.847 -20.783,-15.17 -33.57,-17.807 -29.732,-6.13 -54.436,-37.881 -47.462,-72.884 8.142,-40.866 53.247,-62.991 90.107,-43.552 23.824,12.564 36.456,37.078 33.072,63.762 -2.045,16.12 -9.338,29.269 -21.563,39.839"
+ fill="#ffffff"
+ id="path27" /></g></g></g><image
+ width="292.86456"
+ height="292.86456"
+ preserveAspectRatio="none"
+ style="image-rendering:optimizeQuality"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAABGdBTUEAALGPC/xhBQAAACBjSFJN AAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAACA AElEQVR42uz9d7xl2VnfCX+ftdbe+8Qb694KXVVd1Tm3pFZGiSwQYMwYp9cYDzjwGgcMg2EMJth4 7Hk9tsf2MGN7xtiYDxjLwGCShBAogKRuhVZnde6qrurKN5+0917h/WOtfe6pUrfULbUQtO/Tn+p7 7sn3nPXk3/N7YE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2 ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2 ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2 ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE/2ZE9epMiX+w3syZdOQgjT71dEwpXX/VGV5r1e+bc83/V78sWJ +nK/gT354qVR6tmfzeVZxf/joPwv9PfsKf+XRv5YHIg9efFypdd/mZRegPAC13PFbS903y/8xdPf MWvM9gzCyyPmy/0G9uSly6x3/1wK/hKVv1HcWQWeVfDne64r7/f5rpvK+94Pw6+B1wGHfeChT98j cBCRqy97Ve8f4dH7Py1AeDgEeQL4xB/2B/4Klr0I4I+hNB7wZfDuL0XZX+SbQ9Kjw+lnnuH9J09y 5B3vkDdPCJ86iegbCRsnT/AxuxLm6nU5wJFQmZNy5tJFRN3OcH0o9XgQCIFibim054Ign+HY0vFQ 16t8183R8//rJ4P8zetmooAQhL2o4CXLngH4YyxfpAF4MQovV9z+Wd7dpuu1LWV9s+A9z3je8Ton Zx7S4eKx4L+pb6ZK+Y//YF2rVVVcunip3Ue36mCyatLNLGvGZqKd0tqi1AKirNQyFpE8qCBOghtY r9sdmxWlbVmx1cTVstydDDYH9Wpbxj/6FdeMvtzfxx9H2TMAf0zk+bz+S4gCns/Tf677fc7nHG7C dgv6k0ucWtiHs6i1hx5jePSYfddyKwD8w48/k1eDyQFX6v2Vby0Hq5fWd3aOjDd2rrEM9itll7V2 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 me8hWHTJHr9/WifF/vpvYw3D6aHRB650ArDYKQzlyBbxDuZvG943b/Bm7jn13HvdKyVIa2gM6f75 MHne8GHWuJm7voi/v6gJZ7hTL3I286+dXheu3OWHty26Pv8Z59dnZPzpi91f/+0utcfv85oDcOUu vYh3YBbclm5n7jEwG1XArANYFA0MnRfMOoR54wlXuW2R8Q8fu2iHn08xFoXqV3sMzDoABo+df/8s uL7o99/32ncA/20vdQ337XV5hd4AVzqF4eP0Ho/b67nS7WHB61+tjXZ4W7p8vt11L3BtkUHPP254 /6L79nrtvX6/2tp3APvrD2Vdy3lwNYGL+VB9eH2eoPR8j12EAXw67zPhBH7utkWGlvLsa2HOhWu4 np5zr9edf324NqP+Azf8T+cL3V/7C65ujMnoFjmHvUBHrvL4vW6fxwAWrWvJkfeKFJ7vvs/k/qsZ 7x+aYV/r2ncA++szWc+XOswDjVf7m+dzBs/3Pj7THfTTAdOezwFcy3M83337a3/tr/21v/bX/tpf +2t/7a/9tb/21/7aX/trf+2v/bW/9tf+2l/7a3/tr/21v/bX/tpf+2t/7a/9tb/21/7aX/trf+2v /bW/9tf+2l/7a3/tr/21v/bX/tpf+2t/7a/9tb/21/7aX/trf+2v/bW/9tf+2l/7a3/tr/21v/bX /tpf+2t/7a/9tb/21/7aX/trf+2v/bW/9tf+2l/7a3/tr/21v/bX/tpf+2t/7a/9tb/21/7aX/tr f+2v/bW/9tf+2l/7a3/tr8/u9f8Hy0CzRRKiMyQAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjAtMTEt MTJUMTU6MjQ6MjcrMDA6MDDoOkHhAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIwLTExLTEyVDE1OjI0 OjI3KzAwOjAwmWf5XQAAAABJRU5ErkJggg== "
+ id="image1086"
+ x="47.787258"
+ y="85.720528" /></g></svg>
diff --git a/installer/fetcher/manifest.xml b/installer/fetcher/manifest.xml
new file mode 100644
index 00000000..92205f98
--- /dev/null
+++ b/installer/fetcher/manifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="wireguard-installer" type="win32" />
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <!-- Windows 10 -->
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
+ <!-- Windows 8.1 -->
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
+ <!-- Windows 8 -->
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
+ <!-- Windows 7 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
+ </application>
+ </compatibility>
+ <dependency>
+ <dependentAssembly>
+ <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*" />
+ </dependentAssembly>
+ </dependency>
+ <application xmlns="urn:schemas-microsoft-com:asm.v3">
+ <windowsSettings>
+ <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
+ <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True</dpiAware>
+ </windowsSettings>
+ </application>
+ <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+ <security>
+ <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
+ <requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
+ </requestedPrivileges>
+ </security>
+ </trustInfo>
+</assembly>
diff --git a/installer/fetcher/resources.rc b/installer/fetcher/resources.rc
new file mode 100644
index 00000000..f2bedb66
--- /dev/null
+++ b/installer/fetcher/resources.rc
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright (C) 2020-2022 Jason A. Donenfeld. All Rights Reserved.
+ */
+
+#include <windows.h>
+#include "version.h"
+
+#pragma code_page(65001) // UTF-8
+
+LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
+CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST manifest.xml
+7 ICON icon.ico
+
+VS_VERSION_INFO VERSIONINFO
+FILEVERSION VERSION_ARRAY
+PRODUCTVERSION VERSION_ARRAY
+FILEOS VOS_NT_WINDOWS32
+FILETYPE VFT_APP
+FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "CompanyName", "WireGuard LLC"
+ VALUE "FileDescription", "WireGuard Installer: Fast, Modern, Secure VPN Tunnel"
+ VALUE "FileVersion", VERSION_STR
+ VALUE "InternalName", "wireguard-installer"
+ VALUE "LegalCopyright", "Copyright © 2015-2022 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved."
+ VALUE "OriginalFilename", "wireguard-installer.exe"
+ VALUE "ProductName", "WireGuard"
+ VALUE "ProductVersion", VERSION_STR
+ VALUE "Comments", "https://www.wireguard.com/"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 0x4b0
+ END
+END
diff --git a/installer/fetcher/systeminfo.c b/installer/fetcher/systeminfo.c
new file mode 100644
index 00000000..f1f80e7f
--- /dev/null
+++ b/installer/fetcher/systeminfo.c
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020-2022 Jason A. Donenfeld. All Rights Reserved.
+ */
+
+#include "systeminfo.h"
+#include "version.h"
+#include <windows.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+extern NTAPI __declspec(dllimport) void RtlGetNtVersionNumbers(DWORD *MajorVersion, DWORD *MinorVersion, DWORD *BuildNumber);
+
+const char *architecture(void)
+{
+ static const char *cached_arch;
+ HMODULE kernel32;
+ BOOL(WINAPI *IsWow64Process2)(HANDLE hProcess, USHORT *pProcessMachine, USHORT *pNativeMachine);
+ USHORT process_machine, native_machine;
+ BOOL is_wow64_process;
+
+ if (cached_arch)
+ return cached_arch;
+
+ kernel32 = GetModuleHandleA("kernel32.dll");
+ if (!kernel32)
+ return NULL;
+ *(FARPROC *)&IsWow64Process2 = GetProcAddress(kernel32, "IsWow64Process2");
+ if (IsWow64Process2) {
+ if (!IsWow64Process2(GetCurrentProcess(), &process_machine, &native_machine))
+ return NULL;
+ switch (native_machine) {
+ case IMAGE_FILE_MACHINE_I386:
+ return cached_arch = "x86";
+ case IMAGE_FILE_MACHINE_AMD64:
+ return cached_arch = "amd64";
+ case IMAGE_FILE_MACHINE_ARMNT:
+ return cached_arch = "arm";
+ case IMAGE_FILE_MACHINE_ARM64:
+ return cached_arch = "arm64";
+ }
+ } else {
+ if (!IsWow64Process(GetCurrentProcess(), &is_wow64_process))
+ return NULL;
+ return cached_arch = is_wow64_process ? "amd64" : "x86";
+ }
+ return NULL;
+}
+
+const char *useragent(void)
+{
+ static char useragent[0x200];
+ DWORD maj, min, build;
+
+ if (useragent[0])
+ return useragent;
+ RtlGetNtVersionNumbers(&maj, &min, &build);
+ _snprintf_s(useragent, sizeof(useragent), _TRUNCATE, "WireGuard-Fetcher/" VERSION_STR " (Windows %lu.%lu.%lu; %s)", maj, min, build & 0xffff, architecture());
+ return useragent;
+}
+
+bool is_win7(void)
+{
+ DWORD maj, min, build;
+ RtlGetNtVersionNumbers(&maj, &min, &build);
+ return maj == 6 && min == 1;
+}
+
+bool is_win8dotzero_or_below(void)
+{
+ DWORD maj, min, build;
+ RtlGetNtVersionNumbers(&maj, &min, &build);
+ return maj == 6 && min <= 2;
+}
diff --git a/installer/fetcher/systeminfo.h b/installer/fetcher/systeminfo.h
new file mode 100644
index 00000000..2c06a166
--- /dev/null
+++ b/installer/fetcher/systeminfo.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright (C) 2020-2022 Jason A. Donenfeld. All Rights Reserved.
+ */
+
+#ifndef _SYSTEMINFO_H
+#define _SYSTEMINFO_H
+
+#include <stdbool.h>
+
+const char *architecture(void);
+const char *useragent(void);
+bool is_win7(void);
+bool is_win8dotzero_or_below(void);
+
+#endif
diff --git a/installer/fetcher/version.h b/installer/fetcher/version.h
new file mode 100644
index 00000000..dd6f54d1
--- /dev/null
+++ b/installer/fetcher/version.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright (C) 2020-2022 Jason A. Donenfeld. All Rights Reserved.
+ */
+
+#ifndef _VERSION_H
+#define _VERSION_H
+
+#define VERSION_STR "1.0"
+#define VERSION_ARRAY 1,0,0,0
+
+#endif
diff --git a/installer/wireguard.wxs b/installer/wireguard.wxs
index 5bbb1ebb..ab90d3cd 100644
--- a/installer/wireguard.wxs
+++ b/installer/wireguard.wxs
@@ -2,18 +2,22 @@
<!--
SPDX-License-Identifier: GPL-2.0
- Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
-->
-<?if $(var.WIREGUARD_PLATFORM) = "x86"?>
- <?define PlatformProgramFilesFolder = "ProgramFilesFolder"?>
-<?else?>
+<?if $(var.WIREGUARD_PLATFORM) = "amd64" Or $(var.WIREGUARD_PLATFORM) = "arm64"?>
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder"?>
+<?else?>
+ <?define PlatformProgramFilesFolder = "ProgramFilesFolder"?>
<?endif?>
<?if $(var.WIREGUARD_PLATFORM) = "amd64"?>
<?define UpgradeCode = "5e5a1da5-ba36-404d-92ec-41050d1c799c"?>
<?elseif $(var.WIREGUARD_PLATFORM) = "x86"?>
<?define UpgradeCode = "62754a0a-fee9-4412-b739-e8da2e7c9405"?>
+<?elseif $(var.WIREGUARD_PLATFORM) = "arm"?>
+ <?define UpgradeCode = "f90bca59-9627-431d-92b4-a5c2d9a529ff"?>
+<?elseif $(var.WIREGUARD_PLATFORM) = "arm64"?>
+ <?define UpgradeCode = "7ff76099-8940-4d3e-99b9-50a3b3ca1ee9"?>
<?else?>
<?error Unknown platform ?>
<?endif?>
@@ -27,7 +31,7 @@
Manufacturer="WireGuard LLC"
UpgradeCode="$(var.UpgradeCode)">
<Package
- InstallerVersion="400"
+ InstallerVersion="500"
Compressed="yes"
InstallScope="perMachine"
Description="WireGuard: Fast, Modern, Secure VPN Tunnel"
@@ -53,7 +57,8 @@
AllowDowngrades="no"
AllowSameVersionUpgrades="yes"
DowngradeErrorMessage="A newer version of [ProductName] is already installed."
- Schedule="afterInstallExecute" />
+ Schedule="afterInstallExecute"
+ IgnoreRemoveFailure="yes" />
<!--
Folders
@@ -63,10 +68,6 @@
<Directory Id="WireGuardFolder" Name="WireGuard" />
</Directory>
<Directory Id="ProgramMenuFolder" />
- <Directory Id="SystemFolder" />
- <?if $(var.WIREGUARD_PLATFORM) != "x86"?>
- <Directory Id="System64Folder" />
- <?endif?>
</Directory>
<!--
@@ -79,32 +80,26 @@
</File>
<ServiceControl Id="DummyService.3AA0C492_29F4_4342_B608_DB95B2DECB13" Name="DummyService.3AA0C492_29F4_4342_B608_DB95B2DECB13" /><!-- A dummy to make WiX create ServiceControl table for us. -->
</Component>
- <Component Directory="SystemFolder" Win64="no" Id="Wg32Executable" Guid="5ca31841-97d8-4614-a318-f1e268135ba7">
- <File Source="..\x86\wg.exe" Id="Wg32Executable" />
- </Component>
- <?if $(var.WIREGUARD_PLATFORM) != "x86"?>
- <Component Directory="System64Folder" Win64="yes" Id="Wg64Executable" Guid="d9b494ec-0959-442c-89ad-6aa175acfd03">
- <File Source="..\$(var.WIREGUARD_PLATFORM)\wg.exe" Id="Wg64Executable" />
+ <Component Directory="WireGuardFolder" Id="WgExecutable" Guid="540cf446-fcc3-4452-b9fb-eb4c02780251">
+ <File Source="..\$(var.WIREGUARD_PLATFORM)\wg.exe" KeyPath="yes" />
+ <Environment Id="PATH" Name="PATH" System="yes" Action="set" Part="last" Permanent="no" Value="[WireGuardFolder]" />
</Component>
- <?endif?>
</ComponentGroup>
<!--
- Merge modules
- -->
- <DirectoryRef Id="WireGuardFolder">
- <Merge Id="WintunMergeModule" Language="0" DiskId="1" SourceFile=".deps\wintun-$(var.WIREGUARD_PLATFORM).msm" />
- </DirectoryRef>
-
- <!--
Features
-->
<Feature Id="WireGuardFeature" Title="WireGuard" Level="1">
<ComponentGroupRef Id="WireGuardComponents" />
</Feature>
- <Feature Id="WintunFeature" Title="Wintun" Level="1">
- <MergeRef Id="WintunMergeModule" />
- </Feature>
+
+ <!--
+ Abort early if running under Wow64
+ -->
+ <CustomAction Id="CheckWow64" BinaryKey="customactions.dll" DllEntry="CheckWow64" />
+ <InstallExecuteSequence>
+ <Custom Action="CheckWow64" After="FindRelatedProducts">NOT REMOVE</Custom>
+ </InstallExecuteSequence>
<!--
Evaluate WireGuard services and populate ServiceControl table
@@ -115,11 +110,19 @@
</InstallExecuteSequence>
<!--
- Clear out our config folder on uninstall
+ Launch wireguard.exe on product reconfiguration (starting same MSI again)
-->
- <CustomAction Id="RemoveConfigFolder" BinaryKey="customactions.dll" DllEntry="RemoveConfigFolder" Execute="deferred" Impersonate="no" />
+ <CustomAction Id="LaunchApplicationAndAbort" BinaryKey="customactions.dll" DllEntry="LaunchApplicationAndAbort" />
+ <InstallExecuteSequence>
+ <Custom Action="LaunchApplicationAndAbort" After="CostFinalize">ProductState=5 AND NOT REMOVE AND NOT DO_NOT_LAUNCH</Custom>
+ </InstallExecuteSequence>
+
+ <!--
+ Evaluate WireGuard components
+ -->
+ <CustomAction Id="EvaluateWireGuardComponents" BinaryKey="customactions.dll" DllEntry="EvaluateWireGuardComponents" />
<InstallExecuteSequence>
- <Custom Action="RemoveConfigFolder" After="DeleteServices">(NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")</Custom>
+ <Custom Action="EvaluateWireGuardComponents" After="ProcessComponents" />
</InstallExecuteSequence>
<!--
@@ -131,19 +134,27 @@
</InstallExecuteSequence>
<!--
- Launch wireguard.exe after setup complete
+ Clear out our config folder on uninstall
-->
- <CustomAction Id="LaunchApplication" HideTarget="yes" Impersonate="no" Execute="deferred" FileKey="wireguard.exe" ExeCommand="" Return="asyncNoWait" />
+ <CustomAction Id="RemoveConfigFolder" BinaryKey="customactions.dll" DllEntry="RemoveConfigFolder" Execute="deferred" Impersonate="no" />
<InstallExecuteSequence>
- <Custom Action="LaunchApplication" Before="InstallFinalize">(&amp;WireGuardFeature = 3) AND NOT DO_NOT_LAUNCH</Custom>
+ <Custom Action="RemoveConfigFolder" After="DeleteServices" />
</InstallExecuteSequence>
<!--
- Launch wireguard.exe on product reconfiguration (starting same MSI again)
+ Clear out our adapters on uninstall
-->
- <CustomAction Id="LaunchApplicationAsOrdinaryUser" HideTarget="yes" FileKey="wireguard.exe" ExeCommand="" Return="asyncNoWait" />
+ <CustomAction Id="RemoveAdapters" BinaryKey="customactions.dll" DllEntry="RemoveAdapters" Execute="deferred" Impersonate="no" />
<InstallExecuteSequence>
- <Custom Action="LaunchApplicationAsOrdinaryUser" After="InstallFinalize">(&amp;WireGuardFeature = -1) AND (!WireGuardFeature = 3) AND NOT DO_NOT_LAUNCH</Custom>
+ <Custom Action="RemoveAdapters" Before="RemoveFiles" />
+ </InstallExecuteSequence>
+
+ <!--
+ Launch wireguard.exe after setup complete
+ -->
+ <CustomAction Id="LaunchApplication" HideTarget="yes" Impersonate="no" Execute="deferred" FileKey="wireguard.exe" ExeCommand="" Return="asyncNoWait" />
+ <InstallExecuteSequence>
+ <Custom Action="LaunchApplication" Before="InstallFinalize">(&amp;WireGuardFeature = 3) AND NOT DO_NOT_LAUNCH</Custom>
</InstallExecuteSequence>
</Product>
</Wix>
diff --git a/l18n/l18n.go b/l18n/l18n.go
new file mode 100644
index 00000000..512fe2a2
--- /dev/null
+++ b/l18n/l18n.go
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package l18n
+
+import (
+ "sync"
+
+ "golang.org/x/sys/windows"
+ "golang.org/x/text/language"
+ "golang.org/x/text/message"
+)
+
+var (
+ printer *message.Printer
+ printerLock sync.Mutex
+)
+
+// prn returns the printer for user preferred UI language.
+func prn() *message.Printer {
+ if printer != nil {
+ return printer
+ }
+ printerLock.Lock()
+ if printer != nil {
+ printerLock.Unlock()
+ return printer
+ }
+ printer = message.NewPrinter(lang())
+ printerLock.Unlock()
+ return printer
+}
+
+// lang returns the user preferred UI language we have most confident translation in the default catalog available.
+func lang() (tag language.Tag) {
+ tag = language.English
+ confidence := language.No
+ languages, err := windows.GetUserPreferredUILanguages(windows.MUI_LANGUAGE_NAME)
+ if err != nil {
+ return
+ }
+ for i := range languages {
+ t, _, c := message.DefaultCatalog.Matcher().Match(message.MatchLanguage(languages[i]))
+ if c > confidence {
+ tag = t
+ confidence = c
+ }
+ }
+ return
+}
+
+// Sprintf is like fmt.Sprintf, but using language-specific formatting.
+func Sprintf(key message.Reference, a ...any) string {
+ return prn().Sprintf(key, a...)
+}
+
+// EnumerationSeparator returns enumeration separator. For English and western languages,
+// enumeration separator is a comma followed by a space (i.e. ", "). For Chinese, it returns
+// "\u3001".
+func EnumerationSeparator() string {
+ // BUG: We could just use `Sprintf(", " /* ...translator instructions... */)` and let the
+ // individual locale catalog handle its translation. Unfortunately, the gotext utility tries to
+ // be nice to translators and skips all strings without letters when updating catalogs.
+ return Sprintf("[EnumerationSeparator]" /* Text to insert between items when listing - most western languages will translate ‘[EnumerationSeparator]’ into ‘, ’ to produce lists like ‘apple, orange, strawberry’. Eastern languages might translate into ‘、’ to produce lists like ‘リンゴ、オレンジ、イチゴ’. */)
+}
+
+// UnitSeparator returns the separator to use when concatenating multiple units of the same metric
+// (e.g. "1 minute, 32 seconds", "6 feet, 1 inch"). For English and western languages, unit
+// separator is a comma followed by a space (i.e. ", "). For Slovenian and Japanese, it returns
+// just space.
+func UnitSeparator() string {
+ // BUG: We could just use `Sprintf(", " /* ...translator instructions... */)` and let the
+ // individual locale catalog handle its translation. Unfortunately, the gotext utility tries to
+ // be nice to translators and skips all strings without letters when updating catalogs.
+ return Sprintf("[UnitSeparator]" /* Text to insert when combining units of a measure - most languages will translate ‘[UnitSeparator]’ into ‘ ’ (space) to produce lists like ‘2 minuti 30 sekund’, or empty string ‘’ to produce ‘2分30秒’. */)
+}
diff --git a/locales/ca/messages.gotext.json b/locales/ca/messages.gotext.json
new file mode 100644
index 00000000..fbc227b1
--- /dev/null
+++ b/locales/ca/messages.gotext.json
@@ -0,0 +1,751 @@
+{
+ "language": "ca",
+ "messages": [
+ {
+ "id": "Error",
+ "message": "Error",
+ "translation": "Error",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(no argument): elevate and install manager service",
+ "message": "(no argument): elevate and install manager service",
+ "translation": "(sense argument): eleva i instala el servei d'administrador",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Usage: {Args0} [\n{String}]",
+ "message": "Usage: {Args0} [\n{String}]",
+ "translation": "Ús: {Args0} [\n{String}]",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Args0",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "os.Args[0]"
+ },
+ {
+ "id": "String",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "builder.String()"
+ }
+ ]
+ },
+ {
+ "id": "Command Line Options",
+ "message": "Command Line Options",
+ "translation": "Opcions de línia d'ordres",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to determine whether the process is running under WOW64: {Err}",
+ "message": "Unable to determine whether the process is running under WOW64: {Err}",
+ "translation": "No s'ha pogut determinar si el procés corre sota WOW64: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "You must use the native version of WireGuard on this computer.",
+ "message": "You must use the native version of WireGuard on this computer.",
+ "translation": "Heu de fer servir la versio nativa de WireGuard en aquest ordinador.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to open current process token: {Err}",
+ "message": "Unable to open current process token: {Err}",
+ "translation": "No s'ha pogut obrir el token del procés actual: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard només es pot fer servir per els usuaris que són membres del grup del sistema {AdminGroupName}.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard s'està executsnt, pero la interfície gràfica només és accessible als usuaris que són membres del grup del sistema {AdminGroupName}.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "Now",
+ "message": "Now",
+ "translation": "Ara",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "System clock wound backward!",
+ "message": "System clock wound backward!",
+ "translation": "El rellotge del sistema s'ha atraçat!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Years} year(s)",
+ "message": "{Years} year(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Years",
+ "cases": {
+ "one": {
+ "msg": "{Years} any"
+ },
+ "other": {
+ "msg": "{Years} anys"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Years",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "years"
+ }
+ ]
+ },
+ {
+ "id": "{Days} day(s)",
+ "message": "{Days} day(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Days",
+ "cases": {
+ "one": {
+ "msg": "{Days} dia"
+ },
+ "other": {
+ "msg": "{Days} dies"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Days",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "days"
+ }
+ ]
+ },
+ {
+ "id": "{Hours} hour(s)",
+ "message": "{Hours} hour(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Hours",
+ "cases": {
+ "one": {
+ "msg": "{Hours} hora"
+ },
+ "other": {
+ "msg": "{Hours} hores"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Hours",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "hours"
+ }
+ ]
+ },
+ {
+ "id": "{Minutes} minute(s)",
+ "message": "{Minutes} minute(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Minutes",
+ "cases": {
+ "one": {
+ "msg": "{Minutes} minut"
+ },
+ "other": {
+ "msg": "{Minutes} minuts"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Minutes",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "minutes"
+ }
+ ]
+ },
+ {
+ "id": "{Seconds} second(s)",
+ "message": "{Seconds} second(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Seconds",
+ "cases": {
+ "one": {
+ "msg": "{Seconds} segon"
+ },
+ "other": {
+ "msg": "{Seconds} segons"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Seconds",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "seconds"
+ }
+ ]
+ },
+ {
+ "id": "{Timestamp} ago",
+ "message": "{Timestamp} ago",
+ "translation": "Fa {Timestamp}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Timestamp",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "timestamp"
+ }
+ ]
+ },
+ {
+ "id": "{Bytes} B",
+ "message": "{Bytes} B",
+ "translation": "{Bytes} B",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Bytes",
+ "string": "%[1]d",
+ "type": "golang.zx2c4.com/wireguard/windows/conf.Bytes",
+ "underlyingType": "uint64",
+ "argNum": 1,
+ "expr": "b"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024} KiB",
+ "message": "{Float64b__1024} KiB",
+ "translation": "{Float64b__1024} KiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024} MiB",
+ "message": "{Float64b__1024__1024} MiB",
+ "translation": "{Float64b__1024__1024} MiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024} GiB",
+ "message": "{Float64b__1024__1024__1024} GiB",
+ "translation": "{Float64b__1024__1024__1024} GiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024__1024} TiB",
+ "message": "{Float64b__1024__1024__1024__1024} TiB",
+ "translation": "{Float64b__1024__1024__1024__1024} TiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Why}: {Offender}",
+ "message": "{Why}: {Offender}",
+ "translation": "{Why}: {Offender}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Why",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "e.why"
+ },
+ {
+ "id": "Offender",
+ "string": "%[2]q",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "e.offender"
+ }
+ ]
+ },
+ {
+ "id": "Invalid IP address",
+ "message": "Invalid IP address",
+ "translation": "Adreça IP invàlida",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid network prefix length",
+ "message": "Invalid network prefix length",
+ "translation": "Tamany del prefix de xarxa invàlid",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Missing port from endpoint",
+ "message": "Missing port from endpoint",
+ "translation": "Falta el port de l'extrem",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid endpoint host",
+ "message": "Invalid endpoint host",
+ "translation": "El format de l'extrem no és valid",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid MTU",
+ "message": "Invalid MTU",
+ "translation": "MTU invàlida",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid port",
+ "message": "Invalid port",
+ "translation": "Port invàlid",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid persistent keepalive",
+ "message": "Invalid persistent keepalive",
+ "translation": "Temps de missatge de persistència invàlid",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key: {Err}",
+ "message": "Invalid key: {Err}",
+ "translation": "Clau invàlida: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Number must be a number between 0 and 2^64-1: {Err}",
+ "message": "Number must be a number between 0 and 2^64-1: {Err}",
+ "translation": "El nombre ha de estar entre 0 i 2^64-1: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Two commas in a row",
+ "message": "Two commas in a row",
+ "translation": "Dos comes seguides",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name is not valid",
+ "message": "Tunnel name is not valid",
+ "translation": "El nom del túnel no és vàlid",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[none specified]",
+ "message": "[none specified]",
+ "translation": "[no especificat]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "All peers must have public keys",
+ "message": "All peers must have public keys",
+ "translation": "Tots els parells han de tenir claus públiques",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error in getting configuration",
+ "message": "Error in getting configuration",
+ "translation": "Error obtenint configuració",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[EnumerationSeparator]",
+ "message": "[EnumerationSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert between items when listing - most western languages will translate ‘[EnumerationSeparator]’ into ‘, ’ to produce lists like ‘apple, orange, strawberry’. Eastern languages might translate into ‘、’ to produce lists like ‘リンゴ、オレンジ、イチゴ’."
+ },
+ {
+ "id": "[UnitSeparator]",
+ "message": "[UnitSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert when combining units of a measure - most languages will translate ‘[UnitSeparator]’ into ‘ ’ (space) to produce lists like ‘2 minuti 30 sekund’, or empty string ‘’ to produce ‘2分30秒’."
+ },
+ {
+ "id": "About WireGuard",
+ "message": "About WireGuard",
+ "translation": "Sobre WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard logo image",
+ "message": "WireGuard logo image",
+ "translation": "Logo de WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Close",
+ "message": "Close",
+ "translation": "Tanca",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "♥ &Donate!",
+ "message": "♥ &Donate!",
+ "translation": "♥ & Dona!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status:",
+ "message": "Status:",
+ "translation": "Estat:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Deactivate",
+ "message": "&Deactivate",
+ "translation": "&Desactiva",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Activate",
+ "message": "&Activate",
+ "translation": "&Activa",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Public key:",
+ "message": "Public key:",
+ "translation": "Clau pública:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Listen port:",
+ "message": "Listen port:",
+ "translation": "Port d'escolta:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "MTU:",
+ "message": "MTU:",
+ "translation": "MTU:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses:",
+ "message": "Addresses:",
+ "translation": "Adreces:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "DNS servers:",
+ "message": "DNS servers:",
+ "translation": "Servidors DNS:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Preshared key:",
+ "message": "Preshared key:",
+ "translation": "Clau precompartida:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Allowed IPs:",
+ "message": "Allowed IPs:",
+ "translation": "IPs permeses:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Endpoint:",
+ "message": "Endpoint:",
+ "translation": "Extrem:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Persistent keepalive:",
+ "message": "Persistent keepalive:",
+ "translation": "Missatge de persistència:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Latest handshake:",
+ "message": "Latest handshake:",
+ "translation": "Últim handshake:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Inactive",
+ "message": "Inactive",
+ "translation": "Inactiu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Deactivating",
+ "message": "Deactivating",
+ "translation": "Desactivant",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unknown state",
+ "message": "Unknown state",
+ "translation": "Estat desconegut",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log",
+ "message": "Log",
+ "translation": "Registre",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Copy",
+ "message": "&Copy",
+ "translation": "&Copia",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Select &all",
+ "message": "Select &all",
+ "translation": "Selecciona-ho tot",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save to file…",
+ "message": "&Save to file…",
+ "translation": "Desa en un arxiu…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Time",
+ "message": "Time",
+ "translation": "Temps",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log message",
+ "message": "Log message",
+ "translation": "Missatge de registre",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export log to file",
+ "message": "Export log to file",
+ "translation": "Exporta registre a fitxer",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&About WireGuard…",
+ "message": "&About WireGuard…",
+ "translation": "&Sobre WireGuard…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel Error",
+ "message": "Tunnel Error",
+ "translation": "Error de túnel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "message": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "translation": "{ErrMsg}\n\nSi us plau, consulteu el registre per més informació.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "ErrMsg",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errMsg"
+ }
+ ]
+ },
+ {
+ "id": "{Title} (out of date)",
+ "message": "{Title} (out of date)",
+ "translation": "{Title} (desactualitzat)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "Error Exiting WireGuard",
+ "message": "Error Exiting WireGuard",
+ "translation": "Error al sortir de WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "message": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "translation": "Una actualització per WireGuard està disponible. Es recomana actualitzar immediatament.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Update Now",
+ "message": "Update Now",
+ "translation": "Actualitza ara",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error: {Err}. Please try again.",
+ "message": "Error: {Err}. Please try again.",
+ "translation": "Error: {Err}. Si us plau, torneu-ho a provar.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Status: Complete!",
+ "message": "Status: Complete!",
+ "translation": "Estat: Completat!",
+ "translatorComment": "Copied from source."
+ }
+ ]
+} \ No newline at end of file
diff --git a/locales/cs/messages.gotext.json b/locales/cs/messages.gotext.json
new file mode 100644
index 00000000..a28d47b9
--- /dev/null
+++ b/locales/cs/messages.gotext.json
@@ -0,0 +1,1907 @@
+{
+ "language": "cs",
+ "messages": [
+ {
+ "id": "Error",
+ "message": "Error",
+ "translation": "Chyba",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(no argument): elevate and install manager service",
+ "message": "(no argument): elevate and install manager service",
+ "translation": "(žádný argument): Zvýšit oprávnění a instalovat službu správce",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Usage: {Args0} [\n{String}]",
+ "message": "Usage: {Args0} [\n{String}]",
+ "translation": "Použití: {Args0} [\n{String}]",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Args0",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "os.Args[0]"
+ },
+ {
+ "id": "String",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "builder.String()"
+ }
+ ]
+ },
+ {
+ "id": "Command Line Options",
+ "message": "Command Line Options",
+ "translation": "Možnosti příkazového řádku",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to determine whether the process is running under WOW64: {Err}",
+ "message": "Unable to determine whether the process is running under WOW64: {Err}",
+ "translation": "Nelze zjistit, zda proces běží pod WOW64: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "You must use the native version of WireGuard on this computer.",
+ "message": "You must use the native version of WireGuard on this computer.",
+ "translation": "Musíte použít nativní verzi aplikace WireGuard na tomto počítači.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to open current process token: {Err}",
+ "message": "Unable to open current process token: {Err}",
+ "translation": "Nelze otevřít token aktuálního procesu: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard můžou používat pouze uživatelé, kteří jsou členy Builtin skupiny {AdminGroupName}.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard je spuštěn, ale uživatelské rozhraní je přístupné pouze uživatelům Builtin skupiny {AdminGroupName}.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard system tray icon did not appear after 30 seconds.",
+ "message": "WireGuard system tray icon did not appear after 30 seconds.",
+ "translation": "Ikona WireGuard se ani po 30 sekundách nezobrazila na systémové liště.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Now",
+ "message": "Now",
+ "translation": "Teď",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "System clock wound backward!",
+ "message": "System clock wound backward!",
+ "translation": "Systémové hodiny byly posunuty dozadu!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Years} year(s)",
+ "message": "{Years} year(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Years",
+ "cases": {
+ "one": {
+ "msg": "{Years} rok"
+ },
+ "few": {
+ "msg": "{Years} roky"
+ },
+ "many": {
+ "msg": "{Years} let"
+ },
+ "other": {
+ "msg": "{Years} let"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Years",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "years"
+ }
+ ]
+ },
+ {
+ "id": "{Days} day(s)",
+ "message": "{Days} day(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Days",
+ "cases": {
+ "one": {
+ "msg": "{Days} den"
+ },
+ "few": {
+ "msg": "{Days} dny"
+ },
+ "many": {
+ "msg": "{Days} dnů"
+ },
+ "other": {
+ "msg": "{Days} dny"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Days",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "days"
+ }
+ ]
+ },
+ {
+ "id": "{Hours} hour(s)",
+ "message": "{Hours} hour(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Hours",
+ "cases": {
+ "one": {
+ "msg": "{Hours} hodina"
+ },
+ "few": {
+ "msg": "{Hours} hodiny"
+ },
+ "many": {
+ "msg": "{Hours} hodin"
+ },
+ "other": {
+ "msg": "{Hours} hodin"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Hours",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "hours"
+ }
+ ]
+ },
+ {
+ "id": "{Minutes} minute(s)",
+ "message": "{Minutes} minute(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Minutes",
+ "cases": {
+ "one": {
+ "msg": "{Minutes} minuta"
+ },
+ "few": {
+ "msg": "{Minutes} minuty"
+ },
+ "many": {
+ "msg": "{Minutes} minut"
+ },
+ "other": {
+ "msg": "{Minutes} minut"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Minutes",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "minutes"
+ }
+ ]
+ },
+ {
+ "id": "{Seconds} second(s)",
+ "message": "{Seconds} second(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Seconds",
+ "cases": {
+ "one": {
+ "msg": "{Seconds} sekunda"
+ },
+ "few": {
+ "msg": "{Seconds} sekundy"
+ },
+ "many": {
+ "msg": "{Seconds} sekund"
+ },
+ "other": {
+ "msg": "{Seconds} sekund"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Seconds",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "seconds"
+ }
+ ]
+ },
+ {
+ "id": "{Timestamp} ago",
+ "message": "{Timestamp} ago",
+ "translation": "před {Timestamp}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Timestamp",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "timestamp"
+ }
+ ]
+ },
+ {
+ "id": "{Bytes} B",
+ "message": "{Bytes} B",
+ "translation": "{Bytes} B",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Bytes",
+ "string": "%[1]d",
+ "type": "golang.zx2c4.com/wireguard/windows/conf.Bytes",
+ "underlyingType": "uint64",
+ "argNum": 1,
+ "expr": "b"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024} KiB",
+ "message": "{Float64b__1024} KiB",
+ "translation": "{Float64b__1024} KiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024} MiB",
+ "message": "{Float64b__1024__1024} MiB",
+ "translation": "{Float64b__1024__1024} MiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024} GiB",
+ "message": "{Float64b__1024__1024__1024} GiB",
+ "translation": "{Float64b__1024__1024__1024} GiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024__1024} TiB",
+ "message": "{Float64b__1024__1024__1024__1024} TiB",
+ "translation": "{Float64b__1024__1024__1024__1024} TiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Why}: {Offender}",
+ "message": "{Why}: {Offender}",
+ "translation": "{Why}: {Offender}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Why",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "e.why"
+ },
+ {
+ "id": "Offender",
+ "string": "%[2]q",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "e.offender"
+ }
+ ]
+ },
+ {
+ "id": "Invalid IP address",
+ "message": "Invalid IP address",
+ "translation": "Neplatná IP adresa",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid network prefix length",
+ "message": "Invalid network prefix length",
+ "translation": "Neplatná délka síťového prefixu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Missing port from endpoint",
+ "message": "Missing port from endpoint",
+ "translation": "Endpointu chybí port",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid endpoint host",
+ "message": "Invalid endpoint host",
+ "translation": "Neplatný endpoint",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Brackets must contain an IPv6 address",
+ "message": "Brackets must contain an IPv6 address",
+ "translation": "Závorky musí obsahovat IPv6 adresu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid MTU",
+ "message": "Invalid MTU",
+ "translation": "Neplatné MTU",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid port",
+ "message": "Invalid port",
+ "translation": "Neplatný port",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid persistent keepalive",
+ "message": "Invalid persistent keepalive",
+ "translation": "Neplatný persistentní keepalive",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key: {Err}",
+ "message": "Invalid key: {Err}",
+ "translation": "Neplatný klíč: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Keys must decode to exactly 32 bytes",
+ "message": "Keys must decode to exactly 32 bytes",
+ "translation": "Klíče musí být dekódovány přesně na 32 bajtů",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Number must be a number between 0 and 2^64-1: {Err}",
+ "message": "Number must be a number between 0 and 2^64-1: {Err}",
+ "translation": "Číslo musí mít hodnotu mezi 0 a 2^64-1: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Two commas in a row",
+ "message": "Two commas in a row",
+ "translation": "Dvě čárky za sebou",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name is not valid",
+ "message": "Tunnel name is not valid",
+ "translation": "Název tunelu je neplatný",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Line must occur in a section",
+ "message": "Line must occur in a section",
+ "translation": "Řádek musí být v některé sekci",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Config key is missing an equals separator",
+ "message": "Config key is missing an equals separator",
+ "translation": "Konfigurační klíč neobsahuje oddělovač (znak 'rovná se')",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Key must have a value",
+ "message": "Key must have a value",
+ "translation": "Klíč musí mít hodnotu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Interface] section",
+ "message": "Invalid key for [Interface] section",
+ "translation": "Neplatný klíč pro sekci [Interface]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Peer] section",
+ "message": "Invalid key for [Peer] section",
+ "translation": "Neplatný klíč pro sekci [Peer]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An interface must have a private key",
+ "message": "An interface must have a private key",
+ "translation": "Rozhraní musí obsahovat soukromý klíč",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[none specified]",
+ "message": "[none specified]",
+ "translation": "[není specifikováno]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "All peers must have public keys",
+ "message": "All peers must have public keys",
+ "translation": "Všichni peeři musí mít veřejné klíče",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error in getting configuration",
+ "message": "Error in getting configuration",
+ "translation": "Chyba při načítání konfigurace",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for interface section",
+ "message": "Invalid key for interface section",
+ "translation": "Neplatný klíč pro sekci rozhraní",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Protocol version must be 1",
+ "message": "Protocol version must be 1",
+ "translation": "Verze protokolu musí být 1",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for peer section",
+ "message": "Invalid key for peer section",
+ "translation": "Neplatný klíč v sekci peer",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[EnumerationSeparator]",
+ "message": "[EnumerationSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert between items when listing - most western languages will translate ‘[EnumerationSeparator]’ into ‘, ’ to produce lists like ‘apple, orange, strawberry’. Eastern languages might translate into ‘、’ to produce lists like ‘リンゴ、オレンジ、イチゴ’."
+ },
+ {
+ "id": "[UnitSeparator]",
+ "message": "[UnitSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert when combining units of a measure - most languages will translate ‘[UnitSeparator]’ into ‘ ’ (space) to produce lists like ‘2 minuti 30 sekund’, or empty string ‘’ to produce ‘2分30秒’."
+ },
+ {
+ "id": "About WireGuard",
+ "message": "About WireGuard",
+ "translation": "O aplikaci WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard logo image",
+ "message": "WireGuard logo image",
+ "translation": "Obrázek loga WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "message": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "translation": "Verze aplikace: {Number}\nVerze Go backendu: {WireGuardGoVersion}\nVerze Go: {Version_go}-{GOARCH}\nOperační systém: {OsName}\nArchitektura: {NativeArch}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Number",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "version.Number"
+ },
+ {
+ "id": "WireGuardGoVersion",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "device.WireGuardGoVersion"
+ },
+ {
+ "id": "Version_go",
+ "string": "%[3]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 3,
+ "expr": "strings.TrimPrefix(runtime.Version(), \"go\")"
+ },
+ {
+ "id": "GOARCH",
+ "string": "%[4]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 4,
+ "expr": "runtime.GOARCH"
+ },
+ {
+ "id": "OsName",
+ "string": "%[5]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 5,
+ "expr": "version.OsName()"
+ },
+ {
+ "id": "NativeArch",
+ "string": "%[6]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 6,
+ "expr": "version.NativeArch()"
+ }
+ ]
+ },
+ {
+ "id": "Close",
+ "message": "Close",
+ "translation": "Zavřít",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "♥ &Donate!",
+ "message": "♥ &Donate!",
+ "translation": "♥ &Darovat!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status:",
+ "message": "Status:",
+ "translation": "Stav:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Deactivate",
+ "message": "&Deactivate",
+ "translation": "&Deaktivovat",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Activate",
+ "message": "&Activate",
+ "translation": "&Aktivovat",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Public key:",
+ "message": "Public key:",
+ "translation": "Veřejný klíč:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Listen port:",
+ "message": "Listen port:",
+ "translation": "Port pro naslouchání:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "MTU:",
+ "message": "MTU:",
+ "translation": "MTU:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses:",
+ "message": "Addresses:",
+ "translation": "Adresy:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "DNS servers:",
+ "message": "DNS servers:",
+ "translation": "DNS servery:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Scripts:",
+ "message": "Scripts:",
+ "translation": "Skripty:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Preshared key:",
+ "message": "Preshared key:",
+ "translation": "Předsdílený klíč:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Allowed IPs:",
+ "message": "Allowed IPs:",
+ "translation": "Povolené IP:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Endpoint:",
+ "message": "Endpoint:",
+ "translation": "Endpoint:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Persistent keepalive:",
+ "message": "Persistent keepalive:",
+ "translation": "Persistent keepalive:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Latest handshake:",
+ "message": "Latest handshake:",
+ "translation": "Poslední handshake:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Transfer:",
+ "message": "Transfer:",
+ "translation": "Přenos:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-up",
+ "message": "pre-up",
+ "translation": "před-zapnutím",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-up",
+ "message": "post-up",
+ "translation": "po-zapnutí",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-down",
+ "message": "pre-down",
+ "translation": "před-vypnutím",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-down",
+ "message": "post-down",
+ "translation": "po-vypnutí",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "disabled, per policy",
+ "message": "disabled, per policy",
+ "translation": "vypnuto, podle zásad",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "enabled",
+ "message": "enabled",
+ "translation": "zapnuto",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{String} received, {String_1} sent",
+ "message": "{String} received, {String_1} sent",
+ "translation": "{String} přijato, {String_1} odesláno",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "String",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "c.RxBytes.String()"
+ },
+ {
+ "id": "String_1",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "c.TxBytes.String()"
+ }
+ ]
+ },
+ {
+ "id": "Failed to determine tunnel state",
+ "message": "Failed to determine tunnel state",
+ "translation": "Nepodařilo se zjistit stav tunelu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to activate tunnel",
+ "message": "Failed to activate tunnel",
+ "translation": "Nepodařilo se aktivovat tunel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to deactivate tunnel",
+ "message": "Failed to deactivate tunnel",
+ "translation": "Nepodařilo se deaktivovat tunel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Interface: {Name}",
+ "message": "Interface: {Name}",
+ "translation": "Rozhraní: {Name}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "config.Name"
+ }
+ ]
+ },
+ {
+ "id": "Peer",
+ "message": "Peer",
+ "translation": "Peer",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Create new tunnel",
+ "message": "Create new tunnel",
+ "translation": "Vytvořit nový tunel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit tunnel",
+ "message": "Edit tunnel",
+ "translation": "Upravit tunel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Name:",
+ "message": "&Name:",
+ "translation": "&Název:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Public key:",
+ "message": "&Public key:",
+ "translation": "&Veřejný klíč:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(unknown)",
+ "message": "(unknown)",
+ "translation": "(neznámý)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Block untunneled traffic (kill-switch)",
+ "message": "&Block untunneled traffic (kill-switch)",
+ "translation": "&Blokovat netunelovaný provoz (kill-switch)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.",
+ "message": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.",
+ "translation": "Pokud má konfigurace přesně jednoho peera a tento peer má povolené IP adresy obsahující alespoň jednu z 0.0.0.0/0 nebo ::/0, pak služba tunelu použije sadu pravidel firewallu k blokování veškerého provozu, který nesměřuje z/do rozhraní tunelu nebo na nesprávný server DNS, se zvláštními výjimkami pro DHCP a NDP.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save",
+ "message": "&Save",
+ "translation": "&Uložit",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Cancel",
+ "message": "Cancel",
+ "translation": "Zrušit",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Configuration:",
+ "message": "&Configuration:",
+ "translation": "&Nastavení:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid name",
+ "message": "Invalid name",
+ "translation": "Neplatný název",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A name is required.",
+ "message": "A name is required.",
+ "translation": "Název je povinný.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name ‘{NewName}’ is invalid.",
+ "message": "Tunnel name ‘{NewName}’ is invalid.",
+ "translation": "Název tunelu '{NewName}' je neplatný.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to list existing tunnels",
+ "message": "Unable to list existing tunnels",
+ "translation": "Nepodařilo se zobrazit existující tunely",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel already exists",
+ "message": "Tunnel already exists",
+ "translation": "Tunel již existuje",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{NewName}’.",
+ "message": "Another tunnel already exists with the name ‘{NewName}’.",
+ "translation": "Tunel s názvem '{NewName}' již existuje.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create new configuration",
+ "message": "Unable to create new configuration",
+ "translation": "Nepodařilo se vytvořit novou konfiguraci",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Writing file failed",
+ "message": "Writing file failed",
+ "translation": "Zápis souboru se nezdařil",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "message": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "translation": "Soubor \"{FilePath}\" již existuje.\n\nChcete jej přepsat?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "FilePath",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "filePath"
+ }
+ ]
+ },
+ {
+ "id": "Active",
+ "message": "Active",
+ "translation": "Aktivní",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Activating",
+ "message": "Activating",
+ "translation": "Aktivuji",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Inactive",
+ "message": "Inactive",
+ "translation": "Neaktivní",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Deactivating",
+ "message": "Deactivating",
+ "translation": "Deaktivuji",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unknown state",
+ "message": "Unknown state",
+ "translation": "Neznámý stav",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log",
+ "message": "Log",
+ "translation": "Záznamy",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Copy",
+ "message": "&Copy",
+ "translation": "&Kopírovat",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Select &all",
+ "message": "Select &all",
+ "translation": "Vybr&at vše",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save to file…",
+ "message": "&Save to file…",
+ "translation": "&Uložit do souboru…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Time",
+ "message": "Time",
+ "translation": "Čas",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log message",
+ "message": "Log message",
+ "translation": "Zpráva logu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "message": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "translation": "Textové soubory (*.txt)|*.txt|Všechny soubory (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export log to file",
+ "message": "Export log to file",
+ "translation": "Exportovat záznam do souboru",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&About WireGuard…",
+ "message": "&About WireGuard…",
+ "translation": "&O aplikaci WireGuard…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel Error",
+ "message": "Tunnel Error",
+ "translation": "Chyba tunelu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "message": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "translation": "{ErrMsg}\n\nPro více informací se prosím podívejte do logu.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "ErrMsg",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errMsg"
+ }
+ ]
+ },
+ {
+ "id": "{Title} (out of date)",
+ "message": "{Title} (out of date)",
+ "translation": "{Title} (neaktuální)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Detection Error",
+ "message": "WireGuard Detection Error",
+ "translation": "Chyba při detekci WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to wait for WireGuard window to appear: {Err}",
+ "message": "Unable to wait for WireGuard window to appear: {Err}",
+ "translation": "Nelze čekat na zobrazení okna WireGuard: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard: Deactivated",
+ "message": "WireGuard: Deactivated",
+ "translation": "WireGuard: Deaktivován",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Unknown",
+ "message": "Status: Unknown",
+ "translation": "Stav: Neznámý",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses: None",
+ "message": "Addresses: None",
+ "translation": "Adresy: žádné",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Manage tunnels…",
+ "message": "&Manage tunnels…",
+ "translation": "Spravovat tunely…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Import tunnel(s) from file…",
+ "message": "&Import tunnel(s) from file…",
+ "translation": "&Importovat tunel(y) ze souboru…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "E&xit",
+ "message": "E&xit",
+ "translation": "U&končit",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Tunnels",
+ "message": "&Tunnels",
+ "translation": "&Tunely",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Activated",
+ "message": "WireGuard Activated",
+ "translation": "WireGuard aktivován",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been activated.",
+ "message": "The {Name} tunnel has been activated.",
+ "translation": "Tunel {Name} byl aktivován.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Deactivated",
+ "message": "WireGuard Deactivated",
+ "translation": "WireGuard deaktivován",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been deactivated.",
+ "message": "The {Name} tunnel has been deactivated.",
+ "translation": "Tunel {Name} byl deaktivován.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Tunnel Error",
+ "message": "WireGuard Tunnel Error",
+ "translation": "WireGuard Chyba Tunelu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard: {TextForStateglobalState_true}",
+ "message": "WireGuard: {TextForStateglobalState_true}",
+ "translation": "WireGuard: {TextForStateglobalState_true}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TextForStateglobalState_true",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "textForState(globalState, true)"
+ }
+ ]
+ },
+ {
+ "id": "Status: {StateText}",
+ "message": "Status: {StateText}",
+ "translation": "Stav: {StateText}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "StateText",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "stateText"
+ }
+ ]
+ },
+ {
+ "id": "Addresses: {EnumerationSeparator}",
+ "message": "Addresses: {EnumerationSeparator}",
+ "translation": "Adresy: {EnumerationSeparator}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "EnumerationSeparator",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "strings.Join(addrs, l18n.EnumerationSeparator())"
+ }
+ ]
+ },
+ {
+ "id": "An Update is Available!",
+ "message": "An Update is Available!",
+ "translation": "Aktualizace je k dispozici!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Update Available",
+ "message": "WireGuard Update Available",
+ "translation": "Aktualizace WireGuard je k dispozici",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "message": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "translation": "Aktualizace aplikace WireGuard je nyní k dispozici. Doporučujeme ji aktualizovat co nejdříve.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnels",
+ "message": "Tunnels",
+ "translation": "Tunely",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Edit",
+ "message": "&Edit",
+ "translation": "&Upravit",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add &empty tunnel…",
+ "message": "Add &empty tunnel…",
+ "translation": "Přidat &prázdný tunel…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add Tunnel",
+ "message": "Add Tunnel",
+ "translation": "Přidat tunel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Remove selected tunnel(s)",
+ "message": "Remove selected tunnel(s)",
+ "translation": "Odstranit vybrané tunely",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to zip",
+ "message": "Export all tunnels to zip",
+ "translation": "Exportovat všechny tunely do zip",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Toggle",
+ "message": "&Toggle",
+ "translation": "&Přepnout",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to &zip…",
+ "message": "Export all tunnels to &zip…",
+ "translation": "Exportovat všechny tunely do &zip…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit &selected tunnel…",
+ "message": "Edit &selected tunnel…",
+ "translation": "Upravit &vybraný tunel…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Remove selected tunnel(s)",
+ "message": "&Remove selected tunnel(s)",
+ "translation": "&Odstranit vybrané tunely",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "no configuration files were found",
+ "message": "no configuration files were found",
+ "translation": "nebyly nalezeny žádné konfigurační soubory",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Could not import selected configuration: {LastErr}",
+ "message": "Could not import selected configuration: {LastErr}",
+ "translation": "Nelze importovat vybranou konfiguraci: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Could not enumerate existing tunnels: {LastErr}",
+ "message": "Could not enumerate existing tunnels: {LastErr}",
+ "translation": "Nepodařilo se vyjmenovat existující tunely: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{Name}’",
+ "message": "Another tunnel already exists with the name ‘{Name}’",
+ "translation": "Tunel s názvem '{Name}' již existuje",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "unparsedConfig.Name"
+ }
+ ]
+ },
+ {
+ "id": "Unable to import configuration: {LastErr}",
+ "message": "Unable to import configuration: {LastErr}",
+ "translation": "Nelze importovat konfiguraci: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Imported tunnels",
+ "message": "Imported tunnels",
+ "translation": "Importované tunely",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Imported {M} tunnels",
+ "message": "Imported {M} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "M",
+ "cases": {
+ "one": {
+ "msg": "Importován {M} tunel"
+ },
+ "few": {
+ "msg": "Importovány {M} tunely"
+ },
+ "many": {
+ "msg": "Importováno {M} tunelů"
+ },
+ "other": {
+ "msg": "Importováno {M} tunelů"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ }
+ ]
+ },
+ {
+ "id": "Imported {M} of {N} tunnels",
+ "message": "Imported {M} of {N} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "N",
+ "cases": {
+ "one": {
+ "msg": "Importován {M} z {N} tunel"
+ },
+ "few": {
+ "msg": "Importováno {M} z {N} tunelů"
+ },
+ "many": {
+ "msg": "Importováno {M} z {N} tunelů"
+ },
+ "other": {
+ "msg": "Importováno {M} z {N} tunelů"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ },
+ {
+ "id": "N",
+ "string": "%[2]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 2,
+ "expr": "n"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create tunnel",
+ "message": "Unable to create tunnel",
+ "translation": "Nelze vytvořit tunel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Delete {TunnelCount} tunnels",
+ "message": "Delete {TunnelCount} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "Odstranit {TunnelCount} tunel"
+ },
+ "few": {
+ "msg": "Odstranit {TunnelCount} tunely"
+ },
+ "many": {
+ "msg": "Odstranit {TunnelCount} tunelů"
+ },
+ "other": {
+ "msg": "Odstranit {TunnelCount} tunelů"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "message": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "Opravdu chcete odstranit {TunnelCount} tunel?"
+ },
+ "few": {
+ "msg": "Opravdu chcete odstranit {TunnelCount} tunely?"
+ },
+ "many": {
+ "msg": "Opravdu chcete odstranit {TunnelCount} tunelů?"
+ },
+ "other": {
+ "msg": "Opravdu chcete odstranit {TunnelCount} tunelů?"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Delete tunnel ‘{TunnelName}’",
+ "message": "Delete tunnel ‘{TunnelName}’",
+ "translation": "Odstranit tunel \"{TunnelName}\"",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "message": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "translation": "Opravdu chcete odstranit tunel \"{TunnelName}\"?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "{Question} You cannot undo this action.",
+ "message": "{Question} You cannot undo this action.",
+ "translation": "{Question} Tuto akci nelze vrátit zpět.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Question",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "question"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnel",
+ "message": "Unable to delete tunnel",
+ "translation": "Nelze odstranit tunel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A tunnel was unable to be removed: {Error}",
+ "message": "A tunnel was unable to be removed: {Error}",
+ "translation": "Tunel nebylo možné odstranit: {Error}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Error",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errors[0].Error()"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnels",
+ "message": "Unable to delete tunnels",
+ "translation": "Nelze odstranit tunely",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Lenerrors} tunnels were unable to be removed.",
+ "message": "{Lenerrors} tunnels were unable to be removed.",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Lenerrors",
+ "cases": {
+ "one": {
+ "msg": "{Lenerrors} tunel nebylo možné odstranit."
+ },
+ "few": {
+ "msg": "{Lenerrors} tunely nebylo možné odstranit."
+ },
+ "many": {
+ "msg": "{Lenerrors} tunelů nebylo možné odstranit."
+ },
+ "other": {
+ "msg": "{Lenerrors} tunelů nebylo možné odstranit."
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Lenerrors",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "len(errors)"
+ }
+ ]
+ },
+ {
+ "id": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "message": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "translation": "Konfigurace souborů (*.zip, *.conf)|*.zip; *.conf|Všechny soubory (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Import tunnel(s) from file",
+ "message": "Import tunnel(s) from file",
+ "translation": "Importovat tunel(y) ze souboru",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Configuration ZIP Files (*.zip)|*.zip",
+ "message": "Configuration ZIP Files (*.zip)|*.zip",
+ "translation": "Konfigurace souborů ZIP (*.zip)|*.zip",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export tunnels to zip",
+ "message": "Export tunnels to zip",
+ "translation": "Exportovat tunely do zip",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Title} (unsigned build, no updates)",
+ "message": "{Title} (unsigned build, no updates)",
+ "translation": "{Title} (nepodepsaná verze, žádné aktualizace)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "Error Exiting WireGuard",
+ "message": "Error Exiting WireGuard",
+ "translation": "Chyba při ukončování aplikace WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "message": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "translation": "Nelze ukončit službu z důvodu: {Err}. WireGuard můžete zastavit ve správci služeb.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "message": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "translation": "Aktualizace aplikace WireGuard je nyní k dispozici. Silně doporučujeme ji aktualizovat co nejdříve.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for user",
+ "message": "Status: Waiting for user",
+ "translation": "Stav: Čekání na uživatele",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Update Now",
+ "message": "Update Now",
+ "translation": "Aktualizovat nyní",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for updater service",
+ "message": "Status: Waiting for updater service",
+ "translation": "Stav: Čeká se na službu aktualizací",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error: {Err}. Please try again.",
+ "message": "Error: {Err}. Please try again.",
+ "translation": "Chyba: {Err}. Zkuste to znovu.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Status: Complete!",
+ "message": "Status: Complete!",
+ "translation": "Stav: Dokončeno!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "http2: Framer {F}: failed to decode just-written frame",
+ "message": "http2: Framer {F}: failed to decode just-written frame",
+ "translation": "http2: Framer {F}: failed to decode just-written frame",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ }
+ ]
+ },
+ {
+ "id": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "message": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "translation": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ },
+ {
+ "id": "Http2summarizeFramefr",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(fr)"
+ }
+ ]
+ },
+ {
+ "id": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "message": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "translation": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Fr",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "fr"
+ },
+ {
+ "id": "Http2summarizeFramef",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(f)"
+ }
+ ]
+ },
+ {
+ "id": "http2: decoded hpack field {HeaderField}",
+ "message": "http2: decoded hpack field {HeaderField}",
+ "translation": "http2: decoded hpack field {HeaderField}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "HeaderField",
+ "string": "%+[1]v",
+ "type": "vendor/golang.org/x/net/http2/hpack.HeaderField",
+ "underlyingType": "struct{Name string; Value string; Sensitive bool}",
+ "argNum": 1,
+ "expr": "hf"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/locales/de/messages.gotext.json b/locales/de/messages.gotext.json
new file mode 100644
index 00000000..5d26c8ab
--- /dev/null
+++ b/locales/de/messages.gotext.json
@@ -0,0 +1,1847 @@
+{
+ "language": "de",
+ "messages": [
+ {
+ "id": "Error",
+ "message": "Error",
+ "translation": "Fehler",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(no argument): elevate and install manager service",
+ "message": "(no argument): elevate and install manager service",
+ "translation": "(kein Argument): Als Administrator ausführen und den Manager-Dienst installieren",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Usage: {Args0} [\n{String}]",
+ "message": "Usage: {Args0} [\n{String}]",
+ "translation": "Verwendung: {Args0} [\n{String}]",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Args0",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "os.Args[0]"
+ },
+ {
+ "id": "String",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "builder.String()"
+ }
+ ]
+ },
+ {
+ "id": "Command Line Options",
+ "message": "Command Line Options",
+ "translation": "Kommandozeilenoptionen",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to determine whether the process is running under WOW64: {Err}",
+ "message": "Unable to determine whether the process is running under WOW64: {Err}",
+ "translation": "Es kann nicht festgestellt werden, ob der Prozess unter WOW64 ausgeführt wird: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "You must use the native version of WireGuard on this computer.",
+ "message": "You must use the native version of WireGuard on this computer.",
+ "translation": "Sie müssen die Version von Wireguard benutzen, die für ihren Computer bestimmt ist.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to open current process token: {Err}",
+ "message": "Unable to open current process token: {Err}",
+ "translation": "Konnte aktuellen Prozess-Token nicht öffnen: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard kann nur von Benutzern verwendet werden, die Mitglied der Gruppe {AdminGroupName} sind.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard wird ausgeführt, aber auf die Benutzeroberfläche kann nur von Desktops der Gruppe {AdminGroupName} zugegriffen werden.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard system tray icon did not appear after 30 seconds.",
+ "message": "WireGuard system tray icon did not appear after 30 seconds.",
+ "translation": "Das WireGuard-Taskleistensymbol ist nicht innerhalb von 30 Sekunden erschienen.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Now",
+ "message": "Now",
+ "translation": "Jetzt",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "System clock wound backward!",
+ "message": "System clock wound backward!",
+ "translation": "Die Systemuhr wurde zurück gestellt!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Years} year(s)",
+ "message": "{Years} year(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Years",
+ "cases": {
+ "one": {
+ "msg": "{Years} Jahr"
+ },
+ "other": {
+ "msg": "{Years} Jahre"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Years",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "years"
+ }
+ ]
+ },
+ {
+ "id": "{Days} day(s)",
+ "message": "{Days} day(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Days",
+ "cases": {
+ "one": {
+ "msg": "{Days} Tag"
+ },
+ "other": {
+ "msg": "{Days} Tage"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Days",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "days"
+ }
+ ]
+ },
+ {
+ "id": "{Hours} hour(s)",
+ "message": "{Hours} hour(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Hours",
+ "cases": {
+ "one": {
+ "msg": "{Hours} Stunde"
+ },
+ "other": {
+ "msg": "{Hours} Stunden"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Hours",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "hours"
+ }
+ ]
+ },
+ {
+ "id": "{Minutes} minute(s)",
+ "message": "{Minutes} minute(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Minutes",
+ "cases": {
+ "one": {
+ "msg": "{Minutes} Minute"
+ },
+ "other": {
+ "msg": "{Minutes} Minuten"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Minutes",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "minutes"
+ }
+ ]
+ },
+ {
+ "id": "{Seconds} second(s)",
+ "message": "{Seconds} second(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Seconds",
+ "cases": {
+ "one": {
+ "msg": "{Seconds} Sekunde"
+ },
+ "other": {
+ "msg": "{Seconds} Sekunden"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Seconds",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "seconds"
+ }
+ ]
+ },
+ {
+ "id": "{Timestamp} ago",
+ "message": "{Timestamp} ago",
+ "translation": "vor {Timestamp}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Timestamp",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "timestamp"
+ }
+ ]
+ },
+ {
+ "id": "{Bytes} B",
+ "message": "{Bytes} B",
+ "translation": "{Bytes} B",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Bytes",
+ "string": "%[1]d",
+ "type": "golang.zx2c4.com/wireguard/windows/conf.Bytes",
+ "underlyingType": "uint64",
+ "argNum": 1,
+ "expr": "b"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024} KiB",
+ "message": "{Float64b__1024} KiB",
+ "translation": "{Float64b__1024} KiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024} MiB",
+ "message": "{Float64b__1024__1024} MiB",
+ "translation": "{Float64b__1024__1024} MiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024} GiB",
+ "message": "{Float64b__1024__1024__1024} GiB",
+ "translation": "{Float64b__1024__1024__1024} GiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024__1024} TiB",
+ "message": "{Float64b__1024__1024__1024__1024} TiB",
+ "translation": "{Float64b__1024__1024__1024__1024} TiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Why}: {Offender}",
+ "message": "{Why}: {Offender}",
+ "translation": "{Why}: {Offender}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Why",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "e.why"
+ },
+ {
+ "id": "Offender",
+ "string": "%[2]q",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "e.offender"
+ }
+ ]
+ },
+ {
+ "id": "Invalid IP address",
+ "message": "Invalid IP address",
+ "translation": "Ungültige IP-Adresse",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid network prefix length",
+ "message": "Invalid network prefix length",
+ "translation": "Ungültige Länge des Netzwerkpräfixes",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Missing port from endpoint",
+ "message": "Missing port from endpoint",
+ "translation": "Fehlender Port des Endpunktes",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid endpoint host",
+ "message": "Invalid endpoint host",
+ "translation": "Ungültiger Endpunkt-Host",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Brackets must contain an IPv6 address",
+ "message": "Brackets must contain an IPv6 address",
+ "translation": "Eckige Klammern müssen eine IPv6 Adresse enthalten",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid MTU",
+ "message": "Invalid MTU",
+ "translation": "Ungültige MTU",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid port",
+ "message": "Invalid port",
+ "translation": "Ungültiger Port",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid persistent keepalive",
+ "message": "Invalid persistent keepalive",
+ "translation": "Ungültiges Erhaltungsintervall",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key: {Err}",
+ "message": "Invalid key: {Err}",
+ "translation": "Ungültiger Schlüssel: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Keys must decode to exactly 32 bytes",
+ "message": "Keys must decode to exactly 32 bytes",
+ "translation": "Schlüssel müssen auf exakt 32 Bytes dekodiert werden",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Number must be a number between 0 and 2^64-1: {Err}",
+ "message": "Number must be a number between 0 and 2^64-1: {Err}",
+ "translation": "Zahl muss zwischen 0 und 2^64-1 sein: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Two commas in a row",
+ "message": "Two commas in a row",
+ "translation": "Zwei Kommata in einer Zeile",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name is not valid",
+ "message": "Tunnel name is not valid",
+ "translation": "Der Tunnelname ist ungültig",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Line must occur in a section",
+ "message": "Line must occur in a section",
+ "translation": "Die Zeile muss innerhalb eines Abschnitts stehen",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Config key is missing an equals separator",
+ "message": "Config key is missing an equals separator",
+ "translation": "Konfigurationsschlüssel fehlt ein Gleichheitstrennzeichen",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Key must have a value",
+ "message": "Key must have a value",
+ "translation": "Eintrag muss einen Wert haben",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Interface] section",
+ "message": "Invalid key for [Interface] section",
+ "translation": "Ungültiger Eintrage im [Interface] Abschnitt",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Peer] section",
+ "message": "Invalid key for [Peer] section",
+ "translation": "Ungültiger Eintrag im [Peer] Abschnitt",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An interface must have a private key",
+ "message": "An interface must have a private key",
+ "translation": "Eine Schnittstelle muss einen privaten Schlssel enthalten",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[none specified]",
+ "message": "[none specified]",
+ "translation": "[nicht spezifiziert]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "All peers must have public keys",
+ "message": "All peers must have public keys",
+ "translation": "Alle Teilnehmer (peers) müssen öffentliche Schlüssel haben",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error in getting configuration",
+ "message": "Error in getting configuration",
+ "translation": "Fehler beim Abrufen der Konfiguration",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for interface section",
+ "message": "Invalid key for interface section",
+ "translation": "Ungültiger Eintrag im Abschnitt [interface]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Protocol version must be 1",
+ "message": "Protocol version must be 1",
+ "translation": "Die Protokollversion muss 1 sein",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for peer section",
+ "message": "Invalid key for peer section",
+ "translation": "Ungültiger Eintrag im Abschnitt [peer]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[EnumerationSeparator]",
+ "message": "[EnumerationSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert between items when listing - most western languages will translate ‘[EnumerationSeparator]’ into ‘, ’ to produce lists like ‘apple, orange, strawberry’. Eastern languages might translate into ‘、’ to produce lists like ‘リンゴ、オレンジ、イチゴ’."
+ },
+ {
+ "id": "[UnitSeparator]",
+ "message": "[UnitSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert when combining units of a measure - most languages will translate ‘[UnitSeparator]’ into ‘ ’ (space) to produce lists like ‘2 minuti 30 sekund’, or empty string ‘’ to produce ‘2分30秒’."
+ },
+ {
+ "id": "About WireGuard",
+ "message": "About WireGuard",
+ "translation": "Über WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard logo image",
+ "message": "WireGuard logo image",
+ "translation": "WireGuard Logo",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "message": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "translation": "App Version: {Number}\nGo backend Version: {WireGuardGoVersion}\nGo Version: {Version_go}-{GOARCH}\nBetriebssystem: {OsName}\nArchitektur: {NativeArch}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Number",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "version.Number"
+ },
+ {
+ "id": "WireGuardGoVersion",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "device.WireGuardGoVersion"
+ },
+ {
+ "id": "Version_go",
+ "string": "%[3]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 3,
+ "expr": "strings.TrimPrefix(runtime.Version(), \"go\")"
+ },
+ {
+ "id": "GOARCH",
+ "string": "%[4]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 4,
+ "expr": "runtime.GOARCH"
+ },
+ {
+ "id": "OsName",
+ "string": "%[5]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 5,
+ "expr": "version.OsName()"
+ },
+ {
+ "id": "NativeArch",
+ "string": "%[6]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 6,
+ "expr": "version.NativeArch()"
+ }
+ ]
+ },
+ {
+ "id": "Close",
+ "message": "Close",
+ "translation": "Schließen",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "♥ &Donate!",
+ "message": "♥ &Donate!",
+ "translation": "♥ &Spenden!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status:",
+ "message": "Status:",
+ "translation": "Status:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Deactivate",
+ "message": "&Deactivate",
+ "translation": "&Deaktivieren",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Activate",
+ "message": "&Activate",
+ "translation": "&Aktivieren",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Public key:",
+ "message": "Public key:",
+ "translation": "Öffentlicher Schlüssel:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Listen port:",
+ "message": "Listen port:",
+ "translation": "Eingangsport:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "MTU:",
+ "message": "MTU:",
+ "translation": "MTU:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses:",
+ "message": "Addresses:",
+ "translation": "Adressen:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "DNS servers:",
+ "message": "DNS servers:",
+ "translation": "DNS-Server:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Scripts:",
+ "message": "Scripts:",
+ "translation": "Skripte:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Preshared key:",
+ "message": "Preshared key:",
+ "translation": "Geteilter Schlüssel:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Allowed IPs:",
+ "message": "Allowed IPs:",
+ "translation": "Erlaubte IPs:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Endpoint:",
+ "message": "Endpoint:",
+ "translation": "Endpunkt:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Persistent keepalive:",
+ "message": "Persistent keepalive:",
+ "translation": "Erhaltungsintervall:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Latest handshake:",
+ "message": "Latest handshake:",
+ "translation": "Letzter Schlüsseltausch:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Transfer:",
+ "message": "Transfer:",
+ "translation": "Übertragen:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-up",
+ "message": "pre-up",
+ "translation": "vor Verbindsaufbau",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-up",
+ "message": "post-up",
+ "translation": "nach Verbindungsaufbau",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-down",
+ "message": "pre-down",
+ "translation": "vor Verbindungsabbau",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-down",
+ "message": "post-down",
+ "translation": "nach Verbindungsabbau",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "disabled, per policy",
+ "message": "disabled, per policy",
+ "translation": "deaktiviert, per Richtlinie",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "enabled",
+ "message": "enabled",
+ "translation": "aktiviert",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{String} received, {String_1} sent",
+ "message": "{String} received, {String_1} sent",
+ "translation": "{String} empfangen, {String_1} gesendet",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "String",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "c.RxBytes.String()"
+ },
+ {
+ "id": "String_1",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "c.TxBytes.String()"
+ }
+ ]
+ },
+ {
+ "id": "Failed to determine tunnel state",
+ "message": "Failed to determine tunnel state",
+ "translation": "Tunnelstatus konnte nicht ermittelt werden",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to activate tunnel",
+ "message": "Failed to activate tunnel",
+ "translation": "Tunnel aktivieren ist fehlgeschlagen",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to deactivate tunnel",
+ "message": "Failed to deactivate tunnel",
+ "translation": "Tunnel deaktivieren ist fehlgeschlagen",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Interface: {Name}",
+ "message": "Interface: {Name}",
+ "translation": "Schnittstelle: {Name}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "config.Name"
+ }
+ ]
+ },
+ {
+ "id": "Peer",
+ "message": "Peer",
+ "translation": "Teilnehmer",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Create new tunnel",
+ "message": "Create new tunnel",
+ "translation": "Einen neuen Tunnel erstellen",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit tunnel",
+ "message": "Edit tunnel",
+ "translation": "Tunnel bearbeiten",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Name:",
+ "message": "&Name:",
+ "translation": "&Name:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Public key:",
+ "message": "&Public key:",
+ "translation": "&Öffentlicher Schlüssel:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(unknown)",
+ "message": "(unknown)",
+ "translation": "(unbekannt)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Block untunneled traffic (kill-switch)",
+ "message": "&Block untunneled traffic (kill-switch)",
+ "translation": "&Blockiere Verkehr außerhalb des Tunnels (Not-Aus)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.",
+ "message": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.",
+ "translation": "Wenn eine Konfiguration genau einen Teilnehmer enthält und dieser einen der Einträge 0.0.0.0/0 oder ::/0 in den erlaubten IPs enthält, dann erstellt der Tunneldienst ein Firewall-Regelsatz, der allen Verkehr (insbesondere zum falschen DNS-Server) blockiert, der weder aus dem Tunnel stammt noch in den Tunnel geht, mit Ausnahmen für DHCP- und NDP-Verkehr.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save",
+ "message": "&Save",
+ "translation": "&Speichern",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Cancel",
+ "message": "Cancel",
+ "translation": "Abbrechen",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Configuration:",
+ "message": "&Configuration:",
+ "translation": "&Konfiguration:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid name",
+ "message": "Invalid name",
+ "translation": "Ungültiger Name",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A name is required.",
+ "message": "A name is required.",
+ "translation": "Ein Name ist notwendig.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name ‘{NewName}’ is invalid.",
+ "message": "Tunnel name ‘{NewName}’ is invalid.",
+ "translation": "Der Name „{NewName}“ ist ungültig.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to list existing tunnels",
+ "message": "Unable to list existing tunnels",
+ "translation": "Vorhandene Tunnel können nicht aufgelistet werden",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel already exists",
+ "message": "Tunnel already exists",
+ "translation": "Tunnel existiert bereits",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{NewName}’.",
+ "message": "Another tunnel already exists with the name ‘{NewName}’.",
+ "translation": "Ein Tunnel mit dem Namen „{NewName}“ existiert bereits.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create new configuration",
+ "message": "Unable to create new configuration",
+ "translation": "Neue Konfiguration kann nicht erstellt werden",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Writing file failed",
+ "message": "Writing file failed",
+ "translation": "Schreiben der Datei schlug fehl",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "message": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "translation": "Die Datei „{FilePath}“ existiert bereits.\n\nMöchten Sie sie ersetzen?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "FilePath",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "filePath"
+ }
+ ]
+ },
+ {
+ "id": "Active",
+ "message": "Active",
+ "translation": "Aktiv",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Activating",
+ "message": "Activating",
+ "translation": "Aktiviere",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Inactive",
+ "message": "Inactive",
+ "translation": "Inaktiv",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Deactivating",
+ "message": "Deactivating",
+ "translation": "Deaktiviere",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unknown state",
+ "message": "Unknown state",
+ "translation": "Unbekannter Zustand",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log",
+ "message": "Log",
+ "translation": "Protokoll",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Copy",
+ "message": "&Copy",
+ "translation": "&Kopieren",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Select &all",
+ "message": "Select &all",
+ "translation": "&Alles markieren",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save to file…",
+ "message": "&Save to file…",
+ "translation": "&In Datei Speichern…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Time",
+ "message": "Time",
+ "translation": "Zeit",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log message",
+ "message": "Log message",
+ "translation": "Protokolleintrag",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "message": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "translation": "Textdateien (*.txt)|*.txt|Alle Dateien (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export log to file",
+ "message": "Export log to file",
+ "translation": "Exportiere Protokoll in Datei",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&About WireGuard…",
+ "message": "&About WireGuard…",
+ "translation": "&Über WireGuard…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel Error",
+ "message": "Tunnel Error",
+ "translation": "Tunnel Fehler",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "message": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "translation": "{ErrMsg}\n\nBitte lesen Sie das Protokoll für weitere Informationen.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "ErrMsg",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errMsg"
+ }
+ ]
+ },
+ {
+ "id": "{Title} (out of date)",
+ "message": "{Title} (out of date)",
+ "translation": "{Title} (veraltet)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Detection Error",
+ "message": "WireGuard Detection Error",
+ "translation": "WireGuard Erkennungsfehler",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to wait for WireGuard window to appear: {Err}",
+ "message": "Unable to wait for WireGuard window to appear: {Err}",
+ "translation": "Warten auf das Erscheinen des WireGuard Fensters nicht möglich: {Err} ",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard: Deactivated",
+ "message": "WireGuard: Deactivated",
+ "translation": "WireGuard: Deaktiviert",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Unknown",
+ "message": "Status: Unknown",
+ "translation": "Status: Unbekannt",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses: None",
+ "message": "Addresses: None",
+ "translation": "Adressen: Keine",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Manage tunnels…",
+ "message": "&Manage tunnels…",
+ "translation": "Tunnel &verwalten…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Import tunnel(s) from file…",
+ "message": "&Import tunnel(s) from file…",
+ "translation": "Tunnel aus Datei &importieren…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "E&xit",
+ "message": "E&xit",
+ "translation": "&Beenden",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Tunnels",
+ "message": "&Tunnels",
+ "translation": "&Tunnel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Activated",
+ "message": "WireGuard Activated",
+ "translation": "WireGuard aktiviert",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been activated.",
+ "message": "The {Name} tunnel has been activated.",
+ "translation": "Der Tunnel {Name} wurde aktiviert.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Deactivated",
+ "message": "WireGuard Deactivated",
+ "translation": "WireGuard deaktiviert",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been deactivated.",
+ "message": "The {Name} tunnel has been deactivated.",
+ "translation": "Der Tunnel {Name} wurde deaktiviert.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Tunnel Error",
+ "message": "WireGuard Tunnel Error",
+ "translation": "WireGuard Tunnel Fehler",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard: {TextForStateglobalState_true}",
+ "message": "WireGuard: {TextForStateglobalState_true}",
+ "translation": "WireGuard: {TextForStateglobalState_true}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TextForStateglobalState_true",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "textForState(globalState, true)"
+ }
+ ]
+ },
+ {
+ "id": "Status: {StateText}",
+ "message": "Status: {StateText}",
+ "translation": "Status: {StateText}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "StateText",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "stateText"
+ }
+ ]
+ },
+ {
+ "id": "Addresses: {EnumerationSeparator}",
+ "message": "Addresses: {EnumerationSeparator}",
+ "translation": "Adressen: {EnumerationSeparator}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "EnumerationSeparator",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "strings.Join(addrs, l18n.EnumerationSeparator())"
+ }
+ ]
+ },
+ {
+ "id": "An Update is Available!",
+ "message": "An Update is Available!",
+ "translation": "Eine Aktualisierung ist verfügbar!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Update Available",
+ "message": "WireGuard Update Available",
+ "translation": "WireGuard Aktualisierung verfügbar",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "message": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "translation": "Eine Aktualisierung für WireGuard ist jetzt verfügbar. Es wird empfohlen diese schnellstmöglich durchzuführen.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnels",
+ "message": "Tunnels",
+ "translation": "Tunnel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Edit",
+ "message": "&Edit",
+ "translation": "&Bearbeiten",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add &empty tunnel…",
+ "message": "Add &empty tunnel…",
+ "translation": "Einen &leeren Tunnel hinzufügen…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add Tunnel",
+ "message": "Add Tunnel",
+ "translation": "Tunnel hinzufügen",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Remove selected tunnel(s)",
+ "message": "Remove selected tunnel(s)",
+ "translation": "Markierte(n) Tunnel entfernen",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to zip",
+ "message": "Export all tunnels to zip",
+ "translation": "Alle Tunnel in eine Zip-Datei exportieren",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Toggle",
+ "message": "&Toggle",
+ "translation": "&Umschalten",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to &zip…",
+ "message": "Export all tunnels to &zip…",
+ "translation": "Exportiere alle Tunnel in &Zip-Datei",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit &selected tunnel…",
+ "message": "Edit &selected tunnel…",
+ "translation": "Ausgewählten Tunnel &bearbeiten…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Remove selected tunnel(s)",
+ "message": "&Remove selected tunnel(s)",
+ "translation": "Ausgewählte(n) Tunnel &löschen",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "no configuration files were found",
+ "message": "no configuration files were found",
+ "translation": "keine Konfigurationsdateien gefunden",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Could not import selected configuration: {LastErr}",
+ "message": "Could not import selected configuration: {LastErr}",
+ "translation": "Ausgewählte Konfiguration konnte nicht importiert werden: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Could not enumerate existing tunnels: {LastErr}",
+ "message": "Could not enumerate existing tunnels: {LastErr}",
+ "translation": "Konnte existierende Tunnel nicht auflisten: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{Name}’",
+ "message": "Another tunnel already exists with the name ‘{Name}’",
+ "translation": "Es existiert bereits ein Tunnel mit dem Namen „{Name}“",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "unparsedConfig.Name"
+ }
+ ]
+ },
+ {
+ "id": "Unable to import configuration: {LastErr}",
+ "message": "Unable to import configuration: {LastErr}",
+ "translation": "Importieren der Konfiguration nicht möglich: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Imported tunnels",
+ "message": "Imported tunnels",
+ "translation": "Tunnel importiert",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Imported {M} tunnels",
+ "message": "Imported {M} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "M",
+ "cases": {
+ "one": {
+ "msg": "{M} Tunnel importiert"
+ },
+ "other": {
+ "msg": "{M} Tunnel importiert"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ }
+ ]
+ },
+ {
+ "id": "Imported {M} of {N} tunnels",
+ "message": "Imported {M} of {N} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "N",
+ "cases": {
+ "one": {
+ "msg": "{M} von {N} Tunnel importiert"
+ },
+ "other": {
+ "msg": "{M} von {N} Tunnel importiert"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ },
+ {
+ "id": "N",
+ "string": "%[2]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 2,
+ "expr": "n"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create tunnel",
+ "message": "Unable to create tunnel",
+ "translation": "Tunnel erstellen nicht möglich",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Delete {TunnelCount} tunnels",
+ "message": "Delete {TunnelCount} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "{TunnelCount} Tunnel löschen"
+ },
+ "other": {
+ "msg": "{TunnelCount} Tunnel löschen"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "message": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "Möchten Sie diesen {TunnelCount} Tunnel wirklich löschen?"
+ },
+ "other": {
+ "msg": "Möchten Sie diese {TunnelCount} Tunnel wirklich löschen?"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Delete tunnel ‘{TunnelName}’",
+ "message": "Delete tunnel ‘{TunnelName}’",
+ "translation": "Tunnel „{TunnelName}“ löschen",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "message": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "translation": "Möchten Sie den Tunnel „{TunnelName}“ wirklich löschen?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "{Question} You cannot undo this action.",
+ "message": "{Question} You cannot undo this action.",
+ "translation": "{Question} Dieser Schritt kann nicht rückgängig gemacht werden.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Question",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "question"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnel",
+ "message": "Unable to delete tunnel",
+ "translation": "Tunnel löschen nicht möglich",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A tunnel was unable to be removed: {Error}",
+ "message": "A tunnel was unable to be removed: {Error}",
+ "translation": "Ein Tunnel konnte nicht gelöscht werden: {Error}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Error",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errors[0].Error()"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnels",
+ "message": "Unable to delete tunnels",
+ "translation": "Tunnel konnten nicht gelöscht werden",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Lenerrors} tunnels were unable to be removed.",
+ "message": "{Lenerrors} tunnels were unable to be removed.",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Lenerrors",
+ "cases": {
+ "one": {
+ "msg": "{Lenerrors} Tunnel konnte nicht entfernt werden."
+ },
+ "other": {
+ "msg": "{Lenerrors} Tunnel konnten nicht gelöscht werden."
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Lenerrors",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "len(errors)"
+ }
+ ]
+ },
+ {
+ "id": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "message": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "translation": "Konfigurationsdateien (*.zip, *.conf)|*.zip;*.conf|Alle Dateien (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Import tunnel(s) from file",
+ "message": "Import tunnel(s) from file",
+ "translation": "Importiere Tunnel aus Datei",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Configuration ZIP Files (*.zip)|*.zip",
+ "message": "Configuration ZIP Files (*.zip)|*.zip",
+ "translation": "Konfigurations-ZIP-Dateien (*.zip)|*.zip",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export tunnels to zip",
+ "message": "Export tunnels to zip",
+ "translation": "Exportiere Tunnel in Zip-Datei",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Title} (unsigned build, no updates)",
+ "message": "{Title} (unsigned build, no updates)",
+ "translation": "{Title} (unsigniert, keine Aktualisierungen)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "Error Exiting WireGuard",
+ "message": "Error Exiting WireGuard",
+ "translation": "Fehler beim Beenden von WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "message": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "translation": "Der Dienst konnte nicht gestoppt werden: {Err}. Versuchen Sie WireGuard in der Dienstverwaltung zu beenden.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "message": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "translation": "Eine Aktualisierung für WireGuard ist verfügbar. Es ist höchst empfehlenswert diese sofort durchzuführen.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for user",
+ "message": "Status: Waiting for user",
+ "translation": "Status: Auf Nutzer warten",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Update Now",
+ "message": "Update Now",
+ "translation": "Jetzt aktualisieren",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for updater service",
+ "message": "Status: Waiting for updater service",
+ "translation": "Status: Auf Aktualisierungsdienst warten",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error: {Err}. Please try again.",
+ "message": "Error: {Err}. Please try again.",
+ "translation": "Fehler: {Err}. Bitte versuchen Sie es erneut.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Status: Complete!",
+ "message": "Status: Complete!",
+ "translation": "Status: Fertig!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "http2: Framer {F}: failed to decode just-written frame",
+ "message": "http2: Framer {F}: failed to decode just-written frame",
+ "translation": "http2: Framer {F}: Fehler beim dekodieren des gerade geschriebenen Frames",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ }
+ ]
+ },
+ {
+ "id": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "message": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "translation": "http2: Framer {F}: {Http2summarizeFramefr} geschrieben",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ },
+ {
+ "id": "Http2summarizeFramefr",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(fr)"
+ }
+ ]
+ },
+ {
+ "id": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "message": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "translation": "http2: Framer {Fr}: {Http2summarizeFramef} gelesen",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Fr",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "fr"
+ },
+ {
+ "id": "Http2summarizeFramef",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(f)"
+ }
+ ]
+ },
+ {
+ "id": "http2: decoded hpack field {HeaderField}",
+ "message": "http2: decoded hpack field {HeaderField}",
+ "translation": "http2: hpack Feld {HeaderField} dekodiert",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "HeaderField",
+ "string": "%+[1]v",
+ "type": "vendor/golang.org/x/net/http2/hpack.HeaderField",
+ "underlyingType": "struct{Name string; Value string; Sensitive bool}",
+ "argNum": 1,
+ "expr": "hf"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/locales/en/messages.gotext.json b/locales/en/messages.gotext.json
new file mode 100644
index 00000000..735df0d5
--- /dev/null
+++ b/locales/en/messages.gotext.json
@@ -0,0 +1,1955 @@
+{
+ "language": "en",
+ "messages": [
+ {
+ "id": "Error",
+ "message": "Error",
+ "translation": "Error",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "(no argument): elevate and install manager service",
+ "message": "(no argument): elevate and install manager service",
+ "translation": "(no argument): elevate and install manager service",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Usage: {Args0} [\n{String}]",
+ "message": "Usage: {Args0} [\n{String}]",
+ "translation": "Usage: {Args0} [\n{String}]",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Args0",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "os.Args[0]"
+ },
+ {
+ "id": "String",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "builder.String()"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Command Line Options",
+ "message": "Command Line Options",
+ "translation": "Command Line Options",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Unable to determine whether the process is running under WOW64: {Err}",
+ "message": "Unable to determine whether the process is running under WOW64: {Err}",
+ "translation": "Unable to determine whether the process is running under WOW64: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "You must use the 64-bit version of WireGuard on this computer.",
+ "message": "You must use the 64-bit version of WireGuard on this computer.",
+ "translation": "You must use the 64-bit version of WireGuard on this computer.",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Unable to open current process token: {Err}",
+ "message": "Unable to open current process token: {Err}",
+ "translation": "Unable to open current process token: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "WireGuard system tray icon did not appear after 30 seconds.",
+ "message": "WireGuard system tray icon did not appear after 30 seconds.",
+ "translation": "WireGuard system tray icon did not appear after 30 seconds.",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Now",
+ "message": "Now",
+ "translation": "Now",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "System clock wound backward!",
+ "message": "System clock wound backward!",
+ "translation": "System clock wound backward!",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "{Years} year(s)",
+ "message": "{Years} year(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Years",
+ "cases": {
+ "one": {
+ "msg": "{Years} year"
+ },
+ "other": {
+ "msg": "{Years} years"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Years",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "years"
+ }
+ ]
+ },
+ {
+ "id": "{Days} day(s)",
+ "message": "{Days} day(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Days",
+ "cases": {
+ "one": {
+ "msg": "{Days} day"
+ },
+ "other": {
+ "msg": "{Days} days"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Days",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "days"
+ }
+ ]
+ },
+ {
+ "id": "{Hours} hour(s)",
+ "message": "{Hours} hour(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Hours",
+ "cases": {
+ "one": {
+ "msg": "{Hours} hour"
+ },
+ "other": {
+ "msg": "{Hours} hours"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Hours",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "hours"
+ }
+ ]
+ },
+ {
+ "id": "{Minutes} minute(s)",
+ "message": "{Minutes} minute(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Minutes",
+ "cases": {
+ "one": {
+ "msg": "{Minutes} minute"
+ },
+ "other": {
+ "msg": "{Minutes} minutes"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Minutes",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "minutes"
+ }
+ ]
+ },
+ {
+ "id": "{Seconds} second(s)",
+ "message": "{Seconds} second(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Seconds",
+ "cases": {
+ "one": {
+ "msg": "{Seconds} second"
+ },
+ "other": {
+ "msg": "{Seconds} seconds"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Seconds",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "seconds"
+ }
+ ]
+ },
+ {
+ "id": "{Timestamp} ago",
+ "message": "{Timestamp} ago",
+ "translation": "{Timestamp} ago",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Timestamp",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "timestamp"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "{Bytes} B",
+ "message": "{Bytes} B",
+ "translation": "{Bytes} B",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Bytes",
+ "string": "%[1]d",
+ "type": "golang.zx2c4.com/wireguard/windows/conf.Bytes",
+ "underlyingType": "uint64",
+ "argNum": 1,
+ "expr": "b"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "{Float64b__1024} KiB",
+ "message": "{Float64b__1024} KiB",
+ "translation": "{Float64b__1024} KiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / 1024"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "{Float64b__1024__1024} MiB",
+ "message": "{Float64b__1024__1024} MiB",
+ "translation": "{Float64b__1024__1024} MiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024)"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "{Float64b__1024__1024__1024} GiB",
+ "message": "{Float64b__1024__1024__1024} GiB",
+ "translation": "{Float64b__1024__1024__1024} GiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024)"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "{Float64b__1024__1024__1024__1024} TiB",
+ "message": "{Float64b__1024__1024__1024__1024} TiB",
+ "translation": "{Float64b__1024__1024__1024__1024} TiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024) / 1024"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "{Why}: {Offender}",
+ "message": "{Why}: {Offender}",
+ "translation": "{Why}: {Offender}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Why",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "e.why"
+ },
+ {
+ "id": "Offender",
+ "string": "%[2]q",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "e.offender"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Invalid IP address",
+ "message": "Invalid IP address",
+ "translation": "Invalid IP address",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Invalid network prefix length",
+ "message": "Invalid network prefix length",
+ "translation": "Invalid network prefix length",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Missing port from endpoint",
+ "message": "Missing port from endpoint",
+ "translation": "Missing port from endpoint",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Invalid endpoint host",
+ "message": "Invalid endpoint host",
+ "translation": "Invalid endpoint host",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Brackets must contain an IPv6 address",
+ "message": "Brackets must contain an IPv6 address",
+ "translation": "Brackets must contain an IPv6 address",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Invalid MTU",
+ "message": "Invalid MTU",
+ "translation": "Invalid MTU",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Invalid port",
+ "message": "Invalid port",
+ "translation": "Invalid port",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Invalid persistent keepalive",
+ "message": "Invalid persistent keepalive",
+ "translation": "Invalid persistent keepalive",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Invalid key: {Err}",
+ "message": "Invalid key: {Err}",
+ "translation": "Invalid key: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Keys must decode to exactly 32 bytes",
+ "message": "Keys must decode to exactly 32 bytes",
+ "translation": "Keys must decode to exactly 32 bytes",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Number must be a number between 0 and 2^64-1: {Err}",
+ "message": "Number must be a number between 0 and 2^64-1: {Err}",
+ "translation": "Number must be a number between 0 and 2^64-1: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Two commas in a row",
+ "message": "Two commas in a row",
+ "translation": "Two commas in a row",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Tunnel name is not valid",
+ "message": "Tunnel name is not valid",
+ "translation": "Tunnel name is not valid",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Line must occur in a section",
+ "message": "Line must occur in a section",
+ "translation": "Line must occur in a section",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Config key is missing an equals separator",
+ "message": "Config key is missing an equals separator",
+ "translation": "Config key is missing an equals separator",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Key must have a value",
+ "message": "Key must have a value",
+ "translation": "Key must have a value",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Invalid key for [Interface] section",
+ "message": "Invalid key for [Interface] section",
+ "translation": "Invalid key for [Interface] section",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Invalid key for [Peer] section",
+ "message": "Invalid key for [Peer] section",
+ "translation": "Invalid key for [Peer] section",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "An interface must have a private key",
+ "message": "An interface must have a private key",
+ "translation": "An interface must have a private key",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "[none specified]",
+ "message": "[none specified]",
+ "translation": "[none specified]",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "All peers must have public keys",
+ "message": "All peers must have public keys",
+ "translation": "All peers must have public keys",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Error in getting configuration",
+ "message": "Error in getting configuration",
+ "translation": "Error in getting configuration",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Invalid key for interface section",
+ "message": "Invalid key for interface section",
+ "translation": "Invalid key for interface section",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Protocol version must be 1",
+ "message": "Protocol version must be 1",
+ "translation": "Protocol version must be 1",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Invalid key for peer section",
+ "message": "Invalid key for peer section",
+ "translation": "Invalid key for peer section",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "[EnumerationSeparator]",
+ "message": "[EnumerationSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert between items when listing - most western languages will translate ‘[EnumerationSeparator]’ into ‘, ’ to produce lists like ‘apple, orange, strawberry’. Eastern languages might translate into ‘、’ to produce lists like ‘リンゴ、オレンジ、イチゴ’."
+ },
+ {
+ "id": "[UnitSeparator]",
+ "message": "[UnitSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert when combining units of a measure - most languages will translate ‘[UnitSeparator]’ into ‘ ’ (space) to produce lists like ‘2 minuti 30 sekund’, or empty string ‘’ to produce ‘2分30秒’."
+ },
+ {
+ "id": "About WireGuard",
+ "message": "About WireGuard",
+ "translation": "About WireGuard",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "WireGuard logo image",
+ "message": "WireGuard logo image",
+ "translation": "WireGuard logo image",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}\nOperating system: {OsName}\nArchitecture: {GOARCH}",
+ "message": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}\nOperating system: {OsName}\nArchitecture: {GOARCH}",
+ "translation": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}\nOperating system: {OsName}\nArchitecture: {GOARCH}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Number",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "version.Number"
+ },
+ {
+ "id": "WireGuardGoVersion",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "device.WireGuardGoVersion"
+ },
+ {
+ "id": "Version_go",
+ "string": "%[3]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 3,
+ "expr": "strings.TrimPrefix(runtime.Version(), \"go\")"
+ },
+ {
+ "id": "OsName",
+ "string": "%[4]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 4,
+ "expr": "version.OsName()"
+ },
+ {
+ "id": "GOARCH",
+ "string": "%[5]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 5,
+ "expr": "runtime.GOARCH"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Close",
+ "message": "Close",
+ "translation": "Close",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "♥ \u0026Donate!",
+ "message": "♥ \u0026Donate!",
+ "translation": "♥ \u0026Donate!",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Status:",
+ "message": "Status:",
+ "translation": "Status:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Deactivate",
+ "message": "\u0026Deactivate",
+ "translation": "\u0026Deactivate",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Activate",
+ "message": "\u0026Activate",
+ "translation": "\u0026Activate",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Public key:",
+ "message": "Public key:",
+ "translation": "Public key:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Listen port:",
+ "message": "Listen port:",
+ "translation": "Listen port:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "MTU:",
+ "message": "MTU:",
+ "translation": "MTU:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Addresses:",
+ "message": "Addresses:",
+ "translation": "Addresses:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "DNS servers:",
+ "message": "DNS servers:",
+ "translation": "DNS servers:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Preshared key:",
+ "message": "Preshared key:",
+ "translation": "Preshared key:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Allowed IPs:",
+ "message": "Allowed IPs:",
+ "translation": "Allowed IPs:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Endpoint:",
+ "message": "Endpoint:",
+ "translation": "Endpoint:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Persistent keepalive:",
+ "message": "Persistent keepalive:",
+ "translation": "Persistent keepalive:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Latest handshake:",
+ "message": "Latest handshake:",
+ "translation": "Latest handshake:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Transfer:",
+ "message": "Transfer:",
+ "translation": "Transfer:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "enabled",
+ "message": "enabled",
+ "translation": "enabled",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "{String} received, {String_1} sent",
+ "message": "{String} received, {String_1} sent",
+ "translation": "{String} received, {String_1} sent",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "String",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "c.RxBytes.String()"
+ },
+ {
+ "id": "String_1",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "c.TxBytes.String()"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Failed to determine tunnel state",
+ "message": "Failed to determine tunnel state",
+ "translation": "Failed to determine tunnel state",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Failed to activate tunnel",
+ "message": "Failed to activate tunnel",
+ "translation": "Failed to activate tunnel",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Failed to deactivate tunnel",
+ "message": "Failed to deactivate tunnel",
+ "translation": "Failed to deactivate tunnel",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Interface: {Name}",
+ "message": "Interface: {Name}",
+ "translation": "Interface: {Name}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "config.Name"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Peer",
+ "message": "Peer",
+ "translation": "Peer",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Create new tunnel",
+ "message": "Create new tunnel",
+ "translation": "Create new tunnel",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Edit tunnel",
+ "message": "Edit tunnel",
+ "translation": "Edit tunnel",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Name:",
+ "message": "\u0026Name:",
+ "translation": "\u0026Name:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Public key:",
+ "message": "\u0026Public key:",
+ "translation": "\u0026Public key:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "(unknown)",
+ "message": "(unknown)",
+ "translation": "(unknown)",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Block untunneled traffic (kill-switch)",
+ "message": "\u0026Block untunneled traffic (kill-switch)",
+ "translation": "\u0026Block untunneled traffic (kill-switch)",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface, with special exceptions for DHCP and NDP.",
+ "message": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface, with special exceptions for DHCP and NDP.",
+ "translation": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface, with special exceptions for DHCP and NDP.",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Save",
+ "message": "\u0026Save",
+ "translation": "\u0026Save",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Cancel",
+ "message": "Cancel",
+ "translation": "Cancel",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Configuration:",
+ "message": "\u0026Configuration:",
+ "translation": "\u0026Configuration:",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Invalid name",
+ "message": "Invalid name",
+ "translation": "Invalid name",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "A name is required.",
+ "message": "A name is required.",
+ "translation": "A name is required.",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Tunnel name ‘{NewName}’ is invalid.",
+ "message": "Tunnel name ‘{NewName}’ is invalid.",
+ "translation": "Tunnel name ‘{NewName}’ is invalid.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Unable to list existing tunnels",
+ "message": "Unable to list existing tunnels",
+ "translation": "Unable to list existing tunnels",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Tunnel already exists",
+ "message": "Tunnel already exists",
+ "translation": "Tunnel already exists",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{NewName}’.",
+ "message": "Another tunnel already exists with the name ‘{NewName}’.",
+ "translation": "Another tunnel already exists with the name ‘{NewName}’.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Unable to create new configuration",
+ "message": "Unable to create new configuration",
+ "translation": "Unable to create new configuration",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Writing file failed",
+ "message": "Writing file failed",
+ "translation": "Writing file failed",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "message": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "translation": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "FilePath",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "filePath"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Active",
+ "message": "Active",
+ "translation": "Active",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Activating",
+ "message": "Activating",
+ "translation": "Activating",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Inactive",
+ "message": "Inactive",
+ "translation": "Inactive",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Deactivating",
+ "message": "Deactivating",
+ "translation": "Deactivating",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Unknown state",
+ "message": "Unknown state",
+ "translation": "Unknown state",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Log",
+ "message": "Log",
+ "translation": "Log",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Copy",
+ "message": "\u0026Copy",
+ "translation": "\u0026Copy",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Select \u0026all",
+ "message": "Select \u0026all",
+ "translation": "Select \u0026all",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Save to file…",
+ "message": "\u0026Save to file…",
+ "translation": "\u0026Save to file…",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Time",
+ "message": "Time",
+ "translation": "Time",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Log message",
+ "message": "Log message",
+ "translation": "Log message",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "message": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "translation": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Export log to file",
+ "message": "Export log to file",
+ "translation": "Export log to file",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026About WireGuard…",
+ "message": "\u0026About WireGuard…",
+ "translation": "\u0026About WireGuard…",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Tunnel Error",
+ "message": "Tunnel Error",
+ "translation": "Tunnel Error",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "message": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "translation": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "ErrMsg",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errMsg"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "{Title} (out of date)",
+ "message": "{Title} (out of date)",
+ "translation": "{Title} (out of date)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "WireGuard Detection Error",
+ "message": "WireGuard Detection Error",
+ "translation": "WireGuard Detection Error",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Unable to wait for WireGuard window to appear: {Err}",
+ "message": "Unable to wait for WireGuard window to appear: {Err}",
+ "translation": "Unable to wait for WireGuard window to appear: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "WireGuard: Deactivated",
+ "message": "WireGuard: Deactivated",
+ "translation": "WireGuard: Deactivated",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Status: Unknown",
+ "message": "Status: Unknown",
+ "translation": "Status: Unknown",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Addresses: None",
+ "message": "Addresses: None",
+ "translation": "Addresses: None",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Manage tunnels…",
+ "message": "\u0026Manage tunnels…",
+ "translation": "\u0026Manage tunnels…",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Import tunnel(s) from file…",
+ "message": "\u0026Import tunnel(s) from file…",
+ "translation": "\u0026Import tunnel(s) from file…",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "E\u0026xit",
+ "message": "E\u0026xit",
+ "translation": "E\u0026xit",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "WireGuard Tunnel Error",
+ "message": "WireGuard Tunnel Error",
+ "translation": "WireGuard Tunnel Error",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "WireGuard: {TextForStateglobalState_true}",
+ "message": "WireGuard: {TextForStateglobalState_true}",
+ "translation": "WireGuard: {TextForStateglobalState_true}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TextForStateglobalState_true",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "textForState(globalState, true)"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Status: {StateText}",
+ "message": "Status: {StateText}",
+ "translation": "Status: {StateText}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "StateText",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "stateText"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Addresses: {String}",
+ "message": "Addresses: {String}",
+ "translation": "Addresses: {String}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "String",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "sb.String()"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "WireGuard Activated",
+ "message": "WireGuard Activated",
+ "translation": "WireGuard Activated",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "The {Name} tunnel has been activated.",
+ "message": "The {Name} tunnel has been activated.",
+ "translation": "The {Name} tunnel has been activated.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "WireGuard Deactivated",
+ "message": "WireGuard Deactivated",
+ "translation": "WireGuard Deactivated",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "The {Name} tunnel has been deactivated.",
+ "message": "The {Name} tunnel has been deactivated.",
+ "translation": "The {Name} tunnel has been deactivated.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "An Update is Available!",
+ "message": "An Update is Available!",
+ "translation": "An Update is Available!",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "WireGuard Update Available",
+ "message": "WireGuard Update Available",
+ "translation": "WireGuard Update Available",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "message": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "translation": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Tunnels",
+ "message": "Tunnels",
+ "translation": "Tunnels",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Edit",
+ "message": "\u0026Edit",
+ "translation": "\u0026Edit",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Add \u0026empty tunnel…",
+ "message": "Add \u0026empty tunnel…",
+ "translation": "Add \u0026empty tunnel…",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Add Tunnel",
+ "message": "Add Tunnel",
+ "translation": "Add Tunnel",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Remove selected tunnel(s)",
+ "message": "Remove selected tunnel(s)",
+ "translation": "Remove selected tunnel(s)",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Export all tunnels to zip",
+ "message": "Export all tunnels to zip",
+ "translation": "Export all tunnels to zip",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Toggle",
+ "message": "\u0026Toggle",
+ "translation": "\u0026Toggle",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Export all tunnels to \u0026zip…",
+ "message": "Export all tunnels to \u0026zip…",
+ "translation": "Export all tunnels to \u0026zip…",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Edit \u0026selected tunnel…",
+ "message": "Edit \u0026selected tunnel…",
+ "translation": "Edit \u0026selected tunnel…",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "\u0026Remove selected tunnel(s)",
+ "message": "\u0026Remove selected tunnel(s)",
+ "translation": "\u0026Remove selected tunnel(s)",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Could not import selected configuration: {LastErr}",
+ "message": "Could not import selected configuration: {LastErr}",
+ "translation": "Could not import selected configuration: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Could not enumerate existing tunnels: {LastErr}",
+ "message": "Could not enumerate existing tunnels: {LastErr}",
+ "translation": "Could not enumerate existing tunnels: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{Name}’",
+ "message": "Another tunnel already exists with the name ‘{Name}’",
+ "translation": "Another tunnel already exists with the name ‘{Name}’",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "unparsedConfig.Name"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Unable to import configuration: {LastErr}",
+ "message": "Unable to import configuration: {LastErr}",
+ "translation": "Unable to import configuration: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Imported tunnels",
+ "message": "Imported tunnels",
+ "translation": "Imported tunnels",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Imported {M} tunnels",
+ "message": "Imported {M} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "M",
+ "cases": {
+ "one": {
+ "msg": "Imported {M} tunnel"
+ },
+ "other": {
+ "msg": "Imported {M} tunnels"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ }
+ ]
+ },
+ {
+ "id": "Imported {M} of {N} tunnels",
+ "message": "Imported {M} of {N} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "N",
+ "cases": {
+ "one": {
+ "msg": "Imported {M} of {N} tunnel"
+ },
+ "other": {
+ "msg": "Imported {M} of {N} tunnels"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ },
+ {
+ "id": "N",
+ "string": "%[2]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 2,
+ "expr": "n"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create tunnel",
+ "message": "Unable to create tunnel",
+ "translation": "Unable to create tunnel",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Delete {TunnelCount} tunnels",
+ "message": "Delete {TunnelCount} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "Delete {TunnelCount} tunnel"
+ },
+ "other": {
+ "msg": "Delete {TunnelCount} tunnels"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "message": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "Are you sure you would like to delete {TunnelCount} tunnel?"
+ },
+ "other": {
+ "msg": "Are you sure you would like to delete {TunnelCount} tunnels?"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Delete tunnel ‘{TunnelName}’",
+ "message": "Delete tunnel ‘{TunnelName}’",
+ "translation": "Delete tunnel ‘{TunnelName}’",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "message": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "translation": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "{Question} You cannot undo this action.",
+ "message": "{Question} You cannot undo this action.",
+ "translation": "{Question} You cannot undo this action.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Question",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "question"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Unable to delete tunnel",
+ "message": "Unable to delete tunnel",
+ "translation": "Unable to delete tunnel",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "A tunnel was unable to be removed: {Error}",
+ "message": "A tunnel was unable to be removed: {Error}",
+ "translation": "A tunnel was unable to be removed: {Error}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Error",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errors[0].Error()"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Unable to delete tunnels",
+ "message": "Unable to delete tunnels",
+ "translation": "Unable to delete tunnels",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "{Lenerrors} tunnels were unable to be removed.",
+ "message": "{Lenerrors} tunnels were unable to be removed.",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Lenerrors",
+ "cases": {
+ "one": {
+ "msg": "{Lenerrors} tunnel was unable to be removed."
+ },
+ "other": {
+ "msg": "{Lenerrors} tunnels were unable to be removed."
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Lenerrors",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "len(errors)"
+ }
+ ]
+ },
+ {
+ "id": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "message": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "translation": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Import tunnel(s) from file",
+ "message": "Import tunnel(s) from file",
+ "translation": "Import tunnel(s) from file",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Configuration ZIP Files (*.zip)|*.zip",
+ "message": "Configuration ZIP Files (*.zip)|*.zip",
+ "translation": "Configuration ZIP Files (*.zip)|*.zip",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Export tunnels to zip",
+ "message": "Export tunnels to zip",
+ "translation": "Export tunnels to zip",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "{Title} (unsigned build, no updates)",
+ "message": "{Title} (unsigned build, no updates)",
+ "translation": "{Title} (unsigned build, no updates)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Error Exiting WireGuard",
+ "message": "Error Exiting WireGuard",
+ "translation": "Error Exiting WireGuard",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "message": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "translation": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "message": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "translation": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Status: Waiting for user",
+ "message": "Status: Waiting for user",
+ "translation": "Status: Waiting for user",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Update Now",
+ "message": "Update Now",
+ "translation": "Update Now",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Status: Waiting for updater service",
+ "message": "Status: Waiting for updater service",
+ "translation": "Status: Waiting for updater service",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "Error: {Err}. Please try again.",
+ "message": "Error: {Err}. Please try again.",
+ "translation": "Error: {Err}. Please try again.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "Status: Complete!",
+ "message": "Status: Complete!",
+ "translation": "Status: Complete!",
+ "translatorComment": "Copied from source.",
+ "fuzzy": true
+ },
+ {
+ "id": "http2: Framer {F}: failed to decode just-written frame",
+ "message": "http2: Framer {F}: failed to decode just-written frame",
+ "translation": "http2: Framer {F}: failed to decode just-written frame",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "message": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "translation": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ },
+ {
+ "id": "Http2summarizeFramefr",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(fr)"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "message": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "translation": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Fr",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "fr"
+ },
+ {
+ "id": "Http2summarizeFramef",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(f)"
+ }
+ ],
+ "fuzzy": true
+ },
+ {
+ "id": "http2: decoded hpack field {HeaderField}",
+ "message": "http2: decoded hpack field {HeaderField}",
+ "translation": "http2: decoded hpack field {HeaderField}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "HeaderField",
+ "string": "%+[1]v",
+ "type": "vendor/golang.org/x/net/http2/hpack.HeaderField",
+ "underlyingType": "struct{Name string; Value string; Sensitive bool}",
+ "argNum": 1,
+ "expr": "hf"
+ }
+ ],
+ "fuzzy": true
+ }
+ ]
+} \ No newline at end of file
diff --git a/locales/es-ES/messages.gotext.json b/locales/es-ES/messages.gotext.json
new file mode 100644
index 00000000..a4cff055
--- /dev/null
+++ b/locales/es-ES/messages.gotext.json
@@ -0,0 +1,1077 @@
+{
+ "language": "es-ES",
+ "messages": [
+ {
+ "id": "Error",
+ "message": "Error",
+ "translation": "Error",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(no argument): elevate and install manager service",
+ "message": "(no argument): elevate and install manager service",
+ "translation": "(sin argumento): eleva e instala el servicio de administrador",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Usage: {Args0} [\n{String}]",
+ "message": "Usage: {Args0} [\n{String}]",
+ "translation": "Uso: {Args0} [\n{String}]",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Args0",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "os.Args[0]"
+ },
+ {
+ "id": "String",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "builder.String()"
+ }
+ ]
+ },
+ {
+ "id": "Command Line Options",
+ "message": "Command Line Options",
+ "translation": "Opciones de línea de comandos",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Now",
+ "message": "Now",
+ "translation": "Ahora",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Years} year(s)",
+ "message": "{Years} year(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Years",
+ "cases": {
+ "one": {
+ "msg": "{Years} año"
+ },
+ "other": {
+ "msg": "{Years} años"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Years",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "years"
+ }
+ ]
+ },
+ {
+ "id": "{Days} day(s)",
+ "message": "{Days} day(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Days",
+ "cases": {
+ "one": {
+ "msg": "{Days} día"
+ },
+ "other": {
+ "msg": "{Days} dias"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Days",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "days"
+ }
+ ]
+ },
+ {
+ "id": "{Hours} hour(s)",
+ "message": "{Hours} hour(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Hours",
+ "cases": {
+ "one": {
+ "msg": "{Hours} hora"
+ },
+ "other": {
+ "msg": "{Hours} horas"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Hours",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "hours"
+ }
+ ]
+ },
+ {
+ "id": "{Minutes} minute(s)",
+ "message": "{Minutes} minute(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Minutes",
+ "cases": {
+ "one": {
+ "msg": "{Minutes} minuto"
+ },
+ "other": {
+ "msg": "{Minutes} minutos"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Minutes",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "minutes"
+ }
+ ]
+ },
+ {
+ "id": "{Seconds} second(s)",
+ "message": "{Seconds} second(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Seconds",
+ "cases": {
+ "one": {
+ "msg": "{Seconds} segundo"
+ },
+ "other": {
+ "msg": "{Seconds} segundos"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Seconds",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "seconds"
+ }
+ ]
+ },
+ {
+ "id": "{Timestamp} ago",
+ "message": "{Timestamp} ago",
+ "translation": "hace {Timestamp}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Timestamp",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "timestamp"
+ }
+ ]
+ },
+ {
+ "id": "{Bytes} B",
+ "message": "{Bytes} B",
+ "translation": "{Bytes} B",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Bytes",
+ "string": "%[1]d",
+ "type": "golang.zx2c4.com/wireguard/windows/conf.Bytes",
+ "underlyingType": "uint64",
+ "argNum": 1,
+ "expr": "b"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024} KiB",
+ "message": "{Float64b__1024} KiB",
+ "translation": "{Float64b__1024} KiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024} MiB",
+ "message": "{Float64b__1024__1024} MiB",
+ "translation": "{Float64b__1024__1024} MiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024} GiB",
+ "message": "{Float64b__1024__1024__1024} GiB",
+ "translation": "{Float64b__1024__1024__1024} GiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024__1024} TiB",
+ "message": "{Float64b__1024__1024__1024__1024} TiB",
+ "translation": "{Float64b__1024__1024__1024__1024} TiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "Invalid IP address",
+ "message": "Invalid IP address",
+ "translation": "Dirección IP inválida",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid network prefix length",
+ "message": "Invalid network prefix length",
+ "translation": "Longitud de prefijo de red no válida",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Missing port from endpoint",
+ "message": "Missing port from endpoint",
+ "translation": "Falta el puerto del extremo",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid endpoint host",
+ "message": "Invalid endpoint host",
+ "translation": "Host de endpoint no válido",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid MTU",
+ "message": "Invalid MTU",
+ "translation": "MTU no valido",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid port",
+ "message": "Invalid port",
+ "translation": "Puerto no válido",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key: {Err}",
+ "message": "Invalid key: {Err}",
+ "translation": "Clave no válida: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "About WireGuard",
+ "message": "About WireGuard",
+ "translation": "Acerca de WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard logo image",
+ "message": "WireGuard logo image",
+ "translation": "Imagen del logo de WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Close",
+ "message": "Close",
+ "translation": "Cerrar",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "♥ &Donate!",
+ "message": "♥ &Donate!",
+ "translation": "♥ &Donar!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status:",
+ "message": "Status:",
+ "translation": "Estado:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Deactivate",
+ "message": "&Deactivate",
+ "translation": "&Desactivar",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Activate",
+ "message": "&Activate",
+ "translation": "&Activar",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Public key:",
+ "message": "Public key:",
+ "translation": "Clave pública:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Listen port:",
+ "message": "Listen port:",
+ "translation": "Puerto de escucha:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "MTU:",
+ "message": "MTU:",
+ "translation": "MTU:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses:",
+ "message": "Addresses:",
+ "translation": "Direcciones:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "DNS servers:",
+ "message": "DNS servers:",
+ "translation": "Servidores DNS:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Preshared key:",
+ "message": "Preshared key:",
+ "translation": "Clave compartida:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Allowed IPs:",
+ "message": "Allowed IPs:",
+ "translation": "IPs permitidas:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Latest handshake:",
+ "message": "Latest handshake:",
+ "translation": "Último saludo:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Transfer:",
+ "message": "Transfer:",
+ "translation": "Transferir:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "enabled",
+ "message": "enabled",
+ "translation": "activado",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to activate tunnel",
+ "message": "Failed to activate tunnel",
+ "translation": "Error al activar el túnel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to deactivate tunnel",
+ "message": "Failed to deactivate tunnel",
+ "translation": "Error al desactivar el túnel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Interface: {Name}",
+ "message": "Interface: {Name}",
+ "translation": "Interfaz: {Name}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "config.Name"
+ }
+ ]
+ },
+ {
+ "id": "Peer",
+ "message": "Peer",
+ "translation": "Pares",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Create new tunnel",
+ "message": "Create new tunnel",
+ "translation": "Crear un túnel nuevo",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit tunnel",
+ "message": "Edit tunnel",
+ "translation": "Editar túnel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Name:",
+ "message": "&Name:",
+ "translation": "&Nombre:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Public key:",
+ "message": "&Public key:",
+ "translation": "Clave pública:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(unknown)",
+ "message": "(unknown)",
+ "translation": "(desconocido)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save",
+ "message": "&Save",
+ "translation": "&Guardar",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Cancel",
+ "message": "Cancel",
+ "translation": "Cancelar",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Configuration:",
+ "message": "&Configuration:",
+ "translation": "&Configuración:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid name",
+ "message": "Invalid name",
+ "translation": "Nombre no válido",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A name is required.",
+ "message": "A name is required.",
+ "translation": "Se requiere un nombre.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name ‘{NewName}’ is invalid.",
+ "message": "Tunnel name ‘{NewName}’ is invalid.",
+ "translation": "El nombre del túnel ‘{NewName}’ no es válido.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Active",
+ "message": "Active",
+ "translation": "Activo",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Activating",
+ "message": "Activating",
+ "translation": "Activando",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Inactive",
+ "message": "Inactive",
+ "translation": "Inactivo",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Deactivating",
+ "message": "Deactivating",
+ "translation": "Desactivando",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unknown state",
+ "message": "Unknown state",
+ "translation": "Estado desconocido",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log",
+ "message": "Log",
+ "translation": "Registro",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Copy",
+ "message": "&Copy",
+ "translation": "&Copiar",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Select &all",
+ "message": "Select &all",
+ "translation": "Seleccionar &todo",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save to file…",
+ "message": "&Save to file…",
+ "translation": "&Guardar en archivo…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Time",
+ "message": "Time",
+ "translation": "Tiempo",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log message",
+ "message": "Log message",
+ "translation": "Registro de mensajes",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "message": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "translation": "Archivos de texto (*.txt)|*.txt|Todos los archivos (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export log to file",
+ "message": "Export log to file",
+ "translation": "Exportar archivo de registro",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&About WireGuard…",
+ "message": "&About WireGuard…",
+ "translation": "&Acerca de WireGuard…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel Error",
+ "message": "Tunnel Error",
+ "translation": "Error en el túnel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "message": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "translation": "{ErrMsg}\n\nPor favor, consulte el registro para más información.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "ErrMsg",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errMsg"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard: Deactivated",
+ "message": "WireGuard: Deactivated",
+ "translation": "WireGuard: Desactivado",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Unknown",
+ "message": "Status: Unknown",
+ "translation": "Estado: Desconocido",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses: None",
+ "message": "Addresses: None",
+ "translation": "Direcciones: Ninguna",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "E&xit",
+ "message": "E&xit",
+ "translation": "&Salir",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Tunnels",
+ "message": "&Tunnels",
+ "translation": "&Túneles",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Activated",
+ "message": "WireGuard Activated",
+ "translation": "WireGuard Activado",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Deactivated",
+ "message": "WireGuard Deactivated",
+ "translation": "WireGuard: Desactivado",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Tunnel Error",
+ "message": "WireGuard Tunnel Error",
+ "translation": "Error en el túnel de WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard: {TextForStateglobalState_true}",
+ "message": "WireGuard: {TextForStateglobalState_true}",
+ "translation": "WireGuard: {TextForStateglobalState_true}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TextForStateglobalState_true",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "textForState(globalState, true)"
+ }
+ ]
+ },
+ {
+ "id": "Status: {StateText}",
+ "message": "Status: {StateText}",
+ "translation": "Estado: {StateText}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "StateText",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "stateText"
+ }
+ ]
+ },
+ {
+ "id": "Addresses: {EnumerationSeparator}",
+ "message": "Addresses: {EnumerationSeparator}",
+ "translation": "Direcciones: {EnumerationSeparator}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "EnumerationSeparator",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "strings.Join(addrs, l18n.EnumerationSeparator())"
+ }
+ ]
+ },
+ {
+ "id": "An Update is Available!",
+ "message": "An Update is Available!",
+ "translation": "Hay una actualización disponible!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Update Available",
+ "message": "WireGuard Update Available",
+ "translation": "Actualización de WireGuard disponible",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "message": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "translation": "Ya está disponible una actualización de WireGuard. Se recomienda actualizar lo antes posible.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnels",
+ "message": "Tunnels",
+ "translation": "Túneles",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Edit",
+ "message": "&Edit",
+ "translation": "&Editar",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add &empty tunnel…",
+ "message": "Add &empty tunnel…",
+ "translation": "Añadir &túnel vacío…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add Tunnel",
+ "message": "Add Tunnel",
+ "translation": "Añadir túnel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Remove selected tunnel(s)",
+ "message": "Remove selected tunnel(s)",
+ "translation": "Eliminar túneles seleccionados",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to zip",
+ "message": "Export all tunnels to zip",
+ "translation": "Exportar todos los túneles a zip",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Toggle",
+ "message": "&Toggle",
+ "translation": "&Alternar",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Could not enumerate existing tunnels: {LastErr}",
+ "message": "Could not enumerate existing tunnels: {LastErr}",
+ "translation": "No se pueden enumerar los túneles existentes: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{Name}’",
+ "message": "Another tunnel already exists with the name ‘{Name}’",
+ "translation": "Ya existe otro túnel con el nombre '{Name}'",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "unparsedConfig.Name"
+ }
+ ]
+ },
+ {
+ "id": "Unable to import configuration: {LastErr}",
+ "message": "Unable to import configuration: {LastErr}",
+ "translation": "Imposible importar la configuración: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Imported tunnels",
+ "message": "Imported tunnels",
+ "translation": "Túneles importados",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Imported {M} tunnels",
+ "message": "Imported {M} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "M",
+ "cases": {
+ "one": {
+ "msg": "{M} túnel importado"
+ },
+ "other": {
+ "msg": "{M} túneles importados"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ }
+ ]
+ },
+ {
+ "id": "Imported {M} of {N} tunnels",
+ "message": "Imported {M} of {N} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "N",
+ "cases": {
+ "one": {
+ "msg": "Importado {M} de {N} túnel"
+ },
+ "other": {
+ "msg": "Importados {M} de {N} túneles"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ },
+ {
+ "id": "N",
+ "string": "%[2]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 2,
+ "expr": "n"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create tunnel",
+ "message": "Unable to create tunnel",
+ "translation": "No se pudo crear el túnel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Delete {TunnelCount} tunnels",
+ "message": "Delete {TunnelCount} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "Eliminar {TunnelCount} túnel"
+ },
+ "other": {
+ "msg": "Eliminar {TunnelCount} túneles"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Delete tunnel ‘{TunnelName}’",
+ "message": "Delete tunnel ‘{TunnelName}’",
+ "translation": "Eliminar túnel ‘{TunnelName}’",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "message": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "translation": "¿Está seguro de que desea eliminar el túnel ‘{TunnelName}’?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "{Question} You cannot undo this action.",
+ "message": "{Question} You cannot undo this action.",
+ "translation": "{Question} No puedes deshacer esta acción.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Question",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "question"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnel",
+ "message": "Unable to delete tunnel",
+ "translation": "No se pudo eliminar el tunel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A tunnel was unable to be removed: {Error}",
+ "message": "A tunnel was unable to be removed: {Error}",
+ "translation": "No se ha podido eliminar un túnel: {Error}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Error",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errors[0].Error()"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnels",
+ "message": "Unable to delete tunnels",
+ "translation": "Imposible eliminar túneles",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for user",
+ "message": "Status: Waiting for user",
+ "translation": "Estado: Esperando al usuario",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Update Now",
+ "message": "Update Now",
+ "translation": "Actualizar",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error: {Err}. Please try again.",
+ "message": "Error: {Err}. Please try again.",
+ "translation": "Error: {Err}. Inténtalo de nuevo.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Status: Complete!",
+ "message": "Status: Complete!",
+ "translation": "Estado: Completo!",
+ "translatorComment": "Copied from source."
+ }
+ ]
+} \ No newline at end of file
diff --git a/locales/fa/messages.gotext.json b/locales/fa/messages.gotext.json
new file mode 100644
index 00000000..f0918cc7
--- /dev/null
+++ b/locales/fa/messages.gotext.json
@@ -0,0 +1,841 @@
+{
+ "language": "fa",
+ "messages": [
+ {
+ "id": "Error",
+ "message": "Error",
+ "translation": "خطا",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Command Line Options",
+ "message": "Command Line Options",
+ "translation": "گزینه‌های خط فرمان",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Now",
+ "message": "Now",
+ "translation": "هم اکنون",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Years} year(s)",
+ "message": "{Years} year(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Years",
+ "cases": {
+ "one": {
+ "msg": "{Years} سال"
+ },
+ "other": {
+ "msg": "{Years} سال"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Years",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "years"
+ }
+ ]
+ },
+ {
+ "id": "{Days} day(s)",
+ "message": "{Days} day(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Days",
+ "cases": {
+ "one": {
+ "msg": "{Days} روز"
+ },
+ "other": {
+ "msg": "{Days} روز"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Days",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "days"
+ }
+ ]
+ },
+ {
+ "id": "{Hours} hour(s)",
+ "message": "{Hours} hour(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Hours",
+ "cases": {
+ "one": {
+ "msg": "{Hours} ساعت"
+ },
+ "other": {
+ "msg": "{Hours} ساعت"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Hours",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "hours"
+ }
+ ]
+ },
+ {
+ "id": "{Minutes} minute(s)",
+ "message": "{Minutes} minute(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Minutes",
+ "cases": {
+ "one": {
+ "msg": "{Minutes} دقیقه"
+ },
+ "other": {
+ "msg": "{Minutes} دقیقه"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Minutes",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "minutes"
+ }
+ ]
+ },
+ {
+ "id": "{Seconds} second(s)",
+ "message": "{Seconds} second(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Seconds",
+ "cases": {
+ "one": {
+ "msg": "{Seconds} ثانیه"
+ },
+ "other": {
+ "msg": "{Seconds} ثانیه"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Seconds",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "seconds"
+ }
+ ]
+ },
+ {
+ "id": "{Timestamp} ago",
+ "message": "{Timestamp} ago",
+ "translation": "{Timestamp} پیش",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Timestamp",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "timestamp"
+ }
+ ]
+ },
+ {
+ "id": "{Bytes} B",
+ "message": "{Bytes} B",
+ "translation": "{Bytes} بایت",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Bytes",
+ "string": "%[1]d",
+ "type": "golang.zx2c4.com/wireguard/windows/conf.Bytes",
+ "underlyingType": "uint64",
+ "argNum": 1,
+ "expr": "b"
+ }
+ ]
+ },
+ {
+ "id": "Invalid IP address",
+ "message": "Invalid IP address",
+ "translation": "نشانی آی‌پی نامعتبر است",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid MTU",
+ "message": "Invalid MTU",
+ "translation": "MTU نامعتبر است",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid port",
+ "message": "Invalid port",
+ "translation": "پورت نامعتبر",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Key must have a value",
+ "message": "Key must have a value",
+ "translation": "کلید باید یک مقدار داشته باشد",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[none specified]",
+ "message": "[none specified]",
+ "translation": "[مشخص نشده]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "All peers must have public keys",
+ "message": "All peers must have public keys",
+ "translation": "همه همتاها باید کلید‌های عمومی داشته باشند",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error in getting configuration",
+ "message": "Error in getting configuration",
+ "translation": "خطا در دریافت پیکربندی",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[EnumerationSeparator]",
+ "message": "[EnumerationSeparator]",
+ "translation": "، ",
+ "comment": "Text to insert between items when listing - most western languages will translate ‘[EnumerationSeparator]’ into ‘, ’ to produce lists like ‘apple, orange, strawberry’. Eastern languages might translate into ‘、’ to produce lists like ‘リンゴ、オレンジ、イチゴ’."
+ },
+ {
+ "id": "[UnitSeparator]",
+ "message": "[UnitSeparator]",
+ "translation": "، ",
+ "comment": "Text to insert when combining units of a measure - most languages will translate ‘[UnitSeparator]’ into ‘ ’ (space) to produce lists like ‘2 minuti 30 sekund’, or empty string ‘’ to produce ‘2分30秒’."
+ },
+ {
+ "id": "About WireGuard",
+ "message": "About WireGuard",
+ "translation": "درباره WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Close",
+ "message": "Close",
+ "translation": "بستن",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "♥ &Donate!",
+ "message": "♥ &Donate!",
+ "translation": "♥&کمک‌مالی!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status:",
+ "message": "Status:",
+ "translation": "وضعیت:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Deactivate",
+ "message": "&Deactivate",
+ "translation": "&غیرفعال‌سازی",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Activate",
+ "message": "&Activate",
+ "translation": "&فعال‌سازی",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Public key:",
+ "message": "Public key:",
+ "translation": "کلید عمومی:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Listen port:",
+ "message": "Listen port:",
+ "translation": "پورت شنود:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "MTU:",
+ "message": "MTU:",
+ "translation": "MTU:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses:",
+ "message": "Addresses:",
+ "translation": "نشانی‌ها:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "DNS servers:",
+ "message": "DNS servers:",
+ "translation": "سرورهای DNS:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Preshared key:",
+ "message": "Preshared key:",
+ "translation": "کلید از پیش تقسیم شده:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Allowed IPs:",
+ "message": "Allowed IPs:",
+ "translation": "IPهای مجاز:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Endpoint:",
+ "message": "Endpoint:",
+ "translation": "نقطه پایان:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Persistent keepalive:",
+ "message": "Persistent keepalive:",
+ "translation": "زنده نگه‌داشتن پیوسته:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Transfer:",
+ "message": "Transfer:",
+ "translation": "انتقال:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "enabled",
+ "message": "enabled",
+ "translation": "فعال شده",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Interface: {Name}",
+ "message": "Interface: {Name}",
+ "translation": "رابط: {Name}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "config.Name"
+ }
+ ]
+ },
+ {
+ "id": "Peer",
+ "message": "Peer",
+ "translation": "همتا",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Create new tunnel",
+ "message": "Create new tunnel",
+ "translation": "ایجاد تونل جدید",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit tunnel",
+ "message": "Edit tunnel",
+ "translation": "ویرایش تونل",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Name:",
+ "message": "&Name:",
+ "translation": "&نام:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Public key:",
+ "message": "&Public key:",
+ "translation": "&کلید عمومی:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(unknown)",
+ "message": "(unknown)",
+ "translation": "(ناشناخته)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save",
+ "message": "&Save",
+ "translation": "&ذخیره",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Cancel",
+ "message": "Cancel",
+ "translation": "لغو",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Configuration:",
+ "message": "&Configuration:",
+ "translation": "&پیکربندی:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid name",
+ "message": "Invalid name",
+ "translation": "نام نامعتبر",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A name is required.",
+ "message": "A name is required.",
+ "translation": "یک نام الزامی است.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to list existing tunnels",
+ "message": "Unable to list existing tunnels",
+ "translation": "نمی‌توان تونل‌های موجود را فهرست کرد",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel already exists",
+ "message": "Tunnel already exists",
+ "translation": "تونل هم‌اکنون موجود است",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Active",
+ "message": "Active",
+ "translation": "فعال",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Activating",
+ "message": "Activating",
+ "translation": "در حال فعال‌سازی",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Inactive",
+ "message": "Inactive",
+ "translation": "غیرفعال",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Deactivating",
+ "message": "Deactivating",
+ "translation": "در حال غیرفعال‌سازی",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unknown state",
+ "message": "Unknown state",
+ "translation": "وضعیت ناشناخته",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log",
+ "message": "Log",
+ "translation": "گزارش وقایع",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Copy",
+ "message": "&Copy",
+ "translation": "&روگرفت",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save to file…",
+ "message": "&Save to file…",
+ "translation": "&ذخیره در پرونده…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Time",
+ "message": "Time",
+ "translation": "زمان",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log message",
+ "message": "Log message",
+ "translation": "پیام گزارش رویداد",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export log to file",
+ "message": "Export log to file",
+ "translation": "برون‌برد گزارش رویداد به پرونده",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&About WireGuard…",
+ "message": "&About WireGuard…",
+ "translation": "&درباره WireGuard…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel Error",
+ "message": "Tunnel Error",
+ "translation": "خطالی تونل",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Unknown",
+ "message": "Status: Unknown",
+ "translation": "وضعیت: ناشناخته",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses: None",
+ "message": "Addresses: None",
+ "translation": "نشانی‌ها: هیچ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Manage tunnels…",
+ "message": "&Manage tunnels…",
+ "translation": "&مدیریت تونل‌ها…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Activated",
+ "message": "WireGuard Activated",
+ "translation": "WireGuard فعال‌شد",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been activated.",
+ "message": "The {Name} tunnel has been activated.",
+ "translation": "تونل {Name} فعال‌شده.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Deactivated",
+ "message": "WireGuard Deactivated",
+ "translation": "WireGuard غیرفعال شد",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Tunnel Error",
+ "message": "WireGuard Tunnel Error",
+ "translation": "خطای تونل WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: {StateText}",
+ "message": "Status: {StateText}",
+ "translation": "وضعیت: {StateText}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "StateText",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "stateText"
+ }
+ ]
+ },
+ {
+ "id": "An Update is Available!",
+ "message": "An Update is Available!",
+ "translation": "یک به‌روزرسانی در دسترس است!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Update Available",
+ "message": "WireGuard Update Available",
+ "translation": "به‌روزرسانی WireGuard در دسترس است",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnels",
+ "message": "Tunnels",
+ "translation": "تونل‌ها",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Edit",
+ "message": "&Edit",
+ "translation": "&ویرایش",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add &empty tunnel…",
+ "message": "Add &empty tunnel…",
+ "translation": "افزودن &خالی‌کردن تونل…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add Tunnel",
+ "message": "Add Tunnel",
+ "translation": "افزودن تونل",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Remove selected tunnel(s)",
+ "message": "Remove selected tunnel(s)",
+ "translation": "حذف تونل(ها) انتخابی",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to zip",
+ "message": "Export all tunnels to zip",
+ "translation": "برون‌بری همه تونل‌ها به زیپ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to &zip…",
+ "message": "Export all tunnels to &zip…",
+ "translation": "برون‌بری همه تونل‌ها به &زیپ…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Imported tunnels",
+ "message": "Imported tunnels",
+ "translation": "تونل‌های وارد شده",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Imported {M} tunnels",
+ "message": "Imported {M} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "M",
+ "cases": {
+ "one": {
+ "msg": "{M} تونل وارد شد"
+ },
+ "other": {
+ "msg": "{M} تونل وارد شد"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ }
+ ]
+ },
+ {
+ "id": "Imported {M} of {N} tunnels",
+ "message": "Imported {M} of {N} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "N",
+ "cases": {
+ "one": {
+ "msg": "{M} از {N} تونل وارد شد"
+ },
+ "other": {
+ "msg": "{M} از {N} تونل وارد شد"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ },
+ {
+ "id": "N",
+ "string": "%[2]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 2,
+ "expr": "n"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create tunnel",
+ "message": "Unable to create tunnel",
+ "translation": "نمی‌توان تونل ایجاد کرد",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Delete {TunnelCount} tunnels",
+ "message": "Delete {TunnelCount} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "حذف {TunnelCount} تونل"
+ },
+ "other": {
+ "msg": "حذف {TunnelCount} تونل"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Delete tunnel ‘{TunnelName}’",
+ "message": "Delete tunnel ‘{TunnelName}’",
+ "translation": "حذف تونل ‘{TunnelName}’",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnel",
+ "message": "Unable to delete tunnel",
+ "translation": "حذف تونل‌ امکان‌پذیر نیست",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to delete tunnels",
+ "message": "Unable to delete tunnels",
+ "translation": "نمی‌توان تونل‌ها را حذف کرد",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Import tunnel(s) from file",
+ "message": "Import tunnel(s) from file",
+ "translation": "وارد کردن تونل(ها) از پرونده",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Configuration ZIP Files (*.zip)|*.zip",
+ "message": "Configuration ZIP Files (*.zip)|*.zip",
+ "translation": "پرونده‌های پیکربندی زیپ (*.zip)|*.zip",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export tunnels to zip",
+ "message": "Export tunnels to zip",
+ "translation": "برون‌بری تونل‌ها به زیپ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error Exiting WireGuard",
+ "message": "Error Exiting WireGuard",
+ "translation": "خطا در هنگام خارج شدن از WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for user",
+ "message": "Status: Waiting for user",
+ "translation": "وضعیت: درانتظار برای کاربر",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Update Now",
+ "message": "Update Now",
+ "translation": "اکنون به‌روز رسانی کن",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for updater service",
+ "message": "Status: Waiting for updater service",
+ "translation": "وضعیت: درانتظار برای سرویس به‌روزرسانی",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error: {Err}. Please try again.",
+ "message": "Error: {Err}. Please try again.",
+ "translation": "خطا: {Err}. لطفا دوباره تلاش کنید.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Status: Complete!",
+ "message": "Status: Complete!",
+ "translation": "وضعیت: کامل شد!",
+ "translatorComment": "Copied from source."
+ }
+ ]
+} \ No newline at end of file
diff --git a/locales/fi/messages.gotext.json b/locales/fi/messages.gotext.json
new file mode 100644
index 00000000..59521b02
--- /dev/null
+++ b/locales/fi/messages.gotext.json
@@ -0,0 +1,445 @@
+{
+ "language": "fi",
+ "messages": [
+ {
+ "id": "Error",
+ "message": "Error",
+ "translation": "Virhe",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Now",
+ "message": "Now",
+ "translation": "Nyt",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "System clock wound backward!",
+ "message": "System clock wound backward!",
+ "translation": "Järjestelmän kello jättää!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Timestamp} ago",
+ "message": "{Timestamp} ago",
+ "translation": "{Timestamp} sitten",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Timestamp",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "timestamp"
+ }
+ ]
+ },
+ {
+ "id": "{Bytes} B",
+ "message": "{Bytes} B",
+ "translation": "{Bytes} B",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Bytes",
+ "string": "%[1]d",
+ "type": "golang.zx2c4.com/wireguard/windows/conf.Bytes",
+ "underlyingType": "uint64",
+ "argNum": 1,
+ "expr": "b"
+ }
+ ]
+ },
+ {
+ "id": "Invalid MTU",
+ "message": "Invalid MTU",
+ "translation": "Virheellinen MTU",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid port",
+ "message": "Invalid port",
+ "translation": "Virheellinen portti",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid persistent keepalive",
+ "message": "Invalid persistent keepalive",
+ "translation": "Virheellinen jatkuva keepalive",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key: {Err}",
+ "message": "Invalid key: {Err}",
+ "translation": "Virheellinen avain: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "[EnumerationSeparator]",
+ "message": "[EnumerationSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert between items when listing - most western languages will translate ‘[EnumerationSeparator]’ into ‘, ’ to produce lists like ‘apple, orange, strawberry’. Eastern languages might translate into ‘、’ to produce lists like ‘リンゴ、オレンジ、イチゴ’."
+ },
+ {
+ "id": "[UnitSeparator]",
+ "message": "[UnitSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert when combining units of a measure - most languages will translate ‘[UnitSeparator]’ into ‘ ’ (space) to produce lists like ‘2 minuti 30 sekund’, or empty string ‘’ to produce ‘2分30秒’."
+ },
+ {
+ "id": "About WireGuard",
+ "message": "About WireGuard",
+ "translation": "Tietoa WireGuardista",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard logo image",
+ "message": "WireGuard logo image",
+ "translation": "WireGuard logon kuva",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Close",
+ "message": "Close",
+ "translation": "Sulje",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "♥ &Donate!",
+ "message": "♥ &Donate!",
+ "translation": "♥ &Lahjoita!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status:",
+ "message": "Status:",
+ "translation": "Tila:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Public key:",
+ "message": "Public key:",
+ "translation": "Julkinen avain:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Listen port:",
+ "message": "Listen port:",
+ "translation": "Kuuntele porttia:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "MTU:",
+ "message": "MTU:",
+ "translation": "MTU:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses:",
+ "message": "Addresses:",
+ "translation": "Osoitteet:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "DNS servers:",
+ "message": "DNS servers:",
+ "translation": "DNS palvelimet:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Scripts:",
+ "message": "Scripts:",
+ "translation": "Komentosarjat:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Preshared key:",
+ "message": "Preshared key:",
+ "translation": "Jaettu avain:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Allowed IPs:",
+ "message": "Allowed IPs:",
+ "translation": "Sallitut IP-osoitteet:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Endpoint:",
+ "message": "Endpoint:",
+ "translation": "Päätepiste:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Persistent keepalive:",
+ "message": "Persistent keepalive:",
+ "translation": "Jatkuva keepalive:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Latest handshake:",
+ "message": "Latest handshake:",
+ "translation": "Viimeisin kättely:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Transfer:",
+ "message": "Transfer:",
+ "translation": "Siirrot:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "enabled",
+ "message": "enabled",
+ "translation": "käytössä",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{String} received, {String_1} sent",
+ "message": "{String} received, {String_1} sent",
+ "translation": "{String} vastaanotettu, {String_1} lähetetty",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "String",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "c.RxBytes.String()"
+ },
+ {
+ "id": "String_1",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "c.TxBytes.String()"
+ }
+ ]
+ },
+ {
+ "id": "Interface: {Name}",
+ "message": "Interface: {Name}",
+ "translation": "Verkkoyhteys: {Name}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "config.Name"
+ }
+ ]
+ },
+ {
+ "id": "Peer",
+ "message": "Peer",
+ "translation": "Osapuoli",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Create new tunnel",
+ "message": "Create new tunnel",
+ "translation": "Luo uusi tunneli",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit tunnel",
+ "message": "Edit tunnel",
+ "translation": "Muokkaa tunnelia",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Name:",
+ "message": "&Name:",
+ "translation": "&Nimi:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Public key:",
+ "message": "&Public key:",
+ "translation": "&Julkinen avain:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(unknown)",
+ "message": "(unknown)",
+ "translation": "(tuntematon)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Block untunneled traffic (kill-switch)",
+ "message": "&Block untunneled traffic (kill-switch)",
+ "translation": "&Estä tunneloimaton liikenne (pääkatkaisija)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save",
+ "message": "&Save",
+ "translation": "&Tallenna",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Configuration:",
+ "message": "&Configuration:",
+ "translation": "&Konfiguraatio:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid name",
+ "message": "Invalid name",
+ "translation": "Virheellinen nimi",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A name is required.",
+ "message": "A name is required.",
+ "translation": "Nimi on pakollinen.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel already exists",
+ "message": "Tunnel already exists",
+ "translation": "Tunneli on jo olemassa",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log",
+ "message": "Log",
+ "translation": "Loki",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Copy",
+ "message": "&Copy",
+ "translation": "&Kopioi",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Import tunnel(s) from file…",
+ "message": "&Import tunnel(s) from file…",
+ "translation": "Tuo tunnele&ita tiedostosta…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An Update is Available!",
+ "message": "An Update is Available!",
+ "translation": "Päivitys on saatavilla!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Update Available",
+ "message": "WireGuard Update Available",
+ "translation": "WireGuard päivitys saatavilla",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "message": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "translation": "WireGuardin päivitys on nyt saatavilla. Sinua kehotetaan päivittämään mahdollisimman pian.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnels",
+ "message": "Tunnels",
+ "translation": "Tunneli",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Edit",
+ "message": "&Edit",
+ "translation": "&Muokkaa",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add &empty tunnel…",
+ "message": "Add &empty tunnel…",
+ "translation": "Lisää tyhjä tunn&eli…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add Tunnel",
+ "message": "Add Tunnel",
+ "translation": "Lisää tunneli",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Imported tunnels",
+ "message": "Imported tunnels",
+ "translation": "Tuodut tunnelit",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Imported {M} tunnels",
+ "message": "Imported {M} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "M",
+ "cases": {
+ "one": {
+ "msg": "Tuotu {M} tunneli"
+ },
+ "other": {
+ "msg": "Tuotu {M} tunnelia"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnels",
+ "message": "Unable to delete tunnels",
+ "translation": "Tunnelia ei voitu poistaa",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Import tunnel(s) from file",
+ "message": "Import tunnel(s) from file",
+ "translation": "Tuo tunneli(t) tiedostosta",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for user",
+ "message": "Status: Waiting for user",
+ "translation": "Tila: Odotetaan käyttäjää",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Update Now",
+ "message": "Update Now",
+ "translation": "Päivitä nyt",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Complete!",
+ "message": "Status: Complete!",
+ "translation": "Tila: Valmis!",
+ "translatorComment": "Copied from source."
+ }
+ ]
+} \ No newline at end of file
diff --git a/locales/fr/messages.gotext.json b/locales/fr/messages.gotext.json
new file mode 100644
index 00000000..95f02eec
--- /dev/null
+++ b/locales/fr/messages.gotext.json
@@ -0,0 +1,1847 @@
+{
+ "language": "fr",
+ "messages": [
+ {
+ "id": "Error",
+ "message": "Error",
+ "translation": "Erreur",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(no argument): elevate and install manager service",
+ "message": "(no argument): elevate and install manager service",
+ "translation": "(sans argument) : élever et installer service du gestionnaire",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Usage: {Args0} [\n{String}]",
+ "message": "Usage: {Args0} [\n{String}]",
+ "translation": "Utilisation : {Args0} [\n{String}]",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Args0",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "os.Args[0]"
+ },
+ {
+ "id": "String",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "builder.String()"
+ }
+ ]
+ },
+ {
+ "id": "Command Line Options",
+ "message": "Command Line Options",
+ "translation": "Options de la ligne de commande",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to determine whether the process is running under WOW64: {Err}",
+ "message": "Unable to determine whether the process is running under WOW64: {Err}",
+ "translation": "Impossible de détecter si le processus s’exécute sous WOW64 : {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "You must use the native version of WireGuard on this computer.",
+ "message": "You must use the native version of WireGuard on this computer.",
+ "translation": "Vous devez utiliser la version native de WireGuard sur cet ordinateur.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to open current process token: {Err}",
+ "message": "Unable to open current process token: {Err}",
+ "translation": "Impossible d'ouvrir le jeton du processus actuel : {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "translation": "Seulement les utilisateurs qui sont membres du groupe intégré {AdminGroupName} peuvent utiliser WireGuard.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard est en cours d'exécution, mais l'IU est accessible seulement à partir des bureaux du group intégré {AdminGroupName}.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard system tray icon did not appear after 30 seconds.",
+ "message": "WireGuard system tray icon did not appear after 30 seconds.",
+ "translation": "L’icône de la barre d’état système du WireGuard n'est pas apparue après 30 secondes.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Now",
+ "message": "Now",
+ "translation": "Maintenant",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "System clock wound backward!",
+ "message": "System clock wound backward!",
+ "translation": "L’horloge système est inversé!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Years} year(s)",
+ "message": "{Years} year(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Years",
+ "cases": {
+ "one": {
+ "msg": "{Years} an"
+ },
+ "other": {
+ "msg": "{Years} ans"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Years",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "years"
+ }
+ ]
+ },
+ {
+ "id": "{Days} day(s)",
+ "message": "{Days} day(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Days",
+ "cases": {
+ "one": {
+ "msg": "{Days} jour"
+ },
+ "other": {
+ "msg": "{Days} jours"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Days",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "days"
+ }
+ ]
+ },
+ {
+ "id": "{Hours} hour(s)",
+ "message": "{Hours} hour(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Hours",
+ "cases": {
+ "one": {
+ "msg": "{Hours} heure"
+ },
+ "other": {
+ "msg": "{Hours} heures"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Hours",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "hours"
+ }
+ ]
+ },
+ {
+ "id": "{Minutes} minute(s)",
+ "message": "{Minutes} minute(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Minutes",
+ "cases": {
+ "one": {
+ "msg": "{Minutes} minute"
+ },
+ "other": {
+ "msg": "{Minutes} minutes"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Minutes",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "minutes"
+ }
+ ]
+ },
+ {
+ "id": "{Seconds} second(s)",
+ "message": "{Seconds} second(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Seconds",
+ "cases": {
+ "one": {
+ "msg": "{Seconds} seconde"
+ },
+ "other": {
+ "msg": "{Seconds} secondes"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Seconds",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "seconds"
+ }
+ ]
+ },
+ {
+ "id": "{Timestamp} ago",
+ "message": "{Timestamp} ago",
+ "translation": "Il y a {Timestamp}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Timestamp",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "timestamp"
+ }
+ ]
+ },
+ {
+ "id": "{Bytes} B",
+ "message": "{Bytes} B",
+ "translation": "{Bytes} o",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Bytes",
+ "string": "%[1]d",
+ "type": "golang.zx2c4.com/wireguard/windows/conf.Bytes",
+ "underlyingType": "uint64",
+ "argNum": 1,
+ "expr": "b"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024} KiB",
+ "message": "{Float64b__1024} KiB",
+ "translation": "{Float64b__1024} Kio",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024} MiB",
+ "message": "{Float64b__1024__1024} MiB",
+ "translation": "{Float64b__1024__1024} Mio",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024} GiB",
+ "message": "{Float64b__1024__1024__1024} GiB",
+ "translation": "{Float64b__1024__1024__1024} Gio",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024__1024} TiB",
+ "message": "{Float64b__1024__1024__1024__1024} TiB",
+ "translation": "{Float64b__1024__1024__1024__1024} Tio",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Why}: {Offender}",
+ "message": "{Why}: {Offender}",
+ "translation": "{Why} : {Offender}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Why",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "e.why"
+ },
+ {
+ "id": "Offender",
+ "string": "%[2]q",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "e.offender"
+ }
+ ]
+ },
+ {
+ "id": "Invalid IP address",
+ "message": "Invalid IP address",
+ "translation": "Adresse IP non valide",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid network prefix length",
+ "message": "Invalid network prefix length",
+ "translation": "Longueur du préfixe réseau non valide",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Missing port from endpoint",
+ "message": "Missing port from endpoint",
+ "translation": "Port manquant au point de terminaison",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid endpoint host",
+ "message": "Invalid endpoint host",
+ "translation": "Hôte du point de terminaison non valide",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Brackets must contain an IPv6 address",
+ "message": "Brackets must contain an IPv6 address",
+ "translation": "L’adresse IPv6 doit être contenue entre des crochets",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid MTU",
+ "message": "Invalid MTU",
+ "translation": "MTU non valide",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid port",
+ "message": "Invalid port",
+ "translation": "Port non valide",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid persistent keepalive",
+ "message": "Invalid persistent keepalive",
+ "translation": "Keepalive non valide",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key: {Err}",
+ "message": "Invalid key: {Err}",
+ "translation": "Clé non valide : {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Keys must decode to exactly 32 bytes",
+ "message": "Keys must decode to exactly 32 bytes",
+ "translation": "Clés doivent être décodées sur 32 octets",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Number must be a number between 0 and 2^64-1: {Err}",
+ "message": "Number must be a number between 0 and 2^64-1: {Err}",
+ "translation": "Le numéro doit être compris entre 0 et 2^64-1 : {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Two commas in a row",
+ "message": "Two commas in a row",
+ "translation": "Deux virgules consécutives",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name is not valid",
+ "message": "Tunnel name is not valid",
+ "translation": "Nom du tunnel non valide",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Line must occur in a section",
+ "message": "Line must occur in a section",
+ "translation": "Une ligne doit apparaître dans une section",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Config key is missing an equals separator",
+ "message": "Config key is missing an equals separator",
+ "translation": "Il manque le séparateur égal à la clé de configuration",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Key must have a value",
+ "message": "Key must have a value",
+ "translation": "Clé doit avoir une valeur",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Interface] section",
+ "message": "Invalid key for [Interface] section",
+ "translation": "Clé non valide pour la section [Interface]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Peer] section",
+ "message": "Invalid key for [Peer] section",
+ "translation": "Clé non valide pour la section [Peer]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An interface must have a private key",
+ "message": "An interface must have a private key",
+ "translation": "L'interface doit avoir une clé privée",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[none specified]",
+ "message": "[none specified]",
+ "translation": "[aucune spécification]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "All peers must have public keys",
+ "message": "All peers must have public keys",
+ "translation": "Toutes les pairs doivent contenir une clé publique",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error in getting configuration",
+ "message": "Error in getting configuration",
+ "translation": "Erreur d'obtention de la configuration",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for interface section",
+ "message": "Invalid key for interface section",
+ "translation": "Clé non valide pour la section d'interface",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Protocol version must be 1",
+ "message": "Protocol version must be 1",
+ "translation": "Version du protocole doit être 1",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for peer section",
+ "message": "Invalid key for peer section",
+ "translation": "Clé non valide pour la section d'homologue",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[EnumerationSeparator]",
+ "message": "[EnumerationSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert between items when listing - most western languages will translate ‘[EnumerationSeparator]’ into ‘, ’ to produce lists like ‘apple, orange, strawberry’. Eastern languages might translate into ‘、’ to produce lists like ‘リンゴ、オレンジ、イチゴ’."
+ },
+ {
+ "id": "[UnitSeparator]",
+ "message": "[UnitSeparator]",
+ "translation": " ",
+ "comment": "Text to insert when combining units of a measure - most languages will translate ‘[UnitSeparator]’ into ‘ ’ (space) to produce lists like ‘2 minuti 30 sekund’, or empty string ‘’ to produce ‘2分30秒’."
+ },
+ {
+ "id": "About WireGuard",
+ "message": "About WireGuard",
+ "translation": "À propos du WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard logo image",
+ "message": "WireGuard logo image",
+ "translation": "Image du logo du WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "message": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "translation": "Version de l'application : {Number}\nVersion backend Go : {WireGuardGoVersion}\nVersion Go : {Version_go}-{GOARCH}\nSystème d'exploitation : {OsName}\nArchitecture : {NativeArch}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Number",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "version.Number"
+ },
+ {
+ "id": "WireGuardGoVersion",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "device.WireGuardGoVersion"
+ },
+ {
+ "id": "Version_go",
+ "string": "%[3]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 3,
+ "expr": "strings.TrimPrefix(runtime.Version(), \"go\")"
+ },
+ {
+ "id": "GOARCH",
+ "string": "%[4]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 4,
+ "expr": "runtime.GOARCH"
+ },
+ {
+ "id": "OsName",
+ "string": "%[5]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 5,
+ "expr": "version.OsName()"
+ },
+ {
+ "id": "NativeArch",
+ "string": "%[6]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 6,
+ "expr": "version.NativeArch()"
+ }
+ ]
+ },
+ {
+ "id": "Close",
+ "message": "Close",
+ "translation": "Fermer",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "♥ &Donate!",
+ "message": "♥ &Donate!",
+ "translation": "♥ &Faites un don!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status:",
+ "message": "Status:",
+ "translation": "État :",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Deactivate",
+ "message": "&Deactivate",
+ "translation": "&Désactiver",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Activate",
+ "message": "&Activate",
+ "translation": "&Activer",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Public key:",
+ "message": "Public key:",
+ "translation": "Clé publique :",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Listen port:",
+ "message": "Listen port:",
+ "translation": "Port d'écoute :",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "MTU:",
+ "message": "MTU:",
+ "translation": "MTU :",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses:",
+ "message": "Addresses:",
+ "translation": "Adresses :",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "DNS servers:",
+ "message": "DNS servers:",
+ "translation": "Serveurs DNS :",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Scripts:",
+ "message": "Scripts:",
+ "translation": "Scripts :",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Preshared key:",
+ "message": "Preshared key:",
+ "translation": "Clé pré-partagée :",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Allowed IPs:",
+ "message": "Allowed IPs:",
+ "translation": "Adresses IP autorisées :",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Endpoint:",
+ "message": "Endpoint:",
+ "translation": "Point de terminaison :",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Persistent keepalive:",
+ "message": "Persistent keepalive:",
+ "translation": "Conservation de connexion active permanente :",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Latest handshake:",
+ "message": "Latest handshake:",
+ "translation": "Dernier établissement d'une liaison :",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Transfer:",
+ "message": "Transfer:",
+ "translation": "Transfert :",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-up",
+ "message": "pre-up",
+ "translation": "pré-activation",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-up",
+ "message": "post-up",
+ "translation": "post-activation",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-down",
+ "message": "pre-down",
+ "translation": "pré-désactivation",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-down",
+ "message": "post-down",
+ "translation": "post-désactivation",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "disabled, per policy",
+ "message": "disabled, per policy",
+ "translation": "désactivé, par préférence",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "enabled",
+ "message": "enabled",
+ "translation": "activé(e)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{String} received, {String_1} sent",
+ "message": "{String} received, {String_1} sent",
+ "translation": "{String} reçu(e), {String_1} envoyé(e)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "String",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "c.RxBytes.String()"
+ },
+ {
+ "id": "String_1",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "c.TxBytes.String()"
+ }
+ ]
+ },
+ {
+ "id": "Failed to determine tunnel state",
+ "message": "Failed to determine tunnel state",
+ "translation": "Impossible de déterminer l'état du tunnel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to activate tunnel",
+ "message": "Failed to activate tunnel",
+ "translation": "Impossible d'activer le tunnel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to deactivate tunnel",
+ "message": "Failed to deactivate tunnel",
+ "translation": "Impossible de désactiver le tunnel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Interface: {Name}",
+ "message": "Interface: {Name}",
+ "translation": "Interface : {Name}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "config.Name"
+ }
+ ]
+ },
+ {
+ "id": "Peer",
+ "message": "Peer",
+ "translation": "Homologue",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Create new tunnel",
+ "message": "Create new tunnel",
+ "translation": "Créer un nouveau tunnel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit tunnel",
+ "message": "Edit tunnel",
+ "translation": "Modifier le tunnel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Name:",
+ "message": "&Name:",
+ "translation": "&Nom :",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Public key:",
+ "message": "&Public key:",
+ "translation": "&Clé publique :",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(unknown)",
+ "message": "(unknown)",
+ "translation": "(inconnu(e))",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Block untunneled traffic (kill-switch)",
+ "message": "&Block untunneled traffic (kill-switch)",
+ "translation": "&Bloquer tous le trafic hors tunnel (interrupteur)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.",
+ "message": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.",
+ "translation": "Si la configuration a exactement un homologue et si cet homologue a une adresse IP autorisée contenant au moins un de 0.0.0.0/0 ou ::/0, le service de tunnel utilise un ensemble de règles du pare-feu afin de bloquer tout le trafic qui n'est ni vers ni depuis l'interface de tunnel ou qui est vers le mauvais serveur DNS, avec des exceptions spéciales pour les DHCP et NDP.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save",
+ "message": "&Save",
+ "translation": "&Enregistrer",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Cancel",
+ "message": "Cancel",
+ "translation": "Annuler",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Configuration:",
+ "message": "&Configuration:",
+ "translation": "&Configuration :",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid name",
+ "message": "Invalid name",
+ "translation": "Nom non valide",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A name is required.",
+ "message": "A name is required.",
+ "translation": "Le nom est obligatoire.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name ‘{NewName}’ is invalid.",
+ "message": "Tunnel name ‘{NewName}’ is invalid.",
+ "translation": "Nom de tunnel « {NewName} » est non valide.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to list existing tunnels",
+ "message": "Unable to list existing tunnels",
+ "translation": "Impossible de créer une liste des tunnels existants",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel already exists",
+ "message": "Tunnel already exists",
+ "translation": "Tunnel existe déjà.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{NewName}’.",
+ "message": "Another tunnel already exists with the name ‘{NewName}’.",
+ "translation": "Nom « {NewName} » est déjà utilisé pour un tunnel.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create new configuration",
+ "message": "Unable to create new configuration",
+ "translation": "Impossible de créer une configuration nouvelle",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Writing file failed",
+ "message": "Writing file failed",
+ "translation": "Échec d'écriture du fichier",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "message": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "translation": "Fichier « {FilePath} » existe déjà.\n\nVoulez-vous le remplacer ?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "FilePath",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "filePath"
+ }
+ ]
+ },
+ {
+ "id": "Active",
+ "message": "Active",
+ "translation": "Activée",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Activating",
+ "message": "Activating",
+ "translation": "Activation en cours",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Inactive",
+ "message": "Inactive",
+ "translation": "Éteinte",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Deactivating",
+ "message": "Deactivating",
+ "translation": "Désactivation en cours",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unknown state",
+ "message": "Unknown state",
+ "translation": "État inconnu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log",
+ "message": "Log",
+ "translation": "Journal",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Copy",
+ "message": "&Copy",
+ "translation": "&Copier",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Select &all",
+ "message": "Select &all",
+ "translation": "Sélectionner &tout",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save to file…",
+ "message": "&Save to file…",
+ "translation": "&Enregistrer dans le fichier…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Time",
+ "message": "Time",
+ "translation": "Temps",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log message",
+ "message": "Log message",
+ "translation": "Message du journal",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "message": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "translation": "Fichiers texte (*.txt)|*.txt|Tous les fichiers (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export log to file",
+ "message": "Export log to file",
+ "translation": "Exporter le journal vers le fichier",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&About WireGuard…",
+ "message": "&About WireGuard…",
+ "translation": "&À propos WireGuard…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel Error",
+ "message": "Tunnel Error",
+ "translation": "Erreur du tunnel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "message": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "translation": "{ErrMsg}\n\nConsultez le journal pour plus d’informations, s'il vous plaît.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "ErrMsg",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errMsg"
+ }
+ ]
+ },
+ {
+ "id": "{Title} (out of date)",
+ "message": "{Title} (out of date)",
+ "translation": "{Title} (obsolète)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Detection Error",
+ "message": "WireGuard Detection Error",
+ "translation": "Erreur de détection du WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to wait for WireGuard window to appear: {Err}",
+ "message": "Unable to wait for WireGuard window to appear: {Err}",
+ "translation": "Impossible d’attendre l'affichage du fenêtre WireGuard : {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard: Deactivated",
+ "message": "WireGuard: Deactivated",
+ "translation": "WireGuard: Désactivé",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Unknown",
+ "message": "Status: Unknown",
+ "translation": "État : Inconnu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses: None",
+ "message": "Addresses: None",
+ "translation": "Adresses : Aucune",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Manage tunnels…",
+ "message": "&Manage tunnels…",
+ "translation": "&Gestion des tunnels…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Import tunnel(s) from file…",
+ "message": "&Import tunnel(s) from file…",
+ "translation": "&Importer le(s) tunnel(s) à partir du fichier…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "E&xit",
+ "message": "E&xit",
+ "translation": "Q&uitter",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Tunnels",
+ "message": "&Tunnels",
+ "translation": "& Tunnels",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Activated",
+ "message": "WireGuard Activated",
+ "translation": "WireGuard activé",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been activated.",
+ "message": "The {Name} tunnel has been activated.",
+ "translation": "Tunnel {Name} a été activé.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Deactivated",
+ "message": "WireGuard Deactivated",
+ "translation": "WireGuard désactivé",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been deactivated.",
+ "message": "The {Name} tunnel has been deactivated.",
+ "translation": "Tunnel {Name} a été désactivé.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Tunnel Error",
+ "message": "WireGuard Tunnel Error",
+ "translation": "Erreur du tunnel WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard: {TextForStateglobalState_true}",
+ "message": "WireGuard: {TextForStateglobalState_true}",
+ "translation": "WireGuard : {TextForStateglobalState_true}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TextForStateglobalState_true",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "textForState(globalState, true)"
+ }
+ ]
+ },
+ {
+ "id": "Status: {StateText}",
+ "message": "Status: {StateText}",
+ "translation": "État : {StateText}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "StateText",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "stateText"
+ }
+ ]
+ },
+ {
+ "id": "Addresses: {EnumerationSeparator}",
+ "message": "Addresses: {EnumerationSeparator}",
+ "translation": "Adresses : {EnumerationSeparator}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "EnumerationSeparator",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "strings.Join(addrs, l18n.EnumerationSeparator())"
+ }
+ ]
+ },
+ {
+ "id": "An Update is Available!",
+ "message": "An Update is Available!",
+ "translation": "Mise à jour disponible!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Update Available",
+ "message": "WireGuard Update Available",
+ "translation": "WireGuard mise à jour est disponible",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "message": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "translation": "Une mise à jour du WireGuard est disponible. Il est conseillé de mettre votre WireGuard à jour dès que possible.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnels",
+ "message": "Tunnels",
+ "translation": "Tunnels",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Edit",
+ "message": "&Edit",
+ "translation": "&Modifier",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add &empty tunnel…",
+ "message": "Add &empty tunnel…",
+ "translation": "Ajouter un &tunnel vide…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add Tunnel",
+ "message": "Add Tunnel",
+ "translation": "Ajouter le tunnel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Remove selected tunnel(s)",
+ "message": "Remove selected tunnel(s)",
+ "translation": "Supprimer le(s) tunnel(s) sélectionné(s)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to zip",
+ "message": "Export all tunnels to zip",
+ "translation": "Exporter tous les tunnels vers zip",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Toggle",
+ "message": "&Toggle",
+ "translation": "&Basculer",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to &zip…",
+ "message": "Export all tunnels to &zip…",
+ "translation": "Exporter tous les tunnels vers &zip…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit &selected tunnel…",
+ "message": "Edit &selected tunnel…",
+ "translation": "Modifier &le tunnel sélectionné…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Remove selected tunnel(s)",
+ "message": "&Remove selected tunnel(s)",
+ "translation": "&Supprimer le(s) tunnel(s) sélectionné(s)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "no configuration files were found",
+ "message": "no configuration files were found",
+ "translation": "aucun fichier de configuration trouvé",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Could not import selected configuration: {LastErr}",
+ "message": "Could not import selected configuration: {LastErr}",
+ "translation": "Impossible d'importer la configuration sélectionnée : {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Could not enumerate existing tunnels: {LastErr}",
+ "message": "Could not enumerate existing tunnels: {LastErr}",
+ "translation": "Impossible d'énumérer les tunnels existantes : {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{Name}’",
+ "message": "Another tunnel already exists with the name ‘{Name}’",
+ "translation": "Un tunnel nommé « {Name} » existe déjà.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "unparsedConfig.Name"
+ }
+ ]
+ },
+ {
+ "id": "Unable to import configuration: {LastErr}",
+ "message": "Unable to import configuration: {LastErr}",
+ "translation": "Impossible d'importer la configuration : {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Imported tunnels",
+ "message": "Imported tunnels",
+ "translation": "Tunnels importés",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Imported {M} tunnels",
+ "message": "Imported {M} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "M",
+ "cases": {
+ "one": {
+ "msg": "{M} tunnel importé"
+ },
+ "other": {
+ "msg": "{M} tunnels importés"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ }
+ ]
+ },
+ {
+ "id": "Imported {M} of {N} tunnels",
+ "message": "Imported {M} of {N} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "N",
+ "cases": {
+ "one": {
+ "msg": "{M} de {N} tunnels importé"
+ },
+ "other": {
+ "msg": "{M} de {N} tunnels importés"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ },
+ {
+ "id": "N",
+ "string": "%[2]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 2,
+ "expr": "n"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create tunnel",
+ "message": "Unable to create tunnel",
+ "translation": "Impossible de créer le tunnel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Delete {TunnelCount} tunnels",
+ "message": "Delete {TunnelCount} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "Supprimer {TunnelCount} tunnel"
+ },
+ "other": {
+ "msg": "Supprimer {TunnelCount} tunnels"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "message": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "Voulez-vous vraiment supprimer {TunnelCount} tunnel?"
+ },
+ "other": {
+ "msg": "Voulez-vous vraiment supprimer {TunnelCount} tunnels?"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Delete tunnel ‘{TunnelName}’",
+ "message": "Delete tunnel ‘{TunnelName}’",
+ "translation": "Supprimer le tunnel ‘{TunnelName}’",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "message": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "translation": "Voulez-vous vraiment supprimer le tunnel « {TunnelName} »?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "{Question} You cannot undo this action.",
+ "message": "{Question} You cannot undo this action.",
+ "translation": "{Question} Vous ne pouvez pas annuler cette action.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Question",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "question"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnel",
+ "message": "Unable to delete tunnel",
+ "translation": "Impossible de supprimer le tunnel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A tunnel was unable to be removed: {Error}",
+ "message": "A tunnel was unable to be removed: {Error}",
+ "translation": "Il a été impossible de supprimer un tunnel : {Error}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Error",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errors[0].Error()"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnels",
+ "message": "Unable to delete tunnels",
+ "translation": "Impossible de supprimer les tunnels",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Lenerrors} tunnels were unable to be removed.",
+ "message": "{Lenerrors} tunnels were unable to be removed.",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Lenerrors",
+ "cases": {
+ "one": {
+ "msg": "Il a été impossible de supprimer {Lenerrors} tunnel."
+ },
+ "other": {
+ "msg": "Il a été impossible de supprimer {Lenerrors} tunnels."
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Lenerrors",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "len(errors)"
+ }
+ ]
+ },
+ {
+ "id": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "message": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "translation": "Fichiers de configuration (*.zip, *.conf)|*.zip;*.conf|Tous les fichiers (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Import tunnel(s) from file",
+ "message": "Import tunnel(s) from file",
+ "translation": "Importer le(s) tunnel(s) à partir du fichier",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Configuration ZIP Files (*.zip)|*.zip",
+ "message": "Configuration ZIP Files (*.zip)|*.zip",
+ "translation": "Fichiers de configuration ZIP (*.zip)|*.zip",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export tunnels to zip",
+ "message": "Export tunnels to zip",
+ "translation": "Exporter les tunnels vers zip",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Title} (unsigned build, no updates)",
+ "message": "{Title} (unsigned build, no updates)",
+ "translation": "{Title} (version non signée, aucune mise à jour)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "Error Exiting WireGuard",
+ "message": "Error Exiting WireGuard",
+ "translation": "Erreur de sortie du WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "message": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "translation": "Impossible de quitter le service en raison de : {Err}. Essayez d'arrêter WireGuard à partir du gestionnair des services.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "message": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "translation": "Une mise à jour du WireGuard est disponible. Il est fortement conseillé de metter votre WireGuard à jour sans délai.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for user",
+ "message": "Status: Waiting for user",
+ "translation": "État: En attente de l’utilisateur",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Update Now",
+ "message": "Update Now",
+ "translation": "Mettre à jour maintenant",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for updater service",
+ "message": "Status: Waiting for updater service",
+ "translation": "État: En attente du programme de mise à jour",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error: {Err}. Please try again.",
+ "message": "Error: {Err}. Please try again.",
+ "translation": "Erreur : {Err}. Veuillez réessayer.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Status: Complete!",
+ "message": "Status: Complete!",
+ "translation": "État: Terminé!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "http2: Framer {F}: failed to decode just-written frame",
+ "message": "http2: Framer {F}: failed to decode just-written frame",
+ "translation": "http2: Trameur {F} : impossible de décoder la trame just écrite.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ }
+ ]
+ },
+ {
+ "id": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "message": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "translation": "http2: Trameur {F}: a écrit {Http2summarizeFramefr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ },
+ {
+ "id": "Http2summarizeFramefr",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(fr)"
+ }
+ ]
+ },
+ {
+ "id": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "message": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "translation": "http2: Trameur {Fr} : a lu {Http2summarizeFramef}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Fr",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "fr"
+ },
+ {
+ "id": "Http2summarizeFramef",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(f)"
+ }
+ ]
+ },
+ {
+ "id": "http2: decoded hpack field {HeaderField}",
+ "message": "http2: decoded hpack field {HeaderField}",
+ "translation": "http2 : champ hpack {HeaderField} décodé",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "HeaderField",
+ "string": "%+[1]v",
+ "type": "vendor/golang.org/x/net/http2/hpack.HeaderField",
+ "underlyingType": "struct{Name string; Value string; Sensitive bool}",
+ "argNum": 1,
+ "expr": "hf"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/locales/id/messages.gotext.json b/locales/id/messages.gotext.json
new file mode 100644
index 00000000..07fad080
--- /dev/null
+++ b/locales/id/messages.gotext.json
@@ -0,0 +1,820 @@
+{
+ "language": "id",
+ "messages": [
+ {
+ "id": "Error",
+ "message": "Error",
+ "translation": "Kesalahan",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(no argument): elevate and install manager service",
+ "message": "(no argument): elevate and install manager service",
+ "translation": "(tidak ada argumen): naikkan akses dan instal servis manajer",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Usage: {Args0} [\n{String}]",
+ "message": "Usage: {Args0} [\n{String}]",
+ "translation": "Penggunaan: {Args0} [\n{String}]",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Args0",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "os.Args[0]"
+ },
+ {
+ "id": "String",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "builder.String()"
+ }
+ ]
+ },
+ {
+ "id": "Command Line Options",
+ "message": "Command Line Options",
+ "translation": "Opsi Command Line",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to determine whether the process is running under WOW64: {Err}",
+ "message": "Unable to determine whether the process is running under WOW64: {Err}",
+ "translation": "Tidak dapat menentukan apakah proses sedang berjalan di bawah WOW64: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Unable to open current process token: {Err}",
+ "message": "Unable to open current process token: {Err}",
+ "translation": "Tidak dapat membuka token proses saat ini: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard hanya dapat digunakan oleh pengguna yang merupakan anggota grup Bawaan {AdminGroupName}.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard sedang berjalan, tetapi UI hanya dapat diakses dari desktop grup Bawaan {AdminGroupName}.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard system tray icon did not appear after 30 seconds.",
+ "message": "WireGuard system tray icon did not appear after 30 seconds.",
+ "translation": "Ikon sistem WireGuard tidak muncul setelah 30 detik.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Now",
+ "message": "Now",
+ "translation": "Sekarang",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "System clock wound backward!",
+ "message": "System clock wound backward!",
+ "translation": "Jam sistem mundur!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Years} year(s)",
+ "message": "{Years} year(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Years",
+ "cases": {
+ "other": {
+ "msg": "{Years} tahun\n{Years} tahun"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Years",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "years"
+ }
+ ]
+ },
+ {
+ "id": "{Days} day(s)",
+ "message": "{Days} day(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Days",
+ "cases": {
+ "other": {
+ "msg": "{Days} Hari"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Days",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "days"
+ }
+ ]
+ },
+ {
+ "id": "{Hours} hour(s)",
+ "message": "{Hours} hour(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Hours",
+ "cases": {
+ "other": {
+ "msg": "{Hours} jam"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Hours",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "hours"
+ }
+ ]
+ },
+ {
+ "id": "{Minutes} minute(s)",
+ "message": "{Minutes} minute(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Minutes",
+ "cases": {
+ "other": {
+ "msg": "{Minutes} menit"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Minutes",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "minutes"
+ }
+ ]
+ },
+ {
+ "id": "{Seconds} second(s)",
+ "message": "{Seconds} second(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Seconds",
+ "cases": {
+ "other": {
+ "msg": "{Seconds} detik"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Seconds",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "seconds"
+ }
+ ]
+ },
+ {
+ "id": "{Timestamp} ago",
+ "message": "{Timestamp} ago",
+ "translation": "{Timestamp} yang lalu",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Timestamp",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "timestamp"
+ }
+ ]
+ },
+ {
+ "id": "{Bytes} B",
+ "message": "{Bytes} B",
+ "translation": "{Bytes} B",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Bytes",
+ "string": "%[1]d",
+ "type": "golang.zx2c4.com/wireguard/windows/conf.Bytes",
+ "underlyingType": "uint64",
+ "argNum": 1,
+ "expr": "b"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024} KiB",
+ "message": "{Float64b__1024} KiB",
+ "translation": "{Float64b__1024} KiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024} MiB",
+ "message": "{Float64b__1024__1024} MiB",
+ "translation": "{Float64b__1024__1024} MiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024} GiB",
+ "message": "{Float64b__1024__1024__1024} GiB",
+ "translation": "{Float64b__1024__1024__1024} GiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024__1024} TiB",
+ "message": "{Float64b__1024__1024__1024__1024} TiB",
+ "translation": "{Float64b__1024__1024__1024__1024} TiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Why}: {Offender}",
+ "message": "{Why}: {Offender}",
+ "translation": "{Why}: {Offender}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Why",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "e.why"
+ },
+ {
+ "id": "Offender",
+ "string": "%[2]q",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "e.offender"
+ }
+ ]
+ },
+ {
+ "id": "Invalid IP address",
+ "message": "Invalid IP address",
+ "translation": "Alamat IP tidak valid",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid network prefix length",
+ "message": "Invalid network prefix length",
+ "translation": "Network prefix tidak valid",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Missing port from endpoint",
+ "message": "Missing port from endpoint",
+ "translation": "Port belum terisi dari endpoint",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid endpoint host",
+ "message": "Invalid endpoint host",
+ "translation": "Host endpoint tidak valid",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Brackets must contain an IPv6 address",
+ "message": "Brackets must contain an IPv6 address",
+ "translation": "Dalam Kurung harus berisi alamat IPv6",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid MTU",
+ "message": "Invalid MTU",
+ "translation": "MTU tidak valid",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid port",
+ "message": "Invalid port",
+ "translation": "Port tidak valid",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid persistent keepalive",
+ "message": "Invalid persistent keepalive",
+ "translation": "Persistent keepalive tidak valid",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key: {Err}",
+ "message": "Invalid key: {Err}",
+ "translation": "Kunci tidak sah:{Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Keys must decode to exactly 32 bytes",
+ "message": "Keys must decode to exactly 32 bytes",
+ "translation": "Kunci harus diterjemahkan tepat 32 byte",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Number must be a number between 0 and 2^64-1: {Err}",
+ "message": "Number must be a number between 0 and 2^64-1: {Err}",
+ "translation": "Nomor harus diantara 0 sampai dengan 2^64-1:{Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Two commas in a row",
+ "message": "Two commas in a row",
+ "translation": "Dua koma dalam satu baris",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name is not valid",
+ "message": "Tunnel name is not valid",
+ "translation": "Nama Tunnel tidak valid",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Line must occur in a section",
+ "message": "Line must occur in a section",
+ "translation": "Garis harus muncul perbagian",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Key must have a value",
+ "message": "Key must have a value",
+ "translation": "Kunci harus memiliki value",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Interface] section",
+ "message": "Invalid key for [Interface] section",
+ "translation": "Kunci tidak valid pada bagian [Interface]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Peer] section",
+ "message": "Invalid key for [Peer] section",
+ "translation": "Kunci tidak valid pada bagian [Peer]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An interface must have a private key",
+ "message": "An interface must have a private key",
+ "translation": "Interface harus memiliki Private Key",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[none specified]",
+ "message": "[none specified]",
+ "translation": "Tidak Ditetapkan",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "All peers must have public keys",
+ "message": "All peers must have public keys",
+ "translation": "Semua peers harus memiliki kunci publik",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error in getting configuration",
+ "message": "Error in getting configuration",
+ "translation": "Eror ketika mendapatkan konfigurasi",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for interface section",
+ "message": "Invalid key for interface section",
+ "translation": "Kunci tidak valid pada bagian [Interface]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Protocol version must be 1",
+ "message": "Protocol version must be 1",
+ "translation": "Versi protokol harus 1",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for peer section",
+ "message": "Invalid key for peer section",
+ "translation": "Kunci tidak valid pada bagian [Peer]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[EnumerationSeparator]",
+ "message": "[EnumerationSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert between items when listing - most western languages will translate ‘[EnumerationSeparator]’ into ‘, ’ to produce lists like ‘apple, orange, strawberry’. Eastern languages might translate into ‘、’ to produce lists like ‘リンゴ、オレンジ、イチゴ’."
+ },
+ {
+ "id": "About WireGuard",
+ "message": "About WireGuard",
+ "translation": "Tentang WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Close",
+ "message": "Close",
+ "translation": "Tutup",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "♥ &Donate!",
+ "message": "♥ &Donate!",
+ "translation": "♥ &Donasi!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status:",
+ "message": "Status:",
+ "translation": "Status:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Active",
+ "message": "Active",
+ "translation": "Aktif",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Activating",
+ "message": "Activating",
+ "translation": "Mengaktifkan",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Inactive",
+ "message": "Inactive",
+ "translation": "Nonaktif",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Deactivating",
+ "message": "Deactivating",
+ "translation": "Menonaktifkan",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unknown state",
+ "message": "Unknown state",
+ "translation": "Status tidak diketahui",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log",
+ "message": "Log",
+ "translation": "Catatan",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Copy",
+ "message": "&Copy",
+ "translation": "Salin",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Select &all",
+ "message": "Select &all",
+ "translation": "Pilih semua",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save to file…",
+ "message": "&Save to file…",
+ "translation": "Menyimpan ke dalam berkas…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Time",
+ "message": "Time",
+ "translation": "Waktu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log message",
+ "message": "Log message",
+ "translation": "Pesan log",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "message": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "translation": "Berkas Txt (*.Txt)|*.Txt|Semua berkas (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export log to file",
+ "message": "Export log to file",
+ "translation": "Ekspor log kedalam file",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&About WireGuard…",
+ "message": "&About WireGuard…",
+ "translation": "&Tentang WireGuard…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel Error",
+ "message": "Tunnel Error",
+ "translation": "Tunnel eror",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "message": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "translation": "{ErrMsg}\n\nSilakan baca log untuk informasi lebih lanjut.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "ErrMsg",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errMsg"
+ }
+ ]
+ },
+ {
+ "id": "{Title} (out of date)",
+ "message": "{Title} (out of date)",
+ "translation": "{Title} (kadaluarsa)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Detection Error",
+ "message": "WireGuard Detection Error",
+ "translation": "Deteksi eror WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to wait for WireGuard window to appear: {Err}",
+ "message": "Unable to wait for WireGuard window to appear: {Err}",
+ "translation": "Tidak dapat menunggu jendela WireGuard muncul: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard: Deactivated",
+ "message": "WireGuard: Deactivated",
+ "translation": "WireGuard: Dinonaktifkan",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Unknown",
+ "message": "Status: Unknown",
+ "translation": "Status: Tidak diketahui",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses: None",
+ "message": "Addresses: None",
+ "translation": "Alamat: Kosong",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Manage tunnels…",
+ "message": "&Manage tunnels…",
+ "translation": "&Manajer Tunnel…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Import tunnel(s) from file…",
+ "message": "&Import tunnel(s) from file…",
+ "translation": "&Impor tunnel dari file…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "E&xit",
+ "message": "E&xit",
+ "translation": "&Keluar",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Tunnel Error",
+ "message": "WireGuard Tunnel Error",
+ "translation": "Wireguard Tunnel Eror",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard: {TextForStateglobalState_true}",
+ "message": "WireGuard: {TextForStateglobalState_true}",
+ "translation": "WireGuard: {TextForStateglobalState_true}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TextForStateglobalState_true",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "textForState(globalState, true)"
+ }
+ ]
+ },
+ {
+ "id": "Status: {StateText}",
+ "message": "Status: {StateText}",
+ "translation": "Status: {StateText}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "StateText",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "stateText"
+ }
+ ]
+ },
+ {
+ "id": "Export all tunnels to &zip…",
+ "message": "Export all tunnels to &zip…",
+ "translation": "Ekspor semua tunnel ke &zip…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit &selected tunnel…",
+ "message": "Edit &selected tunnel…",
+ "translation": "Ubah tunnel &terpilih…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Remove selected tunnel(s)",
+ "message": "&Remove selected tunnel(s)",
+ "translation": "&Hapus tunnel terpilih",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Could not import selected configuration: {LastErr}",
+ "message": "Could not import selected configuration: {LastErr}",
+ "translation": "Tidak dapat mengimpor konfigurasi yang dipilih: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/locales/it/messages.gotext.json b/locales/it/messages.gotext.json
new file mode 100644
index 00000000..5bea90c2
--- /dev/null
+++ b/locales/it/messages.gotext.json
@@ -0,0 +1,1847 @@
+{
+ "language": "it",
+ "messages": [
+ {
+ "id": "Error",
+ "message": "Error",
+ "translation": "Errore",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(no argument): elevate and install manager service",
+ "message": "(no argument): elevate and install manager service",
+ "translation": "(nessun argomento): eleva e installa il servizio di gestione",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Usage: {Args0} [\n{String}]",
+ "message": "Usage: {Args0} [\n{String}]",
+ "translation": "Utilizzo: {Args0} [\n{String}]",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Args0",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "os.Args[0]"
+ },
+ {
+ "id": "String",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "builder.String()"
+ }
+ ]
+ },
+ {
+ "id": "Command Line Options",
+ "message": "Command Line Options",
+ "translation": "Opzioni riga di comando",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to determine whether the process is running under WOW64: {Err}",
+ "message": "Unable to determine whether the process is running under WOW64: {Err}",
+ "translation": "Impossibile determinare se il processo è in esecuzione in WOW64: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "You must use the native version of WireGuard on this computer.",
+ "message": "You must use the native version of WireGuard on this computer.",
+ "translation": "Devi utilizzare la versione nativa di WireGuard su questo computer.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to open current process token: {Err}",
+ "message": "Unable to open current process token: {Err}",
+ "translation": "Impossibile aprire il token del processo corrente: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard può essere utilizzato solo dagli utenti membri del gruppo {AdminGroupName} di sistema.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard è in esecuzione, ma l'interfaccia utente è accessibile solo dai desktop del gruppo {AdminGroupName} di sistema.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard system tray icon did not appear after 30 seconds.",
+ "message": "WireGuard system tray icon did not appear after 30 seconds.",
+ "translation": "L'icona della barra delle applicazioni di WireGuard non è apparsa dopo 30 secondi.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Now",
+ "message": "Now",
+ "translation": "Ora",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "System clock wound backward!",
+ "message": "System clock wound backward!",
+ "translation": "L'orologio di sistema va all'indietro!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Years} year(s)",
+ "message": "{Years} year(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Years",
+ "cases": {
+ "one": {
+ "msg": "{Years} anno"
+ },
+ "other": {
+ "msg": "{Years} anni"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Years",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "years"
+ }
+ ]
+ },
+ {
+ "id": "{Days} day(s)",
+ "message": "{Days} day(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Days",
+ "cases": {
+ "one": {
+ "msg": "{Days} giorno"
+ },
+ "other": {
+ "msg": "{Days} giorni"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Days",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "days"
+ }
+ ]
+ },
+ {
+ "id": "{Hours} hour(s)",
+ "message": "{Hours} hour(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Hours",
+ "cases": {
+ "one": {
+ "msg": "{Hours} ora"
+ },
+ "other": {
+ "msg": "{Hours} ore"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Hours",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "hours"
+ }
+ ]
+ },
+ {
+ "id": "{Minutes} minute(s)",
+ "message": "{Minutes} minute(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Minutes",
+ "cases": {
+ "one": {
+ "msg": "{Minutes} minuto"
+ },
+ "other": {
+ "msg": "{Minutes} minuti"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Minutes",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "minutes"
+ }
+ ]
+ },
+ {
+ "id": "{Seconds} second(s)",
+ "message": "{Seconds} second(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Seconds",
+ "cases": {
+ "one": {
+ "msg": "{Seconds} secondo"
+ },
+ "other": {
+ "msg": "{Seconds} secondi"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Seconds",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "seconds"
+ }
+ ]
+ },
+ {
+ "id": "{Timestamp} ago",
+ "message": "{Timestamp} ago",
+ "translation": "{Timestamp} fa",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Timestamp",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "timestamp"
+ }
+ ]
+ },
+ {
+ "id": "{Bytes} B",
+ "message": "{Bytes} B",
+ "translation": "{Bytes} B",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Bytes",
+ "string": "%[1]d",
+ "type": "golang.zx2c4.com/wireguard/windows/conf.Bytes",
+ "underlyingType": "uint64",
+ "argNum": 1,
+ "expr": "b"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024} KiB",
+ "message": "{Float64b__1024} KiB",
+ "translation": "{Float64b__1024} KiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024} MiB",
+ "message": "{Float64b__1024__1024} MiB",
+ "translation": "{Float64b__1024__1024} MiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024} GiB",
+ "message": "{Float64b__1024__1024__1024} GiB",
+ "translation": "{Float64b__1024__1024__1024} GiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024__1024} TiB",
+ "message": "{Float64b__1024__1024__1024__1024} TiB",
+ "translation": "{Float64b__1024__1024__1024__1024} TiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Why}: {Offender}",
+ "message": "{Why}: {Offender}",
+ "translation": "{Why}: {Offender}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Why",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "e.why"
+ },
+ {
+ "id": "Offender",
+ "string": "%[2]q",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "e.offender"
+ }
+ ]
+ },
+ {
+ "id": "Invalid IP address",
+ "message": "Invalid IP address",
+ "translation": "Indirizzo IP non valido",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid network prefix length",
+ "message": "Invalid network prefix length",
+ "translation": "Lunghezza del prefisso di rete non valida",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Missing port from endpoint",
+ "message": "Missing port from endpoint",
+ "translation": "Manca la porta dall'endpoint",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid endpoint host",
+ "message": "Invalid endpoint host",
+ "translation": "Host dell'endpoint non valido",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Brackets must contain an IPv6 address",
+ "message": "Brackets must contain an IPv6 address",
+ "translation": "Le parentesi devono contenere un indirizzo IPv6",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid MTU",
+ "message": "Invalid MTU",
+ "translation": "MTU non valido",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid port",
+ "message": "Invalid port",
+ "translation": "Porta non valida",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid persistent keepalive",
+ "message": "Invalid persistent keepalive",
+ "translation": "Keepalive permanente non valido",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key: {Err}",
+ "message": "Invalid key: {Err}",
+ "translation": "Chiave non valida: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Keys must decode to exactly 32 bytes",
+ "message": "Keys must decode to exactly 32 bytes",
+ "translation": "Le chiavi devono decodificare esattamente 32 byte",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Number must be a number between 0 and 2^64-1: {Err}",
+ "message": "Number must be a number between 0 and 2^64-1: {Err}",
+ "translation": "Il numero deve essere un numero compreso tra 0 e 2^64-1: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Two commas in a row",
+ "message": "Two commas in a row",
+ "translation": "Due virgole in una riga",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name is not valid",
+ "message": "Tunnel name is not valid",
+ "translation": "Il nome del tunnel non è valido",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Line must occur in a section",
+ "message": "Line must occur in a section",
+ "translation": "Una riga deve essere presente in una sezione",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Config key is missing an equals separator",
+ "message": "Config key is missing an equals separator",
+ "translation": "Manca un separatore di uguaglianza per la chiave di configurazione",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Key must have a value",
+ "message": "Key must have a value",
+ "translation": "La chiave deve avere un valore",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Interface] section",
+ "message": "Invalid key for [Interface] section",
+ "translation": "Chiave non valida per la sezione [Interface]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Peer] section",
+ "message": "Invalid key for [Peer] section",
+ "translation": "Chiave non valida per la sezione [Peer]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An interface must have a private key",
+ "message": "An interface must have a private key",
+ "translation": "Un'interfaccia deve avere una chiave privata",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[none specified]",
+ "message": "[none specified]",
+ "translation": "[non specificato]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "All peers must have public keys",
+ "message": "All peers must have public keys",
+ "translation": "Tutti i peer devono avere una chiave pubblica",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error in getting configuration",
+ "message": "Error in getting configuration",
+ "translation": "Errore durante il recupero della configurazione",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for interface section",
+ "message": "Invalid key for interface section",
+ "translation": "Chiave non valida per la sezione dell'interfaccia",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Protocol version must be 1",
+ "message": "Protocol version must be 1",
+ "translation": "La versione del protocollo deve essere 1",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for peer section",
+ "message": "Invalid key for peer section",
+ "translation": "Chiave non valida per la sezione peer",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[EnumerationSeparator]",
+ "message": "[EnumerationSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert between items when listing - most western languages will translate ‘[EnumerationSeparator]’ into ‘, ’ to produce lists like ‘apple, orange, strawberry’. Eastern languages might translate into ‘、’ to produce lists like ‘リンゴ、オレンジ、イチゴ’."
+ },
+ {
+ "id": "[UnitSeparator]",
+ "message": "[UnitSeparator]",
+ "translation": " ",
+ "comment": "Text to insert when combining units of a measure - most languages will translate ‘[UnitSeparator]’ into ‘ ’ (space) to produce lists like ‘2 minuti 30 sekund’, or empty string ‘’ to produce ‘2分30秒’."
+ },
+ {
+ "id": "About WireGuard",
+ "message": "About WireGuard",
+ "translation": "Informazioni su WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard logo image",
+ "message": "WireGuard logo image",
+ "translation": "Immagine del logo di WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "message": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "translation": "Versione applicazione: {Number}\nVersione backend Go: {WireGuardGoVersion}\nVersione Go: {Version_go}-{GOARCH}\nSistema operativo: {OsName}\nArchitettura: {NativeArch}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Number",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "version.Number"
+ },
+ {
+ "id": "WireGuardGoVersion",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "device.WireGuardGoVersion"
+ },
+ {
+ "id": "Version_go",
+ "string": "%[3]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 3,
+ "expr": "strings.TrimPrefix(runtime.Version(), \"go\")"
+ },
+ {
+ "id": "GOARCH",
+ "string": "%[4]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 4,
+ "expr": "runtime.GOARCH"
+ },
+ {
+ "id": "OsName",
+ "string": "%[5]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 5,
+ "expr": "version.OsName()"
+ },
+ {
+ "id": "NativeArch",
+ "string": "%[6]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 6,
+ "expr": "version.NativeArch()"
+ }
+ ]
+ },
+ {
+ "id": "Close",
+ "message": "Close",
+ "translation": "Chiudi",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "♥ &Donate!",
+ "message": "♥ &Donate!",
+ "translation": "♥ Fai una &donazione!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status:",
+ "message": "Status:",
+ "translation": "Stato:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Deactivate",
+ "message": "&Deactivate",
+ "translation": "&Disattiva",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Activate",
+ "message": "&Activate",
+ "translation": "&Attiva",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Public key:",
+ "message": "Public key:",
+ "translation": "Chiave pubblica:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Listen port:",
+ "message": "Listen port:",
+ "translation": "Porta in ascolto:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "MTU:",
+ "message": "MTU:",
+ "translation": "MTU:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses:",
+ "message": "Addresses:",
+ "translation": "Indirizzi:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "DNS servers:",
+ "message": "DNS servers:",
+ "translation": "Server DNS:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Scripts:",
+ "message": "Scripts:",
+ "translation": "Script:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Preshared key:",
+ "message": "Preshared key:",
+ "translation": "Chiave pre-condivisa:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Allowed IPs:",
+ "message": "Allowed IPs:",
+ "translation": "IP consentiti:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Endpoint:",
+ "message": "Endpoint:",
+ "translation": "Endpoint:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Persistent keepalive:",
+ "message": "Persistent keepalive:",
+ "translation": "Keepalive permanente:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Latest handshake:",
+ "message": "Latest handshake:",
+ "translation": "Ultima negoziazione:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Transfer:",
+ "message": "Transfer:",
+ "translation": "Trasferimento:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-up",
+ "message": "pre-up",
+ "translation": "pre-up",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-up",
+ "message": "post-up",
+ "translation": "post-up",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-down",
+ "message": "pre-down",
+ "translation": "pre-down",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-down",
+ "message": "post-down",
+ "translation": "post-down",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "disabled, per policy",
+ "message": "disabled, per policy",
+ "translation": "disattivato, per criterio",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "enabled",
+ "message": "enabled",
+ "translation": "abilitato",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{String} received, {String_1} sent",
+ "message": "{String} received, {String_1} sent",
+ "translation": "{String} ricevuti, {String_1} inviati",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "String",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "c.RxBytes.String()"
+ },
+ {
+ "id": "String_1",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "c.TxBytes.String()"
+ }
+ ]
+ },
+ {
+ "id": "Failed to determine tunnel state",
+ "message": "Failed to determine tunnel state",
+ "translation": "Determinazione dello stato del tunnel non riuscita",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to activate tunnel",
+ "message": "Failed to activate tunnel",
+ "translation": "Attivazione del tunnel non riuscita",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to deactivate tunnel",
+ "message": "Failed to deactivate tunnel",
+ "translation": "Disattivazione del tunnel non riuscita",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Interface: {Name}",
+ "message": "Interface: {Name}",
+ "translation": "Interfaccia: {Name}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "config.Name"
+ }
+ ]
+ },
+ {
+ "id": "Peer",
+ "message": "Peer",
+ "translation": "Peer",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Create new tunnel",
+ "message": "Create new tunnel",
+ "translation": "Crea tunnel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit tunnel",
+ "message": "Edit tunnel",
+ "translation": "Modifica tunnel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Name:",
+ "message": "&Name:",
+ "translation": "&Nome:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Public key:",
+ "message": "&Public key:",
+ "translation": "Chiave &pubblica:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(unknown)",
+ "message": "(unknown)",
+ "translation": "(sconosciuto)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Block untunneled traffic (kill-switch)",
+ "message": "&Block untunneled traffic (kill-switch)",
+ "translation": "&Blocca traffico fuori dal tunnel (kill-switch)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.",
+ "message": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.",
+ "translation": "Quando una configurazione ha esattamente un peer e quel peer ha un IP consentito contenente almeno uno tra 0.0.0.0/0 o ::/0, il servizio tunnel impiega un insieme di regole del firewall per bloccare tutto il traffico nè diretto nè proveniente dall'interfaccia tunnel o verso un server DNS sbagliato, con speciali eccezioni per DHCP e NDP.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save",
+ "message": "&Save",
+ "translation": "&Salva",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Cancel",
+ "message": "Cancel",
+ "translation": "Annulla",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Configuration:",
+ "message": "&Configuration:",
+ "translation": "&Configurazione:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid name",
+ "message": "Invalid name",
+ "translation": "Nome non valido",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A name is required.",
+ "message": "A name is required.",
+ "translation": "Un nome è richiesto.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name ‘{NewName}’ is invalid.",
+ "message": "Tunnel name ‘{NewName}’ is invalid.",
+ "translation": "Il nome del tunnel ‘{NewName}’ non è valido.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to list existing tunnels",
+ "message": "Unable to list existing tunnels",
+ "translation": "Impossibile elencare i tunnel esistenti",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel already exists",
+ "message": "Tunnel already exists",
+ "translation": "Il tunnel esiste già",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{NewName}’.",
+ "message": "Another tunnel already exists with the name ‘{NewName}’.",
+ "translation": "Un altro tunnel con il nome ‘{NewName}’ esiste già.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create new configuration",
+ "message": "Unable to create new configuration",
+ "translation": "Impossibile creare la nuova configurazione",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Writing file failed",
+ "message": "Writing file failed",
+ "translation": "Scrittura del file non riuscita",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "message": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "translation": "Il file ‘{FilePath}’ esiste già.\n\nVuoi sovrascriverlo?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "FilePath",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "filePath"
+ }
+ ]
+ },
+ {
+ "id": "Active",
+ "message": "Active",
+ "translation": "Attivo",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Activating",
+ "message": "Activating",
+ "translation": "Attivazione",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Inactive",
+ "message": "Inactive",
+ "translation": "Inattivo",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Deactivating",
+ "message": "Deactivating",
+ "translation": "Disattivazione",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unknown state",
+ "message": "Unknown state",
+ "translation": "Stato sconosciuto",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log",
+ "message": "Log",
+ "translation": "Log",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Copy",
+ "message": "&Copy",
+ "translation": "&Copia",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Select &all",
+ "message": "Select &all",
+ "translation": "Selezion&a tutto",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save to file…",
+ "message": "&Save to file…",
+ "translation": "&Salva su file…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Time",
+ "message": "Time",
+ "translation": "Tempo",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log message",
+ "message": "Log message",
+ "translation": "Messaggio di log",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "message": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "translation": "File di testo (*.txt)|*.txt|Tutti i file (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export log to file",
+ "message": "Export log to file",
+ "translation": "Esporta log su file",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&About WireGuard…",
+ "message": "&About WireGuard…",
+ "translation": "Inform&azioni su WireGuard…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel Error",
+ "message": "Tunnel Error",
+ "translation": "Errore del tunnel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "message": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "translation": "{ErrMsg}\n\nConsulta il log per ulteriori Informazioni.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "ErrMsg",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errMsg"
+ }
+ ]
+ },
+ {
+ "id": "{Title} (out of date)",
+ "message": "{Title} (out of date)",
+ "translation": "{Title} (obsoleto)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Detection Error",
+ "message": "WireGuard Detection Error",
+ "translation": "Errore di rilevamento di WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to wait for WireGuard window to appear: {Err}",
+ "message": "Unable to wait for WireGuard window to appear: {Err}",
+ "translation": "Impossibile attendere la comparsa della finestra di WireGuard: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard: Deactivated",
+ "message": "WireGuard: Deactivated",
+ "translation": "WireGuard: disattivato",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Unknown",
+ "message": "Status: Unknown",
+ "translation": "Stato: sconosciuto",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses: None",
+ "message": "Addresses: None",
+ "translation": "Indirizzi: nessuno",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Manage tunnels…",
+ "message": "&Manage tunnels…",
+ "translation": "&Gestisci i tunnel…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Import tunnel(s) from file…",
+ "message": "&Import tunnel(s) from file…",
+ "translation": "&Importa tunnel da file…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "E&xit",
+ "message": "E&xit",
+ "translation": "E&sci",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Tunnels",
+ "message": "&Tunnels",
+ "translation": "&Tunnel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Activated",
+ "message": "WireGuard Activated",
+ "translation": "WireGuard attivato",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been activated.",
+ "message": "The {Name} tunnel has been activated.",
+ "translation": "Il tunnel {Name} è stato attivato.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Deactivated",
+ "message": "WireGuard Deactivated",
+ "translation": "WireGuard disattivato",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been deactivated.",
+ "message": "The {Name} tunnel has been deactivated.",
+ "translation": "Il tunnel {Name} è stato disattivato.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Tunnel Error",
+ "message": "WireGuard Tunnel Error",
+ "translation": "Errore tunnel di WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard: {TextForStateglobalState_true}",
+ "message": "WireGuard: {TextForStateglobalState_true}",
+ "translation": "WireGuard: {TextForStateglobalState_true}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TextForStateglobalState_true",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "textForState(globalState, true)"
+ }
+ ]
+ },
+ {
+ "id": "Status: {StateText}",
+ "message": "Status: {StateText}",
+ "translation": "Stato: {StateText}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "StateText",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "stateText"
+ }
+ ]
+ },
+ {
+ "id": "Addresses: {EnumerationSeparator}",
+ "message": "Addresses: {EnumerationSeparator}",
+ "translation": "Indirizzi: {EnumerationSeparator}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "EnumerationSeparator",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "strings.Join(addrs, l18n.EnumerationSeparator())"
+ }
+ ]
+ },
+ {
+ "id": "An Update is Available!",
+ "message": "An Update is Available!",
+ "translation": "Un aggiornamento è disponibile!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Update Available",
+ "message": "WireGuard Update Available",
+ "translation": "Aggiornamento di WireGuard disponibile",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "message": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "translation": "Un aggiornamento di WireGuard è disponibile. Ti consigliamo di aggiornare il prima possibile.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnels",
+ "message": "Tunnels",
+ "translation": "Tunnel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Edit",
+ "message": "&Edit",
+ "translation": "&Modifica",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add &empty tunnel…",
+ "message": "Add &empty tunnel…",
+ "translation": "Aggiungi tunn&el vuoto...",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add Tunnel",
+ "message": "Add Tunnel",
+ "translation": "Aggiungi tunnel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Remove selected tunnel(s)",
+ "message": "Remove selected tunnel(s)",
+ "translation": "Rimuovi tunnel selezionati",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to zip",
+ "message": "Export all tunnels to zip",
+ "translation": "Esporta tutti i tunnel in zip",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Toggle",
+ "message": "&Toggle",
+ "translation": "Commu&ta",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to &zip…",
+ "message": "Export all tunnels to &zip…",
+ "translation": "Esporta tutti i tunnel in &zip...",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit &selected tunnel…",
+ "message": "Edit &selected tunnel…",
+ "translation": "Modifica il tunnel &selezionato…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Remove selected tunnel(s)",
+ "message": "&Remove selected tunnel(s)",
+ "translation": "&Rimuovi i tunnel selezionati",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "no configuration files were found",
+ "message": "no configuration files were found",
+ "translation": "nessun file di configurazione trovato",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Could not import selected configuration: {LastErr}",
+ "message": "Could not import selected configuration: {LastErr}",
+ "translation": "Impossibile importare la configurazione selezionata: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Could not enumerate existing tunnels: {LastErr}",
+ "message": "Could not enumerate existing tunnels: {LastErr}",
+ "translation": "Impossibile enumerare i tunnel esistenti: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{Name}’",
+ "message": "Another tunnel already exists with the name ‘{Name}’",
+ "translation": "Un altro tunnel esiste già con il nome ‘{Name}‘",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "unparsedConfig.Name"
+ }
+ ]
+ },
+ {
+ "id": "Unable to import configuration: {LastErr}",
+ "message": "Unable to import configuration: {LastErr}",
+ "translation": "Impossibile importare la configurazione: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Imported tunnels",
+ "message": "Imported tunnels",
+ "translation": "Tunnel importati",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Imported {M} tunnels",
+ "message": "Imported {M} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "M",
+ "cases": {
+ "one": {
+ "msg": "{M} tunnel importato"
+ },
+ "other": {
+ "msg": "{M} tunnel importati"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ }
+ ]
+ },
+ {
+ "id": "Imported {M} of {N} tunnels",
+ "message": "Imported {M} of {N} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "N",
+ "cases": {
+ "one": {
+ "msg": "{M} de {N} tunnel importato"
+ },
+ "other": {
+ "msg": "{M} di {N} tunnel importati"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ },
+ {
+ "id": "N",
+ "string": "%[2]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 2,
+ "expr": "n"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create tunnel",
+ "message": "Unable to create tunnel",
+ "translation": "Impossibile creare il tunnel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Delete {TunnelCount} tunnels",
+ "message": "Delete {TunnelCount} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "Elimina {TunnelCount} tunnel"
+ },
+ "other": {
+ "msg": "Elimina {TunnelCount} tunnel"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "message": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "Sei sicuro di voler eliminare {TunnelCount} tunnel?"
+ },
+ "other": {
+ "msg": "Sei sicuro di voler eliminare {TunnelCount} tunnel?"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Delete tunnel ‘{TunnelName}’",
+ "message": "Delete tunnel ‘{TunnelName}’",
+ "translation": "Elimina tunnel ‘{TunnelName}‘",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "message": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "translation": "Sei sicuro di voler eliminare il tunnel ‘{TunnelName}‘?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "{Question} You cannot undo this action.",
+ "message": "{Question} You cannot undo this action.",
+ "translation": "{Question} Non è possibile annullare questa azione.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Question",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "question"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnel",
+ "message": "Unable to delete tunnel",
+ "translation": "Impossibile eliminare il tunnel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A tunnel was unable to be removed: {Error}",
+ "message": "A tunnel was unable to be removed: {Error}",
+ "translation": "Non è stato possibile rimuovere un tunnel: {Error}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Error",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errors[0].Error()"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnels",
+ "message": "Unable to delete tunnels",
+ "translation": "Impossibile eliminare i tunnel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Lenerrors} tunnels were unable to be removed.",
+ "message": "{Lenerrors} tunnels were unable to be removed.",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Lenerrors",
+ "cases": {
+ "one": {
+ "msg": "Non è stato possibile eliminare {Lenerrors} tunnel."
+ },
+ "other": {
+ "msg": "Non è stato possibile eliminare {Lenerrors} tunnel."
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Lenerrors",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "len(errors)"
+ }
+ ]
+ },
+ {
+ "id": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "message": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "translation": "File di configurazione (*.zip, *.conf)|*.zip;*.conf|Tutti i file (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Import tunnel(s) from file",
+ "message": "Import tunnel(s) from file",
+ "translation": "Importa tunnel da file",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Configuration ZIP Files (*.zip)|*.zip",
+ "message": "Configuration ZIP Files (*.zip)|*.zip",
+ "translation": "File di configurazione ZIP (*.zip)|*.zip",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export tunnels to zip",
+ "message": "Export tunnels to zip",
+ "translation": "Esporta tunnel in zip",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Title} (unsigned build, no updates)",
+ "message": "{Title} (unsigned build, no updates)",
+ "translation": "{Title} (versione non firmata, nessun aggiornamento)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "Error Exiting WireGuard",
+ "message": "Error Exiting WireGuard",
+ "translation": "Errore durante la chiusura di WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "message": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "translation": "Impossibile uscire dal servizio a causa di: {Err}. Potresti voler interrompere WireGuard dal gestore dei servizi.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "message": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "translation": "Un aggiornamento di WireGuard è disponibile. Ti consigliamo vivamente di aggiornare immediatamente.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for user",
+ "message": "Status: Waiting for user",
+ "translation": "Stato: in attesa dell'utente",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Update Now",
+ "message": "Update Now",
+ "translation": "Aggiorna ora",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for updater service",
+ "message": "Status: Waiting for updater service",
+ "translation": "Stato: in attesa del servizio di aggiornamento",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error: {Err}. Please try again.",
+ "message": "Error: {Err}. Please try again.",
+ "translation": "Errore: {Err}. Prova ancora.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Status: Complete!",
+ "message": "Status: Complete!",
+ "translation": "Stato: Completo!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "http2: Framer {F}: failed to decode just-written frame",
+ "message": "http2: Framer {F}: failed to decode just-written frame",
+ "translation": "http2: Framer {F}: decodifica del frame appena scritto non riuscita",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ }
+ ]
+ },
+ {
+ "id": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "message": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "translation": "http2: Framer {F}: ha scritto {Http2summarizeFramefr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ },
+ {
+ "id": "Http2summarizeFramefr",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(fr)"
+ }
+ ]
+ },
+ {
+ "id": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "message": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "translation": "http2: Framer {Fr}: ha letto {Http2summarizeFramef}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Fr",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "fr"
+ },
+ {
+ "id": "Http2summarizeFramef",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(f)"
+ }
+ ]
+ },
+ {
+ "id": "http2: decoded hpack field {HeaderField}",
+ "message": "http2: decoded hpack field {HeaderField}",
+ "translation": "http2: campo hpack {HeaderField} decodificato",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "HeaderField",
+ "string": "%+[1]v",
+ "type": "vendor/golang.org/x/net/http2/hpack.HeaderField",
+ "underlyingType": "struct{Name string; Value string; Sensitive bool}",
+ "argNum": 1,
+ "expr": "hf"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/locales/ja/messages.gotext.json b/locales/ja/messages.gotext.json
new file mode 100644
index 00000000..77234a68
--- /dev/null
+++ b/locales/ja/messages.gotext.json
@@ -0,0 +1,1817 @@
+{
+ "language": "ja",
+ "messages": [
+ {
+ "id": "Error",
+ "message": "Error",
+ "translation": "エラー",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(no argument): elevate and install manager service",
+ "message": "(no argument): elevate and install manager service",
+ "translation": "(引数なし): 管理者権限でmanagerサービスをインストールする",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Usage: {Args0} [\n{String}]",
+ "message": "Usage: {Args0} [\n{String}]",
+ "translation": "使い方: {Args0} [\n{String}]",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Args0",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "os.Args[0]"
+ },
+ {
+ "id": "String",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "builder.String()"
+ }
+ ]
+ },
+ {
+ "id": "Command Line Options",
+ "message": "Command Line Options",
+ "translation": "コマンドラインオプション",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to determine whether the process is running under WOW64: {Err}",
+ "message": "Unable to determine whether the process is running under WOW64: {Err}",
+ "translation": "プロセスがWOW64下で動作しているか確認できません: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "You must use the native version of WireGuard on this computer.",
+ "message": "You must use the native version of WireGuard on this computer.",
+ "translation": "このコンピュータではネイティブ版の WireGuard を使ってください。",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to open current process token: {Err}",
+ "message": "Unable to open current process token: {Err}",
+ "translation": "現在のプロセスのトークンを開けません: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard は組み込みの {AdminGroupName} グループのメンバーだけが使えます。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard は実行中ですが、UI画面は組み込みの {AdminGroupName} グループのデスクトップからしか開けません。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard system tray icon did not appear after 30 seconds.",
+ "message": "WireGuard system tray icon did not appear after 30 seconds.",
+ "translation": "WireGuard システムトレイアイコンは30秒後に非表示になります。",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Now",
+ "message": "Now",
+ "translation": "今",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "System clock wound backward!",
+ "message": "System clock wound backward!",
+ "translation": "システム時刻が巻き戻った!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Years} year(s)",
+ "message": "{Years} year(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Years",
+ "cases": {
+ "other": {
+ "msg": "{Years} 年"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Years",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "years"
+ }
+ ]
+ },
+ {
+ "id": "{Days} day(s)",
+ "message": "{Days} day(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Days",
+ "cases": {
+ "other": {
+ "msg": "{Days} 日"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Days",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "days"
+ }
+ ]
+ },
+ {
+ "id": "{Hours} hour(s)",
+ "message": "{Hours} hour(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Hours",
+ "cases": {
+ "other": {
+ "msg": "{Hours} 時間"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Hours",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "hours"
+ }
+ ]
+ },
+ {
+ "id": "{Minutes} minute(s)",
+ "message": "{Minutes} minute(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Minutes",
+ "cases": {
+ "other": {
+ "msg": "{Minutes} 分"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Minutes",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "minutes"
+ }
+ ]
+ },
+ {
+ "id": "{Seconds} second(s)",
+ "message": "{Seconds} second(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Seconds",
+ "cases": {
+ "other": {
+ "msg": "{Seconds} 秒"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Seconds",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "seconds"
+ }
+ ]
+ },
+ {
+ "id": "{Timestamp} ago",
+ "message": "{Timestamp} ago",
+ "translation": "{Timestamp} 前",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Timestamp",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "timestamp"
+ }
+ ]
+ },
+ {
+ "id": "{Bytes} B",
+ "message": "{Bytes} B",
+ "translation": "{Bytes} B",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Bytes",
+ "string": "%[1]d",
+ "type": "golang.zx2c4.com/wireguard/windows/conf.Bytes",
+ "underlyingType": "uint64",
+ "argNum": 1,
+ "expr": "b"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024} KiB",
+ "message": "{Float64b__1024} KiB",
+ "translation": "{Float64b__1024} KiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024} MiB",
+ "message": "{Float64b__1024__1024} MiB",
+ "translation": "{Float64b__1024__1024} MiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024} GiB",
+ "message": "{Float64b__1024__1024__1024} GiB",
+ "translation": "{Float64b__1024__1024__1024} GiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024__1024} TiB",
+ "message": "{Float64b__1024__1024__1024__1024} TiB",
+ "translation": "{Float64b__1024__1024__1024__1024} TiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Why}: {Offender}",
+ "message": "{Why}: {Offender}",
+ "translation": "{Why}: {Offender}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Why",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "e.why"
+ },
+ {
+ "id": "Offender",
+ "string": "%[2]q",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "e.offender"
+ }
+ ]
+ },
+ {
+ "id": "Invalid IP address",
+ "message": "Invalid IP address",
+ "translation": "無効な IP アドレス",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid network prefix length",
+ "message": "Invalid network prefix length",
+ "translation": "無効なネットワークプレフィックス長",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Missing port from endpoint",
+ "message": "Missing port from endpoint",
+ "translation": "エンドポイントのポート指定なし",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid endpoint host",
+ "message": "Invalid endpoint host",
+ "translation": "無効なエンドポイントホスト",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Brackets must contain an IPv6 address",
+ "message": "Brackets must contain an IPv6 address",
+ "translation": "カッコ内は IPv6 アドレスが入ります",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid MTU",
+ "message": "Invalid MTU",
+ "translation": "無効な MTU",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid port",
+ "message": "Invalid port",
+ "translation": "無効なポート番号",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid persistent keepalive",
+ "message": "Invalid persistent keepalive",
+ "translation": "無効な持続的キープアライブ値",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key: {Err}",
+ "message": "Invalid key: {Err}",
+ "translation": "不正な鍵: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Keys must decode to exactly 32 bytes",
+ "message": "Keys must decode to exactly 32 bytes",
+ "translation": "鍵は 32 バイトでなければなりません",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Number must be a number between 0 and 2^64-1: {Err}",
+ "message": "Number must be a number between 0 and 2^64-1: {Err}",
+ "translation": "数値は0から2の64乗-1の範囲内の値でなければなりません: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Two commas in a row",
+ "message": "Two commas in a row",
+ "translation": "1行にカンマが2つあります",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name is not valid",
+ "message": "Tunnel name is not valid",
+ "translation": "トンネル名が不正です",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Line must occur in a section",
+ "message": "Line must occur in a section",
+ "translation": "行がセクション内にありません",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Config key is missing an equals separator",
+ "message": "Config key is missing an equals separator",
+ "translation": "設定項目にイコール(=)セパレータがない",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Key must have a value",
+ "message": "Key must have a value",
+ "translation": "キー項目に対応する値がありません",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Interface] section",
+ "message": "Invalid key for [Interface] section",
+ "translation": "無効な [Interface] セクションのキー項目",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Peer] section",
+ "message": "Invalid key for [Peer] section",
+ "translation": "無効な [Peer] セクションのキー項目",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An interface must have a private key",
+ "message": "An interface must have a private key",
+ "translation": "インターフェースには秘密鍵が必須です",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[none specified]",
+ "message": "[none specified]",
+ "translation": "[指定なし]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "All peers must have public keys",
+ "message": "All peers must have public keys",
+ "translation": "すべてのピアには公開鍵が必須です",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error in getting configuration",
+ "message": "Error in getting configuration",
+ "translation": "設定の読込中にエラーが発生しました",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for interface section",
+ "message": "Invalid key for interface section",
+ "translation": "無効な Interface セクションのキー項目",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Protocol version must be 1",
+ "message": "Protocol version must be 1",
+ "translation": "プロトコルバージョンは 1 でなければなりません",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for peer section",
+ "message": "Invalid key for peer section",
+ "translation": "無効な Peer セクションのキー項目",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[EnumerationSeparator]",
+ "message": "[EnumerationSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert between items when listing - most western languages will translate ‘[EnumerationSeparator]’ into ‘, ’ to produce lists like ‘apple, orange, strawberry’. Eastern languages might translate into ‘、’ to produce lists like ‘リンゴ、オレンジ、イチゴ’."
+ },
+ {
+ "id": "[UnitSeparator]",
+ "message": "[UnitSeparator]",
+ "translation": " ",
+ "comment": "Text to insert when combining units of a measure - most languages will translate ‘[UnitSeparator]’ into ‘ ’ (space) to produce lists like ‘2 minuti 30 sekund’, or empty string ‘’ to produce ‘2分30秒’."
+ },
+ {
+ "id": "About WireGuard",
+ "message": "About WireGuard",
+ "translation": "WireGuard について",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard logo image",
+ "message": "WireGuard logo image",
+ "translation": "WireGuard ロゴ画像",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "message": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "translation": "アプリ バージョン: {Number}\nGo バックエンド バージョン: {WireGuardGoVersion}\nGo バージョン: {Version_go}-{GOARCH}\nOS: {OsName}\nアーキテクチャ: {NativeArch}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Number",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "version.Number"
+ },
+ {
+ "id": "WireGuardGoVersion",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "device.WireGuardGoVersion"
+ },
+ {
+ "id": "Version_go",
+ "string": "%[3]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 3,
+ "expr": "strings.TrimPrefix(runtime.Version(), \"go\")"
+ },
+ {
+ "id": "GOARCH",
+ "string": "%[4]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 4,
+ "expr": "runtime.GOARCH"
+ },
+ {
+ "id": "OsName",
+ "string": "%[5]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 5,
+ "expr": "version.OsName()"
+ },
+ {
+ "id": "NativeArch",
+ "string": "%[6]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 6,
+ "expr": "version.NativeArch()"
+ }
+ ]
+ },
+ {
+ "id": "Close",
+ "message": "Close",
+ "translation": "閉じる",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "♥ &Donate!",
+ "message": "♥ &Donate!",
+ "translation": "♥ 寄付のお願い!(&D)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status:",
+ "message": "Status:",
+ "translation": "状態:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Deactivate",
+ "message": "&Deactivate",
+ "translation": "無効化(&D)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Activate",
+ "message": "&Activate",
+ "translation": "有効化(&A)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Public key:",
+ "message": "Public key:",
+ "translation": "公開鍵:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Listen port:",
+ "message": "Listen port:",
+ "translation": "待受ポート番号:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "MTU:",
+ "message": "MTU:",
+ "translation": "MTU:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses:",
+ "message": "Addresses:",
+ "translation": "アドレス:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "DNS servers:",
+ "message": "DNS servers:",
+ "translation": "DNS サーバ:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Scripts:",
+ "message": "Scripts:",
+ "translation": "スクリプト:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Preshared key:",
+ "message": "Preshared key:",
+ "translation": "事前共有鍵:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Allowed IPs:",
+ "message": "Allowed IPs:",
+ "translation": "Allowed IPs:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Endpoint:",
+ "message": "Endpoint:",
+ "translation": "エンドポイント:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Persistent keepalive:",
+ "message": "Persistent keepalive:",
+ "translation": "持続的キープアライブ:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Latest handshake:",
+ "message": "Latest handshake:",
+ "translation": "直近のハンドシェイク:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Transfer:",
+ "message": "Transfer:",
+ "translation": "転送:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-up",
+ "message": "pre-up",
+ "translation": "pre-up",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-up",
+ "message": "post-up",
+ "translation": "post-up",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-down",
+ "message": "pre-down",
+ "translation": "pre-down",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-down",
+ "message": "post-down",
+ "translation": "post-down",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "disabled, per policy",
+ "message": "disabled, per policy",
+ "translation": "ポリシーにより無効です",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "enabled",
+ "message": "enabled",
+ "translation": "有効",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{String} received, {String_1} sent",
+ "message": "{String} received, {String_1} sent",
+ "translation": "{String} 受信済み、{String_1} 送信済み",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "String",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "c.RxBytes.String()"
+ },
+ {
+ "id": "String_1",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "c.TxBytes.String()"
+ }
+ ]
+ },
+ {
+ "id": "Failed to determine tunnel state",
+ "message": "Failed to determine tunnel state",
+ "translation": "トンネルの状態取得に失敗しました",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to activate tunnel",
+ "message": "Failed to activate tunnel",
+ "translation": "トンネルの有効化に失敗しました",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to deactivate tunnel",
+ "message": "Failed to deactivate tunnel",
+ "translation": "トンネルの無効化に失敗しました",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Interface: {Name}",
+ "message": "Interface: {Name}",
+ "translation": "インターフェース: {Name}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "config.Name"
+ }
+ ]
+ },
+ {
+ "id": "Peer",
+ "message": "Peer",
+ "translation": "ピア",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Create new tunnel",
+ "message": "Create new tunnel",
+ "translation": "トンネルの新規作成",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit tunnel",
+ "message": "Edit tunnel",
+ "translation": "トンネルの編集",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Name:",
+ "message": "&Name:",
+ "translation": "名前(&N):",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Public key:",
+ "message": "&Public key:",
+ "translation": "公開鍵(&P):",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(unknown)",
+ "message": "(unknown)",
+ "translation": "(不明)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Block untunneled traffic (kill-switch)",
+ "message": "&Block untunneled traffic (kill-switch)",
+ "translation": "トンネルを通らないトラフィックのブロック(キルスイッチ)(&B)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.",
+ "message": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.",
+ "translation": "ピアが1つだけ設定されていて、Allowed IPs に 0.0.0.0/0 または :: / 0 が含まれている場合、トンネルサービスはトンネルインターフェイスを通らないトラフィックや間違った DNS サーバーに向かう通信をブロックするファイアウォールルールを追加します。\nDHCP と NDP には特別な例外があります。",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save",
+ "message": "&Save",
+ "translation": "保存(&S)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Cancel",
+ "message": "Cancel",
+ "translation": "キャンセル",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Configuration:",
+ "message": "&Configuration:",
+ "translation": "設定(&C):",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid name",
+ "message": "Invalid name",
+ "translation": "無効な名前",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A name is required.",
+ "message": "A name is required.",
+ "translation": "名前は必須です。",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name ‘{NewName}’ is invalid.",
+ "message": "Tunnel name ‘{NewName}’ is invalid.",
+ "translation": "トンネル名 ‘{NewName}’ は不正です。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to list existing tunnels",
+ "message": "Unable to list existing tunnels",
+ "translation": "既存のトンネルを表示できません",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel already exists",
+ "message": "Tunnel already exists",
+ "translation": "トンネルはすでに存在します",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{NewName}’.",
+ "message": "Another tunnel already exists with the name ‘{NewName}’.",
+ "translation": "‘{NewName}’ という名前の別のトンネルがすでに存在します。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create new configuration",
+ "message": "Unable to create new configuration",
+ "translation": "新しい設定を作成できませんでした",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Writing file failed",
+ "message": "Writing file failed",
+ "translation": "ファイルの書き込みに失敗",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "message": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "translation": "ファイル ‘{FilePath}’ はすでに存在します。\n\n上書きしますか?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "FilePath",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "filePath"
+ }
+ ]
+ },
+ {
+ "id": "Active",
+ "message": "Active",
+ "translation": "有効",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Activating",
+ "message": "Activating",
+ "translation": "有効化中",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Inactive",
+ "message": "Inactive",
+ "translation": "無効",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Deactivating",
+ "message": "Deactivating",
+ "translation": "無効化中",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unknown state",
+ "message": "Unknown state",
+ "translation": "不明な状態",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log",
+ "message": "Log",
+ "translation": "ログ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Copy",
+ "message": "&Copy",
+ "translation": "コピー(&C)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Select &all",
+ "message": "Select &all",
+ "translation": "すべて選択(&A)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save to file…",
+ "message": "&Save to file…",
+ "translation": "ファイルに保存…(&S)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Time",
+ "message": "Time",
+ "translation": "時刻",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log message",
+ "message": "Log message",
+ "translation": "ログ メッセージ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "message": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "translation": "テキストファイル (*.txt)|*.txt|すべてのファイル (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export log to file",
+ "message": "Export log to file",
+ "translation": "ログをファイルにエクスポート",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&About WireGuard…",
+ "message": "&About WireGuard…",
+ "translation": "WireGuardについて…(&A)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel Error",
+ "message": "Tunnel Error",
+ "translation": "トンネルエラー",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "message": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "translation": "{ErrMsg}\n\n詳細はログを参照してください。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "ErrMsg",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errMsg"
+ }
+ ]
+ },
+ {
+ "id": "{Title} (out of date)",
+ "message": "{Title} (out of date)",
+ "translation": "{Title} (更新あり)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Detection Error",
+ "message": "WireGuard Detection Error",
+ "translation": "WireGuard 検出エラー",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to wait for WireGuard window to appear: {Err}",
+ "message": "Unable to wait for WireGuard window to appear: {Err}",
+ "translation": "WireGuard ウィンドウが表示できませんでした: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard: Deactivated",
+ "message": "WireGuard: Deactivated",
+ "translation": "WireGuard: 無効化済み",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Unknown",
+ "message": "Status: Unknown",
+ "translation": "状態: 不明",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses: None",
+ "message": "Addresses: None",
+ "translation": "アドレス: なし",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Manage tunnels…",
+ "message": "&Manage tunnels…",
+ "translation": "トンネルの管理…(&M)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Import tunnel(s) from file…",
+ "message": "&Import tunnel(s) from file…",
+ "translation": "トンネルをファイルからインポート…(&I)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "E&xit",
+ "message": "E&xit",
+ "translation": "終了(&X)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Tunnels",
+ "message": "&Tunnels",
+ "translation": "& トンネル",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Activated",
+ "message": "WireGuard Activated",
+ "translation": "WireGuard 有効化済み",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been activated.",
+ "message": "The {Name} tunnel has been activated.",
+ "translation": "トンネル {Name} は有効になりました。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Deactivated",
+ "message": "WireGuard Deactivated",
+ "translation": "WireGuard 無効化済み",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been deactivated.",
+ "message": "The {Name} tunnel has been deactivated.",
+ "translation": "トンネル {Name} は無効になりました。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Tunnel Error",
+ "message": "WireGuard Tunnel Error",
+ "translation": "WireGuard トンネルエラー",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard: {TextForStateglobalState_true}",
+ "message": "WireGuard: {TextForStateglobalState_true}",
+ "translation": "WireGuard: {TextForStateglobalState_true}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TextForStateglobalState_true",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "textForState(globalState, true)"
+ }
+ ]
+ },
+ {
+ "id": "Status: {StateText}",
+ "message": "Status: {StateText}",
+ "translation": "状態: {StateText}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "StateText",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "stateText"
+ }
+ ]
+ },
+ {
+ "id": "Addresses: {EnumerationSeparator}",
+ "message": "Addresses: {EnumerationSeparator}",
+ "translation": "アドレス: {EnumerationSeparator}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "EnumerationSeparator",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "strings.Join(addrs, l18n.EnumerationSeparator())"
+ }
+ ]
+ },
+ {
+ "id": "An Update is Available!",
+ "message": "An Update is Available!",
+ "translation": "更新が利用できます!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Update Available",
+ "message": "WireGuard Update Available",
+ "translation": "WireGuard の更新が利用可能です",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "message": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "translation": "WireGuard の更新が利用可能になりました。できるだけ早く更新してください。",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnels",
+ "message": "Tunnels",
+ "translation": "トンネル",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Edit",
+ "message": "&Edit",
+ "translation": "編集(&E)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add &empty tunnel…",
+ "message": "Add &empty tunnel…",
+ "translation": "空のトンネルを追加…(&E)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add Tunnel",
+ "message": "Add Tunnel",
+ "translation": "トンネルの追加",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Remove selected tunnel(s)",
+ "message": "Remove selected tunnel(s)",
+ "translation": "選択したトンネルの削除",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to zip",
+ "message": "Export all tunnels to zip",
+ "translation": "すべてのトンネルをzipにエクスポート",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Toggle",
+ "message": "&Toggle",
+ "translation": "切り替え(&T)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to &zip…",
+ "message": "Export all tunnels to &zip…",
+ "translation": "すべてのトンネルをzipにエクスポート…(&Z)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit &selected tunnel…",
+ "message": "Edit &selected tunnel…",
+ "translation": "選択したトンネルの編集…(&S)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Remove selected tunnel(s)",
+ "message": "&Remove selected tunnel(s)",
+ "translation": "選択したトンネルの削除(&R)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "no configuration files were found",
+ "message": "no configuration files were found",
+ "translation": "設定ファイルが見つかりません",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Could not import selected configuration: {LastErr}",
+ "message": "Could not import selected configuration: {LastErr}",
+ "translation": "選択したファイルからインポートできませんでした: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Could not enumerate existing tunnels: {LastErr}",
+ "message": "Could not enumerate existing tunnels: {LastErr}",
+ "translation": "既存のトンネルを表示できませんでした: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{Name}’",
+ "message": "Another tunnel already exists with the name ‘{Name}’",
+ "translation": "‘{Name}’ という名前の別のトンネルがすでに存在します",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "unparsedConfig.Name"
+ }
+ ]
+ },
+ {
+ "id": "Unable to import configuration: {LastErr}",
+ "message": "Unable to import configuration: {LastErr}",
+ "translation": "設定をインポートできませんでした: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Imported tunnels",
+ "message": "Imported tunnels",
+ "translation": "トンネルのインポート結果",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Imported {M} tunnels",
+ "message": "Imported {M} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "M",
+ "cases": {
+ "other": {
+ "msg": "{M} トンネルをインポートしました"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ }
+ ]
+ },
+ {
+ "id": "Imported {M} of {N} tunnels",
+ "message": "Imported {M} of {N} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "N",
+ "cases": {
+ "other": {
+ "msg": "{N} 中の {M} トンネルをインポートしました"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ },
+ {
+ "id": "N",
+ "string": "%[2]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 2,
+ "expr": "n"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create tunnel",
+ "message": "Unable to create tunnel",
+ "translation": "トンネルを作成できません",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Delete {TunnelCount} tunnels",
+ "message": "Delete {TunnelCount} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "other": {
+ "msg": "{TunnelCount} トンネルを削除"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "message": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "other": {
+ "msg": "本当に {TunnelCount} トンネルを削除しますか?"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Delete tunnel ‘{TunnelName}’",
+ "message": "Delete tunnel ‘{TunnelName}’",
+ "translation": "トンネル ‘{TunnelName}’ を削除",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "message": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "translation": "本当にトンネル ‘{TunnelName}’ を削除しますか?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "{Question} You cannot undo this action.",
+ "message": "{Question} You cannot undo this action.",
+ "translation": "{Question} この操作はもとに戻せません。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Question",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "question"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnel",
+ "message": "Unable to delete tunnel",
+ "translation": "トンネルを削除できません",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A tunnel was unable to be removed: {Error}",
+ "message": "A tunnel was unable to be removed: {Error}",
+ "translation": "トンネルを削除できませんでした: {Error}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Error",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errors[0].Error()"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnels",
+ "message": "Unable to delete tunnels",
+ "translation": "トンネルを削除できません",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Lenerrors} tunnels were unable to be removed.",
+ "message": "{Lenerrors} tunnels were unable to be removed.",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Lenerrors",
+ "cases": {
+ "other": {
+ "msg": "{Lenerrors} トンネルを削除できませんでした"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Lenerrors",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "len(errors)"
+ }
+ ]
+ },
+ {
+ "id": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "message": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "translation": "設定ファイル (*.zip, *.conf)|*.zip;*.conf|すべてのファイル (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Import tunnel(s) from file",
+ "message": "Import tunnel(s) from file",
+ "translation": "ファイルからトンネルをインポート",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Configuration ZIP Files (*.zip)|*.zip",
+ "message": "Configuration ZIP Files (*.zip)|*.zip",
+ "translation": "ZIP形式設定ファイル (*.zip)|*.zip",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export tunnels to zip",
+ "message": "Export tunnels to zip",
+ "translation": "トンネルをZIPにエクスポート",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Title} (unsigned build, no updates)",
+ "message": "{Title} (unsigned build, no updates)",
+ "translation": "{Title} (未署名のビルド、更新の提供なし)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "Error Exiting WireGuard",
+ "message": "Error Exiting WireGuard",
+ "translation": "WireGuard 終了エラー",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "message": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "translation": "{Err} のためサービスを終了できませんでした。サービスマネージャから WireGuard を停止できます。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "message": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "translation": "WireGuard の更新が利用可能です。速やかに更新することを強く推奨します。",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for user",
+ "message": "Status: Waiting for user",
+ "translation": "状態: ユーザーからの応答待ち",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Update Now",
+ "message": "Update Now",
+ "translation": "今すぐ更新",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for updater service",
+ "message": "Status: Waiting for updater service",
+ "translation": "状態: アップデータサービスを待機中",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error: {Err}. Please try again.",
+ "message": "Error: {Err}. Please try again.",
+ "translation": "エラー: {Err}。再度実行してください。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Status: Complete!",
+ "message": "Status: Complete!",
+ "translation": "状態: 完了!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "http2: Framer {F}: failed to decode just-written frame",
+ "message": "http2: Framer {F}: failed to decode just-written frame",
+ "translation": "http2: Framer {F}: just-writtenフレームのデコードに失敗",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ }
+ ]
+ },
+ {
+ "id": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "message": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "translation": "http2: Framer {F}: {Http2summarizeFramefr} を書き込みました",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ },
+ {
+ "id": "Http2summarizeFramefr",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(fr)"
+ }
+ ]
+ },
+ {
+ "id": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "message": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "translation": "http2: Framer {Fr}: {Http2summarizeFramef} を読み込みました",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Fr",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "fr"
+ },
+ {
+ "id": "Http2summarizeFramef",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(f)"
+ }
+ ]
+ },
+ {
+ "id": "http2: decoded hpack field {HeaderField}",
+ "message": "http2: decoded hpack field {HeaderField}",
+ "translation": "http2: hpack フィールド {HeaderField} をデコードしました",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "HeaderField",
+ "string": "%+[1]v",
+ "type": "vendor/golang.org/x/net/http2/hpack.HeaderField",
+ "underlyingType": "struct{Name string; Value string; Sensitive bool}",
+ "argNum": 1,
+ "expr": "hf"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/locales/ko/messages.gotext.json b/locales/ko/messages.gotext.json
new file mode 100644
index 00000000..d7a7040e
--- /dev/null
+++ b/locales/ko/messages.gotext.json
@@ -0,0 +1,11 @@
+{
+ "language": "ko",
+ "messages": [
+ {
+ "id": "Error",
+ "message": "Error",
+ "translation": "오류",
+ "translatorComment": "Copied from source."
+ }
+ ]
+} \ No newline at end of file
diff --git a/locales/pa-IN/messages.gotext.json b/locales/pa-IN/messages.gotext.json
new file mode 100644
index 00000000..e9626d24
--- /dev/null
+++ b/locales/pa-IN/messages.gotext.json
@@ -0,0 +1,1467 @@
+{
+ "language": "pa-IN",
+ "messages": [
+ {
+ "id": "Error",
+ "message": "Error",
+ "translation": "ਗ਼ਲਤੀ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Usage: {Args0} [\n{String}]",
+ "message": "Usage: {Args0} [\n{String}]",
+ "translation": "ਵਰਤੋਂ: {Args0} [\n{String}]",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Args0",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "os.Args[0]"
+ },
+ {
+ "id": "String",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "builder.String()"
+ }
+ ]
+ },
+ {
+ "id": "Command Line Options",
+ "message": "Command Line Options",
+ "translation": "ਕਮਾਂਡ ਲਾਈਨ ਚੋਣਾਂ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to determine whether the process is running under WOW64: {Err}",
+ "message": "Unable to determine whether the process is running under WOW64: {Err}",
+ "translation": "ਪਤਾ ਲਗਾਉਣ ਲਈ ਅਸਮਰੱਥ ਹੈ ਕਿ ਪਰੋਸੈਸ WOW64 ਅਧੀਨ ਚੱਲ ਰਿਹਾ ਹੈ: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "You must use the native version of WireGuard on this computer.",
+ "message": "You must use the native version of WireGuard on this computer.",
+ "translation": "ਤੁਹਾਨੂੰ ਇਸ ਕੰਪਿਊਟਰ ਉੱਤੇ WireGuard ਦਾ ਮੂਲ ਵਰਜ਼ਨ ਵਰਤਣਾ ਚਾਹੀਦਾ ਹੈ।",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to open current process token: {Err}",
+ "message": "Unable to open current process token: {Err}",
+ "translation": "ਮੌਜੂਦਾ ਪਰੋਸੈਸ ਟੋਕਨ ਖੋਲ੍ਹਣ ਲਈ ਅਸਮਰੱਥ: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard ਨੂੰ ਸਿਰਫ਼ ਉਹੀ ਵਰਤੋਂਕਾਰ ਵਰਤ ਸਕਦੇ ਹਨ, ਜੋ ਕਿ ਪਹਿਲਾਂ ਮੌਜੂਦ {AdminGroupName} ਗਰੁੱਪ ਦੇ ਮੈਂਬਰ ਹਨ।",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard ਚੱਲ ਰਿਹਾ ਹੈ, ਪਰ UI ਨੂੰ ਸਿਰਫ਼ ਪਹਿਲਾਂ ਮੌਜੂਦ {AdminGroupName} ਗਰੁੱਪ ਦੇ ਡੈਸਕਟਾਪ ਰਾਹੀਂ ਹੀ ਵਰਤਿਆ ਜਾ ਸਕਦਾ ਹੈ।",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard system tray icon did not appear after 30 seconds.",
+ "message": "WireGuard system tray icon did not appear after 30 seconds.",
+ "translation": "WireGuard ਸਿਸਟਮ ਟਰੇ ਆਈਕਾਨ 30 ਸਕਿੰਟਾਂ ਬਾਅਦ ਦਿਖਾਈ ਨਹੀਂ ਦਿੱਤਾ ਹੈ।",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Now",
+ "message": "Now",
+ "translation": "ਹੁਣ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "System clock wound backward!",
+ "message": "System clock wound backward!",
+ "translation": "ਸਿਸਟਮ ਘੜੀ ਪੁ਼ੱਠੀ ਮੋੜੀ ਗਈ!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Years} year(s)",
+ "message": "{Years} year(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Years",
+ "cases": {
+ "one": {
+ "msg": "{Years} ਸਾਲ"
+ },
+ "other": {
+ "msg": "{Years} ਸਾਲ"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Years",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "years"
+ }
+ ]
+ },
+ {
+ "id": "{Days} day(s)",
+ "message": "{Days} day(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Days",
+ "cases": {
+ "one": {
+ "msg": "{Days} ਦਿਨ"
+ },
+ "other": {
+ "msg": "{Days} ਦਿਨ"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Days",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "days"
+ }
+ ]
+ },
+ {
+ "id": "{Hours} hour(s)",
+ "message": "{Hours} hour(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Hours",
+ "cases": {
+ "one": {
+ "msg": "{Hours} ਘੰਟਾ"
+ },
+ "other": {
+ "msg": "{Hours} ਘੰਟੇ"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Hours",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "hours"
+ }
+ ]
+ },
+ {
+ "id": "{Minutes} minute(s)",
+ "message": "{Minutes} minute(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Minutes",
+ "cases": {
+ "one": {
+ "msg": "{Minutes} ਮਿੰਟ"
+ },
+ "other": {
+ "msg": "{Minutes} ਮਿੰਟ"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Minutes",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "minutes"
+ }
+ ]
+ },
+ {
+ "id": "{Seconds} second(s)",
+ "message": "{Seconds} second(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Seconds",
+ "cases": {
+ "one": {
+ "msg": "{Seconds} ਸਕਿੰਟ"
+ },
+ "other": {
+ "msg": "{Seconds} ਸਕਿੰਟ"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Seconds",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "seconds"
+ }
+ ]
+ },
+ {
+ "id": "{Timestamp} ago",
+ "message": "{Timestamp} ago",
+ "translation": "{Timestamp} ਪਹਿਲਾਂ",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Timestamp",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "timestamp"
+ }
+ ]
+ },
+ {
+ "id": "{Bytes} B",
+ "message": "{Bytes} B",
+ "translation": "{Bytes} B",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Bytes",
+ "string": "%[1]d",
+ "type": "golang.zx2c4.com/wireguard/windows/conf.Bytes",
+ "underlyingType": "uint64",
+ "argNum": 1,
+ "expr": "b"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024} KiB",
+ "message": "{Float64b__1024} KiB",
+ "translation": "{Float64b__1024} KiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024} MiB",
+ "message": "{Float64b__1024__1024} MiB",
+ "translation": "{Float64b__1024__1024} MiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024} GiB",
+ "message": "{Float64b__1024__1024__1024} GiB",
+ "translation": "{Float64b__1024__1024__1024} GiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024__1024} TiB",
+ "message": "{Float64b__1024__1024__1024__1024} TiB",
+ "translation": "{Float64b__1024__1024__1024__1024} TiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Why}: {Offender}",
+ "message": "{Why}: {Offender}",
+ "translation": "{Why}: {Offender}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Why",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "e.why"
+ },
+ {
+ "id": "Offender",
+ "string": "%[2]q",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "e.offender"
+ }
+ ]
+ },
+ {
+ "id": "Invalid IP address",
+ "message": "Invalid IP address",
+ "translation": "ਅਵੈਧ IP ਸਿਰਨਾਵਾਂ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid network prefix length",
+ "message": "Invalid network prefix length",
+ "translation": "ਗਲਤ ਨੈੱਟਵਰਕ ਅਗੇਤਰ ਲੰਬਾਈ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Missing port from endpoint",
+ "message": "Missing port from endpoint",
+ "translation": "ਐਂਡਪੁਆਇੰਟ ਤੋਂ ਪੋਰਟ ਗੁੰਮ ਹੈ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid MTU",
+ "message": "Invalid MTU",
+ "translation": "ਗ਼ੈਰ-ਵਾਜਬ MTU",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid port",
+ "message": "Invalid port",
+ "translation": "ਗ਼ੈਰ-ਵਾਜਬ ਪੋਰਟ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid persistent keepalive",
+ "message": "Invalid persistent keepalive",
+ "translation": "ਗ਼ੈਰ-ਵਾਜਬ persistent keepalive",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key: {Err}",
+ "message": "Invalid key: {Err}",
+ "translation": "ਗ਼ੈਰ-ਵਾਜਬ ਕੁੰਜੀ: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Keys must decode to exactly 32 bytes",
+ "message": "Keys must decode to exactly 32 bytes",
+ "translation": "ਕੁੰਜੀਆਂ ਠੀਕ 32 ਬਾਈਟ ਲਈ ਡੀਕੋਡ ਹੋਣੀਆਂ ਚਾਹੀਦੀਆਂ ਹਨ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Number must be a number between 0 and 2^64-1: {Err}",
+ "message": "Number must be a number between 0 and 2^64-1: {Err}",
+ "translation": "ਨੰਬਰ 0 ਅਤੇ 2^64-1 ਦੇ ਵਿਚਾਲੇ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Two commas in a row",
+ "message": "Two commas in a row",
+ "translation": "ਇੱਕ ਕਤਾਰ ਵਿੱਚ ਦੋ ਕੌਮੇ ਹਨ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name is not valid",
+ "message": "Tunnel name is not valid",
+ "translation": "ਟਨਲ ਦਾ ਨਾਂ ਠੀਕ ਨਹੀਂ ਹੈ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Line must occur in a section",
+ "message": "Line must occur in a section",
+ "translation": "ਭਾਗ ਵਿੱਚ ਲਾਈਨ ਹੋਣੀ ਚਾਹੀਦੀ ਹੈ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[EnumerationSeparator]",
+ "message": "[EnumerationSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert between items when listing - most western languages will translate ‘[EnumerationSeparator]’ into ‘, ’ to produce lists like ‘apple, orange, strawberry’. Eastern languages might translate into ‘、’ to produce lists like ‘リンゴ、オレンジ、イチゴ’."
+ },
+ {
+ "id": "[UnitSeparator]",
+ "message": "[UnitSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert when combining units of a measure - most languages will translate ‘[UnitSeparator]’ into ‘ ’ (space) to produce lists like ‘2 minuti 30 sekund’, or empty string ‘’ to produce ‘2分30秒’."
+ },
+ {
+ "id": "About WireGuard",
+ "message": "About WireGuard",
+ "translation": "ਵਾਇਰਗਾਰਡ ਬਾਰੇ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "message": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "translation": "ਐਪ ਦਾ ਵਰਜ਼ਨ: {Number}\nਗੋ ਬੈਕਐਂਡ ਦਾ ਵਰਜ਼ਨ: {WireGuardGoVersion}\nਗੋ ਦਾ ਵਰਜ਼ਨ: {Version_go}-{GOARCH}\nਓਪਰੇਟਿੰਗ ਸਿਸਟਮ: {OsName}\nਢਾਂਚਾ: {NativeArch}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Number",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "version.Number"
+ },
+ {
+ "id": "WireGuardGoVersion",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "device.WireGuardGoVersion"
+ },
+ {
+ "id": "Version_go",
+ "string": "%[3]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 3,
+ "expr": "strings.TrimPrefix(runtime.Version(), \"go\")"
+ },
+ {
+ "id": "GOARCH",
+ "string": "%[4]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 4,
+ "expr": "runtime.GOARCH"
+ },
+ {
+ "id": "OsName",
+ "string": "%[5]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 5,
+ "expr": "version.OsName()"
+ },
+ {
+ "id": "NativeArch",
+ "string": "%[6]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 6,
+ "expr": "version.NativeArch()"
+ }
+ ]
+ },
+ {
+ "id": "Close",
+ "message": "Close",
+ "translation": "ਬੰਦ ਕਰੋ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "♥ &Donate!",
+ "message": "♥ &Donate!",
+ "translation": "♥ ਦਾਨ ਦਿਓ(&D)!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status:",
+ "message": "Status:",
+ "translation": "ਸਥਿਤੀ:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Deactivate",
+ "message": "&Deactivate",
+ "translation": "ਨਾ-ਸਰਗਰਮ ਕਰੋ(&D)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Activate",
+ "message": "&Activate",
+ "translation": "ਸਰਗਰਮ ਕਰੋ(&A)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Public key:",
+ "message": "Public key:",
+ "translation": "ਪਬਲਿਕ ਕੁੰਜੀ:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Listen port:",
+ "message": "Listen port:",
+ "translation": "ਸੁਣਨ ਵਾਲੀ ਪੋਰਟ:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "MTU:",
+ "message": "MTU:",
+ "translation": "MTU:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses:",
+ "message": "Addresses:",
+ "translation": "ਸਿਰਨਾਵੇ:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "DNS servers:",
+ "message": "DNS servers:",
+ "translation": "DNS ਸਰਵਰ:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Scripts:",
+ "message": "Scripts:",
+ "translation": "ਸਕ੍ਰਿਪਟਾਂ:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Allowed IPs:",
+ "message": "Allowed IPs:",
+ "translation": "ਮਨਜ਼ੂਰ ਕੀਤੇ IP:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Endpoint:",
+ "message": "Endpoint:",
+ "translation": "ਐਂਡ-ਪੁਆਇੰਟ:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Transfer:",
+ "message": "Transfer:",
+ "translation": "ਟਰਾਂਸਫਰ:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "enabled",
+ "message": "enabled",
+ "translation": "ਸਮਰੱਥ ਹੈ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{String} received, {String_1} sent",
+ "message": "{String} received, {String_1} sent",
+ "translation": "{String} ਮਿਲੇ, {String_1} ਭੇਜੇ",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "String",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "c.RxBytes.String()"
+ },
+ {
+ "id": "String_1",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "c.TxBytes.String()"
+ }
+ ]
+ },
+ {
+ "id": "Failed to determine tunnel state",
+ "message": "Failed to determine tunnel state",
+ "translation": "ਟਨਲ ਸਥਿਤੀ ਪਤਾ ਲਗਾਉਣ ਲਈ ਅਸਫ਼ਲ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to activate tunnel",
+ "message": "Failed to activate tunnel",
+ "translation": "ਟਨਲ ਸਰਗਰਮ ਕਰਨ ਲਈ ਅਸਫ਼ਲ ਹੈ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to deactivate tunnel",
+ "message": "Failed to deactivate tunnel",
+ "translation": "ਟਨਲ ਨਾ-ਸਰਗਰਮ ਕਰਨ ਲਈ ਅਸਫ਼ਲ ਹੈ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Interface: {Name}",
+ "message": "Interface: {Name}",
+ "translation": "ਇੰਟਰਫੇਸ: {Name}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "config.Name"
+ }
+ ]
+ },
+ {
+ "id": "Peer",
+ "message": "Peer",
+ "translation": "ਪੀਅਰ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Create new tunnel",
+ "message": "Create new tunnel",
+ "translation": "ਨਵੀਂ ਟਨਲ ਬਣਾਓ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit tunnel",
+ "message": "Edit tunnel",
+ "translation": "ਟਨਲ ਨੂੰ ਸੋਧੋ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Name:",
+ "message": "&Name:",
+ "translation": "ਨਾਂ(&N):",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Public key:",
+ "message": "&Public key:",
+ "translation": "ਪਬਲਿਕ ਕੁੰਜੀ(&P):",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(unknown)",
+ "message": "(unknown)",
+ "translation": "(ਅਣਪਛਾਤਾ)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Block untunneled traffic (kill-switch)",
+ "message": "&Block untunneled traffic (kill-switch)",
+ "translation": "ਬਿਨਾਂ-ਟਨਲ ਵਾਲੇ ਟਰੈਫਿਕ ਉੱਤੇ ਪਾਬੰਦੀ ਲਾਓ (&B) (kill-switch)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save",
+ "message": "&Save",
+ "translation": "ਸੰਭਾਲੋ(&S)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Cancel",
+ "message": "Cancel",
+ "translation": "ਰੱਦ ਕਰੋ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Configuration:",
+ "message": "&Configuration:",
+ "translation": "ਸੰਰਚਨਾ(&C):",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid name",
+ "message": "Invalid name",
+ "translation": "ਅਯੋਗ ਨਾਂ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A name is required.",
+ "message": "A name is required.",
+ "translation": "ਨਾਂ ਚਾਹੀਦਾ ਹੈ।",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel already exists",
+ "message": "Tunnel already exists",
+ "translation": "ਟਨਲ ਪਹਿਲਾਂ ਹੀ ਮੌਜੂਦ ਹੈ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{NewName}’.",
+ "message": "Another tunnel already exists with the name ‘{NewName}’.",
+ "translation": "ਨਾਂ ‘{NewName}’ ਨਾਲ ਟਨਲ ਪਹਿਲਾਂ ਹੀ ਮੌਜੂਦ ਹੈ।",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create new configuration",
+ "message": "Unable to create new configuration",
+ "translation": "ਨਵੀਂ ਸੰਰਚਨਾ ਬਣਾਉਣ ਲਈ ਅਸਮਰੱਥ ਹੈ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Writing file failed",
+ "message": "Writing file failed",
+ "translation": "ਫ਼ਾਇਲ ਬਣਾਉਣ ਲਈ ਅਸਫ਼ਲ ਹੈ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "message": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "translation": "‘{FilePath}’ ਫ਼ਾਇਲ ਪਹਿਲਾਂ ਹੀ ਮੌਜੂਦ ਹੈ।\n\nਕੀ ਤੁਸੀਂ ਇਸ ਉੱਤੇ ਲਿਖਣਾ ਚਾਹੁੰਦੇ ਹੋ?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "FilePath",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "filePath"
+ }
+ ]
+ },
+ {
+ "id": "Active",
+ "message": "Active",
+ "translation": "ਸਰਗਰਮ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Activating",
+ "message": "Activating",
+ "translation": "ਸਰਗਰਮ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Inactive",
+ "message": "Inactive",
+ "translation": "ਨਾ-ਸਰਗਰਮ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Deactivating",
+ "message": "Deactivating",
+ "translation": "ਨਾ-ਸਰਗਰਮ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unknown state",
+ "message": "Unknown state",
+ "translation": "ਅਣਪਛਾਤੀ ਸਥਿਤੀ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log",
+ "message": "Log",
+ "translation": "ਲਾਗੂ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Copy",
+ "message": "&Copy",
+ "translation": "ਕਾਪੀ ਕਰੋ(&C)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Select &all",
+ "message": "Select &all",
+ "translation": "ਸਾਰੇ ਚੁਣੋ(&a)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save to file…",
+ "message": "&Save to file…",
+ "translation": "ਫ਼ਾਇਲ ਵਿੱਚ ਸੰਭਾਲੋ(&S)…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Time",
+ "message": "Time",
+ "translation": "ਸਮਾਂ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log message",
+ "message": "Log message",
+ "translation": "ਲਾਗ ਸੁਨੇਹਾ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "message": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "translation": "ਲਿਖਤ ਫ਼ਾਇਲਾਂ (*.txt)|*.txt|ਸਾਰੀਆਂ ਫ਼ਾਇਲਾਂ (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export log to file",
+ "message": "Export log to file",
+ "translation": "ਲਾਗ ਫ਼ਾਇਲ ਵਿੱਚ ਬਰਾਮਦ ਕਰੋ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&About WireGuard…",
+ "message": "&About WireGuard…",
+ "translation": "ਵਾਇਰਗਾਰਡ ਬਾਰੇ(&A)…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel Error",
+ "message": "Tunnel Error",
+ "translation": "ਟਨਲ ਗਲਤੀ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "message": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "translation": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "ErrMsg",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errMsg"
+ }
+ ]
+ },
+ {
+ "id": "{Title} (out of date)",
+ "message": "{Title} (out of date)",
+ "translation": "{Title} (out of date)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Detection Error",
+ "message": "WireGuard Detection Error",
+ "translation": "WireGuard ਖੋਜ ਗ਼ਲਤੀ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard: Deactivated",
+ "message": "WireGuard: Deactivated",
+ "translation": "ਵਾਇਰਗਾਰਡ: ਨਾ-ਸਰਗਰਮ ਕੀਤਾ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Unknown",
+ "message": "Status: Unknown",
+ "translation": "ਸਥਿਤੀ: ਅਣਪਛਾਤੀ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses: None",
+ "message": "Addresses: None",
+ "translation": "ਸਿਰਨਾਵੇਂ: ਕੋਈ ਨਹੀਂ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Manage tunnels…",
+ "message": "&Manage tunnels…",
+ "translation": "ਟਨਲਾਂ ਦਾ ਇੰਤਜ਼ਾਮ ਕਰੋ(&M)…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Import tunnel(s) from file…",
+ "message": "&Import tunnel(s) from file…",
+ "translation": "ਫ਼ਾਇਲ ਤੋਂ ਟਨਲਾਂ ਦਰਾਮਦ ਕਰੋ(&I)…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "E&xit",
+ "message": "E&xit",
+ "translation": "ਬਾਹਰ(&x)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Tunnels",
+ "message": "&Tunnels",
+ "translation": "ਟਨਲ(&T)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Activated",
+ "message": "WireGuard Activated",
+ "translation": "ਵਾਇਰਗਾਰਡ ਸਰਗਰਮ ਕੀਤਾ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been activated.",
+ "message": "The {Name} tunnel has been activated.",
+ "translation": "{Name} ਟਨਲ ਸਰਗਰਮ ਕੀਤੀ ਗਈ ਹੈ।",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Deactivated",
+ "message": "WireGuard Deactivated",
+ "translation": "ਵਾਇਰਗਾਰਡ ਨਾ-ਸਰਗਰਮ ਕੀਤਾ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been deactivated.",
+ "message": "The {Name} tunnel has been deactivated.",
+ "translation": "{Name} ਟਨਲ ਨਾ-ਸਰਗਰਮ ਕੀਤੀ ਗਈ ਹੈ।",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Tunnel Error",
+ "message": "WireGuard Tunnel Error",
+ "translation": "ਵਾਇਰਗਾਰਡ ਟਨਲ ਗਲਤੀ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard: {TextForStateglobalState_true}",
+ "message": "WireGuard: {TextForStateglobalState_true}",
+ "translation": "ਵਾਇਰਗਾਰਡ: {TextForStateglobalState_true}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TextForStateglobalState_true",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "textForState(globalState, true)"
+ }
+ ]
+ },
+ {
+ "id": "Status: {StateText}",
+ "message": "Status: {StateText}",
+ "translation": "ਸਥਿਤੀ: {StateText}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "StateText",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "stateText"
+ }
+ ]
+ },
+ {
+ "id": "Addresses: {EnumerationSeparator}",
+ "message": "Addresses: {EnumerationSeparator}",
+ "translation": "ਸਿਰਨਾਵੇਂ: {EnumerationSeparator}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "EnumerationSeparator",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "strings.Join(addrs, l18n.EnumerationSeparator())"
+ }
+ ]
+ },
+ {
+ "id": "An Update is Available!",
+ "message": "An Update is Available!",
+ "translation": "ਅੱਪਡੇਟ ਮੌਜੂਦ ਹੈ!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Update Available",
+ "message": "WireGuard Update Available",
+ "translation": "ਵਾਇਰਗਾਰਡ ਅੱਪਡੇਟ ਮੌਜੂਦ ਹੈ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "message": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "translation": "ਵਾਇਰਗਾਰਡ ਲਈ ਅੱਪਡੇਟ ਹੁਣ ਮੌਜੂਦ ਹੈ। ਜਿੰਨਾ ਛੇਤੀ ਹੋ ਸਕੇ ਤੁਹਾਨੂੰ ਅੱਪਡੇਟ ਕਰਨ ਦੀ ਸਲਾਹ ਦਿੱਤੀ ਜਾਂਦੀ ਹੈ।",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnels",
+ "message": "Tunnels",
+ "translation": "ਟਨਲਾਂ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Edit",
+ "message": "&Edit",
+ "translation": "ਸੋਧੋ(&E)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add &empty tunnel…",
+ "message": "Add &empty tunnel…",
+ "translation": "…ਖਾਲੀ ਟਨਲ ਜੋੜੋ(&e)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add Tunnel",
+ "message": "Add Tunnel",
+ "translation": "ਟਨਲ ਜੋੜੋ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Remove selected tunnel(s)",
+ "message": "Remove selected tunnel(s)",
+ "translation": "ਚੁਣੀਆਂ ਟਨਲਾਂ ਨੂੰ ਹਟਾਓ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to zip",
+ "message": "Export all tunnels to zip",
+ "translation": "ਸਾਰੀਆਂ ਟਨਲਾਂ ਨੂੰ ਜ਼ਿੱਪ ਵਜੋਂ ਬਰਾਮਦ ਕਰੋ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Toggle",
+ "message": "&Toggle",
+ "translation": "ਪਲਟੋ(&T)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to &zip…",
+ "message": "Export all tunnels to &zip…",
+ "translation": "ਸਾਰੀਆਂ ਟਨਲਾਂ ਨੂੰ ਜ਼ਿੱਪ ਵਜੋਂ ਬਰਾਮਦ ਕਰੋ…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit &selected tunnel…",
+ "message": "Edit &selected tunnel…",
+ "translation": "ਚੁਣੀ ਟਨਲ ਸੋਧੋ(&s)…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Remove selected tunnel(s)",
+ "message": "&Remove selected tunnel(s)",
+ "translation": "ਚੁਣੀਆਂ ਟਨਲਾਂ ਨੂੰ ਹਟਾਓ(&R)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "no configuration files were found",
+ "message": "no configuration files were found",
+ "translation": "ਕੋਈ ਸੰਰਚਨਾ ਫ਼ਾਇਲਾਂ ਨਹੀਂ ਲੱਭੀਆਂ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{Name}’",
+ "message": "Another tunnel already exists with the name ‘{Name}’",
+ "translation": "‘{Name}’ ਨਾਂ ਨਾਲ ਹੋਰ ਟਨਲ ਪਹਿਲਾਂ ਹੀ ਮੌਜੂਦ ਹੈ",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "unparsedConfig.Name"
+ }
+ ]
+ },
+ {
+ "id": "Unable to import configuration: {LastErr}",
+ "message": "Unable to import configuration: {LastErr}",
+ "translation": "ਸੰਰਚਨਾ ਇੰਪੋਰਟ ਕਰਨ ਲਈ ਅਸਮਰੱਥ: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Imported tunnels",
+ "message": "Imported tunnels",
+ "translation": "ਇੰਪੋਰਟ ਕੀਤੀਆਂ ਟਨਲਾਂ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Imported {M} tunnels",
+ "message": "Imported {M} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "M",
+ "cases": {
+ "one": {
+ "msg": "{M} ਟਨਲ ਇੰਪੋਰਟ ਕੀਤੀ"
+ },
+ "other": {
+ "msg": "{M} ਟਨਲਾਂ ਇੰਪੋਰਟ ਕੀਤੀਆਂ"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create tunnel",
+ "message": "Unable to create tunnel",
+ "translation": "ਟਨਲ ਬਣਾਉਣ ਲਈ ਅਸਮਰੱਥ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Delete {TunnelCount} tunnels",
+ "message": "Delete {TunnelCount} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "{TunnelCount} ਟਨਲ ਹਟਾਓ"
+ },
+ "other": {
+ "msg": "{TunnelCount} ਟਨਲਾਂ ਹਟਾਓ"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "message": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "ਕੀ ਤੁਸੀਂ {TunnelCount} ਟਨਲ ਨੂੰ ਹਟਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ?"
+ },
+ "other": {
+ "msg": "ਕੀ ਤੁਸੀਂ {TunnelCount} ਟਨਲਾਂ ਨੂੰ ਹਟਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ?"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Delete tunnel ‘{TunnelName}’",
+ "message": "Delete tunnel ‘{TunnelName}’",
+ "translation": "‘{TunnelName}’ ਟਨਲ ਨੂੰ ਹਟਾਓ",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "message": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "translation": "ਕੀ ਤੁਸੀਂ ‘{TunnelName}‘ ਟਨਲ ਨੂੰ ਹਟਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "{Question} You cannot undo this action.",
+ "message": "{Question} You cannot undo this action.",
+ "translation": "{Question} ਤੁਸੀਂ ਇਹ ਕਾਰਵਾਈ ਵਾਪਸ ਨਹੀਂ ਲੈ ਸਕਦੇ ਹੋ।",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Question",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "question"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnel",
+ "message": "Unable to delete tunnel",
+ "translation": "ਟਨਲ ਹਟਾਉਣ ਲਈ ਅਸਮਰੱਥ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A tunnel was unable to be removed: {Error}",
+ "message": "A tunnel was unable to be removed: {Error}",
+ "translation": "ਟਨਲ ਹਟਾਉਣ ਲਈ ਅਸਮਰੱਥ ਹੈ: {Error}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Error",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errors[0].Error()"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnels",
+ "message": "Unable to delete tunnels",
+ "translation": "ਟਨਲਾਂ ਹਟਾਉਣ ਲਈ ਅਸਮਰੱਥ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "message": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "translation": "ਸੰਰਚਨਾ ਫ਼ਾਇਲਾਂ (*.zip, *.conf)|*.zip;*.conf|ਸਾਰੀਆਂ ਫ਼ਾਇਲਾਂ (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Import tunnel(s) from file",
+ "message": "Import tunnel(s) from file",
+ "translation": "ਫ਼ਾਇਲ ਤੋਂ ਟਨਲਾਂ ਦਰਾਮਦ ਕਰੋ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Configuration ZIP Files (*.zip)|*.zip",
+ "message": "Configuration ZIP Files (*.zip)|*.zip",
+ "translation": "ਸੰਰਚਨਾ ਜ਼ਿੱਪ ਫਾਇਲਾਂ (*.zip)|*.zip",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error Exiting WireGuard",
+ "message": "Error Exiting WireGuard",
+ "translation": "WireGuard ਤੋਂ ਬਾਹਰ ਜਾਣ ਲਈ ਗ਼ਲਤੀ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "message": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "translation": "ਸੇਵਾ ਤੋਂ ਬਾਹਰ ਜਾਣ ਲਈ ਅਸਮਰੱਥ, ਕਾਰਨ: {Err}। ਤੁਸੀਂ ਸੇਵਾ ਮੈਨੇਜਰ ਤੋਂ WireGuard ਨੂੰ ਰੋਕਣਾ ਚਾਹੋਗੇ।",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "message": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "translation": "WireGuard ਲਈ ਅੱਪਡੇਟ ਮੌਜੂਦ ਹੈ। ਤੁਹਾਨੂੰ ਬਿਨਾਂ ਦੇਰ ਕੀਤਿਆਂ ਅੱਪਡੇਟ ਕਰਨ ਦੀ ਸਲਾਹ ਦਿੱਤੀ ਜਾਂਦੀ ਹੈ।",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for user",
+ "message": "Status: Waiting for user",
+ "translation": "ਹਾਲਤ: ਵਰਤੋਂਕਾਰ ਲਈ ਉਡੀਕ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Update Now",
+ "message": "Update Now",
+ "translation": "ਹੁਣੇ ਅੱਪਡੇਟ ਕਰੋ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for updater service",
+ "message": "Status: Waiting for updater service",
+ "translation": "ਹਾਲਤ: ਅੱਪਡੇਟਰ ਸੇਵਾ ਦੀ ਉਡੀਕ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error: {Err}. Please try again.",
+ "message": "Error: {Err}. Please try again.",
+ "translation": "ਗ਼ਲਤੀ: {Err}। ਫੇਰ ਕੋਸ਼ਿਸ਼ ਕਰੋ।",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Status: Complete!",
+ "message": "Status: Complete!",
+ "translation": "ਸਥਿਤੀ: ਪੂਰਾ!",
+ "translatorComment": "Copied from source."
+ }
+ ]
+} \ No newline at end of file
diff --git a/locales/pl/messages.gotext.json b/locales/pl/messages.gotext.json
new file mode 100644
index 00000000..e702e1ca
--- /dev/null
+++ b/locales/pl/messages.gotext.json
@@ -0,0 +1,1907 @@
+{
+ "language": "pl",
+ "messages": [
+ {
+ "id": "Error",
+ "message": "Error",
+ "translation": "Błąd",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(no argument): elevate and install manager service",
+ "message": "(no argument): elevate and install manager service",
+ "translation": "(brak argumentu): Podnieś uprawnienia i zainstaluj usługę menedżera",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Usage: {Args0} [\n{String}]",
+ "message": "Usage: {Args0} [\n{String}]",
+ "translation": "Użycie: {Args0} [\n{String}]",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Args0",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "os.Args[0]"
+ },
+ {
+ "id": "String",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "builder.String()"
+ }
+ ]
+ },
+ {
+ "id": "Command Line Options",
+ "message": "Command Line Options",
+ "translation": "Opcje wiersza poleceń",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to determine whether the process is running under WOW64: {Err}",
+ "message": "Unable to determine whether the process is running under WOW64: {Err}",
+ "translation": "Nie można określić, czy proces jest uruchomiony w środowisku WOW64: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "You must use the native version of WireGuard on this computer.",
+ "message": "You must use the native version of WireGuard on this computer.",
+ "translation": "Należy użyć natywnej wersji WireGuard na tym komputerze.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to open current process token: {Err}",
+ "message": "Unable to open current process token: {Err}",
+ "translation": "Nie można otworzyć bieżącego tokenu procesu: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard może być używany tylko przez użytkowników, którzy są członkami wbudowanej grupy {AdminGroupName}.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard jest uruchomiony, ale interfejs jest dostępny tylko z poziomu użytkowników należących do wbudowanej grupy {AdminGroupName}.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard system tray icon did not appear after 30 seconds.",
+ "message": "WireGuard system tray icon did not appear after 30 seconds.",
+ "translation": "Ikona WireGuard nie pojawiła się po 30 sekundach w zasobniku systemowym.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Now",
+ "message": "Now",
+ "translation": "Teraz",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "System clock wound backward!",
+ "message": "System clock wound backward!",
+ "translation": "Zegar systemowy został cofnięty!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Years} year(s)",
+ "message": "{Years} year(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Years",
+ "cases": {
+ "one": {
+ "msg": "{Years} rok"
+ },
+ "few": {
+ "msg": "{Years} lata"
+ },
+ "many": {
+ "msg": "{Years} lat"
+ },
+ "other": {
+ "msg": "{Years} lat"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Years",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "years"
+ }
+ ]
+ },
+ {
+ "id": "{Days} day(s)",
+ "message": "{Days} day(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Days",
+ "cases": {
+ "one": {
+ "msg": "{Days} dzień"
+ },
+ "few": {
+ "msg": "{Days} dni"
+ },
+ "many": {
+ "msg": "{Days} dni"
+ },
+ "other": {
+ "msg": "{Days} dni"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Days",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "days"
+ }
+ ]
+ },
+ {
+ "id": "{Hours} hour(s)",
+ "message": "{Hours} hour(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Hours",
+ "cases": {
+ "one": {
+ "msg": "{Hours} godzina"
+ },
+ "few": {
+ "msg": "{Hours} godziny"
+ },
+ "many": {
+ "msg": "{Hours} godzin"
+ },
+ "other": {
+ "msg": "{Hours} godzin"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Hours",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "hours"
+ }
+ ]
+ },
+ {
+ "id": "{Minutes} minute(s)",
+ "message": "{Minutes} minute(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Minutes",
+ "cases": {
+ "one": {
+ "msg": "{Minutes} minuta"
+ },
+ "few": {
+ "msg": "{Minutes} minuty"
+ },
+ "many": {
+ "msg": "{Minutes} minut"
+ },
+ "other": {
+ "msg": "{Minutes} minut"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Minutes",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "minutes"
+ }
+ ]
+ },
+ {
+ "id": "{Seconds} second(s)",
+ "message": "{Seconds} second(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Seconds",
+ "cases": {
+ "one": {
+ "msg": "{Seconds} sekunda"
+ },
+ "few": {
+ "msg": "{Seconds} sekundy"
+ },
+ "many": {
+ "msg": "{Seconds} sekund"
+ },
+ "other": {
+ "msg": "{Seconds} sekund"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Seconds",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "seconds"
+ }
+ ]
+ },
+ {
+ "id": "{Timestamp} ago",
+ "message": "{Timestamp} ago",
+ "translation": "{Timestamp} temu",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Timestamp",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "timestamp"
+ }
+ ]
+ },
+ {
+ "id": "{Bytes} B",
+ "message": "{Bytes} B",
+ "translation": "{Bytes} B",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Bytes",
+ "string": "%[1]d",
+ "type": "golang.zx2c4.com/wireguard/windows/conf.Bytes",
+ "underlyingType": "uint64",
+ "argNum": 1,
+ "expr": "b"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024} KiB",
+ "message": "{Float64b__1024} KiB",
+ "translation": "{Float64b__1024} KiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024} MiB",
+ "message": "{Float64b__1024__1024} MiB",
+ "translation": "{Float64b__1024__1024} MiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024} GiB",
+ "message": "{Float64b__1024__1024__1024} GiB",
+ "translation": "{Float64b__1024__1024__1024} GiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024__1024} TiB",
+ "message": "{Float64b__1024__1024__1024__1024} TiB",
+ "translation": "{Float64b__1024__1024__1024__1024} TiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Why}: {Offender}",
+ "message": "{Why}: {Offender}",
+ "translation": "{Why}: {Offender}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Why",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "e.why"
+ },
+ {
+ "id": "Offender",
+ "string": "%[2]q",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "e.offender"
+ }
+ ]
+ },
+ {
+ "id": "Invalid IP address",
+ "message": "Invalid IP address",
+ "translation": "Nieprawidłowy adres IP",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid network prefix length",
+ "message": "Invalid network prefix length",
+ "translation": "Nieprawidłowa długość prefiksu sieci",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Missing port from endpoint",
+ "message": "Missing port from endpoint",
+ "translation": "Brak portu urządzenia końcowego",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid endpoint host",
+ "message": "Invalid endpoint host",
+ "translation": "Nieprawidłowy host (urządzenie końcowe)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Brackets must contain an IPv6 address",
+ "message": "Brackets must contain an IPv6 address",
+ "translation": "Nawiasy muszą zawierać adres IPv6",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid MTU",
+ "message": "Invalid MTU",
+ "translation": "Nieprawidłowe MTU",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid port",
+ "message": "Invalid port",
+ "translation": "Nieprawidłowy port",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid persistent keepalive",
+ "message": "Invalid persistent keepalive",
+ "translation": "Nieprawidłowy parametr utrzymania połączenia",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key: {Err}",
+ "message": "Invalid key: {Err}",
+ "translation": "Nieprawidłowy klucz: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Keys must decode to exactly 32 bytes",
+ "message": "Keys must decode to exactly 32 bytes",
+ "translation": "Klucze muszą zostać zdekodowane do dokładnie 32 bajtów",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Number must be a number between 0 and 2^64-1: {Err}",
+ "message": "Number must be a number between 0 and 2^64-1: {Err}",
+ "translation": "Liczba musi zawierać się w przedziale 0 - 2^64-1: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Two commas in a row",
+ "message": "Two commas in a row",
+ "translation": "Dwa przecinki z rzędu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name is not valid",
+ "message": "Tunnel name is not valid",
+ "translation": "Nazwa tunelu jest nieprawidłowa",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Line must occur in a section",
+ "message": "Line must occur in a section",
+ "translation": "Linia musi występować w sekcji",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Config key is missing an equals separator",
+ "message": "Config key is missing an equals separator",
+ "translation": "Klucz konfiguracyjny nie zawiera separatora równorzędnego",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Key must have a value",
+ "message": "Key must have a value",
+ "translation": "Klucz musi mieć wartość",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Interface] section",
+ "message": "Invalid key for [Interface] section",
+ "translation": "Nieprawidłowy klucz dla sekcji [Interface]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Peer] section",
+ "message": "Invalid key for [Peer] section",
+ "translation": "Nieprawidłowy klucz dla sekcji [Peer]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An interface must have a private key",
+ "message": "An interface must have a private key",
+ "translation": "Interfejs musi mieć klucz prywatny",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[none specified]",
+ "message": "[none specified]",
+ "translation": "[nie określono]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "All peers must have public keys",
+ "message": "All peers must have public keys",
+ "translation": "Wszyscy uczestnicy muszą mieć klucze publiczne",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error in getting configuration",
+ "message": "Error in getting configuration",
+ "translation": "Błąd podczas pobierania konfiguracji",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for interface section",
+ "message": "Invalid key for interface section",
+ "translation": "Nieprawidłowy klucz dla sekcji interface",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Protocol version must be 1",
+ "message": "Protocol version must be 1",
+ "translation": "Wersja protokołu musi być 1",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for peer section",
+ "message": "Invalid key for peer section",
+ "translation": "Nieprawidłowy klucz dla sekcji peer",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[EnumerationSeparator]",
+ "message": "[EnumerationSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert between items when listing - most western languages will translate ‘[EnumerationSeparator]’ into ‘, ’ to produce lists like ‘apple, orange, strawberry’. Eastern languages might translate into ‘、’ to produce lists like ‘リンゴ、オレンジ、イチゴ’."
+ },
+ {
+ "id": "[UnitSeparator]",
+ "message": "[UnitSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert when combining units of a measure - most languages will translate ‘[UnitSeparator]’ into ‘ ’ (space) to produce lists like ‘2 minuti 30 sekund’, or empty string ‘’ to produce ‘2分30秒’."
+ },
+ {
+ "id": "About WireGuard",
+ "message": "About WireGuard",
+ "translation": "Informacje o WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard logo image",
+ "message": "WireGuard logo image",
+ "translation": "Logo WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "message": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "translation": "Wersja aplikacji: {Number}\nWersja implementacji w jęz. Go: {WireGuardGoVersion}\nWersja jęz. Go: {Version_go}-{GOARCH}\nSystem operacyjny: {OsName}\nArchitektura: {NativeArch}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Number",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "version.Number"
+ },
+ {
+ "id": "WireGuardGoVersion",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "device.WireGuardGoVersion"
+ },
+ {
+ "id": "Version_go",
+ "string": "%[3]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 3,
+ "expr": "strings.TrimPrefix(runtime.Version(), \"go\")"
+ },
+ {
+ "id": "GOARCH",
+ "string": "%[4]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 4,
+ "expr": "runtime.GOARCH"
+ },
+ {
+ "id": "OsName",
+ "string": "%[5]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 5,
+ "expr": "version.OsName()"
+ },
+ {
+ "id": "NativeArch",
+ "string": "%[6]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 6,
+ "expr": "version.NativeArch()"
+ }
+ ]
+ },
+ {
+ "id": "Close",
+ "message": "Close",
+ "translation": "Zamknij",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "♥ &Donate!",
+ "message": "♥ &Donate!",
+ "translation": "♥ &Wpłać!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status:",
+ "message": "Status:",
+ "translation": "Status:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Deactivate",
+ "message": "&Deactivate",
+ "translation": "&Dezaktywuj",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Activate",
+ "message": "&Activate",
+ "translation": "&Aktywuj",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Public key:",
+ "message": "Public key:",
+ "translation": "Klucz publiczny:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Listen port:",
+ "message": "Listen port:",
+ "translation": "Port nasłuchu:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "MTU:",
+ "message": "MTU:",
+ "translation": "MTU:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses:",
+ "message": "Addresses:",
+ "translation": "Adresy:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "DNS servers:",
+ "message": "DNS servers:",
+ "translation": "Serwery DNS:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Scripts:",
+ "message": "Scripts:",
+ "translation": "Skrypty:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Preshared key:",
+ "message": "Preshared key:",
+ "translation": "PSK:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Allowed IPs:",
+ "message": "Allowed IPs:",
+ "translation": "Dozwolone adresy IP:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Endpoint:",
+ "message": "Endpoint:",
+ "translation": "Urządzenie końcowe:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Persistent keepalive:",
+ "message": "Persistent keepalive:",
+ "translation": "Utrzymanie połączenia:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Latest handshake:",
+ "message": "Latest handshake:",
+ "translation": "Ostatni uścisk dłoni (handshake):",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Transfer:",
+ "message": "Transfer:",
+ "translation": "Transfer:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-up",
+ "message": "pre-up",
+ "translation": "przed włączeniem",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-up",
+ "message": "post-up",
+ "translation": "po włączeniu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-down",
+ "message": "pre-down",
+ "translation": "przed wyłączeniem",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-down",
+ "message": "post-down",
+ "translation": "po wyłączeniu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "disabled, per policy",
+ "message": "disabled, per policy",
+ "translation": "wyłączone, według zasad grupy",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "enabled",
+ "message": "enabled",
+ "translation": "włączyć",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{String} received, {String_1} sent",
+ "message": "{String} received, {String_1} sent",
+ "translation": "{String} odebrano, {String_1} wysłano",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "String",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "c.RxBytes.String()"
+ },
+ {
+ "id": "String_1",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "c.TxBytes.String()"
+ }
+ ]
+ },
+ {
+ "id": "Failed to determine tunnel state",
+ "message": "Failed to determine tunnel state",
+ "translation": "Nie udało się określić stanu tunelu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to activate tunnel",
+ "message": "Failed to activate tunnel",
+ "translation": "Nie udało się aktywować tunelu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to deactivate tunnel",
+ "message": "Failed to deactivate tunnel",
+ "translation": "Nie można dezaktywować tunelu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Interface: {Name}",
+ "message": "Interface: {Name}",
+ "translation": "Interfejs: {Name}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "config.Name"
+ }
+ ]
+ },
+ {
+ "id": "Peer",
+ "message": "Peer",
+ "translation": "Peer",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Create new tunnel",
+ "message": "Create new tunnel",
+ "translation": "Utwórz nowy tunel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit tunnel",
+ "message": "Edit tunnel",
+ "translation": "Edytuj tunel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Name:",
+ "message": "&Name:",
+ "translation": "&Nazwa:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Public key:",
+ "message": "&Public key:",
+ "translation": "&Klucz publiczny:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(unknown)",
+ "message": "(unknown)",
+ "translation": "(nieznany)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Block untunneled traffic (kill-switch)",
+ "message": "&Block untunneled traffic (kill-switch)",
+ "translation": "Zablokuj niezabezpieczony ruch (wyłącznik awaryjny)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.",
+ "message": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.",
+ "translation": "Jeżeli konfiguracja ma dokładnie jednego peer'a i ma w liście dozwolonych IP przynajmniej jeden adres 0.0.0.0/0 lub ::/0, wtedy usługa tunelowania dołącza do zapory sieciowej zestaw zasad, żeby zablokować ruch, który nie jest z interfejsu sieciowego lub jest skierowany do złego serwera DNS, z wyjątkiem dla serwera DHCP i NDP.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save",
+ "message": "&Save",
+ "translation": "&Zapisz",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Cancel",
+ "message": "Cancel",
+ "translation": "Anuluj",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Configuration:",
+ "message": "&Configuration:",
+ "translation": "&Konfiguracja:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid name",
+ "message": "Invalid name",
+ "translation": "Nieprawidłowa nazwa",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A name is required.",
+ "message": "A name is required.",
+ "translation": "Nazwa jest wymagana.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name ‘{NewName}’ is invalid.",
+ "message": "Tunnel name ‘{NewName}’ is invalid.",
+ "translation": "Nazwa tunelu ‘{NewName}’ jest niepoprawna.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to list existing tunnels",
+ "message": "Unable to list existing tunnels",
+ "translation": "Nie można wylistować istniejących tuneli",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel already exists",
+ "message": "Tunnel already exists",
+ "translation": "Tunel już istnieje",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{NewName}’.",
+ "message": "Another tunnel already exists with the name ‘{NewName}’.",
+ "translation": "Inny tunel już istnieje z tą samą nazwą ‘{NewName}’.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create new configuration",
+ "message": "Unable to create new configuration",
+ "translation": "Nie można utworzyć nowej konfiguracji",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Writing file failed",
+ "message": "Writing file failed",
+ "translation": "Zapis pliku się nie powiódł",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "message": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "translation": "Plik ‘{FilePath}’ już istnieje. Czy chcesz go nadpisać?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "FilePath",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "filePath"
+ }
+ ]
+ },
+ {
+ "id": "Active",
+ "message": "Active",
+ "translation": "Aktywny",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Activating",
+ "message": "Activating",
+ "translation": "Aktywowanie",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Inactive",
+ "message": "Inactive",
+ "translation": "Nieaktywny",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Deactivating",
+ "message": "Deactivating",
+ "translation": "Dezaktywowanie",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unknown state",
+ "message": "Unknown state",
+ "translation": "Stan nieznany",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log",
+ "message": "Log",
+ "translation": "Dziennik",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Copy",
+ "message": "&Copy",
+ "translation": "&Kopiuj",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Select &all",
+ "message": "Select &all",
+ "translation": "Wybierz &wszystko",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save to file…",
+ "message": "&Save to file…",
+ "translation": "&Zapisz do pliku…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Time",
+ "message": "Time",
+ "translation": "Czas",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log message",
+ "message": "Log message",
+ "translation": "Wiadomości dziennika",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "message": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "translation": "Pliki tekstowe (*.txt)|*.txt|Wszystkie pliki (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export log to file",
+ "message": "Export log to file",
+ "translation": "Eksportuj dziennik do pliku",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&About WireGuard…",
+ "message": "&About WireGuard…",
+ "translation": "&Informacje o WireGuard…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel Error",
+ "message": "Tunnel Error",
+ "translation": "Błąd tunelu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "message": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "translation": "{ErrMsg}\n\nAby uzyskać więcej informacji, zapoznaj się z logiem.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "ErrMsg",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errMsg"
+ }
+ ]
+ },
+ {
+ "id": "{Title} (out of date)",
+ "message": "{Title} (out of date)",
+ "translation": "{Title} (nieaktualny)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Detection Error",
+ "message": "WireGuard Detection Error",
+ "translation": "Błąd detekcji WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to wait for WireGuard window to appear: {Err}",
+ "message": "Unable to wait for WireGuard window to appear: {Err}",
+ "translation": "Nie można poczekać na pojawienie się okna WireGuard: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard: Deactivated",
+ "message": "WireGuard: Deactivated",
+ "translation": "WireGuard: Dezaktywowany",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Unknown",
+ "message": "Status: Unknown",
+ "translation": "Status: Nieznany",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses: None",
+ "message": "Addresses: None",
+ "translation": "Adresy: Brak",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Manage tunnels…",
+ "message": "&Manage tunnels…",
+ "translation": "&Zarządzaj tunelami…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Import tunnel(s) from file…",
+ "message": "&Import tunnel(s) from file…",
+ "translation": "&Importuj tunel (tunele) z pliku…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "E&xit",
+ "message": "E&xit",
+ "translation": "W&yjście",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Tunnels",
+ "message": "&Tunnels",
+ "translation": "&Tunele",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Activated",
+ "message": "WireGuard Activated",
+ "translation": "WireGuard Aktywny",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been activated.",
+ "message": "The {Name} tunnel has been activated.",
+ "translation": "Tunel {Name} został aktywowany.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Deactivated",
+ "message": "WireGuard Deactivated",
+ "translation": "WireGuard Dezaktywowany",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been deactivated.",
+ "message": "The {Name} tunnel has been deactivated.",
+ "translation": "Tunel {Name} został dezaktywowany.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Tunnel Error",
+ "message": "WireGuard Tunnel Error",
+ "translation": "Błąd tunelu WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard: {TextForStateglobalState_true}",
+ "message": "WireGuard: {TextForStateglobalState_true}",
+ "translation": "WireGuard: {TextForStateglobalState_true}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TextForStateglobalState_true",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "textForState(globalState, true)"
+ }
+ ]
+ },
+ {
+ "id": "Status: {StateText}",
+ "message": "Status: {StateText}",
+ "translation": "Status: {StateText}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "StateText",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "stateText"
+ }
+ ]
+ },
+ {
+ "id": "Addresses: {EnumerationSeparator}",
+ "message": "Addresses: {EnumerationSeparator}",
+ "translation": "Adresy: {EnumerationSeparator}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "EnumerationSeparator",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "strings.Join(addrs, l18n.EnumerationSeparator())"
+ }
+ ]
+ },
+ {
+ "id": "An Update is Available!",
+ "message": "An Update is Available!",
+ "translation": "Dostępna nowa aktualizacja!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Update Available",
+ "message": "WireGuard Update Available",
+ "translation": "Aktualizacja WireGuard jest dostępna",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "message": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "translation": "Aktualizacja WireGuard jest już dostępna. Zaleca się jak najszybszą aktualizację.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnels",
+ "message": "Tunnels",
+ "translation": "Tunele",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Edit",
+ "message": "&Edit",
+ "translation": "&Edytuj",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add &empty tunnel…",
+ "message": "Add &empty tunnel…",
+ "translation": "Dodaj &pusty tunel…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add Tunnel",
+ "message": "Add Tunnel",
+ "translation": "Dodaj Tunel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Remove selected tunnel(s)",
+ "message": "Remove selected tunnel(s)",
+ "translation": "Usuń wybrany tunel (tunele)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to zip",
+ "message": "Export all tunnels to zip",
+ "translation": "Eksportuj wszystkie tunele do archiwum ZIP",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Toggle",
+ "message": "&Toggle",
+ "translation": "&Przełącz",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to &zip…",
+ "message": "Export all tunnels to &zip…",
+ "translation": "Eksportuj wszystkie tunele do archiwum &zip…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit &selected tunnel…",
+ "message": "Edit &selected tunnel…",
+ "translation": "Edytuj &wybrany tunel…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Remove selected tunnel(s)",
+ "message": "&Remove selected tunnel(s)",
+ "translation": "&Usuń wybrany tunel (tunele)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "no configuration files were found",
+ "message": "no configuration files were found",
+ "translation": "brak plików konfiguracyjnych",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Could not import selected configuration: {LastErr}",
+ "message": "Could not import selected configuration: {LastErr}",
+ "translation": "Nie można zaimportować wybranej konfiguracji: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Could not enumerate existing tunnels: {LastErr}",
+ "message": "Could not enumerate existing tunnels: {LastErr}",
+ "translation": "Nie można wskazać istniejących tuneli: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{Name}’",
+ "message": "Another tunnel already exists with the name ‘{Name}’",
+ "translation": "Inny tunel już istnieje z tą samą nazwą ‘{Name}’",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "unparsedConfig.Name"
+ }
+ ]
+ },
+ {
+ "id": "Unable to import configuration: {LastErr}",
+ "message": "Unable to import configuration: {LastErr}",
+ "translation": "Nie można zaimportować konfiguracji: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Imported tunnels",
+ "message": "Imported tunnels",
+ "translation": "Zaimportowane tunele",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Imported {M} tunnels",
+ "message": "Imported {M} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "M",
+ "cases": {
+ "one": {
+ "msg": "Zaimportowano {M} tunel"
+ },
+ "few": {
+ "msg": "Zaimportowano {M} tunele"
+ },
+ "many": {
+ "msg": "Zaimportowano {M} tuneli"
+ },
+ "other": {
+ "msg": "Zaimportowano {M} tuneli"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ }
+ ]
+ },
+ {
+ "id": "Imported {M} of {N} tunnels",
+ "message": "Imported {M} of {N} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "N",
+ "cases": {
+ "one": {
+ "msg": "Zaimportowano {M} z {N} tunel"
+ },
+ "few": {
+ "msg": "Zaimportowano {M} z {N} tunele"
+ },
+ "many": {
+ "msg": "Zaimportowano {M} z {N} tuneli"
+ },
+ "other": {
+ "msg": "Zaimportowano {M} z {N} tuneli"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ },
+ {
+ "id": "N",
+ "string": "%[2]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 2,
+ "expr": "n"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create tunnel",
+ "message": "Unable to create tunnel",
+ "translation": "Nie można utworzyć tunelu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Delete {TunnelCount} tunnels",
+ "message": "Delete {TunnelCount} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "Usuń {TunnelCount} tunel"
+ },
+ "few": {
+ "msg": "Usuń {TunnelCount} tunele"
+ },
+ "many": {
+ "msg": "Usuń {TunnelCount} tuneli"
+ },
+ "other": {
+ "msg": "Usuń {TunnelCount} tuneli"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "message": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "Czy na pewno chcesz usunąć {TunnelCount} tunel?"
+ },
+ "few": {
+ "msg": "Czy na pewno chcesz usunąć {TunnelCount} tunele?"
+ },
+ "many": {
+ "msg": "Czy na pewno chcesz usunąć {TunnelCount} tuneli?"
+ },
+ "other": {
+ "msg": "Czy na pewno chcesz usunąć {TunnelCount} tuneli?"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Delete tunnel ‘{TunnelName}’",
+ "message": "Delete tunnel ‘{TunnelName}’",
+ "translation": "Usuń tunel ‘{TunnelName}’",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "message": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "translation": "Czy na pewno chcesz usunąć tunel ‘{TunnelName}’?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "{Question} You cannot undo this action.",
+ "message": "{Question} You cannot undo this action.",
+ "translation": "{Question} Tej akcji nie można cofnąć.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Question",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "question"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnel",
+ "message": "Unable to delete tunnel",
+ "translation": "Nie można usunąć tunelu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A tunnel was unable to be removed: {Error}",
+ "message": "A tunnel was unable to be removed: {Error}",
+ "translation": "Tunel nie mógł zostać usunięty: {Error}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Error",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errors[0].Error()"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnels",
+ "message": "Unable to delete tunnels",
+ "translation": "Nie można usunąć tuneli",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Lenerrors} tunnels were unable to be removed.",
+ "message": "{Lenerrors} tunnels were unable to be removed.",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Lenerrors",
+ "cases": {
+ "one": {
+ "msg": "{Lenerrors} tunel nie może zostać usunięty."
+ },
+ "few": {
+ "msg": "{Lenerrors} tunele nie mogą być usunięte."
+ },
+ "many": {
+ "msg": "{Lenerrors} tunele nie mogą być usunięte."
+ },
+ "other": {
+ "msg": "{Lenerrors} tunele nie mogą być usunięte."
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Lenerrors",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "len(errors)"
+ }
+ ]
+ },
+ {
+ "id": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "message": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "translation": "Pliki konfiguracji (*.zip, *.conf)|*.zip;*.conf|Wszystkie pliki (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Import tunnel(s) from file",
+ "message": "Import tunnel(s) from file",
+ "translation": "Importuj tunel (tunele) z pliku",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Configuration ZIP Files (*.zip)|*.zip",
+ "message": "Configuration ZIP Files (*.zip)|*.zip",
+ "translation": "Pliki ZIP konfiguracji (*.zip)|*.zip",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export tunnels to zip",
+ "message": "Export tunnels to zip",
+ "translation": "Eksportuj tunele do archiwum ZIP",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Title} (unsigned build, no updates)",
+ "message": "{Title} (unsigned build, no updates)",
+ "translation": "{Title} (wersja niepodpisana, brak aktualizacji)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "Error Exiting WireGuard",
+ "message": "Error Exiting WireGuard",
+ "translation": "Błąd podczas zamykania WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "message": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "translation": "Nie można wyłączyć usługi ze względu na: {Err}. Jeśli chcesz wyłączyć WireGuard możesz to zrobić z poziomu menedżera usług.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "message": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "translation": "Aktualizacja WireGuard jest dostępna. Zaleca się natychmiastową aktualizację.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for user",
+ "message": "Status: Waiting for user",
+ "translation": "Status: Czekam na użytkownika",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Update Now",
+ "message": "Update Now",
+ "translation": "Uaktualnij teraz",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for updater service",
+ "message": "Status: Waiting for updater service",
+ "translation": "Status: Czekam na usługę aktualizacji",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error: {Err}. Please try again.",
+ "message": "Error: {Err}. Please try again.",
+ "translation": "Błąd: {Err}. Spróbuj ponownie.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Status: Complete!",
+ "message": "Status: Complete!",
+ "translation": "Status: Ukończone!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "http2: Framer {F}: failed to decode just-written frame",
+ "message": "http2: Framer {F}: failed to decode just-written frame",
+ "translation": "http2: Framer {F}: nie mógł zdekodować właśnie zapisanej ramki",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ }
+ ]
+ },
+ {
+ "id": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "message": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "translation": "http2: Framer {F}: zapis {Http2summarizeFramefr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ },
+ {
+ "id": "Http2summarizeFramefr",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(fr)"
+ }
+ ]
+ },
+ {
+ "id": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "message": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "translation": "http2: Framer {Fr}: odczyt {Http2summarizeFramef}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Fr",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "fr"
+ },
+ {
+ "id": "Http2summarizeFramef",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(f)"
+ }
+ ]
+ },
+ {
+ "id": "http2: decoded hpack field {HeaderField}",
+ "message": "http2: decoded hpack field {HeaderField}",
+ "translation": "http2: zdekodwanie hpack nie powiodło się {HeaderField}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "HeaderField",
+ "string": "%+[1]v",
+ "type": "vendor/golang.org/x/net/http2/hpack.HeaderField",
+ "underlyingType": "struct{Name string; Value string; Sensitive bool}",
+ "argNum": 1,
+ "expr": "hf"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/locales/ro/messages.gotext.json b/locales/ro/messages.gotext.json
new file mode 100644
index 00000000..56a96a63
--- /dev/null
+++ b/locales/ro/messages.gotext.json
@@ -0,0 +1,1877 @@
+{
+ "language": "ro",
+ "messages": [
+ {
+ "id": "Error",
+ "message": "Error",
+ "translation": "Eroare",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(no argument): elevate and install manager service",
+ "message": "(no argument): elevate and install manager service",
+ "translation": "(fără argument): obținere drept administrativ și instalare serviciu de gestionare",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Usage: {Args0} [\n{String}]",
+ "message": "Usage: {Args0} [\n{String}]",
+ "translation": "Utilizare: {Args0} [\n{String}]",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Args0",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "os.Args[0]"
+ },
+ {
+ "id": "String",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "builder.String()"
+ }
+ ]
+ },
+ {
+ "id": "Command Line Options",
+ "message": "Command Line Options",
+ "translation": "Opțiuni linie de comandă",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to determine whether the process is running under WOW64: {Err}",
+ "message": "Unable to determine whether the process is running under WOW64: {Err}",
+ "translation": "Nu se poate determina dacă procesul rulează sub WOW64: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "You must use the native version of WireGuard on this computer.",
+ "message": "You must use the native version of WireGuard on this computer.",
+ "translation": "Trebuie să utilizezi versiunea nativă a WireGuard pe acest calculator.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to open current process token: {Err}",
+ "message": "Unable to open current process token: {Err}",
+ "translation": "Nu poate fi deschis tokenul actual de proces: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard poate fi utilizat doar de către utilizatorii care sunt membri ai grupului Builtin {AdminGroupName}.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard rulează, dar interfața cu utilizatorul este accesibilă doar din spațiile de lucru ale grupului Builtin {AdminGroupName}.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard system tray icon did not appear after 30 seconds.",
+ "message": "WireGuard system tray icon did not appear after 30 seconds.",
+ "translation": "Pictograma WireGuard din bara de sistem nu a apărut după 30 de secunde.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Now",
+ "message": "Now",
+ "translation": "Acum",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "System clock wound backward!",
+ "message": "System clock wound backward!",
+ "translation": "Ceasul de sistem a fost dat în spate!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Years} year(s)",
+ "message": "{Years} year(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Years",
+ "cases": {
+ "one": {
+ "msg": "{Years} an"
+ },
+ "few": {
+ "msg": "{Years} ani"
+ },
+ "other": {
+ "msg": "{Years} de ani"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Years",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "years"
+ }
+ ]
+ },
+ {
+ "id": "{Days} day(s)",
+ "message": "{Days} day(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Days",
+ "cases": {
+ "one": {
+ "msg": "{Days} zi"
+ },
+ "few": {
+ "msg": "{Days} zile"
+ },
+ "other": {
+ "msg": "{Days} de zile"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Days",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "days"
+ }
+ ]
+ },
+ {
+ "id": "{Hours} hour(s)",
+ "message": "{Hours} hour(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Hours",
+ "cases": {
+ "one": {
+ "msg": "{Hours} oră"
+ },
+ "few": {
+ "msg": "{Hours} ore"
+ },
+ "other": {
+ "msg": "{Hours} de ore"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Hours",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "hours"
+ }
+ ]
+ },
+ {
+ "id": "{Minutes} minute(s)",
+ "message": "{Minutes} minute(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Minutes",
+ "cases": {
+ "one": {
+ "msg": "{Minutes} minut"
+ },
+ "few": {
+ "msg": "{Minutes} minute"
+ },
+ "other": {
+ "msg": "{Minutes} de minute"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Minutes",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "minutes"
+ }
+ ]
+ },
+ {
+ "id": "{Seconds} second(s)",
+ "message": "{Seconds} second(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Seconds",
+ "cases": {
+ "one": {
+ "msg": "{Seconds} secundă"
+ },
+ "few": {
+ "msg": "{Seconds} secunde"
+ },
+ "other": {
+ "msg": "{Seconds} de secunde"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Seconds",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "seconds"
+ }
+ ]
+ },
+ {
+ "id": "{Timestamp} ago",
+ "message": "{Timestamp} ago",
+ "translation": "acum {Timestamp}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Timestamp",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "timestamp"
+ }
+ ]
+ },
+ {
+ "id": "{Bytes} B",
+ "message": "{Bytes} B",
+ "translation": "{Bytes} B",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Bytes",
+ "string": "%[1]d",
+ "type": "golang.zx2c4.com/wireguard/windows/conf.Bytes",
+ "underlyingType": "uint64",
+ "argNum": 1,
+ "expr": "b"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024} KiB",
+ "message": "{Float64b__1024} KiB",
+ "translation": "{Float64b__1024} KiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024} MiB",
+ "message": "{Float64b__1024__1024} MiB",
+ "translation": "{Float64b__1024__1024} MiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024} GiB",
+ "message": "{Float64b__1024__1024__1024} GiB",
+ "translation": "{Float64b__1024__1024__1024} GiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024__1024} TiB",
+ "message": "{Float64b__1024__1024__1024__1024} TiB",
+ "translation": "{Float64b__1024__1024__1024__1024} TiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Why}: {Offender}",
+ "message": "{Why}: {Offender}",
+ "translation": "{Why}: {Offender}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Why",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "e.why"
+ },
+ {
+ "id": "Offender",
+ "string": "%[2]q",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "e.offender"
+ }
+ ]
+ },
+ {
+ "id": "Invalid IP address",
+ "message": "Invalid IP address",
+ "translation": "Adresă IP invalidă",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid network prefix length",
+ "message": "Invalid network prefix length",
+ "translation": "Lungimea prefixului de rețea este invalidă",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Missing port from endpoint",
+ "message": "Missing port from endpoint",
+ "translation": "Lipsește portul de la punctul final",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid endpoint host",
+ "message": "Invalid endpoint host",
+ "translation": "Gazdă invalidă a punctului final",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Brackets must contain an IPv6 address",
+ "message": "Brackets must contain an IPv6 address",
+ "translation": "Parantezele trebuie să conțină o adresă IPv6",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid MTU",
+ "message": "Invalid MTU",
+ "translation": "MTU invalidă",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid port",
+ "message": "Invalid port",
+ "translation": "Port invalid",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid persistent keepalive",
+ "message": "Invalid persistent keepalive",
+ "translation": "Mesaj keepalive persistent invalid",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key: {Err}",
+ "message": "Invalid key: {Err}",
+ "translation": "Cheie invalidă: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Keys must decode to exactly 32 bytes",
+ "message": "Keys must decode to exactly 32 bytes",
+ "translation": "Rezultatul decodificat de chei trebuie să aibă exact 32 de octeți",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Number must be a number between 0 and 2^64-1: {Err}",
+ "message": "Number must be a number between 0 and 2^64-1: {Err}",
+ "translation": "Numărul trebuie să fie cuprins între 0 și 2^64-1: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Two commas in a row",
+ "message": "Two commas in a row",
+ "translation": "Două virgule una după cealaltă",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name is not valid",
+ "message": "Tunnel name is not valid",
+ "translation": "Numele tunelului nu este valid",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Line must occur in a section",
+ "message": "Line must occur in a section",
+ "translation": "Linia trebuie să apară într-o secțiune",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Config key is missing an equals separator",
+ "message": "Config key is missing an equals separator",
+ "translation": "Cheii de configurare îi lipsește un separator de forma semnului egal",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Key must have a value",
+ "message": "Key must have a value",
+ "translation": "Cheia trebuie să conțină o valoare",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Interface] section",
+ "message": "Invalid key for [Interface] section",
+ "translation": "Cheie invalidă pentru secțiunea [Interface]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Peer] section",
+ "message": "Invalid key for [Peer] section",
+ "translation": "Cheie invalidă pentru secțiunea [Peer]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An interface must have a private key",
+ "message": "An interface must have a private key",
+ "translation": "O interfață trebuie să aibă o cheie privată",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[none specified]",
+ "message": "[none specified]",
+ "translation": "[niciuna specificată]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "All peers must have public keys",
+ "message": "All peers must have public keys",
+ "translation": "Toate perechile trebuie să aibă chei publice",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error in getting configuration",
+ "message": "Error in getting configuration",
+ "translation": "Eroare la obținerea configurației",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for interface section",
+ "message": "Invalid key for interface section",
+ "translation": "Cheie invalidă pentru secțiunea interfeței",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Protocol version must be 1",
+ "message": "Protocol version must be 1",
+ "translation": "Versiunea de protocol trebuie să fie 1",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for peer section",
+ "message": "Invalid key for peer section",
+ "translation": "Cheie invalidă pentru secțiunea perechii",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[EnumerationSeparator]",
+ "message": "[EnumerationSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert between items when listing - most western languages will translate ‘[EnumerationSeparator]’ into ‘, ’ to produce lists like ‘apple, orange, strawberry’. Eastern languages might translate into ‘、’ to produce lists like ‘リンゴ、オレンジ、イチゴ’."
+ },
+ {
+ "id": "[UnitSeparator]",
+ "message": "[UnitSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert when combining units of a measure - most languages will translate ‘[UnitSeparator]’ into ‘ ’ (space) to produce lists like ‘2 minuti 30 sekund’, or empty string ‘’ to produce ‘2分30秒’."
+ },
+ {
+ "id": "About WireGuard",
+ "message": "About WireGuard",
+ "translation": "Despre WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard logo image",
+ "message": "WireGuard logo image",
+ "translation": "Imagine siglă WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "message": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "translation": "Versiunea aplicației: {Number}\nVersiunea bibliotecii Go: {WireGuardGoVersion}\nVersiunea Go: {Version_go}-{GOARCH}\nSistem de operare: {OsName}\nArhitectură: {NativeArch}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Number",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "version.Number"
+ },
+ {
+ "id": "WireGuardGoVersion",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "device.WireGuardGoVersion"
+ },
+ {
+ "id": "Version_go",
+ "string": "%[3]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 3,
+ "expr": "strings.TrimPrefix(runtime.Version(), \"go\")"
+ },
+ {
+ "id": "GOARCH",
+ "string": "%[4]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 4,
+ "expr": "runtime.GOARCH"
+ },
+ {
+ "id": "OsName",
+ "string": "%[5]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 5,
+ "expr": "version.OsName()"
+ },
+ {
+ "id": "NativeArch",
+ "string": "%[6]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 6,
+ "expr": "version.NativeArch()"
+ }
+ ]
+ },
+ {
+ "id": "Close",
+ "message": "Close",
+ "translation": "Închidere",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "♥ &Donate!",
+ "message": "♥ &Donate!",
+ "translation": "♥ &Donează!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status:",
+ "message": "Status:",
+ "translation": "Stare:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Deactivate",
+ "message": "&Deactivate",
+ "translation": "&Dezactivare",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Activate",
+ "message": "&Activate",
+ "translation": "&Activare",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Public key:",
+ "message": "Public key:",
+ "translation": "Cheie publică:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Listen port:",
+ "message": "Listen port:",
+ "translation": "Port de ascultare:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "MTU:",
+ "message": "MTU:",
+ "translation": "MTU:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses:",
+ "message": "Addresses:",
+ "translation": "Adrese:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "DNS servers:",
+ "message": "DNS servers:",
+ "translation": "Servere DNS:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Scripts:",
+ "message": "Scripts:",
+ "translation": "Scripturi:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Preshared key:",
+ "message": "Preshared key:",
+ "translation": "Cheie predistribuită:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Allowed IPs:",
+ "message": "Allowed IPs:",
+ "translation": "IP-uri permise:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Endpoint:",
+ "message": "Endpoint:",
+ "translation": "Punct final:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Persistent keepalive:",
+ "message": "Persistent keepalive:",
+ "translation": "Mesaj keepalive persistent:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Latest handshake:",
+ "message": "Latest handshake:",
+ "translation": "Ultimul acord de interogare:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Transfer:",
+ "message": "Transfer:",
+ "translation": "Transferare:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-up",
+ "message": "pre-up",
+ "translation": "pre-pornire",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-up",
+ "message": "post-up",
+ "translation": "post-pornire",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-down",
+ "message": "pre-down",
+ "translation": "pre-oprire",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-down",
+ "message": "post-down",
+ "translation": "post-oprire",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "disabled, per policy",
+ "message": "disabled, per policy",
+ "translation": "dezactivat, conform politicii",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "enabled",
+ "message": "enabled",
+ "translation": "activată",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{String} received, {String_1} sent",
+ "message": "{String} received, {String_1} sent",
+ "translation": "{String} primit, {String_1} trimis",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "String",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "c.RxBytes.String()"
+ },
+ {
+ "id": "String_1",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "c.TxBytes.String()"
+ }
+ ]
+ },
+ {
+ "id": "Failed to determine tunnel state",
+ "message": "Failed to determine tunnel state",
+ "translation": "Nu a putut fi determinată starea tunelului",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to activate tunnel",
+ "message": "Failed to activate tunnel",
+ "translation": "Tunelul nu a putut fi activat",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to deactivate tunnel",
+ "message": "Failed to deactivate tunnel",
+ "translation": "Tunelul nu a putut fi dezactivat",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Interface: {Name}",
+ "message": "Interface: {Name}",
+ "translation": "Interfață: {Name}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "config.Name"
+ }
+ ]
+ },
+ {
+ "id": "Peer",
+ "message": "Peer",
+ "translation": "Pereche",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Create new tunnel",
+ "message": "Create new tunnel",
+ "translation": "Creare tunel nou",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit tunnel",
+ "message": "Edit tunnel",
+ "translation": "Editare tunel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Name:",
+ "message": "&Name:",
+ "translation": "&Nume:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Public key:",
+ "message": "&Public key:",
+ "translation": "Cheie &publică:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(unknown)",
+ "message": "(unknown)",
+ "translation": "(necunoscută)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Block untunneled traffic (kill-switch)",
+ "message": "&Block untunneled traffic (kill-switch)",
+ "translation": "&Blochează traficul care nu trece prin tunel (întrerupător de activitate)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.",
+ "message": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.",
+ "translation": "Când o configurație conține exact o pereche, iar acea pereche are IP-uri permise care conțin cel puțin o variantă dintre 0.0.0.0/0 sau ::/0, atunci serviciul tunelului activează un set de reguli al paravanului de protecție pentru a bloca întregul trafic care nu provine de la interfața tunelului sau care este direcționat către serverul DNS greșit, existând excepții speciale pentru DHCP și NDP.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save",
+ "message": "&Save",
+ "translation": "&Salvare",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Cancel",
+ "message": "Cancel",
+ "translation": "Anulare",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Configuration:",
+ "message": "&Configuration:",
+ "translation": "&Configurație:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid name",
+ "message": "Invalid name",
+ "translation": "Nume invalid",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A name is required.",
+ "message": "A name is required.",
+ "translation": "Este necesar un nume.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name ‘{NewName}’ is invalid.",
+ "message": "Tunnel name ‘{NewName}’ is invalid.",
+ "translation": "Numele tunelului „{NewName}” este invalid.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to list existing tunnels",
+ "message": "Unable to list existing tunnels",
+ "translation": "Tunelurile existente nu pot fi listate",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel already exists",
+ "message": "Tunnel already exists",
+ "translation": "Tunelul există deja",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{NewName}’.",
+ "message": "Another tunnel already exists with the name ‘{NewName}’.",
+ "translation": "Există deja un alt tunel cu numele „{NewName}”.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create new configuration",
+ "message": "Unable to create new configuration",
+ "translation": "Nu se poate crea configurația nouă",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Writing file failed",
+ "message": "Writing file failed",
+ "translation": "Scrierea fișierului a eșuat",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "message": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "translation": "Fișierul „{FilePath}” există deja.\n\nDorești suprascrierea acestuia?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "FilePath",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "filePath"
+ }
+ ]
+ },
+ {
+ "id": "Active",
+ "message": "Active",
+ "translation": "Activ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Activating",
+ "message": "Activating",
+ "translation": "Se activează",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Inactive",
+ "message": "Inactive",
+ "translation": "Inactiv",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Deactivating",
+ "message": "Deactivating",
+ "translation": "Se dezactivează",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unknown state",
+ "message": "Unknown state",
+ "translation": "Stare necunoscută",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log",
+ "message": "Log",
+ "translation": "Jurnal",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Copy",
+ "message": "&Copy",
+ "translation": "&Copiere",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Select &all",
+ "message": "Select &all",
+ "translation": "Selectare &totală",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save to file…",
+ "message": "&Save to file…",
+ "translation": "&Salvare în fișier…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Time",
+ "message": "Time",
+ "translation": "Timp",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log message",
+ "message": "Log message",
+ "translation": "Mesaj de jurnal",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "message": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "translation": "Fișiere text (*.txt)|*.txt|Toate fișierele (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export log to file",
+ "message": "Export log to file",
+ "translation": "Exportare jurnal în fișier",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&About WireGuard…",
+ "message": "&About WireGuard…",
+ "translation": "&Despre WireGuard…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel Error",
+ "message": "Tunnel Error",
+ "translation": "Eroare de tunel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "message": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "translation": "{ErrMsg}\n\nConsultă jurnalul pentru mai multe informații.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "ErrMsg",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errMsg"
+ }
+ ]
+ },
+ {
+ "id": "{Title} (out of date)",
+ "message": "{Title} (out of date)",
+ "translation": "{Title} (neactualizat)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Detection Error",
+ "message": "WireGuard Detection Error",
+ "translation": "Eroare de detectare WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to wait for WireGuard window to appear: {Err}",
+ "message": "Unable to wait for WireGuard window to appear: {Err}",
+ "translation": "Nu se poate aștepta ca fereastra WireGuard să apară: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard: Deactivated",
+ "message": "WireGuard: Deactivated",
+ "translation": "WireGuard: dezactivat",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Unknown",
+ "message": "Status: Unknown",
+ "translation": "Stare: necunoscută",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses: None",
+ "message": "Addresses: None",
+ "translation": "Adrese: niciuna",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Manage tunnels…",
+ "message": "&Manage tunnels…",
+ "translation": "&Gestionare tuneluri…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Import tunnel(s) from file…",
+ "message": "&Import tunnel(s) from file…",
+ "translation": "&Importare tunel(uri) din fișier…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "E&xit",
+ "message": "E&xit",
+ "translation": "Ie&șire",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Tunnels",
+ "message": "&Tunnels",
+ "translation": "&Tuneluri",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Activated",
+ "message": "WireGuard Activated",
+ "translation": "WireGuard activat",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been activated.",
+ "message": "The {Name} tunnel has been activated.",
+ "translation": "Tunelul {Name} a fost activat.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Deactivated",
+ "message": "WireGuard Deactivated",
+ "translation": "WireGuard dezactivat",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been deactivated.",
+ "message": "The {Name} tunnel has been deactivated.",
+ "translation": "Tunelul {Name} a fost dezactivat.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Tunnel Error",
+ "message": "WireGuard Tunnel Error",
+ "translation": "Eroare de tunel WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard: {TextForStateglobalState_true}",
+ "message": "WireGuard: {TextForStateglobalState_true}",
+ "translation": "WireGuard: {TextForStateglobalState_true}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TextForStateglobalState_true",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "textForState(globalState, true)"
+ }
+ ]
+ },
+ {
+ "id": "Status: {StateText}",
+ "message": "Status: {StateText}",
+ "translation": "Stare: {StateText}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "StateText",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "stateText"
+ }
+ ]
+ },
+ {
+ "id": "Addresses: {EnumerationSeparator}",
+ "message": "Addresses: {EnumerationSeparator}",
+ "translation": "Adrese: {EnumerationSeparator}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "EnumerationSeparator",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "strings.Join(addrs, l18n.EnumerationSeparator())"
+ }
+ ]
+ },
+ {
+ "id": "An Update is Available!",
+ "message": "An Update is Available!",
+ "translation": "Este disponibilă o actualizare!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Update Available",
+ "message": "WireGuard Update Available",
+ "translation": "Actualizare disponibilă pentru WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "message": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "translation": "O actualizare pentru WireGuard este acum disponibilă. Se recomandă efectuarea actualizării cât mai rapid posibil.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnels",
+ "message": "Tunnels",
+ "translation": "Tuneluri",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Edit",
+ "message": "&Edit",
+ "translation": "&Editare",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add &empty tunnel…",
+ "message": "Add &empty tunnel…",
+ "translation": "Adăugare tunel &gol…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add Tunnel",
+ "message": "Add Tunnel",
+ "translation": "Adăugare tunel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Remove selected tunnel(s)",
+ "message": "Remove selected tunnel(s)",
+ "translation": "Eliminare tunel(uri) selectat(e)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to zip",
+ "message": "Export all tunnels to zip",
+ "translation": "Exportă toate tunelurile în zip",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Toggle",
+ "message": "&Toggle",
+ "translation": "&Comutare",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to &zip…",
+ "message": "Export all tunnels to &zip…",
+ "translation": "Exportă toate tunelurile în &zip…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit &selected tunnel…",
+ "message": "Edit &selected tunnel…",
+ "translation": "Editare tunel &selectat…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Remove selected tunnel(s)",
+ "message": "&Remove selected tunnel(s)",
+ "translation": "&Eliminare tunel(uri) selectat(e)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "no configuration files were found",
+ "message": "no configuration files were found",
+ "translation": "nu au fost găsite fișiere de configurare",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Could not import selected configuration: {LastErr}",
+ "message": "Could not import selected configuration: {LastErr}",
+ "translation": "Configurația selectată nu a putut fi importată: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Could not enumerate existing tunnels: {LastErr}",
+ "message": "Could not enumerate existing tunnels: {LastErr}",
+ "translation": "Tunelurile existente nu au putut fi enumerate: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{Name}’",
+ "message": "Another tunnel already exists with the name ‘{Name}’",
+ "translation": "Există deja un alt tunel cu numele „{Name}”",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "unparsedConfig.Name"
+ }
+ ]
+ },
+ {
+ "id": "Unable to import configuration: {LastErr}",
+ "message": "Unable to import configuration: {LastErr}",
+ "translation": "Configurația nu poate fi importată: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Imported tunnels",
+ "message": "Imported tunnels",
+ "translation": "Tuneluri importate",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Imported {M} tunnels",
+ "message": "Imported {M} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "M",
+ "cases": {
+ "one": {
+ "msg": "Importat {M} tunel"
+ },
+ "few": {
+ "msg": "Importat {M} tuneluri"
+ },
+ "other": {
+ "msg": "Importat {M} de tuneluri"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ }
+ ]
+ },
+ {
+ "id": "Imported {M} of {N} tunnels",
+ "message": "Imported {M} of {N} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "N",
+ "cases": {
+ "one": {
+ "msg": "Importat {M} din {N} tunel"
+ },
+ "few": {
+ "msg": "Importat {M} din {N} tuneluri"
+ },
+ "other": {
+ "msg": "Importat {M} din {N} de tuneluri"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ },
+ {
+ "id": "N",
+ "string": "%[2]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 2,
+ "expr": "n"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create tunnel",
+ "message": "Unable to create tunnel",
+ "translation": "Tunelul nu poate fi creat",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Delete {TunnelCount} tunnels",
+ "message": "Delete {TunnelCount} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "Ștergere {TunnelCount} tunel"
+ },
+ "few": {
+ "msg": "Ștergere {TunnelCount} tuneluri"
+ },
+ "other": {
+ "msg": "Ștergere {TunnelCount} de tuneluri"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "message": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "Ești sigur că dorești să ștergi {TunnelCount} tunel?"
+ },
+ "few": {
+ "msg": "Ești sigur că dorești să ștergi {TunnelCount} tuneluri?"
+ },
+ "other": {
+ "msg": "Ești sigur că dorești să ștergi {TunnelCount} de tuneluri?"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Delete tunnel ‘{TunnelName}’",
+ "message": "Delete tunnel ‘{TunnelName}’",
+ "translation": "Ștergere tunel „{TunnelName}”",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "message": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "translation": "Ești sigur că dorești să ștergi tunelul „{TunnelName}”?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "{Question} You cannot undo this action.",
+ "message": "{Question} You cannot undo this action.",
+ "translation": "{Question} Această acțiune nu poate fi anulată.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Question",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "question"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnel",
+ "message": "Unable to delete tunnel",
+ "translation": "Tunelul nu poate fi șters",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A tunnel was unable to be removed: {Error}",
+ "message": "A tunnel was unable to be removed: {Error}",
+ "translation": "Un tunel nu a putut fi eliminat: {Error}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Error",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errors[0].Error()"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnels",
+ "message": "Unable to delete tunnels",
+ "translation": "Nu se pot șterge tunelurile",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Lenerrors} tunnels were unable to be removed.",
+ "message": "{Lenerrors} tunnels were unable to be removed.",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Lenerrors",
+ "cases": {
+ "one": {
+ "msg": "{Lenerrors} tunel nu a putut fi eliminat."
+ },
+ "few": {
+ "msg": "{Lenerrors} tuneluri nu au putut fi eliminate."
+ },
+ "other": {
+ "msg": "{Lenerrors} de tuneluri nu au putut fi eliminate."
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Lenerrors",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "len(errors)"
+ }
+ ]
+ },
+ {
+ "id": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "message": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "translation": "Fișiere de configurare (*.zip, *.conf)|*.zip;*.conf|Toate fișierele (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Import tunnel(s) from file",
+ "message": "Import tunnel(s) from file",
+ "translation": "Importare tunel(uri) din fișier",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Configuration ZIP Files (*.zip)|*.zip",
+ "message": "Configuration ZIP Files (*.zip)|*.zip",
+ "translation": "Fișiere ZIP de configurare (*.zip)|*.zip",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export tunnels to zip",
+ "message": "Export tunnels to zip",
+ "translation": "Exportare tuneluri în zip",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Title} (unsigned build, no updates)",
+ "message": "{Title} (unsigned build, no updates)",
+ "translation": "{Title} (versiune nesemnată, fără actualizări)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "Error Exiting WireGuard",
+ "message": "Error Exiting WireGuard",
+ "translation": "Eroare la ieșirea din WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "message": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "translation": "Nu se poate ieși din serviciu din cauza: {Err}. Poți opri WireGuard din managerul de servicii.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "message": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "translation": "Este disponibilă o actualizare pentru WireGuard. Se recomandă ferm actualizarea imediată.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for user",
+ "message": "Status: Waiting for user",
+ "translation": "Stare: se așteaptă utilizatorul",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Update Now",
+ "message": "Update Now",
+ "translation": "Actualizează acum",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for updater service",
+ "message": "Status: Waiting for updater service",
+ "translation": "Stare: se așteaptă serviciul de actualizare",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error: {Err}. Please try again.",
+ "message": "Error: {Err}. Please try again.",
+ "translation": "Eroare: {Err}. Încearcă din nou.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Status: Complete!",
+ "message": "Status: Complete!",
+ "translation": "Stare: finalizată!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "http2: Framer {F}: failed to decode just-written frame",
+ "message": "http2: Framer {F}: failed to decode just-written frame",
+ "translation": "http2: Framer {F}: decodificarea cadrului recent scris a eșuat",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ }
+ ]
+ },
+ {
+ "id": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "message": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "translation": "http2: Framer {F}: a scris {Http2summarizeFramefr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ },
+ {
+ "id": "Http2summarizeFramefr",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(fr)"
+ }
+ ]
+ },
+ {
+ "id": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "message": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "translation": "http2: Framer {Fr}: a citit {Http2summarizeFramef}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Fr",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "fr"
+ },
+ {
+ "id": "Http2summarizeFramef",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(f)"
+ }
+ ]
+ },
+ {
+ "id": "http2: decoded hpack field {HeaderField}",
+ "message": "http2: decoded hpack field {HeaderField}",
+ "translation": "http2: câmp hpack decodificat {HeaderField}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "HeaderField",
+ "string": "%+[1]v",
+ "type": "vendor/golang.org/x/net/http2/hpack.HeaderField",
+ "underlyingType": "struct{Name string; Value string; Sensitive bool}",
+ "argNum": 1,
+ "expr": "hf"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/locales/ru/messages.gotext.json b/locales/ru/messages.gotext.json
new file mode 100644
index 00000000..9cc9b0f1
--- /dev/null
+++ b/locales/ru/messages.gotext.json
@@ -0,0 +1,1907 @@
+{
+ "language": "ru",
+ "messages": [
+ {
+ "id": "Error",
+ "message": "Error",
+ "translation": "Ошибка",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(no argument): elevate and install manager service",
+ "message": "(no argument): elevate and install manager service",
+ "translation": "(нет аргумента): получить права администратора и установить административную службу",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Usage: {Args0} [\n{String}]",
+ "message": "Usage: {Args0} [\n{String}]",
+ "translation": "Использование: {Args0} [\n{String}]",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Args0",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "os.Args[0]"
+ },
+ {
+ "id": "String",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "builder.String()"
+ }
+ ]
+ },
+ {
+ "id": "Command Line Options",
+ "message": "Command Line Options",
+ "translation": "Параметры командной строки",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to determine whether the process is running under WOW64: {Err}",
+ "message": "Unable to determine whether the process is running under WOW64: {Err}",
+ "translation": "Ошибка определения или процесс работает как WOW64: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "You must use the native version of WireGuard on this computer.",
+ "message": "You must use the native version of WireGuard on this computer.",
+ "translation": "Используйте нативную версию WireGuard на этом компьютере.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to open current process token: {Err}",
+ "message": "Unable to open current process token: {Err}",
+ "translation": "Не удается открыть токен текущего процесса: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard может использоваться только пользователями, входящими в группу {AdminGroupName}.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard запущен, но пользовательский интерфейс доступен только с рабочих столов группы {AdminGroupName}.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard system tray icon did not appear after 30 seconds.",
+ "message": "WireGuard system tray icon did not appear after 30 seconds.",
+ "translation": "Значок в системном трее WireGuard не появился после 30 секунд.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Now",
+ "message": "Now",
+ "translation": "Сейчас",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "System clock wound backward!",
+ "message": "System clock wound backward!",
+ "translation": "Системные часы переведены назад!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Years} year(s)",
+ "message": "{Years} year(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Years",
+ "cases": {
+ "one": {
+ "msg": "{Years}г"
+ },
+ "few": {
+ "msg": "{Years}г"
+ },
+ "many": {
+ "msg": "{Years}г"
+ },
+ "other": {
+ "msg": "{Years}г"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Years",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "years"
+ }
+ ]
+ },
+ {
+ "id": "{Days} day(s)",
+ "message": "{Days} day(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Days",
+ "cases": {
+ "one": {
+ "msg": "{Days}д"
+ },
+ "few": {
+ "msg": "{Days}д"
+ },
+ "many": {
+ "msg": "{Days}д"
+ },
+ "other": {
+ "msg": "{Days}д"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Days",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "days"
+ }
+ ]
+ },
+ {
+ "id": "{Hours} hour(s)",
+ "message": "{Hours} hour(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Hours",
+ "cases": {
+ "one": {
+ "msg": "{Hours}ч"
+ },
+ "few": {
+ "msg": "{Hours}ч"
+ },
+ "many": {
+ "msg": "{Hours}ч"
+ },
+ "other": {
+ "msg": "{Hours}ч"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Hours",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "hours"
+ }
+ ]
+ },
+ {
+ "id": "{Minutes} minute(s)",
+ "message": "{Minutes} minute(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Minutes",
+ "cases": {
+ "one": {
+ "msg": "{Minutes}мин"
+ },
+ "few": {
+ "msg": "{Minutes}мин"
+ },
+ "many": {
+ "msg": "{Minutes}мин"
+ },
+ "other": {
+ "msg": "{Minutes}мин"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Minutes",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "minutes"
+ }
+ ]
+ },
+ {
+ "id": "{Seconds} second(s)",
+ "message": "{Seconds} second(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Seconds",
+ "cases": {
+ "one": {
+ "msg": "{Seconds}сек"
+ },
+ "few": {
+ "msg": "{Seconds}сек"
+ },
+ "many": {
+ "msg": "{Seconds}сек"
+ },
+ "other": {
+ "msg": "{Seconds}сек"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Seconds",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "seconds"
+ }
+ ]
+ },
+ {
+ "id": "{Timestamp} ago",
+ "message": "{Timestamp} ago",
+ "translation": "{Timestamp} назад",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Timestamp",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "timestamp"
+ }
+ ]
+ },
+ {
+ "id": "{Bytes} B",
+ "message": "{Bytes} B",
+ "translation": "{Bytes} Б",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Bytes",
+ "string": "%[1]d",
+ "type": "golang.zx2c4.com/wireguard/windows/conf.Bytes",
+ "underlyingType": "uint64",
+ "argNum": 1,
+ "expr": "b"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024} KiB",
+ "message": "{Float64b__1024} KiB",
+ "translation": "{Float64b__1024} Кб",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024} MiB",
+ "message": "{Float64b__1024__1024} MiB",
+ "translation": "{Float64b__1024__1024} Мб",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024} GiB",
+ "message": "{Float64b__1024__1024__1024} GiB",
+ "translation": "{Float64b__1024__1024__1024} Гб",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024__1024} TiB",
+ "message": "{Float64b__1024__1024__1024__1024} TiB",
+ "translation": "{Float64b__1024__1024__1024__1024} Тб",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Why}: {Offender}",
+ "message": "{Why}: {Offender}",
+ "translation": "{Why}: {Offender}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Why",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "e.why"
+ },
+ {
+ "id": "Offender",
+ "string": "%[2]q",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "e.offender"
+ }
+ ]
+ },
+ {
+ "id": "Invalid IP address",
+ "message": "Invalid IP address",
+ "translation": "Недопустимый IP-адрес",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid network prefix length",
+ "message": "Invalid network prefix length",
+ "translation": "Недопустимая длина префикса сети",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Missing port from endpoint",
+ "message": "Missing port from endpoint",
+ "translation": "Отсутствует порт IP-адреса сервера",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid endpoint host",
+ "message": "Invalid endpoint host",
+ "translation": "Неверный IP-адрес сервера",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Brackets must contain an IPv6 address",
+ "message": "Brackets must contain an IPv6 address",
+ "translation": "В скобках должен быть IPv6 адрес",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid MTU",
+ "message": "Invalid MTU",
+ "translation": "Недопустимый MTU",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid port",
+ "message": "Invalid port",
+ "translation": "Недопустимый порт",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid persistent keepalive",
+ "message": "Invalid persistent keepalive",
+ "translation": "Недопустимое значение поддержания соединения",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key: {Err}",
+ "message": "Invalid key: {Err}",
+ "translation": "Недопустимый ключ: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Keys must decode to exactly 32 bytes",
+ "message": "Keys must decode to exactly 32 bytes",
+ "translation": "Ключи должны декодироваться ровно с 32 байтами",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Number must be a number between 0 and 2^64-1: {Err}",
+ "message": "Number must be a number between 0 and 2^64-1: {Err}",
+ "translation": "Число должно быть между 0 и 2^64-1: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Two commas in a row",
+ "message": "Two commas in a row",
+ "translation": "Две запятые подряд",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name is not valid",
+ "message": "Tunnel name is not valid",
+ "translation": "Название туннеля недействительно",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Line must occur in a section",
+ "message": "Line must occur in a section",
+ "translation": "Строка должна быть в секции",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Config key is missing an equals separator",
+ "message": "Config key is missing an equals separator",
+ "translation": "В ключе конфигурации отсутствует разделитель",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Key must have a value",
+ "message": "Key must have a value",
+ "translation": "Ключ должен иметь значение",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Interface] section",
+ "message": "Invalid key for [Interface] section",
+ "translation": "Неверный ключ для секции [Interface]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Peer] section",
+ "message": "Invalid key for [Peer] section",
+ "translation": "Неверный ключ для секции [Peer]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An interface must have a private key",
+ "message": "An interface must have a private key",
+ "translation": "В Интерфейсе должен быть приватный ключ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[none specified]",
+ "message": "[none specified]",
+ "translation": "[не указано]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "All peers must have public keys",
+ "message": "All peers must have public keys",
+ "translation": "Все пиры должны иметь публичные ключи",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error in getting configuration",
+ "message": "Error in getting configuration",
+ "translation": "Ошибка при получении конфигурации",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for interface section",
+ "message": "Invalid key for interface section",
+ "translation": "Неверный ключ для секции Интерфейса",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Protocol version must be 1",
+ "message": "Protocol version must be 1",
+ "translation": "Версия протокола должна быть 1",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for peer section",
+ "message": "Invalid key for peer section",
+ "translation": "Недействительный ключ для секции пира",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[EnumerationSeparator]",
+ "message": "[EnumerationSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert between items when listing - most western languages will translate ‘[EnumerationSeparator]’ into ‘, ’ to produce lists like ‘apple, orange, strawberry’. Eastern languages might translate into ‘、’ to produce lists like ‘リンゴ、オレンジ、イチゴ’."
+ },
+ {
+ "id": "[UnitSeparator]",
+ "message": "[UnitSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert when combining units of a measure - most languages will translate ‘[UnitSeparator]’ into ‘ ’ (space) to produce lists like ‘2 minuti 30 sekund’, or empty string ‘’ to produce ‘2分30秒’."
+ },
+ {
+ "id": "About WireGuard",
+ "message": "About WireGuard",
+ "translation": "О WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard logo image",
+ "message": "WireGuard logo image",
+ "translation": "Логотип WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "message": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "translation": "Версия приложения: {Number}\nВерсия Go-бэкенда: {WireGuardGoVersion}\nВерсия Go: {Version_go}-{GOARCH}\nОперационная система: {OsName}\nАрхитектура: {NativeArch}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Number",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "version.Number"
+ },
+ {
+ "id": "WireGuardGoVersion",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "device.WireGuardGoVersion"
+ },
+ {
+ "id": "Version_go",
+ "string": "%[3]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 3,
+ "expr": "strings.TrimPrefix(runtime.Version(), \"go\")"
+ },
+ {
+ "id": "GOARCH",
+ "string": "%[4]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 4,
+ "expr": "runtime.GOARCH"
+ },
+ {
+ "id": "OsName",
+ "string": "%[5]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 5,
+ "expr": "version.OsName()"
+ },
+ {
+ "id": "NativeArch",
+ "string": "%[6]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 6,
+ "expr": "version.NativeArch()"
+ }
+ ]
+ },
+ {
+ "id": "Close",
+ "message": "Close",
+ "translation": "Закрыть",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "♥ &Donate!",
+ "message": "♥ &Donate!",
+ "translation": "♥ &Пожертвовать!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status:",
+ "message": "Status:",
+ "translation": "Статус:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Deactivate",
+ "message": "&Deactivate",
+ "translation": "&Отключить",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Activate",
+ "message": "&Activate",
+ "translation": "&Подключить",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Public key:",
+ "message": "Public key:",
+ "translation": "Публичный ключ:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Listen port:",
+ "message": "Listen port:",
+ "translation": "Порт:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "MTU:",
+ "message": "MTU:",
+ "translation": "MTU:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses:",
+ "message": "Addresses:",
+ "translation": "IP-адреса:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "DNS servers:",
+ "message": "DNS servers:",
+ "translation": "DNS-серверы:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Scripts:",
+ "message": "Scripts:",
+ "translation": "Скрипты:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Preshared key:",
+ "message": "Preshared key:",
+ "translation": "Общий ключ:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Allowed IPs:",
+ "message": "Allowed IPs:",
+ "translation": "Разрешенные IP-адреса:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Endpoint:",
+ "message": "Endpoint:",
+ "translation": "IP-адрес сервера:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Persistent keepalive:",
+ "message": "Persistent keepalive:",
+ "translation": "Поддерживание соединения:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Latest handshake:",
+ "message": "Latest handshake:",
+ "translation": "Последнее рукопожатие:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Transfer:",
+ "message": "Transfer:",
+ "translation": "Передача:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-up",
+ "message": "pre-up",
+ "translation": "перед подключением",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-up",
+ "message": "post-up",
+ "translation": "после подключения",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-down",
+ "message": "pre-down",
+ "translation": "перед отключением",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-down",
+ "message": "post-down",
+ "translation": "после отключения",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "disabled, per policy",
+ "message": "disabled, per policy",
+ "translation": "отключено, по политике",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "enabled",
+ "message": "enabled",
+ "translation": "включено",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{String} received, {String_1} sent",
+ "message": "{String} received, {String_1} sent",
+ "translation": "Получено {String}, отправлено {String_1}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "String",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "c.RxBytes.String()"
+ },
+ {
+ "id": "String_1",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "c.TxBytes.String()"
+ }
+ ]
+ },
+ {
+ "id": "Failed to determine tunnel state",
+ "message": "Failed to determine tunnel state",
+ "translation": "Не удалось определить состояние туннеля",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to activate tunnel",
+ "message": "Failed to activate tunnel",
+ "translation": "Не удалось подключить туннель",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to deactivate tunnel",
+ "message": "Failed to deactivate tunnel",
+ "translation": "Не удалось отключить туннель",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Interface: {Name}",
+ "message": "Interface: {Name}",
+ "translation": "Интерфейс: {Name}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "config.Name"
+ }
+ ]
+ },
+ {
+ "id": "Peer",
+ "message": "Peer",
+ "translation": "Пир",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Create new tunnel",
+ "message": "Create new tunnel",
+ "translation": "Создать туннель",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit tunnel",
+ "message": "Edit tunnel",
+ "translation": "Редактировать туннель",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Name:",
+ "message": "&Name:",
+ "translation": "&Название:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Public key:",
+ "message": "&Public key:",
+ "translation": "&Публичный ключ:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(unknown)",
+ "message": "(unknown)",
+ "translation": "(неизвестно)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Block untunneled traffic (kill-switch)",
+ "message": "&Block untunneled traffic (kill-switch)",
+ "translation": "&Блокировать нетуннелированный трафик",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.",
+ "message": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.",
+ "translation": "Если конфигурация имеет ровно один пир, а этот пир имеет разрешенные IP, содержащие хотя бы один из 0.0.0.0/0 или ::/0, то туннельный сервис использует набор правил брандмауэра для блокирования всего трафика, который не проходит через туннель или к неверным DNS-серверам, за исключением DHCP и NDP.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save",
+ "message": "&Save",
+ "translation": "&Сохранить",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Cancel",
+ "message": "Cancel",
+ "translation": "Отмена",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Configuration:",
+ "message": "&Configuration:",
+ "translation": "&Конфигурация:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid name",
+ "message": "Invalid name",
+ "translation": "Некорректное название",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A name is required.",
+ "message": "A name is required.",
+ "translation": "Необходимо название.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name ‘{NewName}’ is invalid.",
+ "message": "Tunnel name ‘{NewName}’ is invalid.",
+ "translation": "Название туннеля ‘{NewName}’ недопустимо.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to list existing tunnels",
+ "message": "Unable to list existing tunnels",
+ "translation": "Не удалось отобразить туннели",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel already exists",
+ "message": "Tunnel already exists",
+ "translation": "Туннель уже существует",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{NewName}’.",
+ "message": "Another tunnel already exists with the name ‘{NewName}’.",
+ "translation": "Туннель с именем ’{NewName}’ уже существует.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create new configuration",
+ "message": "Unable to create new configuration",
+ "translation": "Не удалось создать новую конфигурацию",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Writing file failed",
+ "message": "Writing file failed",
+ "translation": "Ошибка записи файла",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "message": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "translation": "Файл '{FilePath}' уже существует!\n\nВы хотите перезаписать его?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "FilePath",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "filePath"
+ }
+ ]
+ },
+ {
+ "id": "Active",
+ "message": "Active",
+ "translation": "Подключен",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Activating",
+ "message": "Activating",
+ "translation": "Подключение",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Inactive",
+ "message": "Inactive",
+ "translation": "Отключен",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Deactivating",
+ "message": "Deactivating",
+ "translation": "Отключение",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unknown state",
+ "message": "Unknown state",
+ "translation": "Неизвестное состояние",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log",
+ "message": "Log",
+ "translation": "Журнал",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Copy",
+ "message": "&Copy",
+ "translation": "&Скопировать",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Select &all",
+ "message": "Select &all",
+ "translation": "Выбрать &все",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save to file…",
+ "message": "&Save to file…",
+ "translation": "&Сохранить в файл…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Time",
+ "message": "Time",
+ "translation": "Время",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log message",
+ "message": "Log message",
+ "translation": "Сообщение журнала",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "message": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "translation": "Текстовые файлы (*.txt)|*.txt|Все файлы (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export log to file",
+ "message": "Export log to file",
+ "translation": "Экспорт журнала в файл",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&About WireGuard…",
+ "message": "&About WireGuard…",
+ "translation": "&О WireGuard…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel Error",
+ "message": "Tunnel Error",
+ "translation": "Ошибка туннеля",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "message": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "translation": "{ErrMsg}\n\nОбратитесь к журналу для получения дополнительной информации.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "ErrMsg",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errMsg"
+ }
+ ]
+ },
+ {
+ "id": "{Title} (out of date)",
+ "message": "{Title} (out of date)",
+ "translation": "{Title} (устарел)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Detection Error",
+ "message": "WireGuard Detection Error",
+ "translation": "Ошибка обнаружения WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to wait for WireGuard window to appear: {Err}",
+ "message": "Unable to wait for WireGuard window to appear: {Err}",
+ "translation": "Не удалось дождаться появления окна WireGuard: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard: Deactivated",
+ "message": "WireGuard: Deactivated",
+ "translation": "WireGuard: Деактивирован",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Unknown",
+ "message": "Status: Unknown",
+ "translation": "Статус: Неизвестен",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses: None",
+ "message": "Addresses: None",
+ "translation": "Адреса: нет",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Manage tunnels…",
+ "message": "&Manage tunnels…",
+ "translation": "&Управление туннелями…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Import tunnel(s) from file…",
+ "message": "&Import tunnel(s) from file…",
+ "translation": "&Импорт туннелей из файла…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "E&xit",
+ "message": "E&xit",
+ "translation": "Вы&ход",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Tunnels",
+ "message": "&Tunnels",
+ "translation": "&Туннели",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Activated",
+ "message": "WireGuard Activated",
+ "translation": "WireGuard Включен",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been activated.",
+ "message": "The {Name} tunnel has been activated.",
+ "translation": "Туннель {Name} подключен.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Deactivated",
+ "message": "WireGuard Deactivated",
+ "translation": "WireGuard Выключен",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been deactivated.",
+ "message": "The {Name} tunnel has been deactivated.",
+ "translation": "Туннель {Name} отключен.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Tunnel Error",
+ "message": "WireGuard Tunnel Error",
+ "translation": "Ошибка туннеля WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard: {TextForStateglobalState_true}",
+ "message": "WireGuard: {TextForStateglobalState_true}",
+ "translation": "WireGuard: {TextForStateglobalState_true}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TextForStateglobalState_true",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "textForState(globalState, true)"
+ }
+ ]
+ },
+ {
+ "id": "Status: {StateText}",
+ "message": "Status: {StateText}",
+ "translation": "Статус: {StateText}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "StateText",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "stateText"
+ }
+ ]
+ },
+ {
+ "id": "Addresses: {EnumerationSeparator}",
+ "message": "Addresses: {EnumerationSeparator}",
+ "translation": "Адреса: {EnumerationSeparator}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "EnumerationSeparator",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "strings.Join(addrs, l18n.EnumerationSeparator())"
+ }
+ ]
+ },
+ {
+ "id": "An Update is Available!",
+ "message": "An Update is Available!",
+ "translation": "Доступно обновление!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Update Available",
+ "message": "WireGuard Update Available",
+ "translation": "Доступно обновление WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "message": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "translation": "Доступно обновление для WireGuard. Рекомендуется обновить его как можно скорее.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnels",
+ "message": "Tunnels",
+ "translation": "Туннели",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Edit",
+ "message": "&Edit",
+ "translation": "&Редактировать",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add &empty tunnel…",
+ "message": "Add &empty tunnel…",
+ "translation": "Добавить &пустой туннель…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add Tunnel",
+ "message": "Add Tunnel",
+ "translation": "Добавить туннель",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Remove selected tunnel(s)",
+ "message": "Remove selected tunnel(s)",
+ "translation": "Удалить выбранные туннели",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to zip",
+ "message": "Export all tunnels to zip",
+ "translation": "Экспорт всех туннелей в zip-архив",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Toggle",
+ "message": "&Toggle",
+ "translation": "&Переключить",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to &zip…",
+ "message": "Export all tunnels to &zip…",
+ "translation": "Экспорт всех туннелей в &zip-архив…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit &selected tunnel…",
+ "message": "Edit &selected tunnel…",
+ "translation": "Редактировать &выбранный туннель…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Remove selected tunnel(s)",
+ "message": "&Remove selected tunnel(s)",
+ "translation": "&Удалить выбранные туннели",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "no configuration files were found",
+ "message": "no configuration files were found",
+ "translation": "файлы конфигурации не были найдены",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Could not import selected configuration: {LastErr}",
+ "message": "Could not import selected configuration: {LastErr}",
+ "translation": "Невозможно импортировать конфигурацию: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Could not enumerate existing tunnels: {LastErr}",
+ "message": "Could not enumerate existing tunnels: {LastErr}",
+ "translation": "Не удалось перечислить существующие туннели: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{Name}’",
+ "message": "Another tunnel already exists with the name ‘{Name}’",
+ "translation": "Туннель с именем ’{Name}’ уже существует",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "unparsedConfig.Name"
+ }
+ ]
+ },
+ {
+ "id": "Unable to import configuration: {LastErr}",
+ "message": "Unable to import configuration: {LastErr}",
+ "translation": "Невозможно импортировать конфигурацию: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Imported tunnels",
+ "message": "Imported tunnels",
+ "translation": "Импортированные туннели",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Imported {M} tunnels",
+ "message": "Imported {M} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "M",
+ "cases": {
+ "one": {
+ "msg": "Импортированный {M} туннель"
+ },
+ "few": {
+ "msg": "Импортированы туннели: {M}"
+ },
+ "many": {
+ "msg": "Импортированы туннели: {M}"
+ },
+ "other": {
+ "msg": "Импортированы туннели: {M}"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ }
+ ]
+ },
+ {
+ "id": "Imported {M} of {N} tunnels",
+ "message": "Imported {M} of {N} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "N",
+ "cases": {
+ "one": {
+ "msg": "Импортированы туннели: {M} из {N}"
+ },
+ "few": {
+ "msg": "Импортированы туннели: {M} из {N}"
+ },
+ "many": {
+ "msg": "Импортированы туннели: {M} из {N}"
+ },
+ "other": {
+ "msg": "Импортированы туннели: {M} из {N}"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ },
+ {
+ "id": "N",
+ "string": "%[2]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 2,
+ "expr": "n"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create tunnel",
+ "message": "Unable to create tunnel",
+ "translation": "Не удалось создать туннель",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Delete {TunnelCount} tunnels",
+ "message": "Delete {TunnelCount} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "Удалить {TunnelCount} туннель"
+ },
+ "few": {
+ "msg": "Удалить туннели: {TunnelCount}"
+ },
+ "many": {
+ "msg": "Удалить туннели: {TunnelCount}"
+ },
+ "other": {
+ "msg": "Удалить туннели: {TunnelCount}"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "message": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "Вы уверены, что хотите удалить {TunnelCount} туннель?"
+ },
+ "few": {
+ "msg": "Вы уверены, что хотите удалить туннели: {TunnelCount}?"
+ },
+ "many": {
+ "msg": "Вы уверены, что хотите удалить туннели: {TunnelCount}?"
+ },
+ "other": {
+ "msg": "Вы уверены, что хотите удалить туннели: {TunnelCount}?"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Delete tunnel ‘{TunnelName}’",
+ "message": "Delete tunnel ‘{TunnelName}’",
+ "translation": "Удалить туннель ‘{TunnelName}’",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "message": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "translation": "Вы уверены, что хотите удалить '{TunnelName}' туннель?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "{Question} You cannot undo this action.",
+ "message": "{Question} You cannot undo this action.",
+ "translation": "{Question} Данное действие невозможно отменить.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Question",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "question"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnel",
+ "message": "Unable to delete tunnel",
+ "translation": "Не удалось удалить туннель",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A tunnel was unable to be removed: {Error}",
+ "message": "A tunnel was unable to be removed: {Error}",
+ "translation": "Невозможно удалить туннель: {Error}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Error",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errors[0].Error()"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnels",
+ "message": "Unable to delete tunnels",
+ "translation": "Не удалось удалить туннели",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Lenerrors} tunnels were unable to be removed.",
+ "message": "{Lenerrors} tunnels were unable to be removed.",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Lenerrors",
+ "cases": {
+ "one": {
+ "msg": "{Lenerrors} туннель не удалось удалить."
+ },
+ "few": {
+ "msg": "туннелей не удалось удалить: {Lenerrors}"
+ },
+ "many": {
+ "msg": "туннелей не удалось удалить: {Lenerrors}"
+ },
+ "other": {
+ "msg": "туннелей не удалось удалить: {Lenerrors}"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Lenerrors",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "len(errors)"
+ }
+ ]
+ },
+ {
+ "id": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "message": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "translation": "Файлы конфигурации (*.zip, *.conf)|*.zip;*.conf|Все файлы (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Import tunnel(s) from file",
+ "message": "Import tunnel(s) from file",
+ "translation": "Импорт туннелей из файла",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Configuration ZIP Files (*.zip)|*.zip",
+ "message": "Configuration ZIP Files (*.zip)|*.zip",
+ "translation": "Конфигурация ZIP файлов (*.zip)|*.zip",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export tunnels to zip",
+ "message": "Export tunnels to zip",
+ "translation": "Экспорт туннелей в zip-архив",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Title} (unsigned build, no updates)",
+ "message": "{Title} (unsigned build, no updates)",
+ "translation": "{Title} (неподписанная сборка, нет обновлений)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "Error Exiting WireGuard",
+ "message": "Error Exiting WireGuard",
+ "translation": "Ошибка выхода из WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "message": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "translation": "Не удается выйти из сервиса из-за: {Err}. Вы можете остановить WireGuard из менеджера служб.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "message": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "translation": "Доступно обновление WireGuard. Настоятельно рекомендуем обновить приложение.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for user",
+ "message": "Status: Waiting for user",
+ "translation": "Статус: Ожидание пользователя",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Update Now",
+ "message": "Update Now",
+ "translation": "Обновить Сейчас",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for updater service",
+ "message": "Status: Waiting for updater service",
+ "translation": "Статус: Ожидание обновления",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error: {Err}. Please try again.",
+ "message": "Error: {Err}. Please try again.",
+ "translation": "Ошибка: {Err}. Попробуйте еще раз.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Status: Complete!",
+ "message": "Status: Complete!",
+ "translation": "Статус: Завершено!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "http2: Framer {F}: failed to decode just-written frame",
+ "message": "http2: Framer {F}: failed to decode just-written frame",
+ "translation": "http2: Framer {F}: не удалось декодировать только что записанный кадр",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ }
+ ]
+ },
+ {
+ "id": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "message": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "translation": "http2: Framer {F}: написал {Http2summarizeFramefr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ },
+ {
+ "id": "Http2summarizeFramefr",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(fr)"
+ }
+ ]
+ },
+ {
+ "id": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "message": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "translation": "http2: Framer {Fr}: прочитать {Http2summarizeFramef}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Fr",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "fr"
+ },
+ {
+ "id": "Http2summarizeFramef",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(f)"
+ }
+ ]
+ },
+ {
+ "id": "http2: decoded hpack field {HeaderField}",
+ "message": "http2: decoded hpack field {HeaderField}",
+ "translation": "http2: декодирован hpack поле {HeaderField}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "HeaderField",
+ "string": "%+[1]v",
+ "type": "vendor/golang.org/x/net/http2/hpack.HeaderField",
+ "underlyingType": "struct{Name string; Value string; Sensitive bool}",
+ "argNum": 1,
+ "expr": "hf"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/locales/si-LK/messages.gotext.json b/locales/si-LK/messages.gotext.json
new file mode 100644
index 00000000..81e82150
--- /dev/null
+++ b/locales/si-LK/messages.gotext.json
@@ -0,0 +1,715 @@
+{
+ "language": "si-LK",
+ "messages": [
+ {
+ "id": "Error",
+ "message": "Error",
+ "translation": "දෝෂයකි",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Usage: {Args0} [\n{String}]",
+ "message": "Usage: {Args0} [\n{String}]",
+ "translation": "භාවිතය: {Args0} [\n{String}]",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Args0",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "os.Args[0]"
+ },
+ {
+ "id": "String",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "builder.String()"
+ }
+ ]
+ },
+ {
+ "id": "Now",
+ "message": "Now",
+ "translation": "දැන්",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Years} year(s)",
+ "message": "{Years} year(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Years",
+ "cases": {
+ "one": {
+ "msg": "අවුරුදු {Years}"
+ },
+ "other": {
+ "msg": "අවුරුදු {Years}"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Years",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "years"
+ }
+ ]
+ },
+ {
+ "id": "{Days} day(s)",
+ "message": "{Days} day(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Days",
+ "cases": {
+ "one": {
+ "msg": "දවස් {Days}"
+ },
+ "other": {
+ "msg": "දවස් {Days}"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Days",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "days"
+ }
+ ]
+ },
+ {
+ "id": "{Hours} hour(s)",
+ "message": "{Hours} hour(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Hours",
+ "cases": {
+ "one": {
+ "msg": "පැය {Hours}"
+ },
+ "other": {
+ "msg": "පැය {Hours}"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Hours",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "hours"
+ }
+ ]
+ },
+ {
+ "id": "{Minutes} minute(s)",
+ "message": "{Minutes} minute(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Minutes",
+ "cases": {
+ "one": {
+ "msg": "විනාඩි {Minutes}"
+ },
+ "other": {
+ "msg": "විනාඩි {Minutes}"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Minutes",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "minutes"
+ }
+ ]
+ },
+ {
+ "id": "{Seconds} second(s)",
+ "message": "{Seconds} second(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Seconds",
+ "cases": {
+ "one": {
+ "msg": "තත්පර {Seconds}"
+ },
+ "other": {
+ "msg": "තත්පර {Seconds}"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Seconds",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "seconds"
+ }
+ ]
+ },
+ {
+ "id": "{Timestamp} ago",
+ "message": "{Timestamp} ago",
+ "translation": "{Timestamp} ට පෙර",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Timestamp",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "timestamp"
+ }
+ ]
+ },
+ {
+ "id": "{Bytes} B",
+ "message": "{Bytes} B",
+ "translation": "බ. {Bytes}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Bytes",
+ "string": "%[1]d",
+ "type": "golang.zx2c4.com/wireguard/windows/conf.Bytes",
+ "underlyingType": "uint64",
+ "argNum": 1,
+ "expr": "b"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024} KiB",
+ "message": "{Float64b__1024} KiB",
+ "translation": "කි.බ. {Float64b__1024}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024} MiB",
+ "message": "{Float64b__1024__1024} MiB",
+ "translation": "මෙ.බ. {Float64b__1024__1024}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024} GiB",
+ "message": "{Float64b__1024__1024__1024} GiB",
+ "translation": "ගි.බ. {Float64b__1024__1024__1024}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024__1024} TiB",
+ "message": "{Float64b__1024__1024__1024__1024} TiB",
+ "translation": "ටෙ.බ. {Float64b__1024__1024__1024__1024}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Why}: {Offender}",
+ "message": "{Why}: {Offender}",
+ "translation": "{Why}: {Offender}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Why",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "e.why"
+ },
+ {
+ "id": "Offender",
+ "string": "%[2]q",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "e.offender"
+ }
+ ]
+ },
+ {
+ "id": "Invalid IP address",
+ "message": "Invalid IP address",
+ "translation": "වලංගු නොවන අ.ජා.කෙ. ලිපිනයකි",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid port",
+ "message": "Invalid port",
+ "translation": "වලංගු නොවන කෙවෙනියකි",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key: {Err}",
+ "message": "Invalid key: {Err}",
+ "translation": "වලංගු නොවන යතුර: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Two commas in a row",
+ "message": "Two commas in a row",
+ "translation": "පේළියකට අල්පවිරාම දෙකක්",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Key must have a value",
+ "message": "Key must have a value",
+ "translation": "යතුරට අගයක් තිබිය යුතුය",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An interface must have a private key",
+ "message": "An interface must have a private key",
+ "translation": "අතුරුමුහුතකට පුද්ගලික යතුරක් තිබිය යුතුය",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Protocol version must be 1",
+ "message": "Protocol version must be 1",
+ "translation": "කෙටුම්පතෙහි අනුවාදය 1 විය යුතුය",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[EnumerationSeparator]",
+ "message": "[EnumerationSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert between items when listing - most western languages will translate ‘[EnumerationSeparator]’ into ‘, ’ to produce lists like ‘apple, orange, strawberry’. Eastern languages might translate into ‘、’ to produce lists like ‘リンゴ、オレンジ、イチゴ’."
+ },
+ {
+ "id": "[UnitSeparator]",
+ "message": "[UnitSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert when combining units of a measure - most languages will translate ‘[UnitSeparator]’ into ‘ ’ (space) to produce lists like ‘2 minuti 30 sekund’, or empty string ‘’ to produce ‘2分30秒’."
+ },
+ {
+ "id": "About WireGuard",
+ "message": "About WireGuard",
+ "translation": "වයර්ගාඩ් පිළිබඳව",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Close",
+ "message": "Close",
+ "translation": "වසන්න",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "♥ &Donate!",
+ "message": "♥ &Donate!",
+ "translation": "♥ &පරිත්‍යාග!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status:",
+ "message": "Status:",
+ "translation": "තත්වය:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Listen port:",
+ "message": "Listen port:",
+ "translation": "සවන්දීමේ කෙවෙනිය:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses:",
+ "message": "Addresses:",
+ "translation": "ලිපින:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "DNS servers:",
+ "message": "DNS servers:",
+ "translation": "ව.නා.ප. සේවාදායක:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Allowed IPs:",
+ "message": "Allowed IPs:",
+ "translation": "ඉඩදුන් අ.ජා.කෙ.:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "enabled",
+ "message": "enabled",
+ "translation": "සබල කර ඇත",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{String} received, {String_1} sent",
+ "message": "{String} received, {String_1} sent",
+ "translation": "{String} ලැබුණී, {String_1} යැවිණි",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "String",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "c.RxBytes.String()"
+ },
+ {
+ "id": "String_1",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "c.TxBytes.String()"
+ }
+ ]
+ },
+ {
+ "id": "Interface: {Name}",
+ "message": "Interface: {Name}",
+ "translation": "අතුරුමුහුණත: {Name}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "config.Name"
+ }
+ ]
+ },
+ {
+ "id": "&Name:",
+ "message": "&Name:",
+ "translation": "&නම:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(unknown)",
+ "message": "(unknown)",
+ "translation": "(නොදනී)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save",
+ "message": "&Save",
+ "translation": "&සුරකින්න",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Cancel",
+ "message": "Cancel",
+ "translation": "අවලංගු",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Configuration:",
+ "message": "&Configuration:",
+ "translation": "&වින්‍යාසය:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid name",
+ "message": "Invalid name",
+ "translation": "වලංගු නොවන නමකි",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A name is required.",
+ "message": "A name is required.",
+ "translation": "නමක් අවශ්‍යයි.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to create new configuration",
+ "message": "Unable to create new configuration",
+ "translation": "නව වින්‍යාසය සෑදීමට නොහැකියි",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Writing file failed",
+ "message": "Writing file failed",
+ "translation": "ගොනුව ලිවීමට අසමත්විය",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Activating",
+ "message": "Activating",
+ "translation": "ක්‍රියාත්මක වෙමින්",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unknown state",
+ "message": "Unknown state",
+ "translation": "නොදන්නා තත්වයකි",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Copy",
+ "message": "&Copy",
+ "translation": "&පිටපත්",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save to file…",
+ "message": "&Save to file…",
+ "translation": "&ගොනුවකට සුරකින්න…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Time",
+ "message": "Time",
+ "translation": "වේලාව",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&About WireGuard…",
+ "message": "&About WireGuard…",
+ "translation": "&වයර්ගාඩ් පිළිබඳව…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Title} (out of date)",
+ "message": "{Title} (out of date)",
+ "translation": "{Title} (කල් ඉකුත් වී ඇත)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "Unable to wait for WireGuard window to appear: {Err}",
+ "message": "Unable to wait for WireGuard window to appear: {Err}",
+ "translation": "වයර්ගාඩ් කවුළුව පෙනෙන තෙක් රැඳීසිටිය නොහැකිය: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Status: Unknown",
+ "message": "Status: Unknown",
+ "translation": "තත්වය: නොදනී",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "E&xit",
+ "message": "E&xit",
+ "translation": "පි&ටවන්න",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Activated",
+ "message": "WireGuard Activated",
+ "translation": "වයර්ගාඩ් ක්‍රියාත්මකයි",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: {StateText}",
+ "message": "Status: {StateText}",
+ "translation": "තත්වය: {StateText}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "StateText",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "stateText"
+ }
+ ]
+ },
+ {
+ "id": "Addresses: {EnumerationSeparator}",
+ "message": "Addresses: {EnumerationSeparator}",
+ "translation": "ලිපින: {EnumerationSeparator}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "EnumerationSeparator",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "strings.Join(addrs, l18n.EnumerationSeparator())"
+ }
+ ]
+ },
+ {
+ "id": "&Edit",
+ "message": "&Edit",
+ "translation": "&සංස්කරණය",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to import configuration: {LastErr}",
+ "message": "Unable to import configuration: {LastErr}",
+ "translation": "වින්‍යාසය ආයාත කළ නොහැකිය: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "{Question} You cannot undo this action.",
+ "message": "{Question} You cannot undo this action.",
+ "translation": "{Question} මෙම ක්‍රියාමාර්ගය ආපසු හැරවිය නොහැකිය.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Question",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "question"
+ }
+ ]
+ },
+ {
+ "id": "Error Exiting WireGuard",
+ "message": "Error Exiting WireGuard",
+ "translation": "වයර්ගාඩ් පිටවීමේදී දෝෂයකි",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for user",
+ "message": "Status: Waiting for user",
+ "translation": "තත්වය: පරිශීලක සඳහා රැඳෙමින්",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Update Now",
+ "message": "Update Now",
+ "translation": "යාවත්කාල කරන්න",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for updater service",
+ "message": "Status: Waiting for updater service",
+ "translation": "තත්වය: යාවත්කාල සේවාව සඳහා රැඳෙමින්",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error: {Err}. Please try again.",
+ "message": "Error: {Err}. Please try again.",
+ "translation": "දෝෂය: {Err}. යළි උත්සාහ කරන්න.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Status: Complete!",
+ "message": "Status: Complete!",
+ "translation": "තත්වය: සම්පූර්ණයි!",
+ "translatorComment": "Copied from source."
+ }
+ ]
+} \ No newline at end of file
diff --git a/locales/sk/messages.gotext.json b/locales/sk/messages.gotext.json
new file mode 100644
index 00000000..d5aeb165
--- /dev/null
+++ b/locales/sk/messages.gotext.json
@@ -0,0 +1,1827 @@
+{
+ "language": "sk",
+ "messages": [
+ {
+ "id": "Error",
+ "message": "Error",
+ "translation": "Chyba",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(no argument): elevate and install manager service",
+ "message": "(no argument): elevate and install manager service",
+ "translation": "(bez argumentu): získať administrátorské práva a nainštalovať službu manažéra",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Usage: {Args0} [\n{String}]",
+ "message": "Usage: {Args0} [\n{String}]",
+ "translation": "Použitie: {Args0} [\n{String}]",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Args0",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "os.Args[0]"
+ },
+ {
+ "id": "String",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "builder.String()"
+ }
+ ]
+ },
+ {
+ "id": "Command Line Options",
+ "message": "Command Line Options",
+ "translation": "Možnosti príkazového riadku",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to determine whether the process is running under WOW64: {Err}",
+ "message": "Unable to determine whether the process is running under WOW64: {Err}",
+ "translation": "Nepodarilo sa zistiť, či proces beží pod WOW64: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "You must use the native version of WireGuard on this computer.",
+ "message": "You must use the native version of WireGuard on this computer.",
+ "translation": "V tomto počítači musíte používať pôvodnú verziu programu WireGuard.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to open current process token: {Err}",
+ "message": "Unable to open current process token: {Err}",
+ "translation": "Nepodarilo sa otvoriť token aktuálneho procesu: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard môžu používať iba členovia Builtin skupiny {AdminGroupName}.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard je spustený, ale používateľské rozhranie je prístupné iba členom Builtin skupiny {AdminGroupName}.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard system tray icon did not appear after 30 seconds.",
+ "message": "WireGuard system tray icon did not appear after 30 seconds.",
+ "translation": "WireGuard ikona sa ani po 30 sekundách neobjavila na systémovej lište.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Now",
+ "message": "Now",
+ "translation": "Teraz",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "System clock wound backward!",
+ "message": "System clock wound backward!",
+ "translation": "Systémové hodiny sa vrátili v čase!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Years} year(s)",
+ "message": "{Years} year(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Years",
+ "cases": {
+ "one": {
+ "msg": "{Years} rok"
+ },
+ "few": {
+ "msg": "{Years} roky"
+ },
+ "many": {
+ "msg": "{Years} rokov"
+ },
+ "other": {
+ "msg": "{Years} rokov"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Years",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "years"
+ }
+ ]
+ },
+ {
+ "id": "{Days} day(s)",
+ "message": "{Days} day(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Days",
+ "cases": {
+ "one": {
+ "msg": "{Days} deň"
+ },
+ "few": {
+ "msg": "{Days} dni"
+ },
+ "many": {
+ "msg": "{Days} dní"
+ },
+ "other": {
+ "msg": "{Days} dní"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Days",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "days"
+ }
+ ]
+ },
+ {
+ "id": "{Hours} hour(s)",
+ "message": "{Hours} hour(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Hours",
+ "cases": {
+ "one": {
+ "msg": "{Hours} hodina"
+ },
+ "few": {
+ "msg": "{Hours} hodiny"
+ },
+ "many": {
+ "msg": "{Hours} hodín"
+ },
+ "other": {
+ "msg": "{Hours} hodín"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Hours",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "hours"
+ }
+ ]
+ },
+ {
+ "id": "{Minutes} minute(s)",
+ "message": "{Minutes} minute(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Minutes",
+ "cases": {
+ "one": {
+ "msg": "{Minutes} minúta"
+ },
+ "few": {
+ "msg": "{Minutes} minúty"
+ },
+ "many": {
+ "msg": "{Minutes} minút"
+ },
+ "other": {
+ "msg": "{Minutes} minút"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Minutes",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "minutes"
+ }
+ ]
+ },
+ {
+ "id": "{Seconds} second(s)",
+ "message": "{Seconds} second(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Seconds",
+ "cases": {
+ "one": {
+ "msg": "{Seconds} sekunda"
+ },
+ "few": {
+ "msg": "{Seconds} sekundy"
+ },
+ "many": {
+ "msg": "{Seconds} sekúnd"
+ },
+ "other": {
+ "msg": "{Seconds} sekúnd"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Seconds",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "seconds"
+ }
+ ]
+ },
+ {
+ "id": "{Timestamp} ago",
+ "message": "{Timestamp} ago",
+ "translation": "Pred {Timestamp}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Timestamp",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "timestamp"
+ }
+ ]
+ },
+ {
+ "id": "{Bytes} B",
+ "message": "{Bytes} B",
+ "translation": "{Bytes} B",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Bytes",
+ "string": "%[1]d",
+ "type": "golang.zx2c4.com/wireguard/windows/conf.Bytes",
+ "underlyingType": "uint64",
+ "argNum": 1,
+ "expr": "b"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024} KiB",
+ "message": "{Float64b__1024} KiB",
+ "translation": "{Float64b__1024} KiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024} MiB",
+ "message": "{Float64b__1024__1024} MiB",
+ "translation": "{Float64b__1024__1024} MiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024} GiB",
+ "message": "{Float64b__1024__1024__1024} GiB",
+ "translation": "{Float64b__1024__1024__1024} GiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024__1024} TiB",
+ "message": "{Float64b__1024__1024__1024__1024} TiB",
+ "translation": "{Float64b__1024__1024__1024__1024} TiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Why}: {Offender}",
+ "message": "{Why}: {Offender}",
+ "translation": "{Why}: {Offender}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Why",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "e.why"
+ },
+ {
+ "id": "Offender",
+ "string": "%[2]q",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "e.offender"
+ }
+ ]
+ },
+ {
+ "id": "Invalid IP address",
+ "message": "Invalid IP address",
+ "translation": "Neplatná adresa IP",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid network prefix length",
+ "message": "Invalid network prefix length",
+ "translation": "Neplatná dĺžka sieťového prefixu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Missing port from endpoint",
+ "message": "Missing port from endpoint",
+ "translation": "Koncovému bodu chýba číslo portu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid endpoint host",
+ "message": "Invalid endpoint host",
+ "translation": "Neplatný hostiteľ koncového bodu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Brackets must contain an IPv6 address",
+ "message": "Brackets must contain an IPv6 address",
+ "translation": "Medzi zátvorkami musí byť IPv6 adresa",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid MTU",
+ "message": "Invalid MTU",
+ "translation": "Neplatné MTU",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid port",
+ "message": "Invalid port",
+ "translation": "Neplatný port",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid persistent keepalive",
+ "message": "Invalid persistent keepalive",
+ "translation": "Neplatný perzistentný keepalive",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key: {Err}",
+ "message": "Invalid key: {Err}",
+ "translation": "Neplatný kľúč: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Keys must decode to exactly 32 bytes",
+ "message": "Keys must decode to exactly 32 bytes",
+ "translation": "Dekódované kľúče musia mať veľkosť 32 bajtov",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Number must be a number between 0 and 2^64-1: {Err}",
+ "message": "Number must be a number between 0 and 2^64-1: {Err}",
+ "translation": "Číslo musí mať hodnotu medzi 0 a 2^64-1: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Two commas in a row",
+ "message": "Two commas in a row",
+ "translation": "Dve čiarky v poradí",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name is not valid",
+ "message": "Tunnel name is not valid",
+ "translation": "Názov tunela nie je platný",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Line must occur in a section",
+ "message": "Line must occur in a section",
+ "translation": "Sekcia musí obsahovať čiaru",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Config key is missing an equals separator",
+ "message": "Config key is missing an equals separator",
+ "translation": "Konfiguračný kľúč neobsahuje separátor (znamienko rovnosti)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Key must have a value",
+ "message": "Key must have a value",
+ "translation": "Kľúč musí obsahovať hodnotu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Interface] section",
+ "message": "Invalid key for [Interface] section",
+ "translation": "Neplatný kľúč sekcie [Interface]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Peer] section",
+ "message": "Invalid key for [Peer] section",
+ "translation": "Neplatný kľúč sekcie [Peer]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An interface must have a private key",
+ "message": "An interface must have a private key",
+ "translation": "Rozhranie musí mať priradený súkromný kľúč",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[none specified]",
+ "message": "[none specified]",
+ "translation": "[nešpecifikované]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "All peers must have public keys",
+ "message": "All peers must have public keys",
+ "translation": "Všetci peeri musia mať priradený verejný kľúč",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error in getting configuration",
+ "message": "Error in getting configuration",
+ "translation": "Chyba pri získavaní konfigurácie",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for interface section",
+ "message": "Invalid key for interface section",
+ "translation": "Neplatný kľúč sekcie rozhrania",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Protocol version must be 1",
+ "message": "Protocol version must be 1",
+ "translation": "Verzia protokolu musí byť 1",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for peer section",
+ "message": "Invalid key for peer section",
+ "translation": "Neplatný kľúč peer sekcie",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[EnumerationSeparator]",
+ "message": "[EnumerationSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert between items when listing - most western languages will translate ‘[EnumerationSeparator]’ into ‘, ’ to produce lists like ‘apple, orange, strawberry’. Eastern languages might translate into ‘、’ to produce lists like ‘リンゴ、オレンジ、イチゴ’."
+ },
+ {
+ "id": "[UnitSeparator]",
+ "message": "[UnitSeparator]",
+ "translation": " ",
+ "comment": "Text to insert when combining units of a measure - most languages will translate ‘[UnitSeparator]’ into ‘ ’ (space) to produce lists like ‘2 minuti 30 sekund’, or empty string ‘’ to produce ‘2分30秒’."
+ },
+ {
+ "id": "About WireGuard",
+ "message": "About WireGuard",
+ "translation": "O WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard logo image",
+ "message": "WireGuard logo image",
+ "translation": "Obrázok WireGuard loga",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "message": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "translation": "Verzia aplikácie.: {Number}\nVerzia Go backendu: {WireGuardGoVersion}\nVerzia Go: {Version_go}-{GOARCH}\nOperačný systém: {OsName}\nArchitektúra: {NativeArch}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Number",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "version.Number"
+ },
+ {
+ "id": "WireGuardGoVersion",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "device.WireGuardGoVersion"
+ },
+ {
+ "id": "Version_go",
+ "string": "%[3]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 3,
+ "expr": "strings.TrimPrefix(runtime.Version(), \"go\")"
+ },
+ {
+ "id": "GOARCH",
+ "string": "%[4]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 4,
+ "expr": "runtime.GOARCH"
+ },
+ {
+ "id": "OsName",
+ "string": "%[5]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 5,
+ "expr": "version.OsName()"
+ },
+ {
+ "id": "NativeArch",
+ "string": "%[6]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 6,
+ "expr": "version.NativeArch()"
+ }
+ ]
+ },
+ {
+ "id": "Close",
+ "message": "Close",
+ "translation": "Zatvoriť",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "♥ &Donate!",
+ "message": "♥ &Donate!",
+ "translation": "♥ a Darovat!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status:",
+ "message": "Status:",
+ "translation": "Stav:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Deactivate",
+ "message": "&Deactivate",
+ "translation": "a Deaktivovať",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Activate",
+ "message": "&Activate",
+ "translation": "a Aktivovať",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Public key:",
+ "message": "Public key:",
+ "translation": "Verejný kľúč:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Listen port:",
+ "message": "Listen port:",
+ "translation": "Otvorený port:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "MTU:",
+ "message": "MTU:",
+ "translation": "MTU:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses:",
+ "message": "Addresses:",
+ "translation": "Adresy:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "DNS servers:",
+ "message": "DNS servers:",
+ "translation": "Servery DNS:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Scripts:",
+ "message": "Scripts:",
+ "translation": "Skripty:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Preshared key:",
+ "message": "Preshared key:",
+ "translation": "Vopred zdieľaný kľúč:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Allowed IPs:",
+ "message": "Allowed IPs:",
+ "translation": "Povolené IP adresy:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Endpoint:",
+ "message": "Endpoint:",
+ "translation": "Koncový bod:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Persistent keepalive:",
+ "message": "Persistent keepalive:",
+ "translation": "Perzistentný keepalive:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Latest handshake:",
+ "message": "Latest handshake:",
+ "translation": "Posledné spojenie (handshake):",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Transfer:",
+ "message": "Transfer:",
+ "translation": "Prenos:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-up",
+ "message": "pre-up",
+ "translation": "pred-zapnutím",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-up",
+ "message": "post-up",
+ "translation": "po-zapnutí",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-down",
+ "message": "pre-down",
+ "translation": "pred-vypnutím",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-down",
+ "message": "post-down",
+ "translation": "po-vypnutí",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "disabled, per policy",
+ "message": "disabled, per policy",
+ "translation": "zakázané, na základe pravidla",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "enabled",
+ "message": "enabled",
+ "translation": "povolené",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{String} received, {String_1} sent",
+ "message": "{String} received, {String_1} sent",
+ "translation": "{String} prijatých, {String_1} odoslaných",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "String",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "c.RxBytes.String()"
+ },
+ {
+ "id": "String_1",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "c.TxBytes.String()"
+ }
+ ]
+ },
+ {
+ "id": "Failed to determine tunnel state",
+ "message": "Failed to determine tunnel state",
+ "translation": "Nepodarilo sa zistiť stav tunela",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to activate tunnel",
+ "message": "Failed to activate tunnel",
+ "translation": "Nepodarilo sa aktivovať tunel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to deactivate tunnel",
+ "message": "Failed to deactivate tunnel",
+ "translation": "Nepodarilo sa deaktivovať tunel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Interface: {Name}",
+ "message": "Interface: {Name}",
+ "translation": "Rozhranie: {Name}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "config.Name"
+ }
+ ]
+ },
+ {
+ "id": "Peer",
+ "message": "Peer",
+ "translation": "Peer",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Create new tunnel",
+ "message": "Create new tunnel",
+ "translation": "Vytvoriť nový tunel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit tunnel",
+ "message": "Edit tunnel",
+ "translation": "Upraviť tunel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Name:",
+ "message": "&Name:",
+ "translation": "&Názov:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Public key:",
+ "message": "&Public key:",
+ "translation": "&Verejný kľúč:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(unknown)",
+ "message": "(unknown)",
+ "translation": "(neznámy)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Block untunneled traffic (kill-switch)",
+ "message": "&Block untunneled traffic (kill-switch)",
+ "translation": "&Blokovať netunelovaný prenos (kill-switch)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.",
+ "message": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.",
+ "translation": "Ak konfigurácia obsahuje práve jedného peera a tento má v povolených IP 0.0.0.0/0 alebo ::/0, tak tak tento tunel aktivuje pravidlo firewallu blokujúce komunikáciu, ktorá nepochádza z alebo nesmeruje na rozhranie tohto tunela alebo smeruje na nesprávny DNS server, špeciálnou výnimkou je DHCP a NDP komunikácia.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save",
+ "message": "&Save",
+ "translation": "&Uložiť",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Cancel",
+ "message": "Cancel",
+ "translation": "Zrušiť",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Configuration:",
+ "message": "&Configuration:",
+ "translation": "&Konfigurácia:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid name",
+ "message": "Invalid name",
+ "translation": "Neplatný názov",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A name is required.",
+ "message": "A name is required.",
+ "translation": "Názov je povinný.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name ‘{NewName}’ is invalid.",
+ "message": "Tunnel name ‘{NewName}’ is invalid.",
+ "translation": "Názov tunela ‘{NewName}’ je neplatný.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to list existing tunnels",
+ "message": "Unable to list existing tunnels",
+ "translation": "Nepodarilo sa pripraviť zoznam existujúcich tunelov",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel already exists",
+ "message": "Tunnel already exists",
+ "translation": "Tunel už existuje",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{NewName}’.",
+ "message": "Another tunnel already exists with the name ‘{NewName}’.",
+ "translation": "Tunel s názvom ‘{NewName}’ už existuje.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create new configuration",
+ "message": "Unable to create new configuration",
+ "translation": "Nie je možné vytvoriť novú konfiguráciu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Writing file failed",
+ "message": "Writing file failed",
+ "translation": "Nepodarilo sa zapísať do súboru",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "message": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "translation": "Súbor ‘{FilePath}’ už existuje.\n\nŽeláte si ho prepísať?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "FilePath",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "filePath"
+ }
+ ]
+ },
+ {
+ "id": "Active",
+ "message": "Active",
+ "translation": "Aktívny",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Activating",
+ "message": "Activating",
+ "translation": "Aktivuje sa",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Inactive",
+ "message": "Inactive",
+ "translation": "Neaktívny",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Deactivating",
+ "message": "Deactivating",
+ "translation": "Deaktivuje sa",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unknown state",
+ "message": "Unknown state",
+ "translation": "Neznámy stav",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log",
+ "message": "Log",
+ "translation": "Denník udalostí",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Copy",
+ "message": "&Copy",
+ "translation": "&Kopírovať",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Select &all",
+ "message": "Select &all",
+ "translation": "Vybr&ať všetko",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save to file…",
+ "message": "&Save to file…",
+ "translation": "Uložiť do &súboru…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Time",
+ "message": "Time",
+ "translation": "Čas",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log message",
+ "message": "Log message",
+ "translation": "Správa v denníku udalostí",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "message": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "translation": "Textové súbory (*.txt)|*.txt|Všetky súbory (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export log to file",
+ "message": "Export log to file",
+ "translation": "Exportovať denník udalostí do súboru",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&About WireGuard…",
+ "message": "&About WireGuard…",
+ "translation": "&O WireGuard…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel Error",
+ "message": "Tunnel Error",
+ "translation": "Chyba tunela",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "message": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "translation": "{ErrMsg}\n\nViac informácií nájdete v denníku udalostí.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "ErrMsg",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errMsg"
+ }
+ ]
+ },
+ {
+ "id": "{Title} (out of date)",
+ "message": "{Title} (out of date)",
+ "translation": "{Title} (neaktuány)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Detection Error",
+ "message": "WireGuard Detection Error",
+ "translation": "Chyba detekcie WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to wait for WireGuard window to appear: {Err}",
+ "message": "Unable to wait for WireGuard window to appear: {Err}",
+ "translation": "Nie je možné čakať na zobrazenie WireGuard okna: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard: Deactivated",
+ "message": "WireGuard: Deactivated",
+ "translation": "WireGuard: deaktivovaný",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Unknown",
+ "message": "Status: Unknown",
+ "translation": "Stav: Nezámy",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses: None",
+ "message": "Addresses: None",
+ "translation": "Adresa: žiadna",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Manage tunnels…",
+ "message": "&Manage tunnels…",
+ "translation": "&Spravovať tunely…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Import tunnel(s) from file…",
+ "message": "&Import tunnel(s) from file…",
+ "translation": "&Importovať tunel(y) zo súboru…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "E&xit",
+ "message": "E&xit",
+ "translation": "U&končiť",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Tunnels",
+ "message": "&Tunnels",
+ "translation": "&Tunely",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Activated",
+ "message": "WireGuard Activated",
+ "translation": "WireGuard je aktivovaný",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been activated.",
+ "message": "The {Name} tunnel has been activated.",
+ "translation": "Tunel {Name} bol aktivovaný.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Deactivated",
+ "message": "WireGuard Deactivated",
+ "translation": "WireGuard je deaktivovaný",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been deactivated.",
+ "message": "The {Name} tunnel has been deactivated.",
+ "translation": "Tunel {Name} bol deaktivovaný.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Tunnel Error",
+ "message": "WireGuard Tunnel Error",
+ "translation": "Chyba WireGuard tunelu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard: {TextForStateglobalState_true}",
+ "message": "WireGuard: {TextForStateglobalState_true}",
+ "translation": "WireGuard: {TextForStateglobalState_true}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TextForStateglobalState_true",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "textForState(globalState, true)"
+ }
+ ]
+ },
+ {
+ "id": "Status: {StateText}",
+ "message": "Status: {StateText}",
+ "translation": "Stav: {StateText}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "StateText",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "stateText"
+ }
+ ]
+ },
+ {
+ "id": "Addresses: {EnumerationSeparator}",
+ "message": "Addresses: {EnumerationSeparator}",
+ "translation": "Adresa: {EnumerationSeparator}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "EnumerationSeparator",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "strings.Join(addrs, l18n.EnumerationSeparator())"
+ }
+ ]
+ },
+ {
+ "id": "An Update is Available!",
+ "message": "An Update is Available!",
+ "translation": "Je dostupná aktualizácia!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Update Available",
+ "message": "WireGuard Update Available",
+ "translation": "Dostupná aktualizácia pre WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "message": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "translation": "Je k dispozícii aktualizácia programu WireGuard. Je odporúčané čo najskôr vykonať aktualizáciu.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnels",
+ "message": "Tunnels",
+ "translation": "Tunely",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Edit",
+ "message": "&Edit",
+ "translation": "&Upraviť",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add &empty tunnel…",
+ "message": "Add &empty tunnel…",
+ "translation": "Pridať &prázdny tunel…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add Tunnel",
+ "message": "Add Tunnel",
+ "translation": "Pridať tunel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Remove selected tunnel(s)",
+ "message": "Remove selected tunnel(s)",
+ "translation": "Odstrániť označený(é) tunel(y)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to zip",
+ "message": "Export all tunnels to zip",
+ "translation": "Export všetkých tunelov do zip súboru",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Toggle",
+ "message": "&Toggle",
+ "translation": "P&repnúť",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to &zip…",
+ "message": "Export all tunnels to &zip…",
+ "translation": "Export všetkých tunelov do &zip súboru…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit &selected tunnel…",
+ "message": "Edit &selected tunnel…",
+ "translation": "Upraviť &označený tunel…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Remove selected tunnel(s)",
+ "message": "&Remove selected tunnel(s)",
+ "translation": "&Odstrániť označené tunely",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "no configuration files were found",
+ "message": "no configuration files were found",
+ "translation": "neboli nájdené žiadne konfiguračné súbory",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Could not import selected configuration: {LastErr}",
+ "message": "Could not import selected configuration: {LastErr}",
+ "translation": "Nepodarilo sa naimportovať vybrané konfigurácie: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Could not enumerate existing tunnels: {LastErr}",
+ "message": "Could not enumerate existing tunnels: {LastErr}",
+ "translation": "Nepodarilo sa načítať existujúce tunely: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{Name}’",
+ "message": "Another tunnel already exists with the name ‘{Name}’",
+ "translation": "Už existuje tunel s názvom '{Name}'",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "unparsedConfig.Name"
+ }
+ ]
+ },
+ {
+ "id": "Unable to import configuration: {LastErr}",
+ "message": "Unable to import configuration: {LastErr}",
+ "translation": "Nepodarilo sa naimportovať konfiguráciu: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Imported tunnels",
+ "message": "Imported tunnels",
+ "translation": "Naimportované tunely",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Imported {M} tunnels",
+ "message": "Imported {M} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "M",
+ "cases": {
+ "one": {
+ "msg": "Importovaný {M} tunel"
+ },
+ "few": {
+ "msg": "Naimportované {M} tunely"
+ },
+ "many": {
+ "msg": "Naimportovaných {M} tunelov"
+ },
+ "other": {
+ "msg": "Naimportovaných {M} tunelov"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ }
+ ]
+ },
+ {
+ "id": "Imported {M} of {N} tunnels",
+ "message": "Imported {M} of {N} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "N",
+ "cases": {
+ "one": {
+ "msg": "Naimportovaný {M} z {N} tunelov"
+ },
+ "few": {
+ "msg": "Naimportované {M} z {N} tunelov"
+ },
+ "many": {
+ "msg": "Naimportovaných {M} z {N} tunelov"
+ },
+ "other": {
+ "msg": "Naimportovaných {M} z {N} tunelov"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ },
+ {
+ "id": "N",
+ "string": "%[2]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 2,
+ "expr": "n"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create tunnel",
+ "message": "Unable to create tunnel",
+ "translation": "Tunel sa nedá vytvoriť",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Delete {TunnelCount} tunnels",
+ "message": "Delete {TunnelCount} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "Odstránený {TunnelCount} tunel"
+ },
+ "few": {
+ "msg": "Odstránene {TunnelCount} tunely"
+ },
+ "many": {
+ "msg": "Odstránených {TunnelCount} tunelov"
+ },
+ "other": {
+ "msg": "Odstránených {TunnelCount} tunelov"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "message": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "Ste si istý, že si želáte odstrániť {TunnelCount} tunel?"
+ },
+ "few": {
+ "msg": "Ste si istý, že si želáte odstrániť {TunnelCount} tunely?"
+ },
+ "many": {
+ "msg": "Ste si istý, že si želáte odstrániť {TunnelCount} tunelov?"
+ },
+ "other": {
+ "msg": "Ste si istý, že si želáte odstrániť {TunnelCount} tunelov?"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Delete tunnel ‘{TunnelName}’",
+ "message": "Delete tunnel ‘{TunnelName}’",
+ "translation": "Odstránenie tunela ‘{TunnelName}’",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "message": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "translation": "Ste si istý, že si želáte odstrániť tunel ‘{TunnelName}’?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "{Question} You cannot undo this action.",
+ "message": "{Question} You cannot undo this action.",
+ "translation": "{Question} Túto akciu nemôže vrátiť späť.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Question",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "question"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnel",
+ "message": "Unable to delete tunnel",
+ "translation": "Tunel sa nedá odstrániť",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A tunnel was unable to be removed: {Error}",
+ "message": "A tunnel was unable to be removed: {Error}",
+ "translation": "Nebolo možné odstrániť tunel: {Error}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Error",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errors[0].Error()"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnels",
+ "message": "Unable to delete tunnels",
+ "translation": "Tunely sa nedajú odstrániť",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Lenerrors} tunnels were unable to be removed.",
+ "message": "{Lenerrors} tunnels were unable to be removed.",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Lenerrors",
+ "cases": {
+ "one": {
+ "msg": "{Lenerrors} tunel nebolo možné odstrániť."
+ },
+ "few": {
+ "msg": "{Lenerrors} tunely nebolo možné odstrániť."
+ },
+ "many": {
+ "msg": "{Lenerrors} tunelov nebolo možné odstrániť."
+ },
+ "other": {
+ "msg": "{Lenerrors} tunelov nebolo možné odstrániť."
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Lenerrors",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "len(errors)"
+ }
+ ]
+ },
+ {
+ "id": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "message": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "translation": "Konfirugačné súbory (*.zip, *.conf)|*.zip;*.conf|Všetky súbory (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Import tunnel(s) from file",
+ "message": "Import tunnel(s) from file",
+ "translation": "Importovať tunel(y) zo súboru",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Configuration ZIP Files (*.zip)|*.zip",
+ "message": "Configuration ZIP Files (*.zip)|*.zip",
+ "translation": "Konfiguračné ZIP súbry (*.zip)|*.zip",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export tunnels to zip",
+ "message": "Export tunnels to zip",
+ "translation": "Export tunelov do zip súboru",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Title} (unsigned build, no updates)",
+ "message": "{Title} (unsigned build, no updates)",
+ "translation": "{Title} (nepodpísaná verzia, žiadne aktualizácie)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "Error Exiting WireGuard",
+ "message": "Error Exiting WireGuard",
+ "translation": "Chyba ukončenia WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "message": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "translation": "Nie je možné ukončiť služby z dôvodu: {Err}. Skúste zastaviť WireGuard v správcovi služieb.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "message": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "translation": "Je k dispozícii nová verzia programu WireGuard. Odporúčame bezodkladne vykonať aktualizáciu.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for user",
+ "message": "Status: Waiting for user",
+ "translation": "Stav: Čaká sa na užívateľa",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Update Now",
+ "message": "Update Now",
+ "translation": "Aktualizovať teraz",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for updater service",
+ "message": "Status: Waiting for updater service",
+ "translation": "Stav: Čaká sa na aktualizačnú službu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error: {Err}. Please try again.",
+ "message": "Error: {Err}. Please try again.",
+ "translation": "Chyba: {Err}. Skúste to znova.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Status: Complete!",
+ "message": "Status: Complete!",
+ "translation": "Stav: Dokončené!",
+ "translatorComment": "Copied from source."
+ }
+ ]
+} \ No newline at end of file
diff --git a/locales/sl/messages.gotext.json b/locales/sl/messages.gotext.json
new file mode 100644
index 00000000..c3418f02
--- /dev/null
+++ b/locales/sl/messages.gotext.json
@@ -0,0 +1,1907 @@
+{
+ "language": "sl",
+ "messages": [
+ {
+ "id": "Error",
+ "message": "Error",
+ "translation": "Napaka",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(no argument): elevate and install manager service",
+ "message": "(no argument): elevate and install manager service",
+ "translation": "(brez argumenta): povzdigni na skrbniške pravice in namesti skrbniško storitev",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Usage: {Args0} [\n{String}]",
+ "message": "Usage: {Args0} [\n{String}]",
+ "translation": "Uporaba: {Args0} [\n{String}]",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Args0",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "os.Args[0]"
+ },
+ {
+ "id": "String",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "builder.String()"
+ }
+ ]
+ },
+ {
+ "id": "Command Line Options",
+ "message": "Command Line Options",
+ "translation": "Možnosti ukazne vrstice",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to determine whether the process is running under WOW64: {Err}",
+ "message": "Unable to determine whether the process is running under WOW64: {Err}",
+ "translation": "Napaka pri določanju ali proces teče kot WOW64: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "You must use the native version of WireGuard on this computer.",
+ "message": "You must use the native version of WireGuard on this computer.",
+ "translation": "Na temu računalniku morate uporabiti enako-arhitekturno različico WireGuarda.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to open current process token: {Err}",
+ "message": "Unable to open current process token: {Err}",
+ "translation": "Napaka pri odpiranju žetona trenutnega procesa: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard lahko uporabljajo samo uporabniki, ki so člani vgrajene skupine {AdminGroupName}.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard je zagnan, vendar je up. vmesnik dostopen samo z namizij uporabnikov članov skupine {AdminGroupName}.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard system tray icon did not appear after 30 seconds.",
+ "message": "WireGuard system tray icon did not appear after 30 seconds.",
+ "translation": "Ikona WireGuarda se po 30 sekundah ni pojavila v sistemski vrstici.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Now",
+ "message": "Now",
+ "translation": "Zdaj",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "System clock wound backward!",
+ "message": "System clock wound backward!",
+ "translation": "Sistemska ura prevrtena nazaj!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Years} year(s)",
+ "message": "{Years} year(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Years",
+ "cases": {
+ "one": {
+ "msg": "{Years} leto"
+ },
+ "two": {
+ "msg": "{Years} leti"
+ },
+ "few": {
+ "msg": "{Years} leta"
+ },
+ "other": {
+ "msg": "{Years} let"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Years",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "years"
+ }
+ ]
+ },
+ {
+ "id": "{Days} day(s)",
+ "message": "{Days} day(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Days",
+ "cases": {
+ "one": {
+ "msg": "{Days} dan"
+ },
+ "two": {
+ "msg": "{Days} dneva"
+ },
+ "few": {
+ "msg": "{Days} dni"
+ },
+ "other": {
+ "msg": "{Days} dni"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Days",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "days"
+ }
+ ]
+ },
+ {
+ "id": "{Hours} hour(s)",
+ "message": "{Hours} hour(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Hours",
+ "cases": {
+ "one": {
+ "msg": "{Hours} uro"
+ },
+ "two": {
+ "msg": "{Hours} uri"
+ },
+ "few": {
+ "msg": "{Hours} ure"
+ },
+ "other": {
+ "msg": "{Hours} ur"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Hours",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "hours"
+ }
+ ]
+ },
+ {
+ "id": "{Minutes} minute(s)",
+ "message": "{Minutes} minute(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Minutes",
+ "cases": {
+ "one": {
+ "msg": "{Minutes} minuto"
+ },
+ "two": {
+ "msg": "{Minutes} minuti"
+ },
+ "few": {
+ "msg": "{Minutes} minute"
+ },
+ "other": {
+ "msg": "{Minutes} minut"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Minutes",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "minutes"
+ }
+ ]
+ },
+ {
+ "id": "{Seconds} second(s)",
+ "message": "{Seconds} second(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Seconds",
+ "cases": {
+ "one": {
+ "msg": "{Seconds} sekundo"
+ },
+ "two": {
+ "msg": "{Seconds} sekundi"
+ },
+ "few": {
+ "msg": "{Seconds} sekunde"
+ },
+ "other": {
+ "msg": "{Seconds} sekund"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Seconds",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "seconds"
+ }
+ ]
+ },
+ {
+ "id": "{Timestamp} ago",
+ "message": "{Timestamp} ago",
+ "translation": "{Timestamp} nazaj",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Timestamp",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "timestamp"
+ }
+ ]
+ },
+ {
+ "id": "{Bytes} B",
+ "message": "{Bytes} B",
+ "translation": "{Bytes} B",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Bytes",
+ "string": "%[1]d",
+ "type": "golang.zx2c4.com/wireguard/windows/conf.Bytes",
+ "underlyingType": "uint64",
+ "argNum": 1,
+ "expr": "b"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024} KiB",
+ "message": "{Float64b__1024} KiB",
+ "translation": "{Float64b__1024} KiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024} MiB",
+ "message": "{Float64b__1024__1024} MiB",
+ "translation": "{Float64b__1024__1024} MiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024} GiB",
+ "message": "{Float64b__1024__1024__1024} GiB",
+ "translation": "{Float64b__1024__1024__1024} GiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024__1024} TiB",
+ "message": "{Float64b__1024__1024__1024__1024} TiB",
+ "translation": "{Float64b__1024__1024__1024__1024} TiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Why}: {Offender}",
+ "message": "{Why}: {Offender}",
+ "translation": "{Why}: {Offender}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Why",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "e.why"
+ },
+ {
+ "id": "Offender",
+ "string": "%[2]q",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "e.offender"
+ }
+ ]
+ },
+ {
+ "id": "Invalid IP address",
+ "message": "Invalid IP address",
+ "translation": "Napačen naslov IP",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid network prefix length",
+ "message": "Invalid network prefix length",
+ "translation": "Napačna dolžina predpone omrežja",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Missing port from endpoint",
+ "message": "Missing port from endpoint",
+ "translation": "Pri končni točki manjkajo vrata",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid endpoint host",
+ "message": "Invalid endpoint host",
+ "translation": "Pri končni točki je gostitelj napačen",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Brackets must contain an IPv6 address",
+ "message": "Brackets must contain an IPv6 address",
+ "translation": "Oklepaji morajo vsebovati naslov IPv6",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid MTU",
+ "message": "Invalid MTU",
+ "translation": "Napačen MTU",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid port",
+ "message": "Invalid port",
+ "translation": "Napačna vrata",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid persistent keepalive",
+ "message": "Invalid persistent keepalive",
+ "translation": "Napačno trajno ohranjanje povezave",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key: {Err}",
+ "message": "Invalid key: {Err}",
+ "translation": "Napačen ključ: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Keys must decode to exactly 32 bytes",
+ "message": "Keys must decode to exactly 32 bytes",
+ "translation": "Dekodirani ključi morajo biti natanko 32 bajtov",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Number must be a number between 0 and 2^64-1: {Err}",
+ "message": "Number must be a number between 0 and 2^64-1: {Err}",
+ "translation": "Številka mora biti število med 0 in 2^64-1: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Two commas in a row",
+ "message": "Two commas in a row",
+ "translation": "Dve zaporedni vejici",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name is not valid",
+ "message": "Tunnel name is not valid",
+ "translation": "Ime tunela ni veljavno",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Line must occur in a section",
+ "message": "Line must occur in a section",
+ "translation": "Vrstica mora biti v odseku",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Config key is missing an equals separator",
+ "message": "Config key is missing an equals separator",
+ "translation": "Ključu v konfiguraciji manjka ločilo enačaj",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Key must have a value",
+ "message": "Key must have a value",
+ "translation": "Ključ mora imeti vrednost",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Interface] section",
+ "message": "Invalid key for [Interface] section",
+ "translation": "Napačen ključ za odsek [Interface]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Peer] section",
+ "message": "Invalid key for [Peer] section",
+ "translation": "Napačen ključ za odsek [Peer]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An interface must have a private key",
+ "message": "An interface must have a private key",
+ "translation": "Vmesnik mora imeti zasebni ključ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[none specified]",
+ "message": "[none specified]",
+ "translation": "[ni navedeno]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "All peers must have public keys",
+ "message": "All peers must have public keys",
+ "translation": "Vsi vrstniki morajo imeti javni ključ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error in getting configuration",
+ "message": "Error in getting configuration",
+ "translation": "Napaka pri branju konfiguracije",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for interface section",
+ "message": "Invalid key for interface section",
+ "translation": "Napačen ključ za odsek vmesnika",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Protocol version must be 1",
+ "message": "Protocol version must be 1",
+ "translation": "Verzija protokola mora biti 1",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for peer section",
+ "message": "Invalid key for peer section",
+ "translation": "Napačen ključ za odsek vrstnika",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[EnumerationSeparator]",
+ "message": "[EnumerationSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert between items when listing - most western languages will translate ‘[EnumerationSeparator]’ into ‘, ’ to produce lists like ‘apple, orange, strawberry’. Eastern languages might translate into ‘、’ to produce lists like ‘リンゴ、オレンジ、イチゴ’."
+ },
+ {
+ "id": "[UnitSeparator]",
+ "message": "[UnitSeparator]",
+ "translation": " ",
+ "comment": "Text to insert when combining units of a measure - most languages will translate ‘[UnitSeparator]’ into ‘ ’ (space) to produce lists like ‘2 minuti 30 sekund’, or empty string ‘’ to produce ‘2分30秒’."
+ },
+ {
+ "id": "About WireGuard",
+ "message": "About WireGuard",
+ "translation": "O WireGuardu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard logo image",
+ "message": "WireGuard logo image",
+ "translation": "Slika WireGuardovega logotipa",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "message": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "translation": "Verzija aplikacije: {Number}\nVerzija wireguard-go: {WireGuardGoVersion}\nVerzija Go: {Version_go}-{GOARCH}\nOperacijski sistem: {OsName}\nArhitektura: {NativeArch}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Number",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "version.Number"
+ },
+ {
+ "id": "WireGuardGoVersion",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "device.WireGuardGoVersion"
+ },
+ {
+ "id": "Version_go",
+ "string": "%[3]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 3,
+ "expr": "strings.TrimPrefix(runtime.Version(), \"go\")"
+ },
+ {
+ "id": "GOARCH",
+ "string": "%[4]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 4,
+ "expr": "runtime.GOARCH"
+ },
+ {
+ "id": "OsName",
+ "string": "%[5]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 5,
+ "expr": "version.OsName()"
+ },
+ {
+ "id": "NativeArch",
+ "string": "%[6]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 6,
+ "expr": "version.NativeArch()"
+ }
+ ]
+ },
+ {
+ "id": "Close",
+ "message": "Close",
+ "translation": "Zapri",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "♥ &Donate!",
+ "message": "♥ &Donate!",
+ "translation": "♥ &Doniraj!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status:",
+ "message": "Status:",
+ "translation": "Status:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Deactivate",
+ "message": "&Deactivate",
+ "translation": "&Deaktiviraj",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Activate",
+ "message": "&Activate",
+ "translation": "&Aktiviraj",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Public key:",
+ "message": "Public key:",
+ "translation": "Javni ključ:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Listen port:",
+ "message": "Listen port:",
+ "translation": "Vrata poslušanja:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "MTU:",
+ "message": "MTU:",
+ "translation": "MTU:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses:",
+ "message": "Addresses:",
+ "translation": "Naslovi:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "DNS servers:",
+ "message": "DNS servers:",
+ "translation": "Strežniki DNS:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Scripts:",
+ "message": "Scripts:",
+ "translation": "Skripta:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Preshared key:",
+ "message": "Preshared key:",
+ "translation": "Ključ v skupni rabi:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Allowed IPs:",
+ "message": "Allowed IPs:",
+ "translation": "Dovoljeni IP-ji:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Endpoint:",
+ "message": "Endpoint:",
+ "translation": "Končna točka:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Persistent keepalive:",
+ "message": "Persistent keepalive:",
+ "translation": "Trajno ohranjanje povezave:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Latest handshake:",
+ "message": "Latest handshake:",
+ "translation": "Zadnje rokovanje:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Transfer:",
+ "message": "Transfer:",
+ "translation": "Prenos:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-up",
+ "message": "pre-up",
+ "translation": "pred-aktivacijo",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-up",
+ "message": "post-up",
+ "translation": "po-aktivaciji",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-down",
+ "message": "pre-down",
+ "translation": "pred-deaktivacijo",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-down",
+ "message": "post-down",
+ "translation": "po-deaktivaciji",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "disabled, per policy",
+ "message": "disabled, per policy",
+ "translation": "onemogočeno, zaradi politike",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "enabled",
+ "message": "enabled",
+ "translation": "omogočeno",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{String} received, {String_1} sent",
+ "message": "{String} received, {String_1} sent",
+ "translation": "{String} prejeto, {String_1} poslano",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "String",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "c.RxBytes.String()"
+ },
+ {
+ "id": "String_1",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "c.TxBytes.String()"
+ }
+ ]
+ },
+ {
+ "id": "Failed to determine tunnel state",
+ "message": "Failed to determine tunnel state",
+ "translation": "Napaka pri določanju stanja tunela",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to activate tunnel",
+ "message": "Failed to activate tunnel",
+ "translation": "Napaka pri aktiviranju tunela",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to deactivate tunnel",
+ "message": "Failed to deactivate tunnel",
+ "translation": "Napaka pri deaktiviranju tunela",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Interface: {Name}",
+ "message": "Interface: {Name}",
+ "translation": "Vmesnik: {Name}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "config.Name"
+ }
+ ]
+ },
+ {
+ "id": "Peer",
+ "message": "Peer",
+ "translation": "Vrstnik",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Create new tunnel",
+ "message": "Create new tunnel",
+ "translation": "Ustvari nov tunel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit tunnel",
+ "message": "Edit tunnel",
+ "translation": "Uredi tunel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Name:",
+ "message": "&Name:",
+ "translation": "&Ime:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Public key:",
+ "message": "&Public key:",
+ "translation": "&Javni ključ:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(unknown)",
+ "message": "(unknown)",
+ "translation": "(neznano)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Block untunneled traffic (kill-switch)",
+ "message": "&Block untunneled traffic (kill-switch)",
+ "translation": "&Blokiraj promet izven tunela (varovalka)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.",
+ "message": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.",
+ "translation": "Kadar ima konfiguracija natanko enega vrstnika in njegov spisek dovoljenih IP-jev vsebuje vsaj enega izmed 0.0.0.0/0 ali ::/0, bo storitev tunela vzpostavila pravila požarnega zidu, ki bodo blokirala ves promet, ki ni niti za niti iz vmesnika tunela oz. za napačen strežnik DNS, s posebnimi izjemami za DHCP and NDP.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save",
+ "message": "&Save",
+ "translation": "&Shrani",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Cancel",
+ "message": "Cancel",
+ "translation": "Prekliči",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Configuration:",
+ "message": "&Configuration:",
+ "translation": "&Konfiguracija:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid name",
+ "message": "Invalid name",
+ "translation": "Napačno ime",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A name is required.",
+ "message": "A name is required.",
+ "translation": "Ime je obvezno.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name ‘{NewName}’ is invalid.",
+ "message": "Tunnel name ‘{NewName}’ is invalid.",
+ "translation": "Ime tunela »{NewName}« ni veljavno.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to list existing tunnels",
+ "message": "Unable to list existing tunnels",
+ "translation": "Napaka pri pripravi seznama obstoječih tunelov",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel already exists",
+ "message": "Tunnel already exists",
+ "translation": "Tunel že obstaja",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{NewName}’.",
+ "message": "Another tunnel already exists with the name ‘{NewName}’.",
+ "translation": "Drug tunel z imenom »{NewName}« že obstaja.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create new configuration",
+ "message": "Unable to create new configuration",
+ "translation": "Napaka pri izdelavi nove konfiguracije",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Writing file failed",
+ "message": "Writing file failed",
+ "translation": "Napaka pri pisanju v datoteko",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "message": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "translation": "Datoteka »{FilePath}« že obstaja.\n\nAli jo želite prepisati?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "FilePath",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "filePath"
+ }
+ ]
+ },
+ {
+ "id": "Active",
+ "message": "Active",
+ "translation": "Aktivno",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Activating",
+ "message": "Activating",
+ "translation": "Se aktivira",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Inactive",
+ "message": "Inactive",
+ "translation": "Neaktivno",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Deactivating",
+ "message": "Deactivating",
+ "translation": "Se deaktivira",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unknown state",
+ "message": "Unknown state",
+ "translation": "Neznano stanje",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log",
+ "message": "Log",
+ "translation": "Dnevnik",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Copy",
+ "message": "&Copy",
+ "translation": "&Kopiraj",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Select &all",
+ "message": "Select &all",
+ "translation": "&Izberi vse",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save to file…",
+ "message": "&Save to file…",
+ "translation": "&Shrani v datoteko …",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Time",
+ "message": "Time",
+ "translation": "Čas",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log message",
+ "message": "Log message",
+ "translation": "Sporočilo v dnevniku",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "message": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "translation": "Tekstovne datoteke (*.txt)|*.txt|Vse datoteke (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export log to file",
+ "message": "Export log to file",
+ "translation": "Izvozi dnevnik v datoteko",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&About WireGuard…",
+ "message": "&About WireGuard…",
+ "translation": "O WireGu&ardu …",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel Error",
+ "message": "Tunnel Error",
+ "translation": "Napaka tunela",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "message": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "translation": "{ErrMsg}\n\nDodatne informacije najdete v dnevniku.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "ErrMsg",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errMsg"
+ }
+ ]
+ },
+ {
+ "id": "{Title} (out of date)",
+ "message": "{Title} (out of date)",
+ "translation": "{Title} (neposodobljen)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Detection Error",
+ "message": "WireGuard Detection Error",
+ "translation": "Napaka zaznavanja WireGuarda",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to wait for WireGuard window to appear: {Err}",
+ "message": "Unable to wait for WireGuard window to appear: {Err}",
+ "translation": "Čakanje, da se pojavi WireGuardovo okno, ni možno: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard: Deactivated",
+ "message": "WireGuard: Deactivated",
+ "translation": "WireGuard: Deaktiviran",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Unknown",
+ "message": "Status: Unknown",
+ "translation": "Status: Neznan",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses: None",
+ "message": "Addresses: None",
+ "translation": "Naslovi: Brez",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Manage tunnels…",
+ "message": "&Manage tunnels…",
+ "translation": "&Upravljaj tunele …",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Import tunnel(s) from file…",
+ "message": "&Import tunnel(s) from file…",
+ "translation": "&Uvozi tunel(e) iz datoteke…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "E&xit",
+ "message": "E&xit",
+ "translation": "I&zhod",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Tunnels",
+ "message": "&Tunnels",
+ "translation": "&Tuneli",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Activated",
+ "message": "WireGuard Activated",
+ "translation": "WireGuard aktiviran",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been activated.",
+ "message": "The {Name} tunnel has been activated.",
+ "translation": "Tunel {Name} je bil aktiviran.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Deactivated",
+ "message": "WireGuard Deactivated",
+ "translation": "WireGuard deaktiviran",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been deactivated.",
+ "message": "The {Name} tunnel has been deactivated.",
+ "translation": "Tunel {Name} je bil deaktiviran.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Tunnel Error",
+ "message": "WireGuard Tunnel Error",
+ "translation": "Napaka tunela WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard: {TextForStateglobalState_true}",
+ "message": "WireGuard: {TextForStateglobalState_true}",
+ "translation": "WireGuard: {TextForStateglobalState_true}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TextForStateglobalState_true",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "textForState(globalState, true)"
+ }
+ ]
+ },
+ {
+ "id": "Status: {StateText}",
+ "message": "Status: {StateText}",
+ "translation": "Status: {StateText}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "StateText",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "stateText"
+ }
+ ]
+ },
+ {
+ "id": "Addresses: {EnumerationSeparator}",
+ "message": "Addresses: {EnumerationSeparator}",
+ "translation": "Naslovi: {EnumerationSeparator}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "EnumerationSeparator",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "strings.Join(addrs, l18n.EnumerationSeparator())"
+ }
+ ]
+ },
+ {
+ "id": "An Update is Available!",
+ "message": "An Update is Available!",
+ "translation": "Na voljo je posodobitev!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Update Available",
+ "message": "WireGuard Update Available",
+ "translation": "Posodobitev WireGuarda je na voljo",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "message": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "translation": "Posodobitev WireGuarda je na voljo. Svetujemo posodobitev čim prej.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnels",
+ "message": "Tunnels",
+ "translation": "Tuneli",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Edit",
+ "message": "&Edit",
+ "translation": "&Uredi",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add &empty tunnel…",
+ "message": "Add &empty tunnel…",
+ "translation": "Dodaj &prazen tunel …",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add Tunnel",
+ "message": "Add Tunnel",
+ "translation": "Dodaj tunel",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Remove selected tunnel(s)",
+ "message": "Remove selected tunnel(s)",
+ "translation": "Odstrani izbrane tunele",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to zip",
+ "message": "Export all tunnels to zip",
+ "translation": "Izvozi vse tunele v zip",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Toggle",
+ "message": "&Toggle",
+ "translation": "&Preklopi",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to &zip…",
+ "message": "Export all tunnels to &zip…",
+ "translation": "Izvozi vse tunele v &zip …",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit &selected tunnel…",
+ "message": "Edit &selected tunnel…",
+ "translation": "Uredi &izbran tunel …",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Remove selected tunnel(s)",
+ "message": "&Remove selected tunnel(s)",
+ "translation": "Odst&rani izbrane tunele",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "no configuration files were found",
+ "message": "no configuration files were found",
+ "translation": "ni najdenih konfiguracijskih datotek",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Could not import selected configuration: {LastErr}",
+ "message": "Could not import selected configuration: {LastErr}",
+ "translation": "Napaka pri uvozu izbrane konfiguracije: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Could not enumerate existing tunnels: {LastErr}",
+ "message": "Could not enumerate existing tunnels: {LastErr}",
+ "translation": "Napaka pri preštevanju obstoječih tunelov: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{Name}’",
+ "message": "Another tunnel already exists with the name ‘{Name}’",
+ "translation": "Tunel z imenom »{Name}« že obstaja",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "unparsedConfig.Name"
+ }
+ ]
+ },
+ {
+ "id": "Unable to import configuration: {LastErr}",
+ "message": "Unable to import configuration: {LastErr}",
+ "translation": "Napaka pri uvozu konfiguracije: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Imported tunnels",
+ "message": "Imported tunnels",
+ "translation": "Uvoženi tuneli",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Imported {M} tunnels",
+ "message": "Imported {M} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "M",
+ "cases": {
+ "one": {
+ "msg": "Uvožen {M} tunel"
+ },
+ "two": {
+ "msg": "Uvožena {M} tunela"
+ },
+ "few": {
+ "msg": "Uvoženi {M} tuneli"
+ },
+ "other": {
+ "msg": "Uvoženo {M} tunelov"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ }
+ ]
+ },
+ {
+ "id": "Imported {M} of {N} tunnels",
+ "message": "Imported {M} of {N} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "N",
+ "cases": {
+ "one": {
+ "msg": "Uvožen {M} od {N} tunelov"
+ },
+ "two": {
+ "msg": "Uvožena {M} od {N} tunelov"
+ },
+ "few": {
+ "msg": "Uvoženi {M} od {N} tunelov"
+ },
+ "other": {
+ "msg": "Uvoženo {M} od {N} tunelov"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ },
+ {
+ "id": "N",
+ "string": "%[2]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 2,
+ "expr": "n"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create tunnel",
+ "message": "Unable to create tunnel",
+ "translation": "Tunela ni bilo mogoče ustvariti",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Delete {TunnelCount} tunnels",
+ "message": "Delete {TunnelCount} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "Izbriši {TunnelCount} tunel"
+ },
+ "two": {
+ "msg": "Izbriši {TunnelCount} tunela"
+ },
+ "few": {
+ "msg": "Izbriši {TunnelCount} tunele"
+ },
+ "other": {
+ "msg": "Izbriši {TunnelCount} tunelov"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "message": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "Ali ste prepričani, da želite izbrisati {TunnelCount} tunel?"
+ },
+ "two": {
+ "msg": "Ali ste prepričani, da želite izbrisati {TunnelCount} tunela?"
+ },
+ "few": {
+ "msg": "Ali ste prepričani, da želite izbrisati {TunnelCount} tunele?"
+ },
+ "other": {
+ "msg": "Ali ste prepričani, da želite izbrisati {TunnelCount} tunelov?"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Delete tunnel ‘{TunnelName}’",
+ "message": "Delete tunnel ‘{TunnelName}’",
+ "translation": "Izbriši tunel ‘{TunnelName}’",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "message": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "translation": "Ali ste prepričani, da želite izbrisati tunel »{TunnelName}«?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "{Question} You cannot undo this action.",
+ "message": "{Question} You cannot undo this action.",
+ "translation": "{Question} Tega dejanja ne morete razveljaviti.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Question",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "question"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnel",
+ "message": "Unable to delete tunnel",
+ "translation": "Napaka pri izbrisu tunela",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A tunnel was unable to be removed: {Error}",
+ "message": "A tunnel was unable to be removed: {Error}",
+ "translation": "Napaka pri odstranjevanju tunela: {Error}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Error",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errors[0].Error()"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnels",
+ "message": "Unable to delete tunnels",
+ "translation": "Napaka pri izbrisu tunelov",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Lenerrors} tunnels were unable to be removed.",
+ "message": "{Lenerrors} tunnels were unable to be removed.",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Lenerrors",
+ "cases": {
+ "one": {
+ "msg": "{Lenerrors} tunela ni bilo mogoče odstraniti."
+ },
+ "two": {
+ "msg": "{Lenerrors} tunelov ni bilo mogoče odstraniti."
+ },
+ "few": {
+ "msg": "{Lenerrors} tunelov ni bilo mogoče odstraniti."
+ },
+ "other": {
+ "msg": "{Lenerrors} tunelov ni bilo mogoče odstraniti."
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Lenerrors",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "len(errors)"
+ }
+ ]
+ },
+ {
+ "id": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "message": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "translation": "Konfiguracijske datoteke (*.zip, *.conf)|*.zip;*.conf|Vse datoteke (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Import tunnel(s) from file",
+ "message": "Import tunnel(s) from file",
+ "translation": "Uvozi tunele iz datoteke",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Configuration ZIP Files (*.zip)|*.zip",
+ "message": "Configuration ZIP Files (*.zip)|*.zip",
+ "translation": "Konfiguracijske datoteke ZIP (*.zip)|*.zip",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export tunnels to zip",
+ "message": "Export tunnels to zip",
+ "translation": "Izvozi tunele v datoteko zip",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Title} (unsigned build, no updates)",
+ "message": "{Title} (unsigned build, no updates)",
+ "translation": "{Title} (nepodpisane različice, brez posodobitev)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "Error Exiting WireGuard",
+ "message": "Error Exiting WireGuard",
+ "translation": "Napaka pri izhodu iz WireGuarda",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "message": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "translation": "Storitve ni bilo mogoče zaustaviti, ker: {Err}. Poskusite zaustaviti WireGuard z uporabo programa Storitve.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "message": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "translation": "Posodobitev WireGuarda je na voljo. Zelo priporočamo posodobitev brez odlašanja.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for user",
+ "message": "Status: Waiting for user",
+ "translation": "Status: Čaka na uporabnika",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Update Now",
+ "message": "Update Now",
+ "translation": "Posodobi zdaj",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for updater service",
+ "message": "Status: Waiting for updater service",
+ "translation": "Status: Čaka na servis za posodobitev",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error: {Err}. Please try again.",
+ "message": "Error: {Err}. Please try again.",
+ "translation": "Napaka: {Err}. Poskusite ponovno.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Status: Complete!",
+ "message": "Status: Complete!",
+ "translation": "Status: Končano!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "http2: Framer {F}: failed to decode just-written frame",
+ "message": "http2: Framer {F}: failed to decode just-written frame",
+ "translation": "http2: Framer {F}: napaka pri dekodiranju ravnokar zapisanega okvirja",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ }
+ ]
+ },
+ {
+ "id": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "message": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "translation": "http2: Framer {F}: zapisano {Http2summarizeFramefr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ },
+ {
+ "id": "Http2summarizeFramefr",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(fr)"
+ }
+ ]
+ },
+ {
+ "id": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "message": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "translation": "http2: Framer {Fr}: prebrano {Http2summarizeFramef}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Fr",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "fr"
+ },
+ {
+ "id": "Http2summarizeFramef",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(f)"
+ }
+ ]
+ },
+ {
+ "id": "http2: decoded hpack field {HeaderField}",
+ "message": "http2: decoded hpack field {HeaderField}",
+ "translation": "http2: dekodirano polje hpack {HeaderField}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "HeaderField",
+ "string": "%+[1]v",
+ "type": "vendor/golang.org/x/net/http2/hpack.HeaderField",
+ "underlyingType": "struct{Name string; Value string; Sensitive bool}",
+ "argNum": 1,
+ "expr": "hf"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/locales/tr/messages.gotext.json b/locales/tr/messages.gotext.json
new file mode 100644
index 00000000..d51b67e8
--- /dev/null
+++ b/locales/tr/messages.gotext.json
@@ -0,0 +1,1847 @@
+{
+ "language": "tr",
+ "messages": [
+ {
+ "id": "Error",
+ "message": "Error",
+ "translation": "Hata",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(no argument): elevate and install manager service",
+ "message": "(no argument): elevate and install manager service",
+ "translation": "(argüman verilmediyse): gerekli izinleri al ve yönetim hizmetini kur",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Usage: {Args0} [\n{String}]",
+ "message": "Usage: {Args0} [\n{String}]",
+ "translation": "Kullanım: {Args0} [\n{String}]",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Args0",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "os.Args[0]"
+ },
+ {
+ "id": "String",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "builder.String()"
+ }
+ ]
+ },
+ {
+ "id": "Command Line Options",
+ "message": "Command Line Options",
+ "translation": "Komut Satırı Seçenekleri",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to determine whether the process is running under WOW64: {Err}",
+ "message": "Unable to determine whether the process is running under WOW64: {Err}",
+ "translation": "İşlemin WOW64 altında çalıştığından emin olunamadı: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "You must use the native version of WireGuard on this computer.",
+ "message": "You must use the native version of WireGuard on this computer.",
+ "translation": "Bu bilgisayarda WireGuard'ın yerel sürümünü kullanmanız gerek.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to open current process token: {Err}",
+ "message": "Unable to open current process token: {Err}",
+ "translation": "Şu anki işlem jetonu açılamadı: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard sadece sisteme yerleşik {AdminGroupName} grubunun üyeleri tarafından kullanılabilir.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard çalışıyor, fakat kullanıcı arayüzü sadece sisteme yerleşik {AdminGroupName} grubunun üyesi olan kullanıcılar tarafından masaüstünde erişilebilir.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard system tray icon did not appear after 30 seconds.",
+ "message": "WireGuard system tray icon did not appear after 30 seconds.",
+ "translation": "WireGuard sistem tepsisi ikonu 30 saniye sonunda belirmedi.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Now",
+ "message": "Now",
+ "translation": "Şimdi",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "System clock wound backward!",
+ "message": "System clock wound backward!",
+ "translation": "Sistem saati geriye sarılmış!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Years} year(s)",
+ "message": "{Years} year(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Years",
+ "cases": {
+ "one": {
+ "msg": "{Years} yıl"
+ },
+ "other": {
+ "msg": "{Years} yıl"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Years",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "years"
+ }
+ ]
+ },
+ {
+ "id": "{Days} day(s)",
+ "message": "{Days} day(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Days",
+ "cases": {
+ "one": {
+ "msg": "{Days} gün"
+ },
+ "other": {
+ "msg": "{Days} gün"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Days",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "days"
+ }
+ ]
+ },
+ {
+ "id": "{Hours} hour(s)",
+ "message": "{Hours} hour(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Hours",
+ "cases": {
+ "one": {
+ "msg": "{Hours} saat"
+ },
+ "other": {
+ "msg": "{Hours} saat"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Hours",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "hours"
+ }
+ ]
+ },
+ {
+ "id": "{Minutes} minute(s)",
+ "message": "{Minutes} minute(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Minutes",
+ "cases": {
+ "one": {
+ "msg": "{Minutes} dakika"
+ },
+ "other": {
+ "msg": "{Minutes} dakika"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Minutes",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "minutes"
+ }
+ ]
+ },
+ {
+ "id": "{Seconds} second(s)",
+ "message": "{Seconds} second(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Seconds",
+ "cases": {
+ "one": {
+ "msg": "{Seconds} saniye"
+ },
+ "other": {
+ "msg": "{Seconds} saniye"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Seconds",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "seconds"
+ }
+ ]
+ },
+ {
+ "id": "{Timestamp} ago",
+ "message": "{Timestamp} ago",
+ "translation": "{Timestamp} önce",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Timestamp",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "timestamp"
+ }
+ ]
+ },
+ {
+ "id": "{Bytes} B",
+ "message": "{Bytes} B",
+ "translation": "{Bytes} B",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Bytes",
+ "string": "%[1]d",
+ "type": "golang.zx2c4.com/wireguard/windows/conf.Bytes",
+ "underlyingType": "uint64",
+ "argNum": 1,
+ "expr": "b"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024} KiB",
+ "message": "{Float64b__1024} KiB",
+ "translation": "{Float64b__1024} KiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024} MiB",
+ "message": "{Float64b__1024__1024} MiB",
+ "translation": "{Float64b__1024__1024} MiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024} GiB",
+ "message": "{Float64b__1024__1024__1024} GiB",
+ "translation": "{Float64b__1024__1024__1024} GiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024__1024} TiB",
+ "message": "{Float64b__1024__1024__1024__1024} TiB",
+ "translation": "{Float64b__1024__1024__1024__1024} TiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Why}: {Offender}",
+ "message": "{Why}: {Offender}",
+ "translation": "{Why}: {Offender}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Why",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "e.why"
+ },
+ {
+ "id": "Offender",
+ "string": "%[2]q",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "e.offender"
+ }
+ ]
+ },
+ {
+ "id": "Invalid IP address",
+ "message": "Invalid IP address",
+ "translation": "Geçersiz IP adresi",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid network prefix length",
+ "message": "Invalid network prefix length",
+ "translation": "Ağ öneki uzunluğu geçersiz",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Missing port from endpoint",
+ "message": "Missing port from endpoint",
+ "translation": "Uç nokta port ayarı eksik",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid endpoint host",
+ "message": "Invalid endpoint host",
+ "translation": "Geçersiz uç nokta",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Brackets must contain an IPv6 address",
+ "message": "Brackets must contain an IPv6 address",
+ "translation": "Parantezler bir IPv6 adresi içermelidir",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid MTU",
+ "message": "Invalid MTU",
+ "translation": "Geçersiz MTU",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid port",
+ "message": "Invalid port",
+ "translation": "Geçersiz port",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid persistent keepalive",
+ "message": "Invalid persistent keepalive",
+ "translation": "Geçersiz kalıcı açık bırakma",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key: {Err}",
+ "message": "Invalid key: {Err}",
+ "translation": "Geçersiz anahtar: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Keys must decode to exactly 32 bytes",
+ "message": "Keys must decode to exactly 32 bytes",
+ "translation": "Anahtarlar çözüldüğünde tam 32 byte olmalı",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Number must be a number between 0 and 2^64-1: {Err}",
+ "message": "Number must be a number between 0 and 2^64-1: {Err}",
+ "translation": "Sayı 0 ve 2^64-1 arasında bir sayı olmalı: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Two commas in a row",
+ "message": "Two commas in a row",
+ "translation": "Yan yana iki virgül",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name is not valid",
+ "message": "Tunnel name is not valid",
+ "translation": "Tünel adı geçerli değil",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Line must occur in a section",
+ "message": "Line must occur in a section",
+ "translation": "Satır bir bölümde olmalı",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Config key is missing an equals separator",
+ "message": "Config key is missing an equals separator",
+ "translation": "Yapılandırma anahtarında eşittir operatörü eksik",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Key must have a value",
+ "message": "Key must have a value",
+ "translation": "Anahtar bir değere sahip olmalı",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Interface] section",
+ "message": "Invalid key for [Interface] section",
+ "translation": "[Interface] bölümü için geçersiz anahtar",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Peer] section",
+ "message": "Invalid key for [Peer] section",
+ "translation": "[Peer] bölümü için geçersiz anahtar",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An interface must have a private key",
+ "message": "An interface must have a private key",
+ "translation": "Bir arabirim gizli anahtara sahip olmalıdır",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[none specified]",
+ "message": "[none specified]",
+ "translation": "[hiçbir şey belirtilmemiş}",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "All peers must have public keys",
+ "message": "All peers must have public keys",
+ "translation": "Tüm eşler açık anahtarlara sahip olmalı",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error in getting configuration",
+ "message": "Error in getting configuration",
+ "translation": "Yapılandırma bilgisi alınırken hata oluştu",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for interface section",
+ "message": "Invalid key for interface section",
+ "translation": "Arabirim bölümünde geçersiz anahtar",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Protocol version must be 1",
+ "message": "Protocol version must be 1",
+ "translation": "Protokol sürümü 1 olmak zorunda",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for peer section",
+ "message": "Invalid key for peer section",
+ "translation": "Eş bölümünde geçersiz anahtar",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[EnumerationSeparator]",
+ "message": "[EnumerationSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert between items when listing - most western languages will translate ‘[EnumerationSeparator]’ into ‘, ’ to produce lists like ‘apple, orange, strawberry’. Eastern languages might translate into ‘、’ to produce lists like ‘リンゴ、オレンジ、イチゴ’."
+ },
+ {
+ "id": "[UnitSeparator]",
+ "message": "[UnitSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert when combining units of a measure - most languages will translate ‘[UnitSeparator]’ into ‘ ’ (space) to produce lists like ‘2 minuti 30 sekund’, or empty string ‘’ to produce ‘2分30秒’."
+ },
+ {
+ "id": "About WireGuard",
+ "message": "About WireGuard",
+ "translation": "WireGuard Hakkında",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard logo image",
+ "message": "WireGuard logo image",
+ "translation": "WireGuard logo resmi",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "message": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "translation": "Uygulama sürümü: {Number}\nGo arkauç sürümü: {WireGuardGoVersion}\nGo sürümü: {Version_go}-{GOARCH}\nİşletim sistemi: {OsName}\nMimari: {NativeArch}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Number",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "version.Number"
+ },
+ {
+ "id": "WireGuardGoVersion",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "device.WireGuardGoVersion"
+ },
+ {
+ "id": "Version_go",
+ "string": "%[3]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 3,
+ "expr": "strings.TrimPrefix(runtime.Version(), \"go\")"
+ },
+ {
+ "id": "GOARCH",
+ "string": "%[4]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 4,
+ "expr": "runtime.GOARCH"
+ },
+ {
+ "id": "OsName",
+ "string": "%[5]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 5,
+ "expr": "version.OsName()"
+ },
+ {
+ "id": "NativeArch",
+ "string": "%[6]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 6,
+ "expr": "version.NativeArch()"
+ }
+ ]
+ },
+ {
+ "id": "Close",
+ "message": "Close",
+ "translation": "Kapat",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "♥ &Donate!",
+ "message": "♥ &Donate!",
+ "translation": "♥ &Bağış yap!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status:",
+ "message": "Status:",
+ "translation": "Durum:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Deactivate",
+ "message": "&Deactivate",
+ "translation": "&Devre dışı bırak",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Activate",
+ "message": "&Activate",
+ "translation": "&Etkinleştir",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Public key:",
+ "message": "Public key:",
+ "translation": "Açık anahtar:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Listen port:",
+ "message": "Listen port:",
+ "translation": "Dinlenen port:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "MTU:",
+ "message": "MTU:",
+ "translation": "MTU:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses:",
+ "message": "Addresses:",
+ "translation": "Adresler:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "DNS servers:",
+ "message": "DNS servers:",
+ "translation": "DNS sunucuları:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Scripts:",
+ "message": "Scripts:",
+ "translation": "Komut dosyaları:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Preshared key:",
+ "message": "Preshared key:",
+ "translation": "Önceden paylaşılmış anahtar:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Allowed IPs:",
+ "message": "Allowed IPs:",
+ "translation": "İzin verilen IP'ler:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Endpoint:",
+ "message": "Endpoint:",
+ "translation": "Uç nokta:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Persistent keepalive:",
+ "message": "Persistent keepalive:",
+ "translation": "Kalıcı açık tutma:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Latest handshake:",
+ "message": "Latest handshake:",
+ "translation": "En son el sıkışma:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Transfer:",
+ "message": "Transfer:",
+ "translation": "Aktarım:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-up",
+ "message": "pre-up",
+ "translation": "bağlantı-öncesi",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-up",
+ "message": "post-up",
+ "translation": "bağlantı-sonrası",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-down",
+ "message": "pre-down",
+ "translation": "bağlantı-kesme-öncesi",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-down",
+ "message": "post-down",
+ "translation": "bağlantı-kesme-sonrası",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "disabled, per policy",
+ "message": "disabled, per policy",
+ "translation": "ilke gereği kapalı",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "enabled",
+ "message": "enabled",
+ "translation": "etkin",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{String} received, {String_1} sent",
+ "message": "{String} received, {String_1} sent",
+ "translation": "{String} alındı, {String_1} gönderildi",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "String",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "c.RxBytes.String()"
+ },
+ {
+ "id": "String_1",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "c.TxBytes.String()"
+ }
+ ]
+ },
+ {
+ "id": "Failed to determine tunnel state",
+ "message": "Failed to determine tunnel state",
+ "translation": "Tünel durumu belirlenemedi",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to activate tunnel",
+ "message": "Failed to activate tunnel",
+ "translation": "Tünel etkinleştirilemedi",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to deactivate tunnel",
+ "message": "Failed to deactivate tunnel",
+ "translation": "Tünel devre dışı bırakılamadı",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Interface: {Name}",
+ "message": "Interface: {Name}",
+ "translation": "Arabirim: {Name}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "config.Name"
+ }
+ ]
+ },
+ {
+ "id": "Peer",
+ "message": "Peer",
+ "translation": "Eş",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Create new tunnel",
+ "message": "Create new tunnel",
+ "translation": "Yeni tünel oluştur",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit tunnel",
+ "message": "Edit tunnel",
+ "translation": "Tüneli düzenle",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Name:",
+ "message": "&Name:",
+ "translation": "&İsim:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Public key:",
+ "message": "&Public key:",
+ "translation": "&Açık anahtar:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(unknown)",
+ "message": "(unknown)",
+ "translation": "(bilinmiyor)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Block untunneled traffic (kill-switch)",
+ "message": "&Block untunneled traffic (kill-switch)",
+ "translation": "&Tünelden geçmeyen trafiği durdur (kill-switch)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.",
+ "message": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.",
+ "translation": "Bir yapılandırmada tam bir eş varsa, ve bu eş 0.0.0.0/0 veya ::/0 arasından en az birini içeren izin verilen IPler listesine sahipse, tünel hizmeti DHCP ve NDP için bazı özel istisnalar hariç tünel arabirimine gitmeyen veya bu arabirimden gelmeyen, veya yanlış DNS sunucusuna giden tüm trafiği durdurmak için güvenlik duvarında bir ilke kümesi ayarlar.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save",
+ "message": "&Save",
+ "translation": "&Kaydet",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Cancel",
+ "message": "Cancel",
+ "translation": "İptal",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Configuration:",
+ "message": "&Configuration:",
+ "translation": "&Yapılandırma:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid name",
+ "message": "Invalid name",
+ "translation": "Geçersiz isim",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A name is required.",
+ "message": "A name is required.",
+ "translation": "Bir isim gerekli.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name ‘{NewName}’ is invalid.",
+ "message": "Tunnel name ‘{NewName}’ is invalid.",
+ "translation": "`{NewName}` geçersiz bir tünel ismi.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to list existing tunnels",
+ "message": "Unable to list existing tunnels",
+ "translation": "Mevcut tüneller listelenemiyor",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel already exists",
+ "message": "Tunnel already exists",
+ "translation": "Tünel zaten mevcut",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{NewName}’.",
+ "message": "Another tunnel already exists with the name ‘{NewName}’.",
+ "translation": "‘{NewName}’ adında başka bir tünel mevcut.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create new configuration",
+ "message": "Unable to create new configuration",
+ "translation": "Yeni yapılandırma oluşturulamıyor",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Writing file failed",
+ "message": "Writing file failed",
+ "translation": "Dosya yazılamadı",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "message": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "translation": "`{FilePath}` dosyası zaten mevcut.\n\nÜzerine yazmak ister misiniz?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "FilePath",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "filePath"
+ }
+ ]
+ },
+ {
+ "id": "Active",
+ "message": "Active",
+ "translation": "Etkin",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Activating",
+ "message": "Activating",
+ "translation": "Etkinleştiriliyor",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Inactive",
+ "message": "Inactive",
+ "translation": "Etkin değil",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Deactivating",
+ "message": "Deactivating",
+ "translation": "Devre dışı bırakılıyor",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unknown state",
+ "message": "Unknown state",
+ "translation": "Durum bilinmiyor",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log",
+ "message": "Log",
+ "translation": "Günlük",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Copy",
+ "message": "&Copy",
+ "translation": "Kopyala (&c)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Select &all",
+ "message": "Select &all",
+ "translation": "&tümünü seç",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save to file…",
+ "message": "&Save to file…",
+ "translation": "Dosyaya kaydet (&s)…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Time",
+ "message": "Time",
+ "translation": "Zaman",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log message",
+ "message": "Log message",
+ "translation": "Günlük mesajı",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "message": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "translation": "Metin Dosyaları (*.txt)|*.txt|Tüm Dosyalar (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export log to file",
+ "message": "Export log to file",
+ "translation": "Günlük dosyasını dışa aktar",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&About WireGuard…",
+ "message": "&About WireGuard…",
+ "translation": "WireGuard Hakkında (&a)…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel Error",
+ "message": "Tunnel Error",
+ "translation": "Tünel Hatası",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "message": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "translation": "{ErrMsg}\n\nLütfen daha fazla bilgi için günlüğe göz atın.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "ErrMsg",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errMsg"
+ }
+ ]
+ },
+ {
+ "id": "{Title} (out of date)",
+ "message": "{Title} (out of date)",
+ "translation": "{Title} (eski sürüm)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Detection Error",
+ "message": "WireGuard Detection Error",
+ "translation": "WireGuard Tespit Hatası",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to wait for WireGuard window to appear: {Err}",
+ "message": "Unable to wait for WireGuard window to appear: {Err}",
+ "translation": "WireGuard penceresinin belirmesi beklenemedi: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard: Deactivated",
+ "message": "WireGuard: Deactivated",
+ "translation": "WireGuard: Devre dışı",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Unknown",
+ "message": "Status: Unknown",
+ "translation": "Durum: Bilinmiyor",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses: None",
+ "message": "Addresses: None",
+ "translation": "Adresler: Yok",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Manage tunnels…",
+ "message": "&Manage tunnels…",
+ "translation": "Tünelleri yönet (&m)…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Import tunnel(s) from file…",
+ "message": "&Import tunnel(s) from file…",
+ "translation": "Dosyadan tünelleri içe aktar (&i)…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "E&xit",
+ "message": "E&xit",
+ "translation": "Çık (&x)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Tunnels",
+ "message": "&Tunnels",
+ "translation": "Tüneller (&t)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Activated",
+ "message": "WireGuard Activated",
+ "translation": "WireGuard Etkin",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been activated.",
+ "message": "The {Name} tunnel has been activated.",
+ "translation": "{Name} tüneli etkinleştirildi.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Deactivated",
+ "message": "WireGuard Deactivated",
+ "translation": "WireGuard Devre Dışı Bırakıldı",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been deactivated.",
+ "message": "The {Name} tunnel has been deactivated.",
+ "translation": "{Name} tüneli devre dışı bırakıldı.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Tunnel Error",
+ "message": "WireGuard Tunnel Error",
+ "translation": "WireGuard Tünel Hatası",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard: {TextForStateglobalState_true}",
+ "message": "WireGuard: {TextForStateglobalState_true}",
+ "translation": "WireGuard: {TextForStateglobalState_true}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TextForStateglobalState_true",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "textForState(globalState, true)"
+ }
+ ]
+ },
+ {
+ "id": "Status: {StateText}",
+ "message": "Status: {StateText}",
+ "translation": "Durum: {StateText}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "StateText",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "stateText"
+ }
+ ]
+ },
+ {
+ "id": "Addresses: {EnumerationSeparator}",
+ "message": "Addresses: {EnumerationSeparator}",
+ "translation": "Adresler: {EnumerationSeparator}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "EnumerationSeparator",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "strings.Join(addrs, l18n.EnumerationSeparator())"
+ }
+ ]
+ },
+ {
+ "id": "An Update is Available!",
+ "message": "An Update is Available!",
+ "translation": "Güncelleme Mevcut!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Update Available",
+ "message": "WireGuard Update Available",
+ "translation": "WireGuard Güncellemesi Mevcut",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "message": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "translation": "WireGuard için bir güncelleme mevcut. İlk fırsatta güncelleme yapmanız tavsiye edilir.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnels",
+ "message": "Tunnels",
+ "translation": "Tüneller",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Edit",
+ "message": "&Edit",
+ "translation": "&Edit",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add &empty tunnel…",
+ "message": "Add &empty tunnel…",
+ "translation": "Boş tünel ekle (&e)…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add Tunnel",
+ "message": "Add Tunnel",
+ "translation": "Tünel Ekle",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Remove selected tunnel(s)",
+ "message": "Remove selected tunnel(s)",
+ "translation": "Seçilen tünelleri kaldır",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to zip",
+ "message": "Export all tunnels to zip",
+ "translation": "Tüm tünelleri zip olarak dışa aktar",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Toggle",
+ "message": "&Toggle",
+ "translation": "&Değiştir",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to &zip…",
+ "message": "Export all tunnels to &zip…",
+ "translation": "Tüm tünelleri &zip olarak dışa aktar…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit &selected tunnel…",
+ "message": "Edit &selected tunnel…",
+ "translation": "&Seçilen tüneli düzenle…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Remove selected tunnel(s)",
+ "message": "&Remove selected tunnel(s)",
+ "translation": "S&eçilen tünelleri kaldır",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "no configuration files were found",
+ "message": "no configuration files were found",
+ "translation": "yapılandırma dosyası bulunamadı",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Could not import selected configuration: {LastErr}",
+ "message": "Could not import selected configuration: {LastErr}",
+ "translation": "Seçilen yapılandırma içe aktarılamadı: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Could not enumerate existing tunnels: {LastErr}",
+ "message": "Could not enumerate existing tunnels: {LastErr}",
+ "translation": "Mevcut tüneller numaralandırılamadı: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{Name}’",
+ "message": "Another tunnel already exists with the name ‘{Name}’",
+ "translation": "‘{Name}’ adında başka bir tünel mevcut",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "unparsedConfig.Name"
+ }
+ ]
+ },
+ {
+ "id": "Unable to import configuration: {LastErr}",
+ "message": "Unable to import configuration: {LastErr}",
+ "translation": "Yapılandırma içe aktarılamıyor: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Imported tunnels",
+ "message": "Imported tunnels",
+ "translation": "Tüneller içe aktarıldı",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Imported {M} tunnels",
+ "message": "Imported {M} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "M",
+ "cases": {
+ "one": {
+ "msg": "{M} tünel içe aktarıldı"
+ },
+ "other": {
+ "msg": "{M} tünel içe aktarıldı"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ }
+ ]
+ },
+ {
+ "id": "Imported {M} of {N} tunnels",
+ "message": "Imported {M} of {N} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "N",
+ "cases": {
+ "one": {
+ "msg": "{N}/{M} tünel içe aktarıldı"
+ },
+ "other": {
+ "msg": "{N}/{M} tünel içe aktarıldı"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ },
+ {
+ "id": "N",
+ "string": "%[2]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 2,
+ "expr": "n"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create tunnel",
+ "message": "Unable to create tunnel",
+ "translation": "Tünel oluşturulamıyor",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Delete {TunnelCount} tunnels",
+ "message": "Delete {TunnelCount} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "{TunnelCount} tüneli sil"
+ },
+ "other": {
+ "msg": "{TunnelCount} tüneli sil"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "message": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "one": {
+ "msg": "{TunnelCount} tüneli silmek istediğinizden emin misiniz?"
+ },
+ "other": {
+ "msg": "{TunnelCount} tüneli silmek istediğinizden emin misiniz?"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Delete tunnel ‘{TunnelName}’",
+ "message": "Delete tunnel ‘{TunnelName}’",
+ "translation": "‘{TunnelName}’ tünelini sil",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "message": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "translation": "‘{TunnelName}’ tünelini silmek istediğinizden emin misiniz?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "{Question} You cannot undo this action.",
+ "message": "{Question} You cannot undo this action.",
+ "translation": "{Question} Bu işlemi geri alamazsınız.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Question",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "question"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnel",
+ "message": "Unable to delete tunnel",
+ "translation": "Tünel silinemiyor",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A tunnel was unable to be removed: {Error}",
+ "message": "A tunnel was unable to be removed: {Error}",
+ "translation": "Bir tünel kaldırılamadı: {Error}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Error",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errors[0].Error()"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnels",
+ "message": "Unable to delete tunnels",
+ "translation": "Tüneller silinemiyor",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Lenerrors} tunnels were unable to be removed.",
+ "message": "{Lenerrors} tunnels were unable to be removed.",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Lenerrors",
+ "cases": {
+ "one": {
+ "msg": "{Lenerrors} tünel kaldırılamadı."
+ },
+ "other": {
+ "msg": "{Lenerrors} tünel kaldırılamadı."
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Lenerrors",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "len(errors)"
+ }
+ ]
+ },
+ {
+ "id": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "message": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "translation": "Yapılandırma Dosyaları (*.zip, *.conf)|*.zip;*.conf|Tüm Dosyalar (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Import tunnel(s) from file",
+ "message": "Import tunnel(s) from file",
+ "translation": "Tünelleri dosyadan içe aktar",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Configuration ZIP Files (*.zip)|*.zip",
+ "message": "Configuration ZIP Files (*.zip)|*.zip",
+ "translation": "Yapılandırma ZIP Dosyası (*.zip)|*.zip",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export tunnels to zip",
+ "message": "Export tunnels to zip",
+ "translation": "Tünelleri zip olarak dışa aktar",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Title} (unsigned build, no updates)",
+ "message": "{Title} (unsigned build, no updates)",
+ "translation": "{Title} (imzasız derleme, güncelleme yok)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "Error Exiting WireGuard",
+ "message": "Error Exiting WireGuard",
+ "translation": "WireGuard Çıkış Hatası",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "message": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "translation": "Hizmet şu nedenden dolayı kapatılamıyor: {Err}. WireGuard'ı hizmet yöneticisinden durdurabilirsiniz.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "message": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "translation": "WireGuard için bir güncelleme mevcut. Bekletmeden güncelleme yapmanız önemle tavsiye edilir.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for user",
+ "message": "Status: Waiting for user",
+ "translation": "Durum: Kullanıcı bekleniyor",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Update Now",
+ "message": "Update Now",
+ "translation": "Şimdi Güncelle",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for updater service",
+ "message": "Status: Waiting for updater service",
+ "translation": "Durum: Güncelleştirme hizmeti bekleniyor",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error: {Err}. Please try again.",
+ "message": "Error: {Err}. Please try again.",
+ "translation": "Hata: {Err}. Lütfen yeniden deneyin.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Status: Complete!",
+ "message": "Status: Complete!",
+ "translation": "Durum: Tamamlandı!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "http2: Framer {F}: failed to decode just-written frame",
+ "message": "http2: Framer {F}: failed to decode just-written frame",
+ "translation": "http2: Framer {F}: az önce yazılmış çerçeve çözülemedi",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ }
+ ]
+ },
+ {
+ "id": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "message": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "translation": "http2: Framer {F}: {Http2summarizeFramefr} yazıldı",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ },
+ {
+ "id": "Http2summarizeFramefr",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(fr)"
+ }
+ ]
+ },
+ {
+ "id": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "message": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "translation": "http2: Framer {Fr}: {Http2summarizeFramef} okundu",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Fr",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "fr"
+ },
+ {
+ "id": "Http2summarizeFramef",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(f)"
+ }
+ ]
+ },
+ {
+ "id": "http2: decoded hpack field {HeaderField}",
+ "message": "http2: decoded hpack field {HeaderField}",
+ "translation": "https2: {HeaderField} hpack alanı çözüldü",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "HeaderField",
+ "string": "%+[1]v",
+ "type": "vendor/golang.org/x/net/http2/hpack.HeaderField",
+ "underlyingType": "struct{Name string; Value string; Sensitive bool}",
+ "argNum": 1,
+ "expr": "hf"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/locales/uk/messages.gotext.json b/locales/uk/messages.gotext.json
new file mode 100644
index 00000000..7079e424
--- /dev/null
+++ b/locales/uk/messages.gotext.json
@@ -0,0 +1,933 @@
+{
+ "language": "uk",
+ "messages": [
+ {
+ "id": "Error",
+ "message": "Error",
+ "translation": "Помилка",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(no argument): elevate and install manager service",
+ "message": "(no argument): elevate and install manager service",
+ "translation": "(немає аргумента): отримати права аднімістратора і встановити службу",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Usage: {Args0} [\n{String}]",
+ "message": "Usage: {Args0} [\n{String}]",
+ "translation": "Використання: {Args0} [\n{String}]",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Args0",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "os.Args[0]"
+ },
+ {
+ "id": "String",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "builder.String()"
+ }
+ ]
+ },
+ {
+ "id": "Command Line Options",
+ "message": "Command Line Options",
+ "translation": "Параметри командного рядка",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to determine whether the process is running under WOW64: {Err}",
+ "message": "Unable to determine whether the process is running under WOW64: {Err}",
+ "translation": "Неможливо визначити, чи працює процес під WOW64: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "You must use the native version of WireGuard on this computer.",
+ "message": "You must use the native version of WireGuard on this computer.",
+ "translation": "Ви повинні використовувати нативну версію WireGuard на цьому комп'ютері.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to open current process token: {Err}",
+ "message": "Unable to open current process token: {Err}",
+ "translation": "Не вдалося відкрити токен поточного процесу: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard може бути використаний тільки користувачами, які є членами вбудованих {AdminGroupName} груп.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard запущено, але UI доступний лише з комп\"ютерів вбудованої {AdminGroupName} групи.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard system tray icon did not appear after 30 seconds.",
+ "message": "WireGuard system tray icon did not appear after 30 seconds.",
+ "translation": "Значок системи WireGuard не з'явився через 30 секунд.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Now",
+ "message": "Now",
+ "translation": "Зараз",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "System clock wound backward!",
+ "message": "System clock wound backward!",
+ "translation": "Системний годинник налаштований некоректно!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Timestamp} ago",
+ "message": "{Timestamp} ago",
+ "translation": "{Timestamp} тому",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Timestamp",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "timestamp"
+ }
+ ]
+ },
+ {
+ "id": "{Bytes} B",
+ "message": "{Bytes} B",
+ "translation": "{Bytes} Б",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Bytes",
+ "string": "%[1]d",
+ "type": "golang.zx2c4.com/wireguard/windows/conf.Bytes",
+ "underlyingType": "uint64",
+ "argNum": 1,
+ "expr": "b"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024} KiB",
+ "message": "{Float64b__1024} KiB",
+ "translation": "{Float64b__1024} КБ",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024} MiB",
+ "message": "{Float64b__1024__1024} MiB",
+ "translation": "{Float64b__1024__1024} МБ",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024} GiB",
+ "message": "{Float64b__1024__1024__1024} GiB",
+ "translation": "{Float64b__1024__1024__1024} ГБ",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024__1024} TiB",
+ "message": "{Float64b__1024__1024__1024__1024} TiB",
+ "translation": "{Float64b__1024__1024__1024__1024} ТБ",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Why}: {Offender}",
+ "message": "{Why}: {Offender}",
+ "translation": "{Why}: {Offender}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Why",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "e.why"
+ },
+ {
+ "id": "Offender",
+ "string": "%[2]q",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "e.offender"
+ }
+ ]
+ },
+ {
+ "id": "Invalid IP address",
+ "message": "Invalid IP address",
+ "translation": "Недійсна IP-адреса",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid network prefix length",
+ "message": "Invalid network prefix length",
+ "translation": "Невірна довжина префіксу мережі",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Missing port from endpoint",
+ "message": "Missing port from endpoint",
+ "translation": "Відсутній порт з кінцевої точки",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid endpoint host",
+ "message": "Invalid endpoint host",
+ "translation": "Недійсний хост кінцевої точки",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Brackets must contain an IPv6 address",
+ "message": "Brackets must contain an IPv6 address",
+ "translation": "Дужки повинні містити адресу IPv6",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid MTU",
+ "message": "Invalid MTU",
+ "translation": "Недійсний MTU",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid port",
+ "message": "Invalid port",
+ "translation": "Недійсний порт",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid persistent keepalive",
+ "message": "Invalid persistent keepalive",
+ "translation": "Некоректне значення keepalive",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key: {Err}",
+ "message": "Invalid key: {Err}",
+ "translation": "Недійсний ключ: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Keys must decode to exactly 32 bytes",
+ "message": "Keys must decode to exactly 32 bytes",
+ "translation": "Ключ повинен декодуватись до 32 байт",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Number must be a number between 0 and 2^64-1: {Err}",
+ "message": "Number must be a number between 0 and 2^64-1: {Err}",
+ "translation": "Номер повинен бути числом від 0 до 2^64-1: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Two commas in a row",
+ "message": "Two commas in a row",
+ "translation": "Дві коми поспіль",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name is not valid",
+ "message": "Tunnel name is not valid",
+ "translation": "Назва тунелю некоректна",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Line must occur in a section",
+ "message": "Line must occur in a section",
+ "translation": "Рядок повинен бути вказаним у розділі",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Config key is missing an equals separator",
+ "message": "Config key is missing an equals separator",
+ "translation": "Ключ конфігурації відсутній роздільник рівності",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Key must have a value",
+ "message": "Key must have a value",
+ "translation": "Ключ повинен мати значення",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Interface] section",
+ "message": "Invalid key for [Interface] section",
+ "translation": "Хибний ключ для [Interface] розділу",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Peer] section",
+ "message": "Invalid key for [Peer] section",
+ "translation": "Хибний ключ для [Peer] розділу",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An interface must have a private key",
+ "message": "An interface must have a private key",
+ "translation": "Інтерфейс повинен мати особистий ключ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[none specified]",
+ "message": "[none specified]",
+ "translation": "[жодного не вказано]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "All peers must have public keys",
+ "message": "All peers must have public keys",
+ "translation": "Всі учасники повинні мати відкриті ключі",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error in getting configuration",
+ "message": "Error in getting configuration",
+ "translation": "Помилка при отриманні конфігурації",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for interface section",
+ "message": "Invalid key for interface section",
+ "translation": "Недійсний ключ для розділу інтерфейсу",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Protocol version must be 1",
+ "message": "Protocol version must be 1",
+ "translation": "Версія протоколу повинна бути 1",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for peer section",
+ "message": "Invalid key for peer section",
+ "translation": "Хибний ключ для [Peer] розділу",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[EnumerationSeparator]",
+ "message": "[EnumerationSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert between items when listing - most western languages will translate ‘[EnumerationSeparator]’ into ‘, ’ to produce lists like ‘apple, orange, strawberry’. Eastern languages might translate into ‘、’ to produce lists like ‘リンゴ、オレンジ、イチゴ’."
+ },
+ {
+ "id": "[UnitSeparator]",
+ "message": "[UnitSeparator]",
+ "translation": ", ",
+ "comment": "Text to insert when combining units of a measure - most languages will translate ‘[UnitSeparator]’ into ‘ ’ (space) to produce lists like ‘2 minuti 30 sekund’, or empty string ‘’ to produce ‘2分30秒’."
+ },
+ {
+ "id": "About WireGuard",
+ "message": "About WireGuard",
+ "translation": "Про WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard logo image",
+ "message": "WireGuard logo image",
+ "translation": "Зображення логотипу WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "message": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "translation": "Версія додатку: {Number}\nВерсія Go бекенду: {WireGuardGoVersion}\nВерсія Go: {Version_go}-{GOARCH}\nОпераційна система: {OsName}\nАрхітектура {NativeArch}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Number",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "version.Number"
+ },
+ {
+ "id": "WireGuardGoVersion",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "device.WireGuardGoVersion"
+ },
+ {
+ "id": "Version_go",
+ "string": "%[3]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 3,
+ "expr": "strings.TrimPrefix(runtime.Version(), \"go\")"
+ },
+ {
+ "id": "GOARCH",
+ "string": "%[4]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 4,
+ "expr": "runtime.GOARCH"
+ },
+ {
+ "id": "OsName",
+ "string": "%[5]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 5,
+ "expr": "version.OsName()"
+ },
+ {
+ "id": "NativeArch",
+ "string": "%[6]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 6,
+ "expr": "version.NativeArch()"
+ }
+ ]
+ },
+ {
+ "id": "Close",
+ "message": "Close",
+ "translation": "Закрити",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "♥ &Donate!",
+ "message": "♥ &Donate!",
+ "translation": "♥ &Пожертвувати!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status:",
+ "message": "Status:",
+ "translation": "Статус:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Deactivate",
+ "message": "&Deactivate",
+ "translation": "&Деактивувати",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Activate",
+ "message": "&Activate",
+ "translation": "&Активувати",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Public key:",
+ "message": "Public key:",
+ "translation": "Відкритий ключ:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Listen port:",
+ "message": "Listen port:",
+ "translation": "Порт:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "MTU:",
+ "message": "MTU:",
+ "translation": "MTU:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses:",
+ "message": "Addresses:",
+ "translation": "Адреси:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "DNS servers:",
+ "message": "DNS servers:",
+ "translation": "DNS-сервери:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Scripts:",
+ "message": "Scripts:",
+ "translation": "Скрипти:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Preshared key:",
+ "message": "Preshared key:",
+ "translation": "Preshared ключ:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Allowed IPs:",
+ "message": "Allowed IPs:",
+ "translation": "Дозволені IP адреси:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Endpoint:",
+ "message": "Endpoint:",
+ "translation": "Endpoint:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Persistent keepalive:",
+ "message": "Persistent keepalive:",
+ "translation": "Persistent keepalive:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Latest handshake:",
+ "message": "Latest handshake:",
+ "translation": "Останнє рукостискання:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Transfer:",
+ "message": "Transfer:",
+ "translation": "Передано:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-up",
+ "message": "pre-up",
+ "translation": "перед-запуском",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-up",
+ "message": "post-up",
+ "translation": "після-запуску",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-down",
+ "message": "pre-down",
+ "translation": "перед-зупинкою",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-down",
+ "message": "post-down",
+ "translation": "після-зупинки",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "disabled, per policy",
+ "message": "disabled, per policy",
+ "translation": "вимкнено, відповідно до політики",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "enabled",
+ "message": "enabled",
+ "translation": "увімкнено",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{String} received, {String_1} sent",
+ "message": "{String} received, {String_1} sent",
+ "translation": "{String} отримано, {String_1} відправлено",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "String",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "c.RxBytes.String()"
+ },
+ {
+ "id": "String_1",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "c.TxBytes.String()"
+ }
+ ]
+ },
+ {
+ "id": "Failed to determine tunnel state",
+ "message": "Failed to determine tunnel state",
+ "translation": "Не вдалося визначити стан тунелю",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to activate tunnel",
+ "message": "Failed to activate tunnel",
+ "translation": "Не вдалося активувати тунель",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to deactivate tunnel",
+ "message": "Failed to deactivate tunnel",
+ "translation": "Не вдалося деактивувати тунель",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Interface: {Name}",
+ "message": "Interface: {Name}",
+ "translation": "Інтерфейс: {Name}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "config.Name"
+ }
+ ]
+ },
+ {
+ "id": "Peer",
+ "message": "Peer",
+ "translation": "Пір",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Create new tunnel",
+ "message": "Create new tunnel",
+ "translation": "Створити новий тунель",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit tunnel",
+ "message": "Edit tunnel",
+ "translation": "Редагувати тунель",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Name:",
+ "message": "&Name:",
+ "translation": "&Назва:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Public key:",
+ "message": "&Public key:",
+ "translation": "&Публічний ключ:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(unknown)",
+ "message": "(unknown)",
+ "translation": "(невідомий)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Block untunneled traffic (kill-switch)",
+ "message": "&Block untunneled traffic (kill-switch)",
+ "translation": "&Блокувати трафік поза тунелем",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.",
+ "message": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.",
+ "translation": "У випадку якщо в конфігурації вказаний лише один пір, та його дозволені ІР-адреси містять 0.0.0.0/0 або ::/0, сервіс тунелю вводить у дію правила фаєрволу що блокують увесь трафік поза тунелем або до невідповідних DNS серверів, з виключеннями для DHCP та NDP.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save",
+ "message": "&Save",
+ "translation": "&Зберегти",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Cancel",
+ "message": "Cancel",
+ "translation": "Скасувати",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Configuration:",
+ "message": "&Configuration:",
+ "translation": "&Налаштування:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid name",
+ "message": "Invalid name",
+ "translation": "Хибне ім'я",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A name is required.",
+ "message": "A name is required.",
+ "translation": "Необхідно ввести ім'я.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name ‘{NewName}’ is invalid.",
+ "message": "Tunnel name ‘{NewName}’ is invalid.",
+ "translation": "Ім'я тунелю '{NewName}' некоректне.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to list existing tunnels",
+ "message": "Unable to list existing tunnels",
+ "translation": "Не вдалося відобразити існуючі тунелі",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel already exists",
+ "message": "Tunnel already exists",
+ "translation": "Тунель вже існує",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{NewName}’.",
+ "message": "Another tunnel already exists with the name ‘{NewName}’.",
+ "translation": "Тунель з ім'ям ‘{NewName}’ вже існує.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "no configuration files were found",
+ "message": "no configuration files were found",
+ "translation": "не знайдено файлів конфігурації",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Could not import selected configuration: {LastErr}",
+ "message": "Could not import selected configuration: {LastErr}",
+ "translation": "Не вдалося імпортувати вибрану конфігурацію: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Could not enumerate existing tunnels: {LastErr}",
+ "message": "Could not enumerate existing tunnels: {LastErr}",
+ "translation": "Не вдалося перерахувати існуючі тунелі: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{Name}’",
+ "message": "Another tunnel already exists with the name ‘{Name}’",
+ "translation": "Тунель з ім'ям ‘{Name}’ вже існує",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "unparsedConfig.Name"
+ }
+ ]
+ },
+ {
+ "id": "Unable to import configuration: {LastErr}",
+ "message": "Unable to import configuration: {LastErr}",
+ "translation": "Не вдалося імпортувати конфігурацію: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Imported tunnels",
+ "message": "Imported tunnels",
+ "translation": "Імпортовано тунелі",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Imported {M} tunnels",
+ "message": "Imported {M} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "M",
+ "cases": {
+ "one": {
+ "msg": "Імпортовано {M} тунель"
+ },
+ "few": {
+ "msg": "Імпортовано {M} тунелі"
+ },
+ "many": {
+ "msg": "Імпортовано {M} тунелів"
+ },
+ "other": {
+ "msg": "Імпортовано {M} тунелів"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/locales/vi/messages.gotext.json b/locales/vi/messages.gotext.json
new file mode 100644
index 00000000..94d44344
--- /dev/null
+++ b/locales/vi/messages.gotext.json
@@ -0,0 +1,334 @@
+{
+ "language": "vi",
+ "messages": [
+ {
+ "id": "Error",
+ "message": "Error",
+ "translation": "Lỗi",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Now",
+ "message": "Now",
+ "translation": "Vừa xong",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Years} year(s)",
+ "message": "{Years} year(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Years",
+ "cases": {
+ "other": {
+ "msg": "{Years} năm"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Years",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "years"
+ }
+ ]
+ },
+ {
+ "id": "{Days} day(s)",
+ "message": "{Days} day(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Days",
+ "cases": {
+ "other": {
+ "msg": "{Days} ngày"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Days",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "days"
+ }
+ ]
+ },
+ {
+ "id": "{Hours} hour(s)",
+ "message": "{Hours} hour(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Hours",
+ "cases": {
+ "other": {
+ "msg": "{Hours} giờ"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Hours",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "hours"
+ }
+ ]
+ },
+ {
+ "id": "{Minutes} minute(s)",
+ "message": "{Minutes} minute(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Minutes",
+ "cases": {
+ "other": {
+ "msg": "{Minutes} phút"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Minutes",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "minutes"
+ }
+ ]
+ },
+ {
+ "id": "{Seconds} second(s)",
+ "message": "{Seconds} second(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Seconds",
+ "cases": {
+ "other": {
+ "msg": "{Seconds} giây"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Seconds",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "seconds"
+ }
+ ]
+ },
+ {
+ "id": "{Timestamp} ago",
+ "message": "{Timestamp} ago",
+ "translation": "{Timestamp} trước",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Timestamp",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "timestamp"
+ }
+ ]
+ },
+ {
+ "id": "Invalid IP address",
+ "message": "Invalid IP address",
+ "translation": "Địa chỉ IP không hợp lệ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid port",
+ "message": "Invalid port",
+ "translation": "Cổng (port) không hợp lệ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name is not valid",
+ "message": "Tunnel name is not valid",
+ "translation": "Tên VPN không hợp lệ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "About WireGuard",
+ "message": "About WireGuard",
+ "translation": "Thông tin về WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard logo image",
+ "message": "WireGuard logo image",
+ "translation": "Logo WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Close",
+ "message": "Close",
+ "translation": "Đóng",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status:",
+ "message": "Status:",
+ "translation": "Trạng thái:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Endpoint:",
+ "message": "Endpoint:",
+ "translation": "Đầu cuối:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "enabled",
+ "message": "enabled",
+ "translation": "đã kích hoạt",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{String} received, {String_1} sent",
+ "message": "{String} received, {String_1} sent",
+ "translation": "Nhận {String}, gứi {String_1}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "String",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "c.RxBytes.String()"
+ },
+ {
+ "id": "String_1",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "c.TxBytes.String()"
+ }
+ ]
+ },
+ {
+ "id": "Failed to determine tunnel state",
+ "message": "Failed to determine tunnel state",
+ "translation": "Không thể xác định tình trạng VPN",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to activate tunnel",
+ "message": "Failed to activate tunnel",
+ "translation": "Không thể kích hoạt VPN",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to deactivate tunnel",
+ "message": "Failed to deactivate tunnel",
+ "translation": "Không thể vô hiệu hóa VPN",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Peer",
+ "message": "Peer",
+ "translation": "Mạng ngang hàng",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Create new tunnel",
+ "message": "Create new tunnel",
+ "translation": "Tạo VPN",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit tunnel",
+ "message": "Edit tunnel",
+ "translation": "Chỉnh sửa VPN",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Cancel",
+ "message": "Cancel",
+ "translation": "Huỷ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid name",
+ "message": "Invalid name",
+ "translation": "Tên không hợp lệ",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A name is required.",
+ "message": "A name is required.",
+ "translation": "Yêu cầu nhập tên.",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name ‘{NewName}’ is invalid.",
+ "message": "Tunnel name ‘{NewName}’ is invalid.",
+ "translation": "Tên VPN ‘{NewName}' không hợp lệ.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to list existing tunnels",
+ "message": "Unable to list existing tunnels",
+ "translation": "Không thể liệt kê các VPN",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel already exists",
+ "message": "Tunnel already exists",
+ "translation": "VPN đã tồn tại",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{NewName}’.",
+ "message": "Another tunnel already exists with the name ‘{NewName}’.",
+ "translation": "Đã tồn tại VPN với tên ‘{NewName}’.",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/locales/zh-CN/messages.gotext.json b/locales/zh-CN/messages.gotext.json
new file mode 100644
index 00000000..5f70db44
--- /dev/null
+++ b/locales/zh-CN/messages.gotext.json
@@ -0,0 +1,1817 @@
+{
+ "language": "zh-CN",
+ "messages": [
+ {
+ "id": "Error",
+ "message": "Error",
+ "translation": "错误",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(no argument): elevate and install manager service",
+ "message": "(no argument): elevate and install manager service",
+ "translation": "(无参数): 提升并安装管理服务",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Usage: {Args0} [\n{String}]",
+ "message": "Usage: {Args0} [\n{String}]",
+ "translation": "用法: {Args0} [\n{String}]",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Args0",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "os.Args[0]"
+ },
+ {
+ "id": "String",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "builder.String()"
+ }
+ ]
+ },
+ {
+ "id": "Command Line Options",
+ "message": "Command Line Options",
+ "translation": "命令行选项",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to determine whether the process is running under WOW64: {Err}",
+ "message": "Unable to determine whether the process is running under WOW64: {Err}",
+ "translation": "无法确定该进程是否在WOW64下运行: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "You must use the native version of WireGuard on this computer.",
+ "message": "You must use the native version of WireGuard on this computer.",
+ "translation": "您必须在此计算机上使用原生版本的 WireGuard。",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to open current process token: {Err}",
+ "message": "Unable to open current process token: {Err}",
+ "translation": "无法打开当前进程令牌: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard 可能只能被内建的 {AdminGroupName} 小组中的成员使用。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard 正在运行,但用户界面只能从内建的 {AdminGroupName} 小组的桌面访问。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard system tray icon did not appear after 30 seconds.",
+ "message": "WireGuard system tray icon did not appear after 30 seconds.",
+ "translation": "WireGuard 系统托盘图标在30秒后没有出现。",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Now",
+ "message": "Now",
+ "translation": "刚刚",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "System clock wound backward!",
+ "message": "System clock wound backward!",
+ "translation": "系统时间倒退了!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Years} year(s)",
+ "message": "{Years} year(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Years",
+ "cases": {
+ "other": {
+ "msg": "{Years} 年"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Years",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "years"
+ }
+ ]
+ },
+ {
+ "id": "{Days} day(s)",
+ "message": "{Days} day(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Days",
+ "cases": {
+ "other": {
+ "msg": "{Days} 天"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Days",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "days"
+ }
+ ]
+ },
+ {
+ "id": "{Hours} hour(s)",
+ "message": "{Hours} hour(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Hours",
+ "cases": {
+ "other": {
+ "msg": "{Hours} 小时"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Hours",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "hours"
+ }
+ ]
+ },
+ {
+ "id": "{Minutes} minute(s)",
+ "message": "{Minutes} minute(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Minutes",
+ "cases": {
+ "other": {
+ "msg": "{Minutes} 分钟"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Minutes",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "minutes"
+ }
+ ]
+ },
+ {
+ "id": "{Seconds} second(s)",
+ "message": "{Seconds} second(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Seconds",
+ "cases": {
+ "other": {
+ "msg": "{Seconds} 秒"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Seconds",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "seconds"
+ }
+ ]
+ },
+ {
+ "id": "{Timestamp} ago",
+ "message": "{Timestamp} ago",
+ "translation": "{Timestamp} 前",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Timestamp",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "timestamp"
+ }
+ ]
+ },
+ {
+ "id": "{Bytes} B",
+ "message": "{Bytes} B",
+ "translation": "{Bytes} B",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Bytes",
+ "string": "%[1]d",
+ "type": "golang.zx2c4.com/wireguard/windows/conf.Bytes",
+ "underlyingType": "uint64",
+ "argNum": 1,
+ "expr": "b"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024} KiB",
+ "message": "{Float64b__1024} KiB",
+ "translation": "{Float64b__1024} KiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024} MiB",
+ "message": "{Float64b__1024__1024} MiB",
+ "translation": "{Float64b__1024__1024} MiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024} GiB",
+ "message": "{Float64b__1024__1024__1024} GiB",
+ "translation": "{Float64b__1024__1024__1024} GiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024__1024} TiB",
+ "message": "{Float64b__1024__1024__1024__1024} TiB",
+ "translation": "{Float64b__1024__1024__1024__1024} TiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Why}: {Offender}",
+ "message": "{Why}: {Offender}",
+ "translation": "{Why}: {Offender}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Why",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "e.why"
+ },
+ {
+ "id": "Offender",
+ "string": "%[2]q",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "e.offender"
+ }
+ ]
+ },
+ {
+ "id": "Invalid IP address",
+ "message": "Invalid IP address",
+ "translation": "IP地址无效",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid network prefix length",
+ "message": "Invalid network prefix length",
+ "translation": "网络前缀长度无效",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Missing port from endpoint",
+ "message": "Missing port from endpoint",
+ "translation": "对端 (endpoint) 中缺少端口",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid endpoint host",
+ "message": "Invalid endpoint host",
+ "translation": "对端主机名 (endpoint host) 无效",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Brackets must contain an IPv6 address",
+ "message": "Brackets must contain an IPv6 address",
+ "translation": "方括号中应包含一个 IPv6 地址",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid MTU",
+ "message": "Invalid MTU",
+ "translation": "MTU 无效",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid port",
+ "message": "Invalid port",
+ "translation": "端口无效",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid persistent keepalive",
+ "message": "Invalid persistent keepalive",
+ "translation": "连接保活间隔无效",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key: {Err}",
+ "message": "Invalid key: {Err}",
+ "translation": "无效的密钥:{Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Keys must decode to exactly 32 bytes",
+ "message": "Keys must decode to exactly 32 bytes",
+ "translation": "解码后的密钥长度必须为32字节",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Number must be a number between 0 and 2^64-1: {Err}",
+ "message": "Number must be a number between 0 and 2^64-1: {Err}",
+ "translation": "数值必须介于 0 至 2^64-1 之间: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Two commas in a row",
+ "message": "Two commas in a row",
+ "translation": "一行中有两个逗号",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name is not valid",
+ "message": "Tunnel name is not valid",
+ "translation": "隧道名称无效",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Line must occur in a section",
+ "message": "Line must occur in a section",
+ "translation": "行必须出现在段落中",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Config key is missing an equals separator",
+ "message": "Config key is missing an equals separator",
+ "translation": "配置项必须要有一个等于号",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Key must have a value",
+ "message": "Key must have a value",
+ "translation": "必须有一个值",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Interface] section",
+ "message": "Invalid key for [Interface] section",
+ "translation": "[Interface] 段落中的该键无效",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Peer] section",
+ "message": "Invalid key for [Peer] section",
+ "translation": "[Peer] 段落中的该键无效",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An interface must have a private key",
+ "message": "An interface must have a private key",
+ "translation": "接口必须有一个私钥",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[none specified]",
+ "message": "[none specified]",
+ "translation": "[未指定]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "All peers must have public keys",
+ "message": "All peers must have public keys",
+ "translation": "每个节点都必须指定公钥",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error in getting configuration",
+ "message": "Error in getting configuration",
+ "translation": "获取配置时出错",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for interface section",
+ "message": "Invalid key for interface section",
+ "translation": "接口段落的键无效",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Protocol version must be 1",
+ "message": "Protocol version must be 1",
+ "translation": "协议版本必须为 1",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for peer section",
+ "message": "Invalid key for peer section",
+ "translation": "节点段落的键无效",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[EnumerationSeparator]",
+ "message": "[EnumerationSeparator]",
+ "translation": "、",
+ "comment": "Text to insert between items when listing - most western languages will translate ‘[EnumerationSeparator]’ into ‘, ’ to produce lists like ‘apple, orange, strawberry’. Eastern languages might translate into ‘、’ to produce lists like ‘リンゴ、オレンジ、イチゴ’."
+ },
+ {
+ "id": "[UnitSeparator]",
+ "message": "[UnitSeparator]",
+ "translation": " ",
+ "comment": "Text to insert when combining units of a measure - most languages will translate ‘[UnitSeparator]’ into ‘ ’ (space) to produce lists like ‘2 minuti 30 sekund’, or empty string ‘’ to produce ‘2分30秒’."
+ },
+ {
+ "id": "About WireGuard",
+ "message": "About WireGuard",
+ "translation": "关于 WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard logo image",
+ "message": "WireGuard logo image",
+ "translation": "WireGuard logo 图片",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "message": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "translation": "应用版本: {Number}\nGo 后端版本: {WireGuardGoVersion}\nGo 语言版本: {Version_go}-{GOARCH}\n操作系统: {OsName}\n架构: {NativeArch}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Number",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "version.Number"
+ },
+ {
+ "id": "WireGuardGoVersion",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "device.WireGuardGoVersion"
+ },
+ {
+ "id": "Version_go",
+ "string": "%[3]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 3,
+ "expr": "strings.TrimPrefix(runtime.Version(), \"go\")"
+ },
+ {
+ "id": "GOARCH",
+ "string": "%[4]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 4,
+ "expr": "runtime.GOARCH"
+ },
+ {
+ "id": "OsName",
+ "string": "%[5]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 5,
+ "expr": "version.OsName()"
+ },
+ {
+ "id": "NativeArch",
+ "string": "%[6]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 6,
+ "expr": "version.NativeArch()"
+ }
+ ]
+ },
+ {
+ "id": "Close",
+ "message": "Close",
+ "translation": "关闭",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "♥ &Donate!",
+ "message": "♥ &Donate!",
+ "translation": "♥ 捐助! (&D)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status:",
+ "message": "Status:",
+ "translation": "状态:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Deactivate",
+ "message": "&Deactivate",
+ "translation": "断开 (&D)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Activate",
+ "message": "&Activate",
+ "translation": "连接 (&A)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Public key:",
+ "message": "Public key:",
+ "translation": "公钥:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Listen port:",
+ "message": "Listen port:",
+ "translation": "监听端口:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "MTU:",
+ "message": "MTU:",
+ "translation": "MTU:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses:",
+ "message": "Addresses:",
+ "translation": "地址:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "DNS servers:",
+ "message": "DNS servers:",
+ "translation": "DNS 服务器:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Scripts:",
+ "message": "Scripts:",
+ "translation": "脚本:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Preshared key:",
+ "message": "Preshared key:",
+ "translation": "预共享密钥:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Allowed IPs:",
+ "message": "Allowed IPs:",
+ "translation": "允许的 IP:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Endpoint:",
+ "message": "Endpoint:",
+ "translation": "对端:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Persistent keepalive:",
+ "message": "Persistent keepalive:",
+ "translation": "连接保活间隔:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Latest handshake:",
+ "message": "Latest handshake:",
+ "translation": "上次握手时间:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Transfer:",
+ "message": "Transfer:",
+ "translation": "流量:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-up",
+ "message": "pre-up",
+ "translation": "连接前",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-up",
+ "message": "post-up",
+ "translation": "连接后",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-down",
+ "message": "pre-down",
+ "translation": "断开前",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-down",
+ "message": "post-down",
+ "translation": "断开后",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "disabled, per policy",
+ "message": "disabled, per policy",
+ "translation": "已禁用(依管理策略)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "enabled",
+ "message": "enabled",
+ "translation": "已启用",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{String} received, {String_1} sent",
+ "message": "{String} received, {String_1} sent",
+ "translation": "接收 {String}, 发送 {String_1}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "String",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "c.RxBytes.String()"
+ },
+ {
+ "id": "String_1",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "c.TxBytes.String()"
+ }
+ ]
+ },
+ {
+ "id": "Failed to determine tunnel state",
+ "message": "Failed to determine tunnel state",
+ "translation": "无法确认隧道状态",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to activate tunnel",
+ "message": "Failed to activate tunnel",
+ "translation": "无法连接隧道",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to deactivate tunnel",
+ "message": "Failed to deactivate tunnel",
+ "translation": "无法断开隧道连接",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Interface: {Name}",
+ "message": "Interface: {Name}",
+ "translation": "接口: {Name}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "config.Name"
+ }
+ ]
+ },
+ {
+ "id": "Peer",
+ "message": "Peer",
+ "translation": "节点",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Create new tunnel",
+ "message": "Create new tunnel",
+ "translation": "创建新隧道",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit tunnel",
+ "message": "Edit tunnel",
+ "translation": "编辑隧道",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Name:",
+ "message": "&Name:",
+ "translation": "名称 (&N):",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Public key:",
+ "message": "&Public key:",
+ "translation": "公钥 (&P):",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(unknown)",
+ "message": "(unknown)",
+ "translation": "(未知)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Block untunneled traffic (kill-switch)",
+ "message": "&Block untunneled traffic (kill-switch)",
+ "translation": "拦截未经隧道的流量 (kill-switch) (&B)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.",
+ "message": "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.",
+ "translation": "只有一个节点,且该节点允许的 IP 中包含 0.0.0.0/0 或 ::/0 时,使用防火墙规则拦截所有未通过隧道,或是发往错误的 DNS 服务器的流量。DHCP 和 NDP 流量不受影响。",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save",
+ "message": "&Save",
+ "translation": "保存 (&S)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Cancel",
+ "message": "Cancel",
+ "translation": "取消",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Configuration:",
+ "message": "&Configuration:",
+ "translation": "配置 (&C):",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid name",
+ "message": "Invalid name",
+ "translation": "名称无效",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A name is required.",
+ "message": "A name is required.",
+ "translation": "必须输入名称。",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name ‘{NewName}’ is invalid.",
+ "message": "Tunnel name ‘{NewName}’ is invalid.",
+ "translation": "隧道名「{NewName}」无效。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to list existing tunnels",
+ "message": "Unable to list existing tunnels",
+ "translation": "无法列出现有隧道",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel already exists",
+ "message": "Tunnel already exists",
+ "translation": "隧道已存在",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{NewName}’.",
+ "message": "Another tunnel already exists with the name ‘{NewName}’.",
+ "translation": "隧道名「{NewName}」已存在。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create new configuration",
+ "message": "Unable to create new configuration",
+ "translation": "无法创建新的配置",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Writing file failed",
+ "message": "Writing file failed",
+ "translation": "写入文件失败",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "message": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "translation": "文件「{FilePath}」已存在。\n\n您确定要覆盖它吗?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "FilePath",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "filePath"
+ }
+ ]
+ },
+ {
+ "id": "Active",
+ "message": "Active",
+ "translation": "已连接",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Activating",
+ "message": "Activating",
+ "translation": "正在连接",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Inactive",
+ "message": "Inactive",
+ "translation": "已断开",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Deactivating",
+ "message": "Deactivating",
+ "translation": "正在断开",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unknown state",
+ "message": "Unknown state",
+ "translation": "未知",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log",
+ "message": "Log",
+ "translation": "日志",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Copy",
+ "message": "&Copy",
+ "translation": "复制 (&C)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Select &all",
+ "message": "Select &all",
+ "translation": "全选 (&A)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save to file…",
+ "message": "&Save to file…",
+ "translation": "导出… (&S)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Time",
+ "message": "Time",
+ "translation": "时间",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log message",
+ "message": "Log message",
+ "translation": "日志消息",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "message": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "translation": "文本文件 (*.txt)|*.txt|所有文件 (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export log to file",
+ "message": "Export log to file",
+ "translation": "导出日志",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&About WireGuard…",
+ "message": "&About WireGuard…",
+ "translation": "关于 WireGuard… (&A)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel Error",
+ "message": "Tunnel Error",
+ "translation": "隧道错误",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "message": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "translation": "{ErrMsg}\n\n更多信息请查看日志。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "ErrMsg",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errMsg"
+ }
+ ]
+ },
+ {
+ "id": "{Title} (out of date)",
+ "message": "{Title} (out of date)",
+ "translation": "{Title} (已过时)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Detection Error",
+ "message": "WireGuard Detection Error",
+ "translation": "WireGuard 检测错误",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to wait for WireGuard window to appear: {Err}",
+ "message": "Unable to wait for WireGuard window to appear: {Err}",
+ "translation": "无法等待 WireGuard 窗口出现: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard: Deactivated",
+ "message": "WireGuard: Deactivated",
+ "translation": "WireGuard: 已断开",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Unknown",
+ "message": "Status: Unknown",
+ "translation": "状态: 未知",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses: None",
+ "message": "Addresses: None",
+ "translation": "地址: 无",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Manage tunnels…",
+ "message": "&Manage tunnels…",
+ "translation": "管理隧道… (&M)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Import tunnel(s) from file…",
+ "message": "&Import tunnel(s) from file…",
+ "translation": "从文件导入隧道… (&I)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "E&xit",
+ "message": "E&xit",
+ "translation": "退出 (&E)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Tunnels",
+ "message": "&Tunnels",
+ "translation": "隧道 (&T)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Activated",
+ "message": "WireGuard Activated",
+ "translation": "WireGuard 已连接",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been activated.",
+ "message": "The {Name} tunnel has been activated.",
+ "translation": "隧道「{Name}」已连接。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Deactivated",
+ "message": "WireGuard Deactivated",
+ "translation": "WireGuard 已断开",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been deactivated.",
+ "message": "The {Name} tunnel has been deactivated.",
+ "translation": "隧道「{Name}」已断开连接。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Tunnel Error",
+ "message": "WireGuard Tunnel Error",
+ "translation": "WireGuard 隧道错误",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard: {TextForStateglobalState_true}",
+ "message": "WireGuard: {TextForStateglobalState_true}",
+ "translation": "WireGuard: {TextForStateglobalState_true}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TextForStateglobalState_true",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "textForState(globalState, true)"
+ }
+ ]
+ },
+ {
+ "id": "Status: {StateText}",
+ "message": "Status: {StateText}",
+ "translation": "状态: {StateText}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "StateText",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "stateText"
+ }
+ ]
+ },
+ {
+ "id": "Addresses: {EnumerationSeparator}",
+ "message": "Addresses: {EnumerationSeparator}",
+ "translation": "地址: {EnumerationSeparator}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "EnumerationSeparator",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "strings.Join(addrs, l18n.EnumerationSeparator())"
+ }
+ ]
+ },
+ {
+ "id": "An Update is Available!",
+ "message": "An Update is Available!",
+ "translation": "发现更新!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Update Available",
+ "message": "WireGuard Update Available",
+ "translation": "WireGuard 更新",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "message": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "translation": "新的 WireGuard 版本发布了。强烈建议您现在安装。",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnels",
+ "message": "Tunnels",
+ "translation": "隧道",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Edit",
+ "message": "&Edit",
+ "translation": "编辑 (&E)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add &empty tunnel…",
+ "message": "Add &empty tunnel…",
+ "translation": "新建空隧道… (&E)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add Tunnel",
+ "message": "Add Tunnel",
+ "translation": "新建隧道",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Remove selected tunnel(s)",
+ "message": "Remove selected tunnel(s)",
+ "translation": "删除所选隧道",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to zip",
+ "message": "Export all tunnels to zip",
+ "translation": "导出所有隧道 (ZIP 压缩包)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Toggle",
+ "message": "&Toggle",
+ "translation": "切换连接状态 (&T)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to &zip…",
+ "message": "Export all tunnels to &zip…",
+ "translation": "导出所有隧道 (ZIP 压缩包)… (&Z)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit &selected tunnel…",
+ "message": "Edit &selected tunnel…",
+ "translation": "编辑所选隧道… (&E)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Remove selected tunnel(s)",
+ "message": "&Remove selected tunnel(s)",
+ "translation": "删除所选隧道 (&R)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "no configuration files were found",
+ "message": "no configuration files were found",
+ "translation": "未找到配置文件",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Could not import selected configuration: {LastErr}",
+ "message": "Could not import selected configuration: {LastErr}",
+ "translation": "无法导入配置: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Could not enumerate existing tunnels: {LastErr}",
+ "message": "Could not enumerate existing tunnels: {LastErr}",
+ "translation": "无法列出现有隧道: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{Name}’",
+ "message": "Another tunnel already exists with the name ‘{Name}’",
+ "translation": "另一个同名的隧道「{Name}」已存在",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "unparsedConfig.Name"
+ }
+ ]
+ },
+ {
+ "id": "Unable to import configuration: {LastErr}",
+ "message": "Unable to import configuration: {LastErr}",
+ "translation": "无法导入配置: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Imported tunnels",
+ "message": "Imported tunnels",
+ "translation": "导入隧道",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Imported {M} tunnels",
+ "message": "Imported {M} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "M",
+ "cases": {
+ "other": {
+ "msg": "导入了 {M} 个隧道"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ }
+ ]
+ },
+ {
+ "id": "Imported {M} of {N} tunnels",
+ "message": "Imported {M} of {N} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "N",
+ "cases": {
+ "other": {
+ "msg": "导入了 {N} 个隧道中的 {M} 个隧道"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ },
+ {
+ "id": "N",
+ "string": "%[2]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 2,
+ "expr": "n"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create tunnel",
+ "message": "Unable to create tunnel",
+ "translation": "无法创建隧道",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Delete {TunnelCount} tunnels",
+ "message": "Delete {TunnelCount} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "other": {
+ "msg": "删除 {TunnelCount} 个隧道"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "message": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "other": {
+ "msg": "您确定要删除这 {TunnelCount} 个隧道吗?"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Delete tunnel ‘{TunnelName}’",
+ "message": "Delete tunnel ‘{TunnelName}’",
+ "translation": "删除隧道「{TunnelName}」",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "message": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "translation": "您确定要删除隧道「{TunnelName}」吗?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "{Question} You cannot undo this action.",
+ "message": "{Question} You cannot undo this action.",
+ "translation": "{Question}此操作无法撤销。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Question",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "question"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnel",
+ "message": "Unable to delete tunnel",
+ "translation": "无法删除隧道",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A tunnel was unable to be removed: {Error}",
+ "message": "A tunnel was unable to be removed: {Error}",
+ "translation": "无法删除隧道: {Error}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Error",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errors[0].Error()"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnels",
+ "message": "Unable to delete tunnels",
+ "translation": "无法删除隧道",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Lenerrors} tunnels were unable to be removed.",
+ "message": "{Lenerrors} tunnels were unable to be removed.",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Lenerrors",
+ "cases": {
+ "other": {
+ "msg": "无法删除 {Lenerrors} 个隧道。"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Lenerrors",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "len(errors)"
+ }
+ ]
+ },
+ {
+ "id": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "message": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "translation": "配置文件 (*.zip, *.conf)|*.zip;*.conf|所有文件 (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Import tunnel(s) from file",
+ "message": "Import tunnel(s) from file",
+ "translation": "从文件导入隧道",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Configuration ZIP Files (*.zip)|*.zip",
+ "message": "Configuration ZIP Files (*.zip)|*.zip",
+ "translation": "配置文件 (*.zip)|*.zip",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export tunnels to zip",
+ "message": "Export tunnels to zip",
+ "translation": "导出配置文件 (ZIP 压缩包)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Title} (unsigned build, no updates)",
+ "message": "{Title} (unsigned build, no updates)",
+ "translation": "{Title} (未签名版本,禁用自动更新)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "Error Exiting WireGuard",
+ "message": "Error Exiting WireGuard",
+ "translation": "退出 WireGuard 时出错",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "message": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "translation": "无法停止服务: {Err}。您可能需要在服务管理器中手动停止 WireGuard 服务。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "message": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "translation": "发现新版 WireGuard。强烈建议您现在安装。",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for user",
+ "message": "Status: Waiting for user",
+ "translation": "状态: 等待用户",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Update Now",
+ "message": "Update Now",
+ "translation": "立即更新",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for updater service",
+ "message": "Status: Waiting for updater service",
+ "translation": "状态: 等待更新服务",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error: {Err}. Please try again.",
+ "message": "Error: {Err}. Please try again.",
+ "translation": "错误: {Err}。请重试。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Status: Complete!",
+ "message": "Status: Complete!",
+ "translation": "状态: 完成!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "http2: Framer {F}: failed to decode just-written frame",
+ "message": "http2: Framer {F}: failed to decode just-written frame",
+ "translation": "http2: 成帧器 {F}: 解码刚写入的帧失败",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ }
+ ]
+ },
+ {
+ "id": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "message": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "translation": "http2: 成帧器 {F}: 写入了 {Http2summarizeFramefr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ },
+ {
+ "id": "Http2summarizeFramefr",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(fr)"
+ }
+ ]
+ },
+ {
+ "id": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "message": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "translation": "http2: 成帧器 {Fr}: 读取了 {Http2summarizeFramef}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Fr",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "fr"
+ },
+ {
+ "id": "Http2summarizeFramef",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(f)"
+ }
+ ]
+ },
+ {
+ "id": "http2: decoded hpack field {HeaderField}",
+ "message": "http2: decoded hpack field {HeaderField}",
+ "translation": "http2: 解码的 hpack 字段 {HeaderField}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "HeaderField",
+ "string": "%+[1]v",
+ "type": "vendor/golang.org/x/net/http2/hpack.HeaderField",
+ "underlyingType": "struct{Name string; Value string; Sensitive bool}",
+ "argNum": 1,
+ "expr": "hf"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/locales/zh-TW/messages.gotext.json b/locales/zh-TW/messages.gotext.json
new file mode 100644
index 00000000..d7ebe665
--- /dev/null
+++ b/locales/zh-TW/messages.gotext.json
@@ -0,0 +1,1805 @@
+{
+ "language": "zh-TW",
+ "messages": [
+ {
+ "id": "Error",
+ "message": "Error",
+ "translation": "錯誤",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(no argument): elevate and install manager service",
+ "message": "(no argument): elevate and install manager service",
+ "translation": "(無參數):提升權限並安裝管理服務",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Usage: {Args0} [\n{String}]",
+ "message": "Usage: {Args0} [\n{String}]",
+ "translation": "使用方法: {Args0} [\n{String}]",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Args0",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "os.Args[0]"
+ },
+ {
+ "id": "String",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "builder.String()"
+ }
+ ]
+ },
+ {
+ "id": "Command Line Options",
+ "message": "Command Line Options",
+ "translation": "命令列選項",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to determine whether the process is running under WOW64: {Err}",
+ "message": "Unable to determine whether the process is running under WOW64: {Err}",
+ "translation": "無法確定該處理程序是否在 WOW64 下執行: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "You must use the native version of WireGuard on this computer.",
+ "message": "You must use the native version of WireGuard on this computer.",
+ "translation": "您必須在此電腦上執行原生版本的 WireGuard。",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to open current process token: {Err}",
+ "message": "Unable to open current process token: {Err}",
+ "translation": "無法開啓目前處理程序的權杖: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard may only be used by users who are a member of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard 可能只能被內建的「{AdminGroupName}」群組成員使用。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "message": "WireGuard is running, but the UI is only accessible from desktops of the Builtin {AdminGroupName} group.",
+ "translation": "WireGuard 正在執行,但 UI 只能從內建的內建的「{AdminGroupName}」群組成員的桌面存取。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "AdminGroupName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "elevate.AdminGroupName()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard system tray icon did not appear after 30 seconds.",
+ "message": "WireGuard system tray icon did not appear after 30 seconds.",
+ "translation": "WireGuard 的工作列圖示在 30 秒後並沒有顯示。",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Now",
+ "message": "Now",
+ "translation": "就是現在",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "System clock wound backward!",
+ "message": "System clock wound backward!",
+ "translation": "系統時鐘倒退了!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Years} year(s)",
+ "message": "{Years} year(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Years",
+ "cases": {
+ "other": {
+ "msg": "{Years} 年"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Years",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "years"
+ }
+ ]
+ },
+ {
+ "id": "{Days} day(s)",
+ "message": "{Days} day(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Days",
+ "cases": {
+ "other": {
+ "msg": "{Days} 天"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Days",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "days"
+ }
+ ]
+ },
+ {
+ "id": "{Hours} hour(s)",
+ "message": "{Hours} hour(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Hours",
+ "cases": {
+ "other": {
+ "msg": "{Hours} 小時"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Hours",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "hours"
+ }
+ ]
+ },
+ {
+ "id": "{Minutes} minute(s)",
+ "message": "{Minutes} minute(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Minutes",
+ "cases": {
+ "other": {
+ "msg": "{Minutes} 分鐘"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Minutes",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "minutes"
+ }
+ ]
+ },
+ {
+ "id": "{Seconds} second(s)",
+ "message": "{Seconds} second(s)",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Seconds",
+ "cases": {
+ "other": {
+ "msg": "{Seconds} 秒"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Seconds",
+ "string": "%[1]d",
+ "type": "int64",
+ "underlyingType": "int64",
+ "argNum": 1,
+ "expr": "seconds"
+ }
+ ]
+ },
+ {
+ "id": "{Timestamp} ago",
+ "message": "{Timestamp} ago",
+ "translation": "{Timestamp} 前",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Timestamp",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "timestamp"
+ }
+ ]
+ },
+ {
+ "id": "{Bytes} B",
+ "message": "{Bytes} B",
+ "translation": "{Bytes} B",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Bytes",
+ "string": "%[1]d",
+ "type": "golang.zx2c4.com/wireguard/windows/conf.Bytes",
+ "underlyingType": "uint64",
+ "argNum": 1,
+ "expr": "b"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024} KiB",
+ "message": "{Float64b__1024} KiB",
+ "translation": "{Float64b__1024} KiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024} MiB",
+ "message": "{Float64b__1024__1024} MiB",
+ "translation": "{Float64b__1024__1024} MiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024} GiB",
+ "message": "{Float64b__1024__1024__1024} GiB",
+ "translation": "{Float64b__1024__1024__1024} GiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024)"
+ }
+ ]
+ },
+ {
+ "id": "{Float64b__1024__1024__1024__1024} TiB",
+ "message": "{Float64b__1024__1024__1024__1024} TiB",
+ "translation": "{Float64b__1024__1024__1024__1024} TiB",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Float64b__1024__1024__1024__1024",
+ "string": "%.2[1]f",
+ "type": "float64",
+ "underlyingType": "float64",
+ "argNum": 1,
+ "expr": "float64(b) / (1024 * 1024 * 1024) / 1024"
+ }
+ ]
+ },
+ {
+ "id": "{Why}: {Offender}",
+ "message": "{Why}: {Offender}",
+ "translation": "{Why}: {Offender}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Why",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "e.why"
+ },
+ {
+ "id": "Offender",
+ "string": "%[2]q",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "e.offender"
+ }
+ ]
+ },
+ {
+ "id": "Invalid IP address",
+ "message": "Invalid IP address",
+ "translation": "無效的 IP 位址",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid network prefix length",
+ "message": "Invalid network prefix length",
+ "translation": "無效的網路位址首碼長度",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Missing port from endpoint",
+ "message": "Missing port from endpoint",
+ "translation": "Endpoint 中沒有指定埠號",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid endpoint host",
+ "message": "Invalid endpoint host",
+ "translation": "無效的 Endpoint 位址",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Brackets must contain an IPv6 address",
+ "message": "Brackets must contain an IPv6 address",
+ "translation": "括號中必須包含一個 IPv6 位址",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid MTU",
+ "message": "Invalid MTU",
+ "translation": "無效的 MTU",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid port",
+ "message": "Invalid port",
+ "translation": "無效的埠號",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid persistent keepalive",
+ "message": "Invalid persistent keepalive",
+ "translation": "無效的 Persistent Keepalive 設定",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key: {Err}",
+ "message": "Invalid key: {Err}",
+ "translation": "無效的金鑰: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Keys must decode to exactly 32 bytes",
+ "message": "Keys must decode to exactly 32 bytes",
+ "translation": "金鑰必須剛好長 32 bytes",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Number must be a number between 0 and 2^64-1: {Err}",
+ "message": "Number must be a number between 0 and 2^64-1: {Err}",
+ "translation": "數值必須介於 0 到 2^64-1: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Two commas in a row",
+ "message": "Two commas in a row",
+ "translation": "一行中有兩個逗號",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name is not valid",
+ "message": "Tunnel name is not valid",
+ "translation": "隧道名稱無效",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Line must occur in a section",
+ "message": "Line must occur in a section",
+ "translation": "行必須出現在段落中",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Config key is missing an equals separator",
+ "message": "Config key is missing an equals separator",
+ "translation": "設定的項目必須要有一個等號",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Key must have a value",
+ "message": "Key must have a value",
+ "translation": "必須要有一個值",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Interface] section",
+ "message": "Invalid key for [Interface] section",
+ "translation": "[Interface] 中有無效選項",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for [Peer] section",
+ "message": "Invalid key for [Peer] section",
+ "translation": "[Peer] 中有無效選項",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An interface must have a private key",
+ "message": "An interface must have a private key",
+ "translation": "Interface 中必須要有一把私鑰",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[none specified]",
+ "message": "[none specified]",
+ "translation": "[未指定]",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "All peers must have public keys",
+ "message": "All peers must have public keys",
+ "translation": "每個 Peer 都必須要有公鑰",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error in getting configuration",
+ "message": "Error in getting configuration",
+ "translation": "讀取設定時發生錯誤",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for interface section",
+ "message": "Invalid key for interface section",
+ "translation": "Interface 中的金鑰無效",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Protocol version must be 1",
+ "message": "Protocol version must be 1",
+ "translation": "協定版本必須為 1",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid key for peer section",
+ "message": "Invalid key for peer section",
+ "translation": "Peer 中的金鑰無效",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "[EnumerationSeparator]",
+ "message": "[EnumerationSeparator]",
+ "translation": "、",
+ "comment": "Text to insert between items when listing - most western languages will translate ‘[EnumerationSeparator]’ into ‘, ’ to produce lists like ‘apple, orange, strawberry’. Eastern languages might translate into ‘、’ to produce lists like ‘リンゴ、オレンジ、イチゴ’."
+ },
+ {
+ "id": "[UnitSeparator]",
+ "message": "[UnitSeparator]",
+ "translation": " ",
+ "comment": "Text to insert when combining units of a measure - most languages will translate ‘[UnitSeparator]’ into ‘ ’ (space) to produce lists like ‘2 minuti 30 sekund’, or empty string ‘’ to produce ‘2分30秒’."
+ },
+ {
+ "id": "About WireGuard",
+ "message": "About WireGuard",
+ "translation": "關於 WireGuard",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard logo image",
+ "message": "WireGuard logo image",
+ "translation": "WireGuard logo 圖片",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "message": "App version: {Number}\nGo backend version: {WireGuardGoVersion}\nGo version: {Version_go}-{GOARCH}\nOperating system: {OsName}\nArchitecture: {NativeArch}",
+ "translation": "應用程式版本: {Number}\n後端程式(Go 實作)版本: {WireGuardGoVersion}\nGo 版本: {Version_go}-{GOARCH}\n作業系統: {OsName}\n架構: {NativeArch}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Number",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "version.Number"
+ },
+ {
+ "id": "WireGuardGoVersion",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "device.WireGuardGoVersion"
+ },
+ {
+ "id": "Version_go",
+ "string": "%[3]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 3,
+ "expr": "strings.TrimPrefix(runtime.Version(), \"go\")"
+ },
+ {
+ "id": "GOARCH",
+ "string": "%[4]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 4,
+ "expr": "runtime.GOARCH"
+ },
+ {
+ "id": "OsName",
+ "string": "%[5]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 5,
+ "expr": "version.OsName()"
+ },
+ {
+ "id": "NativeArch",
+ "string": "%[6]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 6,
+ "expr": "version.NativeArch()"
+ }
+ ]
+ },
+ {
+ "id": "Close",
+ "message": "Close",
+ "translation": "關閉",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "♥ &Donate!",
+ "message": "♥ &Donate!",
+ "translation": "♥ 捐贈! (&D)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status:",
+ "message": "Status:",
+ "translation": "狀態",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Deactivate",
+ "message": "&Deactivate",
+ "translation": "中斷連線 (&D)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Activate",
+ "message": "&Activate",
+ "translation": "連線 (&A)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Public key:",
+ "message": "Public key:",
+ "translation": "公鑰",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Listen port:",
+ "message": "Listen port:",
+ "translation": "監聽埠",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "MTU:",
+ "message": "MTU:",
+ "translation": "MTU",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses:",
+ "message": "Addresses:",
+ "translation": "位址",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "DNS servers:",
+ "message": "DNS servers:",
+ "translation": "DNS 伺服器",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Scripts:",
+ "message": "Scripts:",
+ "translation": "指令碼:",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Preshared key:",
+ "message": "Preshared key:",
+ "translation": "預交換金鑰",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Allowed IPs:",
+ "message": "Allowed IPs:",
+ "translation": "允許的位址",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Endpoint:",
+ "message": "Endpoint:",
+ "translation": "連接點",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Persistent keepalive:",
+ "message": "Persistent keepalive:",
+ "translation": "Keepalive 間隔",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Latest handshake:",
+ "message": "Latest handshake:",
+ "translation": "最後交握時間",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Transfer:",
+ "message": "Transfer:",
+ "translation": "流量",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-up",
+ "message": "pre-up",
+ "translation": "連接前",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-up",
+ "message": "post-up",
+ "translation": "連接後",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "pre-down",
+ "message": "pre-down",
+ "translation": "斷線前",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "post-down",
+ "message": "post-down",
+ "translation": "斷線後",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "disabled, per policy",
+ "message": "disabled, per policy",
+ "translation": "已關閉, 隨著策略",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "enabled",
+ "message": "enabled",
+ "translation": "已啓用",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{String} received, {String_1} sent",
+ "message": "{String} received, {String_1} sent",
+ "translation": "已收到 {String};已傳送 {String_1}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "String",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "c.RxBytes.String()"
+ },
+ {
+ "id": "String_1",
+ "string": "%[2]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "c.TxBytes.String()"
+ }
+ ]
+ },
+ {
+ "id": "Failed to determine tunnel state",
+ "message": "Failed to determine tunnel state",
+ "translation": "無法確認隧道狀態",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to activate tunnel",
+ "message": "Failed to activate tunnel",
+ "translation": "無法連接隧道",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Failed to deactivate tunnel",
+ "message": "Failed to deactivate tunnel",
+ "translation": "無法斷開隧道連線",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Interface: {Name}",
+ "message": "Interface: {Name}",
+ "translation": "[隧道] {Name}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "config.Name"
+ }
+ ]
+ },
+ {
+ "id": "Peer",
+ "message": "Peer",
+ "translation": "節點",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Create new tunnel",
+ "message": "Create new tunnel",
+ "translation": "建立新隧道",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit tunnel",
+ "message": "Edit tunnel",
+ "translation": "編輯隧道設定",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Name:",
+ "message": "&Name:",
+ "translation": "名稱 (&N)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Public key:",
+ "message": "&Public key:",
+ "translation": "公鑰 (&P)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "(unknown)",
+ "message": "(unknown)",
+ "translation": "(未知)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Block untunneled traffic (kill-switch)",
+ "message": "&Block untunneled traffic (kill-switch)",
+ "translation": "阻斷未經過隧道的流量(kill-switch) (&B)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save",
+ "message": "&Save",
+ "translation": "儲存 (&S)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Cancel",
+ "message": "Cancel",
+ "translation": "取消",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Configuration:",
+ "message": "&Configuration:",
+ "translation": "設定 (&C)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Invalid name",
+ "message": "Invalid name",
+ "translation": "無效的名稱",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A name is required.",
+ "message": "A name is required.",
+ "translation": "必須填寫名稱。",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel name ‘{NewName}’ is invalid.",
+ "message": "Tunnel name ‘{NewName}’ is invalid.",
+ "translation": "無效的隧道名稱「{NewName}」。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to list existing tunnels",
+ "message": "Unable to list existing tunnels",
+ "translation": "無法列出現有隧道",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel already exists",
+ "message": "Tunnel already exists",
+ "translation": "隧道已存在",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{NewName}’.",
+ "message": "Another tunnel already exists with the name ‘{NewName}’.",
+ "translation": "已有同名隧道「{NewName}」。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "NewName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "newName"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create new configuration",
+ "message": "Unable to create new configuration",
+ "translation": "無法建立新的隧道設定",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Writing file failed",
+ "message": "Writing file failed",
+ "translation": "檔案寫入失敗",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "message": "File ‘{FilePath}’ already exists.\n\nDo you want to overwrite it?",
+ "translation": "檔案已存在: {FilePath}\n\n您確定要覆蓋嗎?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "FilePath",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "filePath"
+ }
+ ]
+ },
+ {
+ "id": "Active",
+ "message": "Active",
+ "translation": "已連線",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Activating",
+ "message": "Activating",
+ "translation": "正在連線…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Inactive",
+ "message": "Inactive",
+ "translation": "已中斷連線",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Deactivating",
+ "message": "Deactivating",
+ "translation": "正在中斷…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unknown state",
+ "message": "Unknown state",
+ "translation": "未知",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log",
+ "message": "Log",
+ "translation": "日誌",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Copy",
+ "message": "&Copy",
+ "translation": "複製 (&C)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Select &all",
+ "message": "Select &all",
+ "translation": "全選 (&A)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Save to file…",
+ "message": "&Save to file…",
+ "translation": "匯出… (&S)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Time",
+ "message": "Time",
+ "translation": "時間",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Log message",
+ "message": "Log message",
+ "translation": "日誌訊息",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "message": "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ "translation": "純文字 (*.txt)|*.txt|所有檔案 (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export log to file",
+ "message": "Export log to file",
+ "translation": "匯出日誌…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&About WireGuard…",
+ "message": "&About WireGuard…",
+ "translation": "關於 WireGuard (&A)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnel Error",
+ "message": "Tunnel Error",
+ "translation": "隧道錯誤",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "message": "{ErrMsg}\n\nPlease consult the log for more information.",
+ "translation": "{ErrMsg}\n\n如需更多資訊,請查看日誌。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "ErrMsg",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errMsg"
+ }
+ ]
+ },
+ {
+ "id": "{Title} (out of date)",
+ "message": "{Title} (out of date)",
+ "translation": "{Title}(已過時)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Detection Error",
+ "message": "WireGuard Detection Error",
+ "translation": "偵測 WireGuard 錯誤",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to wait for WireGuard window to appear: {Err}",
+ "message": "Unable to wait for WireGuard window to appear: {Err}",
+ "translation": "無法等待 WireGuard 視窗開啓: {Err}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard: Deactivated",
+ "message": "WireGuard: Deactivated",
+ "translation": "WireGuard - 未連線",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Unknown",
+ "message": "Status: Unknown",
+ "translation": "[狀態] 未知",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Addresses: None",
+ "message": "Addresses: None",
+ "translation": "[位址] 無",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Manage tunnels…",
+ "message": "&Manage tunnels…",
+ "translation": "管理隧道 (&M)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Import tunnel(s) from file…",
+ "message": "&Import tunnel(s) from file…",
+ "translation": "從檔案匯入… (&I)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "E&xit",
+ "message": "E&xit",
+ "translation": "離開 (&X)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Activated",
+ "message": "WireGuard Activated",
+ "translation": "WireGuard 已連線",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been activated.",
+ "message": "The {Name} tunnel has been activated.",
+ "translation": "已連線至隧道 - {Name}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Deactivated",
+ "message": "WireGuard Deactivated",
+ "translation": "WireGuard 已中斷連線",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "The {Name} tunnel has been deactivated.",
+ "message": "The {Name} tunnel has been deactivated.",
+ "translation": "已中斷與隧道的連線 - {Name}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnel.Name"
+ }
+ ]
+ },
+ {
+ "id": "WireGuard Tunnel Error",
+ "message": "WireGuard Tunnel Error",
+ "translation": "WireGuard 隧道錯誤",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard: {TextForStateglobalState_true}",
+ "message": "WireGuard: {TextForStateglobalState_true}",
+ "translation": "WireGuard - {TextForStateglobalState_true}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TextForStateglobalState_true",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "textForState(globalState, true)"
+ }
+ ]
+ },
+ {
+ "id": "Status: {StateText}",
+ "message": "Status: {StateText}",
+ "translation": "[狀態] {StateText}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "StateText",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "stateText"
+ }
+ ]
+ },
+ {
+ "id": "Addresses: {EnumerationSeparator}",
+ "message": "Addresses: {EnumerationSeparator}",
+ "translation": "位址: {EnumerationSeparator}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "EnumerationSeparator",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "strings.Join(addrs, l18n.EnumerationSeparator())"
+ }
+ ]
+ },
+ {
+ "id": "An Update is Available!",
+ "message": "An Update is Available!",
+ "translation": "更新",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "WireGuard Update Available",
+ "message": "WireGuard Update Available",
+ "translation": "WireGuard 更新",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "message": "An update to WireGuard is now available. You are advised to update as soon as possible.",
+ "translation": "更新的 WireGuard 已經為您準備好了。\n強烈建議您立即更新 WireGuard。",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Tunnels",
+ "message": "Tunnels",
+ "translation": "隧道",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Edit",
+ "message": "&Edit",
+ "translation": "編輯 (&E)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add &empty tunnel…",
+ "message": "Add &empty tunnel…",
+ "translation": "新增隧道精靈 (&E)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Add Tunnel",
+ "message": "Add Tunnel",
+ "translation": "新增隧道",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Remove selected tunnel(s)",
+ "message": "Remove selected tunnel(s)",
+ "translation": "刪除選取隧道",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to zip",
+ "message": "Export all tunnels to zip",
+ "translation": "匯出所有隧道(ZIP 格式)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Toggle",
+ "message": "&Toggle",
+ "translation": "切換連線狀態 (&T)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export all tunnels to &zip…",
+ "message": "Export all tunnels to &zip…",
+ "translation": "匯出所有隧道至 &ZIP 壓縮檔",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Edit &selected tunnel…",
+ "message": "Edit &selected tunnel…",
+ "translation": "編輯選取隧道 (&S)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "&Remove selected tunnel(s)",
+ "message": "&Remove selected tunnel(s)",
+ "translation": "刪除已選取隧道 (&R)",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "no configuration files were found",
+ "message": "no configuration files were found",
+ "translation": "找不到設定檔",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Could not import selected configuration: {LastErr}",
+ "message": "Could not import selected configuration: {LastErr}",
+ "translation": "無法匯入設定: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Could not enumerate existing tunnels: {LastErr}",
+ "message": "Could not enumerate existing tunnels: {LastErr}",
+ "translation": "無法列出隧道: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Another tunnel already exists with the name ‘{Name}’",
+ "message": "Another tunnel already exists with the name ‘{Name}’",
+ "translation": "已有另一個同名的隧道「{Name}」",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Name",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "unparsedConfig.Name"
+ }
+ ]
+ },
+ {
+ "id": "Unable to import configuration: {LastErr}",
+ "message": "Unable to import configuration: {LastErr}",
+ "translation": "無法匯入設定: {LastErr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "LastErr",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "lastErr"
+ }
+ ]
+ },
+ {
+ "id": "Imported tunnels",
+ "message": "Imported tunnels",
+ "translation": "已匯入隧道",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Imported {M} tunnels",
+ "message": "Imported {M} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "M",
+ "cases": {
+ "other": {
+ "msg": "已匯入 {M} 個隧道"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ }
+ ]
+ },
+ {
+ "id": "Imported {M} of {N} tunnels",
+ "message": "Imported {M} of {N} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "N",
+ "cases": {
+ "other": {
+ "msg": "已匯入 {M} 個隧道(共 {N} 個)"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "M",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "m"
+ },
+ {
+ "id": "N",
+ "string": "%[2]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 2,
+ "expr": "n"
+ }
+ ]
+ },
+ {
+ "id": "Unable to create tunnel",
+ "message": "Unable to create tunnel",
+ "translation": "無法建立隧道",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Delete {TunnelCount} tunnels",
+ "message": "Delete {TunnelCount} tunnels",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "other": {
+ "msg": "刪除 {TunnelCount} 個隧道"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "message": "Are you sure you would like to delete {TunnelCount} tunnels?",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "TunnelCount",
+ "cases": {
+ "other": {
+ "msg": "您確定要刪除 {TunnelCount} 個隧道嗎?"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "TunnelCount",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "tunnelCount"
+ }
+ ]
+ },
+ {
+ "id": "Delete tunnel ‘{TunnelName}’",
+ "message": "Delete tunnel ‘{TunnelName}’",
+ "translation": "刪除隧道 - {TunnelName}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "message": "Are you sure you would like to delete tunnel ‘{TunnelName}’?",
+ "translation": "您確定要刪除隧道「{TunnelName}」嗎?",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "TunnelName",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "tunnelName"
+ }
+ ]
+ },
+ {
+ "id": "{Question} You cannot undo this action.",
+ "message": "{Question} You cannot undo this action.",
+ "translation": "{Question}\n\n您將無法復原此操作。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Question",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "question"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnel",
+ "message": "Unable to delete tunnel",
+ "translation": "無法刪除隧道",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "A tunnel was unable to be removed: {Error}",
+ "message": "A tunnel was unable to be removed: {Error}",
+ "translation": "無法刪除隧道: {Error}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Error",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "errors[0].Error()"
+ }
+ ]
+ },
+ {
+ "id": "Unable to delete tunnels",
+ "message": "Unable to delete tunnels",
+ "translation": "無法刪除隧道",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Lenerrors} tunnels were unable to be removed.",
+ "message": "{Lenerrors} tunnels were unable to be removed.",
+ "translation": {
+ "select": {
+ "feature": "plural",
+ "arg": "Lenerrors",
+ "cases": {
+ "other": {
+ "msg": "無法刪除 {Lenerrors} 個隧道"
+ }
+ }
+ }
+ },
+ "placeholders": [
+ {
+ "id": "Lenerrors",
+ "string": "%[1]d",
+ "type": "int",
+ "underlyingType": "int",
+ "argNum": 1,
+ "expr": "len(errors)"
+ }
+ ]
+ },
+ {
+ "id": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "message": "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
+ "translation": "隧道設定檔 (*.zip, *.conf)|*.zip;*.conf|所有檔案 (*.*)|*.*",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Import tunnel(s) from file",
+ "message": "Import tunnel(s) from file",
+ "translation": "從檔案中匯入隧道…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Configuration ZIP Files (*.zip)|*.zip",
+ "message": "Configuration ZIP Files (*.zip)|*.zip",
+ "translation": "隧道設定檔 (*.zip)|*.zip",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Export tunnels to zip",
+ "message": "Export tunnels to zip",
+ "translation": "匯出隧道設定至…",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "{Title} (unsigned build, no updates)",
+ "message": "{Title} (unsigned build, no updates)",
+ "translation": "{Title}(未簽署發行版本,無法自動更新)",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Title",
+ "string": "%[1]s",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 1,
+ "expr": "mtw.Title()"
+ }
+ ]
+ },
+ {
+ "id": "Error Exiting WireGuard",
+ "message": "Error Exiting WireGuard",
+ "translation": "離開 WireGuard 失敗",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "message": "Unable to exit service due to: {Err}. You may want to stop WireGuard from the service manager.",
+ "translation": "無法結束服務: {Err}。\n您可能需要手動從服務管理中結束 WireGuard 服務。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "message": "An update to WireGuard is available. It is highly advisable to update without delay.",
+ "translation": "更新的 WireGuard 已經為您準備好了。\n強烈建議您立即進行更新。",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for user",
+ "message": "Status: Waiting for user",
+ "translation": "狀態:等待使用者",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Update Now",
+ "message": "Update Now",
+ "translation": "立即更新",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Status: Waiting for updater service",
+ "message": "Status: Waiting for updater service",
+ "translation": "狀態:等待更新服務",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "Error: {Err}. Please try again.",
+ "message": "Error: {Err}. Please try again.",
+ "translation": "錯誤: {Err}。請稍後再試。",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Err",
+ "string": "%[1]v",
+ "type": "error",
+ "underlyingType": "interface{Error() string}",
+ "argNum": 1,
+ "expr": "err"
+ }
+ ]
+ },
+ {
+ "id": "Status: Complete!",
+ "message": "Status: Complete!",
+ "translation": "狀態:已完成!",
+ "translatorComment": "Copied from source."
+ },
+ {
+ "id": "http2: Framer {F}: failed to decode just-written frame",
+ "message": "http2: Framer {F}: failed to decode just-written frame",
+ "translation": "http2: Framer {F}: failed to decode just-written frame",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ }
+ ]
+ },
+ {
+ "id": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "message": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "translation": "http2: Framer {F}: wrote {Http2summarizeFramefr}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "F",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "f"
+ },
+ {
+ "id": "Http2summarizeFramefr",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(fr)"
+ }
+ ]
+ },
+ {
+ "id": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "message": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "translation": "http2: Framer {Fr}: read {Http2summarizeFramef}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "Fr",
+ "string": "%[1]p",
+ "type": "*net/http.http2Framer",
+ "underlyingType": "*net/http.http2Framer",
+ "argNum": 1,
+ "expr": "fr"
+ },
+ {
+ "id": "Http2summarizeFramef",
+ "string": "%[2]v",
+ "type": "string",
+ "underlyingType": "string",
+ "argNum": 2,
+ "expr": "http2summarizeFrame(f)"
+ }
+ ]
+ },
+ {
+ "id": "http2: decoded hpack field {HeaderField}",
+ "message": "http2: decoded hpack field {HeaderField}",
+ "translation": "http2: decoded hpack field {HeaderField}",
+ "translatorComment": "Copied from source.",
+ "placeholders": [
+ {
+ "id": "HeaderField",
+ "string": "%+[1]v",
+ "type": "vendor/golang.org/x/net/http2/hpack.HeaderField",
+ "underlyingType": "struct{Name string; Value string; Sensitive bool}",
+ "argNum": 1,
+ "expr": "hf"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/main.go b/main.go
index a84b4205..62a0b559 100644
--- a/main.go
+++ b/main.go
@@ -1,12 +1,16 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package main
import (
+ "debug/pe"
+ "errors"
"fmt"
+ "io"
+ "log"
"os"
"strconv"
"strings"
@@ -14,55 +18,94 @@ import (
"golang.org/x/sys/windows"
+ "golang.zx2c4.com/wireguard/windows/conf"
+ "golang.zx2c4.com/wireguard/windows/driver"
"golang.zx2c4.com/wireguard/windows/elevate"
+ "golang.zx2c4.com/wireguard/windows/l18n"
"golang.zx2c4.com/wireguard/windows/manager"
"golang.zx2c4.com/wireguard/windows/ringlogger"
"golang.zx2c4.com/wireguard/windows/tunnel"
"golang.zx2c4.com/wireguard/windows/ui"
+ "golang.zx2c4.com/wireguard/windows/updater"
)
-var flags = [...]string{
- "(no argument): elevate and install manager service for current user",
- "/installmanagerservice",
- "/installtunnelservice CONFIG_PATH",
- "/uninstallmanagerservice",
- "/uninstalltunnelservice TUNNEL_NAME",
- "/managerservice",
- "/tunnelservice CONFIG_PATH",
- "/ui CMD_READ_HANDLE CMD_WRITE_HANDLE CMD_EVENT_HANDLE LOG_MAPPING_HANDLE",
- "/dumplog OUTPUT_PATH",
+func setLogFile() {
+ logHandle, err := windows.GetStdHandle(windows.STD_ERROR_HANDLE)
+ if logHandle == 0 || err != nil {
+ logHandle, err = windows.GetStdHandle(windows.STD_OUTPUT_HANDLE)
+ }
+ if logHandle == 0 || err != nil {
+ log.SetOutput(io.Discard)
+ } else {
+ log.SetOutput(os.NewFile(uintptr(logHandle), "stderr"))
+ }
}
-func fatal(v ...interface{}) {
- windows.MessageBox(0, windows.StringToUTF16Ptr(fmt.Sprint(v...)), windows.StringToUTF16Ptr("Error"), windows.MB_ICONERROR)
- os.Exit(1)
+func fatal(v ...any) {
+ if log.Writer() == io.Discard {
+ windows.MessageBox(0, windows.StringToUTF16Ptr(fmt.Sprint(v...)), windows.StringToUTF16Ptr(l18n.Sprintf("Error")), windows.MB_ICONERROR)
+ os.Exit(1)
+ } else {
+ log.Fatal(append([]any{l18n.Sprintf("Error: ")}, v...))
+ }
}
-func fatalf(format string, v ...interface{}) {
- fatal(fmt.Sprintf(format, v...))
+func fatalf(format string, v ...any) {
+ fatal(l18n.Sprintf(format, v...))
}
-func info(title string, format string, v ...interface{}) {
- windows.MessageBox(0, windows.StringToUTF16Ptr(fmt.Sprintf(format, v...)), windows.StringToUTF16Ptr(title), windows.MB_ICONINFORMATION)
+func info(title, format string, v ...any) {
+ if log.Writer() == io.Discard {
+ windows.MessageBox(0, windows.StringToUTF16Ptr(l18n.Sprintf(format, v...)), windows.StringToUTF16Ptr(title), windows.MB_ICONINFORMATION)
+ } else {
+ log.Printf(title+":\n"+format, v...)
+ }
}
func usage() {
+ flags := [...]string{
+ l18n.Sprintf("(no argument): elevate and install manager service"),
+ "/installmanagerservice",
+ "/installtunnelservice CONFIG_PATH",
+ "/uninstallmanagerservice",
+ "/uninstalltunnelservice TUNNEL_NAME",
+ "/managerservice",
+ "/tunnelservice CONFIG_PATH",
+ "/ui CMD_READ_HANDLE CMD_WRITE_HANDLE CMD_EVENT_HANDLE LOG_MAPPING_HANDLE",
+ "/dumplog [/tail]",
+ "/update",
+ "/removedriver",
+ }
builder := strings.Builder{}
for _, flag := range flags {
builder.WriteString(fmt.Sprintf(" %s\n", flag))
}
- info("Command Line Options", "Usage: %s [\n%s]", os.Args[0], builder.String())
+ info(l18n.Sprintf("Command Line Options"), "Usage: %s [\n%s]", os.Args[0], builder.String())
os.Exit(1)
}
func checkForWow64() {
- var b bool
- err := windows.IsWow64Process(windows.CurrentProcess(), &b)
+ b, err := func() (bool, error) {
+ var processMachine, nativeMachine uint16
+ err := windows.IsWow64Process2(windows.CurrentProcess(), &processMachine, &nativeMachine)
+ if err == nil {
+ return processMachine != pe.IMAGE_FILE_MACHINE_UNKNOWN, nil
+ }
+ if !errors.Is(err, windows.ERROR_PROC_NOT_FOUND) {
+ return false, err
+ }
+ var b bool
+ err = windows.IsWow64Process(windows.CurrentProcess(), &b)
+ if err != nil {
+ return false, err
+ }
+ return b, nil
+ }()
if err != nil {
fatalf("Unable to determine whether the process is running under WOW64: %v", err)
}
if b {
- fatal("You must use the 64-bit version of WireGuard on this computer.")
+ fatalf("You must use the native version of WireGuard on this computer.")
}
}
@@ -92,11 +135,11 @@ func execElevatedManagerServiceInstaller() error {
return err
}
err = elevate.ShellExecute(path, "/installmanagerservice", "", windows.SW_SHOW)
- if err != nil {
+ if err != nil && err != windows.ERROR_CANCELLED {
return err
}
os.Exit(0)
- return windows.ERROR_ACCESS_DENIED // Not reached
+ return windows.ERROR_UNHANDLED_EXCEPTION // Not reached
}
func pipeFromHandleArgument(handleStr string) (*os.File, error) {
@@ -108,13 +151,18 @@ func pipeFromHandleArgument(handleStr string) (*os.File, error) {
}
func main() {
+ if windows.SetDllDirectory("") != nil || windows.SetDefaultDllDirectories(windows.LOAD_LIBRARY_SEARCH_SYSTEM32) != nil {
+ panic("failed to restrict dll search path")
+ }
+
+ setLogFile()
checkForWow64()
if len(os.Args) <= 1 {
- checkForAdminGroup()
if ui.RaiseUI() {
return
}
+ checkForAdminGroup()
err := execElevatedManagerServiceInstaller()
if err != nil {
fatal(err)
@@ -136,7 +184,7 @@ func main() {
}
checkForAdminDesktop()
time.Sleep(30 * time.Second)
- fatal("WireGuard system tray icon did not appear after 30 seconds.")
+ fatalf("WireGuard system tray icon did not appear after 30 seconds.")
return
case "/uninstallmanagerservice":
if len(os.Args) != 2 {
@@ -187,9 +235,18 @@ func main() {
if len(os.Args) != 6 {
usage()
}
- err := elevate.DropAllPrivileges(false)
- if err != nil {
- fatal(err)
+ var processToken windows.Token
+ isAdmin := false
+ err := windows.OpenProcessToken(windows.CurrentProcess(), windows.TOKEN_QUERY|windows.TOKEN_DUPLICATE, &processToken)
+ if err == nil {
+ isAdmin = elevate.TokenIsElevatedOrElevatable(processToken)
+ processToken.Close()
+ }
+ if isAdmin {
+ err := elevate.DropAllPrivileges(false)
+ if err != nil {
+ fatal(err)
+ }
}
readPipe, err := pipeFromHandleArgument(os.Args[2])
if err != nil {
@@ -208,18 +265,61 @@ func main() {
fatal(err)
}
manager.InitializeIPCClient(readPipe, writePipe, eventPipe)
+ ui.IsAdmin = isAdmin
ui.RunUI()
return
case "/dumplog":
- if len(os.Args) != 3 {
+ if len(os.Args) != 2 && len(os.Args) != 3 {
usage()
}
- file, err := os.Create(os.Args[2])
+ outputHandle, err := windows.GetStdHandle(windows.STD_OUTPUT_HANDLE)
if err != nil {
fatal(err)
}
+ if outputHandle == 0 {
+ fatal("Stdout must be set")
+ }
+ file := os.NewFile(uintptr(outputHandle), "stdout")
defer file.Close()
- err = ringlogger.DumpTo(file, true)
+ logPath, err := conf.LogFile(false)
+ if err != nil {
+ fatal(err)
+ }
+ err = ringlogger.DumpTo(logPath, file, len(os.Args) == 3 && os.Args[2] == "/tail")
+ if err != nil {
+ fatal(err)
+ }
+ return
+ case "/update":
+ if len(os.Args) != 2 {
+ usage()
+ }
+ for progress := range updater.DownloadVerifyAndExecute(0) {
+ if len(progress.Activity) > 0 {
+ if progress.BytesTotal > 0 || progress.BytesDownloaded > 0 {
+ var percent float64
+ if progress.BytesTotal > 0 {
+ percent = float64(progress.BytesDownloaded) / float64(progress.BytesTotal) * 100.0
+ }
+ log.Printf("%s: %d/%d (%.2f%%)\n", progress.Activity, progress.BytesDownloaded, progress.BytesTotal, percent)
+ } else {
+ log.Println(progress.Activity)
+ }
+ }
+ if progress.Error != nil {
+ log.Printf("Error: %v\n", progress.Error)
+ }
+ if progress.Complete || progress.Error != nil {
+ return
+ }
+ }
+ return
+ case "/removedriver":
+ if len(os.Args) != 2 {
+ usage()
+ }
+ _ = driver.UninstallLegacyWintun() // Best effort
+ err := driver.Uninstall()
if err != nil {
fatal(err)
}
diff --git a/manager/install.go b/manager/install.go
index f84a96ae..44a744cf 100644
--- a/manager/install.go
+++ b/manager/install.go
@@ -1,13 +1,15 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package manager
import (
"errors"
+ "log"
"os"
+ "strings"
"time"
"golang.org/x/sys/windows"
@@ -15,7 +17,6 @@ import (
"golang.org/x/sys/windows/svc/mgr"
"golang.zx2c4.com/wireguard/windows/conf"
- "golang.zx2c4.com/wireguard/windows/services"
)
var cachedServiceManager *mgr.Mgr
@@ -56,6 +57,12 @@ func InstallManager() error {
}
if status.State != svc.Stopped {
service.Close()
+ if status.State == svc.StartPending {
+ // We were *just* started by something else, so return success here, assuming the other program
+ // starting this does the right thing. This can happen when, e.g., the updater relaunches the
+ // manager service and then invokes wireguard.exe to raise the UI.
+ return nil
+ }
return ErrManagerAlreadyRunning
}
err = service.Delete()
@@ -122,7 +129,7 @@ func InstallTunnel(configPath string) error {
return err
}
- serviceName, err := services.ServiceNameOfTunnel(name)
+ serviceName, err := conf.ServiceNameOfTunnel(name)
if err != nil {
return err
}
@@ -156,7 +163,7 @@ func InstallTunnel(configPath string) error {
ServiceType: windows.SERVICE_WIN32_OWN_PROCESS,
StartType: mgr.StartAutomatic,
ErrorControl: mgr.ErrorNormal,
- Dependencies: []string{"Nsi"},
+ Dependencies: []string{"Nsi", "TcpIp"},
DisplayName: "WireGuard Tunnel: " + name,
SidType: windows.SERVICE_SID_TYPE_UNRESTRICTED,
}
@@ -175,7 +182,7 @@ func UninstallTunnel(name string) error {
if err != nil {
return err
}
- serviceName, err := services.ServiceNameOfTunnel(name)
+ serviceName, err := conf.ServiceNameOfTunnel(name)
if err != nil {
return err
}
@@ -191,3 +198,45 @@ func UninstallTunnel(name string) error {
}
return err2
}
+
+func changeTunnelServiceConfigFilePath(name, oldPath, newPath string) {
+ var err error
+ defer func() {
+ if err != nil {
+ log.Printf("Unable to change tunnel service command line argument from %#q to %#q: %v", oldPath, newPath, err)
+ }
+ }()
+ m, err := serviceManager()
+ if err != nil {
+ return
+ }
+ serviceName, err := conf.ServiceNameOfTunnel(name)
+ if err != nil {
+ return
+ }
+ service, err := m.OpenService(serviceName)
+ if err == windows.ERROR_SERVICE_DOES_NOT_EXIST {
+ err = nil
+ return
+ } else if err != nil {
+ return
+ }
+ defer service.Close()
+ config, err := service.Config()
+ if err != nil {
+ return
+ }
+ exePath, err := os.Executable()
+ if err != nil {
+ return
+ }
+ args, err := windows.DecomposeCommandLine(config.BinaryPathName)
+ if err != nil || len(args) != 3 ||
+ !strings.EqualFold(args[0], exePath) || args[1] != "/tunnelservice" || !strings.EqualFold(args[2], oldPath) {
+ err = nil
+ return
+ }
+ args[2] = newPath
+ config.BinaryPathName = windows.ComposeCommandLine(args)
+ err = service.UpdateConfig(config)
+}
diff --git a/manager/interfacecleanup.go b/manager/interfacecleanup.go
deleted file mode 100644
index f5d9ef48..00000000
--- a/manager/interfacecleanup.go
+++ /dev/null
@@ -1,58 +0,0 @@
-/* SPDX-License-Identifier: MIT
- *
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
- */
-
-package manager
-
-import (
- "log"
-
- "golang.org/x/sys/windows"
- "golang.org/x/sys/windows/svc"
- "golang.org/x/sys/windows/svc/mgr"
- "golang.zx2c4.com/wireguard/tun/wintun"
-
- "golang.zx2c4.com/wireguard/tun"
- "golang.zx2c4.com/wireguard/windows/services"
-)
-
-func cleanupStaleWintunInterfaces() {
- defer printPanic()
-
- m, err := mgr.Connect()
- if err != nil {
- return
- }
- defer m.Disconnect()
-
- tun.WintunPool.DeleteMatchingInterfaces(func(wintun *wintun.Interface) bool {
- interfaceName, err := wintun.Name()
- if err != nil {
- log.Printf("Removing Wintun interface %s because determining interface name failed: %v", wintun.GUID().String(), err)
- return true
- }
- serviceName, err := services.ServiceNameOfTunnel(interfaceName)
- if err != nil {
- log.Printf("Removing Wintun interface ‘%s’ because determining tunnel service name failed: %v", interfaceName, err)
- return true
- }
- service, err := m.OpenService(serviceName)
- if err == windows.ERROR_SERVICE_DOES_NOT_EXIST {
- log.Printf("Removing Wintun interface ‘%s’ because no service for it exists", interfaceName)
- return true
- } else if err != nil {
- return false
- }
- defer service.Close()
- status, err := service.Query()
- if err != nil {
- return false
- }
- if status.State == svc.Stopped {
- log.Printf("Removing Wintun interface ‘%s’ because its service is stopped", interfaceName)
- return true
- }
- return false
- })
-}
diff --git a/manager/ipc_client.go b/manager/ipc_client.go
index c8b2f852..8c9c4c04 100644
--- a/manager/ipc_client.go
+++ b/manager/ipc_client.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package manager
@@ -64,7 +64,7 @@ var (
)
type TunnelChangeCallback struct {
- cb func(tunnel *Tunnel, state TunnelState, globalState TunnelState, err error)
+ cb func(tunnel *Tunnel, state, globalState TunnelState, err error)
}
var tunnelChangeCallbacks = make(map[*TunnelChangeCallback]bool)
@@ -93,7 +93,7 @@ type UpdateProgressCallback struct {
var updateProgressCallbacks = make(map[*UpdateProgressCallback]bool)
-func InitializeIPCClient(reader *os.File, writer *os.File, events *os.File) {
+func InitializeIPCClient(reader, writer, events *os.File) {
rpcDecoder = gob.NewDecoder(reader)
rpcEncoder = gob.NewEncoder(writer)
go func() {
@@ -431,43 +431,52 @@ func IPCClientUpdate() error {
return rpcEncoder.Encode(UpdateMethodType)
}
-func IPCClientRegisterTunnelChange(cb func(tunnel *Tunnel, state TunnelState, globalState TunnelState, err error)) *TunnelChangeCallback {
+func IPCClientRegisterTunnelChange(cb func(tunnel *Tunnel, state, globalState TunnelState, err error)) *TunnelChangeCallback {
s := &TunnelChangeCallback{cb}
tunnelChangeCallbacks[s] = true
return s
}
+
func (cb *TunnelChangeCallback) Unregister() {
delete(tunnelChangeCallbacks, cb)
}
+
func IPCClientRegisterTunnelsChange(cb func()) *TunnelsChangeCallback {
s := &TunnelsChangeCallback{cb}
tunnelsChangeCallbacks[s] = true
return s
}
+
func (cb *TunnelsChangeCallback) Unregister() {
delete(tunnelsChangeCallbacks, cb)
}
+
func IPCClientRegisterManagerStopping(cb func()) *ManagerStoppingCallback {
s := &ManagerStoppingCallback{cb}
managerStoppingCallbacks[s] = true
return s
}
+
func (cb *ManagerStoppingCallback) Unregister() {
delete(managerStoppingCallbacks, cb)
}
+
func IPCClientRegisterUpdateFound(cb func(updateState UpdateState)) *UpdateFoundCallback {
s := &UpdateFoundCallback{cb}
updateFoundCallbacks[s] = true
return s
}
+
func (cb *UpdateFoundCallback) Unregister() {
delete(updateFoundCallbacks, cb)
}
+
func IPCClientRegisterUpdateProgress(cb func(dp updater.DownloadProgress)) *UpdateProgressCallback {
s := &UpdateProgressCallback{cb}
updateProgressCallbacks[s] = true
return s
}
+
func (cb *UpdateProgressCallback) Unregister() {
delete(updateProgressCallbacks, cb)
}
diff --git a/manager/ipc_driver.go b/manager/ipc_driver.go
new file mode 100644
index 00000000..6cb43c38
--- /dev/null
+++ b/manager/ipc_driver.go
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package manager
+
+import (
+ "sync"
+
+ "golang.zx2c4.com/wireguard/windows/driver"
+)
+
+type lockedDriverAdapter struct {
+ *driver.Adapter
+ sync.Mutex
+}
+
+var (
+ driverAdapters = make(map[string]*lockedDriverAdapter)
+ driverAdaptersLock sync.RWMutex
+)
+
+func findDriverAdapter(tunnelName string) (*lockedDriverAdapter, error) {
+ driverAdaptersLock.RLock()
+ driverAdapter, ok := driverAdapters[tunnelName]
+ if ok {
+ driverAdapter.Lock()
+ driverAdaptersLock.RUnlock()
+ return driverAdapter, nil
+ }
+ driverAdaptersLock.RUnlock()
+ driverAdaptersLock.Lock()
+ defer driverAdaptersLock.Unlock()
+ driverAdapter, ok = driverAdapters[tunnelName]
+ if ok {
+ driverAdapter.Lock()
+ return driverAdapter, nil
+ }
+ driverAdapter = &lockedDriverAdapter{}
+ var err error
+ driverAdapter.Adapter, err = driver.OpenAdapter(tunnelName)
+ if err != nil {
+ return nil, err
+ }
+ driverAdapters[tunnelName] = driverAdapter
+ driverAdapter.Lock()
+ return driverAdapter, nil
+}
+
+func releaseDriverAdapter(tunnelName string) {
+ driverAdaptersLock.Lock()
+ defer driverAdaptersLock.Unlock()
+ driverAdapter, ok := driverAdapters[tunnelName]
+ if !ok {
+ return
+ }
+ driverAdapter.Lock()
+ delete(driverAdapters, tunnelName)
+ driverAdapter.Unlock()
+}
diff --git a/manager/ipc_pipe.go b/manager/ipc_pipe.go
deleted file mode 100644
index d4214ac0..00000000
--- a/manager/ipc_pipe.go
+++ /dev/null
@@ -1,55 +0,0 @@
-/* SPDX-License-Identifier: MIT
- *
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
- */
-
-package manager
-
-import (
- "os"
- "strconv"
-
- "golang.org/x/sys/windows"
-)
-
-func makeInheritableAndGetStr(f *os.File) (str string, err error) {
- sc, err := f.SyscallConn()
- if err != nil {
- return
- }
- err2 := sc.Control(func(fd uintptr) {
- err = windows.SetHandleInformation(windows.Handle(fd), windows.HANDLE_FLAG_INHERIT, windows.HANDLE_FLAG_INHERIT)
- str = strconv.FormatUint(uint64(fd), 10)
- })
- if err2 != nil {
- err = err2
- }
- return
-}
-
-func inheritableEvents() (ourEvents *os.File, theirEvents *os.File, theirEventStr string, err error) {
- theirEvents, ourEvents, err = os.Pipe()
- if err != nil {
- return
- }
- theirEventStr, err = makeInheritableAndGetStr(theirEvents)
- return
-}
-
-func inheritableSocketpairEmulation() (ourReader *os.File, theirReader *os.File, theirReaderStr string, ourWriter *os.File, theirWriter *os.File, theirWriterStr string, err error) {
- ourReader, theirWriter, err = os.Pipe()
- if err != nil {
- return
- }
- theirWriterStr, err = makeInheritableAndGetStr(theirWriter)
- if err != nil {
- return
- }
-
- theirReader, ourWriter, err = os.Pipe()
- if err != nil {
- return
- }
- theirReaderStr, err = makeInheritableAndGetStr(theirReader)
- return
-}
diff --git a/manager/ipc_server.go b/manager/ipc_server.go
index 1367c2e9..e21ffaf0 100644
--- a/manager/ipc_server.go
+++ b/manager/ipc_server.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package manager
@@ -10,7 +10,6 @@ import (
"encoding/gob"
"fmt"
"io"
- "io/ioutil"
"log"
"os"
"sync"
@@ -20,64 +19,73 @@ import (
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc"
- "golang.zx2c4.com/wireguard/ipc/winpipe"
-
"golang.zx2c4.com/wireguard/windows/conf"
- "golang.zx2c4.com/wireguard/windows/services"
"golang.zx2c4.com/wireguard/windows/updater"
)
-var managerServices = make(map[*ManagerService]bool)
-var managerServicesLock sync.RWMutex
-var haveQuit uint32
-var quitManagersChan = make(chan struct{}, 1)
+var (
+ managerServices = make(map[*ManagerService]bool)
+ managerServicesLock sync.RWMutex
+ haveQuit uint32
+ quitManagersChan = make(chan struct{}, 1)
+)
type ManagerService struct {
events *os.File
+ eventLock sync.Mutex
elevatedToken windows.Token
}
func (s *ManagerService) StoredConfig(tunnelName string) (*conf.Config, error) {
- return conf.LoadFromName(tunnelName)
-}
-
-func (s *ManagerService) RuntimeConfig(tunnelName string) (*conf.Config, error) {
- storedConfig, err := conf.LoadFromName(tunnelName)
+ conf, err := conf.LoadFromName(tunnelName)
if err != nil {
return nil, err
}
- pipePath, err := services.PipePathOfTunnel(storedConfig.Name)
- if err != nil {
- return nil, err
+ if s.elevatedToken == 0 {
+ conf.Redact()
}
- localSystem, err := windows.CreateWellKnownSid(windows.WinLocalSystemSid)
+ return conf, nil
+}
+
+func (s *ManagerService) RuntimeConfig(tunnelName string) (*conf.Config, error) {
+ storedConfig, err := conf.LoadFromName(tunnelName)
if err != nil {
return nil, err
}
- pipe, err := winpipe.DialPipe(pipePath, nil, localSystem)
+ driverAdapter, err := findDriverAdapter(tunnelName)
if err != nil {
return nil, err
}
- defer pipe.Close()
- pipe.SetWriteDeadline(time.Now().Add(time.Second * 2))
- _, err = pipe.Write([]byte("get=1\n\n"))
+ runtimeConfig, err := driverAdapter.Configuration()
if err != nil {
+ driverAdapter.Unlock()
+ releaseDriverAdapter(tunnelName)
return nil, err
}
- pipe.SetReadDeadline(time.Now().Add(time.Second * 2))
- resp, err := ioutil.ReadAll(pipe)
- if err != nil {
- return nil, err
+ conf := conf.FromDriverConfiguration(runtimeConfig, storedConfig)
+ driverAdapter.Unlock()
+ if s.elevatedToken == 0 {
+ conf.Redact()
}
- return conf.FromUAPI(string(resp), storedConfig)
+ return conf, nil
}
func (s *ManagerService) Start(tunnelName string) error {
- // For now, enforce only one tunnel at a time. Later we'll remove this silly restriction.
+ c, err := conf.LoadFromName(tunnelName)
+ if err != nil {
+ return err
+ }
+
+ // Figure out which tunnels have intersecting addresses/routes and stop those.
trackedTunnelsLock.Lock()
tt := make([]string, 0, len(trackedTunnels))
var inTransition string
for t, state := range trackedTunnels {
+ c2, err := conf.LoadFromName(t)
+ if err != nil || !c.IntersectsWith(c2) {
+ // If we can't get the config, assume it doesn't intersect.
+ continue
+ }
tt = append(tt, t)
if len(t) > 0 && (state == TunnelStarting || state == TunnelUnknown) {
inTransition = t
@@ -88,6 +96,8 @@ func (s *ManagerService) Start(tunnelName string) error {
if len(inTransition) != 0 {
return fmt.Errorf("Please allow the tunnel ‘%s’ to finish activating", inTransition)
}
+
+ // Stop those intersecting tunnels asynchronously.
go func() {
for _, t := range tt {
s.Stop(t)
@@ -101,13 +111,7 @@ func (s *ManagerService) Start(tunnelName string) error {
}
}
}()
- time.AfterFunc(time.Second*10, cleanupStaleWintunInterfaces)
-
- // After that process is started -- it's somewhat asynchronous -- we install the new one.
- c, err := conf.LoadFromName(tunnelName)
- if err != nil {
- return err
- }
+ // After the stop process has begun, but before it's finished, we install the new one.
path, err := c.Path()
if err != nil {
return err
@@ -116,8 +120,6 @@ func (s *ManagerService) Start(tunnelName string) error {
}
func (s *ManagerService) Stop(tunnelName string) error {
- time.AfterFunc(time.Second*10, cleanupStaleWintunInterfaces)
-
err := UninstallTunnel(tunnelName)
if err == windows.ERROR_SERVICE_DOES_NOT_EXIST {
_, notExistsError := conf.LoadFromName(tunnelName)
@@ -129,7 +131,7 @@ func (s *ManagerService) Stop(tunnelName string) error {
}
func (s *ManagerService) WaitForStop(tunnelName string) error {
- serviceName, err := services.ServiceNameOfTunnel(tunnelName)
+ serviceName, err := conf.ServiceNameOfTunnel(tunnelName)
if err != nil {
return err
}
@@ -149,6 +151,9 @@ func (s *ManagerService) WaitForStop(tunnelName string) error {
}
func (s *ManagerService) Delete(tunnelName string) error {
+ if s.elevatedToken == 0 {
+ return windows.ERROR_ACCESS_DENIED
+ }
err := s.Stop(tunnelName)
if err != nil {
return err
@@ -157,7 +162,7 @@ func (s *ManagerService) Delete(tunnelName string) error {
}
func (s *ManagerService) State(tunnelName string) (TunnelState, error) {
- serviceName, err := services.ServiceNameOfTunnel(tunnelName)
+ serviceName, err := conf.ServiceNameOfTunnel(tunnelName)
if err != nil {
return 0, err
}
@@ -193,7 +198,10 @@ func (s *ManagerService) GlobalState() TunnelState {
}
func (s *ManagerService) Create(tunnelConfig *conf.Config) (*Tunnel, error) {
- err := tunnelConfig.Save()
+ if s.elevatedToken == 0 {
+ return nil, windows.ERROR_ACCESS_DENIED
+ }
+ err := tunnelConfig.Save(true)
if err != nil {
return nil, err
}
@@ -209,19 +217,25 @@ func (s *ManagerService) Tunnels() ([]Tunnel, error) {
}
tunnels := make([]Tunnel, len(names))
for i := 0; i < len(tunnels); i++ {
- (tunnels)[i].Name = names[i]
+ tunnels[i].Name = names[i]
}
return tunnels, nil
// TODO: account for running ones that aren't in the configuration store somehow
}
func (s *ManagerService) Quit(stopTunnelsOnQuit bool) (alreadyQuit bool, err error) {
+ if s.elevatedToken == 0 {
+ return false, windows.ERROR_ACCESS_DENIED
+ }
if !atomic.CompareAndSwapUint32(&haveQuit, 0, 1) {
return true, nil
}
// Work around potential race condition of delivering messages to the wrong process by removing from notifications.
managerServicesLock.Lock()
+ s.eventLock.Lock()
+ s.events = nil
+ s.eventLock.Unlock()
delete(managerServices, s)
managerServicesLock.Unlock()
@@ -244,6 +258,9 @@ func (s *ManagerService) UpdateState() UpdateState {
}
func (s *ManagerService) Update() {
+ if s.elevatedToken == 0 {
+ return
+ }
progress := updater.DownloadVerifyAndExecute(uintptr(s.elevatedToken))
go func() {
for {
@@ -374,6 +391,9 @@ func (s *ManagerService) ServeConn(reader io.Reader, writer io.Writer) {
return
}
tunnel, retErr := s.Create(&config)
+ if tunnel == nil {
+ tunnel = &Tunnel{}
+ }
err = encoder.Encode(tunnel)
if err != nil {
return
@@ -421,26 +441,27 @@ func (s *ManagerService) ServeConn(reader io.Reader, writer io.Writer) {
}
}
-func IPCServerListen(reader *os.File, writer *os.File, events *os.File, elevatedToken windows.Token) {
+func IPCServerListen(reader, writer, events *os.File, elevatedToken windows.Token) {
service := &ManagerService{
events: events,
elevatedToken: elevatedToken,
}
go func() {
- defer printPanic()
managerServicesLock.Lock()
managerServices[service] = true
managerServicesLock.Unlock()
service.ServeConn(reader, writer)
managerServicesLock.Lock()
+ service.eventLock.Lock()
+ service.events = nil
+ service.eventLock.Unlock()
delete(managerServices, service)
managerServicesLock.Unlock()
-
}()
}
-func notifyAll(notificationType NotificationType, ifaces ...interface{}) {
+func notifyAll(notificationType NotificationType, adminOnly bool, ifaces ...any) {
if len(managerServices) == 0 {
return
}
@@ -460,8 +481,17 @@ func notifyAll(notificationType NotificationType, ifaces ...interface{}) {
managerServicesLock.RLock()
for m := range managerServices {
- m.events.SetWriteDeadline(time.Now().Add(time.Second))
- m.events.Write(buf.Bytes())
+ if m.elevatedToken == 0 && adminOnly {
+ continue
+ }
+ go func(m *ManagerService) {
+ m.eventLock.Lock()
+ defer m.eventLock.Unlock()
+ if m.events != nil {
+ m.events.SetWriteDeadline(time.Now().Add(time.Second))
+ m.events.Write(buf.Bytes())
+ }
+ }(m)
}
managerServicesLock.RUnlock()
}
@@ -474,22 +504,22 @@ func errToString(err error) string {
}
func IPCServerNotifyTunnelChange(name string, state TunnelState, err error) {
- notifyAll(TunnelChangeNotificationType, name, state, trackedTunnelsGlobalState(), errToString(err))
+ notifyAll(TunnelChangeNotificationType, false, name, state, trackedTunnelsGlobalState(), errToString(err))
}
func IPCServerNotifyTunnelsChange() {
- notifyAll(TunnelsChangeNotificationType)
+ notifyAll(TunnelsChangeNotificationType, false)
}
func IPCServerNotifyUpdateFound(state UpdateState) {
- notifyAll(UpdateFoundNotificationType, state)
+ notifyAll(UpdateFoundNotificationType, false, state)
}
func IPCServerNotifyUpdateProgress(dp updater.DownloadProgress) {
- notifyAll(UpdateProgressNotificationType, dp.Activity, dp.BytesDownloaded, dp.BytesTotal, errToString(dp.Error), dp.Complete)
+ notifyAll(UpdateProgressNotificationType, true, dp.Activity, dp.BytesDownloaded, dp.BytesTotal, errToString(dp.Error), dp.Complete)
}
func IPCServerNotifyManagerStopping() {
- notifyAll(ManagerStoppingNotificationType)
+ notifyAll(ManagerStoppingNotificationType, false)
time.Sleep(time.Millisecond * 200)
}
diff --git a/manager/service.go b/manager/service.go
index e493e7cb..47e20d45 100644
--- a/manager/service.go
+++ b/manager/service.go
@@ -1,46 +1,32 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package manager
import (
"errors"
- "fmt"
"log"
"os"
"runtime"
- "runtime/debug"
- "strings"
+ "strconv"
"sync"
- "syscall"
"time"
"unsafe"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc"
+ "golang.zx2c4.com/wireguard/windows/driver"
"golang.zx2c4.com/wireguard/windows/conf"
"golang.zx2c4.com/wireguard/windows/elevate"
"golang.zx2c4.com/wireguard/windows/ringlogger"
"golang.zx2c4.com/wireguard/windows/services"
- "golang.zx2c4.com/wireguard/windows/version"
)
type managerService struct{}
-func printPanic() {
- if x := recover(); x != nil {
- for _, line := range append([]string{fmt.Sprint(x)}, strings.Split(string(debug.Stack()), "\n")...) {
- if len(strings.TrimSpace(line)) > 0 {
- log.Println(line)
- }
- }
- panic(x)
- }
-}
-
func (service *managerService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) {
changes <- svc.Status{State: svc.StartPending}
@@ -56,40 +42,40 @@ func (service *managerService) Execute(args []string, r <-chan svc.ChangeRequest
changes <- svc.Status{State: svc.StopPending}
}()
- err = ringlogger.InitGlobalLogger("MGR")
+ var logFile string
+ logFile, err = conf.LogFile(true)
if err != nil {
serviceError = services.ErrorRingloggerOpen
return
}
- defer printPanic()
-
- log.Println("Starting", version.UserAgent())
-
- path, err := os.Executable()
+ err = ringlogger.InitGlobalLogger(logFile, "MGR")
if err != nil {
- serviceError = services.ErrorDetermineExecutablePath
+ serviceError = services.ErrorRingloggerOpen
return
}
- devNull, err := os.OpenFile(os.DevNull, os.O_RDWR, 0)
+ services.PrintStarting()
+
+ path, err := os.Executable()
if err != nil {
- serviceError = services.ErrorOpenNULFile
+ serviceError = services.ErrorDetermineExecutablePath
return
}
- err = trackExistingTunnels()
+ err = watchNewTunnelServices()
if err != nil {
serviceError = services.ErrorTrackTunnels
return
}
- conf.RegisterStoreChangeCallback(func() { conf.MigrateUnencryptedConfigs() }) // Ignore return value for now, but could be useful later.
+ conf.RegisterStoreChangeCallback(func() { conf.MigrateUnencryptedConfigs(changeTunnelServiceConfigFilePath) })
conf.RegisterStoreChangeCallback(IPCServerNotifyTunnelsChange)
- procs := make(map[uint32]*os.Process)
+ procs := make(map[uint32]*uiProcess)
aliveSessions := make(map[uint32]bool)
procsLock := sync.Mutex{}
stoppingManager := false
+ operatorGroupSid, _ := windows.CreateWellKnownSid(windows.WinBuiltinNetworkConfigurationOperatorsSid)
startProcess := func(session uint32) {
defer func() {
@@ -104,7 +90,24 @@ func (service *managerService) Execute(args []string, r <-chan svc.ChangeRequest
if err != nil {
return
}
- if !elevate.TokenIsElevatedOrElevatable(userToken) {
+ isAdmin := elevate.TokenIsElevatedOrElevatable(userToken)
+ isOperator := false
+ if !isAdmin && conf.AdminBool("LimitedOperatorUI") && operatorGroupSid != nil {
+ linkedToken, err := userToken.GetLinkedToken()
+ var impersonationToken windows.Token
+ if err == nil {
+ err = windows.DuplicateTokenEx(linkedToken, windows.TOKEN_QUERY, nil, windows.SecurityImpersonation, windows.TokenImpersonation, &impersonationToken)
+ linkedToken.Close()
+ } else {
+ err = windows.DuplicateTokenEx(userToken, windows.TOKEN_QUERY, nil, windows.SecurityImpersonation, windows.TokenImpersonation, &impersonationToken)
+ }
+ if err == nil {
+ isOperator, err = impersonationToken.IsMember(operatorGroupSid)
+ isOperator = isOperator && err == nil
+ impersonationToken.Close()
+ }
+ }
+ if !isAdmin && !isOperator {
userToken.Close()
return
}
@@ -124,23 +127,29 @@ func (service *managerService) Execute(args []string, r <-chan svc.ChangeRequest
userToken.Close()
return
}
- var elevatedToken windows.Token
- if userToken.IsElevated() {
- elevatedToken = userToken
- } else {
- elevatedToken, err = userToken.GetLinkedToken()
- userToken.Close()
- if err != nil {
- log.Printf("Unable to elevate token: %v", err)
- return
- }
- if !elevatedToken.IsElevated() {
- elevatedToken.Close()
- log.Println("Linked token is not elevated")
- return
+ userProfileDirectory, _ := userToken.GetUserProfileDirectory()
+ var elevatedToken, runToken windows.Token
+ if isAdmin {
+ if userToken.IsElevated() {
+ elevatedToken = userToken
+ } else {
+ elevatedToken, err = userToken.GetLinkedToken()
+ userToken.Close()
+ if err != nil {
+ log.Printf("Unable to elevate token: %v", err)
+ return
+ }
+ if !elevatedToken.IsElevated() {
+ elevatedToken.Close()
+ log.Println("Linked token is not elevated")
+ return
+ }
}
+ runToken = elevatedToken
+ } else {
+ runToken = userToken
}
- defer elevatedToken.Close()
+ defer runToken.Close()
userToken = 0
first := true
for {
@@ -161,38 +170,45 @@ func (service *managerService) Execute(args []string, r <-chan svc.ChangeRequest
first = false
}
- // TODO: we lock the OS thread so that these inheritable handles don't escape into other processes that
- // might be running in parallel Go routines. But the Go runtime is strange and who knows what's really
- // happening with these or what is inherited. We need to do some analysis to be certain of what's going on.
- runtime.LockOSThread()
- ourReader, theirReader, theirReaderStr, ourWriter, theirWriter, theirWriterStr, err := inheritableSocketpairEmulation()
+ ourReader, theirWriter, err := os.Pipe()
+ if err != nil {
+ log.Printf("Unable to create pipe: %v", err)
+ return
+ }
+ theirReader, ourWriter, err := os.Pipe()
if err != nil {
- log.Printf("Unable to create two inheritable RPC pipes: %v", err)
+ log.Printf("Unable to create pipe: %v", err)
return
}
- ourEvents, theirEvents, theirEventStr, err := inheritableEvents()
+ theirEvents, ourEvents, err := os.Pipe()
if err != nil {
- log.Printf("Unable to create one inheritable events pipe: %v", err)
+ log.Printf("Unable to create pipe: %v", err)
return
}
IPCServerListen(ourReader, ourWriter, ourEvents, elevatedToken)
- theirLogMapping, theirLogMappingHandle, err := ringlogger.Global.ExportInheritableMappingHandleStr()
+ theirLogMapping, err := ringlogger.Global.ExportInheritableMappingHandle()
if err != nil {
log.Printf("Unable to export inheritable mapping handle for logging: %v", err)
return
}
log.Printf("Starting UI process for user ‘%s@%s’ for session %d", username, domain, session)
- attr := &os.ProcAttr{
- Sys: &syscall.SysProcAttr{
- Token: syscall.Token(elevatedToken),
- },
- Files: []*os.File{devNull, devNull, devNull},
- }
procsLock.Lock()
- var proc *os.Process
+ var proc *uiProcess
if alive := aliveSessions[session]; alive {
- proc, err = os.StartProcess(path, []string{path, "/ui", theirReaderStr, theirWriterStr, theirEventStr, theirLogMapping}, attr)
+ proc, err = launchUIProcess(path, []string{
+ path,
+ "/ui",
+ strconv.FormatUint(uint64(theirReader.Fd()), 10),
+ strconv.FormatUint(uint64(theirWriter.Fd()), 10),
+ strconv.FormatUint(uint64(theirEvents.Fd()), 10),
+ strconv.FormatUint(uint64(theirLogMapping), 10),
+ }, userProfileDirectory, []windows.Handle{
+ windows.Handle(theirReader.Fd()),
+ windows.Handle(theirWriter.Fd()),
+ windows.Handle(theirEvents.Fd()),
+ theirLogMapping,
+ }, runToken)
} else {
err = errors.New("Session has logged out")
}
@@ -200,8 +216,7 @@ func (service *managerService) Execute(args []string, r <-chan svc.ChangeRequest
theirReader.Close()
theirWriter.Close()
theirEvents.Close()
- windows.Close(theirLogMappingHandle)
- runtime.UnlockOSThread()
+ windows.CloseHandle(theirLogMapping)
if err != nil {
ourReader.Close()
ourWriter.Close()
@@ -215,9 +230,7 @@ func (service *managerService) Execute(args []string, r <-chan svc.ChangeRequest
procsLock.Unlock()
sessionIsDead := false
- processStatus, err := proc.Wait()
- if err == nil {
- exitCode := processStatus.Sys().(syscall.WaitStatus).ExitCode
+ if exitCode, err := proc.Wait(); err == nil {
log.Printf("Exited UI process for user '%s@%s' for session %d with status %x", username, domain, session, exitCode)
const STATUS_DLL_INIT_FAILED_LOGOFF = 0xC000026B
sessionIsDead = exitCode == STATUS_DLL_INIT_FAILED_LOGOFF
@@ -241,14 +254,13 @@ func (service *managerService) Execute(args []string, r <-chan svc.ChangeRequest
goStartProcess := func(session uint32) {
procsGroup.Add(1)
go func() {
- defer printPanic()
startProcess(session)
procsGroup.Done()
}()
}
- time.AfterFunc(time.Second*10, cleanupStaleWintunInterfaces)
go checkForUpdates()
+ go driver.UninstallLegacyWintun() // We uninstall opportunistically here, so that we don't have to carry around the uninstaller code forever.
var sessionsPointer *windows.WTS_SESSION_INFO
var count uint32
@@ -257,12 +269,7 @@ func (service *managerService) Execute(args []string, r <-chan svc.ChangeRequest
serviceError = services.ErrorEnumerateSessions
return
}
- sessions := *(*[]windows.WTS_SESSION_INFO)(unsafe.Pointer(&struct {
- addr *windows.WTS_SESSION_INFO
- len int
- cap int
- }{sessionsPointer, int(count), int(count)}))
- for _, session := range sessions {
+ for _, session := range unsafe.Slice(sessionsPointer, count) {
if session.State != windows.WTSActive && session.State != windows.WTSDisconnected {
continue
}
diff --git a/manager/tunneltracker.go b/manager/tunneltracker.go
index 0f222aac..9003d445 100644
--- a/manager/tunneltracker.go
+++ b/manager/tunneltracker.go
@@ -1,17 +1,20 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package manager
import (
+ "errors"
"fmt"
"log"
"runtime"
"sync"
+ "sync/atomic"
"syscall"
"time"
+ "unsafe"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc"
@@ -21,50 +24,10 @@ import (
"golang.zx2c4.com/wireguard/windows/services"
)
-func trackExistingTunnels() error {
- m, err := serviceManager()
- if err != nil {
- return err
- }
- names, err := conf.ListConfigNames()
- if err != nil {
- return err
- }
- for _, name := range names {
- serviceName, err := services.ServiceNameOfTunnel(name)
- if err != nil {
- continue
- }
- service, err := m.OpenService(serviceName)
- if err != nil {
- continue
- }
- go trackTunnelService(name, service)
- }
- return nil
-}
-
-var serviceTrackerCallbackPtr = windows.NewCallback(func(notifier *windows.SERVICE_NOTIFY) uintptr {
- return 0
-})
-
-var trackedTunnels = make(map[string]TunnelState)
-var trackedTunnelsLock = sync.Mutex{}
-
-func svcStateToTunState(s svc.State) TunnelState {
- switch s {
- case svc.StartPending:
- return TunnelStarting
- case svc.Running:
- return TunnelStarted
- case svc.StopPending:
- return TunnelStopping
- case svc.Stopped:
- return TunnelStopped
- default:
- return TunnelUnknown
- }
-}
+var (
+ trackedTunnels = make(map[string]TunnelState)
+ trackedTunnelsLock = sync.Mutex{}
+)
func trackedTunnelsGlobalState() (state TunnelState) {
state = TunnelStopped
@@ -82,50 +45,95 @@ func trackedTunnelsGlobalState() (state TunnelState) {
return
}
-func trackTunnelService(tunnelName string, service *mgr.Service) {
- defer func() {
- service.Close()
- log.Printf("[%s] Tunnel service tracker finished", tunnelName)
- }()
+var serviceTrackerCallbackPtr = windows.NewCallback(func(notifier *windows.SERVICE_NOTIFY) uintptr {
+ return 0
+})
- trackedTunnelsLock.Lock()
- if _, found := trackedTunnels[tunnelName]; found {
- trackedTunnelsLock.Unlock()
- return
+type serviceSubscriptionState struct {
+ service *mgr.Service
+ cb func(status uint32) bool
+ done sync.WaitGroup
+ once uint32
+}
+
+var serviceSubscriptionCallbackPtr = windows.NewCallback(func(notification uint32, context uintptr) uintptr {
+ state := (*serviceSubscriptionState)(unsafe.Pointer(context))
+ if atomic.LoadUint32(&state.once) != 0 {
+ return 0
}
- trackedTunnels[tunnelName] = TunnelUnknown
- trackedTunnelsLock.Unlock()
- defer func() {
- trackedTunnelsLock.Lock()
- delete(trackedTunnels, tunnelName)
- trackedTunnelsLock.Unlock()
- }()
+ if notification == 0 {
+ status, err := state.service.Query()
+ if err == nil {
+ notification = svcStateToNotifyState(uint32(status.State))
+ }
+ }
+ if state.cb(notification) && atomic.CompareAndSwapUint32(&state.once, 0, 1) {
+ state.done.Done()
+ }
+ return 0
+})
- const serviceNotifications = windows.SERVICE_NOTIFY_RUNNING | windows.SERVICE_NOTIFY_START_PENDING | windows.SERVICE_NOTIFY_STOP_PENDING | windows.SERVICE_NOTIFY_STOPPED | windows.SERVICE_NOTIFY_DELETE_PENDING
- notifier := &windows.SERVICE_NOTIFY{
- Version: windows.SERVICE_NOTIFY_STATUS_CHANGE,
- NotifyCallback: serviceTrackerCallbackPtr,
+func svcStateToNotifyState(s uint32) uint32 {
+ switch s {
+ case windows.SERVICE_STOPPED:
+ return windows.SERVICE_NOTIFY_STOPPED
+ case windows.SERVICE_START_PENDING:
+ return windows.SERVICE_NOTIFY_START_PENDING
+ case windows.SERVICE_STOP_PENDING:
+ return windows.SERVICE_NOTIFY_STOP_PENDING
+ case windows.SERVICE_RUNNING:
+ return windows.SERVICE_NOTIFY_RUNNING
+ case windows.SERVICE_CONTINUE_PENDING:
+ return windows.SERVICE_NOTIFY_CONTINUE_PENDING
+ case windows.SERVICE_PAUSE_PENDING:
+ return windows.SERVICE_NOTIFY_PAUSE_PENDING
+ case windows.SERVICE_PAUSED:
+ return windows.SERVICE_NOTIFY_PAUSED
+ case windows.SERVICE_NO_CHANGE:
+ return 0
+ default:
+ return 0
}
+}
- checkForDisabled := func() (shouldReturn bool) {
- config, err := service.Config()
- if err == windows.ERROR_SERVICE_MARKED_FOR_DELETE || config.StartType == windows.SERVICE_DISABLED {
- log.Printf("[%s] Found disabled service via timeout, so deleting", tunnelName)
- service.Delete()
- trackedTunnelsLock.Lock()
- trackedTunnels[tunnelName] = TunnelStopped
- trackedTunnelsLock.Unlock()
- IPCServerNotifyTunnelChange(tunnelName, TunnelStopped, nil)
- return true
+func notifyStateToTunState(s uint32) TunnelState {
+ if s&(windows.SERVICE_NOTIFY_STOPPED|windows.SERVICE_NOTIFY_DELETED) != 0 {
+ return TunnelStopped
+ } else if s&(windows.SERVICE_NOTIFY_DELETE_PENDING|windows.SERVICE_NOTIFY_STOP_PENDING) != 0 {
+ return TunnelStopping
+ } else if s&windows.SERVICE_NOTIFY_RUNNING != 0 {
+ return TunnelStarted
+ } else if s&windows.SERVICE_NOTIFY_START_PENDING != 0 {
+ return TunnelStarting
+ } else {
+ return TunnelUnknown
+ }
+}
+
+func trackService(service *mgr.Service, callback func(status uint32) bool) error {
+ var subscription uintptr
+ state := &serviceSubscriptionState{service: service, cb: callback}
+ state.done.Add(1)
+ err := windows.SubscribeServiceChangeNotifications(service.Handle, windows.SC_EVENT_STATUS_CHANGE, serviceSubscriptionCallbackPtr, uintptr(unsafe.Pointer(state)), &subscription)
+ if err == nil {
+ defer windows.UnsubscribeServiceChangeNotifications(subscription)
+ status, err := service.Query()
+ if err == nil {
+ if callback(svcStateToNotifyState(uint32(status.State))) {
+ return nil
+ }
}
- return false
+ state.done.Wait()
+ runtime.KeepAlive(state.cb)
+ return nil
}
- if checkForDisabled() {
- return
+ if !errors.Is(err, windows.ERROR_PROC_NOT_FOUND) {
+ return err
}
- runtime.LockOSThread()
+ // TODO: Below this line is Windows 7 compatibility code, which hopefully we can delete at some point.
+ runtime.LockOSThread()
// This line would be fitting but is intentionally commented out:
//
// defer runtime.UnlockOSThread()
@@ -134,7 +142,11 @@ func trackTunnelService(tunnelName string, service *mgr.Service) {
// with the thread local context, which in turn appears to corrupt Go's own usage of TLS,
// leading to crashes sometime later (usually in runtime_unlock()) when the thread is recycled.
- lastState := TunnelUnknown
+ const serviceNotifications = windows.SERVICE_NOTIFY_RUNNING | windows.SERVICE_NOTIFY_START_PENDING | windows.SERVICE_NOTIFY_STOP_PENDING | windows.SERVICE_NOTIFY_STOPPED | windows.SERVICE_NOTIFY_DELETE_PENDING
+ notifier := &windows.SERVICE_NOTIFY{
+ Version: windows.SERVICE_NOTIFY_STATUS_CHANGE,
+ NotifyCallback: serviceTrackerCallbackPtr,
+ }
for {
err := windows.NotifyServiceStatusChange(service.Handle, serviceNotifications, notifier)
switch err {
@@ -142,42 +154,94 @@ func trackTunnelService(tunnelName string, service *mgr.Service) {
for {
if windows.SleepEx(uint32(time.Second*3/time.Millisecond), true) == windows.WAIT_IO_COMPLETION {
break
- } else if checkForDisabled() {
- return
+ } else if callback(0) {
+ return nil
}
}
case windows.ERROR_SERVICE_MARKED_FOR_DELETE:
- trackedTunnelsLock.Lock()
- trackedTunnels[tunnelName] = TunnelStopped
- trackedTunnelsLock.Unlock()
- IPCServerNotifyTunnelChange(tunnelName, TunnelStopped, nil)
- return
+ // Should be SERVICE_NOTIFY_DELETE_PENDING, but actually, we must release the handle and return here; otherwise it never deletes.
+ if callback(windows.SERVICE_NOTIFY_DELETED) {
+ return nil
+ }
case windows.ERROR_SERVICE_NOTIFY_CLIENT_LAGGING:
continue
default:
+ return err
+ }
+ if callback(svcStateToNotifyState(notifier.ServiceStatus.CurrentState)) {
+ return nil
+ }
+ }
+}
+
+func trackTunnelService(tunnelName string, service *mgr.Service) {
+ trackedTunnelsLock.Lock()
+ if _, found := trackedTunnels[tunnelName]; found {
+ trackedTunnelsLock.Unlock()
+ service.Close()
+ return
+ }
+
+ defer func() {
+ service.Close()
+ log.Printf("[%s] Tunnel service tracker finished", tunnelName)
+ }()
+ trackedTunnels[tunnelName] = TunnelUnknown
+ trackedTunnelsLock.Unlock()
+ defer func() {
+ trackedTunnelsLock.Lock()
+ delete(trackedTunnels, tunnelName)
+ trackedTunnelsLock.Unlock()
+ }()
+
+ for i := 0; i < 20; i++ {
+ if i > 0 {
+ time.Sleep(time.Second / 5)
+ }
+ if status, err := service.Query(); err != nil || status.State != svc.Stopped {
+ break
+ }
+ }
+
+ checkForDisabled := func() (shouldReturn bool) {
+ config, err := service.Config()
+ if err == windows.ERROR_SERVICE_MARKED_FOR_DELETE || (err != nil && config.StartType == windows.SERVICE_DISABLED) {
+ log.Printf("[%s] Found disabled service via timeout, so deleting", tunnelName)
+ service.Delete()
trackedTunnelsLock.Lock()
trackedTunnels[tunnelName] = TunnelStopped
trackedTunnelsLock.Unlock()
- IPCServerNotifyTunnelChange(tunnelName, TunnelStopped, fmt.Errorf("Unable to continue monitoring service, so stopping: %v", err))
- service.Control(svc.Stop)
- return
+ IPCServerNotifyTunnelChange(tunnelName, TunnelStopped, nil)
+ return true
}
-
- state := svcStateToTunState(svc.State(notifier.ServiceStatus.CurrentState))
+ return false
+ }
+ if checkForDisabled() {
+ return
+ }
+ lastState := TunnelUnknown
+ err := trackService(service, func(status uint32) bool {
+ state := notifyStateToTunState(status)
var tunnelError error
if state == TunnelStopped {
- if notifier.ServiceStatus.Win32ExitCode == uint32(windows.ERROR_SERVICE_SPECIFIC_ERROR) {
- maybeErr := services.Error(notifier.ServiceStatus.ServiceSpecificExitCode)
- if maybeErr != services.ErrorSuccess {
- tunnelError = maybeErr
- }
- } else {
- switch notifier.ServiceStatus.Win32ExitCode {
- case uint32(windows.NO_ERROR), uint32(windows.ERROR_SERVICE_NEVER_STARTED):
- default:
- tunnelError = syscall.Errno(notifier.ServiceStatus.Win32ExitCode)
+ serviceStatus, err := service.Query()
+ if err == nil {
+ if serviceStatus.Win32ExitCode == uint32(windows.ERROR_SERVICE_SPECIFIC_ERROR) {
+ maybeErr := services.Error(serviceStatus.ServiceSpecificExitCode)
+ if maybeErr != services.ErrorSuccess {
+ tunnelError = maybeErr
+ }
+ } else {
+ switch serviceStatus.Win32ExitCode {
+ case uint32(windows.NO_ERROR), uint32(windows.ERROR_SERVICE_NEVER_STARTED):
+ default:
+ tunnelError = syscall.Errno(serviceStatus.Win32ExitCode)
+ }
}
}
+ if tunnelError != nil {
+ service.Delete()
+ }
}
if state != lastState {
trackedTunnelsLock.Lock()
@@ -186,5 +250,94 @@ func trackTunnelService(tunnelName string, service *mgr.Service) {
IPCServerNotifyTunnelChange(tunnelName, state, tunnelError)
lastState = state
}
+ if state == TunnelUnknown && checkForDisabled() {
+ return true
+ }
+ return state == TunnelStopped
+ })
+ if err != nil && !checkForDisabled() {
+ trackedTunnelsLock.Lock()
+ trackedTunnels[tunnelName] = TunnelStopped
+ trackedTunnelsLock.Unlock()
+ IPCServerNotifyTunnelChange(tunnelName, TunnelStopped, fmt.Errorf("Unable to continue monitoring service, so stopping: %w", err))
+ service.Control(svc.Stop)
+ }
+}
+
+func trackExistingTunnels() error {
+ m, err := serviceManager()
+ if err != nil {
+ return err
+ }
+ names, err := conf.ListConfigNames()
+ if err != nil {
+ return err
+ }
+ for _, name := range names {
+ trackedTunnelsLock.Lock()
+ if _, found := trackedTunnels[name]; found {
+ trackedTunnelsLock.Unlock()
+ continue
+ }
+ trackedTunnelsLock.Unlock()
+ serviceName, err := conf.ServiceNameOfTunnel(name)
+ if err != nil {
+ continue
+ }
+ service, err := m.OpenService(serviceName)
+ if err != nil {
+ continue
+ }
+ go trackTunnelService(name, service)
}
+ return nil
+}
+
+var servicesSubscriptionWatcherCallbackPtr = windows.NewCallback(func(notification uint32, context uintptr) uintptr {
+ trackExistingTunnels()
+ return 0
+})
+
+func watchNewTunnelServices() error {
+ m, err := serviceManager()
+ if err != nil {
+ return err
+ }
+ var subscription uintptr
+ err = windows.SubscribeServiceChangeNotifications(m.Handle, windows.SC_EVENT_DATABASE_CHANGE, servicesSubscriptionWatcherCallbackPtr, 0, &subscription)
+ if err == nil {
+ // We probably could do:
+ // defer windows.UnsubscribeServiceChangeNotifications(subscription)
+ // and then terminate after some point, but instead we just let this go forever; it's process-lived.
+ return trackExistingTunnels()
+ }
+ if !errors.Is(err, windows.ERROR_PROC_NOT_FOUND) {
+ return err
+ }
+
+ // TODO: Below this line is Windows 7 compatibility code, which hopefully we can delete at some point.
+ go func() {
+ runtime.LockOSThread()
+ notifier := &windows.SERVICE_NOTIFY{
+ Version: windows.SERVICE_NOTIFY_STATUS_CHANGE,
+ NotifyCallback: serviceTrackerCallbackPtr,
+ }
+ for {
+ err := windows.NotifyServiceStatusChange(m.Handle, windows.SERVICE_NOTIFY_CREATED, notifier)
+ if err == nil {
+ windows.SleepEx(windows.INFINITE, true)
+ if notifier.ServiceNames != nil {
+ windows.LocalFree(windows.Handle(unsafe.Pointer(notifier.ServiceNames)))
+ notifier.ServiceNames = nil
+ }
+ trackExistingTunnels()
+ } else if err == windows.ERROR_SERVICE_NOTIFY_CLIENT_LAGGING {
+ continue
+ } else {
+ time.Sleep(time.Second * 3)
+ trackExistingTunnels()
+ }
+ }
+ }()
+ return trackExistingTunnels()
}
diff --git a/manager/uiprocess.go b/manager/uiprocess.go
new file mode 100644
index 00000000..b33b1ad3
--- /dev/null
+++ b/manager/uiprocess.go
@@ -0,0 +1,103 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package manager
+
+import (
+ "errors"
+ "runtime"
+ "sync/atomic"
+ "syscall"
+ "unsafe"
+
+ "golang.org/x/sys/windows"
+)
+
+type uiProcess struct {
+ handle uintptr
+}
+
+func launchUIProcess(executable string, args []string, workingDirectory string, handles []windows.Handle, token windows.Token) (*uiProcess, error) {
+ executable16, err := windows.UTF16PtrFromString(executable)
+ if err != nil {
+ return nil, err
+ }
+ args16, err := windows.UTF16PtrFromString(windows.ComposeCommandLine(args))
+ if err != nil {
+ return nil, err
+ }
+ workingDirectory16, err := windows.UTF16PtrFromString(workingDirectory)
+ if err != nil {
+ return nil, err
+ }
+ var environmentBlock *uint16
+ err = windows.CreateEnvironmentBlock(&environmentBlock, token, false)
+ if err != nil {
+ return nil, err
+ }
+ defer windows.DestroyEnvironmentBlock(environmentBlock)
+ attributeList, err := windows.NewProcThreadAttributeList(1)
+ if err != nil {
+ return nil, err
+ }
+ defer attributeList.Delete()
+ si := &windows.StartupInfoEx{
+ StartupInfo: windows.StartupInfo{Cb: uint32(unsafe.Sizeof(windows.StartupInfoEx{}))},
+ ProcThreadAttributeList: attributeList.List(),
+ }
+ if len(handles) == 0 {
+ handles = []windows.Handle{0}
+ }
+ attributeList.Update(windows.PROC_THREAD_ATTRIBUTE_HANDLE_LIST, unsafe.Pointer(&handles[0]), uintptr(len(handles))*unsafe.Sizeof(handles[0]))
+ pi := new(windows.ProcessInformation)
+ err = windows.CreateProcessAsUser(token, executable16, args16, nil, nil, true, windows.CREATE_DEFAULT_ERROR_MODE|windows.CREATE_UNICODE_ENVIRONMENT|windows.EXTENDED_STARTUPINFO_PRESENT, environmentBlock, workingDirectory16, &si.StartupInfo, pi)
+ if err != nil {
+ return nil, err
+ }
+ windows.CloseHandle(pi.Thread)
+ uiProc := &uiProcess{handle: uintptr(pi.Process)}
+ runtime.SetFinalizer(uiProc, (*uiProcess).release)
+ return uiProc, nil
+}
+
+func (p *uiProcess) release() error {
+ handle := windows.Handle(atomic.SwapUintptr(&p.handle, uintptr(windows.InvalidHandle)))
+ if handle == windows.InvalidHandle {
+ return nil
+ }
+ err := windows.CloseHandle(handle)
+ if err != nil {
+ return err
+ }
+ runtime.SetFinalizer(p, nil)
+ return nil
+}
+
+func (p *uiProcess) Wait() (uint32, error) {
+ handle := windows.Handle(atomic.LoadUintptr(&p.handle))
+ s, err := windows.WaitForSingleObject(handle, syscall.INFINITE)
+ switch s {
+ case windows.WAIT_OBJECT_0:
+ case windows.WAIT_FAILED:
+ return 0, err
+ default:
+ return 0, errors.New("unexpected result from WaitForSingleObject")
+ }
+ var exitCode uint32
+ err = windows.GetExitCodeProcess(handle, &exitCode)
+ if err != nil {
+ return 0, err
+ }
+ p.release()
+ return exitCode, nil
+}
+
+func (p *uiProcess) Kill() error {
+ handle := windows.Handle(atomic.LoadUintptr(&p.handle))
+ if handle == windows.InvalidHandle {
+ return nil
+ }
+ return windows.TerminateProcess(handle, 1)
+}
diff --git a/manager/updatestate.go b/manager/updatestate.go
index b54cc367..d5a19c8d 100644
--- a/manager/updatestate.go
+++ b/manager/updatestate.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package manager
@@ -8,11 +8,16 @@ package manager
import (
"log"
"time"
+ _ "unsafe"
+ "golang.zx2c4.com/wireguard/windows/services"
"golang.zx2c4.com/wireguard/windows/updater"
"golang.zx2c4.com/wireguard/windows/version"
)
+//go:linkname fastrandn runtime.fastrandn
+func fastrandn(n uint32) uint32
+
type UpdateState uint32
const (
@@ -23,35 +28,38 @@ const (
var updateState = UpdateStateUnknown
-func checkForUpdates() {
- defer printPanic()
+func jitterSleep(min, max time.Duration) {
+ time.Sleep(min + time.Millisecond*time.Duration(fastrandn(uint32((max-min+1)/time.Millisecond))))
+}
+func checkForUpdates() {
if !version.IsRunningOfficialVersion() {
log.Println("Build is not official, so updates are disabled")
updateState = UpdateStateUpdatesDisabledUnofficialBuild
IPCServerNotifyUpdateFound(updateState)
return
}
-
- first := true
+ if services.StartedAtBoot() {
+ jitterSleep(time.Minute*2, time.Minute*5)
+ }
+ noError, didNotify := true, false
for {
update, err := updater.CheckForUpdate()
- if err == nil && update != nil {
+ if err == nil && update != nil && !didNotify {
log.Println("An update is available")
updateState = UpdateStateFoundUpdate
IPCServerNotifyUpdateFound(updateState)
- return
- }
- if err != nil {
+ didNotify = true
+ } else if err != nil && !didNotify {
log.Printf("Update checker: %v", err)
- if first {
- time.Sleep(time.Minute * 4)
- first = false
+ if noError {
+ jitterSleep(time.Minute*4, time.Minute*6)
+ noError = false
} else {
- time.Sleep(time.Minute * 25)
+ jitterSleep(time.Minute*25, time.Minute*30)
}
} else {
- time.Sleep(time.Hour)
+ jitterSleep(time.Hour-time.Minute*3, time.Hour+time.Minute*3)
}
}
}
diff --git a/quickinstall.bat b/quickinstall.bat
index 9765fdb0..47d66a30 100644
--- a/quickinstall.bat
+++ b/quickinstall.bat
@@ -1,6 +1,6 @@
@echo off
rem SPDX-License-Identifier: MIT
-rem Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+rem Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
setlocal
cd /d %~dp0 || exit /b 1
@@ -11,6 +11,6 @@ call .\installer\build.bat || exit /b 1
echo [+] Uninstalling old versions
for /f %%a in ('reg query HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall /s /d /c /e /f WireGuard ^| findstr CurrentVersion\Uninstall') do msiexec /qb /x %%~na
echo [+] Installing new version
-for /f "tokens=3" %%a in ('findstr /r "WIREGUARD_WINDOWS_VERSION_STRING.*[0-9.]*" .\version.h') do set WIREGUARD_VERSION=%%a
+for /f "tokens=3" %%a in ('findstr /r "Number.*=.*[0-9.]*" .\version\version.go') do set WIREGUARD_VERSION=%%a
set WIREGUARD_VERSION=%WIREGUARD_VERSION:"=%
msiexec /qb /i installer\dist\wireguard-%PROCESSOR_ARCHITECTURE%-%WIREGUARD_VERSION%.msi
diff --git a/resources.rc b/resources.rc
index 845e9684..0601d938 100644
--- a/resources.rc
+++ b/resources.rc
@@ -1,40 +1,88 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
#include <windows.h>
-#include "version/version.h"
+#pragma code_page(65001) // UTF-8
+
+#define STRINGIZE(x) #x
+#define EXPAND(x) STRINGIZE(x)
+
+LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST manifest.xml
+7 ICON ui/icon/wireguard.ico
+8 ICON ui/icon/dot.ico
+wireguard.dll RCDATA wireguard.dll
-$wireguard.ico ICON ui/icon/wireguard.ico
-dot-gray.ico ICON ui/icon/dot-gray.ico
-
-VS_VERSION_INFO VERSIONINFO
-FILEVERSION WIREGUARD_WINDOWS_VERSION_ARRAY
-PRODUCTVERSION WIREGUARD_WINDOWS_VERSION_ARRAY
-FILEOS VOS_NT_WINDOWS32
-FILETYPE VFT_APP
-FILESUBTYPE VFT2_UNKNOWN
-BEGIN
- BLOCK "StringFileInfo"
- BEGIN
- BLOCK "040904b0"
- BEGIN
- VALUE "CompanyName", "WireGuard LLC"
- VALUE "FileDescription", "WireGuard: Fast, Modern, Secure VPN Tunnel"
- VALUE "FileVersion", WIREGUARD_WINDOWS_VERSION_STRING
- VALUE "InternalName", "wireguard"
- VALUE "LegalCopyright", "Copyright \xa9 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved."
- VALUE "OriginalFilename", "wireguard.exe"
- VALUE "ProductName", "WireGuard"
- VALUE "ProductVersion", WIREGUARD_WINDOWS_VERSION_STRING
- VALUE "Comments", "https://www.wireguard.com/"
- END
- END
- BLOCK "VarFileInfo"
- BEGIN
- VALUE "Translation", 0x409, 1200
- END
+#define VERSIONINFO_TEMPLATE(block_id, lang_id, codepage_id, file_desc, comments) \
+VS_VERSION_INFO VERSIONINFO \
+FILEVERSION WIREGUARD_VERSION_ARRAY \
+PRODUCTVERSION WIREGUARD_VERSION_ARRAY \
+FILEOS VOS_NT_WINDOWS32 \
+FILETYPE VFT_APP \
+FILESUBTYPE VFT2_UNKNOWN \
+BEGIN \
+ BLOCK "StringFileInfo" \
+ BEGIN \
+ BLOCK block_id \
+ BEGIN \
+ VALUE "CompanyName", "WireGuard LLC" \
+ VALUE "FileDescription", file_desc \
+ VALUE "FileVersion", EXPAND(WIREGUARD_VERSION_STR) \
+ VALUE "InternalName", "wireguard-windows" \
+ VALUE "LegalCopyright", "Copyright © 2015-2022 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved." \
+ VALUE "OriginalFilename", "wireguard.exe" \
+ VALUE "ProductName", "WireGuard" \
+ VALUE "ProductVersion", EXPAND(WIREGUARD_VERSION_STR) \
+ VALUE "Comments", comments \
+ END \
+ END \
+ BLOCK "VarFileInfo" \
+ BEGIN \
+ VALUE "Translation", lang_id, codepage_id \
+ END \
END
+
+LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT
+VERSIONINFO_TEMPLATE(
+ "040904b0", 0x409, 0x4b0,
+ "WireGuard: Fast, Modern, Secure VPN Tunnel",
+ "https://www.wireguard.com/"
+)
+
+LANGUAGE LANG_FRENCH, SUBLANG_DEFAULT
+VERSIONINFO_TEMPLATE(
+ "040c04b0", 0x40c, 0x4b0,
+ "WireGuard: tunnel VPN rapide, moderne, sécurisé",
+ "https://www.wireguard.com/"
+)
+
+LANGUAGE LANG_ITALIAN, SUBLANG_DEFAULT
+VERSIONINFO_TEMPLATE(
+ "041004b0", 0x410, 0x4b0,
+ "WireGuard: Tunnel VPN veloce, moderno e sicuro",
+ "https://www.wireguard.com/"
+)
+
+LANGUAGE LANG_JAPANESE, SUBLANG_DEFAULT
+VERSIONINFO_TEMPLATE(
+ "041104b0", 0x411, 0x4b0,
+ "WireGuard: 高速で、現代的で、セキュアな VPN トンネル",
+ "https://www.wireguard.com/"
+)
+
+LANGUAGE LANG_SLOVENIAN, SUBLANG_DEFAULT
+VERSIONINFO_TEMPLATE(
+ "042404b0", 0x424, 0x4b0,
+ "WireGuard: hiter, sodoben, varen tunel VPN",
+ "https://www.wireguard.com/"
+)
+
+LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL
+VERSIONINFO_TEMPLATE(
+ "040404b0", 0x404, 0x4b0,
+ "WireGuard:快速、現代、安全的 VPN 隧道",
+ "https://www.wireguard.com/"
+)
diff --git a/ringlogger/cli_test.go b/ringlogger/cli_test.go
index b1a88a0a..12541c81 100644
--- a/ringlogger/cli_test.go
+++ b/ringlogger/cli_test.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ringlogger
diff --git a/ringlogger/dump.go b/ringlogger/dump.go
index 05a9b27f..3c089751 100644
--- a/ringlogger/dump.go
+++ b/ringlogger/dump.go
@@ -1,53 +1,28 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ringlogger
import (
+ "errors"
+ "fmt"
"io"
"os"
- "path/filepath"
+ "time"
"golang.org/x/sys/windows"
- "golang.org/x/sys/windows/registry"
-
- "golang.zx2c4.com/wireguard/windows/conf"
)
-func DumpTo(out io.Writer, localSystem bool) error {
- var path string
- if !localSystem {
- root, err := conf.RootDirectory()
- if err != nil {
- return err
- }
- path = filepath.Join(root, "log.bin")
- } else {
- k, err := registry.OpenKey(registry.LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\S-1-5-18", registry.QUERY_VALUE)
- if err != nil {
- return err
- }
- defer k.Close()
-
- systemprofile, _, err := k.GetStringValue("ProfileImagePath")
- if err != nil {
- return err
- }
- systemprofile, err = registry.ExpandString(systemprofile)
- if err != nil {
- return err
- }
- path = filepath.Join(systemprofile, "AppData", "Local", "WireGuard", "log.bin")
- }
- file, err := os.Open(path)
+func DumpTo(inPath string, out io.Writer, continuous bool) error {
+ file, err := os.Open(inPath)
if err != nil {
return err
}
defer file.Close()
mapping, err := windows.CreateFileMapping(windows.Handle(file.Fd()), nil, windows.PAGE_READONLY, 0, 0, nil)
- if err != nil {
+ if err != nil && err != windows.ERROR_ALREADY_EXISTS {
return err
}
rl, err := newRingloggerFromMappingHandle(mapping, "DMP", windows.FILE_MAP_READ)
@@ -56,9 +31,26 @@ func DumpTo(out io.Writer, localSystem bool) error {
return err
}
defer rl.Close()
- _, err = rl.WriteTo(out)
- if err != nil {
- return err
+ if !continuous {
+ _, err = rl.WriteTo(out)
+ if err != nil {
+ return err
+ }
+ } else {
+ cursor := CursorAll
+ for {
+ var items []FollowLine
+ items, cursor = rl.FollowFromCursor(cursor)
+ for _, item := range items {
+ _, err = fmt.Fprintf(out, "%s: %s\n", item.Stamp.Format("2006-01-02 15:04:05.000000"), item.Line)
+ if errors.Is(err, io.EOF) {
+ return nil
+ } else if err != nil {
+ return err
+ }
+ }
+ time.Sleep(time.Millisecond * 100)
+ }
}
return nil
}
diff --git a/ringlogger/global.go b/ringlogger/global.go
index 60e750a5..5c522a70 100644
--- a/ringlogger/global.go
+++ b/ringlogger/global.go
@@ -1,32 +1,63 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ringlogger
import (
"log"
- "path/filepath"
-
- "golang.zx2c4.com/wireguard/windows/conf"
+ "unsafe"
)
var Global *Ringlogger
-func InitGlobalLogger(tag string) error {
+func InitGlobalLogger(file, tag string) error {
if Global != nil {
return nil
}
- root, err := conf.RootDirectory()
- if err != nil {
- return err
- }
- Global, err = NewRinglogger(filepath.Join(root, "log.bin"), tag)
+ var err error
+ Global, err = NewRinglogger(file, tag)
if err != nil {
return err
}
log.SetOutput(Global)
log.SetFlags(0)
+ overrideWrite = globalWrite
return nil
}
+
+//go:linkname overrideWrite runtime.overrideWrite
+var overrideWrite func(fd uintptr, p unsafe.Pointer, n int32) int32
+
+var (
+ globalBuffer [maxLogLineLength - 1 - maxTagLength - 3]byte
+ globalBufferLocation int
+)
+
+//go:nosplit
+func globalWrite(fd uintptr, p unsafe.Pointer, n int32) int32 {
+ b := (*[1 << 30]byte)(p)[:n]
+ for len(b) > 0 {
+ amountAvailable := len(globalBuffer) - globalBufferLocation
+ amountToCopy := len(b)
+ if amountToCopy > amountAvailable {
+ amountToCopy = amountAvailable
+ }
+ copy(globalBuffer[globalBufferLocation:], b[:amountToCopy])
+ b = b[amountToCopy:]
+ globalBufferLocation += amountToCopy
+ foundNl := false
+ for i := globalBufferLocation - amountToCopy; i < globalBufferLocation; i++ {
+ if globalBuffer[i] == '\n' {
+ foundNl = true
+ break
+ }
+ }
+ if foundNl || len(b) > 0 {
+ Global.Write(globalBuffer[:globalBufferLocation])
+ globalBufferLocation = 0
+ }
+ }
+ return n
+}
diff --git a/ringlogger/ringlogger.go b/ringlogger/ringlogger.go
index fe88fdc7..e932f88c 100644
--- a/ringlogger/ringlogger.go
+++ b/ringlogger/ringlogger.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ringlogger
@@ -21,6 +21,7 @@ import (
const (
maxLogLineLength = 512
+ maxTagLength = 5
maxLines = 2048
magic = 0xbadbabe
)
@@ -44,8 +45,11 @@ type Ringlogger struct {
readOnly bool
}
-func NewRinglogger(filename string, tag string) (*Ringlogger, error) {
- file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0600)
+func NewRinglogger(filename, tag string) (*Ringlogger, error) {
+ if len(tag) > maxTagLength {
+ return nil, windows.ERROR_LABEL_TOO_LONG
+ }
+ file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0o600)
if err != nil {
return nil, err
}
@@ -54,18 +58,19 @@ func NewRinglogger(filename string, tag string) (*Ringlogger, error) {
return nil, err
}
mapping, err := windows.CreateFileMapping(windows.Handle(file.Fd()), nil, windows.PAGE_READWRITE, 0, 0, nil)
- if err != nil {
+ if err != nil && err != windows.ERROR_ALREADY_EXISTS {
return nil, err
}
rl, err := newRingloggerFromMappingHandle(mapping, tag, windows.FILE_MAP_WRITE)
if err != nil {
+ windows.CloseHandle(mapping)
return nil, err
}
rl.file = file
return rl, nil
}
-func NewRingloggerFromInheritedMappingHandle(handleStr string, tag string) (*Ringlogger, error) {
+func NewRingloggerFromInheritedMappingHandle(handleStr, tag string) (*Ringlogger, error) {
handle, err := strconv.ParseUint(handleStr, 10, 64)
if err != nil {
return nil, err
@@ -78,10 +83,6 @@ func newRingloggerFromMappingHandle(mappingHandle windows.Handle, tag string, ac
if err != nil {
return nil, err
}
- if err != nil {
- windows.CloseHandle(mappingHandle)
- return nil, err
- }
log := (*logMem)(unsafe.Pointer(view))
if log.magic != magic {
bytes := (*[unsafe.Sizeof(logMem{})]byte)(unsafe.Pointer(log))
@@ -103,12 +104,20 @@ func newRingloggerFromMappingHandle(mappingHandle windows.Handle, tag string, ac
}
func (rl *Ringlogger) Write(p []byte) (n int, err error) {
+ // Race: This isn't synchronized with the fetch_add below, so items might be slightly out of order.
+ ts := time.Now().UnixNano()
+ return rl.WriteWithTimestamp(p, ts)
+}
+
+func (rl *Ringlogger) WriteWithTimestamp(p []byte, ts int64) (n int, err error) {
if rl.readOnly {
return 0, io.ErrShortWrite
}
-
- // Race: This isn't synchronized with the fetch_add below, so items might be slightly out of order.
- ts := time.Now().UnixNano()
+ ret := len(p)
+ p = bytes.TrimSpace(p)
+ if len(p) == 0 {
+ return ret, nil
+ }
if rl.log == nil {
return 0, io.EOF
@@ -124,18 +133,21 @@ func (rl *Ringlogger) Write(p []byte) (n int, err error) {
line.line[i] = 0
}
- text := []byte(fmt.Sprintf("[%s] %s", rl.tag, bytes.TrimSpace(p)))
- if len(text) > maxLogLineLength-1 {
- text = text[:maxLogLineLength-1]
+ textLen := 3 + len(p) + len(rl.tag)
+ if textLen > maxLogLineLength-1 {
+ p = p[:maxLogLineLength-1-3-len(rl.tag)]
+ textLen = maxLogLineLength - 1
}
- line.line[len(text)] = 0
- copy(line.line[:], text[:])
+ line.line[textLen] = 0
+ line.line[0] = 0 // Null out the beginning and only let it extend after the other writes have completed
+ copy(line.line[1:], rl.tag)
+ line.line[1+len(rl.tag)] = ']'
+ line.line[2+len(rl.tag)] = ' '
+ copy(line.line[3+len(rl.tag):], p[:])
+ line.line[0] = '['
atomic.StoreInt64(&line.timeNs, ts)
- windows.FlushViewOfFile((uintptr)(unsafe.Pointer(&rl.log.nextIndex)), unsafe.Sizeof(rl.log.nextIndex))
- windows.FlushViewOfFile((uintptr)(unsafe.Pointer(line)), unsafe.Sizeof(*line))
-
- return len(p), nil
+ return ret, nil
}
func (rl *Ringlogger) WriteTo(out io.Writer) (n int64, err error) {
@@ -223,17 +235,16 @@ func (rl *Ringlogger) Close() error {
return nil
}
-func (rl *Ringlogger) ExportInheritableMappingHandleStr() (str string, handleToClose windows.Handle, err error) {
+func (rl *Ringlogger) ExportInheritableMappingHandle() (handleToClose windows.Handle, err error) {
handleToClose, err = windows.CreateFileMapping(windows.Handle(rl.file.Fd()), nil, windows.PAGE_READONLY, 0, 0, nil)
- if err != nil {
+ if err != nil && err != windows.ERROR_ALREADY_EXISTS {
return
}
err = windows.SetHandleInformation(handleToClose, windows.HANDLE_FLAG_INHERIT, windows.HANDLE_FLAG_INHERIT)
if err != nil {
- windows.Close(handleToClose)
+ windows.CloseHandle(handleToClose)
handleToClose = 0
return
}
- str = strconv.FormatUint(uint64(handleToClose), 10)
return
}
diff --git a/services/boot.go b/services/boot.go
new file mode 100644
index 00000000..ae7aadda
--- /dev/null
+++ b/services/boot.go
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package services
+
+import (
+ "errors"
+ "log"
+ "sync"
+ "time"
+
+ "golang.org/x/sys/windows"
+ "golang.org/x/sys/windows/svc"
+ "golang.zx2c4.com/wireguard/windows/version"
+)
+
+var (
+ startedAtBoot bool
+ startedAtBootOnce sync.Once
+)
+
+func StartedAtBoot() bool {
+ startedAtBootOnce.Do(func() {
+ if isService, err := svc.IsWindowsService(); err == nil && !isService {
+ return
+ }
+ if reason, err := svc.DynamicStartReason(); err == nil {
+ startedAtBoot = (reason&svc.StartReasonAuto) != 0 || (reason&svc.StartReasonDelayedAuto) != 0
+ } else if errors.Is(err, windows.ERROR_PROC_NOT_FOUND) {
+ // TODO: Below this line is Windows 7 compatibility code, which hopefully we can delete at some point.
+ startedAtBoot = windows.DurationSinceBoot() < time.Minute*10
+ } else {
+ log.Printf("Unable to determine service start reason: %v", err)
+ }
+ })
+ return startedAtBoot
+}
+
+func PrintStarting() {
+ boot := ""
+ if StartedAtBoot() {
+ boot = " at boot"
+ }
+ log.Printf("Starting%s %s", boot, version.UserAgent())
+}
diff --git a/services/errors.go b/services/errors.go
index 7a441778..c0588494 100644
--- a/services/errors.go
+++ b/services/errors.go
@@ -1,13 +1,12 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package services
import (
"fmt"
- "syscall"
"golang.org/x/sys/windows"
)
@@ -18,18 +17,19 @@ const (
ErrorSuccess Error = iota
ErrorRingloggerOpen
ErrorLoadConfiguration
- ErrorCreateWintun
- ErrorUAPIListen
+ ErrorCreateNetworkAdapter
ErrorDNSLookup
ErrorFirewall
ErrorDeviceSetConfig
+ ErrorDeviceBringUp
ErrorBindSocketsToDefaultRoutes
+ ErrorMonitorMTUChanges
ErrorSetNetConfig
ErrorDetermineExecutablePath
- ErrorOpenNULFile
ErrorTrackTunnels
ErrorEnumerateSessions
ErrorDropPrivileges
+ ErrorRunScript
ErrorWin32
)
@@ -43,28 +43,30 @@ func (e Error) Error() string {
return "Unable to determine path of running executable"
case ErrorLoadConfiguration:
return "Unable to load configuration from path"
- case ErrorCreateWintun:
- return "Unable to create Wintun interface"
- case ErrorUAPIListen:
- return "Unable to listen on named pipe"
+ case ErrorCreateNetworkAdapter:
+ return "Unable to create network adapter"
case ErrorDNSLookup:
return "Unable to resolve one or more DNS hostname endpoints"
case ErrorFirewall:
return "Unable to enable firewall rules"
case ErrorDeviceSetConfig:
return "Unable to set device configuration"
+ case ErrorDeviceBringUp:
+ return "Unable to bring up adapter"
case ErrorBindSocketsToDefaultRoutes:
return "Unable to bind sockets to default route"
+ case ErrorMonitorMTUChanges:
+ return "Unable to monitor default route MTU for changes"
case ErrorSetNetConfig:
- return "Unable to set interface addresses, routes, dns, and/or interface settings"
- case ErrorOpenNULFile:
- return "Unable to open NUL file"
+ return "Unable to configure adapter network settings"
case ErrorTrackTunnels:
return "Unable to track existing tunnels"
case ErrorEnumerateSessions:
return "Unable to enumerate current sessions"
case ErrorDropPrivileges:
return "Unable to drop privileges"
+ case ErrorRunScript:
+ return "An error occurred while running a configuration script command"
case ErrorWin32:
return "An internal Windows error has occurred"
default:
@@ -73,7 +75,7 @@ func (e Error) Error() string {
}
func DetermineErrorCode(err error, serviceError Error) (bool, uint32) {
- if syserr, ok := err.(syscall.Errno); ok {
+ if syserr, ok := err.(windows.Errno); ok {
return false, uint32(syserr)
} else if serviceError != ErrorSuccess {
return true, uint32(serviceError)
@@ -85,7 +87,7 @@ func DetermineErrorCode(err error, serviceError Error) (bool, uint32) {
func CombineErrors(err error, serviceError Error) error {
if serviceError != ErrorSuccess {
if err != nil {
- return fmt.Errorf("%v: %v", serviceError, err)
+ return fmt.Errorf("%v: %w", serviceError, err)
}
return serviceError
}
diff --git a/services/names.go b/services/names.go
deleted file mode 100644
index 74445482..00000000
--- a/services/names.go
+++ /dev/null
@@ -1,26 +0,0 @@
-/* SPDX-License-Identifier: MIT
- *
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
- */
-
-package services
-
-import (
- "errors"
-
- "golang.zx2c4.com/wireguard/windows/conf"
-)
-
-func ServiceNameOfTunnel(tunnelName string) (string, error) {
- if !conf.TunnelNameIsValid(tunnelName) {
- return "", errors.New("Tunnel name is not valid")
- }
- return "WireGuardTunnel$" + tunnelName, nil
-}
-
-func PipePathOfTunnel(tunnelName string) (string, error) {
- if !conf.TunnelNameIsValid(tunnelName) {
- return "", errors.New("Tunnel name is not valid")
- }
- return `\\.\pipe\ProtectedPrefix\Administrators\WireGuard\` + tunnelName, nil
-}
diff --git a/tunnel/addressconfig.go b/tunnel/addressconfig.go
index 777c96cd..a3ce6295 100644
--- a/tunnel/addressconfig.go
+++ b/tunnel/addressconfig.go
@@ -1,42 +1,30 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package tunnel
import (
- "bytes"
+ "fmt"
"log"
- "net"
- "sort"
+ "net/netip"
+ "time"
"golang.org/x/sys/windows"
- "golang.zx2c4.com/wireguard/tun"
-
"golang.zx2c4.com/wireguard/windows/conf"
+ "golang.zx2c4.com/wireguard/windows/services"
"golang.zx2c4.com/wireguard/windows/tunnel/firewall"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
)
-func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, addresses []net.IPNet) {
+func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, addresses []netip.Prefix) {
if len(addresses) == 0 {
return
}
- includedInAddresses := func(a net.IPNet) bool {
- // TODO: this makes the whole algorithm O(n^2). But we can't stick net.IPNet in a Go hashmap. Bummer!
- for _, addr := range addresses {
- ip := addr.IP
- if ip4 := ip.To4(); ip4 != nil {
- ip = ip4
- }
- mA, _ := addr.Mask.Size()
- mB, _ := a.Mask.Size()
- if bytes.Equal(ip, a.IP) && mA == mB {
- return true
- }
- }
- return false
+ addrHash := make(map[netip.Addr]bool, len(addresses))
+ for i := range addresses {
+ addrHash[addresses[i].Addr()] = true
}
interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagDefault)
if err != nil {
@@ -47,144 +35,124 @@ func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, add
continue
}
for address := iface.FirstUnicastAddress; address != nil; address = address.Next {
- ip := address.Address.IP()
- ipnet := net.IPNet{IP: ip, Mask: net.CIDRMask(int(address.OnLinkPrefixLength), 8*len(ip))}
- if includedInAddresses(ipnet) {
- log.Printf("Cleaning up stale address %s from interface ‘%s’", ipnet.String(), iface.FriendlyName())
- iface.LUID.DeleteIPAddress(ipnet)
+ if ip, _ := netip.AddrFromSlice(address.Address.IP()); addrHash[ip] {
+ prefix := netip.PrefixFrom(ip, int(address.OnLinkPrefixLength))
+ log.Printf("Cleaning up stale address %s from interface ‘%s’", prefix.String(), iface.FriendlyName())
+ iface.LUID.DeleteIPAddress(prefix)
}
}
}
}
-func configureInterface(family winipcfg.AddressFamily, conf *conf.Config, tun *tun.NativeTun) error {
- luid := winipcfg.LUID(tun.LUID())
+func configureInterface(family winipcfg.AddressFamily, conf *conf.Config, luid winipcfg.LUID) error {
+ retryOnFailure := services.StartedAtBoot()
+ tryTimes := 0
+startOver:
+ var err error
+ if tryTimes > 0 {
+ log.Printf("Retrying interface configuration after failure because system just booted (T+%v): %v", windows.DurationSinceBoot(), err)
+ time.Sleep(time.Second)
+ retryOnFailure = retryOnFailure && tryTimes < 15
+ }
+ tryTimes++
estimatedRouteCount := 0
for _, peer := range conf.Peers {
estimatedRouteCount += len(peer.AllowedIPs)
}
- routes := make([]winipcfg.RouteData, 0, estimatedRouteCount)
- addresses := make([]net.IPNet, len(conf.Interface.Addresses))
- var haveV4Address, haveV6Address bool
- for i, addr := range conf.Interface.Addresses {
- addresses[i] = addr.IPNet()
- if addr.Bits() == 32 {
- haveV4Address = true
- } else if addr.Bits() == 128 {
- haveV6Address = true
- }
- }
+ routes := make(map[winipcfg.RouteData]bool, estimatedRouteCount)
foundDefault4 := false
foundDefault6 := false
for _, peer := range conf.Peers {
for _, allowedip := range peer.AllowedIPs {
- if (allowedip.Bits() == 32 && !haveV4Address) || (allowedip.Bits() == 128 && !haveV6Address) {
- continue
- }
route := winipcfg.RouteData{
- Destination: allowedip.IPNet(),
+ Destination: allowedip.Masked(),
Metric: 0,
}
- if allowedip.Bits() == 32 {
- if allowedip.Cidr == 0 {
+ if allowedip.Addr().Is4() {
+ if allowedip.Bits() == 0 {
foundDefault4 = true
}
- route.NextHop = net.IPv4zero
- } else if allowedip.Bits() == 128 {
- if allowedip.Cidr == 0 {
+ route.NextHop = netip.IPv4Unspecified()
+ } else if allowedip.Addr().Is6() {
+ if allowedip.Bits() == 0 {
foundDefault6 = true
}
- route.NextHop = net.IPv6zero
+ route.NextHop = netip.IPv6Unspecified()
}
- routes = append(routes, route)
+ routes[route] = true
}
}
- err := luid.SetIPAddressesForFamily(family, addresses)
- if err == windows.ERROR_OBJECT_ALREADY_EXISTS {
- cleanupAddressesOnDisconnectedInterfaces(family, addresses)
- err = luid.SetIPAddressesForFamily(family, addresses)
- }
- if err != nil {
- return err
+ deduplicatedRoutes := make([]*winipcfg.RouteData, 0, len(routes))
+ for route := range routes {
+ r := route
+ deduplicatedRoutes = append(deduplicatedRoutes, &r)
}
- deduplicatedRoutes := make([]*winipcfg.RouteData, 0, len(routes))
- sort.Slice(routes, func(i, j int) bool {
- return routes[i].Metric < routes[j].Metric ||
- bytes.Compare(routes[i].NextHop, routes[j].NextHop) == -1 ||
- bytes.Compare(routes[i].Destination.IP, routes[j].Destination.IP) == -1 ||
- bytes.Compare(routes[i].Destination.Mask, routes[j].Destination.Mask) == -1
- })
- for i := 0; i < len(routes); i++ {
- if i > 0 && routes[i].Metric == routes[i-1].Metric &&
- bytes.Equal(routes[i].NextHop, routes[i-1].NextHop) &&
- bytes.Equal(routes[i].Destination.IP, routes[i-1].Destination.IP) &&
- bytes.Equal(routes[i].Destination.Mask, routes[i-1].Destination.Mask) {
- continue
+ if !conf.Interface.TableOff {
+ err = luid.SetRoutesForFamily(family, deduplicatedRoutes)
+ if err == windows.ERROR_NOT_FOUND && retryOnFailure {
+ goto startOver
+ } else if err != nil {
+ return fmt.Errorf("unable to set routes: %w", err)
}
- deduplicatedRoutes = append(deduplicatedRoutes, &routes[i])
}
- err = luid.SetRoutesForFamily(family, deduplicatedRoutes)
- if err != nil {
- return nil
+ err = luid.SetIPAddressesForFamily(family, conf.Interface.Addresses)
+ if err == windows.ERROR_OBJECT_ALREADY_EXISTS {
+ cleanupAddressesOnDisconnectedInterfaces(family, conf.Interface.Addresses)
+ err = luid.SetIPAddressesForFamily(family, conf.Interface.Addresses)
+ }
+ if err == windows.ERROR_NOT_FOUND && retryOnFailure {
+ goto startOver
+ } else if err != nil {
+ return fmt.Errorf("unable to set ips: %w", err)
}
- ipif, err := luid.IPInterface(family)
+ var ipif *winipcfg.MibIPInterfaceRow
+ ipif, err = luid.IPInterface(family)
if err != nil {
return err
}
+ ipif.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled
+ ipif.DadTransmits = 0
+ ipif.ManagedAddressConfigurationSupported = false
+ ipif.OtherStatefulConfigurationSupported = false
if conf.Interface.MTU > 0 {
ipif.NLMTU = uint32(conf.Interface.MTU)
- tun.ForceMTU(int(ipif.NLMTU))
}
- if family == windows.AF_INET {
- if foundDefault4 {
- ipif.UseAutomaticMetric = false
- ipif.Metric = 0
- }
- } else if family == windows.AF_INET6 {
- if foundDefault6 {
- ipif.UseAutomaticMetric = false
- ipif.Metric = 0
- }
- ipif.DadTransmits = 0
- ipif.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled
+ if (family == windows.AF_INET && foundDefault4) || (family == windows.AF_INET6 && foundDefault6) {
+ ipif.UseAutomaticMetric = false
+ ipif.Metric = 0
}
err = ipif.Set()
- if err != nil {
- return err
+ if err == windows.ERROR_NOT_FOUND && retryOnFailure {
+ goto startOver
+ } else if err != nil {
+ return fmt.Errorf("unable to set metric and MTU: %w", err)
}
- err = luid.SetDNSForFamily(family, conf.Interface.DNS)
- if err != nil {
- return err
+ err = luid.SetDNS(family, conf.Interface.DNS, conf.Interface.DNSSearch)
+ if err == windows.ERROR_NOT_FOUND && retryOnFailure {
+ goto startOver
+ } else if err != nil {
+ return fmt.Errorf("unable to set DNS: %w", err)
}
-
return nil
}
-func enableFirewall(conf *conf.Config, tun *tun.NativeTun) error {
- restrictAll := false
- if len(conf.Peers) == 1 {
- nextallowedip:
+func enableFirewall(conf *conf.Config, luid winipcfg.LUID) error {
+ doNotRestrict := true
+ if len(conf.Peers) == 1 && !conf.Interface.TableOff {
for _, allowedip := range conf.Peers[0].AllowedIPs {
- if allowedip.Cidr == 0 {
- for _, b := range allowedip.IP {
- if b != 0 {
- continue nextallowedip
- }
- }
- restrictAll = true
+ if allowedip.Bits() == 0 && allowedip == allowedip.Masked() {
+ doNotRestrict = false
break
}
}
}
- if restrictAll && len(conf.Interface.DNS) == 0 {
- log.Println("Warning: no DNS server specified, despite having an allowed IPs of 0.0.0.0/0 or ::/0. There may be connectivity issues.")
- }
- return firewall.EnableFirewall(tun.LUID(), conf.Interface.DNS, restrictAll)
+ log.Println("Enabling firewall rules")
+ return firewall.EnableFirewall(uint64(luid), doNotRestrict, conf.Interface.DNS)
}
diff --git a/tunnel/deterministicguid.go b/tunnel/deterministicguid.go
index 8c0f34c0..405d33a3 100644
--- a/tunnel/deterministicguid.go
+++ b/tunnel/deterministicguid.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package tunnel
@@ -18,8 +18,10 @@ import (
"golang.zx2c4.com/wireguard/windows/conf"
)
-const deterministicGUIDLabel = "Deterministic WireGuard Windows GUID v1 jason@zx2c4.com"
-const fixedGUIDLabel = "Fixed WireGuard Windows GUID v1 jason@zx2c4.com"
+const (
+ deterministicGUIDLabel = "Deterministic WireGuard Windows GUID v1 jason@zx2c4.com"
+ fixedGUIDLabel = "Fixed WireGuard Windows GUID v1 jason@zx2c4.com"
+)
// Escape hatch for external consumers, not us.
var UseFixedGUIDInsteadOfDeterministic = false
@@ -80,13 +82,13 @@ func deterministicGUID(c *conf.Config) *windows.GUID {
b2Number(len(peer.AllowedIPs))
sortedAllowedIPs := peer.AllowedIPs
sort.Slice(sortedAllowedIPs, func(i, j int) bool {
- if bi, bj := sortedAllowedIPs[i].Bits(), sortedAllowedIPs[j].Bits(); bi != bj {
+ if bi, bj := sortedAllowedIPs[i].Addr().BitLen(), sortedAllowedIPs[j].Addr().BitLen(); bi != bj {
return bi < bj
}
- if sortedAllowedIPs[i].Cidr != sortedAllowedIPs[j].Cidr {
- return sortedAllowedIPs[i].Cidr < sortedAllowedIPs[j].Cidr
+ if sortedAllowedIPs[i].Bits() != sortedAllowedIPs[j].Bits() {
+ return sortedAllowedIPs[i].Bits() < sortedAllowedIPs[j].Bits()
}
- return bytes.Compare(sortedAllowedIPs[i].IP[:], sortedAllowedIPs[j].IP[:]) < 0
+ return sortedAllowedIPs[i].Addr().Compare(sortedAllowedIPs[j].Addr()) < 0
})
for _, allowedip := range sortedAllowedIPs {
b2String(allowedip.String())
diff --git a/tunnel/firewall/blocker.go b/tunnel/firewall/blocker.go
index 7da391ca..8a4967ba 100644
--- a/tunnel/firewall/blocker.go
+++ b/tunnel/firewall/blocker.go
@@ -1,13 +1,13 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package firewall
import (
"errors"
- "net"
+ "net/netip"
"unsafe"
"golang.org/x/sys/windows"
@@ -101,7 +101,7 @@ func registerBaseObjects(session uintptr) (*baseObjects, error) {
return bo, nil
}
-func EnableFirewall(luid uint64, restrictToDNSServers []net.IP, restrictAll bool) error {
+func EnableFirewall(luid uint64, doNotRestrict bool, restrictToDNSServers []netip.Addr) error {
if wfpSession != 0 {
return errors.New("The firewall has already been enabled")
}
@@ -122,26 +122,24 @@ func EnableFirewall(luid uint64, restrictToDNSServers []net.IP, restrictAll bool
return wrapErr(err)
}
- if len(restrictToDNSServers) > 0 {
- err = blockDNS(restrictToDNSServers, session, baseObjects, 15, 14)
- if err != nil {
- return wrapErr(err)
+ if !doNotRestrict {
+ if len(restrictToDNSServers) > 0 {
+ err = blockDNS(restrictToDNSServers, session, baseObjects, 15, 14)
+ if err != nil {
+ return wrapErr(err)
+ }
}
- }
- if restrictAll {
err = permitLoopback(session, baseObjects, 13)
if err != nil {
return wrapErr(err)
}
- }
- err = permitTunInterface(session, baseObjects, 12, luid)
- if err != nil {
- return wrapErr(err)
- }
+ err = permitTunInterface(session, baseObjects, 12, luid)
+ if err != nil {
+ return wrapErr(err)
+ }
- if restrictAll {
err = permitDHCPIPv4(session, baseObjects, 12)
if err != nil {
return wrapErr(err)
@@ -164,9 +162,7 @@ func EnableFirewall(luid uint64, restrictToDNSServers []net.IP, restrictAll bool
return wrapErr(err)
}
*/
- }
- if restrictAll {
err = blockAll(session, baseObjects, 0)
if err != nil {
return wrapErr(err)
diff --git a/tunnel/firewall/helpers.go b/tunnel/firewall/helpers.go
index 91c6617e..46e43aa5 100644
--- a/tunnel/firewall/helpers.go
+++ b/tunnel/firewall/helpers.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package firewall
@@ -66,13 +66,11 @@ func wrapErr(err error) error {
}
_, file, line, ok := runtime.Caller(1)
if !ok {
- return fmt.Errorf("Firewall error at unknown location: %v", err)
+ return fmt.Errorf("Firewall error at unknown location: %w", err)
}
- return fmt.Errorf("Firewall error at %s:%d: %v", file, line, err)
+ return fmt.Errorf("Firewall error at %s:%d: %w", file, line, err)
}
-var ExemptBuiltinAdministrators = false
-
func getCurrentProcessSecurityDescriptor() (*windows.SECURITY_DESCRIPTOR, error) {
var processToken windows.Token
err := windows.OpenProcessToken(windows.CurrentProcess(), windows.TOKEN_QUERY, &processToken)
@@ -111,21 +109,6 @@ func getCurrentProcessSecurityDescriptor() (*windows.SECURITY_DESCRIPTOR, error)
TrusteeValue: windows.TrusteeValueFromSID(sid),
},
}}
- if ExemptBuiltinAdministrators {
- builtinAdmins, err := windows.CreateWellKnownSid(windows.WinBuiltinAdministratorsSid)
- if err != nil {
- return nil, err
- }
- access = append(access, windows.EXPLICIT_ACCESS{
- AccessPermissions: cFWP_ACTRL_MATCH_FILTER,
- AccessMode: windows.GRANT_ACCESS,
- Trustee: windows.TRUSTEE{
- TrusteeForm: windows.TRUSTEE_IS_SID,
- TrusteeType: windows.TRUSTEE_IS_GROUP,
- TrusteeValue: windows.TrusteeValueFromSID(builtinAdmins),
- },
- })
- }
dacl, err := windows.ACLFromEntries(access, nil)
if err != nil {
return nil, wrapErr(err)
diff --git a/tunnel/firewall/mksyscall.go b/tunnel/firewall/mksyscall.go
index 060c3b1c..fc108007 100644
--- a/tunnel/firewall/mksyscall.go
+++ b/tunnel/firewall/mksyscall.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package firewall
diff --git a/tunnel/firewall/rules.go b/tunnel/firewall/rules.go
index 7bca508b..41632f98 100644
--- a/tunnel/firewall/rules.go
+++ b/tunnel/firewall/rules.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package firewall
@@ -8,7 +8,7 @@ package firewall
import (
"encoding/binary"
"errors"
- "net"
+ "net/netip"
"runtime"
"unsafe"
@@ -582,7 +582,6 @@ func permitDHCPIPv6(session uintptr, baseObjects *baseObjects, weight uint8) err
}
func permitNdp(session uintptr, baseObjects *baseObjects, weight uint8) error {
-
/* TODO: actually handle the hop limit somehow! The rules should vaguely be:
* - icmpv6 133: must be outgoing, dst must be FF02::2/128, hop limit must be 255
* - icmpv6 134: must be incoming, src must be FE80::/10, hop limit must be 255
@@ -985,7 +984,7 @@ func blockAll(session uintptr, baseObjects *baseObjects, weight uint8) error {
}
// Block all DNS traffic except towards specified DNS servers.
-func blockDNS(except []net.IP, session uintptr, baseObjects *baseObjects, weightAllow uint8, weightDeny uint8) error {
+func blockDNS(except []netip.Addr, session uintptr, baseObjects *baseObjects, weightAllow, weightDeny uint8) error {
if weightDeny >= weightAllow {
return errors.New("The allow weight must be greater than the deny weight")
}
@@ -1106,8 +1105,7 @@ func blockDNS(except []net.IP, session uintptr, baseObjects *baseObjects, weight
allowConditionsV4 := make([]wtFwpmFilterCondition0, 0, len(denyConditions)+len(except))
allowConditionsV4 = append(allowConditionsV4, denyConditions...)
for _, ip := range except {
- ip4 := ip.To4()
- if ip4 == nil {
+ if !ip.Is4() {
continue
}
allowConditionsV4 = append(allowConditionsV4, wtFwpmFilterCondition0{
@@ -1115,7 +1113,7 @@ func blockDNS(except []net.IP, session uintptr, baseObjects *baseObjects, weight
matchType: cFWP_MATCH_EQUAL,
conditionValue: wtFwpConditionValue0{
_type: cFWP_UINT32,
- value: uintptr(binary.BigEndian.Uint32(ip4)),
+ value: uintptr(binary.BigEndian.Uint32(ip.AsSlice())),
},
})
}
@@ -1124,11 +1122,10 @@ func blockDNS(except []net.IP, session uintptr, baseObjects *baseObjects, weight
allowConditionsV6 := make([]wtFwpmFilterCondition0, 0, len(denyConditions)+len(except))
allowConditionsV6 = append(allowConditionsV6, denyConditions...)
for _, ip := range except {
- if ip.To4() != nil {
+ if !ip.Is6() {
continue
}
- var address wtFwpByteArray16
- copy(address.byteArray16[:], ip)
+ address := wtFwpByteArray16{byteArray16: ip.As16()}
allowConditionsV6 = append(allowConditionsV6, wtFwpmFilterCondition0{
fieldKey: cFWPM_CONDITION_IP_REMOTE_ADDRESS,
matchType: cFWP_MATCH_EQUAL,
diff --git a/tunnel/firewall/syscall_windows.go b/tunnel/firewall/syscall_windows.go
index 1d2696a1..4d8eea42 100644
--- a/tunnel/firewall/syscall_windows.go
+++ b/tunnel/firewall/syscall_windows.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package firewall
diff --git a/tunnel/firewall/types_windows.go b/tunnel/firewall/types_windows.go
index 9192c023..54e2aad7 100644
--- a/tunnel/firewall/types_windows.go
+++ b/tunnel/firewall/types_windows.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package firewall
@@ -148,8 +148,10 @@ var cFWPM_CONDITION_IP_LOCAL_ADDRESS = windows.GUID{
Data4: [8]byte{0xbf, 0xe3, 0xff, 0xd8, 0xf5, 0xa0, 0x89, 0x57},
}
-var cFWPM_CONDITION_ICMP_TYPE = cFWPM_CONDITION_IP_LOCAL_PORT
-var cFWPM_CONDITION_ICMP_CODE = cFWPM_CONDITION_IP_REMOTE_PORT
+var (
+ cFWPM_CONDITION_ICMP_TYPE = cFWPM_CONDITION_IP_LOCAL_PORT
+ cFWPM_CONDITION_ICMP_CODE = cFWPM_CONDITION_IP_REMOTE_PORT
+)
// 7bc43cbf-37ba-45f1-b74a-82ff518eeb10
var cFWPM_CONDITION_L2_FLAGS = windows.GUID{
diff --git a/tunnel/firewall/types_windows_386.go b/tunnel/firewall/types_windows_32.go
index e8e90663..29ae553a 100644
--- a/tunnel/firewall/types_windows_386.go
+++ b/tunnel/firewall/types_windows_32.go
@@ -1,6 +1,8 @@
+//go:build 386 || arm
+
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package firewall
diff --git a/tunnel/firewall/types_windows_amd64.go b/tunnel/firewall/types_windows_64.go
index 13fde97a..a476a745 100644
--- a/tunnel/firewall/types_windows_amd64.go
+++ b/tunnel/firewall/types_windows_64.go
@@ -1,6 +1,8 @@
+//go:build amd64 || arm64
+
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package firewall
diff --git a/tunnel/firewall/types_windows_test.go b/tunnel/firewall/types_windows_test.go
index 97cb032c..afa1988f 100644
--- a/tunnel/firewall/types_windows_test.go
+++ b/tunnel/firewall/types_windows_test.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package firewall
@@ -11,7 +11,6 @@ import (
)
func TestWtFwpByteBlobSize(t *testing.T) {
-
const actualWtFwpByteBlobSize = unsafe.Sizeof(wtFwpByteBlob{})
if actualWtFwpByteBlobSize != wtFwpByteBlob_Size {
@@ -21,7 +20,6 @@ func TestWtFwpByteBlobSize(t *testing.T) {
}
func TestWtFwpByteBlobOffsets(t *testing.T) {
-
s := wtFwpByteBlob{}
sp := uintptr(unsafe.Pointer(&s))
@@ -34,7 +32,6 @@ func TestWtFwpByteBlobOffsets(t *testing.T) {
}
func TestWtFwpmAction0Size(t *testing.T) {
-
const actualWtFwpmAction0Size = unsafe.Sizeof(wtFwpmAction0{})
if actualWtFwpmAction0Size != wtFwpmAction0_Size {
@@ -44,7 +41,6 @@ func TestWtFwpmAction0Size(t *testing.T) {
}
func TestWtFwpmAction0Offsets(t *testing.T) {
-
s := wtFwpmAction0{}
sp := uintptr(unsafe.Pointer(&s))
@@ -58,7 +54,6 @@ func TestWtFwpmAction0Offsets(t *testing.T) {
}
func TestWtFwpBitmapArray64Size(t *testing.T) {
-
const actualWtFwpBitmapArray64Size = unsafe.Sizeof(wtFwpBitmapArray64{})
if actualWtFwpBitmapArray64Size != wtFwpBitmapArray64_Size {
@@ -68,7 +63,6 @@ func TestWtFwpBitmapArray64Size(t *testing.T) {
}
func TestWtFwpByteArray6Size(t *testing.T) {
-
const actualWtFwpByteArray6Size = unsafe.Sizeof(wtFwpByteArray6{})
if actualWtFwpByteArray6Size != wtFwpByteArray6_Size {
@@ -78,7 +72,6 @@ func TestWtFwpByteArray6Size(t *testing.T) {
}
func TestWtFwpByteArray16Size(t *testing.T) {
-
const actualWtFwpByteArray16Size = unsafe.Sizeof(wtFwpByteArray16{})
if actualWtFwpByteArray16Size != wtFwpByteArray16_Size {
@@ -88,7 +81,6 @@ func TestWtFwpByteArray16Size(t *testing.T) {
}
func TestWtFwpConditionValue0Size(t *testing.T) {
-
const actualWtFwpConditionValue0Size = unsafe.Sizeof(wtFwpConditionValue0{})
if actualWtFwpConditionValue0Size != wtFwpConditionValue0_Size {
@@ -98,7 +90,6 @@ func TestWtFwpConditionValue0Size(t *testing.T) {
}
func TestWtFwpConditionValue0Offsets(t *testing.T) {
-
s := wtFwpConditionValue0{}
sp := uintptr(unsafe.Pointer(&s))
@@ -111,7 +102,6 @@ func TestWtFwpConditionValue0Offsets(t *testing.T) {
}
func TestWtFwpV4AddrAndMaskSize(t *testing.T) {
-
const actualWtFwpV4AddrAndMaskSize = unsafe.Sizeof(wtFwpV4AddrAndMask{})
if actualWtFwpV4AddrAndMaskSize != wtFwpV4AddrAndMask_Size {
@@ -121,7 +111,6 @@ func TestWtFwpV4AddrAndMaskSize(t *testing.T) {
}
func TestWtFwpV4AddrAndMaskOffsets(t *testing.T) {
-
s := wtFwpV4AddrAndMask{}
sp := uintptr(unsafe.Pointer(&s))
@@ -135,7 +124,6 @@ func TestWtFwpV4AddrAndMaskOffsets(t *testing.T) {
}
func TestWtFwpV6AddrAndMaskSize(t *testing.T) {
-
const actualWtFwpV6AddrAndMaskSize = unsafe.Sizeof(wtFwpV6AddrAndMask{})
if actualWtFwpV6AddrAndMaskSize != wtFwpV6AddrAndMask_Size {
@@ -145,7 +133,6 @@ func TestWtFwpV6AddrAndMaskSize(t *testing.T) {
}
func TestWtFwpV6AddrAndMaskOffsets(t *testing.T) {
-
s := wtFwpV6AddrAndMask{}
sp := uintptr(unsafe.Pointer(&s))
@@ -159,7 +146,6 @@ func TestWtFwpV6AddrAndMaskOffsets(t *testing.T) {
}
func TestWtFwpValue0Size(t *testing.T) {
-
const actualWtFwpValue0Size = unsafe.Sizeof(wtFwpValue0{})
if actualWtFwpValue0Size != wtFwpValue0_Size {
@@ -168,7 +154,6 @@ func TestWtFwpValue0Size(t *testing.T) {
}
func TestWtFwpValue0Offsets(t *testing.T) {
-
s := wtFwpValue0{}
sp := uintptr(unsafe.Pointer(&s))
@@ -181,7 +166,6 @@ func TestWtFwpValue0Offsets(t *testing.T) {
}
func TestWtFwpmDisplayData0Size(t *testing.T) {
-
const actualWtFwpmDisplayData0Size = unsafe.Sizeof(wtFwpmDisplayData0{})
if actualWtFwpmDisplayData0Size != wtFwpmDisplayData0_Size {
@@ -191,7 +175,6 @@ func TestWtFwpmDisplayData0Size(t *testing.T) {
}
func TestWtFwpmDisplayData0Offsets(t *testing.T) {
-
s := wtFwpmDisplayData0{}
sp := uintptr(unsafe.Pointer(&s))
@@ -205,7 +188,6 @@ func TestWtFwpmDisplayData0Offsets(t *testing.T) {
}
func TestWtFwpmFilterCondition0Size(t *testing.T) {
-
const actualWtFwpmFilterCondition0Size = unsafe.Sizeof(wtFwpmFilterCondition0{})
if actualWtFwpmFilterCondition0Size != wtFwpmFilterCondition0_Size {
@@ -215,7 +197,6 @@ func TestWtFwpmFilterCondition0Size(t *testing.T) {
}
func TestWtFwpmFilterCondition0Offsets(t *testing.T) {
-
s := wtFwpmFilterCondition0{}
sp := uintptr(unsafe.Pointer(&s))
@@ -237,7 +218,6 @@ func TestWtFwpmFilterCondition0Offsets(t *testing.T) {
}
func TestWtFwpmFilter0Size(t *testing.T) {
-
const actualWtFwpmFilter0Size = unsafe.Sizeof(wtFwpmFilter0{})
if actualWtFwpmFilter0Size != wtFwpmFilter0_Size {
@@ -247,7 +227,6 @@ func TestWtFwpmFilter0Size(t *testing.T) {
}
func TestWtFwpmFilter0Offsets(t *testing.T) {
-
s := wtFwpmFilter0{}
sp := uintptr(unsafe.Pointer(&s))
@@ -364,7 +343,6 @@ func TestWtFwpmFilter0Offsets(t *testing.T) {
}
func TestWtFwpProvider0Size(t *testing.T) {
-
const actualWtFwpProvider0Size = unsafe.Sizeof(wtFwpProvider0{})
if actualWtFwpProvider0Size != wtFwpProvider0_Size {
@@ -374,7 +352,6 @@ func TestWtFwpProvider0Size(t *testing.T) {
}
func TestWtFwpProvider0Offsets(t *testing.T) {
-
s := wtFwpProvider0{}
sp := uintptr(unsafe.Pointer(&s))
@@ -412,7 +389,6 @@ func TestWtFwpProvider0Offsets(t *testing.T) {
}
func TestWtFwpmSession0Size(t *testing.T) {
-
const actualWtFwpmSession0Size = unsafe.Sizeof(wtFwpmSession0{})
if actualWtFwpmSession0Size != wtFwpmSession0_Size {
@@ -422,7 +398,6 @@ func TestWtFwpmSession0Size(t *testing.T) {
}
func TestWtFwpmSession0Offsets(t *testing.T) {
-
s := wtFwpmSession0{}
sp := uintptr(unsafe.Pointer(&s))
@@ -482,7 +457,6 @@ func TestWtFwpmSession0Offsets(t *testing.T) {
}
func TestWtFwpmSublayer0Size(t *testing.T) {
-
const actualWtFwpmSublayer0Size = unsafe.Sizeof(wtFwpmSublayer0{})
if actualWtFwpmSublayer0Size != wtFwpmSublayer0_Size {
@@ -492,7 +466,6 @@ func TestWtFwpmSublayer0Size(t *testing.T) {
}
func TestWtFwpmSublayer0Offsets(t *testing.T) {
-
s := wtFwpmSublayer0{}
sp := uintptr(unsafe.Pointer(&s))
diff --git a/tunnel/firewall/zsyscall_windows.go b/tunnel/firewall/zsyscall_windows.go
index 846d4ae8..9e60132d 100644
--- a/tunnel/firewall/zsyscall_windows.go
+++ b/tunnel/firewall/zsyscall_windows.go
@@ -19,6 +19,7 @@ const (
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
+ errERROR_EINVAL error = syscall.EINVAL
)
// errnoErr returns common boxed Errno values, to prevent
@@ -26,7 +27,7 @@ var (
func errnoErr(e syscall.Errno) error {
switch e {
case 0:
- return nil
+ return errERROR_EINVAL
case errnoERROR_IO_PENDING:
return errERROR_IO_PENDING
}
@@ -39,62 +40,38 @@ func errnoErr(e syscall.Errno) error {
var (
modfwpuclnt = windows.NewLazySystemDLL("fwpuclnt.dll")
- procFwpmEngineOpen0 = modfwpuclnt.NewProc("FwpmEngineOpen0")
procFwpmEngineClose0 = modfwpuclnt.NewProc("FwpmEngineClose0")
- procFwpmSubLayerAdd0 = modfwpuclnt.NewProc("FwpmSubLayerAdd0")
- procFwpmGetAppIdFromFileName0 = modfwpuclnt.NewProc("FwpmGetAppIdFromFileName0")
- procFwpmFreeMemory0 = modfwpuclnt.NewProc("FwpmFreeMemory0")
+ procFwpmEngineOpen0 = modfwpuclnt.NewProc("FwpmEngineOpen0")
procFwpmFilterAdd0 = modfwpuclnt.NewProc("FwpmFilterAdd0")
+ procFwpmFreeMemory0 = modfwpuclnt.NewProc("FwpmFreeMemory0")
+ procFwpmGetAppIdFromFileName0 = modfwpuclnt.NewProc("FwpmGetAppIdFromFileName0")
+ procFwpmProviderAdd0 = modfwpuclnt.NewProc("FwpmProviderAdd0")
+ procFwpmSubLayerAdd0 = modfwpuclnt.NewProc("FwpmSubLayerAdd0")
+ procFwpmTransactionAbort0 = modfwpuclnt.NewProc("FwpmTransactionAbort0")
procFwpmTransactionBegin0 = modfwpuclnt.NewProc("FwpmTransactionBegin0")
procFwpmTransactionCommit0 = modfwpuclnt.NewProc("FwpmTransactionCommit0")
- procFwpmTransactionAbort0 = modfwpuclnt.NewProc("FwpmTransactionAbort0")
- procFwpmProviderAdd0 = modfwpuclnt.NewProc("FwpmProviderAdd0")
)
-func fwpmEngineOpen0(serverName *uint16, authnService wtRpcCAuthN, authIdentity *uintptr, session *wtFwpmSession0, engineHandle unsafe.Pointer) (err error) {
- r1, _, e1 := syscall.Syscall6(procFwpmEngineOpen0.Addr(), 5, uintptr(unsafe.Pointer(serverName)), uintptr(authnService), uintptr(unsafe.Pointer(authIdentity)), uintptr(unsafe.Pointer(session)), uintptr(engineHandle), 0)
- if r1 != 0 {
- if e1 != 0 {
- err = errnoErr(e1)
- } else {
- err = syscall.EINVAL
- }
- }
- return
-}
-
func fwpmEngineClose0(engineHandle uintptr) (err error) {
r1, _, e1 := syscall.Syscall(procFwpmEngineClose0.Addr(), 1, uintptr(engineHandle), 0, 0)
if r1 != 0 {
- if e1 != 0 {
- err = errnoErr(e1)
- } else {
- err = syscall.EINVAL
- }
+ err = errnoErr(e1)
}
return
}
-func fwpmSubLayerAdd0(engineHandle uintptr, subLayer *wtFwpmSublayer0, sd uintptr) (err error) {
- r1, _, e1 := syscall.Syscall(procFwpmSubLayerAdd0.Addr(), 3, uintptr(engineHandle), uintptr(unsafe.Pointer(subLayer)), uintptr(sd))
+func fwpmEngineOpen0(serverName *uint16, authnService wtRpcCAuthN, authIdentity *uintptr, session *wtFwpmSession0, engineHandle unsafe.Pointer) (err error) {
+ r1, _, e1 := syscall.Syscall6(procFwpmEngineOpen0.Addr(), 5, uintptr(unsafe.Pointer(serverName)), uintptr(authnService), uintptr(unsafe.Pointer(authIdentity)), uintptr(unsafe.Pointer(session)), uintptr(engineHandle), 0)
if r1 != 0 {
- if e1 != 0 {
- err = errnoErr(e1)
- } else {
- err = syscall.EINVAL
- }
+ err = errnoErr(e1)
}
return
}
-func fwpmGetAppIdFromFileName0(fileName *uint16, appID unsafe.Pointer) (err error) {
- r1, _, e1 := syscall.Syscall(procFwpmGetAppIdFromFileName0.Addr(), 2, uintptr(unsafe.Pointer(fileName)), uintptr(appID), 0)
+func fwpmFilterAdd0(engineHandle uintptr, filter *wtFwpmFilter0, sd uintptr, id *uint64) (err error) {
+ r1, _, e1 := syscall.Syscall6(procFwpmFilterAdd0.Addr(), 4, uintptr(engineHandle), uintptr(unsafe.Pointer(filter)), uintptr(sd), uintptr(unsafe.Pointer(id)), 0, 0)
if r1 != 0 {
- if e1 != 0 {
- err = errnoErr(e1)
- } else {
- err = syscall.EINVAL
- }
+ err = errnoErr(e1)
}
return
}
@@ -104,38 +81,26 @@ func fwpmFreeMemory0(p unsafe.Pointer) {
return
}
-func fwpmFilterAdd0(engineHandle uintptr, filter *wtFwpmFilter0, sd uintptr, id *uint64) (err error) {
- r1, _, e1 := syscall.Syscall6(procFwpmFilterAdd0.Addr(), 4, uintptr(engineHandle), uintptr(unsafe.Pointer(filter)), uintptr(sd), uintptr(unsafe.Pointer(id)), 0, 0)
+func fwpmGetAppIdFromFileName0(fileName *uint16, appID unsafe.Pointer) (err error) {
+ r1, _, e1 := syscall.Syscall(procFwpmGetAppIdFromFileName0.Addr(), 2, uintptr(unsafe.Pointer(fileName)), uintptr(appID), 0)
if r1 != 0 {
- if e1 != 0 {
- err = errnoErr(e1)
- } else {
- err = syscall.EINVAL
- }
+ err = errnoErr(e1)
}
return
}
-func fwpmTransactionBegin0(engineHandle uintptr, flags uint32) (err error) {
- r1, _, e1 := syscall.Syscall(procFwpmTransactionBegin0.Addr(), 2, uintptr(engineHandle), uintptr(flags), 0)
+func fwpmProviderAdd0(engineHandle uintptr, provider *wtFwpmProvider0, sd uintptr) (err error) {
+ r1, _, e1 := syscall.Syscall(procFwpmProviderAdd0.Addr(), 3, uintptr(engineHandle), uintptr(unsafe.Pointer(provider)), uintptr(sd))
if r1 != 0 {
- if e1 != 0 {
- err = errnoErr(e1)
- } else {
- err = syscall.EINVAL
- }
+ err = errnoErr(e1)
}
return
}
-func fwpmTransactionCommit0(engineHandle uintptr) (err error) {
- r1, _, e1 := syscall.Syscall(procFwpmTransactionCommit0.Addr(), 1, uintptr(engineHandle), 0, 0)
+func fwpmSubLayerAdd0(engineHandle uintptr, subLayer *wtFwpmSublayer0, sd uintptr) (err error) {
+ r1, _, e1 := syscall.Syscall(procFwpmSubLayerAdd0.Addr(), 3, uintptr(engineHandle), uintptr(unsafe.Pointer(subLayer)), uintptr(sd))
if r1 != 0 {
- if e1 != 0 {
- err = errnoErr(e1)
- } else {
- err = syscall.EINVAL
- }
+ err = errnoErr(e1)
}
return
}
@@ -143,23 +108,23 @@ func fwpmTransactionCommit0(engineHandle uintptr) (err error) {
func fwpmTransactionAbort0(engineHandle uintptr) (err error) {
r1, _, e1 := syscall.Syscall(procFwpmTransactionAbort0.Addr(), 1, uintptr(engineHandle), 0, 0)
if r1 != 0 {
- if e1 != 0 {
- err = errnoErr(e1)
- } else {
- err = syscall.EINVAL
- }
+ err = errnoErr(e1)
}
return
}
-func fwpmProviderAdd0(engineHandle uintptr, provider *wtFwpmProvider0, sd uintptr) (err error) {
- r1, _, e1 := syscall.Syscall(procFwpmProviderAdd0.Addr(), 3, uintptr(engineHandle), uintptr(unsafe.Pointer(provider)), uintptr(sd))
+func fwpmTransactionBegin0(engineHandle uintptr, flags uint32) (err error) {
+ r1, _, e1 := syscall.Syscall(procFwpmTransactionBegin0.Addr(), 2, uintptr(engineHandle), uintptr(flags), 0)
+ if r1 != 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
+
+func fwpmTransactionCommit0(engineHandle uintptr) (err error) {
+ r1, _, e1 := syscall.Syscall(procFwpmTransactionCommit0.Addr(), 1, uintptr(engineHandle), 0, 0)
if r1 != 0 {
- if e1 != 0 {
- err = errnoErr(e1)
- } else {
- err = syscall.EINVAL
- }
+ err = errnoErr(e1)
}
return
}
diff --git a/tunnel/interfacewatcher.go b/tunnel/interfacewatcher.go
index 1f632725..a831d06e 100644
--- a/tunnel/interfacewatcher.go
+++ b/tunnel/interfacewatcher.go
@@ -1,20 +1,20 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package tunnel
import (
+ "errors"
+ "fmt"
"log"
"sync"
+ "time"
"golang.org/x/sys/windows"
-
- "golang.zx2c4.com/wireguard/device"
- "golang.zx2c4.com/wireguard/tun"
-
"golang.zx2c4.com/wireguard/windows/conf"
+ "golang.zx2c4.com/wireguard/windows/driver"
"golang.zx2c4.com/wireguard/windows/services"
"golang.zx2c4.com/wireguard/windows/tunnel/firewall"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
@@ -24,63 +24,30 @@ type interfaceWatcherError struct {
serviceError services.Error
err error
}
+
type interfaceWatcherEvent struct {
luid winipcfg.LUID
family winipcfg.AddressFamily
}
+
type interfaceWatcher struct {
- errors chan interfaceWatcherError
+ errors chan interfaceWatcherError
+ started chan winipcfg.AddressFamily
- device *device.Device
- conf *conf.Config
- tun *tun.NativeTun
+ conf *conf.Config
+ adapter *driver.Adapter
+ luid winipcfg.LUID
setupMutex sync.Mutex
interfaceChangeCallback winipcfg.ChangeCallback
changeCallbacks4 []winipcfg.ChangeCallback
changeCallbacks6 []winipcfg.ChangeCallback
storedEvents []interfaceWatcherEvent
-}
-
-func hasDefaultRoute(family winipcfg.AddressFamily, peers []conf.Peer) bool {
- var (
- foundV401 bool
- foundV41281 bool
- foundV600001 bool
- foundV680001 bool
- foundV400 bool
- foundV600 bool
- v40 = [4]byte{}
- v60 = [16]byte{}
- v48 = [4]byte{0x80}
- v68 = [16]byte{0x80}
- )
- for _, peer := range peers {
- for _, allowedip := range peer.AllowedIPs {
- if allowedip.Cidr == 1 && len(allowedip.IP) == 16 && allowedip.IP.Equal(v60[:]) {
- foundV600001 = true
- } else if allowedip.Cidr == 1 && len(allowedip.IP) == 16 && allowedip.IP.Equal(v68[:]) {
- foundV680001 = true
- } else if allowedip.Cidr == 1 && len(allowedip.IP) == 4 && allowedip.IP.Equal(v40[:]) {
- foundV401 = true
- } else if allowedip.Cidr == 1 && len(allowedip.IP) == 4 && allowedip.IP.Equal(v48[:]) {
- foundV41281 = true
- } else if allowedip.Cidr == 0 && len(allowedip.IP) == 16 && allowedip.IP.Equal(v60[:]) {
- foundV600 = true
- } else if allowedip.Cidr == 0 && len(allowedip.IP) == 4 && allowedip.IP.Equal(v40[:]) {
- foundV400 = true
- }
- }
- }
- if family == windows.AF_INET {
- return foundV400 || (foundV401 && foundV41281)
- } else if family == windows.AF_INET6 {
- return foundV600 || (foundV600001 && foundV680001)
- }
- return false
+ watchdog *time.Timer
}
func (iw *interfaceWatcher) setup(family winipcfg.AddressFamily) {
+ iw.watchdog.Stop()
var changeCallbacks *[]winipcfg.ChangeCallback
var ipversion string
if family == windows.AF_INET {
@@ -100,25 +67,35 @@ func (iw *interfaceWatcher) setup(family winipcfg.AddressFamily) {
}
var err error
- log.Printf("Monitoring default %s routes", ipversion)
- *changeCallbacks, err = monitorDefaultRoutes(family, iw.device, iw.conf.Interface.MTU == 0, hasDefaultRoute(family, iw.conf.Peers), iw.tun)
- if err != nil {
- iw.errors <- interfaceWatcherError{services.ErrorBindSocketsToDefaultRoutes, err}
- return
+ if iw.conf.Interface.MTU == 0 {
+ log.Printf("Monitoring MTU of default %s routes", ipversion)
+ *changeCallbacks, err = monitorMTU(family, iw.luid)
+ if err != nil {
+ iw.errors <- interfaceWatcherError{services.ErrorMonitorMTUChanges, err}
+ return
+ }
}
log.Printf("Setting device %s addresses", ipversion)
- err = configureInterface(family, iw.conf, iw.tun)
+ err = configureInterface(family, iw.conf, iw.luid)
if err != nil {
iw.errors <- interfaceWatcherError{services.ErrorSetNetConfig, err}
return
}
+ evaluateDynamicPitfalls(family, iw.conf, iw.luid)
+
+ iw.started <- family
}
func watchInterface() (*interfaceWatcher, error) {
iw := &interfaceWatcher{
- errors: make(chan interfaceWatcherError, 2),
+ errors: make(chan interfaceWatcherError, 2),
+ started: make(chan winipcfg.AddressFamily, 4),
}
+ iw.watchdog = time.AfterFunc(time.Duration(1<<63-1), func() {
+ iw.errors <- interfaceWatcherError{services.ErrorCreateNetworkAdapter, errors.New("TCP/IP interface for adapter did not appear after one minute")}
+ })
+ iw.watchdog.Stop()
var err error
iw.interfaceChangeCallback, err = winipcfg.RegisterInterfaceChangeCallback(func(notificationType winipcfg.MibNotificationType, iface *winipcfg.MibIPInterfaceRow) {
iw.setupMutex.Lock()
@@ -127,28 +104,41 @@ func watchInterface() (*interfaceWatcher, error) {
if notificationType != winipcfg.MibAddInstance {
return
}
- if iw.tun == nil {
+ if iw.luid == 0 {
iw.storedEvents = append(iw.storedEvents, interfaceWatcherEvent{iface.InterfaceLUID, iface.Family})
return
}
- if iface.InterfaceLUID != winipcfg.LUID(iw.tun.LUID()) {
+ if iface.InterfaceLUID != iw.luid {
return
}
iw.setup(iface.Family)
+
+ if state, err := iw.adapter.AdapterState(); err == nil && state == driver.AdapterStateDown {
+ log.Println("Reinitializing adapter configuration")
+ err = iw.adapter.SetConfiguration(iw.conf.ToDriverConfiguration())
+ if err != nil {
+ log.Println(fmt.Errorf("%v: %w", services.ErrorDeviceSetConfig, err))
+ }
+ err = iw.adapter.SetAdapterState(driver.AdapterStateUp)
+ if err != nil {
+ log.Println(fmt.Errorf("%v: %w", services.ErrorDeviceBringUp, err))
+ }
+ }
})
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("unable to register interface change callback: %w", err)
}
return iw, nil
}
-func (iw *interfaceWatcher) Configure(device *device.Device, conf *conf.Config, tun *tun.NativeTun) {
+func (iw *interfaceWatcher) Configure(adapter *driver.Adapter, conf *conf.Config, luid winipcfg.LUID) {
iw.setupMutex.Lock()
defer iw.setupMutex.Unlock()
+ iw.watchdog.Reset(time.Minute)
- iw.device, iw.conf, iw.tun = device, conf, tun
+ iw.adapter, iw.conf, iw.luid = adapter, conf, luid
for _, event := range iw.storedEvents {
- if event.luid == winipcfg.LUID(iw.tun.LUID()) {
+ if event.luid == luid {
iw.setup(event.family)
}
}
@@ -157,10 +147,11 @@ func (iw *interfaceWatcher) Configure(device *device.Device, conf *conf.Config,
func (iw *interfaceWatcher) Destroy() {
iw.setupMutex.Lock()
+ iw.watchdog.Stop()
changeCallbacks4 := iw.changeCallbacks4
changeCallbacks6 := iw.changeCallbacks6
interfaceChangeCallback := iw.interfaceChangeCallback
- tun := iw.tun
+ luid := iw.luid
iw.setupMutex.Unlock()
if interfaceChangeCallback != nil {
@@ -186,15 +177,15 @@ func (iw *interfaceWatcher) Destroy() {
changeCallbacks6 = changeCallbacks6[1:]
}
firewall.DisableFirewall()
- if tun != nil && iw.tun == tun {
+ if luid != 0 && iw.luid == luid {
// It seems that the Windows networking stack doesn't like it when we destroy interfaces that have active
// routes, so to be certain, just remove everything before destroying.
- luid := winipcfg.LUID(tun.LUID())
luid.FlushRoutes(windows.AF_INET)
luid.FlushIPAddresses(windows.AF_INET)
+ luid.FlushDNS(windows.AF_INET)
luid.FlushRoutes(windows.AF_INET6)
luid.FlushIPAddresses(windows.AF_INET6)
- luid.FlushDNS()
+ luid.FlushDNS(windows.AF_INET6)
}
iw.setupMutex.Unlock()
}
diff --git a/tunnel/ipcpermissions.go b/tunnel/ipcpermissions.go
deleted file mode 100644
index 613d0283..00000000
--- a/tunnel/ipcpermissions.go
+++ /dev/null
@@ -1,63 +0,0 @@
-/* SPDX-License-Identifier: MIT
- *
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
- */
-
-package tunnel
-
-import (
- "golang.org/x/sys/windows"
-
- "golang.zx2c4.com/wireguard/ipc"
-
- "golang.zx2c4.com/wireguard/windows/conf"
-)
-
-func CopyConfigOwnerToIPCSecurityDescriptor(filename string) error {
- if conf.PathIsEncrypted(filename) {
- return nil
- }
-
- fileSd, err := windows.GetNamedSecurityInfo(filename, windows.SE_FILE_OBJECT, windows.OWNER_SECURITY_INFORMATION)
- if err != nil {
- return err
- }
- fileOwner, _, err := fileSd.Owner()
- if err != nil {
- return err
- }
- if fileOwner.IsWellKnown(windows.WinLocalSystemSid) {
- return nil
- }
- additionalEntries := []windows.EXPLICIT_ACCESS{{
- AccessPermissions: windows.GENERIC_ALL,
- AccessMode: windows.GRANT_ACCESS,
- Trustee: windows.TRUSTEE{
- TrusteeForm: windows.TRUSTEE_IS_SID,
- TrusteeType: windows.TRUSTEE_IS_USER,
- TrusteeValue: windows.TrusteeValueFromSID(fileOwner),
- },
- }}
-
- sd, err := ipc.UAPISecurityDescriptor.ToAbsolute()
- if err != nil {
- return err
- }
- dacl, defaulted, _ := sd.DACL()
-
- newDacl, err := windows.ACLFromEntries(additionalEntries, dacl)
- if err != nil {
- return err
- }
- err = sd.SetDACL(newDacl, true, defaulted)
- if err != nil {
- return err
- }
- sd, err = sd.ToSelfRelative()
- if err != nil {
- return err
- }
- ipc.UAPISecurityDescriptor = sd
-
- return nil
-}
diff --git a/tunnel/defaultroutemonitor.go b/tunnel/mtumonitor.go
index c102b644..c07823a2 100644
--- a/tunnel/defaultroutemonitor.go
+++ b/tunnel/mtumonitor.go
@@ -1,30 +1,23 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package tunnel
import (
- "log"
- "sync"
- "time"
-
"golang.org/x/sys/windows"
- "golang.zx2c4.com/wireguard/device"
- "golang.zx2c4.com/wireguard/tun"
-
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
)
-func bindSocketRoute(family winipcfg.AddressFamily, device *device.Device, ourLUID winipcfg.LUID, lastLUID *winipcfg.LUID, lastIndex *uint32, blackholeWhenLoop bool) error {
+func findDefaultLUID(family winipcfg.AddressFamily, ourLUID winipcfg.LUID, lastLUID *winipcfg.LUID, lastIndex *uint32) error {
r, err := winipcfg.GetIPForwardTable2(family)
if err != nil {
return err
}
lowestMetric := ^uint32(0)
- index := uint32(0) // Zero is "unspecified", which for IP_UNICAST_IF resets the value, which is what we want.
- luid := winipcfg.LUID(0) // Hopefully luid zero is unspecified, but hard to find docs saying so.
+ index := uint32(0)
+ luid := winipcfg.LUID(0)
for i := range r {
if r[i].DestinationPrefix.PrefixLength != 0 || r[i].InterfaceLUID == ourLUID {
continue
@@ -33,8 +26,14 @@ func bindSocketRoute(family winipcfg.AddressFamily, device *device.Device, ourLU
if err != nil || ifrow.OperStatus != winipcfg.IfOperStatusUp {
continue
}
- if r[i].Metric < lowestMetric {
- lowestMetric = r[i].Metric
+
+ iface, err := r[i].InterfaceLUID.IPInterface(family)
+ if err != nil {
+ continue
+ }
+
+ if r[i].Metric+iface.Metric < lowestMetric {
+ lowestMetric = r[i].Metric + iface.Metric
index = r[i].InterfaceIndex
luid = r[i].InterfaceLUID
}
@@ -44,36 +43,24 @@ func bindSocketRoute(family winipcfg.AddressFamily, device *device.Device, ourLU
}
*lastLUID = luid
*lastIndex = index
- blackhole := blackholeWhenLoop && index == 0
- if family == windows.AF_INET {
- log.Printf("Binding v4 socket to interface %d (blackhole=%v)", index, blackhole)
- return device.BindSocketToInterface4(index, blackhole)
- } else if family == windows.AF_INET6 {
- log.Printf("Binding v6 socket to interface %d (blackhole=%v)", index, blackhole)
- return device.BindSocketToInterface6(index, blackhole)
- }
return nil
}
-func monitorDefaultRoutes(family winipcfg.AddressFamily, device *device.Device, autoMTU bool, blackholeWhenLoop bool, tun *tun.NativeTun) ([]winipcfg.ChangeCallback, error) {
+func monitorMTU(family winipcfg.AddressFamily, ourLUID winipcfg.LUID) ([]winipcfg.ChangeCallback, error) {
var minMTU uint32
if family == windows.AF_INET {
minMTU = 576
} else if family == windows.AF_INET6 {
minMTU = 1280
}
- ourLUID := winipcfg.LUID(tun.LUID())
lastLUID := winipcfg.LUID(0)
lastIndex := ^uint32(0)
lastMTU := uint32(0)
doIt := func() error {
- err := bindSocketRoute(family, device, ourLUID, &lastLUID, &lastIndex, blackholeWhenLoop)
+ err := findDefaultLUID(family, ourLUID, &lastLUID, &lastIndex)
if err != nil {
return err
}
- if !autoMTU {
- return nil
- }
mtu := uint32(0)
if lastLUID != 0 {
iface, err := lastLUID.Interface()
@@ -97,7 +84,6 @@ func monitorDefaultRoutes(family winipcfg.AddressFamily, device *device.Device,
if err != nil {
return err
}
- tun.ForceMTU(int(iface.NLMTU)) // TODO: having one MTU for both v4 and v6 kind of breaks the windows model, so right now this just gets the second one which is... bad.
lastMTU = mtu
}
return nil
@@ -106,32 +92,9 @@ func monitorDefaultRoutes(family winipcfg.AddressFamily, device *device.Device,
if err != nil {
return nil, err
}
-
- firstBurst := time.Time{}
- burstMutex := sync.Mutex{}
- burstTimer := time.AfterFunc(time.Hour*200, func() {
- burstMutex.Lock()
- firstBurst = time.Time{}
- doIt()
- burstMutex.Unlock()
- })
- burstTimer.Stop()
- bump := func() {
- burstMutex.Lock()
- burstTimer.Reset(time.Millisecond * 150)
- if firstBurst.IsZero() {
- firstBurst = time.Now()
- } else if time.Since(firstBurst) > time.Second*2 {
- firstBurst = time.Time{}
- burstTimer.Stop()
- doIt()
- }
- burstMutex.Unlock()
- }
-
cbr, err := winipcfg.RegisterRouteChangeCallback(func(notificationType winipcfg.MibNotificationType, route *winipcfg.MibIPforwardRow2) {
if route != nil && route.DestinationPrefix.PrefixLength == 0 {
- bump()
+ doIt()
}
})
if err != nil {
@@ -139,7 +102,7 @@ func monitorDefaultRoutes(family winipcfg.AddressFamily, device *device.Device,
}
cbi, err := winipcfg.RegisterInterfaceChangeCallback(func(notificationType winipcfg.MibNotificationType, iface *winipcfg.MibIPInterfaceRow) {
if notificationType == winipcfg.MibParameterNotification {
- bump()
+ doIt()
}
})
if err != nil {
diff --git a/tunnel/pitfalls.go b/tunnel/pitfalls.go
new file mode 100644
index 00000000..fdef6eb2
--- /dev/null
+++ b/tunnel/pitfalls.go
@@ -0,0 +1,177 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package tunnel
+
+import (
+ "log"
+ "net/netip"
+ "strings"
+ "unsafe"
+
+ "golang.org/x/sys/windows"
+ "golang.org/x/sys/windows/svc/mgr"
+ "golang.zx2c4.com/wireguard/windows/conf"
+ "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
+)
+
+func evaluateStaticPitfalls() {
+ go func() {
+ pitfallDnsCacheDisabled()
+ pitfallVirtioNetworkDriver()
+ }()
+}
+
+func evaluateDynamicPitfalls(family winipcfg.AddressFamily, conf *conf.Config, luid winipcfg.LUID) {
+ go func() {
+ pitfallWeakHostSend(family, conf, luid)
+ }()
+}
+
+func pitfallDnsCacheDisabled() {
+ scm, err := mgr.Connect()
+ if err != nil {
+ return
+ }
+ defer scm.Disconnect()
+ svc := mgr.Service{Name: "dnscache"}
+ svc.Handle, err = windows.OpenService(scm.Handle, windows.StringToUTF16Ptr(svc.Name), windows.SERVICE_QUERY_CONFIG)
+ if err != nil {
+ return
+ }
+ defer svc.Close()
+ cfg, err := svc.Config()
+ if err != nil {
+ return
+ }
+ if cfg.StartType != mgr.StartDisabled {
+ return
+ }
+
+ log.Printf("Warning: the %q (dnscache) service is disabled; please re-enable it", cfg.DisplayName)
+}
+
+func pitfallVirtioNetworkDriver() {
+ var modules []windows.RTL_PROCESS_MODULE_INFORMATION
+ for bufferSize := uint32(128 * 1024); ; {
+ moduleBuffer := make([]byte, bufferSize)
+ err := windows.NtQuerySystemInformation(windows.SystemModuleInformation, unsafe.Pointer(&moduleBuffer[0]), bufferSize, &bufferSize)
+ switch err {
+ case windows.STATUS_INFO_LENGTH_MISMATCH:
+ continue
+ case nil:
+ break
+ default:
+ return
+ }
+ mods := (*windows.RTL_PROCESS_MODULES)(unsafe.Pointer(&moduleBuffer[0]))
+ modules = unsafe.Slice(&mods.Modules[0], mods.NumberOfModules)
+ break
+ }
+ for i := range modules {
+ if !strings.EqualFold(windows.ByteSliceToString(modules[i].FullPathName[modules[i].OffsetToFileName:]), "netkvm.sys") {
+ continue
+ }
+ driverPath := `\\?\GLOBALROOT` + windows.ByteSliceToString(modules[i].FullPathName[:])
+ var zero windows.Handle
+ infoSize, err := windows.GetFileVersionInfoSize(driverPath, &zero)
+ if err != nil {
+ return
+ }
+ versionInfo := make([]byte, infoSize)
+ err = windows.GetFileVersionInfo(driverPath, 0, infoSize, unsafe.Pointer(&versionInfo[0]))
+ if err != nil {
+ return
+ }
+ var fixedInfo *windows.VS_FIXEDFILEINFO
+ fixedInfoLen := uint32(unsafe.Sizeof(*fixedInfo))
+ err = windows.VerQueryValue(unsafe.Pointer(&versionInfo[0]), `\`, unsafe.Pointer(&fixedInfo), &fixedInfoLen)
+ if err != nil {
+ return
+ }
+ const minimumPlausibleVersion = 40 << 48
+ const minimumGoodVersion = (100 << 48) | (85 << 32) | (104 << 16) | (20800 << 0)
+ version := (uint64(fixedInfo.FileVersionMS) << 32) | uint64(fixedInfo.FileVersionLS)
+ if version >= minimumGoodVersion || version < minimumPlausibleVersion {
+ return
+ }
+ log.Println("Warning: the VirtIO network driver (NetKVM) is out of date and may cause known problems; please update to v100.85.104.20800 or later")
+ return
+ }
+}
+
+func pitfallWeakHostSend(family winipcfg.AddressFamily, conf *conf.Config, ourLUID winipcfg.LUID) {
+ routingTable, err := winipcfg.GetIPForwardTable2(family)
+ if err != nil {
+ return
+ }
+ type endpointRoute struct {
+ addr netip.Addr
+ name string
+ lowestMetric uint32
+ highestCIDR uint8
+ weakHostSend bool
+ finalIsOurs bool
+ }
+ endpoints := make([]endpointRoute, 0, len(conf.Peers))
+ for _, peer := range conf.Peers {
+ addr, err := netip.ParseAddr(peer.Endpoint.Host)
+ if err != nil || (addr.Is4() && family != windows.AF_INET) || (addr.Is6() && family != windows.AF_INET6) {
+ continue
+ }
+ endpoints = append(endpoints, endpointRoute{addr: addr, lowestMetric: ^uint32(0)})
+ }
+ for i := range routingTable {
+ var (
+ ifrow *winipcfg.MibIfRow2
+ ifacerow *winipcfg.MibIPInterfaceRow
+ metric uint32
+ )
+ for j := range endpoints {
+ r, e := &routingTable[i], &endpoints[j]
+ if r.DestinationPrefix.PrefixLength < e.highestCIDR {
+ continue
+ }
+ if !r.DestinationPrefix.Prefix().Contains(e.addr) {
+ continue
+ }
+ if ifrow == nil {
+ ifrow, err = r.InterfaceLUID.Interface()
+ if err != nil {
+ continue
+ }
+ }
+ if ifrow.OperStatus != winipcfg.IfOperStatusUp {
+ continue
+ }
+ if ifacerow == nil {
+ ifacerow, err = r.InterfaceLUID.IPInterface(family)
+ if err != nil {
+ continue
+ }
+ metric = r.Metric + ifacerow.Metric
+ }
+ if r.DestinationPrefix.PrefixLength == e.highestCIDR && metric > e.lowestMetric {
+ continue
+ }
+ e.lowestMetric = metric
+ e.highestCIDR = r.DestinationPrefix.PrefixLength
+ e.finalIsOurs = r.InterfaceLUID == ourLUID
+ if !e.finalIsOurs {
+ e.name = ifrow.Alias()
+ e.weakHostSend = ifacerow.ForwardingEnabled || ifacerow.WeakHostSend
+ }
+ }
+ }
+ problematicInterfaces := make(map[string]bool, len(endpoints))
+ for _, e := range endpoints {
+ if e.weakHostSend && e.finalIsOurs {
+ problematicInterfaces[e.name] = true
+ }
+ }
+ for iface := range problematicInterfaces {
+ log.Printf("Warning: the %q interface has Forwarding/WeakHostSend enabled, which will cause routing loops", iface)
+ }
+}
diff --git a/tunnel/scriptrunner.go b/tunnel/scriptrunner.go
new file mode 100644
index 00000000..eb97d98d
--- /dev/null
+++ b/tunnel/scriptrunner.go
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package tunnel
+
+import (
+ "bufio"
+ "fmt"
+ "log"
+ "os"
+ "path/filepath"
+ "syscall"
+
+ "golang.org/x/sys/windows"
+
+ "golang.zx2c4.com/wireguard/windows/conf"
+)
+
+func runScriptCommand(command, interfaceName string) error {
+ if len(command) == 0 {
+ return nil
+ }
+ if !conf.AdminBool("DangerousScriptExecution") {
+ log.Printf("Skipping execution of script, because dangerous script execution is safely disabled: %#q", command)
+ return nil
+ }
+ log.Printf("Executing: %#q", command)
+ comspec, _ := os.LookupEnv("COMSPEC")
+ if len(comspec) == 0 {
+ system32, err := windows.GetSystemDirectory()
+ if err != nil {
+ return err
+ }
+ comspec = filepath.Join(system32, "cmd.exe")
+ }
+
+ devNull, err := os.OpenFile(os.DevNull, os.O_RDWR, 0)
+ if err != nil {
+ return err
+ }
+ defer devNull.Close()
+ reader, writer, err := os.Pipe()
+ if err != nil {
+ return err
+ }
+ process, err := os.StartProcess(comspec, nil /* CmdLine below */, &os.ProcAttr{
+ Files: []*os.File{devNull, writer, writer},
+ Env: append(os.Environ(), "WIREGUARD_TUNNEL_NAME="+interfaceName),
+ Sys: &syscall.SysProcAttr{
+ HideWindow: true,
+ CmdLine: fmt.Sprintf("cmd /c %s", command),
+ },
+ })
+ writer.Close()
+ if err != nil {
+ reader.Close()
+ return err
+ }
+ go func() {
+ scanner := bufio.NewScanner(reader)
+ for scanner.Scan() {
+ log.Printf("cmd> %s", scanner.Text())
+ }
+ }()
+ state, err := process.Wait()
+ reader.Close()
+ if err != nil {
+ return err
+ }
+ if state.ExitCode() == 0 {
+ return nil
+ }
+ log.Printf("Command error exit status: %d", state.ExitCode())
+ return windows.ERROR_GENERIC_COMMAND_FAILED
+}
diff --git a/tunnel/service.go b/tunnel/service.go
index 8bd981d6..a56ed1f3 100644
--- a/tunnel/service.go
+++ b/tunnel/service.go
@@ -1,33 +1,27 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package tunnel
import (
- "bufio"
"bytes"
"fmt"
"log"
- "net"
"os"
"runtime"
- "runtime/debug"
- "strings"
"time"
+ "golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/mgr"
- "golang.zx2c4.com/wireguard/device"
- "golang.zx2c4.com/wireguard/ipc"
- "golang.zx2c4.com/wireguard/tun"
-
"golang.zx2c4.com/wireguard/windows/conf"
+ "golang.zx2c4.com/wireguard/windows/driver"
"golang.zx2c4.com/wireguard/windows/elevate"
"golang.zx2c4.com/wireguard/windows/ringlogger"
"golang.zx2c4.com/wireguard/windows/services"
- "golang.zx2c4.com/wireguard/windows/version"
+ "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
)
type tunnelService struct {
@@ -35,12 +29,13 @@ type tunnelService struct {
}
func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) {
- changes <- svc.Status{State: svc.StartPending}
+ serviceState := svc.StartPending
+ changes <- svc.Status{State: serviceState}
- var dev *device.Device
- var uapi net.Listener
var watcher *interfaceWatcher
- var nativeTun *tun.NativeTun
+ var adapter *driver.Adapter
+ var luid winipcfg.LUID
+ var config *conf.Config
var err error
serviceError := services.ErrorSuccess
@@ -50,7 +45,8 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest,
if logErr != nil {
log.Println(logErr)
}
- changes <- svc.Status{State: svc.StopPending}
+ serviceState = svc.StopPending
+ changes <- svc.Status{State: serviceState}
stopIt := make(chan bool, 1)
go func() {
@@ -84,64 +80,63 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest,
}
}()
+ if logErr == nil && adapter != nil && config != nil {
+ logErr = runScriptCommand(config.Interface.PreDown, config.Name)
+ }
if watcher != nil {
watcher.Destroy()
}
- if uapi != nil {
- uapi.Close()
+ if adapter != nil {
+ adapter.Close()
}
- if dev != nil {
- dev.Close()
+ if logErr == nil && adapter != nil && config != nil {
+ _ = runScriptCommand(config.Interface.PostDown, config.Name)
}
stopIt <- true
log.Println("Shutting down")
}()
- err = ringlogger.InitGlobalLogger("TUN")
+ var logFile string
+ logFile, err = conf.LogFile(true)
if err != nil {
serviceError = services.ErrorRingloggerOpen
return
}
- defer func() {
- if x := recover(); x != nil {
- for _, line := range append([]string{fmt.Sprint(x)}, strings.Split(string(debug.Stack()), "\n")...) {
- if len(strings.TrimSpace(line)) > 0 {
- log.Println(line)
- }
- }
- panic(x)
- }
- }()
-
- conf, err := conf.LoadFromPath(service.Path)
+ err = ringlogger.InitGlobalLogger(logFile, "TUN")
if err != nil {
- serviceError = services.ErrorLoadConfiguration
+ serviceError = services.ErrorRingloggerOpen
return
}
- err = CopyConfigOwnerToIPCSecurityDescriptor(service.Path)
+
+ config, err = conf.LoadFromPath(service.Path)
if err != nil {
serviceError = services.ErrorLoadConfiguration
return
}
+ config.DeduplicateNetworkEntries()
- logPrefix := fmt.Sprintf("[%s] ", conf.Name)
- log.SetPrefix(logPrefix)
+ log.SetPrefix(fmt.Sprintf("[%s] ", config.Name))
- log.Println("Starting", version.UserAgent())
+ services.PrintStarting()
- if m, err := mgr.Connect(); err == nil {
- if lockStatus, err := m.LockStatus(); err == nil && lockStatus.IsLocked {
- /* If we don't do this, then the Wintun installation will block forever, because
- * installing a Wintun device starts a service too. Apparently at boot time, Windows
- * 8.1 locks the SCM for each service start, creating a deadlock if we don't announce
- * that we're running before starting additional services.
- */
- log.Printf("SCM locked for %v by %s, marking service as started", lockStatus.Age, lockStatus.Owner)
- changes <- svc.Status{State: svc.Running}
+ if services.StartedAtBoot() {
+ if m, err := mgr.Connect(); err == nil {
+ if lockStatus, err := m.LockStatus(); err == nil && lockStatus.IsLocked {
+ /* If we don't do this, then the driver installation will block forever, because
+ * installing a network adapter starts the driver service too. Apparently at boot time,
+ * Windows 8.1 locks the SCM for each service start, creating a deadlock if we don't
+ * announce that we're running before starting additional services.
+ */
+ log.Printf("SCM locked for %v by %s, marking service as started", lockStatus.Age, lockStatus.Owner)
+ serviceState = svc.Running
+ changes <- svc.Status{State: serviceState}
+ }
+ m.Disconnect()
}
- m.Disconnect()
}
+ evaluateStaticPitfalls()
+
log.Println("Watching network interfaces")
watcher, err = watchInterface()
if err != nil {
@@ -150,28 +145,49 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest,
}
log.Println("Resolving DNS names")
- uapiConf, err := conf.ToUAPI()
+ err = config.ResolveEndpoints()
if err != nil {
serviceError = services.ErrorDNSLookup
return
}
- log.Println("Creating Wintun interface")
- wintun, err := tun.CreateTUNWithRequestedGUID(conf.Name, deterministicGUID(conf), 0)
+ log.Println("Creating network adapter")
+ for i := 0; i < 15; i++ {
+ if i > 0 {
+ time.Sleep(time.Second)
+ log.Printf("Retrying adapter creation after failure because system just booted (T+%v): %v", windows.DurationSinceBoot(), err)
+ }
+ adapter, err = driver.CreateAdapter(config.Name, "WireGuard", deterministicGUID(config))
+ if err == nil || !services.StartedAtBoot() {
+ break
+ }
+ }
if err != nil {
- serviceError = services.ErrorCreateWintun
+ err = fmt.Errorf("Error creating adapter: %w", err)
+ serviceError = services.ErrorCreateNetworkAdapter
return
}
- nativeTun = wintun.(*tun.NativeTun)
- wintunVersion, ndisVersion, err := nativeTun.Version()
+ luid = adapter.LUID()
+ driverVersion, err := driver.RunningVersion()
if err != nil {
- log.Printf("Warning: unable to determine Wintun version: %v", err)
+ log.Printf("Warning: unable to determine driver version: %v", err)
} else {
- log.Printf("Using Wintun/%s (NDIS %s)", wintunVersion, ndisVersion)
+ log.Printf("Using WireGuardNT/%d.%d", (driverVersion>>16)&0xffff, driverVersion&0xffff)
+ }
+ err = adapter.SetLogging(driver.AdapterLogOn)
+ if err != nil {
+ err = fmt.Errorf("Error enabling adapter logging: %w", err)
+ serviceError = services.ErrorCreateNetworkAdapter
+ return
}
- log.Println("Enabling firewall rules")
- err = enableFirewall(conf, nativeTun)
+ err = runScriptCommand(config.Interface.PreUp, config.Name)
+ if err != nil {
+ serviceError = services.ErrorRunScript
+ return
+ }
+
+ err = enableFirewall(config, luid)
if err != nil {
serviceError = services.ErrorFirewall
return
@@ -184,43 +200,28 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest,
return
}
- log.Println("Creating interface instance")
- logOutput := log.New(ringlogger.Global, logPrefix, 0)
- logger := &device.Logger{logOutput, logOutput, logOutput}
- dev = device.NewDevice(wintun, logger)
-
log.Println("Setting interface configuration")
- uapi, err = ipc.UAPIListen(conf.Name)
+ err = adapter.SetConfiguration(config.ToDriverConfiguration())
if err != nil {
- serviceError = services.ErrorUAPIListen
+ serviceError = services.ErrorDeviceSetConfig
return
}
- ipcErr := dev.IpcSetOperation(bufio.NewReader(strings.NewReader(uapiConf)))
- if ipcErr != nil {
- err = ipcErr
- serviceError = services.ErrorDeviceSetConfig
+ err = adapter.SetAdapterState(driver.AdapterStateUp)
+ if err != nil {
+ serviceError = services.ErrorDeviceBringUp
return
}
+ watcher.Configure(adapter, config, luid)
- log.Println("Bringing peers up")
- dev.Up()
-
- watcher.Configure(dev, conf, nativeTun)
-
- log.Println("Listening for UAPI requests")
- go func() {
- for {
- conn, err := uapi.Accept()
- if err != nil {
- continue
- }
- go dev.IpcHandle(conn)
- }
- }()
+ err = runScriptCommand(config.Interface.PostUp, config.Name)
+ if err != nil {
+ serviceError = services.ErrorRunScript
+ return
+ }
- changes <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}
- log.Println("Startup complete")
+ changes <- svc.Status{State: serviceState, Accepts: svc.AcceptStop | svc.AcceptShutdown}
+ var started bool
for {
select {
case c := <-r:
@@ -232,8 +233,13 @@ func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest,
default:
log.Printf("Unexpected service control request #%d\n", c)
}
- case <-dev.Wait():
- return
+ case <-watcher.started:
+ if !started {
+ serviceState = svc.Running
+ changes <- svc.Status{State: serviceState, Accepts: svc.AcceptStop | svc.AcceptShutdown}
+ log.Println("Startup complete")
+ started = true
+ }
case e := <-watcher.errors:
serviceError, err = e.serviceError, e.err
return
@@ -246,7 +252,7 @@ func Run(confPath string) error {
if err != nil {
return err
}
- serviceName, err := services.ServiceNameOfTunnel(name)
+ serviceName, err := conf.ServiceNameOfTunnel(name)
if err != nil {
return err
}
diff --git a/tunnel/winipcfg/interface_change_handler.go b/tunnel/winipcfg/interface_change_handler.go
index 9406c18a..af29801a 100644
--- a/tunnel/winipcfg/interface_change_handler.go
+++ b/tunnel/winipcfg/interface_change_handler.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package winipcfg
diff --git a/tunnel/winipcfg/luid.go b/tunnel/winipcfg/luid.go
index 396fbbb2..0c898b89 100644
--- a/tunnel/winipcfg/luid.go
+++ b/tunnel/winipcfg/luid.go
@@ -1,13 +1,14 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package winipcfg
import (
- "fmt"
- "net"
+ "errors"
+ "net/netip"
+ "strings"
"golang.org/x/sys/windows"
)
@@ -62,12 +63,23 @@ func LUIDFromGUID(guid *windows.GUID) (LUID, error) {
return luid, nil
}
+// LUIDFromIndex function converts a local index for a network interface to the locally unique identifier (LUID) for the interface.
+// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-convertinterfaceindextoluid
+func LUIDFromIndex(index uint32) (LUID, error) {
+ var luid LUID
+ err := convertInterfaceIndexToLUID(index, &luid)
+ if err != nil {
+ return 0, err
+ }
+ return luid, nil
+}
+
// IPAddress method returns MibUnicastIPAddressRow struct that matches to provided 'ip' argument. Corresponds to GetUnicastIpAddressEntry
// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getunicastipaddressentry)
-func (luid LUID) IPAddress(ip net.IP) (*MibUnicastIPAddressRow, error) {
+func (luid LUID) IPAddress(addr netip.Addr) (*MibUnicastIPAddressRow, error) {
row := &MibUnicastIPAddressRow{InterfaceLUID: luid}
- err := row.Address.SetIP(ip, 0)
+ err := row.Address.SetAddr(addr)
if err != nil {
return nil, err
}
@@ -82,22 +94,24 @@ func (luid LUID) IPAddress(ip net.IP) (*MibUnicastIPAddressRow, error) {
// AddIPAddress method adds new unicast IP address to the interface. Corresponds to CreateUnicastIpAddressEntry function
// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-createunicastipaddressentry).
-func (luid LUID) AddIPAddress(address net.IPNet) error {
+func (luid LUID) AddIPAddress(address netip.Prefix) error {
row := &MibUnicastIPAddressRow{}
row.Init()
row.InterfaceLUID = luid
- err := row.Address.SetIP(address.IP, 0)
+ row.DadState = DadStatePreferred
+ row.ValidLifetime = 0xffffffff
+ row.PreferredLifetime = 0xffffffff
+ err := row.Address.SetAddr(address.Addr())
if err != nil {
return err
}
- ones, _ := address.Mask.Size()
- row.OnLinkPrefixLength = uint8(ones)
+ row.OnLinkPrefixLength = uint8(address.Bits())
return row.Create()
}
// AddIPAddresses method adds multiple new unicast IP addresses to the interface. Corresponds to CreateUnicastIpAddressEntry function
// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-createunicastipaddressentry).
-func (luid LUID) AddIPAddresses(addresses []net.IPNet) error {
+func (luid LUID) AddIPAddresses(addresses []netip.Prefix) error {
for i := range addresses {
err := luid.AddIPAddress(addresses[i])
if err != nil {
@@ -108,7 +122,7 @@ func (luid LUID) AddIPAddresses(addresses []net.IPNet) error {
}
// SetIPAddresses method sets new unicast IP addresses to the interface.
-func (luid LUID) SetIPAddresses(addresses []net.IPNet) error {
+func (luid LUID) SetIPAddresses(addresses []netip.Prefix) error {
err := luid.FlushIPAddresses(windows.AF_UNSPEC)
if err != nil {
return err
@@ -117,16 +131,15 @@ func (luid LUID) SetIPAddresses(addresses []net.IPNet) error {
}
// SetIPAddressesForFamily method sets new unicast IP addresses for a specific family to the interface.
-func (luid LUID) SetIPAddressesForFamily(family AddressFamily, addresses []net.IPNet) error {
+func (luid LUID) SetIPAddressesForFamily(family AddressFamily, addresses []netip.Prefix) error {
err := luid.FlushIPAddresses(family)
if err != nil {
return err
}
for i := range addresses {
- asV4 := addresses[i].IP.To4()
- if asV4 == nil && family == windows.AF_INET {
+ if !addresses[i].Addr().Is4() && family == windows.AF_INET {
continue
- } else if asV4 != nil && family == windows.AF_INET6 {
+ } else if !addresses[i].Addr().Is6() && family == windows.AF_INET6 {
continue
}
err := luid.AddIPAddress(addresses[i])
@@ -139,17 +152,16 @@ func (luid LUID) SetIPAddressesForFamily(family AddressFamily, addresses []net.I
// DeleteIPAddress method deletes interface's unicast IP address. Corresponds to DeleteUnicastIpAddressEntry function
// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-deleteunicastipaddressentry).
-func (luid LUID) DeleteIPAddress(address net.IPNet) error {
+func (luid LUID) DeleteIPAddress(address netip.Prefix) error {
row := &MibUnicastIPAddressRow{}
row.Init()
row.InterfaceLUID = luid
- err := row.Address.SetIP(address.IP, 0)
+ err := row.Address.SetAddr(address.Addr())
if err != nil {
return err
}
// Note: OnLinkPrefixLength member is ignored by DeleteUnicastIpAddressEntry().
- ones, _ := address.Mask.Size()
- row.OnLinkPrefixLength = uint8(ones)
+ row.OnLinkPrefixLength = uint8(address.Bits())
return row.Delete()
}
@@ -173,15 +185,17 @@ func (luid LUID) FlushIPAddresses(family AddressFamily) error {
// Route method returns route determined with the input arguments. Corresponds to GetIpForwardEntry2 function
// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getipforwardentry2).
// NOTE: If the corresponding route isn't found, the method will return error.
-func (luid LUID) Route(destination net.IPNet, nextHop net.IP) (*MibIPforwardRow2, error) {
+func (luid LUID) Route(destination netip.Prefix, nextHop netip.Addr) (*MibIPforwardRow2, error) {
row := &MibIPforwardRow2{}
row.Init()
row.InterfaceLUID = luid
- err := row.DestinationPrefix.SetIPNet(destination)
+ row.ValidLifetime = 0xffffffff
+ row.PreferredLifetime = 0xffffffff
+ err := row.DestinationPrefix.SetPrefix(destination)
if err != nil {
return nil, err
}
- err = row.NextHop.SetIP(nextHop, 0)
+ err = row.NextHop.SetAddr(nextHop)
if err != nil {
return nil, err
}
@@ -195,15 +209,15 @@ func (luid LUID) Route(destination net.IPNet, nextHop net.IP) (*MibIPforwardRow2
// AddRoute method adds a route to the interface. Corresponds to CreateIpForwardEntry2 function, with added splitDefault feature.
// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-createipforwardentry2)
-func (luid LUID) AddRoute(destination net.IPNet, nextHop net.IP, metric uint32) error {
+func (luid LUID) AddRoute(destination netip.Prefix, nextHop netip.Addr, metric uint32) error {
row := &MibIPforwardRow2{}
row.Init()
row.InterfaceLUID = luid
- err := row.DestinationPrefix.SetIPNet(destination)
+ err := row.DestinationPrefix.SetPrefix(destination)
if err != nil {
return err
}
- err = row.NextHop.SetIP(nextHop, 0)
+ err = row.NextHop.SetAddr(nextHop)
if err != nil {
return err
}
@@ -238,10 +252,9 @@ func (luid LUID) SetRoutesForFamily(family AddressFamily, routesData []*RouteDat
return err
}
for _, rd := range routesData {
- asV4 := rd.Destination.IP.To4()
- if asV4 == nil && family == windows.AF_INET {
+ if !rd.Destination.Addr().Is4() && family == windows.AF_INET {
continue
- } else if asV4 != nil && family == windows.AF_INET6 {
+ } else if !rd.Destination.Addr().Is6() && family == windows.AF_INET6 {
continue
}
err := luid.AddRoute(rd.Destination, rd.NextHop, rd.Metric)
@@ -254,15 +267,15 @@ func (luid LUID) SetRoutesForFamily(family AddressFamily, routesData []*RouteDat
// DeleteRoute method deletes a route that matches the criteria. Corresponds to DeleteIpForwardEntry2 function
// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-deleteipforwardentry2).
-func (luid LUID) DeleteRoute(destination net.IPNet, nextHop net.IP) error {
+func (luid LUID) DeleteRoute(destination netip.Prefix, nextHop netip.Addr) error {
row := &MibIPforwardRow2{}
row.Init()
row.InterfaceLUID = luid
- err := row.DestinationPrefix.SetIPNet(destination)
+ err := row.DestinationPrefix.SetPrefix(destination)
if err != nil {
return err
}
- err = row.NextHop.SetIP(nextHop, 0)
+ err = row.NextHop.SetAddr(nextHop)
if err != nil {
return err
}
@@ -295,17 +308,19 @@ func (luid LUID) FlushRoutes(family AddressFamily) error {
}
// DNS method returns all DNS server addresses associated with the adapter.
-func (luid LUID) DNS() ([]net.IP, error) {
+func (luid LUID) DNS() ([]netip.Addr, error) {
addresses, err := GetAdaptersAddresses(windows.AF_UNSPEC, GAAFlagDefault)
if err != nil {
return nil, err
}
- r := make([]net.IP, 0, len(addresses))
+ r := make([]netip.Addr, 0, len(addresses))
for _, addr := range addresses {
if addr.LUID == luid {
for dns := addr.FirstDNSServerAddress; dns != nil; dns = dns.Next {
if ip := dns.Address.IP(); ip != nil {
- r = append(r, ip)
+ if a, ok := netip.AddrFromSlice(ip); ok {
+ r = append(r, a)
+ }
} else {
return nil, windows.ERROR_INVALID_PARAMETER
}
@@ -315,114 +330,58 @@ func (luid LUID) DNS() ([]net.IP, error) {
return r, nil
}
-const (
- netshCmdTemplateFlush4 = "interface ipv4 set dnsservers name=%d source=static address=none validate=no register=both"
- netshCmdTemplateFlush6 = "interface ipv6 set dnsservers name=%d source=static address=none validate=no register=both"
- netshCmdTemplateAdd4 = "interface ipv4 add dnsservers name=%d address=%s validate=no"
- netshCmdTemplateAdd6 = "interface ipv6 add dnsservers name=%d address=%s validate=no"
-)
-
-// FlushDNS method clears all DNS servers associated with the adapter.
-func (luid LUID) FlushDNS() error {
- cmds := make([]string, 0, 2)
- ipif4, err := luid.IPInterface(windows.AF_INET)
- if err == nil {
- cmds = append(cmds, fmt.Sprintf(netshCmdTemplateFlush4, ipif4.InterfaceIndex))
- }
- ipif6, err := luid.IPInterface(windows.AF_INET6)
- if err == nil {
- cmds = append(cmds, fmt.Sprintf(netshCmdTemplateFlush6, ipif6.InterfaceIndex))
- }
-
- if len(cmds) == 0 {
- return nil
+// SetDNS method clears previous and associates new DNS servers and search domains with the adapter for a specific family.
+func (luid LUID) SetDNS(family AddressFamily, servers []netip.Addr, domains []string) error {
+ if family != windows.AF_INET && family != windows.AF_INET6 {
+ return windows.ERROR_PROTOCOL_UNREACHABLE
}
- return runNetsh(cmds)
-}
-// AddDNS method associates additional DNS servers with the adapter.
-func (luid LUID) AddDNS(dnses []net.IP) error {
- var ipif4, ipif6 *MibIPInterfaceRow
- var err error
- cmds := make([]string, 0, len(dnses))
- for i := 0; i < len(dnses); i++ {
- if v4 := dnses[i].To4(); v4 != nil {
- if ipif4 == nil {
- ipif4, err = luid.IPInterface(windows.AF_INET)
- if err != nil {
- return err
- }
- }
- cmds = append(cmds, fmt.Sprintf(netshCmdTemplateAdd4, ipif4.InterfaceIndex, v4.String()))
- } else if v6 := dnses[i].To16(); v6 != nil {
- if ipif6 == nil {
- ipif6, err = luid.IPInterface(windows.AF_INET6)
- if err != nil {
- return err
- }
- }
- cmds = append(cmds, fmt.Sprintf(netshCmdTemplateAdd6, ipif6.InterfaceIndex, v6.String()))
+ var filteredServers []string
+ for _, server := range servers {
+ if (server.Is4() && family == windows.AF_INET) || (server.Is6() && family == windows.AF_INET6) {
+ filteredServers = append(filteredServers, server.String())
}
}
-
- if len(cmds) == 0 {
- return nil
+ servers16, err := windows.UTF16PtrFromString(strings.Join(filteredServers, ","))
+ if err != nil {
+ return err
}
- return runNetsh(cmds)
-}
-
-// SetDNS method clears previous and associates new DNS servers with the adapter.
-func (luid LUID) SetDNS(dnses []net.IP) error {
- cmds := make([]string, 0, 2+len(dnses))
- ipif4, err := luid.IPInterface(windows.AF_INET)
- if err == nil {
- cmds = append(cmds, fmt.Sprintf(netshCmdTemplateFlush4, ipif4.InterfaceIndex))
- }
- ipif6, err := luid.IPInterface(windows.AF_INET6)
- if err == nil {
- cmds = append(cmds, fmt.Sprintf(netshCmdTemplateFlush6, ipif6.InterfaceIndex))
- }
- for i := 0; i < len(dnses); i++ {
- if v4 := dnses[i].To4(); v4 != nil {
- if ipif4 == nil {
- return windows.ERROR_NOT_SUPPORTED
- }
- cmds = append(cmds, fmt.Sprintf(netshCmdTemplateAdd4, ipif4.InterfaceIndex, v4.String()))
- } else if v6 := dnses[i].To16(); v6 != nil {
- if ipif6 == nil {
- return windows.ERROR_NOT_SUPPORTED
- }
- cmds = append(cmds, fmt.Sprintf(netshCmdTemplateAdd6, ipif6.InterfaceIndex, v6.String()))
- }
+ domains16, err := windows.UTF16PtrFromString(strings.Join(domains, ","))
+ if err != nil {
+ return err
}
-
- if len(cmds) == 0 {
- return nil
+ guid, err := luid.GUID()
+ if err != nil {
+ return err
}
- return runNetsh(cmds)
-}
-
-// SetDNSForFamily method clears previous and associates new DNS servers with the adapter for a specific family.
-func (luid LUID) SetDNSForFamily(family AddressFamily, dnses []net.IP) error {
- var templateFlush string
- if family == windows.AF_INET {
- templateFlush = netshCmdTemplateFlush4
- } else if family == windows.AF_INET6 {
- templateFlush = netshCmdTemplateFlush6
+ dnsInterfaceSettings := &DnsInterfaceSettings{
+ Version: DnsInterfaceSettingsVersion1,
+ Flags: DnsInterfaceSettingsFlagNameserver | DnsInterfaceSettingsFlagSearchList,
+ NameServer: servers16,
+ SearchList: domains16,
+ }
+ if family == windows.AF_INET6 {
+ dnsInterfaceSettings.Flags |= DnsInterfaceSettingsFlagIPv6
+ }
+ // For >= Windows 10 1809
+ err = SetInterfaceDnsSettings(*guid, dnsInterfaceSettings)
+ if err == nil || !errors.Is(err, windows.ERROR_PROC_NOT_FOUND) {
+ return err
}
- cmds := make([]string, 0, 1+len(dnses))
- ipif, err := luid.IPInterface(family)
+ // For < Windows 10 1809
+ err = luid.fallbackSetDNSForFamily(family, servers)
if err != nil {
return err
}
- cmds = append(cmds, fmt.Sprintf(templateFlush, ipif.InterfaceIndex))
- for i := 0; i < len(dnses); i++ {
- if v4 := dnses[i].To4(); v4 != nil && family == windows.AF_INET {
- cmds = append(cmds, fmt.Sprintf(netshCmdTemplateAdd4, ipif.InterfaceIndex, v4.String()))
- } else if v6 := dnses[i].To16(); v4 == nil && v6 != nil && family == windows.AF_INET6 {
- cmds = append(cmds, fmt.Sprintf(netshCmdTemplateAdd6, ipif.InterfaceIndex, v6.String()))
- }
+ if len(domains) > 0 {
+ return luid.fallbackSetDNSDomain(domains[0])
+ } else {
+ return luid.fallbackSetDNSDomain("")
}
- return runNetsh(cmds)
+}
+
+// FlushDNS method clears all DNS servers associated with the adapter.
+func (luid LUID) FlushDNS(family AddressFamily) error {
+ return luid.SetDNS(family, nil, nil)
}
diff --git a/tunnel/winipcfg/mksyscall.go b/tunnel/winipcfg/mksyscall.go
index 8edb1cf2..d62d38df 100644
--- a/tunnel/winipcfg/mksyscall.go
+++ b/tunnel/winipcfg/mksyscall.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package winipcfg
diff --git a/tunnel/winipcfg/netsh.go b/tunnel/winipcfg/netsh.go
index 4714c520..4f8e5b13 100644
--- a/tunnel/winipcfg/netsh.go
+++ b/tunnel/winipcfg/netsh.go
@@ -1,49 +1,108 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package winipcfg
import (
"bytes"
+ "errors"
"fmt"
"io"
+ "net/netip"
"os/exec"
"path/filepath"
"strings"
+ "syscall"
"golang.org/x/sys/windows"
+ "golang.org/x/sys/windows/registry"
)
-// I wish we didn't have to do this. netiohlp.dll (what's used by netsh.exe) has some nice tricks with writing directly
-// to the registry and the nsi kernel object, but it's not clear copying those makes for a stable interface. WMI doesn't
-// work with v6. CMI isn't in Windows 7.
func runNetsh(cmds []string) error {
system32, err := windows.GetSystemDirectory()
if err != nil {
return err
}
- cmd := exec.Command(filepath.Join(system32, "netsh.exe")) // I wish we could append (, "-f", "CONIN$") but Go sets up the process context wrong.
+ cmd := exec.Command(filepath.Join(system32, "netsh.exe"))
+ cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
+
stdin, err := cmd.StdinPipe()
if err != nil {
- return fmt.Errorf("runNetsh stdin pipe - %v", err)
+ return fmt.Errorf("runNetsh stdin pipe - %w", err)
}
go func() {
defer stdin.Close()
io.WriteString(stdin, strings.Join(append(cmds, "exit\r\n"), "\r\n"))
}()
output, err := cmd.CombinedOutput()
- if err != nil {
- return fmt.Errorf("runNetsh run - %v", err)
- }
// Horrible kludges, sorry.
- cleaned := bytes.ReplaceAll(output, []byte("netsh>"), []byte{})
+ cleaned := bytes.ReplaceAll(output, []byte{'\r', '\n'}, []byte{'\n'})
+ cleaned = bytes.ReplaceAll(cleaned, []byte("netsh>"), []byte{})
cleaned = bytes.ReplaceAll(cleaned, []byte("There are no Domain Name Servers (DNS) configured on this computer."), []byte{})
cleaned = bytes.TrimSpace(cleaned)
- if len(cleaned) != 0 {
- return fmt.Errorf("runNetsh returned error strings.\ninput:\n%s\noutput\n:%s",
- strings.Join(cmds, "\n"), bytes.ReplaceAll(output, []byte{'\r', '\n'}, []byte{'\n'}))
+ if len(cleaned) != 0 && err == nil {
+ return fmt.Errorf("netsh: %#q", string(cleaned))
+ } else if err != nil {
+ return fmt.Errorf("netsh: %v: %#q", err, string(cleaned))
}
return nil
}
+
+const (
+ netshCmdTemplateFlush4 = "interface ipv4 set dnsservers name=%d source=static address=none validate=no register=both"
+ netshCmdTemplateFlush6 = "interface ipv6 set dnsservers name=%d source=static address=none validate=no register=both"
+ netshCmdTemplateAdd4 = "interface ipv4 add dnsservers name=%d address=%s validate=no"
+ netshCmdTemplateAdd6 = "interface ipv6 add dnsservers name=%d address=%s validate=no"
+)
+
+func (luid LUID) fallbackSetDNSForFamily(family AddressFamily, dnses []netip.Addr) error {
+ var templateFlush string
+ if family == windows.AF_INET {
+ templateFlush = netshCmdTemplateFlush4
+ } else if family == windows.AF_INET6 {
+ templateFlush = netshCmdTemplateFlush6
+ }
+
+ cmds := make([]string, 0, 1+len(dnses))
+ ipif, err := luid.IPInterface(family)
+ if err != nil {
+ return err
+ }
+ cmds = append(cmds, fmt.Sprintf(templateFlush, ipif.InterfaceIndex))
+ for i := 0; i < len(dnses); i++ {
+ if dnses[i].Is4() && family == windows.AF_INET {
+ cmds = append(cmds, fmt.Sprintf(netshCmdTemplateAdd4, ipif.InterfaceIndex, dnses[i].String()))
+ } else if dnses[i].Is6() && family == windows.AF_INET6 {
+ cmds = append(cmds, fmt.Sprintf(netshCmdTemplateAdd6, ipif.InterfaceIndex, dnses[i].String()))
+ }
+ }
+ return runNetsh(cmds)
+}
+
+func (luid LUID) fallbackSetDNSDomain(domain string) error {
+ guid, err := luid.GUID()
+ if err != nil {
+ return fmt.Errorf("Error converting luid to guid: %w", err)
+ }
+ key, err := registry.OpenKey(registry.LOCAL_MACHINE, fmt.Sprintf("SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Adapters\\%v", guid), registry.QUERY_VALUE)
+ if err != nil {
+ return fmt.Errorf("Error opening adapter-specific TCP/IP network registry key: %w", err)
+ }
+ paths, _, err := key.GetStringsValue("IpConfig")
+ key.Close()
+ if err != nil {
+ return fmt.Errorf("Error reading IpConfig registry key: %w", err)
+ }
+ if len(paths) == 0 {
+ return errors.New("No TCP/IP interfaces found on adapter")
+ }
+ key, err = registry.OpenKey(registry.LOCAL_MACHINE, fmt.Sprintf("SYSTEM\\CurrentControlSet\\Services\\%s", paths[0]), registry.SET_VALUE)
+ if err != nil {
+ return fmt.Errorf("Unable to open TCP/IP network registry key: %w", err)
+ }
+ err = key.SetStringValue("Domain", domain)
+ key.Close()
+ return err
+}
diff --git a/tunnel/winipcfg/route_change_handler.go b/tunnel/winipcfg/route_change_handler.go
index 1b4bad95..4b78331e 100644
--- a/tunnel/winipcfg/route_change_handler.go
+++ b/tunnel/winipcfg/route_change_handler.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package winipcfg
diff --git a/tunnel/winipcfg/types.go b/tunnel/winipcfg/types.go
index 81f9335d..8e8f4a59 100644
--- a/tunnel/winipcfg/types.go
+++ b/tunnel/winipcfg/types.go
@@ -1,13 +1,15 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package winipcfg
import (
- "bytes"
- "net"
+ "encoding/binary"
+ "fmt"
+ "net/netip"
+ "strconv"
"unsafe"
"golang.org/x/sys/windows"
@@ -581,19 +583,17 @@ const (
ScopeLevelCount = 16
)
-// Theoretical array index limitations
-const (
- maxIndexCount8 = (1 << 31) - 1
- maxIndexCount16 = (1 << 30) - 1
-)
-
// RouteData structure describes a route to add
type RouteData struct {
- Destination net.IPNet
- NextHop net.IP
+ Destination netip.Prefix
+ NextHop netip.Addr
Metric uint32
}
+func (routeData *RouteData) String() string {
+ return fmt.Sprintf("%+v", *routeData)
+}
+
// IPAdapterDNSSuffix structure stores a DNS suffix in a linked list of DNS suffixes for a particular adapter.
// https://docs.microsoft.com/en-us/windows/desktop/api/iptypes/ns-iptypes-_ip_adapter_dns_suffix
type IPAdapterDNSSuffix struct {
@@ -609,15 +609,7 @@ func (obj *IPAdapterDNSSuffix) String() string {
// AdapterName method returns the name of the adapter with which these addresses are associated.
// Unlike an adapter's friendly name, the adapter name returned by AdapterName is permanent and cannot be modified by the user.
func (addr *IPAdapterAddresses) AdapterName() string {
- if addr.adapterName == nil {
- return ""
- }
- slice := (*(*[maxIndexCount8]uint8)(unsafe.Pointer(addr.adapterName)))[:]
- null := bytes.IndexByte(slice, 0)
- if null != -1 {
- slice = slice[:null]
- }
- return string(slice)
+ return windows.BytePtrToString(addr.adapterName)
}
// DNSSuffix method returns adapter DNS suffix associated with this adapter.
@@ -625,7 +617,7 @@ func (addr *IPAdapterAddresses) DNSSuffix() string {
if addr.dnsSuffix == nil {
return ""
}
- return windows.UTF16ToString((*(*[maxIndexCount16]uint16)(unsafe.Pointer(addr.dnsSuffix)))[:])
+ return windows.UTF16PtrToString(addr.dnsSuffix)
}
// Description method returns description for the adapter.
@@ -633,7 +625,7 @@ func (addr *IPAdapterAddresses) Description() string {
if addr.description == nil {
return ""
}
- return windows.UTF16ToString((*(*[maxIndexCount16]uint16)(unsafe.Pointer(addr.description)))[:])
+ return windows.UTF16PtrToString(addr.description)
}
// FriendlyName method returns a user-friendly name for the adapter. For example: "Local Area Connection 1."
@@ -642,7 +634,7 @@ func (addr *IPAdapterAddresses) FriendlyName() string {
if addr.friendlyName == nil {
return ""
}
- return windows.UTF16ToString((*(*[maxIndexCount16]uint16)(unsafe.Pointer(addr.friendlyName)))[:])
+ return windows.UTF16PtrToString(addr.friendlyName)
}
// PhysicalAddress method returns the Media Access Control (MAC) address for the adapter.
@@ -693,9 +685,8 @@ func (row *MibIPInterfaceRow) Set() error {
}
// get method returns all table rows as a Go slice.
-func (tab *mibIPInterfaceTable) get() []MibIPInterfaceRow {
- const maxCount = maxIndexCount8 / unsafe.Sizeof(MibIPInterfaceRow{})
- return (*[maxCount]MibIPInterfaceRow)(unsafe.Pointer(&tab.table[0]))[:tab.numEntries]
+func (tab *mibIPInterfaceTable) get() (s []MibIPInterfaceRow) {
+ return unsafe.Slice(&tab.table[0], tab.numEntries)
}
// free method frees the buffer allocated by the functions that return tables of network interfaces, addresses, and routes.
@@ -731,9 +722,8 @@ func (row *MibIfRow2) get() (ret error) {
}
// get method returns all table rows as a Go slice.
-func (tab *mibIfTable2) get() []MibIfRow2 {
- const maxCount = maxIndexCount8 / unsafe.Sizeof(MibIfRow2{})
- return (*[maxCount]MibIfRow2)(unsafe.Pointer(&tab.table[0]))[:tab.numEntries]
+func (tab *mibIfTable2) get() (s []MibIfRow2) {
+ return unsafe.Slice(&tab.table[0], tab.numEntries)
}
// free method frees the buffer allocated by the functions that return tables of network interfaces, addresses, and routes.
@@ -749,45 +739,82 @@ type RawSockaddrInet struct {
data [26]byte
}
-// SetIP method sets family, address, and port to the given IPv4 or IPv6 address and port.
+func ntohs(i uint16) uint16 {
+ return binary.BigEndian.Uint16((*[2]byte)(unsafe.Pointer(&i))[:])
+}
+
+func htons(i uint16) uint16 {
+ b := make([]byte, 2)
+ binary.BigEndian.PutUint16(b, i)
+ return *(*uint16)(unsafe.Pointer(&b[0]))
+}
+
+// SetAddrPort method sets family, address, and port to the given IPv4 or IPv6 address and port.
// All other members of the structure are set to zero.
-func (addr *RawSockaddrInet) SetIP(ip net.IP, port uint16) error {
- if v4 := ip.To4(); v4 != nil {
+func (addr *RawSockaddrInet) SetAddrPort(addrPort netip.AddrPort) error {
+ if addrPort.Addr().Is4() {
addr4 := (*windows.RawSockaddrInet4)(unsafe.Pointer(addr))
addr4.Family = windows.AF_INET
- copy(addr4.Addr[:], v4)
- addr4.Port = port
+ addr4.Addr = addrPort.Addr().As4()
+ addr4.Port = htons(addrPort.Port())
for i := 0; i < 8; i++ {
addr4.Zero[i] = 0
}
return nil
- }
-
- if v6 := ip.To16(); v6 != nil {
+ } else if addrPort.Addr().Is6() {
addr6 := (*windows.RawSockaddrInet6)(unsafe.Pointer(addr))
addr6.Family = windows.AF_INET6
- addr6.Port = port
+ addr6.Addr = addrPort.Addr().As16()
+ addr6.Port = htons(addrPort.Port())
addr6.Flowinfo = 0
- copy(addr6.Addr[:], v6)
- addr6.Scope_id = 0
+ scopeId := uint32(0)
+ if z := addrPort.Addr().Zone(); z != "" {
+ if s, err := strconv.ParseUint(z, 10, 32); err == nil {
+ scopeId = uint32(s)
+ }
+ }
+ addr6.Scope_id = scopeId
return nil
}
-
return windows.ERROR_INVALID_PARAMETER
}
-// IP method returns IPv4 or IPv6 address.
-// If the address is neither IPv4 not IPv6 nil is returned.
-func (addr *RawSockaddrInet) IP() net.IP {
+// SetAddr method sets family and address to the given IPv4 or IPv6 address.
+// All other members of the structure are set to zero.
+func (addr *RawSockaddrInet) SetAddr(netAddr netip.Addr) error {
+ return addr.SetAddrPort(netip.AddrPortFrom(netAddr, 0))
+}
+
+// AddrPort returns the IP address and port.
+func (addr *RawSockaddrInet) AddrPort() netip.AddrPort {
+ return netip.AddrPortFrom(addr.Addr(), addr.Port())
+}
+
+// Addr returns IPv4 or IPv6 address, or an invalid address if the address is neither.
+func (addr *RawSockaddrInet) Addr() netip.Addr {
switch addr.Family {
case windows.AF_INET:
- return (*windows.RawSockaddrInet4)(unsafe.Pointer(addr)).Addr[:]
-
+ return netip.AddrFrom4((*windows.RawSockaddrInet4)(unsafe.Pointer(addr)).Addr)
case windows.AF_INET6:
- return (*windows.RawSockaddrInet6)(unsafe.Pointer(addr)).Addr[:]
+ raw := (*windows.RawSockaddrInet6)(unsafe.Pointer(addr))
+ a := netip.AddrFrom16(raw.Addr)
+ if raw.Scope_id != 0 {
+ a = a.WithZone(strconv.FormatUint(uint64(raw.Scope_id), 10))
+ }
+ return a
}
+ return netip.Addr{}
+}
- return nil
+// Port returns the port if the address if IPv4 or IPv6, or 0 if neither.
+func (addr *RawSockaddrInet) Port() uint16 {
+ switch addr.Family {
+ case windows.AF_INET:
+ return ntohs((*windows.RawSockaddrInet4)(unsafe.Pointer(addr)).Port)
+ case windows.AF_INET6:
+ return ntohs((*windows.RawSockaddrInet6)(unsafe.Pointer(addr)).Port)
+ }
+ return 0
}
// Init method initializes a MibUnicastIPAddressRow structure with default values for a unicast IP address entry on the local computer.
@@ -821,9 +848,8 @@ func (row *MibUnicastIPAddressRow) Delete() error {
}
// get method returns all table rows as a Go slice.
-func (tab *mibUnicastIPAddressTable) get() []MibUnicastIPAddressRow {
- const maxCount = maxIndexCount8 / unsafe.Sizeof(MibUnicastIPAddressRow{})
- return (*[maxCount]MibUnicastIPAddressRow)(unsafe.Pointer(&tab.table[0]))[:tab.numEntries]
+func (tab *mibUnicastIPAddressTable) get() (s []MibUnicastIPAddressRow) {
+ return unsafe.Slice(&tab.table[0], tab.numEntries)
}
// free method frees the buffer allocated by the functions that return tables of network interfaces, addresses, and routes.
@@ -851,9 +877,8 @@ func (row *MibAnycastIPAddressRow) Delete() error {
}
// get method returns all table rows as a Go slice.
-func (tab *mibAnycastIPAddressTable) get() []MibAnycastIPAddressRow {
- const maxCount = maxIndexCount8 / unsafe.Sizeof(MibAnycastIPAddressRow{})
- return (*[maxCount]MibAnycastIPAddressRow)(unsafe.Pointer(&tab.table[0]))[:tab.numEntries]
+func (tab *mibAnycastIPAddressTable) get() (s []MibAnycastIPAddressRow) {
+ return unsafe.Slice(&tab.table[0], tab.numEntries)
}
// free method frees the buffer allocated by the functions that return tables of network interfaces, addresses, and routes.
@@ -865,32 +890,30 @@ func (tab *mibAnycastIPAddressTable) free() {
// IPAddressPrefix structure stores an IP address prefix.
// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/ns-netioapi-_ip_address_prefix
type IPAddressPrefix struct {
- Prefix RawSockaddrInet
+ RawPrefix RawSockaddrInet
PrefixLength uint8
_ [2]byte
}
-// SetIPNet method sets IP address prefix using net.IPNet.
-func (prefix *IPAddressPrefix) SetIPNet(net net.IPNet) error {
- err := prefix.Prefix.SetIP(net.IP, 0)
+// SetPrefix method sets IP address prefix using netip.Prefix.
+func (prefix *IPAddressPrefix) SetPrefix(netPrefix netip.Prefix) error {
+ err := prefix.RawPrefix.SetAddr(netPrefix.Addr())
if err != nil {
return err
}
- ones, _ := net.Mask.Size()
- prefix.PrefixLength = uint8(ones)
+ prefix.PrefixLength = uint8(netPrefix.Bits())
return nil
}
-// IPNet method returns IP address prefix as net.IPNet.
-// If the address is neither IPv4 not IPv6 an empty net.IPNet is returned. The resulting net.IPNet should be checked appropriately.
-func (prefix *IPAddressPrefix) IPNet() net.IPNet {
- switch prefix.Prefix.Family {
+// Prefix returns IP address prefix as netip.Prefix.
+func (prefix *IPAddressPrefix) Prefix() netip.Prefix {
+ switch prefix.RawPrefix.Family {
case windows.AF_INET:
- return net.IPNet{IP: (*windows.RawSockaddrInet4)(unsafe.Pointer(&prefix.Prefix)).Addr[:], Mask: net.CIDRMask(int(prefix.PrefixLength), 8*net.IPv4len)}
+ return netip.PrefixFrom(netip.AddrFrom4((*windows.RawSockaddrInet4)(unsafe.Pointer(&prefix.RawPrefix)).Addr), int(prefix.PrefixLength))
case windows.AF_INET6:
- return net.IPNet{IP: (*windows.RawSockaddrInet6)(unsafe.Pointer(&prefix.Prefix)).Addr[:], Mask: net.CIDRMask(int(prefix.PrefixLength), 8*net.IPv6len)}
+ return netip.PrefixFrom(netip.AddrFrom16((*windows.RawSockaddrInet6)(unsafe.Pointer(&prefix.RawPrefix)).Addr), int(prefix.PrefixLength))
}
- return net.IPNet{}
+ return netip.Prefix{}
}
// MibIPforwardRow2 structure stores information about an IP route entry.
@@ -944,9 +967,8 @@ func (row *MibIPforwardRow2) Delete() error {
}
// get method returns all table rows as a Go slice.
-func (tab *mibIPforwardTable2) get() []MibIPforwardRow2 {
- const maxCount = maxIndexCount8 / unsafe.Sizeof(MibIPforwardRow2{})
- return (*[maxCount]MibIPforwardRow2)(unsafe.Pointer(&tab.table[0]))[:tab.numEntries]
+func (tab *mibIPforwardTable2) get() (s []MibIPforwardRow2) {
+ return unsafe.Slice(&tab.table[0], tab.numEntries)
}
// free method frees the buffer allocated by the functions that return tables of network interfaces, addresses, and routes.
@@ -954,3 +976,43 @@ func (tab *mibIPforwardTable2) get() []MibIPforwardRow2 {
func (tab *mibIPforwardTable2) free() {
freeMibTable(unsafe.Pointer(tab))
}
+
+//
+// DNS API
+//
+
+// DnsInterfaceSettings is meant to be used with SetInterfaceDnsSettings
+type DnsInterfaceSettings struct {
+ Version uint32
+ _ [4]byte
+ Flags uint64
+ Domain *uint16
+ NameServer *uint16
+ SearchList *uint16
+ RegistrationEnabled uint32
+ RegisterAdapterName uint32
+ EnableLLMNR uint32
+ QueryAdapterName uint32
+ ProfileNameServer *uint16
+}
+
+const (
+ DnsInterfaceSettingsVersion1 = 1 // for DnsInterfaceSettings
+ DnsInterfaceSettingsVersion2 = 2 // for DnsInterfaceSettingsEx
+ DnsInterfaceSettingsVersion3 = 3 // for DnsInterfaceSettings3
+
+ DnsInterfaceSettingsFlagIPv6 = 0x0001
+ DnsInterfaceSettingsFlagNameserver = 0x0002
+ DnsInterfaceSettingsFlagSearchList = 0x0004
+ DnsInterfaceSettingsFlagRegistrationEnabled = 0x0008
+ DnsInterfaceSettingsFlagRegisterAdapterName = 0x0010
+ DnsInterfaceSettingsFlagDomain = 0x0020
+ DnsInterfaceSettingsFlagHostname = 0x0040
+ DnsInterfaceSettingsFlagEnableLLMNR = 0x0080
+ DnsInterfaceSettingsFlagQueryAdapterName = 0x0100
+ DnsInterfaceSettingsFlagProfileNameserver = 0x0200
+ DnsInterfaceSettingsFlagDisableUnconstrainedQueries = 0x0400 // v2 only
+ DnsInterfaceSettingsFlagSupplementalSearchList = 0x0800 // v2 only
+ DnsInterfaceSettingsFlagDOH = 0x1000 // v3 only
+ DnsInterfaceSettingsFlagDOHProfile = 0x2000 // v3 only
+)
diff --git a/tunnel/winipcfg/types_386.go b/tunnel/winipcfg/types_32.go
index 3a4b5733..1a8d4443 100644
--- a/tunnel/winipcfg/types_386.go
+++ b/tunnel/winipcfg/types_32.go
@@ -1,6 +1,8 @@
+//go:build 386 || arm
+
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package winipcfg
diff --git a/tunnel/winipcfg/types_amd64.go b/tunnel/winipcfg/types_64.go
index 11242891..3a1fe07f 100644
--- a/tunnel/winipcfg/types_amd64.go
+++ b/tunnel/winipcfg/types_64.go
@@ -1,6 +1,8 @@
+//go:build amd64 || arm64
+
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package winipcfg
diff --git a/tunnel/winipcfg/types_test.go b/tunnel/winipcfg/types_test.go
index c7494e8c..b72d73f5 100644
--- a/tunnel/winipcfg/types_test.go
+++ b/tunnel/winipcfg/types_test.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package winipcfg
@@ -957,7 +957,6 @@ func TestIPAddressPrefix(t *testing.T) {
offset := uintptr(unsafe.Pointer(&s.PrefixLength)) - sp
if offset != ipAddressPrefixPrefixLengthOffset {
t.Errorf("IPAddressPrefix.PrefixLength offset is %d although %d is expected", offset, ipAddressPrefixPrefixLengthOffset)
-
}
}
diff --git a/tunnel/winipcfg/types_test_386.go b/tunnel/winipcfg/types_test_32.go
index db5f5f86..9e62bfef 100644
--- a/tunnel/winipcfg/types_test_386.go
+++ b/tunnel/winipcfg/types_test_32.go
@@ -1,6 +1,8 @@
+//go:build 386 || arm
+
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package winipcfg
diff --git a/tunnel/winipcfg/types_test_amd64.go b/tunnel/winipcfg/types_test_64.go
index acc74118..8a181575 100644
--- a/tunnel/winipcfg/types_test_amd64.go
+++ b/tunnel/winipcfg/types_test_64.go
@@ -1,6 +1,8 @@
+//go:build amd64 || arm64
+
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package winipcfg
diff --git a/tunnel/winipcfg/unicast_address_change_handler.go b/tunnel/winipcfg/unicast_address_change_handler.go
index 5f8f2c96..cf4fcb3a 100644
--- a/tunnel/winipcfg/unicast_address_change_handler.go
+++ b/tunnel/winipcfg/unicast_address_change_handler.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package winipcfg
diff --git a/tunnel/winipcfg/winipcfg.go b/tunnel/winipcfg/winipcfg.go
index 2fc0c875..e24157b9 100644
--- a/tunnel/winipcfg/winipcfg.go
+++ b/tunnel/winipcfg/winipcfg.go
@@ -1,11 +1,12 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package winipcfg
import (
+ "runtime"
"unsafe"
"golang.org/x/sys/windows"
@@ -29,6 +30,7 @@ import (
//sys getIfTable2Ex(level MibIfEntryLevel, table **mibIfTable2) (ret error) = iphlpapi.GetIfTable2Ex
//sys convertInterfaceLUIDToGUID(interfaceLUID *LUID, interfaceGUID *windows.GUID) (ret error) = iphlpapi.ConvertInterfaceLuidToGuid
//sys convertInterfaceGUIDToLUID(interfaceGUID *windows.GUID, interfaceLUID *LUID) (ret error) = iphlpapi.ConvertInterfaceGuidToLuid
+//sys convertInterfaceIndexToLUID(interfaceIndex uint32, interfaceLUID *LUID) (ret error) = iphlpapi.ConvertInterfaceIndexToLuid
// GetAdaptersAddresses function retrieves the addresses associated with the adapters on the local computer.
// https://docs.microsoft.com/en-us/windows/desktop/api/iphlpapi/nf-iphlpapi-getadaptersaddresses
@@ -166,3 +168,29 @@ func GetIPForwardTable2(family AddressFamily) ([]MibIPforwardRow2, error) {
// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-cancelmibchangenotify2
//sys cancelMibChangeNotify2(notificationHandle windows.Handle) (ret error) = iphlpapi.CancelMibChangeNotify2
+
+//
+// DNS-related functions
+//
+
+//sys setInterfaceDnsSettingsByPtr(guid *windows.GUID, settings *DnsInterfaceSettings) (ret error) = iphlpapi.SetInterfaceDnsSettings?
+//sys setInterfaceDnsSettingsByQwords(guid1 uintptr, guid2 uintptr, settings *DnsInterfaceSettings) (ret error) = iphlpapi.SetInterfaceDnsSettings?
+//sys setInterfaceDnsSettingsByDwords(guid1 uintptr, guid2 uintptr, guid3 uintptr, guid4 uintptr, settings *DnsInterfaceSettings) (ret error) = iphlpapi.SetInterfaceDnsSettings?
+
+// The GUID is passed by value, not by reference, which means different
+// things on different calling conventions. On amd64, this means it's
+// passed by reference anyway, while on arm, arm64, and 386, it's split
+// into words.
+func SetInterfaceDnsSettings(guid windows.GUID, settings *DnsInterfaceSettings) error {
+ words := (*[4]uintptr)(unsafe.Pointer(&guid))
+ switch runtime.GOARCH {
+ case "amd64":
+ return setInterfaceDnsSettingsByPtr(&guid, settings)
+ case "arm64":
+ return setInterfaceDnsSettingsByQwords(words[0], words[1], settings)
+ case "arm", "386":
+ return setInterfaceDnsSettingsByDwords(words[0], words[1], words[2], words[3], settings)
+ default:
+ panic("unknown calling convention")
+ }
+}
diff --git a/tunnel/winipcfg/winipcfg_test.go b/tunnel/winipcfg/winipcfg_test.go
index 0251aecf..b49daf33 100644
--- a/tunnel/winipcfg/winipcfg_test.go
+++ b/tunnel/winipcfg/winipcfg_test.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
/*
@@ -8,8 +8,8 @@
Some tests in this file require:
- A dedicated network adapter
- Any network adapter will do. It may be virtual (Wintun etc.). The adapter name
- must contain string "winipcfg_test".
+ Any network adapter will do. It may be virtual (WireGuardNT, Wintun,
+ etc.). The adapter name must contain string "winipcfg_test".
Tests will add, remove, flush DNS servers, change adapter IP address, manipulate
routes etc.
The adapter will not be returned to previous state, so use an expendable one.
@@ -22,9 +22,9 @@ Some tests in this file require:
package winipcfg
import (
- "bytes"
- "net"
+ "net/netip"
"strings"
+ "syscall"
"testing"
"time"
@@ -37,22 +37,13 @@ const (
// TODO: Add IPv6 tests.
var (
- unexistentIPAddresToAdd = net.IPNet{
- IP: net.IP{172, 16, 1, 114},
- Mask: net.IPMask{255, 255, 255, 0},
- }
- unexistentRouteIPv4ToAdd = RouteData{
- Destination: net.IPNet{
- IP: net.IP{172, 16, 200, 0},
- Mask: net.IPMask{255, 255, 255, 0},
- },
- NextHop: net.IP{172, 16, 1, 2},
- Metric: 0,
- }
- dnsesToSet = []net.IP{
- net.IPv4(8, 8, 8, 8),
- net.IPv4(8, 8, 4, 4),
+ nonexistantIPv4ToAdd = netip.MustParsePrefix("172.16.1.114/24")
+ nonexistentRouteIPv4ToAdd = RouteData{
+ Destination: netip.MustParsePrefix("172.16.200.0/24"),
+ NextHop: netip.MustParseAddr("172.16.1.2"),
+ Metric: 0,
}
+ dnsesToSet = []netip.Addr{netip.MustParseAddr("8.8.8.8"), netip.MustParseAddr("8.8.4.4")}
)
func runningElevated() bool {
@@ -73,7 +64,7 @@ func getTestInterface() (*IPAdapterAddresses, error) {
marker := strings.ToLower(testInterfaceMarker)
for _, ifc := range ifcs {
- if strings.Index(strings.ToLower(ifc.FriendlyName()), marker) != -1 {
+ if strings.Contains(strings.ToLower(ifc.FriendlyName()), marker) {
return ifc, nil
}
}
@@ -93,7 +84,7 @@ func getTestIPInterface(family AddressFamily) (*MibIPInterfaceRow, error) {
func TestAdaptersAddresses(t *testing.T) {
ifcs, err := GetAdaptersAddresses(windows.AF_UNSPEC, GAAFlagIncludeAll)
if err != nil {
- t.Errorf("GetAdaptersAddresses() returned error: %v", err)
+ t.Errorf("GetAdaptersAddresses() returned error: %w", err)
} else if ifcs == nil {
t.Errorf("GetAdaptersAddresses() returned nil.")
} else if len(ifcs) == 0 {
@@ -107,7 +98,7 @@ func TestAdaptersAddresses(t *testing.T) {
i.PhysicalAddress()
i.DHCPv6ClientDUID()
for dnsSuffix := i.FirstDNSSuffix; dnsSuffix != nil; dnsSuffix = dnsSuffix.Next {
- dnsSuffix.String()
+ _ = dnsSuffix.String()
}
}
}
@@ -117,7 +108,7 @@ func TestAdaptersAddresses(t *testing.T) {
for _, i := range ifcs {
ifc, err := i.LUID.Interface()
if err != nil {
- t.Errorf("LUID.Interface() returned an error: %v", err)
+ t.Errorf("LUID.Interface() returned an error: %w", err)
continue
} else if ifc == nil {
t.Errorf("LUID.Interface() returned nil.")
@@ -128,7 +119,7 @@ func TestAdaptersAddresses(t *testing.T) {
for _, i := range ifcs {
guid, err := i.LUID.GUID()
if err != nil {
- t.Errorf("LUID.GUID() returned an error: %v", err)
+ t.Errorf("LUID.GUID() returned an error: %w", err)
continue
}
if guid == nil {
@@ -138,7 +129,7 @@ func TestAdaptersAddresses(t *testing.T) {
luid, err := LUIDFromGUID(guid)
if err != nil {
- t.Errorf("LUIDFromGUID() returned an error: %v", err)
+ t.Errorf("LUIDFromGUID() returned an error: %w", err)
continue
}
if luid != i.LUID {
@@ -151,7 +142,7 @@ func TestAdaptersAddresses(t *testing.T) {
func TestIPInterface(t *testing.T) {
ifcs, err := GetAdaptersAddresses(windows.AF_UNSPEC, GAAFlagDefault)
if err != nil {
- t.Errorf("GetAdaptersAddresses() returned error: %v", err)
+ t.Errorf("GetAdaptersAddresses() returned error: %w", err)
}
for _, i := range ifcs {
@@ -161,12 +152,12 @@ func TestIPInterface(t *testing.T) {
continue
}
if err != nil {
- t.Errorf("LUID.IPInterface(%s) returned an error: %v", i.FriendlyName(), err)
+ t.Errorf("LUID.IPInterface(%s) returned an error: %w", i.FriendlyName(), err)
}
_, err = i.LUID.IPInterface(windows.AF_INET6)
if err != nil {
- t.Errorf("LUID.IPInterface(%s) returned an error: %v", i.FriendlyName(), err)
+ t.Errorf("LUID.IPInterface(%s) returned an error: %w", i.FriendlyName(), err)
}
}
}
@@ -174,7 +165,7 @@ func TestIPInterface(t *testing.T) {
func TestIPInterfaces(t *testing.T) {
tab, err := GetIPInterfaceTable(windows.AF_UNSPEC)
if err != nil {
- t.Errorf("GetIPInterfaceTable() returned an error: %v", err)
+ t.Errorf("GetIPInterfaceTable() returned an error: %w", err)
return
} else if tab == nil {
t.Error("GetIPInterfaceTable() returned nil.")
@@ -189,7 +180,7 @@ func TestIPInterfaces(t *testing.T) {
func TestIPChangeMetric(t *testing.T) {
ipifc, err := getTestIPInterface(windows.AF_INET)
if err != nil {
- t.Errorf("getTestIPInterface() returned an error: %v", err)
+ t.Errorf("getTestIPInterface() returned an error: %w", err)
return
}
if !runningElevated() {
@@ -208,13 +199,13 @@ func TestIPChangeMetric(t *testing.T) {
}
})
if err != nil {
- t.Errorf("RegisterInterfaceChangeCallback() returned error: %v", err)
+ t.Errorf("RegisterInterfaceChangeCallback() returned error: %w", err)
return
}
defer func() {
err = cb.Unregister()
if err != nil {
- t.Errorf("UnregisterInterfaceChangeCallback() returned error: %v", err)
+ t.Errorf("UnregisterInterfaceChangeCallback() returned error: %w", err)
}
}()
@@ -230,14 +221,14 @@ func TestIPChangeMetric(t *testing.T) {
ipifc.Metric = newMetric
err = ipifc.Set()
if err != nil {
- t.Errorf("MibIPInterfaceRow.Set() returned an error: %v", err)
+ t.Errorf("MibIPInterfaceRow.Set() returned an error: %w", err)
}
time.Sleep(500 * time.Millisecond)
ipifc, err = getTestIPInterface(windows.AF_INET)
if err != nil {
- t.Errorf("getTestIPInterface() returned an error: %v", err)
+ t.Errorf("getTestIPInterface() returned an error: %w", err)
return
}
if ipifc.Metric != newMetric {
@@ -255,14 +246,14 @@ func TestIPChangeMetric(t *testing.T) {
ipifc.Metric = metric
err = ipifc.Set()
if err != nil {
- t.Errorf("MibIPInterfaceRow.Set() returned an error: %v", err)
+ t.Errorf("MibIPInterfaceRow.Set() returned an error: %w", err)
}
time.Sleep(500 * time.Millisecond)
ipifc, err = getTestIPInterface(windows.AF_INET)
if err != nil {
- t.Errorf("getTestIPInterface() returned an error: %v", err)
+ t.Errorf("getTestIPInterface() returned an error: %w", err)
return
}
if ipifc.Metric != metric {
@@ -279,7 +270,7 @@ func TestIPChangeMetric(t *testing.T) {
func TestIPChangeMTU(t *testing.T) {
ipifc, err := getTestIPInterface(windows.AF_INET)
if err != nil {
- t.Errorf("getTestIPInterface() returned an error: %v", err)
+ t.Errorf("getTestIPInterface() returned an error: %w", err)
return
}
if !runningElevated() {
@@ -292,14 +283,14 @@ func TestIPChangeMTU(t *testing.T) {
ipifc.NLMTU = mtuToSet
err = ipifc.Set()
if err != nil {
- t.Errorf("Interface.Set() returned error: %v", err)
+ t.Errorf("Interface.Set() returned error: %w", err)
}
time.Sleep(500 * time.Millisecond)
ipifc, err = getTestIPInterface(windows.AF_INET)
if err != nil {
- t.Errorf("getTestIPInterface() returned an error: %v", err)
+ t.Errorf("getTestIPInterface() returned an error: %w", err)
return
}
if ipifc.NLMTU != mtuToSet {
@@ -309,14 +300,14 @@ func TestIPChangeMTU(t *testing.T) {
ipifc.NLMTU = prevMTU
err = ipifc.Set()
if err != nil {
- t.Errorf("Interface.Set() returned error: %v", err)
+ t.Errorf("Interface.Set() returned error: %w", err)
}
time.Sleep(500 * time.Millisecond)
ipifc, err = getTestIPInterface(windows.AF_INET)
if err != nil {
- t.Errorf("getTestIPInterface() returned an error: %v", err)
+ t.Errorf("getTestIPInterface() returned an error: %w", err)
}
if ipifc.NLMTU != prevMTU {
t.Errorf("Interface.NLMTU is %d although %d is expected.", ipifc.NLMTU, prevMTU)
@@ -326,13 +317,13 @@ func TestIPChangeMTU(t *testing.T) {
func TestGetIfRow(t *testing.T) {
ifc, err := getTestInterface()
if err != nil {
- t.Errorf("getTestInterface() returned an error: %v", err)
+ t.Errorf("getTestInterface() returned an error: %w", err)
return
}
row, err := ifc.LUID.Interface()
if err != nil {
- t.Errorf("LUID.Interface() returned an error: %v", err)
+ t.Errorf("LUID.Interface() returned an error: %w", err)
return
}
@@ -345,7 +336,7 @@ func TestGetIfRow(t *testing.T) {
func TestGetIfRows(t *testing.T) {
tab, err := GetIfTable2Ex(MibIfEntryNormal)
if err != nil {
- t.Errorf("GetIfTable2Ex() returned an error: %v", err)
+ t.Errorf("GetIfTable2Ex() returned an error: %w", err)
return
} else if tab == nil {
t.Errorf("GetIfTable2Ex() returned nil")
@@ -363,7 +354,7 @@ func TestGetIfRows(t *testing.T) {
func TestUnicastIPAddress(t *testing.T) {
_, err := GetUnicastIPAddressTable(windows.AF_UNSPEC)
if err != nil {
- t.Errorf("GetUnicastAddresses() returned an error: %v", err)
+ t.Errorf("GetUnicastAddresses() returned an error: %w", err)
return
}
}
@@ -371,7 +362,7 @@ func TestUnicastIPAddress(t *testing.T) {
func TestAddDeleteIPAddress(t *testing.T) {
ifc, err := getTestInterface()
if err != nil {
- t.Errorf("getTestInterface() returned an error: %v", err)
+ t.Errorf("getTestInterface() returned an error: %w", err)
return
}
if !runningElevated() {
@@ -379,12 +370,12 @@ func TestAddDeleteIPAddress(t *testing.T) {
return
}
- addr, err := ifc.LUID.IPAddress(unexistentIPAddresToAdd.IP)
+ addr, err := ifc.LUID.IPAddress(nonexistantIPv4ToAdd.Addr())
if err == nil {
- t.Errorf("Unicast address %s already exists. Please set unexistentIPAddresToAdd appropriately.", unexistentIPAddresToAdd.IP.String())
+ t.Errorf("Unicast address %s already exists. Please set nonexistantIPv4ToAdd appropriately.", nonexistantIPv4ToAdd.Addr().String())
return
} else if err != windows.ERROR_NOT_FOUND {
- t.Errorf("LUID.IPAddress() returned an error: %v", err)
+ t.Errorf("LUID.IPAddress() returned an error: %w", err)
return
}
@@ -401,7 +392,7 @@ func TestAddDeleteIPAddress(t *testing.T) {
}
})
if err != nil {
- t.Errorf("RegisterUnicastAddressChangeCallback() returned an error: %v", err)
+ t.Errorf("RegisterUnicastAddressChangeCallback() returned an error: %w", err)
} else {
defer cb.Unregister()
}
@@ -409,9 +400,9 @@ func TestAddDeleteIPAddress(t *testing.T) {
for addr := ifc.FirstUnicastAddress; addr != nil; addr = addr.Next {
count--
}
- err = ifc.LUID.AddIPAddresses([]net.IPNet{unexistentIPAddresToAdd})
+ err = ifc.LUID.AddIPAddresses([]netip.Prefix{nonexistantIPv4ToAdd})
if err != nil {
- t.Errorf("LUID.AddIPAddresses() returned an error: %v", err)
+ t.Errorf("LUID.AddIPAddresses() returned an error: %w", err)
}
time.Sleep(500 * time.Millisecond)
@@ -423,28 +414,28 @@ func TestAddDeleteIPAddress(t *testing.T) {
if count != 1 {
t.Errorf("After adding there are %d new interface(s).", count)
}
- addr, err = ifc.LUID.IPAddress(unexistentIPAddresToAdd.IP)
+ addr, err = ifc.LUID.IPAddress(nonexistantIPv4ToAdd.Addr())
if err != nil {
- t.Errorf("LUID.IPAddress() returned an error: %v", err)
+ t.Errorf("LUID.IPAddress() returned an error: %w", err)
} else if addr == nil {
- t.Errorf("Unicast address %s still doesn't exist, although it's added successfully.", unexistentIPAddresToAdd.IP.String())
+ t.Errorf("Unicast address %s still doesn't exist, although it's added successfully.", nonexistantIPv4ToAdd.Addr().String())
}
if !created {
t.Errorf("Notification handler has not been called on add.")
}
- err = ifc.LUID.DeleteIPAddress(unexistentIPAddresToAdd)
+ err = ifc.LUID.DeleteIPAddress(nonexistantIPv4ToAdd)
if err != nil {
- t.Errorf("LUID.DeleteIPAddress() returned an error: %v", err)
+ t.Errorf("LUID.DeleteIPAddress() returned an error: %w", err)
}
time.Sleep(500 * time.Millisecond)
- addr, err = ifc.LUID.IPAddress(unexistentIPAddresToAdd.IP)
+ addr, err = ifc.LUID.IPAddress(nonexistantIPv4ToAdd.Addr())
if err == nil {
- t.Errorf("Unicast address %s still exists, although it's deleted successfully.", unexistentIPAddresToAdd.IP.String())
+ t.Errorf("Unicast address %s still exists, although it's deleted successfully.", nonexistantIPv4ToAdd.Addr().String())
} else if err != windows.ERROR_NOT_FOUND {
- t.Errorf("LUID.IPAddress() returned an error: %v", err)
+ t.Errorf("LUID.IPAddress() returned an error: %w", err)
}
if !deleted {
t.Errorf("Notification handler has not been called on delete.")
@@ -454,19 +445,18 @@ func TestAddDeleteIPAddress(t *testing.T) {
func TestGetRoutes(t *testing.T) {
_, err := GetIPForwardTable2(windows.AF_UNSPEC)
if err != nil {
- t.Errorf("GetIPForwardTable2() returned error: %v", err)
+ t.Errorf("GetIPForwardTable2() returned error: %w", err)
}
}
func TestAddDeleteRoute(t *testing.T) {
- findRoute := func(luid LUID, dest net.IPNet) ([]MibIPforwardRow2, error) {
+ findRoute := func(luid LUID, dest netip.Prefix) ([]MibIPforwardRow2, error) {
var family AddressFamily
- switch {
- case dest.IP.To4() != nil:
+ if dest.Addr().Is4() {
family = windows.AF_INET
- case dest.IP.To16() != nil:
+ } else if dest.Addr().Is6() {
family = windows.AF_INET6
- default:
+ } else {
return nil, windows.ERROR_INVALID_PARAMETER
}
r, err := GetIPForwardTable2(family)
@@ -474,9 +464,8 @@ func TestAddDeleteRoute(t *testing.T) {
return nil, err
}
matches := make([]MibIPforwardRow2, 0, len(r))
- ones, _ := dest.Mask.Size()
for _, route := range r {
- if route.InterfaceLUID == luid && route.DestinationPrefix.PrefixLength == uint8(ones) && route.DestinationPrefix.Prefix.Family == family && route.DestinationPrefix.Prefix.IP().Equal(dest.IP) {
+ if route.InterfaceLUID == luid && route.DestinationPrefix.PrefixLength == uint8(dest.Bits()) && route.DestinationPrefix.RawPrefix.Family == family && route.DestinationPrefix.RawPrefix.Addr() == dest.Addr() {
matches = append(matches, route)
}
}
@@ -485,7 +474,7 @@ func TestAddDeleteRoute(t *testing.T) {
ifc, err := getTestInterface()
if err != nil {
- t.Errorf("getTestInterface() returned an error: %v", err)
+ t.Errorf("getTestInterface() returned an error: %w", err)
return
}
if !runningElevated() {
@@ -493,20 +482,20 @@ func TestAddDeleteRoute(t *testing.T) {
return
}
- _, err = ifc.LUID.Route(unexistentRouteIPv4ToAdd.Destination, unexistentRouteIPv4ToAdd.NextHop)
+ _, err = ifc.LUID.Route(nonexistentRouteIPv4ToAdd.Destination, nonexistentRouteIPv4ToAdd.NextHop)
if err == nil {
- t.Error("LUID.Route() returned a route although it isn't added yet. Have you forgot to set unexistentRouteIPv4ToAdd appropriately?")
+ t.Error("LUID.Route() returned a route although it isn't added yet. Have you forgot to set nonexistentRouteIPv4ToAdd appropriately?")
return
} else if err != windows.ERROR_NOT_FOUND {
- t.Errorf("LUID.Route() returned an error: %v", err)
+ t.Errorf("LUID.Route() returned an error: %w", err)
return
}
- routes, err := findRoute(ifc.LUID, unexistentRouteIPv4ToAdd.Destination)
+ routes, err := findRoute(ifc.LUID, nonexistentRouteIPv4ToAdd.Destination)
if err != nil {
- t.Errorf("findRoute() returned an error: %v", err)
+ t.Errorf("findRoute() returned an error: %w", err)
} else if len(routes) != 0 {
- t.Errorf("findRoute() returned %d items although the route isn't added yet. Have you forgot to set unexistentRouteIPv4ToAdd appropriately?", len(routes))
+ t.Errorf("findRoute() returned %d items although the route isn't added yet. Have you forgot to set nonexistentRouteIPv4ToAdd appropriately?", len(routes))
}
var created, deleted bool
@@ -519,58 +508,58 @@ func TestAddDeleteRoute(t *testing.T) {
}
})
if err != nil {
- t.Errorf("RegisterRouteChangeCallback() returned an error: %v", err)
+ t.Errorf("RegisterRouteChangeCallback() returned an error: %w", err)
} else {
defer cb.Unregister()
}
- err = ifc.LUID.AddRoute(unexistentRouteIPv4ToAdd.Destination, unexistentRouteIPv4ToAdd.NextHop, unexistentRouteIPv4ToAdd.Metric)
+ err = ifc.LUID.AddRoute(nonexistentRouteIPv4ToAdd.Destination, nonexistentRouteIPv4ToAdd.NextHop, nonexistentRouteIPv4ToAdd.Metric)
if err != nil {
- t.Errorf("LUID.AddRoute() returned an error: %v", err)
+ t.Errorf("LUID.AddRoute() returned an error: %w", err)
}
time.Sleep(500 * time.Millisecond)
- route, err := ifc.LUID.Route(unexistentRouteIPv4ToAdd.Destination, unexistentRouteIPv4ToAdd.NextHop)
+ route, err := ifc.LUID.Route(nonexistentRouteIPv4ToAdd.Destination, nonexistentRouteIPv4ToAdd.NextHop)
if err == windows.ERROR_NOT_FOUND {
t.Error("LUID.Route() returned nil although the route is added successfully.")
} else if err != nil {
- t.Errorf("LUID.Route() returned an error: %v", err)
- } else if !route.DestinationPrefix.Prefix.IP().Equal(unexistentRouteIPv4ToAdd.Destination.IP) || !route.NextHop.IP().Equal(unexistentRouteIPv4ToAdd.NextHop) {
+ t.Errorf("LUID.Route() returned an error: %w", err)
+ } else if route.DestinationPrefix.RawPrefix.Addr() != nonexistentRouteIPv4ToAdd.Destination.Addr() || route.NextHop.Addr() != nonexistentRouteIPv4ToAdd.NextHop {
t.Error("LUID.Route() returned a wrong route!")
}
if !created {
t.Errorf("Route handler has not been called on add.")
}
- routes, err = findRoute(ifc.LUID, unexistentRouteIPv4ToAdd.Destination)
+ routes, err = findRoute(ifc.LUID, nonexistentRouteIPv4ToAdd.Destination)
if err != nil {
- t.Errorf("findRoute() returned an error: %v", err)
+ t.Errorf("findRoute() returned an error: %w", err)
} else if len(routes) != 1 {
t.Errorf("findRoute() returned %d items although %d is expected.", len(routes), 1)
- } else if !routes[0].DestinationPrefix.Prefix.IP().Equal(unexistentRouteIPv4ToAdd.Destination.IP) {
- t.Errorf("findRoute() returned a wrong route. Dest: %s; expected: %s.", routes[0].DestinationPrefix.Prefix.IP().String(), unexistentRouteIPv4ToAdd.Destination.IP.String())
+ } else if routes[0].DestinationPrefix.RawPrefix.Addr() != nonexistentRouteIPv4ToAdd.Destination.Addr() {
+ t.Errorf("findRoute() returned a wrong route. Dest: %s; expected: %s.", routes[0].DestinationPrefix.RawPrefix.Addr().String(), nonexistentRouteIPv4ToAdd.Destination.Addr().String())
}
- err = ifc.LUID.DeleteRoute(unexistentRouteIPv4ToAdd.Destination, unexistentRouteIPv4ToAdd.NextHop)
+ err = ifc.LUID.DeleteRoute(nonexistentRouteIPv4ToAdd.Destination, nonexistentRouteIPv4ToAdd.NextHop)
if err != nil {
- t.Errorf("LUID.DeleteRoute() returned an error: %v", err)
+ t.Errorf("LUID.DeleteRoute() returned an error: %w", err)
}
time.Sleep(500 * time.Millisecond)
- _, err = ifc.LUID.Route(unexistentRouteIPv4ToAdd.Destination, unexistentRouteIPv4ToAdd.NextHop)
+ _, err = ifc.LUID.Route(nonexistentRouteIPv4ToAdd.Destination, nonexistentRouteIPv4ToAdd.NextHop)
if err == nil {
t.Error("LUID.Route() returned a route although it is removed successfully.")
} else if err != windows.ERROR_NOT_FOUND {
- t.Errorf("LUID.Route() returned an error: %v", err)
+ t.Errorf("LUID.Route() returned an error: %w", err)
}
if !deleted {
t.Errorf("Route handler has not been called on delete.")
}
- routes, err = findRoute(ifc.LUID, unexistentRouteIPv4ToAdd.Destination)
+ routes, err = findRoute(ifc.LUID, nonexistentRouteIPv4ToAdd.Destination)
if err != nil {
- t.Errorf("findRoute() returned an error: %v", err)
+ t.Errorf("findRoute() returned an error: %w", err)
} else if len(routes) != 0 {
t.Errorf("findRoute() returned %d items although the route is deleted successfully.", len(routes))
}
@@ -579,7 +568,7 @@ func TestAddDeleteRoute(t *testing.T) {
func TestFlushDNS(t *testing.T) {
ifc, err := getTestInterface()
if err != nil {
- t.Errorf("getTestInterface() returned an error: %v", err)
+ t.Errorf("getTestInterface() returned an error: %w", err)
return
}
if !runningElevated() {
@@ -589,12 +578,12 @@ func TestFlushDNS(t *testing.T) {
prevDNSes, err := ifc.LUID.DNS()
if err != nil {
- t.Errorf("LUID.DNS() returned an error: %v", err)
+ t.Errorf("LUID.DNS() returned an error: %w", err)
}
- err = ifc.LUID.FlushDNS()
+ err = ifc.LUID.FlushDNS(syscall.AF_INET)
if err != nil {
- t.Errorf("LUID.FlushDNS() returned an error: %v", err)
+ t.Errorf("LUID.FlushDNS() returned an error: %w", err)
}
ifc, _ = getTestInterface()
@@ -602,10 +591,10 @@ func TestFlushDNS(t *testing.T) {
n := 0
dns, err := ifc.LUID.DNS()
if err != nil {
- t.Errorf("LUID.DNS() returned an error: %v", err)
+ t.Errorf("LUID.DNS() returned an error: %w", err)
}
for _, a := range dns {
- if len(a) != 16 || a.To4() != nil || !((a[15] == 1 || a[15] == 2 || a[15] == 3) && bytes.HasPrefix(a, []byte{0xfe, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})) {
+ if a.Is4() {
n++
}
}
@@ -613,51 +602,7 @@ func TestFlushDNS(t *testing.T) {
t.Errorf("DNSServerAddresses contains %d items, although FlushDNS is executed successfully.", n)
}
- err = ifc.LUID.SetDNS(prevDNSes)
- if err != nil {
- t.Errorf("LUID.SetDNS() returned an error: %v.", err)
- }
-}
-
-func TestAddDNS(t *testing.T) {
- ifc, err := getTestInterface()
- if err != nil {
- t.Errorf("getTestInterface() returned an error: %v", err)
- return
- }
- if !runningElevated() {
- t.Errorf("%s requires elevation", t.Name())
- return
- }
-
- prevDNSes, err := ifc.LUID.DNS()
- if err != nil {
- t.Errorf("LUID.DNS() returned an error: %v", err)
- }
- expectedDNSes := append(prevDNSes, dnsesToSet...)
-
- err = ifc.LUID.AddDNS(dnsesToSet)
- if err != nil {
- t.Errorf("LUID.AddDNS() returned an error: %v", err)
- return
- }
-
- ifc, _ = getTestInterface()
-
- newDNSes, err := ifc.LUID.DNS()
- if err != nil {
- t.Errorf("LUID.DNS() returned an error: %v", err)
- } else if len(newDNSes) != len(expectedDNSes) {
- t.Errorf("expectedDNSes contains %d items, while DNSServerAddresses contains %d.", len(expectedDNSes), len(newDNSes))
- } else {
- for i := range expectedDNSes {
- if !expectedDNSes[i].Equal(newDNSes[i]) {
- t.Errorf("expectedDNSes[%d] = %s while DNSServerAddresses[%d] = %s.", i, expectedDNSes[i].String(), i, newDNSes[i].String())
- }
- }
- }
-
- err = ifc.LUID.SetDNS(prevDNSes)
+ err = ifc.LUID.SetDNS(windows.AF_INET, prevDNSes, nil)
if err != nil {
t.Errorf("LUID.SetDNS() returned an error: %v.", err)
}
@@ -666,7 +611,7 @@ func TestAddDNS(t *testing.T) {
func TestSetDNS(t *testing.T) {
ifc, err := getTestInterface()
if err != nil {
- t.Errorf("getTestInterface() returned an error: %v", err)
+ t.Errorf("getTestInterface() returned an error: %w", err)
return
}
if !runningElevated() {
@@ -676,12 +621,12 @@ func TestSetDNS(t *testing.T) {
prevDNSes, err := ifc.LUID.DNS()
if err != nil {
- t.Errorf("LUID.DNS() returned an error: %v", err)
+ t.Errorf("LUID.DNS() returned an error: %w", err)
}
- err = ifc.LUID.SetDNS(dnsesToSet)
+ err = ifc.LUID.SetDNS(windows.AF_INET, dnsesToSet, nil)
if err != nil {
- t.Errorf("LUID.SetDNS() returned an error: %v", err)
+ t.Errorf("LUID.SetDNS() returned an error: %w", err)
return
}
@@ -689,18 +634,18 @@ func TestSetDNS(t *testing.T) {
newDNSes, err := ifc.LUID.DNS()
if err != nil {
- t.Errorf("LUID.DNS() returned an error: %v", err)
+ t.Errorf("LUID.DNS() returned an error: %w", err)
} else if len(newDNSes) != len(dnsesToSet) {
t.Errorf("dnsesToSet contains %d items, while DNSServerAddresses contains %d.", len(dnsesToSet), len(newDNSes))
} else {
for i := range dnsesToSet {
- if !dnsesToSet[i].Equal(newDNSes[i]) {
+ if dnsesToSet[i] != newDNSes[i] {
t.Errorf("dnsesToSet[%d] = %s while DNSServerAddresses[%d] = %s.", i, dnsesToSet[i].String(), i, newDNSes[i].String())
}
}
}
- err = ifc.LUID.SetDNS(prevDNSes)
+ err = ifc.LUID.SetDNS(windows.AF_INET, prevDNSes, nil)
if err != nil {
t.Errorf("LUID.SetDNS() returned an error: %v.", err)
}
@@ -709,7 +654,7 @@ func TestSetDNS(t *testing.T) {
func TestAnycastIPAddress(t *testing.T) {
_, err := GetAnycastIPAddressTable(windows.AF_UNSPEC)
if err != nil {
- t.Errorf("GetAnycastIPAddressTable() returned an error: %v", err)
+ t.Errorf("GetAnycastIPAddressTable() returned an error: %w", err)
return
}
}
diff --git a/tunnel/winipcfg/zwinipcfg_windows.go b/tunnel/winipcfg/zwinipcfg_windows.go
index 8f37ac26..3a0d8680 100644
--- a/tunnel/winipcfg/zwinipcfg_windows.go
+++ b/tunnel/winipcfg/zwinipcfg_windows.go
@@ -19,6 +19,7 @@ const (
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
+ errERROR_EINVAL error = syscall.EINVAL
)
// errnoErr returns common boxed Errno values, to prevent
@@ -26,7 +27,7 @@ var (
func errnoErr(e syscall.Errno) error {
switch e {
case 0:
- return nil
+ return errERROR_EINVAL
case errnoERROR_IO_PENDING:
return errERROR_IO_PENDING
}
@@ -39,182 +40,198 @@ func errnoErr(e syscall.Errno) error {
var (
modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll")
- procFreeMibTable = modiphlpapi.NewProc("FreeMibTable")
- procInitializeIpInterfaceEntry = modiphlpapi.NewProc("InitializeIpInterfaceEntry")
- procGetIpInterfaceTable = modiphlpapi.NewProc("GetIpInterfaceTable")
- procGetIpInterfaceEntry = modiphlpapi.NewProc("GetIpInterfaceEntry")
- procSetIpInterfaceEntry = modiphlpapi.NewProc("SetIpInterfaceEntry")
- procGetIfEntry2 = modiphlpapi.NewProc("GetIfEntry2")
- procGetIfTable2Ex = modiphlpapi.NewProc("GetIfTable2Ex")
- procConvertInterfaceLuidToGuid = modiphlpapi.NewProc("ConvertInterfaceLuidToGuid")
+ procCancelMibChangeNotify2 = modiphlpapi.NewProc("CancelMibChangeNotify2")
procConvertInterfaceGuidToLuid = modiphlpapi.NewProc("ConvertInterfaceGuidToLuid")
- procGetUnicastIpAddressTable = modiphlpapi.NewProc("GetUnicastIpAddressTable")
- procInitializeUnicastIpAddressEntry = modiphlpapi.NewProc("InitializeUnicastIpAddressEntry")
- procGetUnicastIpAddressEntry = modiphlpapi.NewProc("GetUnicastIpAddressEntry")
- procSetUnicastIpAddressEntry = modiphlpapi.NewProc("SetUnicastIpAddressEntry")
+ procConvertInterfaceIndexToLuid = modiphlpapi.NewProc("ConvertInterfaceIndexToLuid")
+ procConvertInterfaceLuidToGuid = modiphlpapi.NewProc("ConvertInterfaceLuidToGuid")
+ procCreateAnycastIpAddressEntry = modiphlpapi.NewProc("CreateAnycastIpAddressEntry")
+ procCreateIpForwardEntry2 = modiphlpapi.NewProc("CreateIpForwardEntry2")
procCreateUnicastIpAddressEntry = modiphlpapi.NewProc("CreateUnicastIpAddressEntry")
+ procDeleteAnycastIpAddressEntry = modiphlpapi.NewProc("DeleteAnycastIpAddressEntry")
+ procDeleteIpForwardEntry2 = modiphlpapi.NewProc("DeleteIpForwardEntry2")
procDeleteUnicastIpAddressEntry = modiphlpapi.NewProc("DeleteUnicastIpAddressEntry")
- procGetAnycastIpAddressTable = modiphlpapi.NewProc("GetAnycastIpAddressTable")
+ procFreeMibTable = modiphlpapi.NewProc("FreeMibTable")
procGetAnycastIpAddressEntry = modiphlpapi.NewProc("GetAnycastIpAddressEntry")
- procCreateAnycastIpAddressEntry = modiphlpapi.NewProc("CreateAnycastIpAddressEntry")
- procDeleteAnycastIpAddressEntry = modiphlpapi.NewProc("DeleteAnycastIpAddressEntry")
+ procGetAnycastIpAddressTable = modiphlpapi.NewProc("GetAnycastIpAddressTable")
+ procGetIfEntry2 = modiphlpapi.NewProc("GetIfEntry2")
+ procGetIfTable2Ex = modiphlpapi.NewProc("GetIfTable2Ex")
+ procGetIpForwardEntry2 = modiphlpapi.NewProc("GetIpForwardEntry2")
procGetIpForwardTable2 = modiphlpapi.NewProc("GetIpForwardTable2")
+ procGetIpInterfaceEntry = modiphlpapi.NewProc("GetIpInterfaceEntry")
+ procGetIpInterfaceTable = modiphlpapi.NewProc("GetIpInterfaceTable")
+ procGetUnicastIpAddressEntry = modiphlpapi.NewProc("GetUnicastIpAddressEntry")
+ procGetUnicastIpAddressTable = modiphlpapi.NewProc("GetUnicastIpAddressTable")
procInitializeIpForwardEntry = modiphlpapi.NewProc("InitializeIpForwardEntry")
- procGetIpForwardEntry2 = modiphlpapi.NewProc("GetIpForwardEntry2")
- procSetIpForwardEntry2 = modiphlpapi.NewProc("SetIpForwardEntry2")
- procCreateIpForwardEntry2 = modiphlpapi.NewProc("CreateIpForwardEntry2")
- procDeleteIpForwardEntry2 = modiphlpapi.NewProc("DeleteIpForwardEntry2")
+ procInitializeIpInterfaceEntry = modiphlpapi.NewProc("InitializeIpInterfaceEntry")
+ procInitializeUnicastIpAddressEntry = modiphlpapi.NewProc("InitializeUnicastIpAddressEntry")
procNotifyIpInterfaceChange = modiphlpapi.NewProc("NotifyIpInterfaceChange")
- procNotifyUnicastIpAddressChange = modiphlpapi.NewProc("NotifyUnicastIpAddressChange")
procNotifyRouteChange2 = modiphlpapi.NewProc("NotifyRouteChange2")
- procCancelMibChangeNotify2 = modiphlpapi.NewProc("CancelMibChangeNotify2")
+ procNotifyUnicastIpAddressChange = modiphlpapi.NewProc("NotifyUnicastIpAddressChange")
+ procSetInterfaceDnsSettings = modiphlpapi.NewProc("SetInterfaceDnsSettings")
+ procSetIpForwardEntry2 = modiphlpapi.NewProc("SetIpForwardEntry2")
+ procSetIpInterfaceEntry = modiphlpapi.NewProc("SetIpInterfaceEntry")
+ procSetUnicastIpAddressEntry = modiphlpapi.NewProc("SetUnicastIpAddressEntry")
)
-func freeMibTable(memory unsafe.Pointer) {
- syscall.Syscall(procFreeMibTable.Addr(), 1, uintptr(memory), 0, 0)
+func cancelMibChangeNotify2(notificationHandle windows.Handle) (ret error) {
+ r0, _, _ := syscall.Syscall(procCancelMibChangeNotify2.Addr(), 1, uintptr(notificationHandle), 0, 0)
+ if r0 != 0 {
+ ret = syscall.Errno(r0)
+ }
return
}
-func initializeIPInterfaceEntry(row *MibIPInterfaceRow) {
- syscall.Syscall(procInitializeIpInterfaceEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0)
+func convertInterfaceGUIDToLUID(interfaceGUID *windows.GUID, interfaceLUID *LUID) (ret error) {
+ r0, _, _ := syscall.Syscall(procConvertInterfaceGuidToLuid.Addr(), 2, uintptr(unsafe.Pointer(interfaceGUID)), uintptr(unsafe.Pointer(interfaceLUID)), 0)
+ if r0 != 0 {
+ ret = syscall.Errno(r0)
+ }
return
}
-func getIPInterfaceTable(family AddressFamily, table **mibIPInterfaceTable) (ret error) {
- r0, _, _ := syscall.Syscall(procGetIpInterfaceTable.Addr(), 2, uintptr(family), uintptr(unsafe.Pointer(table)), 0)
+func convertInterfaceIndexToLUID(interfaceIndex uint32, interfaceLUID *LUID) (ret error) {
+ r0, _, _ := syscall.Syscall(procConvertInterfaceIndexToLuid.Addr(), 2, uintptr(interfaceIndex), uintptr(unsafe.Pointer(interfaceLUID)), 0)
if r0 != 0 {
ret = syscall.Errno(r0)
}
return
}
-func getIPInterfaceEntry(row *MibIPInterfaceRow) (ret error) {
- r0, _, _ := syscall.Syscall(procGetIpInterfaceEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0)
+func convertInterfaceLUIDToGUID(interfaceLUID *LUID, interfaceGUID *windows.GUID) (ret error) {
+ r0, _, _ := syscall.Syscall(procConvertInterfaceLuidToGuid.Addr(), 2, uintptr(unsafe.Pointer(interfaceLUID)), uintptr(unsafe.Pointer(interfaceGUID)), 0)
if r0 != 0 {
ret = syscall.Errno(r0)
}
return
}
-func setIPInterfaceEntry(row *MibIPInterfaceRow) (ret error) {
- r0, _, _ := syscall.Syscall(procSetIpInterfaceEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0)
+func createAnycastIPAddressEntry(row *MibAnycastIPAddressRow) (ret error) {
+ r0, _, _ := syscall.Syscall(procCreateAnycastIpAddressEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0)
if r0 != 0 {
ret = syscall.Errno(r0)
}
return
}
-func getIfEntry2(row *MibIfRow2) (ret error) {
- r0, _, _ := syscall.Syscall(procGetIfEntry2.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0)
+func createIPForwardEntry2(route *MibIPforwardRow2) (ret error) {
+ r0, _, _ := syscall.Syscall(procCreateIpForwardEntry2.Addr(), 1, uintptr(unsafe.Pointer(route)), 0, 0)
if r0 != 0 {
ret = syscall.Errno(r0)
}
return
}
-func getIfTable2Ex(level MibIfEntryLevel, table **mibIfTable2) (ret error) {
- r0, _, _ := syscall.Syscall(procGetIfTable2Ex.Addr(), 2, uintptr(level), uintptr(unsafe.Pointer(table)), 0)
+func createUnicastIPAddressEntry(row *MibUnicastIPAddressRow) (ret error) {
+ r0, _, _ := syscall.Syscall(procCreateUnicastIpAddressEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0)
if r0 != 0 {
ret = syscall.Errno(r0)
}
return
}
-func convertInterfaceLUIDToGUID(interfaceLUID *LUID, interfaceGUID *windows.GUID) (ret error) {
- r0, _, _ := syscall.Syscall(procConvertInterfaceLuidToGuid.Addr(), 2, uintptr(unsafe.Pointer(interfaceLUID)), uintptr(unsafe.Pointer(interfaceGUID)), 0)
+func deleteAnycastIPAddressEntry(row *MibAnycastIPAddressRow) (ret error) {
+ r0, _, _ := syscall.Syscall(procDeleteAnycastIpAddressEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0)
if r0 != 0 {
ret = syscall.Errno(r0)
}
return
}
-func convertInterfaceGUIDToLUID(interfaceGUID *windows.GUID, interfaceLUID *LUID) (ret error) {
- r0, _, _ := syscall.Syscall(procConvertInterfaceGuidToLuid.Addr(), 2, uintptr(unsafe.Pointer(interfaceGUID)), uintptr(unsafe.Pointer(interfaceLUID)), 0)
+func deleteIPForwardEntry2(route *MibIPforwardRow2) (ret error) {
+ r0, _, _ := syscall.Syscall(procDeleteIpForwardEntry2.Addr(), 1, uintptr(unsafe.Pointer(route)), 0, 0)
if r0 != 0 {
ret = syscall.Errno(r0)
}
return
}
-func getUnicastIPAddressTable(family AddressFamily, table **mibUnicastIPAddressTable) (ret error) {
- r0, _, _ := syscall.Syscall(procGetUnicastIpAddressTable.Addr(), 2, uintptr(family), uintptr(unsafe.Pointer(table)), 0)
+func deleteUnicastIPAddressEntry(row *MibUnicastIPAddressRow) (ret error) {
+ r0, _, _ := syscall.Syscall(procDeleteUnicastIpAddressEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0)
if r0 != 0 {
ret = syscall.Errno(r0)
}
return
}
-func initializeUnicastIPAddressEntry(row *MibUnicastIPAddressRow) {
- syscall.Syscall(procInitializeUnicastIpAddressEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0)
+func freeMibTable(memory unsafe.Pointer) {
+ syscall.Syscall(procFreeMibTable.Addr(), 1, uintptr(memory), 0, 0)
return
}
-func getUnicastIPAddressEntry(row *MibUnicastIPAddressRow) (ret error) {
- r0, _, _ := syscall.Syscall(procGetUnicastIpAddressEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0)
+func getAnycastIPAddressEntry(row *MibAnycastIPAddressRow) (ret error) {
+ r0, _, _ := syscall.Syscall(procGetAnycastIpAddressEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0)
if r0 != 0 {
ret = syscall.Errno(r0)
}
return
}
-func setUnicastIPAddressEntry(row *MibUnicastIPAddressRow) (ret error) {
- r0, _, _ := syscall.Syscall(procSetUnicastIpAddressEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0)
+func getAnycastIPAddressTable(family AddressFamily, table **mibAnycastIPAddressTable) (ret error) {
+ r0, _, _ := syscall.Syscall(procGetAnycastIpAddressTable.Addr(), 2, uintptr(family), uintptr(unsafe.Pointer(table)), 0)
if r0 != 0 {
ret = syscall.Errno(r0)
}
return
}
-func createUnicastIPAddressEntry(row *MibUnicastIPAddressRow) (ret error) {
- r0, _, _ := syscall.Syscall(procCreateUnicastIpAddressEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0)
+func getIfEntry2(row *MibIfRow2) (ret error) {
+ r0, _, _ := syscall.Syscall(procGetIfEntry2.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0)
if r0 != 0 {
ret = syscall.Errno(r0)
}
return
}
-func deleteUnicastIPAddressEntry(row *MibUnicastIPAddressRow) (ret error) {
- r0, _, _ := syscall.Syscall(procDeleteUnicastIpAddressEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0)
+func getIfTable2Ex(level MibIfEntryLevel, table **mibIfTable2) (ret error) {
+ r0, _, _ := syscall.Syscall(procGetIfTable2Ex.Addr(), 2, uintptr(level), uintptr(unsafe.Pointer(table)), 0)
if r0 != 0 {
ret = syscall.Errno(r0)
}
return
}
-func getAnycastIPAddressTable(family AddressFamily, table **mibAnycastIPAddressTable) (ret error) {
- r0, _, _ := syscall.Syscall(procGetAnycastIpAddressTable.Addr(), 2, uintptr(family), uintptr(unsafe.Pointer(table)), 0)
+func getIPForwardEntry2(route *MibIPforwardRow2) (ret error) {
+ r0, _, _ := syscall.Syscall(procGetIpForwardEntry2.Addr(), 1, uintptr(unsafe.Pointer(route)), 0, 0)
if r0 != 0 {
ret = syscall.Errno(r0)
}
return
}
-func getAnycastIPAddressEntry(row *MibAnycastIPAddressRow) (ret error) {
- r0, _, _ := syscall.Syscall(procGetAnycastIpAddressEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0)
+func getIPForwardTable2(family AddressFamily, table **mibIPforwardTable2) (ret error) {
+ r0, _, _ := syscall.Syscall(procGetIpForwardTable2.Addr(), 2, uintptr(family), uintptr(unsafe.Pointer(table)), 0)
if r0 != 0 {
ret = syscall.Errno(r0)
}
return
}
-func createAnycastIPAddressEntry(row *MibAnycastIPAddressRow) (ret error) {
- r0, _, _ := syscall.Syscall(procCreateAnycastIpAddressEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0)
+func getIPInterfaceEntry(row *MibIPInterfaceRow) (ret error) {
+ r0, _, _ := syscall.Syscall(procGetIpInterfaceEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0)
if r0 != 0 {
ret = syscall.Errno(r0)
}
return
}
-func deleteAnycastIPAddressEntry(row *MibAnycastIPAddressRow) (ret error) {
- r0, _, _ := syscall.Syscall(procDeleteAnycastIpAddressEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0)
+func getIPInterfaceTable(family AddressFamily, table **mibIPInterfaceTable) (ret error) {
+ r0, _, _ := syscall.Syscall(procGetIpInterfaceTable.Addr(), 2, uintptr(family), uintptr(unsafe.Pointer(table)), 0)
if r0 != 0 {
ret = syscall.Errno(r0)
}
return
}
-func getIPForwardTable2(family AddressFamily, table **mibIPforwardTable2) (ret error) {
- r0, _, _ := syscall.Syscall(procGetIpForwardTable2.Addr(), 2, uintptr(family), uintptr(unsafe.Pointer(table)), 0)
+func getUnicastIPAddressEntry(row *MibUnicastIPAddressRow) (ret error) {
+ r0, _, _ := syscall.Syscall(procGetUnicastIpAddressEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0)
+ if r0 != 0 {
+ ret = syscall.Errno(r0)
+ }
+ return
+}
+
+func getUnicastIPAddressTable(family AddressFamily, table **mibUnicastIPAddressTable) (ret error) {
+ r0, _, _ := syscall.Syscall(procGetUnicastIpAddressTable.Addr(), 2, uintptr(family), uintptr(unsafe.Pointer(table)), 0)
if r0 != 0 {
ret = syscall.Errno(r0)
}
@@ -226,82 +243,106 @@ func initializeIPForwardEntry(route *MibIPforwardRow2) {
return
}
-func getIPForwardEntry2(route *MibIPforwardRow2) (ret error) {
- r0, _, _ := syscall.Syscall(procGetIpForwardEntry2.Addr(), 1, uintptr(unsafe.Pointer(route)), 0, 0)
+func initializeIPInterfaceEntry(row *MibIPInterfaceRow) {
+ syscall.Syscall(procInitializeIpInterfaceEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0)
+ return
+}
+
+func initializeUnicastIPAddressEntry(row *MibUnicastIPAddressRow) {
+ syscall.Syscall(procInitializeUnicastIpAddressEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0)
+ return
+}
+
+func notifyIPInterfaceChange(family AddressFamily, callback uintptr, callerContext uintptr, initialNotification bool, notificationHandle *windows.Handle) (ret error) {
+ var _p0 uint32
+ if initialNotification {
+ _p0 = 1
+ }
+ r0, _, _ := syscall.Syscall6(procNotifyIpInterfaceChange.Addr(), 5, uintptr(family), uintptr(callback), uintptr(callerContext), uintptr(_p0), uintptr(unsafe.Pointer(notificationHandle)), 0)
if r0 != 0 {
ret = syscall.Errno(r0)
}
return
}
-func setIPForwardEntry2(route *MibIPforwardRow2) (ret error) {
- r0, _, _ := syscall.Syscall(procSetIpForwardEntry2.Addr(), 1, uintptr(unsafe.Pointer(route)), 0, 0)
+func notifyRouteChange2(family AddressFamily, callback uintptr, callerContext uintptr, initialNotification bool, notificationHandle *windows.Handle) (ret error) {
+ var _p0 uint32
+ if initialNotification {
+ _p0 = 1
+ }
+ r0, _, _ := syscall.Syscall6(procNotifyRouteChange2.Addr(), 5, uintptr(family), uintptr(callback), uintptr(callerContext), uintptr(_p0), uintptr(unsafe.Pointer(notificationHandle)), 0)
if r0 != 0 {
ret = syscall.Errno(r0)
}
return
}
-func createIPForwardEntry2(route *MibIPforwardRow2) (ret error) {
- r0, _, _ := syscall.Syscall(procCreateIpForwardEntry2.Addr(), 1, uintptr(unsafe.Pointer(route)), 0, 0)
+func notifyUnicastIPAddressChange(family AddressFamily, callback uintptr, callerContext uintptr, initialNotification bool, notificationHandle *windows.Handle) (ret error) {
+ var _p0 uint32
+ if initialNotification {
+ _p0 = 1
+ }
+ r0, _, _ := syscall.Syscall6(procNotifyUnicastIpAddressChange.Addr(), 5, uintptr(family), uintptr(callback), uintptr(callerContext), uintptr(_p0), uintptr(unsafe.Pointer(notificationHandle)), 0)
if r0 != 0 {
ret = syscall.Errno(r0)
}
return
}
-func deleteIPForwardEntry2(route *MibIPforwardRow2) (ret error) {
- r0, _, _ := syscall.Syscall(procDeleteIpForwardEntry2.Addr(), 1, uintptr(unsafe.Pointer(route)), 0, 0)
+func setInterfaceDnsSettingsByDwords(guid1 uintptr, guid2 uintptr, guid3 uintptr, guid4 uintptr, settings *DnsInterfaceSettings) (ret error) {
+ ret = procSetInterfaceDnsSettings.Find()
+ if ret != nil {
+ return
+ }
+ r0, _, _ := syscall.Syscall6(procSetInterfaceDnsSettings.Addr(), 5, uintptr(guid1), uintptr(guid2), uintptr(guid3), uintptr(guid4), uintptr(unsafe.Pointer(settings)), 0)
if r0 != 0 {
ret = syscall.Errno(r0)
}
return
}
-func notifyIPInterfaceChange(family AddressFamily, callback uintptr, callerContext uintptr, initialNotification bool, notificationHandle *windows.Handle) (ret error) {
- var _p0 uint32
- if initialNotification {
- _p0 = 1
- } else {
- _p0 = 0
+func setInterfaceDnsSettingsByQwords(guid1 uintptr, guid2 uintptr, settings *DnsInterfaceSettings) (ret error) {
+ ret = procSetInterfaceDnsSettings.Find()
+ if ret != nil {
+ return
}
- r0, _, _ := syscall.Syscall6(procNotifyIpInterfaceChange.Addr(), 5, uintptr(family), uintptr(callback), uintptr(callerContext), uintptr(_p0), uintptr(unsafe.Pointer(notificationHandle)), 0)
+ r0, _, _ := syscall.Syscall(procSetInterfaceDnsSettings.Addr(), 3, uintptr(guid1), uintptr(guid2), uintptr(unsafe.Pointer(settings)))
if r0 != 0 {
ret = syscall.Errno(r0)
}
return
}
-func notifyUnicastIPAddressChange(family AddressFamily, callback uintptr, callerContext uintptr, initialNotification bool, notificationHandle *windows.Handle) (ret error) {
- var _p0 uint32
- if initialNotification {
- _p0 = 1
- } else {
- _p0 = 0
+func setInterfaceDnsSettingsByPtr(guid *windows.GUID, settings *DnsInterfaceSettings) (ret error) {
+ ret = procSetInterfaceDnsSettings.Find()
+ if ret != nil {
+ return
}
- r0, _, _ := syscall.Syscall6(procNotifyUnicastIpAddressChange.Addr(), 5, uintptr(family), uintptr(callback), uintptr(callerContext), uintptr(_p0), uintptr(unsafe.Pointer(notificationHandle)), 0)
+ r0, _, _ := syscall.Syscall(procSetInterfaceDnsSettings.Addr(), 2, uintptr(unsafe.Pointer(guid)), uintptr(unsafe.Pointer(settings)), 0)
if r0 != 0 {
ret = syscall.Errno(r0)
}
return
}
-func notifyRouteChange2(family AddressFamily, callback uintptr, callerContext uintptr, initialNotification bool, notificationHandle *windows.Handle) (ret error) {
- var _p0 uint32
- if initialNotification {
- _p0 = 1
- } else {
- _p0 = 0
+func setIPForwardEntry2(route *MibIPforwardRow2) (ret error) {
+ r0, _, _ := syscall.Syscall(procSetIpForwardEntry2.Addr(), 1, uintptr(unsafe.Pointer(route)), 0, 0)
+ if r0 != 0 {
+ ret = syscall.Errno(r0)
}
- r0, _, _ := syscall.Syscall6(procNotifyRouteChange2.Addr(), 5, uintptr(family), uintptr(callback), uintptr(callerContext), uintptr(_p0), uintptr(unsafe.Pointer(notificationHandle)), 0)
+ return
+}
+
+func setIPInterfaceEntry(row *MibIPInterfaceRow) (ret error) {
+ r0, _, _ := syscall.Syscall(procSetIpInterfaceEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0)
if r0 != 0 {
ret = syscall.Errno(r0)
}
return
}
-func cancelMibChangeNotify2(notificationHandle windows.Handle) (ret error) {
- r0, _, _ := syscall.Syscall(procCancelMibChangeNotify2.Addr(), 1, uintptr(notificationHandle), 0, 0)
+func setUnicastIPAddressEntry(row *MibUnicastIPAddressRow) (ret error) {
+ r0, _, _ := syscall.Syscall(procSetUnicastIpAddressEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0)
if r0 != 0 {
ret = syscall.Errno(r0)
}
diff --git a/tunnel/wintun_test.go b/tunnel/wintun_test.go
deleted file mode 100644
index 11d7ab2c..00000000
--- a/tunnel/wintun_test.go
+++ /dev/null
@@ -1,202 +0,0 @@
-/* SPDX-License-Identifier: MIT
- *
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
- */
-
-package tunnel_test
-
-import (
- "bytes"
- "crypto/rand"
- "encoding/binary"
- "fmt"
- "net"
- "sync"
- "testing"
- "time"
-
- "golang.org/x/sys/windows"
-
- "golang.zx2c4.com/wireguard/tun"
-
- "golang.zx2c4.com/wireguard/windows/elevate"
- "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
-)
-
-func TestWintunOrdering(t *testing.T) {
- var tunDevice tun.Device
- err := elevate.DoAsSystem(func() error {
- var err error
- tunDevice, err = tun.CreateTUNWithRequestedGUID("tunordertest", &windows.GUID{12, 12, 12, [8]byte{12, 12, 12, 12, 12, 12, 12, 12}}, 1500)
- return err
- })
- if err != nil {
- t.Fatal(err)
- }
- defer tunDevice.Close()
- nativeTunDevice := tunDevice.(*tun.NativeTun)
- luid := winipcfg.LUID(nativeTunDevice.LUID())
- ip, ipnet, _ := net.ParseCIDR("10.82.31.4/24")
- err = luid.SetIPAddresses([]net.IPNet{{ip, ipnet.Mask}})
- if err != nil {
- t.Fatal(err)
- }
- err = luid.SetRoutes([]*winipcfg.RouteData{{*ipnet, ipnet.IP, 0}})
- if err != nil {
- t.Fatal(err)
- }
- var token [32]byte
- _, err = rand.Read(token[:])
- if err != nil {
- t.Fatal(err)
- }
- var sockWrite net.Conn
- for i := 0; i < 1000; i++ {
- sockWrite, err = net.Dial("udp", "10.82.31.5:9999")
- if err == nil {
- defer sockWrite.Close()
- break
- }
- time.Sleep(time.Millisecond * 100)
- }
- if err != nil {
- t.Fatal(err)
- }
- var sockRead *net.UDPConn
- for i := 0; i < 1000; i++ {
- var listenAddress *net.UDPAddr
- listenAddress, err = net.ResolveUDPAddr("udp", "10.82.31.4:9999")
- if err != nil {
- continue
- }
- sockRead, err = net.ListenUDP("udp", listenAddress)
- if err == nil {
- defer sockRead.Close()
- break
- }
- time.Sleep(time.Millisecond * 100)
- }
- if err != nil {
- t.Fatal(err)
- }
- var wait sync.WaitGroup
- wait.Add(4)
- doneSockWrite := false
- doneTunWrite := false
- fatalErrors := make(chan error, 2)
- errors := make(chan error, 2)
- go func() {
- defer wait.Done()
- buffer := append(token[:], 0, 0, 0, 0, 0, 0, 0, 0)
- for sendingIndex := uint64(0); !doneSockWrite; sendingIndex++ {
- binary.LittleEndian.PutUint64(buffer[32:], sendingIndex)
- _, err := sockWrite.Write(buffer[:])
- if err != nil {
- fatalErrors <- err
- }
- }
- }()
- go func() {
- defer wait.Done()
- packet := [20 + 8 + 32 + 8]byte{
- 0x45, 0, 0, 20 + 8 + 32 + 8,
- 0, 0, 0, 0,
- 0x80, 0x11, 0, 0,
- 10, 82, 31, 5,
- 10, 82, 31, 4,
- 8888 >> 8, 8888 & 0xff, 9999 >> 8, 9999 & 0xff, 0, 8 + 32 + 8, 0, 0,
- }
- copy(packet[28:], token[:])
- for sendingIndex := uint64(0); !doneTunWrite; sendingIndex++ {
- binary.BigEndian.PutUint16(packet[4:], uint16(sendingIndex))
- var checksum uint32
- for i := 0; i < 20; i += 2 {
- if i != 10 {
- checksum += uint32(binary.BigEndian.Uint16(packet[i:]))
- }
- }
- binary.BigEndian.PutUint16(packet[10:], ^(uint16(checksum>>16) + uint16(checksum&0xffff)))
- binary.LittleEndian.PutUint64(packet[20+8+32:], sendingIndex)
- n, err := tunDevice.Write(packet[:], 0)
- if err != nil {
- fatalErrors <- err
- }
- if n == 0 {
- time.Sleep(time.Millisecond * 300)
- }
- }
- }()
- const packetsPerTest = 1 << 21
- go func() {
- defer func() {
- doneSockWrite = true
- wait.Done()
- }()
- var expectedIndex uint64
- for i := uint64(0); i < packetsPerTest; {
- var buffer [(1 << 16) - 1]byte
- bytesRead, err := tunDevice.Read(buffer[:], 0)
- if err != nil {
- fatalErrors <- err
- }
- if bytesRead < 0 || bytesRead > len(buffer) {
- continue
- }
- packet := buffer[:bytesRead]
- tokenPos := bytes.Index(packet, token[:])
- if tokenPos == -1 || tokenPos+32+8 > len(packet) {
- continue
- }
- foundIndex := binary.LittleEndian.Uint64(packet[tokenPos+32:])
- if foundIndex < expectedIndex {
- errors <- fmt.Errorf("Sock write, tun read: expected packet %d, received packet %d", expectedIndex, foundIndex)
- }
- expectedIndex = foundIndex + 1
- i++
- }
- }()
- go func() {
- defer func() {
- doneTunWrite = true
- wait.Done()
- }()
- var expectedIndex uint64
- for i := uint64(0); i < packetsPerTest; {
- var buffer [(1 << 16) - 1]byte
- bytesRead, err := sockRead.Read(buffer[:])
- if err != nil {
- fatalErrors <- err
- }
- if bytesRead < 0 || bytesRead > len(buffer) {
- continue
- }
- packet := buffer[:bytesRead]
- if len(packet) != 32+8 || !bytes.HasPrefix(packet, token[:]) {
- continue
- }
- foundIndex := binary.LittleEndian.Uint64(packet[32:])
- if foundIndex < expectedIndex {
- errors <- fmt.Errorf("Tun write, sock read: expected packet %d, received packet %d", expectedIndex, foundIndex)
- }
- expectedIndex = foundIndex + 1
- i++
- }
- }()
- done := make(chan bool, 2)
- doneFunc := func() {
- wait.Wait()
- done <- true
- }
- defer doneFunc()
- go doneFunc()
- for {
- select {
- case err := <-fatalErrors:
- t.Fatal(err)
- case err := <-errors:
- t.Error(err)
- case <-done:
- return
- }
- }
-}
diff --git a/ui/aboutdialog.go b/ui/aboutdialog.go
index d87727aa..0f939b15 100644
--- a/ui/aboutdialog.go
+++ b/ui/aboutdialog.go
@@ -1,24 +1,27 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ui
import (
- "fmt"
"runtime"
"strings"
"github.com/lxn/walk"
"github.com/lxn/win"
"golang.org/x/sys/windows"
- "golang.zx2c4.com/wireguard/device"
+ "golang.zx2c4.com/wireguard/windows/driver"
+
+ "golang.zx2c4.com/wireguard/windows/l18n"
"golang.zx2c4.com/wireguard/windows/version"
)
-var easterEggIndex = -1
-var showingAboutDialog *walk.Dialog
+var (
+ easterEggIndex = -1
+ showingAboutDialog *walk.Dialog
+)
func onAbout(owner walk.Form) {
showError(runAboutDialog(owner), owner)
@@ -47,7 +50,7 @@ func runAboutDialog(owner walk.Form) error {
showingAboutDialog = nil
}()
disposables.Add(showingAboutDialog)
- showingAboutDialog.SetTitle("About WireGuard")
+ showingAboutDialog.SetTitle(l18n.Sprintf("About WireGuard"))
showingAboutDialog.SetLayout(vbl)
if icon, err := loadLogoIcon(32); err == nil {
showingAboutDialog.SetIcon(icon)
@@ -79,7 +82,7 @@ func runAboutDialog(owner walk.Form) error {
if logo, err := loadLogoIcon(128); err == nil {
iv.SetImage(logo)
}
- iv.Accessibility().SetName("WireGuard logo image")
+ iv.Accessibility().SetName(l18n.Sprintf("WireGuard logo image"))
wgLbl, err := walk.NewTextLabel(showingAboutDialog)
if err != nil {
@@ -95,7 +98,7 @@ func runAboutDialog(owner walk.Form) error {
return err
}
detailsLbl.SetTextAlignment(walk.AlignHCenterVNear)
- detailsLbl.SetText(fmt.Sprintf("App version: %s\nGo backend version: %s\nGo version: %s\nOperating system: %s\nArchitecture: %s", version.Number, device.WireGuardGoVersion, strings.TrimPrefix(runtime.Version(), "go"), version.OsName(), runtime.GOARCH))
+ detailsLbl.SetText(l18n.Sprintf("App version: %s\nDriver version: %s\nGo version: %s\nOperating system: %s\nArchitecture: %s", version.Number, driver.Version(), strings.TrimPrefix(runtime.Version(), "go"), version.OsName(), version.Arch()))
copyrightLbl, err := walk.NewTextLabel(showingAboutDialog)
if err != nil {
@@ -104,7 +107,7 @@ func runAboutDialog(owner walk.Form) error {
copyrightFont, _ := walk.NewFont("Segoe UI", 7, 0)
copyrightLbl.SetFont(copyrightFont)
copyrightLbl.SetTextAlignment(walk.AlignHCenterVNear)
- copyrightLbl.SetText("Copyright © 2015-2019 Jason A. Donenfeld. All Rights Reserved.")
+ copyrightLbl.SetText("Copyright © 2015-2022 Jason A. Donenfeld. All Rights Reserved.")
buttonCP, err := walk.NewComposite(showingAboutDialog)
if err != nil {
@@ -119,14 +122,14 @@ func runAboutDialog(owner walk.Form) error {
return err
}
closePB.SetAlignment(walk.AlignHCenterVNear)
- closePB.SetText("Close")
+ closePB.SetText(l18n.Sprintf("Close"))
closePB.Clicked().Attach(showingAboutDialog.Accept)
donatePB, err := walk.NewPushButton(buttonCP)
if err != nil {
return err
}
donatePB.SetAlignment(walk.AlignHCenterVNear)
- donatePB.SetText("♥ &Donate!")
+ donatePB.SetText(l18n.Sprintf("♥ &Donate!"))
donatePB.Clicked().Attach(func() {
if easterEggIndex == -1 {
easterEggIndex = 0
diff --git a/ui/confview.go b/ui/confview.go
index 1c378aa1..78e4df91 100644
--- a/ui/confview.go
+++ b/ui/confview.go
@@ -1,12 +1,11 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ui
import (
- "fmt"
"strconv"
"strings"
"time"
@@ -15,6 +14,7 @@ import (
"github.com/lxn/win"
"golang.zx2c4.com/wireguard/windows/conf"
+ "golang.zx2c4.com/wireguard/windows/l18n"
"golang.zx2c4.com/wireguard/windows/manager"
)
@@ -50,6 +50,8 @@ type interfaceView struct {
mtu *labelTextLine
addresses *labelTextLine
dns *labelTextLine
+ scripts *labelTextLine
+ table *labelTextLine
toggleActive *toggleActiveLine
lines []widgetsLine
}
@@ -108,7 +110,7 @@ func newLabelStatusLine(parent walk.Container) (*labelStatusLine, error) {
return nil, err
}
disposables.Add(lsl.label)
- lsl.label.SetText("Status:")
+ lsl.label.SetText(l18n.Sprintf("Status:"))
lsl.label.SetTextAlignment(walk.AlignHFarVNear)
if lsl.statusComposite, err = walk.NewComposite(parent); err != nil {
@@ -180,7 +182,7 @@ func newLabelTextLine(fieldName string, parent walk.Container) (*labelTextLine,
return nil, err
}
disposables.Add(lt.label)
- lt.label.SetText(fieldName + ":")
+ lt.label.SetText(fieldName)
lt.label.SetTextAlignment(walk.AlignHFarVNear)
lt.label.SetVisible(false)
@@ -216,9 +218,9 @@ func (tal *toggleActiveLine) update(state manager.TunnelState) {
switch state {
case manager.TunnelStarted:
- text = "&Deactivate"
+ text = l18n.Sprintf("&Deactivate")
case manager.TunnelStopped:
- text = "&Activate"
+ text = l18n.Sprintf("&Activate")
case manager.TunnelStarting, manager.TunnelStopping:
text = textForState(state, true)
default:
@@ -300,11 +302,13 @@ func newInterfaceView(parent walk.Container) (*interfaceView, error) {
disposables.Add(iv.status)
items := []labelTextLineItem{
- {"Public key", &iv.publicKey},
- {"Listen port", &iv.listenPort},
- {"MTU", &iv.mtu},
- {"Addresses", &iv.addresses},
- {"DNS servers", &iv.dns},
+ {l18n.Sprintf("Public key:"), &iv.publicKey},
+ {l18n.Sprintf("Listen port:"), &iv.listenPort},
+ {l18n.Sprintf("MTU:"), &iv.mtu},
+ {l18n.Sprintf("Addresses:"), &iv.addresses},
+ {l18n.Sprintf("DNS servers:"), &iv.dns},
+ {l18n.Sprintf("Scripts:"), &iv.scripts},
+ {l18n.Sprintf("Table:"), &iv.table},
}
if iv.lines, err = createLabelTextLines(items, parent, &disposables); err != nil {
return nil, err
@@ -328,13 +332,13 @@ func newPeerView(parent walk.Container) (*peerView, error) {
pv := new(peerView)
items := []labelTextLineItem{
- {"Public key", &pv.publicKey},
- {"Preshared key", &pv.presharedKey},
- {"Allowed IPs", &pv.allowedIPs},
- {"Endpoint", &pv.endpoint},
- {"Persistent keepalive", &pv.persistentKeepalive},
- {"Latest handshake", &pv.latestHandshake},
- {"Transfer", &pv.transfer},
+ {l18n.Sprintf("Public key:"), &pv.publicKey},
+ {l18n.Sprintf("Preshared key:"), &pv.presharedKey},
+ {l18n.Sprintf("Allowed IPs:"), &pv.allowedIPs},
+ {l18n.Sprintf("Endpoint:"), &pv.endpoint},
+ {l18n.Sprintf("Persistent keepalive:"), &pv.persistentKeepalive},
+ {l18n.Sprintf("Latest handshake:"), &pv.latestHandshake},
+ {l18n.Sprintf("Transfer:"), &pv.transfer},
}
var err error
if pv.lines, err = createLabelTextLines(items, parent, nil); err != nil {
@@ -364,7 +368,11 @@ func (iv *interfaceView) widgetsLines() []widgetsLine {
}
func (iv *interfaceView) apply(c *conf.Interface) {
- iv.publicKey.show(c.PrivateKey.Public().String())
+ if IsAdmin {
+ iv.publicKey.show(c.PrivateKey.Public().String())
+ } else {
+ iv.publicKey.hide()
+ }
if c.ListenPort > 0 {
iv.listenPort.show(strconv.Itoa(int(c.ListenPort)))
@@ -383,20 +391,50 @@ func (iv *interfaceView) apply(c *conf.Interface) {
for i, address := range c.Addresses {
addrStrings[i] = address.String()
}
- iv.addresses.show(strings.Join(addrStrings[:], ", "))
+ iv.addresses.show(strings.Join(addrStrings[:], l18n.EnumerationSeparator()))
} else {
iv.addresses.hide()
}
- if len(c.DNS) > 0 {
- addrStrings := make([]string, len(c.DNS))
- for i, address := range c.DNS {
- addrStrings[i] = address.String()
+ if len(c.DNS)+len(c.DNSSearch) > 0 {
+ addrStrings := make([]string, 0, len(c.DNS)+len(c.DNSSearch))
+ for _, address := range c.DNS {
+ addrStrings = append(addrStrings, address.String())
}
- iv.dns.show(strings.Join(addrStrings[:], ", "))
+ addrStrings = append(addrStrings, c.DNSSearch...)
+ iv.dns.show(strings.Join(addrStrings[:], l18n.EnumerationSeparator()))
} else {
iv.dns.hide()
}
+
+ var scriptsInUse []string
+ if len(c.PreUp) > 0 {
+ scriptsInUse = append(scriptsInUse, l18n.Sprintf("pre-up"))
+ }
+ if len(c.PostUp) > 0 {
+ scriptsInUse = append(scriptsInUse, l18n.Sprintf("post-up"))
+ }
+ if len(c.PreDown) > 0 {
+ scriptsInUse = append(scriptsInUse, l18n.Sprintf("pre-down"))
+ }
+ if len(c.PostDown) > 0 {
+ scriptsInUse = append(scriptsInUse, l18n.Sprintf("post-down"))
+ }
+ if len(scriptsInUse) > 0 {
+ if conf.AdminBool("DangerousScriptExecution") {
+ iv.scripts.show(strings.Join(scriptsInUse, l18n.EnumerationSeparator()))
+ } else {
+ iv.scripts.show(l18n.Sprintf("disabled, per policy"))
+ }
+ } else {
+ iv.scripts.hide()
+ }
+
+ if c.TableOff {
+ iv.table.show(l18n.Sprintf("off"))
+ } else {
+ iv.table.hide()
+ }
}
func (pv *peerView) widgetsLines() []widgetsLine {
@@ -404,10 +442,14 @@ func (pv *peerView) widgetsLines() []widgetsLine {
}
func (pv *peerView) apply(c *conf.Peer) {
- pv.publicKey.show(c.PublicKey.String())
+ if IsAdmin {
+ pv.publicKey.show(c.PublicKey.String())
+ } else {
+ pv.publicKey.hide()
+ }
- if !c.PresharedKey.IsZero() {
- pv.presharedKey.show("enabled")
+ if !c.PresharedKey.IsZero() && IsAdmin {
+ pv.presharedKey.show(l18n.Sprintf("enabled"))
} else {
pv.presharedKey.hide()
}
@@ -417,7 +459,7 @@ func (pv *peerView) apply(c *conf.Peer) {
for i, address := range c.AllowedIPs {
addrStrings[i] = address.String()
}
- pv.allowedIPs.show(strings.Join(addrStrings[:], ", "))
+ pv.allowedIPs.show(strings.Join(addrStrings[:], l18n.EnumerationSeparator()))
} else {
pv.allowedIPs.hide()
}
@@ -441,7 +483,7 @@ func (pv *peerView) apply(c *conf.Peer) {
}
if c.RxBytes > 0 || c.TxBytes > 0 {
- pv.transfer.show(fmt.Sprintf("%s received, %s sent", c.RxBytes.String(), c.TxBytes.String()))
+ pv.transfer.show(l18n.Sprintf("%s received, %s sent", c.RxBytes.String(), c.TxBytes.String()))
} else {
pv.transfer.hide()
}
@@ -552,18 +594,18 @@ func (cv *ConfView) onToggleActiveClicked() {
if err != nil {
cv.Synchronize(func() {
if oldState == manager.TunnelUnknown {
- showErrorCustom(cv.Form(), "Failed to determine tunnel state", err.Error())
+ showErrorCustom(cv.Form(), l18n.Sprintf("Failed to determine tunnel state"), err.Error())
} else if oldState == manager.TunnelStopped {
- showErrorCustom(cv.Form(), "Failed to activate tunnel", err.Error())
+ showErrorCustom(cv.Form(), l18n.Sprintf("Failed to activate tunnel"), err.Error())
} else if oldState == manager.TunnelStarted {
- showErrorCustom(cv.Form(), "Failed to deactivate tunnel", err.Error())
+ showErrorCustom(cv.Form(), l18n.Sprintf("Failed to deactivate tunnel"), err.Error())
}
})
}
}()
}
-func (cv *ConfView) onTunnelChanged(tunnel *manager.Tunnel, state manager.TunnelState, globalState manager.TunnelState, err error) {
+func (cv *ConfView) onTunnelChanged(tunnel *manager.Tunnel, state, globalState manager.TunnelState, err error) {
cv.Synchronize(func() {
cv.interfaze.toggleActive.updateGlobal(globalState)
if cv.tunnel != nil && cv.tunnel.Name == tunnel.Name {
@@ -586,7 +628,7 @@ func (cv *ConfView) onTunnelChanged(tunnel *manager.Tunnel, state manager.Tunnel
}
func (cv *ConfView) SetTunnel(tunnel *manager.Tunnel) {
- cv.tunnel = tunnel //XXX: This races with the read in the updateTicker, but it's pointer-sized!
+ cv.tunnel = tunnel // XXX: This races with the read in the updateTicker, but it's pointer-sized!
var config conf.Config
var state manager.TunnelState
@@ -612,7 +654,7 @@ func (cv *ConfView) setTunnel(tunnel *manager.Tunnel, config *conf.Config, state
return
}
- title := "Interface: " + config.Name
+ title := l18n.Sprintf("Interface: %s", config.Name)
if cv.name.Title() != title {
cv.SetSuspended(true)
defer cv.SetSuspended(false)
@@ -656,7 +698,7 @@ func (cv *ConfView) setTunnel(tunnel *manager.Tunnel, config *conf.Config, state
if err != nil {
continue
}
- group.SetTitle("Peer")
+ group.SetTitle(l18n.Sprintf("Peer"))
pv, err := newPeerView(group)
if err != nil {
group.Dispose()
diff --git a/ui/editdialog.go b/ui/editdialog.go
index 4d25dd79..45b25fd0 100644
--- a/ui/editdialog.go
+++ b/ui/editdialog.go
@@ -1,12 +1,12 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ui
import (
- "fmt"
+ "net/netip"
"strings"
"github.com/lxn/walk"
@@ -14,6 +14,7 @@ import (
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/windows/conf"
+ "golang.zx2c4.com/wireguard/windows/l18n"
"golang.zx2c4.com/wireguard/windows/manager"
"golang.zx2c4.com/wireguard/windows/ui/syntax"
)
@@ -52,9 +53,9 @@ func newEditDialog(owner walk.Form, tunnel *manager.Tunnel) (*EditDialog, error)
var title string
if tunnel == nil {
- title = "Create new tunnel"
+ title = l18n.Sprintf("Create new tunnel")
} else {
- title = "Edit tunnel"
+ title = l18n.Sprintf("Edit tunnel")
}
if tunnel == nil {
@@ -78,7 +79,7 @@ func newEditDialog(owner walk.Form, tunnel *manager.Tunnel) (*EditDialog, error)
dlg.SetTitle(title)
dlg.SetLayout(layout)
dlg.SetMinMaxSize(walk.Size{500, 400}, walk.Size{0, 0})
- if icon, err := loadSystemIcon("imageres", 109, 32); err == nil {
+ if icon, err := loadSystemIcon("imageres", -114, 32); err == nil {
dlg.SetIcon(icon)
}
@@ -88,7 +89,7 @@ func newEditDialog(owner walk.Form, tunnel *manager.Tunnel) (*EditDialog, error)
}
layout.SetRange(nameLabel, walk.Rectangle{0, 0, 1, 1})
nameLabel.SetTextAlignment(walk.AlignHFarVCenter)
- nameLabel.SetText("&Name:")
+ nameLabel.SetText(l18n.Sprintf("&Name:"))
if dlg.nameEdit, err = walk.NewLineEdit(dlg); err != nil {
return nil, err
@@ -102,14 +103,14 @@ func newEditDialog(owner walk.Form, tunnel *manager.Tunnel) (*EditDialog, error)
}
layout.SetRange(pubkeyLabel, walk.Rectangle{0, 1, 1, 1})
pubkeyLabel.SetTextAlignment(walk.AlignHFarVCenter)
- pubkeyLabel.SetText("&Public key:")
+ pubkeyLabel.SetText(l18n.Sprintf("&Public key:"))
if dlg.pubkeyEdit, err = walk.NewLineEdit(dlg); err != nil {
return nil, err
}
layout.SetRange(dlg.pubkeyEdit, walk.Rectangle{1, 1, 1, 1})
dlg.pubkeyEdit.SetReadOnly(true)
- dlg.pubkeyEdit.SetText("(unknown)")
+ dlg.pubkeyEdit.SetText(l18n.Sprintf("(unknown)"))
dlg.pubkeyEdit.Accessibility().SetRole(walk.AccRoleStatictext)
if dlg.syntaxEdit, err = syntax.NewSyntaxEdit(dlg); err != nil {
@@ -128,8 +129,8 @@ func newEditDialog(owner walk.Form, tunnel *manager.Tunnel) (*EditDialog, error)
if dlg.blockUntunneledTrafficCB, err = walk.NewCheckBox(buttonsContainer); err != nil {
return nil, err
}
- dlg.blockUntunneledTrafficCB.SetText("&Block untunneled traffic (kill-switch)")
- dlg.blockUntunneledTrafficCB.SetToolTipText("When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface, with special exceptions for DHCP and NDP.")
+ dlg.blockUntunneledTrafficCB.SetText(l18n.Sprintf("&Block untunneled traffic (kill-switch)"))
+ dlg.blockUntunneledTrafficCB.SetToolTipText(l18n.Sprintf("When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, and the interface does not have table off, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP."))
dlg.blockUntunneledTrafficCB.SetVisible(false)
dlg.blockUntunneledTrafficCB.CheckedChanged().Attach(dlg.onBlockUntunneledTrafficCBCheckedChanged)
@@ -138,14 +139,14 @@ func newEditDialog(owner walk.Form, tunnel *manager.Tunnel) (*EditDialog, error)
if dlg.saveButton, err = walk.NewPushButton(buttonsContainer); err != nil {
return nil, err
}
- dlg.saveButton.SetText("&Save")
+ dlg.saveButton.SetText(l18n.Sprintf("&Save"))
dlg.saveButton.Clicked().Attach(dlg.onSaveButtonClicked)
cancelButton, err := walk.NewPushButton(buttonsContainer)
if err != nil {
return nil, err
}
- cancelButton.SetText("Cancel")
+ cancelButton.SetText(l18n.Sprintf("Cancel"))
cancelButton.Clicked().Attach(dlg.Cancel)
dlg.SetCancelButton(cancelButton)
@@ -160,7 +161,7 @@ func newEditDialog(owner walk.Form, tunnel *manager.Tunnel) (*EditDialog, error)
syntaxEditWnd := dlg.syntaxEdit.Handle()
parentWnd := win.GetParent(syntaxEditWnd)
labelWnd := win.CreateWindowEx(0,
- windows.StringToUTF16Ptr("STATIC"), windows.StringToUTF16Ptr("&Configuration:"),
+ windows.StringToUTF16Ptr("STATIC"), windows.StringToUTF16Ptr(l18n.Sprintf("&Configuration:")),
win.WS_CHILD|win.WS_GROUP|win.SS_LEFT, 0, 0, 0, 0,
parentWnd, win.HMENU(^uintptr(0)), win.HINSTANCE(win.GetWindowLongPtr(parentWnd, win.GWLP_HINSTANCE)), nil)
prevWnd := win.GetWindow(syntaxEditWnd, win.GW_HWNDPREV)
@@ -185,15 +186,17 @@ func (dlg *EditDialog) onBlockUntunneledTrafficCBCheckedChanged() {
return
}
var (
- v40 = [4]byte{}
- v60 = [16]byte{}
- v48 = [4]byte{0x80}
- v68 = [16]byte{0x80}
+ v400 = netip.PrefixFrom(netip.IPv4Unspecified(), 0)
+ v600000 = netip.PrefixFrom(netip.IPv6Unspecified(), 0)
+ v401 = netip.PrefixFrom(netip.AddrFrom4([4]byte{}), 1)
+ v600001 = netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 1)
+ v41281 = netip.PrefixFrom(netip.AddrFrom4([4]byte{0x80}), 1)
+ v680001 = netip.PrefixFrom(netip.AddrFrom16([16]byte{0x80}), 1)
)
block := dlg.blockUntunneledTrafficCB.Checked()
cfg, err := conf.FromWgQuick(dlg.syntaxEdit.Text(), "temporary")
- var newAllowedIPs []conf.IPCidr
+ var newAllowedIPs []netip.Prefix
if err != nil {
goto err
@@ -202,7 +205,7 @@ func (dlg *EditDialog) onBlockUntunneledTrafficCBCheckedChanged() {
goto err
}
- newAllowedIPs = make([]conf.IPCidr, 0, len(cfg.Peers[0].AllowedIPs))
+ newAllowedIPs = make([]netip.Prefix, 0, len(cfg.Peers[0].AllowedIPs))
if block {
var (
foundV401 bool
@@ -211,13 +214,13 @@ func (dlg *EditDialog) onBlockUntunneledTrafficCBCheckedChanged() {
foundV680001 bool
)
for _, allowedip := range cfg.Peers[0].AllowedIPs {
- if allowedip.Cidr == 1 && len(allowedip.IP) == 16 && allowedip.IP.Equal(v60[:]) {
+ if allowedip == v600001 {
foundV600001 = true
- } else if allowedip.Cidr == 1 && len(allowedip.IP) == 16 && allowedip.IP.Equal(v68[:]) {
+ } else if allowedip == v680001 {
foundV680001 = true
- } else if allowedip.Cidr == 1 && len(allowedip.IP) == 4 && allowedip.IP.Equal(v40[:]) {
+ } else if allowedip == v401 {
foundV401 = true
- } else if allowedip.Cidr == 1 && len(allowedip.IP) == 4 && allowedip.IP.Equal(v48[:]) {
+ } else if allowedip == v41281 {
foundV41281 = true
} else {
newAllowedIPs = append(newAllowedIPs, allowedip)
@@ -227,44 +230,44 @@ func (dlg *EditDialog) onBlockUntunneledTrafficCBCheckedChanged() {
goto err
}
if foundV401 && foundV41281 {
- newAllowedIPs = append(newAllowedIPs, conf.IPCidr{v40[:], 0})
+ newAllowedIPs = append(newAllowedIPs, v400)
} else if foundV401 {
- newAllowedIPs = append(newAllowedIPs, conf.IPCidr{v40[:], 1})
+ newAllowedIPs = append(newAllowedIPs, v401)
} else if foundV41281 {
- newAllowedIPs = append(newAllowedIPs, conf.IPCidr{v48[:], 1})
+ newAllowedIPs = append(newAllowedIPs, v41281)
}
if foundV600001 && foundV680001 {
- newAllowedIPs = append(newAllowedIPs, conf.IPCidr{v60[:], 0})
+ newAllowedIPs = append(newAllowedIPs, v600000)
} else if foundV600001 {
- newAllowedIPs = append(newAllowedIPs, conf.IPCidr{v60[:], 1})
+ newAllowedIPs = append(newAllowedIPs, v600001)
} else if foundV680001 {
- newAllowedIPs = append(newAllowedIPs, conf.IPCidr{v68[:], 1})
+ newAllowedIPs = append(newAllowedIPs, v680001)
}
cfg.Peers[0].AllowedIPs = newAllowedIPs
} else {
var (
- foundV400 bool
- foundV600 bool
+ foundV400 bool
+ foundV600000 bool
)
for _, allowedip := range cfg.Peers[0].AllowedIPs {
- if allowedip.Cidr == 0 && len(allowedip.IP) == 16 && allowedip.IP.Equal(v60[:]) {
- foundV600 = true
- } else if allowedip.Cidr == 0 && len(allowedip.IP) == 4 && allowedip.IP.Equal(v40[:]) {
+ if allowedip == v600000 {
+ foundV600000 = true
+ } else if allowedip == v400 {
foundV400 = true
} else {
newAllowedIPs = append(newAllowedIPs, allowedip)
}
}
- if !(foundV400 || foundV600) {
+ if !(foundV400 || foundV600000) {
goto err
}
if foundV400 {
- newAllowedIPs = append(newAllowedIPs, conf.IPCidr{v40[:], 1})
- newAllowedIPs = append(newAllowedIPs, conf.IPCidr{v48[:], 1})
+ newAllowedIPs = append(newAllowedIPs, v401)
+ newAllowedIPs = append(newAllowedIPs, v41281)
}
- if foundV600 {
- newAllowedIPs = append(newAllowedIPs, conf.IPCidr{v60[:], 1})
- newAllowedIPs = append(newAllowedIPs, conf.IPCidr{v68[:], 1})
+ if foundV600000 {
+ newAllowedIPs = append(newAllowedIPs, v600001)
+ newAllowedIPs = append(newAllowedIPs, v680001)
}
cfg.Peers[0].AllowedIPs = newAllowedIPs
}
@@ -279,7 +282,7 @@ err:
func (dlg *EditDialog) onBlockUntunneledTrafficStateChanged(state int) {
dlg.blockUntunneledTraficCheckGuard = true
- switch state {
+ switch syntax.BlockState(state) {
case syntax.InevaluableBlockingUntunneledTraffic:
dlg.blockUntunneledTrafficCB.SetVisible(false)
case syntax.BlockingUntunneledTraffic:
@@ -301,18 +304,18 @@ func (dlg *EditDialog) onSyntaxEditPrivateKeyChanged(privateKey string) {
if key != nil {
dlg.pubkeyEdit.SetText(key.Public().String())
} else {
- dlg.pubkeyEdit.SetText("(unknown)")
+ dlg.pubkeyEdit.SetText(l18n.Sprintf("(unknown)"))
}
}
func (dlg *EditDialog) onSaveButtonClicked() {
newName := dlg.nameEdit.Text()
if newName == "" {
- showWarningCustom(dlg, "Invalid name", "A name is required.")
+ showWarningCustom(dlg, l18n.Sprintf("Invalid name"), l18n.Sprintf("A name is required."))
return
}
if !conf.TunnelNameIsValid(newName) {
- showWarningCustom(dlg, "Invalid name", fmt.Sprintf("Tunnel name ‘%s’ is invalid.", newName))
+ showWarningCustom(dlg, l18n.Sprintf("Invalid name"), l18n.Sprintf("Tunnel name ‘%s’ is invalid.", newName))
return
}
newNameLower := strings.ToLower(newName)
@@ -320,12 +323,12 @@ func (dlg *EditDialog) onSaveButtonClicked() {
if newNameLower != strings.ToLower(dlg.config.Name) {
existingTunnelList, err := manager.IPCClientTunnels()
if err != nil {
- showWarningCustom(dlg, "Unable to list existing tunnels", err.Error())
+ showWarningCustom(dlg, l18n.Sprintf("Unable to list existing tunnels"), err.Error())
return
}
for _, tunnel := range existingTunnelList {
if strings.ToLower(tunnel.Name) == newNameLower {
- showWarningCustom(dlg, "Tunnel already exists", fmt.Sprintf("Another tunnel already exists with the name ‘%s’.", newName))
+ showWarningCustom(dlg, l18n.Sprintf("Tunnel already exists"), l18n.Sprintf("Another tunnel already exists with the name ‘%s’.", newName))
return
}
}
@@ -333,7 +336,7 @@ func (dlg *EditDialog) onSaveButtonClicked() {
cfg, err := conf.FromWgQuick(dlg.syntaxEdit.Text(), newName)
if err != nil {
- showErrorCustom(dlg, "Unable to create new configuration", err.Error())
+ showErrorCustom(dlg, l18n.Sprintf("Unable to create new configuration"), err.Error())
return
}
diff --git a/ui/filesave.go b/ui/filesave.go
index 4b5c2947..3a54f015 100644
--- a/ui/filesave.go
+++ b/ui/filesave.go
@@ -1,15 +1,16 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ui
import (
- "fmt"
"os"
"github.com/lxn/walk"
+
+ "golang.zx2c4.com/wireguard/windows/l18n"
)
func writeFileWithOverwriteHandling(owner walk.Form, filePath string, write func(file *os.File) error) bool {
@@ -18,15 +19,15 @@ func writeFileWithOverwriteHandling(owner walk.Form, filePath string, write func
return false
}
- showErrorCustom(owner, "Writing file failed", err.Error())
+ showErrorCustom(owner, l18n.Sprintf("Writing file failed"), err.Error())
return true
}
- file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0600)
+ file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0o600)
if err != nil {
if os.IsExist(err) {
- if walk.DlgCmdNo == walk.MsgBox(owner, "Writing file failed", fmt.Sprintf(`File ‘%s’ already exists.
+ if walk.DlgCmdNo == walk.MsgBox(owner, l18n.Sprintf("Writing file failed"), l18n.Sprintf(`File ‘%s’ already exists.
Do you want to overwrite it?`, filePath), walk.MsgBoxYesNo|walk.MsgBoxDefButton2|walk.MsgBoxIconWarning) {
return false
diff --git a/ui/icon/dot-gray.svg b/ui/icon/dot.svg
index f2ca5c8d..f2ca5c8d 100644
--- a/ui/icon/dot-gray.svg
+++ b/ui/icon/dot.svg
diff --git a/ui/iconprovider.go b/ui/iconprovider.go
index e5177ac3..154a1628 100644
--- a/ui/iconprovider.go
+++ b/ui/iconprovider.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ui
@@ -8,6 +8,7 @@ package ui
import (
"github.com/lxn/walk"
+ "golang.zx2c4.com/wireguard/windows/l18n"
"golang.zx2c4.com/wireguard/windows/manager"
)
@@ -72,11 +73,11 @@ func iconForState(state manager.TunnelState, size int) (icon *walk.Icon, err err
}
switch state {
case manager.TunnelStarted:
- icon, err = loadSystemIcon("imageres", 101, size)
+ icon, err = loadSystemIcon("imageres", -106, size)
case manager.TunnelStopped:
- icon, err = walk.NewIconFromResourceWithSize("dot-gray.ico", walk.Size{size, size}) // TODO: replace with real icon
+ icon, err = walk.NewIconFromResourceIdWithSize(8, walk.Size{size, size}) // TODO: replace with real icon from imageres/shell32
default:
- icon, err = loadSystemIcon("shell32", 238, size) // TODO: this doesn't look that great overlayed on the app icon
+ icon, err = loadSystemIcon("shell32", -16739, size) // TODO: this doesn't look that great overlayed on the app icon
}
if err == nil {
cachedIconsForWidthAndState[widthAndState{size, state}] = icon
@@ -87,15 +88,15 @@ func iconForState(state manager.TunnelState, size int) (icon *walk.Icon, err err
func textForState(state manager.TunnelState, withEllipsis bool) (text string) {
switch state {
case manager.TunnelStarted:
- text = "Active"
+ text = l18n.Sprintf("Active")
case manager.TunnelStarting:
- text = "Activating"
+ text = l18n.Sprintf("Activating")
case manager.TunnelStopped:
- text = "Inactive"
+ text = l18n.Sprintf("Inactive")
case manager.TunnelStopping:
- text = "Deactivating"
+ text = l18n.Sprintf("Deactivating")
case manager.TunnelUnknown:
- text = "Unknown state"
+ text = l18n.Sprintf("Unknown state")
}
if withEllipsis {
switch state {
@@ -120,6 +121,14 @@ func loadSystemIcon(dll string, index int32, size int) (icon *walk.Icon, err err
return
}
+func loadShieldIcon(size int) (icon *walk.Icon, err error) {
+ icon, err = loadSystemIcon("imageres", -1028, size)
+ if err != nil {
+ icon, err = loadSystemIcon("imageres", 1, size)
+ }
+ return
+}
+
var cachedLogoIconsForWidth = make(map[int]*walk.Icon)
func loadLogoIcon(size int) (icon *walk.Icon, err error) {
@@ -127,7 +136,7 @@ func loadLogoIcon(size int) (icon *walk.Icon, err error) {
if icon != nil {
return
}
- icon, err = walk.NewIconFromResourceWithSize("$wireguard.ico", walk.Size{size, size})
+ icon, err = walk.NewIconFromResourceIdWithSize(7, walk.Size{size, size})
if err == nil {
cachedLogoIconsForWidth[size] = icon
}
diff --git a/ui/listview.go b/ui/listview.go
index e9ecb2f8..609feb40 100644
--- a/ui/listview.go
+++ b/ui/listview.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ui
@@ -32,7 +32,7 @@ func (t *ListModel) RowCount() int {
return len(t.tunnels)
}
-func (t *ListModel) Value(row, col int) interface{} {
+func (t *ListModel) Value(row, col int) any {
if col != 0 || row < 0 || row >= len(t.tunnels) {
return ""
}
@@ -181,7 +181,7 @@ func (tv *ListView) StyleCell(style *walk.CellStyle) {
}
}
-func (tv *ListView) onTunnelChange(tunnel *manager.Tunnel, state manager.TunnelState, globalState manager.TunnelState, err error) {
+func (tv *ListView) onTunnelChange(tunnel *manager.Tunnel, state, globalState manager.TunnelState, err error) {
tv.Synchronize(func() {
idx := -1
for i := range tv.model.tunnels {
@@ -225,21 +225,14 @@ func (tv *ListView) Load(asyncUI bool) {
for _, tunnel := range tunnels {
newTunnels[tunnel] = true
}
- for _, tunnel := range tv.model.tunnels {
+ for i := len(tv.model.tunnels); i > 0; {
+ i--
+ tunnel := tv.model.tunnels[i]
oldTunnels[tunnel] = true
- }
-
- for tunnel := range oldTunnels {
if !newTunnels[tunnel] {
- for i, t := range tv.model.tunnels {
- // TODO: this is inefficient. Use a map here instead.
- if t.Name == tunnel.Name {
- tv.model.tunnels = append(tv.model.tunnels[:i], tv.model.tunnels[i+1:]...)
- tv.model.PublishRowsRemoved(i, i) // TODO: Do we have to call that everytime or can we pass a range?
- delete(tv.model.lastObservedState, t)
- break
- }
- }
+ tv.model.tunnels = append(tv.model.tunnels[:i], tv.model.tunnels[i+1:]...)
+ tv.model.PublishRowsRemoved(i, i) // TODO: Do we have to call that everytime or can we pass a range?
+ delete(tv.model.lastObservedState, tunnel)
}
}
didAdd := false
diff --git a/ui/logpage.go b/ui/logpage.go
index b8febcbb..5b7681b3 100644
--- a/ui/logpage.go
+++ b/ui/logpage.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ui
@@ -12,6 +12,7 @@ import (
"time"
"github.com/lxn/walk"
+ "golang.zx2c4.com/wireguard/windows/l18n"
"golang.zx2c4.com/wireguard/windows/ringlogger"
)
@@ -41,7 +42,7 @@ func NewLogPage() (*LogPage, error) {
lp.model.quit <- true
})
- lp.SetTitle("Log")
+ lp.SetTitle(l18n.Sprintf("Log"))
lp.SetLayout(walk.NewVBoxLayout())
if lp.logView, err = walk.NewTableView(lp); err != nil {
@@ -57,19 +58,19 @@ func NewLogPage() (*LogPage, error) {
}
lp.logView.AddDisposable(contextMenu)
copyAction := walk.NewAction()
- copyAction.SetText("&Copy")
+ copyAction.SetText(l18n.Sprintf("&Copy"))
copyAction.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyC})
copyAction.Triggered().Attach(lp.onCopy)
contextMenu.Actions().Add(copyAction)
lp.ShortcutActions().Add(copyAction)
selectAllAction := walk.NewAction()
- selectAllAction.SetText("Select &all")
+ selectAllAction.SetText(l18n.Sprintf("Select &all"))
selectAllAction.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyA})
selectAllAction.Triggered().Attach(lp.onSelectAll)
contextMenu.Actions().Add(selectAllAction)
lp.ShortcutActions().Add(selectAllAction)
saveAction := walk.NewAction()
- saveAction.SetText("&Save to file…")
+ saveAction.SetText(l18n.Sprintf("&Save to file…"))
saveAction.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyS})
saveAction.Triggered().Attach(lp.onSave)
contextMenu.Actions().Add(saveAction)
@@ -83,14 +84,14 @@ func NewLogPage() (*LogPage, error) {
stampCol := walk.NewTableViewColumn()
stampCol.SetName("Stamp")
- stampCol.SetTitle("Time")
+ stampCol.SetTitle(l18n.Sprintf("Time"))
stampCol.SetFormat("2006-01-02 15:04:05.000")
stampCol.SetWidth(140)
lp.logView.Columns().Add(stampCol)
msgCol := walk.NewTableViewColumn()
msgCol.SetName("Line")
- msgCol.SetTitle("Log message")
+ msgCol.SetTitle(l18n.Sprintf("Log message"))
lp.logView.Columns().Add(msgCol)
lp.model = newLogModel(lp)
@@ -111,7 +112,7 @@ func NewLogPage() (*LogPage, error) {
if err != nil {
return nil, err
}
- saveButton.SetText("&Save")
+ saveButton.SetText(l18n.Sprintf("&Save"))
saveButton.Clicked().Attach(lp.onSave)
disposables.Spare()
@@ -146,9 +147,9 @@ func (lp *LogPage) onSelectAll() {
func (lp *LogPage) onSave() {
fd := walk.FileDialog{
- Filter: "Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
+ Filter: l18n.Sprintf("Text Files (*.txt)|*.txt|All Files (*.*)|*.*"),
FilePath: fmt.Sprintf("wireguard-log-%s.txt", time.Now().Format("2006-01-02T150405")),
- Title: "Export log to file",
+ Title: l18n.Sprintf("Export log to file"),
}
form := lp.Form()
@@ -163,7 +164,7 @@ func (lp *LogPage) onSave() {
writeFileWithOverwriteHandling(form, fd.FilePath, func(file *os.File) error {
if _, err := ringlogger.Global.WriteTo(file); err != nil {
- return fmt.Errorf("exportLog: Ringlogger.WriteTo failed: %v", err)
+ return fmt.Errorf("exportLog: Ringlogger.WriteTo failed: %w", err)
}
return nil
@@ -215,6 +216,6 @@ func newLogModel(lp *LogPage) *logModel {
return mdl
}
-func (mdl *logModel) Items() interface{} {
+func (mdl *logModel) Items() any {
return mdl.items
}
diff --git a/ui/managewindow.go b/ui/managewindow.go
index 542c91b9..30a1cedc 100644
--- a/ui/managewindow.go
+++ b/ui/managewindow.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ui
@@ -13,6 +13,7 @@ import (
"github.com/lxn/win"
"golang.org/x/sys/windows"
+ "golang.zx2c4.com/wireguard/windows/l18n"
"golang.zx2c4.com/wireguard/windows/manager"
)
@@ -118,7 +119,7 @@ func NewManageTunnelsWindow() (*ManageTunnelsWindow, error) {
CbSize: uint32(unsafe.Sizeof(win.MENUITEMINFO{})),
FMask: win.MIIM_ID | win.MIIM_STRING | win.MIIM_FTYPE,
FType: win.MIIM_STRING,
- DwTypeData: windows.StringToUTF16Ptr("&About WireGuard…"),
+ DwTypeData: windows.StringToUTF16Ptr(l18n.Sprintf("&About WireGuard…")),
WID: uint32(aboutWireGuardCmd),
})
win.InsertMenuItem(systemMenu, 1, true, &win.MENUITEMINFO{
@@ -160,7 +161,7 @@ func (mtw *ManageTunnelsWindow) updateProgressIndicator(globalState manager.Tunn
}
}
-func (mtw *ManageTunnelsWindow) onTunnelChange(tunnel *manager.Tunnel, state manager.TunnelState, globalState manager.TunnelState, err error) {
+func (mtw *ManageTunnelsWindow) onTunnelChange(tunnel *manager.Tunnel, state, globalState manager.TunnelState, err error) {
mtw.Synchronize(func() {
mtw.updateProgressIndicator(globalState)
@@ -169,7 +170,7 @@ func (mtw *ManageTunnelsWindow) onTunnelChange(tunnel *manager.Tunnel, state man
if len(errMsg) > 0 && errMsg[len(errMsg)-1] != '.' {
errMsg += "."
}
- showWarningCustom(mtw, "Tunnel Error", errMsg+"\n\nPlease consult the log for more information.")
+ showWarningCustom(mtw, l18n.Sprintf("Tunnel Error"), l18n.Sprintf("%s\n\nPlease consult the log for more information.", errMsg))
}
})
}
@@ -178,7 +179,9 @@ func (mtw *ManageTunnelsWindow) UpdateFound() {
if mtw.updatePage != nil {
return
}
- mtw.SetTitle(mtw.Title() + " (out of date)")
+ if IsAdmin {
+ mtw.SetTitle(l18n.Sprintf("%s (out of date)", mtw.Title()))
+ }
updatePage, err := NewUpdatePage()
if err == nil {
mtw.updatePage = updatePage
diff --git a/ui/raise.go b/ui/raise.go
index 42509994..3cea8a88 100644
--- a/ui/raise.go
+++ b/ui/raise.go
@@ -1,17 +1,18 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ui
import (
- "fmt"
"os"
"runtime"
"github.com/lxn/win"
"golang.org/x/sys/windows"
+
+ "golang.zx2c4.com/wireguard/windows/l18n"
)
func raise(hwnd win.HWND) {
@@ -54,7 +55,7 @@ func WaitForRaiseUIThenQuit() {
var handle win.HWINEVENTHOOK
runtime.LockOSThread()
defer runtime.UnlockOSThread()
- handle, err := win.SetWinEventHook(win.EVENT_OBJECT_CREATE, win.EVENT_OBJECT_CREATE, 0, func(hWinEventHook win.HWINEVENTHOOK, event uint32, hwnd win.HWND, idObject int32, idChild int32, idEventThread uint32, dwmsEventTime uint32) uintptr {
+ handle, err := win.SetWinEventHook(win.EVENT_OBJECT_CREATE, win.EVENT_OBJECT_CREATE, 0, func(hWinEventHook win.HWINEVENTHOOK, event uint32, hwnd win.HWND, idObject, idChild int32, idEventThread, dwmsEventTime uint32) uintptr {
class := make([]uint16, len(manageWindowWindowClass)+2) /* Plus 2, one for the null terminator, and one to see if this is only a prefix */
n, err := win.GetClassName(hwnd, &class[0], len(class))
if err != nil || n != len(manageWindowWindowClass) || windows.UTF16ToString(class) != manageWindowWindowClass {
@@ -66,7 +67,7 @@ func WaitForRaiseUIThenQuit() {
return 0
}, 0, 0, win.WINEVENT_SKIPOWNPROCESS|win.WINEVENT_OUTOFCONTEXT)
if err != nil {
- showErrorCustom(nil, "WireGuard Detection Error", fmt.Sprintf("Unable to wait for WireGuard window to appear: %v", err))
+ showErrorCustom(nil, l18n.Sprintf("WireGuard Detection Error"), l18n.Sprintf("Unable to wait for WireGuard window to appear: %v", err))
}
for {
var msg win.MSG
diff --git a/ui/syntax/highlighter.c b/ui/syntax/highlighter.c
deleted file mode 100644
index 1913e359..00000000
--- a/ui/syntax/highlighter.c
+++ /dev/null
@@ -1,620 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
- */
-
-#include <stdbool.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include "highlighter.h"
-
-typedef struct {
- const char *s;
- size_t len;
-} string_span_t;
-
-static bool is_decimal(char c)
-{
- return c >= '0' && c <= '9';
-}
-
-static bool is_hexadecimal(char c)
-{
- return is_decimal(c) || ((c | 32) >= 'a' && (c | 32) <= 'f');
-}
-
-static bool is_alphabet(char c)
-{
- return (c | 32) >= 'a' && (c | 32) <= 'z';
-}
-
-static bool is_same(string_span_t s, const char *c)
-{
- size_t len = strlen(c);
-
- if (len != s.len)
- return false;
- return !memcmp(s.s, c, len);
-}
-
-static bool is_caseless_same(string_span_t s, const char *c)
-{
- size_t len = strlen(c);
-
- if (len != s.len)
- return false;
- for (size_t i = 0; i < len; ++i) {
- char a = c[i], b = s.s[i];
- if ((unsigned)a - 'a' < 26)
- a &= 95;
- if ((unsigned)b - 'a' < 26)
- b &= 95;
- if (a != b)
- return false;
- }
- return true;
-}
-
-static bool is_valid_key(string_span_t s)
-{
- if (s.len != 44 || s.s[43] != '=')
- return false;
-
- for (size_t i = 0; i < 43; ++i) {
- if (!is_decimal(s.s[i]) && !is_alphabet(s.s[i]) &&
- s.s[i] != '/' && s.s[i] != '+')
- return false;
- }
- return true;
-}
-
-static bool is_valid_hostname(string_span_t s)
-{
- size_t num_digit = 0, num_entity = s.len;
-
- if (s.len > 63 || !s.len)
- return false;
- if (s.s[0] == '-' || s.s[s.len - 1] == '-')
- return false;
- if (s.s[0] == '.' || s.s[s.len - 1] == '.')
- return false;
-
- for (size_t i = 0; i < s.len; ++i) {
- if (is_decimal(s.s[i])) {
- ++num_digit;
- continue;
- }
- if (s.s[i] == '.') {
- --num_entity;
- continue;
- }
-
- if (!is_alphabet(s.s[i]) && s.s[i] != '-')
- return false;
-
- if (i && s.s[i] == '.' && s.s[i - 1] == '.')
- return false;
- }
- return num_digit != num_entity;
-}
-
-static bool is_valid_ipv4(string_span_t s)
-{
- for (size_t j, i = 0, pos = 0; i < 4 && pos < s.len; ++i) {
- uint32_t val = 0;
-
- for (j = 0; j < 3 && pos + j < s.len && is_decimal(s.s[pos + j]); ++j)
- val = 10 * val + s.s[pos + j] - '0';
- if (j == 0 || (j > 1 && s.s[pos] == '0') || val > 255)
- return false;
- if (pos + j == s.len && i == 3)
- return true;
- if (s.s[pos + j] != '.')
- return false;
- pos += j + 1;
- }
- return false;
-}
-
-static bool is_valid_ipv6(string_span_t s)
-{
- size_t pos = 0;
- bool seen_colon = false;
-
- if (s.len < 2)
- return false;
- if (s.s[pos] == ':' && s.s[++pos] != ':')
- return false;
- if (s.s[s.len - 1] == ':' && s.s[s.len - 2] != ':')
- return false;
-
- for (size_t j, i = 0; pos < s.len; ++i) {
- if (s.s[pos] == ':' && !seen_colon) {
- seen_colon = true;
- if (++pos == s.len)
- break;
- if (i == 7)
- return false;
- continue;
- }
- for (j = 0; j < 4 && pos + j < s.len && is_hexadecimal(s.s[pos + j]); ++j);
- if (j == 0)
- return false;
- if (pos + j == s.len && (seen_colon || i == 7))
- break;
- if (i == 7)
- return false;
- if (s.s[pos + j] != ':') {
- if (s.s[pos + j] != '.' || (i < 6 && !seen_colon))
- return false;
- return is_valid_ipv4((string_span_t){ s.s + pos, s.len - pos });
- }
- pos += j + 1;
- }
- return true;
-}
-
-static bool is_valid_uint(string_span_t s, bool support_hex, uint64_t min, uint64_t max)
-{
- uint64_t val = 0;
-
- /* Bound this around 32 bits, so that we don't have to write overflow logic. */
- if (s.len > 10 || !s.len)
- return false;
-
- if (support_hex && s.len > 2 && s.s[0] == '0' && s.s[1] == 'x') {
- for (size_t i = 2; i < s.len; ++i) {
- if ((unsigned)s.s[i] - '0' < 10)
- val = 16 * val + (s.s[i] - '0');
- else if (((unsigned)s.s[i] | 32) - 'a' < 6)
- val = 16 * val + (s.s[i] | 32) - 'a' + 10;
- else
- return false;
- }
- } else {
- for (size_t i = 0; i < s.len; ++i) {
- if (!is_decimal(s.s[i]))
- return false;
- val = 10 * val + s.s[i] - '0';
- }
- }
- return val <= max && val >= min;
-}
-
-static bool is_valid_port(string_span_t s)
-{
- return is_valid_uint(s, false, 0, 65535);
-}
-
-static bool is_valid_mtu(string_span_t s)
-{
- return is_valid_uint(s, false, 576, 65535);
-}
-
-static bool is_valid_persistentkeepalive(string_span_t s)
-{
- if (is_same(s, "off"))
- return true;
- return is_valid_uint(s, false, 0, 65535);
-}
-
-#ifndef MOBILE_WGQUICK_SUBSET
-
-static bool is_valid_fwmark(string_span_t s)
-{
- if (is_same(s, "off"))
- return true;
- return is_valid_uint(s, true, 0, 4294967295);
-}
-
-static bool is_valid_table(string_span_t s)
-{
- if (is_same(s, "auto"))
- return true;
- if (is_same(s, "off"))
- return true;
- /* This pretty much invalidates the other checks, but rt_names.c's
- * fread_id_name does no validation aside from this. */
- if (s.len < 512)
- return true;
- return is_valid_uint(s, false, 0, 4294967295);
-}
-
-static bool is_valid_saveconfig(string_span_t s)
-{
- return is_same(s, "true") || is_same(s, "false");
-}
-
-static bool is_valid_prepostupdown(string_span_t s)
-{
- /* It's probably not worthwhile to try to validate a bash expression.
- * So instead we just demand non-zero length. */
- return s.len;
-}
-#endif
-
-static bool is_valid_scope(string_span_t s)
-{
- if (s.len > 64 || !s.len)
- return false;
- for (size_t i = 0; i < s.len; ++i) {
- if (!is_alphabet(s.s[i]) && !is_decimal(s.s[i]) &&
- s.s[i] != '_' && s.s[i] != '=' && s.s[i] != '+' &&
- s.s[i] != '.' && s.s[i] != '-')
- return false;
- }
- return true;
-}
-
-static bool is_valid_endpoint(string_span_t s)
-{
-
- if (!s.len)
- return false;
-
- if (s.s[0] == '[') {
- bool seen_scope = false;
- string_span_t hostspan = { s.s + 1, 0 };
-
- for (size_t i = 1; i < s.len; ++i) {
- if (s.s[i] == '%') {
- if (seen_scope)
- return false;
- seen_scope = true;
- if (!is_valid_ipv6(hostspan))
- return false;
- hostspan = (string_span_t){ s.s + i + 1, 0 };
- } else if (s.s[i] == ']') {
- if (seen_scope) {
- if (!is_valid_scope(hostspan))
- return false;
- } else if (!is_valid_ipv6(hostspan)) {
- return false;
- }
- if (i == s.len - 1 || s.s[i + 1] != ':')
- return false;
- return is_valid_port((string_span_t){ s.s + i + 2, s.len - i - 2 });
- } else {
- ++hostspan.len;
- }
- }
- return false;
- }
- for (size_t i = 0; i < s.len; ++i) {
- if (s.s[i] == ':') {
- string_span_t host = { s.s, i }, port = { s.s + i + 1, s.len - i - 1};
- return is_valid_port(port) && (is_valid_ipv4(host) || is_valid_hostname(host));
- }
- }
- return false;
-}
-
-static bool is_valid_network(string_span_t s)
-{
- for (size_t i = 0; i < s.len; ++i) {
- if (s.s[i] == '/') {
- string_span_t ip = { s.s, i }, cidr = { s.s + i + 1, s.len - i - 1};
- uint16_t cidrval = 0;
-
- if (cidr.len > 3 || !cidr.len)
- return false;
-
- for (size_t j = 0; j < cidr.len; ++j) {
- if (!is_decimal(cidr.s[j]))
- return false;
- cidrval = 10 * cidrval + cidr.s[j] - '0';
- }
- if (is_valid_ipv4(ip))
- return cidrval <= 32;
- else if (is_valid_ipv6(ip))
- return cidrval <= 128;
- return false;
- }
- }
- return is_valid_ipv4(s) || is_valid_ipv6(s);
-}
-
-static bool is_valid_dns(string_span_t s)
-{
- return is_valid_ipv4(s) || is_valid_ipv6(s);
-}
-
-enum field {
- InterfaceSection,
- PrivateKey,
- ListenPort,
- Address,
- DNS,
- MTU,
-#ifndef MOBILE_WGQUICK_SUBSET
- FwMark,
- Table,
- PreUp, PostUp, PreDown, PostDown,
- SaveConfig,
-#endif
-
- PeerSection,
- PublicKey,
- PresharedKey,
- AllowedIPs,
- Endpoint,
- PersistentKeepalive,
-
- Invalid
-};
-
-static enum field section_for_field(enum field t)
-{
- if (t > InterfaceSection && t < PeerSection)
- return InterfaceSection;
- if (t > PeerSection && t < Invalid)
- return PeerSection;
- return Invalid;
-}
-
-static enum field get_field(string_span_t s)
-{
-#define check_enum(t) do { if (is_caseless_same(s, #t)) return t; } while (0)
- check_enum(PrivateKey);
- check_enum(ListenPort);
- check_enum(Address);
- check_enum(DNS);
- check_enum(MTU);
- check_enum(PublicKey);
- check_enum(PresharedKey);
- check_enum(AllowedIPs);
- check_enum(Endpoint);
- check_enum(PersistentKeepalive);
-#ifndef MOBILE_WGQUICK_SUBSET
- check_enum(FwMark);
- check_enum(Table);
- check_enum(PreUp);
- check_enum(PostUp);
- check_enum(PreDown);
- check_enum(PostDown);
- check_enum(SaveConfig);
-#endif
- return Invalid;
-#undef check_enum
-}
-
-static enum field get_sectiontype(string_span_t s)
-{
- if (is_caseless_same(s, "[Peer]"))
- return PeerSection;
- if (is_caseless_same(s, "[Interface]"))
- return InterfaceSection;
- return Invalid;
-}
-
-struct highlight_span_array {
- size_t len, capacity;
- struct highlight_span *spans;
-};
-
-/* A useful OpenBSD-ism. */
-static void *realloc_array(void *optr, size_t nmemb, size_t size)
-{
- if ((nmemb >= (size_t)1 << (sizeof(size_t) * 4) ||
- size >= (size_t)1 << (sizeof(size_t) * 4)) &&
- nmemb > 0 && SIZE_MAX / nmemb < size) {
- errno = ENOMEM;
- return NULL;
- }
- return realloc(optr, size * nmemb);
-}
-
-static bool append_highlight_span(struct highlight_span_array *a, const char *o, string_span_t s, enum highlight_type t)
-{
- if (!s.len)
- return true;
- if (a->len >= a->capacity) {
- struct highlight_span *resized;
-
- a->capacity = a->capacity ? a->capacity * 2 : 64;
- resized = realloc_array(a->spans, a->capacity, sizeof(*resized));
- if (!resized) {
- free(a->spans);
- memset(a, 0, sizeof(*a));
- return false;
- }
- a->spans = resized;
- }
- a->spans[a->len++] = (struct highlight_span){ t, s.s - o, s.len };
- return true;
-}
-
-static void highlight_multivalue_value(struct highlight_span_array *ret, const string_span_t parent, const string_span_t s, enum field section)
-{
- switch (section) {
- case DNS:
- append_highlight_span(ret, parent.s, s, is_valid_dns(s) ? HighlightIP : HighlightError);
- break;
- case Address:
- case AllowedIPs: {
- size_t slash;
-
- if (!is_valid_network(s)) {
- append_highlight_span(ret, parent.s, s, HighlightError);
- break;
- }
- for (slash = 0; slash < s.len; ++slash) {
- if (s.s[slash] == '/')
- break;
- }
- if (slash == s.len) {
- append_highlight_span(ret, parent.s, s, HighlightIP);
- } else {
- append_highlight_span(ret, parent.s, (string_span_t){ s.s, slash }, HighlightIP);
- append_highlight_span(ret, parent.s, (string_span_t){ s.s + slash, 1 }, HighlightDelimiter);
- append_highlight_span(ret, parent.s, (string_span_t){ s.s + slash + 1, s.len - slash - 1 }, HighlightCidr);
- }
- break;
- }
- default:
- append_highlight_span(ret, parent.s, s, HighlightError);
- }
-}
-
-static void highlight_multivalue(struct highlight_span_array *ret, const string_span_t parent, const string_span_t s, enum field section)
-{
- string_span_t current_span = { s.s, 0 };
- size_t len_at_last_space = 0;
-
- for (size_t i = 0; i < s.len; ++i) {
- if (s.s[i] == ',') {
- current_span.len = len_at_last_space;
- highlight_multivalue_value(ret, parent, current_span, section);
- append_highlight_span(ret, parent.s, (string_span_t){ s.s + i, 1 }, HighlightDelimiter);
- len_at_last_space = 0;
- current_span = (string_span_t){ s.s + i + 1, 0 };
- } else if (s.s[i] == ' ' || s.s[i] == '\t') {
- if (&s.s[i] == current_span.s && !current_span.len)
- ++current_span.s;
- else
- ++current_span.len;
- } else {
- len_at_last_space = ++current_span.len;
- }
- }
- current_span.len = len_at_last_space;
- if (current_span.len)
- highlight_multivalue_value(ret, parent, current_span, section);
- else if (ret->spans[ret->len - 1].type == HighlightDelimiter)
- ret->spans[ret->len - 1].type = HighlightError;
-}
-
-static void highlight_value(struct highlight_span_array *ret, const string_span_t parent, const string_span_t s, enum field section)
-{
- switch (section) {
- case PrivateKey:
- append_highlight_span(ret, parent.s, s, is_valid_key(s) ? HighlightPrivateKey : HighlightError);
- break;
- case PublicKey:
- append_highlight_span(ret, parent.s, s, is_valid_key(s) ? HighlightPublicKey : HighlightError);
- break;
- case PresharedKey:
- append_highlight_span(ret, parent.s, s, is_valid_key(s) ? HighlightPresharedKey : HighlightError);
- break;
- case MTU:
- append_highlight_span(ret, parent.s, s, is_valid_mtu(s) ? HighlightMTU : HighlightError);
- break;
-#ifndef MOBILE_WGQUICK_SUBSET
- case SaveConfig:
- append_highlight_span(ret, parent.s, s, is_valid_saveconfig(s) ? HighlightSaveConfig : HighlightError);
- break;
- case FwMark:
- append_highlight_span(ret, parent.s, s, is_valid_fwmark(s) ? HighlightFwMark : HighlightError);
- break;
- case Table:
- append_highlight_span(ret, parent.s, s, is_valid_table(s) ? HighlightTable : HighlightError);
- break;
- case PreUp:
- case PostUp:
- case PreDown:
- case PostDown:
- append_highlight_span(ret, parent.s, s, is_valid_prepostupdown(s) ? HighlightCmd : HighlightError);
- break;
-#endif
- case ListenPort:
- append_highlight_span(ret, parent.s, s, is_valid_port(s) ? HighlightPort : HighlightError);
- break;
- case PersistentKeepalive:
- append_highlight_span(ret, parent.s, s, is_valid_persistentkeepalive(s) ? HighlightKeepalive : HighlightError);
- break;
- case Endpoint: {
- size_t colon;
-
- if (!is_valid_endpoint(s)) {
- append_highlight_span(ret, parent.s, s, HighlightError);
- break;
- }
- for (colon = s.len; colon --> 0;) {
- if (s.s[colon] == ':')
- break;
- }
- append_highlight_span(ret, parent.s, (string_span_t){ s.s, colon }, HighlightHost);
- append_highlight_span(ret, parent.s, (string_span_t){ s.s + colon, 1 }, HighlightDelimiter);
- append_highlight_span(ret, parent.s, (string_span_t){ s.s + colon + 1, s.len - colon - 1 }, HighlightPort);
- break;
- }
- case Address:
- case DNS:
- case AllowedIPs:
- highlight_multivalue(ret, parent, s, section);
- break;
- default:
- append_highlight_span(ret, parent.s, s, HighlightError);
- }
-}
-
-struct highlight_span *highlight_config(const char *config)
-{
- struct highlight_span_array ret = { 0 };
- const string_span_t s = { config, strlen(config) };
- string_span_t current_span = { s.s, 0 };
- enum field current_section = Invalid, current_field = Invalid;
- enum { OnNone, OnKey, OnValue, OnComment, OnSection } state = OnNone;
- size_t len_at_last_space = 0, equals_location = 0;
-
- for (size_t i = 0; i <= s.len; ++i) {
- if (i == s.len || s.s[i] == '\n' || (state != OnComment && s.s[i] == '#')) {
- if (state == OnKey) {
- current_span.len = len_at_last_space;
- append_highlight_span(&ret, s.s, current_span, HighlightError);
- } else if (state == OnValue) {
- if (current_span.len) {
- append_highlight_span(&ret, s.s, (string_span_t){ s.s + equals_location, 1 }, HighlightDelimiter);
- current_span.len = len_at_last_space;
- highlight_value(&ret, s, current_span, current_field);
- } else {
- append_highlight_span(&ret, s.s, (string_span_t){ s.s + equals_location, 1 }, HighlightError);
- }
- } else if (state == OnSection) {
- current_span.len = len_at_last_space;
- current_section = get_sectiontype(current_span);
- append_highlight_span(&ret, s.s, current_span, current_section == Invalid ? HighlightError : HighlightSection);
- } else if (state == OnComment) {
- append_highlight_span(&ret, s.s, current_span, HighlightComment);
- }
- if (i == s.len)
- break;
- len_at_last_space = 0;
- current_field = Invalid;
- if (s.s[i] == '#') {
- current_span = (string_span_t){ s.s + i, 1 };
- state = OnComment;
- } else {
- current_span = (string_span_t){ s.s + i + 1, 0 };
- state = OnNone;
- }
- } else if (state == OnComment) {
- ++current_span.len;
- } else if (s.s[i] == ' ' || s.s[i] == '\t') {
- if (&s.s[i] == current_span.s && !current_span.len)
- ++current_span.s;
- else
- ++current_span.len;
- } else if (s.s[i] == '=' && state == OnKey) {
- current_span.len = len_at_last_space;
- current_field = get_field(current_span);
- enum field section = section_for_field(current_field);
- if (section == Invalid || current_field == Invalid || section != current_section)
- append_highlight_span(&ret, s.s, current_span, HighlightError);
- else
- append_highlight_span(&ret, s.s, current_span, HighlightField);
- equals_location = i;
- current_span = (string_span_t){ s.s + i + 1, 0 };
- state = OnValue;
- } else {
- if (state == OnNone)
- state = s.s[i] == '[' ? OnSection : OnKey;
- len_at_last_space = ++current_span.len;
- }
- }
-
- append_highlight_span(&ret, s.s, (string_span_t){ s.s, -1 }, HighlightEnd);
- return ret.spans;
-}
diff --git a/ui/syntax/highlighter.go b/ui/syntax/highlighter.go
new file mode 100644
index 00000000..099a23b0
--- /dev/null
+++ b/ui/syntax/highlighter.go
@@ -0,0 +1,631 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
+ *
+ * This is a direct translation of the original C, and for that reason, it's pretty unusual Go code:
+ * https://git.zx2c4.com/wireguard-tools/tree/contrib/highlighter/highlighter.c
+ */
+
+package syntax
+
+import "unsafe"
+
+type highlight int
+
+const (
+ highlightSection highlight = iota
+ highlightField
+ highlightPrivateKey
+ highlightPublicKey
+ highlightPresharedKey
+ highlightIP
+ highlightCidr
+ highlightHost
+ highlightPort
+ highlightMTU
+ highlightKeepalive
+ highlightComment
+ highlightDelimiter
+ highlightTable
+ highlightCmd
+ highlightError
+)
+
+func validateHighlight(isValid bool, t highlight) highlight {
+ if isValid {
+ return t
+ }
+ return highlightError
+}
+
+type highlightSpan struct {
+ t highlight
+ s int
+ len int
+}
+
+func isDecimal(c byte) bool {
+ return c >= '0' && c <= '9'
+}
+
+func isHexadecimal(c byte) bool {
+ return isDecimal(c) || (c|32) >= 'a' && (c|32) <= 'f'
+}
+
+func isAlphabet(c byte) bool {
+ return (c|32) >= 'a' && (c|32) <= 'z'
+}
+
+type stringSpan struct {
+ s *byte
+ len int
+}
+
+func (s stringSpan) at(i int) *byte {
+ return (*byte)(unsafe.Add(unsafe.Pointer(s.s), uintptr(i)))
+}
+
+func (s stringSpan) isSame(c string) bool {
+ if s.len != len(c) {
+ return false
+ }
+ cb := ([]byte)(c)
+ for i := 0; i < s.len; i++ {
+ if *s.at(i) != cb[i] {
+ return false
+ }
+ }
+ return true
+}
+
+func (s stringSpan) isCaselessSame(c string) bool {
+ if s.len != len(c) {
+ return false
+ }
+ cb := ([]byte)(c)
+ for i := 0; i < s.len; i++ {
+ a := *s.at(i)
+ b := cb[i]
+ if a-'a' < 26 {
+ a &= 95
+ }
+ if b-'a' < 26 {
+ b &= 95
+ }
+ if a != b {
+ return false
+ }
+ }
+ return true
+}
+
+func (s stringSpan) isValidKey() bool {
+ if s.len != 44 || *s.at(43) != '=' {
+ return false
+ }
+ for i := 0; i < 42; i++ {
+ if !isDecimal(*s.at(i)) && !isAlphabet(*s.at(i)) && *s.at(i) != '/' && *s.at(i) != '+' {
+ return false
+ }
+ }
+ switch *s.at(42) {
+ case 'A', 'E', 'I', 'M', 'Q', 'U', 'Y', 'c', 'g', 'k', 'o', 's', 'w', '4', '8', '0':
+ return true
+ }
+ return false
+}
+
+func (s stringSpan) isValidHostname() bool {
+ numDigit := 0
+ numEntity := s.len
+ if s.len > 63 || s.len == 0 {
+ return false
+ }
+ if *s.s == '-' || *s.at(s.len - 1) == '-' {
+ return false
+ }
+ if *s.s == '.' || *s.at(s.len - 1) == '.' {
+ return false
+ }
+ for i := 0; i < s.len; i++ {
+ if isDecimal(*s.at(i)) {
+ numDigit++
+ continue
+ }
+ if *s.at(i) == '.' {
+ numEntity--
+ continue
+ }
+ if !isAlphabet(*s.at(i)) && *s.at(i) != '-' {
+ return false
+ }
+ if i != 0 && *s.at(i) == '.' && *s.at(i - 1) == '.' {
+ return false
+ }
+ }
+ return numDigit != numEntity
+}
+
+func (s stringSpan) isValidIPv4() bool {
+ pos := 0
+ for i := 0; i < 4 && pos < s.len; i++ {
+ val := 0
+ j := 0
+ for ; j < 3 && pos+j < s.len && isDecimal(*s.at(pos + j)); j++ {
+ val = 10*val + int(*s.at(pos + j)-'0')
+ }
+ if j == 0 || j > 1 && *s.at(pos) == '0' || val > 255 {
+ return false
+ }
+ if pos+j == s.len && i == 3 {
+ return true
+ }
+ if *s.at(pos + j) != '.' {
+ return false
+ }
+ pos += j + 1
+ }
+ return false
+}
+
+func (s stringSpan) isValidIPv6() bool {
+ if s.len < 2 {
+ return false
+ }
+ pos := 0
+ if *s.at(0) == ':' {
+ if *s.at(1) != ':' {
+ return false
+ }
+ pos = 1
+ }
+ if *s.at(s.len - 1) == ':' && *s.at(s.len - 2) != ':' {
+ return false
+ }
+ seenColon := false
+ for i := 0; pos < s.len; i++ {
+ if *s.at(pos) == ':' && !seenColon {
+ seenColon = true
+ pos++
+ if pos == s.len {
+ break
+ }
+ if i == 7 {
+ return false
+ }
+ continue
+ }
+ j := 0
+ for ; ; j++ {
+ if j < 4 && pos+j < s.len && isHexadecimal(*s.at(pos + j)) {
+ continue
+ }
+ break
+ }
+ if j == 0 {
+ return false
+ }
+ if pos+j == s.len && (seenColon || i == 7) {
+ break
+ }
+ if i == 7 {
+ return false
+ }
+ if *s.at(pos + j) != ':' {
+ if *s.at(pos + j) != '.' || i < 6 && !seenColon {
+ return false
+ }
+ return stringSpan{s.at(pos), s.len - pos}.isValidIPv4()
+ }
+ pos += j + 1
+ }
+ return true
+}
+
+func (s stringSpan) isValidUint(supportHex bool, min, max uint64) bool {
+ // Bound this around 32 bits, so that we don't have to write overflow logic.
+ if s.len > 10 || s.len == 0 {
+ return false
+ }
+ val := uint64(0)
+ if supportHex && s.len > 2 && *s.s == '0' && *s.at(1) == 'x' {
+ for i := 2; i < s.len; i++ {
+ if *s.at(i)-'0' < 10 {
+ val = 16*val + uint64(*s.at(i)-'0')
+ } else if (*s.at(i))|32-'a' < 6 {
+ val = 16*val + uint64((*s.at(i)|32)-'a'+10)
+ } else {
+ return false
+ }
+ }
+ } else {
+ for i := 0; i < s.len; i++ {
+ if !isDecimal(*s.at(i)) {
+ return false
+ }
+ val = 10*val + uint64(*s.at(i)-'0')
+ }
+ }
+ return val <= max && val >= min
+}
+
+func (s stringSpan) isValidPort() bool {
+ return s.isValidUint(false, 0, 65535)
+}
+
+func (s stringSpan) isValidMTU() bool {
+ return s.isValidUint(false, 576, 65535)
+}
+
+func (s stringSpan) isValidTable() bool {
+ return s.isSame("off") || s.isSame("auto") || s.isSame("main") || s.isValidUint(false, 0, (1<<32)-1)
+}
+
+func (s stringSpan) isValidPersistentKeepAlive() bool {
+ if s.isSame("off") {
+ return true
+ }
+ return s.isValidUint(false, 0, 65535)
+}
+
+// It's probably not worthwhile to try to validate a bash expression. So instead we just demand non-zero length.
+func (s stringSpan) isValidPrePostUpDown() bool {
+ return s.len != 0
+}
+
+func (s stringSpan) isValidScope() bool {
+ if s.len > 64 || s.len == 0 {
+ return false
+ }
+ for i := 0; i < s.len; i++ {
+ if isAlphabet(*s.at(i)) && !isDecimal(*s.at(i)) && *s.at(i) != '_' && *s.at(i) != '=' && *s.at(i) != '+' && *s.at(i) != '.' && *s.at(i) != '-' {
+ return false
+ }
+ }
+ return true
+}
+
+func (s stringSpan) isValidEndpoint() bool {
+ if s.len == 0 {
+ return false
+ }
+ if *s.s == '[' {
+ seenScope := false
+ hostspan := stringSpan{s.at(1), 0}
+ for i := 1; i < s.len; i++ {
+ if *s.at(i) == '%' {
+ if seenScope {
+ return false
+ }
+ seenScope = true
+ if !hostspan.isValidIPv6() {
+ return false
+ }
+ hostspan = stringSpan{s.at(i + 1), 0}
+ } else if *s.at(i) == ']' {
+ if seenScope {
+ if !hostspan.isValidScope() {
+ return false
+ }
+ } else if !hostspan.isValidIPv6() {
+ return false
+ }
+ if i == s.len-1 || *s.at((i + 1)) != ':' {
+ return false
+ }
+ return stringSpan{s.at(i + 2), s.len - i - 2}.isValidPort()
+ } else {
+ hostspan.len++
+ }
+ }
+ return false
+ }
+ for i := 0; i < s.len; i++ {
+ if *s.at(i) == ':' {
+ host := stringSpan{s.s, i}
+ port := stringSpan{s.at(i + 1), s.len - i - 1}
+ return port.isValidPort() && (host.isValidIPv4() || host.isValidHostname())
+ }
+ }
+ return false
+}
+
+func (s stringSpan) isValidNetwork() bool {
+ for i := 0; i < s.len; i++ {
+ if *s.at(i) == '/' {
+ ip := stringSpan{s.s, i}
+ cidr := stringSpan{s.at(i + 1), s.len - i - 1}
+ cidrval := uint16(0)
+ if cidr.len > 3 || cidr.len == 0 {
+ return false
+ }
+ for j := 0; j < cidr.len; j++ {
+ if !isDecimal(*cidr.at(j)) {
+ return false
+ }
+ cidrval = 10*cidrval + uint16(*cidr.at(j)-'0')
+ }
+ if ip.isValidIPv4() {
+ return cidrval <= 32
+ } else if ip.isValidIPv6() {
+ return cidrval <= 128
+ }
+ return false
+ }
+ }
+ return s.isValidIPv4() || s.isValidIPv6()
+}
+
+type field int32
+
+const (
+ fieldInterfaceSection field = iota
+ fieldPrivateKey
+ fieldListenPort
+ fieldAddress
+ fieldDNS
+ fieldMTU
+ fieldTable
+ fieldPreUp
+ fieldPostUp
+ fieldPreDown
+ fieldPostDown
+ fieldPeerSection
+ fieldPublicKey
+ fieldPresharedKey
+ fieldAllowedIPs
+ fieldEndpoint
+ fieldPersistentKeepalive
+ fieldInvalid
+)
+
+func sectionForField(t field) field {
+ if t > fieldInterfaceSection && t < fieldPeerSection {
+ return fieldInterfaceSection
+ }
+ if t > fieldPeerSection && t < fieldInvalid {
+ return fieldPeerSection
+ }
+ return fieldInvalid
+}
+
+func (s stringSpan) field() field {
+ switch {
+ case s.isCaselessSame("PrivateKey"):
+ return fieldPrivateKey
+ case s.isCaselessSame("ListenPort"):
+ return fieldListenPort
+ case s.isCaselessSame("Address"):
+ return fieldAddress
+ case s.isCaselessSame("DNS"):
+ return fieldDNS
+ case s.isCaselessSame("MTU"):
+ return fieldMTU
+ case s.isCaselessSame("Table"):
+ return fieldTable
+ case s.isCaselessSame("PublicKey"):
+ return fieldPublicKey
+ case s.isCaselessSame("PresharedKey"):
+ return fieldPresharedKey
+ case s.isCaselessSame("AllowedIPs"):
+ return fieldAllowedIPs
+ case s.isCaselessSame("Endpoint"):
+ return fieldEndpoint
+ case s.isCaselessSame("PersistentKeepalive"):
+ return fieldPersistentKeepalive
+ case s.isCaselessSame("PreUp"):
+ return fieldPreUp
+ case s.isCaselessSame("PostUp"):
+ return fieldPostUp
+ case s.isCaselessSame("PreDown"):
+ return fieldPreDown
+ case s.isCaselessSame("PostDown"):
+ return fieldPostDown
+ }
+ return fieldInvalid
+}
+
+func (s stringSpan) sectionType() field {
+ switch {
+ case s.isCaselessSame("[Peer]"):
+ return fieldPeerSection
+ case s.isCaselessSame("[Interface]"):
+ return fieldInterfaceSection
+ }
+ return fieldInvalid
+}
+
+type highlightSpanArray []highlightSpan
+
+func (hsa *highlightSpanArray) append(o *byte, s stringSpan, t highlight) {
+ if s.len == 0 {
+ return
+ }
+ *hsa = append(*hsa, highlightSpan{t, int((uintptr(unsafe.Pointer(s.s))) - (uintptr(unsafe.Pointer(o)))), s.len})
+}
+
+func (hsa *highlightSpanArray) highlightMultivalueValue(parent, s stringSpan, section field) {
+ switch section {
+ case fieldDNS:
+ if s.isValidIPv4() || s.isValidIPv6() {
+ hsa.append(parent.s, s, highlightIP)
+ } else if s.isValidHostname() {
+ hsa.append(parent.s, s, highlightHost)
+ } else {
+ hsa.append(parent.s, s, highlightError)
+ }
+ case fieldAddress, fieldAllowedIPs:
+ if !s.isValidNetwork() {
+ hsa.append(parent.s, s, highlightError)
+ break
+ }
+ slash := 0
+ for ; slash < s.len; slash++ {
+ if *s.at(slash) == '/' {
+ break
+ }
+ }
+ if slash == s.len {
+ hsa.append(parent.s, s, highlightIP)
+ } else {
+ hsa.append(parent.s, stringSpan{s.s, slash}, highlightIP)
+ hsa.append(parent.s, stringSpan{s.at(slash), 1}, highlightDelimiter)
+ hsa.append(parent.s, stringSpan{s.at(slash + 1), s.len - slash - 1}, highlightCidr)
+ }
+ default:
+ hsa.append(parent.s, s, highlightError)
+ }
+}
+
+func (hsa *highlightSpanArray) highlightMultivalue(parent, s stringSpan, section field) {
+ currentSpan := stringSpan{s.s, 0}
+ lenAtLastSpace := 0
+ for i := 0; i < s.len; i++ {
+ if *s.at(i) == ',' {
+ currentSpan.len = lenAtLastSpace
+ hsa.highlightMultivalueValue(parent, currentSpan, section)
+ hsa.append(parent.s, stringSpan{s.at(i), 1}, highlightDelimiter)
+ lenAtLastSpace = 0
+ currentSpan = stringSpan{s.at(i + 1), 0}
+ } else if *s.at(i) == ' ' || *s.at(i) == '\t' {
+ if s.at(i) == currentSpan.s && currentSpan.len == 0 {
+ currentSpan.s = currentSpan.at(1)
+ } else {
+ currentSpan.len++
+ }
+ } else {
+ currentSpan.len++
+ lenAtLastSpace = currentSpan.len
+ }
+ }
+ currentSpan.len = lenAtLastSpace
+ if currentSpan.len != 0 {
+ hsa.highlightMultivalueValue(parent, currentSpan, section)
+ } else if (*hsa)[len(*hsa)-1].t == highlightDelimiter {
+ (*hsa)[len(*hsa)-1].t = highlightError
+ }
+}
+
+func (hsa *highlightSpanArray) highlightValue(parent, s stringSpan, section field) {
+ switch section {
+ case fieldPrivateKey:
+ hsa.append(parent.s, s, validateHighlight(s.isValidKey(), highlightPrivateKey))
+ case fieldPublicKey:
+ hsa.append(parent.s, s, validateHighlight(s.isValidKey(), highlightPublicKey))
+ case fieldPresharedKey:
+ hsa.append(parent.s, s, validateHighlight(s.isValidKey(), highlightPresharedKey))
+ case fieldMTU:
+ hsa.append(parent.s, s, validateHighlight(s.isValidMTU(), highlightMTU))
+ case fieldTable:
+ hsa.append(parent.s, s, validateHighlight(s.isValidTable(), highlightTable))
+ case fieldPreUp, fieldPostUp, fieldPreDown, fieldPostDown:
+ hsa.append(parent.s, s, validateHighlight(s.isValidPrePostUpDown(), highlightCmd))
+ case fieldListenPort:
+ hsa.append(parent.s, s, validateHighlight(s.isValidPort(), highlightPort))
+ case fieldPersistentKeepalive:
+ hsa.append(parent.s, s, validateHighlight(s.isValidPersistentKeepAlive(), highlightKeepalive))
+ case fieldEndpoint:
+ if !s.isValidEndpoint() {
+ hsa.append(parent.s, s, highlightError)
+ break
+ }
+ colon := s.len
+ for colon > 0 {
+ colon--
+ if *s.at(colon) == ':' {
+ break
+ }
+ }
+ hsa.append(parent.s, stringSpan{s.s, colon}, highlightHost)
+ hsa.append(parent.s, stringSpan{s.at(colon), 1}, highlightDelimiter)
+ hsa.append(parent.s, stringSpan{s.at(colon + 1), s.len - colon - 1}, highlightPort)
+ case fieldAddress, fieldDNS, fieldAllowedIPs:
+ hsa.highlightMultivalue(parent, s, section)
+ default:
+ hsa.append(parent.s, s, highlightError)
+ }
+}
+
+func highlightConfig(config string) []highlightSpan {
+ var ret highlightSpanArray
+ b := append([]byte(config), 0)
+ s := stringSpan{&b[0], len(b) - 1}
+ currentSpan := stringSpan{s.s, 0}
+ currentSection := fieldInvalid
+ currentField := fieldInvalid
+ const (
+ onNone = iota
+ onKey
+ onValue
+ onComment
+ onSection
+ )
+ state := onNone
+ lenAtLastSpace := 0
+ equalsLocation := 0
+ for i := 0; i <= s.len; i++ {
+ if i == s.len || *s.at(i) == '\n' || state != onComment && *s.at(i) == '#' {
+ if state == onKey {
+ currentSpan.len = lenAtLastSpace
+ ret.append(s.s, currentSpan, highlightError)
+ } else if state == onValue {
+ if currentSpan.len != 0 {
+ ret.append(s.s, stringSpan{s.at(equalsLocation), 1}, highlightDelimiter)
+ currentSpan.len = lenAtLastSpace
+ ret.highlightValue(s, currentSpan, currentField)
+ } else {
+ ret.append(s.s, stringSpan{s.at(equalsLocation), 1}, highlightError)
+ }
+ } else if state == onSection {
+ currentSpan.len = lenAtLastSpace
+ currentSection = currentSpan.sectionType()
+ ret.append(s.s, currentSpan, validateHighlight(currentSection != fieldInvalid, highlightSection))
+ } else if state == onComment {
+ ret.append(s.s, currentSpan, highlightComment)
+ }
+ if i == s.len {
+ break
+ }
+ lenAtLastSpace = 0
+ currentField = fieldInvalid
+ if *s.at(i) == '#' {
+ currentSpan = stringSpan{s.at(i), 1}
+ state = onComment
+ } else {
+ currentSpan = stringSpan{s.at(i + 1), 0}
+ state = onNone
+ }
+ } else if state == onComment {
+ currentSpan.len++
+ } else if *s.at(i) == ' ' || *s.at(i) == '\t' {
+ if s.at(i) == currentSpan.s && currentSpan.len == 0 {
+ currentSpan.s = currentSpan.at(1)
+ } else {
+ currentSpan.len++
+ }
+ } else if *s.at(i) == '=' && state == onKey {
+ currentSpan.len = lenAtLastSpace
+ currentField = currentSpan.field()
+ section := sectionForField(currentField)
+ if section == fieldInvalid || currentField == fieldInvalid || section != currentSection {
+ ret.append(s.s, currentSpan, highlightError)
+ } else {
+ ret.append(s.s, currentSpan, highlightField)
+ }
+ equalsLocation = i
+ currentSpan = stringSpan{s.at(i + 1), 0}
+ state = onValue
+ } else {
+ if state == onNone {
+ if *s.at(i) == '[' {
+ state = onSection
+ } else {
+ state = onKey
+ }
+ }
+ currentSpan.len++
+ lenAtLastSpace = currentSpan.len
+ }
+ }
+ return ([]highlightSpan)(ret)
+}
diff --git a/ui/syntax/highlighter.h b/ui/syntax/highlighter.h
deleted file mode 100644
index c6c8b5d5..00000000
--- a/ui/syntax/highlighter.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-/*
- * Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
- */
-
-#include <sys/types.h>
-
-#define MOBILE_WGQUICK_SUBSET
-
-enum highlight_type {
- HighlightSection,
- HighlightField,
- HighlightPrivateKey,
- HighlightPublicKey,
- HighlightPresharedKey,
- HighlightIP,
- HighlightCidr,
- HighlightHost,
- HighlightPort,
- HighlightMTU,
- HighlightKeepalive,
- HighlightComment,
- HighlightDelimiter,
-#ifndef MOBILE_WGQUICK_SUBSET
- HighlightTable,
- HighlightFwMark,
- HighlightSaveConfig,
- HighlightCmd,
-#endif
- HighlightError,
- HighlightEnd
-};
-
-struct highlight_span {
- enum highlight_type type;
- size_t start, len;
-};
-
-struct highlight_span *highlight_config(const char *config);
diff --git a/ui/syntax/syntaxedit.c b/ui/syntax/syntaxedit.c
deleted file mode 100644
index 80cff689..00000000
--- a/ui/syntax/syntaxedit.c
+++ /dev/null
@@ -1,411 +0,0 @@
-/* SPDX-License-Identifier: MIT
- *
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
- */
-
-#include <stdlib.h>
-#include <stdbool.h>
-#include <stdint.h>
-#include <string.h>
-#include <assert.h>
-#include <windows.h>
-#include <windowsx.h>
-#include <richedit.h>
-#include <richole.h>
-#include <tom.h>
-
-#include "syntaxedit.h"
-#include "highlighter.h"
-
-const GUID CDECL IID_ITextDocument = { 0x8CC497C0, 0xA1DF, 0x11CE, { 0x80, 0x98, 0x00, 0xAA, 0x00, 0x47, 0xBE, 0x5D } };
-
-struct syntaxedit_data {
- IRichEditOle *irich;
- ITextDocument *idoc;
- enum block_state last_block_state;
- LONG yheight;
- bool highlight_guard;
-};
-
-static WNDPROC parent_proc;
-
-struct span_style {
- COLORREF color;
- DWORD effects;
-};
-
-static const struct span_style stylemap[] = {
- [HighlightSection] = { .color = RGB(0x32, 0x6D, 0x74), .effects = CFE_BOLD },
- [HighlightField] = { .color = RGB(0x9B, 0x23, 0x93), .effects = CFE_BOLD },
- [HighlightPrivateKey] = { .color = RGB(0x64, 0x38, 0x20) },
- [HighlightPublicKey] = { .color = RGB(0x64, 0x38, 0x20) },
- [HighlightPresharedKey] = { .color = RGB(0x64, 0x38, 0x20) },
- [HighlightIP] = { .color = RGB(0x0E, 0x0E, 0xFF) },
- [HighlightCidr] = { .color = RGB(0x81, 0x5F, 0x03) },
- [HighlightHost] = { .color = RGB(0x0E, 0x0E, 0xFF) },
- [HighlightPort] = { .color = RGB(0x81, 0x5F, 0x03) },
- [HighlightMTU] = { .color = RGB(0x1C, 0x00, 0xCF) },
- [HighlightKeepalive] = { .color = RGB(0x1C, 0x00, 0xCF) },
- [HighlightComment] = { .color = RGB(0x53, 0x65, 0x79), .effects = CFE_ITALIC },
- [HighlightDelimiter] = { .color = RGB(0x00, 0x00, 0x00) },
-#ifndef MOBILE_WGQUICK_SUBSET
- [HighlightTable] = { .color = RGB(0x1C, 0x00, 0xCF) },
- [HighlightFwMark] = { .color = RGB(0x1C, 0x00, 0xCF) },
- [HighlightSaveConfig] = { .color = RGB(0x81, 0x5F, 0x03) },
- [HighlightCmd] = { .color = RGB(0x63, 0x75, 0x89) },
-#endif
- [HighlightError] = { .color = RGB(0xC4, 0x1A, 0x16), .effects = CFE_UNDERLINE }
-};
-
-static void evaluate_untunneled_blocking(struct syntaxedit_data *this, HWND hWnd, const char *msg, struct highlight_span *spans)
-{
- enum block_state state = InevaluableBlockingUntunneledTraffic;
- bool on_allowedips = false;
- bool seen_peer = false;
- bool seen_v6_00 = false, seen_v4_00 = false;
- bool seen_v6_01 = false, seen_v6_80001 = false, seen_v4_01 = false, seen_v4_1281 = false;
-
- for (struct highlight_span *span = spans; span->type != HighlightEnd; ++span) {
- switch (span->type) {
- case HighlightError:
- goto done;
- case HighlightSection:
- if (span->len != 6 || strncasecmp(&msg[span->start], "[peer]", 6))
- break;
- if (!seen_peer)
- seen_peer = true;
- else
- goto done;
- break;
- case HighlightField:
- on_allowedips = span->len == 10 && !strncasecmp(&msg[span->start], "allowedips", 10);
- break;
- case HighlightIP:
- if (!on_allowedips || !seen_peer)
- break;
- if ((span + 1)->type != HighlightDelimiter || (span + 2)->type != HighlightCidr)
- break;
- if ((span + 2)->len != 1)
- break;
- if (msg[(span + 2)->start] == '0') {
- if (span->len == 7 && !strncmp(&msg[span->start], "0.0.0.0", 7))
- seen_v4_00 = true;
- else if (span->len == 2 && !strncmp(&msg[span->start], "::", 2))
- seen_v6_00 = true;
- } else if (msg[(span + 2)->start] == '1') {
- if (span->len == 7 && !strncmp(&msg[span->start], "0.0.0.0", 7))
- seen_v4_01 = true;
- else if (span->len == 9 && !strncmp(&msg[span->start], "128.0.0.0", 9))
- seen_v4_1281 = true;
- else if (span->len == 2 && !strncmp(&msg[span->start], "::", 2))
- seen_v6_01 = true;
- else if (span->len == 6 && !strncmp(&msg[span->start], "8000::", 6))
- seen_v6_80001 = true;
- }
- break;
- }
- }
-
- if (seen_v4_00 || seen_v6_00)
- state = BlockingUntunneledTraffic;
- else if ((seen_v4_01 && seen_v4_1281) || (seen_v6_01 && seen_v6_80001))
- state = NotBlockingUntunneledTraffic;
-
-done:
- if (state != this->last_block_state) {
- SendMessage(hWnd, SE_TRAFFIC_BLOCK, 0, state);
- this->last_block_state = state;
- }
-}
-
-static void highlight_text(HWND hWnd)
-{
- struct syntaxedit_data *this = (struct syntaxedit_data *)GetWindowLongPtr(hWnd, GWLP_USERDATA);
- GETTEXTLENGTHEX gettextlengthex = {
- .flags = GTL_NUMBYTES,
- .codepage = CP_ACP /* Probably CP_UTF8 would be better, but (wine at least) returns utf32 sizes. */
- };
- GETTEXTEX gettextex = {
- .flags = GT_NOHIDDENTEXT,
- .codepage = gettextlengthex.codepage
- };
- CHARFORMAT2 format = {
- .cbSize = sizeof(CHARFORMAT2),
- .dwMask = CFM_COLOR | CFM_CHARSET | CFM_SIZE | CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE,
- .dwEffects = CFE_AUTOCOLOR,
- .yHeight = this->yheight ?: 20 * 10,
- .bCharSet = ANSI_CHARSET
- };
- LRESULT msg_size;
- char *msg = NULL;
- struct highlight_span *spans = NULL;
- CHARRANGE orig_selection;
- POINT original_scroll;
- bool found_private_key = false;
- COLORREF bg_color, bg_inversion;
-
- if (this->highlight_guard)
- return;
- this->highlight_guard = true;
-
- msg_size = SendMessage(hWnd, EM_GETTEXTLENGTHEX, (WPARAM)&gettextlengthex, 0);
- if (msg_size == E_INVALIDARG)
- return;
- gettextex.cb = msg_size + 1;
-
- msg = malloc(msg_size + 1);
- if (!msg)
- goto out;
- if (SendMessage(hWnd, EM_GETTEXTEX, (WPARAM)&gettextex, (LPARAM)msg) <= 0)
- goto out;
-
- /* By default we get CR not CRLF, so just convert to LF. */
- for (size_t i = 0; i < msg_size; ++i) {
- if (msg[i] == '\r')
- msg[i] = '\n';
- }
-
- spans = highlight_config(msg);
- if (!spans)
- goto out;
-
- evaluate_untunneled_blocking(this, hWnd, msg, spans);
-
- this->idoc->lpVtbl->Undo(this->idoc, tomSuspend, NULL);
- SendMessage(hWnd, EM_SETEVENTMASK, 0, 0);
- SendMessage(hWnd, WM_SETREDRAW, FALSE, 0);
- SendMessage(hWnd, EM_EXGETSEL, 0, (LPARAM)&orig_selection);
- SendMessage(hWnd, EM_GETSCROLLPOS, 0, (LPARAM)&original_scroll);
- SendMessage(hWnd, EM_HIDESELECTION, TRUE, 0);
- SendMessage(hWnd, EM_SETCHARFORMAT, SCF_ALL, (LPARAM)&format);
- bg_color = GetSysColor(COLOR_WINDOW);
- bg_inversion = (bg_color & RGB(0xFF, 0xFF, 0xFF)) ^ RGB(0xFF, 0xFF, 0xFF);
- SendMessage(hWnd, EM_SETBKGNDCOLOR, 0, bg_color);
- for (struct highlight_span *span = spans; span->type != HighlightEnd; ++span) {
- CHARRANGE selection = { span->start, span->len + span->start };
- SendMessage(hWnd, EM_EXSETSEL, 0, (LPARAM)&selection);
- format.crTextColor = stylemap[span->type].color ^ bg_inversion;
- format.dwEffects = stylemap[span->type].effects;
- SendMessage(hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&format);
- if (span->type == HighlightPrivateKey && !found_private_key) {
- /* Rather than allocating a new string, we mangle this one, since (for now) we don't use msg again. */
- msg[span->start + span->len] = '\0';
- SendMessage(hWnd, SE_PRIVATE_KEY, 0, (LPARAM)&msg[span->start]);
- found_private_key = true;
- }
- }
- SendMessage(hWnd, EM_SETSCROLLPOS, 0, (LPARAM)&original_scroll);
- SendMessage(hWnd, EM_EXSETSEL, 0, (LPARAM)&orig_selection);
- SendMessage(hWnd, EM_HIDESELECTION, FALSE, 0);
- SendMessage(hWnd, WM_SETREDRAW, TRUE, 0);
- RedrawWindow(hWnd, NULL, NULL, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN);
- SendMessage(hWnd, EM_SETEVENTMASK, 0, ENM_CHANGE);
- this->idoc->lpVtbl->Undo(this->idoc, tomResume, NULL);
- if (!found_private_key)
- SendMessage(hWnd, SE_PRIVATE_KEY, 0, 0);
-
-out:
- free(spans);
- free(msg);
- this->highlight_guard = false;
-}
-
-static void context_menu(HWND hWnd, INT x, INT y)
-{
- GETTEXTLENGTHEX gettextlengthex = {
- .flags = GTL_DEFAULT,
- .codepage = CP_ACP
- };
- /* This disturbing hack grabs the system edit menu normally used for the EDIT control. */
- HMENU popup, menu = LoadMenuW(GetModuleHandleW(L"comctl32.dll"), MAKEINTRESOURCEW(1));
- CHARRANGE selection = { 0 };
- bool has_selection, can_selectall, can_undo, can_paste;
- UINT cmd;
-
- if (!menu)
- return;
-
- SendMessage(hWnd, EM_EXGETSEL, 0, (LPARAM)&selection);
- has_selection = selection.cpMax - selection.cpMin;
- can_selectall = selection.cpMin || (selection.cpMax < SendMessage(hWnd, EM_GETTEXTLENGTHEX, (WPARAM)&gettextlengthex, 0));
- can_undo = SendMessage(hWnd, EM_CANUNDO, 0, 0);
- can_paste = SendMessage(hWnd, EM_CANPASTE, CF_TEXT, 0);
-
- popup = GetSubMenu(menu, 0);
- EnableMenuItem(popup, WM_UNDO, MF_BYCOMMAND | (can_undo ? MF_ENABLED : MF_GRAYED));
- EnableMenuItem(popup, WM_CUT, MF_BYCOMMAND | (has_selection ? MF_ENABLED : MF_GRAYED));
- EnableMenuItem(popup, WM_COPY, MF_BYCOMMAND | (has_selection ? MF_ENABLED : MF_GRAYED));
- EnableMenuItem(popup, WM_PASTE, MF_BYCOMMAND | (can_paste ? MF_ENABLED : MF_GRAYED));
- EnableMenuItem(popup, WM_CLEAR, MF_BYCOMMAND | (has_selection ? MF_ENABLED : MF_GRAYED));
- EnableMenuItem(popup, EM_SETSEL, MF_BYCOMMAND | (can_selectall ? MF_ENABLED : MF_GRAYED));
-
- /* Delete items that we don't handle. */
- for (int ctl = GetMenuItemCount(popup) - 1; ctl >= 0; --ctl) {
- MENUITEMINFOW menu_item = {
- .cbSize = sizeof(MENUITEMINFOW),
- .fMask = MIIM_FTYPE | MIIM_ID
- };
- if (!GetMenuItemInfoW(popup, ctl, MF_BYPOSITION, &menu_item))
- continue;
- if (menu_item.fType & MFT_SEPARATOR)
- continue;
- switch (menu_item.wID) {
- case WM_UNDO:
- case WM_CUT:
- case WM_COPY:
- case WM_PASTE:
- case WM_CLEAR:
- case EM_SETSEL:
- continue;
- }
- DeleteMenu(popup, ctl, MF_BYPOSITION);
- }
- /* Delete trailing and adjacent separators. */
- for (int ctl = GetMenuItemCount(popup) - 1, end = true; ctl >= 0; --ctl) {
- MENUITEMINFOW menu_item = {
- .cbSize = sizeof(MENUITEMINFOW),
- .fMask = MIIM_FTYPE
- };
- if (!GetMenuItemInfoW(popup, ctl, MF_BYPOSITION, &menu_item))
- continue;
- if (!(menu_item.fType & MFT_SEPARATOR)) {
- end = false;
- continue;
- }
- if (!end && ctl) {
- if (!GetMenuItemInfoW(popup, ctl - 1, MF_BYPOSITION, &menu_item))
- continue;
- if (!(menu_item.fType & MFT_SEPARATOR))
- continue;
- }
- DeleteMenu(popup, ctl, MF_BYPOSITION);
- }
-
- if (x == -1 && y == -1) {
- RECT rect;
- GetWindowRect(hWnd, &rect);
- x = (rect.left + rect.right) / 2;
- y = (rect.top + rect.bottom) / 2;
- }
-
- if (GetFocus() != hWnd)
- SetFocus(hWnd);
-
- cmd = TrackPopupMenu(popup, TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, x, y, 0, hWnd, NULL);
- if (cmd)
- SendMessage(hWnd, cmd, 0, cmd == EM_SETSEL ? -1 : 0);
-
- DestroyMenu(menu);
-}
-
-static LRESULT CALLBACK child_proc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
-{
- switch (Msg) {
- case WM_CREATE: {
- struct syntaxedit_data *this = calloc(1, sizeof(*this));
- SetWindowLong(hWnd, GWL_EXSTYLE, GetWindowLong(hWnd, GWL_EXSTYLE) & ~WS_EX_CLIENTEDGE);
- assert(this);
- SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)this);
- SendMessage(hWnd, EM_GETOLEINTERFACE, 0, (LPARAM)&this->irich);
- assert(this->irich);
- this->irich->lpVtbl->QueryInterface(this->irich, &IID_ITextDocument, (void **)&this->idoc);
- assert(this->idoc);
- SendMessage(hWnd, EM_SETEVENTMASK, 0, ENM_CHANGE);
- SendMessage(hWnd, EM_SETTEXTMODE, TM_SINGLECODEPAGE, 0);
- break;
- }
- case WM_DESTROY: {
- struct syntaxedit_data *this = (struct syntaxedit_data *)GetWindowLongPtr(hWnd, GWLP_USERDATA);
- this->idoc->lpVtbl->Release(this->idoc);
- this->irich->lpVtbl->Release(this->irich);
- free(this);
- }
- case WM_SETTEXT: {
- LRESULT ret = parent_proc(hWnd, Msg, wParam, lParam);
- highlight_text(hWnd);
- SendMessage(hWnd, EM_EMPTYUNDOBUFFER, 0, 0);
- return ret;
- }
- case SE_SET_PARENT_DPI: {
- struct syntaxedit_data *this = (struct syntaxedit_data *)GetWindowLongPtr(hWnd, GWLP_USERDATA);
- HDC hdc = GetDC(hWnd);
- if (this->yheight)
- SendMessage(hWnd, EM_SETZOOM, GetDeviceCaps(hdc, LOGPIXELSY), wParam);
- this->yheight = MulDiv(20 * 10, wParam, GetDeviceCaps(hdc, LOGPIXELSY));
- ReleaseDC(hWnd, hdc);
- highlight_text(hWnd);
- return 0;
- }
- case WM_REFLECT + WM_COMMAND:
- case WM_COMMAND:
- case WM_REFLECT + WM_NOTIFY:
- case WM_NOTIFY:
- switch (HIWORD(wParam)) {
- case EN_CHANGE:
- highlight_text(hWnd);
- break;
- }
- break;
- case WM_PASTE:
- SendMessage(hWnd, EM_PASTESPECIAL, CF_TEXT, 0);
- return 0;
- case WM_KEYDOWN: {
- WORD key = LOWORD(wParam);
- if ((key == 'V' && GetKeyState(VK_CONTROL) < 0) ||
- (key == VK_INSERT && GetKeyState(VK_SHIFT) < 0)) {
- SendMessage(hWnd, EM_PASTESPECIAL, CF_TEXT, 0);
- return 0;
- }
- break;
- }
- case WM_CONTEXTMENU:
- context_menu(hWnd, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
- return 0;
- case WM_THEMECHANGED:
- highlight_text(hWnd);
- break;
- case WM_GETDLGCODE: {
- MSG *m = (MSG *)lParam;
- LRESULT lres = parent_proc(hWnd, Msg, wParam, lParam);
- lres &= ~DLGC_WANTTAB;
- if (m && m->message == WM_KEYDOWN && m->wParam == VK_TAB && GetKeyState(VK_CONTROL) >= 0)
- lres &= ~DLGC_WANTMESSAGE;
- return lres;
- }
- }
- return parent_proc(hWnd, Msg, wParam, lParam);
-}
-
-static long has_loaded = 0;
-
-bool register_syntax_edit(void)
-{
- WNDCLASSEXW class = { .cbSize = sizeof(WNDCLASSEXW) };
- WNDPROC pp;
- HANDLE lib;
-
- if (InterlockedCompareExchange(&has_loaded, 1, 0) != 0)
- return !!parent_proc;
-
- lib = LoadLibraryExW(L"msftedit.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
- if (!lib)
- return false;
-
- if (!GetClassInfoExW(NULL, L"RICHEDIT50W", &class))
- goto err;
- pp = class.lpfnWndProc;
- if (!pp)
- goto err;
- class.cbSize = sizeof(WNDCLASSEXW);
- class.hInstance = GetModuleHandleW(NULL);
- class.lpszClassName = L"WgQuickSyntaxEdit";
- class.lpfnWndProc = child_proc;
- if (!RegisterClassExW(&class))
- goto err;
- parent_proc = pp;
- return true;
-
-err:
- FreeLibrary(lib);
- return false;
-}
diff --git a/ui/syntax/syntaxedit.go b/ui/syntax/syntaxedit.go
index 257bdbd2..a3c94d86 100644
--- a/ui/syntax/syntaxedit.go
+++ b/ui/syntax/syntaxedit.go
@@ -1,35 +1,41 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package syntax
import (
"errors"
+ "fmt"
"strings"
+ "sync/atomic"
"syscall"
"unsafe"
"github.com/lxn/walk"
"github.com/lxn/win"
+ "golang.org/x/sys/windows"
)
-// #cgo LDFLAGS: -lgdi32
-// #include "syntaxedit.h"
-import "C"
-
type SyntaxEdit struct {
walk.WidgetBase
+ irich *win.IRichEditOle
+ idoc *win.ITextDocument
+ lastBlockState BlockState
+ yheight int
+ highlightGuard uint32
textChangedPublisher walk.EventPublisher
privateKeyPublisher walk.StringEventPublisher
blockUntunneledTrafficPublisher walk.IntEventPublisher
}
+type BlockState int
+
const (
- InevaluableBlockingUntunneledTraffic = C.InevaluableBlockingUntunneledTraffic
- BlockingUntunneledTraffic = C.BlockingUntunneledTraffic
- NotBlockingUntunneledTraffic = C.NotBlockingUntunneledTraffic
+ InevaluableBlockingUntunneledTraffic BlockState = iota
+ BlockingUntunneledTraffic
+ NotBlockingUntunneledTraffic
)
func (se *SyntaxEdit) LayoutFlags() walk.LayoutFlags {
@@ -63,7 +69,6 @@ func (se *SyntaxEdit) SetText(text string) (err error) {
if win.TRUE != se.SendMessage(win.WM_SETTEXT, 0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(text)))) {
err = errors.New("WM_SETTEXT failed")
}
- se.textChangedPublisher.Publish()
return
}
@@ -79,59 +84,420 @@ func (se *SyntaxEdit) BlockUntunneledTrafficStateChanged() *walk.IntEvent {
return se.blockUntunneledTrafficPublisher.Event()
}
-func (se *SyntaxEdit) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr {
+type spanStyle struct {
+ color win.COLORREF
+ effects uint32
+}
+
+var stylemap = map[highlight]spanStyle{
+ highlightSection: {color: win.RGB(0x32, 0x6D, 0x74), effects: win.CFE_BOLD},
+ highlightField: {color: win.RGB(0x9B, 0x23, 0x93), effects: win.CFE_BOLD},
+ highlightPrivateKey: {color: win.RGB(0x64, 0x38, 0x20)},
+ highlightPublicKey: {color: win.RGB(0x64, 0x38, 0x20)},
+ highlightPresharedKey: {color: win.RGB(0x64, 0x38, 0x20)},
+ highlightIP: {color: win.RGB(0x0E, 0x0E, 0xFF)},
+ highlightCidr: {color: win.RGB(0x81, 0x5F, 0x03)},
+ highlightHost: {color: win.RGB(0x0E, 0x0E, 0xFF)},
+ highlightPort: {color: win.RGB(0x81, 0x5F, 0x03)},
+ highlightMTU: {color: win.RGB(0x1C, 0x00, 0xCF)},
+ highlightTable: {color: win.RGB(0x1C, 0x00, 0xCF)},
+ highlightKeepalive: {color: win.RGB(0x1C, 0x00, 0xCF)},
+ highlightComment: {color: win.RGB(0x53, 0x65, 0x79), effects: win.CFE_ITALIC},
+ highlightDelimiter: {color: win.RGB(0x00, 0x00, 0x00)},
+ highlightCmd: {color: win.RGB(0x63, 0x75, 0x89)},
+ highlightError: {color: win.RGB(0xC4, 0x1A, 0x16), effects: win.CFE_UNDERLINE},
+}
+
+func (se *SyntaxEdit) evaluateUntunneledBlocking(cfg string, spans []highlightSpan) {
+ state := InevaluableBlockingUntunneledTraffic
+ var onAllowedIPs,
+ onTable,
+ tableOff,
+ seenPeer,
+ seen00v6,
+ seen00v4,
+ seen01v6,
+ seen80001v6,
+ seen01v4,
+ seen1281v4 bool
+
+ for i := range spans {
+ span := &spans[i]
+ switch span.t {
+ case highlightError:
+ goto done
+ case highlightSection:
+ if !strings.EqualFold(cfg[span.s:span.s+span.len], "[Peer]") {
+ break
+ }
+ if !seenPeer {
+ seenPeer = true
+ } else {
+ goto done
+ }
+ case highlightField:
+ onAllowedIPs = strings.EqualFold(cfg[span.s:span.s+span.len], "AllowedIPs")
+ onTable = strings.EqualFold(cfg[span.s:span.s+span.len], "Table")
+ case highlightTable:
+ if onTable {
+ tableOff = cfg[span.s:span.s+span.len] == "off"
+ }
+ case highlightIP:
+ if !onAllowedIPs || !seenPeer {
+ break
+ }
+ if i+2 >= len(spans) || spans[i+1].t != highlightDelimiter || spans[i+2].t != highlightCidr {
+ break
+ }
+ if spans[i+2].len != 1 {
+ break
+ }
+ switch cfg[spans[i+2].s] {
+ case '0':
+ switch cfg[span.s : span.s+span.len] {
+ case "0.0.0.0":
+ seen00v4 = true
+ case "::":
+ seen00v6 = true
+ }
+ case '1':
+ switch cfg[span.s : span.s+span.len] {
+ case "0.0.0.0":
+ seen01v4 = true
+ case "128.0.0.0":
+ seen1281v4 = true
+ case "::":
+ seen01v6 = true
+ case "8000::":
+ seen80001v6 = true
+ }
+ }
+ }
+ }
+ if tableOff {
+ return
+ }
+
+ if seen00v4 || seen00v6 {
+ state = BlockingUntunneledTraffic
+ } else if (seen01v4 && seen1281v4) || (seen01v6 && seen80001v6) {
+ state = NotBlockingUntunneledTraffic
+ }
+
+done:
+ if state != se.lastBlockState {
+ se.blockUntunneledTrafficPublisher.Publish(int(state))
+ se.lastBlockState = state
+ }
+}
+
+func (se *SyntaxEdit) highlightText() error {
+ if !atomic.CompareAndSwapUint32(&se.highlightGuard, 0, 1) {
+ return nil
+ }
+ defer atomic.StoreUint32(&se.highlightGuard, 0)
+
+ hWnd := se.Handle()
+ gettextlengthex := win.GETTEXTLENGTHEX{
+ Flags: win.GTL_NUMBYTES,
+ Codepage: win.CP_ACP, // Probably CP_UTF8 would be better, but (wine at least) returns utf32 sizes.
+ }
+ msgSize := uint32(win.SendMessage(hWnd, win.EM_GETTEXTLENGTHEX, uintptr(unsafe.Pointer(&gettextlengthex)), 0))
+ if msgSize == win.E_INVALIDARG {
+ return errors.New("Failed to get text length")
+ }
+
+ gettextex := win.GETTEXTEX{
+ Flags: win.GT_NOHIDDENTEXT,
+ Codepage: gettextlengthex.Codepage,
+ Cb: msgSize + 1,
+ }
+ msg := make([]byte, msgSize+1)
+ msgCount := win.SendMessage(hWnd, win.EM_GETTEXTEX, uintptr(unsafe.Pointer(&gettextex)), uintptr(unsafe.Pointer(&msg[0])))
+ if msgCount < 0 {
+ return errors.New("Failed to get text")
+ }
+ cfg := strings.Replace(string(msg[:msgCount]), "\r", "\n", -1)
+
+ spans := highlightConfig(cfg)
+ se.evaluateUntunneledBlocking(cfg, spans)
+
+ se.idoc.Undo(win.TomSuspend, nil)
+ win.SendMessage(hWnd, win.EM_SETEVENTMASK, 0, 0)
+ win.SendMessage(hWnd, win.WM_SETREDRAW, win.FALSE, 0)
+ var origSelection win.CHARRANGE
+ win.SendMessage(hWnd, win.EM_EXGETSEL, 0, uintptr(unsafe.Pointer(&origSelection)))
+ var origScroll win.POINT
+ win.SendMessage(hWnd, win.EM_GETSCROLLPOS, 0, uintptr(unsafe.Pointer(&origScroll)))
+ win.SendMessage(hWnd, win.EM_HIDESELECTION, win.TRUE, 0)
+ format := win.CHARFORMAT2{
+ CHARFORMAT: win.CHARFORMAT{
+ CbSize: uint32(unsafe.Sizeof(win.CHARFORMAT2{})),
+ DwMask: win.CFM_COLOR | win.CFM_CHARSET | win.CFM_SIZE | win.CFM_BOLD | win.CFM_ITALIC | win.CFM_UNDERLINE,
+ DwEffects: win.CFE_AUTOCOLOR,
+ BCharSet: win.ANSI_CHARSET,
+ },
+ }
+ if se.yheight != 0 {
+ format.YHeight = 20 * 10
+ }
+ win.SendMessage(hWnd, win.EM_SETCHARFORMAT, win.SCF_ALL, uintptr(unsafe.Pointer(&format)))
+ bgColor := win.COLORREF(win.GetSysColor(win.COLOR_WINDOW))
+ bgInversion := (bgColor & win.RGB(0xFF, 0xFF, 0xFF)) ^ win.RGB(0xFF, 0xFF, 0xFF)
+ win.SendMessage(hWnd, win.EM_SETBKGNDCOLOR, 0, uintptr(bgColor))
+ numSpans := len(spans)
+ foundPrivateKey := false
+ for i := range spans {
+ span := &spans[i]
+ if numSpans <= 2048 {
+ selection := win.CHARRANGE{int32(span.s), int32(span.s + span.len)}
+ win.SendMessage(hWnd, win.EM_EXSETSEL, 0, uintptr(unsafe.Pointer(&selection)))
+ format.CrTextColor = stylemap[span.t].color ^ bgInversion
+ format.DwEffects = stylemap[span.t].effects
+ win.SendMessage(hWnd, win.EM_SETCHARFORMAT, win.SCF_SELECTION, uintptr(unsafe.Pointer(&format)))
+ }
+ if span.t == highlightPrivateKey && !foundPrivateKey {
+ privateKey := cfg[span.s : span.s+span.len]
+ se.privateKeyPublisher.Publish(privateKey)
+ foundPrivateKey = true
+ }
+ }
+ win.SendMessage(hWnd, win.EM_SETSCROLLPOS, 0, uintptr(unsafe.Pointer(&origScroll)))
+ win.SendMessage(hWnd, win.EM_EXSETSEL, 0, uintptr(unsafe.Pointer(&origSelection)))
+ win.SendMessage(hWnd, win.EM_HIDESELECTION, win.FALSE, 0)
+ win.SendMessage(hWnd, win.WM_SETREDRAW, win.TRUE, 0)
+ win.RedrawWindow(hWnd, nil, 0, win.RDW_ERASE|win.RDW_FRAME|win.RDW_INVALIDATE|win.RDW_ALLCHILDREN)
+ win.SendMessage(hWnd, win.EM_SETEVENTMASK, 0, win.ENM_CHANGE)
+ se.idoc.Undo(win.TomResume, nil)
+ if !foundPrivateKey {
+ se.privateKeyPublisher.Publish("")
+ }
+ return nil
+}
+
+func (se *SyntaxEdit) contextMenu(x, y int32) error {
+ /* This disturbing hack grabs the system edit menu normally used for the EDIT control. */
+ comctl32UTF16, err := windows.UTF16PtrFromString("comctl32.dll")
+ if err != nil {
+ return err
+ }
+ comctl32Handle := win.GetModuleHandle(comctl32UTF16)
+ if comctl32Handle == 0 {
+ return errors.New("Failed to get comctl32.dll handle")
+ }
+ menu := win.LoadMenu(comctl32Handle, win.MAKEINTRESOURCE(1))
+ if menu == 0 {
+ return errors.New("Failed to load menu")
+ }
+ defer win.DestroyMenu(menu)
+
+ hWnd := se.Handle()
+ enableWhenSelected := uint32(win.MF_GRAYED)
+ var selection win.CHARRANGE
+ win.SendMessage(hWnd, win.EM_EXGETSEL, 0, uintptr(unsafe.Pointer(&selection)))
+ if selection.CpMin < selection.CpMax {
+ enableWhenSelected = win.MF_ENABLED
+ }
+ enableSelectAll := uint32(win.MF_GRAYED)
+ gettextlengthex := win.GETTEXTLENGTHEX{
+ Flags: win.GTL_DEFAULT,
+ Codepage: win.CP_ACP,
+ }
+ if selection.CpMin != 0 || (selection.CpMax < int32(win.SendMessage(hWnd, win.EM_GETTEXTLENGTHEX, uintptr(unsafe.Pointer(&gettextlengthex)), 0))) {
+ enableSelectAll = win.MF_ENABLED
+ }
+ enableUndo := uint32(win.MF_GRAYED)
+ if win.SendMessage(hWnd, win.EM_CANUNDO, 0, 0) != 0 {
+ enableUndo = win.MF_ENABLED
+ }
+ enablePaste := uint32(win.MF_GRAYED)
+ if win.SendMessage(hWnd, win.EM_CANPASTE, win.CF_TEXT, 0) != 0 {
+ enablePaste = win.MF_ENABLED
+ }
+
+ popup := win.GetSubMenu(menu, 0)
+ win.EnableMenuItem(popup, win.WM_UNDO, win.MF_BYCOMMAND|enableUndo)
+ win.EnableMenuItem(popup, win.WM_CUT, win.MF_BYCOMMAND|enableWhenSelected)
+ win.EnableMenuItem(popup, win.WM_COPY, win.MF_BYCOMMAND|enableWhenSelected)
+ win.EnableMenuItem(popup, win.WM_PASTE, win.MF_BYCOMMAND|enablePaste)
+ win.EnableMenuItem(popup, win.WM_CLEAR, win.MF_BYCOMMAND|enableWhenSelected)
+ win.EnableMenuItem(popup, win.EM_SETSEL, win.MF_BYCOMMAND|enableSelectAll)
+
+ // Delete items that we don't handle.
+ for ctl := win.GetMenuItemCount(popup) - 1; ctl >= 0; ctl-- {
+ menuItem := win.MENUITEMINFO{
+ CbSize: uint32(unsafe.Sizeof(win.MENUITEMINFO{})),
+ FMask: win.MIIM_FTYPE | win.MIIM_ID,
+ }
+ if !win.GetMenuItemInfo(popup, uint32(ctl), win.MF_BYPOSITION, &menuItem) {
+ continue
+ }
+ if (menuItem.FType & win.MFT_SEPARATOR) != 0 {
+ continue
+ }
+ switch menuItem.WID {
+ case win.WM_UNDO, win.WM_CUT, win.WM_COPY, win.WM_PASTE, win.WM_CLEAR, win.EM_SETSEL:
+ continue
+ }
+ win.DeleteMenu(popup, uint32(ctl), win.MF_BYPOSITION)
+ }
+ // Delete trailing and adjacent separators.
+ end := true
+ for ctl := win.GetMenuItemCount(popup) - 1; ctl >= 0; ctl-- {
+ menuItem := win.MENUITEMINFO{
+ CbSize: uint32(unsafe.Sizeof(win.MENUITEMINFO{})),
+ FMask: win.MIIM_FTYPE,
+ }
+ if !win.GetMenuItemInfo(popup, uint32(ctl), win.MF_BYPOSITION, &menuItem) {
+ continue
+ }
+ if (menuItem.FType & win.MFT_SEPARATOR) == 0 {
+ end = false
+ continue
+ }
+ if !end && ctl > 0 {
+ if !win.GetMenuItemInfo(popup, uint32(ctl-1), win.MF_BYPOSITION, &menuItem) {
+ continue
+ }
+ if (menuItem.FType & win.MFT_SEPARATOR) == 0 {
+ continue
+ }
+ }
+ win.DeleteMenu(popup, uint32(ctl), win.MF_BYPOSITION)
+ }
+
+ if x == -1 && y == -1 {
+ var rect win.RECT
+ win.GetWindowRect(hWnd, &rect)
+ x = (rect.Left + rect.Right) / 2
+ y = (rect.Top + rect.Bottom) / 2
+ }
+
+ if win.GetFocus() != hWnd {
+ win.SetFocus(hWnd)
+ }
+
+ cmd := win.TrackPopupMenu(popup, win.TPM_LEFTALIGN|win.TPM_RIGHTBUTTON|win.TPM_RETURNCMD|win.TPM_NONOTIFY, x, y, 0, hWnd, nil)
+ if cmd != 0 {
+ lParam := uintptr(0)
+ if cmd == win.EM_SETSEL {
+ lParam = ^uintptr(0)
+ }
+ win.SendMessage(hWnd, cmd, 0, lParam)
+ }
+
+ return nil
+}
+
+func (*SyntaxEdit) NeedsWmSize() bool {
+ return true
+}
+
+func (se *SyntaxEdit) WndProc(hWnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr {
switch msg {
- case win.WM_NOTIFY, win.WM_COMMAND:
+ case win.WM_DESTROY:
+ if se.idoc != nil {
+ se.idoc.Release()
+ }
+ if se.irich != nil {
+ se.irich.Release()
+ }
+
+ case win.WM_SETTEXT:
+ ret := se.WidgetBase.WndProc(hWnd, msg, wParam, lParam)
+ se.highlightText()
+ win.SendMessage(hWnd, win.EM_EMPTYUNDOBUFFER, 0, 0)
+ se.textChangedPublisher.Publish()
+ return ret
+
+ case win.WM_COMMAND, win.WM_NOTIFY:
switch win.HIWORD(uint32(wParam)) {
case win.EN_CHANGE:
+ se.highlightText()
se.textChangedPublisher.Publish()
}
- // This is a horrible trick from MFC where we reflect the event back to the child.
- se.SendMessage(msg+C.WM_REFLECT, wParam, lParam)
- case C.SE_PRIVATE_KEY:
- if lParam == 0 {
- se.privateKeyPublisher.Publish("")
- } else {
- se.privateKeyPublisher.Publish(C.GoString((*C.char)(unsafe.Pointer(lParam))))
+
+ case win.WM_PASTE:
+ win.SendMessage(hWnd, win.EM_PASTESPECIAL, win.CF_TEXT, 0)
+ return 0
+
+ case win.WM_KEYDOWN:
+ key := win.LOWORD(uint32(wParam))
+ if key == 'V' && win.GetKeyState(win.VK_CONTROL) < 0 ||
+ key == win.VK_INSERT && win.GetKeyState(win.VK_SHIFT) < 0 {
+ win.SendMessage(hWnd, win.EM_PASTESPECIAL, win.CF_TEXT, 0)
+ return 0
+ }
+
+ case win.WM_CONTEXTMENU:
+ se.contextMenu(win.GET_X_LPARAM(lParam), win.GET_Y_LPARAM(lParam))
+ return 0
+
+ case win.WM_THEMECHANGED:
+ se.highlightText()
+
+ case win.WM_GETDLGCODE:
+ m := (*win.MSG)(unsafe.Pointer(lParam))
+ ret := se.WidgetBase.WndProc(hWnd, msg, wParam, lParam)
+ ret &^= win.DLGC_WANTTAB
+ if m != nil && m.Message == win.WM_KEYDOWN && m.WParam == win.VK_TAB && win.GetKeyState(win.VK_CONTROL) >= 0 {
+ ret &^= win.DLGC_WANTMESSAGE
}
- case C.SE_TRAFFIC_BLOCK:
- se.blockUntunneledTrafficPublisher.Publish(int(lParam))
+ return ret
}
- return se.WidgetBase.WndProc(hwnd, msg, wParam, lParam)
+
+ return se.WidgetBase.WndProc(hWnd, msg, wParam, lParam)
}
func NewSyntaxEdit(parent walk.Container) (*SyntaxEdit, error) {
- C.register_syntax_edit()
+ const LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800
+ _, err := windows.LoadLibraryEx("msftedit.dll", 0, LOAD_LIBRARY_SEARCH_SYSTEM32)
+ if err != nil {
+ return nil, fmt.Errorf("Failed to load msftedit.dll: %w", err)
+ }
+
se := &SyntaxEdit{}
- err := walk.InitWidget(
+ if err := walk.InitWidget(
se,
parent,
- "WgQuickSyntaxEdit",
- C.SYNTAXEDIT_STYLE,
- C.SYNTAXEDIT_EXTSTYLE,
- )
- if err != nil {
+ win.MSFTEDIT_CLASS,
+ win.WS_CHILD|win.ES_MULTILINE|win.WS_VISIBLE|win.WS_VSCROLL|win.WS_BORDER|win.WS_HSCROLL|win.WS_TABSTOP|win.ES_WANTRETURN|win.ES_NOOLEDRAGDROP,
+ 0); err != nil {
return nil, err
}
- se.SendMessage(C.SE_SET_PARENT_DPI, uintptr(parent.DPI()), 0)
-
+ hWnd := se.Handle()
+ win.SetWindowLong(hWnd, win.GWL_EXSTYLE, win.GetWindowLong(hWnd, win.GWL_EXSTYLE)&^win.WS_EX_CLIENTEDGE)
+ win.SendMessage(hWnd, win.EM_GETOLEINTERFACE, 0, uintptr(unsafe.Pointer(&se.irich)))
+ var idoc unsafe.Pointer
+ se.irich.QueryInterface(&win.IID_ITextDocument, &idoc)
+ se.idoc = (*win.ITextDocument)(idoc)
+ win.SendMessage(hWnd, win.EM_SETEVENTMASK, 0, win.ENM_CHANGE)
+ win.SendMessage(hWnd, win.EM_SETTEXTMODE, win.TM_SINGLECODEPAGE, 0)
+ se.ApplyDPI(parent.DPI())
se.GraphicsEffects().Add(walk.InteractionEffect)
se.GraphicsEffects().Add(walk.FocusEffect)
se.MustRegisterProperty("Text", walk.NewProperty(
- func() interface{} {
+ func() any {
return se.Text()
},
- func(v interface{}) error {
+ func(v any) error {
if s, ok := v.(string); ok {
return se.SetText(s)
}
return se.SetText("")
},
se.textChangedPublisher.Event()))
-
return se, nil
}
func (se *SyntaxEdit) ApplyDPI(dpi int) {
- se.SendMessage(C.SE_SET_PARENT_DPI, uintptr(dpi), 0)
+ hWnd := se.Handle()
+ hdc := win.GetDC(hWnd)
+ logPixels := win.GetDeviceCaps(hdc, win.LOGPIXELSY)
+ if se.yheight != 0 {
+ win.SendMessage(hWnd, win.EM_SETZOOM, uintptr(logPixels), uintptr(dpi))
+ }
+ se.yheight = 20 * 10 * dpi / int(logPixels)
+ win.ReleaseDC(hWnd, hdc)
+ se.highlightText()
}
diff --git a/ui/syntax/syntaxedit.h b/ui/syntax/syntaxedit.h
deleted file mode 100644
index 048e7bc4..00000000
--- a/ui/syntax/syntaxedit.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/* SPDX-License-Identifier: MIT
- *
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
- */
-
-#ifndef SYNTAXEDIT_H
-#define SYNTAXEDIT_H
-
-#include <stdbool.h>
-#include <windows.h>
-#include <richedit.h>
-
-#define SYNTAXEDIT_STYLE (WS_CHILD | ES_MULTILINE | WS_VISIBLE | WS_VSCROLL | WS_BORDER | WS_HSCROLL | WS_TABSTOP | ES_WANTRETURN | ES_NOOLEDRAGDROP)
-#define SYNTAXEDIT_EXTSTYLE (0)
-
-/* The old MFC reflection trick. */
-#define WM_REFLECT (WM_USER + 0x1C00)
-
-#define SE_PRIVATE_KEY (WM_USER + 0x3100)
-#define SE_TRAFFIC_BLOCK (WM_USER + 0x3101)
-#define SE_SET_PARENT_DPI (WM_USER + 0x3102)
-
-enum block_state {
- InevaluableBlockingUntunneledTraffic,
- BlockingUntunneledTraffic,
- NotBlockingUntunneledTraffic
-};
-
-extern bool register_syntax_edit(void);
-
-#endif
diff --git a/ui/tray.go b/ui/tray.go
index 810c759b..7771c966 100644
--- a/ui/tray.go
+++ b/ui/tray.go
@@ -1,17 +1,17 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ui
import (
- "fmt"
"sort"
"strings"
"time"
"golang.zx2c4.com/wireguard/windows/conf"
+ "golang.zx2c4.com/wireguard/windows/l18n"
"golang.zx2c4.com/wireguard/windows/manager"
"github.com/lxn/walk"
@@ -24,7 +24,8 @@ type Tray struct {
*walk.NotifyIcon
// Current known tunnels by name
- tunnels map[string]*walk.Action
+ tunnels map[string]*walk.Action
+ tunnelsAreInBreakoutMenu bool
mtw *ManageTunnelsWindow
@@ -53,7 +54,7 @@ func NewTray(mtw *ManageTunnelsWindow) (*Tray, error) {
func (tray *Tray) setup() error {
tray.clicked = tray.onManageTunnels
- tray.SetToolTip("WireGuard: Deactivated")
+ tray.SetToolTip(l18n.Sprintf("WireGuard: Deactivated"))
tray.SetVisible(true)
if icon, err := loadLogoIcon(16); err == nil {
tray.SetIcon(icon)
@@ -76,15 +77,15 @@ func (tray *Tray) setup() error {
separator bool
defawlt bool
}{
- {label: "Status: Unknown"},
- {label: "Addresses: None", hidden: true},
+ {label: l18n.Sprintf("Status: Unknown")},
+ {label: l18n.Sprintf("Addresses: None"), hidden: true},
{separator: true},
{separator: true},
- {label: "&Manage tunnels…", handler: tray.onManageTunnels, enabled: true, defawlt: true},
- {label: "&Import tunnel(s) from file…", handler: tray.onImport, enabled: true},
+ {label: l18n.Sprintf("&Manage tunnels…"), handler: tray.onManageTunnels, enabled: true, defawlt: true},
+ {label: l18n.Sprintf("&Import tunnel(s) from file…"), handler: tray.onImport, enabled: true, hidden: !IsAdmin},
{separator: true},
- {label: "&About WireGuard…", handler: tray.onAbout, enabled: true},
- {label: "E&xit", handler: onQuit, enabled: true},
+ {label: l18n.Sprintf("&About WireGuard…"), handler: tray.onAbout, enabled: true},
+ {label: l18n.Sprintf("E&xit"), handler: onQuit, enabled: true, hidden: !IsAdmin},
} {
var action *walk.Action
if item.separator {
@@ -144,6 +145,17 @@ func (tray *Tray) onTunnelsChange() {
})
}
+func (tray *Tray) sortedTunnels() []string {
+ var names []string
+ for name := range tray.tunnels {
+ names = append(names, name)
+ }
+ sort.SliceStable(names, func(i, j int) bool {
+ return conf.TunnelNameIsLess(names[i], names[j])
+ })
+ return names
+}
+
func (tray *Tray) addTunnelAction(tunnel *manager.Tunnel) {
tunnelAction := walk.NewAction()
tunnelAction.SetText(tunnel.Name)
@@ -160,11 +172,11 @@ func (tray *Tray) addTunnelAction(tunnel *manager.Tunnel) {
tray.mtw.tunnelsPage.listView.selectTunnel(tclosure.Name)
tray.mtw.tabs.SetCurrentIndex(0)
if oldState == manager.TunnelUnknown {
- showErrorCustom(tray.mtw, "Failed to determine tunnel state", err.Error())
+ showErrorCustom(tray.mtw, l18n.Sprintf("Failed to determine tunnel state"), err.Error())
} else if oldState == manager.TunnelStopped {
- showErrorCustom(tray.mtw, "Failed to activate tunnel", err.Error())
+ showErrorCustom(tray.mtw, l18n.Sprintf("Failed to activate tunnel"), err.Error())
} else if oldState == manager.TunnelStarted {
- showErrorCustom(tray.mtw, "Failed to deactivate tunnel", err.Error())
+ showErrorCustom(tray.mtw, l18n.Sprintf("Failed to deactivate tunnel"), err.Error())
}
})
}
@@ -172,25 +184,22 @@ func (tray *Tray) addTunnelAction(tunnel *manager.Tunnel) {
})
tray.tunnels[tunnel.Name] = tunnelAction
- var names []string
- for name := range tray.tunnels {
- names = append(names, name)
- }
- sort.SliceStable(names, func(i, j int) bool {
- return conf.TunnelNameIsLess(names[i], names[j])
- })
-
var (
idx int
name string
)
- for idx, name = range names {
+ for idx, name = range tray.sortedTunnels() {
if name == tunnel.Name {
break
}
}
- tray.ContextMenu().Actions().Insert(trayTunnelActionsOffset+idx, tunnelAction)
+ if tray.tunnelsAreInBreakoutMenu {
+ tray.ContextMenu().Actions().At(trayTunnelActionsOffset).Menu().Actions().Insert(idx, tunnelAction)
+ } else {
+ tray.ContextMenu().Actions().Insert(trayTunnelActionsOffset+idx, tunnelAction)
+ }
+ tray.rebalanceTunnelsMenu()
go func() {
state, err := tclosure.State()
@@ -198,23 +207,76 @@ func (tray *Tray) addTunnelAction(tunnel *manager.Tunnel) {
return
}
tray.mtw.Synchronize(func() {
- tray.SetTunnelState(&tclosure, state, false)
+ tray.setTunnelState(&tclosure, state)
})
}()
}
func (tray *Tray) removeTunnelAction(tunnelName string) {
- tray.ContextMenu().Actions().Remove(tray.tunnels[tunnelName])
+ if tray.tunnelsAreInBreakoutMenu {
+ tray.ContextMenu().Actions().At(trayTunnelActionsOffset).Menu().Actions().Remove(tray.tunnels[tunnelName])
+ } else {
+ tray.ContextMenu().Actions().Remove(tray.tunnels[tunnelName])
+ }
delete(tray.tunnels, tunnelName)
+ tray.rebalanceTunnelsMenu()
+}
+
+func (tray *Tray) rebalanceTunnelsMenu() {
+ if tray.tunnelsAreInBreakoutMenu && len(tray.tunnels) <= 10 {
+ menuAction := tray.ContextMenu().Actions().At(trayTunnelActionsOffset)
+ idx := 1
+ for _, name := range tray.sortedTunnels() {
+ tray.ContextMenu().Actions().Insert(trayTunnelActionsOffset+idx, tray.tunnels[name])
+ idx++
+ }
+ tray.ContextMenu().Actions().Remove(menuAction)
+ menuAction.Menu().Dispose()
+ tray.tunnelsAreInBreakoutMenu = false
+ } else if !tray.tunnelsAreInBreakoutMenu && len(tray.tunnels) > 10 {
+ menu, err := walk.NewMenu()
+ if err != nil {
+ return
+ }
+ for _, name := range tray.sortedTunnels() {
+ action := tray.tunnels[name]
+ menu.Actions().Add(action)
+ tray.ContextMenu().Actions().Remove(action)
+ }
+ menuAction, err := tray.ContextMenu().Actions().InsertMenu(trayTunnelActionsOffset, menu)
+ if err != nil {
+ return
+ }
+ menuAction.SetText(l18n.Sprintf("&Tunnels"))
+ tray.tunnelsAreInBreakoutMenu = true
+ }
}
-func (tray *Tray) onTunnelChange(tunnel *manager.Tunnel, state manager.TunnelState, globalState manager.TunnelState, err error) {
+func (tray *Tray) onTunnelChange(tunnel *manager.Tunnel, state, globalState manager.TunnelState, err error) {
tray.mtw.Synchronize(func() {
tray.updateGlobalState(globalState)
- tray.SetTunnelState(tunnel, state, err == nil)
- if !tray.mtw.Visible() && err != nil {
- tray.ShowError("WireGuard Tunnel Error", err.Error())
+ if err == nil {
+ tunnelAction := tray.tunnels[tunnel.Name]
+ if tunnelAction != nil {
+ wasChecked := tunnelAction.Checked()
+ switch state {
+ case manager.TunnelStarted:
+ if !wasChecked {
+ icon, _ := iconWithOverlayForState(state, 128)
+ tray.ShowCustom(l18n.Sprintf("WireGuard Activated"), l18n.Sprintf("The %s tunnel has been activated.", tunnel.Name), icon)
+ }
+
+ case manager.TunnelStopped:
+ if wasChecked {
+ icon, _ := loadSystemIcon("imageres", -31, 128) // TODO: this icon isn't very good...
+ tray.ShowCustom(l18n.Sprintf("WireGuard Deactivated"), l18n.Sprintf("The %s tunnel has been deactivated.", tunnel.Name), icon)
+ }
+ }
+ }
+ } else if !tray.mtw.Visible() {
+ tray.ShowError(l18n.Sprintf("WireGuard Tunnel Error"), err.Error())
}
+ tray.setTunnelState(tunnel, state)
})
}
@@ -225,85 +287,63 @@ func (tray *Tray) updateGlobalState(globalState manager.TunnelState) {
actions := tray.ContextMenu().Actions()
statusAction := actions.At(0)
- activeCIDRsAction := actions.At(1)
- setTunnelActionsEnabled := func(enabled bool) {
- for i := 0; i < len(tray.tunnels); i++ {
- action := actions.At(trayTunnelActionsOffset + i)
- action.SetEnabled(enabled)
- }
+ tray.SetToolTip(l18n.Sprintf("WireGuard: %s", textForState(globalState, true)))
+ stateText := textForState(globalState, false)
+ stateIcon, err := iconForState(globalState, 16)
+ if err == nil {
+ statusAction.SetImage(stateIcon)
}
+ statusAction.SetText(l18n.Sprintf("Status: %s", stateText))
- tray.SetToolTip(fmt.Sprintf("WireGuard: %s", textForState(globalState, true)))
- statusAction.SetText(fmt.Sprintf("Status: %s", textForState(globalState, false)))
-
- switch globalState {
- case manager.TunnelStarting:
- setTunnelActionsEnabled(false)
-
- case manager.TunnelStarted:
- activeCIDRsAction.SetVisible(true)
- setTunnelActionsEnabled(true)
-
- case manager.TunnelStopping:
- setTunnelActionsEnabled(false)
+ go func() {
+ var addrs []string
+ tunnels, err := manager.IPCClientTunnels()
+ if err == nil {
+ for i := range tunnels {
+ state, err := tunnels[i].State()
+ if err == nil && state == manager.TunnelStarted {
+ config, err := tunnels[i].RuntimeConfig()
+ if err == nil {
+ for _, addr := range config.Interface.Addresses {
+ addrs = append(addrs, addr.String())
+ }
+ }
+ }
+ }
+ }
+ tray.mtw.Synchronize(func() {
+ activeCIDRsAction := tray.ContextMenu().Actions().At(1)
+ activeCIDRsAction.SetText(l18n.Sprintf("Addresses: %s", strings.Join(addrs, l18n.EnumerationSeparator())))
+ activeCIDRsAction.SetVisible(len(addrs) > 0)
+ })
+ }()
- case manager.TunnelStopped:
- activeCIDRsAction.SetVisible(false)
- setTunnelActionsEnabled(true)
+ for _, action := range tray.tunnels {
+ action.SetEnabled(globalState == manager.TunnelStarted || globalState == manager.TunnelStopped)
}
}
-func (tray *Tray) SetTunnelState(tunnel *manager.Tunnel, state manager.TunnelState, showNotifications bool) {
+func (tray *Tray) setTunnelState(tunnel *manager.Tunnel, state manager.TunnelState) {
tunnelAction := tray.tunnels[tunnel.Name]
if tunnelAction == nil {
return
}
- actions := tray.ContextMenu().Actions()
- activeCIDRsAction := actions.At(1)
-
- wasChecked := tunnelAction.Checked()
-
switch state {
case manager.TunnelStarted:
- activeCIDRsAction.SetText("")
- go func() {
- config, err := tunnel.RuntimeConfig()
- if err == nil {
- var sb strings.Builder
- for i, addr := range config.Interface.Addresses {
- if i > 0 {
- sb.WriteString(", ")
- }
-
- sb.WriteString(addr.String())
- }
- tray.mtw.Synchronize(func() {
- activeCIDRsAction.SetText(fmt.Sprintf("Addresses: %s", sb.String()))
- })
- }
- }()
tunnelAction.SetEnabled(true)
tunnelAction.SetChecked(true)
- if !wasChecked && showNotifications {
- icon, _ := iconWithOverlayForState(state, 128)
- tray.ShowCustom("WireGuard Activated", fmt.Sprintf("The %s tunnel has been activated.", tunnel.Name), icon)
- }
case manager.TunnelStopped:
tunnelAction.SetChecked(false)
- if wasChecked && showNotifications {
- icon, _ := loadSystemIcon("imageres", 26, 128) // TODO: this icon isn't very good...
- tray.ShowCustom("WireGuard Deactivated", fmt.Sprintf("The %s tunnel has been deactivated.", tunnel.Name), icon)
- }
}
}
func (tray *Tray) UpdateFound() {
action := walk.NewAction()
- action.SetText("An Update is Available!")
- menuIcon, _ := loadSystemIcon("imageres", 1, 16)
+ action.SetText(l18n.Sprintf("An Update is Available!"))
+ menuIcon, _ := loadShieldIcon(16)
action.SetImage(menuIcon)
action.SetDefault(true)
showUpdateTab := func() {
@@ -318,8 +358,8 @@ func (tray *Tray) UpdateFound() {
tray.ContextMenu().Actions().Insert(tray.ContextMenu().Actions().Len()-2, action)
showUpdateBalloon := func() {
- icon, _ := loadSystemIcon("imageres", 1, 128)
- tray.ShowCustom("WireGuard Update Available", "An update to WireGuard is now available. You are advised to update as soon as possible.", icon)
+ icon, _ := loadShieldIcon(128)
+ tray.ShowCustom(l18n.Sprintf("WireGuard Update Available"), l18n.Sprintf("An update to WireGuard is now available. You are advised to update as soon as possible."), icon)
}
timeSinceStart := time.Now().Sub(startTime)
diff --git a/ui/tunnelspage.go b/ui/tunnelspage.go
index aed8d157..d104f598 100644
--- a/ui/tunnelspage.go
+++ b/ui/tunnelspage.go
@@ -1,14 +1,15 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ui
import (
"archive/zip"
+ "errors"
"fmt"
- "io/ioutil"
+ "io"
"os"
"path/filepath"
"sort"
@@ -17,6 +18,7 @@ import (
"github.com/lxn/walk"
"golang.zx2c4.com/wireguard/windows/conf"
+ "golang.zx2c4.com/wireguard/windows/l18n"
"golang.zx2c4.com/wireguard/windows/manager"
)
@@ -45,7 +47,7 @@ func NewTunnelsPage() (*TunnelsPage, error) {
}
disposables.Add(tp)
- tp.SetTitle("Tunnels")
+ tp.SetTitle(l18n.Sprintf("Tunnels"))
tp.SetLayout(walk.NewHBoxLayout())
tp.listContainer, _ = walk.NewComposite(tp)
@@ -74,6 +76,7 @@ func NewTunnelsPage() (*TunnelsPage, error) {
tp.fillerContainer.SetLayout(hlayout)
tp.fillerButton, _ = walk.NewPushButton(tp.fillerContainer)
tp.fillerButton.SetMinMaxSize(walk.Size{200, 0}, walk.Size{200, 0})
+ tp.fillerButton.SetVisible(IsAdmin)
tp.fillerButton.Clicked().Attach(func() {
if tp.fillerHandler != nil {
tp.fillerHandler()
@@ -88,8 +91,9 @@ func NewTunnelsPage() (*TunnelsPage, error) {
if err != nil {
return nil, err
}
- controlsContainer.SetLayout(walk.NewHBoxLayout())
- controlsContainer.Layout().SetMargins(walk.Margins{})
+ hlayout = walk.NewHBoxLayout()
+ hlayout.SetMargins(walk.Margins{})
+ controlsContainer.SetLayout(hlayout)
walk.NewHSpacer(controlsContainer)
@@ -101,8 +105,9 @@ func NewTunnelsPage() (*TunnelsPage, error) {
tp.listView.CurrentIndexChanged().Attach(func() {
editTunnel.SetEnabled(tp.listView.CurrentIndex() > -1)
})
- editTunnel.SetText("&Edit")
+ editTunnel.SetText(l18n.Sprintf("&Edit"))
editTunnel.Clicked().Attach(tp.onEditTunnel)
+ editTunnel.SetVisible(IsAdmin)
disposables.Spare()
@@ -131,6 +136,7 @@ func (tp *TunnelsPage) CreateToolbar() error {
hlayout := walk.NewHBoxLayout()
hlayout.SetMargins(walk.Margins{})
toolBarContainer.SetLayout(hlayout)
+ toolBarContainer.SetVisible(IsAdmin)
if tp.listToolbar, err = walk.NewToolBarWithOrientationAndButtonStyle(toolBarContainer, walk.Horizontal, walk.ToolBarButtonImageBeforeText); err != nil {
return err
@@ -142,24 +148,24 @@ func (tp *TunnelsPage) CreateToolbar() error {
}
tp.AddDisposable(addMenu)
importAction := walk.NewAction()
- importAction.SetText("&Import tunnel(s) from file…")
- importActionIcon, _ := loadSystemIcon("imageres", 3, 16)
+ importAction.SetText(l18n.Sprintf("&Import tunnel(s) from file…"))
+ importActionIcon, _ := loadSystemIcon("imageres", -3, 16)
importAction.SetImage(importActionIcon)
importAction.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyO})
importAction.SetDefault(true)
importAction.Triggered().Attach(tp.onImport)
addMenu.Actions().Add(importAction)
addAction := walk.NewAction()
- addAction.SetText("Add &empty tunnel…")
- addActionIcon, _ := loadSystemIcon("imageres", 2, 16)
+ addAction.SetText(l18n.Sprintf("Add &empty tunnel…"))
+ addActionIcon, _ := loadSystemIcon("imageres", -2, 16)
addAction.SetImage(addActionIcon)
addAction.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyN})
addAction.Triggered().Attach(tp.onAddTunnel)
addMenu.Actions().Add(addAction)
addMenuAction := walk.NewMenuAction(addMenu)
- addMenuActionIcon, _ := loadSystemIcon("shell32", 149, 16)
+ addMenuActionIcon, _ := loadSystemIcon("shell32", -258, 16)
addMenuAction.SetImage(addMenuActionIcon)
- addMenuAction.SetText("Add Tunnel")
+ addMenuAction.SetText(l18n.Sprintf("Add Tunnel"))
addMenuAction.SetToolTip(importAction.Text())
addMenuAction.Triggered().Attach(tp.onImport)
tp.listToolbar.Actions().Add(addMenuAction)
@@ -167,18 +173,18 @@ func (tp *TunnelsPage) CreateToolbar() error {
tp.listToolbar.Actions().Add(walk.NewSeparatorAction())
deleteAction := walk.NewAction()
- deleteActionIcon, _ := loadSystemIcon("shell32", 131, 16)
+ deleteActionIcon, _ := loadSystemIcon("shell32", -240, 16)
deleteAction.SetImage(deleteActionIcon)
deleteAction.SetShortcut(walk.Shortcut{0, walk.KeyDelete})
- deleteAction.SetToolTip("Remove selected tunnel(s)")
+ deleteAction.SetToolTip(l18n.Sprintf("Remove selected tunnel(s)"))
deleteAction.Triggered().Attach(tp.onDelete)
tp.listToolbar.Actions().Add(deleteAction)
tp.listToolbar.Actions().Add(walk.NewSeparatorAction())
exportAction := walk.NewAction()
- exportActionIcon, _ := loadSystemIcon("imageres", 165, 16) // Or "shell32", 45?
+ exportActionIcon, _ := loadSystemIcon("imageres", -174, 16)
exportAction.SetImage(exportActionIcon)
- exportAction.SetToolTip("Export all tunnels to zip…")
+ exportAction.SetToolTip(l18n.Sprintf("Export all tunnels to zip"))
exportAction.Triggered().Attach(tp.onExportTunnels)
tp.listToolbar.Actions().Add(exportAction)
@@ -195,43 +201,49 @@ func (tp *TunnelsPage) CreateToolbar() error {
}
tp.listView.AddDisposable(contextMenu)
toggleAction := walk.NewAction()
- toggleAction.SetText("&Toggle")
+ toggleAction.SetText(l18n.Sprintf("&Toggle"))
toggleAction.SetDefault(true)
toggleAction.Triggered().Attach(tp.onTunnelsViewItemActivated)
contextMenu.Actions().Add(toggleAction)
contextMenu.Actions().Add(walk.NewSeparatorAction())
importAction2 := walk.NewAction()
- importAction2.SetText("&Import tunnel(s) from file…")
+ importAction2.SetText(l18n.Sprintf("&Import tunnel(s) from file…"))
importAction2.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyO})
importAction2.Triggered().Attach(tp.onImport)
+ importAction2.SetVisible(IsAdmin)
contextMenu.Actions().Add(importAction2)
tp.ShortcutActions().Add(importAction2)
addAction2 := walk.NewAction()
- addAction2.SetText("Add &empty tunnel…")
+ addAction2.SetText(l18n.Sprintf("Add &empty tunnel…"))
addAction2.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyN})
addAction2.Triggered().Attach(tp.onAddTunnel)
+ addAction2.SetVisible(IsAdmin)
contextMenu.Actions().Add(addAction2)
tp.ShortcutActions().Add(addAction2)
exportAction2 := walk.NewAction()
- exportAction2.SetText("Export all tunnels to &zip…")
+ exportAction2.SetText(l18n.Sprintf("Export all tunnels to &zip…"))
exportAction2.Triggered().Attach(tp.onExportTunnels)
+ exportAction2.SetVisible(IsAdmin)
contextMenu.Actions().Add(exportAction2)
contextMenu.Actions().Add(walk.NewSeparatorAction())
editAction := walk.NewAction()
- editAction.SetText("Edit &selected tunnel…")
+ editAction.SetText(l18n.Sprintf("Edit &selected tunnel…"))
editAction.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyE})
+ editAction.SetVisible(IsAdmin)
editAction.Triggered().Attach(tp.onEditTunnel)
contextMenu.Actions().Add(editAction)
tp.ShortcutActions().Add(editAction)
deleteAction2 := walk.NewAction()
- deleteAction2.SetText("&Remove selected tunnel(s)")
+ deleteAction2.SetText(l18n.Sprintf("&Remove selected tunnel(s)"))
deleteAction2.SetShortcut(walk.Shortcut{0, walk.KeyDelete})
+ deleteAction2.SetVisible(IsAdmin)
deleteAction2.Triggered().Attach(tp.onDelete)
contextMenu.Actions().Add(deleteAction2)
tp.listView.ShortcutActions().Add(deleteAction2)
selectAllAction := walk.NewAction()
- selectAllAction.SetText("Select &all")
+ selectAllAction.SetText(l18n.Sprintf("Select &all"))
selectAllAction.SetShortcut(walk.Shortcut{walk.ModControl, walk.KeyA})
+ selectAllAction.SetVisible(IsAdmin)
selectAllAction.Triggered().Attach(tp.onSelectAll)
contextMenu.Actions().Add(selectAllAction)
tp.listView.ShortcutActions().Add(selectAllAction)
@@ -268,7 +280,7 @@ func (tp *TunnelsPage) updateConfView() {
func (tp *TunnelsPage) importFiles(paths []string) {
go func() {
- syncedMsgBox := func(title string, message string, flags walk.MsgBoxStyle) {
+ syncedMsgBox := func(title, message string, flags walk.MsgBoxStyle) {
tp.Synchronize(func() {
walk.MsgBox(tp.Form(), title, message, flags)
})
@@ -286,7 +298,7 @@ func (tp *TunnelsPage) importFiles(paths []string) {
for _, path := range paths {
switch strings.ToLower(filepath.Ext(path)) {
case ".conf":
- textConfig, err := ioutil.ReadFile(path)
+ textConfig, err := os.ReadFile(path)
if err != nil {
lastErr = err
continue
@@ -310,7 +322,7 @@ func (tp *TunnelsPage) importFiles(paths []string) {
lastErr = err
continue
}
- textConfig, err := ioutil.ReadAll(rc)
+ textConfig, err := io.ReadAll(rc)
rc.Close()
if err != nil {
lastErr = err
@@ -324,7 +336,10 @@ func (tp *TunnelsPage) importFiles(paths []string) {
}
if lastErr != nil || unparsedConfigs == nil {
- syncedMsgBox("Error", fmt.Sprintf("Could not import selected configuration: %v", lastErr), walk.MsgBoxIconWarning)
+ if lastErr == nil {
+ lastErr = errors.New(l18n.Sprintf("no configuration files were found"))
+ }
+ syncedMsgBox(l18n.Sprintf("Error"), l18n.Sprintf("Could not import selected configuration: %v", lastErr), walk.MsgBoxIconWarning)
return
}
@@ -335,7 +350,7 @@ func (tp *TunnelsPage) importFiles(paths []string) {
existingTunnelList, err := manager.IPCClientTunnels()
if err != nil {
- syncedMsgBox("Error", fmt.Sprintf("Could not enumerate existing tunnels: %v", lastErr), walk.MsgBoxIconWarning)
+ syncedMsgBox(l18n.Sprintf("Error"), l18n.Sprintf("Could not enumerate existing tunnels: %v", lastErr), walk.MsgBoxIconWarning)
return
}
existingLowerTunnels := make(map[string]bool, len(existingTunnelList))
@@ -347,7 +362,7 @@ func (tp *TunnelsPage) importFiles(paths []string) {
tp.listView.SetSuspendTunnelsUpdate(true)
for _, unparsedConfig := range unparsedConfigs {
if existingLowerTunnels[strings.ToLower(unparsedConfig.Name)] {
- lastErr = fmt.Errorf("Another tunnel already exists with the name ‘%s’", unparsedConfig.Name)
+ lastErr = errors.New(l18n.Sprintf("Another tunnel already exists with the name ‘%s’", unparsedConfig.Name))
continue
}
config, err := conf.FromWgQuickWithUnknownEncoding(unparsedConfig.Config, unparsedConfig.Name)
@@ -367,13 +382,13 @@ func (tp *TunnelsPage) importFiles(paths []string) {
m, n := configCount, len(unparsedConfigs)
switch {
case n == 1 && m != n:
- syncedMsgBox("Error", fmt.Sprintf("Unable to import configuration: %v", lastErr), walk.MsgBoxIconWarning)
+ syncedMsgBox(l18n.Sprintf("Error"), l18n.Sprintf("Unable to import configuration: %v", lastErr), walk.MsgBoxIconWarning)
case n == 1 && m == n:
// nothing
case m == n:
- syncedMsgBox("Imported tunnels", fmt.Sprintf("Imported %d tunnels", m), walk.MsgBoxIconInformation)
+ syncedMsgBox(l18n.Sprintf("Imported tunnels"), l18n.Sprintf("Imported %d tunnels", m), walk.MsgBoxIconInformation)
case m != n:
- syncedMsgBox("Imported tunnels", fmt.Sprintf("Imported %d of %d tunnels", m, n), walk.MsgBoxIconWarning)
+ syncedMsgBox(l18n.Sprintf("Imported tunnels"), l18n.Sprintf("Imported %d of %d tunnels", m, n), walk.MsgBoxIconWarning)
}
}()
}
@@ -385,16 +400,16 @@ func (tp *TunnelsPage) exportTunnels(filePath string) {
for _, tunnel := range tp.listView.model.tunnels {
cfg, err := tunnel.StoredConfig()
if err != nil {
- return fmt.Errorf("onExportTunnels: tunnel.StoredConfig failed: %v", err)
+ return fmt.Errorf("onExportTunnels: tunnel.StoredConfig failed: %w", err)
}
w, err := writer.Create(tunnel.Name + ".conf")
if err != nil {
- return fmt.Errorf("onExportTunnels: writer.Create failed: %v", err)
+ return fmt.Errorf("onExportTunnels: writer.Create failed: %w", err)
}
if _, err := w.Write(([]byte)(cfg.ToWgQuick())); err != nil {
- return fmt.Errorf("onExportTunnels: cfg.ToWgQuick failed: %v", err)
+ return fmt.Errorf("onExportTunnels: cfg.ToWgQuick failed: %w", err)
}
}
@@ -405,9 +420,8 @@ func (tp *TunnelsPage) exportTunnels(filePath string) {
func (tp *TunnelsPage) addTunnel(config *conf.Config) {
_, err := manager.IPCClientNewTunnel(config)
if err != nil {
- showErrorCustom(tp.Form(), "Unable to create tunnel", err.Error())
+ showErrorCustom(tp.Form(), l18n.Sprintf("Unable to create tunnel"), err.Error())
}
-
}
// Handlers
@@ -422,11 +436,11 @@ func (tp *TunnelsPage) onTunnelsViewItemActivated() {
if err != nil {
tp.Synchronize(func() {
if oldState == manager.TunnelUnknown {
- showErrorCustom(tp.Form(), "Failed to determine tunnel state", err.Error())
+ showErrorCustom(tp.Form(), l18n.Sprintf("Failed to determine tunnel state"), err.Error())
} else if oldState == manager.TunnelStopped {
- showErrorCustom(tp.Form(), "Failed to activate tunnel", err.Error())
+ showErrorCustom(tp.Form(), l18n.Sprintf("Failed to activate tunnel"), err.Error())
} else if oldState == manager.TunnelStarted {
- showErrorCustom(tp.Form(), "Failed to deactivate tunnel", err.Error())
+ showErrorCustom(tp.Form(), l18n.Sprintf("Failed to deactivate tunnel"), err.Error())
}
})
return
@@ -466,16 +480,20 @@ func (tp *TunnelsPage) onDelete() {
return
}
- var topic string
+ var title, question string
if len(indices) > 1 {
- topic = fmt.Sprintf("%d tunnels", len(indices))
+ tunnelCount := len(indices)
+ title = l18n.Sprintf("Delete %d tunnels", tunnelCount)
+ question = l18n.Sprintf("Are you sure you would like to delete %d tunnels?", tunnelCount)
} else {
- topic = fmt.Sprintf("‘%s’", tp.listView.model.tunnels[indices[0]].Name)
+ tunnelName := tp.listView.model.tunnels[indices[0]].Name
+ title = l18n.Sprintf("Delete tunnel ‘%s’", tunnelName)
+ question = l18n.Sprintf("Are you sure you would like to delete tunnel ‘%s’?", tunnelName)
}
if walk.DlgCmdNo == walk.MsgBox(
tp.Form(),
- fmt.Sprintf("Delete %s", topic),
- fmt.Sprintf("Are you sure you would like to delete %s? You cannot undo this action.", topic),
+ title,
+ l18n.Sprintf("%s You cannot undo this action.", question),
walk.MsgBoxYesNo|walk.MsgBoxIconWarning) {
return
}
@@ -500,7 +518,6 @@ func (tp *TunnelsPage) onDelete() {
tunnelsToDelete := make([]manager.Tunnel, len(indices))
for i, j := range indices {
tunnelsToDelete[i] = tp.listView.model.tunnels[j]
-
}
go func() {
tp.listView.SetSuspendTunnelsUpdate(true)
@@ -515,9 +532,9 @@ func (tp *TunnelsPage) onDelete() {
if len(errors) > 0 {
tp.listView.Synchronize(func() {
if len(errors) == 1 {
- showErrorCustom(tp.Form(), "Unable to delete tunnel", fmt.Sprintf("A tunnel was unable to be removed: %s", errors[0].Error()))
+ showErrorCustom(tp.Form(), l18n.Sprintf("Unable to delete tunnel"), l18n.Sprintf("A tunnel was unable to be removed: %s", errors[0].Error()))
} else {
- showErrorCustom(tp.Form(), "Unable to delete tunnels", fmt.Sprintf("%d tunnels were unable to be removed.", len(errors)))
+ showErrorCustom(tp.Form(), l18n.Sprintf("Unable to delete tunnels"), l18n.Sprintf("%d tunnels were unable to be removed.", len(errors)))
}
})
}
@@ -530,8 +547,8 @@ func (tp *TunnelsPage) onSelectAll() {
func (tp *TunnelsPage) onImport() {
dlg := walk.FileDialog{
- Filter: "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*",
- Title: "Import tunnel(s) from file",
+ Filter: l18n.Sprintf("Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*"),
+ Title: l18n.Sprintf("Import tunnel(s) from file"),
}
if ok, _ := dlg.ShowOpenMultiple(tp.Form()); !ok {
@@ -543,8 +560,8 @@ func (tp *TunnelsPage) onImport() {
func (tp *TunnelsPage) onExportTunnels() {
dlg := walk.FileDialog{
- Filter: "Configuration ZIP Files (*.zip)|*.zip",
- Title: "Export tunnels to zip",
+ Filter: l18n.Sprintf("Configuration ZIP Files (*.zip)|*.zip"),
+ Title: l18n.Sprintf("Export tunnels to zip"),
}
if ok, _ := dlg.ShowSave(tp.Form()); !ok {
@@ -571,7 +588,7 @@ func (tp *TunnelsPage) swapFiller(enabled bool) bool {
func (tp *TunnelsPage) onTunnelsChanged() {
if tp.swapFiller(tp.listView.model.RowCount() == 0) {
- tp.fillerButton.SetText("Import tunnel(s) from file")
+ tp.fillerButton.SetText(l18n.Sprintf("Import tunnel(s) from file"))
tp.fillerHandler = tp.onImport
}
}
@@ -581,8 +598,9 @@ func (tp *TunnelsPage) onSelectedTunnelsChanged() {
return
}
indices := tp.listView.SelectedIndexes()
- if tp.swapFiller(len(indices) > 1) {
- tp.fillerButton.SetText(fmt.Sprintf("Delete %d tunnels", len(indices)))
+ tunnelCount := len(indices)
+ if tp.swapFiller(tunnelCount > 1) {
+ tp.fillerButton.SetText(l18n.Sprintf("Delete %d tunnels", tunnelCount))
tp.fillerHandler = tp.onDelete
}
}
diff --git a/ui/ui.go b/ui/ui.go
index 22354d12..569fc8ae 100644
--- a/ui/ui.go
+++ b/ui/ui.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ui
@@ -15,13 +15,17 @@ import (
"github.com/lxn/win"
"golang.org/x/sys/windows"
+ "golang.zx2c4.com/wireguard/windows/l18n"
"golang.zx2c4.com/wireguard/windows/manager"
"golang.zx2c4.com/wireguard/windows/version"
)
-var noTrayAvailable = false
-var shouldQuitManagerWhenExiting = false
-var startTime = time.Now()
+var (
+ noTrayAvailable = false
+ shouldQuitManagerWhenExiting = false
+ startTime = time.Now()
+ IsAdmin = false // A global, because this really is global for the process
+)
func RunUI() {
runtime.LockOSThread()
@@ -71,11 +75,11 @@ func RunUI() {
switch updateState {
case manager.UpdateStateFoundUpdate:
mtw.UpdateFound()
- if tray != nil {
+ if tray != nil && IsAdmin {
tray.UpdateFound()
}
case manager.UpdateStateUpdatesDisabledUnofficialBuild:
- mtw.SetTitle(mtw.Title() + " (unsigned build, no updates)")
+ mtw.SetTitle(l18n.Sprintf("%s (unsigned build, no updates)", mtw.Title()))
}
})
}
@@ -100,7 +104,7 @@ func RunUI() {
if shouldQuitManagerWhenExiting {
_, err := manager.IPCClientQuit(true)
if err != nil {
- showErrorCustom(nil, "Error Exiting WireGuard", fmt.Sprintf("Unable to exit service due to: %v. You may want to stop WireGuard from the service manager.", err))
+ showErrorCustom(nil, l18n.Sprintf("Error Exiting WireGuard"), l18n.Sprintf("Unable to exit service due to: %v. You may want to stop WireGuard from the service manager.", err))
}
}
}
@@ -115,7 +119,7 @@ func showError(err error, owner walk.Form) bool {
return false
}
- showErrorCustom(owner, "Error", err.Error())
+ showErrorCustom(owner, l18n.Sprintf("Error"), err.Error())
return true
}
diff --git a/ui/updatepage.go b/ui/updatepage.go
index 9e73781f..ff2bbe5f 100644
--- a/ui/updatepage.go
+++ b/ui/updatepage.go
@@ -1,15 +1,14 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package ui
import (
- "fmt"
-
"github.com/lxn/walk"
+ "golang.zx2c4.com/wireguard/windows/l18n"
"golang.zx2c4.com/wireguard/windows/manager"
"golang.zx2c4.com/wireguard/windows/updater"
)
@@ -30,9 +29,9 @@ func NewUpdatePage() (*UpdatePage, error) {
}
disposables.Add(up)
- up.SetTitle("An Update is Available!")
+ up.SetTitle(l18n.Sprintf("An Update is Available!"))
- tabIcon, _ := loadSystemIcon("imageres", 1, 16)
+ tabIcon, _ := loadShieldIcon(16)
up.SetImage(tabIcon)
up.SetLayout(walk.NewVBoxLayout())
@@ -41,14 +40,14 @@ func NewUpdatePage() (*UpdatePage, error) {
if err != nil {
return nil, err
}
- instructions.SetText("An update to WireGuard is available. It is highly advisable to update without delay.")
+ instructions.SetText(l18n.Sprintf("An update to WireGuard is available. It is highly advisable to update without delay."))
instructions.SetMinMaxSize(walk.Size{1, 0}, walk.Size{0, 0})
status, err := walk.NewTextLabel(up)
if err != nil {
return nil, err
}
- status.SetText("Status: Waiting for user")
+ status.SetText(l18n.Sprintf("Status: Waiting for user"))
status.SetMinMaxSize(walk.Size{1, 0}, walk.Size{0, 0})
bar, err := walk.NewProgressBar(up)
@@ -61,9 +60,15 @@ func NewUpdatePage() (*UpdatePage, error) {
if err != nil {
return nil, err
}
- updateIcon, _ := loadSystemIcon("shell32", 46, 32)
+ updateIcon, _ := loadSystemIcon("shell32", -47, 32)
button.SetImage(updateIcon)
- button.SetText("Update Now")
+ button.SetText(l18n.Sprintf("Update Now"))
+
+ if !IsAdmin {
+ button.SetText(l18n.Sprintf("Please ask the system administrator to update."))
+ button.SetEnabled(false)
+ status.SetText(l18n.Sprintf("Status: Waiting for administrator"))
+ }
walk.NewVSpacer(up)
@@ -75,7 +80,7 @@ func NewUpdatePage() (*UpdatePage, error) {
bar.SetVisible(true)
bar.SetMarqueeMode(true)
up.SetSuspended(false)
- status.SetText("Status: Waiting for updater service")
+ status.SetText(l18n.Sprintf("Status: Waiting for updater service"))
}
}
@@ -97,7 +102,7 @@ func NewUpdatePage() (*UpdatePage, error) {
err := manager.IPCClientUpdate()
if err != nil {
switchToReadyState()
- status.SetText(fmt.Sprintf("Error: %v. Please try again.", err))
+ status.SetText(l18n.Sprintf("Error: %v. Please try again.", err))
}
})
@@ -106,11 +111,13 @@ func NewUpdatePage() (*UpdatePage, error) {
switchToUpdatingState()
if dp.Error != nil {
switchToReadyState()
- status.SetText(fmt.Sprintf("Error: %v. Please try again.", dp.Error))
+ err := dp.Error
+ status.SetText(l18n.Sprintf("Error: %v. Please try again.", err))
return
}
if len(dp.Activity) > 0 {
- status.SetText(fmt.Sprintf("Status: %s", dp.Activity))
+ stateText := dp.Activity
+ status.SetText(l18n.Sprintf("Status: %s", stateText))
}
if dp.BytesTotal > 0 {
bar.SetMarqueeMode(false)
@@ -123,7 +130,7 @@ func NewUpdatePage() (*UpdatePage, error) {
}
if dp.Complete {
switchToReadyState()
- status.SetText("Status: Complete!")
+ status.SetText(l18n.Sprintf("Status: Complete!"))
return
}
})
diff --git a/updater/authenticode.go b/updater/authenticode.go
new file mode 100644
index 00000000..1e0a25c0
--- /dev/null
+++ b/updater/authenticode.go
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package updater
+
+import (
+ "unsafe"
+
+ "golang.org/x/sys/windows"
+)
+
+func verifyAuthenticode(path string) bool {
+ path16, err := windows.UTF16PtrFromString(path)
+ if err != nil {
+ return false
+ }
+ data := &windows.WinTrustData{
+ Size: uint32(unsafe.Sizeof(windows.WinTrustData{})),
+ UIChoice: windows.WTD_UI_NONE,
+ RevocationChecks: windows.WTD_REVOKE_WHOLECHAIN, // Full revocation checking, as this is called with network connectivity.
+ UnionChoice: windows.WTD_CHOICE_FILE,
+ StateAction: windows.WTD_STATEACTION_VERIFY,
+ FileOrCatalogOrBlobOrSgnrOrCert: unsafe.Pointer(&windows.WinTrustFileInfo{
+ Size: uint32(unsafe.Sizeof(windows.WinTrustFileInfo{})),
+ FilePath: path16,
+ }),
+ }
+ verified := windows.WinVerifyTrustEx(windows.InvalidHWND, &windows.WINTRUST_ACTION_GENERIC_VERIFY_V2, data) == nil
+ data.StateAction = windows.WTD_STATEACTION_CLOSE
+ windows.WinVerifyTrustEx(windows.InvalidHWND, &windows.WINTRUST_ACTION_GENERIC_VERIFY_V2, data)
+ return verified
+}
diff --git a/updater/constants.go b/updater/constants.go
index 2ae78c8d..cf0ced92 100644
--- a/updater/constants.go
+++ b/updater/constants.go
@@ -1,14 +1,17 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package updater
const (
releasePublicKeyBase64 = "RWRNqGKtBXftKTKPpBPGDMe8jHLnFQ0EdRy8Wg0apV6vTDFLAODD83G4"
- latestVersionURL = "https://download.wireguard.com/windows-client/latest.sig"
- msiURL = "https://download.wireguard.com/windows-client/%s"
+ updateServerHost = "download.wireguard.com"
+ updateServerPort = 443
+ updateServerUseHttps = true
+ latestVersionPath = "/windows-client/latest.sig"
+ msiPath = "/windows-client/%s"
msiArchPrefix = "wireguard-%s-"
msiSuffix = ".msi"
)
diff --git a/updater/downloader.go b/updater/downloader.go
index a12b5037..bf28db54 100644
--- a/updater/downloader.go
+++ b/updater/downloader.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package updater
@@ -11,11 +11,12 @@ import (
"fmt"
"hash"
"io"
- "net/http"
- "os"
"sync/atomic"
"golang.org/x/crypto/blake2b"
+
+ "golang.zx2c4.com/wireguard/windows/elevate"
+ "golang.zx2c4.com/wireguard/windows/updater/winhttp"
"golang.zx2c4.com/wireguard/windows/version"
)
@@ -46,27 +47,55 @@ type UpdateFound struct {
hash [blake2b.Size256]byte
}
-func CheckForUpdate() (*UpdateFound, error) {
- request, err := http.NewRequest(http.MethodGet, latestVersionURL, nil)
+func CheckForUpdate() (updateFound *UpdateFound, err error) {
+ updateFound, _, _, err = checkForUpdate(false)
+ return
+}
+
+func checkForUpdate(keepSession bool) (*UpdateFound, *winhttp.Session, *winhttp.Connection, error) {
+ if !version.IsRunningOfficialVersion() {
+ return nil, nil, nil, errors.New("Build is not official, so updates are disabled")
+ }
+ session, err := winhttp.NewSession(version.UserAgent())
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ defer func() {
+ if err != nil || !keepSession {
+ session.Close()
+ }
+ }()
+ connection, err := session.Connect(updateServerHost, updateServerPort, updateServerUseHttps)
if err != nil {
- return nil, err
+ return nil, nil, nil, err
}
- request.Header.Add("User-Agent", version.UserAgent())
- response, err := http.DefaultClient.Do(request)
+ defer func() {
+ if err != nil || !keepSession {
+ connection.Close()
+ }
+ }()
+ response, err := connection.Get(latestVersionPath, true)
if err != nil {
- return nil, err
+ return nil, nil, nil, err
}
- defer response.Body.Close()
+ defer response.Close()
var fileList [1024 * 512] /* 512 KiB */ byte
- bytesRead, err := response.Body.Read(fileList[:])
+ bytesRead, err := response.Read(fileList[:])
if err != nil && (err != io.EOF || bytesRead == 0) {
- return nil, err
+ return nil, nil, nil, err
}
files, err := readFileList(fileList[:bytesRead])
if err != nil {
- return nil, err
+ return nil, nil, nil, err
+ }
+ updateFound, err := findCandidate(files)
+ if err != nil {
+ return nil, nil, nil, err
}
- return findCandidate(files)
+ if keepSession {
+ return updateFound, session, connection, nil
+ }
+ return updateFound, nil, nil, nil
}
var updateInProgress = uint32(0)
@@ -80,17 +109,19 @@ func DownloadVerifyAndExecute(userToken uintptr) (progress chan DownloadProgress
return
}
- go func() {
+ doIt := func() {
defer atomic.StoreUint32(&updateInProgress, 0)
- progress <- DownloadProgress{Activity: "Rechecking for update"}
- update, err := CheckForUpdate()
+ progress <- DownloadProgress{Activity: "Checking for update"}
+ update, session, connection, err := checkForUpdate(true)
if err != nil {
progress <- DownloadProgress{Error: err}
return
}
+ defer connection.Close()
+ defer session.Close()
if update == nil {
- progress <- DownloadProgress{Error: errors.New("No update was found when re-checking for updates")}
+ progress <- DownloadProgress{Error: errors.New("No update was found")}
return
}
@@ -100,33 +131,24 @@ func DownloadVerifyAndExecute(userToken uintptr) (progress chan DownloadProgress
progress <- DownloadProgress{Error: err}
return
}
+ progress <- DownloadProgress{Activity: fmt.Sprintf("Msi destination is %#q", file.Name())}
defer func() {
if file != nil {
- name := file.Name()
- file.Seek(0, io.SeekStart)
- file.Truncate(0)
- file.Close()
- os.Remove(name) // TODO: Do we have any sort of TOCTOU here?
+ file.Delete()
}
}()
dp := DownloadProgress{Activity: "Downloading update"}
progress <- dp
- request, err := http.NewRequest(http.MethodGet, fmt.Sprintf(msiURL, update.name), nil)
- if err != nil {
- progress <- DownloadProgress{Error: err}
- return
- }
- request.Header.Add("User-Agent", version.UserAgent())
- request.Header.Set("Accept-Encoding", "identity")
- response, err := http.DefaultClient.Do(request)
+ response, err := connection.Get(fmt.Sprintf(msiPath, update.name), false)
if err != nil {
progress <- DownloadProgress{Error: err}
return
}
- defer response.Body.Close()
- if response.ContentLength >= 0 {
- dp.BytesTotal = uint64(response.ContentLength)
+ defer response.Close()
+ length, err := response.Length()
+ if err == nil && length >= 0 {
+ dp.BytesTotal = length
progress <- dp
}
hasher, err := blake2b.New256(nil)
@@ -135,7 +157,7 @@ func DownloadVerifyAndExecute(userToken uintptr) (progress chan DownloadProgress
return
}
pm := &progressHashWatcher{&dp, progress, hasher}
- _, err = io.Copy(file, io.TeeReader(io.LimitReader(response.Body, 1024*1024*100 /* 100 MiB */), pm))
+ _, err = io.Copy(file, io.TeeReader(io.LimitReader(response, 1024*1024*100 /* 100 MiB */), pm))
if err != nil {
progress <- DownloadProgress{Error: err}
return
@@ -145,29 +167,34 @@ func DownloadVerifyAndExecute(userToken uintptr) (progress chan DownloadProgress
return
}
- // TODO: it would be nice to rename in place from "file.msi.unverified" to "file.msi", but Windows TOCTOU stuff
- // is hard, so we'll come back to this later.
- name := file.Name()
- file.Close()
- file = nil
-
progress <- DownloadProgress{Activity: "Verifying authenticode signature"}
- if !version.VerifyAuthenticode(name) {
- os.Remove(name) // TODO: Do we have any sort of TOCTOU here?
+ if !verifyAuthenticode(file.ExclusivePath()) {
progress <- DownloadProgress{Error: errors.New("The downloaded update does not have an authentic authenticode signature")}
return
}
progress <- DownloadProgress{Activity: "Installing update"}
- err = runMsi(name, userToken)
- os.Remove(name) // TODO: Do we have any sort of TOCTOU here?
+ err = runMsi(file, userToken)
if err != nil {
progress <- DownloadProgress{Error: err}
return
}
progress <- DownloadProgress{Complete: true}
- }()
+ }
+ if userToken == 0 {
+ go func() {
+ err := elevate.DoAsSystem(func() error {
+ doIt()
+ return nil
+ })
+ if err != nil {
+ progress <- DownloadProgress{Error: err}
+ }
+ }()
+ } else {
+ go doIt()
+ }
return progress
}
diff --git a/updater/msirunner_windows.go b/updater/msirunner.go
index c11d4db9..ec6b1bd3 100644
--- a/updater/msirunner_windows.go
+++ b/updater/msirunner.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package updater
@@ -19,7 +19,35 @@ import (
"golang.org/x/sys/windows"
)
-func runMsi(msiPath string, userToken uintptr) error {
+type tempFile struct {
+ *os.File
+ originalHandle windows.Handle
+}
+
+func (t *tempFile) ExclusivePath() string {
+ if t.originalHandle != 0 {
+ t.Close() // TODO: sort of a toctou, but msi requires unshared file
+ t.originalHandle = 0
+ }
+ return t.Name()
+}
+
+func (t *tempFile) Delete() error {
+ if t.originalHandle == 0 {
+ name16, err := windows.UTF16PtrFromString(t.Name())
+ if err != nil {
+ return err
+ }
+ return windows.DeleteFile(name16) // TODO: how does this deal with reparse points?
+ }
+ disposition := byte(1)
+ err := windows.SetFileInformationByHandle(t.originalHandle, windows.FileDispositionInfo, &disposition, 1)
+ t.originalHandle = 0
+ t.Close()
+ return err
+}
+
+func runMsi(msi *tempFile, userToken uintptr) error {
system32, err := windows.GetSystemDirectory()
if err != nil {
return err
@@ -29,6 +57,7 @@ func runMsi(msiPath string, userToken uintptr) error {
return err
}
defer devNull.Close()
+ msiPath := msi.ExclusivePath()
attr := &os.ProcAttr{
Sys: &syscall.SysProcAttr{
Token: syscall.Token(userToken),
@@ -51,7 +80,7 @@ func runMsi(msiPath string, userToken uintptr) error {
return nil
}
-func msiTempFile() (*os.File, error) {
+func msiTempFile() (*tempFile, error) {
var randBytes [32]byte
n, err := rand.Read(randBytes[:])
if err != nil {
@@ -68,16 +97,20 @@ func msiTempFile() (*os.File, error) {
Length: uint32(unsafe.Sizeof(windows.SecurityAttributes{})),
SecurityDescriptor: sd,
}
- // TODO: os.TempDir() returns C:\windows\temp when calling from this context. Supposedly this is mostly secure
- // against TOCTOU, but who knows! Look into this!
- name := filepath.Join(os.TempDir(), hex.EncodeToString(randBytes[:]))
+ windir, err := windows.GetWindowsDirectory()
+ if err != nil {
+ return nil, err
+ }
+ name := filepath.Join(windir, "Temp", hex.EncodeToString(randBytes[:]))
name16 := windows.StringToUTF16Ptr(name)
- // TODO: it would be nice to specify delete_on_close, but msiexec.exe doesn't open its files with read sharing.
- fileHandle, err := windows.CreateFile(name16, windows.GENERIC_WRITE, windows.FILE_SHARE_READ, sa, windows.CREATE_NEW, windows.FILE_ATTRIBUTE_NORMAL, 0)
+ fileHandle, err := windows.CreateFile(name16, windows.GENERIC_WRITE|windows.DELETE, 0, sa, windows.CREATE_NEW, windows.FILE_ATTRIBUTE_TEMPORARY, 0)
runtime.KeepAlive(sd)
if err != nil {
return nil, err
}
windows.MoveFileEx(name16, nil, windows.MOVEFILE_DELAY_UNTIL_REBOOT)
- return os.NewFile(uintptr(fileHandle), name), nil
+ return &tempFile{
+ File: os.NewFile(uintptr(fileHandle), name),
+ originalHandle: fileHandle,
+ }, nil
}
diff --git a/updater/msirunner_linux.go b/updater/msirunner_linux.go
deleted file mode 100644
index 6550025c..00000000
--- a/updater/msirunner_linux.go
+++ /dev/null
@@ -1,23 +0,0 @@
-/* SPDX-License-Identifier: MIT
- *
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
- */
-
-package updater
-
-import (
- "fmt"
- "io/ioutil"
- "os"
- "os/exec"
-)
-
-// This isn't a Linux program, yes, but having the updater package work across platforms is quite helpful for testing.
-
-func runMsi(msiPath string, userToken uintptr, env []string) error {
- return exec.Command("qarma", "--info", "--text", fmt.Sprintf("It seems to be working! Were we on Windows, ‘%s’ would be executed.", msiPath)).Run()
-}
-
-func msiTempFile() (*os.File, error) {
- return ioutil.TempFile(os.TempDir(), "")
-}
diff --git a/updater/signify.go b/updater/signify.go
index af024ebd..de50fcba 100644
--- a/updater/signify.go
+++ b/updater/signify.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package updater
@@ -53,17 +53,17 @@ func readFileList(input []byte) (fileList, error) {
if len(line) == 0 && index == len(fileLines)-1 {
break
}
- components := strings.SplitN(line, " ", 2)
- if len(components) != 2 {
+ first, second, ok := strings.Cut(line, " ")
+ if !ok {
return nil, errors.New("File hash line has too few components")
}
- maybeHash, err := hex.DecodeString(components[0])
+ maybeHash, err := hex.DecodeString(first)
if err != nil || len(maybeHash) != blake2b.Size256 {
return nil, errors.New("File hash is invalid base64 or incorrect number of bytes")
}
var hash [blake2b.Size256]byte
copy(hash[:], maybeHash)
- fileHashes[components[1]] = hash
+ fileHashes[second] = hash
}
if len(fileHashes) == 0 {
return nil, errors.New("No file hashes found in signed input")
diff --git a/updater/updater_test.go b/updater/updater_test.go
index fbd1080d..809262bd 100644
--- a/updater/updater_test.go
+++ b/updater/updater_test.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package updater
@@ -20,7 +20,7 @@ func TestUpdate(t *testing.T) {
return
}
t.Log("Found update")
- progress := DownloadVerifyAndExecute(0, nil)
+ progress := DownloadVerifyAndExecute(0)
for {
dp := <-progress
if dp.Error != nil {
diff --git a/updater/versions.go b/updater/versions.go
index 72f90240..dfc4c665 100644
--- a/updater/versions.go
+++ b/updater/versions.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package updater
@@ -8,7 +8,6 @@ package updater
import (
"errors"
"fmt"
- "runtime"
"strconv"
"strings"
@@ -55,17 +54,7 @@ func versionNewerThanUs(candidate string) (bool, error) {
}
func findCandidate(candidates fileList) (*UpdateFound, error) {
- var arch string
- if runtime.GOARCH == "amd64" {
- arch = "amd64"
- } else if runtime.GOARCH == "386" {
- arch = "x86"
- } else if runtime.GOARCH == "arm64" {
- arch = "arm64"
- } else {
- return nil, errors.New("Invalid GOARCH")
- }
- prefix := fmt.Sprintf(msiArchPrefix, arch)
+ prefix := fmt.Sprintf(msiArchPrefix, version.Arch())
suffix := msiSuffix
for name, hash := range candidates {
if strings.HasPrefix(name, prefix) && strings.HasSuffix(name, suffix) {
diff --git a/elevate/mksyscall.go b/updater/winhttp/mksyscall.go
index 8522c980..54c06e85 100644
--- a/elevate/mksyscall.go
+++ b/updater/winhttp/mksyscall.go
@@ -1,8 +1,8 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
-package elevate
+package winhttp
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go syscall_windows.go
diff --git a/updater/winhttp/syscall_windows.go b/updater/winhttp/syscall_windows.go
new file mode 100644
index 00000000..4f967bfa
--- /dev/null
+++ b/updater/winhttp/syscall_windows.go
@@ -0,0 +1,355 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package winhttp
+
+import (
+ "golang.org/x/sys/windows"
+)
+
+type _HINTERNET windows.Handle
+
+type Error uint32
+
+const (
+ _WINHTTP_ACCESS_TYPE_DEFAULT_PROXY = 0
+ _WINHTTP_ACCESS_TYPE_NO_PROXY = 1
+ _WINHTTP_ACCESS_TYPE_NAMED_PROXY = 3
+ _WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY = 4
+
+ _WINHTTP_FLAG_ASYNC = 0x10000000
+
+ _WINHTTP_INVALID_STATUS_CALLBACK = ^uintptr(0)
+
+ _WINHTTP_CALLBACK_STATUS_RESOLVING_NAME = 0x00000001
+ _WINHTTP_CALLBACK_STATUS_NAME_RESOLVED = 0x00000002
+ _WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER = 0x00000004
+ _WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER = 0x00000008
+ _WINHTTP_CALLBACK_STATUS_SENDING_REQUEST = 0x00000010
+ _WINHTTP_CALLBACK_STATUS_REQUEST_SENT = 0x00000020
+ _WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE = 0x00000040
+ _WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED = 0x00000080
+ _WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION = 0x00000100
+ _WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED = 0x00000200
+ _WINHTTP_CALLBACK_STATUS_HANDLE_CREATED = 0x00000400
+ _WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING = 0x00000800
+ _WINHTTP_CALLBACK_STATUS_DETECTING_PROXY = 0x00001000
+ _WINHTTP_CALLBACK_STATUS_REDIRECT = 0x00004000
+ _WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE = 0x00008000
+ _WINHTTP_CALLBACK_STATUS_SECURE_FAILURE = 0x00010000
+ _WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE = 0x00020000
+ _WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE = 0x00040000
+ _WINHTTP_CALLBACK_STATUS_READ_COMPLETE = 0x00080000
+ _WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE = 0x00100000
+ _WINHTTP_CALLBACK_STATUS_REQUEST_ERROR = 0x00200000
+ _WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE = 0x00400000
+ _WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE = 0x01000000
+ _WINHTTP_CALLBACK_STATUS_CLOSE_COMPLETE = 0x02000000
+ _WINHTTP_CALLBACK_STATUS_SHUTDOWN_COMPLETE = 0x04000000
+ _WINHTTP_CALLBACK_STATUS_SETTINGS_WRITE_COMPLETE = 0x10000000
+ _WINHTTP_CALLBACK_STATUS_SETTINGS_READ_COMPLETE = 0x20000000
+
+ _WINHTTP_CALLBACK_FLAG_RESOLVE_NAME = _WINHTTP_CALLBACK_STATUS_RESOLVING_NAME | _WINHTTP_CALLBACK_STATUS_NAME_RESOLVED
+ _WINHTTP_CALLBACK_FLAG_CONNECT_TO_SERVER = _WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER | _WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER
+ _WINHTTP_CALLBACK_FLAG_SEND_REQUEST = _WINHTTP_CALLBACK_STATUS_SENDING_REQUEST | _WINHTTP_CALLBACK_STATUS_REQUEST_SENT
+ _WINHTTP_CALLBACK_FLAG_RECEIVE_RESPONSE = _WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE | _WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED
+ _WINHTTP_CALLBACK_FLAG_CLOSE_CONNECTION = _WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION | _WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED
+ _WINHTTP_CALLBACK_FLAG_HANDLES = _WINHTTP_CALLBACK_STATUS_HANDLE_CREATED | _WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING
+ _WINHTTP_CALLBACK_FLAG_DETECTING_PROXY = _WINHTTP_CALLBACK_STATUS_DETECTING_PROXY
+ _WINHTTP_CALLBACK_FLAG_REDIRECT = _WINHTTP_CALLBACK_STATUS_REDIRECT
+ _WINHTTP_CALLBACK_FLAG_INTERMEDIATE_RESPONSE = _WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE
+ _WINHTTP_CALLBACK_FLAG_SECURE_FAILURE = _WINHTTP_CALLBACK_STATUS_SECURE_FAILURE
+ _WINHTTP_CALLBACK_FLAG_SENDREQUEST_COMPLETE = _WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE
+ _WINHTTP_CALLBACK_FLAG_HEADERS_AVAILABLE = _WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE
+ _WINHTTP_CALLBACK_FLAG_DATA_AVAILABLE = _WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE
+ _WINHTTP_CALLBACK_FLAG_READ_COMPLETE = _WINHTTP_CALLBACK_STATUS_READ_COMPLETE
+ _WINHTTP_CALLBACK_FLAG_WRITE_COMPLETE = _WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE
+ _WINHTTP_CALLBACK_FLAG_REQUEST_ERROR = _WINHTTP_CALLBACK_STATUS_REQUEST_ERROR
+ _WINHTTP_CALLBACK_FLAG_GETPROXYFORURL_COMPLETE = _WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE
+ _WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS = _WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE | _WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE | _WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE | _WINHTTP_CALLBACK_STATUS_READ_COMPLETE | _WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE | _WINHTTP_CALLBACK_STATUS_REQUEST_ERROR | _WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE
+ _WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS = 0xffffffff
+
+ _INTERNET_DEFAULT_PORT = 0
+ _INTERNET_DEFAULT_HTTP_PORT = 80
+ _INTERNET_DEFAULT_HTTPS_PORT = 443
+
+ _WINHTTP_FLAG_SECURE = 0x00800000
+ _WINHTTP_FLAG_ESCAPE_PERCENT = 0x00000004
+ _WINHTTP_FLAG_NULL_CODEPAGE = 0x00000008
+ _WINHTTP_FLAG_BYPASS_PROXY_CACHE = 0x00000100
+ _WINHTTP_FLAG_REFRESH = _WINHTTP_FLAG_BYPASS_PROXY_CACHE
+ _WINHTTP_FLAG_ESCAPE_DISABLE = 0x00000040
+ _WINHTTP_FLAG_ESCAPE_DISABLE_QUERY = 0x00000080
+
+ _WINHTTP_QUERY_MIME_VERSION = 0
+ _WINHTTP_QUERY_CONTENT_TYPE = 1
+ _WINHTTP_QUERY_CONTENT_TRANSFER_ENCODING = 2
+ _WINHTTP_QUERY_CONTENT_ID = 3
+ _WINHTTP_QUERY_CONTENT_DESCRIPTION = 4
+ _WINHTTP_QUERY_CONTENT_LENGTH = 5
+ _WINHTTP_QUERY_CONTENT_LANGUAGE = 6
+ _WINHTTP_QUERY_ALLOW = 7
+ _WINHTTP_QUERY_PUBLIC = 8
+ _WINHTTP_QUERY_DATE = 9
+ _WINHTTP_QUERY_EXPIRES = 10
+ _WINHTTP_QUERY_LAST_MODIFIED = 11
+ _WINHTTP_QUERY_MESSAGE_ID = 12
+ _WINHTTP_QUERY_URI = 13
+ _WINHTTP_QUERY_DERIVED_FROM = 14
+ _WINHTTP_QUERY_COST = 15
+ _WINHTTP_QUERY_LINK = 16
+ _WINHTTP_QUERY_PRAGMA = 17
+ _WINHTTP_QUERY_VERSION = 18
+ _WINHTTP_QUERY_STATUS_CODE = 19
+ _WINHTTP_QUERY_STATUS_TEXT = 20
+ _WINHTTP_QUERY_RAW_HEADERS = 21
+ _WINHTTP_QUERY_RAW_HEADERS_CRLF = 22
+ _WINHTTP_QUERY_CONNECTION = 23
+ _WINHTTP_QUERY_ACCEPT = 24
+ _WINHTTP_QUERY_ACCEPT_CHARSET = 25
+ _WINHTTP_QUERY_ACCEPT_ENCODING = 26
+ _WINHTTP_QUERY_ACCEPT_LANGUAGE = 27
+ _WINHTTP_QUERY_AUTHORIZATION = 28
+ _WINHTTP_QUERY_CONTENT_ENCODING = 29
+ _WINHTTP_QUERY_FORWARDED = 30
+ _WINHTTP_QUERY_FROM = 31
+ _WINHTTP_QUERY_IF_MODIFIED_SINCE = 32
+ _WINHTTP_QUERY_LOCATION = 33
+ _WINHTTP_QUERY_ORIG_URI = 34
+ _WINHTTP_QUERY_REFERER = 35
+ _WINHTTP_QUERY_RETRY_AFTER = 36
+ _WINHTTP_QUERY_SERVER = 37
+ _WINHTTP_QUERY_TITLE = 38
+ _WINHTTP_QUERY_USER_AGENT = 39
+ _WINHTTP_QUERY_WWW_AUTHENTICATE = 40
+ _WINHTTP_QUERY_PROXY_AUTHENTICATE = 41
+ _WINHTTP_QUERY_ACCEPT_RANGES = 42
+ _WINHTTP_QUERY_SET_COOKIE = 43
+ _WINHTTP_QUERY_COOKIE = 44
+ _WINHTTP_QUERY_REQUEST_METHOD = 45
+ _WINHTTP_QUERY_REFRESH = 46
+ _WINHTTP_QUERY_CONTENT_DISPOSITION = 47
+ _WINHTTP_QUERY_AGE = 48
+ _WINHTTP_QUERY_CACHE_CONTROL = 49
+ _WINHTTP_QUERY_CONTENT_BASE = 50
+ _WINHTTP_QUERY_CONTENT_LOCATION = 51
+ _WINHTTP_QUERY_CONTENT_MD5 = 52
+ _WINHTTP_QUERY_CONTENT_RANGE = 53
+ _WINHTTP_QUERY_ETAG = 54
+ _WINHTTP_QUERY_HOST = 55
+ _WINHTTP_QUERY_IF_MATCH = 56
+ _WINHTTP_QUERY_IF_NONE_MATCH = 57
+ _WINHTTP_QUERY_IF_RANGE = 58
+ _WINHTTP_QUERY_IF_UNMODIFIED_SINCE = 59
+ _WINHTTP_QUERY_MAX_FORWARDS = 60
+ _WINHTTP_QUERY_PROXY_AUTHORIZATION = 61
+ _WINHTTP_QUERY_RANGE = 62
+ _WINHTTP_QUERY_TRANSFER_ENCODING = 63
+ _WINHTTP_QUERY_UPGRADE = 64
+ _WINHTTP_QUERY_VARY = 65
+ _WINHTTP_QUERY_VIA = 66
+ _WINHTTP_QUERY_WARNING = 67
+ _WINHTTP_QUERY_EXPECT = 68
+ _WINHTTP_QUERY_PROXY_CONNECTION = 69
+ _WINHTTP_QUERY_UNLESS_MODIFIED_SINCE = 70
+ _WINHTTP_QUERY_PROXY_SUPPORT = 75
+ _WINHTTP_QUERY_AUTHENTICATION_INFO = 76
+ _WINHTTP_QUERY_PASSPORT_URLS = 77
+ _WINHTTP_QUERY_PASSPORT_CONFIG = 78
+ _WINHTTP_QUERY_MAX = 78
+ _WINHTTP_QUERY_CUSTOM = 65535
+ _WINHTTP_QUERY_FLAG_REQUEST_HEADERS = 0x80000000
+ _WINHTTP_QUERY_FLAG_SYSTEMTIME = 0x40000000
+ _WINHTTP_QUERY_FLAG_NUMBER = 0x20000000
+ _WINHTTP_QUERY_FLAG_NUMBER64 = 0x08000000
+
+ _WINHTTP_FIRST_OPTION = _WINHTTP_OPTION_CALLBACK
+ _WINHTTP_OPTION_CALLBACK = 1
+ _WINHTTP_OPTION_RESOLVE_TIMEOUT = 2
+ _WINHTTP_OPTION_CONNECT_TIMEOUT = 3
+ _WINHTTP_OPTION_CONNECT_RETRIES = 4
+ _WINHTTP_OPTION_SEND_TIMEOUT = 5
+ _WINHTTP_OPTION_RECEIVE_TIMEOUT = 6
+ _WINHTTP_OPTION_RECEIVE_RESPONSE_TIMEOUT = 7
+ _WINHTTP_OPTION_HANDLE_TYPE = 9
+ _WINHTTP_OPTION_READ_BUFFER_SIZE = 12
+ _WINHTTP_OPTION_WRITE_BUFFER_SIZE = 13
+ _WINHTTP_OPTION_PARENT_HANDLE = 21
+ _WINHTTP_OPTION_EXTENDED_ERROR = 24
+ _WINHTTP_OPTION_SECURITY_FLAGS = 31
+ _WINHTTP_OPTION_SECURITY_CERTIFICATE_STRUCT = 32
+ _WINHTTP_OPTION_URL = 34
+ _WINHTTP_OPTION_SECURITY_KEY_BITNESS = 36
+ _WINHTTP_OPTION_PROXY = 38
+ _WINHTTP_OPTION_PROXY_RESULT_ENTRY = 39
+ _WINHTTP_OPTION_USER_AGENT = 41
+ _WINHTTP_OPTION_CONTEXT_VALUE = 45
+ _WINHTTP_OPTION_CLIENT_CERT_CONTEXT = 47
+ _WINHTTP_OPTION_REQUEST_PRIORITY = 58
+ _WINHTTP_OPTION_HTTP_VERSION = 59
+ _WINHTTP_OPTION_DISABLE_FEATURE = 63
+ _WINHTTP_OPTION_CODEPAGE = 68
+ _WINHTTP_OPTION_MAX_CONNS_PER_SERVER = 73
+ _WINHTTP_OPTION_MAX_CONNS_PER_1_0_SERVER = 74
+ _WINHTTP_OPTION_AUTOLOGON_POLICY = 77
+ _WINHTTP_OPTION_SERVER_CERT_CONTEXT = 78
+ _WINHTTP_OPTION_ENABLE_FEATURE = 79
+ _WINHTTP_OPTION_WORKER_THREAD_COUNT = 80
+ _WINHTTP_OPTION_PASSPORT_COBRANDING_TEXT = 81
+ _WINHTTP_OPTION_PASSPORT_COBRANDING_URL = 82
+ _WINHTTP_OPTION_CONFIGURE_PASSPORT_AUTH = 83
+ _WINHTTP_OPTION_SECURE_PROTOCOLS = 84
+ _WINHTTP_OPTION_ENABLETRACING = 85
+ _WINHTTP_OPTION_PASSPORT_SIGN_OUT = 86
+ _WINHTTP_OPTION_PASSPORT_RETURN_URL = 87
+ _WINHTTP_OPTION_REDIRECT_POLICY = 88
+ _WINHTTP_OPTION_MAX_HTTP_AUTOMATIC_REDIRECTS = 89
+ _WINHTTP_OPTION_MAX_HTTP_STATUS_CONTINUE = 90
+ _WINHTTP_OPTION_MAX_RESPONSE_HEADER_SIZE = 91
+ _WINHTTP_OPTION_MAX_RESPONSE_DRAIN_SIZE = 92
+ _WINHTTP_OPTION_CONNECTION_INFO = 93
+ _WINHTTP_OPTION_CLIENT_CERT_ISSUER_LIST = 94
+ _WINHTTP_OPTION_SPN = 96
+ _WINHTTP_OPTION_GLOBAL_PROXY_CREDS = 97
+ _WINHTTP_OPTION_GLOBAL_SERVER_CREDS = 98
+ _WINHTTP_OPTION_UNLOAD_NOTIFY_EVENT = 99
+ _WINHTTP_OPTION_REJECT_USERPWD_IN_URL = 100
+ _WINHTTP_OPTION_USE_GLOBAL_SERVER_CREDENTIALS = 101
+ _WINHTTP_OPTION_RECEIVE_PROXY_CONNECT_RESPONSE = 103
+ _WINHTTP_OPTION_IS_PROXY_CONNECT_RESPONSE = 104
+ _WINHTTP_OPTION_SERVER_SPN_USED = 106
+ _WINHTTP_OPTION_PROXY_SPN_USED = 107
+ _WINHTTP_OPTION_SERVER_CBT = 108
+ _WINHTTP_OPTION_UNSAFE_HEADER_PARSING = 110
+ _WINHTTP_OPTION_ASSURED_NON_BLOCKING_CALLBACKS = 111
+ _WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET = 114
+ _WINHTTP_OPTION_WEB_SOCKET_CLOSE_TIMEOUT = 115
+ _WINHTTP_OPTION_WEB_SOCKET_KEEPALIVE_INTERVAL = 116
+ _WINHTTP_OPTION_DECOMPRESSION = 118
+ _WINHTTP_OPTION_WEB_SOCKET_RECEIVE_BUFFER_SIZE = 122
+ _WINHTTP_OPTION_WEB_SOCKET_SEND_BUFFER_SIZE = 123
+ _WINHTTP_OPTION_TCP_PRIORITY_HINT = 128
+ _WINHTTP_OPTION_CONNECTION_FILTER = 131
+ _WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL = 133
+ _WINHTTP_OPTION_HTTP_PROTOCOL_USED = 134
+ _WINHTTP_OPTION_KDC_PROXY_SETTINGS = 136
+ _WINHTTP_OPTION_ENCODE_EXTRA = 138
+ _WINHTTP_OPTION_DISABLE_STREAM_QUEUE = 139
+ _WINHTTP_OPTION_IPV6_FAST_FALLBACK = 140
+ _WINHTTP_OPTION_CONNECTION_STATS_V0 = 141
+ _WINHTTP_OPTION_REQUEST_TIMES = 142
+ _WINHTTP_OPTION_EXPIRE_CONNECTION = 143
+ _WINHTTP_OPTION_DISABLE_SECURE_PROTOCOL_FALLBACK = 144
+ _WINHTTP_OPTION_HTTP_PROTOCOL_REQUIRED = 145
+ _WINHTTP_OPTION_REQUEST_STATS = 146
+ _WINHTTP_OPTION_SERVER_CERT_CHAIN_CONTEXT = 147
+ _WINHTTP_LAST_OPTION = _WINHTTP_OPTION_SERVER_CERT_CHAIN_CONTEXT
+
+ _ICU_ESCAPE = 0x80000000
+ _ICU_ESCAPE_AUTHORITY = 0x00002000
+ _ICU_REJECT_USERPWD = 0x00004000
+
+ _INTERNET_SCHEME_HTTP = 1
+ _INTERNET_SCHEME_HTTPS = 2
+ _INTERNET_SCHEME_FTP = 3
+ _INTERNET_SCHEME_SOCKS = 4
+
+ _WINHTTP_FLAG_SECURE_PROTOCOL_SSL2 = 0x00000008
+ _WINHTTP_FLAG_SECURE_PROTOCOL_SSL3 = 0x00000020
+ _WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 = 0x00000080
+ _WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 = 0x00000200
+ _WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 = 0x00000800
+ _WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3 = 0x00002000
+ _WINHTTP_FLAG_SECURE_PROTOCOL_ALL = _WINHTTP_FLAG_SECURE_PROTOCOL_SSL2 | _WINHTTP_FLAG_SECURE_PROTOCOL_SSL3 | _WINHTTP_FLAG_SECURE_PROTOCOL_TLS1
+
+ _WINHTTP_PROTOCOL_FLAG_HTTP2 = 0x1
+
+ _WINHTTP_ERROR_BASE = 12000
+ _ERROR_WINHTTP_OUT_OF_HANDLES = Error(12000 + 1)
+ _ERROR_WINHTTP_TIMEOUT = Error(12000 + 2)
+ _ERROR_WINHTTP_INTERNAL_ERROR = Error(12000 + 4)
+ _ERROR_WINHTTP_INVALID_URL = Error(12000 + 5)
+ _ERROR_WINHTTP_UNRECOGNIZED_SCHEME = Error(12000 + 6)
+ _ERROR_WINHTTP_NAME_NOT_RESOLVED = Error(12000 + 7)
+ _ERROR_WINHTTP_INVALID_OPTION = Error(12000 + 9)
+ _ERROR_WINHTTP_OPTION_NOT_SETTABLE = Error(12000 + 11)
+ _ERROR_WINHTTP_SHUTDOWN = Error(12000 + 12)
+ _ERROR_WINHTTP_LOGIN_FAILURE = Error(12000 + 15)
+ _ERROR_WINHTTP_OPERATION_CANCELLED = Error(12000 + 17)
+ _ERROR_WINHTTP_INCORRECT_HANDLE_TYPE = Error(12000 + 18)
+ _ERROR_WINHTTP_INCORRECT_HANDLE_STATE = Error(12000 + 19)
+ _ERROR_WINHTTP_CANNOT_CONNECT = Error(12000 + 29)
+ _ERROR_WINHTTP_CONNECTION_ERROR = Error(12000 + 30)
+ _ERROR_WINHTTP_RESEND_REQUEST = Error(12000 + 32)
+ _ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED = Error(12000 + 44)
+ _ERROR_WINHTTP_CANNOT_CALL_BEFORE_OPEN = Error(12000 + 100)
+ _ERROR_WINHTTP_CANNOT_CALL_BEFORE_SEND = Error(12000 + 101)
+ _ERROR_WINHTTP_CANNOT_CALL_AFTER_SEND = Error(12000 + 102)
+ _ERROR_WINHTTP_CANNOT_CALL_AFTER_OPEN = Error(12000 + 103)
+ _ERROR_WINHTTP_HEADER_NOT_FOUND = Error(12000 + 150)
+ _ERROR_WINHTTP_INVALID_SERVER_RESPONSE = Error(12000 + 152)
+ _ERROR_WINHTTP_INVALID_HEADER = Error(12000 + 153)
+ _ERROR_WINHTTP_INVALID_QUERY_REQUEST = Error(12000 + 154)
+ _ERROR_WINHTTP_HEADER_ALREADY_EXISTS = Error(12000 + 155)
+ _ERROR_WINHTTP_REDIRECT_FAILED = Error(12000 + 156)
+ _ERROR_WINHTTP_AUTO_PROXY_SERVICE_ERROR = Error(12000 + 178)
+ _ERROR_WINHTTP_BAD_AUTO_PROXY_SCRIPT = Error(12000 + 166)
+ _ERROR_WINHTTP_UNABLE_TO_DOWNLOAD_SCRIPT = Error(12000 + 167)
+ _ERROR_WINHTTP_UNHANDLED_SCRIPT_TYPE = Error(12000 + 176)
+ _ERROR_WINHTTP_SCRIPT_EXECUTION_ERROR = Error(12000 + 177)
+ _ERROR_WINHTTP_NOT_INITIALIZED = Error(12000 + 172)
+ _ERROR_WINHTTP_SECURE_FAILURE = Error(12000 + 175)
+ _ERROR_WINHTTP_SECURE_CERT_DATE_INVALID = Error(12000 + 37)
+ _ERROR_WINHTTP_SECURE_CERT_CN_INVALID = Error(12000 + 38)
+ _ERROR_WINHTTP_SECURE_INVALID_CA = Error(12000 + 45)
+ _ERROR_WINHTTP_SECURE_CERT_REV_FAILED = Error(12000 + 57)
+ _ERROR_WINHTTP_SECURE_CHANNEL_ERROR = Error(12000 + 157)
+ _ERROR_WINHTTP_SECURE_INVALID_CERT = Error(12000 + 169)
+ _ERROR_WINHTTP_SECURE_CERT_REVOKED = Error(12000 + 170)
+ _ERROR_WINHTTP_SECURE_CERT_WRONG_USAGE = Error(12000 + 179)
+ _ERROR_WINHTTP_AUTODETECTION_FAILED = Error(12000 + 180)
+ _ERROR_WINHTTP_HEADER_COUNT_EXCEEDED = Error(12000 + 181)
+ _ERROR_WINHTTP_HEADER_SIZE_OVERFLOW = Error(12000 + 182)
+ _ERROR_WINHTTP_CHUNKED_ENCODING_HEADER_SIZE_OVERFLOW = Error(12000 + 183)
+ _ERROR_WINHTTP_RESPONSE_DRAIN_OVERFLOW = Error(12000 + 184)
+ _ERROR_WINHTTP_CLIENT_CERT_NO_PRIVATE_KEY = Error(12000 + 185)
+ _ERROR_WINHTTP_CLIENT_CERT_NO_ACCESS_PRIVATE_KEY = Error(12000 + 186)
+ _ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED_PROXY = Error(12000 + 187)
+ _ERROR_WINHTTP_SECURE_FAILURE_PROXY = Error(12000 + 188)
+ _ERROR_WINHTTP_RESERVED_189 = Error(12000 + 189)
+ _ERROR_WINHTTP_HTTP_PROTOCOL_MISMATCH = Error(12000 + 190)
+ _WINHTTP_ERROR_LAST = _WINHTTP_ERROR_BASE + 190
+)
+
+type _URL_COMPONENTS struct {
+ structSize uint32
+ scheme *uint16
+ schemeLength uint32
+ schemeType uint32
+ hostName *uint16
+ hostNameLength uint32
+ port uint16
+ username *uint16
+ usernameLength uint32
+ password *uint16
+ passwordLength uint32
+ urlPath *uint16
+ urlPathLength uint32
+ extraInfo *uint16
+ extraInfoLength uint32
+}
+
+//sys winHttpOpen(userAgent *uint16, accessType uint32, proxy *uint16, proxyBypass *uint16, flags uint32) (sessionHandle _HINTERNET, err error) = winhttp.WinHttpOpen
+//sys winHttpSetStatusCallback(handle _HINTERNET, callback uintptr, notificationFlags uint32, reserved uintptr) (previousCallback uintptr, err error) [failretval==_WINHTTP_INVALID_STATUS_CALLBACK] = winhttp.WinHttpSetStatusCallback
+//sys winHttpCloseHandle(handle _HINTERNET) (err error) = winhttp.WinHttpCloseHandle
+//sys winHttpConnect(sessionHandle _HINTERNET, serverName *uint16, serverPort uint16, reserved uint32) (handle _HINTERNET, err error) = winhttp.WinHttpConnect
+//sys winHttpOpenRequest(connectHandle _HINTERNET, verb *uint16, objectName *uint16, version *uint16, referrer *uint16, acceptTypes **uint16, flags uint32) (requestHandle _HINTERNET, err error) = winhttp.WinHttpOpenRequest
+//sys winHttpSendRequest(requestHandle _HINTERNET, headers *uint16, headersLength uint32, optional *byte, optionalLength uint32, totalLength uint32, context uintptr) (err error) = winhttp.WinHttpSendRequest
+//sys winHttpReceiveResponse(requestHandle _HINTERNET, reserved uintptr) (err error) = winhttp.WinHttpReceiveResponse
+//sys winHttpQueryHeaders(requestHandle _HINTERNET, infoLevel uint32, name *uint16, buffer unsafe.Pointer, bufferLen *uint32, index *uint32) (err error) = winhttp.WinHttpQueryHeaders
+//sys winHttpQueryDataAvailable(requestHandle _HINTERNET, bytesAvailable *uint32) (err error) = winhttp.WinHttpQueryDataAvailable
+//sys winHttpReadData(requestHandle _HINTERNET, buffer *byte, bufferSize uint32, bytesRead *uint32) (err error) = winhttp.WinHttpReadData
+//sys winHttpCrackUrl(url *uint16, urlSize uint32, flags uint32, components *_URL_COMPONENTS) (err error) = winhttp.WinHttpCrackUrl
+//sys winHttpSetOption(sessionOrRequestHandle _HINTERNET, option uint32, buffer unsafe.Pointer, bufferLen uint32) (err error) = winhttp.WinHttpSetOption
diff --git a/updater/winhttp/winhttp.go b/updater/winhttp/winhttp.go
new file mode 100644
index 00000000..cb19f194
--- /dev/null
+++ b/updater/winhttp/winhttp.go
@@ -0,0 +1,230 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package winhttp
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "runtime"
+ "strconv"
+ "strings"
+ "sync/atomic"
+ "unsafe"
+
+ "golang.org/x/sys/windows"
+)
+
+type Session struct {
+ handle _HINTERNET
+}
+
+type Connection struct {
+ handle _HINTERNET
+ session *Session
+ https bool
+}
+
+type Response struct {
+ handle _HINTERNET
+ connection *Connection
+}
+
+func convertError(err *error) {
+ if *err == nil {
+ return
+ }
+ var errno windows.Errno
+ if errors.As(*err, &errno) {
+ if errno > _WINHTTP_ERROR_BASE && errno <= _WINHTTP_ERROR_LAST {
+ *err = Error(errno)
+ }
+ }
+}
+
+func isWin7() bool {
+ maj, min, _ := windows.RtlGetNtVersionNumbers()
+ return maj < 6 || (maj == 6 && min <= 1)
+}
+
+func isWin8DotZeroOrBelow() bool {
+ maj, min, _ := windows.RtlGetNtVersionNumbers()
+ return maj < 6 || (maj == 6 && min <= 2)
+}
+
+func NewSession(userAgent string) (session *Session, err error) {
+ session = new(Session)
+ defer convertError(&err)
+ defer func() {
+ if err != nil {
+ session.Close()
+ session = nil
+ }
+ }()
+ userAgent16, err := windows.UTF16PtrFromString(userAgent)
+ if err != nil {
+ return
+ }
+ var proxyFlag uint32 = _WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY
+ if isWin7() {
+ proxyFlag = _WINHTTP_ACCESS_TYPE_DEFAULT_PROXY
+ }
+ session.handle, err = winHttpOpen(userAgent16, proxyFlag, nil, nil, 0)
+ if err != nil {
+ return
+ }
+ var enableHttp2 uint32 = _WINHTTP_PROTOCOL_FLAG_HTTP2
+ _ = winHttpSetOption(session.handle, _WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL, unsafe.Pointer(&enableHttp2), uint32(unsafe.Sizeof(enableHttp2))) // Don't check return value, in case of old Windows
+
+ if isWin8DotZeroOrBelow() {
+ var enableTLS12 uint32 = _WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2
+ err = winHttpSetOption(session.handle, _WINHTTP_OPTION_SECURE_PROTOCOLS, unsafe.Pointer(&enableTLS12), uint32(unsafe.Sizeof(enableTLS12)))
+ if err != nil {
+ return
+ }
+ }
+
+ runtime.SetFinalizer(session, func(session *Session) {
+ session.Close()
+ })
+ return
+}
+
+func (session *Session) Close() (err error) {
+ defer convertError(&err)
+ handle := (_HINTERNET)(atomic.SwapUintptr((*uintptr)(&session.handle), 0))
+ if handle == 0 {
+ return
+ }
+ return winHttpCloseHandle(handle)
+}
+
+func (session *Session) Connect(server string, port uint16, https bool) (connection *Connection, err error) {
+ connection = &Connection{session: session}
+ defer convertError(&err)
+ defer func() {
+ if err != nil {
+ connection.Close()
+ connection = nil
+ }
+ }()
+ server16, err := windows.UTF16PtrFromString(server)
+ if err != nil {
+ return
+ }
+ connection.handle, err = winHttpConnect(session.handle, server16, port, 0)
+ if err != nil {
+ return
+ }
+ connection.https = https
+
+ runtime.SetFinalizer(connection, func(connection *Connection) {
+ connection.Close()
+ })
+ return
+}
+
+func (connection *Connection) Close() (err error) {
+ defer convertError(&err)
+ handle := (_HINTERNET)(atomic.SwapUintptr((*uintptr)(&connection.handle), 0))
+ if handle == 0 {
+ return
+ }
+ return winHttpCloseHandle(handle)
+}
+
+func (connection *Connection) Get(path string, refresh bool) (response *Response, err error) {
+ response = &Response{connection: connection}
+ defer convertError(&err)
+ defer func() {
+ if err != nil {
+ response.Close()
+ response = nil
+ }
+ }()
+ var flags uint32
+ if refresh {
+ flags |= _WINHTTP_FLAG_REFRESH
+ }
+ if connection.https {
+ flags |= _WINHTTP_FLAG_SECURE
+ }
+ path16, err := windows.UTF16PtrFromString(path)
+ if err != nil {
+ return
+ }
+ get16, err := windows.UTF16PtrFromString("GET")
+ if err != nil {
+ return
+ }
+ response.handle, err = winHttpOpenRequest(connection.handle, get16, path16, nil, nil, nil, flags)
+ if err != nil {
+ return
+ }
+ err = winHttpSendRequest(response.handle, nil, 0, nil, 0, 0, 0)
+ if err != nil {
+ return
+ }
+ err = winHttpReceiveResponse(response.handle, 0)
+ if err != nil {
+ return
+ }
+
+ runtime.SetFinalizer(response, func(response *Response) {
+ response.Close()
+ })
+ return
+}
+
+func (response *Response) Length() (length uint64, err error) {
+ defer convertError(&err)
+ numBuf := make([]uint16, 22)
+ numLen := uint32(len(numBuf) * 2)
+ err = winHttpQueryHeaders(response.handle, _WINHTTP_QUERY_CONTENT_LENGTH, nil, unsafe.Pointer(&numBuf[0]), &numLen, nil)
+ if err != nil {
+ return
+ }
+ length, err = strconv.ParseUint(windows.UTF16ToString(numBuf[:numLen]), 10, 64)
+ if err != nil {
+ return
+ }
+ return
+}
+
+func (response *Response) Read(p []byte) (n int, err error) {
+ defer convertError(&err)
+ if len(p) == 0 {
+ return 0, nil
+ }
+ var bytesRead uint32
+ err = winHttpReadData(response.handle, &p[0], uint32(len(p)), &bytesRead)
+ if err != nil {
+ return 0, nil
+ }
+ if bytesRead == 0 || int(bytesRead) < 0 {
+ return 0, io.EOF
+ }
+ return int(bytesRead), nil
+}
+
+func (response *Response) Close() (err error) {
+ defer convertError(&err)
+ handle := (_HINTERNET)(atomic.SwapUintptr((*uintptr)(&response.handle), 0))
+ if handle == 0 {
+ return
+ }
+ return winHttpCloseHandle(handle)
+}
+
+func (error Error) Error() string {
+ var message [2048]uint16
+ n, err := windows.FormatMessage(windows.FORMAT_MESSAGE_FROM_HMODULE|windows.FORMAT_MESSAGE_IGNORE_INSERTS|windows.FORMAT_MESSAGE_MAX_WIDTH_MASK,
+ modwinhttp.Handle(), uint32(error), 0, message[:], nil)
+ if err != nil {
+ return fmt.Sprintf("WinHTTP error #%d", error)
+ }
+ return strings.TrimSpace(windows.UTF16ToString(message[:n]))
+}
diff --git a/updater/winhttp/winhttp_test.go b/updater/winhttp/winhttp_test.go
new file mode 100644
index 00000000..36017a48
--- /dev/null
+++ b/updater/winhttp/winhttp_test.go
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package winhttp
+
+import (
+ "fmt"
+ "io"
+ "runtime"
+ "testing"
+)
+
+type progressPrinter struct {
+ downloaded uint64
+ total uint64
+}
+
+func (pp *progressPrinter) Write(p []byte) (int, error) {
+ bytes := len(p)
+ pp.downloaded += uint64(bytes)
+ fmt.Printf("%d/%d bytes, %f%%\n", pp.downloaded, pp.total, float64(pp.downloaded)/float64(pp.total)*100.0)
+ return bytes, nil
+}
+
+func TestResponse(t *testing.T) {
+ session, err := NewSession("WinHTTP Test Suite/1.0")
+ if err != nil {
+ t.Fatal(err)
+ }
+ connection, err := session.Connect("zx2c4.com", 443, true)
+ if err != nil {
+ t.Fatal(err)
+ }
+ r, err := connection.Get("/ip", true)
+ length, err := r.Length()
+ if err != nil {
+ t.Fatal(err)
+ }
+ fmt.Printf("The length is %d\n", length)
+ bytes, err := io.ReadAll(r)
+ if err != nil {
+ t.Fatal(err)
+ }
+ fmt.Println(string(bytes))
+ r.Close()
+
+ connection, err = session.Connect("speed.hetzner.de", 443, true)
+ if err != nil {
+ t.Fatal(err)
+ }
+ r, err = connection.Get("/10GB.bin", false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ length, err = r.Length()
+ if err != nil {
+ t.Fatal(err)
+ }
+ amountRead, err := io.Copy(&progressPrinter{total: length}, r)
+ if err != nil {
+ t.Fatal(err)
+ }
+ r.Close()
+ if length != uint64(amountRead) {
+ t.Fatalf("Expected to read %d, but only read %d", length, amountRead)
+ }
+
+ runtime.GC() // Try to force the finalizers to be called
+}
diff --git a/updater/winhttp/zsyscall_windows.go b/updater/winhttp/zsyscall_windows.go
new file mode 100644
index 00000000..1c45994f
--- /dev/null
+++ b/updater/winhttp/zsyscall_windows.go
@@ -0,0 +1,155 @@
+// Code generated by 'go generate'; DO NOT EDIT.
+
+package winhttp
+
+import (
+ "syscall"
+ "unsafe"
+
+ "golang.org/x/sys/windows"
+)
+
+var _ unsafe.Pointer
+
+// Do the interface allocations only once for common
+// Errno values.
+const (
+ errnoERROR_IO_PENDING = 997
+)
+
+var (
+ errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
+ errERROR_EINVAL error = syscall.EINVAL
+)
+
+// errnoErr returns common boxed Errno values, to prevent
+// allocations at runtime.
+func errnoErr(e syscall.Errno) error {
+ switch e {
+ case 0:
+ return errERROR_EINVAL
+ case errnoERROR_IO_PENDING:
+ return errERROR_IO_PENDING
+ }
+ // TODO: add more here, after collecting data on the common
+ // error values see on Windows. (perhaps when running
+ // all.bat?)
+ return e
+}
+
+var (
+ modwinhttp = windows.NewLazySystemDLL("winhttp.dll")
+
+ procWinHttpCloseHandle = modwinhttp.NewProc("WinHttpCloseHandle")
+ procWinHttpConnect = modwinhttp.NewProc("WinHttpConnect")
+ procWinHttpCrackUrl = modwinhttp.NewProc("WinHttpCrackUrl")
+ procWinHttpOpen = modwinhttp.NewProc("WinHttpOpen")
+ procWinHttpOpenRequest = modwinhttp.NewProc("WinHttpOpenRequest")
+ procWinHttpQueryDataAvailable = modwinhttp.NewProc("WinHttpQueryDataAvailable")
+ procWinHttpQueryHeaders = modwinhttp.NewProc("WinHttpQueryHeaders")
+ procWinHttpReadData = modwinhttp.NewProc("WinHttpReadData")
+ procWinHttpReceiveResponse = modwinhttp.NewProc("WinHttpReceiveResponse")
+ procWinHttpSendRequest = modwinhttp.NewProc("WinHttpSendRequest")
+ procWinHttpSetOption = modwinhttp.NewProc("WinHttpSetOption")
+ procWinHttpSetStatusCallback = modwinhttp.NewProc("WinHttpSetStatusCallback")
+)
+
+func winHttpCloseHandle(handle _HINTERNET) (err error) {
+ r1, _, e1 := syscall.Syscall(procWinHttpCloseHandle.Addr(), 1, uintptr(handle), 0, 0)
+ if r1 == 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
+
+func winHttpConnect(sessionHandle _HINTERNET, serverName *uint16, serverPort uint16, reserved uint32) (handle _HINTERNET, err error) {
+ r0, _, e1 := syscall.Syscall6(procWinHttpConnect.Addr(), 4, uintptr(sessionHandle), uintptr(unsafe.Pointer(serverName)), uintptr(serverPort), uintptr(reserved), 0, 0)
+ handle = _HINTERNET(r0)
+ if handle == 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
+
+func winHttpCrackUrl(url *uint16, urlSize uint32, flags uint32, components *_URL_COMPONENTS) (err error) {
+ r1, _, e1 := syscall.Syscall6(procWinHttpCrackUrl.Addr(), 4, uintptr(unsafe.Pointer(url)), uintptr(urlSize), uintptr(flags), uintptr(unsafe.Pointer(components)), 0, 0)
+ if r1 == 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
+
+func winHttpOpen(userAgent *uint16, accessType uint32, proxy *uint16, proxyBypass *uint16, flags uint32) (sessionHandle _HINTERNET, err error) {
+ r0, _, e1 := syscall.Syscall6(procWinHttpOpen.Addr(), 5, uintptr(unsafe.Pointer(userAgent)), uintptr(accessType), uintptr(unsafe.Pointer(proxy)), uintptr(unsafe.Pointer(proxyBypass)), uintptr(flags), 0)
+ sessionHandle = _HINTERNET(r0)
+ if sessionHandle == 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
+
+func winHttpOpenRequest(connectHandle _HINTERNET, verb *uint16, objectName *uint16, version *uint16, referrer *uint16, acceptTypes **uint16, flags uint32) (requestHandle _HINTERNET, err error) {
+ r0, _, e1 := syscall.Syscall9(procWinHttpOpenRequest.Addr(), 7, uintptr(connectHandle), uintptr(unsafe.Pointer(verb)), uintptr(unsafe.Pointer(objectName)), uintptr(unsafe.Pointer(version)), uintptr(unsafe.Pointer(referrer)), uintptr(unsafe.Pointer(acceptTypes)), uintptr(flags), 0, 0)
+ requestHandle = _HINTERNET(r0)
+ if requestHandle == 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
+
+func winHttpQueryDataAvailable(requestHandle _HINTERNET, bytesAvailable *uint32) (err error) {
+ r1, _, e1 := syscall.Syscall(procWinHttpQueryDataAvailable.Addr(), 2, uintptr(requestHandle), uintptr(unsafe.Pointer(bytesAvailable)), 0)
+ if r1 == 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
+
+func winHttpQueryHeaders(requestHandle _HINTERNET, infoLevel uint32, name *uint16, buffer unsafe.Pointer, bufferLen *uint32, index *uint32) (err error) {
+ r1, _, e1 := syscall.Syscall6(procWinHttpQueryHeaders.Addr(), 6, uintptr(requestHandle), uintptr(infoLevel), uintptr(unsafe.Pointer(name)), uintptr(buffer), uintptr(unsafe.Pointer(bufferLen)), uintptr(unsafe.Pointer(index)))
+ if r1 == 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
+
+func winHttpReadData(requestHandle _HINTERNET, buffer *byte, bufferSize uint32, bytesRead *uint32) (err error) {
+ r1, _, e1 := syscall.Syscall6(procWinHttpReadData.Addr(), 4, uintptr(requestHandle), uintptr(unsafe.Pointer(buffer)), uintptr(bufferSize), uintptr(unsafe.Pointer(bytesRead)), 0, 0)
+ if r1 == 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
+
+func winHttpReceiveResponse(requestHandle _HINTERNET, reserved uintptr) (err error) {
+ r1, _, e1 := syscall.Syscall(procWinHttpReceiveResponse.Addr(), 2, uintptr(requestHandle), uintptr(reserved), 0)
+ if r1 == 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
+
+func winHttpSendRequest(requestHandle _HINTERNET, headers *uint16, headersLength uint32, optional *byte, optionalLength uint32, totalLength uint32, context uintptr) (err error) {
+ r1, _, e1 := syscall.Syscall9(procWinHttpSendRequest.Addr(), 7, uintptr(requestHandle), uintptr(unsafe.Pointer(headers)), uintptr(headersLength), uintptr(unsafe.Pointer(optional)), uintptr(optionalLength), uintptr(totalLength), uintptr(context), 0, 0)
+ if r1 == 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
+
+func winHttpSetOption(sessionOrRequestHandle _HINTERNET, option uint32, buffer unsafe.Pointer, bufferLen uint32) (err error) {
+ r1, _, e1 := syscall.Syscall6(procWinHttpSetOption.Addr(), 4, uintptr(sessionOrRequestHandle), uintptr(option), uintptr(buffer), uintptr(bufferLen), 0, 0)
+ if r1 == 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
+
+func winHttpSetStatusCallback(handle _HINTERNET, callback uintptr, notificationFlags uint32, reserved uintptr) (previousCallback uintptr, err error) {
+ r0, _, e1 := syscall.Syscall6(procWinHttpSetStatusCallback.Addr(), 4, uintptr(handle), uintptr(callback), uintptr(notificationFlags), uintptr(reserved), 0, 0)
+ previousCallback = uintptr(r0)
+ if previousCallback == _WINHTTP_INVALID_STATUS_CALLBACK {
+ err = errnoErr(e1)
+ }
+ return
+}
diff --git a/version/certificate_test.go b/version/certificate_test.go
new file mode 100644
index 00000000..724c524f
--- /dev/null
+++ b/version/certificate_test.go
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package version
+
+import (
+ "fmt"
+ "path/filepath"
+ "testing"
+
+ "golang.org/x/sys/windows"
+)
+
+func TestExtractCertificateNames(t *testing.T) {
+ system32, err := windows.GetSystemDirectory()
+ if err != nil {
+ t.Fatal(err)
+ }
+ names, err := extractCertificateNames(filepath.Join(system32, "ntoskrnl.exe"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ for i, name := range names {
+ fmt.Printf("%d: %s\n", i, name)
+ }
+}
+
+func TestExtractCertificateExtension(t *testing.T) {
+ system32, err := windows.GetSystemDirectory()
+ if err != nil {
+ t.Fatal(err)
+ }
+ policies, err := extractCertificatePolicies(filepath.Join(system32, "ntoskrnl.exe"), "2.5.29.32")
+ if err != nil {
+ t.Fatal(err)
+ }
+ for i, policy := range policies {
+ fmt.Printf("%d: %s\n", i, policy)
+ }
+}
diff --git a/version/debugging_linux.go b/version/debugging_linux.go
deleted file mode 100644
index ddea4242..00000000
--- a/version/debugging_linux.go
+++ /dev/null
@@ -1,35 +0,0 @@
-/* SPDX-License-Identifier: MIT
- *
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
- */
-
-package version
-
-import (
- "bytes"
- "fmt"
-
- "golang.org/x/sys/unix"
-)
-
-// For testing the updater package from linux. Debug stuff only.
-
-func utsToStr(u [65]byte) string {
- i := bytes.IndexByte(u[:], 0)
- if i < 0 {
- return string(u[:])
- }
- return string(u[:i])
-}
-
-func OsName() string {
- var utsname unix.Utsname
- if unix.Uname(&utsname) != nil {
- return "Unix Unknown"
- }
- return fmt.Sprintf("%s %s %s", utsToStr(utsname.Sysname), utsToStr(utsname.Release), utsToStr(utsname.Version))
-}
-
-func VerifyAuthenticode(path string) bool {
- return true
-}
diff --git a/version/official.go b/version/official.go
new file mode 100644
index 00000000..2951c893
--- /dev/null
+++ b/version/official.go
@@ -0,0 +1,156 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package version
+
+import (
+ "errors"
+ "os"
+ "unsafe"
+
+ "golang.org/x/sys/windows"
+)
+
+const (
+ officialCommonName = "WireGuard LLC"
+ evPolicyOid = "2.23.140.1.3"
+ policyExtensionOid = "2.5.29.32"
+)
+
+// These are easily by-passable checks, which do not serve security purposes.
+// DO NOT PLACE SECURITY-SENSITIVE FUNCTIONS IN THIS FILE
+
+func IsRunningOfficialVersion() bool {
+ path, err := os.Executable()
+ if err != nil {
+ return false
+ }
+
+ names, err := extractCertificateNames(path)
+ if err != nil {
+ return false
+ }
+ for _, name := range names {
+ if name == officialCommonName {
+ return true
+ }
+ }
+ return false
+}
+
+func IsRunningEVSigned() bool {
+ path, err := os.Executable()
+ if err != nil {
+ return false
+ }
+
+ policies, err := extractCertificatePolicies(path, policyExtensionOid)
+ if err != nil {
+ return false
+ }
+ for _, policy := range policies {
+ if policy == evPolicyOid {
+ return true
+ }
+ }
+ return false
+}
+
+func extractCertificateNames(path string) ([]string, error) {
+ path16, err := windows.UTF16PtrFromString(path)
+ if err != nil {
+ return nil, err
+ }
+ var certStore windows.Handle
+ err = windows.CryptQueryObject(windows.CERT_QUERY_OBJECT_FILE, unsafe.Pointer(path16), windows.CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, windows.CERT_QUERY_FORMAT_FLAG_ALL, 0, nil, nil, nil, &certStore, nil, nil)
+ if err != nil {
+ return nil, err
+ }
+ defer windows.CertCloseStore(certStore, 0)
+ var cert *windows.CertContext
+ var names []string
+ for {
+ cert, err = windows.CertEnumCertificatesInStore(certStore, cert)
+ if err != nil {
+ if errors.Is(err, windows.Errno(windows.CRYPT_E_NOT_FOUND)) {
+ break
+ }
+ return nil, err
+ }
+ if cert == nil {
+ break
+ }
+ nameLen := windows.CertGetNameString(cert, windows.CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nil, nil, 0)
+ if nameLen == 0 {
+ continue
+ }
+ name16 := make([]uint16, nameLen)
+ if windows.CertGetNameString(cert, windows.CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nil, &name16[0], nameLen) != nameLen {
+ continue
+ }
+ if name16[0] == 0 {
+ continue
+ }
+ names = append(names, windows.UTF16ToString(name16))
+ }
+ if names == nil {
+ return nil, windows.Errno(windows.CRYPT_E_NOT_FOUND)
+ }
+ return names, nil
+}
+
+func extractCertificatePolicies(path, oid string) ([]string, error) {
+ path16, err := windows.UTF16PtrFromString(path)
+ if err != nil {
+ return nil, err
+ }
+ oid8, err := windows.BytePtrFromString(oid)
+ if err != nil {
+ return nil, err
+ }
+ var certStore windows.Handle
+ err = windows.CryptQueryObject(windows.CERT_QUERY_OBJECT_FILE, unsafe.Pointer(path16), windows.CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, windows.CERT_QUERY_FORMAT_FLAG_ALL, 0, nil, nil, nil, &certStore, nil, nil)
+ if err != nil {
+ return nil, err
+ }
+ defer windows.CertCloseStore(certStore, 0)
+ var cert *windows.CertContext
+ var policies []string
+ for {
+ cert, err = windows.CertEnumCertificatesInStore(certStore, cert)
+ if err != nil {
+ if errors.Is(err, windows.Errno(windows.CRYPT_E_NOT_FOUND)) {
+ break
+ }
+ return nil, err
+ }
+ if cert == nil {
+ break
+ }
+ ext := windows.CertFindExtension(oid8, cert.CertInfo.CountExtensions, cert.CertInfo.Extensions)
+ if ext == nil {
+ continue
+ }
+ var decodedLen uint32
+ err = windows.CryptDecodeObject(windows.X509_ASN_ENCODING|windows.PKCS_7_ASN_ENCODING, ext.ObjId, ext.Value.Data, ext.Value.Size, 0, nil, &decodedLen)
+ if err != nil {
+ return nil, err
+ }
+ bytes := make([]byte, decodedLen)
+ certPoliciesInfo := (*windows.CertPoliciesInfo)(unsafe.Pointer(&bytes[0]))
+ err = windows.CryptDecodeObject(windows.X509_ASN_ENCODING|windows.PKCS_7_ASN_ENCODING, ext.ObjId, ext.Value.Data, ext.Value.Size, 0, unsafe.Pointer(&bytes[0]), &decodedLen)
+ if err != nil {
+ return nil, err
+ }
+ for i := uintptr(0); i < uintptr(certPoliciesInfo.Count); i++ {
+ cp := (*windows.CertPolicyInfo)(unsafe.Add(unsafe.Pointer(certPoliciesInfo.PolicyInfos), i*unsafe.Sizeof(*certPoliciesInfo.PolicyInfos)))
+ policies = append(policies, windows.BytePtrToString(cp.Identifier))
+ }
+ }
+ if policies == nil {
+ return nil, windows.Errno(windows.CRYPT_E_NOT_FOUND)
+ }
+ return policies, nil
+}
diff --git a/version/official_windows.go b/version/official_windows.go
deleted file mode 100644
index 5f8ea731..00000000
--- a/version/official_windows.go
+++ /dev/null
@@ -1,105 +0,0 @@
-/* SPDX-License-Identifier: MIT
- *
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
- */
-
-package version
-
-import (
- "encoding/asn1"
- "os"
- "unsafe"
-
- "golang.org/x/sys/windows"
- "golang.zx2c4.com/wireguard/windows/version/wintrust"
-)
-
-const (
- officialCommonName = "WireGuard LLC"
- evPolicyOid = "2.23.140.1.3"
- policyExtensionOid = "2.5.29.32"
-)
-
-type policyQualifierInfo struct {
- PolicyQualifierId asn1.ObjectIdentifier
- Qualifier asn1.RawValue
-}
-
-type policyInformation struct {
- Policy asn1.ObjectIdentifier
- Qualifiers []policyQualifierInfo `asn1:"optional"`
-}
-
-func VerifyAuthenticode(path string) bool {
- path16, err := windows.UTF16PtrFromString(path)
- if err != nil {
- return false
- }
- file := &wintrust.WinTrustFileInfo{
- CbStruct: uint32(unsafe.Sizeof(wintrust.WinTrustFileInfo{})),
- FilePath: path16,
- }
- data := &wintrust.WinTrustData{
- CbStruct: uint32(unsafe.Sizeof(wintrust.WinTrustData{})),
- UIChoice: wintrust.WTD_UI_NONE,
- RevocationChecks: wintrust.WTD_REVOKE_WHOLECHAIN, // Full revocation checking, as this is called with network connectivity.
- UnionChoice: wintrust.WTD_CHOICE_FILE,
- StateAction: wintrust.WTD_STATEACTION_VERIFY,
- FileOrCatalogOrBlobOrSgnrOrCert: uintptr(unsafe.Pointer(file)),
- }
- return wintrust.WinVerifyTrust(0, &wintrust.WINTRUST_ACTION_GENERIC_VERIFY_V2, data) == nil
-}
-
-// This is an easily by-passable check, which doesn't serve a security purpose but mostly just a low-grade
-// informational and semantic one.
-func IsRunningOfficialVersion() bool {
- path, err := os.Executable()
- if err != nil {
- return false
- }
-
- // This is easily circumvented. We don't even verify the chain before hand with WinVerifyTrust.
- // False certificates can be appended. But that's okay, as this isn't security related.
-
- certs, err := wintrust.ExtractCertificates(path)
- if err != nil {
- return false
- }
- for _, cert := range certs {
- if cert.Subject.CommonName == officialCommonName {
- return true
- }
- }
- return false
-}
-
-func IsRunningEVSigned() bool {
- path, err := os.Executable()
- if err != nil {
- return false
- }
-
- // This is easily circumvented. We don't even verify the chain before hand with WinVerifyTrust.
- // False certificates can be appended. But that's okay, as this isn't security related.
-
- certs, err := wintrust.ExtractCertificates(path)
- if err != nil {
- return false
- }
- for _, cert := range certs {
- for _, extension := range cert.Extensions {
- if extension.Id.String() == policyExtensionOid {
- var policies []policyInformation
- if _, err = asn1.Unmarshal(extension.Value, &policies); err != nil {
- continue
- }
- for _, policy := range policies {
- if policy.Policy.String() == evPolicyOid {
- return true
- }
- }
- }
- }
- }
- return false
-}
diff --git a/version/os_windows.go b/version/os.go
index 670d3da7..ff9188bb 100644
--- a/version/os_windows.go
+++ b/version/os.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package version
diff --git a/version/useragent.go b/version/useragent.go
index 1c1adf55..3b5240c6 100644
--- a/version/useragent.go
+++ b/version/useragent.go
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
*/
package version
@@ -10,11 +10,17 @@ import (
"runtime"
)
-// #include "version.h"
-import "C"
-
-const Number = C.WIREGUARD_WINDOWS_VERSION_STRING
+func Arch() string {
+ switch runtime.GOARCH {
+ case "arm", "arm64", "amd64":
+ return runtime.GOARCH
+ case "386":
+ return "x86"
+ default:
+ panic("Unrecognized GOARCH")
+ }
+}
func UserAgent() string {
- return fmt.Sprintf("WireGuard/%s (%s; %s)", Number, OsName(), runtime.GOARCH)
+ return fmt.Sprintf("WireGuard/%s (%s; %s)", Number, OsName(), Arch())
}
diff --git a/version/version.go b/version/version.go
new file mode 100644
index 00000000..a7f908e8
--- /dev/null
+++ b/version/version.go
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
+ */
+
+package version
+
+const (
+ Number = "0.5.3"
+)
diff --git a/version/version.h b/version/version.h
deleted file mode 100644
index 30f17ca8..00000000
--- a/version/version.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#define WIREGUARD_WINDOWS_VERSION_ARRAY 0,0,37
-#define WIREGUARD_WINDOWS_VERSION_STRING "0.0.37"
diff --git a/version/wintrust/certificate_windows.go b/version/wintrust/certificate_windows.go
deleted file mode 100644
index 1e145095..00000000
--- a/version/wintrust/certificate_windows.go
+++ /dev/null
@@ -1,59 +0,0 @@
-/* SPDX-License-Identifier: MIT
- *
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
- */
-
-package wintrust
-
-import (
- "crypto/x509"
- "syscall"
- "unsafe"
-
- "golang.org/x/sys/windows"
-)
-
-const (
- _CERT_QUERY_OBJECT_FILE = 1
- _CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED = 1024
- _CERT_QUERY_FORMAT_FLAG_ALL = 14
-)
-
-//sys cryptQueryObject(objectType uint32, object uintptr, expectedContentTypeFlags uint32, expectedFormatTypeFlags uint32, flags uint32, msgAndCertEncodingType *uint32, contentType *uint32, formatType *uint32, certStore *windows.Handle, msg *windows.Handle, context *uintptr) (err error) = crypt32.CryptQueryObject
-
-func ExtractCertificates(path string) ([]x509.Certificate, error) {
- path16, err := windows.UTF16PtrFromString(path)
- if err != nil {
- return nil, err
- }
- var certStore windows.Handle
- err = cryptQueryObject(_CERT_QUERY_OBJECT_FILE, uintptr(unsafe.Pointer(path16)), _CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, _CERT_QUERY_FORMAT_FLAG_ALL, 0, nil, nil, nil, &certStore, nil, nil)
- if err != nil {
- return nil, err
- }
- defer windows.CertCloseStore(certStore, 0)
- var certs []x509.Certificate
- var cert *windows.CertContext
- for {
- cert, err = windows.CertEnumCertificatesInStore(certStore, cert)
- if err != nil {
- if errno, ok := err.(syscall.Errno); ok {
- if errno == syscall.Errno(windows.CRYPT_E_NOT_FOUND) {
- break
- }
- }
- return nil, err
- }
- if cert == nil {
- break
- }
- buf := make([]byte, cert.Length)
- copy(buf, (*[1 << 20]byte)(unsafe.Pointer(cert.EncodedCert))[:])
- if c, err := x509.ParseCertificate(buf); err == nil {
- certs = append(certs, *c)
- } else {
- return nil, err
- }
- }
- return certs, nil
-}
diff --git a/version/wintrust/mksyscall.go b/version/wintrust/mksyscall.go
deleted file mode 100644
index 890b2668..00000000
--- a/version/wintrust/mksyscall.go
+++ /dev/null
@@ -1,8 +0,0 @@
-/* SPDX-License-Identifier: MIT
- *
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
- */
-
-package wintrust
-
-//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go wintrust_windows.go certificate_windows.go
diff --git a/version/wintrust/wintrust_windows.go b/version/wintrust/wintrust_windows.go
deleted file mode 100644
index fa3b2f0b..00000000
--- a/version/wintrust/wintrust_windows.go
+++ /dev/null
@@ -1,116 +0,0 @@
-/* SPDX-License-Identifier: MIT
- *
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
- */
-
-package wintrust
-
-import (
- "syscall"
-
- "golang.org/x/sys/windows"
-)
-
-type WinTrustData struct {
- CbStruct uint32
- PolicyCallbackData uintptr
- SIPClientData uintptr
- UIChoice uint32
- RevocationChecks uint32
- UnionChoice uint32
- FileOrCatalogOrBlobOrSgnrOrCert uintptr
- StateAction uint32
- StateData syscall.Handle
- URLReference *uint16
- ProvFlags uint32
- UIContext uint32
- SignatureSettings *WintrustSignatureSettings
-}
-
-const (
- WTD_UI_ALL = 1
- WTD_UI_NONE = 2
- WTD_UI_NOBAD = 3
- WTD_UI_NOGOOD = 4
-)
-
-const (
- WTD_REVOKE_NONE = 0
- WTD_REVOKE_WHOLECHAIN = 1
-)
-
-const (
- WTD_CHOICE_FILE = 1
- WTD_CHOICE_CATALOG = 2
- WTD_CHOICE_BLOB = 3
- WTD_CHOICE_SIGNER = 4
- WTD_CHOICE_CERT = 5
-)
-
-const (
- WTD_STATEACTION_IGNORE = 0x00000000
- WTD_STATEACTION_VERIFY = 0x00000010
- WTD_STATEACTION_CLOSE = 0x00000002
- WTD_STATEACTION_AUTO_CACHE = 0x00000003
- WTD_STATEACTION_AUTO_CACHE_FLUSH = 0x00000004
-)
-
-const (
- WTD_USE_IE4_TRUST_FLAG = 0x1
- WTD_NO_IE4_CHAIN_FLAG = 0x2
- WTD_NO_POLICY_USAGE_FLAG = 0x4
- WTD_REVOCATION_CHECK_NONE = 0x10
- WTD_REVOCATION_CHECK_END_CERT = 0x20
- WTD_REVOCATION_CHECK_CHAIN = 0x40
- WTD_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT = 0x80
- WTD_SAFER_FLAG = 0x100
- WTD_HASH_ONLY_FLAG = 0x200
- WTD_USE_DEFAULT_OSVER_CHECK = 0x400
- WTD_LIFETIME_SIGNING_FLAG = 0x800
- WTD_CACHE_ONLY_URL_RETRIEVAL = 0x1000
- WTD_DISABLE_MD2_MD4 = 0x2000
- WTD_MOTW = 0x4000
-)
-
-const (
- TRUST_E_NOSIGNATURE = 0x800B0100
- TRUST_E_EXPLICIT_DISTRUST = 0x800B0111
- TRUST_E_SUBJECT_NOT_TRUSTED = 0x800B0004
- CRYPT_E_SECURITY_SETTINGS = 0x80092026
-)
-
-const (
- WTD_UICONTEXT_EXECUTE = 0
- WTD_UICONTEXT_INSTALL = 1
-)
-
-var WINTRUST_ACTION_GENERIC_VERIFY_V2 = windows.GUID{
- Data1: 0xaac56b,
- Data2: 0xcd44,
- Data3: 0x11d0,
- Data4: [8]byte{0x8c, 0xc2, 0x0, 0xc0, 0x4f, 0xc2, 0x95, 0xee},
-}
-
-type WinTrustFileInfo struct {
- CbStruct uint32
- FilePath *uint16
- File windows.Handle
- KnownSubject *windows.GUID
-}
-
-type WintrustSignatureSettings struct {
- CbStruct uint32
- Index uint32
- Flags uint32
- SecondarySigs uint32
- VerifiedSigIndex uint32
- CryptoPolicy *CertStrongSignPara
-}
-
-type CertStrongSignPara struct {
- CbStruct uint32
- InfoChoice uint32
- InfoOrSerializedInfoOrOID uintptr
-}
-
-//sys WinVerifyTrust(hWnd windows.Handle, actionId *windows.GUID, data *WinTrustData) (err error) [r1 != 0] = wintrust.WinVerifyTrust
diff --git a/version/wintrust/zsyscall_windows.go b/version/wintrust/zsyscall_windows.go
deleted file mode 100644
index 4d73cc5e..00000000
--- a/version/wintrust/zsyscall_windows.go
+++ /dev/null
@@ -1,69 +0,0 @@
-// Code generated by 'go generate'; DO NOT EDIT.
-
-package wintrust
-
-import (
- "syscall"
- "unsafe"
-
- "golang.org/x/sys/windows"
-)
-
-var _ unsafe.Pointer
-
-// Do the interface allocations only once for common
-// Errno values.
-const (
- errnoERROR_IO_PENDING = 997
-)
-
-var (
- errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
-)
-
-// errnoErr returns common boxed Errno values, to prevent
-// allocations at runtime.
-func errnoErr(e syscall.Errno) error {
- switch e {
- case 0:
- return nil
- case errnoERROR_IO_PENDING:
- return errERROR_IO_PENDING
- }
- // TODO: add more here, after collecting data on the common
- // error values see on Windows. (perhaps when running
- // all.bat?)
- return e
-}
-
-var (
- modwintrust = windows.NewLazySystemDLL("wintrust.dll")
- modcrypt32 = windows.NewLazySystemDLL("crypt32.dll")
-
- procWinVerifyTrust = modwintrust.NewProc("WinVerifyTrust")
- procCryptQueryObject = modcrypt32.NewProc("CryptQueryObject")
-)
-
-func WinVerifyTrust(hWnd windows.Handle, actionId *windows.GUID, data *WinTrustData) (err error) {
- r1, _, e1 := syscall.Syscall(procWinVerifyTrust.Addr(), 3, uintptr(hWnd), uintptr(unsafe.Pointer(actionId)), uintptr(unsafe.Pointer(data)))
- if r1 != 0 {
- if e1 != 0 {
- err = errnoErr(e1)
- } else {
- err = syscall.EINVAL
- }
- }
- return
-}
-
-func cryptQueryObject(objectType uint32, object uintptr, expectedContentTypeFlags uint32, expectedFormatTypeFlags uint32, flags uint32, msgAndCertEncodingType *uint32, contentType *uint32, formatType *uint32, certStore *windows.Handle, msg *windows.Handle, context *uintptr) (err error) {
- r1, _, e1 := syscall.Syscall12(procCryptQueryObject.Addr(), 11, uintptr(objectType), uintptr(object), uintptr(expectedContentTypeFlags), uintptr(expectedFormatTypeFlags), uintptr(flags), uintptr(unsafe.Pointer(msgAndCertEncodingType)), uintptr(unsafe.Pointer(contentType)), uintptr(unsafe.Pointer(formatType)), uintptr(unsafe.Pointer(certStore)), uintptr(unsafe.Pointer(msg)), uintptr(unsafe.Pointer(context)), 0)
- if r1 == 0 {
- if e1 != 0 {
- err = errnoErr(e1)
- } else {
- err = syscall.EINVAL
- }
- }
- return
-}
diff --git a/zgotext.go b/zgotext.go
new file mode 100644
index 00000000..efbb9a80
--- /dev/null
+++ b/zgotext.go
@@ -0,0 +1,3850 @@
+// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
+
+package main
+
+import (
+ "golang.org/x/text/language"
+ "golang.org/x/text/message"
+ "golang.org/x/text/message/catalog"
+)
+
+type dictionary struct {
+ index []uint32
+ data string
+}
+
+func (d *dictionary) Lookup(key string) (data string, ok bool) {
+ p, ok := messageKeyToIndex[key]
+ if !ok {
+ return "", false
+ }
+ start, end := d.index[p], d.index[p+1]
+ if start == end {
+ return "", false
+ }
+ return d.data[start:end], true
+}
+
+func init() {
+ dict := map[string]catalog.Dictionary{
+ "ca": &dictionary{index: caIndex, data: caData},
+ "cs": &dictionary{index: csIndex, data: csData},
+ "de": &dictionary{index: deIndex, data: deData},
+ "en": &dictionary{index: enIndex, data: enData},
+ "es_ES": &dictionary{index: es_ESIndex, data: es_ESData},
+ "fa": &dictionary{index: faIndex, data: faData},
+ "fi": &dictionary{index: fiIndex, data: fiData},
+ "fr": &dictionary{index: frIndex, data: frData},
+ "id": &dictionary{index: idIndex, data: idData},
+ "it": &dictionary{index: itIndex, data: itData},
+ "ja": &dictionary{index: jaIndex, data: jaData},
+ "ko": &dictionary{index: koIndex, data: koData},
+ "pa_IN": &dictionary{index: pa_INIndex, data: pa_INData},
+ "pl": &dictionary{index: plIndex, data: plData},
+ "ro": &dictionary{index: roIndex, data: roData},
+ "ru": &dictionary{index: ruIndex, data: ruData},
+ "si_LK": &dictionary{index: si_LKIndex, data: si_LKData},
+ "sk": &dictionary{index: skIndex, data: skData},
+ "sl": &dictionary{index: slIndex, data: slData},
+ "tr": &dictionary{index: trIndex, data: trData},
+ "uk": &dictionary{index: ukIndex, data: ukData},
+ "vi": &dictionary{index: viIndex, data: viData},
+ "zh_CN": &dictionary{index: zh_CNIndex, data: zh_CNData},
+ "zh_TW": &dictionary{index: zh_TWIndex, data: zh_TWData},
+ }
+ fallback := language.MustParse("en")
+ cat, err := catalog.NewFromMap(dict, catalog.Fallback(fallback))
+ if err != nil {
+ panic(err)
+ }
+ message.DefaultCatalog = cat
+}
+
+var messageKeyToIndex = map[string]int{
+ "%.2f\u00a0GiB": 20,
+ "%.2f\u00a0KiB": 18,
+ "%.2f\u00a0MiB": 19,
+ "%.2f\u00a0TiB": 21,
+ "%d day(s)": 12,
+ "%d hour(s)": 13,
+ "%d minute(s)": 14,
+ "%d second(s)": 15,
+ "%d tunnels were unable to be removed.": 169,
+ "%d year(s)": 11,
+ "%d\u00a0B": 17,
+ "%s\n\nPlease consult the log for more information.": 68,
+ "%s (out of date)": 69,
+ "%s (unsigned build, no updates)": 174,
+ "%s - Handshake did not complete after %d attempts, giving up": 234,
+ "%s - Handshake did not complete after %d seconds, retrying (try %d)": 235,
+ "%s - Removing all keys, since we haven't received a new one in %d seconds": 237,
+ "%s - Retrying handshake because we stopped hearing back after %d seconds": 236,
+ "%s You cannot undo this action.": 165,
+ "%s ago": 16,
+ "%s received, %s sent": 95,
+ "%s: %q": 22,
+ "%v": 255,
+ "%v - ConsumeMessageInitiation: handshake flood": 188,
+ "%v - ConsumeMessageInitiation: handshake replay @ %v": 187,
+ "%v - Failed to create initiation message: %v": 220,
+ "%v - Failed to create response message: %v": 223,
+ "%v - Failed to derive keypair: %v": 209,
+ "%v - Failed to send data packet: %v": 233,
+ "%v - Failed to send handshake initiation: %v": 221,
+ "%v - Failed to send handshake response: %v": 224,
+ "%v - Received handshake initiation": 205,
+ "%v - Received handshake response": 208,
+ "%v - Receiving keepalive packet": 212,
+ "%v - Routine: sequential receiver - started": 211,
+ "%v - Routine: sequential receiver - stopped": 210,
+ "%v - Routine: sequential sender - started": 232,
+ "%v - Sending handshake initiation": 219,
+ "%v - Sending handshake response": 222,
+ "%v - Sending keepalive packet": 218,
+ "%v - Starting": 189,
+ "%v - Stopping": 190,
+ "%v - UAPI: Adding allowedip": 266,
+ "%v - UAPI: Created": 260,
+ "%v - UAPI: Removing": 261,
+ "%v - UAPI: Removing all allowedips": 265,
+ "%v - UAPI: Updating endpoint": 263,
+ "%v - UAPI: Updating persistent keepalive interval": 264,
+ "%v - UAPI: Updating preshared key": 262,
+ "&About WireGuard…": 66,
+ "&Activate": 45,
+ "&Block untunneled traffic (kill-switch)": 106,
+ "&Configuration:": 109,
+ "&Copy": 60,
+ "&Deactivate": 44,
+ "&Edit": 143,
+ "&Import tunnel(s) from file…": 128,
+ "&Manage tunnels…": 127,
+ "&Name:": 103,
+ "&Public key:": 104,
+ "&Remove selected tunnel(s)": 151,
+ "&Save": 107,
+ "&Save to file…": 62,
+ "&Test experimental kernel driver": 269,
+ "&Toggle": 148,
+ "&Tunnels": 130,
+ "(no argument): elevate and install manager service": 1,
+ "(unknown)": 105,
+ "A name is required.": 111,
+ "A tunnel was unable to be removed: %s": 167,
+ "About WireGuard": 39,
+ "Activating": 120,
+ "Active": 119,
+ "Add &empty tunnel…": 144,
+ "Add Tunnel": 145,
+ "Addresses:": 49,
+ "Addresses: %s": 138,
+ "Addresses: None": 126,
+ "All peers must have public keys": 35,
+ "Allowed IPs:": 52,
+ "An Update is Available!": 139,
+ "An interface must have a private key": 83,
+ "An update to WireGuard is available. It is highly advisable to update without delay.": 71,
+ "An update to WireGuard is now available. You are advised to update as soon as possible.": 141,
+ "Another tunnel already exists with the name ‘%s’": 155,
+ "Another tunnel already exists with the name ‘%s’.": 115,
+ "App version: %s\nGo backend version: %s\nGo version: %s-%s\nOperating system: %s\nArchitecture: %s": 268,
+ "Are you sure you would like to delete %d tunnels?": 162,
+ "Are you sure you would like to delete tunnel ‘%s’?": 164,
+ "Bind close failed: %v": 182,
+ "Brackets must contain an IPv6 address": 76,
+ "Cancel": 108,
+ "Close": 41,
+ "Command Line Options": 3,
+ "Config key is missing an equals separator": 79,
+ "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*": 170,
+ "Configuration ZIP Files (*.zip)|*.zip": 172,
+ "Could not decrypt invalid cookie response": 200,
+ "Could not enumerate existing tunnels: %v": 154,
+ "Could not import selected configuration: %v": 153,
+ "Create new tunnel": 101,
+ "DNS servers:": 50,
+ "Deactivating": 57,
+ "Delete %d tunnels": 161,
+ "Delete tunnel ‘%s’": 163,
+ "Device closed": 185,
+ "Device closing": 184,
+ "E&xit": 129,
+ "Edit &selected tunnel…": 150,
+ "Edit tunnel": 102,
+ "Enable experimental kernel driver?": 270,
+ "Endpoint:": 53,
+ "Error": 0,
+ "Error Exiting WireGuard": 70,
+ "Error in getting configuration": 36,
+ "Error: ": 178,
+ "Error: %v. Please try again.": 73,
+ "Export all tunnels to &zip…": 149,
+ "Export all tunnels to zip": 147,
+ "Export log to file": 65,
+ "Export tunnels to zip": 173,
+ "Failed to activate tunnel": 97,
+ "Failed to create cookie reply: %v": 226,
+ "Failed to deactivate tunnel": 98,
+ "Failed to decode cookie reply": 198,
+ "Failed to decode initiation message": 203,
+ "Failed to decode response message": 206,
+ "Failed to determine tunnel state": 96,
+ "Failed to load updated MTU of device: %v": 239,
+ "Failed to read packet from TUN device: %v": 229,
+ "Failed to receive %s packet: %v": 193,
+ "Failed to write packet to TUN device: %v": 216,
+ "File ‘%s’ already exists.\n\nDo you want to overwrite it?": 118,
+ "IPv4 packet with disallowed source address from %v": 213,
+ "IPv6 packet with disallowed source address from %v": 214,
+ "Import tunnel(s) from file": 171,
+ "Imported %d of %d tunnels": 159,
+ "Imported %d tunnels": 158,
+ "Imported tunnels": 157,
+ "Inactive": 56,
+ "Interface closed, ignored requested state %s": 179,
+ "Interface down requested": 243,
+ "Interface state was %s, requested %s, now %s": 180,
+ "Interface up requested": 242,
+ "Interface: %s": 99,
+ "Invalid IP address": 23,
+ "Invalid MTU": 27,
+ "Invalid endpoint host": 26,
+ "Invalid key for [Interface] section": 81,
+ "Invalid key for [Peer] section": 82,
+ "Invalid key for interface section": 84,
+ "Invalid key for peer section": 86,
+ "Invalid key: %v": 30,
+ "Invalid name": 110,
+ "Invalid network prefix length": 24,
+ "Invalid packet ended up in the handshake queue": 202,
+ "Invalid persistent keepalive": 29,
+ "Invalid port": 28,
+ "Key must have a value": 80,
+ "Keys must decode to exactly 32 bytes": 77,
+ "Latest handshake:": 55,
+ "Line must occur in a section": 78,
+ "Listen port:": 47,
+ "Log": 59,
+ "Log message": 64,
+ "MTU not updated to negative value: %v": 240,
+ "MTU updated: %v%s": 241,
+ "MTU:": 48,
+ "Missing port from endpoint": 25,
+ "Now": 9,
+ "Number must be a number between 0 and 2^64-1: %v": 31,
+ "Packet with invalid IP version from %v": 215,
+ "Peer": 100,
+ "Persistent keepalive:": 54,
+ "Please ask the system administrator to update.": 275,
+ "Preshared key:": 51,
+ "Protocol version must be 1": 85,
+ "Public key:": 46,
+ "Received invalid initiation message from %s": 204,
+ "Received invalid response message from %s": 207,
+ "Received message with unknown type": 194,
+ "Received packet with invalid mac1": 201,
+ "Received packet with unknown IP version": 230,
+ "Receiving cookie response from %s": 199,
+ "Remove selected tunnel(s)": 146,
+ "Routine: TUN reader - started": 228,
+ "Routine: TUN reader - stopped": 227,
+ "Routine: decryption worker %d - started": 195,
+ "Routine: encryption worker %d - started": 231,
+ "Routine: event worker - started": 238,
+ "Routine: event worker - stopped": 244,
+ "Routine: handshake worker %d - started": 197,
+ "Routine: handshake worker %d - stopped": 196,
+ "Routine: receive incoming %s - started": 192,
+ "Routine: receive incoming %s - stopped": 191,
+ "Scripts:": 87,
+ "Select &all": 61,
+ "Sending cookie response for denied handshake message for %v": 225,
+ "Status:": 43,
+ "Status: %s": 137,
+ "Status: Complete!": 74,
+ "Status: Unknown": 125,
+ "Status: Waiting for administrator": 276,
+ "Status: Waiting for updater service": 177,
+ "Status: Waiting for user": 176,
+ "System clock wound backward!": 10,
+ "Table:": 272,
+ "Text Files (*.txt)|*.txt|All Files (*.*)|*.*": 121,
+ "The %s tunnel has been activated.": 132,
+ "The %s tunnel has been deactivated.": 134,
+ "The WireGuard project is currently testing a high performance kernel driver called WireGuardNT. It will eventually be enabled by default, but for now the project needs testers to try it out. Whether you encounter problems or you find that it works well, please do email team@wireguard.com about your experience.\n\nWould you like to enable the experimental kernel driver?": 271,
+ "Time": 63,
+ "Transfer:": 88,
+ "Trouble determining MTU, assuming default: %v": 183,
+ "Tunnel Error": 67,
+ "Tunnel already exists": 114,
+ "Tunnel name is not valid": 33,
+ "Tunnel name ‘%s’ is invalid.": 112,
+ "Tunnels": 142,
+ "Two commas in a row": 32,
+ "UAPI: Removing all peers": 259,
+ "UAPI: Updating fwmark": 258,
+ "UAPI: Updating listen port": 257,
+ "UAPI: Updating private key": 256,
+ "UDP bind has been updated": 186,
+ "Unable to create new configuration": 116,
+ "Unable to create tunnel": 160,
+ "Unable to delete tunnel": 166,
+ "Unable to delete tunnels": 168,
+ "Unable to determine whether the process is running under WOW64: %v": 4,
+ "Unable to exit service due to: %v. You may want to stop WireGuard from the service manager.": 175,
+ "Unable to flush packets: %v": 217,
+ "Unable to import configuration: %v": 156,
+ "Unable to list existing tunnels": 113,
+ "Unable to open current process token: %v": 6,
+ "Unable to update bind: %v": 181,
+ "Unable to wait for WireGuard window to appear: %v": 123,
+ "Unknown state": 58,
+ "Update Now": 72,
+ "Usage: %s [\n%s]": 2,
+ "When a configuration has exactly one peer, and that peer has an allowed IPs containing at least one of 0.0.0.0/0 or ::/0, and the interface does not have table off, then the tunnel service engages a firewall ruleset to block all traffic that is neither to nor from the tunnel interface or is to the wrong DNS server, with special exceptions for DHCP and NDP.": 274,
+ "WireGuard Activated": 131,
+ "WireGuard Deactivated": 133,
+ "WireGuard Detection Error": 122,
+ "WireGuard Tunnel Error": 135,
+ "WireGuard Update Available": 140,
+ "WireGuard is running, but the UI is only accessible from desktops of the Builtin %s group.": 8,
+ "WireGuard logo image": 40,
+ "WireGuard may only be used by users who are a member of the Builtin %s group.": 7,
+ "WireGuard system tray icon did not appear after 30 seconds.": 75,
+ "WireGuard: %s": 136,
+ "WireGuard: Deactivated": 124,
+ "Writing file failed": 117,
+ "You must use the native version of WireGuard on this computer.": 5,
+ "[EnumerationSeparator]": 37,
+ "[UnitSeparator]": 38,
+ "[none specified]": 34,
+ "allowed_ip=%s/%d": 254,
+ "disabled, per policy": 93,
+ "enabled": 94,
+ "endpoint=%s": 248,
+ "fwmark=%d": 246,
+ "invalid UAPI operation: %v": 267,
+ "last_handshake_time_nsec=%d": 250,
+ "last_handshake_time_sec=%d": 249,
+ "listen_port=%d": 245,
+ "no configuration files were found": 152,
+ "off": 273,
+ "persistent_keepalive_interval=%d": 253,
+ "post-down": 92,
+ "post-up": 90,
+ "pre-down": 91,
+ "pre-up": 89,
+ "protocol_version=1": 247,
+ "rx_bytes=%d": 252,
+ "tx_bytes=%d": 251,
+ "♥ &Donate!": 42,
+}
+
+var caIndex = []uint32{ // 278 elements
+ // Entry 0 - 1F
+ 0x00000000, 0x00000006, 0x00000042, 0x00000056,
+ 0x00000071, 0x000000b0, 0x000000f5, 0x0000012c,
+ 0x0000018c, 0x00000215, 0x00000219, 0x00000240,
+ 0x0000025e, 0x0000027c, 0x0000029c, 0x000002be,
+ 0x000002e0, 0x000002e9, 0x000002f2, 0x000002ff,
+ 0x0000030c, 0x00000319, 0x00000326, 0x00000333,
+ 0x00000348, 0x0000036c, 0x00000386, 0x000003a9,
+ 0x000003b7, 0x000003c5, 0x000003f1, 0x00000407,
+ // Entry 20 - 3F
+ 0x00000435, 0x00000448, 0x00000468, 0x00000479,
+ 0x000004a8, 0x000004c5, 0x000004c8, 0x000004cb,
+ 0x000004db, 0x000004ed, 0x000004f3, 0x000004ff,
+ 0x00000506, 0x00000511, 0x00000519, 0x00000528,
+ 0x00000538, 0x0000053d, 0x00000546, 0x00000555,
+ 0x00000569, 0x00000577, 0x0000057f, 0x0000059a,
+ 0x000005ac, 0x000005b4, 0x000005c0, 0x000005d1,
+ 0x000005da, 0x000005e1, 0x000005f3, 0x00000607,
+ // Entry 40 - 5F
+ 0x0000060d, 0x00000622, 0x0000063c, 0x00000650,
+ 0x00000660, 0x0000069f, 0x000006b6, 0x000006d3,
+ 0x0000072d, 0x0000073c, 0x0000076a, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ // Entry 60 - 7F
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ // Entry 80 - 9F
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ // Entry A0 - BF
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ // Entry C0 - DF
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ // Entry E0 - FF
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ // Entry 100 - 11F
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c, 0x0000077c, 0x0000077c,
+ 0x0000077c, 0x0000077c,
+} // Size: 1136 bytes
+
+const caData string = "" + // Size: 1916 bytes
+ "\x02Error\x02(sense argument): eleva i instala el servei d'administrador" +
+ "\x02Ús: %[1]s [\x0a%[2]s]\x02Opcions de línia d'ordres\x02No s'ha pogut " +
+ "determinar si el procés corre sota WOW64: %[1]v\x02Heu de fer servir la " +
+ "versio nativa de WireGuard en aquest ordinador.\x02No s'ha pogut obrir e" +
+ "l token del procés actual: %[1]v\x02WireGuard només es pot fer servir pe" +
+ "r els usuaris que són membres del grup del sistema %[1]s.\x02WireGuard s" +
+ "'està executsnt, pero la interfície gràfica només és accessible als usua" +
+ "ris que són membres del grup del sistema %[1]s.\x02Ara\x02El rellotge de" +
+ "l sistema s'ha atraçat!\x14\x01\x81\x01\x00\x02\x0a\x02%[1]d any\x00\x0b" +
+ "\x02%[1]d anys\x14\x01\x81\x01\x00\x02\x0a\x02%[1]d dia\x00\x0b\x02%[1]d" +
+ " dies\x14\x01\x81\x01\x00\x02\x0b\x02%[1]d hora\x00\x0c\x02%[1]d hores" +
+ "\x14\x01\x81\x01\x00\x02\x0c\x02%[1]d minut\x00\x0d\x02%[1]d minuts\x14" +
+ "\x01\x81\x01\x00\x02\x0c\x02%[1]d segon\x00\x0d\x02%[1]d segons\x02Fa %[" +
+ "1]s\x02%[1]d\u00a0B\x02%.2[1]f\u00a0KiB\x02%.2[1]f\u00a0MiB\x02%.2[1]f" +
+ "\u00a0GiB\x02%.2[1]f\u00a0TiB\x02%[1]s: %[2]q\x02Adreça IP invàlida\x02T" +
+ "amany del prefix de xarxa invàlid\x02Falta el port de l'extrem\x02El for" +
+ "mat de l'extrem no és valid\x02MTU invàlida\x02Port invàlid\x02Temps de " +
+ "missatge de persistència invàlid\x02Clau invàlida: %[1]v\x02El nombre ha" +
+ " de estar entre 0 i 2^64-1: %[1]v\x02Dos comes seguides\x02El nom del tú" +
+ "nel no és vàlid\x02[no especificat]\x02Tots els parells han de tenir cla" +
+ "us públiques\x02Error obtenint configuració\x02, \x02, \x02Sobre WireGua" +
+ "rd\x02Logo de WireGuard\x02Tanca\x02♥ & Dona!\x02Estat:\x02&Desactiva" +
+ "\x02&Activa\x02Clau pública:\x02Port d'escolta:\x02MTU:\x02Adreces:\x02S" +
+ "ervidors DNS:\x02Clau precompartida:\x02IPs permeses:\x02Extrem:\x02Miss" +
+ "atge de persistència:\x02Últim handshake:\x02Inactiu\x02Desactivant\x02E" +
+ "stat desconegut\x02Registre\x02&Copia\x02Selecciona-ho tot\x02Desa en un" +
+ " arxiu…\x02Temps\x02Missatge de registre\x02Exporta registre a fitxer" +
+ "\x02&Sobre WireGuard…\x02Error de túnel\x02%[1]s\x0a\x0aSi us plau, cons" +
+ "ulteu el registre per més informació.\x02%[1]s (desactualitzat)\x02Error" +
+ " al sortir de WireGuard\x02Una actualització per WireGuard està disponib" +
+ "le. Es recomana actualitzar immediatament.\x02Actualitza ara\x02Error: %" +
+ "[1]v. Si us plau, torneu-ho a provar.\x02Estat: Completat!"
+
+var csIndex = []uint32{ // 278 elements
+ // Entry 0 - 1F
+ 0x00000000, 0x00000006, 0x0000004f, 0x00000069,
+ 0x0000008a, 0x000000bd, 0x00000106, 0x00000138,
+ 0x00000193, 0x00000201, 0x00000206, 0x0000022f,
+ 0x00000265, 0x0000029b, 0x000002da, 0x00000319,
+ 0x0000035c, 0x00000368, 0x00000371, 0x0000037e,
+ 0x0000038b, 0x00000398, 0x000003a5, 0x000003b2,
+ 0x000003c6, 0x000003eb, 0x00000401, 0x00000414,
+ 0x00000422, 0x00000431, 0x00000453, 0x0000046b,
+ // Entry 20 - 3F
+ 0x0000049d, 0x000004b3, 0x000004ce, 0x000004e5,
+ 0x00000512, 0x00000536, 0x00000539, 0x0000053c,
+ 0x00000551, 0x00000569, 0x00000572, 0x00000580,
+ 0x00000586, 0x00000593, 0x0000059e, 0x000005b0,
+ 0x000005c8, 0x000005cd, 0x000005d5, 0x000005e2,
+ 0x000005f9, 0x00000607, 0x00000611, 0x00000627,
+ 0x0000063c, 0x00000647, 0x00000652, 0x00000661,
+ 0x0000066a, 0x00000676, 0x00000683, 0x0000069a,
+ // Entry 40 - 5F
+ 0x0000069f, 0x000006ac, 0x000006ca, 0x000006e3,
+ 0x000006f0, 0x0000072b, 0x00000740, 0x0000076c,
+ 0x000007d5, 0x000007e8, 0x00000807, 0x00000819,
+ 0x00000865, 0x0000088a, 0x000008c0, 0x000008e5,
+ 0x00000925, 0x0000093f, 0x00000966, 0x00000988,
+ 0x000009b3, 0x000009d8, 0x000009f5, 0x00000a13,
+ 0x00000a1c, 0x00000a25, 0x00000a35, 0x00000a41,
+ 0x00000a51, 0x00000a5d, 0x00000a73, 0x00000a7b,
+ // Entry 60 - 7F
+ 0x00000a9b, 0x00000abe, 0x00000add, 0x00000afe,
+ 0x00000b0f, 0x00000b14, 0x00000b2a, 0x00000b38,
+ 0x00000b41, 0x00000b54, 0x00000b60, 0x00000b8d,
+ 0x00000b96, 0x00000b9e, 0x00000bab, 0x00000bbc,
+ 0x00000bd0, 0x00000bf4, 0x00000c20, 0x00000c34,
+ 0x00000c5b, 0x00000c86, 0x00000ca2, 0x00000cd6,
+ 0x00000cdf, 0x00000ce8, 0x00000d22, 0x00000d3f,
+ 0x00000d70, 0x00000d88, 0x00000d98, 0x00000da9,
+ // Entry 80 - 9F
+ 0x00000dbd, 0x00000de0, 0x00000dea, 0x00000df2,
+ 0x00000e07, 0x00000e23, 0x00000e3a, 0x00000e58,
+ 0x00000e6f, 0x00000e80, 0x00000e8c, 0x00000e9a,
+ 0x00000eb6, 0x00000edb, 0x00000f3d, 0x00000f44,
+ 0x00000f4d, 0x00000f69, 0x00000f77, 0x00000f91,
+ 0x00000fb3, 0x00000fbe, 0x00000fe4, 0x00000fff,
+ 0x0000101a, 0x0000104a, 0x00001077, 0x000010ac,
+ 0x000010d2, 0x000010f6, 0x0000110a, 0x0000117f,
+ // Entry A0 - BF
+ 0x00001214, 0x0000122a, 0x00001294, 0x0000133e,
+ 0x00001356, 0x0000137e, 0x000013a3, 0x000013b9,
+ 0x000013df, 0x000013f6, 0x000014a0, 0x000014ee,
+ 0x0000150d, 0x00001534, 0x0000154d, 0x0000157e,
+ 0x000015d8, 0x000015f6, 0x0000161e, 0x0000161e,
+ 0x0000161e, 0x0000161e, 0x0000161e, 0x0000161e,
+ 0x0000161e, 0x0000161e, 0x0000161e, 0x0000161e,
+ 0x0000161e, 0x0000161e, 0x0000161e, 0x0000161e,
+ // Entry C0 - DF
+ 0x0000161e, 0x0000161e, 0x0000161e, 0x0000161e,
+ 0x0000161e, 0x0000161e, 0x0000161e, 0x0000161e,
+ 0x0000161e, 0x0000161e, 0x0000161e, 0x0000161e,
+ 0x0000161e, 0x0000161e, 0x0000161e, 0x0000161e,
+ 0x0000161e, 0x0000161e, 0x0000161e, 0x0000161e,
+ 0x0000161e, 0x0000161e, 0x0000161e, 0x0000161e,
+ 0x0000161e, 0x0000161e, 0x0000161e, 0x0000161e,
+ 0x0000161e, 0x0000161e, 0x0000161e, 0x0000161e,
+ // Entry E0 - FF
+ 0x0000161e, 0x0000161e, 0x0000161e, 0x0000161e,
+ 0x0000161e, 0x0000161e, 0x0000161e, 0x0000161e,
+ 0x0000161e, 0x0000161e, 0x0000161e, 0x0000161e,
+ 0x0000161e, 0x0000161e, 0x0000161e, 0x0000161e,
+ 0x0000161e, 0x0000161e, 0x0000161e, 0x0000161e,
+ 0x0000161e, 0x0000161e, 0x0000161e, 0x0000161e,
+ 0x0000161e, 0x0000161e, 0x0000161e, 0x0000161e,
+ 0x0000161e, 0x0000161e, 0x0000161e, 0x0000161e,
+ // Entry 100 - 11F
+ 0x0000161e, 0x0000161e, 0x0000161e, 0x0000161e,
+ 0x0000161e, 0x0000161e, 0x0000161e, 0x0000161e,
+ 0x0000161e, 0x0000161e, 0x0000161e, 0x0000161e,
+ 0x0000161e, 0x0000161e, 0x0000161e, 0x0000161e,
+ 0x0000161e, 0x0000161e, 0x0000161e, 0x0000161e,
+ 0x0000161e, 0x0000161e,
+} // Size: 1136 bytes
+
+const csData string = "" + // Size: 5662 bytes
+ "\x02Chyba\x02(žádný argument): Zvýšit oprávnění a instalovat službu sprá" +
+ "vce\x02Použití: %[1]s [\x0a%[2]s]\x02Možnosti příkazového řádku\x02Nelze" +
+ " zjistit, zda proces běží pod WOW64: %[1]v\x02Musíte použít nativní verz" +
+ "i aplikace WireGuard na tomto počítači.\x02Nelze otevřít token aktuálníh" +
+ "o procesu: %[1]v\x02WireGuard můžou používat pouze uživatelé, kteří jsou" +
+ " členy Builtin skupiny %[1]s.\x02WireGuard je spuštěn, ale uživatelské r" +
+ "ozhraní je přístupné pouze uživatelům Builtin skupiny %[1]s.\x02Teď\x02S" +
+ "ystémové hodiny byly posunuty dozadu!\x14\x01\x81\x01\x00\x04\x0b\x02%[1" +
+ "]d roky\x05\x0a\x02%[1]d let\x02\x0a\x02%[1]d rok\x00\x0a\x02%[1]d let" +
+ "\x14\x01\x81\x01\x00\x04\x0a\x02%[1]d dny\x05\x0b\x02%[1]d dnů\x02\x0a" +
+ "\x02%[1]d den\x00\x0a\x02%[1]d dny\x14\x01\x81\x01\x00\x04\x0d\x02%[1]d " +
+ "hodiny\x05\x0c\x02%[1]d hodin\x02\x0d\x02%[1]d hodina\x00\x0c\x02%[1]d h" +
+ "odin\x14\x01\x81\x01\x00\x04\x0d\x02%[1]d minuty\x05\x0c\x02%[1]d minut" +
+ "\x02\x0d\x02%[1]d minuta\x00\x0c\x02%[1]d minut\x14\x01\x81\x01\x00\x04" +
+ "\x0e\x02%[1]d sekundy\x05\x0d\x02%[1]d sekund\x02\x0e\x02%[1]d sekunda" +
+ "\x00\x0d\x02%[1]d sekund\x02před %[1]s\x02%[1]d\u00a0B\x02%.2[1]f\u00a0K" +
+ "iB\x02%.2[1]f\u00a0MiB\x02%.2[1]f\u00a0GiB\x02%.2[1]f\u00a0TiB\x02%[1]s:" +
+ " %[2]q\x02Neplatná IP adresa\x02Neplatná délka síťového prefixu\x02Endpo" +
+ "intu chybí port\x02Neplatný endpoint\x02Neplatné MTU\x02Neplatný port" +
+ "\x02Neplatný persistentní keepalive\x02Neplatný klíč: %[1]v\x02Číslo mus" +
+ "í mít hodnotu mezi 0 a 2^64-1: %[1]v\x02Dvě čárky za sebou\x02Název tun" +
+ "elu je neplatný\x02[není specifikováno]\x02Všichni peeři musí mít veřejn" +
+ "é klíče\x02Chyba při načítání konfigurace\x02, \x02, \x02O aplikaci Wir" +
+ "eGuard\x02Obrázek loga WireGuard\x02Zavřít\x02♥ &Darovat!\x02Stav:\x02&D" +
+ "eaktivovat\x02&Aktivovat\x02Veřejný klíč:\x02Port pro naslouchání:\x02MT" +
+ "U:\x02Adresy:\x02DNS servery:\x02Předsdílený klíč:\x02Povolené IP:\x02En" +
+ "dpoint:\x02Persistent keepalive:\x02Poslední handshake:\x02Neaktivní\x02" +
+ "Deaktivuji\x02Neznámý stav\x02Záznamy\x02&Kopírovat\x02Vybr&at vše\x02&U" +
+ "ložit do souboru…\x02Čas\x02Zpráva logu\x02Exportovat záznam do souboru" +
+ "\x02&O aplikaci WireGuard…\x02Chyba tunelu\x02%[1]s\x0a\x0aPro více info" +
+ "rmací se prosím podívejte do logu.\x02%[1]s (neaktuální)\x02Chyba při uk" +
+ "ončování aplikace WireGuard\x02Aktualizace aplikace WireGuard je nyní k " +
+ "dispozici. Silně doporučujeme ji aktualizovat co nejdříve.\x02Aktualizov" +
+ "at nyní\x02Chyba: %[1]v. Zkuste to znovu.\x02Stav: Dokončeno!\x02Ikona W" +
+ "ireGuard se ani po 30 sekundách nezobrazila na systémové liště.\x02Závor" +
+ "ky musí obsahovat IPv6 adresu\x02Klíče musí být dekódovány přesně na 32 " +
+ "bajtů\x02Řádek musí být v některé sekci\x02Konfigurační klíč neobsahuje " +
+ "oddělovač (znak 'rovná se')\x02Klíč musí mít hodnotu\x02Neplatný klíč pr" +
+ "o sekci [Interface]\x02Neplatný klíč pro sekci [Peer]\x02Rozhraní musí o" +
+ "bsahovat soukromý klíč\x02Neplatný klíč pro sekci rozhraní\x02Verze prot" +
+ "okolu musí být 1\x02Neplatný klíč v sekci peer\x02Skripty:\x02Přenos:" +
+ "\x02před-zapnutím\x02po-zapnutí\x02před-vypnutím\x02po-vypnutí\x02vypnut" +
+ "o, podle zásad\x02zapnuto\x02%[1]s přijato, %[2]s odesláno\x02Nepodařilo" +
+ " se zjistit stav tunelu\x02Nepodařilo se aktivovat tunel\x02Nepodařilo s" +
+ "e deaktivovat tunel\x02Rozhraní: %[1]s\x02Peer\x02Vytvořit nový tunel" +
+ "\x02Upravit tunel\x02&Název:\x02&Veřejný klíč:\x02(neznámý)\x02&Blokovat" +
+ " netunelovaný provoz (kill-switch)\x02&Uložit\x02Zrušit\x02&Nastavení:" +
+ "\x02Neplatný název\x02Název je povinný.\x02Název tunelu '%[1]s' je nepla" +
+ "tný.\x02Nepodařilo se zobrazit existující tunely\x02Tunel již existuje" +
+ "\x02Tunel s názvem '%[1]s' již existuje.\x02Nepodařilo se vytvořit novou" +
+ " konfiguraci\x02Zápis souboru se nezdařil\x02Soubor \x22%[1]s\x22 již ex" +
+ "istuje.\x0a\x0aChcete jej přepsat?\x02Aktivní\x02Aktivuji\x02Textové sou" +
+ "bory (*.txt)|*.txt|Všechny soubory (*.*)|*.*\x02Chyba při detekci WireGu" +
+ "ard\x02Nelze čekat na zobrazení okna WireGuard: %[1]v\x02WireGuard: Deak" +
+ "tivován\x02Stav: Neznámý\x02Adresy: žádné\x02Spravovat tunely…\x02&Impor" +
+ "tovat tunel(y) ze souboru…\x02U&končit\x02&Tunely\x02WireGuard aktivován" +
+ "\x02Tunel %[1]s byl aktivován.\x02WireGuard deaktivován\x02Tunel %[1]s b" +
+ "yl deaktivován.\x02WireGuard Chyba Tunelu\x02WireGuard: %[1]s\x02Stav: %" +
+ "[1]s\x02Adresy: %[1]s\x02Aktualizace je k dispozici!\x02Aktualizace Wire" +
+ "Guard je k dispozici\x02Aktualizace aplikace WireGuard je nyní k dispozi" +
+ "ci. Doporučujeme ji aktualizovat co nejdříve.\x02Tunely\x02&Upravit\x02P" +
+ "řidat &prázdný tunel…\x02Přidat tunel\x02Odstranit vybrané tunely\x02Ex" +
+ "portovat všechny tunely do zip\x02&Přepnout\x02Exportovat všechny tunely" +
+ " do &zip…\x02Upravit &vybraný tunel…\x02&Odstranit vybrané tunely\x02neb" +
+ "yly nalezeny žádné konfigurační soubory\x02Nelze importovat vybranou kon" +
+ "figuraci: %[1]v\x02Nepodařilo se vyjmenovat existující tunely: %[1]v\x02" +
+ "Tunel s názvem '%[1]s' již existuje\x02Nelze importovat konfiguraci: %[1" +
+ "]v\x02Importované tunely\x14\x01\x81\x01\x00\x04\x1a\x02Importovány %[1]" +
+ "d tunely\x05\x1b\x02Importováno %[1]d tunelů\x02\x18\x02Importován %[1]d" +
+ " tunel\x00\x1b\x02Importováno %[1]d tunelů\x14\x02\x80\x01\x04#\x02Impor" +
+ "továno %[1]d z %[2]d tunelů\x05#\x02Importováno %[1]d z %[2]d tunelů\x02" +
+ " \x02Importován %[1]d z %[2]d tunel\x00#\x02Importováno %[1]d z %[2]d tu" +
+ "nelů\x02Nelze vytvořit tunel\x14\x01\x81\x01\x00\x04\x17\x02Odstranit %[" +
+ "1]d tunely\x05\x18\x02Odstranit %[1]d tunelů\x02\x16\x02Odstranit %[1]d " +
+ "tunel\x00\x18\x02Odstranit %[1]d tunelů\x14\x01\x81\x01\x00\x04'\x02Opra" +
+ "vdu chcete odstranit %[1]d tunely?\x05(\x02Opravdu chcete odstranit %[1]" +
+ "d tunelů?\x02&\x02Opravdu chcete odstranit %[1]d tunel?\x00(\x02Opravdu " +
+ "chcete odstranit %[1]d tunelů?\x02Odstranit tunel \x22%[1]s\x22\x02Oprav" +
+ "du chcete odstranit tunel \x22%[1]s\x22?\x02%[1]s Tuto akci nelze vrátit" +
+ " zpět.\x02Nelze odstranit tunel\x02Tunel nebylo možné odstranit: %[1]s" +
+ "\x02Nelze odstranit tunely\x14\x01\x81\x01\x00\x04'\x02%[1]d tunely neby" +
+ "lo možné odstranit.\x05(\x02%[1]d tunelů nebylo možné odstranit.\x02&" +
+ "\x02%[1]d tunel nebylo možné odstranit.\x00(\x02%[1]d tunelů nebylo možn" +
+ "é odstranit.\x02Konfigurace souborů (*.zip, *.conf)|*.zip; *.conf|Všech" +
+ "ny soubory (*.*)|*.*\x02Importovat tunel(y) ze souboru\x02Konfigurace so" +
+ "uborů ZIP (*.zip)|*.zip\x02Exportovat tunely do zip\x02%[1]s (nepodepsan" +
+ "á verze, žádné aktualizace)\x02Nelze ukončit službu z důvodu: %[1]v. Wi" +
+ "reGuard můžete zastavit ve správci služeb.\x02Stav: Čekání na uživatele" +
+ "\x02Stav: Čeká se na službu aktualizací"
+
+var deIndex = []uint32{ // 278 elements
+ // Entry 0 - 1F
+ 0x00000000, 0x00000007, 0x00000059, 0x00000074,
+ 0x0000008b, 0x000000e1, 0x00000137, 0x0000016b,
+ 0x000001c2, 0x0000023a, 0x00000240, 0x00000266,
+ 0x00000286, 0x000002a4, 0x000002c8, 0x000002ec,
+ 0x00000312, 0x0000031c, 0x00000325, 0x00000332,
+ 0x0000033f, 0x0000034c, 0x00000359, 0x00000366,
+ 0x0000037c, 0x000003a4, 0x000003c2, 0x000003dc,
+ 0x000003eb, 0x000003fc, 0x0000041c, 0x0000043a,
+ // Entry 20 - 3F
+ 0x00000466, 0x00000482, 0x0000049f, 0x000004b4,
+ 0x000004f2, 0x00000518, 0x0000051b, 0x0000051e,
+ 0x0000052e, 0x0000053d, 0x00000548, 0x00000556,
+ 0x0000055e, 0x0000056c, 0x00000578, 0x00000592,
+ 0x000005a0, 0x000005a5, 0x000005af, 0x000005bb,
+ 0x000005d1, 0x000005df, 0x000005e9, 0x000005fe,
+ 0x00000618, 0x00000620, 0x0000062c, 0x00000640,
+ 0x0000064a, 0x00000654, 0x00000665, 0x0000067c,
+ // Entry 40 - 5F
+ 0x00000681, 0x00000692, 0x000006b0, 0x000006c4,
+ 0x000006d2, 0x00000713, 0x00000724, 0x00000746,
+ 0x000007b4, 0x000007c8, 0x000007f6, 0x00000806,
+ 0x00000856, 0x0000088a, 0x000008c1, 0x000008f2,
+ 0x0000092d, 0x0000094b, 0x00000979, 0x000009a1,
+ 0x000009db, 0x00000a08, 0x00000a29, 0x00000a51,
+ 0x00000a5a, 0x00000a67, 0x00000a7a, 0x00000a91,
+ 0x00000aa6, 0x00000abc, 0x00000ad8, 0x00000ae2,
+ // Entry 60 - 7F
+ 0x00000b02, 0x00000b2d, 0x00000b52, 0x00000b79,
+ 0x00000b8e, 0x00000b99, 0x00000bb6, 0x00000bc8,
+ 0x00000bcf, 0x00000bea, 0x00000bf6, 0x00000c2a,
+ 0x00000c35, 0x00000c3f, 0x00000c4f, 0x00000c60,
+ 0x00000c78, 0x00000c9c, 0x00000ccf, 0x00000ce8,
+ 0x00000d20, 0x00000d4e, 0x00000d6e, 0x00000db3,
+ 0x00000db9, 0x00000dc3, 0x00000df4, 0x00000e0f,
+ 0x00000e57, 0x00000e6e, 0x00000e80, 0x00000e90,
+ // Entry 80 - 9F
+ 0x00000ea5, 0x00000ec6, 0x00000ecf, 0x00000ed7,
+ 0x00000eeb, 0x00000f0d, 0x00000f23, 0x00000f47,
+ 0x00000f5f, 0x00000f70, 0x00000f7e, 0x00000f8e,
+ 0x00000fb2, 0x00000fd6, 0x00001049, 0x00001050,
+ 0x0000105c, 0x00001080, 0x00001093, 0x000010b1,
+ 0x000010db, 0x000010e7, 0x0000110c, 0x00001130,
+ 0x00001151, 0x00001176, 0x000011b7, 0x000011e9,
+ 0x00001223, 0x00001257, 0x00001269, 0x000012a2,
+ // Entry A0 - BF
+ 0x000012ee, 0x0000130e, 0x00001343, 0x000013b3,
+ 0x000013cf, 0x00001406, 0x00001443, 0x00001462,
+ 0x00001492, 0x000014b8, 0x00001519, 0x00001563,
+ 0x0000157f, 0x000015a8, 0x000015c7, 0x000015f2,
+ 0x0000165e, 0x00001678, 0x000016a1, 0x000016a1,
+ 0x000016a1, 0x000016a1, 0x000016a1, 0x000016a1,
+ 0x000016a1, 0x000016a1, 0x000016a1, 0x000016a1,
+ 0x000016a1, 0x000016a1, 0x000016a1, 0x000016a1,
+ // Entry C0 - DF
+ 0x000016a1, 0x000016a1, 0x000016a1, 0x000016a1,
+ 0x000016a1, 0x000016a1, 0x000016a1, 0x000016a1,
+ 0x000016a1, 0x000016a1, 0x000016a1, 0x000016a1,
+ 0x000016a1, 0x000016a1, 0x000016a1, 0x000016a1,
+ 0x000016a1, 0x000016a1, 0x000016a1, 0x000016a1,
+ 0x000016a1, 0x000016a1, 0x000016a1, 0x000016a1,
+ 0x000016a1, 0x000016a1, 0x000016a1, 0x000016a1,
+ 0x000016a1, 0x000016a1, 0x000016a1, 0x000016a1,
+ // Entry E0 - FF
+ 0x000016a1, 0x000016a1, 0x000016a1, 0x000016a1,
+ 0x000016a1, 0x000016a1, 0x000016a1, 0x000016a1,
+ 0x000016a1, 0x000016a1, 0x000016a1, 0x000016a1,
+ 0x000016a1, 0x000016a1, 0x000016a1, 0x000016a1,
+ 0x000016a1, 0x000016a1, 0x000016a1, 0x000016a1,
+ 0x000016a1, 0x000016a1, 0x000016a1, 0x000016a1,
+ 0x000016a1, 0x000016a1, 0x000016a1, 0x000016a1,
+ 0x000016a1, 0x000016a1, 0x000016a1, 0x000016a1,
+ // Entry 100 - 11F
+ 0x000016a1, 0x000016a1, 0x000016a1, 0x000016a1,
+ 0x000016a1, 0x000016a1, 0x000016a1, 0x000016a1,
+ 0x000016a1, 0x000016a1, 0x000016a1, 0x000016a1,
+ 0x000016a1, 0x000016a1, 0x000016a1, 0x000016a1,
+ 0x000016a1, 0x000016a1, 0x000016a1, 0x000016a1,
+ 0x000016a1, 0x000016a1,
+} // Size: 1136 bytes
+
+const deData string = "" + // Size: 5793 bytes
+ "\x02Fehler\x02(kein Argument): Als Administrator ausführen und den Manag" +
+ "er-Dienst installieren\x02Verwendung: %[1]s [\x0a%[2]s]\x02Kommandozeile" +
+ "noptionen\x02Es kann nicht festgestellt werden, ob der Prozess unter WOW" +
+ "64 ausgeführt wird: %[1]v\x02Sie müssen die Version von Wireguard benutz" +
+ "en, die für ihren Computer bestimmt ist.\x02Konnte aktuellen Prozess-Tok" +
+ "en nicht öffnen: %[1]v\x02WireGuard kann nur von Benutzern verwendet wer" +
+ "den, die Mitglied der Gruppe %[1]s sind.\x02WireGuard wird ausgeführt, a" +
+ "ber auf die Benutzeroberfläche kann nur von Desktops der Gruppe %[1]s zu" +
+ "gegriffen werden.\x02Jetzt\x02Die Systemuhr wurde zurück gestellt!\x14" +
+ "\x01\x81\x01\x00\x02\x0b\x02%[1]d Jahr\x00\x0c\x02%[1]d Jahre\x14\x01" +
+ "\x81\x01\x00\x02\x0a\x02%[1]d Tag\x00\x0b\x02%[1]d Tage\x14\x01\x81\x01" +
+ "\x00\x02\x0d\x02%[1]d Stunde\x00\x0e\x02%[1]d Stunden\x14\x01\x81\x01" +
+ "\x00\x02\x0d\x02%[1]d Minute\x00\x0e\x02%[1]d Minuten\x14\x01\x81\x01" +
+ "\x00\x02\x0e\x02%[1]d Sekunde\x00\x0f\x02%[1]d Sekunden\x02vor %[1]s\x02" +
+ "%[1]d\u00a0B\x02%.2[1]f\u00a0KiB\x02%.2[1]f\u00a0MiB\x02%.2[1]f\u00a0GiB" +
+ "\x02%.2[1]f\u00a0TiB\x02%[1]s: %[2]q\x02Ungültige IP-Adresse\x02Ungültig" +
+ "e Länge des Netzwerkpräfixes\x02Fehlender Port des Endpunktes\x02Ungülti" +
+ "ger Endpunkt-Host\x02Ungültige MTU\x02Ungültiger Port\x02Ungültiges Erha" +
+ "ltungsintervall\x02Ungültiger Schlüssel: %[1]v\x02Zahl muss zwischen 0 u" +
+ "nd 2^64-1 sein: %[1]v\x02Zwei Kommata in einer Zeile\x02Der Tunnelname i" +
+ "st ungültig\x02[nicht spezifiziert]\x02Alle Teilnehmer (peers) müssen öf" +
+ "fentliche Schlüssel haben\x02Fehler beim Abrufen der Konfiguration\x02, " +
+ "\x02, \x02Über WireGuard\x02WireGuard Logo\x02Schließen\x02♥ &Spenden!" +
+ "\x02Status:\x02&Deaktivieren\x02&Aktivieren\x02Öffentlicher Schlüssel:" +
+ "\x02Eingangsport:\x02MTU:\x02Adressen:\x02DNS-Server:\x02Geteilter Schlü" +
+ "ssel:\x02Erlaubte IPs:\x02Endpunkt:\x02Erhaltungsintervall:\x02Letzter S" +
+ "chlüsseltausch:\x02Inaktiv\x02Deaktiviere\x02Unbekannter Zustand\x02Prot" +
+ "okoll\x02&Kopieren\x02&Alles markieren\x02&In Datei Speichern…\x02Zeit" +
+ "\x02Protokolleintrag\x02Exportiere Protokoll in Datei\x02&Über WireGuard" +
+ "…\x02Tunnel Fehler\x02%[1]s\x0a\x0aBitte lesen Sie das Protokoll für w" +
+ "eitere Informationen.\x02%[1]s (veraltet)\x02Fehler beim Beenden von Wir" +
+ "eGuard\x02Eine Aktualisierung für WireGuard ist verfügbar. Es ist höchst" +
+ " empfehlenswert diese sofort durchzuführen.\x02Jetzt aktualisieren\x02Fe" +
+ "hler: %[1]v. Bitte versuchen Sie es erneut.\x02Status: Fertig!\x02Das Wi" +
+ "reGuard-Taskleistensymbol ist nicht innerhalb von 30 Sekunden erschienen" +
+ ".\x02Eckige Klammern müssen eine IPv6 Adresse enthalten\x02Schlüssel müs" +
+ "sen auf exakt 32 Bytes dekodiert werden\x02Die Zeile muss innerhalb eine" +
+ "s Abschnitts stehen\x02Konfigurationsschlüssel fehlt ein Gleichheitstren" +
+ "nzeichen\x02Eintrag muss einen Wert haben\x02Ungültiger Eintrage im [Int" +
+ "erface] Abschnitt\x02Ungültiger Eintrag im [Peer] Abschnitt\x02Eine Schn" +
+ "ittstelle muss einen privaten Schlssel enthalten\x02Ungültiger Eintrag i" +
+ "m Abschnitt [interface]\x02Die Protokollversion muss 1 sein\x02Ungültige" +
+ "r Eintrag im Abschnitt [peer]\x02Skripte:\x02Übertragen:\x02vor Verbinds" +
+ "aufbau\x02nach Verbindungsaufbau\x02vor Verbindungsabbau\x02nach Verbind" +
+ "ungsabbau\x02deaktiviert, per Richtlinie\x02aktiviert\x02%[1]s empfangen" +
+ ", %[2]s gesendet\x02Tunnelstatus konnte nicht ermittelt werden\x02Tunnel" +
+ " aktivieren ist fehlgeschlagen\x02Tunnel deaktivieren ist fehlgeschlagen" +
+ "\x02Schnittstelle: %[1]s\x02Teilnehmer\x02Einen neuen Tunnel erstellen" +
+ "\x02Tunnel bearbeiten\x02&Name:\x02&Öffentlicher Schlüssel:\x02(unbekann" +
+ "t)\x02&Blockiere Verkehr außerhalb des Tunnels (Not-Aus)\x02&Speichern" +
+ "\x02Abbrechen\x02&Konfiguration:\x02Ungültiger Name\x02Ein Name ist notw" +
+ "endig.\x02Der Name „%[1]s“ ist ungültig.\x02Vorhandene Tunnel können nic" +
+ "ht aufgelistet werden\x02Tunnel existiert bereits\x02Ein Tunnel mit dem " +
+ "Namen „%[1]s“ existiert bereits.\x02Neue Konfiguration kann nicht erstel" +
+ "lt werden\x02Schreiben der Datei schlug fehl\x02Die Datei „%[1]s“ existi" +
+ "ert bereits.\x0a\x0aMöchten Sie sie ersetzen?\x02Aktiv\x02Aktiviere\x02T" +
+ "extdateien (*.txt)|*.txt|Alle Dateien (*.*)|*.*\x02WireGuard Erkennungsf" +
+ "ehler\x02Warten auf das Erscheinen des WireGuard Fensters nicht möglich:" +
+ " %[1]v \x02WireGuard: Deaktiviert\x02Status: Unbekannt\x02Adressen: Kein" +
+ "e\x02Tunnel &verwalten…\x02Tunnel aus Datei &importieren…\x02&Beenden" +
+ "\x02&Tunnel\x02WireGuard aktiviert\x02Der Tunnel %[1]s wurde aktiviert." +
+ "\x02WireGuard deaktiviert\x02Der Tunnel %[1]s wurde deaktiviert.\x02Wire" +
+ "Guard Tunnel Fehler\x02WireGuard: %[1]s\x02Status: %[1]s\x02Adressen: %[" +
+ "1]s\x02Eine Aktualisierung ist verfügbar!\x02WireGuard Aktualisierung ve" +
+ "rfügbar\x02Eine Aktualisierung für WireGuard ist jetzt verfügbar. Es wir" +
+ "d empfohlen diese schnellstmöglich durchzuführen.\x02Tunnel\x02&Bearbeit" +
+ "en\x02Einen &leeren Tunnel hinzufügen…\x02Tunnel hinzufügen\x02Markierte" +
+ "(n) Tunnel entfernen\x02Alle Tunnel in eine Zip-Datei exportieren\x02&Um" +
+ "schalten\x02Exportiere alle Tunnel in &Zip-Datei\x02Ausgewählten Tunnel " +
+ "&bearbeiten…\x02Ausgewählte(n) Tunnel &löschen\x02keine Konfigurationsda" +
+ "teien gefunden\x02Ausgewählte Konfiguration konnte nicht importiert werd" +
+ "en: %[1]v\x02Konnte existierende Tunnel nicht auflisten: %[1]v\x02Es exi" +
+ "stiert bereits ein Tunnel mit dem Namen „%[1]s“\x02Importieren der Konfi" +
+ "guration nicht möglich: %[1]v\x02Tunnel importiert\x14\x01\x81\x01\x00" +
+ "\x02\x18\x02%[1]d Tunnel importiert\x00\x18\x02%[1]d Tunnel importiert" +
+ "\x14\x02\x80\x01\x02\x22\x02%[1]d von %[2]d Tunnel importiert\x00\x22" +
+ "\x02%[1]d von %[2]d Tunnel importiert\x02Tunnel erstellen nicht möglich" +
+ "\x14\x01\x81\x01\x00\x02\x16\x02%[1]d Tunnel löschen\x00\x16\x02%[1]d Tu" +
+ "nnel löschen\x14\x01\x81\x01\x00\x024\x02Möchten Sie diesen %[1]d Tunnel" +
+ " wirklich löschen?\x003\x02Möchten Sie diese %[1]d Tunnel wirklich lösch" +
+ "en?\x02Tunnel „%[1]s“ löschen\x02Möchten Sie den Tunnel „%[1]s“ wirklich" +
+ " löschen?\x02%[1]s Dieser Schritt kann nicht rückgängig gemacht werden." +
+ "\x02Tunnel löschen nicht möglich\x02Ein Tunnel konnte nicht gelöscht wer" +
+ "den: %[1]s\x02Tunnel konnten nicht gelöscht werden\x14\x01\x81\x01\x00" +
+ "\x02+\x02%[1]d Tunnel konnte nicht entfernt werden.\x00-\x02%[1]d Tunnel" +
+ " konnten nicht gelöscht werden.\x02Konfigurationsdateien (*.zip, *.conf)" +
+ "|*.zip;*.conf|Alle Dateien (*.*)|*.*\x02Importiere Tunnel aus Datei\x02K" +
+ "onfigurations-ZIP-Dateien (*.zip)|*.zip\x02Exportiere Tunnel in Zip-Date" +
+ "i\x02%[1]s (unsigniert, keine Aktualisierungen)\x02Der Dienst konnte nic" +
+ "ht gestoppt werden: %[1]v. Versuchen Sie WireGuard in der Dienstverwaltu" +
+ "ng zu beenden.\x02Status: Auf Nutzer warten\x02Status: Auf Aktualisierun" +
+ "gsdienst warten"
+
+var enIndex = []uint32{ // 278 elements
+ // Entry 0 - 1F
+ 0x00000000, 0x00000006, 0x00000039, 0x0000004f,
+ 0x00000064, 0x000000aa, 0x000000e9, 0x00000115,
+ 0x00000166, 0x000001c4, 0x000001c8, 0x000001e5,
+ 0x00000205, 0x00000223, 0x00000243, 0x00000267,
+ 0x0000028b, 0x00000295, 0x0000029e, 0x000002ab,
+ 0x000002b8, 0x000002c5, 0x000002d2, 0x000002df,
+ 0x000002f2, 0x00000310, 0x0000032b, 0x00000341,
+ 0x0000034d, 0x0000035a, 0x00000377, 0x0000038a,
+ // Entry 20 - 3F
+ 0x000003be, 0x000003d2, 0x000003eb, 0x000003fc,
+ 0x0000041c, 0x0000043b, 0x0000043e, 0x00000441,
+ 0x00000451, 0x00000466, 0x0000046c, 0x00000479,
+ 0x00000481, 0x0000048d, 0x00000497, 0x000004a3,
+ 0x000004b0, 0x000004b5, 0x000004c0, 0x000004cd,
+ 0x000004dc, 0x000004e9, 0x000004f3, 0x00000509,
+ 0x0000051b, 0x00000524, 0x00000531, 0x0000053f,
+ 0x00000543, 0x00000549, 0x00000555, 0x00000566,
+ // Entry 40 - 5F
+ 0x0000056b, 0x00000577, 0x0000058a, 0x0000059e,
+ 0x000005ab, 0x000005df, 0x000005f3, 0x0000060b,
+ 0x00000660, 0x0000066b, 0x0000068b, 0x0000069d,
+ 0x000006d9, 0x000006ff, 0x00000724, 0x00000741,
+ 0x0000076b, 0x00000781, 0x000007a5, 0x000007c4,
+ 0x000007e9, 0x0000080b, 0x00000826, 0x00000843,
+ 0x0000084c, 0x00000856, 0x0000085d, 0x00000865,
+ 0x0000086e, 0x00000878, 0x0000088d, 0x00000895,
+ // Entry 60 - 7F
+ 0x000008b0, 0x000008d1, 0x000008eb, 0x00000907,
+ 0x00000918, 0x0000091d, 0x0000092f, 0x0000093b,
+ 0x00000942, 0x0000094f, 0x00000959, 0x00000981,
+ 0x00000987, 0x0000098e, 0x0000099e, 0x000009ab,
+ 0x000009bf, 0x000009e3, 0x00000a03, 0x00000a19,
+ 0x00000a52, 0x00000a75, 0x00000a89, 0x00000ac8,
+ 0x00000acf, 0x00000ada, 0x00000b07, 0x00000b21,
+ 0x00000b56, 0x00000b6d, 0x00000b7d, 0x00000b8d,
+ // Entry 80 - 9F
+ 0x00000ba0, 0x00000bbf, 0x00000bc5, 0x00000bce,
+ 0x00000be2, 0x00000c07, 0x00000c1d, 0x00000c44,
+ 0x00000c5b, 0x00000c6c, 0x00000c7a, 0x00000c8b,
+ 0x00000ca3, 0x00000cbe, 0x00000d16, 0x00000d1e,
+ 0x00000d24, 0x00000d39, 0x00000d44, 0x00000d5e,
+ 0x00000d78, 0x00000d80, 0x00000d9e, 0x00000db7,
+ 0x00000dd2, 0x00000df4, 0x00000e23, 0x00000e4f,
+ 0x00000e87, 0x00000ead, 0x00000ebe, 0x00000ef4,
+ // Entry A0 - BF
+ 0x00000f3b, 0x00000f53, 0x00000f85, 0x00000ff7,
+ 0x00001011, 0x0000104b, 0x0000106e, 0x00001086,
+ 0x000010af, 0x000010c8, 0x00001121, 0x00001166,
+ 0x00001181, 0x000011a7, 0x000011bd, 0x000011e0,
+ 0x0000123f, 0x00001258, 0x0000127c, 0x00001288,
+ 0x000012b8, 0x000012ee, 0x0000130b, 0x00001324,
+ 0x00001355, 0x00001364, 0x00001372, 0x0000138c,
+ 0x000013c7, 0x000013f9, 0x0000140a, 0x0000141b,
+ // Entry C0 - DF
+ 0x00001445, 0x0000146f, 0x00001495, 0x000014b8,
+ 0x000014e3, 0x0000150d, 0x00001537, 0x00001555,
+ 0x0000157a, 0x000015a4, 0x000015c6, 0x000015f5,
+ 0x00001619, 0x00001648, 0x0000166e, 0x00001690,
+ 0x000016bd, 0x000016e1, 0x00001709, 0x00001738,
+ 0x00001767, 0x0000178a, 0x000017c0, 0x000017f6,
+ 0x00001820, 0x0000184c, 0x0000186b, 0x0000188c,
+ 0x000018b1, 0x000018e4, 0x00001917, 0x0000193a,
+ // Entry E0 - FF
+ 0x0000196b, 0x0000199c, 0x000019db, 0x00001a00,
+ 0x00001a1e, 0x00001a3c, 0x00001a69, 0x00001a91,
+ 0x00001abc, 0x00001ae9, 0x00001b13, 0x00001b56,
+ 0x00001ba3, 0x00001bf2, 0x00001c42, 0x00001c62,
+ 0x00001c8e, 0x00001cb7, 0x00001ccf, 0x00001ce6,
+ 0x00001cff, 0x00001d1f, 0x00001d31, 0x00001d3e,
+ 0x00001d51, 0x00001d60, 0x00001d7e, 0x00001d9d,
+ 0x00001dac, 0x00001dbb, 0x00001ddf, 0x00001df6,
+ // Entry 100 - 11F
+ 0x00001dfc, 0x00001e17, 0x00001e32, 0x00001e48,
+ 0x00001e61, 0x00001e77, 0x00001e8e, 0x00001eb3,
+ 0x00001ed3, 0x00001f08, 0x00001f2e, 0x00001f4d,
+ 0x00001f6b, 0x00001fdc, 0x00001ffd, 0x00002020,
+ 0x00002192, 0x00002199, 0x0000219d, 0x00002304,
+ 0x00002333, 0x00002355,
+} // Size: 1136 bytes
+
+const enData string = "" + // Size: 9045 bytes
+ "\x02Error\x02(no argument): elevate and install manager service\x02Usage" +
+ ": %[1]s [\x0a%[2]s]\x02Command Line Options\x02Unable to determine wheth" +
+ "er the process is running under WOW64: %[1]v\x02You must use the native " +
+ "version of WireGuard on this computer.\x02Unable to open current process" +
+ " token: %[1]v\x02WireGuard may only be used by users who are a member of" +
+ " the Builtin %[1]s group.\x02WireGuard is running, but the UI is only ac" +
+ "cessible from desktops of the Builtin %[1]s group.\x02Now\x02System cloc" +
+ "k wound backward!\x14\x01\x81\x01\x00\x02\x0b\x02%[1]d year\x00\x0c\x02%" +
+ "[1]d years\x14\x01\x81\x01\x00\x02\x0a\x02%[1]d day\x00\x0b\x02%[1]d day" +
+ "s\x14\x01\x81\x01\x00\x02\x0b\x02%[1]d hour\x00\x0c\x02%[1]d hours\x14" +
+ "\x01\x81\x01\x00\x02\x0d\x02%[1]d minute\x00\x0e\x02%[1]d minutes\x14" +
+ "\x01\x81\x01\x00\x02\x0d\x02%[1]d second\x00\x0e\x02%[1]d seconds\x02%[1" +
+ "]s ago\x02%[1]d\u00a0B\x02%.2[1]f\u00a0KiB\x02%.2[1]f\u00a0MiB\x02%.2[1]" +
+ "f\u00a0GiB\x02%.2[1]f\u00a0TiB\x02%[1]s: %[2]q\x02Invalid IP address\x02" +
+ "Invalid network prefix length\x02Missing port from endpoint\x02Invalid e" +
+ "ndpoint host\x02Invalid MTU\x02Invalid port\x02Invalid persistent keepal" +
+ "ive\x02Invalid key: %[1]v\x02Number must be a number between 0 and 2^64-" +
+ "1: %[1]v\x02Two commas in a row\x02Tunnel name is not valid\x02[none spe" +
+ "cified]\x02All peers must have public keys\x02Error in getting configura" +
+ "tion\x02, \x02, \x02About WireGuard\x02WireGuard logo image\x02Close\x02" +
+ "♥ &Donate!\x02Status:\x02&Deactivate\x02&Activate\x02Public key:\x02Li" +
+ "sten port:\x02MTU:\x02Addresses:\x02DNS servers:\x02Preshared key:\x02Al" +
+ "lowed IPs:\x02Endpoint:\x02Persistent keepalive:\x02Latest handshake:" +
+ "\x02Inactive\x02Deactivating\x02Unknown state\x02Log\x02&Copy\x02Select " +
+ "&all\x02&Save to file…\x02Time\x02Log message\x02Export log to file\x02&" +
+ "About WireGuard…\x02Tunnel Error\x02%[1]s\x0a\x0aPlease consult the log " +
+ "for more information.\x02%[1]s (out of date)\x02Error Exiting WireGuard" +
+ "\x02An update to WireGuard is available. It is highly advisable to updat" +
+ "e without delay.\x02Update Now\x02Error: %[1]v. Please try again.\x02Sta" +
+ "tus: Complete!\x02WireGuard system tray icon did not appear after 30 sec" +
+ "onds.\x02Brackets must contain an IPv6 address\x02Keys must decode to ex" +
+ "actly 32 bytes\x02Line must occur in a section\x02Config key is missing " +
+ "an equals separator\x02Key must have a value\x02Invalid key for [Interfa" +
+ "ce] section\x02Invalid key for [Peer] section\x02An interface must have " +
+ "a private key\x02Invalid key for interface section\x02Protocol version m" +
+ "ust be 1\x02Invalid key for peer section\x02Scripts:\x02Transfer:\x02pre" +
+ "-up\x02post-up\x02pre-down\x02post-down\x02disabled, per policy\x02enabl" +
+ "ed\x02%[1]s received, %[2]s sent\x02Failed to determine tunnel state\x02" +
+ "Failed to activate tunnel\x02Failed to deactivate tunnel\x02Interface: %" +
+ "[1]s\x02Peer\x02Create new tunnel\x02Edit tunnel\x02&Name:\x02&Public ke" +
+ "y:\x02(unknown)\x02&Block untunneled traffic (kill-switch)\x02&Save\x02C" +
+ "ancel\x02&Configuration:\x02Invalid name\x02A name is required.\x02Tunne" +
+ "l name ‘%[1]s’ is invalid.\x02Unable to list existing tunnels\x02Tunnel " +
+ "already exists\x02Another tunnel already exists with the name ‘%[1]s’." +
+ "\x02Unable to create new configuration\x02Writing file failed\x02File ‘%" +
+ "[1]s’ already exists.\x0a\x0aDo you want to overwrite it?\x02Active\x02A" +
+ "ctivating\x02Text Files (*.txt)|*.txt|All Files (*.*)|*.*\x02WireGuard D" +
+ "etection Error\x02Unable to wait for WireGuard window to appear: %[1]v" +
+ "\x02WireGuard: Deactivated\x02Status: Unknown\x02Addresses: None\x02&Man" +
+ "age tunnels…\x02&Import tunnel(s) from file…\x02E&xit\x02&Tunnels\x02Wir" +
+ "eGuard Activated\x02The %[1]s tunnel has been activated.\x02WireGuard De" +
+ "activated\x02The %[1]s tunnel has been deactivated.\x02WireGuard Tunnel " +
+ "Error\x02WireGuard: %[1]s\x02Status: %[1]s\x02Addresses: %[1]s\x02An Upd" +
+ "ate is Available!\x02WireGuard Update Available\x02An update to WireGuar" +
+ "d is now available. You are advised to update as soon as possible.\x02Tu" +
+ "nnels\x02&Edit\x02Add &empty tunnel…\x02Add Tunnel\x02Remove selected tu" +
+ "nnel(s)\x02Export all tunnels to zip\x02&Toggle\x02Export all tunnels to" +
+ " &zip…\x02Edit &selected tunnel…\x02&Remove selected tunnel(s)\x02no con" +
+ "figuration files were found\x02Could not import selected configuration: " +
+ "%[1]v\x02Could not enumerate existing tunnels: %[1]v\x02Another tunnel a" +
+ "lready exists with the name ‘%[1]s’\x02Unable to import configuration: %" +
+ "[1]v\x02Imported tunnels\x14\x01\x81\x01\x00\x02\x16\x02Imported %[1]d t" +
+ "unnel\x00\x17\x02Imported %[1]d tunnels\x14\x02\x80\x01\x02\x1f\x02Impor" +
+ "ted %[1]d of %[2]d tunnel\x00 \x02Imported %[1]d of %[2]d tunnels\x02Una" +
+ "ble to create tunnel\x14\x01\x81\x01\x00\x02\x14\x02Delete %[1]d tunnel" +
+ "\x00\x15\x02Delete %[1]d tunnels\x14\x01\x81\x01\x00\x024\x02Are you sur" +
+ "e you would like to delete %[1]d tunnel?\x005\x02Are you sure you would " +
+ "like to delete %[1]d tunnels?\x02Delete tunnel ‘%[1]s’\x02Are you sure y" +
+ "ou would like to delete tunnel ‘%[1]s’?\x02%[1]s You cannot undo this ac" +
+ "tion.\x02Unable to delete tunnel\x02A tunnel was unable to be removed: %" +
+ "[1]s\x02Unable to delete tunnels\x14\x01\x81\x01\x00\x02'\x02%[1]d tunne" +
+ "l was unable to be removed.\x00)\x02%[1]d tunnels were unable to be remo" +
+ "ved.\x02Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)" +
+ "|*.*\x02Import tunnel(s) from file\x02Configuration ZIP Files (*.zip)|*." +
+ "zip\x02Export tunnels to zip\x02%[1]s (unsigned build, no updates)\x02Un" +
+ "able to exit service due to: %[1]v. You may want to stop WireGuard from " +
+ "the service manager.\x02Status: Waiting for user\x02Status: Waiting for " +
+ "updater service\x04\x00\x01 \x07\x02Error:\x02Interface closed, ignored " +
+ "requested state %[1]s\x02Interface state was %[1]s, requested %[2]s, now" +
+ " %[3]s\x02Unable to update bind: %[1]v\x02Bind close failed: %[1]v\x02Tr" +
+ "ouble determining MTU, assuming default: %[1]v\x02Device closing\x02Devi" +
+ "ce closed\x02UDP bind has been updated\x02%[1]v - ConsumeMessageInitiati" +
+ "on: handshake replay @ %[2]v\x02%[1]v - ConsumeMessageInitiation: handsh" +
+ "ake flood\x02%[1]v - Starting\x02%[1]v - Stopping\x02Routine: receive in" +
+ "coming %[1]s - stopped\x02Routine: receive incoming %[1]s - started\x02F" +
+ "ailed to receive %[1]s packet: %[2]v\x02Received message with unknown ty" +
+ "pe\x02Routine: decryption worker %[1]d - started\x02Routine: handshake w" +
+ "orker %[1]d - stopped\x02Routine: handshake worker %[1]d - started\x02Fa" +
+ "iled to decode cookie reply\x02Receiving cookie response from %[1]s\x02C" +
+ "ould not decrypt invalid cookie response\x02Received packet with invalid" +
+ " mac1\x02Invalid packet ended up in the handshake queue\x02Failed to dec" +
+ "ode initiation message\x02Received invalid initiation message from %[1]s" +
+ "\x02%[1]v - Received handshake initiation\x02Failed to decode response m" +
+ "essage\x02Received invalid response message from %[1]s\x02%[1]v - Receiv" +
+ "ed handshake response\x02%[1]v - Failed to derive keypair: %[2]v\x02%[1]" +
+ "v - Routine: sequential receiver - stopped\x02%[1]v - Routine: sequentia" +
+ "l receiver - started\x02%[1]v - Receiving keepalive packet\x02IPv4 packe" +
+ "t with disallowed source address from %[1]v\x02IPv6 packet with disallow" +
+ "ed source address from %[1]v\x02Packet with invalid IP version from %[1]" +
+ "v\x02Failed to write packet to TUN device: %[1]v\x02Unable to flush pack" +
+ "ets: %[1]v\x02%[1]v - Sending keepalive packet\x02%[1]v - Sending handsh" +
+ "ake initiation\x02%[1]v - Failed to create initiation message: %[2]v\x02" +
+ "%[1]v - Failed to send handshake initiation: %[2]v\x02%[1]v - Sending ha" +
+ "ndshake response\x02%[1]v - Failed to create response message: %[2]v\x02" +
+ "%[1]v - Failed to send handshake response: %[2]v\x02Sending cookie respo" +
+ "nse for denied handshake message for %[1]v\x02Failed to create cookie re" +
+ "ply: %[1]v\x02Routine: TUN reader - stopped\x02Routine: TUN reader - sta" +
+ "rted\x02Failed to read packet from TUN device: %[1]v\x02Received packet " +
+ "with unknown IP version\x02Routine: encryption worker %[1]d - started" +
+ "\x02%[1]v - Routine: sequential sender - started\x02%[1]v - Failed to se" +
+ "nd data packet: %[2]v\x02%[1]s - Handshake did not complete after %[2]d " +
+ "attempts, giving up\x02%[1]s - Handshake did not complete after %[2]d se" +
+ "conds, retrying (try %[3]d)\x02%[1]s - Retrying handshake because we sto" +
+ "pped hearing back after %[2]d seconds\x02%[1]s - Removing all keys, sinc" +
+ "e we haven't received a new one in %[2]d seconds\x02Routine: event worke" +
+ "r - started\x02Failed to load updated MTU of device: %[1]v\x02MTU not up" +
+ "dated to negative value: %[1]v\x02MTU updated: %[1]v%[2]s\x02Interface u" +
+ "p requested\x02Interface down requested\x02Routine: event worker - stopp" +
+ "ed\x02listen_port=%[1]d\x02fwmark=%[1]d\x02protocol_version=1\x02endpoin" +
+ "t=%[1]s\x02last_handshake_time_sec=%[1]d\x02last_handshake_time_nsec=%[1" +
+ "]d\x02tx_bytes=%[1]d\x02rx_bytes=%[1]d\x02persistent_keepalive_interval=" +
+ "%[1]d\x02allowed_ip=%[1]s/%[2]d\x02%[1]v\x02UAPI: Updating private key" +
+ "\x02UAPI: Updating listen port\x02UAPI: Updating fwmark\x02UAPI: Removin" +
+ "g all peers\x02%[1]v - UAPI: Created\x02%[1]v - UAPI: Removing\x02%[1]v " +
+ "- UAPI: Updating preshared key\x02%[1]v - UAPI: Updating endpoint\x02%[1" +
+ "]v - UAPI: Updating persistent keepalive interval\x02%[1]v - UAPI: Remov" +
+ "ing all allowedips\x02%[1]v - UAPI: Adding allowedip\x02invalid UAPI ope" +
+ "ration: %[1]v\x02App version: %[1]s\x0aGo backend version: %[2]s\x0aGo v" +
+ "ersion: %[3]s-%[4]s\x0aOperating system: %[5]s\x0aArchitecture: %[6]s" +
+ "\x02&Test experimental kernel driver\x02Enable experimental kernel drive" +
+ "r?\x02The WireGuard project is currently testing a high performance kern" +
+ "el driver called WireGuardNT. It will eventually be enabled by default, " +
+ "but for now the project needs testers to try it out. Whether you encount" +
+ "er problems or you find that it works well, please do email team@wiregua" +
+ "rd.com about your experience.\x0a\x0aWould you like to enable the experi" +
+ "mental kernel driver?\x02Table:\x02off\x02When a configuration has exact" +
+ "ly one peer, and that peer has an allowed IPs containing at least one of" +
+ " 0.0.0.0/0 or ::/0, and the interface does not have table off, then the " +
+ "tunnel service engages a firewall ruleset to block all traffic that is n" +
+ "either to nor from the tunnel interface or is to the wrong DNS server, w" +
+ "ith special exceptions for DHCP and NDP.\x02Please ask the system admini" +
+ "strator to update.\x02Status: Waiting for administrator"
+
+var es_ESIndex = []uint32{ // 278 elements
+ // Entry 0 - 1F
+ 0x00000000, 0x00000006, 0x00000044, 0x00000058,
+ 0x00000077, 0x00000077, 0x00000077, 0x00000077,
+ 0x00000077, 0x00000077, 0x0000007d, 0x0000007d,
+ 0x0000009d, 0x000000bc, 0x000000dc, 0x00000100,
+ 0x00000126, 0x00000131, 0x00000139, 0x00000145,
+ 0x00000151, 0x0000015d, 0x00000169, 0x00000169,
+ 0x00000181, 0x000001a7, 0x000001c3, 0x000001df,
+ 0x000001ed, 0x000001ff, 0x000001ff, 0x00000217,
+ // Entry 20 - 3F
+ 0x00000217, 0x00000217, 0x00000217, 0x00000217,
+ 0x00000217, 0x00000217, 0x00000217, 0x00000217,
+ 0x0000022b, 0x00000248, 0x0000024f, 0x0000025b,
+ 0x00000263, 0x0000026f, 0x00000278, 0x00000288,
+ 0x0000029b, 0x000002a0, 0x000002ad, 0x000002bd,
+ 0x000002cf, 0x000002df, 0x000002df, 0x000002df,
+ 0x000002ef, 0x000002f8, 0x00000305, 0x00000318,
+ 0x00000321, 0x00000329, 0x0000033b, 0x00000352,
+ // Entry 40 - 5F
+ 0x00000359, 0x0000036e, 0x0000038b, 0x000003a3,
+ 0x000003b6, 0x000003f5, 0x000003f5, 0x000003f5,
+ 0x000003f5, 0x00000400, 0x00000423, 0x00000435,
+ 0x00000435, 0x00000435, 0x00000435, 0x00000435,
+ 0x00000435, 0x00000435, 0x00000435, 0x00000435,
+ 0x00000435, 0x00000435, 0x00000435, 0x00000435,
+ 0x00000435, 0x00000441, 0x00000441, 0x00000441,
+ 0x00000441, 0x00000441, 0x00000441, 0x0000044a,
+ // Entry 60 - 7F
+ 0x0000044a, 0x0000044a, 0x00000465, 0x00000483,
+ 0x00000493, 0x00000499, 0x000004af, 0x000004bd,
+ 0x000004c6, 0x000004d6, 0x000004e4, 0x000004e4,
+ 0x000004ed, 0x000004f6, 0x00000507, 0x00000519,
+ 0x00000530, 0x00000560, 0x00000560, 0x00000560,
+ 0x00000560, 0x00000560, 0x00000560, 0x00000560,
+ 0x00000567, 0x00000571, 0x000005ae, 0x000005ae,
+ 0x000005ae, 0x000005c5, 0x000005d9, 0x000005ee,
+ // Entry 80 - 9F
+ 0x000005ee, 0x000005ee, 0x000005f5, 0x000005ff,
+ 0x00000612, 0x00000612, 0x00000629, 0x00000629,
+ 0x00000649, 0x0000065a, 0x00000668, 0x0000067b,
+ 0x0000069e, 0x000006c5, 0x00000725, 0x0000072e,
+ 0x00000736, 0x00000750, 0x0000075f, 0x0000077f,
+ 0x000007a1, 0x000007ab, 0x000007ab, 0x000007ab,
+ 0x000007ab, 0x000007ab, 0x000007ab, 0x000007e0,
+ 0x0000080c, 0x00000838, 0x0000084c, 0x00000886,
+ // Entry A0 - BF
+ 0x000008d1, 0x000008ec, 0x00000923, 0x00000923,
+ 0x0000093f, 0x0000097b, 0x000009a2, 0x000009bf,
+ 0x000009e9, 0x00000a05, 0x00000a05, 0x00000a05,
+ 0x00000a05, 0x00000a05, 0x00000a05, 0x00000a05,
+ 0x00000a05, 0x00000a22, 0x00000a22, 0x00000a22,
+ 0x00000a22, 0x00000a22, 0x00000a22, 0x00000a22,
+ 0x00000a22, 0x00000a22, 0x00000a22, 0x00000a22,
+ 0x00000a22, 0x00000a22, 0x00000a22, 0x00000a22,
+ // Entry C0 - DF
+ 0x00000a22, 0x00000a22, 0x00000a22, 0x00000a22,
+ 0x00000a22, 0x00000a22, 0x00000a22, 0x00000a22,
+ 0x00000a22, 0x00000a22, 0x00000a22, 0x00000a22,
+ 0x00000a22, 0x00000a22, 0x00000a22, 0x00000a22,
+ 0x00000a22, 0x00000a22, 0x00000a22, 0x00000a22,
+ 0x00000a22, 0x00000a22, 0x00000a22, 0x00000a22,
+ 0x00000a22, 0x00000a22, 0x00000a22, 0x00000a22,
+ 0x00000a22, 0x00000a22, 0x00000a22, 0x00000a22,
+ // Entry E0 - FF
+ 0x00000a22, 0x00000a22, 0x00000a22, 0x00000a22,
+ 0x00000a22, 0x00000a22, 0x00000a22, 0x00000a22,
+ 0x00000a22, 0x00000a22, 0x00000a22, 0x00000a22,
+ 0x00000a22, 0x00000a22, 0x00000a22, 0x00000a22,
+ 0x00000a22, 0x00000a22, 0x00000a22, 0x00000a22,
+ 0x00000a22, 0x00000a22, 0x00000a22, 0x00000a22,
+ 0x00000a22, 0x00000a22, 0x00000a22, 0x00000a22,
+ 0x00000a22, 0x00000a22, 0x00000a22, 0x00000a22,
+ // Entry 100 - 11F
+ 0x00000a22, 0x00000a22, 0x00000a22, 0x00000a22,
+ 0x00000a22, 0x00000a22, 0x00000a22, 0x00000a22,
+ 0x00000a22, 0x00000a22, 0x00000a22, 0x00000a22,
+ 0x00000a22, 0x00000a22, 0x00000a22, 0x00000a22,
+ 0x00000a22, 0x00000a22, 0x00000a22, 0x00000a22,
+ 0x00000a22, 0x00000a22,
+} // Size: 1136 bytes
+
+const es_ESData string = "" + // Size: 2594 bytes
+ "\x02Error\x02(sin argumento): eleva e instala el servicio de administrad" +
+ "or\x02Uso: %[1]s [\x0a%[2]s]\x02Opciones de línea de comandos\x02Ahora" +
+ "\x14\x01\x81\x01\x00\x02\x0b\x02%[1]d año\x00\x0c\x02%[1]d años\x14\x01" +
+ "\x81\x01\x00\x02\x0b\x02%[1]d día\x00\x0b\x02%[1]d dias\x14\x01\x81\x01" +
+ "\x00\x02\x0b\x02%[1]d hora\x00\x0c\x02%[1]d horas\x14\x01\x81\x01\x00" +
+ "\x02\x0d\x02%[1]d minuto\x00\x0e\x02%[1]d minutos\x14\x01\x81\x01\x00" +
+ "\x02\x0e\x02%[1]d segundo\x00\x0f\x02%[1]d segundos\x02hace %[1]s\x02%[1" +
+ "]d B\x02%.2[1]f KiB\x02%.2[1]f MiB\x02%.2[1]f GiB\x02%.2[1]f TiB\x02Dire" +
+ "cción IP inválida\x02Longitud de prefijo de red no válida\x02Falta el pu" +
+ "erto del extremo\x02Host de endpoint no válido\x02MTU no valido\x02Puert" +
+ "o no válido\x02Clave no válida: %[1]v\x02Acerca de WireGuard\x02Imagen d" +
+ "el logo de WireGuard\x02Cerrar\x02♥ &Donar!\x02Estado:\x02&Desactivar" +
+ "\x02&Activar\x02Clave pública:\x02Puerto de escucha:\x02MTU:\x02Direccio" +
+ "nes:\x02Servidores DNS:\x02Clave compartida:\x02IPs permitidas:\x02Últim" +
+ "o saludo:\x02Inactivo\x02Desactivando\x02Estado desconocido\x02Registro" +
+ "\x02&Copiar\x02Seleccionar &todo\x02&Guardar en archivo…\x02Tiempo\x02Re" +
+ "gistro de mensajes\x02Exportar archivo de registro\x02&Acerca de WireGua" +
+ "rd…\x02Error en el túnel\x02%[1]s\x0a\x0aPor favor, consulte el registro" +
+ " para más información.\x02Actualizar\x02Error: %[1]v. Inténtalo de nuevo" +
+ ".\x02Estado: Completo!\x02Transferir:\x02activado\x02Error al activar el" +
+ " túnel\x02Error al desactivar el túnel\x02Interfaz: %[1]s\x02Pares\x02Cr" +
+ "ear un túnel nuevo\x02Editar túnel\x02&Nombre:\x02Clave pública:\x02(des" +
+ "conocido)\x02&Guardar\x02Cancelar\x02&Configuración:\x02Nombre no válido" +
+ "\x02Se requiere un nombre.\x02El nombre del túnel ‘%[1]s’ no es válido." +
+ "\x02Activo\x02Activando\x02Archivos de texto (*.txt)|*.txt|Todos los arc" +
+ "hivos (*.*)|*.*\x02WireGuard: Desactivado\x02Estado: Desconocido\x02Dire" +
+ "cciones: Ninguna\x02&Salir\x02&Túneles\x02WireGuard Activado\x02WireGuar" +
+ "d: Desactivado\x02Error en el túnel de WireGuard\x02WireGuard: %[1]s\x02" +
+ "Estado: %[1]s\x02Direcciones: %[1]s\x02Hay una actualización disponible!" +
+ "\x02Actualización de WireGuard disponible\x02Ya está disponible una actu" +
+ "alización de WireGuard. Se recomienda actualizar lo antes posible.\x02Tú" +
+ "neles\x02&Editar\x02Añadir &túnel vacío…\x02Añadir túnel\x02Eliminar tún" +
+ "eles seleccionados\x02Exportar todos los túneles a zip\x02&Alternar\x02N" +
+ "o se pueden enumerar los túneles existentes: %[1]v\x02Ya existe otro tún" +
+ "el con el nombre '%[1]s'\x02Imposible importar la configuración: %[1]v" +
+ "\x02Túneles importados\x14\x01\x81\x01\x00\x02\x17\x02%[1]d túnel import" +
+ "ado\x00\x1a\x02%[1]d túneles importados\x14\x02\x80\x01\x02 \x02Importad" +
+ "o %[1]d de %[2]d túnel\x00#\x02Importados %[1]d de %[2]d túneles\x02No s" +
+ "e pudo crear el túnel\x14\x01\x81\x01\x00\x02\x16\x02Eliminar %[1]d túne" +
+ "l\x00\x18\x02Eliminar %[1]d túneles\x02Eliminar túnel ‘%[1]s’\x02¿Está s" +
+ "eguro de que desea eliminar el túnel ‘%[1]s’?\x02%[1]s No puedes deshace" +
+ "r esta acción.\x02No se pudo eliminar el tunel\x02No se ha podido elimin" +
+ "ar un túnel: %[1]s\x02Imposible eliminar túneles\x02Estado: Esperando al" +
+ " usuario"
+
+var faIndex = []uint32{ // 278 elements
+ // Entry 0 - 1F
+ 0x00000000, 0x00000007, 0x00000007, 0x00000007,
+ 0x0000002b, 0x0000002b, 0x0000002b, 0x0000002b,
+ 0x0000002b, 0x0000002b, 0x0000003b, 0x0000003b,
+ 0x0000005e, 0x00000081, 0x000000a8, 0x000000d3,
+ 0x000000fe, 0x0000010b, 0x0000011b, 0x0000011b,
+ 0x0000011b, 0x0000011b, 0x0000011b, 0x0000011b,
+ 0x00000148, 0x00000148, 0x00000148, 0x00000148,
+ 0x00000162, 0x0000017a, 0x0000017a, 0x0000017a,
+ // Entry 20 - 3F
+ 0x0000017a, 0x0000017a, 0x0000017a, 0x0000018e,
+ 0x000001de, 0x00000208, 0x0000020c, 0x00000210,
+ 0x00000227, 0x00000227, 0x00000230, 0x00000247,
+ 0x00000253, 0x0000026e, 0x00000283, 0x00000298,
+ 0x000002ab, 0x000002b0, 0x000002c3, 0x000002d7,
+ 0x000002ff, 0x00000312, 0x00000327, 0x00000352,
+ 0x00000352, 0x00000361, 0x00000387, 0x000003a3,
+ 0x000003b9, 0x000003c7, 0x000003c7, 0x000003e8,
+ // Entry 40 - 5F
+ 0x000003f1, 0x00000412, 0x0000044e, 0x00000469,
+ 0x0000047d, 0x0000047d, 0x0000047d, 0x000004b3,
+ 0x000004b3, 0x000004dc, 0x00000514, 0x0000052f,
+ 0x0000052f, 0x0000052f, 0x0000052f, 0x0000052f,
+ 0x0000052f, 0x00000565, 0x00000565, 0x00000565,
+ 0x00000565, 0x00000565, 0x00000565, 0x00000565,
+ 0x00000565, 0x00000573, 0x00000573, 0x00000573,
+ 0x00000573, 0x00000573, 0x00000573, 0x00000583,
+ // Entry 60 - 7F
+ 0x00000583, 0x00000583, 0x00000583, 0x00000583,
+ 0x00000593, 0x0000059c, 0x000005b9, 0x000005cf,
+ 0x000005d8, 0x000005ee, 0x00000601, 0x00000601,
+ 0x0000060d, 0x00000614, 0x00000627, 0x0000063d,
+ 0x0000065e, 0x0000065e, 0x000006a4, 0x000006d1,
+ 0x000006d1, 0x000006d1, 0x000006d1, 0x000006d1,
+ 0x000006da, 0x000006fa, 0x000006fa, 0x000006fa,
+ 0x000006fa, 0x000006fa, 0x00000717, 0x00000731,
+ // Entry 80 - 9F
+ 0x00000752, 0x00000752, 0x00000752, 0x00000752,
+ 0x0000076c, 0x0000078e, 0x000007ac, 0x000007ac,
+ 0x000007c8, 0x000007c8, 0x000007da, 0x000007da,
+ 0x0000080f, 0x00000848, 0x00000848, 0x00000858,
+ 0x00000866, 0x00000894, 0x000008aa, 0x000008cf,
+ 0x00000904, 0x00000904, 0x0000093d, 0x0000093d,
+ 0x0000093d, 0x0000093d, 0x0000093d, 0x0000093d,
+ 0x0000093d, 0x0000093d, 0x0000095f, 0x000009a2,
+ // Entry A0 - BF
+ 0x000009fa, 0x00000a27, 0x00000a5c, 0x00000a5c,
+ 0x00000a78, 0x00000a78, 0x00000a78, 0x00000aaa,
+ 0x00000aaa, 0x00000adf, 0x00000adf, 0x00000adf,
+ 0x00000b12, 0x00000b4e, 0x00000b7c, 0x00000b7c,
+ 0x00000b7c, 0x00000bad, 0x00000bf6, 0x00000bf6,
+ 0x00000bf6, 0x00000bf6, 0x00000bf6, 0x00000bf6,
+ 0x00000bf6, 0x00000bf6, 0x00000bf6, 0x00000bf6,
+ 0x00000bf6, 0x00000bf6, 0x00000bf6, 0x00000bf6,
+ // Entry C0 - DF
+ 0x00000bf6, 0x00000bf6, 0x00000bf6, 0x00000bf6,
+ 0x00000bf6, 0x00000bf6, 0x00000bf6, 0x00000bf6,
+ 0x00000bf6, 0x00000bf6, 0x00000bf6, 0x00000bf6,
+ 0x00000bf6, 0x00000bf6, 0x00000bf6, 0x00000bf6,
+ 0x00000bf6, 0x00000bf6, 0x00000bf6, 0x00000bf6,
+ 0x00000bf6, 0x00000bf6, 0x00000bf6, 0x00000bf6,
+ 0x00000bf6, 0x00000bf6, 0x00000bf6, 0x00000bf6,
+ 0x00000bf6, 0x00000bf6, 0x00000bf6, 0x00000bf6,
+ // Entry E0 - FF
+ 0x00000bf6, 0x00000bf6, 0x00000bf6, 0x00000bf6,
+ 0x00000bf6, 0x00000bf6, 0x00000bf6, 0x00000bf6,
+ 0x00000bf6, 0x00000bf6, 0x00000bf6, 0x00000bf6,
+ 0x00000bf6, 0x00000bf6, 0x00000bf6, 0x00000bf6,
+ 0x00000bf6, 0x00000bf6, 0x00000bf6, 0x00000bf6,
+ 0x00000bf6, 0x00000bf6, 0x00000bf6, 0x00000bf6,
+ 0x00000bf6, 0x00000bf6, 0x00000bf6, 0x00000bf6,
+ 0x00000bf6, 0x00000bf6, 0x00000bf6, 0x00000bf6,
+ // Entry 100 - 11F
+ 0x00000bf6, 0x00000bf6, 0x00000bf6, 0x00000bf6,
+ 0x00000bf6, 0x00000bf6, 0x00000bf6, 0x00000bf6,
+ 0x00000bf6, 0x00000bf6, 0x00000bf6, 0x00000bf6,
+ 0x00000bf6, 0x00000bf6, 0x00000bf6, 0x00000bf6,
+ 0x00000bf6, 0x00000bf6, 0x00000bf6, 0x00000bf6,
+ 0x00000bf6, 0x00000bf6,
+} // Size: 1136 bytes
+
+const faData string = "" + // Size: 3062 bytes
+ "\x02خطا\x02گزینه\u200cهای خط فرمان\x02هم اکنون\x14\x01\x81\x01\x00\x02" +
+ "\x0d\x02%[1]d سال\x00\x0d\x02%[1]d سال\x14\x01\x81\x01\x00\x02\x0d\x02%[" +
+ "1]d روز\x00\x0d\x02%[1]d روز\x14\x01\x81\x01\x00\x02\x0f\x02%[1]d ساعت" +
+ "\x00\x0f\x02%[1]d ساعت\x14\x01\x81\x01\x00\x02\x11\x02%[1]d دقیقه\x00" +
+ "\x11\x02%[1]d دقیقه\x14\x01\x81\x01\x00\x02\x11\x02%[1]d ثانیه\x00\x11" +
+ "\x02%[1]d ثانیه\x02%[1]s پیش\x02%[1]d\u00a0بایت\x02نشانی آی\u200cپی نامع" +
+ "تبر است\x02MTU نامعتبر است\x02پورت نامعتبر\x02[مشخص نشده]\x02همه همتاها" +
+ " باید کلید\u200cهای عمومی داشته باشند\x02خطا در دریافت پیکربندی\x02، " +
+ "\x02، \x02درباره WireGuard\x02بستن\x02♥&کمک\u200cمالی!\x02وضعیت:\x02&غیر" +
+ "فعال\u200cسازی\x02&فعال\u200cسازی\x02کلید عمومی:\x02پورت شنود:\x02MTU:" +
+ "\x02نشانی\u200cها:\x02سرورهای DNS:\x02کلید از پیش تقسیم شده:\x02IPهای مج" +
+ "از:\x02نقطه پایان:\x02زنده نگه\u200cداشتن پیوسته:\x02غیرفعال\x02در حال " +
+ "غیرفعال\u200cسازی\x02وضعیت ناشناخته\x02گزارش وقایع\x02&روگرفت\x02&ذخیره" +
+ " در پرونده…\x02زمان\x02پیام گزارش رویداد\x02برون\u200cبرد گزارش رویداد ب" +
+ "ه پرونده\x02&درباره WireGuard…\x02خطالی تونل\x02خطا در هنگام خارج شدن ا" +
+ "ز WireGuard\x02اکنون به\u200cروز رسانی کن\x02خطا: %[1]v. لطفا دوباره تل" +
+ "اش کنید.\x02وضعیت: کامل شد!\x02کلید باید یک مقدار داشته باشد\x02انتقال:" +
+ "\x02فعال شده\x02رابط: %[1]s\x02همتا\x02ایجاد تونل جدید\x02ویرایش تونل" +
+ "\x02&نام:\x02&کلید عمومی:\x02(ناشناخته)\x02&ذخیره\x02لغو\x02&پیکربندی:" +
+ "\x02نام نامعتبر\x02یک نام الزامی است.\x02نمی\u200cتوان تونل\u200cهای موج" +
+ "ود را فهرست کرد\x02تونل هم\u200cاکنون موجود است\x02فعال\x02در حال فعال" +
+ "\u200cسازی\x02وضعیت: ناشناخته\x02نشانی\u200cها: هیچ\x02&مدیریت تونل" +
+ "\u200cها…\x02WireGuard فعال\u200cشد\x02تونل %[1]s فعال\u200cشده.\x02Wire" +
+ "Guard غیرفعال شد\x02خطای تونل WireGuard\x02وضعیت: %[1]s\x02یک به\u200cرو" +
+ "زرسانی در دسترس است!\x02به\u200cروزرسانی WireGuard در دسترس است\x02تونل" +
+ "\u200cها\x02&ویرایش\x02افزودن &خالی\u200cکردن تونل…\x02افزودن تونل\x02حذ" +
+ "ف تونل(ها) انتخابی\x02برون\u200cبری همه تونل\u200cها به زیپ\x02برون" +
+ "\u200cبری همه تونل\u200cها به &زیپ…\x02تونل\u200cهای وارد شده\x14\x01" +
+ "\x81\x01\x00\x02\x1d\x02%[1]d تونل وارد شد\x00\x1d\x02%[1]d تونل وارد شد" +
+ "\x14\x02\x80\x01\x02(\x02%[1]d از %[2]d تونل وارد شد\x00(\x02%[1]d از %[" +
+ "2]d تونل وارد شد\x02نمی\u200cتوان تونل ایجاد کرد\x14\x01\x81\x01\x00\x02" +
+ "\x16\x02حذف %[1]d تونل\x00\x16\x02حذف %[1]d تونل\x02حذف تونل ‘%[1]s’\x02" +
+ "حذف تونل\u200c امکان\u200cپذیر نیست\x02نمی\u200cتوان تونل\u200cها را حذ" +
+ "ف کرد\x02وارد کردن تونل(ها) از پرونده\x02پرونده\u200cهای پیکربندی زیپ (" +
+ "*.zip)|*.zip\x02برون\u200cبری تونل\u200cها به زیپ\x02وضعیت: درانتظار برا" +
+ "ی کاربر\x02وضعیت: درانتظار برای سرویس به\u200cروزرسانی"
+
+var fiIndex = []uint32{ // 278 elements
+ // Entry 0 - 1F
+ 0x00000000, 0x00000006, 0x00000006, 0x00000006,
+ 0x00000006, 0x00000006, 0x00000006, 0x00000006,
+ 0x00000006, 0x00000006, 0x0000000a, 0x0000002a,
+ 0x0000002a, 0x0000002a, 0x0000002a, 0x0000002a,
+ 0x0000002a, 0x00000037, 0x0000003f, 0x0000003f,
+ 0x0000003f, 0x0000003f, 0x0000003f, 0x0000003f,
+ 0x0000003f, 0x0000003f, 0x0000003f, 0x0000003f,
+ 0x00000050, 0x00000064, 0x00000083, 0x0000009d,
+ // Entry 20 - 3F
+ 0x0000009d, 0x0000009d, 0x0000009d, 0x0000009d,
+ 0x0000009d, 0x0000009d, 0x000000a0, 0x000000a3,
+ 0x000000b8, 0x000000cd, 0x000000d3, 0x000000e2,
+ 0x000000e8, 0x000000e8, 0x000000e8, 0x000000f8,
+ 0x0000010a, 0x0000010f, 0x0000011a, 0x0000012a,
+ 0x00000138, 0x0000014f, 0x0000015d, 0x00000170,
+ 0x00000184, 0x00000184, 0x00000184, 0x00000184,
+ 0x00000189, 0x00000191, 0x00000191, 0x00000191,
+ // Entry 40 - 5F
+ 0x00000191, 0x00000191, 0x00000191, 0x00000191,
+ 0x00000191, 0x00000191, 0x00000191, 0x00000191,
+ 0x00000191, 0x0000019f, 0x0000019f, 0x000001ad,
+ 0x000001ad, 0x000001ad, 0x000001ad, 0x000001ad,
+ 0x000001ad, 0x000001ad, 0x000001ad, 0x000001ad,
+ 0x000001ad, 0x000001ad, 0x000001ad, 0x000001ad,
+ 0x000001bc, 0x000001c5, 0x000001c5, 0x000001c5,
+ 0x000001c5, 0x000001c5, 0x000001c5, 0x000001d1,
+ // Entry 60 - 7F
+ 0x000001f7, 0x000001f7, 0x000001f7, 0x000001f7,
+ 0x0000020b, 0x00000214, 0x00000225, 0x00000236,
+ 0x0000023d, 0x0000024e, 0x0000025b, 0x0000028b,
+ 0x00000295, 0x00000295, 0x000002a5, 0x000002b7,
+ 0x000002cb, 0x000002cb, 0x000002cb, 0x000002e2,
+ 0x000002e2, 0x000002e2, 0x000002e2, 0x000002e2,
+ 0x000002e2, 0x000002e2, 0x000002e2, 0x000002e2,
+ 0x000002e2, 0x000002e2, 0x000002e2, 0x000002e2,
+ // Entry 80 - 9F
+ 0x000002e2, 0x00000301, 0x00000301, 0x00000301,
+ 0x00000301, 0x00000301, 0x00000301, 0x00000301,
+ 0x00000301, 0x00000301, 0x00000301, 0x00000301,
+ 0x0000031a, 0x00000339, 0x00000399, 0x000003a1,
+ 0x000003aa, 0x000003c5, 0x000003d5, 0x000003d5,
+ 0x000003d5, 0x000003d5, 0x000003d5, 0x000003d5,
+ 0x000003d5, 0x000003d5, 0x000003d5, 0x000003d5,
+ 0x000003d5, 0x000003d5, 0x000003e5, 0x00000417,
+ // Entry A0 - BF
+ 0x00000417, 0x00000417, 0x00000417, 0x00000417,
+ 0x00000417, 0x00000417, 0x00000417, 0x00000417,
+ 0x00000417, 0x00000431, 0x00000431, 0x00000431,
+ 0x0000044c, 0x0000044c, 0x0000044c, 0x0000044c,
+ 0x0000044c, 0x0000046a, 0x0000046a, 0x0000046a,
+ 0x0000046a, 0x0000046a, 0x0000046a, 0x0000046a,
+ 0x0000046a, 0x0000046a, 0x0000046a, 0x0000046a,
+ 0x0000046a, 0x0000046a, 0x0000046a, 0x0000046a,
+ // Entry C0 - DF
+ 0x0000046a, 0x0000046a, 0x0000046a, 0x0000046a,
+ 0x0000046a, 0x0000046a, 0x0000046a, 0x0000046a,
+ 0x0000046a, 0x0000046a, 0x0000046a, 0x0000046a,
+ 0x0000046a, 0x0000046a, 0x0000046a, 0x0000046a,
+ 0x0000046a, 0x0000046a, 0x0000046a, 0x0000046a,
+ 0x0000046a, 0x0000046a, 0x0000046a, 0x0000046a,
+ 0x0000046a, 0x0000046a, 0x0000046a, 0x0000046a,
+ 0x0000046a, 0x0000046a, 0x0000046a, 0x0000046a,
+ // Entry E0 - FF
+ 0x0000046a, 0x0000046a, 0x0000046a, 0x0000046a,
+ 0x0000046a, 0x0000046a, 0x0000046a, 0x0000046a,
+ 0x0000046a, 0x0000046a, 0x0000046a, 0x0000046a,
+ 0x0000046a, 0x0000046a, 0x0000046a, 0x0000046a,
+ 0x0000046a, 0x0000046a, 0x0000046a, 0x0000046a,
+ 0x0000046a, 0x0000046a, 0x0000046a, 0x0000046a,
+ 0x0000046a, 0x0000046a, 0x0000046a, 0x0000046a,
+ 0x0000046a, 0x0000046a, 0x0000046a, 0x0000046a,
+ // Entry 100 - 11F
+ 0x0000046a, 0x0000046a, 0x0000046a, 0x0000046a,
+ 0x0000046a, 0x0000046a, 0x0000046a, 0x0000046a,
+ 0x0000046a, 0x0000046a, 0x0000046a, 0x0000046a,
+ 0x0000046a, 0x0000046a, 0x0000046a, 0x0000046a,
+ 0x0000046a, 0x0000046a, 0x0000046a, 0x0000046a,
+ 0x0000046a, 0x0000046a,
+} // Size: 1136 bytes
+
+const fiData string = "" + // Size: 1130 bytes
+ "\x02Virhe\x02Nyt\x02Järjestelmän kello jättää!\x02%[1]s sitten\x02%[1]d " +
+ "B\x02Virheellinen MTU\x02Virheellinen portti\x02Virheellinen jatkuva kee" +
+ "palive\x02Virheellinen avain: %[1]v\x02, \x02, \x02Tietoa WireGuardista" +
+ "\x02WireGuard logon kuva\x02Sulje\x02♥ &Lahjoita!\x02Tila:\x02Julkinen a" +
+ "vain:\x02Kuuntele porttia:\x02MTU:\x02Osoitteet:\x02DNS palvelimet:\x02J" +
+ "aettu avain:\x02Sallitut IP-osoitteet:\x02Päätepiste:\x02Jatkuva keepali" +
+ "ve:\x02Viimeisin kättely:\x02Loki\x02&Kopioi\x02Päivitä nyt\x02Tila: Val" +
+ "mis!\x02Komentosarjat:\x02Siirrot:\x02käytössä\x02%[1]s vastaanotettu, %" +
+ "[2]s lähetetty\x02Verkkoyhteys: %[1]s\x02Osapuoli\x02Luo uusi tunneli" +
+ "\x02Muokkaa tunnelia\x02&Nimi:\x02&Julkinen avain:\x02(tuntematon)\x02&E" +
+ "stä tunneloimaton liikenne (pääkatkaisija)\x02&Tallenna\x02&Konfiguraati" +
+ "o:\x02Virheellinen nimi\x02Nimi on pakollinen.\x02Tunneli on jo olemassa" +
+ "\x02Tuo tunnele&ita tiedostosta…\x02Päivitys on saatavilla!\x02WireGuard" +
+ " päivitys saatavilla\x02WireGuardin päivitys on nyt saatavilla. Sinua ke" +
+ "hotetaan päivittämään mahdollisimman pian.\x02Tunneli\x02&Muokkaa\x02Lis" +
+ "ää tyhjä tunn&eli…\x02Lisää tunneli\x02Tuodut tunnelit\x14\x01\x81\x01" +
+ "\x00\x02\x14\x02Tuotu %[1]d tunneli\x00\x15\x02Tuotu %[1]d tunnelia\x02T" +
+ "unnelia ei voitu poistaa\x02Tuo tunneli(t) tiedostosta\x02Tila: Odotetaa" +
+ "n käyttäjää"
+
+var frIndex = []uint32{ // 278 elements
+ // Entry 0 - 1F
+ 0x00000000, 0x00000007, 0x00000046, 0x00000063,
+ 0x00000083, 0x000000cb, 0x00000112, 0x0000014b,
+ 0x000001ad, 0x00000225, 0x00000230, 0x00000253,
+ 0x0000026f, 0x0000028f, 0x000002b1, 0x000002d5,
+ 0x000002fb, 0x00000308, 0x00000311, 0x0000031e,
+ 0x0000032b, 0x00000338, 0x00000345, 0x00000353,
+ 0x00000369, 0x00000391, 0x000003b7, 0x000003e0,
+ 0x000003ef, 0x000003ff, 0x00000414, 0x0000042c,
+ // Entry 20 - 3F
+ 0x00000464, 0x00000480, 0x00000499, 0x000004b1,
+ 0x000004e5, 0x0000050c, 0x0000050f, 0x00000511,
+ 0x00000528, 0x00000543, 0x0000054a, 0x0000055e,
+ 0x00000566, 0x00000573, 0x0000057c, 0x0000058c,
+ 0x0000059d, 0x000005a3, 0x000005ae, 0x000005bd,
+ 0x000005d3, 0x000005ed, 0x00000604, 0x00000632,
+ 0x00000659, 0x00000662, 0x0000067a, 0x00000688,
+ 0x00000690, 0x00000698, 0x000006ac, 0x000006cc,
+ // Entry 40 - 5F
+ 0x000006d2, 0x000006e5, 0x00000709, 0x00000721,
+ 0x00000732, 0x0000077c, 0x0000078e, 0x000007ac,
+ 0x00000825, 0x0000083f, 0x00000864, 0x00000875,
+ 0x000008d2, 0x0000090a, 0x00000937, 0x00000963,
+ 0x0000099e, 0x000009b9, 0x000009e5, 0x00000a0c,
+ 0x00000a34, 0x00000a60, 0x00000a82, 0x00000aae,
+ 0x00000ab8, 0x00000ac4, 0x00000ad4, 0x00000ae4,
+ 0x00000af8, 0x00000b0c, 0x00000b2a, 0x00000b35,
+ // Entry 60 - 7F
+ 0x00000b56, 0x00000b82, 0x00000ba1, 0x00000bc5,
+ 0x00000bd7, 0x00000be1, 0x00000bfa, 0x00000c0d,
+ 0x00000c14, 0x00000c25, 0x00000c32, 0x00000c65,
+ 0x00000c72, 0x00000c7a, 0x00000c8b, 0x00000c9a,
+ 0x00000cb2, 0x00000cdc, 0x00000d11, 0x00000d27,
+ 0x00000d5b, 0x00000d8b, 0x00000da9, 0x00000de8,
+ 0x00000df1, 0x00000e05, 0x00000e3e, 0x00000e60,
+ 0x00000ea2, 0x00000eb9, 0x00000ec9, 0x00000edb,
+ // Entry 80 - 9F
+ 0x00000ef3, 0x00000f25, 0x00000f2e, 0x00000f38,
+ 0x00000f4a, 0x00000f68, 0x00000f7e, 0x00000fa0,
+ 0x00000fbb, 0x00000fcd, 0x00000fdb, 0x00000fec,
+ 0x00001005, 0x0000102b, 0x000010a0, 0x000010a8,
+ 0x000010b2, 0x000010cd, 0x000010df, 0x0000110a,
+ 0x0000112d, 0x00001137, 0x0000115e, 0x00001183,
+ 0x000011af, 0x000011d6, 0x00001214, 0x0000124b,
+ 0x00001277, 0x000012a6, 0x000012b8, 0x000012ef,
+ // Entry A0 - BF
+ 0x00001338, 0x00001357, 0x0000138f, 0x000013f3,
+ 0x00001413, 0x00001449, 0x00001478, 0x0000149a,
+ 0x000014cf, 0x000014f3, 0x0000155f, 0x000015b2,
+ 0x000015e0, 0x0000160c, 0x0000162a, 0x0000165b,
+ 0x000016d6, 0x000016fb, 0x0000172a, 0x0000172a,
+ 0x0000172a, 0x0000172a, 0x0000172a, 0x0000172a,
+ 0x0000172a, 0x0000172a, 0x0000172a, 0x0000172a,
+ 0x0000172a, 0x0000172a, 0x0000172a, 0x0000172a,
+ // Entry C0 - DF
+ 0x0000172a, 0x0000172a, 0x0000172a, 0x0000172a,
+ 0x0000172a, 0x0000172a, 0x0000172a, 0x0000172a,
+ 0x0000172a, 0x0000172a, 0x0000172a, 0x0000172a,
+ 0x0000172a, 0x0000172a, 0x0000172a, 0x0000172a,
+ 0x0000172a, 0x0000172a, 0x0000172a, 0x0000172a,
+ 0x0000172a, 0x0000172a, 0x0000172a, 0x0000172a,
+ 0x0000172a, 0x0000172a, 0x0000172a, 0x0000172a,
+ 0x0000172a, 0x0000172a, 0x0000172a, 0x0000172a,
+ // Entry E0 - FF
+ 0x0000172a, 0x0000172a, 0x0000172a, 0x0000172a,
+ 0x0000172a, 0x0000172a, 0x0000172a, 0x0000172a,
+ 0x0000172a, 0x0000172a, 0x0000172a, 0x0000172a,
+ 0x0000172a, 0x0000172a, 0x0000172a, 0x0000172a,
+ 0x0000172a, 0x0000172a, 0x0000172a, 0x0000172a,
+ 0x0000172a, 0x0000172a, 0x0000172a, 0x0000172a,
+ 0x0000172a, 0x0000172a, 0x0000172a, 0x0000172a,
+ 0x0000172a, 0x0000172a, 0x0000172a, 0x0000172a,
+ // Entry 100 - 11F
+ 0x0000172a, 0x0000172a, 0x0000172a, 0x0000172a,
+ 0x0000172a, 0x0000172a, 0x0000172a, 0x0000172a,
+ 0x0000172a, 0x0000172a, 0x0000172a, 0x0000172a,
+ 0x0000172a, 0x0000172a, 0x0000172a, 0x0000172a,
+ 0x0000172a, 0x0000172a, 0x0000172a, 0x0000172a,
+ 0x0000172a, 0x0000172a,
+} // Size: 1136 bytes
+
+const frData string = "" + // Size: 5930 bytes
+ "\x02Erreur\x02(sans argument) : élever et installer service du gestionna" +
+ "ire\x02Utilisation : %[1]s [\x0a%[2]s]\x02Options de la ligne de command" +
+ "e\x02Impossible de détecter si le processus s’exécute sous WOW64 : %[1]v" +
+ "\x02Vous devez utiliser la version native de WireGuard sur cet ordinateu" +
+ "r.\x02Impossible d'ouvrir le jeton du processus actuel : %[1]v\x02Seulem" +
+ "ent les utilisateurs qui sont membres du groupe intégré %[1]s peuvent ut" +
+ "iliser WireGuard.\x02WireGuard est en cours d'exécution, mais l'IU est a" +
+ "ccessible seulement à partir des bureaux du group intégré %[1]s.\x02Main" +
+ "tenant\x02L’horloge système est inversé!\x14\x01\x81\x01\x00\x02\x09\x02" +
+ "%[1]d an\x00\x0a\x02%[1]d ans\x14\x01\x81\x01\x00\x02\x0b\x02%[1]d jour" +
+ "\x00\x0c\x02%[1]d jours\x14\x01\x81\x01\x00\x02\x0c\x02%[1]d heure\x00" +
+ "\x0d\x02%[1]d heures\x14\x01\x81\x01\x00\x02\x0d\x02%[1]d minute\x00\x0e" +
+ "\x02%[1]d minutes\x14\x01\x81\x01\x00\x02\x0e\x02%[1]d seconde\x00\x0f" +
+ "\x02%[1]d secondes\x02Il y a %[1]s\x02%[1]d\u00a0o\x02%.2[1]f\u00a0Kio" +
+ "\x02%.2[1]f\u00a0Mio\x02%.2[1]f\u00a0Gio\x02%.2[1]f\u00a0Tio\x02%[1]s : " +
+ "%[2]q\x02Adresse IP non valide\x02Longueur du préfixe réseau non valide" +
+ "\x02Port manquant au point de terminaison\x02Hôte du point de terminaiso" +
+ "n non valide\x02MTU non valide\x02Port non valide\x02Keepalive non valid" +
+ "e\x02Clé non valide : %[1]v\x02Le numéro doit être compris entre 0 et 2^" +
+ "64-1 : %[1]v\x02Deux virgules consécutives\x02Nom du tunnel non valide" +
+ "\x02[aucune spécification]\x02Toutes les pairs doivent contenir une clé " +
+ "publique\x02Erreur d'obtention de la configuration\x02, \x02 \x02À propo" +
+ "s du WireGuard\x02Image du logo du WireGuard\x02Fermer\x02♥ &Faites un d" +
+ "on!\x02État :\x02&Désactiver\x02&Activer\x02Clé publique :\x02Port d'éco" +
+ "ute :\x02MTU :\x02Adresses :\x02Serveurs DNS :\x02Clé pré-partagée :\x02" +
+ "Adresses IP autorisées :\x02Point de terminaison :\x02Conservation de co" +
+ "nnexion active permanente :\x02Dernier établissement d'une liaison :\x02" +
+ "Éteinte\x02Désactivation en cours\x02État inconnu\x02Journal\x02&Copier" +
+ "\x02Sélectionner &tout\x02&Enregistrer dans le fichier…\x02Temps\x02Mess" +
+ "age du journal\x02Exporter le journal vers le fichier\x02&À propos WireG" +
+ "uard…\x02Erreur du tunnel\x02%[1]s\x0a\x0aConsultez le journal pour plus" +
+ " d’informations, s'il vous plaît.\x02%[1]s (obsolète)\x02Erreur de sorti" +
+ "e du WireGuard\x02Une mise à jour du WireGuard est disponible. Il est fo" +
+ "rtement conseillé de metter votre WireGuard à jour sans délai.\x02Mettre" +
+ " à jour maintenant\x02Erreur : %[1]v. Veuillez réessayer.\x02État: Termi" +
+ "né!\x02L’icône de la barre d’état système du WireGuard n'est pas apparue" +
+ " après 30 secondes.\x02L’adresse IPv6 doit être contenue entre des croch" +
+ "ets\x02Clés doivent être décodées sur 32 octets\x02Une ligne doit appara" +
+ "ître dans une section\x02Il manque le séparateur égal à la clé de confi" +
+ "guration\x02Clé doit avoir une valeur\x02Clé non valide pour la section " +
+ "[Interface]\x02Clé non valide pour la section [Peer]\x02L'interface doit" +
+ " avoir une clé privée\x02Clé non valide pour la section d'interface\x02V" +
+ "ersion du protocole doit être 1\x02Clé non valide pour la section d'homo" +
+ "logue\x02Scripts :\x02Transfert :\x02pré-activation\x02post-activation" +
+ "\x02pré-désactivation\x02post-désactivation\x02désactivé, par préférence" +
+ "\x02activé(e)\x02%[1]s reçu(e), %[2]s envoyé(e)\x02Impossible de détermi" +
+ "ner l'état du tunnel\x02Impossible d'activer le tunnel\x02Impossible de " +
+ "désactiver le tunnel\x02Interface : %[1]s\x02Homologue\x02Créer un nouve" +
+ "au tunnel\x02Modifier le tunnel\x02&Nom :\x02&Clé publique :\x02(inconnu" +
+ "(e))\x02&Bloquer tous le trafic hors tunnel (interrupteur)\x02&Enregistr" +
+ "er\x02Annuler\x02&Configuration :\x02Nom non valide\x02Le nom est obliga" +
+ "toire.\x02Nom de tunnel « %[1]s » est non valide.\x02Impossible de créer" +
+ " une liste des tunnels existants\x02Tunnel existe déjà.\x02Nom « %[1]s »" +
+ " est déjà utilisé pour un tunnel.\x02Impossible de créer une configurati" +
+ "on nouvelle\x02Échec d'écriture du fichier\x02Fichier « %[1]s » existe d" +
+ "éjà.\x0a\x0aVoulez-vous le remplacer ?\x02Activée\x02Activation en cour" +
+ "s\x02Fichiers texte (*.txt)|*.txt|Tous les fichiers (*.*)|*.*\x02Erreur " +
+ "de détection du WireGuard\x02Impossible d’attendre l'affichage du fenêtr" +
+ "e WireGuard : %[1]v\x02WireGuard: Désactivé\x02État : Inconnu\x02Adresse" +
+ "s : Aucune\x02&Gestion des tunnels…\x02&Importer le(s) tunnel(s) à parti" +
+ "r du fichier…\x02Q&uitter\x02& Tunnels\x02WireGuard activé\x02Tunnel %[1" +
+ "]s a été activé.\x02WireGuard désactivé\x02Tunnel %[1]s a été désactivé." +
+ "\x02Erreur du tunnel WireGuard\x02WireGuard : %[1]s\x02État : %[1]s\x02A" +
+ "dresses : %[1]s\x02Mise à jour disponible!\x02WireGuard mise à jour est " +
+ "disponible\x02Une mise à jour du WireGuard est disponible. Il est consei" +
+ "llé de mettre votre WireGuard à jour dès que possible.\x02Tunnels\x02&Mo" +
+ "difier\x02Ajouter un &tunnel vide…\x02Ajouter le tunnel\x02Supprimer le(" +
+ "s) tunnel(s) sélectionné(s)\x02Exporter tous les tunnels vers zip\x02&Ba" +
+ "sculer\x02Exporter tous les tunnels vers &zip…\x02Modifier &le tunnel sé" +
+ "lectionné…\x02&Supprimer le(s) tunnel(s) sélectionné(s)\x02aucun fichier" +
+ " de configuration trouvé\x02Impossible d'importer la configuration sélec" +
+ "tionnée : %[1]v\x02Impossible d'énumérer les tunnels existantes : %[1]v" +
+ "\x02Un tunnel nommé « %[1]s » existe déjà.\x02Impossible d'importer la c" +
+ "onfiguration : %[1]v\x02Tunnels importés\x14\x01\x81\x01\x00\x02\x16\x02" +
+ "%[1]d tunnel importé\x00\x18\x02%[1]d tunnels importés\x14\x02\x80\x01" +
+ "\x02 \x02%[1]d de %[2]d tunnels importé\x00!\x02%[1]d de %[2]d tunnels i" +
+ "mportés\x02Impossible de créer le tunnel\x14\x01\x81\x01\x00\x02\x17\x02" +
+ "Supprimer %[1]d tunnel\x00\x18\x02Supprimer %[1]d tunnels\x14\x01\x81" +
+ "\x01\x00\x02-\x02Voulez-vous vraiment supprimer %[1]d tunnel?\x00.\x02Vo" +
+ "ulez-vous vraiment supprimer %[1]d tunnels?\x02Supprimer le tunnel ‘%[1]" +
+ "s’\x02Voulez-vous vraiment supprimer le tunnel « %[1]s »?\x02%[1]s Vous " +
+ "ne pouvez pas annuler cette action.\x02Impossible de supprimer le tunnel" +
+ "\x02Il a été impossible de supprimer un tunnel : %[1]s\x02Impossible de " +
+ "supprimer les tunnels\x14\x01\x81\x01\x00\x021\x02Il a été impossible de" +
+ " supprimer %[1]d tunnel.\x002\x02Il a été impossible de supprimer %[1]d " +
+ "tunnels.\x02Fichiers de configuration (*.zip, *.conf)|*.zip;*.conf|Tous " +
+ "les fichiers (*.*)|*.*\x02Importer le(s) tunnel(s) à partir du fichier" +
+ "\x02Fichiers de configuration ZIP (*.zip)|*.zip\x02Exporter les tunnels " +
+ "vers zip\x02%[1]s (version non signée, aucune mise à jour)\x02Impossible" +
+ " de quitter le service en raison de : %[1]v. Essayez d'arrêter WireGuard" +
+ " à partir du gestionnair des services.\x02État: En attente de l’utilisat" +
+ "eur\x02État: En attente du programme de mise à jour"
+
+var idIndex = []uint32{ // 278 elements
+ // Entry 0 - 1F
+ 0x00000000, 0x0000000a, 0x00000047, 0x00000062,
+ 0x00000074, 0x000000bf, 0x000000bf, 0x000000f0,
+ 0x00000148, 0x000001a1, 0x000001aa, 0x000001bd,
+ 0x000001dc, 0x000001ee, 0x000001ff, 0x00000212,
+ 0x00000225, 0x00000235, 0x0000023d, 0x00000249,
+ 0x00000255, 0x00000261, 0x0000026d, 0x0000027a,
+ 0x00000290, 0x000002ab, 0x000002cb, 0x000002e5,
+ 0x000002f5, 0x00000306, 0x00000327, 0x0000033d,
+ // Entry 20 - 3F
+ 0x0000036f, 0x00000389, 0x000003a1, 0x000003b2,
+ 0x000003da, 0x000003fe, 0x00000401, 0x00000401,
+ 0x00000413, 0x00000413, 0x00000419, 0x00000426,
+ 0x0000042e, 0x0000042e, 0x0000042e, 0x0000042e,
+ 0x0000042e, 0x0000042e, 0x0000042e, 0x0000042e,
+ 0x0000042e, 0x0000042e, 0x0000042e, 0x0000042e,
+ 0x0000042e, 0x00000437, 0x00000445, 0x0000045c,
+ 0x00000464, 0x0000046a, 0x00000476, 0x00000493,
+ // Entry 40 - 5F
+ 0x00000499, 0x000004a3, 0x000004bb, 0x000004d1,
+ 0x000004dd, 0x00000513, 0x00000526, 0x00000526,
+ 0x00000526, 0x00000526, 0x00000526, 0x00000526,
+ 0x0000055b, 0x00000581, 0x000005a9, 0x000005c6,
+ 0x000005c6, 0x000005e1, 0x0000060b, 0x00000630,
+ 0x00000655, 0x0000067f, 0x00000696, 0x000006bb,
+ 0x000006bb, 0x000006bb, 0x000006bb, 0x000006bb,
+ 0x000006bb, 0x000006bb, 0x000006bb, 0x000006bb,
+ // Entry 60 - 7F
+ 0x000006bb, 0x000006bb, 0x000006bb, 0x000006bb,
+ 0x000006bb, 0x000006bb, 0x000006bb, 0x000006bb,
+ 0x000006bb, 0x000006bb, 0x000006bb, 0x000006bb,
+ 0x000006bb, 0x000006bb, 0x000006bb, 0x000006bb,
+ 0x000006bb, 0x000006bb, 0x000006bb, 0x000006bb,
+ 0x000006bb, 0x000006bb, 0x000006bb, 0x000006bb,
+ 0x000006c1, 0x000006ce, 0x000006fe, 0x00000715,
+ 0x0000074a, 0x00000763, 0x0000077b, 0x0000078a,
+ // Entry 80 - 9F
+ 0x0000079d, 0x000007b8, 0x000007c0, 0x000007c0,
+ 0x000007c0, 0x000007c0, 0x000007c0, 0x000007c0,
+ 0x000007d6, 0x000007e7, 0x000007f5, 0x000007f5,
+ 0x000007f5, 0x000007f5, 0x000007f5, 0x000007f5,
+ 0x000007f5, 0x000007f5, 0x000007f5, 0x000007f5,
+ 0x000007f5, 0x000007f5, 0x00000814, 0x0000082d,
+ 0x00000844, 0x00000844, 0x0000087a, 0x0000087a,
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ // Entry A0 - BF
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ // Entry C0 - DF
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ // Entry E0 - FF
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ // Entry 100 - 11F
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ 0x0000087a, 0x0000087a, 0x0000087a, 0x0000087a,
+ 0x0000087a, 0x0000087a,
+} // Size: 1136 bytes
+
+const idData string = "" + // Size: 2170 bytes
+ "\x02Kesalahan\x02(tidak ada argumen): naikkan akses dan instal servis ma" +
+ "najer\x02Penggunaan: %[1]s [\x0a%[2]s]\x02Opsi Command Line\x02Tidak dap" +
+ "at menentukan apakah proses sedang berjalan di bawah WOW64: %[1]v\x02Tid" +
+ "ak dapat membuka token proses saat ini: %[1]v\x02WireGuard hanya dapat d" +
+ "igunakan oleh pengguna yang merupakan anggota grup Bawaan %[1]s.\x02Wire" +
+ "Guard sedang berjalan, tetapi UI hanya dapat diakses dari desktop grup B" +
+ "awaan %[1]s.\x02Sekarang\x02Jam sistem mundur!\x14\x01\x81\x01\x00\x00" +
+ "\x18\x02%[1]d tahun\x0a%[1]d tahun\x14\x01\x81\x01\x00\x00\x0b\x02%[1]d " +
+ "Hari\x14\x01\x81\x01\x00\x00\x0a\x02%[1]d jam\x14\x01\x81\x01\x00\x00" +
+ "\x0c\x02%[1]d menit\x14\x01\x81\x01\x00\x00\x0c\x02%[1]d detik\x02%[1]s " +
+ "yang lalu\x02%[1]d B\x02%.2[1]f KiB\x02%.2[1]f MiB\x02%.2[1]f GiB\x02%.2" +
+ "[1]f TiB\x02%[1]s: %[2]q\x02Alamat IP tidak valid\x02Network prefix tida" +
+ "k valid\x02Port belum terisi dari endpoint\x02Host endpoint tidak valid" +
+ "\x02MTU tidak valid\x02Port tidak valid\x02Persistent keepalive tidak va" +
+ "lid\x02Kunci tidak sah:%[1]v\x02Nomor harus diantara 0 sampai dengan 2^6" +
+ "4-1:%[1]v\x02Dua koma dalam satu baris\x02Nama Tunnel tidak valid\x02Tid" +
+ "ak Ditetapkan\x02Semua peers harus memiliki kunci publik\x02Eror ketika " +
+ "mendapatkan konfigurasi\x02, \x02Tentang WireGuard\x02Tutup\x02♥ &Donasi" +
+ "!\x02Status:\x02Nonaktif\x02Menonaktifkan\x02Status tidak diketahui\x02C" +
+ "atatan\x02Salin\x02Pilih semua\x02Menyimpan ke dalam berkas…\x02Waktu" +
+ "\x02Pesan log\x02Ekspor log kedalam file\x02&Tentang WireGuard…\x02Tunne" +
+ "l eror\x02%[1]s\x0a\x0aSilakan baca log untuk informasi lebih lanjut." +
+ "\x02%[1]s (kadaluarsa)\x02Ikon sistem WireGuard tidak muncul setelah 30 " +
+ "detik.\x02Dalam Kurung harus berisi alamat IPv6\x02Kunci harus diterjema" +
+ "hkan tepat 32 byte\x02Garis harus muncul perbagian\x02Kunci harus memili" +
+ "ki value\x02Kunci tidak valid pada bagian [Interface]\x02Kunci tidak val" +
+ "id pada bagian [Peer]\x02Interface harus memiliki Private Key\x02Kunci t" +
+ "idak valid pada bagian [Interface]\x02Versi protokol harus 1\x02Kunci ti" +
+ "dak valid pada bagian [Peer]\x02Aktif\x02Mengaktifkan\x02Berkas Txt (*.T" +
+ "xt)|*.Txt|Semua berkas (*.*)|*.*\x02Deteksi eror WireGuard\x02Tidak dapa" +
+ "t menunggu jendela WireGuard muncul: %[1]v\x02WireGuard: Dinonaktifkan" +
+ "\x02Status: Tidak diketahui\x02Alamat: Kosong\x02&Manajer Tunnel…\x02&Im" +
+ "por tunnel dari file…\x02&Keluar\x02Wireguard Tunnel Eror\x02WireGuard: " +
+ "%[1]s\x02Status: %[1]s\x02Ekspor semua tunnel ke &zip…\x02Ubah tunnel &t" +
+ "erpilih…\x02&Hapus tunnel terpilih\x02Tidak dapat mengimpor konfigurasi " +
+ "yang dipilih: %[1]v"
+
+var itIndex = []uint32{ // 278 elements
+ // Entry 0 - 1F
+ 0x00000000, 0x00000007, 0x00000044, 0x0000005d,
+ 0x00000075, 0x000000bd, 0x00000101, 0x0000013a,
+ 0x00000191, 0x00000202, 0x00000206, 0x0000022d,
+ 0x0000024c, 0x0000026f, 0x0000028c, 0x000002af,
+ 0x000002d4, 0x000002dd, 0x000002e6, 0x000002f3,
+ 0x00000300, 0x0000030d, 0x0000031a, 0x00000327,
+ 0x0000033f, 0x00000369, 0x00000386, 0x000003a4,
+ 0x000003b3, 0x000003c4, 0x000003e4, 0x000003fd,
+ // Entry 20 - 3F
+ 0x0000043c, 0x00000454, 0x00000475, 0x00000487,
+ 0x000004b5, 0x000004e5, 0x000004e8, 0x000004ea,
+ 0x00000504, 0x00000523, 0x0000052a, 0x00000542,
+ 0x00000549, 0x00000554, 0x0000055c, 0x0000056d,
+ 0x0000057f, 0x00000584, 0x0000058f, 0x0000059b,
+ 0x000005b1, 0x000005c0, 0x000005ca, 0x000005e0,
+ 0x000005f5, 0x000005fe, 0x0000060d, 0x0000061f,
+ 0x00000623, 0x0000062a, 0x0000063b, 0x0000064d,
+ // Entry 40 - 5F
+ 0x00000653, 0x00000664, 0x00000678, 0x00000696,
+ 0x000006a8, 0x000006db, 0x000006ec, 0x00000714,
+ 0x00000779, 0x00000786, 0x000007a3, 0x000007b4,
+ 0x00000808, 0x00000838, 0x0000086a, 0x00000897,
+ 0x000008da, 0x000008f9, 0x00000926, 0x0000094e,
+ 0x0000097b, 0x000009ad, 0x000009d6, 0x000009fc,
+ 0x00000a04, 0x00000a13, 0x00000a1a, 0x00000a22,
+ 0x00000a2b, 0x00000a35, 0x00000a4f, 0x00000a59,
+ // Entry 60 - 7F
+ 0x00000a77, 0x00000aaa, 0x00000ace, 0x00000af5,
+ 0x00000b08, 0x00000b0d, 0x00000b19, 0x00000b29,
+ 0x00000b30, 0x00000b42, 0x00000b50, 0x00000b80,
+ 0x00000b87, 0x00000b8f, 0x00000ba0, 0x00000bb0,
+ 0x00000bc6, 0x00000bf4, 0x00000c1c, 0x00000c32,
+ 0x00000c67, 0x00000c92, 0x00000cb2, 0x00000ce9,
+ 0x00000cf0, 0x00000cfc, 0x00000d2f, 0x00000d52,
+ 0x00000d97, 0x00000dae, 0x00000dc1, 0x00000dd4,
+ // Entry 80 - 9F
+ 0x00000dea, 0x00000e05, 0x00000e0b, 0x00000e13,
+ 0x00000e26, 0x00000e49, 0x00000e5f, 0x00000e85,
+ 0x00000ea0, 0x00000eb1, 0x00000ebe, 0x00000ecf,
+ 0x00000ef0, 0x00000f17, 0x00000f76, 0x00000f7d,
+ 0x00000f87, 0x00000fa1, 0x00000fb1, 0x00000fcc,
+ 0x00000fea, 0x00000ff3, 0x00001015, 0x00001038,
+ 0x00001056, 0x0000107c, 0x000010b7, 0x000010e7,
+ 0x0000111b, 0x0000114a, 0x0000115b, 0x00001192,
+ // Entry A0 - BF
+ 0x000011da, 0x000011f7, 0x0000122a, 0x0000128b,
+ 0x000012a6, 0x000012db, 0x0000130b, 0x0000132b,
+ 0x0000135d, 0x0000137c, 0x000013e3, 0x0000142e,
+ 0x00001445, 0x0000146e, 0x00001484, 0x000014b7,
+ 0x00001529, 0x00001546, 0x00001575, 0x00001575,
+ 0x00001575, 0x00001575, 0x00001575, 0x00001575,
+ 0x00001575, 0x00001575, 0x00001575, 0x00001575,
+ 0x00001575, 0x00001575, 0x00001575, 0x00001575,
+ // Entry C0 - DF
+ 0x00001575, 0x00001575, 0x00001575, 0x00001575,
+ 0x00001575, 0x00001575, 0x00001575, 0x00001575,
+ 0x00001575, 0x00001575, 0x00001575, 0x00001575,
+ 0x00001575, 0x00001575, 0x00001575, 0x00001575,
+ 0x00001575, 0x00001575, 0x00001575, 0x00001575,
+ 0x00001575, 0x00001575, 0x00001575, 0x00001575,
+ 0x00001575, 0x00001575, 0x00001575, 0x00001575,
+ 0x00001575, 0x00001575, 0x00001575, 0x00001575,
+ // Entry E0 - FF
+ 0x00001575, 0x00001575, 0x00001575, 0x00001575,
+ 0x00001575, 0x00001575, 0x00001575, 0x00001575,
+ 0x00001575, 0x00001575, 0x00001575, 0x00001575,
+ 0x00001575, 0x00001575, 0x00001575, 0x00001575,
+ 0x00001575, 0x00001575, 0x00001575, 0x00001575,
+ 0x00001575, 0x00001575, 0x00001575, 0x00001575,
+ 0x00001575, 0x00001575, 0x00001575, 0x00001575,
+ 0x00001575, 0x00001575, 0x00001575, 0x00001575,
+ // Entry 100 - 11F
+ 0x00001575, 0x00001575, 0x00001575, 0x00001575,
+ 0x00001575, 0x00001575, 0x00001575, 0x00001575,
+ 0x00001575, 0x00001575, 0x00001575, 0x00001575,
+ 0x00001575, 0x00001575, 0x00001575, 0x00001575,
+ 0x00001575, 0x00001575, 0x00001575, 0x00001575,
+ 0x00001575, 0x00001575,
+} // Size: 1136 bytes
+
+const itData string = "" + // Size: 5493 bytes
+ "\x02Errore\x02(nessun argomento): eleva e installa il servizio di gestio" +
+ "ne\x02Utilizzo: %[1]s [\x0a%[2]s]\x02Opzioni riga di comando\x02Impossib" +
+ "ile determinare se il processo è in esecuzione in WOW64: %[1]v\x02Devi u" +
+ "tilizzare la versione nativa di WireGuard su questo computer.\x02Impossi" +
+ "bile aprire il token del processo corrente: %[1]v\x02WireGuard può esser" +
+ "e utilizzato solo dagli utenti membri del gruppo %[1]s di sistema.\x02Wi" +
+ "reGuard è in esecuzione, ma l'interfaccia utente è accessibile solo dai " +
+ "desktop del gruppo %[1]s di sistema.\x02Ora\x02L'orologio di sistema va " +
+ "all'indietro!\x14\x01\x81\x01\x00\x02\x0b\x02%[1]d anno\x00\x0b\x02%[1]d" +
+ " anni\x14\x01\x81\x01\x00\x02\x0d\x02%[1]d giorno\x00\x0d\x02%[1]d giorn" +
+ "i\x14\x01\x81\x01\x00\x02\x0a\x02%[1]d ora\x00\x0a\x02%[1]d ore\x14\x01" +
+ "\x81\x01\x00\x02\x0d\x02%[1]d minuto\x00\x0d\x02%[1]d minuti\x14\x01\x81" +
+ "\x01\x00\x02\x0e\x02%[1]d secondo\x00\x0e\x02%[1]d secondi\x02%[1]s fa" +
+ "\x02%[1]d\u00a0B\x02%.2[1]f\u00a0KiB\x02%.2[1]f\u00a0MiB\x02%.2[1]f" +
+ "\u00a0GiB\x02%.2[1]f\u00a0TiB\x02%[1]s: %[2]q\x02Indirizzo IP non valido" +
+ "\x02Lunghezza del prefisso di rete non valida\x02Manca la porta dall'end" +
+ "point\x02Host dell'endpoint non valido\x02MTU non valido\x02Porta non va" +
+ "lida\x02Keepalive permanente non valido\x02Chiave non valida: %[1]v\x02I" +
+ "l numero deve essere un numero compreso tra 0 e 2^64-1: %[1]v\x02Due vir" +
+ "gole in una riga\x02Il nome del tunnel non è valido\x02[non specificato]" +
+ "\x02Tutti i peer devono avere una chiave pubblica\x02Errore durante il r" +
+ "ecupero della configurazione\x02, \x02 \x02Informazioni su WireGuard\x02" +
+ "Immagine del logo di WireGuard\x02Chiudi\x02♥ Fai una &donazione!\x02Sta" +
+ "to:\x02&Disattiva\x02&Attiva\x02Chiave pubblica:\x02Porta in ascolto:" +
+ "\x02MTU:\x02Indirizzi:\x02Server DNS:\x02Chiave pre-condivisa:\x02IP con" +
+ "sentiti:\x02Endpoint:\x02Keepalive permanente:\x02Ultima negoziazione:" +
+ "\x02Inattivo\x02Disattivazione\x02Stato sconosciuto\x02Log\x02&Copia\x02" +
+ "Selezion&a tutto\x02&Salva su file…\x02Tempo\x02Messaggio di log\x02Espo" +
+ "rta log su file\x02Inform&azioni su WireGuard…\x02Errore del tunnel\x02%" +
+ "[1]s\x0a\x0aConsulta il log per ulteriori Informazioni.\x02%[1]s (obsole" +
+ "to)\x02Errore durante la chiusura di WireGuard\x02Un aggiornamento di Wi" +
+ "reGuard è disponibile. Ti consigliamo vivamente di aggiornare immediatam" +
+ "ente.\x02Aggiorna ora\x02Errore: %[1]v. Prova ancora.\x02Stato: Completo" +
+ "!\x02L'icona della barra delle applicazioni di WireGuard non è apparsa d" +
+ "opo 30 secondi.\x02Le parentesi devono contenere un indirizzo IPv6\x02Le" +
+ " chiavi devono decodificare esattamente 32 byte\x02Una riga deve essere " +
+ "presente in una sezione\x02Manca un separatore di uguaglianza per la chi" +
+ "ave di configurazione\x02La chiave deve avere un valore\x02Chiave non va" +
+ "lida per la sezione [Interface]\x02Chiave non valida per la sezione [Pee" +
+ "r]\x02Un'interfaccia deve avere una chiave privata\x02Chiave non valida " +
+ "per la sezione dell'interfaccia\x02La versione del protocollo deve esser" +
+ "e 1\x02Chiave non valida per la sezione peer\x02Script:\x02Trasferimento" +
+ ":\x02pre-up\x02post-up\x02pre-down\x02post-down\x02disattivato, per crit" +
+ "erio\x02abilitato\x02%[1]s ricevuti, %[2]s inviati\x02Determinazione del" +
+ "lo stato del tunnel non riuscita\x02Attivazione del tunnel non riuscita" +
+ "\x02Disattivazione del tunnel non riuscita\x02Interfaccia: %[1]s\x02Peer" +
+ "\x02Crea tunnel\x02Modifica tunnel\x02&Nome:\x02Chiave &pubblica:\x02(sc" +
+ "onosciuto)\x02&Blocca traffico fuori dal tunnel (kill-switch)\x02&Salva" +
+ "\x02Annulla\x02&Configurazione:\x02Nome non valido\x02Un nome è richiest" +
+ "o.\x02Il nome del tunnel ‘%[1]s’ non è valido.\x02Impossibile elencare i" +
+ " tunnel esistenti\x02Il tunnel esiste già\x02Un altro tunnel con il nome" +
+ " ‘%[1]s’ esiste già.\x02Impossibile creare la nuova configurazione\x02Sc" +
+ "rittura del file non riuscita\x02Il file ‘%[1]s’ esiste già.\x0a\x0aVuoi" +
+ " sovrascriverlo?\x02Attivo\x02Attivazione\x02File di testo (*.txt)|*.txt" +
+ "|Tutti i file (*.*)|*.*\x02Errore di rilevamento di WireGuard\x02Impossi" +
+ "bile attendere la comparsa della finestra di WireGuard: %[1]v\x02WireGua" +
+ "rd: disattivato\x02Stato: sconosciuto\x02Indirizzi: nessuno\x02&Gestisci" +
+ " i tunnel…\x02&Importa tunnel da file…\x02E&sci\x02&Tunnel\x02WireGuard " +
+ "attivato\x02Il tunnel %[1]s è stato attivato.\x02WireGuard disattivato" +
+ "\x02Il tunnel %[1]s è stato disattivato.\x02Errore tunnel di WireGuard" +
+ "\x02WireGuard: %[1]s\x02Stato: %[1]s\x02Indirizzi: %[1]s\x02Un aggiornam" +
+ "ento è disponibile!\x02Aggiornamento di WireGuard disponibile\x02Un aggi" +
+ "ornamento di WireGuard è disponibile. Ti consigliamo di aggiornare il pr" +
+ "ima possibile.\x02Tunnel\x02&Modifica\x02Aggiungi tunn&el vuoto...\x02Ag" +
+ "giungi tunnel\x02Rimuovi tunnel selezionati\x02Esporta tutti i tunnel in" +
+ " zip\x02Commu&ta\x02Esporta tutti i tunnel in &zip...\x02Modifica il tun" +
+ "nel &selezionato…\x02&Rimuovi i tunnel selezionati\x02nessun file di con" +
+ "figurazione trovato\x02Impossibile importare la configurazione seleziona" +
+ "ta: %[1]v\x02Impossibile enumerare i tunnel esistenti: %[1]v\x02Un altro" +
+ " tunnel esiste già con il nome ‘%[1]s‘\x02Impossibile importare la confi" +
+ "gurazione: %[1]v\x02Tunnel importati\x14\x01\x81\x01\x00\x02\x17\x02%[1]" +
+ "d tunnel importato\x00\x17\x02%[1]d tunnel importati\x14\x02\x80\x01\x02" +
+ " \x02%[1]d de %[2]d tunnel importato\x00 \x02%[1]d di %[2]d tunnel impor" +
+ "tati\x02Impossibile creare il tunnel\x14\x01\x81\x01\x00\x02\x15\x02Elim" +
+ "ina %[1]d tunnel\x00\x15\x02Elimina %[1]d tunnel\x14\x01\x81\x01\x00\x02" +
+ ",\x02Sei sicuro di voler eliminare %[1]d tunnel?\x00,\x02Sei sicuro di v" +
+ "oler eliminare %[1]d tunnel?\x02Elimina tunnel ‘%[1]s‘\x02Sei sicuro di " +
+ "voler eliminare il tunnel ‘%[1]s‘?\x02%[1]s Non è possibile annullare qu" +
+ "esta azione.\x02Impossibile eliminare il tunnel\x02Non è stato possibile" +
+ " rimuovere un tunnel: %[1]s\x02Impossibile eliminare i tunnel\x14\x01" +
+ "\x81\x01\x00\x02/\x02Non è stato possibile eliminare %[1]d tunnel.\x00/" +
+ "\x02Non è stato possibile eliminare %[1]d tunnel.\x02File di configurazi" +
+ "one (*.zip, *.conf)|*.zip;*.conf|Tutti i file (*.*)|*.*\x02Importa tunne" +
+ "l da file\x02File di configurazione ZIP (*.zip)|*.zip\x02Esporta tunnel " +
+ "in zip\x02%[1]s (versione non firmata, nessun aggiornamento)\x02Impossib" +
+ "ile uscire dal servizio a causa di: %[1]v. Potresti voler interrompere W" +
+ "ireGuard dal gestore dei servizi.\x02Stato: in attesa dell'utente\x02Sta" +
+ "to: in attesa del servizio di aggiornamento"
+
+var jaIndex = []uint32{ // 278 elements
+ // Entry 0 - 1F
+ 0x00000000, 0x0000000a, 0x0000005b, 0x00000075,
+ 0x0000009a, 0x000000e6, 0x00000140, 0x0000017e,
+ 0x000001d5, 0x00000258, 0x0000025c, 0x00000284,
+ 0x00000295, 0x000002a6, 0x000002ba, 0x000002cb,
+ 0x000002dc, 0x000002e6, 0x000002ee, 0x000002fb,
+ 0x00000308, 0x00000315, 0x00000322, 0x0000032f,
+ 0x00000349, 0x0000037d, 0x000003ab, 0x000003d3,
+ 0x000003e1, 0x000003fa, 0x00000425, 0x00000439,
+ // Entry 20 - 3F
+ 0x0000048c, 0x000004b0, 0x000004cf, 0x000004de,
+ 0x0000050f, 0x00000543, 0x00000546, 0x00000548,
+ 0x0000055f, 0x00000576, 0x00000580, 0x0000059c,
+ 0x000005a4, 0x000005b2, 0x000005c0, 0x000005cb,
+ 0x000005e2, 0x000005e7, 0x000005f5, 0x00000604,
+ 0x00000615, 0x00000622, 0x00000639, 0x00000659,
+ 0x00000679, 0x00000680, 0x0000068d, 0x0000069d,
+ 0x000006a4, 0x000006b2, 0x000006c6, 0x000006e3,
+ // Entry 40 - 5F
+ 0x000006ea, 0x00000701, 0x0000072c, 0x00000749,
+ 0x0000075f, 0x00000794, 0x000007a9, 0x000007c3,
+ 0x00000828, 0x00000838, 0x0000086d, 0x0000087f,
+ 0x000008d4, 0x00000905, 0x00000937, 0x00000962,
+ 0x00000999, 0x000009ca, 0x000009ff, 0x00000a2f,
+ 0x00000a66, 0x00000a99, 0x00000adc, 0x00000b0a,
+ 0x00000b1b, 0x00000b23, 0x00000b2a, 0x00000b32,
+ 0x00000b3b, 0x00000b45, 0x00000b67, 0x00000b6e,
+ // Entry 60 - 7F
+ 0x00000b96, 0x00000bc7, 0x00000bf5, 0x00000c23,
+ 0x00000c43, 0x00000c4a, 0x00000c66, 0x00000c7c,
+ 0x00000c88, 0x00000c97, 0x00000ca0, 0x00000cf9,
+ 0x00000d04, 0x00000d14, 0x00000d20, 0x00000d30,
+ 0x00000d49, 0x00000d78, 0x00000da6, 0x00000dce,
+ 0x00000e1d, 0x00000e4e, 0x00000e73, 0x00000ec5,
+ 0x00000ecc, 0x00000ed9, 0x00000f23, 0x00000f3d,
+ 0x00000f7f, 0x00000f9a, 0x00000fa9, 0x00000fbe,
+ // Entry 80 - 9F
+ 0x00000fdb, 0x00001013, 0x0000101e, 0x0000102d,
+ 0x00001047, 0x00001079, 0x00001093, 0x000010c5,
+ 0x000010e5, 0x000010f6, 0x00001104, 0x00001118,
+ 0x00001137, 0x00001160, 0x000011c8, 0x000011d5,
+ 0x000011e0, 0x00001203, 0x00001219, 0x0000123b,
+ 0x0000126f, 0x00001280, 0x000012bb, 0x000012e4,
+ 0x0000130a, 0x00001335, 0x00001382, 0x000013c0,
+ 0x0000140c, 0x00001444, 0x00001469, 0x000014a1,
+ // Entry A0 - BF
+ 0x000014e5, 0x0000150a, 0x0000152d, 0x00001569,
+ 0x0000158c, 0x000015c7, 0x000015f8, 0x0000161d,
+ 0x00001652, 0x00001677, 0x000016b2, 0x00001705,
+ 0x00001736, 0x00001760, 0x00001788, 0x000017be,
+ 0x00001842, 0x0000186c, 0x0000189f, 0x0000189f,
+ 0x0000189f, 0x0000189f, 0x0000189f, 0x0000189f,
+ 0x0000189f, 0x0000189f, 0x0000189f, 0x0000189f,
+ 0x0000189f, 0x0000189f, 0x0000189f, 0x0000189f,
+ // Entry C0 - DF
+ 0x0000189f, 0x0000189f, 0x0000189f, 0x0000189f,
+ 0x0000189f, 0x0000189f, 0x0000189f, 0x0000189f,
+ 0x0000189f, 0x0000189f, 0x0000189f, 0x0000189f,
+ 0x0000189f, 0x0000189f, 0x0000189f, 0x0000189f,
+ 0x0000189f, 0x0000189f, 0x0000189f, 0x0000189f,
+ 0x0000189f, 0x0000189f, 0x0000189f, 0x0000189f,
+ 0x0000189f, 0x0000189f, 0x0000189f, 0x0000189f,
+ 0x0000189f, 0x0000189f, 0x0000189f, 0x0000189f,
+ // Entry E0 - FF
+ 0x0000189f, 0x0000189f, 0x0000189f, 0x0000189f,
+ 0x0000189f, 0x0000189f, 0x0000189f, 0x0000189f,
+ 0x0000189f, 0x0000189f, 0x0000189f, 0x0000189f,
+ 0x0000189f, 0x0000189f, 0x0000189f, 0x0000189f,
+ 0x0000189f, 0x0000189f, 0x0000189f, 0x0000189f,
+ 0x0000189f, 0x0000189f, 0x0000189f, 0x0000189f,
+ 0x0000189f, 0x0000189f, 0x0000189f, 0x0000189f,
+ 0x0000189f, 0x0000189f, 0x0000189f, 0x0000189f,
+ // Entry 100 - 11F
+ 0x0000189f, 0x0000189f, 0x0000189f, 0x0000189f,
+ 0x0000189f, 0x0000189f, 0x0000189f, 0x0000189f,
+ 0x0000189f, 0x0000189f, 0x0000189f, 0x0000189f,
+ 0x0000189f, 0x0000189f, 0x0000189f, 0x0000189f,
+ 0x0000189f, 0x0000189f, 0x0000189f, 0x0000189f,
+ 0x0000189f, 0x0000189f,
+} // Size: 1136 bytes
+
+const jaData string = "" + // Size: 6303 bytes
+ "\x02エラー\x02(引数なし): 管理者権限でmanagerサービスをインストールする\x02使い方: %[1]s [\x0a%[2]s]" +
+ "\x02コマンドラインオプション\x02プロセスがWOW64下で動作しているか確認できません: %[1]v\x02このコンピュータではネイティブ" +
+ "版の WireGuard を使ってください。\x02現在のプロセスのトークンを開けません: %[1]v\x02WireGuard は組み込み" +
+ "の %[1]s グループのメンバーだけが使えます。\x02WireGuard は実行中ですが、UI画面は組み込みの %[1]s グループのデ" +
+ "スクトップからしか開けません。\x02今\x02システム時刻が巻き戻った!\x14\x01\x81\x01\x00\x00\x0a\x02%" +
+ "[1]d 年\x14\x01\x81\x01\x00\x00\x0a\x02%[1]d 日\x14\x01\x81\x01\x00\x00" +
+ "\x0d\x02%[1]d 時間\x14\x01\x81\x01\x00\x00\x0a\x02%[1]d 分\x14\x01\x81\x01" +
+ "\x00\x00\x0a\x02%[1]d 秒\x02%[1]s 前\x02%[1]d B\x02%.2[1]f\u00a0KiB\x02%.2" +
+ "[1]f\u00a0MiB\x02%.2[1]f\u00a0GiB\x02%.2[1]f\u00a0TiB\x02%[1]s: %[2]q" +
+ "\x02無効な IP アドレス\x02無効なネットワークプレフィックス長\x02エンドポイントのポート指定なし\x02無効なエンドポイントホスト" +
+ "\x02無効な MTU\x02無効なポート番号\x02無効な持続的キープアライブ値\x02不正な鍵: %[1]v\x02数値は0から2の64乗-" +
+ "1の範囲内の値でなければなりません: %[1]v\x021行にカンマが2つあります\x02トンネル名が不正です\x02[指定なし]\x02すべて" +
+ "のピアには公開鍵が必須です\x02設定の読込中にエラーが発生しました\x02, \x02 \x02WireGuard について\x02Wir" +
+ "eGuard ロゴ画像\x02閉じる\x02♥ 寄付のお願い!(&D)\x02状態:\x02無効化(&D)\x02有効化(&A)\x02公開鍵:" +
+ "\x02待受ポート番号:\x02MTU:\x02アドレス:\x02DNS サーバ:\x02事前共有鍵:\x02Allowed IPs:\x02エ" +
+ "ンドポイント:\x02持続的キープアライブ:\x02直近のハンドシェイク:\x02無効\x02無効化中\x02不明な状態\x02ログ\x02" +
+ "コピー(&C)\x02すべて選択(&A)\x02ファイルに保存…(&S)\x02時刻\x02ログ メッセージ\x02ログをファイルにエクスポ" +
+ "ート\x02WireGuardについて…(&A)\x02トンネルエラー\x02%[1]s\x0a\x0a詳細はログを参照してください。" +
+ "\x02%[1]s (更新あり)\x02WireGuard 終了エラー\x02WireGuard の更新が利用可能です。速やかに更新することを強" +
+ "く推奨します。\x02今すぐ更新\x02エラー: %[1]v。再度実行してください。\x02状態: 完了!\x02WireGuard システ" +
+ "ムトレイアイコンは30秒後に非表示になります。\x02カッコ内は IPv6 アドレスが入ります\x02鍵は 32 バイトでなければなりません" +
+ "\x02行がセクション内にありません\x02設定項目にイコール(=)セパレータがない\x02キー項目に対応する値がありません\x02無効な [I" +
+ "nterface] セクションのキー項目\x02無効な [Peer] セクションのキー項目\x02インターフェースには秘密鍵が必須です\x02無" +
+ "効な Interface セクションのキー項目\x02プロトコルバージョンは 1 でなければなりません\x02無効な Peer セクションの" +
+ "キー項目\x02スクリプト:\x02転送:\x02pre-up\x02post-up\x02pre-down\x02post-down" +
+ "\x02ポリシーにより無効です\x02有効\x02%[1]s 受信済み、%[2]s 送信済み\x02トンネルの状態取得に失敗しました\x02トン" +
+ "ネルの有効化に失敗しました\x02トンネルの無効化に失敗しました\x02インターフェース: %[1]s\x02ピア\x02トンネルの新規作成" +
+ "\x02トンネルの編集\x02名前(&N):\x02公開鍵(&P):\x02(不明)\x02トンネルを通らないトラフィックのブロック(キルスイッ" +
+ "チ)(&B)\x02保存(&S)\x02キャンセル\x02設定(&C):\x02無効な名前\x02名前は必須です。\x02トンネル名 ‘%[" +
+ "1]s’ は不正です。\x02既存のトンネルを表示できません\x02トンネルはすでに存在します\x02‘%[1]s’ という名前の別のトンネルが" +
+ "すでに存在します。\x02新しい設定を作成できませんでした\x02ファイルの書き込みに失敗\x02ファイル ‘%[1]s’ はすでに存在しま" +
+ "す。\x0a\x0a上書きしますか?\x02有効\x02有効化中\x02テキストファイル (*.txt)|*.txt|すべてのファイル (*" +
+ ".*)|*.*\x02WireGuard 検出エラー\x02WireGuard ウィンドウが表示できませんでした: %[1]v\x02WireG" +
+ "uard: 無効化済み\x02状態: 不明\x02アドレス: なし\x02トンネルの管理…(&M)\x02トンネルをファイルからインポート…(&" +
+ "I)\x02終了(&X)\x02& トンネル\x02WireGuard 有効化済み\x02トンネル %[1]s は有効になりました。\x02Wi" +
+ "reGuard 無効化済み\x02トンネル %[1]s は無効になりました。\x02WireGuard トンネルエラー\x02WireGuard" +
+ ": %[1]s\x02状態: %[1]s\x02アドレス: %[1]s\x02更新が利用できます!\x02WireGuard の更新が利用可能で" +
+ "す\x02WireGuard の更新が利用可能になりました。できるだけ早く更新してください。\x02トンネル\x02編集(&E)\x02空の" +
+ "トンネルを追加…(&E)\x02トンネルの追加\x02選択したトンネルの削除\x02すべてのトンネルをzipにエクスポート\x02切り替え(" +
+ "&T)\x02すべてのトンネルをzipにエクスポート…(&Z)\x02選択したトンネルの編集…(&S)\x02選択したトンネルの削除(&R)" +
+ "\x02設定ファイルが見つかりません\x02選択したファイルからインポートできませんでした: %[1]v\x02既存のトンネルを表示できませんで" +
+ "した: %[1]v\x02‘%[1]s’ という名前の別のトンネルがすでに存在します\x02設定をインポートできませんでした: %[1]v" +
+ "\x02トンネルのインポート結果\x14\x01\x81\x01\x00\x001\x02%[1]d トンネルをインポートしました\x14" +
+ "\x02\x80\x01\x00>\x02%[2]d 中の %[1]d トンネルをインポートしました\x02トンネルを作成できません\x14" +
+ "\x01\x81\x01\x00\x00\x1c\x02%[1]d トンネルを削除\x14\x01\x81\x01\x00\x005\x02本当" +
+ "に %[1]d トンネルを削除しますか?\x02トンネル ‘%[1]s’ を削除\x02本当にトンネル ‘%[1]s’ を削除しますか?" +
+ "\x02%[1]s この操作はもとに戻せません。\x02トンネルを削除できません\x02トンネルを削除できませんでした: %[1]s\x02トン" +
+ "ネルを削除できません\x14\x01\x81\x01\x00\x004\x02%[1]d トンネルを削除できませんでした\x02設定ファイル" +
+ " (*.zip, *.conf)|*.zip;*.conf|すべてのファイル (*.*)|*.*\x02ファイルからトンネルをインポート\x02" +
+ "ZIP形式設定ファイル (*.zip)|*.zip\x02トンネルをZIPにエクスポート\x02%[1]s (未署名のビルド、更新の提供なし)" +
+ "\x02%[1]v のためサービスを終了できませんでした。サービスマネージャから WireGuard を停止できます。\x02状態: ユーザーか" +
+ "らの応答待ち\x02状態: アップデータサービスを待機中"
+
+var koIndex = []uint32{ // 278 elements
+ // Entry 0 - 1F
+ 0x00000000, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ // Entry 20 - 3F
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ // Entry 40 - 5F
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ // Entry 60 - 7F
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ // Entry 80 - 9F
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ // Entry A0 - BF
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ // Entry C0 - DF
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ // Entry E0 - FF
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ // Entry 100 - 11F
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007, 0x00000007, 0x00000007,
+ 0x00000007, 0x00000007,
+} // Size: 1136 bytes
+
+const koData string = "\x02오류"
+
+var pa_INIndex = []uint32{ // 278 elements
+ // Entry 0 - 1F
+ 0x00000000, 0x00000010, 0x00000010, 0x00000030,
+ 0x0000005d, 0x000000ea, 0x0000017f, 0x000001e6,
+ 0x000002b4, 0x0000039c, 0x000003a6, 0x000003e8,
+ 0x00000411, 0x0000043a, 0x00000469, 0x00000498,
+ 0x000004cd, 0x000004e6, 0x000004ef, 0x000004fc,
+ 0x00000509, 0x00000516, 0x00000523, 0x00000530,
+ 0x00000559, 0x00000599, 0x000005e0, 0x000005e0,
+ 0x000005fe, 0x00000625, 0x00000654, 0x00000685,
+ // Entry 20 - 3F
+ 0x000006ed, 0x0000072c, 0x00000765, 0x00000765,
+ 0x00000765, 0x00000765, 0x00000768, 0x0000076b,
+ 0x00000791, 0x00000791, 0x000007a5, 0x000007c2,
+ 0x000007d3, 0x000007f8, 0x00000816, 0x00000837,
+ 0x0000085f, 0x00000864, 0x0000087b, 0x0000088d,
+ 0x0000088d, 0x000008ae, 0x000008cc, 0x000008cc,
+ 0x000008cc, 0x000008e3, 0x00000922, 0x00000948,
+ 0x00000955, 0x00000970, 0x0000098e, 0x000009c5,
+ // Entry 40 - 5F
+ 0x000009d2, 0x000009ef, 0x00000a30, 0x00000a5d,
+ 0x00000a74, 0x00000aa8, 0x00000abc, 0x00000afe,
+ 0x00000bda, 0x00000c04, 0x00000c45, 0x00000c64,
+ 0x00000cf5, 0x00000cf5, 0x00000d6f, 0x00000dba,
+ 0x00000dba, 0x00000dba, 0x00000dba, 0x00000dba,
+ 0x00000dba, 0x00000dba, 0x00000dba, 0x00000dba,
+ 0x00000dd7, 0x00000dee, 0x00000dee, 0x00000dee,
+ 0x00000dee, 0x00000dee, 0x00000dee, 0x00000e05,
+ // Entry 60 - 7F
+ 0x00000e2c, 0x00000e77, 0x00000eb9, 0x00000f02,
+ 0x00000f1f, 0x00000f2c, 0x00000f50, 0x00000f71,
+ 0x00000f80, 0x00000fa5, 0x00000fbd, 0x00001034,
+ 0x0000104b, 0x0000105f, 0x00001077, 0x0000108e,
+ 0x000010b5, 0x000010b5, 0x000010b5, 0x000010f0,
+ 0x0000114e, 0x0000119f, 0x000011dd, 0x00001287,
+ 0x00001297, 0x000012cf, 0x00001333, 0x00001357,
+ 0x00001357, 0x00001395, 0x000013bc, 0x000013ed,
+ // Entry 80 - 9F
+ 0x00001428, 0x00001473, 0x00001484, 0x00001492,
+ 0x000014c8, 0x00001506, 0x00001543, 0x00001588,
+ 0x000015b8, 0x000015d8, 0x000015ef, 0x0000160f,
+ 0x0000163a, 0x0000167d, 0x00001773, 0x00001783,
+ 0x00001794, 0x000017bf, 0x000017d6, 0x00001810,
+ 0x00001871, 0x00001882, 0x000018e6, 0x00001911,
+ 0x0000194f, 0x000019a2, 0x000019a2, 0x000019a2,
+ 0x00001a07, 0x00001a58, 0x00001a8e, 0x00001b03,
+ // Entry A0 - BF
+ 0x00001b03, 0x00001b37, 0x00001b80, 0x00001c53,
+ 0x00001c80, 0x00001ce8, 0x00001d50, 0x00001d84,
+ 0x00001dc6, 0x00001e00, 0x00001e00, 0x00001e79,
+ 0x00001ebd, 0x00001efe, 0x00001efe, 0x00001efe,
+ 0x00001fd2, 0x00002032, 0x0000209c, 0x0000209c,
+ 0x0000209c, 0x0000209c, 0x0000209c, 0x0000209c,
+ 0x0000209c, 0x0000209c, 0x0000209c, 0x0000209c,
+ 0x0000209c, 0x0000209c, 0x0000209c, 0x0000209c,
+ // Entry C0 - DF
+ 0x0000209c, 0x0000209c, 0x0000209c, 0x0000209c,
+ 0x0000209c, 0x0000209c, 0x0000209c, 0x0000209c,
+ 0x0000209c, 0x0000209c, 0x0000209c, 0x0000209c,
+ 0x0000209c, 0x0000209c, 0x0000209c, 0x0000209c,
+ 0x0000209c, 0x0000209c, 0x0000209c, 0x0000209c,
+ 0x0000209c, 0x0000209c, 0x0000209c, 0x0000209c,
+ 0x0000209c, 0x0000209c, 0x0000209c, 0x0000209c,
+ 0x0000209c, 0x0000209c, 0x0000209c, 0x0000209c,
+ // Entry E0 - FF
+ 0x0000209c, 0x0000209c, 0x0000209c, 0x0000209c,
+ 0x0000209c, 0x0000209c, 0x0000209c, 0x0000209c,
+ 0x0000209c, 0x0000209c, 0x0000209c, 0x0000209c,
+ 0x0000209c, 0x0000209c, 0x0000209c, 0x0000209c,
+ 0x0000209c, 0x0000209c, 0x0000209c, 0x0000209c,
+ 0x0000209c, 0x0000209c, 0x0000209c, 0x0000209c,
+ 0x0000209c, 0x0000209c, 0x0000209c, 0x0000209c,
+ 0x0000209c, 0x0000209c, 0x0000209c, 0x0000209c,
+ // Entry 100 - 11F
+ 0x0000209c, 0x0000209c, 0x0000209c, 0x0000209c,
+ 0x0000209c, 0x0000209c, 0x0000209c, 0x0000209c,
+ 0x0000209c, 0x0000209c, 0x0000209c, 0x0000209c,
+ 0x0000209c, 0x0000209c, 0x0000209c, 0x0000209c,
+ 0x0000209c, 0x0000209c, 0x0000209c, 0x0000209c,
+ 0x0000209c, 0x0000209c,
+} // Size: 1136 bytes
+
+const pa_INData string = "" + // Size: 8348 bytes
+ "\x02ਗ਼ਲਤੀ\x02ਵਰਤੋਂ: %[1]s [\x0a%[2]s]\x02ਕਮਾਂਡ ਲਾਈਨ ਚੋਣਾਂ\x02ਪਤਾ ਲਗਾਉਣ ਲ" +
+ "ਈ ਅਸਮਰੱਥ ਹੈ ਕਿ ਪਰੋਸੈਸ WOW64 ਅਧੀਨ ਚੱਲ ਰਿਹਾ ਹੈ: %[1]v\x02ਤੁਹਾਨੂੰ ਇਸ ਕੰਪਿ" +
+ "ਊਟਰ ਉੱਤੇ WireGuard ਦਾ ਮੂਲ ਵਰਜ਼ਨ ਵਰਤਣਾ ਚਾਹੀਦਾ ਹੈ।\x02ਮੌਜੂਦਾ ਪਰੋਸੈਸ ਟੋਕਨ " +
+ "ਖੋਲ੍ਹਣ ਲਈ ਅਸਮਰੱਥ: %[1]v\x02WireGuard ਨੂੰ ਸਿਰਫ਼ ਉਹੀ ਵਰਤੋਂਕਾਰ ਵਰਤ ਸਕਦੇ ਹ" +
+ "ਨ, ਜੋ ਕਿ ਪਹਿਲਾਂ ਮੌਜੂਦ %[1]s ਗਰੁੱਪ ਦੇ ਮੈਂਬਰ ਹਨ।\x02WireGuard ਚੱਲ ਰਿਹਾ ਹ" +
+ "ੈ, ਪਰ UI ਨੂੰ ਸਿਰਫ਼ ਪਹਿਲਾਂ ਮੌਜੂਦ %[1]s ਗਰੁੱਪ ਦੇ ਡੈਸਕਟਾਪ ਰਾਹੀਂ ਹੀ ਵਰਤਿਆ " +
+ "ਜਾ ਸਕਦਾ ਹੈ।\x02ਹੁਣ\x02ਸਿਸਟਮ ਘੜੀ ਪੁ਼ੱਠੀ ਮੋੜੀ ਗਈ!\x14\x01\x81\x01\x00" +
+ "\x02\x10\x02%[1]d ਸਾਲ\x00\x10\x02%[1]d ਸਾਲ\x14\x01\x81\x01\x00\x02\x10" +
+ "\x02%[1]d ਦਿਨ\x00\x10\x02%[1]d ਦਿਨ\x14\x01\x81\x01\x00\x02\x13\x02%[1]d " +
+ "ਘੰਟਾ\x00\x13\x02%[1]d ਘੰਟੇ\x14\x01\x81\x01\x00\x02\x13\x02%[1]d ਮਿੰਟ" +
+ "\x00\x13\x02%[1]d ਮਿੰਟ\x14\x01\x81\x01\x00\x02\x16\x02%[1]d ਸਕਿੰਟ\x00" +
+ "\x16\x02%[1]d ਸਕਿੰਟ\x02%[1]s ਪਹਿਲਾਂ\x02%[1]d\u00a0B\x02%.2[1]f\u00a0KiB" +
+ "\x02%.2[1]f\u00a0MiB\x02%.2[1]f\u00a0GiB\x02%.2[1]f\u00a0TiB\x02%[1]s: %" +
+ "[2]q\x02ਅਵੈਧ IP ਸਿਰਨਾਵਾਂ\x02ਗਲਤ ਨੈੱਟਵਰਕ ਅਗੇਤਰ ਲੰਬਾਈ\x02ਐਂਡਪੁਆਇੰਟ ਤੋਂ ਪੋਰ" +
+ "ਟ ਗੁੰਮ ਹੈ\x02ਗ਼ੈਰ-ਵਾਜਬ MTU\x02ਗ਼ੈਰ-ਵਾਜਬ ਪੋਰਟ\x02ਗ਼ੈਰ-ਵਾਜਬ persistent k" +
+ "eepalive\x02ਗ਼ੈਰ-ਵਾਜਬ ਕੁੰਜੀ: %[1]v\x02ਨੰਬਰ 0 ਅਤੇ 2^64-1 ਦੇ ਵਿਚਾਲੇ ਹੋਣਾ ਚ" +
+ "ਾਹੀਦਾ ਹੈ: %[1]v\x02ਇੱਕ ਕਤਾਰ ਵਿੱਚ ਦੋ ਕੌਮੇ ਹਨ\x02ਟਨਲ ਦਾ ਨਾਂ ਠੀਕ ਨਹੀਂ ਹੈ" +
+ "\x02, \x02, \x02ਵਾਇਰਗਾਰਡ ਬਾਰੇ\x02ਬੰਦ ਕਰੋ\x02♥ ਦਾਨ ਦਿਓ(&D)!\x02ਸਥਿਤੀ:\x02" +
+ "ਨਾ-ਸਰਗਰਮ ਕਰੋ(&D)\x02ਸਰਗਰਮ ਕਰੋ(&A)\x02ਪਬਲਿਕ ਕੁੰਜੀ:\x02ਸੁਣਨ ਵਾਲੀ ਪੋਰਟ:" +
+ "\x02MTU:\x02ਸਿਰਨਾਵੇ:\x02DNS ਸਰਵਰ:\x02ਮਨਜ਼ੂਰ ਕੀਤੇ IP:\x02ਐਂਡ-ਪੁਆਇੰਟ:\x02ਨਾ" +
+ "-ਸਰਗਰਮ\x02ਨਾ-ਸਰਗਰਮ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ\x02ਅਣਪਛਾਤੀ ਸਥਿਤੀ\x02ਲਾਗੂ\x02ਕਾਪੀ ਕਰੋ(" +
+ "&C)\x02ਸਾਰੇ ਚੁਣੋ(&a)\x02ਫ਼ਾਇਲ ਵਿੱਚ ਸੰਭਾਲੋ(&S)…\x02ਸਮਾਂ\x02ਲਾਗ ਸੁਨੇਹਾ\x02" +
+ "ਲਾਗ ਫ਼ਾਇਲ ਵਿੱਚ ਬਰਾਮਦ ਕਰੋ\x02ਵਾਇਰਗਾਰਡ ਬਾਰੇ(&A)…\x02ਟਨਲ ਗਲਤੀ\x02%[1]s" +
+ "\x0a\x0aPlease consult the log for more information.\x02%[1]s (out of da" +
+ "te)\x02WireGuard ਤੋਂ ਬਾਹਰ ਜਾਣ ਲਈ ਗ਼ਲਤੀ\x02WireGuard ਲਈ ਅੱਪਡੇਟ ਮੌਜੂਦ ਹੈ। " +
+ "ਤੁਹਾਨੂੰ ਬਿਨਾਂ ਦੇਰ ਕੀਤਿਆਂ ਅੱਪਡੇਟ ਕਰਨ ਦੀ ਸਲਾਹ ਦਿੱਤੀ ਜਾਂਦੀ ਹੈ।\x02ਹੁਣੇ ਅੱ" +
+ "ਪਡੇਟ ਕਰੋ\x02ਗ਼ਲਤੀ: %[1]v। ਫੇਰ ਕੋਸ਼ਿਸ਼ ਕਰੋ।\x02ਸਥਿਤੀ: ਪੂਰਾ!\x02WireGuard " +
+ "ਸਿਸਟਮ ਟਰੇ ਆਈਕਾਨ 30 ਸਕਿੰਟਾਂ ਬਾਅਦ ਦਿਖਾਈ ਨਹੀਂ ਦਿੱਤਾ ਹੈ।\x02ਕੁੰਜੀਆਂ ਠੀਕ 32" +
+ " ਬਾਈਟ ਲਈ ਡੀਕੋਡ ਹੋਣੀਆਂ ਚਾਹੀਦੀਆਂ ਹਨ\x02ਭਾਗ ਵਿੱਚ ਲਾਈਨ ਹੋਣੀ ਚਾਹੀਦੀ ਹੈ\x02ਸਕ੍" +
+ "ਰਿਪਟਾਂ:\x02ਟਰਾਂਸਫਰ:\x02ਸਮਰੱਥ ਹੈ\x02%[1]s ਮਿਲੇ, %[2]s ਭੇਜੇ\x02ਟਨਲ ਸਥਿਤੀ" +
+ " ਪਤਾ ਲਗਾਉਣ ਲਈ ਅਸਫ਼ਲ\x02ਟਨਲ ਸਰਗਰਮ ਕਰਨ ਲਈ ਅਸਫ਼ਲ ਹੈ\x02ਟਨਲ ਨਾ-ਸਰਗਰਮ ਕਰਨ ਲਈ " +
+ "ਅਸਫ਼ਲ ਹੈ\x02ਇੰਟਰਫੇਸ: %[1]s\x02ਪੀਅਰ\x02ਨਵੀਂ ਟਨਲ ਬਣਾਓ\x02ਟਨਲ ਨੂੰ ਸੋਧੋ" +
+ "\x02ਨਾਂ(&N):\x02ਪਬਲਿਕ ਕੁੰਜੀ(&P):\x02(ਅਣਪਛਾਤਾ)\x02ਬਿਨਾਂ-ਟਨਲ ਵਾਲੇ ਟਰੈਫਿਕ ਉ" +
+ "ੱਤੇ ਪਾਬੰਦੀ ਲਾਓ (&B) (kill-switch)\x02ਸੰਭਾਲੋ(&S)\x02ਰੱਦ ਕਰੋ\x02ਸੰਰਚਨਾ(&" +
+ "C):\x02ਅਯੋਗ ਨਾਂ\x02ਨਾਂ ਚਾਹੀਦਾ ਹੈ।\x02ਟਨਲ ਪਹਿਲਾਂ ਹੀ ਮੌਜੂਦ ਹੈ\x02ਨਾਂ ‘%[1]" +
+ "s’ ਨਾਲ ਟਨਲ ਪਹਿਲਾਂ ਹੀ ਮੌਜੂਦ ਹੈ।\x02ਨਵੀਂ ਸੰਰਚਨਾ ਬਣਾਉਣ ਲਈ ਅਸਮਰੱਥ ਹੈ\x02ਫ਼ਾਇ" +
+ "ਲ ਬਣਾਉਣ ਲਈ ਅਸਫ਼ਲ ਹੈ\x02‘%[1]s’ ਫ਼ਾਇਲ ਪਹਿਲਾਂ ਹੀ ਮੌਜੂਦ ਹੈ।\x0a\x0aਕੀ ਤੁਸ" +
+ "ੀਂ ਇਸ ਉੱਤੇ ਲਿਖਣਾ ਚਾਹੁੰਦੇ ਹੋ?\x02ਸਰਗਰਮ\x02ਸਰਗਰਮ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ\x02ਲਿਖਤ" +
+ " ਫ਼ਾਇਲਾਂ (*.txt)|*.txt|ਸਾਰੀਆਂ ਫ਼ਾਇਲਾਂ (*.*)|*.*\x02WireGuard ਖੋਜ ਗ਼ਲਤੀ" +
+ "\x02ਵਾਇਰਗਾਰਡ: ਨਾ-ਸਰਗਰਮ ਕੀਤਾ\x02ਸਥਿਤੀ: ਅਣਪਛਾਤੀ\x02ਸਿਰਨਾਵੇਂ: ਕੋਈ ਨਹੀਂ\x02ਟ" +
+ "ਨਲਾਂ ਦਾ ਇੰਤਜ਼ਾਮ ਕਰੋ(&M)…\x02ਫ਼ਾਇਲ ਤੋਂ ਟਨਲਾਂ ਦਰਾਮਦ ਕਰੋ(&I)…\x02ਬਾਹਰ(&x)" +
+ "\x02ਟਨਲ(&T)\x02ਵਾਇਰਗਾਰਡ ਸਰਗਰਮ ਕੀਤਾ\x02%[1]s ਟਨਲ ਸਰਗਰਮ ਕੀਤੀ ਗਈ ਹੈ।\x02ਵਾਇ" +
+ "ਰਗਾਰਡ ਨਾ-ਸਰਗਰਮ ਕੀਤਾ\x02%[1]s ਟਨਲ ਨਾ-ਸਰਗਰਮ ਕੀਤੀ ਗਈ ਹੈ।\x02ਵਾਇਰਗਾਰਡ ਟਨਲ " +
+ "ਗਲਤੀ\x02ਵਾਇਰਗਾਰਡ: %[1]s\x02ਸਥਿਤੀ: %[1]s\x02ਸਿਰਨਾਵੇਂ: %[1]s\x02ਅੱਪਡੇਟ ਮ" +
+ "ੌਜੂਦ ਹੈ!\x02ਵਾਇਰਗਾਰਡ ਅੱਪਡੇਟ ਮੌਜੂਦ ਹੈ\x02ਵਾਇਰਗਾਰਡ ਲਈ ਅੱਪਡੇਟ ਹੁਣ ਮੌਜੂਦ ਹ" +
+ "ੈ। ਜਿੰਨਾ ਛੇਤੀ ਹੋ ਸਕੇ ਤੁਹਾਨੂੰ ਅੱਪਡੇਟ ਕਰਨ ਦੀ ਸਲਾਹ ਦਿੱਤੀ ਜਾਂਦੀ ਹੈ।\x02ਟਨਲ" +
+ "ਾਂ\x02ਸੋਧੋ(&E)\x02…ਖਾਲੀ ਟਨਲ ਜੋੜੋ(&e)\x02ਟਨਲ ਜੋੜੋ\x02ਚੁਣੀਆਂ ਟਨਲਾਂ ਨੂੰ ਹ" +
+ "ਟਾਓ\x02ਸਾਰੀਆਂ ਟਨਲਾਂ ਨੂੰ ਜ਼ਿੱਪ ਵਜੋਂ ਬਰਾਮਦ ਕਰੋ\x02ਪਲਟੋ(&T)\x02ਸਾਰੀਆਂ ਟਨਲਾ" +
+ "ਂ ਨੂੰ ਜ਼ਿੱਪ ਵਜੋਂ ਬਰਾਮਦ ਕਰੋ…\x02ਚੁਣੀ ਟਨਲ ਸੋਧੋ(&s)…\x02ਚੁਣੀਆਂ ਟਨਲਾਂ ਨੂੰ ਹ" +
+ "ਟਾਓ(&R)\x02ਕੋਈ ਸੰਰਚਨਾ ਫ਼ਾਇਲਾਂ ਨਹੀਂ ਲੱਭੀਆਂ\x02‘%[1]s’ ਨਾਂ ਨਾਲ ਹੋਰ ਟਨਲ ਪ" +
+ "ਹਿਲਾਂ ਹੀ ਮੌਜੂਦ ਹੈ\x02ਸੰਰਚਨਾ ਇੰਪੋਰਟ ਕਰਨ ਲਈ ਅਸਮਰੱਥ: %[1]v\x02ਇੰਪੋਰਟ ਕੀਤੀ" +
+ "ਆਂ ਟਨਲਾਂ\x14\x01\x81\x01\x00\x020\x02%[1]d ਟਨਲ ਇੰਪੋਰਟ ਕੀਤੀ\x00<\x02%[1" +
+ "]d ਟਨਲਾਂ ਇੰਪੋਰਟ ਕੀਤੀਆਂ\x02ਟਨਲ ਬਣਾਉਣ ਲਈ ਅਸਮਰੱਥ\x14\x01\x81\x01\x00\x02" +
+ "\x1d\x02%[1]d ਟਨਲ ਹਟਾਓ\x00#\x02%[1]d ਟਨਲਾਂ ਹਟਾਓ\x14\x01\x81\x01\x00\x02b" +
+ "\x02ਕੀ ਤੁਸੀਂ %[1]d ਟਨਲ ਨੂੰ ਹਟਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ?\x00h\x02ਕੀ ਤੁਸੀਂ %[1]d ਟਨਲ" +
+ "ਾਂ ਨੂੰ ਹਟਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ?\x02‘%[1]s’ ਟਨਲ ਨੂੰ ਹਟਾਓ\x02ਕੀ ਤੁਸੀਂ ‘%[1]s‘ " +
+ "ਟਨਲ ਨੂੰ ਹਟਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ?\x02%[1]s ਤੁਸੀਂ ਇਹ ਕਾਰਵਾਈ ਵਾਪਸ ਨਹੀਂ ਲੈ ਸਕਦੇ " +
+ "ਹੋ।\x02ਟਨਲ ਹਟਾਉਣ ਲਈ ਅਸਮਰੱਥ\x02ਟਨਲ ਹਟਾਉਣ ਲਈ ਅਸਮਰੱਥ ਹੈ: %[1]s\x02ਟਨਲਾਂ ਹ" +
+ "ਟਾਉਣ ਲਈ ਅਸਮਰੱਥ\x02ਸੰਰਚਨਾ ਫ਼ਾਇਲਾਂ (*.zip, *.conf)|*.zip;*.conf|ਸਾਰੀਆਂ ਫ" +
+ "਼ਾਇਲਾਂ (*.*)|*.*\x02ਫ਼ਾਇਲ ਤੋਂ ਟਨਲਾਂ ਦਰਾਮਦ ਕਰੋ\x02ਸੰਰਚਨਾ ਜ਼ਿੱਪ ਫਾਇਲਾਂ (*" +
+ ".zip)|*.zip\x02ਸੇਵਾ ਤੋਂ ਬਾਹਰ ਜਾਣ ਲਈ ਅਸਮਰੱਥ, ਕਾਰਨ: %[1]v। ਤੁਸੀਂ ਸੇਵਾ ਮੈਨੇ" +
+ "ਜਰ ਤੋਂ WireGuard ਨੂੰ ਰੋਕਣਾ ਚਾਹੋਗੇ।\x02ਹਾਲਤ: ਵਰਤੋਂਕਾਰ ਲਈ ਉਡੀਕ ਕੀਤੀ ਜਾ ਰ" +
+ "ਹੀ ਹੈ\x02ਹਾਲਤ: ਅੱਪਡੇਟਰ ਸੇਵਾ ਦੀ ਉਡੀਕ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ"
+
+var plIndex = []uint32{ // 278 elements
+ // Entry 0 - 1F
+ 0x00000000, 0x00000007, 0x0000004f, 0x00000067,
+ 0x0000007e, 0x000000cc, 0x00000108, 0x0000013f,
+ 0x000001a8, 0x00000228, 0x0000022e, 0x00000251,
+ 0x00000287, 0x000002bf, 0x00000302, 0x00000341,
+ 0x00000384, 0x0000038f, 0x00000398, 0x000003a5,
+ 0x000003b2, 0x000003bf, 0x000003cc, 0x000003d9,
+ 0x000003f1, 0x0000041a, 0x0000043c, 0x00000467,
+ 0x0000047a, 0x0000048e, 0x000004be, 0x000004da,
+ // Entry 20 - 3F
+ 0x00000514, 0x0000052b, 0x0000054c, 0x0000055d,
+ 0x0000058e, 0x000005b5, 0x000005b8, 0x000005bb,
+ 0x000005d2, 0x000005e1, 0x000005e9, 0x000005f7,
+ 0x000005ff, 0x0000060b, 0x00000614, 0x00000625,
+ 0x00000635, 0x0000063a, 0x00000642, 0x0000064f,
+ 0x00000654, 0x00000669, 0x0000067f, 0x00000698,
+ 0x000006bc, 0x000006c7, 0x000006d6, 0x000006e4,
+ 0x000006ed, 0x000006f5, 0x00000707, 0x0000071b,
+ // Entry 40 - 5F
+ 0x00000720, 0x00000736, 0x00000752, 0x0000076d,
+ 0x0000077b, 0x000007bb, 0x000007cf, 0x000007f2,
+ 0x00000844, 0x00000855, 0x00000877, 0x0000088b,
+ 0x000008d6, 0x000008fa, 0x00000935, 0x00000956,
+ 0x00000992, 0x000009ad, 0x000009d9, 0x00000a00,
+ 0x00000a24, 0x00000a4e, 0x00000a6c, 0x00000a91,
+ 0x00000a9a, 0x00000aa4, 0x00000ab7, 0x00000ac6,
+ 0x00000ada, 0x00000aea, 0x00000b0b, 0x00000b16,
+ // Entry 60 - 7F
+ 0x00000b35, 0x00000b5d, 0x00000b7f, 0x00000b9f,
+ 0x00000bb0, 0x00000bb5, 0x00000bc8, 0x00000bd5,
+ 0x00000bdd, 0x00000bef, 0x00000bfa, 0x00000c30,
+ 0x00000c38, 0x00000c3f, 0x00000c4e, 0x00000c63,
+ 0x00000c78, 0x00000ca3, 0x00000ccf, 0x00000ce3,
+ 0x00000d1c, 0x00000d44, 0x00000d63, 0x00000d9c,
+ 0x00000da4, 0x00000db0, 0x00000de7, 0x00000e01,
+ 0x00000e3f, 0x00000e58, 0x00000e69, 0x00000e76,
+ // Entry 80 - 9F
+ 0x00000e8e, 0x00000eb2, 0x00000ebc, 0x00000ec4,
+ 0x00000ed6, 0x00000ef6, 0x00000f0e, 0x00000f31,
+ 0x00000f49, 0x00000f5a, 0x00000f68, 0x00000f76,
+ 0x00000f93, 0x00000fb9, 0x00001010, 0x00001017,
+ 0x0000101f, 0x00001035, 0x00001041, 0x0000105e,
+ 0x00001089, 0x00001095, 0x000010c4, 0x000010dd,
+ 0x000010fb, 0x00001119, 0x0000114f, 0x0000117f,
+ 0x000011b7, 0x000011e4, 0x000011f9, 0x00001271,
+ // Entry A0 - BF
+ 0x00001308, 0x00001324, 0x0000137c, 0x00001434,
+ 0x0000144c, 0x0000147c, 0x000014a1, 0x000014bc,
+ 0x000014e6, 0x00001501, 0x000015ac, 0x000015f6,
+ 0x00001616, 0x0000163b, 0x0000165c, 0x0000168b,
+ 0x00001715, 0x00001734, 0x0000175c, 0x0000175c,
+ 0x0000175c, 0x0000175c, 0x0000175c, 0x0000175c,
+ 0x0000175c, 0x0000175c, 0x0000175c, 0x0000175c,
+ 0x0000175c, 0x0000175c, 0x0000175c, 0x0000175c,
+ // Entry C0 - DF
+ 0x0000175c, 0x0000175c, 0x0000175c, 0x0000175c,
+ 0x0000175c, 0x0000175c, 0x0000175c, 0x0000175c,
+ 0x0000175c, 0x0000175c, 0x0000175c, 0x0000175c,
+ 0x0000175c, 0x0000175c, 0x0000175c, 0x0000175c,
+ 0x0000175c, 0x0000175c, 0x0000175c, 0x0000175c,
+ 0x0000175c, 0x0000175c, 0x0000175c, 0x0000175c,
+ 0x0000175c, 0x0000175c, 0x0000175c, 0x0000175c,
+ 0x0000175c, 0x0000175c, 0x0000175c, 0x0000175c,
+ // Entry E0 - FF
+ 0x0000175c, 0x0000175c, 0x0000175c, 0x0000175c,
+ 0x0000175c, 0x0000175c, 0x0000175c, 0x0000175c,
+ 0x0000175c, 0x0000175c, 0x0000175c, 0x0000175c,
+ 0x0000175c, 0x0000175c, 0x0000175c, 0x0000175c,
+ 0x0000175c, 0x0000175c, 0x0000175c, 0x0000175c,
+ 0x0000175c, 0x0000175c, 0x0000175c, 0x0000175c,
+ 0x0000175c, 0x0000175c, 0x0000175c, 0x0000175c,
+ 0x0000175c, 0x0000175c, 0x0000175c, 0x0000175c,
+ // Entry 100 - 11F
+ 0x0000175c, 0x0000175c, 0x0000175c, 0x0000175c,
+ 0x0000175c, 0x0000175c, 0x0000175c, 0x0000175c,
+ 0x0000175c, 0x0000175c, 0x0000175c, 0x0000175c,
+ 0x0000175c, 0x0000175c, 0x0000175c, 0x0000175c,
+ 0x0000175c, 0x0000175c, 0x0000175c, 0x0000175c,
+ 0x0000175c, 0x0000175c,
+} // Size: 1136 bytes
+
+const plData string = "" + // Size: 5980 bytes
+ "\x02Błąd\x02(brak argumentu): Podnieś uprawnienia i zainstaluj usługę me" +
+ "nedżera\x02Użycie: %[1]s [\x0a%[2]s]\x02Opcje wiersza poleceń\x02Nie moż" +
+ "na określić, czy proces jest uruchomiony w środowisku WOW64: %[1]v\x02Na" +
+ "leży użyć natywnej wersji WireGuard na tym komputerze.\x02Nie można otwo" +
+ "rzyć bieżącego tokenu procesu: %[1]v\x02WireGuard może być używany tylko" +
+ " przez użytkowników, którzy są członkami wbudowanej grupy %[1]s.\x02Wire" +
+ "Guard jest uruchomiony, ale interfejs jest dostępny tylko z poziomu użyt" +
+ "kowników należących do wbudowanej grupy %[1]s.\x02Teraz\x02Zegar systemo" +
+ "wy został cofnięty!\x14\x01\x81\x01\x00\x04\x0b\x02%[1]d lata\x05\x0a" +
+ "\x02%[1]d lat\x02\x0a\x02%[1]d rok\x00\x0a\x02%[1]d lat\x14\x01\x81\x01" +
+ "\x00\x04\x0a\x02%[1]d dni\x05\x0a\x02%[1]d dni\x02\x0d\x02%[1]d dzień" +
+ "\x00\x0a\x02%[1]d dni\x14\x01\x81\x01\x00\x04\x0e\x02%[1]d godziny\x05" +
+ "\x0d\x02%[1]d godzin\x02\x0e\x02%[1]d godzina\x00\x0d\x02%[1]d godzin" +
+ "\x14\x01\x81\x01\x00\x04\x0d\x02%[1]d minuty\x05\x0c\x02%[1]d minut\x02" +
+ "\x0d\x02%[1]d minuta\x00\x0c\x02%[1]d minut\x14\x01\x81\x01\x00\x04\x0e" +
+ "\x02%[1]d sekundy\x05\x0d\x02%[1]d sekund\x02\x0e\x02%[1]d sekunda\x00" +
+ "\x0d\x02%[1]d sekund\x02%[1]s temu\x02%[1]d\u00a0B\x02%.2[1]f\u00a0KiB" +
+ "\x02%.2[1]f\u00a0MiB\x02%.2[1]f\u00a0GiB\x02%.2[1]f\u00a0TiB\x02%[1]s: %" +
+ "[2]q\x02Nieprawidłowy adres IP\x02Nieprawidłowa długość prefiksu sieci" +
+ "\x02Brak portu urządzenia końcowego\x02Nieprawidłowy host (urządzenie ko" +
+ "ńcowe)\x02Nieprawidłowe MTU\x02Nieprawidłowy port\x02Nieprawidłowy para" +
+ "metr utrzymania połączenia\x02Nieprawidłowy klucz: %[1]v\x02Liczba musi " +
+ "zawierać się w przedziale 0 - 2^64-1: %[1]v\x02Dwa przecinki z rzędu\x02" +
+ "Nazwa tunelu jest nieprawidłowa\x02[nie określono]\x02Wszyscy uczestnicy" +
+ " muszą mieć klucze publiczne\x02Błąd podczas pobierania konfiguracji\x02" +
+ ", \x02, \x02Informacje o WireGuard\x02Logo WireGuard\x02Zamknij\x02♥ &Wp" +
+ "łać!\x02Status:\x02&Dezaktywuj\x02&Aktywuj\x02Klucz publiczny:\x02Port " +
+ "nasłuchu:\x02MTU:\x02Adresy:\x02Serwery DNS:\x02PSK:\x02Dozwolone adresy" +
+ " IP:\x02Urządzenie końcowe:\x02Utrzymanie połączenia:\x02Ostatni uścisk " +
+ "dłoni (handshake):\x02Nieaktywny\x02Dezaktywowanie\x02Stan nieznany\x02D" +
+ "ziennik\x02&Kopiuj\x02Wybierz &wszystko\x02&Zapisz do pliku…\x02Czas\x02" +
+ "Wiadomości dziennika\x02Eksportuj dziennik do pliku\x02&Informacje o Wir" +
+ "eGuard…\x02Błąd tunelu\x02%[1]s\x0a\x0aAby uzyskać więcej informacji, za" +
+ "poznaj się z logiem.\x02%[1]s (nieaktualny)\x02Błąd podczas zamykania Wi" +
+ "reGuard\x02Aktualizacja WireGuard jest dostępna. Zaleca się natychmiasto" +
+ "wą aktualizację.\x02Uaktualnij teraz\x02Błąd: %[1]v. Spróbuj ponownie." +
+ "\x02Status: Ukończone!\x02Ikona WireGuard nie pojawiła się po 30 sekunda" +
+ "ch w zasobniku systemowym.\x02Nawiasy muszą zawierać adres IPv6\x02Klucz" +
+ "e muszą zostać zdekodowane do dokładnie 32 bajtów\x02Linia musi występow" +
+ "ać w sekcji\x02Klucz konfiguracyjny nie zawiera separatora równorzędnego" +
+ "\x02Klucz musi mieć wartość\x02Nieprawidłowy klucz dla sekcji [Interface" +
+ "]\x02Nieprawidłowy klucz dla sekcji [Peer]\x02Interfejs musi mieć klucz " +
+ "prywatny\x02Nieprawidłowy klucz dla sekcji interface\x02Wersja protokołu" +
+ " musi być 1\x02Nieprawidłowy klucz dla sekcji peer\x02Skrypty:\x02Transf" +
+ "er:\x02przed włączeniem\x02po włączeniu\x02przed wyłączeniem\x02po wyłąc" +
+ "zeniu\x02wyłączone, według zasad grupy\x02włączyć\x02%[1]s odebrano, %[2" +
+ "]s wysłano\x02Nie udało się określić stanu tunelu\x02Nie udało się aktyw" +
+ "ować tunelu\x02Nie można dezaktywować tunelu\x02Interfejs: %[1]s\x02Peer" +
+ "\x02Utwórz nowy tunel\x02Edytuj tunel\x02&Nazwa:\x02&Klucz publiczny:" +
+ "\x02(nieznany)\x02Zablokuj niezabezpieczony ruch (wyłącznik awaryjny)" +
+ "\x02&Zapisz\x02Anuluj\x02&Konfiguracja:\x02Nieprawidłowa nazwa\x02Nazwa " +
+ "jest wymagana.\x02Nazwa tunelu ‘%[1]s’ jest niepoprawna.\x02Nie można wy" +
+ "listować istniejących tuneli\x02Tunel już istnieje\x02Inny tunel już ist" +
+ "nieje z tą samą nazwą ‘%[1]s’.\x02Nie można utworzyć nowej konfiguracji" +
+ "\x02Zapis pliku się nie powiódł\x02Plik ‘%[1]s’ już istnieje. Czy chcesz" +
+ " go nadpisać?\x02Aktywny\x02Aktywowanie\x02Pliki tekstowe (*.txt)|*.txt|" +
+ "Wszystkie pliki (*.*)|*.*\x02Błąd detekcji WireGuard\x02Nie można poczek" +
+ "ać na pojawienie się okna WireGuard: %[1]v\x02WireGuard: Dezaktywowany" +
+ "\x02Status: Nieznany\x02Adresy: Brak\x02&Zarządzaj tunelami…\x02&Importu" +
+ "j tunel (tunele) z pliku…\x02W&yjście\x02&Tunele\x02WireGuard Aktywny" +
+ "\x02Tunel %[1]s został aktywowany.\x02WireGuard Dezaktywowany\x02Tunel %" +
+ "[1]s został dezaktywowany.\x02Błąd tunelu WireGuard\x02WireGuard: %[1]s" +
+ "\x02Status: %[1]s\x02Adresy: %[1]s\x02Dostępna nowa aktualizacja!\x02Akt" +
+ "ualizacja WireGuard jest dostępna\x02Aktualizacja WireGuard jest już dos" +
+ "tępna. Zaleca się jak najszybszą aktualizację.\x02Tunele\x02&Edytuj\x02D" +
+ "odaj &pusty tunel…\x02Dodaj Tunel\x02Usuń wybrany tunel (tunele)\x02Eksp" +
+ "ortuj wszystkie tunele do archiwum ZIP\x02&Przełącz\x02Eksportuj wszystk" +
+ "ie tunele do archiwum &zip…\x02Edytuj &wybrany tunel…\x02&Usuń wybrany t" +
+ "unel (tunele)\x02brak plików konfiguracyjnych\x02Nie można zaimportować " +
+ "wybranej konfiguracji: %[1]v\x02Nie można wskazać istniejących tuneli: %" +
+ "[1]v\x02Inny tunel już istnieje z tą samą nazwą ‘%[1]s’\x02Nie można zai" +
+ "mportować konfiguracji: %[1]v\x02Zaimportowane tunele\x14\x01\x81\x01" +
+ "\x00\x04\x1b\x02Zaimportowano %[1]d tunele\x05\x1b\x02Zaimportowano %[1]" +
+ "d tuneli\x02\x1a\x02Zaimportowano %[1]d tunel\x00\x1b\x02Zaimportowano %" +
+ "[1]d tuneli\x14\x02\x80\x01\x04#\x02Zaimportowano %[1]d z %[2]d tunele" +
+ "\x05#\x02Zaimportowano %[1]d z %[2]d tuneli\x02\x22\x02Zaimportowano %[1" +
+ "]d z %[2]d tunel\x00#\x02Zaimportowano %[1]d z %[2]d tuneli\x02Nie można" +
+ " utworzyć tunelu\x14\x01\x81\x01\x00\x04\x13\x02Usuń %[1]d tunele\x05" +
+ "\x13\x02Usuń %[1]d tuneli\x02\x12\x02Usuń %[1]d tunel\x00\x13\x02Usuń %[" +
+ "1]d tuneli\x14\x01\x81\x01\x00\x04+\x02Czy na pewno chcesz usunąć %[1]d " +
+ "tunele?\x05+\x02Czy na pewno chcesz usunąć %[1]d tuneli?\x02*\x02Czy na " +
+ "pewno chcesz usunąć %[1]d tunel?\x00+\x02Czy na pewno chcesz usunąć %[1]" +
+ "d tuneli?\x02Usuń tunel ‘%[1]s’\x02Czy na pewno chcesz usunąć tunel ‘%[1" +
+ "]s’?\x02%[1]s Tej akcji nie można cofnąć.\x02Nie można usunąć tunelu\x02" +
+ "Tunel nie mógł zostać usunięty: %[1]s\x02Nie można usunąć tuneli\x14\x01" +
+ "\x81\x01\x00\x04'\x02%[1]d tunele nie mogą być usunięte.\x05'\x02%[1]d t" +
+ "unele nie mogą być usunięte.\x02)\x02%[1]d tunel nie może zostać usunięt" +
+ "y.\x00'\x02%[1]d tunele nie mogą być usunięte.\x02Pliki konfiguracji (*." +
+ "zip, *.conf)|*.zip;*.conf|Wszystkie pliki (*.*)|*.*\x02Importuj tunel (t" +
+ "unele) z pliku\x02Pliki ZIP konfiguracji (*.zip)|*.zip\x02Eksportuj tune" +
+ "le do archiwum ZIP\x02%[1]s (wersja niepodpisana, brak aktualizacji)\x02" +
+ "Nie można wyłączyć usługi ze względu na: %[1]v. Jeśli chcesz wyłączyć Wi" +
+ "reGuard możesz to zrobić z poziomu menedżera usług.\x02Status: Czekam na" +
+ " użytkownika\x02Status: Czekam na usługę aktualizacji"
+
+var roIndex = []uint32{ // 278 elements
+ // Entry 0 - 1F
+ 0x00000000, 0x00000007, 0x0000005d, 0x00000077,
+ 0x00000092, 0x000000d1, 0x0000011a, 0x0000014e,
+ 0x000001b2, 0x0000022e, 0x00000233, 0x0000025a,
+ 0x00000285, 0x000002b2, 0x000002df, 0x00000313,
+ 0x0000034c, 0x00000357, 0x00000360, 0x0000036d,
+ 0x0000037a, 0x00000387, 0x00000394, 0x000003a1,
+ 0x000003b6, 0x000003e3, 0x00000408, 0x0000042b,
+ 0x00000439, 0x00000446, 0x00000469, 0x00000480,
+ // Entry 20 - 3F
+ 0x000004bc, 0x000004de, 0x000004fd, 0x00000514,
+ 0x00000543, 0x00000567, 0x0000056a, 0x0000056d,
+ 0x0000057e, 0x00000597, 0x000005a2, 0x000005b1,
+ 0x000005b8, 0x000005c5, 0x000005cf, 0x000005df,
+ 0x000005f2, 0x000005f7, 0x000005ff, 0x0000060c,
+ 0x00000623, 0x00000633, 0x00000640, 0x0000065c,
+ 0x00000679, 0x00000681, 0x00000692, 0x000006a5,
+ 0x000006ac, 0x000006b5, 0x000006c8, 0x000006e0,
+ // Entry 40 - 5F
+ 0x000006e5, 0x000006f5, 0x00000712, 0x00000727,
+ 0x00000737, 0x0000076f, 0x00000784, 0x000007a5,
+ 0x00000802, 0x00000815, 0x00000838, 0x0000084c,
+ 0x00000896, 0x000008c7, 0x0000090c, 0x00000937,
+ 0x0000097e, 0x000009a4, 0x000009d2, 0x000009fb,
+ 0x00000a2c, 0x00000a5a, 0x00000a82, 0x00000aad,
+ 0x00000ab8, 0x00000ac5, 0x00000ad1, 0x00000ade,
+ 0x00000ae9, 0x00000af5, 0x00000b13, 0x00000b1d,
+ // Entry 60 - 7F
+ 0x00000b38, 0x00000b64, 0x00000b82, 0x00000ba3,
+ 0x00000bb6, 0x00000bbe, 0x00000bcf, 0x00000bdd,
+ 0x00000be4, 0x00000bf5, 0x00000c04, 0x00000c51,
+ 0x00000c5a, 0x00000c62, 0x00000c72, 0x00000c7f,
+ 0x00000c95, 0x00000cc0, 0x00000ce7, 0x00000cfc,
+ 0x00000d2d, 0x00000d52, 0x00000d70, 0x00000db6,
+ 0x00000dbc, 0x00000dca, 0x00000e01, 0x00000e1f,
+ 0x00000e5d, 0x00000e73, 0x00000e87, 0x00000e97,
+ // Entry 80 - 9F
+ 0x00000eaf, 0x00000ed4, 0x00000edd, 0x00000ee7,
+ 0x00000ef9, 0x00000f17, 0x00000f2c, 0x00000f4d,
+ 0x00000f67, 0x00000f78, 0x00000f85, 0x00000f93,
+ 0x00000fb4, 0x00000fde, 0x00001054, 0x0000105d,
+ 0x00001066, 0x0000107e, 0x0000108e, 0x000010af,
+ 0x000010d1, 0x000010db, 0x00001101, 0x0000111c,
+ 0x0000113e, 0x00001169, 0x000011a2, 0x000011d7,
+ 0x00001207, 0x00001233, 0x00001246, 0x00001299,
+ // Entry A0 - BF
+ 0x00001309, 0x00001323, 0x00001379, 0x00001423,
+ 0x0000143f, 0x00001479, 0x000014a7, 0x000014c2,
+ 0x000014e9, 0x00001506, 0x0000158a, 0x000015da,
+ 0x000015fb, 0x00001625, 0x00001640, 0x00001671,
+ 0x000016d2, 0x000016f4, 0x00001722, 0x00001722,
+ 0x00001722, 0x00001722, 0x00001722, 0x00001722,
+ 0x00001722, 0x00001722, 0x00001722, 0x00001722,
+ 0x00001722, 0x00001722, 0x00001722, 0x00001722,
+ // Entry C0 - DF
+ 0x00001722, 0x00001722, 0x00001722, 0x00001722,
+ 0x00001722, 0x00001722, 0x00001722, 0x00001722,
+ 0x00001722, 0x00001722, 0x00001722, 0x00001722,
+ 0x00001722, 0x00001722, 0x00001722, 0x00001722,
+ 0x00001722, 0x00001722, 0x00001722, 0x00001722,
+ 0x00001722, 0x00001722, 0x00001722, 0x00001722,
+ 0x00001722, 0x00001722, 0x00001722, 0x00001722,
+ 0x00001722, 0x00001722, 0x00001722, 0x00001722,
+ // Entry E0 - FF
+ 0x00001722, 0x00001722, 0x00001722, 0x00001722,
+ 0x00001722, 0x00001722, 0x00001722, 0x00001722,
+ 0x00001722, 0x00001722, 0x00001722, 0x00001722,
+ 0x00001722, 0x00001722, 0x00001722, 0x00001722,
+ 0x00001722, 0x00001722, 0x00001722, 0x00001722,
+ 0x00001722, 0x00001722, 0x00001722, 0x00001722,
+ 0x00001722, 0x00001722, 0x00001722, 0x00001722,
+ 0x00001722, 0x00001722, 0x00001722, 0x00001722,
+ // Entry 100 - 11F
+ 0x00001722, 0x00001722, 0x00001722, 0x00001722,
+ 0x00001722, 0x00001722, 0x00001722, 0x00001722,
+ 0x00001722, 0x00001722, 0x00001722, 0x00001722,
+ 0x00001722, 0x00001722, 0x00001722, 0x00001722,
+ 0x00001722, 0x00001722, 0x00001722, 0x00001722,
+ 0x00001722, 0x00001722,
+} // Size: 1136 bytes
+
+const roData string = "" + // Size: 5922 bytes
+ "\x02Eroare\x02(fără argument): obținere drept administrativ și instalare" +
+ " serviciu de gestionare\x02Utilizare: %[1]s [\x0a%[2]s]\x02Opțiuni linie" +
+ " de comandă\x02Nu se poate determina dacă procesul rulează sub WOW64: %[" +
+ "1]v\x02Trebuie să utilizezi versiunea nativă a WireGuard pe acest calcul" +
+ "ator.\x02Nu poate fi deschis tokenul actual de proces: %[1]v\x02WireGuar" +
+ "d poate fi utilizat doar de către utilizatorii care sunt membri ai grupu" +
+ "lui Builtin %[1]s.\x02WireGuard rulează, dar interfața cu utilizatorul e" +
+ "ste accesibilă doar din spațiile de lucru ale grupului Builtin %[1]s." +
+ "\x02Acum\x02Ceasul de sistem a fost dat în spate!\x14\x01\x81\x01\x00" +
+ "\x04\x0a\x02%[1]d ani\x02\x09\x02%[1]d an\x00\x0d\x02%[1]d de ani\x14" +
+ "\x01\x81\x01\x00\x04\x0b\x02%[1]d zile\x02\x09\x02%[1]d zi\x00\x0e\x02%[" +
+ "1]d de zile\x14\x01\x81\x01\x00\x04\x0a\x02%[1]d ore\x02\x0b\x02%[1]d or" +
+ "ă\x00\x0d\x02%[1]d de ore\x14\x01\x81\x01\x00\x04\x0d\x02%[1]d minute" +
+ "\x02\x0c\x02%[1]d minut\x00\x10\x02%[1]d de minute\x14\x01\x81\x01\x00" +
+ "\x04\x0e\x02%[1]d secunde\x02\x0f\x02%[1]d secundă\x00\x11\x02%[1]d de s" +
+ "ecunde\x02acum %[1]s\x02%[1]d\u00a0B\x02%.2[1]f\u00a0KiB\x02%.2[1]f" +
+ "\u00a0MiB\x02%.2[1]f\u00a0GiB\x02%.2[1]f\u00a0TiB\x02%[1]s: %[2]q\x02Adr" +
+ "esă IP invalidă\x02Lungimea prefixului de rețea este invalidă\x02Lipseșt" +
+ "e portul de la punctul final\x02Gazdă invalidă a punctului final\x02MTU " +
+ "invalidă\x02Port invalid\x02Mesaj keepalive persistent invalid\x02Cheie " +
+ "invalidă: %[1]v\x02Numărul trebuie să fie cuprins între 0 și 2^64-1: %[1" +
+ "]v\x02Două virgule una după cealaltă\x02Numele tunelului nu este valid" +
+ "\x02[niciuna specificată]\x02Toate perechile trebuie să aibă chei public" +
+ "e\x02Eroare la obținerea configurației\x02, \x02, \x02Despre WireGuard" +
+ "\x02Imagine siglă WireGuard\x02Închidere\x02♥ &Donează!\x02Stare:\x02&De" +
+ "zactivare\x02&Activare\x02Cheie publică:\x02Port de ascultare:\x02MTU:" +
+ "\x02Adrese:\x02Servere DNS:\x02Cheie predistribuită:\x02IP-uri permise:" +
+ "\x02Punct final:\x02Mesaj keepalive persistent:\x02Ultimul acord de inte" +
+ "rogare:\x02Inactiv\x02Se dezactivează\x02Stare necunoscută\x02Jurnal\x02" +
+ "&Copiere\x02Selectare &totală\x02&Salvare în fișier…\x02Timp\x02Mesaj de" +
+ " jurnal\x02Exportare jurnal în fișier\x02&Despre WireGuard…\x02Eroare de" +
+ " tunel\x02%[1]s\x0a\x0aConsultă jurnalul pentru mai multe informații." +
+ "\x02%[1]s (neactualizat)\x02Eroare la ieșirea din WireGuard\x02Este disp" +
+ "onibilă o actualizare pentru WireGuard. Se recomandă ferm actualizarea i" +
+ "mediată.\x02Actualizează acum\x02Eroare: %[1]v. Încearcă din nou.\x02Sta" +
+ "re: finalizată!\x02Pictograma WireGuard din bara de sistem nu a apărut d" +
+ "upă 30 de secunde.\x02Parantezele trebuie să conțină o adresă IPv6\x02Re" +
+ "zultatul decodificat de chei trebuie să aibă exact 32 de octeți\x02Linia" +
+ " trebuie să apară într-o secțiune\x02Cheii de configurare îi lipsește un" +
+ " separator de forma semnului egal\x02Cheia trebuie să conțină o valoare" +
+ "\x02Cheie invalidă pentru secțiunea [Interface]\x02Cheie invalidă pentru" +
+ " secțiunea [Peer]\x02O interfață trebuie să aibă o cheie privată\x02Chei" +
+ "e invalidă pentru secțiunea interfeței\x02Versiunea de protocol trebuie " +
+ "să fie 1\x02Cheie invalidă pentru secțiunea perechii\x02Scripturi:\x02Tr" +
+ "ansferare:\x02pre-pornire\x02post-pornire\x02pre-oprire\x02post-oprire" +
+ "\x02dezactivat, conform politicii\x02activată\x02%[1]s primit, %[2]s tri" +
+ "mis\x02Nu a putut fi determinată starea tunelului\x02Tunelul nu a putut " +
+ "fi activat\x02Tunelul nu a putut fi dezactivat\x02Interfață: %[1]s\x02Pe" +
+ "reche\x02Creare tunel nou\x02Editare tunel\x02&Nume:\x02Cheie &publică:" +
+ "\x02(necunoscută)\x02&Blochează traficul care nu trece prin tunel (între" +
+ "rupător de activitate)\x02&Salvare\x02Anulare\x02&Configurație:\x02Nume " +
+ "invalid\x02Este necesar un nume.\x02Numele tunelului „%[1]s” este invali" +
+ "d.\x02Tunelurile existente nu pot fi listate\x02Tunelul există deja\x02E" +
+ "xistă deja un alt tunel cu numele „%[1]s”.\x02Nu se poate crea configura" +
+ "ția nouă\x02Scrierea fișierului a eșuat\x02Fișierul „%[1]s” există deja" +
+ ".\x0a\x0aDorești suprascrierea acestuia?\x02Activ\x02Se activează\x02Fiș" +
+ "iere text (*.txt)|*.txt|Toate fișierele (*.*)|*.*\x02Eroare de detectare" +
+ " WireGuard\x02Nu se poate aștepta ca fereastra WireGuard să apară: %[1]v" +
+ "\x02WireGuard: dezactivat\x02Stare: necunoscută\x02Adrese: niciuna\x02&G" +
+ "estionare tuneluri…\x02&Importare tunel(uri) din fișier…\x02Ie&șire\x02&" +
+ "Tuneluri\x02WireGuard activat\x02Tunelul %[1]s a fost activat.\x02WireGu" +
+ "ard dezactivat\x02Tunelul %[1]s a fost dezactivat.\x02Eroare de tunel Wi" +
+ "reGuard\x02WireGuard: %[1]s\x02Stare: %[1]s\x02Adrese: %[1]s\x02Este dis" +
+ "ponibilă o actualizare!\x02Actualizare disponibilă pentru WireGuard\x02O" +
+ " actualizare pentru WireGuard este acum disponibilă. Se recomandă efectu" +
+ "area actualizării cât mai rapid posibil.\x02Tuneluri\x02&Editare\x02Adău" +
+ "gare tunel &gol…\x02Adăugare tunel\x02Eliminare tunel(uri) selectat(e)" +
+ "\x02Exportă toate tunelurile în zip\x02&Comutare\x02Exportă toate tunelu" +
+ "rile în &zip…\x02Editare tunel &selectat…\x02&Eliminare tunel(uri) selec" +
+ "tat(e)\x02nu au fost găsite fișiere de configurare\x02Configurația selec" +
+ "tată nu a putut fi importată: %[1]v\x02Tunelurile existente nu au putut " +
+ "fi enumerate: %[1]v\x02Există deja un alt tunel cu numele „%[1]s”\x02Con" +
+ "figurația nu poate fi importată: %[1]v\x02Tuneluri importate\x14\x01\x81" +
+ "\x01\x00\x04\x18\x02Importat %[1]d tuneluri\x02\x15\x02Importat %[1]d tu" +
+ "nel\x00\x1b\x02Importat %[1]d de tuneluri\x14\x02\x80\x01\x04\x22\x02Imp" +
+ "ortat %[1]d din %[2]d tuneluri\x02\x1f\x02Importat %[1]d din %[2]d tunel" +
+ "\x00%\x02Importat %[1]d din %[2]d de tuneluri\x02Tunelul nu poate fi cre" +
+ "at\x14\x01\x81\x01\x00\x04\x19\x02Ștergere %[1]d tuneluri\x02\x16\x02Ște" +
+ "rgere %[1]d tunel\x00\x1c\x02Ștergere %[1]d de tuneluri\x14\x01\x81\x01" +
+ "\x00\x045\x02Ești sigur că dorești să ștergi %[1]d tuneluri?\x022\x02Eșt" +
+ "i sigur că dorești să ștergi %[1]d tunel?\x008\x02Ești sigur că dorești " +
+ "să ștergi %[1]d de tuneluri?\x02Ștergere tunel „%[1]s”\x02Ești sigur că " +
+ "dorești să ștergi tunelul „%[1]s”?\x02%[1]s Această acțiune nu poate fi " +
+ "anulată.\x02Tunelul nu poate fi șters\x02Un tunel nu a putut fi eliminat" +
+ ": %[1]s\x02Nu se pot șterge tunelurile\x14\x01\x81\x01\x00\x04)\x02%[1]d" +
+ " tuneluri nu au putut fi eliminate.\x02$\x02%[1]d tunel nu a putut fi el" +
+ "iminat.\x00,\x02%[1]d de tuneluri nu au putut fi eliminate.\x02Fișiere d" +
+ "e configurare (*.zip, *.conf)|*.zip;*.conf|Toate fișierele (*.*)|*.*\x02" +
+ "Importare tunel(uri) din fișier\x02Fișiere ZIP de configurare (*.zip)|*." +
+ "zip\x02Exportare tuneluri în zip\x02%[1]s (versiune nesemnată, fără actu" +
+ "alizări)\x02Nu se poate ieși din serviciu din cauza: %[1]v. Poți opri Wi" +
+ "reGuard din managerul de servicii.\x02Stare: se așteaptă utilizatorul" +
+ "\x02Stare: se așteaptă serviciul de actualizare"
+
+var ruIndex = []uint32{ // 278 elements
+ // Entry 0 - 1F
+ 0x00000000, 0x0000000d, 0x000000a9, 0x000000d4,
+ 0x00000107, 0x00000166, 0x000001c9, 0x00000220,
+ 0x000002a7, 0x0000034b, 0x00000358, 0x00000395,
+ 0x000003c2, 0x000003ef, 0x0000041c, 0x00000459,
+ 0x00000496, 0x000004a7, 0x000004b0, 0x000004bd,
+ 0x000004ca, 0x000004d7, 0x000004e4, 0x000004f1,
+ 0x00000518, 0x00000556, 0x00000595, 0x000005c3,
+ 0x000005e0, 0x00000602, 0x00000658, 0x00000681,
+ // Entry 20 - 3F
+ 0x000006c0, 0x000006e3, 0x00000722, 0x00000738,
+ 0x0000077e, 0x000007be, 0x000007c1, 0x000007c4,
+ 0x000007d1, 0x000007ea, 0x000007f9, 0x00000818,
+ 0x00000826, 0x0000083a, 0x00000850, 0x0000086d,
+ 0x00000877, 0x0000087c, 0x0000088d, 0x000008a1,
+ 0x000008b6, 0x000008de, 0x000008fc, 0x0000092d,
+ 0x00000958, 0x00000969, 0x0000097e, 0x000009a8,
+ 0x000009b5, 0x000009cd, 0x000009e4, 0x00000a07,
+ // Entry 40 - 5F
+ 0x00000a12, 0x00000a34, 0x00000a5e, 0x00000a6f,
+ 0x00000a8b, 0x00000b06, 0x00000b1d, 0x00000b46,
+ 0x00000bce, 0x00000bec, 0x00000c25, 0x00000c47,
+ 0x00000caf, 0x00000ce7, 0x00000d3c, 0x00000d6f,
+ 0x00000dc4, 0x00000df6, 0x00000e30, 0x00000e65,
+ 0x00000eaf, 0x00000ef2, 0x00000f2a, 0x00000f71,
+ 0x00000f81, 0x00000f93, 0x00000fb7, 0x00000fd9,
+ 0x00000ffb, 0x0000101b, 0x00001045, 0x00001056,
+ // Entry 60 - 7F
+ 0x00001089, 0x000010d4, 0x0000110c, 0x00001142,
+ 0x0000115c, 0x00001163, 0x00001181, 0x000011ab,
+ 0x000011be, 0x000011dc, 0x000011f3, 0x0000123b,
+ 0x0000124f, 0x0000125c, 0x00001277, 0x000012a1,
+ 0x000012c8, 0x0000130c, 0x00001344, 0x0000136f,
+ 0x000013b7, 0x000013fe, 0x00001423, 0x00001485,
+ 0x00001498, 0x000014af, 0x000014f7, 0x00001525,
+ 0x00001579, 0x0000159f, 0x000015c2, 0x000015d7,
+ // Entry 80 - 9F
+ 0x00001603, 0x00001635, 0x00001641, 0x00001651,
+ 0x0000166a, 0x00001693, 0x000016ae, 0x000016d5,
+ 0x000016fb, 0x0000170c, 0x00001720, 0x00001734,
+ 0x0000175b, 0x0000178b, 0x00001816, 0x00001825,
+ 0x00001841, 0x00001872, 0x00001892, 0x000018c3,
+ 0x000018fe, 0x00001916, 0x00001955, 0x00001996,
+ 0x000019c8, 0x00001a09, 0x00001a59, 0x00001ab3,
+ 0x00001afa, 0x00001b4a, 0x00001b78, 0x00001c4c,
+ // Entry A0 - BF
+ 0x00001d48, 0x00001d7a, 0x00001e1a, 0x00001f62,
+ 0x00001f8c, 0x00001fdc, 0x00002027, 0x00002059,
+ 0x00002093, 0x000020c5, 0x000021bc, 0x00002219,
+ 0x00002247, 0x0000227f, 0x000022b1, 0x000022fe,
+ 0x00002398, 0x000023d0, 0x00002404, 0x00002404,
+ 0x00002404, 0x00002404, 0x00002404, 0x00002404,
+ 0x00002404, 0x00002404, 0x00002404, 0x00002404,
+ 0x00002404, 0x00002404, 0x00002404, 0x00002404,
+ // Entry C0 - DF
+ 0x00002404, 0x00002404, 0x00002404, 0x00002404,
+ 0x00002404, 0x00002404, 0x00002404, 0x00002404,
+ 0x00002404, 0x00002404, 0x00002404, 0x00002404,
+ 0x00002404, 0x00002404, 0x00002404, 0x00002404,
+ 0x00002404, 0x00002404, 0x00002404, 0x00002404,
+ 0x00002404, 0x00002404, 0x00002404, 0x00002404,
+ 0x00002404, 0x00002404, 0x00002404, 0x00002404,
+ 0x00002404, 0x00002404, 0x00002404, 0x00002404,
+ // Entry E0 - FF
+ 0x00002404, 0x00002404, 0x00002404, 0x00002404,
+ 0x00002404, 0x00002404, 0x00002404, 0x00002404,
+ 0x00002404, 0x00002404, 0x00002404, 0x00002404,
+ 0x00002404, 0x00002404, 0x00002404, 0x00002404,
+ 0x00002404, 0x00002404, 0x00002404, 0x00002404,
+ 0x00002404, 0x00002404, 0x00002404, 0x00002404,
+ 0x00002404, 0x00002404, 0x00002404, 0x00002404,
+ 0x00002404, 0x00002404, 0x00002404, 0x00002404,
+ // Entry 100 - 11F
+ 0x00002404, 0x00002404, 0x00002404, 0x00002404,
+ 0x00002404, 0x00002404, 0x00002404, 0x00002404,
+ 0x00002404, 0x00002404, 0x00002404, 0x00002404,
+ 0x00002404, 0x00002404, 0x00002404, 0x00002404,
+ 0x00002404, 0x00002404, 0x00002404, 0x00002404,
+ 0x00002404, 0x00002404,
+} // Size: 1136 bytes
+
+const ruData string = "" + // Size: 9220 bytes
+ "\x02Ошибка\x02(нет аргумента): получить права администратора и установит" +
+ "ь административную службу\x02Использование: %[1]s [\x0a%[2]s]\x02Параме" +
+ "тры командной строки\x02Ошибка определения или процесс работает как WOW" +
+ "64: %[1]v\x02Используйте нативную версию WireGuard на этом компьютере." +
+ "\x02Не удается открыть токен текущего процесса: %[1]v\x02WireGuard может" +
+ " использоваться только пользователями, входящими в группу %[1]s.\x02Wire" +
+ "Guard запущен, но пользовательский интерфейс доступен только с рабочих с" +
+ "толов группы %[1]s.\x02Сейчас\x02Системные часы переведены назад!\x14" +
+ "\x01\x81\x01\x00\x04\x08\x02%[1]dг\x05\x08\x02%[1]dг\x02\x08\x02%[1]dг" +
+ "\x00\x08\x02%[1]dг\x14\x01\x81\x01\x00\x04\x08\x02%[1]dд\x05\x08\x02%[1]" +
+ "dд\x02\x08\x02%[1]dд\x00\x08\x02%[1]dд\x14\x01\x81\x01\x00\x04\x08\x02%[" +
+ "1]dч\x05\x08\x02%[1]dч\x02\x08\x02%[1]dч\x00\x08\x02%[1]dч\x14\x01\x81" +
+ "\x01\x00\x04\x0c\x02%[1]dмин\x05\x0c\x02%[1]dмин\x02\x0c\x02%[1]dмин\x00" +
+ "\x0c\x02%[1]dмин\x14\x01\x81\x01\x00\x04\x0c\x02%[1]dсек\x05\x0c\x02%[1]" +
+ "dсек\x02\x0c\x02%[1]dсек\x00\x0c\x02%[1]dсек\x02%[1]s назад\x02%[1]d Б" +
+ "\x02%.2[1]f Кб\x02%.2[1]f Мб\x02%.2[1]f Гб\x02%.2[1]f Тб\x02%[1]s: %[2]q" +
+ "\x02Недопустимый IP-адрес\x02Недопустимая длина префикса сети\x02Отсутст" +
+ "вует порт IP-адреса сервера\x02Неверный IP-адрес сервера\x02Недопустимы" +
+ "й MTU\x02Недопустимый порт\x02Недопустимое значение поддержания соедине" +
+ "ния\x02Недопустимый ключ: %[1]v\x02Число должно быть между 0 и 2^64-1: " +
+ "%[1]v\x02Две запятые подряд\x02Название туннеля недействительно\x02[не у" +
+ "казано]\x02Все пиры должны иметь публичные ключи\x02Ошибка при получени" +
+ "и конфигурации\x02, \x02, \x02О WireGuard\x02Логотип WireGuard\x02Закры" +
+ "ть\x02♥ &Пожертвовать!\x02Статус:\x02&Отключить\x02&Подключить\x02Публи" +
+ "чный ключ:\x02Порт:\x02MTU:\x02IP-адреса:\x02DNS-серверы:\x02Общий ключ" +
+ ":\x02Разрешенные IP-адреса:\x02IP-адрес сервера:\x02Поддерживание соедин" +
+ "ения:\x02Последнее рукопожатие:\x02Отключен\x02Отключение\x02Неизвестно" +
+ "е состояние\x02Журнал\x02&Скопировать\x02Выбрать &все\x02&Сохранить в ф" +
+ "айл…\x02Время\x02Сообщение журнала\x02Экспорт журнала в файл\x02&О Wire" +
+ "Guard…\x02Ошибка туннеля\x02%[1]s\x0a\x0aОбратитесь к журналу для получе" +
+ "ния дополнительной информации.\x02%[1]s (устарел)\x02Ошибка выхода из W" +
+ "ireGuard\x02Доступно обновление WireGuard. Настоятельно рекомендуем обно" +
+ "вить приложение.\x02Обновить Сейчас\x02Ошибка: %[1]v. Попробуйте еще ра" +
+ "з.\x02Статус: Завершено!\x02Значок в системном трее WireGuard не появил" +
+ "ся после 30 секунд.\x02В скобках должен быть IPv6 адрес\x02Ключи должны" +
+ " декодироваться ровно с 32 байтами\x02Строка должна быть в секции\x02В к" +
+ "люче конфигурации отсутствует разделитель\x02Ключ должен иметь значение" +
+ "\x02Неверный ключ для секции [Interface]\x02Неверный ключ для секции [Pe" +
+ "er]\x02В Интерфейсе должен быть приватный ключ\x02Неверный ключ для секц" +
+ "ии Интерфейса\x02Версия протокола должна быть 1\x02Недействительный клю" +
+ "ч для секции пира\x02Скрипты:\x02Передача:\x02перед подключением\x02пос" +
+ "ле подключения\x02перед отключением\x02после отключения\x02отключено, п" +
+ "о политике\x02включено\x02Получено %[1]s, отправлено %[2]s\x02Не удалос" +
+ "ь определить состояние туннеля\x02Не удалось подключить туннель\x02Не у" +
+ "далось отключить туннель\x02Интерфейс: %[1]s\x02Пир\x02Создать туннель" +
+ "\x02Редактировать туннель\x02&Название:\x02&Публичный ключ:\x02(неизвест" +
+ "но)\x02&Блокировать нетуннелированный трафик\x02&Сохранить\x02Отмена" +
+ "\x02&Конфигурация:\x02Некорректное название\x02Необходимо название.\x02Н" +
+ "азвание туннеля ‘%[1]s’ недопустимо.\x02Не удалось отобразить туннели" +
+ "\x02Туннель уже существует\x02Туннель с именем ’%[1]s’ уже существует." +
+ "\x02Не удалось создать новую конфигурацию\x02Ошибка записи файла\x02Файл" +
+ " '%[1]s' уже существует!\x0a\x0aВы хотите перезаписать его?\x02Подключен" +
+ "\x02Подключение\x02Текстовые файлы (*.txt)|*.txt|Все файлы (*.*)|*.*\x02" +
+ "Ошибка обнаружения WireGuard\x02Не удалось дождаться появления окна Wir" +
+ "eGuard: %[1]v\x02WireGuard: Деактивирован\x02Статус: Неизвестен\x02Адрес" +
+ "а: нет\x02&Управление туннелями…\x02&Импорт туннелей из файла…\x02Вы&хо" +
+ "д\x02&Туннели\x02WireGuard Включен\x02Туннель %[1]s подключен.\x02WireG" +
+ "uard Выключен\x02Туннель %[1]s отключен.\x02Ошибка туннеля WireGuard\x02" +
+ "WireGuard: %[1]s\x02Статус: %[1]s\x02Адреса: %[1]s\x02Доступно обновлени" +
+ "е!\x02Доступно обновление WireGuard\x02Доступно обновление для WireGuar" +
+ "d. Рекомендуется обновить его как можно скорее.\x02Туннели\x02&Редактиро" +
+ "вать\x02Добавить &пустой туннель…\x02Добавить туннель\x02Удалить выбран" +
+ "ные туннели\x02Экспорт всех туннелей в zip-архив\x02&Переключить\x02Экс" +
+ "порт всех туннелей в &zip-архив…\x02Редактировать &выбранный туннель…" +
+ "\x02&Удалить выбранные туннели\x02файлы конфигурации не были найдены\x02" +
+ "Невозможно импортировать конфигурацию: %[1]v\x02Не удалось перечислить " +
+ "существующие туннели: %[1]v\x02Туннель с именем ’%[1]s’ уже существует" +
+ "\x02Невозможно импортировать конфигурацию: %[1]v\x02Импортированные тунн" +
+ "ели\x14\x01\x81\x01\x00\x041\x02Импортированы туннели: %[1]d\x051\x02Им" +
+ "портированы туннели: %[1]d\x024\x02Импортированный %[1]d туннель\x001" +
+ "\x02Импортированы туннели: %[1]d\x14\x02\x80\x01\x04<\x02Импортированы т" +
+ "уннели: %[1]d из %[2]d\x05<\x02Импортированы туннели: %[1]d из %[2]d" +
+ "\x02<\x02Импортированы туннели: %[1]d из %[2]d\x00<\x02Импортированы тун" +
+ "нели: %[1]d из %[2]d\x02Не удалось создать туннель\x14\x01\x81\x01\x00" +
+ "\x04%\x02Удалить туннели: %[1]d\x05%\x02Удалить туннели: %[1]d\x02$\x02У" +
+ "далить %[1]d туннель\x00%\x02Удалить туннели: %[1]d\x14\x01\x81\x01\x00" +
+ "\x04O\x02Вы уверены, что хотите удалить туннели: %[1]d?\x05O\x02Вы увере" +
+ "ны, что хотите удалить туннели: %[1]d?\x02N\x02Вы уверены, что хотите у" +
+ "далить %[1]d туннель?\x00O\x02Вы уверены, что хотите удалить туннели: %" +
+ "[1]d?\x02Удалить туннель ‘%[1]s’\x02Вы уверены, что хотите удалить '%[1]" +
+ "s' туннель?\x02%[1]s Данное действие невозможно отменить.\x02Не удалось " +
+ "удалить туннель\x02Невозможно удалить туннель: %[1]s\x02Не удалось удал" +
+ "ить туннели\x14\x01\x81\x01\x00\x04;\x02туннелей не удалось удалить: %[" +
+ "1]d\x05;\x02туннелей не удалось удалить: %[1]d\x029\x02%[1]d туннель не " +
+ "удалось удалить.\x00;\x02туннелей не удалось удалить: %[1]d\x02Файлы ко" +
+ "нфигурации (*.zip, *.conf)|*.zip;*.conf|Все файлы (*.*)|*.*\x02Импорт т" +
+ "уннелей из файла\x02Конфигурация ZIP файлов (*.zip)|*.zip\x02Экспорт ту" +
+ "ннелей в zip-архив\x02%[1]s (неподписанная сборка, нет обновлений)\x02Н" +
+ "е удается выйти из сервиса из-за: %[1]v. Вы можете остановить WireGuard" +
+ " из менеджера служб.\x02Статус: Ожидание пользователя\x02Статус: Ожидани" +
+ "е обновления"
+
+var si_LKIndex = []uint32{ // 278 elements
+ // Entry 0 - 1F
+ 0x00000000, 0x00000013, 0x00000013, 0x00000036,
+ 0x00000036, 0x00000036, 0x00000036, 0x00000036,
+ 0x00000036, 0x00000036, 0x00000043, 0x00000043,
+ 0x00000084, 0x000000b3, 0x000000dc, 0x00000117,
+ 0x0000014c, 0x00000160, 0x0000016c, 0x00000180,
+ 0x00000194, 0x000001a8, 0x000001bc, 0x000001c9,
+ 0x00000212, 0x00000212, 0x00000212, 0x00000212,
+ 0x00000212, 0x0000024b, 0x0000024b, 0x0000027c,
+ // Entry 20 - 3F
+ 0x0000027c, 0x000002be, 0x000002be, 0x000002be,
+ 0x000002be, 0x000002be, 0x000002c1, 0x000002c4,
+ 0x000002f3, 0x000002f3, 0x00000303, 0x00000325,
+ 0x00000336, 0x00000336, 0x00000336, 0x00000336,
+ 0x00000366, 0x00000366, 0x00000377, 0x000003a1,
+ 0x000003a1, 0x000003c8, 0x000003c8, 0x000003c8,
+ 0x000003c8, 0x000003c8, 0x000003c8, 0x000003f4,
+ 0x000003f4, 0x00000408, 0x00000408, 0x0000043b,
+ // Entry 40 - 5F
+ 0x0000044b, 0x0000044b, 0x0000044b, 0x0000047e,
+ 0x0000047e, 0x0000047e, 0x000004ae, 0x000004f6,
+ 0x000004f6, 0x0000051f, 0x00000562, 0x00000593,
+ 0x00000593, 0x00000593, 0x00000593, 0x00000593,
+ 0x00000593, 0x000005d3, 0x000005d3, 0x000005d3,
+ 0x00000644, 0x00000644, 0x00000698, 0x00000698,
+ 0x00000698, 0x00000698, 0x00000698, 0x00000698,
+ 0x00000698, 0x00000698, 0x00000698, 0x000006b0,
+ // Entry 60 - 7F
+ 0x000006e3, 0x000006e3, 0x000006e3, 0x000006e3,
+ 0x0000070c, 0x0000070c, 0x0000070c, 0x0000070c,
+ 0x00000715, 0x00000715, 0x00000727, 0x00000727,
+ 0x00000741, 0x00000754, 0x00000772, 0x0000079c,
+ 0x000007c3, 0x000007c3, 0x000007c3, 0x000007c3,
+ 0x000007c3, 0x00000812, 0x0000084e, 0x0000084e,
+ 0x0000084e, 0x00000883, 0x00000883, 0x00000883,
+ 0x00000905, 0x00000905, 0x00000926, 0x00000926,
+ // Entry 80 - 9F
+ 0x00000926, 0x00000926, 0x0000093d, 0x0000093d,
+ 0x0000097e, 0x0000097e, 0x0000097e, 0x0000097e,
+ 0x0000097e, 0x0000097e, 0x00000995, 0x000009ac,
+ 0x000009ac, 0x000009ac, 0x000009ac, 0x000009ac,
+ 0x000009c6, 0x000009c6, 0x000009c6, 0x000009c6,
+ 0x000009c6, 0x000009c6, 0x000009c6, 0x000009c6,
+ 0x000009c6, 0x000009c6, 0x000009c6, 0x000009c6,
+ 0x000009c6, 0x00000a13, 0x00000a13, 0x00000a13,
+ // Entry A0 - BF
+ 0x00000a13, 0x00000a13, 0x00000a13, 0x00000a13,
+ 0x00000a13, 0x00000a13, 0x00000a82, 0x00000a82,
+ 0x00000a82, 0x00000a82, 0x00000a82, 0x00000a82,
+ 0x00000a82, 0x00000a82, 0x00000a82, 0x00000a82,
+ 0x00000a82, 0x00000acf, 0x00000b2f, 0x00000b2f,
+ 0x00000b2f, 0x00000b2f, 0x00000b2f, 0x00000b2f,
+ 0x00000b2f, 0x00000b2f, 0x00000b2f, 0x00000b2f,
+ 0x00000b2f, 0x00000b2f, 0x00000b2f, 0x00000b2f,
+ // Entry C0 - DF
+ 0x00000b2f, 0x00000b2f, 0x00000b2f, 0x00000b2f,
+ 0x00000b2f, 0x00000b2f, 0x00000b2f, 0x00000b2f,
+ 0x00000b2f, 0x00000b2f, 0x00000b2f, 0x00000b2f,
+ 0x00000b2f, 0x00000b2f, 0x00000b2f, 0x00000b2f,
+ 0x00000b2f, 0x00000b2f, 0x00000b2f, 0x00000b2f,
+ 0x00000b2f, 0x00000b2f, 0x00000b2f, 0x00000b2f,
+ 0x00000b2f, 0x00000b2f, 0x00000b2f, 0x00000b2f,
+ 0x00000b2f, 0x00000b2f, 0x00000b2f, 0x00000b2f,
+ // Entry E0 - FF
+ 0x00000b2f, 0x00000b2f, 0x00000b2f, 0x00000b2f,
+ 0x00000b2f, 0x00000b2f, 0x00000b2f, 0x00000b2f,
+ 0x00000b2f, 0x00000b2f, 0x00000b2f, 0x00000b2f,
+ 0x00000b2f, 0x00000b2f, 0x00000b2f, 0x00000b2f,
+ 0x00000b2f, 0x00000b2f, 0x00000b2f, 0x00000b2f,
+ 0x00000b2f, 0x00000b2f, 0x00000b2f, 0x00000b2f,
+ 0x00000b2f, 0x00000b2f, 0x00000b2f, 0x00000b2f,
+ 0x00000b2f, 0x00000b2f, 0x00000b2f, 0x00000b2f,
+ // Entry 100 - 11F
+ 0x00000b2f, 0x00000b2f, 0x00000b2f, 0x00000b2f,
+ 0x00000b2f, 0x00000b2f, 0x00000b2f, 0x00000b2f,
+ 0x00000b2f, 0x00000b2f, 0x00000b2f, 0x00000b2f,
+ 0x00000b2f, 0x00000b2f, 0x00000b2f, 0x00000b2f,
+ 0x00000b2f, 0x00000b2f, 0x00000b2f, 0x00000b2f,
+ 0x00000b2f, 0x00000b2f,
+} // Size: 1136 bytes
+
+const si_LKData string = "" + // Size: 2863 bytes
+ "\x02දෝෂයකි\x02භාවිතය: %[1]s [\x0a%[2]s]\x02දැන්\x14\x01\x81\x01\x00\x02" +
+ "\x1c\x02අවුරුදු %[1]d\x00\x1c\x02අවුරුදු %[1]d\x14\x01\x81\x01\x00\x02" +
+ "\x13\x02දවස් %[1]d\x00\x13\x02දවස් %[1]d\x14\x01\x81\x01\x00\x02\x10\x02" +
+ "පැය %[1]d\x00\x10\x02පැය %[1]d\x14\x01\x81\x01\x00\x02\x19\x02විනාඩි %" +
+ "[1]d\x00\x19\x02විනාඩි %[1]d\x14\x01\x81\x01\x00\x02\x16\x02තත්පර %[1]d" +
+ "\x00\x16\x02තත්පර %[1]d\x02%[1]s ට පෙර\x02බ.\u00a0%[1]d\x02කි.බ. %.2[1]f" +
+ "\x02මෙ.බ. %.2[1]f\x02ගි.බ. %.2[1]f\x02ටෙ.බ. %.2[1]f\x02%[1]s: %[2]q\x02ව" +
+ "ලංගු නොවන අ.ජා.කෙ. ලිපිනයකි\x02වලංගු නොවන කෙවෙනියකි\x02වලංගු නොවන යතුර" +
+ ": %[1]v\x02පේළියකට අල්පවිරාම දෙකක්\x02, \x02, \x02වයර්ගාඩ් පිළිබඳව\x02වස" +
+ "න්න\x02♥ &පරිත්\u200dයාග!\x02තත්වය:\x02සවන්දීමේ කෙවෙනිය:\x02ලිපින:\x02" +
+ "ව.නා.ප. සේවාදායක:\x02ඉඩදුන් අ.ජා.කෙ.:\x02නොදන්නා තත්වයකි\x02&පිටපත්" +
+ "\x02&ගොනුවකට සුරකින්න…\x02වේලාව\x02&වයර්ගාඩ් පිළිබඳව…\x02%[1]s (කල් ඉකුත" +
+ "් වී ඇත)\x02වයර්ගාඩ් පිටවීමේදී දෝෂයකි\x02යාවත්කාල කරන්න\x02දෝෂය: %[1]v" +
+ ". යළි උත්සාහ කරන්න.\x02තත්වය: සම්පූර්ණයි!\x02යතුරට අගයක් තිබිය යුතුය\x02" +
+ "අතුරුමුහුතකට පුද්ගලික යතුරක් තිබිය යුතුය\x02කෙටුම්පතෙහි අනුවාදය 1 විය " +
+ "යුතුය\x02සබල කර ඇත\x02%[1]s ලැබුණී, %[2]s යැවිණි\x02අතුරුමුහුණත: %[1]s" +
+ "\x02&නම:\x02(නොදනී)\x02&සුරකින්න\x02අවලංගු\x02&වින්\u200dයාසය:\x02වලංගු " +
+ "නොවන නමකි\x02නමක් අවශ්\u200dයයි.\x02නව වින්\u200dයාසය සෑදීමට නොහැකියි" +
+ "\x02ගොනුව ලිවීමට අසමත්විය\x02ක්\u200dරියාත්මක වෙමින්\x02වයර්ගාඩ් කවුළුව " +
+ "පෙනෙන තෙක් රැඳීසිටිය නොහැකිය: %[1]v\x02තත්වය: නොදනී\x02පි&ටවන්න\x02වයර" +
+ "්ගාඩ් ක්\u200dරියාත්මකයි\x02තත්වය: %[1]s\x02ලිපින: %[1]s\x02&සංස්කරණය" +
+ "\x02වින්\u200dයාසය ආයාත කළ නොහැකිය: %[1]v\x02%[1]s මෙම ක්\u200dරියාමාර්ග" +
+ "ය ආපසු හැරවිය නොහැකිය.\x02තත්වය: පරිශීලක සඳහා රැඳෙමින්\x02තත්වය: යාවත්" +
+ "කාල සේවාව සඳහා රැඳෙමින්"
+
+var skIndex = []uint32{ // 278 elements
+ // Entry 0 - 1F
+ 0x00000000, 0x00000006, 0x0000005e, 0x00000078,
+ 0x00000097, 0x000000d1, 0x0000011e, 0x00000156,
+ 0x00000198, 0x00000202, 0x00000208, 0x00000230,
+ 0x0000026a, 0x000002a2, 0x000002e3, 0x00000326,
+ 0x0000036b, 0x00000376, 0x0000037f, 0x0000038c,
+ 0x00000399, 0x000003a6, 0x000003b3, 0x000003c0,
+ 0x000003d4, 0x000003fa, 0x0000041f, 0x00000443,
+ 0x00000451, 0x00000460, 0x00000482, 0x0000049b,
+ // Entry 20 - 3F
+ 0x000004ce, 0x000004e4, 0x00000501, 0x00000515,
+ 0x0000054a, 0x0000056e, 0x00000571, 0x00000573,
+ 0x0000057f, 0x00000597, 0x000005a1, 0x000005b0,
+ 0x000005b6, 0x000005c5, 0x000005d2, 0x000005e4,
+ 0x000005f4, 0x000005f9, 0x00000601, 0x0000060e,
+ 0x00000629, 0x0000063e, 0x0000064c, 0x00000665,
+ 0x00000685, 0x00000690, 0x0000069e, 0x000006ac,
+ 0x000006be, 0x000006cb, 0x000006dc, 0x000006f4,
+ // Entry 40 - 5F
+ 0x000006f9, 0x00000716, 0x0000073f, 0x0000074f,
+ 0x0000075c, 0x00000794, 0x000007a7, 0x000007c2,
+ 0x00000825, 0x00000839, 0x00000859, 0x0000086c,
+ 0x000008b6, 0x000008df, 0x00000914, 0x00000933,
+ 0x00000975, 0x00000996, 0x000009bb, 0x000009db,
+ 0x00000a0e, 0x00000a31, 0x00000a4f, 0x00000a6d,
+ 0x00000a76, 0x00000a7e, 0x00000a8d, 0x00000a99,
+ 0x00000aa8, 0x00000ab4, 0x00000ad5, 0x00000adf,
+ // Entry 60 - 7F
+ 0x00000b03, 0x00000b25, 0x00000b44, 0x00000b65,
+ 0x00000b76, 0x00000b7b, 0x00000b91, 0x00000ba0,
+ 0x00000ba9, 0x00000bbc, 0x00000bc7, 0x00000bf5,
+ 0x00000bff, 0x00000c08, 0x00000c18, 0x00000c29,
+ 0x00000c3d, 0x00000c65, 0x00000c9b, 0x00000cae,
+ 0x00000cd8, 0x00000d05, 0x00000d28, 0x00000d65,
+ 0x00000d6e, 0x00000d7a, 0x00000db3, 0x00000dcc,
+ 0x00000e07, 0x00000e20, 0x00000e2e, 0x00000e3e,
+ // Entry 80 - 9F
+ 0x00000e54, 0x00000e78, 0x00000e83, 0x00000e8b,
+ 0x00000ea4, 0x00000ec1, 0x00000edc, 0x00000efb,
+ 0x00000f12, 0x00000f23, 0x00000f2f, 0x00000f3d,
+ 0x00000f59, 0x00000f7f, 0x00000fe8, 0x00000fef,
+ 0x00000ff9, 0x00001014, 0x00001022, 0x00001046,
+ 0x0000106f, 0x0000107a, 0x000010a7, 0x000010c5,
+ 0x000010e4, 0x00001114, 0x0000114e, 0x00001181,
+ 0x000011a6, 0x000011d7, 0x000011ed, 0x0000126d,
+ // Entry A0 - BF
+ 0x00001311, 0x0000132a, 0x000013a3, 0x00001491,
+ 0x000014b1, 0x000014ee, 0x0000151a, 0x00001535,
+ 0x0000155d, 0x0000157b, 0x0000162d, 0x0000167b,
+ 0x0000169b, 0x000016c3, 0x000016e1, 0x00001715,
+ 0x0000177b, 0x0000179b, 0x000017c5, 0x000017c5,
+ 0x000017c5, 0x000017c5, 0x000017c5, 0x000017c5,
+ 0x000017c5, 0x000017c5, 0x000017c5, 0x000017c5,
+ 0x000017c5, 0x000017c5, 0x000017c5, 0x000017c5,
+ // Entry C0 - DF
+ 0x000017c5, 0x000017c5, 0x000017c5, 0x000017c5,
+ 0x000017c5, 0x000017c5, 0x000017c5, 0x000017c5,
+ 0x000017c5, 0x000017c5, 0x000017c5, 0x000017c5,
+ 0x000017c5, 0x000017c5, 0x000017c5, 0x000017c5,
+ 0x000017c5, 0x000017c5, 0x000017c5, 0x000017c5,
+ 0x000017c5, 0x000017c5, 0x000017c5, 0x000017c5,
+ 0x000017c5, 0x000017c5, 0x000017c5, 0x000017c5,
+ 0x000017c5, 0x000017c5, 0x000017c5, 0x000017c5,
+ // Entry E0 - FF
+ 0x000017c5, 0x000017c5, 0x000017c5, 0x000017c5,
+ 0x000017c5, 0x000017c5, 0x000017c5, 0x000017c5,
+ 0x000017c5, 0x000017c5, 0x000017c5, 0x000017c5,
+ 0x000017c5, 0x000017c5, 0x000017c5, 0x000017c5,
+ 0x000017c5, 0x000017c5, 0x000017c5, 0x000017c5,
+ 0x000017c5, 0x000017c5, 0x000017c5, 0x000017c5,
+ 0x000017c5, 0x000017c5, 0x000017c5, 0x000017c5,
+ 0x000017c5, 0x000017c5, 0x000017c5, 0x000017c5,
+ // Entry 100 - 11F
+ 0x000017c5, 0x000017c5, 0x000017c5, 0x000017c5,
+ 0x000017c5, 0x000017c5, 0x000017c5, 0x000017c5,
+ 0x000017c5, 0x000017c5, 0x000017c5, 0x000017c5,
+ 0x000017c5, 0x000017c5, 0x000017c5, 0x000017c5,
+ 0x000017c5, 0x000017c5, 0x000017c5, 0x000017c5,
+ 0x000017c5, 0x000017c5,
+} // Size: 1136 bytes
+
+const skData string = "" + // Size: 6085 bytes
+ "\x02Chyba\x02(bez argumentu): získať administrátorské práva a nainštalov" +
+ "ať službu manažéra\x02Použitie: %[1]s [\x0a%[2]s]\x02Možnosti príkazovéh" +
+ "o riadku\x02Nepodarilo sa zistiť, či proces beží pod WOW64: %[1]v\x02V t" +
+ "omto počítači musíte používať pôvodnú verziu programu WireGuard.\x02Nepo" +
+ "darilo sa otvoriť token aktuálneho procesu: %[1]v\x02WireGuard môžu použ" +
+ "ívať iba členovia Builtin skupiny %[1]s.\x02WireGuard je spustený, ale " +
+ "používateľské rozhranie je prístupné iba členom Builtin skupiny %[1]s." +
+ "\x02Teraz\x02Systémové hodiny sa vrátili v čase!\x14\x01\x81\x01\x00\x04" +
+ "\x0b\x02%[1]d roky\x05\x0c\x02%[1]d rokov\x02\x0a\x02%[1]d rok\x00\x0c" +
+ "\x02%[1]d rokov\x14\x01\x81\x01\x00\x04\x0a\x02%[1]d dni\x05\x0b\x02%[1]" +
+ "d dní\x02\x0b\x02%[1]d deň\x00\x0b\x02%[1]d dní\x14\x01\x81\x01\x00\x04" +
+ "\x0d\x02%[1]d hodiny\x05\x0d\x02%[1]d hodín\x02\x0d\x02%[1]d hodina\x00" +
+ "\x0d\x02%[1]d hodín\x14\x01\x81\x01\x00\x04\x0e\x02%[1]d minúty\x05\x0d" +
+ "\x02%[1]d minút\x02\x0e\x02%[1]d minúta\x00\x0d\x02%[1]d minút\x14\x01" +
+ "\x81\x01\x00\x04\x0e\x02%[1]d sekundy\x05\x0e\x02%[1]d sekúnd\x02\x0e" +
+ "\x02%[1]d sekunda\x00\x0e\x02%[1]d sekúnd\x02Pred %[1]s\x02%[1]d\u00a0B" +
+ "\x02%.2[1]f\u00a0KiB\x02%.2[1]f\u00a0MiB\x02%.2[1]f\u00a0GiB\x02%.2[1]f" +
+ "\u00a0TiB\x02%[1]s: %[2]q\x02Neplatná adresa IP\x02Neplatná dĺžka sieťov" +
+ "ého prefixu\x02Koncovému bodu chýba číslo portu\x02Neplatný hostiteľ ko" +
+ "ncového bodu\x02Neplatné MTU\x02Neplatný port\x02Neplatný perzistentný k" +
+ "eepalive\x02Neplatný kľúč: %[1]v\x02Číslo musí mať hodnotu medzi 0 a 2^6" +
+ "4-1: %[1]v\x02Dve čiarky v poradí\x02Názov tunela nie je platný\x02[nešp" +
+ "ecifikované]\x02Všetci peeri musia mať priradený verejný kľúč\x02Chyba p" +
+ "ri získavaní konfigurácie\x02, \x02 \x02O WireGuard\x02Obrázok WireGuard" +
+ " loga\x02Zatvoriť\x02♥ a Darovat!\x02Stav:\x02a Deaktivovať\x02a Aktivov" +
+ "ať\x02Verejný kľúč:\x02Otvorený port:\x02MTU:\x02Adresy:\x02Servery DNS:" +
+ "\x02Vopred zdieľaný kľúč:\x02Povolené IP adresy:\x02Koncový bod:\x02Perz" +
+ "istentný keepalive:\x02Posledné spojenie (handshake):\x02Neaktívny\x02De" +
+ "aktivuje sa\x02Neznámy stav\x02Denník udalostí\x02&Kopírovať\x02Vybr&ať " +
+ "všetko\x02Uložiť do &súboru…\x02Čas\x02Správa v denníku udalostí\x02Expo" +
+ "rtovať denník udalostí do súboru\x02&O WireGuard…\x02Chyba tunela\x02%[1" +
+ "]s\x0a\x0aViac informácií nájdete v denníku udalostí.\x02%[1]s (neaktuán" +
+ "y)\x02Chyba ukončenia WireGuard\x02Je k dispozícii nová verzia programu " +
+ "WireGuard. Odporúčame bezodkladne vykonať aktualizáciu.\x02Aktualizovať " +
+ "teraz\x02Chyba: %[1]v. Skúste to znova.\x02Stav: Dokončené!\x02WireGuard" +
+ " ikona sa ani po 30 sekundách neobjavila na systémovej lište.\x02Medzi z" +
+ "átvorkami musí byť IPv6 adresa\x02Dekódované kľúče musia mať veľkosť 32" +
+ " bajtov\x02Sekcia musí obsahovať čiaru\x02Konfiguračný kľúč neobsahuje s" +
+ "eparátor (znamienko rovnosti)\x02Kľúč musí obsahovať hodnotu\x02Neplatný" +
+ " kľúč sekcie [Interface]\x02Neplatný kľúč sekcie [Peer]\x02Rozhranie mus" +
+ "í mať priradený súkromný kľúč\x02Neplatný kľúč sekcie rozhrania\x02Verz" +
+ "ia protokolu musí byť 1\x02Neplatný kľúč peer sekcie\x02Skripty:\x02Pren" +
+ "os:\x02pred-zapnutím\x02po-zapnutí\x02pred-vypnutím\x02po-vypnutí\x02zak" +
+ "ázané, na základe pravidla\x02povolené\x02%[1]s prijatých, %[2]s odosla" +
+ "ných\x02Nepodarilo sa zistiť stav tunela\x02Nepodarilo sa aktivovať tune" +
+ "l\x02Nepodarilo sa deaktivovať tunel\x02Rozhranie: %[1]s\x02Peer\x02Vytv" +
+ "oriť nový tunel\x02Upraviť tunel\x02&Názov:\x02&Verejný kľúč:\x02(neznám" +
+ "y)\x02&Blokovať netunelovaný prenos (kill-switch)\x02&Uložiť\x02Zrušiť" +
+ "\x02&Konfigurácia:\x02Neplatný názov\x02Názov je povinný.\x02Názov tunel" +
+ "a ‘%[1]s’ je neplatný.\x02Nepodarilo sa pripraviť zoznam existujúcich tu" +
+ "nelov\x02Tunel už existuje\x02Tunel s názvom ‘%[1]s’ už existuje.\x02Nie" +
+ " je možné vytvoriť novú konfiguráciu\x02Nepodarilo sa zapísať do súboru" +
+ "\x02Súbor ‘%[1]s’ už existuje.\x0a\x0aŽeláte si ho prepísať?\x02Aktívny" +
+ "\x02Aktivuje sa\x02Textové súbory (*.txt)|*.txt|Všetky súbory (*.*)|*.*" +
+ "\x02Chyba detekcie WireGuard\x02Nie je možné čakať na zobrazenie WireGua" +
+ "rd okna: %[1]v\x02WireGuard: deaktivovaný\x02Stav: Nezámy\x02Adresa: žia" +
+ "dna\x02&Spravovať tunely…\x02&Importovať tunel(y) zo súboru…\x02U&končiť" +
+ "\x02&Tunely\x02WireGuard je aktivovaný\x02Tunel %[1]s bol aktivovaný." +
+ "\x02WireGuard je deaktivovaný\x02Tunel %[1]s bol deaktivovaný.\x02Chyba " +
+ "WireGuard tunelu\x02WireGuard: %[1]s\x02Stav: %[1]s\x02Adresa: %[1]s\x02" +
+ "Je dostupná aktualizácia!\x02Dostupná aktualizácia pre WireGuard\x02Je k" +
+ " dispozícii aktualizácia programu WireGuard. Je odporúčané čo najskôr vy" +
+ "konať aktualizáciu.\x02Tunely\x02&Upraviť\x02Pridať &prázdny tunel…\x02P" +
+ "ridať tunel\x02Odstrániť označený(é) tunel(y)\x02Export všetkých tunelov" +
+ " do zip súboru\x02P&repnúť\x02Export všetkých tunelov do &zip súboru…" +
+ "\x02Upraviť &označený tunel…\x02&Odstrániť označené tunely\x02neboli náj" +
+ "dené žiadne konfiguračné súbory\x02Nepodarilo sa naimportovať vybrané ko" +
+ "nfigurácie: %[1]v\x02Nepodarilo sa načítať existujúce tunely: %[1]v\x02U" +
+ "ž existuje tunel s názvom '%[1]s'\x02Nepodarilo sa naimportovať konfigu" +
+ "ráciu: %[1]v\x02Naimportované tunely\x14\x01\x81\x01\x00\x04\x1c\x02Naim" +
+ "portované %[1]d tunely\x05\x1f\x02Naimportovaných %[1]d tunelov\x02\x19" +
+ "\x02Importovaný %[1]d tunel\x00\x1f\x02Naimportovaných %[1]d tunelov\x14" +
+ "\x02\x80\x01\x04%\x02Naimportované %[1]d z %[2]d tunelov\x05'\x02Naimpor" +
+ "tovaných %[1]d z %[2]d tunelov\x02%\x02Naimportovaný %[1]d z %[2]d tunel" +
+ "ov\x00'\x02Naimportovaných %[1]d z %[2]d tunelov\x02Tunel sa nedá vytvor" +
+ "iť\x14\x01\x81\x01\x00\x04\x19\x02Odstránene %[1]d tunely\x05\x1d\x02Ods" +
+ "tránených %[1]d tunelov\x02\x19\x02Odstránený %[1]d tunel\x00\x1d\x02Ods" +
+ "tránených %[1]d tunelov\x14\x01\x81\x01\x00\x048\x02Ste si istý, že si ž" +
+ "eláte odstrániť %[1]d tunely?\x059\x02Ste si istý, že si želáte odstráni" +
+ "ť %[1]d tunelov?\x027\x02Ste si istý, že si želáte odstrániť %[1]d tune" +
+ "l?\x009\x02Ste si istý, že si želáte odstrániť %[1]d tunelov?\x02Odstrán" +
+ "enie tunela ‘%[1]s’\x02Ste si istý, že si želáte odstrániť tunel ‘%[1]s’" +
+ "?\x02%[1]s Túto akciu nemôže vrátiť späť.\x02Tunel sa nedá odstrániť\x02" +
+ "Nebolo možné odstrániť tunel: %[1]s\x02Tunely sa nedajú odstrániť\x14" +
+ "\x01\x81\x01\x00\x04)\x02%[1]d tunely nebolo možné odstrániť.\x05*\x02%[" +
+ "1]d tunelov nebolo možné odstrániť.\x02(\x02%[1]d tunel nebolo možné ods" +
+ "trániť.\x00*\x02%[1]d tunelov nebolo možné odstrániť.\x02Konfirugačné sú" +
+ "bory (*.zip, *.conf)|*.zip;*.conf|Všetky súbory (*.*)|*.*\x02Importovať " +
+ "tunel(y) zo súboru\x02Konfiguračné ZIP súbry (*.zip)|*.zip\x02Export tun" +
+ "elov do zip súboru\x02%[1]s (nepodpísaná verzia, žiadne aktualizácie)" +
+ "\x02Nie je možné ukončiť služby z dôvodu: %[1]v. Skúste zastaviť WireGua" +
+ "rd v správcovi služieb.\x02Stav: Čaká sa na užívateľa\x02Stav: Čaká sa n" +
+ "a aktualizačnú službu"
+
+var slIndex = []uint32{ // 278 elements
+ // Entry 0 - 1F
+ 0x00000000, 0x00000007, 0x00000058, 0x00000070,
+ 0x00000089, 0x000000c1, 0x00000111, 0x00000148,
+ 0x0000019a, 0x00000200, 0x00000205, 0x00000224,
+ 0x0000025c, 0x00000293, 0x000002c7, 0x00000307,
+ 0x0000034b, 0x00000357, 0x00000360, 0x0000036d,
+ 0x0000037a, 0x00000387, 0x00000394, 0x000003a1,
+ 0x000003b4, 0x000003d8, 0x000003fa, 0x00000423,
+ 0x00000430, 0x0000043f, 0x00000463, 0x0000047a,
+ // Entry 20 - 3F
+ 0x000004ae, 0x000004c3, 0x000004da, 0x000004e8,
+ 0x0000050f, 0x0000052f, 0x00000532, 0x00000534,
+ 0x00000541, 0x0000055f, 0x00000565, 0x00000573,
+ 0x0000057b, 0x00000588, 0x00000593, 0x000005a1,
+ 0x000005b4, 0x000005b9, 0x000005c2, 0x000005d2,
+ 0x000005e8, 0x000005f9, 0x00000609, 0x00000625,
+ 0x00000637, 0x00000641, 0x0000064f, 0x0000065e,
+ 0x00000666, 0x0000066f, 0x0000067b, 0x00000693,
+ // Entry 40 - 5F
+ 0x00000698, 0x000006ae, 0x000006c8, 0x000006db,
+ 0x000006e9, 0x00000718, 0x0000072e, 0x0000074e,
+ 0x000007a1, 0x000007af, 0x000007d1, 0x000007e3,
+ 0x00000827, 0x0000084d, 0x0000087e, 0x00000899,
+ 0x000008c8, 0x000008e3, 0x00000908, 0x00000928,
+ 0x0000094a, 0x0000096c, 0x0000098a, 0x000009ac,
+ 0x000009b5, 0x000009bd, 0x000009cd, 0x000009db,
+ 0x000009ed, 0x000009fd, 0x00000a1b, 0x00000a26,
+ // Entry 60 - 7F
+ 0x00000a43, 0x00000a67, 0x00000a85, 0x00000aa5,
+ 0x00000ab4, 0x00000abc, 0x00000ace, 0x00000ada,
+ 0x00000ae0, 0x00000aef, 0x00000af9, 0x00000b23,
+ 0x00000b2b, 0x00000b35, 0x00000b45, 0x00000b52,
+ 0x00000b62, 0x00000b84, 0x00000bb4, 0x00000bc6,
+ 0x00000bf1, 0x00000c18, 0x00000c36, 0x00000c71,
+ 0x00000c79, 0x00000c85, 0x00000cbd, 0x00000cda,
+ 0x00000d15, 0x00000d2c, 0x00000d3b, 0x00000d49,
+ // Entry 80 - 9F
+ 0x00000d60, 0x00000d7f, 0x00000d86, 0x00000d8e,
+ 0x00000da2, 0x00000dc0, 0x00000dd6, 0x00000df6,
+ 0x00000e0e, 0x00000e1f, 0x00000e2d, 0x00000e3c,
+ 0x00000e55, 0x00000e78, 0x00000ebd, 0x00000ec4,
+ 0x00000ecb, 0x00000ee4, 0x00000ef0, 0x00000f08,
+ 0x00000f20, 0x00000f2a, 0x00000f48, 0x00000f61,
+ 0x00000f7a, 0x00000f9f, 0x00000fcd, 0x00001000,
+ 0x00001025, 0x0000104b, 0x0000105b, 0x000010bf,
+ // Entry A0 - BF
+ 0x0000114a, 0x0000116b, 0x000011d0, 0x000012bd,
+ 0x000012d8, 0x00001313, 0x0000133e, 0x00001358,
+ 0x00001380, 0x0000139b, 0x0000144f, 0x0000149c,
+ 0x000014b5, 0x000014e0, 0x000014fd, 0x0000152e,
+ 0x0000159b, 0x000015b7, 0x000015de, 0x000015de,
+ 0x000015de, 0x000015de, 0x000015de, 0x000015de,
+ 0x000015de, 0x000015de, 0x000015de, 0x000015de,
+ 0x000015de, 0x000015de, 0x000015de, 0x000015de,
+ // Entry C0 - DF
+ 0x000015de, 0x000015de, 0x000015de, 0x000015de,
+ 0x000015de, 0x000015de, 0x000015de, 0x000015de,
+ 0x000015de, 0x000015de, 0x000015de, 0x000015de,
+ 0x000015de, 0x000015de, 0x000015de, 0x000015de,
+ 0x000015de, 0x000015de, 0x000015de, 0x000015de,
+ 0x000015de, 0x000015de, 0x000015de, 0x000015de,
+ 0x000015de, 0x000015de, 0x000015de, 0x000015de,
+ 0x000015de, 0x000015de, 0x000015de, 0x000015de,
+ // Entry E0 - FF
+ 0x000015de, 0x000015de, 0x000015de, 0x000015de,
+ 0x000015de, 0x000015de, 0x000015de, 0x000015de,
+ 0x000015de, 0x000015de, 0x000015de, 0x000015de,
+ 0x000015de, 0x000015de, 0x000015de, 0x000015de,
+ 0x000015de, 0x000015de, 0x000015de, 0x000015de,
+ 0x000015de, 0x000015de, 0x000015de, 0x000015de,
+ 0x000015de, 0x000015de, 0x000015de, 0x000015de,
+ 0x000015de, 0x000015de, 0x000015de, 0x000015de,
+ // Entry 100 - 11F
+ 0x000015de, 0x000015de, 0x000015de, 0x000015de,
+ 0x000015de, 0x000015de, 0x000015de, 0x000015de,
+ 0x000015de, 0x000015de, 0x000015de, 0x000015de,
+ 0x000015de, 0x000015de, 0x000015de, 0x000015de,
+ 0x000015de, 0x000015de, 0x000015de, 0x000015de,
+ 0x000015de, 0x000015de,
+} // Size: 1136 bytes
+
+const slData string = "" + // Size: 5598 bytes
+ "\x02Napaka\x02(brez argumenta): povzdigni na skrbniške pravice in namest" +
+ "i skrbniško storitev\x02Uporaba: %[1]s [\x0a%[2]s]\x02Možnosti ukazne vr" +
+ "stice\x02Napaka pri določanju ali proces teče kot WOW64: %[1]v\x02Na tem" +
+ "u računalniku morate uporabiti enako-arhitekturno različico WireGuarda." +
+ "\x02Napaka pri odpiranju žetona trenutnega procesa: %[1]v\x02WireGuard l" +
+ "ahko uporabljajo samo uporabniki, ki so člani vgrajene skupine %[1]s." +
+ "\x02WireGuard je zagnan, vendar je up. vmesnik dostopen samo z namizij u" +
+ "porabnikov članov skupine %[1]s.\x02Zdaj\x02Sistemska ura prevrtena naza" +
+ "j!\x14\x01\x81\x01\x00\x04\x0b\x02%[1]d leta\x02\x0b\x02%[1]d leto\x03" +
+ "\x0b\x02%[1]d leti\x00\x0a\x02%[1]d let\x14\x01\x81\x01\x00\x04\x0a\x02%" +
+ "[1]d dni\x02\x0a\x02%[1]d dan\x03\x0c\x02%[1]d dneva\x00\x0a\x02%[1]d dn" +
+ "i\x14\x01\x81\x01\x00\x04\x0a\x02%[1]d ure\x02\x0a\x02%[1]d uro\x03\x0a" +
+ "\x02%[1]d uri\x00\x09\x02%[1]d ur\x14\x01\x81\x01\x00\x04\x0d\x02%[1]d m" +
+ "inute\x02\x0d\x02%[1]d minuto\x03\x0d\x02%[1]d minuti\x00\x0c\x02%[1]d m" +
+ "inut\x14\x01\x81\x01\x00\x04\x0e\x02%[1]d sekunde\x02\x0e\x02%[1]d sekun" +
+ "do\x03\x0e\x02%[1]d sekundi\x00\x0d\x02%[1]d sekund\x02%[1]s nazaj\x02%[" +
+ "1]d\u00a0B\x02%.2[1]f\u00a0KiB\x02%.2[1]f\u00a0MiB\x02%.2[1]f\u00a0GiB" +
+ "\x02%.2[1]f\u00a0TiB\x02%[1]s: %[2]q\x02Napačen naslov IP\x02Napačna dol" +
+ "žina predpone omrežja\x02Pri končni točki manjkajo vrata\x02Pri končni " +
+ "točki je gostitelj napačen\x02Napačen MTU\x02Napačna vrata\x02Napačno tr" +
+ "ajno ohranjanje povezave\x02Napačen ključ: %[1]v\x02Številka mora biti š" +
+ "tevilo med 0 in 2^64-1: %[1]v\x02Dve zaporedni vejici\x02Ime tunela ni v" +
+ "eljavno\x02[ni navedeno]\x02Vsi vrstniki morajo imeti javni ključ\x02Nap" +
+ "aka pri branju konfiguracije\x02, \x02 \x02O WireGuardu\x02Slika WireGua" +
+ "rdovega logotipa\x02Zapri\x02♥ &Doniraj!\x02Status:\x02&Deaktiviraj\x02&" +
+ "Aktiviraj\x02Javni ključ:\x02Vrata poslušanja:\x02MTU:\x02Naslovi:\x02St" +
+ "režniki DNS:\x02Ključ v skupni rabi:\x02Dovoljeni IP-ji:\x02Končna točka" +
+ ":\x02Trajno ohranjanje povezave:\x02Zadnje rokovanje:\x02Neaktivno\x02Se" +
+ " deaktivira\x02Neznano stanje\x02Dnevnik\x02&Kopiraj\x02&Izberi vse\x02&" +
+ "Shrani v datoteko\u00a0…\x02Čas\x02Sporočilo v dnevniku\x02Izvozi dnevni" +
+ "k v datoteko\x02O WireGu&ardu\u00a0…\x02Napaka tunela\x02%[1]s\x0a\x0aDo" +
+ "datne informacije najdete v dnevniku.\x02%[1]s (neposodobljen)\x02Napaka" +
+ " pri izhodu iz WireGuarda\x02Posodobitev WireGuarda je na voljo. Zelo pr" +
+ "iporočamo posodobitev brez odlašanja.\x02Posodobi zdaj\x02Napaka: %[1]v." +
+ " Poskusite ponovno.\x02Status: Končano!\x02Ikona WireGuarda se po 30 sek" +
+ "undah ni pojavila v sistemski vrstici.\x02Oklepaji morajo vsebovati nasl" +
+ "ov IPv6\x02Dekodirani ključi morajo biti natanko 32 bajtov\x02Vrstica mo" +
+ "ra biti v odseku\x02Ključu v konfiguraciji manjka ločilo enačaj\x02Ključ" +
+ " mora imeti vrednost\x02Napačen ključ za odsek [Interface]\x02Napačen kl" +
+ "juč za odsek [Peer]\x02Vmesnik mora imeti zasebni ključ\x02Napačen ključ" +
+ " za odsek vmesnika\x02Verzija protokola mora biti 1\x02Napačen ključ za " +
+ "odsek vrstnika\x02Skripta:\x02Prenos:\x02pred-aktivacijo\x02po-aktivacij" +
+ "i\x02pred-deaktivacijo\x02po-deaktivaciji\x02onemogočeno, zaradi politik" +
+ "e\x02omogočeno\x02%[1]s prejeto, %[2]s poslano\x02Napaka pri določanju s" +
+ "tanja tunela\x02Napaka pri aktiviranju tunela\x02Napaka pri deaktiviranj" +
+ "u tunela\x02Vmesnik: %[1]s\x02Vrstnik\x02Ustvari nov tunel\x02Uredi tune" +
+ "l\x02&Ime:\x02&Javni ključ:\x02(neznano)\x02&Blokiraj promet izven tunel" +
+ "a (varovalka)\x02&Shrani\x02Prekliči\x02&Konfiguracija:\x02Napačno ime" +
+ "\x02Ime je obvezno.\x02Ime tunela »%[1]s« ni veljavno.\x02Napaka pri pri" +
+ "pravi seznama obstoječih tunelov\x02Tunel že obstaja\x02Drug tunel z ime" +
+ "nom »%[1]s« že obstaja.\x02Napaka pri izdelavi nove konfiguracije\x02Nap" +
+ "aka pri pisanju v datoteko\x02Datoteka »%[1]s« že obstaja.\x0a\x0aAli jo" +
+ " želite prepisati?\x02Aktivno\x02Se aktivira\x02Tekstovne datoteke (*.tx" +
+ "t)|*.txt|Vse datoteke (*.*)|*.*\x02Napaka zaznavanja WireGuarda\x02Čakan" +
+ "je, da se pojavi WireGuardovo okno, ni možno: %[1]v\x02WireGuard: Deakti" +
+ "viran\x02Status: Neznan\x02Naslovi: Brez\x02&Upravljaj tunele\u00a0…\x02" +
+ "&Uvozi tunel(e) iz datoteke…\x02I&zhod\x02&Tuneli\x02WireGuard aktiviran" +
+ "\x02Tunel %[1]s je bil aktiviran.\x02WireGuard deaktiviran\x02Tunel %[1]" +
+ "s je bil deaktiviran.\x02Napaka tunela WireGuard\x02WireGuard: %[1]s\x02" +
+ "Status: %[1]s\x02Naslovi: %[1]s\x02Na voljo je posodobitev!\x02Posodobit" +
+ "ev WireGuarda je na voljo\x02Posodobitev WireGuarda je na voljo. Svetuje" +
+ "mo posodobitev čim prej.\x02Tuneli\x02&Uredi\x02Dodaj &prazen tunel" +
+ "\u00a0…\x02Dodaj tunel\x02Odstrani izbrane tunele\x02Izvozi vse tunele v" +
+ " zip\x02&Preklopi\x02Izvozi vse tunele v &zip\u00a0…\x02Uredi &izbran tu" +
+ "nel\u00a0…\x02Odst&rani izbrane tunele\x02ni najdenih konfiguracijskih d" +
+ "atotek\x02Napaka pri uvozu izbrane konfiguracije: %[1]v\x02Napaka pri pr" +
+ "eštevanju obstoječih tunelov: %[1]v\x02Tunel z imenom »%[1]s« že obstaja" +
+ "\x02Napaka pri uvozu konfiguracije: %[1]v\x02Uvoženi tuneli\x14\x01\x81" +
+ "\x01\x00\x04\x16\x02Uvoženi %[1]d tuneli\x02\x14\x02Uvožen %[1]d tunel" +
+ "\x03\x16\x02Uvožena %[1]d tunela\x00\x17\x02Uvoženo %[1]d tunelov\x14" +
+ "\x02\x80\x01\x04 \x02Uvoženi %[1]d od %[2]d tunelov\x02\x1f\x02Uvožen %[" +
+ "1]d od %[2]d tunelov\x03 \x02Uvožena %[1]d od %[2]d tunelov\x00 \x02Uvož" +
+ "eno %[1]d od %[2]d tunelov\x02Tunela ni bilo mogoče ustvariti\x14\x01" +
+ "\x81\x01\x00\x04\x16\x02Izbriši %[1]d tunele\x02\x15\x02Izbriši %[1]d tu" +
+ "nel\x03\x16\x02Izbriši %[1]d tunela\x00\x17\x02Izbriši %[1]d tunelov\x14" +
+ "\x01\x81\x01\x00\x048\x02Ali ste prepričani, da želite izbrisati %[1]d t" +
+ "unele?\x027\x02Ali ste prepričani, da želite izbrisati %[1]d tunel?\x038" +
+ "\x02Ali ste prepričani, da želite izbrisati %[1]d tunela?\x009\x02Ali st" +
+ "e prepričani, da želite izbrisati %[1]d tunelov?\x02Izbriši tunel ‘%[1]s" +
+ "’\x02Ali ste prepričani, da želite izbrisati tunel »%[1]s«?\x02%[1]s T" +
+ "ega dejanja ne morete razveljaviti.\x02Napaka pri izbrisu tunela\x02Napa" +
+ "ka pri odstranjevanju tunela: %[1]s\x02Napaka pri izbrisu tunelov\x14" +
+ "\x01\x81\x01\x00\x04*\x02%[1]d tunelov ni bilo mogoče odstraniti.\x02)" +
+ "\x02%[1]d tunela ni bilo mogoče odstraniti.\x03*\x02%[1]d tunelov ni bil" +
+ "o mogoče odstraniti.\x00*\x02%[1]d tunelov ni bilo mogoče odstraniti." +
+ "\x02Konfiguracijske datoteke (*.zip, *.conf)|*.zip;*.conf|Vse datoteke (" +
+ "*.*)|*.*\x02Uvozi tunele iz datoteke\x02Konfiguracijske datoteke ZIP (*." +
+ "zip)|*.zip\x02Izvozi tunele v datoteko zip\x02%[1]s (nepodpisane različi" +
+ "ce, brez posodobitev)\x02Storitve ni bilo mogoče zaustaviti, ker: %[1]v." +
+ " Poskusite zaustaviti WireGuard z uporabo programa Storitve.\x02Status: " +
+ "Čaka na uporabnika\x02Status: Čaka na servis za posodobitev"
+
+var trIndex = []uint32{ // 278 elements
+ // Entry 0 - 1F
+ 0x00000000, 0x00000005, 0x0000004c, 0x00000066,
+ 0x00000082, 0x000000c6, 0x0000010b, 0x00000136,
+ 0x0000018e, 0x0000022f, 0x00000236, 0x00000257,
+ 0x00000276, 0x00000295, 0x000002b4, 0x000002d7,
+ 0x000002fa, 0x00000306, 0x0000030f, 0x0000031c,
+ 0x00000329, 0x00000336, 0x00000343, 0x00000350,
+ 0x00000364, 0x00000383, 0x0000039f, 0x000003b3,
+ 0x000003c1, 0x000003d0, 0x000003f3, 0x0000040c,
+ // Entry 20 - 3F
+ 0x00000441, 0x00000456, 0x00000472, 0x00000490,
+ 0x000004bd, 0x000004ed, 0x000004f0, 0x000004f3,
+ 0x00000507, 0x0000051c, 0x00000522, 0x00000535,
+ 0x0000053c, 0x00000552, 0x00000560, 0x00000570,
+ 0x0000057f, 0x00000584, 0x0000058e, 0x0000059f,
+ 0x000005c1, 0x000005d7, 0x000005e2, 0x000005f9,
+ 0x0000060f, 0x0000061c, 0x00000639, 0x0000064a,
+ 0x00000653, 0x00000660, 0x00000670, 0x00000687,
+ // Entry 40 - 5F
+ 0x0000068d, 0x0000069e, 0x000006c0, 0x000006dc,
+ 0x000006eb, 0x00000728, 0x0000073d, 0x00000759,
+ 0x000007bb, 0x000007cc, 0x000007f2, 0x00000806,
+ 0x00000842, 0x0000086b, 0x0000089d, 0x000008ba,
+ 0x000008f1, 0x00000913, 0x00000941, 0x0000096a,
+ 0x00000998, 0x000009c0, 0x000009e3, 0x00000a06,
+ 0x00000a18, 0x00000a22, 0x00000a35, 0x00000a49,
+ 0x00000a62, 0x00000a7c, 0x00000a91, 0x00000a97,
+ // Entry 60 - 7F
+ 0x00000ab9, 0x00000ad5, 0x00000af0, 0x00000b15,
+ 0x00000b25, 0x00000b29, 0x00000b3e, 0x00000b4f,
+ 0x00000b57, 0x00000b68, 0x00000b75, 0x00000ba8,
+ 0x00000bb0, 0x00000bb7, 0x00000bc8, 0x00000bd7,
+ 0x00000be9, 0x00000c0c, 0x00000c2c, 0x00000c40,
+ 0x00000c6e, 0x00000c94, 0x00000ca7, 0x00000ce6,
+ 0x00000cec, 0x00000cff, 0x00000d36, 0x00000d4f,
+ 0x00000d83, 0x00000d9c, 0x00000dae, 0x00000dbc,
+ // Entry 80 - 9F
+ 0x00000dd6, 0x00000dfd, 0x00000e08, 0x00000e17,
+ 0x00000e27, 0x00000e47, 0x00000e6c, 0x00000e96,
+ 0x00000eaf, 0x00000ec0, 0x00000ecd, 0x00000edd,
+ 0x00000ef1, 0x00000f10, 0x00000f6d, 0x00000f77,
+ 0x00000f7d, 0x00000f96, 0x00000fa2, 0x00000fbe,
+ 0x00000fe6, 0x00000ff2, 0x0000101e, 0x0000103c,
+ 0x00001059, 0x0000107d, 0x000010b0, 0x000010df,
+ 0x0000110c, 0x00001137, 0x00001152, 0x00001197,
+ // Entry A0 - BF
+ 0x000011e7, 0x00001200, 0x0000122d, 0x0000129c,
+ 0x000012b6, 0x000012f1, 0x00001316, 0x00001329,
+ 0x0000134c, 0x00001362, 0x000013a9, 0x000013f8,
+ 0x00001417, 0x00001441, 0x00001464, 0x0000148e,
+ 0x000014f9, 0x00001517, 0x00001542, 0x00001542,
+ 0x00001542, 0x00001542, 0x00001542, 0x00001542,
+ 0x00001542, 0x00001542, 0x00001542, 0x00001542,
+ 0x00001542, 0x00001542, 0x00001542, 0x00001542,
+ // Entry C0 - DF
+ 0x00001542, 0x00001542, 0x00001542, 0x00001542,
+ 0x00001542, 0x00001542, 0x00001542, 0x00001542,
+ 0x00001542, 0x00001542, 0x00001542, 0x00001542,
+ 0x00001542, 0x00001542, 0x00001542, 0x00001542,
+ 0x00001542, 0x00001542, 0x00001542, 0x00001542,
+ 0x00001542, 0x00001542, 0x00001542, 0x00001542,
+ 0x00001542, 0x00001542, 0x00001542, 0x00001542,
+ 0x00001542, 0x00001542, 0x00001542, 0x00001542,
+ // Entry E0 - FF
+ 0x00001542, 0x00001542, 0x00001542, 0x00001542,
+ 0x00001542, 0x00001542, 0x00001542, 0x00001542,
+ 0x00001542, 0x00001542, 0x00001542, 0x00001542,
+ 0x00001542, 0x00001542, 0x00001542, 0x00001542,
+ 0x00001542, 0x00001542, 0x00001542, 0x00001542,
+ 0x00001542, 0x00001542, 0x00001542, 0x00001542,
+ 0x00001542, 0x00001542, 0x00001542, 0x00001542,
+ 0x00001542, 0x00001542, 0x00001542, 0x00001542,
+ // Entry 100 - 11F
+ 0x00001542, 0x00001542, 0x00001542, 0x00001542,
+ 0x00001542, 0x00001542, 0x00001542, 0x00001542,
+ 0x00001542, 0x00001542, 0x00001542, 0x00001542,
+ 0x00001542, 0x00001542, 0x00001542, 0x00001542,
+ 0x00001542, 0x00001542, 0x00001542, 0x00001542,
+ 0x00001542, 0x00001542,
+} // Size: 1136 bytes
+
+const trData string = "" + // Size: 5442 bytes
+ "\x02Hata\x02(argüman verilmediyse): gerekli izinleri al ve yönetim hizme" +
+ "tini kur\x02Kullanım: %[1]s [\x0a%[2]s]\x02Komut Satırı Seçenekleri\x02İ" +
+ "şlemin WOW64 altında çalıştığından emin olunamadı: %[1]v\x02Bu bilgisay" +
+ "arda WireGuard'ın yerel sürümünü kullanmanız gerek.\x02Şu anki işlem jet" +
+ "onu açılamadı: %[1]v\x02WireGuard sadece sisteme yerleşik %[1]s grubunun" +
+ " üyeleri tarafından kullanılabilir.\x02WireGuard çalışıyor, fakat kullan" +
+ "ıcı arayüzü sadece sisteme yerleşik %[1]s grubunun üyesi olan kullanıcı" +
+ "lar tarafından masaüstünde erişilebilir.\x02Şimdi\x02Sistem saati geriye" +
+ " sarılmış!\x14\x01\x81\x01\x00\x02\x0b\x02%[1]d yıl\x00\x0b\x02%[1]d yıl" +
+ "\x14\x01\x81\x01\x00\x02\x0b\x02%[1]d gün\x00\x0b\x02%[1]d gün\x14\x01" +
+ "\x81\x01\x00\x02\x0b\x02%[1]d saat\x00\x0b\x02%[1]d saat\x14\x01\x81\x01" +
+ "\x00\x02\x0d\x02%[1]d dakika\x00\x0d\x02%[1]d dakika\x14\x01\x81\x01\x00" +
+ "\x02\x0d\x02%[1]d saniye\x00\x0d\x02%[1]d saniye\x02%[1]s önce\x02%[1]d" +
+ "\u00a0B\x02%.2[1]f\u00a0KiB\x02%.2[1]f\u00a0MiB\x02%.2[1]f\u00a0GiB\x02%" +
+ ".2[1]f\u00a0TiB\x02%[1]s: %[2]q\x02Geçersiz IP adresi\x02Ağ öneki uzunlu" +
+ "ğu geçersiz\x02Uç nokta port ayarı eksik\x02Geçersiz uç nokta\x02Geçers" +
+ "iz MTU\x02Geçersiz port\x02Geçersiz kalıcı açık bırakma\x02Geçersiz anah" +
+ "tar: %[1]v\x02Sayı 0 ve 2^64-1 arasında bir sayı olmalı: %[1]v\x02Yan ya" +
+ "na iki virgül\x02Tünel adı geçerli değil\x02[hiçbir şey belirtilmemiş}" +
+ "\x02Tüm eşler açık anahtarlara sahip olmalı\x02Yapılandırma bilgisi alın" +
+ "ırken hata oluştu\x02, \x02, \x02WireGuard Hakkında\x02WireGuard logo r" +
+ "esmi\x02Kapat\x02♥ &Bağış yap!\x02Durum:\x02&Devre dışı bırak\x02&Etkinl" +
+ "eştir\x02Açık anahtar:\x02Dinlenen port:\x02MTU:\x02Adresler:\x02DNS sun" +
+ "ucuları:\x02Önceden paylaşılmış anahtar:\x02İzin verilen IP'ler:\x02Uç n" +
+ "okta:\x02Kalıcı açık tutma:\x02En son el sıkışma:\x02Etkin değil\x02Devr" +
+ "e dışı bırakılıyor\x02Durum bilinmiyor\x02Günlük\x02Kopyala (&c)\x02&tüm" +
+ "ünü seç\x02Dosyaya kaydet (&s)…\x02Zaman\x02Günlük mesajı\x02Günlük dos" +
+ "yasını dışa aktar\x02WireGuard Hakkında (&a)…\x02Tünel Hatası\x02%[1]s" +
+ "\x0a\x0aLütfen daha fazla bilgi için günlüğe göz atın.\x02%[1]s (eski sü" +
+ "rüm)\x02WireGuard Çıkış Hatası\x02WireGuard için bir güncelleme mevcut. " +
+ "Bekletmeden güncelleme yapmanız önemle tavsiye edilir.\x02Şimdi Güncelle" +
+ "\x02Hata: %[1]v. Lütfen yeniden deneyin.\x02Durum: Tamamlandı!\x02WireGu" +
+ "ard sistem tepsisi ikonu 30 saniye sonunda belirmedi.\x02Parantezler bir" +
+ " IPv6 adresi içermelidir\x02Anahtarlar çözüldüğünde tam 32 byte olmalı" +
+ "\x02Satır bir bölümde olmalı\x02Yapılandırma anahtarında eşittir operatö" +
+ "rü eksik\x02Anahtar bir değere sahip olmalı\x02[Interface] bölümü için g" +
+ "eçersiz anahtar\x02[Peer] bölümü için geçersiz anahtar\x02Bir arabirim g" +
+ "izli anahtara sahip olmalıdır\x02Arabirim bölümünde geçersiz anahtar\x02" +
+ "Protokol sürümü 1 olmak zorunda\x02Eş bölümünde geçersiz anahtar\x02Komu" +
+ "t dosyaları:\x02Aktarım:\x02bağlantı-öncesi\x02bağlantı-sonrası\x02bağla" +
+ "ntı-kesme-öncesi\x02bağlantı-kesme-sonrası\x02ilke gereği kapalı\x02etki" +
+ "n\x02%[1]s alındı, %[2]s gönderildi\x02Tünel durumu belirlenemedi\x02Tün" +
+ "el etkinleştirilemedi\x02Tünel devre dışı bırakılamadı\x02Arabirim: %[1]" +
+ "s\x02Eş\x02Yeni tünel oluştur\x02Tüneli düzenle\x02&İsim:\x02&Açık anaht" +
+ "ar:\x02(bilinmiyor)\x02&Tünelden geçmeyen trafiği durdur (kill-switch)" +
+ "\x02&Kaydet\x02İptal\x02&Yapılandırma:\x02Geçersiz isim\x02Bir isim gere" +
+ "kli.\x02`%[1]s` geçersiz bir tünel ismi.\x02Mevcut tüneller listelenemiy" +
+ "or\x02Tünel zaten mevcut\x02‘%[1]s’ adında başka bir tünel mevcut.\x02Ye" +
+ "ni yapılandırma oluşturulamıyor\x02Dosya yazılamadı\x02`%[1]s` dosyası z" +
+ "aten mevcut.\x0a\x0aÜzerine yazmak ister misiniz?\x02Etkin\x02Etkinleşti" +
+ "riliyor\x02Metin Dosyaları (*.txt)|*.txt|Tüm Dosyalar (*.*)|*.*\x02WireG" +
+ "uard Tespit Hatası\x02WireGuard penceresinin belirmesi beklenemedi: %[1]" +
+ "v\x02WireGuard: Devre dışı\x02Durum: Bilinmiyor\x02Adresler: Yok\x02Tüne" +
+ "lleri yönet (&m)…\x02Dosyadan tünelleri içe aktar (&i)…\x02Çık (&x)\x02T" +
+ "üneller (&t)\x02WireGuard Etkin\x02%[1]s tüneli etkinleştirildi.\x02Wir" +
+ "eGuard Devre Dışı Bırakıldı\x02%[1]s tüneli devre dışı bırakıldı.\x02Wir" +
+ "eGuard Tünel Hatası\x02WireGuard: %[1]s\x02Durum: %[1]s\x02Adresler: %[1" +
+ "]s\x02Güncelleme Mevcut!\x02WireGuard Güncellemesi Mevcut\x02WireGuard i" +
+ "çin bir güncelleme mevcut. İlk fırsatta güncelleme yapmanız tavsiye edi" +
+ "lir.\x02Tüneller\x02&Edit\x02Boş tünel ekle (&e)…\x02Tünel Ekle\x02Seçil" +
+ "en tünelleri kaldır\x02Tüm tünelleri zip olarak dışa aktar\x02&Değiştir" +
+ "\x02Tüm tünelleri &zip olarak dışa aktar…\x02&Seçilen tüneli düzenle…" +
+ "\x02S&eçilen tünelleri kaldır\x02yapılandırma dosyası bulunamadı\x02Seçi" +
+ "len yapılandırma içe aktarılamadı: %[1]v\x02Mevcut tüneller numaralandır" +
+ "ılamadı: %[1]v\x02‘%[1]s’ adında başka bir tünel mevcut\x02Yapılandırma" +
+ " içe aktarılamıyor: %[1]v\x02Tüneller içe aktarıldı\x14\x01\x81\x01\x00" +
+ "\x02\x1e\x02%[1]d tünel içe aktarıldı\x00\x1e\x02%[1]d tünel içe aktarıl" +
+ "dı\x14\x02\x80\x01\x02$\x02%[2]d/%[1]d tünel içe aktarıldı\x00$\x02%[2]d" +
+ "/%[1]d tünel içe aktarıldı\x02Tünel oluşturulamıyor\x14\x01\x81\x01\x00" +
+ "\x02\x12\x02%[1]d tüneli sil\x00\x12\x02%[1]d tüneli sil\x14\x01\x81\x01" +
+ "\x00\x023\x02%[1]d tüneli silmek istediğinizden emin misiniz?\x003\x02%[" +
+ "1]d tüneli silmek istediğinizden emin misiniz?\x02‘%[1]s’ tünelini sil" +
+ "\x02‘%[1]s’ tünelini silmek istediğinizden emin misiniz?\x02%[1]s Bu işl" +
+ "emi geri alamazsınız.\x02Tünel silinemiyor\x02Bir tünel kaldırılamadı: %" +
+ "[1]s\x02Tüneller silinemiyor\x14\x01\x81\x01\x00\x02\x1f\x02%[1]d tünel " +
+ "kaldırılamadı.\x00\x1f\x02%[1]d tünel kaldırılamadı.\x02Yapılandırma Dos" +
+ "yaları (*.zip, *.conf)|*.zip;*.conf|Tüm Dosyalar (*.*)|*.*\x02Tünelleri " +
+ "dosyadan içe aktar\x02Yapılandırma ZIP Dosyası (*.zip)|*.zip\x02Tüneller" +
+ "i zip olarak dışa aktar\x02%[1]s (imzasız derleme, güncelleme yok)\x02Hi" +
+ "zmet şu nedenden dolayı kapatılamıyor: %[1]v. WireGuard'ı hizmet yönetic" +
+ "isinden durdurabilirsiniz.\x02Durum: Kullanıcı bekleniyor\x02Durum: Günc" +
+ "elleştirme hizmeti bekleniyor"
+
+var ukIndex = []uint32{ // 278 elements
+ // Entry 0 - 1F
+ 0x00000000, 0x0000000f, 0x0000008e, 0x000000b7,
+ 0x000000ea, 0x00000144, 0x000001c2, 0x0000021b,
+ 0x000002b7, 0x00000339, 0x00000344, 0x00000397,
+ 0x00000397, 0x00000397, 0x00000397, 0x00000397,
+ 0x00000397, 0x000003a6, 0x000003af, 0x000003bc,
+ 0x000003c9, 0x000003d6, 0x000003e3, 0x000003f0,
+ 0x00000411, 0x0000044d, 0x00000488, 0x000004c0,
+ 0x000004d7, 0x000004f3, 0x00000523, 0x00000546,
+ // Entry 20 - 3F
+ 0x00000592, 0x000005b1, 0x000005de, 0x00000603,
+ 0x0000064f, 0x00000691, 0x00000694, 0x00000697,
+ 0x000006a8, 0x000006d8, 0x000006e7, 0x00000706,
+ 0x00000714, 0x0000072e, 0x00000744, 0x00000761,
+ 0x0000076b, 0x00000770, 0x0000077e, 0x00000792,
+ 0x000007a6, 0x000007ca, 0x000007d4, 0x000007ea,
+ 0x00000815, 0x00000815, 0x00000815, 0x00000815,
+ 0x00000815, 0x00000815, 0x00000815, 0x00000815,
+ // Entry 40 - 5F
+ 0x00000815, 0x00000815, 0x00000815, 0x00000815,
+ 0x00000815, 0x00000815, 0x00000815, 0x00000815,
+ 0x00000815, 0x00000815, 0x00000815, 0x00000815,
+ 0x0000086c, 0x000008a7, 0x000008e9, 0x0000092f,
+ 0x0000098a, 0x000009bc, 0x000009f4, 0x00000a27,
+ 0x00000a6e, 0x00000ab5, 0x00000aef, 0x00000b22,
+ 0x00000b32, 0x00000b44, 0x00000b60, 0x00000b7a,
+ 0x00000b96, 0x00000bb0, 0x00000bed, 0x00000c00,
+ // Entry 60 - 7F
+ 0x00000c35, 0x00000c72, 0x00000ca8, 0x00000ce2,
+ 0x00000cfc, 0x00000d03, 0x00000d2c, 0x00000d4e,
+ 0x00000d5b, 0x00000d79, 0x00000d8e, 0x00000dc7,
+ 0x00000dd9, 0x00000dec, 0x00000e07, 0x00000e1a,
+ 0x00000e43, 0x00000e76, 0x00000ebd, 0x00000edc,
+ 0x00000f15, 0x00000f15, 0x00000f15, 0x00000f15,
+ 0x00000f15, 0x00000f15, 0x00000f15, 0x00000f15,
+ 0x00000f15, 0x00000f15, 0x00000f15, 0x00000f15,
+ // Entry 80 - 9F
+ 0x00000f15, 0x00000f15, 0x00000f15, 0x00000f15,
+ 0x00000f15, 0x00000f15, 0x00000f15, 0x00000f15,
+ 0x00000f15, 0x00000f15, 0x00000f15, 0x00000f15,
+ 0x00000f15, 0x00000f15, 0x00000f15, 0x00000f15,
+ 0x00000f15, 0x00000f15, 0x00000f15, 0x00000f15,
+ 0x00000f15, 0x00000f15, 0x00000f15, 0x00000f15,
+ 0x00000f15, 0x00000f51, 0x00000fab, 0x00000ffb,
+ 0x00001033, 0x0000107e, 0x000010a2, 0x0000115b,
+ // Entry A0 - BF
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ // Entry C0 - DF
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ // Entry E0 - FF
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ // Entry 100 - 11F
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ 0x0000115b, 0x0000115b, 0x0000115b, 0x0000115b,
+ 0x0000115b, 0x0000115b,
+} // Size: 1136 bytes
+
+const ukData string = "" + // Size: 4443 bytes
+ "\x02Помилка\x02(немає аргумента): отримати права аднімістратора і встано" +
+ "вити службу\x02Використання: %[1]s [\x0a%[2]s]\x02Параметри командного " +
+ "рядка\x02Неможливо визначити, чи працює процес під WOW64: %[1]v\x02Ви п" +
+ "овинні використовувати нативну версію WireGuard на цьому комп'ютері." +
+ "\x02Не вдалося відкрити токен поточного процесу: %[1]v\x02WireGuard може" +
+ " бути використаний тільки користувачами, які є членами вбудованих %[1]s " +
+ "груп.\x02WireGuard запущено, але UI доступний лише з комп\x22ютерів вбу" +
+ "дованої %[1]s групи.\x02Зараз\x02Системний годинник налаштований некоре" +
+ "ктно!\x02%[1]s тому\x02%[1]d Б\x02%.2[1]f КБ\x02%.2[1]f МБ\x02%.2[1]f Г" +
+ "Б\x02%.2[1]f ТБ\x02%[1]s: %[2]q\x02Недійсна IP-адреса\x02Невірна довжин" +
+ "а префіксу мережі\x02Відсутній порт з кінцевої точки\x02Недійсний хост " +
+ "кінцевої точки\x02Недійсний MTU\x02Недійсний порт\x02Некоректне значенн" +
+ "я keepalive\x02Недійсний ключ: %[1]v\x02Номер повинен бути числом від 0" +
+ " до 2^64-1: %[1]v\x02Дві коми поспіль\x02Назва тунелю некоректна\x02[жод" +
+ "ного не вказано]\x02Всі учасники повинні мати відкриті ключі\x02Помилка" +
+ " при отриманні конфігурації\x02, \x02, \x02Про WireGuard\x02Зображення л" +
+ "оготипу WireGuard\x02Закрити\x02♥ &Пожертвувати!\x02Статус:\x02&Деактив" +
+ "увати\x02&Активувати\x02Відкритий ключ:\x02Порт:\x02MTU:\x02Адреси:\x02" +
+ "DNS-сервери:\x02Preshared ключ:\x02Дозволені IP адреси:\x02Endpoint:\x02" +
+ "Persistent keepalive:\x02Останнє рукостискання:\x02Значок системи WireGu" +
+ "ard не з'явився через 30 секунд.\x02Дужки повинні містити адресу IPv6" +
+ "\x02Ключ повинен декодуватись до 32 байт\x02Рядок повинен бути вказаним " +
+ "у розділі\x02Ключ конфігурації відсутній роздільник рівності\x02Ключ по" +
+ "винен мати значення\x02Хибний ключ для [Interface] розділу\x02Хибний кл" +
+ "юч для [Peer] розділу\x02Інтерфейс повинен мати особистий ключ\x02Недій" +
+ "сний ключ для розділу інтерфейсу\x02Версія протоколу повинна бути 1\x02" +
+ "Хибний ключ для [Peer] розділу\x02Скрипти:\x02Передано:\x02перед-запуск" +
+ "ом\x02після-запуску\x02перед-зупинкою\x02після-зупинки\x02вимкнено, від" +
+ "повідно до політики\x02увімкнено\x02%[1]s отримано, %[2]s відправлено" +
+ "\x02Не вдалося визначити стан тунелю\x02Не вдалося активувати тунель\x02" +
+ "Не вдалося деактивувати тунель\x02Інтерфейс: %[1]s\x02Пір\x02Створити н" +
+ "овий тунель\x02Редагувати тунель\x02&Назва:\x02&Публічний ключ:\x02(нев" +
+ "ідомий)\x02&Блокувати трафік поза тунелем\x02&Зберегти\x02Скасувати\x02" +
+ "&Налаштування:\x02Хибне ім'я\x02Необхідно ввести ім'я.\x02Ім'я тунелю '%" +
+ "[1]s' некоректне.\x02Не вдалося відобразити існуючі тунелі\x02Тунель вже" +
+ " існує\x02Тунель з ім'ям ‘%[1]s’ вже існує.\x02не знайдено файлів конфіг" +
+ "урації\x02Не вдалося імпортувати вибрану конфігурацію: %[1]v\x02Не вдал" +
+ "ося перерахувати існуючі тунелі: %[1]v\x02Тунель з ім'ям ‘%[1]s’ вже іс" +
+ "нує\x02Не вдалося імпортувати конфігурацію: %[1]v\x02Імпортовано тунелі" +
+ "\x14\x01\x81\x01\x00\x04*\x02Імпортовано %[1]d тунелі\x05,\x02Імпортован" +
+ "о %[1]d тунелів\x02*\x02Імпортовано %[1]d тунель\x00,\x02Імпортовано %[" +
+ "1]d тунелів"
+
+var viIndex = []uint32{ // 278 elements
+ // Entry 0 - 1F
+ 0x00000000, 0x00000006, 0x00000006, 0x00000006,
+ 0x00000006, 0x00000006, 0x00000006, 0x00000006,
+ 0x00000006, 0x00000006, 0x00000011, 0x00000011,
+ 0x00000023, 0x00000036, 0x00000049, 0x0000005c,
+ 0x0000006f, 0x0000007e, 0x0000007e, 0x0000007e,
+ 0x0000007e, 0x0000007e, 0x0000007e, 0x0000007e,
+ 0x000000a0, 0x000000a0, 0x000000a0, 0x000000a0,
+ 0x000000a0, 0x000000c0, 0x000000c0, 0x000000c0,
+ // Entry 20 - 3F
+ 0x000000c0, 0x000000c0, 0x000000db, 0x000000db,
+ 0x000000db, 0x000000db, 0x000000db, 0x000000db,
+ 0x000000f5, 0x00000104, 0x0000010b, 0x0000010b,
+ 0x0000011a, 0x0000011a, 0x0000011a, 0x0000011a,
+ 0x0000011a, 0x0000011a, 0x0000011a, 0x0000011a,
+ 0x0000011a, 0x0000011a, 0x00000129, 0x00000129,
+ 0x00000129, 0x00000129, 0x00000129, 0x00000129,
+ 0x00000129, 0x00000129, 0x00000129, 0x00000129,
+ // Entry 40 - 5F
+ 0x00000129, 0x00000129, 0x00000129, 0x00000129,
+ 0x00000129, 0x00000129, 0x00000129, 0x00000129,
+ 0x00000129, 0x00000129, 0x00000129, 0x00000129,
+ 0x00000129, 0x00000129, 0x00000129, 0x00000129,
+ 0x00000129, 0x00000129, 0x00000129, 0x00000129,
+ 0x00000129, 0x00000129, 0x00000129, 0x00000129,
+ 0x00000129, 0x00000129, 0x00000129, 0x00000129,
+ 0x00000129, 0x00000129, 0x00000129, 0x0000013b,
+ // Entry 60 - 7F
+ 0x00000155, 0x00000181, 0x0000019f, 0x000001c0,
+ 0x000001c0, 0x000001d3, 0x000001dd, 0x000001ef,
+ 0x000001ef, 0x000001ef, 0x000001ef, 0x000001ef,
+ 0x000001ef, 0x000001f5, 0x000001f5, 0x0000020c,
+ 0x00000224, 0x0000024a, 0x0000026b, 0x00000280,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ // Entry 80 - 9F
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ // Entry A0 - BF
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ // Entry C0 - DF
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ // Entry E0 - FF
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ // Entry 100 - 11F
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad, 0x000002ad, 0x000002ad,
+ 0x000002ad, 0x000002ad,
+} // Size: 1136 bytes
+
+const viData string = "" + // Size: 685 bytes
+ "\x02Lỗi\x02Vừa xong\x14\x01\x81\x01\x00\x00\x0b\x02%[1]d năm\x14\x01\x81" +
+ "\x01\x00\x00\x0c\x02%[1]d ngày\x14\x01\x81\x01\x00\x00\x0c\x02%[1]d giờ" +
+ "\x14\x01\x81\x01\x00\x00\x0c\x02%[1]d phút\x14\x01\x81\x01\x00\x00\x0c" +
+ "\x02%[1]d giây\x02%[1]s trước\x02Địa chỉ IP không hợp lệ\x02Cổng (port) " +
+ "không hợp lệ\x02Tên VPN không hợp lệ\x02Thông tin về WireGuard\x02Logo W" +
+ "ireGuard\x02Đóng\x02Trạng thái:\x02Đầu cuối:\x02đã kích hoạt\x02Nhận %[1" +
+ "]s, gứi %[2]s\x02Không thể xác định tình trạng VPN\x02Không thể kích hoạ" +
+ "t VPN\x02Không thể vô hiệu hóa VPN\x02Mạng ngang hàng\x02Tạo VPN\x02Chỉn" +
+ "h sửa VPN\x02Huỷ\x02Tên không hợp lệ\x02Yêu cầu nhập tên.\x02Tên VPN ‘%[" +
+ "1]s' không hợp lệ.\x02Không thể liệt kê các VPN\x02VPN đã tồn tại\x02Đã " +
+ "tồn tại VPN với tên ‘%[1]s’."
+
+var zh_CNIndex = []uint32{ // 278 elements
+ // Entry 0 - 1F
+ 0x00000000, 0x00000007, 0x00000030, 0x00000047,
+ 0x00000057, 0x0000008b, 0x000000c9, 0x000000ef,
+ 0x00000134, 0x0000018e, 0x00000195, 0x000001ae,
+ 0x000001bf, 0x000001d0, 0x000001e4, 0x000001f8,
+ 0x00000209, 0x00000213, 0x0000021b, 0x00000228,
+ 0x00000235, 0x00000242, 0x0000024f, 0x0000025c,
+ 0x0000026b, 0x00000284, 0x000002a6, 0x000002cd,
+ 0x000002d8, 0x000002e5, 0x000002fe, 0x00000316,
+ // Entry 20 - 3F
+ 0x00000344, 0x0000035d, 0x00000370, 0x0000037c,
+ 0x0000039e, 0x000003b4, 0x000003b8, 0x000003ba,
+ 0x000003cb, 0x000003e1, 0x000003e8, 0x000003f9,
+ 0x00000401, 0x0000040d, 0x00000419, 0x00000421,
+ 0x0000042f, 0x00000434, 0x0000043c, 0x0000044b,
+ 0x0000045c, 0x0000046a, 0x00000472, 0x00000486,
+ 0x0000049a, 0x000004a4, 0x000004b1, 0x000004b8,
+ 0x000004bf, 0x000004cb, 0x000004d7, 0x000004e6,
+ // Entry 40 - 5F
+ 0x000004ed, 0x000004fa, 0x00000507, 0x00000520,
+ 0x0000052d, 0x00000553, 0x00000565, 0x00000580,
+ 0x000005b8, 0x000005c5, 0x000005e2, 0x000005f4,
+ 0x0000062b, 0x00000653, 0x0000067d, 0x00000699,
+ 0x000006be, 0x000006d1, 0x000006f6, 0x00000716,
+ 0x00000732, 0x0000074b, 0x00000763, 0x0000077c,
+ 0x00000784, 0x0000078c, 0x00000796, 0x000007a0,
+ 0x000007aa, 0x000007b4, 0x000007d3, 0x000007dd,
+ // Entry 60 - 7F
+ 0x000007f8, 0x00000811, 0x00000824, 0x0000083d,
+ 0x0000084b, 0x00000852, 0x00000862, 0x0000086f,
+ 0x0000087c, 0x00000889, 0x00000892, 0x000008c1,
+ 0x000008cd, 0x000008d4, 0x000008e1, 0x000008ee,
+ 0x00000904, 0x00000922, 0x0000093b, 0x0000094b,
+ 0x0000096c, 0x00000985, 0x00000998, 0x000009d3,
+ 0x000009dd, 0x000009ea, 0x00000a1c, 0x00000a33,
+ 0x00000a5e, 0x00000a73, 0x00000a82, 0x00000a8e,
+ // Entry 80 - 9F
+ 0x00000aa3, 0x00000ac1, 0x00000acd, 0x00000ad9,
+ 0x00000aed, 0x00000b0b, 0x00000b1f, 0x00000b43,
+ 0x00000b5a, 0x00000b6b, 0x00000b79, 0x00000b87,
+ 0x00000b97, 0x00000ba8, 0x00000bea, 0x00000bf1,
+ 0x00000bfd, 0x00000c15, 0x00000c22, 0x00000c35,
+ 0x00000c58, 0x00000c70, 0x00000c9b, 0x00000cb6,
+ 0x00000cce, 0x00000ce4, 0x00000cfe, 0x00000d1e,
+ 0x00000d4b, 0x00000d65, 0x00000d72, 0x00000d93,
+ // Entry A0 - BF
+ 0x00000dc9, 0x00000ddc, 0x00000dfa, 0x00000e2d,
+ 0x00000e45, 0x00000e6f, 0x00000e8d, 0x00000ea0,
+ 0x00000eba, 0x00000ecd, 0x00000ef4, 0x00000f35,
+ 0x00000f4b, 0x00000f66, 0x00000f89, 0x00000fb6,
+ 0x00001017, 0x0000102c, 0x00001047, 0x00001047,
+ 0x00001047, 0x00001047, 0x00001047, 0x00001047,
+ 0x00001047, 0x00001047, 0x00001047, 0x00001047,
+ 0x00001047, 0x00001047, 0x00001047, 0x00001047,
+ // Entry C0 - DF
+ 0x00001047, 0x00001047, 0x00001047, 0x00001047,
+ 0x00001047, 0x00001047, 0x00001047, 0x00001047,
+ 0x00001047, 0x00001047, 0x00001047, 0x00001047,
+ 0x00001047, 0x00001047, 0x00001047, 0x00001047,
+ 0x00001047, 0x00001047, 0x00001047, 0x00001047,
+ 0x00001047, 0x00001047, 0x00001047, 0x00001047,
+ 0x00001047, 0x00001047, 0x00001047, 0x00001047,
+ 0x00001047, 0x00001047, 0x00001047, 0x00001047,
+ // Entry E0 - FF
+ 0x00001047, 0x00001047, 0x00001047, 0x00001047,
+ 0x00001047, 0x00001047, 0x00001047, 0x00001047,
+ 0x00001047, 0x00001047, 0x00001047, 0x00001047,
+ 0x00001047, 0x00001047, 0x00001047, 0x00001047,
+ 0x00001047, 0x00001047, 0x00001047, 0x00001047,
+ 0x00001047, 0x00001047, 0x00001047, 0x00001047,
+ 0x00001047, 0x00001047, 0x00001047, 0x00001047,
+ 0x00001047, 0x00001047, 0x00001047, 0x00001047,
+ // Entry 100 - 11F
+ 0x00001047, 0x00001047, 0x00001047, 0x00001047,
+ 0x00001047, 0x00001047, 0x00001047, 0x00001047,
+ 0x00001047, 0x00001047, 0x00001047, 0x00001047,
+ 0x00001047, 0x00001047, 0x00001047, 0x00001047,
+ 0x00001047, 0x00001047, 0x00001047, 0x00001047,
+ 0x00001047, 0x00001047,
+} // Size: 1136 bytes
+
+const zh_CNData string = "" + // Size: 4167 bytes
+ "\x02错误\x02(无参数): 提升并安装管理服务\x02用法: %[1]s [\x0a%[2]s]\x02命令行选项\x02无法确定该进程是" +
+ "否在WOW64下运行: %[1]v\x02您必须在此计算机上使用原生版本的 WireGuard。\x02无法打开当前进程令牌: %[1]v" +
+ "\x02WireGuard 可能只能被内建的 %[1]s 小组中的成员使用。\x02WireGuard 正在运行,但用户界面只能从内建的 %[1" +
+ "]s 小组的桌面访问。\x02刚刚\x02系统时间倒退了!\x14\x01\x81\x01\x00\x00\x0a\x02%[1]d 年\x14" +
+ "\x01\x81\x01\x00\x00\x0a\x02%[1]d 天\x14\x01\x81\x01\x00\x00\x0d\x02%[1]d" +
+ " 小时\x14\x01\x81\x01\x00\x00\x0d\x02%[1]d 分钟\x14\x01\x81\x01\x00\x00\x0a" +
+ "\x02%[1]d 秒\x02%[1]s 前\x02%[1]d B\x02%.2[1]f\u00a0KiB\x02%.2[1]f\u00a0Mi" +
+ "B\x02%.2[1]f\u00a0GiB\x02%.2[1]f\u00a0TiB\x02%[1]s: %[2]q\x02IP地址无效\x02网" +
+ "络前缀长度无效\x02对端 (endpoint) 中缺少端口\x02对端主机名 (endpoint host) 无效\x02MTU 无效" +
+ "\x02端口无效\x02连接保活间隔无效\x02无效的密钥:%[1]v\x02数值必须介于 0 至 2^64-1 之间: %[1]v\x02一行" +
+ "中有两个逗号\x02隧道名称无效\x02[未指定]\x02每个节点都必须指定公钥\x02获取配置时出错\x02、\x02 \x02关于 Wi" +
+ "reGuard\x02WireGuard logo 图片\x02关闭\x02♥ 捐助! (&D)\x02状态:\x02断开 (&D)\x02连接" +
+ " (&A)\x02公钥:\x02监听端口:\x02MTU:\x02地址:\x02DNS 服务器:\x02预共享密钥:\x02允许的 IP:" +
+ "\x02对端:\x02连接保活间隔:\x02上次握手时间:\x02已断开\x02正在断开\x02未知\x02日志\x02复制 (&C)\x02全" +
+ "选 (&A)\x02导出… (&S)\x02时间\x02日志消息\x02导出日志\x02关于 WireGuard… (&A)\x02隧道错误" +
+ "\x02%[1]s\x0a\x0a更多信息请查看日志。\x02%[1]s (已过时)\x02退出 WireGuard 时出错\x02发现新版 W" +
+ "ireGuard。强烈建议您现在安装。\x02立即更新\x02错误: %[1]v。请重试。\x02状态: 完成!\x02WireGuard 系统" +
+ "托盘图标在30秒后没有出现。\x02方括号中应包含一个 IPv6 地址\x02解码后的密钥长度必须为32字节\x02行必须出现在段落中" +
+ "\x02配置项必须要有一个等于号\x02必须有一个值\x02[Interface] 段落中的该键无效\x02[Peer] 段落中的该键无效" +
+ "\x02接口必须有一个私钥\x02接口段落的键无效\x02协议版本必须为 1\x02节点段落的键无效\x02脚本:\x02流量:\x02连接前" +
+ "\x02连接后\x02断开前\x02断开后\x02已禁用(依管理策略)\x02已启用\x02接收 %[1]s, 发送 %[2]s\x02无法确认" +
+ "隧道状态\x02无法连接隧道\x02无法断开隧道连接\x02接口: %[1]s\x02节点\x02创建新隧道\x02编辑隧道\x02名称 (" +
+ "&N):\x02公钥 (&P):\x02(未知)\x02拦截未经隧道的流量 (kill-switch) (&B)\x02保存 (&S)\x02取" +
+ "消\x02配置 (&C):\x02名称无效\x02必须输入名称。\x02隧道名「%[1]s」无效。\x02无法列出现有隧道\x02隧道已存在" +
+ "\x02隧道名「%[1]s」已存在。\x02无法创建新的配置\x02写入文件失败\x02文件「%[1]s」已存在。\x0a\x0a您确定要覆盖它" +
+ "吗?\x02已连接\x02正在连接\x02文本文件 (*.txt)|*.txt|所有文件 (*.*)|*.*\x02WireGuard 检测" +
+ "错误\x02无法等待 WireGuard 窗口出现: %[1]v\x02WireGuard: 已断开\x02状态: 未知\x02地址: 无" +
+ "\x02管理隧道… (&M)\x02从文件导入隧道… (&I)\x02退出 (&E)\x02隧道 (&T)\x02WireGuard 已连接" +
+ "\x02隧道「%[1]s」已连接。\x02WireGuard 已断开\x02隧道「%[1]s」已断开连接。\x02WireGuard 隧道错误" +
+ "\x02WireGuard: %[1]s\x02状态: %[1]s\x02地址: %[1]s\x02发现更新!\x02WireGuard 更新" +
+ "\x02新的 WireGuard 版本发布了。强烈建议您现在安装。\x02隧道\x02编辑 (&E)\x02新建空隧道… (&E)\x02新建隧" +
+ "道\x02删除所选隧道\x02导出所有隧道 (ZIP 压缩包)\x02切换连接状态 (&T)\x02导出所有隧道 (ZIP 压缩包)… (&" +
+ "Z)\x02编辑所选隧道… (&E)\x02删除所选隧道 (&R)\x02未找到配置文件\x02无法导入配置: %[1]v\x02无法列出现有隧" +
+ "道: %[1]v\x02另一个同名的隧道「%[1]s」已存在\x02无法导入配置: %[1]v\x02导入隧道\x14\x01\x81" +
+ "\x01\x00\x00\x1a\x02导入了 %[1]d 个隧道\x14\x02\x80\x01\x000\x02导入了 %[2]d 个隧道中" +
+ "的 %[1]d 个隧道\x02无法创建隧道\x14\x01\x81\x01\x00\x00\x17\x02删除 %[1]d 个隧道\x14" +
+ "\x01\x81\x01\x00\x00,\x02您确定要删除这 %[1]d 个隧道吗?\x02删除隧道「%[1]s」\x02您确定要删除隧道「" +
+ "%[1]s」吗?\x02%[1]s此操作无法撤销。\x02无法删除隧道\x02无法删除隧道: %[1]s\x02无法删除隧道\x14\x01" +
+ "\x81\x01\x00\x00 \x02无法删除 %[1]d 个隧道。\x02配置文件 (*.zip, *.conf)|*.zip;*.con" +
+ "f|所有文件 (*.*)|*.*\x02从文件导入隧道\x02配置文件 (*.zip)|*.zip\x02导出配置文件 (ZIP 压缩包)" +
+ "\x02%[1]s (未签名版本,禁用自动更新)\x02无法停止服务: %[1]v。您可能需要在服务管理器中手动停止 WireGuard 服务。" +
+ "\x02状态: 等待用户\x02状态: 等待更新服务"
+
+var zh_TWIndex = []uint32{ // 278 elements
+ // Entry 0 - 1F
+ 0x00000000, 0x00000007, 0x00000037, 0x00000056,
+ 0x00000066, 0x000000a4, 0x000000df, 0x00000110,
+ 0x00000153, 0x000001b8, 0x000001c5, 0x000001de,
+ 0x000001ef, 0x00000200, 0x00000214, 0x00000228,
+ 0x00000239, 0x00000243, 0x0000024c, 0x00000259,
+ 0x00000266, 0x00000273, 0x00000280, 0x0000028f,
+ 0x000002a3, 0x000002c5, 0x000002e4, 0x000002fe,
+ 0x0000030c, 0x0000031c, 0x00000342, 0x0000035b,
+ // Entry 20 - 3F
+ 0x00000384, 0x0000039d, 0x000003b0, 0x000003bc,
+ 0x000003de, 0x000003fa, 0x000003fe, 0x00000400,
+ 0x00000411, 0x00000427, 0x0000042e, 0x00000441,
+ 0x00000448, 0x0000045a, 0x00000466, 0x0000046d,
+ 0x00000477, 0x0000047b, 0x00000482, 0x00000490,
+ 0x000004a0, 0x000004b0, 0x000004ba, 0x000004cb,
+ 0x000004de, 0x000004ee, 0x000004fe, 0x00000505,
+ 0x0000050c, 0x00000518, 0x00000524, 0x00000533,
+ // Entry 40 - 5F
+ 0x0000053a, 0x00000547, 0x00000557, 0x0000056d,
+ 0x0000057a, 0x000005a9, 0x000005be, 0x000005d6,
+ 0x0000062b, 0x00000638, 0x0000065d, 0x00000673,
+ 0x000006af, 0x000006d7, 0x000006f6, 0x00000712,
+ 0x0000073a, 0x00000750, 0x0000076f, 0x00000789,
+ 0x000007af, 0x000007cc, 0x000007e4, 0x000007fc,
+ 0x00000809, 0x00000810, 0x0000081a, 0x00000824,
+ 0x0000082e, 0x00000838, 0x00000850, 0x0000085a,
+ // Entry 60 - 7F
+ 0x0000087c, 0x00000895, 0x000008a8, 0x000008c1,
+ 0x000008d0, 0x000008d7, 0x000008e7, 0x000008fa,
+ 0x00000906, 0x00000912, 0x0000091b, 0x00000950,
+ 0x0000095c, 0x00000963, 0x0000096f, 0x0000097f,
+ 0x00000995, 0x000009b9, 0x000009d2, 0x000009e2,
+ 0x00000a03, 0x00000a22, 0x00000a35, 0x00000a68,
+ 0x00000a72, 0x00000a82, 0x00000ab1, 0x00000ac9,
+ 0x00000af6, 0x00000b0c, 0x00000b1c, 0x00000b29,
+ // Entry 80 - 9F
+ 0x00000b3b, 0x00000b53, 0x00000b5f, 0x00000b5f,
+ 0x00000b73, 0x00000b8e, 0x00000ba8, 0x00000bcc,
+ 0x00000be3, 0x00000bf5, 0x00000c04, 0x00000c12,
+ 0x00000c19, 0x00000c2a, 0x00000c83, 0x00000c8a,
+ 0x00000c96, 0x00000cae, 0x00000cbb, 0x00000cce,
+ 0x00000cf1, 0x00000d09, 0x00000d2e, 0x00000d46,
+ 0x00000d61, 0x00000d74, 0x00000d90, 0x00000dac,
+ 0x00000dd6, 0x00000df2, 0x00000e02, 0x00000e23,
+ // Entry A0 - BF
+ 0x00000e56, 0x00000e69, 0x00000e87, 0x00000eb7,
+ 0x00000ecc, 0x00000ef6, 0x00000f1c, 0x00000f2f,
+ 0x00000f4b, 0x00000f5e, 0x00000f82, 0x00000fc6,
+ 0x00000fe2, 0x00001000, 0x00001019, 0x0000104f,
+ 0x000010b0, 0x000010c9, 0x000010e5, 0x000010e5,
+ 0x000010e5, 0x000010e5, 0x000010e5, 0x000010e5,
+ 0x000010e5, 0x000010e5, 0x000010e5, 0x000010e5,
+ 0x000010e5, 0x000010e5, 0x000010e5, 0x000010e5,
+ // Entry C0 - DF
+ 0x000010e5, 0x000010e5, 0x000010e5, 0x000010e5,
+ 0x000010e5, 0x000010e5, 0x000010e5, 0x000010e5,
+ 0x000010e5, 0x000010e5, 0x000010e5, 0x000010e5,
+ 0x000010e5, 0x000010e5, 0x000010e5, 0x000010e5,
+ 0x000010e5, 0x000010e5, 0x000010e5, 0x000010e5,
+ 0x000010e5, 0x000010e5, 0x000010e5, 0x000010e5,
+ 0x000010e5, 0x000010e5, 0x000010e5, 0x000010e5,
+ 0x000010e5, 0x000010e5, 0x000010e5, 0x000010e5,
+ // Entry E0 - FF
+ 0x000010e5, 0x000010e5, 0x000010e5, 0x000010e5,
+ 0x000010e5, 0x000010e5, 0x000010e5, 0x000010e5,
+ 0x000010e5, 0x000010e5, 0x000010e5, 0x000010e5,
+ 0x000010e5, 0x000010e5, 0x000010e5, 0x000010e5,
+ 0x000010e5, 0x000010e5, 0x000010e5, 0x000010e5,
+ 0x000010e5, 0x000010e5, 0x000010e5, 0x000010e5,
+ 0x000010e5, 0x000010e5, 0x000010e5, 0x000010e5,
+ 0x000010e5, 0x000010e5, 0x000010e5, 0x000010e5,
+ // Entry 100 - 11F
+ 0x000010e5, 0x000010e5, 0x000010e5, 0x000010e5,
+ 0x000010e5, 0x000010e5, 0x000010e5, 0x000010e5,
+ 0x000010e5, 0x000010e5, 0x000010e5, 0x000010e5,
+ 0x000010e5, 0x000010e5, 0x000010e5, 0x000010e5,
+ 0x000010e5, 0x000010e5, 0x000010e5, 0x000010e5,
+ 0x000010e5, 0x000010e5,
+} // Size: 1136 bytes
+
+const zh_TWData string = "" + // Size: 4325 bytes
+ "\x02錯誤\x02(無參數):提升權限並安裝管理服務\x02使用方法: %[1]s [\x0a%[2]s]\x02命令列選項\x02無法確定該" +
+ "處理程序是否在 WOW64 下執行: %[1]v\x02您必須在此電腦上執行原生版本的 WireGuard。\x02無法開啓目前處理程序的權" +
+ "杖: %[1]v\x02WireGuard 可能只能被內建的「%[1]s」群組成員使用。\x02WireGuard 正在執行,但 UI 只能" +
+ "從內建的內建的「%[1]s」群組成員的桌面存取。\x02就是現在\x02系統時鐘倒退了!\x14\x01\x81\x01\x00\x00" +
+ "\x0a\x02%[1]d 年\x14\x01\x81\x01\x00\x00\x0a\x02%[1]d 天\x14\x01\x81\x01" +
+ "\x00\x00\x0d\x02%[1]d 小時\x14\x01\x81\x01\x00\x00\x0d\x02%[1]d 分鐘\x14\x01" +
+ "\x81\x01\x00\x00\x0a\x02%[1]d 秒\x02%[1]s 前\x02%[1]d\u00a0B\x02%.2[1]f" +
+ "\u00a0KiB\x02%.2[1]f\u00a0MiB\x02%.2[1]f\u00a0GiB\x02%.2[1]f\u00a0TiB" +
+ "\x02%[1]s: %[2]q\x02無效的 IP 位址\x02無效的網路位址首碼長度\x02Endpoint 中沒有指定埠號\x02無效的 " +
+ "Endpoint 位址\x02無效的 MTU\x02無效的埠號\x02無效的 Persistent Keepalive 設定\x02無效的金鑰:" +
+ " %[1]v\x02數值必須介於 0 到 2^64-1: %[1]v\x02一行中有兩個逗號\x02隧道名稱無效\x02[未指定]\x02每個 " +
+ "Peer 都必須要有公鑰\x02讀取設定時發生錯誤\x02、\x02 \x02關於 WireGuard\x02WireGuard logo 圖片" +
+ "\x02關閉\x02♥ 捐贈! (&D)\x02狀態\x02中斷連線 (&D)\x02連線 (&A)\x02公鑰\x02監聽埠\x02MTU" +
+ "\x02位址\x02DNS 伺服器\x02預交換金鑰\x02允許的位址\x02連接點\x02Keepalive 間隔\x02最後交握時間\x02" +
+ "已中斷連線\x02正在中斷…\x02未知\x02日誌\x02複製 (&C)\x02全選 (&A)\x02匯出… (&S)\x02時間\x02" +
+ "日誌訊息\x02匯出日誌…\x02關於 WireGuard (&A)\x02隧道錯誤\x02%[1]s\x0a\x0a如需更多資訊,請查看日" +
+ "誌。\x02%[1]s(已過時)\x02離開 WireGuard 失敗\x02更新的 WireGuard 已經為您準備好了。\x0a強烈建議" +
+ "您立即進行更新。\x02立即更新\x02錯誤: %[1]v。請稍後再試。\x02狀態:已完成!\x02WireGuard 的工作列圖示在 3" +
+ "0 秒後並沒有顯示。\x02括號中必須包含一個 IPv6 位址\x02金鑰必須剛好長 32 bytes\x02行必須出現在段落中\x02設定的項" +
+ "目必須要有一個等號\x02必須要有一個值\x02[Interface] 中有無效選項\x02[Peer] 中有無效選項\x02Interfa" +
+ "ce 中必須要有一把私鑰\x02Interface 中的金鑰無效\x02協定版本必須為 1\x02Peer 中的金鑰無效\x02指令碼:\x02" +
+ "流量\x02連接前\x02連接後\x02斷線前\x02斷線後\x02已關閉, 隨著策略\x02已啓用\x02已收到 %[1]s;已傳送 %[" +
+ "2]s\x02無法確認隧道狀態\x02無法連接隧道\x02無法斷開隧道連線\x02[隧道] %[1]s\x02節點\x02建立新隧道\x02編輯" +
+ "隧道設定\x02名稱 (&N)\x02公鑰 (&P)\x02(未知)\x02阻斷未經過隧道的流量(kill-switch) (&B)\x02" +
+ "儲存 (&S)\x02取消\x02設定 (&C)\x02無效的名稱\x02必須填寫名稱。\x02無效的隧道名稱「%[1]s」。\x02無法列" +
+ "出現有隧道\x02隧道已存在\x02已有同名隧道「%[1]s」。\x02無法建立新的隧道設定\x02檔案寫入失敗\x02檔案已存在: %[1" +
+ "]s\x0a\x0a您確定要覆蓋嗎?\x02已連線\x02正在連線…\x02純文字 (*.txt)|*.txt|所有檔案 (*.*)|*.*" +
+ "\x02偵測 WireGuard 錯誤\x02無法等待 WireGuard 視窗開啓: %[1]v\x02WireGuard - 未連線\x02" +
+ "[狀態] 未知\x02[位址] 無\x02管理隧道 (&M)\x02從檔案匯入… (&I)\x02離開 (&X)\x02WireGuard 已連" +
+ "線\x02已連線至隧道 - %[1]s\x02WireGuard 已中斷連線\x02已中斷與隧道的連線 - %[1]s\x02WireGua" +
+ "rd 隧道錯誤\x02WireGuard - %[1]s\x02[狀態] %[1]s\x02位址: %[1]s\x02更新\x02WireGua" +
+ "rd 更新\x02更新的 WireGuard 已經為您準備好了。\x0a強烈建議您立即更新 WireGuard。\x02隧道\x02編輯 (&E" +
+ ")\x02新增隧道精靈 (&E)\x02新增隧道\x02刪除選取隧道\x02匯出所有隧道(ZIP 格式)\x02切換連線狀態 (&T)\x02匯" +
+ "出所有隧道至 &ZIP 壓縮檔\x02編輯選取隧道 (&S)\x02刪除已選取隧道 (&R)\x02找不到設定檔\x02無法匯入設定: %[" +
+ "1]v\x02無法列出隧道: %[1]v\x02已有另一個同名的隧道「%[1]s」\x02無法匯入設定: %[1]v\x02已匯入隧道\x14" +
+ "\x01\x81\x01\x00\x00\x1a\x02已匯入 %[1]d 個隧道\x14\x02\x80\x01\x00-\x02已匯入 %[" +
+ "1]d 個隧道(共 %[2]d 個)\x02無法建立隧道\x14\x01\x81\x01\x00\x00\x17\x02刪除 %[1]d 個隧道" +
+ "\x14\x01\x81\x01\x00\x00)\x02您確定要刪除 %[1]d 個隧道嗎?\x02刪除隧道 - %[1]s\x02您確定要刪" +
+ "除隧道「%[1]s」嗎?\x02%[1]s\x0a\x0a您將無法復原此操作。\x02無法刪除隧道\x02無法刪除隧道: %[1]s\x02" +
+ "無法刪除隧道\x14\x01\x81\x01\x00\x00\x1d\x02無法刪除 %[1]d 個隧道\x02隧道設定檔 (*.zip, " +
+ "*.conf)|*.zip;*.conf|所有檔案 (*.*)|*.*\x02從檔案中匯入隧道…\x02隧道設定檔 (*.zip)|*.zip" +
+ "\x02匯出隧道設定至…\x02%[1]s(未簽署發行版本,無法自動更新)\x02無法結束服務: %[1]v。\x0a您可能需要手動從服務管理中" +
+ "結束 WireGuard 服務。\x02狀態:等待使用者\x02狀態:等待更新服務"
+
+ // Total table size 139447 bytes (136KiB); checksum: 43780019