Add files via upload

This commit is contained in:
ghostersk
2025-04-19 20:07:04 +01:00
committed by GitHub
parent ffaa2ed72e
commit 7e6ec21ee0
6 changed files with 518 additions and 0 deletions

23
go.mod Normal file
View File

@@ -0,0 +1,23 @@
module win-multitool
go 1.24.0
require (
github.com/getlantern/systray v1.2.2
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794
golang.org/x/sys v0.32.0
)
require (
github.com/akavel/rsrc v0.10.2 // indirect
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 // indirect
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 // indirect
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 // indirect
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 // indirect
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f // indirect
github.com/go-stack/stack v1.8.0 // indirect
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect
)

38
go.sum Normal file
View File

@@ -0,0 +1,38 @@
github.com/akavel/rsrc v0.10.2 h1:Zxm8V5eI1hW4gGaYsJQUhxpjkENuG91ki8B4zCrvEsw=
github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4=
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY=
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 h1:6uJ+sZ/e03gkbqZ0kUG6mfKoqDb4XMAzMIwlajq19So=
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 h1:guBYzEaLz0Vfc/jv0czrr2z7qyzTOGC9hiQ0VC+hKjk=
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7/go.mod h1:zx/1xUUeYPy3Pcmet8OSXLbF47l+3y6hIPpyLWoR9oc=
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 h1:micT5vkcr9tOVk1FiH8SWKID8ultN44Z+yzd2y/Vyb0=
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7/go.mod h1:dD3CgOrwlzca8ed61CsZouQS5h5jIzkK9ZWrTcf0s+o=
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 h1:XYzSdCbkzOC0FDNrgJqGRo8PCMFOBFL9py72DRs7bmc=
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA=
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
github.com/getlantern/systray v1.2.2 h1:dCEHtfmvkJG7HZ8lS/sLklTH4RKUcIsKrAD9sThoEBE=
github.com/getlantern/systray v1.2.2/go.mod h1:pXFOI1wwqwYXEhLPm9ZGjS2u/vVELeIgNMY5HvhHhcE=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794 h1:NVRJ0Uy0SOFcXSKLsS65OmI1sgCCfiDUPj+cwnH7GZw=
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
gopkg.in/Knetic/govaluate.v3 v3.0.0 h1:18mUyIt4ZlRlFZAAfVetz4/rzlJs9yhN+U02F4u1AOc=
gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E=

442
main.go Normal file
View File

