diff --git a/.gitignore b/.gitignore index be910bc..619f459 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,5 @@ go.work.sum # syncthing files .stfolder -chatlog chatlog.exe# Added by goreleaser init: dist/ diff --git a/cmd/chatlog/cmd_dumpmemory.go b/cmd/chatlog/cmd_dumpmemory.go new file mode 100644 index 0000000..3d669cf --- /dev/null +++ b/cmd/chatlog/cmd_dumpmemory.go @@ -0,0 +1,148 @@ +package chatlog + +import ( + "archive/zip" + "fmt" + "io" + "os" + "path/filepath" + "runtime" + "time" + + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" + + "github.com/sjzar/chatlog/internal/wechat" + "github.com/sjzar/chatlog/internal/wechat/key/darwin/glance" +) + +func init() { + rootCmd.AddCommand(dumpmemoryCmd) +} + +var dumpmemoryCmd = &cobra.Command{ + Use: "dumpmemory", + Short: "dump memory", + Run: func(cmd *cobra.Command, args []string) { + if runtime.GOOS != "darwin" { + log.Info().Msg("dump memory only support macOS") + } + + session := time.Now().Format("20060102150405") + + dir, err := os.Getwd() + if err != nil { + log.Fatal().Err(err).Msg("get current directory failed") + return + } + log.Info().Msgf("current directory: %s", dir) + + // step 1. check pid + if err = wechat.Load(); err != nil { + log.Fatal().Err(err).Msg("load wechat failed") + return + } + accounts := wechat.GetAccounts() + if len(accounts) == 0 { + log.Fatal().Msg("no wechat account found") + return + } + + log.Info().Msgf("found %d wechat account", len(accounts)) + for i, a := range accounts { + log.Info().Msgf("%d. %s %d %s", i, a.FullVersion, a.PID, a.DataDir) + } + + return + + // step 2. dump memory + account := accounts[0] + file := fmt.Sprintf("wechat_%s_%d_%s.bin", account.FullVersion, account.PID, session) + path := filepath.Join(dir, file) + log.Info().Msgf("dumping memory to %s", path) + + g := glance.NewGlance(account.PID) + b, err := g.Read() + if err != nil { + log.Fatal().Err(err).Msg("read memory failed") + return + } + + if err = os.WriteFile(path, b, 0644); err != nil { + log.Fatal().Err(err).Msg("write memory failed") + return + } + + log.Info().Msg("dump memory success") + + // step 3. copy encrypted database file + dbFile := "db_storage/session/session.db" + if account.Version == 3 { + dbFile = "Session/session_new.db" + } + from := filepath.Join(account.DataDir, dbFile) + to := filepath.Join(dir, fmt.Sprintf("wechat_%s_%d_session.db", account.FullVersion, account.PID)) + + log.Info().Msgf("copying %s to %s", from, to) + b, err = os.ReadFile(from) + if err != nil { + log.Fatal().Err(err).Msg("read session.db failed") + return + } + if err = os.WriteFile(to, b, 0644); err != nil { + log.Fatal().Err(err).Msg("write session.db failed") + return + } + log.Info().Msg("copy session.db success") + + // step 4. package + zipFile := fmt.Sprintf("wechat_%s_%d_%s.zip", account.FullVersion, account.PID, session) + zipPath := filepath.Join(dir, zipFile) + log.Info().Msgf("packaging to %s", zipPath) + + zf, err := os.Create(zipPath) + if err != nil { + log.Fatal().Err(err).Msg("create zip file failed") + return + } + defer zf.Close() + + zw := zip.NewWriter(zf) + + for _, file := range []string{file, to} { + f, err := os.Open(file) + if err != nil { + log.Fatal().Err(err).Msg("open file failed") + return + } + defer f.Close() + info, err := f.Stat() + if err != nil { + log.Fatal().Err(err).Msg("get file info failed") + return + } + header, err := zip.FileInfoHeader(info) + if err != nil { + log.Fatal().Err(err).Msg("create zip file info header failed") + return + } + header.Name = filepath.Base(file) + header.Method = zip.Deflate + writer, err := zw.CreateHeader(header) + if err != nil { + log.Fatal().Err(err).Msg("create zip file header failed") + return + } + if _, err = io.Copy(writer, f); err != nil { + log.Fatal().Err(err).Msg("copy file to zip failed") + return + } + } + if err = zw.Close(); err != nil { + log.Fatal().Err(err).Msg("close zip writer failed") + return + } + + log.Info().Msgf("package success, please send %s to developer", zipPath) + }, +} diff --git a/internal/chatlog/ctx/context.go b/internal/chatlog/ctx/context.go index 11adb03..a97b833 100644 --- a/internal/chatlog/ctx/context.go +++ b/internal/chatlog/ctx/context.go @@ -74,6 +74,16 @@ func (c *Context) SwitchHistory(account string) { c.WorkDir = history.WorkDir c.HTTPEnabled = history.HTTPEnabled c.HTTPAddr = history.HTTPAddr + } else { + c.Account = "" + c.Platform = "" + c.Version = 0 + c.FullVersion = "" + c.DataKey = "" + c.DataDir = "" + c.WorkDir = "" + c.HTTPEnabled = false + c.HTTPAddr = "" } } diff --git a/internal/wechat/process/darwin/detector.go b/internal/wechat/process/darwin/detector.go index d3a978b..8107bc7 100644 --- a/internal/wechat/process/darwin/detector.go +++ b/internal/wechat/process/darwin/detector.go @@ -15,10 +15,10 @@ import ( ) const ( - V3ProcessName = "WeChat" - V4ProcessName = "Weixin" - V3DBFile = "Message/msg_0.db" - V4DBFile = "db_storage/message/message_0.db" + ProcessNameOfficial = "WeChat" + ProcessNameBeta = "Weixin" + V3DBFile = "Message/msg_0.db" + V4DBFile = "db_storage/session/session.db" ) // Detector 实现 macOS 平台的进程检测器 @@ -40,7 +40,7 @@ func (d *Detector) FindProcesses() ([]*model.Process, error) { var result []*model.Process for _, p := range processes { name, err := p.Name() - if err != nil || (name != V3ProcessName && name != V4ProcessName) { + if err != nil || (name != ProcessNameOfficial && name != ProcessNameBeta) { continue }