~thestr4ng3r/mymcplus

ref: cff55ba378c47ff60022bf5bed1911a98d4b765b mymcplus/mymcplus/save/ps2save.py -rw-r--r-- 5.2 KiB
cff55ba3 — Kistelini Fix Usages of removed from/tostring for Python 3.9 (#10) 9 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
#
# This file is part of mymc+, based on mymc by Ross Ridge.
#
# mymc+ is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# mymc+ is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with mymc+.  If not, see <http://www.gnu.org/licenses/>.
#

"""A simple interface for working with various PS2 save file formats."""

import sys
import os
import binascii

from ..ps2mc_dir import *
from .. import ps2iconsys


from . import format_codebreaker, format_ems, format_max_drive, format_sharkport, format_psv


formats = [
    format_codebreaker,
    format_ems,
    format_max_drive,
    format_sharkport,
    format_psv
]


class Error(Exception):
    """Base for all exceptions specific to this module."""
    pass


class Corrupt(Error):
    """Corrupt save file."""

    def __init__(self, msg, f = None):
        fn = None
        if f != None:
            fn = getattr(f, "name", None)
        self.filename = fn
        Error.__init__(self, "Corrupt save file: " + msg)


class Eof(Corrupt):
    """Save file is truncated."""

    def __init__(self, f = None):
        Corrupt.__init__(self, "Unexpected EOF", f)


class Subdir(Corrupt):
    def __init__(self, f = None):
        Corrupt.__init__(self, "Non-file in save file.", f)


def poll_format(f):
    """Detect the type of PS2 save file.

    The file-like object f should be positioned at the start of the file.
    """

    hdr = f.read(PS2MC_DIRENT_LENGTH * 3)

    for format in formats:
        if format.poll(hdr):
            return format

    return None


def format_for_filename(filename):
    filename = filename.lower()
    if filename.endswith(".max"):
        return format_max_drive
    #elif filename.endswith(".psv"):
    #    return format_psv
    else:
        return format_ems


#
# Set up tables of illegal and problematic characters in file names.
#
_bad_filename_chars = ("".join(map(chr, list(range(32))))
               + "".join(map(chr, list(range(127, 256)))))
_bad_filename_repl = "_" * len(_bad_filename_chars)

if os.name in ["nt", "os2", "ce"]:
    _bad_filename_chars += '<>:"/\\|'
    _bad_filename_repl +=  "()_'___"
    _bad_filename_chars2 = _bad_filename_chars + "?* "
    _bad_filename_repl2 = _bad_filename_repl +   "___"
else:
    _bad_filename_chars += "/"
    _bad_filename_repl += "_"
    _bad_filename_chars2 = _bad_filename_chars + "?*'&|:[<>] \\\""
    _bad_filename_repl2 = _bad_filename_repl +   "______(())___"

_filename_trans = str.maketrans(_bad_filename_chars, _bad_filename_repl)
_filename_trans2 = str.maketrans(_bad_filename_chars2, _bad_filename_repl2)


def fix_filename(filename):
    """Replace illegal or problematic characters from a filename."""
    return filename.translate(_filename_trans)


def make_longname(dirname, sf):
    """Return a string containing a verbose filename for a save file."""

    icon_sys = sf.get_icon_sys()
    title = ""
    if icon_sys is not None:
        title = icon_sys.get_title("ascii")
        title = title[0] + " " + title[1]
        title = " ".join(title.split())
    crc = binascii.crc32(b"")
    for (ent, data) in sf:
        crc = binascii.crc32(data, crc)
    if len(dirname) >= 12 and (dirname[0:2] in ("BA", "BJ", "BE", "BK")):
        if dirname[2:6] == "DATA":
            title = ""
        else:
            #dirname = dirname[2:6] + dirname[7:12]
            dirname = dirname[2:12]

    return fix_filename("%s %s (%08X)" % (dirname, title, crc & 0xFFFFFFFF))







class PS2SaveFile(object):
    """The state of a PlayStation 2 save file."""
    
    def __init__(self):
        self.file_ents = None
        self.file_data = None
        self.dirent = None
        self._defer_load_max_file = None
        self._compressed = None


    def set_directory(self, ent, defer_file = None):
        self._defer_load_max_file = defer_file
        self._compressed = None
        self.file_ents = [None] * ent[2]
        self.file_data = [None] * ent[2]
        self.dirent = list(ent)


    def set_file(self, i, ent, data):
        self.file_ents[i] = ent
        self.file_data[i] = data


    def get_directory(self):
        return self.dirent


    def get_file(self, i):
        if self._defer_load_max_file is not None:
            f = self._defer_load_max_file
            self._defer_load_max_file = None
            format_max_drive.load2(self, f)
        return self.file_ents[i], self.file_data[i]


    def __len__(self):
        return self.dirent[2]


    def __getitem__(self, index):
        return self.get_file(index)

    
    def get_icon_sys(self):
        for i in range(self.dirent[2]):
            (ent, data) = self.get_file(i)
            if ent[8].decode("ascii") == "icon.sys" and len(data) >= 964:
                try:
                    return ps2iconsys.IconSys(data[:964])
                except ps2iconsys.Error:
                    pass
        return None