Skip to content

Commit

Permalink
增加炸档装备全丢失的修复
Browse files Browse the repository at this point in the history
  • Loading branch information
AzureFatty committed Mar 12, 2024
1 parent e0d6b4b commit 89ec716
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 26 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@ __pycache__
*.pyc
*.pyo
*.pyd
*.idea/
*.idea/
build
dist
*.spec
100 changes: 78 additions & 22 deletions ArchiveRepair/cli.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,71 @@
import os
import re
import logging
import os
import pickle
from datetime import datetime
from pathlib import Path

from palworld_save_tools.palsav import compress_gvas_to_sav, decompress_sav_to_gvas
from palworld_save_tools.gvas import GvasFile
from palworld_save_tools.palsav import compress_gvas_to_sav, decompress_sav_to_gvas
from palworld_save_tools.paltypes import PALWORLD_CUSTOM_PROPERTIES, PALWORLD_TYPE_HINTS

from pathlib import Path

MAGIC_BYTES = b"PlZ"

DOCKER_ENV = os.environ.get("PAL_IN_DOCKER", "FALSE")
VERSION = os.environ.get("VERSION_NAME", "UNKNOWN")
COMMIT_DATE = os.environ.get("GIT_COMMIT_DATE", "UNKNOWN")
COMMIT_HASH = os.environ.get("GIT_SHA", "UNKNOWN")
EXPECTED_SAVE_GAME_TYPE = "/Script/Pal.PalWorldPlayerSaveGame"
CORRUPTED_SAVE_GAME_TYPE = "None.PalWorldPlayerSaveGame"
ROOT_PATH = "/data/save_games"
BACKUP_PATH = "/data/backups"
MISSING_JSON_FIELD = "PalStorageContainerId"
SLOT_ID_FILE_NAME = "slot_id.bak"


def load_slot_backups():
_file_path = f"{BACKUP_PATH}/{SLOT_ID_FILE_NAME}"
if not os.path.exists(_file_path):
logging.info(f"{_file_path} is not exists.")
return

with open(_file_path, "rb") as f:
return pickle.load(f)


def save_slot_backups(slot_info):
if not slot_info:
logging.warning(f"Backup users_slot_info with empty data. Returning.")
return
_file_path = f"{BACKUP_PATH}/{SLOT_ID_FILE_NAME}"
logging.debug(f"backup users_slot_info:{slot_info} to {_file_path}")
os.makedirs(os.path.dirname(_file_path), exist_ok=True)
with open(_file_path, 'wb') as file:
pickle.dump(slot_info, file)
logging.info(f"Backup saved to {_file_path}")


def main():
logging.basicConfig(level=logging.INFO)
logging.basicConfig(level=logging.INFO if DOCKER_ENV == "TRUE" else logging.DEBUG)
current_time = datetime.now()
formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S")
logging.info("------------------------------")
logging.info(f"Now time:{formatted_time}")
logging.info(f"PalWorldSaveRepair v{VERSION} {COMMIT_DATE} {COMMIT_HASH}")
logging.info("https://github.com/EngrZhou/PalworldArchiveRepair")
logging.info(f"PalWorldSaveRepair v{VERSION}")
logging.info("https://github.com/AzureChubby/PalworldArchiveRepair")
logging.info("------------------------------")
env_key = "PLAYERS_SAVE_PATH"
path = os.environ.get(env_key)
if path is None:
logging.warning(f"{env_key} environment variable not found, Use default replace.")
path = "/data/Players"

backups_slot_info = load_slot_backups()
if backups_slot_info is None or not isinstance(backups_slot_info, dict):
logging.debug(f"backups_size:0 {type(dict)}")
else:
logging.debug(f"Prepare list {path}")
logging.debug(f"backups_size:{len(backups_slot_info)}")