@@ -0,0 +1,442 @@
package main
import (
"bufio"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/getlantern/systray"
"github.com/lxn/walk"
. "github.com/lxn/walk/declarative"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
)
type NetworkInterface struct {
Name string
IsDHCP bool
IP string
SubnetMask string
Gateway string
DNS string
DNSSuffix string
RegisterInDNS bool
}
func runSystemTray() {
// Set the icon for the system tray
iconData := getIcon()
if iconData == nil {
log.Println("Using default icon due to icon loading failure")
} else {
systray.SetIcon(iconData)
}
systray.SetTitle("Hosts Editor")
systray.SetTooltip("Hosts File Editor Service")
// Menu items
mEditHosts := systray.AddMenuItem("Edit Hosts", "Edit the hosts file")
systray.AddSeparator()
mManageNetwork := systray.AddMenuItem("Manage Network", "Manage network interfaces")
systray.AddSeparator()
mQuit := systray.AddMenuItem("Quit", "Quit the application")
// Handle menu item clicks
go func() {
for {
select {
case <-mEditHosts.ClickedCh:
if err := openHostsFile(); err != nil {
log.Printf("Failed to open hosts file: %v", err)
}
case <-mManageNetwork.ClickedCh:
go func() {
if err := showNetworkGUI(); err != nil {
log.Printf("Failed to show network GUI: %v", err)
}
}()
case <-mQuit.ClickedCh:
if confirmQuit() {
systray.Quit()
os.Exit(0)
}
}
}
}()
}
func getIcon() []byte {
// Load repair.ico from the project directory
iconPath := filepath.Join("repair.ico")
data, err := os.ReadFile(iconPath)
if err != nil {
log.Printf("Failed to load icon: %v", err)
return nil
}
if len(data) == 0 {
log.Println("Icon file is empty")
return nil
}
// Basic .ico file validation (check for ICO header)
if len(data) < 6 || data[0] != 0x00 || data[1] != 0x00 || data[2] != 0x01 || data[3] != 0x00 {
log.Println("Invalid .ico file format")
return nil
}
log.Printf("Loaded icon file, size: %d bytes, first 6 bytes: %x", len(data), data[:6])
return data
}
func openHostsFile() error {
hostsPath := `C:\Windows\System32\drivers\etc\hosts`
cmd := exec.Command("notepad.exe", hostsPath)
return cmd.Start()
}
func confirmQuit() bool {
const MB_YESNO = 0x00000004
const MB_ICONQUESTION = 0x00000020
const IDYES = 6
hwnd, err := windows.MessageBox(0, windows.StringToUTF16Ptr("Are you sure you want to close this application?"),
windows.StringToUTF16Ptr("Confirm Exit"), MB_YESNO|MB_ICONQUESTION)
if err != nil {
log.Printf("MessageBox failed: %v", err)
return false
}
return hwnd == IDYES
}
func showNetworkGUI() error {
interfaces, err := getNetworkInterfaces()
if err != nil {
return fmt.Errorf("failed to get network interfaces: %v", err)
}
var mw *walk.MainWindow
var tabs *walk.TabWidget
// Use a slice of pointers to interface states for dynamic updates
interfaceStates := make([]*NetworkInterface, 0, len(interfaces))
for i := range interfaces {
interfaceStates = append(interfaceStates, &interfaces[i])
}
var tabPages []TabPage
for _, iface := range interfaceStates {
if strings.ToLower(iface.Name) == "loopback" {
continue
}
page, err := createInterfaceTab(iface)
if err != nil {
log.Printf("Failed to create tab for %s: %v", iface.Name, err)
continue
}
tabPages = append(tabPages, page)
}
if len(tabPages) == 0 {
return fmt.Errorf("no valid network interfaces found")
}
err = MainWindow{
AssignTo: &mw,
Title: "Network Interface Manager",
MinSize: Size{Width: 400, Height: 300},
Layout: VBox{},
Children: []Widget{
TabWidget{
AssignTo: &tabs,
Pages: tabPages,
},
},
}.Create()
if err != nil {
return fmt.Errorf("failed to create main window: %v", err)
}
mw.Run()
return nil
}
func createInterfaceTab(iface *NetworkInterface) (TabPage, error) {
var dhcpCheckBox *walk.CheckBox
var ipEdit, maskEdit, gatewayEdit, dnsEdit, suffixEdit *walk.LineEdit
var registerDNSCheckBox *walk.CheckBox
var warningLabel *walk.Label
var saveButton, cancelButton *walk.PushButton
updateFields := func() {
readonly := dhcpCheckBox.Checked()
ipEdit.SetReadOnly(readonly)
maskEdit.SetReadOnly(readonly)
gatewayEdit.SetReadOnly(readonly)
dnsEdit.SetReadOnly(readonly)
// DNS Suffix and RegisterInDNS are always editable
warningLabel.SetVisible(strings.HasPrefix(ipEdit.Text(), "169.254.") && dhcpCheckBox.Checked())
}
return TabPage{
Title: iface.Name,
Layout: Grid{Columns: 2},
Children: []Widget{
Label{Text: "DHCP Enabled:"},
CheckBox{
AssignTo: &dhcpCheckBox,
Checked: iface.IsDHCP,
OnCheckedChanged: func() {
updateFields()
},
},
Label{Text: "IP Address:"},
LineEdit{
AssignTo: &ipEdit,
Text: iface.IP,
ReadOnly: iface.IsDHCP,
},
Label{Text: "Subnet Mask:"},
LineEdit{
AssignTo: &maskEdit,
Text: iface.SubnetMask,
ReadOnly: iface.IsDHCP,
},
Label{Text: "Gateway:"},
LineEdit{
AssignTo: &gatewayEdit,
Text: iface.Gateway,
ReadOnly: iface.IsDHCP,
},
Label{Text: "DNS Server:"},
LineEdit{
AssignTo: &dnsEdit,
Text: iface.DNS,
ReadOnly: iface.IsDHCP,
},
Label{Text: "DNS Suffix:"},
LineEdit{
AssignTo: &suffixEdit,
Text: iface.DNSSuffix,
},
Label{Text: "Register in DNS:"},
CheckBox{
AssignTo: &registerDNSCheckBox,
Checked: iface.RegisterInDNS,
},
Label{
AssignTo: &warningLabel,
Text: "Warning: IP address indicates no DHCP server connection (169.254.x.x)",
Visible: strings.HasPrefix(iface.IP, "169.254.") && iface.IsDHCP,
ColumnSpan: 2,
},
PushButton{
AssignTo: &saveButton,
Text: "Save",
OnClicked: func() {
err := saveInterfaceSettings(*iface, dhcpCheckBox.Checked(), ipEdit.Text(), maskEdit.Text(), gatewayEdit.Text(), dnsEdit.Text(), suffixEdit.Text(), registerDNSCheckBox.Checked())
if err != nil {
walk.MsgBox(nil, "Error", fmt.Sprintf("Failed to save settings: %v", err), walk.MsgBoxIconError)
} else {
walk.MsgBox(nil, "Success", "Settings saved successfully", walk.MsgBoxIconInformation)
// Update the interface state
iface.IsDHCP = dhcpCheckBox.Checked()
iface.IP = ipEdit.Text()
iface.SubnetMask = maskEdit.Text()
iface.Gateway = gatewayEdit.Text()
iface.DNS = dnsEdit.Text()
iface.DNSSuffix = suffixEdit.Text()
iface.RegisterInDNS = registerDNSCheckBox.Checked()
updateFields()
}
},
},
PushButton{
AssignTo: &cancelButton,
Text: "Cancel",
OnClicked: func() {
ipEdit.SetText(iface.IP)
maskEdit.SetText(iface.SubnetMask)
gatewayEdit.SetText(iface.Gateway)
dnsEdit.SetText(iface.DNS)
suffixEdit.SetText(iface.DNSSuffix)
dhcpCheckBox.SetChecked(iface.IsDHCP)
registerDNSCheckBox.SetChecked(iface.RegisterInDNS)
updateFields()
},
},
},
}, nil
}
func getNetworkInterfaces() ([]NetworkInterface, error) {
var interfaces []NetworkInterface
// Get interface configurations
cmd := exec.Command("netsh", "interface", "ipv4", "show", "config")
output, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("failed to list interfaces: %v", err)
}
scanner := bufio.NewScanner(strings.NewReader(string(output)))
var currentIface *NetworkInterface
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}
if !strings.HasPrefix(line, " ") && strings.Contains(line, ":") {
if currentIface != nil {
interfaces = append(interfaces, *currentIface)
}
name := strings.TrimSuffix(line, ":")
currentIface = &NetworkInterface{Name: name}
} else if currentIface != nil && strings.HasPrefix(line, " ") {
if strings.Contains(line, "DHCP enabled:") {
currentIface.IsDHCP = strings.Contains(line, "Yes")
} else if strings.Contains(line, "IP Address:") {
currentIface.IP = strings.TrimSpace(strings.Split(line, ":")[1])
} else if strings.Contains(line, "Subnet Mask:") {
currentIface.SubnetMask = strings.TrimSpace(strings.Split(line, ":")[1])
} else if strings.Contains(line, "Default Gateway:") {
currentIface.Gateway = strings.TrimSpace(strings.Split(line, ":")[1])
} else if strings.Contains(line, "Statically Configured DNS Servers:") || strings.Contains(line, "DNS servers configured through DHCP:") {
currentIface.DNS = strings.TrimSpace(strings.Split(line, ":")[1])
if currentIface.DNS == "None" {
currentIface.DNS = ""
}
}
}
}
if currentIface != nil {
interfaces = append(interfaces, *currentIface)
}
// Get DNS suffix and registration from registry
for i, iface := range interfaces {
ifaceGUID, err := getInterfaceGUID(iface.Name)
if err == nil {
keyPath := `SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\` + ifaceGUID
k, err := registry.OpenKey(registry.LOCAL_MACHINE, keyPath, registry.QUERY_VALUE)
if err == nil {
interfaces[i].DNSSuffix, _, _ = k.GetStringValue("Domain")
register, _, err := k.GetIntegerValue("RegisterAdapterName")
if err == nil && register == 1 {
interfaces[i].RegisterInDNS = true
}
k.Close()
}
}
}
return interfaces, nil
}
func getInterfaceGUID(ifaceName string) (string, error) {
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces`, registry.ENUMERATE_SUB_KEYS)
if err != nil {
return "", fmt.Errorf("failed to open registry: %v", err)
}
defer k.Close()
guids, err := k.ReadSubKeyNames(-1)
if err != nil {
return "", fmt.Errorf("failed to read subkeys: %v", err)
}
for _, guid := range guids {
subKey, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\`+guid, registry.QUERY_VALUE)
if err != nil {
continue
}
name, _, err := subKey.GetStringValue("Name")
if err == nil && name == ifaceName {
subKey.Close()
return guid, nil
}
subKey.Close()
}
return "", fmt.Errorf("interface GUID not found for %s", ifaceName)
}
func saveInterfaceSettings(iface NetworkInterface, isDHCP bool, ip, mask, gateway, dns, suffix string, registerInDNS bool) error {
if isDHCP {
cmd := exec.Command("netsh", "interface", "ipv4", "set", "address", "name="+iface.Name, "source=dhcp")
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to enable DHCP for IP: %v", err)
}
cmd = exec.Command("netsh", "interface", "ipv4", "set", "dnsservers", "name="+iface.Name, "source=dhcp")
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to enable DHCP for DNS: %v", err)
}
} else {
if !isValidIPv4(ip) || !isValidIPv4(mask) {
return fmt.Errorf("invalid IP or subnet mask")
}
args := []string{"interface", "ipv4", "set", "address", "name=" + iface.Name, "source=static", "address=" + ip, "mask=" + mask}
if gateway != "" && isValidIPv4(gateway) {
args = append(args, "gateway="+gateway)
}
cmd := exec.Command("netsh", args...)
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to set static IP: %v", err)
}
if dns != "" && isValidIPv4(dns) {
cmd = exec.Command("netsh", "interface", "ipv4", "set", "dnsservers", "name="+iface.Name, "source=static", "address="+dns, "validate=no")
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to set DNS: %v", err)
}
}
}
// Set DNS suffix and registration
if suffix != iface.DNSSuffix || registerInDNS != iface.RegisterInDNS {
guid, err := getInterfaceGUID(iface.Name)
if err != nil {
return fmt.Errorf("failed to get interface GUID: %v", err)
}
keyPath := `SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\` + guid
k, err := registry.OpenKey(registry.LOCAL_MACHINE, keyPath, registry.SET_VALUE)
if err != nil {
return fmt.Errorf("failed to open registry key: %v", err)
}
defer k.Close()
if err := k.SetStringValue("Domain", suffix); err != nil {
return fmt.Errorf("failed to set DNS suffix: %v", err)
}
registerValue := uint32(0)
if registerInDNS {
registerValue = 1
}
if err := k.SetDWordValue("RegisterAdapterName", registerValue); err != nil {
return fmt.Errorf("failed to set DNS registration: %v", err)
}
}
return nil
}
func isValidIPv4(ip string) bool {
re := regexp.MustCompile(`^(\d{1,3}\.){3}\d{1,3}$`)
if !re.MatchString(ip) {
return false
}
parts := strings.Split(ip, ".")
for _, part := range parts {
num, err := strconv.Atoi(part)
if err != nil || num < 0 || num > 255 {
return false
}
}
return true
}
func main() {
systray.Run(runSystemTray, func() {})
}

15
main.manifest Normal file
View File

@@ -0,0 +1,15 @@
<?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="SomeFunkyNameHere" type="win32"/>
<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>
</assembly>

BIN
repair.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
rsrc.syso Normal file

Binary file not shown.