1
0
Fork 0
mirror of https://github.com/HACKERALERT/Picocrypt.git synced 2024-09-20 17:56:35 +00:00

Update Picocrypt.go

This commit is contained in:
Evan Su 2021-09-19 22:50:36 -04:00 committed by GitHub
parent b7384af9bc
commit 7344f95726
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -17,32 +17,43 @@ import (
// Generic // Generic
"archive/zip" "archive/zip"
"bytes" "bytes"
"encoding/hex"
"encoding/json"
"fmt" "fmt"
"hash" "hash"
"image" "image"
"image/color" "image/color"
"image/png" "image/png"
"io" "io"
"io/ioutil"
"math" "math"
"math/big" "math/big"
"net/http"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime" "runtime"
"runtime/debug" "runtime/debug"
"strconv" "strconv"
"strings" "strings"
"sync"
"syscall"
"time" "time"
// Cryptography // Cryptography
"crypto/cipher" "crypto/cipher"
"crypto/hmac" "crypto/hmac"
"crypto/md5"
"crypto/rand" "crypto/rand"
"crypto/sha1"
"crypto/sha256"
"crypto/subtle" "crypto/subtle"
"github.com/HACKERALERT/serpent" // v0.0.0-20210716182301-293b29869c66 "github.com/HACKERALERT/serpent"
"golang.org/x/crypto/argon2" "golang.org/x/crypto/argon2"
"golang.org/x/crypto/blake2b" "golang.org/x/crypto/blake2b"
"golang.org/x/crypto/blake2s"
"golang.org/x/crypto/chacha20" "golang.org/x/crypto/chacha20"
"golang.org/x/crypto/hkdf" "golang.org/x/crypto/hkdf"
"golang.org/x/crypto/sha3" "golang.org/x/crypto/sha3"
@ -51,11 +62,12 @@ import (
"github.com/AllenDang/giu" "github.com/AllenDang/giu"
// Reed-Solomon // Reed-Solomon
"github.com/HACKERALERT/infectious" // v0.0.0-20210829223857-06884e85204c "github.com/HACKERALERT/infectious"
// Helpers // Helpers
"github.com/HACKERALERT/clipboard" "github.com/HACKERALERT/clipboard"
"github.com/HACKERALERT/dialog" "github.com/HACKERALERT/dialog"
"github.com/HACKERALERT/jibber_jabber"
"github.com/HACKERALERT/zxcvbn-go" "github.com/HACKERALERT/zxcvbn-go"
) )
@ -65,6 +77,12 @@ var icon []byte
//go:embed font.ttf //go:embed font.ttf
var font []byte var font []byte
//go:embed sdelete64.exe
var sdelete64bytes []byte
//go:embed strings.json
var localeBytes []byte
// Localization // Localization
type locale struct { type locale struct {
iso string iso string
@ -75,9 +93,11 @@ var locales []locale
var selectedLocale = "en" var selectedLocale = "en"
var allLocales = []string{ var allLocales = []string{
"en", "en",
"tr",
} }
var languages = []string{ var languages = []string{
"English", "English",
"Türkçe",
} }
var languageSelected int32 var languageSelected int32
@ -89,6 +109,7 @@ var mode string
var working bool var working bool
var recombine bool var recombine bool
var fill float32 = -0.0000001 var fill float32 = -0.0000001
var sdelete64path string
// Three variables store the input files // Three variables store the input files
var onlyFiles []string var onlyFiles []string
@ -170,8 +191,36 @@ var rs32, _ = infectious.NewFEC(32, 96)
var rs64, _ = infectious.NewFEC(64, 192) var rs64, _ = infectious.NewFEC(64, 192)
var rs128, _ = infectious.NewFEC(128, 136) var rs128, _ = infectious.NewFEC(128, 136)
// File checksum generator variables
var cs_md5 string
var cs_sha1 string
var cs_sha256 string
var cs_sha3_256 string
var cs_blake2b string
var cs_blake2s string
var cs_validate string
var md5_color = color.RGBA{0x10, 0x10, 0x10, 0xff}
var sha1_color = color.RGBA{0x10, 0x10, 0x10, 0xff}
var sha256_color = color.RGBA{0x10, 0x10, 0x10, 0xff}
var sha3_256_color = color.RGBA{0x10, 0x10, 0x10, 0xff}
var blake2b_color = color.RGBA{0x10, 0x10, 0x10, 0xff}
var blake2s_color = color.RGBA{0x10, 0x10, 0x10, 0xff}
var cs_progress float32 = 0
var md5_selected = false
var sha1_selected = false
var sha256_selected = false
var sha3_256_selected = false
var blake2b_selected = false
var blake2s_selected = false
// Shredder variables // Shredder variables
var shredding string var shredding string = "Ready."
var shredPasses int32 = 4
var stopShredding bool
var shredProgress float32
var shredDone float32
var shredTotal float32
var shredOverlay string
func draw() { func draw() {
giu.SingleWindow().Layout( giu.SingleWindow().Layout(
@ -623,6 +672,154 @@ func draw() {
tab = 1 tab = 1
} }
}), }),
giu.Label(s("Toggle the hashes you would like to generate and drop a file here.")),
// MD5
giu.Custom(func() {
giu.Checkbox("MD5:", &md5_selected).Build()
giu.SameLine()
w, _ := giu.GetAvailableRegion()
bw, _ := giu.CalcTextSize(s("Copy"))
padding, _ := giu.GetWindowPadding()
bw += 2 * padding
size := w - bw - padding
giu.Dummy(size/dpi, 0).Build()
giu.SameLine()
giu.Button(s("Copy")+"##md5").Size(bw/dpi, 0).OnClick(func() {
clipboard.WriteAll(cs_md5)
}).Build()
}),
giu.Style().SetColor(giu.StyleColorBorder, md5_color).To(
giu.InputText(&cs_md5).Size(fill).Flags(giu.InputTextFlagsReadOnly),
),
// SHA1
giu.Custom(func() {
giu.Checkbox("SHA1:", &sha1_selected).Build()
giu.SameLine()
w, _ := giu.GetAvailableRegion()
bw, _ := giu.CalcTextSize(s("Copy"))
padding, _ := giu.GetWindowPadding()
bw += 2 * padding
size := w - bw - padding
giu.Dummy(size/dpi, 0).Build()
giu.SameLine()
giu.Button(s("Copy")+"##sha1").Size(bw/dpi, 0).OnClick(func() {
clipboard.WriteAll(cs_sha1)
}).Build()
}),
giu.Style().SetColor(giu.StyleColorBorder, sha1_color).To(
giu.InputText(&cs_sha1).Size(fill).Flags(giu.InputTextFlagsReadOnly),
),
// SHA256
giu.Custom(func() {
giu.Checkbox("SHA256:", &sha256_selected).Build()
giu.SameLine()
w, _ := giu.GetAvailableRegion()
bw, _ := giu.CalcTextSize(s("Copy"))
padding, _ := giu.GetWindowPadding()
bw += 2 * padding
size := w - bw - padding
giu.Dummy(size/dpi, 0).Build()
giu.SameLine()
giu.Button(s("Copy")+"##sha256").Size(bw/dpi, 0).OnClick(func() {
clipboard.WriteAll(cs_sha256)
}).Build()
}),
giu.Style().SetColor(giu.StyleColorBorder, sha256_color).To(
giu.InputText(&cs_sha256).Size(fill).Flags(giu.InputTextFlagsReadOnly),
),
// SHA3-256
giu.Custom(func() {
giu.Checkbox("SHA3-256:", &sha3_256_selected).Build()
giu.SameLine()
w, _ := giu.GetAvailableRegion()
bw, _ := giu.CalcTextSize(s("Copy"))
padding, _ := giu.GetWindowPadding()
bw += 2 * padding
size := w - bw - padding
giu.Dummy(size/dpi, 0).Build()
giu.SameLine()
giu.Button(s("Copy")+"##sha3_256").Size(bw/dpi, 0).OnClick(func() {
clipboard.WriteAll(cs_sha3_256)
}).Build()
}),
giu.Style().SetColor(giu.StyleColorBorder, sha3_256_color).To(
giu.InputText(&cs_sha3_256).Size(fill).Flags(giu.InputTextFlagsReadOnly),
),
// BLAKE2b
giu.Custom(func() {
giu.Checkbox("BLAKE2b:", &blake2b_selected).Build()
giu.SameLine()
w, _ := giu.GetAvailableRegion()
bw, _ := giu.CalcTextSize(s("Copy"))
padding, _ := giu.GetWindowPadding()
bw += 2 * padding
size := w - bw - padding
giu.Dummy(size/dpi, 0).Build()
giu.SameLine()
giu.Button(s("Copy")+"##blake2b").Size(bw/dpi, 0).OnClick(func() {
clipboard.WriteAll(cs_blake2b)
}).Build()
}),
giu.Style().SetColor(giu.StyleColorBorder, blake2b_color).To(
giu.InputText(&cs_blake2b).Size(fill).Flags(giu.InputTextFlagsReadOnly),
),
// BLAKE2s
giu.Custom(func() {
giu.Checkbox("BLAKE2s:", &blake2s_selected).Build()
giu.SameLine()
w, _ := giu.GetAvailableRegion()
bw, _ := giu.CalcTextSize(s("Copy"))
padding, _ := giu.GetWindowPadding()
bw += 2 * padding
size := w - bw - padding
giu.Dummy(size/dpi, 0).Build()
giu.SameLine()
giu.Button(s("Copy")+"##blake2s").Size(bw/dpi, 0).OnClick(func() {
clipboard.WriteAll(cs_blake2s)
}).Build()
}),
giu.Style().SetColor(giu.StyleColorBorder, blake2s_color).To(
giu.InputText(&cs_blake2s).Size(fill).Flags(giu.InputTextFlagsReadOnly),
),
// Input entry for validating a checksum
giu.Label(s("Validate a checksum:")),
giu.InputText(&cs_validate).Size(fill).OnChange(func() {
md5_color = color.RGBA{0x10, 0x10, 0x10, 0xff}
sha1_color = color.RGBA{0x10, 0x10, 0x10, 0xff}
sha256_color = color.RGBA{0x10, 0x10, 0x10, 0xff}
sha3_256_color = color.RGBA{0x10, 0x10, 0x10, 0xff}
blake2b_color = color.RGBA{0x10, 0x10, 0x10, 0xff}
blake2s_color = color.RGBA{0x10, 0x10, 0x10, 0xff}
if cs_validate == "" {
return
}
cs_validate = strings.ToLower(cs_validate)
if cs_validate == cs_md5 {
md5_color = color.RGBA{0x00, 0xff, 0x00, 0xff}
} else if cs_validate == cs_sha1 {
sha1_color = color.RGBA{0x00, 0xff, 0x00, 0xff}
} else if cs_validate == cs_sha256 {
sha256_color = color.RGBA{0x00, 0xff, 0x00, 0xff}
} else if cs_validate == cs_sha3_256 {
sha3_256_color = color.RGBA{0x00, 0xff, 0x00, 0xff}
} else if cs_validate == cs_blake2b {
blake2b_color = color.RGBA{0x00, 0xff, 0x00, 0xff}
} else if cs_validate == cs_blake2s {
blake2s_color = color.RGBA{0x00, 0xff, 0x00, 0xff}
}
giu.Update()
}),
// Progress bar
giu.Label(s("Progress:")),
giu.ProgressBar(cs_progress).Size(fill, 0),
), ),
giu.TabItem(s("Shredder")).Layout( giu.TabItem(s("Shredder")).Layout(
giu.Custom(func() { giu.Custom(func() {
@ -630,6 +827,41 @@ func draw() {
tab = 2 tab = 2
} }
}), }),
giu.Label(s("Drop files and folders here to shred them.")),
giu.Custom(func() {
if runtime.GOOS == "darwin" {
giu.Label(s("Number of passes: Not supported on macOS")).Build()
} else {
giu.Row(
giu.Label(s("Number of passes:")),
giu.SliderInt(&shredPasses, 1, 32).Size(fill),
).Build()
}
}),
giu.Dummy(0, -50),
giu.Custom(func() {
w, _ := giu.GetAvailableRegion()
bw, _ := giu.CalcTextSize(s("Cancel"))
padding, _ := giu.GetWindowPadding()
bw += 2 * padding
size := w - bw - padding
giu.Row(
giu.ProgressBar(shredProgress).Overlay(shredOverlay).Size(size/dpi, 0),
giu.Button(s("Cancel")).Size(bw/dpi, 0).OnClick(func() {
stopShredding = true
shredding = s("Ready.")
shredProgress = 0
shredOverlay = ""
}),
).Build()
}),
giu.Custom(func() {
if len(shredding) > 50 {
shredding = "....." + shredding[len(shredding)-50:]
}
giu.Label(shredding).Wrapped(true).Build()
}),
), ),
giu.TabItem(s("About")).Layout( giu.TabItem(s("About")).Layout(
giu.Custom(func() { giu.Custom(func() {
@ -645,9 +877,11 @@ func draw() {
func onDrop(names []string) { func onDrop(names []string) {
if tab == 1 { if tab == 1 {
go generateChecksums(names[0])
return return
} }
if tab == 2 { if tab == 2 {
go shred(names, true)
return return
} }
@ -673,9 +907,6 @@ func onDrop(names []string) {
keyfilePrompt = fmt.Sprintf(s("Using %d keyfiles."), len(keyfiles)) keyfilePrompt = fmt.Sprintf(s("Using %d keyfiles."), len(keyfiles))
return return
} }
mainStatus = "Ready."
mainStatusColor = color.RGBA{0xff, 0xff, 0xff, 0xff}
metadataDisabled = false
// Clear variables // Clear variables
recombine = false recombine = false
@ -683,6 +914,7 @@ func onDrop(names []string) {
onlyFolders = nil onlyFolders = nil
allFiles = nil allFiles = nil
files, folders := 0, 0 files, folders := 0, 0
resetUI()
if len(names) == 1 { if len(names) == 1 {
stat, _ := os.Stat(names[0]) stat, _ := os.Stat(names[0])
@ -1635,8 +1867,287 @@ func broken() {
giu.Update() giu.Update()
} }
func shred(names []string, separate bool) { // Generate file checksums (pretty straightforward)
func generateChecksums(file string) {
fin, _ := os.Open(file)
// Clear UI state
cs_md5 = ""
cs_sha1 = ""
cs_sha256 = ""
cs_sha3_256 = ""
cs_blake2b = ""
cs_blake2s = ""
md5_color = color.RGBA{0x10, 0x10, 0x10, 255}
sha1_color = color.RGBA{0x10, 0x10, 0x10, 255}
sha256_color = color.RGBA{0x10, 0x10, 0x10, 255}
sha3_256_color = color.RGBA{0x10, 0x10, 0x10, 255}
blake2b_color = color.RGBA{0x10, 0x10, 0x10, 255}
blake2s_color = color.RGBA{0x10, 0x10, 0x10, 255}
cs_validate = ""
if md5_selected {
cs_md5 = s("Calculating...")
}
if sha1_selected {
cs_sha1 = s("Calculating...")
}
if sha256_selected {
cs_sha256 = s("Calculating...")
}
if sha3_256_selected {
cs_sha3_256 = s("Calculating...")
}
if blake2b_selected {
cs_blake2b = s("Calculating...")
}
if blake2s_selected {
cs_blake2s = s("Calculating...")
}
// Create the checksum objects
crc_md5 := md5.New()
crc_sha1 := sha1.New()
crc_sha256 := sha256.New()
crc_sha3_256 := sha3.New256()
crc_blake2b, _ := blake2b.New256(nil)
crc_blake2s, _ := blake2s.New256(nil)
stat, _ := os.Stat(file)
total := stat.Size()
var done int64 = 0
for {
var data []byte
_data := make([]byte, 1048576)
size, err := fin.Read(_data)
if err != nil {
break
}
data = _data[:size]
if md5_selected {
crc_md5.Write(data)
}
if sha1_selected {
crc_sha1.Write(data)
}
if sha256_selected {
crc_sha256.Write(data)
}
if sha3_256_selected {
crc_sha3_256.Write(data)
}
if blake2b_selected {
crc_blake2b.Write(data)
}
if blake2s_selected {
crc_blake2s.Write(data)
}
done += int64(size)
cs_progress = float32(done) / float32(total)
giu.Update()
}
cs_progress = 0
if md5_selected {
cs_md5 = hex.EncodeToString(crc_md5.Sum(nil))
}
if sha1_selected {
cs_sha1 = hex.EncodeToString(crc_sha1.Sum(nil))
}
if sha256_selected {
cs_sha256 = hex.EncodeToString(crc_sha256.Sum(nil))
}
if sha3_256_selected {
cs_sha3_256 = hex.EncodeToString(crc_sha3_256.Sum(nil))
}
if blake2b_selected {
cs_blake2b = hex.EncodeToString(crc_blake2b.Sum(nil))
}
if blake2s_selected {
cs_blake2s = hex.EncodeToString(crc_blake2s.Sum(nil))
}
fin.Close()
giu.Update()
}
// Recursively shred all file(s) and folder(s) passed in as 'names'
func shred(names []string, separate bool) {
stopShredding = false
shredTotal = 0
shredDone = 0
// 'separate' is true if this function is being called from the encryption/decryption tab
if separate {
shredOverlay = s("Shredding...")
}
// Walk through directories to get the total number of files for statistics
for _, name := range names {
filepath.Walk(name, func(path string, _ os.FileInfo, err error) error {
if err != nil {
return nil
}
stat, _ := os.Stat(path)
if !stat.IsDir() {
shredTotal++
}
return nil
})
}
for _, name := range names {
shredding = name
// Linux and macOS need a command with similar syntax and usage, so they're combined
if runtime.GOOS == "linux" || runtime.GOOS == "darwin" {
stat, _ := os.Stat(name)
if stat.IsDir() {
var coming []string
// Walk the folder recursively
filepath.Walk(name, func(path string, _ os.FileInfo, err error) error {
if err != nil {
return nil
}
if stopShredding {
return nil
}
stat, _ := os.Stat(path)
if !stat.IsDir() {
if len(coming) == 128 {
// Use a WaitGroup to parallelize shredding
var wg sync.WaitGroup
for i, j := range coming {
wg.Add(1)
go func(wg *sync.WaitGroup, id int, j string) {
defer wg.Done()
shredding = j
var cmd *exec.Cmd
if runtime.GOOS == "linux" {
cmd = exec.Command("shred", "-ufvz", "-n", strconv.Itoa(int(shredPasses)), j)
} else {
cmd = exec.Command("rm", "-rfP", j)
}
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
cmd.Run()
shredDone++
shredUpdate(separate)
giu.Update()
}(&wg, i, j)
}
wg.Wait()
coming = nil
} else {
coming = append(coming, path)
}
}
return nil
})
for _, i := range coming {
if stopShredding {
break
}
go func() {
shredding = i
var cmd *exec.Cmd
if runtime.GOOS == "linux" {
cmd = exec.Command("shred", "-ufvz", "-n", strconv.Itoa(int(shredPasses)), i)
} else {
cmd = exec.Command("rm", "-rfP", i)
}
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
cmd.Run()
shredDone++
shredUpdate(separate)
giu.Update()
}()
}
if !stopShredding {
os.RemoveAll(name)
}
} else { // The path is a file, not a directory, so just shred it
shredding = name
var cmd *exec.Cmd
if runtime.GOOS == "linux" {
cmd = exec.Command("shred", "-ufvz", "-n", strconv.Itoa(int(shredPasses)), name)
} else {
cmd = exec.Command("rm", "-rfP", name)
}
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
cmd.Run()
shredDone++
shredUpdate(separate)
}
} else if runtime.GOOS == "windows" {
stat, _ := os.Stat(name)
if stat.IsDir() {
// Walk the folder recursively
filepath.Walk(name, func(path string, _ os.FileInfo, err error) error {
if err != nil {
return nil
}
stat, _ := os.Stat(path)
if stat.IsDir() {
if stopShredding {
return nil
}
t := 0
files, _ := ioutil.ReadDir(path)
for _, f := range files {
if !f.IsDir() {
t++
}
}
shredDone += float32(t)
shredUpdate(separate)
shredding = strings.ReplaceAll(path, "\\", "/") + "/*"
cmd := exec.Command(sdelete64path, "*", "-p", strconv.Itoa(int(shredPasses)))
cmd.Dir = path
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
cmd.Run()
giu.Update()
}
return nil
})
if !stopShredding {
// sdelete64 doesn't delete the empty folder, so I'll do it manually
os.RemoveAll(name)
}
} else {
shredding = name
cmd := exec.Command(sdelete64path, name, "-p", strconv.Itoa(int(shredPasses)))
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
cmd.Run()
shredDone++
shredUpdate(separate)
}
}
giu.Update()
if stopShredding {
return
}
}
// Clear UI state
shredding = s("Completed.")
shredProgress = 0
shredOverlay = ""
}
// Update shredding statistics
func shredUpdate(separate bool) {
if separate {
shredOverlay = fmt.Sprintf("%d/%d", int(shredDone), int(shredTotal))
shredProgress = shredDone / shredTotal
} else {
popupStatus = fmt.Sprintf("%d/%d", int(shredDone), int(shredTotal))
progress = shredDone / shredTotal
}
giu.Update()
} }
// Reset the UI to a clean state with nothing selected or checked // Reset the UI to a clean state with nothing selected or checked
@ -1645,7 +2156,7 @@ func resetUI() {
onlyFiles = nil onlyFiles = nil
onlyFolders = nil onlyFolders = nil
allFiles = nil allFiles = nil
inputLabel = s("Drag and drop file(s) and folder(s) into this window.") inputLabel = s("Drop files and folders into this window.")
password = "" password = ""
cPassword = "" cPassword = ""
keyfiles = nil keyfiles = nil
@ -1654,6 +2165,7 @@ func resetUI() {
keyfilePrompt = s("None selected.") keyfilePrompt = s("None selected.")
metadata = "" metadata = ""
metadataPrompt = "Metadata:" metadataPrompt = "Metadata:"
metadataDisabled = false
shredTemp = false shredTemp = false
keep = false keep = false
reedsolo = false reedsolo = false
@ -1755,15 +2267,66 @@ func humanize(seconds int) string {
} }
func s(term string) string { func s(term string) string {
for _, i := range locales {
if i.iso == selectedLocale {
for _, j := range locales {
if j.iso == "en" {
for k, l := range j.data {
if l == term {
return i.data[k]
}
}
}
}
}
}
return term return term
} }
func main() { func main() {
// Parse locales
var obj map[string]json.RawMessage
json.Unmarshal(localeBytes, &obj)
for i := range obj {
var tmp []string
json.Unmarshal(obj[i], &tmp)
locales = append(locales, locale{
iso: i,
data: tmp,
})
}
// Check system locale
for _, i := range locales {
tmp, err := jibber_jabber.DetectIETF()
if err == nil {
if strings.HasPrefix(tmp, i.iso) {
selectedLocale = i.iso
for j, k := range allLocales {
if k == i.iso {
languageSelected = int32(j)
}
}
}
}
}
// Create a temporary file to store sdelete64.exe
sdelete64, _ := os.CreateTemp("", "sdelete64.*.exe")
sdelete64path = sdelete64.Name()
sdelete64.Write(sdelete64bytes)
sdelete64.Close()
cmd := exec.Command(sdelete64path, "/accepteula")
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
cmd.Run()
// Set a universal font // Set a universal font
giu.SetDefaultFontFromBytes(font, 18) giu.SetDefaultFontFromBytes(font, 18)
// Create the master window // Create the master window
window := giu.NewMasterWindow("Picocrypt", 442, 504, giu.MasterWindowFlagsNotResizable) window := giu.NewMasterWindow("Picocrypt", 442, 504, giu.MasterWindowFlagsNotResizable)
dialog.Init()
// Set window icon // Set window icon
reader := bytes.NewReader(icon) reader := bytes.NewReader(icon)
@ -1772,10 +2335,31 @@ func main() {
// Set callbacks // Set callbacks
window.SetDropCallback(onDrop) window.SetDropCallback(onDrop)
window.SetCloseCallback(func() bool {
return !working
})
// Set universal DPI // Set universal DPI
dpi = giu.Context.GetPlatform().GetContentScale() dpi = giu.Context.GetPlatform().GetContentScale()
// Start a goroutine to check if a newer version is available
go func() {
v, err := http.Get("https://raw.githubusercontent.com/HACKERALERT/Picocrypt/main/internals/version.txt")
if err == nil {
body, err := io.ReadAll(v.Body)
v.Body.Close()
if err == nil {
if string(body[:5]) != version {
mainStatus = "A newer version is available."
mainStatusColor = color.RGBA{0, 255, 0, 255}
}
}
}
}()
// Start the UI // Start the UI
window.Run(draw) window.Run(draw)
// Window closed, clean up
os.Remove(sdelete64path)
} }