players_path = f"{ROOT_PATH}/Players"
try:
dir_entries = list(Path(path).glob("*.sav"))
dir_entries = list(Path(players_path).glob("*.sav"))
except Exception as e:
logging.exception(f"Failed to read dir: {path}")
logging.exception(f"Failed to read dir: {players_path}")
exit(1)
if not dir_entries:
logging.warning(f"{path} folder is empty.")
logging.warning(f"{players_path} folder is empty.")
return
_users_slot_info = {}
for file_path in dir_entries:
file_name = file_path.name
# base_name = re.sub(r'\..*', '', file_name)
Expand All @@ -63,11 +86,39 @@ def main():
gvas_file = GvasFile.read(
raw_gvas, PALWORLD_TYPE_HINTS, custom_properties, allow_nan=True
)
_file_content_changed = False
if gvas_file.header.save_game_class_name == CORRUPTED_SAVE_GAME_TYPE:
gvas_file.header.save_game_class_name = EXPECTED_SAVE_GAME_TYPE
_file_content_changed = True
logging.info(f"Try fix {file_name} {CORRUPTED_SAVE_GAME_TYPE} to {EXPECTED_SAVE_GAME_TYPE}")
else:
logging.info(f"Skipping {file_name} as it doesn't need fixing")
logging.info(f"Skipping {file_name} as it save_game_class_name doesn't need fixing")

if "SaveData" in gvas_file.properties:
_save_data_dict = gvas_file.properties["SaveData"]
_value = _save_data_dict.get("value")
if _value:
_slot_info = _value.get("PalStorageContainerId")
if _slot_info:
logging.info(f"{file_name} needn't fix PalStorageContainerId")
if not backups_slot_info or file_name not in backups_slot_info:
_users_slot_info[file_name] = _slot_info
logging.info(f"{file_name} will backup PalStorageContainerId")
else:
_current_user_slot_info = None
if backups_slot_info:
_current_user_slot_info = backups_slot_info.get(file_name)
if _current_user_slot_info:
_value["PalStorageContainerId"] = _current_user_slot_info
logging.info(f"Try fix {file_name} PalStorageContainerId")
_file_content_changed = True
else:
logging.warning(f"{file_name} not contain PalStorageContainerId "
f"and no backups for use!!!")
else:
logging.debug(f"SaveData not in gvas_file.properties. {gvas_file.properties.keys()}")

if not _file_content_changed:
continue
# Prepare write file
if ("Pal.PalWorldSaveGame" in gvas_file.header.save_game_class_name
Expand All @@ -84,4 +135,9 @@ def main():
except Exception as e:
logging.exception(f"Failed to read save file:{e}")
continue
if _users_slot_info and _users_slot_info != backups_slot_info:
if backups_slot_info:
_users_slot_info.update(backups_slot_info)
save_slot_backups(_users_slot_info)

logging.info("All save files have been repaired.")
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ FROM builder AS runner
# Install any needed packages specified in requirements.txt
RUN pip install --no-cache-dir -r requirements.txt

ENV PAL_IN_DOCKER=TRUE
ENV VERSION_NAME=1.0.5

RUN chmod +x ./start_up.sh

ENTRYPOINT [ "./start_up.sh" ]
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@
## Instructions

> [!IMPORTANT]
>
> Not thoroughly tested; please make sure to backup before use.
>
> 未经过充分测试,请确定使用前已经做过备份。

## TODO

- ✅ 登录服务器需要重新创建账号问题
- ⌛️ 登录之后光身子无法使用快捷键
- ✅ 登录之后光身子没有任何装备或者帕鲁以及无法使用快捷键问题
- 需要保证首次使用的时候没有此 bug;如果有的话,需要进行手动修复(因 Level.sav 存档文件过大,因此未进行解析后对用户装备槽进行修复。此问题原因是`PalStorageContainerId`字段丢失);
- 当前代码原理是将第一次识别到的用户数据进行备份,后续如果有需要,则使用此备份数据进行恢复`PalStorageContainerId`字段数据;
- ⌛️ U tell me


## Docker Compose
Expand All @@ -31,8 +37,9 @@ services:
archive-repair:
image: qian/palworld-archive-repair:latest
volumes:
- ./data/Pal/Saved/SaveGames/0/9B6884AE9C8644F0A07D67CA7475E474/Players:/data/Players
- ./data/log:/var/log
- ./data/Pal/Saved/SaveGames/0/9B6884AE9C8644F0A07D67CA7475E474:/data/save_games
- ./data/repair/backups:/data/backups
- ./data/repair/log:/var/log
environment:
- CRON_REPAIR=0 */6 * * *
restart: unless-stopped
Expand Down

0 comments on commit 89ec716

Please sign in to comment.