//go:build mage // +build mage package main import ( "bytes" "fmt" "io/ioutil" "os" "path/filepath" "unicode" "github.com/magefile/mage/mg" "github.com/magefile/mage/sh" ) const ( zipName = "DigitalStorageTweaks.zip" contentDir = "./ContentLib" pluginFile = "./DigitalStorageTweaks.uplugin" targetDirs = "Windows WindowsServer LinuxServer" ) var Default = Package // Package creates the distribution zip file func Package() error { mg.Deps(SanitizeContent) defer Clean(false) // Clean temp dirs but keep zip fmt.Println("Creating target directories...") for _, dir := range []string{"Windows", "WindowsServer", "LinuxServer"} { if err := os.MkdirAll(dir, 0755); err != nil { return fmt.Errorf("failed to create directory %s: %v", dir, err) } fmt.Printf("Copying files to %s...\n", dir) if err := sh.Run("cp", "-r", contentDir, pluginFile, dir+"/"); err != nil { return fmt.Errorf("failed to copy files to %s: %v", dir, err) } } fmt.Println("Creating zip archive...") return sh.Run("7z", "a", "-r", zipName, "Windows/", "LinuxServer/", "WindowsServer/") } // Clean removes generated artifacts func Clean(includeZip bool) error { fmt.Println("Cleaning artifacts...") paths := []string{"Windows", "WindowsServer", "LinuxServer"} if includeZip { paths = append(paths, zipName) } for _, path := range paths { if err := sh.Rm(path); err != nil { return err } } return nil } // SanitizeContent cleans all text files of non-ASCII and ensures CRLF line endings func SanitizeContent() error { fmt.Println("Sanitizing content files...") // Process plugin file if err := sanitizeFile(pluginFile); err != nil { return fmt.Errorf("plugin file sanitization failed: %v", err) } // Process content directory return filepath.Walk(contentDir, func(path string, info os.FileInfo, err error) error { if err != nil || info.IsDir() { return err } // Skip binary files switch filepath.Ext(path) { case ".png", ".jpg", ".jpeg", ".bmp", ".gif", ".dds", ".tga", ".psd", ".fbx", ".uasset", ".umap": return nil default: return sanitizeFile(path) } }) } func sanitizeFile(path string) error { content, err := ioutil.ReadFile(path) if err != nil { return fmt.Errorf("failed to read %s: %v", path, err) } // First fix any encoding issues by converting to valid UTF-8 content = bytes.ToValidUTF8(content, []byte{}) // Remove non-ASCII characters but preserve standard whitespace cleanContent := bytes.Map(func(r rune) rune { switch { case r > unicode.MaxASCII: return -1 // Remove non-ASCII case r == '\t', r == '\n', r == '\r': return r // Keep standard whitespace case r < 32 || r == 127: return -1 // Remove control characters default: return r // Keep regular ASCII } }, content) // Normalize line endings to CRLF cleanContent = bytes.ReplaceAll(cleanContent, []byte("\r\n"), []byte("\n")) cleanContent = bytes.ReplaceAll(cleanContent, []byte("\n"), []byte("\r\n")) // Only write back if changes were made if !bytes.Equal(content, cleanContent) { fmt.Printf("Sanitizing %s\n", path) fileInfo, err := os.Stat(path) if err != nil { return fmt.Errorf("failed to get file info for %s: %v", path, err) } if err := ioutil.WriteFile(path, cleanContent, fileInfo.Mode()); err != nil { return fmt.Errorf("failed to write %s: %v", path, err) } } return nil }