吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 802|回复: 23
收起左侧

[其他原创] 原神的账号切换

  [复制链接]
xc4447 发表于 2024-11-18 00:11

原神的账号切换go语言实现

背景

因为一个群友帮别人托管账号,然后有这个需求,用其他语言写了一个,但是太复杂而且有点风险,于是抱着学习的目的用go实现了一个。

基本思路及功能

思路:登录账号后保存注册表,然后切换账号时将已保存的注册表覆盖当前的注册表实现切换账号的功能,没有根据某些cookie登录或者延长登录状态的功能。
网上已有命令行的代码实现,但是刚接触go,主要还是学着写一下,恳请各位斧正。

主要代码如下



[Golang] 纯文本查看 复制代码
package main

import (
	"fmt"
	"os/exec"
	"path/filepath"
	"time"
	"github.com/kbinani/screenshot"
	"github.com/lxn/walk"
	. "github.com/lxn/walk/declarative"
	"golang.org/x/sys/windows/registry"
)

var mw *walk.MainWindow
// var ni *walk.NotifyIcon

func main() {
	var cloudAccounts *walk.ComboBox
	var addButton, deleteButton, startButton, stopButton *walk.PushButton

	MainWindow{
		AssignTo: &mw,
		Title:    "账户管理器",
        // Icon:     "main.ico", // 设置主窗口图标
		Layout:   VBox{},
		Children: []Widget{
			GroupBox{
				Title:  "用户",
				Layout: VBox{},
				Children: []Widget{
					ComboBox{
						AssignTo: &cloudAccounts,
					},
					PushButton{
						AssignTo: &addButton,
						Text:     "添加",
						OnClicked: func() {
							addAccount(cloudAccounts)
						},
					},
					PushButton{
						AssignTo: &deleteButton,
						Text:     "删除",
						OnClicked: func() {
							deleteAccount(cloudAccounts)
						},
					},
					PushButton{
						AssignTo: &startButton,
						Text:     "启动",
						OnClicked: func() {
							startGame(cloudAccounts)
						},
					},
					PushButton{
						AssignTo: &stopButton,
						Text:     "结束",
						OnClicked: func() {
							stopGame()
						},
					},
				},
			},
		},
        
	}.Create()

    // 设置启动时的窗口大小
	mw.SetSize(walk.Size{Width: 300, Height: 200}) 
	// mw.SetX(700)
	// mw.SetY(300)
    //使用截图包获取屏幕尺寸
	screenBounds := screenshot.GetDisplayBounds(0)
    // 计算并设置窗口位置
    bounds := mw.Bounds()
    mw.SetX((screenBounds.Max.X ) / 2 - bounds.Width)
    mw.SetY((screenBounds.Max.Y) / 2 - bounds.Height)
	updateCloudAccounts(cloudAccounts)
	mw.Run()
}


