饥荒切换离线与在线模式
在游戏饥荒中,如果已经以离线或者在线模式创建了世界,就无法直接在游戏中切换离线与在线模式(比如有的时候想在没有网络的环境下玩之前创建的在线模式世界)。但是可以通过修改存档文件来实现。
存档路径: C:\Users\xxx\Documents\Klei\DoNotStarveTogether\xxxxxxx\
1. 修改配置文件
- 进入需要修改的存档文件夹,比如Cluster_1。
- 打开Master/save/shardindex,修改server选项中的
online_mode为true(在线模式)或false(离线模式)。 - 打开Caves/save/shardindex,同上修改server选项中的
online_mode。 - 注意根目录下的cluster.ini文件中的
offline_cluster的值对最终的结果没有影响,改不改都可以。
2. 同步角色数据
当启动游戏后,由于在线模式和离线模式使用的是不同的账号数据,所以直接进入游戏会失去之前的角色数据。所以第一次切换后会提示创建新的角色。但是我们可以手动把之前的角色数据复制过来。
进入到 C:\Users\xxx\Documents\Klei\DoNotStarveTogether\xxxxxxx\Cluster_xx\Master\save\session\xxxxxxx\ 目录下,可以找到一个英文+数字的文件夹(只有进入过游戏才会有,比如你进了地表世界,但没去过洞穴,那么 Caves\save\session\xxxxxxx\ 目录下就没有这个文件夹),该文件夹名称和你的账号是相关的,你可以看到所有的世界里这个文件夹的名称都是一样的。这里假设这个叫 ABC123。此时你通过离线模式启动服务器,会提示你创建角色,这时你会发现 Master\save\session\xxxxxxx\ 目录下多了一个文件夹,这个就是你离线模式的账号数据,假设叫 DEF456。此时你只需要把 ABC123 目录下的所有文件复制到 DEF456 目录下,覆盖掉里面的文件,就可以在离线模式下继续使用之前在线模式的角色数据了。对于 Cave 洞穴世界也是同样的操作。
同理,如果你想从离线模式切换回在线模式,也是一样的操作。只需要把之前离线模式的账号数据复制到在线模式的账号数据目录下覆盖掉就可以了。
3. 脚本实现
我把上述操作写成了一个 python 脚本,方便以后使用:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
饥荒存档在线/离线模式切换工具
用于切换饥荒联机版存档的在线模式
"""
import sys
import configparser
from pathlib import Path
from datetime import datetime
import re
import shutil
import os
# 解决Windows命令行颜色问题
if sys.platform == "win32":
os.system('')
# 存档位置: C:\Users\xxx\Documents\Klei\DoNotStarveTogether\{user_id}\
user_id = '1234567890' # 替换成你的用户id
# 在线模式和离线模式的session_id
# C:\Users\xxx\Documents\Klei\DoNotStarveTogether\{user_id}\Cluster_xx\Master\save\session\xxxxxxx\{session_id}\
# 要获取这两个id,可以分别创建一个在线模式存档和一个离线模式存档,并进入一次游戏,然后进入到上述路径下查看对应的session_id文件夹名称,复制过来即可。所有存档的这两个session_id都是一样的。
online_session_id = 'ABCD123456' # 替换成你的在线模式session_id
offline_session_id = 'CDEFG123456789' # 替换成你的离线模式session_id
class Color:
RED = '\033[91m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
CYAN = '\033[96m'
RESET = '\033[0m'
class ColorStr:
ONLINE = f"{Color.GREEN}在线{Color.RESET}"
OFFLINE = f"{Color.RED}离线{Color.RESET}"
UNKNOWN = f"{Color.YELLOW}未知{Color.RESET}"
def color_str(text, color):
return f"{color}{text}{Color.RESET}"
def align(text, width=20, fillchar=' '):
"""
使中英文混合字符串对齐
"""
# 移除颜色码对长度的影响
temp_text = re.sub(r'\033\[\d+m', '', text)
# 计算实际字符宽度(非ASCII字符即中文按2计算)
actual_len = sum(2 if ord(c) > 127 else 1 for c in temp_text)
# 计算需要填充的空格数
return text + fillchar * (width - actual_len)
class DSTSaveManager:
def __init__(self):
if sys.platform == 'win32':
base_path = Path.home() / 'Documents' / 'Klei' / 'DoNotStarveTogether' / user_id
else:
base_path = Path.home() / '.klei' / 'DoNotStarveTogether'
self.base_path = base_path
if not self.base_path.exists():
print(f"错误:找不到饥荒存档目录")
print(f"预期路径:{self.base_path}")
sys.exit(1)
def get_saves(self):
saves = []
for cluster_dir in self.base_path.iterdir():
if not cluster_dir.is_dir():
continue
if cluster_dir.name.startswith('.'):
continue
cluster_ini = cluster_dir / 'cluster.ini'
if not cluster_ini.exists():
continue
config = configparser.ConfigParser()
config.read(cluster_ini, encoding='utf-8')
cluster_name = config.get('NETWORK', 'cluster_name', fallback=cluster_dir.name)
mod_time = datetime.fromtimestamp(cluster_dir.stat().st_mtime)
online_mode = self.get_online_mode(cluster_dir)
saves.append({
'path': cluster_dir,
'name': cluster_name,
'folder': cluster_dir.name,
'mod_time': mod_time,
'online_mode': online_mode
})
saves.sort(key=lambda x: x['mod_time'], reverse=True)
return saves
def get_online_mode(self, cluster_dir):
shardindex_path = cluster_dir / 'Master' / 'save' / 'shardindex'
if not shardindex_path.exists():
return None
try:
with open(shardindex_path, 'r', encoding='utf-8') as f:
content = f.read()
match = re.search(r'online_mode\s*=\s*(true|false)', content)
if match:
return match.group(1) == 'true'
return None
except:
return None
def set_online_mode(self, cluster_dir, online_mode):
shard_paths = [
cluster_dir / 'Master' / 'save' / 'shardindex',
cluster_dir / 'Caves' / 'save' / 'shardindex'
]
success_count = 0
failed_paths = []
for shard_path in shard_paths:
if not shard_path.exists():
continue
try:
with open(shard_path, 'r', encoding='utf-8') as f:
content = f.read()
new_value = 'true' if online_mode else 'false'
new_content = re.sub(
r'online_mode\s*=\s*(true|false)',
f'online_mode={new_value}',
content
)
with open(shard_path, 'w', encoding='utf-8') as f:
f.write(new_content)
success_count += 1
except Exception as e:
failed_paths.append(str(shard_path))
return success_count, failed_paths
def get_session_root_path(self, cluster_dir, shard):
temp_path = cluster_dir / shard / 'save' / 'session'
if not temp_path.exists():
raise FileNotFoundError(f"找不到路径: {temp_path}")
subdirs = [d for d in temp_path.iterdir() if d.is_dir()]
if not subdirs:
raise FileNotFoundError(f"找不到子目录: {temp_path}")
return subdirs[0]
def backup_session(self, session_path, session_id):
backups_root = session_path.parent / "backups"
backup_path = backups_root / session_id
backups_root.mkdir(exist_ok=True)
if backup_path.exists():
shutil.rmtree(backup_path)
shutil.copytree(session_path, backup_path)
return backup_path
def copy_one_shard(self, cluster_dir, shard, src_id, dst_id):
try:
session_root = self.get_session_root_path(cluster_dir, shard)
except FileNotFoundError as e:
print(f"错误: {e}")
return
src_path = session_root / src_id
dst_path = session_root / dst_id
print(f"\n{session_root}:")
print(f"\t{src_path.name} -> {dst_path.name}")
if not src_path.exists():
print("跳过(源不存在):", src_path)
return
if dst_path.exists():
if dst_path.stat().st_mtime > src_path.stat().st_mtime:
print("⚠ 目标存档较新")
confirm = input("仍然覆盖?(y/n): ").strip().lower()
if confirm != 'y':
print("已跳过")
return
backup_path = self.backup_session(dst_path, dst_id)
print(f"已备份到: {backup_path}")
shutil.rmtree(dst_path)
shutil.copytree(src_path, dst_path)
print(f"✓ {shard} 人物存档同步成功")
def copy_character_save(self, cluster_dir, to_online):
if to_online:
src_id = offline_session_id
dst_id = online_session_id
direction = f"{color_str('离线', Color.RED)} → {color_str('在线', Color.GREEN)}"
else:
src_id = online_session_id
dst_id = offline_session_id
direction = f"{color_str('在线', Color.GREEN)} → {color_str('离线', Color.RED)}"
print("\n人物存档复制")
print(f"方向: {direction}")
self.copy_one_shard(cluster_dir, "Master", src_id, dst_id)
self.copy_one_shard(cluster_dir, "Caves", src_id, dst_id)
def display_saves(self, saves):
print("\n" + "=" * 100)
# print(f"{'序号':<6} {'文件夹名':<20} {'世界名':<25} {'在线/离线':<10} {'修改时间'}")
print(f"{align('序号', 6)} {align('文件夹名', 20)} {align('世界名', 25)} {align('在线/离线', 15)} {'修改时间'}")
print("=" * 100)
for idx, save in enumerate(saves, 1):
if save['online_mode'] is True:
mode_str = ColorStr.ONLINE
elif save['online_mode'] is False:
mode_str = ColorStr.OFFLINE
else:
mode_str = ColorStr.UNKNOWN
mod_time_str = save['mod_time'].strftime('%Y-%m-%d %H:%M:%S')
# print(f"{idx:<6} {save['folder']:<20} {save['name']:<25} {mode_str:<10} {mod_time_str}")
print(f"{align(str(idx), 6)} {align(save['folder'], 20)} {align(save['name'], 25)} {align(mode_str, 15)} {mod_time_str}")
print("=" * 100)
print(f"\n共找到 {len(saves)} 个存档")
def run(self):
print("饥荒存档在线/离线模式切换工具")
print(f"存档目录:{self.base_path}\n")
while True:
saves = self.get_saves()
self.display_saves(saves)
user_input = input("请输入序号(q退出): ").strip()
if user_input.lower() == 'q' or user_input == 'quit':
break
if not user_input.isdigit():
print("请输入有效的序号")
continue
try:
idx = int(user_input) - 1
selected = saves[idx]
new_mode = not selected['online_mode']
if selected['online_mode'] is None:
print("无法切换(未知当前模式)")
continue
print(f"\n将 {selected['folder']}({selected['name']}) 从 {ColorStr.ONLINE if selected['online_mode'] else ColorStr.OFFLINE} 切换到 {ColorStr.ONLINE if new_mode else ColorStr.OFFLINE} 模式")
confirm = input("确认切换?(y/n): ").strip().lower()
if confirm != 'y':
continue
success_count, _ = self.set_online_mode(selected['path'], new_mode)
if success_count:
print("✓ 模式切换成功")
print("\n是否同步人物存档?")
master_session_root = self.get_session_root_path(selected['path'], 'Master')
cave_session_root = self.get_session_root_path(selected['path'], 'Caves') if (selected['path'] / 'Caves').exists() else None
print(f"你也可以稍后手动同步存档,进入到下面目录:")
print(f" {master_session_root}")
if cave_session_root:
print(f" {cave_session_root}")
print(f"将{color_str(offline_session_id if new_mode else online_session_id, Color.RED)}目录的内容复制到{color_str(online_session_id if new_mode else offline_session_id, Color.GREEN)}目录下")
copy_confirm = input("(y/n): ").strip().lower()
if copy_confirm == 'y':
self.copy_character_save(
selected['path'],
to_online=new_mode
)
print("\n⚠ 请重启游戏以加载存档状态变更\n")
except Exception as e:
print("错误:", e)
def main():
DSTSaveManager().run()
if __name__ == '__main__':
main()