Skip to content

Commit

Permalink
Valheim v1.2.1: fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
ZashIn committed Dec 16, 2022
1 parent 16939ac commit 75faff4
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 182 deletions.
1 change: 1 addition & 0 deletions basic_features/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# -*- encoding: utf-8 -*-

from .basic_save_game_info import BasicGameSaveGameInfo # noqa
from .basic_mod_data_checker import BasicModDataChecker # noqa
250 changes: 68 additions & 182 deletions games/game_valheim.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,26 @@

from __future__ import annotations

import fnmatch
import itertools
import re
import shutil
from collections.abc import Collection, Container, Iterable, Mapping, Sequence
from dataclasses import dataclass, field, fields
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional, TextIO, Union

from PyQt5.QtCore import QDir
try:
from PyQt6.QtCore import QDir
except ImportError:
from PyQt5.QtCore import QDir

import mobase

from ..basic_features import BasicModDataChecker
from ..basic_features.basic_save_game_info import BasicGameSaveGame
from ..basic_game import BasicGame


def convert_entry_to_tree(entry: mobase.FileTreeEntry) -> Optional[mobase.IFileTree]:
if not entry.isDir():
return None
if isinstance(entry, mobase.IFileTree):
return entry
if (parent := entry.parent()) is None:
return None
converted_entry = parent.find(
entry.name(), mobase.FileTreeEntry.FileTypes.DIRECTORY
)
if isinstance(converted_entry, mobase.IFileTree):
return converted_entry
return None


def move_file(source: Path, target: Path):
"""Move `source` to `target`. Creates missing (parent) directories and
overwrites existing `target`."""
Expand Down Expand Up @@ -130,7 +118,8 @@ def print(self, output_file: Optional[TextIO] = None):
if self._table:
for line in self._table:
print("|", " | ".join(line.values()), "|", file=output_file)
output_file and output_file.flush()
if output_file:
output_file.flush()
self._table = []


Expand Down Expand Up @@ -277,165 +266,6 @@ def _get_matching_mods(
)


@dataclass
class RegexFilesDefinition:
"""Regex pattern for the file lists in `FilesDefinition` - for globbing support.
Fields should match `RegexFilesDefinition`.
"""

set_as_root: Optional[re.Pattern]
valid: Optional[re.Pattern]
delete: Optional[re.Pattern]
move: Optional[re.Pattern]

@classmethod
def from_filesmap(cls, filesdef: FilesDefinition) -> RegexFilesDefinition:
"""Returns an instance of `RegexFilesDefinition`,
with the file list fields from `FilesDefinition` as regex patterns.
"""
return cls(
**{
f.name: (
cls.file_list_regex(value)
if (value := getattr(filesdef, f.name))
else None
)
for f in fields(cls)
}
)

@staticmethod
def file_list_regex(file_list: Iterable[str]) -> re.Pattern:
"""Returns a regex pattern for a file list with glob patterns.
Every pattern has a capturing group,
so that match.lastindex - 1 will give the file_list index.
"""
return re.compile(
f'(?:{"|".join(f"({fnmatch.translate(f)})" for f in file_list)})', re.I
)


@dataclass
class FilesDefinition:
"""File (pattern) definitions for the `mobase.ModDataChecker`.
Fields should match `RegexFilesDefinition`.
"""

set_as_root: Optional[set[str]]
"""If a folder from this set is found, it will be set as new root dir (unfolded)."""
valid: Optional[set[str]]
"""Files and folders in the right path."""
delete: Optional[set[str]]
"""Files/folders to delete."""
move: Optional[dict[str, str]]
"""Files/folders to move, like `{"*.ext": "path/to/folder/"}`.
If the path ends with / or \\, the entry will be inserted
in the corresponding directory instead of replacing it.
"""

regex: RegexFilesDefinition = field(init=False)
_move_targets: Sequence[str] = field(init=False, repr=False)

def __post_init__(self):
self.regex = RegexFilesDefinition.from_filesmap(self)
if self.move:
self._move_targets = list(self.move.values())

def get_move_target(self, index: int) -> str:
return self._move_targets[index]


class ValheimGameModDataChecker(mobase.ModDataChecker):
files_map = FilesDefinition(
set_as_root={
"BepInExPack_Valheim",
},
valid={
"meta.ini", # Included in installed mod folder.
"BepInEx",
"doorstop_libs",
"unstripped_corlib",
"doorstop_config.ini",
"start_game_bepinex.sh",
"start_server_bepinex.sh",
"winhttp.dll",
#
"InSlimVML",
"valheim_Data",
"inslimvml.ini",
#
"unstripped_managed",
#
"AdvancedBuilder",
},
delete={
"*.txt",
"*.md",
"icon.png",
"license",
"manifest.json",
},
move={
"*_VML.dll": "InSlimVML/Mods/",
#
"plugins": "BepInEx/",
"*.dll": "BepInEx/plugins/",
"config": "BepInEx/",
"*.cfg": "BepInEx/config/",
#
"CustomTextures": "BepInEx/plugins/",
"*.png": "BepInEx/plugins/CustomTextures/",
#
"Builds": "AdvancedBuilder/",
"*.vbuild": "AdvancedBuilder/Builds/",
#
"*.assets": "valheim_Data/",
},
)