func addAccount(cloudAccounts *walk.ComboBox) {
    // 创建一个对话框对象
    dlg, err := walk.NewDialog(nil)
    if err != nil {
        walk.MsgBox(nil, "错误", "创建对话框失败: "+err.Error(), walk.MsgBoxIconError)
        return
    }
    defer dlg.Dispose()

    // 设置对话框属性
    dlg.SetTitle("添加新账户")
    // // 获取屏幕尺寸并计算居中位置
	screenBounds := screenshot.GetDisplayBounds(0)
    dlg.SetSize(walk.Size{Width: 300, Height: 150})
    x := (screenBounds.Dx() - 400) / 2
    y := (screenBounds.Dy() - 250) / 2

    // 设置对话框位置
    dlg.SetBounds(walk.Rectangle{
        X: x,
        Y: y,
        Width:  300,
        Height: 150,
    })
    dlg.SetLayout(walk.NewVBoxLayout())

    // 使用这个简单的方法来居中显示对话框
    dlg.SetOwner(nil)

    // 创建一个文本标签控件
    label, err := walk.NewLabel(dlg)
    if err != nil {
        walk.MsgBox(nil, "错误", "创建标签失败: "+err.Error(), walk.MsgBoxIconError)
        return
    }
    label.SetText("请输入新账户名称:")

    // 创建一个文本框控件
    edit, err := walk.NewLineEdit(dlg)
    if err != nil {
        walk.MsgBox(nil, "错误", "创建输入框失败: "+err.Error(), walk.MsgBoxIconError)
        return
    }

    // 创建一个按钮控件
    button, err := walk.NewPushButton(dlg)
    if err != nil {
        walk.MsgBox(nil, "错误", "创建按钮失败: "+err.Error(), walk.MsgBoxIconError)
        return
    }
    button.SetText("确定")

    // 设置按钮点击事件
    var newAccountName string
    button.Clicked().Attach(func() {
        if edit.Text() == "" {
            walk.MsgBox(dlg, "错误", "账户名不能为空", walk.MsgBoxIconError)
            return
        }
        newAccountName = edit.Text()

        // 检查账户名是否已存在
        existingAccounts := cloudAccounts.Model().([]string)
        for _, account := range existingAccounts {
            if account == newAccountName {
                result := walk.MsgBox(dlg, "警告", "账户名已存在,是否覆盖?", walk.MsgBoxYesNo|walk.MsgBoxIconWarning)
                if result == walk.DlgCmdNo {
                    return // 用户选择不覆盖,返回输入状态
                }
                break // 用户选择覆盖,继续执行
            }
        }

        dlg.Accept()
    })

    // 显示对话框
    if dlg.Run() != walk.DlgCmdOK {
        return // 用户取消
    }

    // 源路径是当前用户的路径
    srcPath := `Software\miHoYo\原神`
    // 目标路径是用户列表中的新路径
    destPath := `Software\miHoYo\米哈游启动器\原神\` + newAccountName

    // 复制注册表项
    err = copyRegistryKey(srcPath, destPath)
    if err != nil {
        walk.MsgBox(nil, "错误", "复制账户失败: "+err.Error(), walk.MsgBoxIconError)
        return
    }

    // 更新云账户列表
    updateCloudAccounts(cloudAccounts)
    walk.MsgBox(nil, "成功", "账户已添加", walk.MsgBoxIconInformation)
}

//补充删除账户
func deleteAccount(cloudAccounts *walk.ComboBox) {
	selectedAccount := cloudAccounts.Text()
	if selectedAccount == "" {
		walk.MsgBox(nil, "错误", "请先选择要删除的账户", walk.MsgBoxIconError)
		return
	}

	result := walk.MsgBox(nil, "确认", "确定要删除账户 "+selectedAccount+" 吗?", walk.MsgBoxYesNo|walk.MsgBoxIconQuestion)
	if result != walk.DlgCmdYes {
		return
	}

	keyPath := `Software\miHoYo\米哈游启动器\原神\` + selectedAccount
	err := registry.DeleteKey(registry.CURRENT_USER, keyPath)
	if err != nil {
		walk.MsgBox(nil, "错误", "删除账户失败: "+err.Error(), walk.MsgBoxIconError)
		return
	}

	updateCloudAccounts(cloudAccounts)
	walk.MsgBox(nil, "成功", "账户已删除", walk.MsgBoxIconInformation)
}


func updateCloudAccounts(cloudAccounts *walk.ComboBox) {
	key, err := registry.OpenKey(registry.CURRENT_USER, `Software\miHoYo\米哈游启动器\原神`, registry.ENUMERATE_SUB_KEYS)
	if err != nil {
		walk.MsgBox(nil, "错误", "无法打开注册表键: "+err.Error(), walk.MsgBoxIconError)
		return
	}
	defer key.Close()

	subKeyNames, err := key.ReadSubKeyNames(-1)
	if err != nil {
		walk.MsgBox(nil, "错误", "无法读取子键: "+err.Error(), walk.MsgBoxIconError)
		return
	}

	cloudAccounts.SetModel(subKeyNames)
}

func startGame(cloudAccounts *walk.ComboBox) {
    selectedAccount := cloudAccounts.Text()
    // 目标路径是当前用户的路径
    destPath := `Software\miHoYo\原神`
    // 源路径是用户列表中的新路径
    srcPath := `Software\miHoYo\米哈游启动器\原神\` + selectedAccount
    //需要对selectedAccount进行判断,如果为空,不进行复制,直接启动
	if selectedAccount != "" {
		err := copyRegistryKey(srcPath, destPath)
        if err != nil {
            walk.MsgBox(nil, "错误", "复制注册表失败: "+err.Error(), walk.MsgBoxIconError)
            return
        }
	}
	key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\原神`, registry.QUERY_VALUE)
	if err != nil {
		walk.MsgBox(nil, "错误", "无法打开注册表键: "+err.Error(), walk.MsgBoxIconError)
		return
	}
	defer key.Close()

	path, _, err := key.GetStringValue("InstallPath")
	if err != nil {
		walk.MsgBox(nil, "错误", "无法读取注册表值: "+err.Error(), walk.MsgBoxIconError)
		return
	}

    gamePath := filepath.Join(path, "Genshin Impact Game", "YuanShen.exe")

    // 显示游戏路径
    // result := walk.MsgBox(nil, "游戏路径", "游戏路径是:\n"+gamePath+"\n\n是否继续?", walk.MsgBoxOKCancel|walk.MsgBoxIconInformation)
    // if result != walk.DlgCmdOK {
    //     return // 用户选择取消,不继续执行
    // }

    // 先执行taskkill命令
    killCmd := exec.Command("taskkill", "/F", "/IM", "YuanShen.exe")
    killOutput, killErr := killCmd.CombinedOutput()
    if killErr != nil {
        fmt.Printf("关闭原神进程时出现错误(可能是进程不存在): %v\n输出: %s\n", killErr, string(killOutput))
        // 不要因为taskkill失败就中断程序,因为可能是游戏本来就没有运行
    }

    // 等待一小段时间,确保进程完全关闭
    time.Sleep(time.Second)

    // 然后启动游戏
    startCmd := exec.Command(gamePath)
    startErr := startCmd.Start()
    if startErr != nil {
        errorMsg := fmt.Sprintf("无法启动游戏: %v", startErr)
        fmt.Println(errorMsg)
        walk.MsgBox(nil, "错误", errorMsg, walk.MsgBoxIconError)
    } else {
        // walk.MsgBox(nil, "成功", "游戏已成功启动", walk.MsgBoxIconInformation)
    }
}

