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 }