Skip to content

Commit

Permalink
convert: New version detection.
Browse files Browse the repository at this point in the history
  • Loading branch information
heinezen committed Feb 15, 2020
1 parent 21502bb commit f758378
Show file tree
Hide file tree
Showing 9 changed files with 262 additions and 153 deletions.
87 changes: 15 additions & 72 deletions openage/convert/dataformat/game_info.py
Original file line number Diff line number Diff line change
@@ -1,85 +1,28 @@
# Copyright 2020-2020 the openage authors. See copying.md for legal info.

"""
Stores information about a game edition/expansion..
"""


class GameInfo:
class GameFileVersion:
"""
Stores information about a game edition or expansion, mostly
indicators to detect the version as well as paths to assets
and data files.
Can be used to associate a file hash with a specific version number.
This can be used to pinpoint the exact version of a game.
"""

def __init__(self, name, support_status, version_detect,
media_paths, target_modpacks):
def __init__(self, filepath, hashes):
"""
Create a new GameInfo instance.
:param name: Name of the game.
:type name: str
:param support_status: Whether the converter can read/convert
the game to openage formats.
:type support_status: SupportStatus
:param version_detect: A set of (file, [hashes]) that is unique to this
version of the game.
:type version_detect: set
:param media_paths: A dictionary with MediaType as keys and
(bool, [str]). bool denotes whether the path
is a file that requires extraction. every str is
a path to a file or folder.
:type media_paths: dict
:param target_modpacks: A list of tuples containing
(modpack_name, uid, expected_manifest_hash).
These modpacks will be created for this version.
:type target_modpacks: list
Create a new file hash to version association.
"""

self.name = name
self.support_status = support_status
self.version_detect = version_detect
self.media_paths = media_paths
self.target_modpacks = target_modpacks
self.path = filepath
self.hashes = hashes


class GameEditionInfo(GameInfo):
"""
Info about a GameEdition.
"""

def __init__(self, name, support_status, version_detect,
media_paths, target_modpacks, expansions):
def get_path(self):
"""
Create a new GameEditionInfo instance.
:param name: Name of the game.
:type name: str
:param support_status: Whether the converter can read/convert
the game to openage formats.
:type support_status: SupportStatus
:param version_detect: A set of of (file, {hash: version}) that is
unique to this version of the game.
:type version_detect: set
:param media_paths: A dictionary with MediaType as keys and
(bool, [str]). bool denotes whether the path
is a file that requires extraction. every str is
a path to a file or folder.
:type media_paths: dict
:param target_modpacks: A list of tuples containing
(modpack_name, uid, expected_manifest_hash).
These modpacks will be created for this version.
:type target_modpacks: list
:param expansions: A list of expansions available for this edition.
:type expansion: list
Return the path of the file.
"""
super().__init__(name, support_status, version_detect,
media_paths, target_modpacks)
return self.path

self.expansions = expansions


class GameExpansionInfo(GameInfo):
"""
Info about a GameExpansion.
"""
def get_hashes(self):
"""
Return the hash-version association for the file.
"""
return self.hashes
12 changes: 7 additions & 5 deletions openage/convert/dataformat/media_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@

class MediaType(Enum):
"""
A type of media.
A type of media. Stores the mount point as the value.
"""

