lychee-slicer/patcher/patcher.go
2024-10-20 23:04:38 +02:00

170 lines
3.1 KiB
Go

package patcher
import (
"bytes"
"errors"
"io"
"log"
"os"
"path/filepath"
"github.com/tdewolff/parse/v2"
"github.com/tdewolff/parse/v2/js"
"vaclive.party/software/lychee-slicer/asar"
"vaclive.party/software/lychee-slicer/platform"
)
type walker struct {
funName string
fun *js.FuncDecl
}
func (w *walker) Enter(n js.INode) js.IVisitor {
switch n := n.(type) {
case *js.BindingElement:
if binding, ok := n.Binding.(*js.Var); ok && string(binding.Name()) == w.funName {
if fun, ok := n.Default.(*js.FuncDecl); ok {
w.fun = fun
}
}
case *js.FuncDecl:
if n.Name != nil && string(n.Name.Data) == w.funName {
w.fun = n
}
}
if w.fun != nil {
return nil
}
return w
}
func (w *walker) Exit(n js.INode) {}
func findFunction(ast *js.AST, name string) *js.FuncDecl {
w := &walker{funName: name}
js.Walk(w, ast)
return w.fun
}
func patchFile(program []byte) ([]byte, error) {
ast, err := js.Parse(parse.NewInputBytes([]byte(program)), js.Options{})
if err != nil {
return nil, err
}
fun := findFunction(ast, "verify")
if fun == nil {
return nil, errors.New("verify function not found")
}
// who needs verification anyway?
fun.Body.List = []js.IStmt{
&js.ReturnStmt{
Value: &js.LiteralExpr{
TokenType: js.TrueToken,
Data: []byte("true"),
},
},
}
stream := bytes.NewBuffer(nil)
ast.JS(stream)
return stream.Bytes(), nil
}
func Patch(program string) error {
asarFile := filepath.Join(filepath.Dir(program), "resources", "app.asar")
log.Printf("Opening %s", asarFile)
old, err := os.OpenFile(asarFile, os.O_RDONLY, 0)
if err != nil {
return err
}
defer old.Close()
stat, err := old.Stat()
if err != nil {
return err
}
fs, err := asar.OpenFilesystem(old, stat.Size())
if err != nil {
return err
}
if v := fs.GetMetaFlag("patched"); v != "" {
log.Printf("Already patched")
return nil
}
patch := func(name string) error {
log.Printf("Patching %s", name)
b, err := fs.ReadFile(name, true)
if err != nil {
return err
}
b, err = patchFile(b)
if err != nil {
return err
}
return fs.InsertFile(name, b, false)
}
if err := patch("node_modules/@mango3d/sign/dist/cjs/index.js"); err != nil {
return err
}
if err := patch("node_modules/@mango3d/sign/dist/esm/index.js"); err != nil {
return err
}
if err := fs.SetMetaFlag("patched", "true"); err != nil {
return err
}
saved, err := os.CreateTemp("", "patched")
if err != nil {
return err
}
defer saved.Close()
defer os.Remove(saved.Name())
if err := fs.Save(saved); err != nil {
return err
}
fs = nil
old.Close()
_, err = saved.Seek(0, io.SeekStart)
if err != nil {
return err
}
log.Printf("Replacing original asar with patched")
new, err := os.Create(asarFile)
if err != nil {
if err, ok := err.(*os.PathError); ok && platform.IsWindowsRetryable(err.Err) {
log.Printf("Windows rename failed, retrying")
saved.Close()
return platform.WindowsRename(saved.Name(), asarFile)
}
return err
}
defer new.Close()
_, err = io.Copy(new, saved)
if err != nil {
return err
}
new.Close()
saved.Close()
return err
}