feat: add config, auth, fetcher, java modules
- config: OS-specific paths, launcher.json load/save, DefaultSettings with MRIXSCRAFT_SERVER_URL env override - auth: Yggdrasil client (authenticate, refresh, validate), session persistence in session.json, EnsureValid flow - fetcher: HTTP download with SHA-1 verification, WorkerPool for concurrent downloads - java: JRE detection (IsInstalled/Find), platform-specific executable name - utils: SHA1File, SHA1Bytes, Unzip with zip-slip protection - cmd/launcher: wire config + auth into main, session restore on startup Co-Authored-By: OWL <noreply@anthropic.com>
This commit is contained in:
@@ -1,2 +1,81 @@
|
||||
// package utils provides shared utility functions (SHA-1, ZIP, etc.).
|
||||
// package utils provides shared utility functions.
|
||||
package utils
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SHA1File computes the SHA-1 hex digest of the file at path.
|
||||
func SHA1File(path string) (string, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("opening %s: %w", path, err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
h := sha1.New()
|
||||
if _, err := io.Copy(h, f); err != nil {
|
||||
return "", fmt.Errorf("hashing %s: %w", path, err)
|
||||
}
|
||||
return fmt.Sprintf("%x", h.Sum(nil)), nil
|
||||
}
|
||||
|
||||
// SHA1Bytes returns the SHA-1 hex digest of data.
|
||||
func SHA1Bytes(data []byte) string {
|
||||
return fmt.Sprintf("%x", sha1.Sum(data))
|
||||
}
|
||||
|
||||
// Unzip extracts a zip archive into dest, preserving the directory structure.
|
||||
func Unzip(src, dest string) error {
|
||||
r, err := zip.OpenReader(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening zip %s: %w", src, err)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
for _, f := range r.File {
|
||||
target := filepath.Join(dest, f.Name)
|
||||
|
||||
// Prevent zip-slip.
|
||||
if !strings.HasPrefix(target, filepath.Clean(dest)+string(os.PathSeparator)) {
|
||||
return fmt.Errorf("illegal zip path: %s", f.Name)
|
||||
}
|
||||
|
||||
if f.FileInfo().IsDir() {
|
||||
if err := os.MkdirAll(target, f.Mode()); err != nil {
|
||||
return fmt.Errorf("creating directory %s: %w", target, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
|
||||
return fmt.Errorf("creating parent for %s: %w", target, err)
|
||||
}
|
||||
|
||||
out, err := os.OpenFile(target, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating file %s: %w", target, err)
|
||||
}
|
||||
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
out.Close()
|
||||
return fmt.Errorf("reading zip entry %s: %w", f.Name, err)
|
||||
}
|
||||
|
||||
if _, err := io.Copy(out, rc); err != nil {
|
||||
rc.Close()
|
||||
out.Close()
|
||||
return fmt.Errorf("extracting %s: %w", f.Name, err)
|
||||
}
|
||||
rc.Close()
|
||||
out.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user