def dataLooksValid(
self, filetree: mobase.IFileTree
) -> mobase.ModDataChecker.CheckReturn:
status = mobase.ModDataChecker.INVALID
for entry in filetree:
name = entry.name().casefold()
regex = self.files_map.regex
if regex.set_as_root and regex.set_as_root.match(name):
return mobase.ModDataChecker.FIXABLE
elif regex.valid and regex.valid.match(name):
if status is not mobase.ModDataChecker.FIXABLE:
status = mobase.ModDataChecker.VALID
elif (regex.move and regex.move.match(name)) or (
regex.delete and regex.delete.match(name)
):
status = mobase.ModDataChecker.FIXABLE
else:
return mobase.ModDataChecker.INVALID
return status

def fix(self, filetree: mobase.IFileTree) -> Optional[mobase.IFileTree]:
for entry in list(filetree):
name = entry.name().casefold()
regex = self.files_map.regex
if regex.set_as_root and regex.set_as_root.match(name):
new_root = convert_entry_to_tree(entry)
return self.fix(new_root) if new_root else None
elif regex.valid and regex.valid.match(name):
continue
elif regex.delete and regex.delete.match(name):
entry.detach()
elif regex.move and (match := regex.move.match(name)):
if match.lastindex is None:
return None
else:
# Get index of matched group
map_index = match.lastindex - 1
# Get the move target corresponding to the matched group
filetree.move(entry, self.files_map.get_move_target(map_index))
return filetree


class ValheimSaveGame(BasicGameSaveGame):
def getName(self) -> str:
return f"[{self.getSaveGroupIdentifier().rstrip('s')}] {self._filepath.stem}"
Expand Down Expand Up @@ -485,7 +315,7 @@ class ValheimGame(BasicGame):

Name = "Valheim Support Plugin"
Author = "Zash"
Version = "1.1.1"
Version = "1.2.1"

GameName = "Valheim"
GameShortName = "valheim"
Expand All @@ -494,12 +324,66 @@ class ValheimGame(BasicGame):
GameBinary = "valheim.exe"
GameDataPath = ""
GameSavesDirectory = r"%USERPROFILE%/AppData/LocalLow/IronGate/Valheim"
GameSupportURL = (
r"https://github.com/ModOrganizer2/modorganizer-basic_games/wiki/Game:-Valheim"
)

forced_libraries = ["winhttp.dll"]
_forced_libraries = ["winhttp.dll"]

def init(self, organizer: mobase.IOrganizer) -> bool:
super().init(organizer)
self._featureMap[mobase.ModDataChecker] = ValheimGameModDataChecker()
self._featureMap[mobase.ModDataChecker] = BasicModDataChecker(
{
"unfold": [
"BepInExPack_Valheim",
],
"valid": [
"meta.ini", # Included in installed mod folder.
"BepInEx",
"doorstop_libs",
"unstripped_corlib",
"doorstop_config.ini",
"start_game_bepinex.sh",
"start_server_bepinex.sh",
"winhttp.dll",
#
"InSlimVML",
"valheim_Data",
"inslimvml.ini",
#
"unstripped_managed",
#
"AdvancedBuilder",
],
"delete": [
"*.txt",
"*.md",
"README",
"icon.png",
"license",
"manifest.json",
"*.dll.mdb",
"*.pdb",
],
"move": {
"*_VML.dll": "InSlimVML/Mods/",
#
"plugins": "BepInEx/",
"*.dll": "BepInEx/plugins/",
"*.xml": "BepInEx/plugins/",
"config": "BepInEx/",
"*.cfg": "BepInEx/config/",
#
"CustomTextures": "BepInEx/plugins/",
"*.png": "BepInEx/plugins/CustomTextures/",
#
"Builds": "AdvancedBuilder/",
"*.vbuild": "AdvancedBuilder/Builds/",
#
"*.assets": "valheim_Data/",
},
}
)
self._featureMap[mobase.LocalSavegames] = ValheimLocalSavegames(
self.savesDirectory()
)
Expand All @@ -510,7 +394,7 @@ def init(self, organizer: mobase.IOrganizer) -> bool:
def executableForcedLoads(self) -> list[mobase.ExecutableForcedLoadSetting]:
return [
mobase.ExecutableForcedLoadSetting(self.binaryName(), lib).withEnabled(True)
for lib in self.forced_libraries
for lib in self._forced_libraries
]

def listSaves(self, folder: QDir) -> list[mobase.ISaveGame]:
Expand Down Expand Up @@ -548,6 +432,8 @@ def _game_finished_event_handler(self, app_path: str, *args) -> None:
self._sync_overwrite()

def _sync_overwrite(self) -> None:
if self._organizer.managedGame() is not self:
return
if self._organizer.pluginSetting(self.name(), "sync_overwrite") is not False:
self._overwrite_sync.search_file_contents = (
self._organizer.pluginSetting(
Expand Down

0 comments on commit 75faff4

Please sign in to comment.