This commit is contained in:
Evan Su 2025-04-14 00:06:28 +00:00 committed by GitHub
commit 504e908195
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 306 additions and 165 deletions

View file

@ -12,11 +12,11 @@ body:
- type: markdown
attributes:
value: |
Picocrypt is a "finished" piece of software and is in a maintenance-only stage. This does not mean the software is old, outdated, or abandonware, but that the focus is on fixing bugs and ensuring the software continues to work smoothly as opposed to actively developing new features. As well, my time as the developer is limited considering that Picocrypt brings me no monetary benefit and is entirely a gift of my time and skill to the community.
Picocrypt is a "finished" piece of software and is in a maintenance-only stage. This does not mean the software is old, outdated, or abandonware, but that the sole focus is on fixing bugs and ensuring the software continues to work smoothly as opposed to actively developing new features. As well, my time as the developer is very limited considering that Picocrypt brings me no monetary benefit and is entirely a gift of my time and skill to the community.
- type: markdown
attributes:
value: |
*To ensure that issues remain relevant and as time-efficient as possible for me, please follow the guidelines below depending on the type/topic of your issue.*
*Therefore, to save me time so that I can focus on the important things, please follow the guidelines below depending on your topic.*
- type: markdown
attributes:
value: |
@ -24,7 +24,7 @@ body:
- type: markdown
attributes:
value: |
These are high-priority issues and the main purpose of this repository's issue tracker. Make the issue with a short description, and then once the issue is created, add a comment with as many details as possible. Ping me (@HACKERALERT) in the comment so that I can get to it as soon as possible. Keep in mind that I define "bug" as something wrong with Picocrypt's code itself. If it's not Picocrypt's fault, it's not a bug.
These are important; make the issue with a short description, and then once the issue is created, add a comment with as many details as possible. Ping me (@HACKERALERT) in the comment so that I can get to it as soon as possible. Keep in mind that I define "bug" as something wrong with Picocrypt's code itself. If it's not Picocrypt's fault, it's not a bug.
- type: markdown
attributes:
value: |
@ -32,7 +32,7 @@ body:
- type: markdown
attributes:
value: |
Usually these issues are not directly caused by Picocrypt's code. Create the issue and in a separate comment, provide details about the environment you're running in (like OS, DE, etc.). **Do not ping me initially.** Let the issue sit for at least *3 days* to allow other users to potentially help you resolve the issue. If after 3 days, you haven't figured things out, then you may ping me (@HACKERALERT).
Usually these issues are not directly caused by Picocrypt's code. If you're on Windows, see [here](https://github.com/Picocrypt/Picocrypt/issues/91). If you're on Linux, install some packages and try again (see [here](https://github.com/Picocrypt/Picocrypt/tree/main/src#1-prerequisites)). Picocrypt only targets Windows 11, Ubuntu 24/Debian 12, and macOS 15 or later, so *do not create an issue if your OS is older than those; that is your problem, not mine*. If none of the points above help, create the issue and in a separate comment, provide details about the environment you're running in (like OS, DE, etc.). **Do not ping me initially.** Let the issue sit for at least *5 days* to allow other users to potentially help you resolve the issue. If after 5 days, you haven't figured things out, then you may ping me (@HACKERALERT).
- type: markdown
attributes:
value: |
@ -40,7 +40,7 @@ body:
- type: markdown
attributes:
value: |
These are unpreventable; Picocrypt is cryptography, file deletion, and passwords bundled into an executable... which looks similar to ransomware, unfortunately. Please report these false positives to your antivirus software provider and do not create an issue about it.
These are unpreventable; report them as false positives to your antivirus software provider and **do not create an issue about it**.
- type: markdown
attributes:
value: |
@ -48,7 +48,7 @@ body:
- type: markdown
attributes:
value: |
Create the issue and ask your question or support request in a separate comment. **Do not ping me initially.** Let the issue sit for at least *5 days* to give other users a chance to help you first. If after 5 days, you have not received any assistance, then you may ping me (@HACKERALERT).
Create the issue and ask your question or support request in a separate comment. **Do not ping me initially.** Let the issue sit for at least *10 days* to give other users a chance to help you first. If after 10 days, you have not received any assistance, then you may ping me (@HACKERALERT).
- type: markdown
attributes:
value: |
@ -56,7 +56,7 @@ body:
- type: markdown
attributes:
value: |
Picocrypt is mature software; I do not intend to add any new features. Generally, do not create any feature requests unless it's very minor and can be implemented with low effort and minimal impact on reliability and security. What is considered "minor" is subjective, but here is an example: "the ability to decrypt a volume entirely in-memory" is pretty significant, while "auto start encryption on pressing the Enter key" is relatively minor. A proof-of-concept link to code or a fork would be appreciated.
Picocrypt is mature software; I do not intend to add any new features. **Do not create these types of issues.**
- type: markdown
attributes:
value: |
@ -64,7 +64,7 @@ body:
- type: markdown
attributes:
value: |
Picocrypt prioritizes correctness and reliability over performance, so many parts of the code are written sequentially and don't use concurrency. This is intentional and need not be pointed out. Unless performance is absolutely atrocious to the point where it is indicative of a potential bug, do not make issues about performance.
Picocrypt prioritizes correctness and reliability over performance, so many parts of the code are written sequentially and don't use concurrency. This is intentional and need not be pointed out. Unless performance is absolutely atrocious to the point where it is indicative of a potential bug, **do not make issues about performance**.
- type: markdown
attributes:
value: |
@ -72,8 +72,9 @@ body:
- type: markdown
attributes:
value: |
You will have to use your best judgement here. Read the sections above to get an idea of what I expect to see and do what you think is best. Ideally, ping me only if sufficient time has passed for other users to assist/answer you, or it is best addressed by me directly.
You will have to use your best judgement here. Read the sections above to get an idea of what I expect to see and do what you think is best. Ideally, ping me only if sufficient time has passed for other users to assist/answer you, or it is best addressed by me directly. You must first look through existing issues or do a web search (AI can help!) before creating the issue. While I am allowing these generic issues to be made, if they become a hassle, I reserve the right to disallow them in the future.
- type: checkboxes
id: confirmation
attributes:
label: "Please confirm:"
options:
@ -83,7 +84,12 @@ body:
required: true
- label: "I acknowledge my issue may be ignored or closed without explanation"
required: true
- label: "I have looked through previous issues and related info already"
required: true
- label: "I will remember to close my issue when it is resolved"
required: true
- type: input
id: summary
attributes:
label: "Describe the issue briefly in a few sentences:"
description: "You can add more details in a separate comment after creating the issue."

21
.github/workflows/close-issues.yml vendored Normal file
View file

@ -0,0 +1,21 @@
name: Close inactive issues
on:
schedule:
- cron: "30 1 * * *"
jobs:
close-issues:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v9
with:
days-before-issue-stale: 30
days-before-issue-close: 14
stale-issue-label: "stale"
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
days-before-pr-stale: -1
days-before-pr-close: -1
repo-token: ${{ secrets.GITHUB_TOKEN }}

View file

@ -1,6 +1,17 @@
# Future
<ul>
<li>Migrate golang.org/x/crypto to standard library imports (https://github.com/golang/go/issues/65269)</li>
<li>Figure out how to remove use of temporary files completely</li>
</ul>
# v1.48 (Released 04/15/2025)
<ul>
<li>✓ Allow pressing 'Enter' key to press Start/Process button</li>
<li>✓ Update "Encrypt" button to "Zip and Encrypt" if multiple files</li>
<li>✓ Give user estimated required free disk space in status label</li>
<li>✓ Encrypt previously unencrypted temporary zip files</li>
<li>✓ Add `.incomplete` to filenames while work is in progress</li>
<li>✓ Use `encrypted-*.zip.pcv` output name instead of `Encrypted.zip.pcv`</li>
<li>✓ Use 0700 permissions when auto unzipping and creating folders</li>
</ul>
# v1.47 (Released 02/19/2025)

View file

@ -18,8 +18,6 @@ Picocrypt is a very small (hence <i>Pico</i>), very simple, yet very secure encr
## Windows
Picocrypt for Windows is as simple as it gets. To download the latest, standalone, and portable executable for Windows, click <a href="https://github.com/Picocrypt/Picocrypt/releases/latest/download/Picocrypt.exe">here</a>. If Microsoft Defender or your antivirus flags Picocrypt as a virus, please do your part and submit it as a false positive for the betterment of everyone.
If you use Picocrypt frequently, you can download an installer <a href="https://github.com/Picocrypt/Picocrypt/releases/download/1.47/Install-Picocrypt.exe">here</a> for easier launching. It does not require any admin permissions to install and it also bundles a software OpenGL renderer for compatibility, so if the portable executable isn't working, this installer likely will.
## macOS
Picocrypt for macOS is very simple as well. Download Picocrypt <a href="https://github.com/Picocrypt/Picocrypt/releases/latest/download/Picocrypt.dmg">here</a>, open the container, and drag Picocrypt to your Applications. You may need to manually trust the app from a terminal and control-click on the app if macOS prevents you from opening it:
```

View file

@ -1 +1 @@
1.47
1.48

View file

@ -1,6 +1,6 @@
1 VERSIONINFO
FILEVERSION 1,47,0,0
PRODUCTVERSION 1,47,0,0
FILEVERSION 1,48,0,0
PRODUCTVERSION 1,48,0,0
FILEOS 0x40004
FILETYPE 0x1
{
@ -8,7 +8,7 @@ BLOCK "StringFileInfo"
{
BLOCK "040904B0"
{
VALUE "FileVersion", "1.47"
VALUE "FileVersion", "1.48"
VALUE "LegalCopyright", "\xA9 Evan Su & contributors, GPLv3"
VALUE "ProductName", "Picocrypt"
}

View file

@ -2,7 +2,7 @@ package main
/*
Picocrypt v1.47
Picocrypt v1.48
Copyright (c) Evan Su
Released under a GNU GPL v3 License
https://github.com/Picocrypt/Picocrypt
@ -60,7 +60,7 @@ var TRANSPARENT = color.RGBA{0x00, 0x00, 0x00, 0x00}
// Generic variables
var window *giu.MasterWindow
var version = "v1.47"
var version = "v1.48"
var dpi float32
var mode string
var working bool
@ -130,6 +130,8 @@ var startLabel = "Start"
var mainStatus = "Ready"
var mainStatusColor = WHITE
var popupStatus string
var usingTempZip bool
var requiredFreeSpace int64
// Progress variables
var progress float32
@ -173,10 +175,144 @@ func (p *compressorProgress) Read(data []byte) (int, error) {
return read, err
}
type encryptedZipWriter struct {
_w io.Writer
_cipher *chacha20.Cipher
}
func (ezw *encryptedZipWriter) Write(data []byte) (n int, err error) {
dst := make([]byte, len(data))
ezw._cipher.XORKeyStream(dst, data)
return ezw._w.Write(dst)
}
type encryptedZipReader struct {
_r io.Reader
_cipher *chacha20.Cipher
}
func (ezr *encryptedZipReader) Read(data []byte) (n int, err error) {
src := make([]byte, len(data))
n, err = ezr._r.Read(src)
if err == nil && n > 0 {
dst := make([]byte, n)
ezr._cipher.XORKeyStream(dst, src[:n])
copy(data, dst)
}
return n, err
}
var onClickStartButton = func() {
// Start button should be disabled if these conditions are true; don't do anything if so
if (len(keyfiles) == 0 && password == "") || (mode == "encrypt" && password != cpassword) {
return
}
if keyfile && keyfiles == nil {
mainStatus = "Please select your keyfiles"
mainStatusColor = RED
return
}
tmp, err := strconv.Atoi(splitSize)
if split && (splitSize == "" || tmp <= 0 || err != nil) {
mainStatus = "Invalid chunk size"
mainStatusColor = RED
return
}
// Check if output file already exists
_, err = os.Stat(outputFile)
// Check if any split chunks already exist
if split {
names, _ := filepath.Glob(outputFile + ".*")
if len(names) > 0 {
err = nil
} else {
err = os.ErrNotExist
}
}
// If files already exist, show the overwrite modal
if err == nil && !recursively {
showOverwrite = true
modalId++
giu.Update()
} else { // Nothing to worry about, start working
showProgress = true
fastDecode = true
canCancel = true
modalId++
giu.Update()
if !recursively {
go func() {
work()
working = false
showProgress = false
giu.Update()
}()
} else {
// Store variables as they will be cleared
oldPassword := password
oldKeyfile := keyfile
oldKeyfiles := keyfiles
oldKeyfileOrdered := keyfileOrdered
oldKeyfileLabel := keyfileLabel
oldComments := comments
oldParanoid := paranoid
oldReedsolo := reedsolo
oldDeniability := deniability
oldSplit := split
oldSplitSize := splitSize
oldSplitSelected := splitSelected
oldDelete := delete
files := allFiles
go func() {
for _, file := range files {
// Simulate dropping the file
onDrop([]string{file})
// Restore variables and options
password = oldPassword
cpassword = oldPassword
keyfile = oldKeyfile
keyfiles = oldKeyfiles
keyfileOrdered = oldKeyfileOrdered
keyfileLabel = oldKeyfileLabel
comments = oldComments
paranoid = oldParanoid
reedsolo = oldReedsolo
deniability = oldDeniability
split = oldSplit
splitSize = oldSplitSize
splitSelected = oldSplitSelected
delete = oldDelete
work()
if !working {
resetUI()
cancel(nil, nil)
showProgress = false
giu.Update()
return
}
}
working = false
showProgress = false
giu.Update()
}()
}
}
}
// The main user interface
func draw() {
giu.SingleWindow().Flags(524351).Layout(
giu.Custom(func() {
if giu.IsKeyReleased(giu.KeyEnter) {
onClickStartButton()
return
}
if showPassgen {
giu.PopupModal("Generate password:##"+strconv.Itoa(modalId)).Flags(6).Layout(
giu.Row(
@ -499,16 +635,8 @@ func draw() {
giu.Checkbox("Paranoid mode", &paranoid),
giu.Tooltip("Provides the highest level of security attainable"),
giu.Dummy(-170, 0),
giu.Style().SetDisabled(recursively).To(
giu.Checkbox("Compress files", &compress).OnChange(func() {
if !(len(allFiles) > 1 || len(onlyFolders) > 0) {
if compress {
outputFile = filepath.Join(filepath.Dir(outputFile), "Encrypted") + ".zip.pcv"
} else {
outputFile = filepath.Join(filepath.Dir(outputFile), filepath.Base(inputFile)) + ".pcv"
}
}
}),
giu.Style().SetDisabled(recursively || !(len(allFiles) > 1 || len(onlyFolders) > 0)).To(
giu.Checkbox("Compress files", &compress),
giu.Tooltip("Compress files with Deflate before encrypting"),
),
).Build()
@ -612,7 +740,7 @@ func draw() {
tmp := strings.TrimSuffix(filepath.Base(outputFile), ".pcv")
f.SetInitFilename(strings.TrimSuffix(tmp, filepath.Ext(tmp)))
if mode == "encrypt" && (len(allFiles) > 1 || len(onlyFolders) > 0 || compress) {
f.SetInitFilename("Encrypted")
f.SetInitFilename("encrypted-" + strconv.Itoa(int(time.Now().Unix())))
}
// Get the chosen file path
@ -653,106 +781,24 @@ func draw() {
return startLabel
}
return "Process"
}()).Size(giu.Auto, 34).OnClick(func() {
if keyfile && keyfiles == nil {
mainStatus = "Please select your keyfiles"
mainStatusColor = RED
return
}
tmp, err := strconv.Atoi(splitSize)
if split && (splitSize == "" || tmp <= 0 || err != nil) {
mainStatus = "Invalid chunk size"
mainStatusColor = RED
return
}
// Check if output file already exists
_, err = os.Stat(outputFile)
// Check if any split chunks already exist
if split {
names, _ := filepath.Glob(outputFile + ".*")
if len(names) > 0 {
err = nil
} else {
err = os.ErrNotExist
}
}
// If files already exist, show the overwrite modal
if err == nil && !recursively {
showOverwrite = true
modalId++
giu.Update()
} else { // Nothing to worry about, start working
showProgress = true
fastDecode = true
canCancel = true
modalId++
giu.Update()
if !recursively {
go func() {
work()
working = false
showProgress = false
giu.Update()
}()
} else {
// Store variables as they will be cleared
oldPassword := password
oldKeyfile := keyfile
oldKeyfiles := keyfiles
oldKeyfileOrdered := keyfileOrdered
oldKeyfileLabel := keyfileLabel
oldComments := comments
oldParanoid := paranoid
oldReedsolo := reedsolo
oldDeniability := deniability
oldSplit := split
oldSplitSize := splitSize
oldSplitSelected := splitSelected
oldDelete := delete
files := allFiles
go func() {
for _, file := range files {
// Simulate dropping the file
onDrop([]string{file})
// Restore variables and options
password = oldPassword
cpassword = oldPassword
keyfile = oldKeyfile
keyfiles = oldKeyfiles
keyfileOrdered = oldKeyfileOrdered
keyfileLabel = oldKeyfileLabel
comments = oldComments
paranoid = oldParanoid
reedsolo = oldReedsolo
deniability = oldDeniability
split = oldSplit
splitSize = oldSplitSize
splitSelected = oldSplitSelected
delete = oldDelete
work()
if !working {
resetUI()
cancel(nil, nil)
showProgress = false
giu.Update()
return
}
}
working = false
showProgress = false
giu.Update()
}()
}
}
}),
}()).Size(giu.Auto, 34).OnClick(onClickStartButton),
giu.Custom(func() {
if mainStatus != "Ready" {
giu.Style().SetColor(giu.StyleColorText, mainStatusColor).To(
giu.Label(mainStatus),
),
).Build()
return
}
if requiredFreeSpace > 0 {
giu.Style().SetColor(giu.StyleColorText, WHITE).To(
giu.Label("Ready (ensure " + sizeify(requiredFreeSpace) + " of disk space is free)"),
).Build()
} else {
giu.Style().SetColor(giu.StyleColorText, WHITE).To(
giu.Label("Ready"),
).Build()
}
}),
),
giu.Custom(func() {
@ -819,12 +865,18 @@ func onDrop(names []string) {
folders++
mode = "encrypt"
inputLabel = "1 folder"
startLabel = "Encrypt"
startLabel = "Zip and Encrypt"
onlyFolders = append(onlyFolders, names[0])
inputFile = filepath.Join(filepath.Dir(names[0]), "Encrypted") + ".zip"
inputFile = filepath.Join(filepath.Dir(names[0]), "encrypted-"+strconv.Itoa(int(time.Now().Unix()))) + ".zip"
outputFile = inputFile + ".pcv"
size, err := dirSize(names[0])
if err != nil {
panic(err)
}
requiredFreeSpace = 2 * size
} else { // A file was dropped
files++
requiredFreeSpace += stat.Size()
// Is the file a part of a split volume?
nums := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}
@ -861,6 +913,7 @@ func onDrop(names []string) {
}
totalFiles++
compressTotal += stat.Size()
requiredFreeSpace += stat.Size()
}
} else {
outputFile = names[0][:len(names[0])-4]
@ -950,7 +1003,7 @@ func onDrop(names []string) {
}
} else { // There are multiple dropped items
mode = "encrypt"
startLabel = "Encrypt"
startLabel = "Zip and Encrypt"
// Go through each dropped item and add to corresponding slices
for _, name := range names {
@ -964,6 +1017,7 @@ func onDrop(names []string) {
allFiles = append(allFiles, name)
compressTotal += stat.Size()
requiredFreeSpace += 2 * stat.Size()
inputLabel = fmt.Sprintf("Scanning files... (%s)", sizeify(compressTotal))
giu.Update()
}
@ -987,8 +1041,9 @@ func onDrop(names []string) {
}
// Set the input and output paths
inputFile = filepath.Join(filepath.Dir(names[0]), "Encrypted") + ".zip"
inputFile = filepath.Join(filepath.Dir(names[0]), "encrypted-"+strconv.Itoa(int(time.Now().Unix()))) + ".zip"
outputFile = inputFile + ".pcv"
usingTempZip = true
}
// Recursively add all files in 'onlyFolders' to 'allFiles'
@ -1001,6 +1056,7 @@ func onDrop(names []string) {
if err == nil && !stat.IsDir() {
allFiles = append(allFiles, path)
compressTotal += stat.Size()
requiredFreeSpace += 2 * stat.Size()
inputLabel = fmt.Sprintf("Scanning files... (%s)", sizeify(compressTotal))
giu.Update()
}
@ -1033,6 +1089,17 @@ func work() {
var keyfileHashRef []byte // Same as 'keyfileHash', but used for comparison
var authTag []byte // 64-byte authentication tag (BLAKE2b or HMAC-SHA3)
var tempZipCipherW *chacha20.Cipher
var tempZipCipherR *chacha20.Cipher
var tempZipInUse bool = false
func() {
key, nonce := make([]byte, 32), make([]byte, 12)
rand.Read(key)
rand.Read(nonce)
tempZipCipherW, _ = chacha20.NewUnauthenticatedCipher(key, nonce)
tempZipCipherR, _ = chacha20.NewUnauthenticatedCipher(key, nonce)
}()
// Combine/compress all files into a .zip file if needed
if len(allFiles) > 1 || len(onlyFolders) > 0 || compress {
// Consider case where compressing only one file
@ -1050,7 +1117,7 @@ func work() {
}
// Open a temporary .zip for writing
inputFile = strings.TrimSuffix(outputFile, ".pcv")
inputFile = strings.TrimSuffix(outputFile, ".pcv") + ".tmp"
file, err := os.Create(inputFile)
if err != nil { // Make sure file is writable
accessDenied("Write")
@ -1058,7 +1125,12 @@ func work() {
}
// Add each file to the .zip
writer := zip.NewWriter(file)
tempZip := encryptedZipWriter{
_w: file,
_cipher: tempZipCipherW,
}
tempZipInUse = true
writer := zip.NewWriter(&tempZip)
compressStart = time.Now()
for i, path := range files {
progressInfo = fmt.Sprintf("%d/%d", i+1, len(files))
@ -1318,7 +1390,7 @@ func work() {
}
// Create the output file
fout, err = os.Create(outputFile)
fout, err = os.Create(outputFile + ".incomplete")
if err != nil {
fin.Close()
if len(allFiles) > 1 || len(onlyFolders) > 0 || compress {
@ -1403,7 +1475,7 @@ func work() {
if len(allFiles) > 1 || len(onlyFolders) > 0 || compress {
os.Remove(inputFile)
}
os.Remove(outputFile)
os.Remove(fout.Name())
return
}
}
@ -1635,7 +1707,7 @@ func work() {
}
// Create the output file for decryption
fout, err = os.Create(outputFile)
fout, err = os.Create(outputFile + ".incomplete")
if err != nil {
fin.Close()
if recombine {
@ -1691,13 +1763,17 @@ func work() {
// Start the main encryption process
canCancel = true
startTime := time.Now()
tempZip := encryptedZipReader{
_r: fin,
_cipher: tempZipCipherR,
}
for {
if !working {
cancel(fin, fout)
if recombine || len(allFiles) > 1 || len(onlyFolders) > 0 || compress {
os.Remove(inputFile)
}
os.Remove(outputFile)
os.Remove(fout.Name())
return
}
@ -1708,7 +1784,13 @@ func work() {
} else {
src = make([]byte, MiB)
}
size, err := fin.Read(src)
var size int
if tempZipInUse {
size, err = tempZip.Read(src)
} else {
size, err = fin.Read(src)
}
if err != nil {
break
}
@ -1828,7 +1910,7 @@ func work() {
if recombine || len(allFiles) > 1 || len(onlyFolders) > 0 || compress {
os.Remove(inputFile)
}
os.Remove(outputFile)
os.Remove(fout.Name())
return
}
@ -1907,6 +1989,8 @@ func work() {
fin.Close()
fout.Close()
os.Rename(outputFile+".incomplete", outputFile)
// Add plausible deniability
if mode == "encrypt" && deniability {
popupStatus = "Adding plausible deniability..."
@ -1914,13 +1998,13 @@ func work() {
giu.Update()
// Get size of volume for showing progress
stat, _ := os.Stat(fout.Name())
stat, _ := os.Stat(outputFile)
total := stat.Size()
// Rename the output volume to free up the filename
os.Rename(fout.Name(), fout.Name()+".tmp")
fin, _ := os.Open(fout.Name() + ".tmp")
fout, _ := os.Create(fout.Name())
os.Rename(outputFile, outputFile+".tmp")
fin, _ := os.Open(outputFile + ".tmp")
fout, _ := os.Create(outputFile + ".incomplete")
// Use a random Argon2 salt and XChaCha20 nonce
salt := make([]byte, 16)
@ -1970,6 +2054,7 @@ func work() {
fin.Close()
fout.Close()
os.Remove(fin.Name())
os.Rename(outputFile+".incomplete", outputFile)
canCancel = true
giu.Update()
}
@ -2014,7 +2099,7 @@ func work() {
startTime := time.Now()
for i := 0; i < chunks; i++ {
// Make the chunk
fout, _ := os.Create(fmt.Sprintf("%s.%d", outputFile, i))
fout, _ := os.Create(fmt.Sprintf("%s.%d.incomplete", outputFile, i))
done := 0
// Copy data into the chunk
@ -2080,6 +2165,10 @@ func work() {
fin.Close()
os.Remove(outputFile)
names, _ = filepath.Glob(outputFile + ".*.incomplete")
for _, i := range names {
os.Rename(i, strings.TrimSuffix(i, ".incomplete"))
}
}
canCancel = false
@ -2251,6 +2340,8 @@ func resetUI() {
mainStatus = "Ready"
mainStatusColor = WHITE
popupStatus = ""
usingTempZip = false
requiredFreeSpace = 0
progress = 0
progressInfo = ""
@ -2395,7 +2486,7 @@ func unpackArchive(zipPath string) error {
// Make directory if current entry is a folder
if f.FileInfo().IsDir() {
if err := os.MkdirAll(outPath, f.Mode()); err != nil {
if err := os.MkdirAll(outPath, 0700); err != nil {
return err
}
}
@ -2414,7 +2505,7 @@ func unpackArchive(zipPath string) error {
outPath := filepath.Join(extractDir, f.Name)
// Otherwise create necessary parent directories
if err := os.MkdirAll(filepath.Dir(outPath), 0755); err != nil {
if err := os.MkdirAll(filepath.Dir(outPath), 0700); err != nil {
return err
}
@ -2462,6 +2553,20 @@ func unpackArchive(zipPath string) error {
return nil
}
func dirSize(path string) (int64, error) {
var size int64
err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
size += info.Size()
}
return err
})
return size, err
}
func main() {
// Create the main window
window = giu.NewMasterWindow("Picocrypt "+version[1:], 318, 507, giu.MasterWindowFlagsNotResizable)

View file

@ -3,18 +3,18 @@ module Picocrypt
go 1.24.2
require (
github.com/Picocrypt/dialog v0.0.0-20240831001746-9ca708a9cd29
github.com/Picocrypt/giu v0.0.0-20240831005244-5771b35043ac
github.com/Picocrypt/imgui-go v0.0.0-20240831004007-6f60d7beadf6
github.com/Picocrypt/infectious v0.0.0-20240830233326-3a050f65f9ec
github.com/Picocrypt/dialog v0.0.0-20250412233924-78f7b909315b
github.com/Picocrypt/giu v0.0.0-20250412235908-fe90a482e6f2
github.com/Picocrypt/imgui-go v0.0.0-20250412235405-d86b230f5fbb
github.com/Picocrypt/infectious v0.0.0-20250412183341-9f88c6307b39
github.com/Picocrypt/serpent v0.0.0-20240830233833-9ad6ab254fd7
github.com/Picocrypt/zxcvbn-go v0.0.0-20240831000415-fccb38ccb913
github.com/Picocrypt/zxcvbn-go v0.0.0-20250412183938-d59695960527
golang.org/x/crypto v0.37.0
)
require (
github.com/Picocrypt/gl v0.0.0-20240831002619-6531d2bba5fc // indirect
github.com/Picocrypt/glfw/v3.3/glfw v0.0.0-20240831003212-7f16c5fb374b // indirect
github.com/Picocrypt/gl v0.0.0-20250412234430-767b58dbf936 // indirect
github.com/Picocrypt/glfw/v3.3/glfw v0.0.0-20250412234750-7b96bfdb8dd8 // indirect
github.com/Picocrypt/mainthread v0.0.0-20240831004314-496f638392b3 // indirect
github.com/Picocrypt/w32 v0.0.0-20240831001500-1183079d4d57 // indirect
golang.org/x/sys v0.32.0 // indirect

View file

@ -1,23 +1,23 @@
github.com/Picocrypt/dialog v0.0.0-20240831001746-9ca708a9cd29 h1:WIgRST/mpLiBEG2MF5MRPBDYYevLw7y14cwUEDjG5+Q=
github.com/Picocrypt/dialog v0.0.0-20240831001746-9ca708a9cd29/go.mod h1:raXVkdcX4495+fW9Ac+kvPMHRNk0rOcNXEWFD71B2As=
github.com/Picocrypt/giu v0.0.0-20240831005244-5771b35043ac h1:Z21enGbi450NyI7UZSoEuu//axifyGl63BJVjHX3ZXc=
github.com/Picocrypt/giu v0.0.0-20240831005244-5771b35043ac/go.mod h1:x7jbmZVofU9rn5WJj2+riU85Zo0MFlfp1sMTnKFQhc0=
github.com/Picocrypt/gl v0.0.0-20240831002619-6531d2bba5fc h1:5ckKMFhiz/Af6+sdkGlw74BU+rKRmoFWqU/rXHGUe3g=
github.com/Picocrypt/gl v0.0.0-20240831002619-6531d2bba5fc/go.mod h1:VknKAZzEoKP9nqrc/dveCwR5L01B9V8yLqtpvYmQ3DA=
github.com/Picocrypt/glfw/v3.3/glfw v0.0.0-20240831003212-7f16c5fb374b h1:hSaQU4P9KbMg9s2Jp2mTk9G5G+zkf4Yse5YRoxWTDTk=
github.com/Picocrypt/glfw/v3.3/glfw v0.0.0-20240831003212-7f16c5fb374b/go.mod h1:r5awTCSm/ugmTRKmT8Hr0T4xGPI6K35eFK0s3jYCW+s=
github.com/Picocrypt/imgui-go v0.0.0-20240831004007-6f60d7beadf6 h1:Y6SuxbSkQSU1hdEOpoMvp6Akq3RVX6KP1U4pKjGv3qo=
github.com/Picocrypt/imgui-go v0.0.0-20240831004007-6f60d7beadf6/go.mod h1:mGfOCkgyafVMIs1tU70va3lFSh6hSb+Vq4paVLX1Fjg=
github.com/Picocrypt/infectious v0.0.0-20240830233326-3a050f65f9ec h1:/cop0/v0HxIJm1XGDgIlzNJ3e4HhM8nIUPZi5RZ/n1w=
github.com/Picocrypt/infectious v0.0.0-20240830233326-3a050f65f9ec/go.mod h1:aaFq/WMVxrU2Exl/tXbTFSXajZrqw0mgn/wi42n0fK4=
github.com/Picocrypt/dialog v0.0.0-20250412233924-78f7b909315b h1:k5YGEx61N6K8l2l6AQ1u5W2aR+47sVZWFyqXS/f5lIA=
github.com/Picocrypt/dialog v0.0.0-20250412233924-78f7b909315b/go.mod h1:OyaP0Tz19qL3RAGq5Ntues+WVrIbHh5MrfqoA/qhqeg=
github.com/Picocrypt/giu v0.0.0-20250412235908-fe90a482e6f2 h1:SPR2efZTpZJON/2mNifLi68Gl9Epxh/1nXb3kGGHCcg=
github.com/Picocrypt/giu v0.0.0-20250412235908-fe90a482e6f2/go.mod h1:jd6AonK0ZI02R7GqLWb4gWJz/A2ClF36Y4fFMR8Lzbk=
github.com/Picocrypt/gl v0.0.0-20250412234430-767b58dbf936 h1:6MChjQ4AZC2ISBjbgZU/z6tSUxYP50NkRvAu0T2kjlY=
github.com/Picocrypt/gl v0.0.0-20250412234430-767b58dbf936/go.mod h1:pMdf3io/y3I+zYZ6/xFb3MlI2AgL38enDDIKuR0n2qA=
github.com/Picocrypt/glfw/v3.3/glfw v0.0.0-20250412234750-7b96bfdb8dd8 h1:i8wXJhSYIJTXb6sqBS6JZW7QosI9u8Ysy1BHZCTuZEc=
github.com/Picocrypt/glfw/v3.3/glfw v0.0.0-20250412234750-7b96bfdb8dd8/go.mod h1:cX5N2TrX03DC5i5eplxopglDue/vHDs+6Ng9G9uItaI=
github.com/Picocrypt/imgui-go v0.0.0-20250412235405-d86b230f5fbb h1:0XMtv2CXx3QvC9ikeH43fJl6ql8j5EsnaiOqhsToFnY=
github.com/Picocrypt/imgui-go v0.0.0-20250412235405-d86b230f5fbb/go.mod h1:N+NVTIIMz6icYltvaKHMvmVIllZDYUyscJ8wpcLKDZ4=
github.com/Picocrypt/infectious v0.0.0-20250412183341-9f88c6307b39 h1:czHyPoiNrILv9xjfQ87UFllJgak8W6gVcYkmfOay/BE=
github.com/Picocrypt/infectious v0.0.0-20250412183341-9f88c6307b39/go.mod h1:2ZVEanURxuWmxYZ6W6xMMy4ZR6xmQr16Vq/XPTLIspQ=
github.com/Picocrypt/mainthread v0.0.0-20240831004314-496f638392b3 h1:a62XmbZYhHGDR15C1gxp/IPfJX5SflrJuGpqNoOOK7w=
github.com/Picocrypt/mainthread v0.0.0-20240831004314-496f638392b3/go.mod h1:bsUKeX+/53rCTrItl3YUaeaN5tXl1v6326ZI90xIOsc=
github.com/Picocrypt/serpent v0.0.0-20240830233833-9ad6ab254fd7 h1:G36G2vmQAS7CVoHQrHDGAoCWll/0kPCI8Dk7mgwcJFE=
github.com/Picocrypt/serpent v0.0.0-20240830233833-9ad6ab254fd7/go.mod h1:BxsgRYwUVd92aEwXnXsfXfHw8aHlD/PUyExC/wwk9oI=
github.com/Picocrypt/w32 v0.0.0-20240831001500-1183079d4d57 h1:jusSXTp0h5wz8lxNXStw0jXr/ogZF6rzRF8gu0534hA=
github.com/Picocrypt/w32 v0.0.0-20240831001500-1183079d4d57/go.mod h1:FkeZHdKlITdP34VknO8yLdRY5pCi+iWEhDSA0YsBhZc=
github.com/Picocrypt/zxcvbn-go v0.0.0-20240831000415-fccb38ccb913 h1:QGv9QiTkNZ2iRmXEd7nNopaUJMBhBdBcsvWPl+v51AY=
github.com/Picocrypt/zxcvbn-go v0.0.0-20240831000415-fccb38ccb913/go.mod h1:dMyJ/0E4MeBo2wH1ZYmvPTChnYSj2MjLUndvYQt0vGw=
github.com/Picocrypt/zxcvbn-go v0.0.0-20250412183938-d59695960527 h1:IqypAzv5COsByMhiSdwlgafA5SBRG7Z0binnBSo3htM=
github.com/Picocrypt/zxcvbn-go v0.0.0-20250412183938-d59695960527/go.mod h1:u0rcUNEwy7st1DnPxdOJdTsh0aSRhrdMOxlIGrXR1Ls=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=