func stopGame() {
    cmd := exec.Command("taskkill", "/F", "/IM", "YuanShen.exe")
    output, err := cmd.CombinedOutput()
    if err != nil {
        if exitError, ok := err.(*exec.ExitError); ok {
            // 进程不存在时,taskkill 会返回错误码 128
            if exitError.ExitCode() == 128 {
                walk.MsgBox(nil, "提示", "未找到正在运行的原神进程", walk.MsgBoxIconInformation)
            } else {
                errorMsg := fmt.Sprintf("结束进程时出错: %v\n输出: %s", err, string(output))
                walk.MsgBox(nil, "错误", errorMsg, walk.MsgBoxIconError)
            }
        } else {
            errorMsg := fmt.Sprintf("执行命令时出错: %v\n输出: %s", err, string(output))
            walk.MsgBox(nil, "错误", errorMsg, walk.MsgBoxIconError)
        }
    } else {
        // walk.MsgBox(nil, "成功", "原神进程已成功结束", walk.MsgBoxIconInformation)
    }
}

func copyRegistryKey(srcPath, destPath string) error {
    srcKey, err := registry.OpenKey(registry.CURRENT_USER, srcPath, registry.READ)
    if err != nil {
        return err
    }
    defer srcKey.Close()

    destKey, _, err := registry.CreateKey(registry.CURRENT_USER, destPath, registry.ALL_ACCESS)
    if err != nil {
        return err
    }
    defer destKey.Close()

    // 复制值
    values, err := srcKey.ReadValueNames(-1)
    if err != nil {
        return err
    }
    for _, name := range values {
        value, valType, err := srcKey.GetValue(name, nil)
        if err != nil {
            return err
        }

        switch valType {
        case registry.SZ, registry.EXPAND_SZ:
            if err := destKey.SetStringValue(name, string(value)); err != nil {
                return err
            }
        case registry.BINARY:
            binaryData, _, err := srcKey.GetBinaryValue(name)
            if err != nil {
                return err
            }
            if err := destKey.SetBinaryValue(name, binaryData); err != nil {
                return err
            }
        case registry.DWORD:
            dwordVal, _, err := srcKey.GetIntegerValue(name)
            if err != nil {
                return err
            }
            if err := destKey.SetDWordValue(name, uint32(dwordVal)); err != nil {
                return err
            }
        case registry.QWORD:
            qwordVal, _, err := srcKey.GetIntegerValue(name)
            if err != nil {
                return err
            }
            if err := destKey.SetQWordValue(name, qwordVal); err != nil {
                return err
            }
        default:
            walk.MsgBox(nil, "错误", "不支持的注册表值类型", walk.MsgBoxIconError)
            return nil
        }
    }

    // 复制键值
    subKeys, err := srcKey.ReadSubKeyNames(-1)
    if err != nil {
        return err
    }
    for _, subKeyName := range subKeys {
        err := copyRegistryKey(srcPath+"\\"+subKeyName, destPath+"\\"+subKeyName)
        if err != nil {
            return err
        }
    }
    return nil
}

免费评分

参与人数 1吾爱币 +7 热心值 +1 收起 理由
苏紫方璇 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

yiluoen0502 发表于 2024-11-18 08:48
感谢分享,支持一下
xiaoyonggaoya 发表于 2024-11-18 07:47
之前研究过一下,在目录里有个叫mhy_user.json的文件,里面存的是账号登录信息,把它复制出来,需要的时候复制回去就能实现简单的账号切换
可惜现在忘了这文件放哪了,研究的是崩铁,不过原神的逻辑估计也差不多
K_OE_JC_CN 发表于 2024-11-18 01:11
好东西,以后让室友帮我代肝的时候就可以把这个打包发给他,减少他切换账号的时间了
Baishuu 发表于 2024-11-18 02:31
好东西!谢谢大佬!
sakuria 发表于 2024-11-18 02:58
go的网络相关项目有点多啊例如小猫咪啥的
zhangxiaoxiao 发表于 2024-11-18 05:50
学习了,谢谢楼主分享
zjtzjt 发表于 2024-11-18 06:43
感谢分享,原神启动
Appkbox 发表于 2024-11-18 08:19
借鉴一下大佬的思路,学习了
zzq23153 发表于 2024-11-18 08:56
感谢楼主分享,原神启动!!!
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 17:52

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表