About EasyLogin
From Thunderstore[AI-generated] Remembers each server's join password and auto-fills/auto-submits it. Per-server records, auto-forget on wrong password, DPAPI-encrypted. Client-only, no server sync.
EasyLogin —— Valheim 联机密码管家(纯客户端)
> AI 生成声明:本模组由 AI 智能体(WorkBuddy)在用户的明确需求与逐项验收下编写; > 功能设计、真机测试与发布决定均由用户主导。
进过一次的服务器,密码自动记住;下次再进自动填/自动提交。
Valheim 原版没有这个能力——不是没做好,是根本没写。本模组用三个 Harmony 补丁补上, 不改游戏数据、不加网络校验、主机和队友装不装都完全不受影响。
一、效果
| 场景 | 原版 | 装了 EasyLogin | |---|---|---| | 第一次进某人的带密码服务器 | 手输密码 | 照常手输(模组会记下来) | | 第二次进同一台服务器 | 再输一遍 | 自动填好并提交,直接进 | | 房主改了密码 | 提示密码错误 | 自动忘掉旧密码,下次让你重新输(然后记住新的) | | 换另一台服务器 | 每次都要输 | 各存各的,互不干扰 |
每个服务器分开记录,匹配依据是 Steam ID / 跨平台 host ID / 房间码 / IP:端口, 不是"上一次输的密码"这种全局变量。
二、安装
把 EasyLogin.dll 丢进 BepInEx/plugins/ 即可(或者用 r2modman 导入 EasyLogin-1.0.0.zip)。
依赖只有 BepInExPack_Valheim。不需要 Jotunn,不需要任何前置框架。
三、原版为什么做不到(IL 实证)
进服密码唯一的入口是 ZNet.RPC_ClientHandshake(ZRpc rpc, bool needPassword, string serverPasswordSalt), 由服务端在握手时发起。1.0 正式版里的实现(assembly_valheim.dll,IL 偏移为函数内相对偏移):
0000 m_connectingDialog.gameObject.SetActive(false)
0011 m_serverPasswordSalt = serverPasswordSalt ← 服务端下发的随机盐
0017 if (!needPassword) { SendPeerInfo(rpc, ""); return; } → 0077
001A m_passwordDialog.gameObject.SetActive(true) ← 弹出要求输密码的框
002B var input = m_passwordDialog.GetComponent<GuiInputField>()
0037 input.text = ""
0042 input.ActivateInputField()
0047 input.OnInputSubmit.AddListener(OnPasswordEntered)
005D m_tempPasswordRPC = rpc
0064 call FejdStartup::get_ServerPassword
0069 brfalse.s → 0083 ← 原版唯一的"自动填充"
006B OnPasswordEntered(FejdStartup.ServerPassword)
也就是说:原版确实内置了自动填充,条件是 FejdStartup.ServerPassword != null。
那这个 ServerPassword 是谁写的?查全程序集的引用:
引用了 ['FejdStartup::set_ServerPassword'] 的方法共 1 个:
FejdStartup ParseArguments
只有 ParseArguments 一处——也就是命令行参数。FejdStartup.ParseArguments 里认的正是 程序集字符串堆里那个 -password(另有 $error_password / $menu_passwordinvalid 等提示文案)。 所以只有像 valheim.exe -password xxx 这样启动才会自动填, 正常点着玩的时候这个字段永远是 null,每次都得手打。
顺带一个必须知道的细节:密码是 MD5(ASCII(明文 + 盐))(ZNet.HashPassword,IL 就是 Encoding.ASCII.GetBytes(password + salt) → MD5CryptoServiceProvider.ComputeHash), 而盐是服务端每次握手现生成的 16 字节随机数(ZNet.ServerPasswordSalt() 里 RandomNumberGenerator.GetBytes(16))。 所以本地没法预先算好哈希存起来,只能存明文 —— 任何"记住联机密码"的模组都绕不开这一点。 本模组会尽量别让它以明文躺在硬盘上(见第五节)。
四、三个补丁(全部只改本地行为)
| 补丁 | 目标方法 | 做什么 | |---|---|---| | ① 自动填 | ZNet.RPC_ClientHandshake postfix | 需要密码、弹窗还开着、本地存过 → 调原版 ZNet.OnPasswordEntered(pwd),完全复用原版那条提交路径(含 MD5+盐),不绕过任何校验 | | ② 记录 | ZNet.OnPasswordEntered postfix | 把这次真正用到的密码记下来(首次手输、后来改的密码、自动提交都经过这里) | | ③ 遗忘 | ZNet.RPC_Error postfix | error == 6 时删掉该服务器的记录 |
补丁 ① 里那个"弹窗还开着"的判断用的是 ZNet.InPasswordDialog(), 正好把"原版已经靠 -password 填过了"这种情况排除掉,不会重复提交。
补丁 ③ 的 6 是 ConnectionStatus.ErrorPassword。服务端校验在 ZNet.RPC_PeerInfo 里:
0411 ldsfld ZNet::m_serverPassword ← 服务端设的密码
0416 ldloc.s 13 ← 客户端发来的哈希
0418 String::op_Inequality
041D brfalse → 04C8 ← 相等就放行
048E ZRpc::Invoke("Error", (int)6) ← 不等就发 Error(6)
客户端 ZNet.RPC_Error(rpc, int error) 的实现只有 m_connectionStatus = error + 一行日志。
> ⚠️ ConnectionStatus 这个枚举在程序集里是 tdNotPublic, > C# 里根本写不出 ConnectionStatus.ErrorPassword(编译期就报"找不到类型"), > 所以源码里只能写魔法数字 6。
这一条特别重要:不删记录的话,房主一改密码,你就会永远自动填那个旧密码、 连密码框都不弹出来,直接把自己锁在门外,只能手动去删 json。 如果实在不想让它忘,配置里的 ForgetOnPasswordError 可以关掉(不推荐)。
五、服务器是怎么认出来的
ZNet 上有三个 SetServerHost 重载,全程序集只有 FejdStartup.JoinServer 会调用 (三个 lambda:<JoinServer>b__74_5、<>c__DisplayClass74_0::<JoinServer>b__4、 <>c__DisplayClass74_1::<JoinServer>b__6),而且每次开头都先 ZNet.ResetServerHost() 清一遍。 所以在"服务端要求输密码"那一刻,这些字段必然就是当前这台服务器的:
| 入口 | 写入的字段 | 本模组的 key | |---|---|---| | Steam 联机(好友/服务器列表) | m_serverSteamID (ulong) | steam:<steamid> | | 跨平台联机 | m_serverPlayFabPlayerId (string) | playfab:<id> | | 跨平台联机(没有 host ID 时) | ZPlayFabMatchmaking.JoinCode | code:<房间码> | | 直连 IP / 专用服务器 | m_serverHost + m_serverHostPort | ip:<ip>:<端口> |
ZNet.m_onlineBackend(OnlineBackendType:0=Steamworks、1=PlayFab、2=EOS、3=CustomSocket) 用来判定走哪条路,避免把上一次联机的房间码套到这一次上。
一个必须说清楚的限制:服务器名字当不了判据
ZNet.m_ServerName 全程序集只有三处写入 —— SetServer、OpenServer、 <OnSteamServerRegistered>...MoveNext,全是开服侧。 客户端走的是 FejdStartup.JoinServer 里的 ZNet.SetServer(false, false, false, "", "", null), 名字传的是空串,客户端这个字段永远是空的。
客户端要拿名字只能用 ZNet.GetWorldName()(实现就是 m_world == null ? null : m_world.m_name), 而 m_world 是认证成功之后由 RPC_PeerInfo 建起来的 —— 也就是说握手那一刻根本不知道服务器叫什么,名字只能事后回填。 所以:
servers.json里的ServerName只是给你看的标签(认证成功后几秒内回填),- 它不参与匹配。想按名字认服务器的方案在这个 API 下走不通。
六、密码怎么存
文件:BepInEx/config/EasyLogin/servers.json(UTF-8 无 BOM,手工可编辑)
{
"Version": 1,
"Comment": "EasyLogin 自动生成。Note 字段随便写,模组不会动它;Password 字段带前缀,别手改前缀。",
"Records": [
{
"Key": "steam:76561198000000001",
"Kind": "Steam",
"SteamId": "76561198000000001",
"PlayFabId": "",
"Host": "",
"Port": 0,
"JoinCode": "",
"ServerName": "老王的英灵殿",
"Password": "dpapi:AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAA...",
"LastJoinedUtc": 1789488735,
"LastJoinedLocal": "2026-09-16 00:12:15",
"UseCount": 3,
"Note": ""
}
]
}
Password 字段带前缀,三种格式可以共存:
| 前缀 | 含义 | 换电脑能读吗 | |---|---|---| | dpapi: | Windows DPAPI(ProtectedData,CurrentUser 作用域),真正用你当前 Windows 账号的密钥加密 | ❌ 不能 | | obf: | 固定密钥异或 + Base64。只是防肩窥,不是加密 | ✅ 能 | | plain: | 明文。只能手工改出来(模组自己不会写) | ✅ 能 |
游戏 Managed 目录下自带 System.Security.dll,实测里面有 System.Security.Cryptography.ProtectedData.Protect/Unprotect,所以默认走 DPAPI。 万一运行环境不支持,会自动退化到 obf: 并打一行警告,功能不受影响 (Secret.cs 里把 DPAPI 的两个调用隔离成独立方法,就是为了让类型解析失败的异常能被 catch 到)。
> 想完全明文(方便自己看/改):把某条记录的 "Password": "dpapi:xxx" 手工改成 > "Password": "plain:你的密码" 即可,模组认这个前缀。
Highlights
From source dataLoaders
Runs on BepInEx.
Footprint
Rated a light load on a server.
Last update
Its author last shipped an update 48d ago.
EasyLogin on Nxeon
From our catalogue- One-click install
- Supported on Nxeon Valheim servers, for BepInEx servers. We download it from Thunderstore, place it, and restart the server.
- Server type needed
- BepInEx
- Server load
- light
- Smallest plan we suggest
- Valheim — 4 GB (4 GB RAM). This follows our own load rating above, not a benchmark.
- Latest build
- 1.0.0, released 2026-09-17
Game versions by loader
| Loader | Game versions | Newest |
|---|---|---|
| BepInEx |
From 1 recorded build of EasyLogin.
Dependencies
- Required: BepInExPack Valheim
Declared by the newest build that lists any. Required ones are installed with it on one-click installs.
Run EasyLogin on a Valheim server
Pick a server or deploy a new one above; it is installed in one click.
What kind of mod it is
From the catalogQuality of life & UI
Small conveniences, tweaks and interface improvements players notice straight away.
Compatibility
- Game
- Valheim
- Loaders
- BepInEx
- Requires
- One-click install needs a BepInEx server.
Verified at the source
- ✓ThunderstoreOfficial listing, checked 2026-10-01Open ↗
- ✓Authors6652289, as published on Thunderstore
- ✓LicenseNot published by the sourceFree