GRAPHICS = {"SLP", "SMX", "PNG"}
TERRAIN = {"SLP", "DDS"}
SOUNDS = {"WAV", "WEM"}
INTERFACE = {"SLP", "BMP"}
DATFILE = "data"
GAMEDATA = "gamedata"
GRAPHICS = "graphics"
TERRAIN = "terrain"
SOUNDS = "sounds"
INTERFACE = "interface"
155 changes: 131 additions & 24 deletions openage/convert/dataformat/version_detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
Detects the base version of the game and installed expansions.
"""

from enum import Enum
from openage.convert.dataformat.game_info import GameEditionInfo,\
GameExpansionInfo
import enum
from openage.convert.dataformat.game_info import GameFileVersion
from openage.convert.dataformat.media_types import MediaType


class Support(Enum):
@enum.unique
class Support(enum.Enum):
"""
Support state of a game version
"""
Expand All @@ -19,22 +19,57 @@ class Support(Enum):
breaks = "presence breaks conversion"


class GameExpansion(Enum):
@enum.unique
class GameExpansion(enum.Enum):
"""
An optional expansion to a GameEdition.
"""

AFRI_KING = GameExpansionInfo("Age of Empires 2: HD - African Kingdoms",
Support.nope,
{("resources/_common/dat/empires2_x2_p1.dat", {})},
{MediaType.GRAPHICS: (False, ["resources/_common/slp/"]),
MediaType.SOUNDS: (False, ["resources/_common/sound/"]),
MediaType.INTERFACE: (False, ["resources/_common/drs/interface/"]),
MediaType.TERRAIN: (False, ["resources/_common/terrain/"])},
["aoe2-ak", "aoe2-ak-graphics"])
AFRI_KING = ("Age of Empires 2: HD - African Kingdoms",
Support.nope,
{GameFileVersion("resources/_common/dat/empires2_x2_p1.dat",
{"f2bf8b128b4bdac36ee36fafe139baf1": "1.0c"})},
{MediaType.GRAPHICS: ["resources/_common/slp/"],
MediaType.SOUNDS: ["resources/_common/sound/"],
MediaType.INTERFACE: ["resources/_common/drs/interface/"],
MediaType.TERRAIN: ["resources/_common/terrain/"]},
["aoe2-ak", "aoe2-ak-graphics"])

def __init__(self, name, support_status, game_file_versions,
media_paths, target_modpacks, **flags):
"""
Create a new GameInfo instance.
class GameEdition(Enum):
:param name: Name of the game.
:type name: str
:param support_status: Whether the converter can read/convert
the game to openage formats.
:type support_status: SupportStatus
:param game_file_versions: A set of files that is unique to this
version of the game.
:type game_file_versions: set
:param media_paths: A dictionary with MediaType as keys and
(bool, [str]). bool denotes whether the path
is a file that requires extraction. every str is
a path to a file or folder.
:type media_paths: dict
:param target_modpacks: A list of tuples containing
(modpack_name, uid, expected_manifest_hash).
These modpacks will be created for this version.
:type target_modpacks: list
:param flags: Anything else specific to this version which is useful
for the converter.
"""
self.expansion_name = name
self.support = support_status
self.game_file_versions = game_file_versions
self.media_paths = media_paths
self.target_modpacks = target_modpacks
self.flags = flags


@enum.unique
class GameEdition(enum.Enum):
"""
Standalone/base version of a game. Multiple standalone versions
may exist, e.g. AoC, HD, DE2 for AoE2.
Expand All @@ -44,13 +79,85 @@ class GameEdition(Enum):
The Conquerors are considered "downgrade" expansions.
"""

AOC = GameEditionInfo("Age of Empires 2: The Conqueror's",
Support.yes,
{('age2_x1/age2_x1.exe', {}),
('data/empires2_x1_p1.dat', {})},
{MediaType.GRAPHICS: (True, ["data/graphics.drs"]),
MediaType.SOUNDS: (True, ["data/sounds.drs", "data/sounds_x1.drs"]),
MediaType.INTERFACE: (True, ["data/interfac.drs"]),
MediaType.TERRAIN: (True, ["data/terrain.drs"])},
["aoe2-base", "aoe2-base-graphics"],
[])
AOC = ("Age of Empires 2: The Conqueror's",
Support.yes,
{GameFileVersion('age2_x1/age2_x1.exe',
{"f2bf8b128b4bdac36ee36fafe139baf1": "1.0c"}),
GameFileVersion('data/empires2_x1_p1.dat',
{"8358c9e64ec0e70e7b13bd34d5a46296": "1.0c"})},
{MediaType.DATFILE: ["data/empires2_x1_p1.dat"],
MediaType.GAMEDATA: ["data/gamedata_x1_p1.drs"],
MediaType.GRAPHICS: ["data/graphics.drs"],
MediaType.SOUNDS: ["data/sounds.drs", "data/sounds_x1.drs"],
MediaType.INTERFACE: ["data/interfac.drs"],
MediaType.TERRAIN: ["data/terrain.drs"]},
["aoe2-base", "aoe2-base-graphics"],
[])

def __init__(self, name, support_status, game_file_versions,
media_paths, target_modpacks, expansions, **flags):
"""
Create a new GameEditionInfo instance.
:param name: Name of the game.
:type name: str
:param support_status: Whether the converter can read/convert
the game to openage formats.
:type support_status: SupportStatus
:param game_file_versions: A set of of files that is
unique to this version of the game.
:type game_file_versions: set
:param media_paths: A dictionary with MediaType as keys and
(bool, [str]). bool denotes whether the path
is a file that requires extraction. every str is
a path to a file or folder.
:type media_paths: dict
:param target_modpacks: A list of tuples containing
(modpack_name, uid, expected_manifest_hash).
These modpacks will be created for this version.
:type target_modpacks: list
:param expansions: A list of expansions available for this edition.
:type expansion: list
:param flags: Anything else specific to this version which is useful
for the converter.
"""
self.edition_name = name
self.support = support_status
self.game_file_versions = game_file_versions
self.media_paths = media_paths
self.target_modpacks = target_modpacks
self.expansions = expansions
self.flags = flags


def get_game_info(srcdir):
"""
Determine what editions and expansions of a game are installed in srcdir.
"""
edition = None
expansions = []

for game_edition in GameEdition:
for detection_hints in game_edition.game_file_versions:
required_path = detection_hints.get_path()
required_file = srcdir.joinpath(required_path)

if not required_file.is_file():
break

else:
edition = game_edition
break

for game_expansion in edition.expansions:
for detection_hints in game_expansion.game_file_versions:
required_path = detection_hints.get_path()
required_file = srcdir.joinpath(required_path)

if not required_file.is_file():
break

else:
expansions.append(game_expansion)

return edition, expansions
20 changes: 8 additions & 12 deletions openage/convert/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from .slp_converter_pool import SLPConverterPool
from .stringresource import StringResource
from .processor.modpack_exporter import ModpackExporter
from openage.convert.dataformat.media_types import MediaType


def get_string_resources(args):
Expand Down Expand Up @@ -77,21 +78,16 @@ def get_blendomatic_data(srcdir):
return Blendomatic(blendomatic_dat)


def get_gamespec(srcdir, game_versions, dont_pickle):
def get_gamespec(srcdir, game_version, dont_pickle):
""" reads empires.dat and fixes it """

if GameVersion.age2_hd_ak in game_versions:
filename = "empires2_x2_p1.dat"
elif has_x1_p1(game_versions):
filename = "empires2_x1_p1.dat"
else:
filename = "empires2_x1.dat"
filepath = srcdir.joinpath(game_version[0].media_paths[MediaType.DATFILE][0])

cache_file = os.path.join(gettempdir(), "{}.pickle".format(filename))
cache_file = os.path.join(gettempdir(), "{}.pickle".format(filepath.name))

with srcdir["data", filename].open('rb') as empiresdat_file:
with filepath.open('rb') as empiresdat_file:
gamespec = load_gamespec(empiresdat_file,
game_versions,
game_version,
cache_file,
not dont_pickle)

Expand Down Expand Up @@ -167,11 +163,11 @@ def convert_metadata(args):
args.converter = AoCProcessor

yield "empires.dat"
gamespec = get_gamespec(args.srcdir, args.game_versions, args.flag("no_pickle_cache"))
gamespec = get_gamespec(args.srcdir, args.game_version, args.flag("no_pickle_cache"))
modpacks = args.converter.convert(gamespec)

for modpack in modpacks:
ModpackExporter.export(modpack, args.targetdir)
ModpackExporter.export(modpack, args.srcdir, args.targetdir)

yield "blendomatic.dat"
blend_data = get_blendomatic_data(args.srcdir)
Expand Down
2 changes: 1 addition & 1 deletion openage/convert/export/media_export_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,4 @@ def save(self, sourcedir, exportdir, palette_file):
image = SMX(media_file.read())

texture = Texture(image, palette_table)
texture.save(self.targetdir, self.target_filename)
texture.save(exportdir.joinpath(self.targetdir), self.target_filename)
4 changes: 2 additions & 2 deletions openage/convert/gamedata/empiresdat.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ class EmpiresDatWrapper(GenieStructure):
]


def load_gamespec(fileobj, game_versions, cachefile_name=None, load_cache=False):
def load_gamespec(fileobj, game_version, cachefile_name=None, load_cache=False):
"""
Helper method that loads the contents of a 'empires.dat' gzipped wrapper
file.
Expand Down Expand Up @@ -410,7 +410,7 @@ def load_gamespec(fileobj, game_versions, cachefile_name=None, load_cache=False)

spam("length of decompressed data: %d", len(file_data))

wrapper = EmpiresDatWrapper(game_versions=game_versions)
wrapper = EmpiresDatWrapper(game_version=game_version)
_, gamespec = wrapper.read(file_data, 0)

# Remove the list sorrounding the converted data
Expand Down
Loading

0 comments on commit f758378

Please sign in to comment.