mirror of
https://github.com/ps2dev/mymc.git
synced 2024-11-27 20:20:49 +01:00
Import mymc 2.5 Python source code
Source: http://www.csclub.uwaterloo.ca:11068/mymc/mymc-pysrc-2.5.zip
This commit is contained in:
commit
7806741c2a
178
README.txt
Normal file
178
README.txt
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
README.txt
|
||||||
|
|
||||||
|
By Ross Ridge
|
||||||
|
Pubic Domain
|
||||||
|
|
||||||
|
@(#) mymc README.txt 1.5 08/02/06 13:26:26
|
||||||
|
|
||||||
|
|
||||||
|
This file describes mymc, a utility for manipulating PlayStation 2
|
||||||
|
memory card images as used by the emulator PCSX2. Its main purpose is
|
||||||
|
to allow save games to be imported and exported to and from these
|
||||||
|
images. Both MAX Drive and EMS (.psu) save files are fully supported,
|
||||||
|
however save files in the SharkPort/X-Port and Code Breaker formats
|
||||||
|
can only be imported and not exported. In addition to these basic
|
||||||
|
functions, mymc can also perform a number of other operations, like
|
||||||
|
creating new memory card images, viewing their contents, and adding
|
||||||
|
and extracting individual files.
|
||||||
|
|
||||||
|
A simple, hopefully easy to use, graphicial user interface (GUI) is
|
||||||
|
provided, but it's limitted to only basic operations. More advanced
|
||||||
|
opterations require the use of a command line tool. To install mymc,
|
||||||
|
unpack the downloaded ZIP archive to a new directory on your machine.
|
||||||
|
You can then run the GUI version of mymc by openning that newn
|
||||||
|
directory with Windows Explorer and double clicking on the "mymc-gui"
|
||||||
|
icon. To make it easier to access, you can drag the "mymc-gui" icon
|
||||||
|
to either your Desktop, Start Menu or Quick Launch toolbar. Make sure
|
||||||
|
if you do so, that you create a shortcut to "mymc-gui.exe". If you
|
||||||
|
copy the file instead, the program won't work.
|
||||||
|
|
||||||
|
The command line utility can be invoked from the Windows Command
|
||||||
|
Prompt by using the "mymc" command. The executable "mymc.exe" and
|
||||||
|
number of support files and these file must kept together in the same
|
||||||
|
directory. To run the command you need to either add the directory
|
||||||
|
where you unpacked the distribution to your PATH or type the full
|
||||||
|
pathname of the executable. For example if you unpacked mymc to a
|
||||||
|
directory named "c:\mymc" you need to enter "c:\mymc\mymc.exe" to run
|
||||||
|
the program.
|
||||||
|
|
||||||
|
The second important thing to note is that mymc is only "alpha"
|
||||||
|
quality software. This means that has is been released without
|
||||||
|
extensive testing and may be unreliable. While it works fine for me,
|
||||||
|
the author, it might not work as well for you. For that reason you
|
||||||
|
should be careful how you use it, and prepared for the eventuality of
|
||||||
|
it corrupting your save game images or producing garbage save files.
|
||||||
|
If you're worried about this, one make things safer is to use two
|
||||||
|
memory card images. Use the first image to load and save your games
|
||||||
|
with under PCSX2, and the second image to import and export saves
|
||||||
|
games using mysc. Then use the PS2 browser to copy files between two
|
||||||
|
card images.
|
||||||
|
|
||||||
|
|
||||||
|
GUI TUTORIAL
|
||||||
|
============
|
||||||
|
|
||||||
|
The GUI for mymc is should be easy to use. After starting mymc, you
|
||||||
|
can select the PS2 memory card image you want to work with by
|
||||||
|
selecting the "Open" command by pressing the first button on the
|
||||||
|
toolbar. You can then import a save file clicking on the Import
|
||||||
|
toolbar button. To export a save files, first select it and then
|
||||||
|
press the Export button. You can delete a save file permanently from
|
||||||
|
your memory card, by selecting the "Delete" command from the File
|
||||||
|
menu.
|
||||||
|
|
||||||
|
Do not try to use mymc to modify a memory card image while PCSX2 is
|
||||||
|
running. Doing so will corrupt your memory card.
|
||||||
|
|
||||||
|
|
||||||
|
COMMAND LINE TUTORIAL
|
||||||
|
=====================
|
||||||
|
|
||||||
|
The basic usage template for mysc is "mymc memcard.ps2 command". The
|
||||||
|
first argument, "memcard.ps2" is the filename of the memory card image
|
||||||
|
while "command" is the name of the command you wish to use on the
|
||||||
|
image. So for example, assuming you've installed mymc in "c:\mymc"
|
||||||
|
and you've installed PCSX2 in "c:\pcsx2" you could enter the following
|
||||||
|
command to see the contents of the memory card in the emulator's slot
|
||||||
|
1:
|
||||||
|
|
||||||
|
c:\mymc\mymc c:\pcsx2\memcards\Mcd001.ps2 dir
|
||||||
|
|
||||||
|
You would see output something like this:
|
||||||
|
|
||||||
|
BASLUS-20678USAGAS00 UNLIMITED SAGA
|
||||||
|
154KB Not Protected SYSTEMDATA
|
||||||
|
|
||||||
|
BADATA-SYSTEM Your System
|
||||||
|
5KB Not Protected Configuration
|
||||||
|
|
||||||
|
BASLUS-20488-0000D SOTET<13>060:08
|
||||||
|
173KB Not Protected Arias
|
||||||
|
|
||||||
|
7,800 KB Free
|
||||||
|
|
||||||
|
This is the simple "user friendly" way to view the contents of a
|
||||||
|
memory card. It displays the same information you can see using the
|
||||||
|
PlayStation 2 memory card browser. On the right is name of each save,
|
||||||
|
and on the left is the size and protection status of the save. Also
|
||||||
|
on the left is one bit of information you won't see in the browser,
|
||||||
|
the directory name of the save file. PlayStation 2 saves are actually
|
||||||
|
a collection of different files all stored in a single directory on
|
||||||
|
the memory card. This is important information, because you need to
|
||||||
|
know it to export save files.
|
||||||
|
|
||||||
|
As mentioned above, if you know the directory name of a save, you can
|
||||||
|
export it. Exporting a save creates a save file in either the EMS
|
||||||
|
(.psu) or MAX Drive (.max) format. You can then transfer the save to
|
||||||
|
real PS2 memory using the appropriate tools. You can also send the
|
||||||
|
saves to someone else to use or just keep them on your hard drive as a
|
||||||
|
backup. The following command demonstrates how to export a save in
|
||||||
|
the EMS format using mymc:
|
||||||
|
|
||||||
|
c:\mymc\mymc c:\pcsx2\memcards\Mcd001.ps2 export BASLUS-20448-000D
|
||||||
|
|
||||||
|
This will create a file called "BASLUS-20448-000D.psu" in the current
|
||||||
|
directory. To create a file in the MAX format instead, use the export
|
||||||
|
command's -m option:
|
||||||
|
|
||||||
|
c:\mymc\mymc c:\pcsx2\memcards\Mcd001.ps2 export -m BASLUS-20448-000D
|
||||||
|
|
||||||
|
This creates a file named "BASLUS-20448-000D.max". Note the "-m"
|
||||||
|
option that appears after the "export" command.
|
||||||
|
|
||||||
|
Importing save files is similar. The save file type is auto-detected,
|
||||||
|
so you don't need use an "-m" option with MAX Drive saves. Here's a
|
||||||
|
couple of examples using each format:
|
||||||
|
|
||||||
|
c:\mymc\mymc c:\pcsx2\memcards\Mcd001.ps2 import BASLUS-20035.psu
|
||||||
|
c:\mymc\mymc c:\pcsx2\memcards\Mcd001.ps2 import 20062_3583_GTA3.max
|
||||||
|
|
||||||
|
|
||||||
|
ADVANCED NOTES
|
||||||
|
==============
|
||||||
|
|
||||||
|
- To get general help with the command line utility use the "-h"
|
||||||
|
global option (eg. "mymc -h"). To get help with a specific
|
||||||
|
command use the "-h" option with that command (eg. "mymc x
|
||||||
|
import -h"). In this later case, you need to specify a memory
|
||||||
|
card image file, but it's ignored and so doesn't need to exist.
|
||||||
|
|
||||||
|
- Both executables in the Windows version, "mymc.exe" and
|
||||||
|
"mymc-gui.exe" do the same thing and support the same options.
|
||||||
|
The difference is that "mymc" is console application, while
|
||||||
|
"mymc-gui" is a Windows appliction. Currently, using "mymc"
|
||||||
|
to start the GUI will result in a fair amount debug messages
|
||||||
|
being printed that are normally not seen "mymc-gui" is used.
|
||||||
|
|
||||||
|
- It's possible to use mymc create images that are bigger (or
|
||||||
|
smaller) than standard PS2 memory cards. Be very careful if you
|
||||||
|
do this, not all games may be compatible with such images.
|
||||||
|
|
||||||
|
- The bad block list on images is ignored. Since memory card
|
||||||
|
images created with either PCSX2 or mymc won't have any bad
|
||||||
|
blocks, this shouldn't be a problem unless you've somehow
|
||||||
|
extracted a complete image from a real memory card and expect to
|
||||||
|
copy it back.
|
||||||
|
|
||||||
|
- The PS2 only uses at most 8,000 KB of a memory card, but there
|
||||||
|
is actually 8,135 KB of allocatable space on a standard
|
||||||
|
error-free memory card. The extra 135 KB is reserved so that
|
||||||
|
memory card with bad blocks don't appear to have less space than
|
||||||
|
memory cards with fewer or no bad blocks. Since there are no
|
||||||
|
bad blocks on memory card images, mymc uses the full capacity
|
||||||
|
provided by standard memory cards.
|
||||||
|
|
||||||
|
|
||||||
|
PYTHON SOURCE DISTRIBUTION
|
||||||
|
==========================
|
||||||
|
|
||||||
|
The "source code" distribution of mymc is provided for users of Linux
|
||||||
|
and other non-Windows operating systems. It uses the same Python code
|
||||||
|
that the Windows distribution is built with (using py2exe) and
|
||||||
|
supports all the same functionality. One big difference is that the
|
||||||
|
Windows DLL "mymcsup.dll" is not included and as a result compressing
|
||||||
|
and decompressing MAX Drive saves will be as much as 100 times slower.
|
||||||
|
The GUI mode is hasn't been extensively tested on non-Windows systems,
|
||||||
|
and the 3D display of save file icons requires the DLL. The Python
|
||||||
|
source version should support big-endian machines, but this hasn't
|
||||||
|
been tested.
|
948
gui.py
Normal file
948
gui.py
Normal file
@ -0,0 +1,948 @@
|
|||||||
|
#
|
||||||
|
# gui.py
|
||||||
|
#
|
||||||
|
# By Ross Ridge
|
||||||
|
# Public Domain
|
||||||
|
#
|
||||||
|
|
||||||
|
"""Graphical user-interface for mymc."""
|
||||||
|
|
||||||
|
_SCCS_ID = "@(#) mymc gui.py 1.3 08/02/06 13:15:41\n"
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import struct
|
||||||
|
import cStringIO
|
||||||
|
import time
|
||||||
|
|
||||||
|
# Work around a problem with mixing wx and py2exe
|
||||||
|
if os.name == "nt" and hasattr(sys, "setdefaultencoding"):
|
||||||
|
sys.setdefaultencoding("mbcs")
|
||||||
|
import wx
|
||||||
|
|
||||||
|
import ps2mc
|
||||||
|
import ps2save
|
||||||
|
import guires
|
||||||
|
|
||||||
|
try:
|
||||||
|
import ctypes
|
||||||
|
import mymcsup
|
||||||
|
D3DXVECTOR3 = mymcsup.D3DXVECTOR3
|
||||||
|
D3DXVECTOR4 = mymcsup.D3DXVECTOR4
|
||||||
|
D3DXVECTOR4_ARRAY3 = mymcsup.D3DXVECTOR4_ARRAY3
|
||||||
|
|
||||||
|
def mkvec4arr3(l):
|
||||||
|
return D3DXVECTOR4_ARRAY3(*[D3DXVECTOR4(*vec)
|
||||||
|
for vec in l])
|
||||||
|
except ImportError:
|
||||||
|
mymcsup = None
|
||||||
|
|
||||||
|
lighting_none = {"lighting": False,
|
||||||
|
"vertex_diffuse": False,
|
||||||
|
"alt_lighting": False,
|
||||||
|
"light_dirs": [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]],
|
||||||
|
"light_colours": [[0, 0, 0, 0], [0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0]],
|
||||||
|
"ambient": [0, 0, 0, 0]}
|
||||||
|
|
||||||
|
lighting_diffuse = {"lighting": False,
|
||||||
|
"vertex_diffuse": True,
|
||||||
|
"alt_lighting": False,
|
||||||
|
"light_dirs": [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]],
|
||||||
|
"light_colours": [[0, 0, 0, 0], [0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0]],
|
||||||
|
"ambient": [0, 0, 0, 0]}
|
||||||
|
|
||||||
|
lighting_icon = {"lighting": True,
|
||||||
|
"vertex_diffuse": True,
|
||||||
|
"alt_lighting": False,
|
||||||
|
"light_dirs": [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]],
|
||||||
|
"light_colours": [[0, 0, 0, 0], [0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0]],
|
||||||
|
"ambient": [0, 0, 0, 0]}
|
||||||
|
|
||||||
|
lighting_alternate = {"lighting": True,
|
||||||
|
"vertex_diffuse": True,
|
||||||
|
"alt_lighting": True,
|
||||||
|
"light_dirs": [[1, -1, 2, 0],
|
||||||
|
[-1, 1, -2, 0],
|
||||||
|
[0, 1, 0, 0]],
|
||||||
|
"light_colours": [[1, 1, 1, 1],
|
||||||
|
[1, 1, 1, 1],
|
||||||
|
[0.7, 0.7, 0.7, 1]],
|
||||||
|
"ambient": [0.5, 0.5, 0.5, 1]}
|
||||||
|
|
||||||
|
lighting_alternate2 = {"lighting": True,
|
||||||
|
"vertex_diffuse": False,
|
||||||
|
"alt_lighting": True,
|
||||||
|
"light_dirs": [[1, -1, 2, 0],
|
||||||
|
[-1, 1, -2, 0],
|
||||||
|
[0, 4, 1, 0]],
|
||||||
|
"light_colours": [[0.7, 0.7, 0.7, 1],
|
||||||
|
[0.7, 0.7, 0.7, 1],
|
||||||
|
[0.2, 0.2, 0.2, 1]],
|
||||||
|
"ambient": [0.3, 0.3, 0.3, 1]}
|
||||||
|
|
||||||
|
camera_default = [0, 4, -8]
|
||||||
|
camera_high = [0, 7, -6]
|
||||||
|
camera_near = [0, 3, -6]
|
||||||
|
camera_flat = [0, 2, -7.5]
|
||||||
|
|
||||||
|
def get_dialog_units(win):
|
||||||
|
return win.ConvertDialogPointToPixels((1, 1))[0]
|
||||||
|
|
||||||
|
def single_title(title):
|
||||||
|
"""Convert the two parts of an icon.sys title into one string."""
|
||||||
|
|
||||||
|
title = title[0] + " " + title[1]
|
||||||
|
return u" ".join(title.split())
|
||||||
|
|
||||||
|
def _get_icon_resource_as_images(name):
|
||||||
|
ico = guires.resources[name]
|
||||||
|
images = []
|
||||||
|
f = cStringIO.StringIO(ico)
|
||||||
|
count = struct.unpack("<HHH", ico[0:6])[2]
|
||||||
|
# count = wx.Image_GetImageCount(f, wx.BITMAP_TYPE_ICO)
|
||||||
|
for i in range(count):
|
||||||
|
f.seek(0)
|
||||||
|
images.append(wx.ImageFromStream(f, wx.BITMAP_TYPE_ICO, i))
|
||||||
|
return images
|
||||||
|
|
||||||
|
def get_icon_resource(name):
|
||||||
|
"""Convert a Window ICO contained in a string to an IconBundle."""
|
||||||
|
|
||||||
|
bundle = wx.IconBundle()
|
||||||
|
for img in _get_icon_resource_as_images(name):
|
||||||
|
bmp = wx.BitmapFromImage(img)
|
||||||
|
icon = wx.IconFromBitmap(bmp)
|
||||||
|
bundle.AddIcon(icon)
|
||||||
|
return bundle
|
||||||
|
|
||||||
|
def get_icon_resource_bmp(name, size):
|
||||||
|
"""Get an icon resource as a Bitmap.
|
||||||
|
|
||||||
|
Tries to find the closest matching size if no exact match exists."""
|
||||||
|
|
||||||
|
best = None
|
||||||
|
best_size = (0, 0)
|
||||||
|
for img in _get_icon_resource_as_images(name):
|
||||||
|
sz = (img.GetWidth(), img.GetHeight())
|
||||||
|
if sz == size:
|
||||||
|
return wx.BitmapFromImage(img)
|
||||||
|
if sz[0] >= size[0] and sz[1] >= size[1]:
|
||||||
|
if ((best_size[0] < size[0] or best_size[1] < size[1])
|
||||||
|
or sz[0] * sz[1] < best_size[0] * best_size[1]):
|
||||||
|
best = img
|
||||||
|
best_size = sz
|
||||||
|
elif sz[0] * sz[1] > best_size[0] * best_size[1]:
|
||||||
|
best = img
|
||||||
|
best_size = sz
|
||||||
|
img = best.Rescale(size[0], size[1], wx.IMAGE_QUALITY_HIGH)
|
||||||
|
return wx.BitmapFromImage(img)
|
||||||
|
|
||||||
|
|
||||||
|
class dirlist_control(wx.ListCtrl):
|
||||||
|
"""Lists all the save files in a memory card image."""
|
||||||
|
|
||||||
|
def __init__(self, parent, evt_focus, evt_select, config):
|
||||||
|
self.config = config
|
||||||
|
self.selected = set()
|
||||||
|
self.evt_select = evt_select
|
||||||
|
wx.ListCtrl.__init__(self, parent, wx.ID_ANY,
|
||||||
|
style = wx.LC_REPORT)
|
||||||
|
wx.EVT_LIST_COL_CLICK(self, -1, self.evt_col_click)
|
||||||
|
wx.EVT_LIST_ITEM_FOCUSED(self, -1, evt_focus)
|
||||||
|
wx.EVT_LIST_ITEM_SELECTED(self, -1, self.evt_item_selected)
|
||||||
|
wx.EVT_LIST_ITEM_DESELECTED(self, -1, self.evt_item_deselected)
|
||||||
|
|
||||||
|
def _update_dirtable(self, mc, dir):
|
||||||
|
self.dirtable = table = []
|
||||||
|
enc = "unicode"
|
||||||
|
if self.config.get_ascii():
|
||||||
|
enc = "ascii"
|
||||||
|
for ent in dir:
|
||||||
|
if not ps2mc.mode_is_dir(ent[0]):
|
||||||
|
continue
|
||||||
|
dirname = "/" + ent[8]
|
||||||
|
s = mc.get_icon_sys(dirname)
|
||||||
|
if s == None:
|
||||||
|
continue
|
||||||
|
a = ps2save.unpack_icon_sys(s)
|
||||||
|
size = mc.dir_size(dirname)
|
||||||
|
title = ps2save.icon_sys_title(a, encoding = enc)
|
||||||
|
table.append((ent, s, size, title))
|
||||||
|
|
||||||
|
def update_dirtable(self, mc):
|
||||||
|
self.dirtable = []
|
||||||
|
if mc == None:
|
||||||
|
return
|
||||||
|
dir = mc.dir_open("/")
|
||||||
|
try:
|
||||||
|
self._update_dirtable(mc, dir)
|
||||||
|
finally:
|
||||||
|
dir.close()
|
||||||
|
|
||||||
|
def cmp_dir_name(self, i1, i2):
|
||||||
|
return self.dirtable[i1][0][8] > self.dirtable[i2][0][8]
|
||||||
|
|
||||||
|
def cmp_dir_title(self, i1, i2):
|
||||||
|
return self.dirtable[i1][3] > self.dirtable[i2][3]
|
||||||
|
|
||||||
|
def cmp_dir_size(self, i1, i2):
|
||||||
|
return self.dirtable[i1][2] > self.dirtable[i2][2]
|
||||||
|
|
||||||
|
def cmp_dir_modified(self, i1, i2):
|
||||||
|
m1 = list(self.dirtable[i1][0][6])
|
||||||
|
m2 = list(self.dirtable[i2][0][6])
|
||||||
|
m1.reverse()
|
||||||
|
m2.reverse()
|
||||||
|
return m1 > m2
|
||||||
|
|
||||||
|
def evt_col_click(self, event):
|
||||||
|
col = event.m_col
|
||||||
|
if col == 0:
|
||||||
|
cmp = self.cmp_dir_name
|
||||||
|
elif col == 1:
|
||||||
|
cmp = self.cmp_dir_size
|
||||||
|
elif col == 2:
|
||||||
|
cmp = self.cmp_dir_modified
|
||||||
|
elif col == 3:
|
||||||
|
cmp = self.cmp_dir_title
|
||||||
|
self.SortItems(cmp)
|
||||||
|
return
|
||||||
|
|
||||||
|
def evt_item_selected(self, event):
|
||||||
|
self.selected.add(event.GetData())
|
||||||
|
self.evt_select(event)
|
||||||
|
|
||||||
|
def evt_item_deselected(self, event):
|
||||||
|
self.selected.discard(event.GetData())
|
||||||
|
self.evt_select(event)
|
||||||
|
|
||||||
|
def update(self, mc):
|
||||||
|
"""Update the ListCtrl according to the contents of the
|
||||||
|
memory card image."""
|
||||||
|
|
||||||
|
self.ClearAll()
|
||||||
|
self.selected = set()
|
||||||
|
self.InsertColumn(0, "Directory")
|
||||||
|
self.InsertColumn(1, "Size")
|
||||||
|
self.InsertColumn(2, "Modified")
|
||||||
|
self.InsertColumn(3, "Description")
|
||||||
|
li = self.GetColumn(1)
|
||||||
|
li.SetAlign(wx.LIST_FORMAT_RIGHT)
|
||||||
|
li.SetText("Size")
|
||||||
|
self.SetColumn(1, li)
|
||||||
|
|
||||||
|
self.update_dirtable(mc)
|
||||||
|
|
||||||
|
empty = len(self.dirtable) == 0
|
||||||
|
self.Enable(not empty)
|
||||||
|
if empty:
|
||||||
|
return
|
||||||
|
|
||||||
|
for (i, a) in enumerate(self.dirtable):
|
||||||
|
(ent, icon_sys, size, title) = a
|
||||||
|
li = self.InsertStringItem(i, ent[8])
|
||||||
|
self.SetStringItem(li, 1, "%dK" % (size / 1024))
|
||||||
|
m = ent[6]
|
||||||
|
m = ("%04d-%02d-%02d %02d:%02d"
|
||||||
|
% (m[5], m[4], m[3], m[2], m[1]))
|
||||||
|
self.SetStringItem(li, 2, m)
|
||||||
|
self.SetStringItem(li, 3, single_title(title))
|
||||||
|
self.SetItemData(li, i)
|
||||||
|
|
||||||
|
du = get_dialog_units(self)
|
||||||
|
for i in range(4):
|
||||||
|
self.SetColumnWidth(i, wx.LIST_AUTOSIZE)
|
||||||
|
self.SetColumnWidth(i, self.GetColumnWidth(i) + du)
|
||||||
|
self.SortItems(self.cmp_dir_name)
|
||||||
|
|
||||||
|
|
||||||
|
class icon_window(wx.Window):
|
||||||
|
"""Displays a save file's 3D icon. Windows only.
|
||||||
|
|
||||||
|
The rendering of the 3D icon is handled by C++ code in the
|
||||||
|
mymcsup DLL which subclasses this window. This class mainly
|
||||||
|
handles configuration options that affect how the 3D icon is
|
||||||
|
displayed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ID_CMD_ANIMATE = 201
|
||||||
|
ID_CMD_LIGHT_NONE = 202
|
||||||
|
ID_CMD_LIGHT_ICON = 203
|
||||||
|
ID_CMD_LIGHT_ALT1 = 204
|
||||||
|
ID_CMD_LIGHT_ALT2 = 205
|
||||||
|
ID_CMD_CAMERA_FLAT = 206
|
||||||
|
ID_CMD_CAMERA_DEFAULT = 207
|
||||||
|
ID_CMD_CAMERA_NEAR = 209
|
||||||
|
ID_CMD_CAMERA_HIGH = 210
|
||||||
|
|
||||||
|
light_options = {ID_CMD_LIGHT_NONE: lighting_none,
|
||||||
|
ID_CMD_LIGHT_ICON: lighting_icon,
|
||||||
|
ID_CMD_LIGHT_ALT1: lighting_alternate,
|
||||||
|
ID_CMD_LIGHT_ALT2: lighting_alternate2}
|
||||||
|
|
||||||
|
camera_options = {ID_CMD_CAMERA_FLAT: camera_flat,
|
||||||
|
ID_CMD_CAMERA_DEFAULT: camera_default,
|
||||||
|
ID_CMD_CAMERA_NEAR: camera_near,
|
||||||
|
ID_CMD_CAMERA_HIGH: camera_high}
|
||||||
|
|
||||||
|
def append_menu_options(self, win, menu):
|
||||||
|
menu.AppendCheckItem(icon_window.ID_CMD_ANIMATE,
|
||||||
|
"Animate Icons")
|
||||||
|
menu.AppendSeparator()
|
||||||
|
menu.AppendRadioItem(icon_window.ID_CMD_LIGHT_NONE,
|
||||||
|
"Lighting Off")
|
||||||
|
menu.AppendRadioItem(icon_window.ID_CMD_LIGHT_ICON,
|
||||||
|
"Icon Lighting")
|
||||||
|
menu.AppendRadioItem(icon_window.ID_CMD_LIGHT_ALT1,
|
||||||
|
"Alternate Lighting")
|
||||||
|
menu.AppendRadioItem(icon_window.ID_CMD_LIGHT_ALT2,
|
||||||
|
"Alternate Lighting 2")
|
||||||
|
menu.AppendSeparator()
|
||||||
|
menu.AppendRadioItem(icon_window.ID_CMD_CAMERA_FLAT,
|
||||||
|
"Camera Flat")
|
||||||
|
menu.AppendRadioItem(icon_window.ID_CMD_CAMERA_DEFAULT,
|
||||||
|
"Camera Default")
|
||||||
|
menu.AppendRadioItem(icon_window.ID_CMD_CAMERA_NEAR,
|
||||||
|
"Camera Near")
|
||||||
|
menu.AppendRadioItem(icon_window.ID_CMD_CAMERA_HIGH,
|
||||||
|
"Camera High")
|
||||||
|
|
||||||
|
wx.EVT_MENU(win, icon_window.ID_CMD_ANIMATE,
|
||||||
|
self.evt_menu_animate)
|
||||||
|
wx.EVT_MENU(win, icon_window.ID_CMD_LIGHT_NONE,
|
||||||
|
self.evt_menu_light)
|
||||||
|
wx.EVT_MENU(win, icon_window.ID_CMD_LIGHT_ICON,
|
||||||
|
self.evt_menu_light)
|
||||||
|
wx.EVT_MENU(win, icon_window.ID_CMD_LIGHT_ALT1,
|
||||||
|
self.evt_menu_light)
|
||||||
|
wx.EVT_MENU(win, icon_window.ID_CMD_LIGHT_ALT2,
|
||||||
|
self.evt_menu_light)
|
||||||
|
|
||||||
|
wx.EVT_MENU(win, icon_window.ID_CMD_CAMERA_FLAT,
|
||||||
|
self.evt_menu_camera)
|
||||||
|
wx.EVT_MENU(win, icon_window.ID_CMD_CAMERA_DEFAULT,
|
||||||
|
self.evt_menu_camera)
|
||||||
|
wx.EVT_MENU(win, icon_window.ID_CMD_CAMERA_NEAR,
|
||||||
|
self.evt_menu_camera)
|
||||||
|
wx.EVT_MENU(win, icon_window.ID_CMD_CAMERA_HIGH,
|
||||||
|
self.evt_menu_camera)
|
||||||
|
|
||||||
|
def __init__(self, parent, focus):
|
||||||
|
self.failed = False
|
||||||
|
wx.Window.__init__(self, parent)
|
||||||
|
if mymcsup == None:
|
||||||
|
self.failed = True
|
||||||
|
return
|
||||||
|
r = mymcsup.init_icon_renderer(focus.GetHandle(),
|
||||||
|
self.GetHandle())
|
||||||
|
if r == -1:
|
||||||
|
print "init_icon_renderer failed"
|
||||||
|
self.failed = True
|
||||||
|
return
|
||||||
|
|
||||||
|
self.config = config = mymcsup.icon_config()
|
||||||
|
config.animate = True
|
||||||
|
|
||||||
|
self.menu = wx.Menu()
|
||||||
|
self.append_menu_options(self, self.menu)
|
||||||
|
self.set_lighting(self.ID_CMD_LIGHT_ALT2)
|
||||||
|
self.set_camera(self.ID_CMD_CAMERA_DEFAULT)
|
||||||
|
|
||||||
|
wx.EVT_CONTEXT_MENU(self, self.evt_context_menu)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
if mymcsup != None:
|
||||||
|
mymcsup.delete_icon_renderer()
|
||||||
|
|
||||||
|
def update_menu(self, menu):
|
||||||
|
"""Update the content menu according to the current config."""
|
||||||
|
|
||||||
|
menu.Check(icon_window.ID_CMD_ANIMATE, self.config.animate)
|
||||||
|
menu.Check(self.lighting_id, True)
|
||||||
|
menu.Check(self.camera_id, True)
|
||||||
|
|
||||||
|
def load_icon(self, icon_sys, icon):
|
||||||
|
"""Pass the raw icon data to the support DLL for display."""
|
||||||
|
|
||||||
|
if self.failed:
|
||||||
|
return
|
||||||
|
|
||||||
|
if icon_sys == None or icon == None:
|
||||||
|
r = mymcsup.load_icon(None, 0, None, 0)
|
||||||
|
else:
|
||||||
|
r = mymcsup.load_icon(icon_sys, len(icon_sys),
|
||||||
|
icon, len(icon))
|
||||||
|
if r != 0:
|
||||||
|
print "load_icon", r
|
||||||
|
self.failed = True
|
||||||
|
|
||||||
|
def _set_lighting(self, lighting, vertex_diffuse, alt_lighting,
|
||||||
|
light_dirs, light_colours, ambient):
|
||||||
|
if self.failed:
|
||||||
|
return
|
||||||
|
config = self.config
|
||||||
|
config.lighting = lighting
|
||||||
|
config.vertex_diffuse = vertex_diffuse
|
||||||
|
config.alt_lighting = alt_lighting
|
||||||
|
config.light_dirs = mkvec4arr3(light_dirs)
|
||||||
|
config.light_colours = mkvec4arr3(light_colours)
|
||||||
|
config.ambient = D3DXVECTOR4(*ambient)
|
||||||
|
if mymcsup.set_config(config) == -1:
|
||||||
|
self.failed = True
|
||||||
|
|
||||||
|
def set_lighting(self, id):
|
||||||
|
self.lighting_id = id
|
||||||
|
self._set_lighting(**self.light_options[id])
|
||||||
|
|
||||||
|
def set_animate(self, animate):
|
||||||
|
if self.failed:
|
||||||
|
return
|
||||||
|
self.config.animate = animate
|
||||||
|
if mymcsup.set_config(self.config) == -1:
|
||||||
|
self.failed = True
|
||||||
|
|
||||||
|
def _set_camera(self, camera):
|
||||||
|
if self.failed:
|
||||||
|
return
|
||||||
|
self.config.camera = mymcsup.D3DXVECTOR3(*camera)
|
||||||
|
if mymcsup.set_config(self.config) == -1:
|
||||||
|
self.failed = True
|
||||||
|
|
||||||
|
def set_camera(self, id):
|
||||||
|
self.camera_id = id
|
||||||
|
self._set_camera(self.camera_options[id])
|
||||||
|
|
||||||
|
def evt_context_menu(self, event):
|
||||||
|
self.update_menu(self.menu)
|
||||||
|
self.PopupMenu(self.menu)
|
||||||
|
|
||||||
|
def evt_menu_animate(self, event):
|
||||||
|
self.set_animate(not self.config.animate)
|
||||||
|
|
||||||
|
def evt_menu_light(self, event):
|
||||||
|
self.set_lighting(event.GetId())
|
||||||
|
|
||||||
|
def evt_menu_camera(self, event):
|
||||||
|
self.set_camera(event.GetId())
|
||||||
|
|
||||||
|
class gui_config(wx.Config):
|
||||||
|
"""A class for holding the persistant configuration state."""
|
||||||
|
|
||||||
|
memcard_dir = "Memory Card Directory"
|
||||||
|
savefile_dir = "Save File Directory"
|
||||||
|
ascii = "ASCII Descriptions"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
wx.Config.__init__(self, "mymc", "Ross Ridge",
|
||||||
|
style = wx.CONFIG_USE_LOCAL_FILE)
|
||||||
|
|
||||||
|
def get_memcard_dir(self, default = None):
|
||||||
|
return self.Read(gui_config.memcard_dir, default)
|
||||||
|
|
||||||
|
def set_memcard_dir(self, value):
|
||||||
|
return self.Write(gui_config.memcard_dir, value)
|
||||||
|
|
||||||
|
def get_savefile_dir(self, default = None):
|
||||||
|
return self.Read(gui_config.savefile_dir, default)
|
||||||
|
|
||||||
|
def set_savefile_dir(self, value):
|
||||||
|
return self.Write(gui_config.savefile_dir, value)
|
||||||
|
|
||||||
|
def get_ascii(self, default = False):
|
||||||
|
return bool(self.ReadInt(gui_config.ascii, int(bool(default))))
|
||||||
|
|
||||||
|
def set_ascii(self, value):
|
||||||
|
return self.WriteInt(gui_config.ascii, int(bool(value)))
|
||||||
|
|
||||||
|
def add_tool(toolbar, id, label, ico):
|
||||||
|
tbsize = toolbar.GetToolBitmapSize()
|
||||||
|
bmp = get_icon_resource_bmp(ico, tbsize)
|
||||||
|
return toolbar.AddLabelTool(id, label, bmp, shortHelp = label)
|
||||||
|
|
||||||
|
class gui_frame(wx.Frame):
|
||||||
|
"""The main top level window."""
|
||||||
|
|
||||||
|
ID_CMD_EXIT = wx.ID_EXIT
|
||||||
|
ID_CMD_OPEN = wx.ID_OPEN
|
||||||
|
ID_CMD_EXPORT = 103
|
||||||
|
ID_CMD_IMPORT = 104
|
||||||
|
ID_CMD_DELETE = wx.ID_DELETE
|
||||||
|
ID_CMD_ASCII = 106
|
||||||
|
|
||||||
|
def message_box(self, message, caption = "mymc", style = wx.OK,
|
||||||
|
x = -1, y = -1):
|
||||||
|
return wx.MessageBox(message, caption, style, self, x, y)
|
||||||
|
|
||||||
|
def error_box(self, msg):
|
||||||
|
return self.message_box(msg, "Error", wx.OK | wx.ICON_ERROR)
|
||||||
|
|
||||||
|
def mc_error(self, value, filename = None):
|
||||||
|
"""Display a message box for EnvironmentError exeception."""
|
||||||
|
|
||||||
|
if filename == None:
|
||||||
|
filename = self.mcname
|
||||||
|
if filename == None:
|
||||||
|
filename = "???"
|
||||||
|
filename = getattr(value, "filename", filename)
|
||||||
|
strerror = getattr(value, "strerror", "unknown error")
|
||||||
|
return self.error_box(filename + ": " + strerror)
|
||||||
|
|
||||||
|
def __init__(self, parent, title, mcname = None):
|
||||||
|
self.f = None
|
||||||
|
self.mc = None
|
||||||
|
self.mcname = None
|
||||||
|
self.icon_win = None
|
||||||
|
|
||||||
|
size = (750, 350)
|
||||||
|
if mymcsup == None:
|
||||||
|
size = (500, 350)
|
||||||
|
wx.Frame.__init__(self, parent, wx.ID_ANY, title, size = size)
|
||||||
|
|
||||||
|
wx.EVT_CLOSE(self, self.evt_close)
|
||||||
|
|
||||||
|
self.config = gui_config()
|
||||||
|
self.title = title
|
||||||
|
|
||||||
|
self.SetIcons(get_icon_resource("mc4.ico"))
|
||||||
|
|
||||||
|
wx.EVT_MENU(self, self.ID_CMD_EXIT, self.evt_cmd_exit)
|
||||||
|
wx.EVT_MENU(self, self.ID_CMD_OPEN, self.evt_cmd_open)
|
||||||
|
wx.EVT_MENU(self, self.ID_CMD_EXPORT, self.evt_cmd_export)
|
||||||
|
wx.EVT_MENU(self, self.ID_CMD_IMPORT, self.evt_cmd_import)
|
||||||
|
wx.EVT_MENU(self, self.ID_CMD_DELETE, self.evt_cmd_delete)
|
||||||
|
wx.EVT_MENU(self, self.ID_CMD_ASCII, self.evt_cmd_ascii)
|
||||||
|
|
||||||
|
filemenu = wx.Menu()
|
||||||
|
filemenu.Append(self.ID_CMD_OPEN, "&Open...",
|
||||||
|
"Opens an existing PS2 memory card image.")
|
||||||
|
filemenu.AppendSeparator()
|
||||||
|
self.export_menu_item = filemenu.Append(
|
||||||
|
self.ID_CMD_EXPORT, "&Export...",
|
||||||
|
"Export a save file from this image.")
|
||||||
|
self.import_menu_item = filemenu.Append(
|
||||||
|
self.ID_CMD_IMPORT, "&Import...",
|
||||||
|
"Import a save file into this image.")
|
||||||
|
self.delete_menu_item = filemenu.Append(
|
||||||
|
self.ID_CMD_DELETE, "&Delete")
|
||||||
|
filemenu.AppendSeparator()
|
||||||
|
filemenu.Append(self.ID_CMD_EXIT, "E&xit")
|
||||||
|
|
||||||
|
optionmenu = wx.Menu()
|
||||||
|
self.ascii_menu_item = optionmenu.AppendCheckItem(
|
||||||
|
self.ID_CMD_ASCII, "&ASCII Descriptions",
|
||||||
|
"Show descriptions in ASCII instead of Shift-JIS")
|
||||||
|
|
||||||
|
|
||||||
|
wx.EVT_MENU_OPEN(self, self.evt_menu_open);
|
||||||
|
|
||||||
|
self.CreateToolBar(wx.TB_HORIZONTAL)
|
||||||
|
self.toolbar = toolbar = self.GetToolBar()
|
||||||
|
tbsize = (32, 32)
|
||||||
|
toolbar.SetToolBitmapSize(tbsize)
|
||||||
|
add_tool(toolbar, self.ID_CMD_OPEN, "Open", "mc2.ico")
|
||||||
|
toolbar.AddSeparator()
|
||||||
|
add_tool(toolbar, self.ID_CMD_IMPORT, "Import", "mc5b.ico")
|
||||||
|
add_tool(toolbar, self.ID_CMD_EXPORT, "Export", "mc6a.ico")
|
||||||
|
toolbar.Realize()
|
||||||
|
|
||||||
|
self.statusbar = self.CreateStatusBar(2,
|
||||||
|
style = wx.ST_SIZEGRIP)
|
||||||
|
self.statusbar.SetStatusWidths([-2, -1])
|
||||||
|
|
||||||
|
panel = wx.Panel(self, wx.ID_ANY, (0, 0))
|
||||||
|
|
||||||
|
self.dirlist = dirlist_control(panel,
|
||||||
|
self.evt_dirlist_item_focused,
|
||||||
|
self.evt_dirlist_select,
|
||||||
|
self.config)
|
||||||
|
if mcname != None:
|
||||||
|
self.open_mc(mcname)
|
||||||
|
else:
|
||||||
|
self.refresh()
|
||||||
|
|
||||||
|
sizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
sizer.Add(self.dirlist, 2, wx.EXPAND)
|
||||||
|
sizer.AddSpacer(5)
|
||||||
|
|
||||||
|
icon_win = None
|
||||||
|
if mymcsup != None:
|
||||||
|
icon_win = icon_window(panel, self)
|
||||||
|
if icon_win.failed:
|
||||||
|
icon_win.Destroy()
|
||||||
|
icon_win = None
|
||||||
|
self.icon_win = icon_win
|
||||||
|
|
||||||
|
if icon_win == None:
|
||||||
|
self.info1 = None
|
||||||
|
self.info2 = None
|
||||||
|
else:
|
||||||
|
self.icon_menu = icon_menu = wx.Menu()
|
||||||
|
icon_win.append_menu_options(self, icon_menu)
|
||||||
|
optionmenu.AppendSubMenu(icon_menu, "Icon Window")
|
||||||
|
title_style = wx.ALIGN_RIGHT | wx.ST_NO_AUTORESIZE
|
||||||
|
|
||||||
|
self.info1 = wx.StaticText(panel, -1, "",
|
||||||
|
style = title_style)
|
||||||
|
self.info2 = wx.StaticText(panel, -1, "",
|
||||||
|
style = title_style)
|
||||||
|
# self.info3 = wx.StaticText(panel, -1, "")
|
||||||
|
|
||||||
|
info_sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
info_sizer.Add(self.info1, 0, wx.EXPAND)
|
||||||
|
info_sizer.Add(self.info2, 0, wx.EXPAND)
|
||||||
|
# info_sizer.Add(self.info3, 0, wx.EXPAND)
|
||||||
|
info_sizer.AddSpacer(5)
|
||||||
|
info_sizer.Add(icon_win, 1, wx.EXPAND)
|
||||||
|
|
||||||
|
sizer.Add(info_sizer, 1, wx.EXPAND | wx.ALL,
|
||||||
|
border = 5)
|
||||||
|
|
||||||
|
menubar = wx.MenuBar()
|
||||||
|
menubar.Append(filemenu, "&File")
|
||||||
|
menubar.Append(optionmenu, "&Options")
|
||||||
|
self.SetMenuBar(menubar)
|
||||||
|
|
||||||
|
|
||||||
|
panel.SetSizer(sizer)
|
||||||
|
panel.SetAutoLayout(True)
|
||||||
|
sizer.Fit(panel)
|
||||||
|
|
||||||
|
self.Show(True)
|
||||||
|
|
||||||
|
if self.mc == None:
|
||||||
|
self.evt_cmd_open()
|
||||||
|
|
||||||
|
def _close_mc(self):
|
||||||
|
if self.mc != None:
|
||||||
|
try:
|
||||||
|
self.mc.close()
|
||||||
|
except EnvironmentError, value:
|
||||||
|
self.mc_error(value)
|
||||||
|
self.mc = None
|
||||||
|
if self.f != None:
|
||||||
|
try:
|
||||||
|
self.f.close()
|
||||||
|
except EnvironmentError, value:
|
||||||
|
self.mc_error(value)
|
||||||
|
self.f = None
|
||||||
|
self.mcname = None
|
||||||
|
|
||||||
|
def refresh(self):
|
||||||
|
try:
|
||||||
|
self.dirlist.update(self.mc)
|
||||||
|
except EnvironmentError, value:
|
||||||
|
self.mc_error(value)
|
||||||
|
self._close_mc()
|
||||||
|
self.dirlist.update(None)
|
||||||
|
|
||||||
|
mc = self.mc
|
||||||
|
|
||||||
|
self.toolbar.EnableTool(self.ID_CMD_IMPORT, mc != None)
|
||||||
|
self.toolbar.EnableTool(self.ID_CMD_EXPORT, False)
|
||||||
|
|
||||||
|
if mc == None:
|
||||||
|
status = "No memory card image"
|
||||||
|
else:
|
||||||
|
free = mc.get_free_space() / 1024
|
||||||
|
limit = mc.get_allocatable_space() / 1024
|
||||||
|
status = "%dK of %dK free" % (free, limit)
|
||||||
|
self.statusbar.SetStatusText(status, 1)
|
||||||
|
|
||||||
|
def open_mc(self, filename):
|
||||||
|
self._close_mc()
|
||||||
|
self.statusbar.SetStatusText("", 1)
|
||||||
|
if self.icon_win != None:
|
||||||
|
self.icon_win.load_icon(None, None)
|
||||||
|
|
||||||
|
f = None
|
||||||
|
try:
|
||||||
|
f = file(filename, "r+b")
|
||||||
|
mc = ps2mc.ps2mc(f)
|
||||||
|
except EnvironmentError, value:
|
||||||
|
if f != None:
|
||||||
|
f.close()
|
||||||
|
self.mc_error(value, filename)
|
||||||
|
self.SetTitle(self.title)
|
||||||
|
self.refresh()
|
||||||
|
return
|
||||||
|
|
||||||
|
self.f = f
|
||||||
|
self.mc = mc
|
||||||
|
self.mcname = filename
|
||||||
|
self.SetTitle(filename + " - " + self.title)
|
||||||
|
self.refresh()
|
||||||
|
|
||||||
|
def evt_menu_open(self, event):
|
||||||
|
self.import_menu_item.Enable(self.mc != None)
|
||||||
|
selected = self.mc != None and len(self.dirlist.selected) > 0
|
||||||
|
self.export_menu_item.Enable(selected)
|
||||||
|
self.delete_menu_item.Enable(selected)
|
||||||
|
self.ascii_menu_item.Check(self.config.get_ascii())
|
||||||
|
if self.icon_win != None:
|
||||||
|
self.icon_win.update_menu(self.icon_menu)
|
||||||
|
|
||||||
|
def evt_dirlist_item_focused(self, event):
|
||||||
|
if self.icon_win == None:
|
||||||
|
return
|
||||||
|
|
||||||
|
mc = self.mc
|
||||||
|
|
||||||
|
i = event.GetData()
|
||||||
|
(ent, icon_sys, size, title) = self.dirlist.dirtable[i]
|
||||||
|
self.info1.SetLabel(title[0])
|
||||||
|
self.info2.SetLabel(title[1])
|
||||||
|
|
||||||
|
a = ps2save.unpack_icon_sys(icon_sys)
|
||||||
|
try:
|
||||||
|
mc.chdir("/" + ent[8])
|
||||||
|
f = mc.open(a[15], "rb")
|
||||||
|
try:
|
||||||
|
icon = f.read()
|
||||||
|
finally:
|
||||||
|
f.close()
|
||||||
|
except EnvironmentError, value:
|
||||||
|
print "icon failed to load", value
|
||||||
|
self.icon_win.load_icon(None, None)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.icon_win.load_icon(icon_sys, icon)
|
||||||
|
|
||||||
|
def evt_dirlist_select(self, event):
|
||||||
|
self.toolbar.EnableTool(self.ID_CMD_IMPORT, self.mc != None)
|
||||||
|
self.toolbar.EnableTool(self.ID_CMD_EXPORT,
|
||||||
|
len(self.dirlist.selected) > 0)
|
||||||
|
|
||||||
|
def evt_cmd_open(self, event = None):
|
||||||
|
fn = wx.FileSelector("Open Memory Card Image",
|
||||||
|
self.config.get_memcard_dir(""),
|
||||||
|
"Mcd001.ps2", "ps2", "*.ps2",
|
||||||
|
wx.FD_FILE_MUST_EXIST | wx.FD_OPEN,
|
||||||
|
self)
|
||||||
|
if fn == "":
|
||||||
|
return
|
||||||
|
self.open_mc(fn)
|
||||||
|
if self.mc != None:
|
||||||
|
dirname = os.path.dirname(fn)
|
||||||
|
if os.path.isabs(dirname):
|
||||||
|
self.config.set_memcard_dir(dirname)
|
||||||
|
|
||||||
|
def evt_cmd_export(self, event):
|
||||||
|
mc = self.mc
|
||||||
|
if mc == None:
|
||||||
|
return
|
||||||
|
|
||||||
|
selected = self.dirlist.selected
|
||||||
|
dirtable = self.dirlist.dirtable
|
||||||
|
sfiles = []
|
||||||
|
for i in selected:
|
||||||
|
dirname = dirtable[i][0][8]
|
||||||
|
try:
|
||||||
|
sf = mc.export_save_file("/" + dirname)
|
||||||
|
longname = ps2save.make_longname(dirname, sf)
|
||||||
|
sfiles.append((dirname, sf, longname))
|
||||||
|
except EnvironmentError, value:
|
||||||
|
self.mc_error(value. dirname)
|
||||||
|
|
||||||
|
if len(sfiles) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
dir = self.config.get_savefile_dir("")
|
||||||
|
if len(selected) == 1:
|
||||||
|
(dirname, sf, longname) = sfiles[0]
|
||||||
|
fn = wx.FileSelector("Export " + dirname,
|
||||||
|
dir, longname, "psu",
|
||||||
|
"EMS save file (.psu)|*.psu"
|
||||||
|
"|MAXDrive save file (.max)"
|
||||||
|
"|*.max",
|
||||||
|
(wx.FD_OVERWRITE_PROMPT
|
||||||
|
| wx.FD_SAVE),
|
||||||
|
self)
|
||||||
|
if fn == "":
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
f = file(fn, "wb")
|
||||||
|
try:
|
||||||
|
if fn.endswith(".max"):
|
||||||
|
sf.save_max_drive(f)
|
||||||
|
else:
|
||||||
|
sf.save_ems(f)
|
||||||
|
finally:
|
||||||
|
f.close()
|
||||||
|
except EnvironmentError, value:
|
||||||
|
self.mc_error(value, fn)
|
||||||
|
return
|
||||||
|
|
||||||
|
dir = os.path.dirname(fn)
|
||||||
|
if os.path.isabs(dir):
|
||||||
|
self.config.set_savefile_dir(dir)
|
||||||
|
|
||||||
|
self.message_box("Exported " + fn + " successfully.")
|
||||||
|
return
|
||||||
|
|
||||||
|
dir = wx.DirSelector("Export Save Files", dir, parent = self)
|
||||||
|
if dir == "":
|
||||||
|
return
|
||||||
|
count = 0
|
||||||
|
for (dirname, sf, longname) in sfiles:
|
||||||
|
fn = os.path.join(dir, longname) + ".psu"
|
||||||
|
try:
|
||||||
|
f = file(fn, "wb")
|
||||||
|
sf.save_ems(f)
|
||||||
|
f.close()
|
||||||
|
count += 1
|
||||||
|
except EnvironmentError, value:
|
||||||
|
self.mc_error(value, fn)
|
||||||
|
if count > 0:
|
||||||
|
if os.path.isabs(dir):
|
||||||
|
self.config.set_savefile_dir(dir)
|
||||||
|
self.message_box("Exported %d file(s) successfully."
|
||||||
|
% count)
|
||||||
|
|
||||||
|
|
||||||
|
def _do_import(self, fn):
|
||||||
|
sf = ps2save.ps2_save_file()
|
||||||
|
f = file(fn, "rb")
|
||||||
|
try:
|
||||||
|
ft = ps2save.detect_file_type(f)
|
||||||
|
f.seek(0)
|
||||||
|
if ft == "max":
|
||||||
|
sf.load_max_drive(f)
|
||||||
|
elif ft == "psu":
|
||||||
|
sf.load_ems(f)
|
||||||
|
elif ft == "cbs":
|
||||||
|
sf.load_codebreaker(f)
|
||||||
|
elif ft == "sps":
|
||||||
|
sf.load_sharkport(f)
|
||||||
|
elif ft == "npo":
|
||||||
|
self.error_box(fn + ": nPort saves"
|
||||||
|
" are not supported.")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
self.error_box(fn + ": Save file format not"
|
||||||
|
" recognized.")
|
||||||
|
return
|
||||||
|
finally:
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
if not self.mc.import_save_file(sf, True):
|
||||||
|
self.error_box(fn + ": Save file already present.")
|
||||||
|
|
||||||
|
def evt_cmd_import(self, event):
|
||||||
|
if self.mc == None:
|
||||||
|
return
|
||||||
|
|
||||||
|
dir = self.config.get_savefile_dir("")
|
||||||
|
fd = wx.FileDialog(self, "Import Save File", dir,
|
||||||
|
wildcard = ("PS2 save files"
|
||||||
|
" (.cbs;.psu;.max;.sps;.xps)"
|
||||||
|
"|*.cbs;*.psu;*.max;*.sps;*.xps"
|
||||||
|
"|All files|*.*"),
|
||||||
|
style = (wx.FD_OPEN | wx.FD_MULTIPLE
|
||||||
|
| wx.FD_FILE_MUST_EXIST))
|
||||||
|
if fd == None:
|
||||||
|
return
|
||||||
|
r = fd.ShowModal()
|
||||||
|
if r == wx.ID_CANCEL:
|
||||||
|
return
|
||||||
|
|
||||||
|
success = None
|
||||||
|
for fn in fd.GetPaths():
|
||||||
|
try:
|
||||||
|
self._do_import(fn)
|
||||||
|
success = fn
|
||||||
|
except EnvironmentError, value:
|
||||||
|
self.mc_error(value, fn)
|
||||||
|
|
||||||
|
if success != None:
|
||||||
|
dir = os.path.dirname(success)
|
||||||
|
if os.path.isabs(dir):
|
||||||
|
self.config.set_savefile_dir(dir)
|
||||||
|
self.refresh()
|
||||||
|
|
||||||
|
def evt_cmd_delete(self, event):
|
||||||
|
mc = self.mc
|
||||||
|
if mc == None:
|
||||||
|
return
|
||||||
|
|
||||||
|
selected = self.dirlist.selected
|
||||||
|
dirtable = self.dirlist.dirtable
|
||||||
|
|
||||||
|
dirnames = [dirtable[i][0][8]
|
||||||
|
for i in selected]
|
||||||
|
if len(selected) == 1:
|
||||||
|
title = dirtable[list(selected)[0]][3]
|
||||||
|
s = dirnames[0] + " (" + single_title(title) + ")"
|
||||||
|
else:
|
||||||
|
s = ", ".join(dirnames)
|
||||||
|
if len(s) > 200:
|
||||||
|
s = s[:200] + "..."
|
||||||
|
r = self.message_box("Are you sure you want to delete "
|
||||||
|
+ s + "?",
|
||||||
|
"Delete Save File Confirmation",
|
||||||
|
wx.YES_NO)
|
||||||
|
if r != wx.YES:
|
||||||
|
return
|
||||||
|
|
||||||
|
for dn in dirnames:
|
||||||
|
try:
|
||||||
|
mc.rmdir("/" + dn)
|
||||||
|
except EnvironmentError, value:
|
||||||
|
self.mc_error(value, dn)
|
||||||
|
|
||||||
|
mc.check()
|
||||||
|
self.refresh()
|
||||||
|
|
||||||
|
def evt_cmd_ascii(self, event):
|
||||||
|
self.config.set_ascii(not self.config.get_ascii())
|
||||||
|
self.refresh()
|
||||||
|
|
||||||
|
def evt_cmd_exit(self, event):
|
||||||
|
self.Close(True)
|
||||||
|
|
||||||
|
def evt_close(self, event):
|
||||||
|
self._close_mc()
|
||||||
|
self.Destroy()
|
||||||
|
|
||||||
|
def run(filename = None):
|
||||||
|
"""Display a GUI for working with memory card images."""
|
||||||
|
|
||||||
|
wx_app = wx.PySimpleApp()
|
||||||
|
frame = gui_frame(None, "mymc", filename)
|
||||||
|
return wx_app.MainLoop()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import gc
|
||||||
|
gc.set_debug(gc.DEBUG_LEAK)
|
||||||
|
|
||||||
|
run("test.ps2")
|
||||||
|
|
||||||
|
gc.collect()
|
||||||
|
for o in gc.garbage:
|
||||||
|
print
|
||||||
|
print o
|
||||||
|
if type(o) == ps2mc.ps2mc_file:
|
||||||
|
for m in dir(o):
|
||||||
|
print m, getattr(o, m)
|
||||||
|
|
||||||
|
|
||||||
|
# while True:
|
||||||
|
# for o in gc.garbage:
|
||||||
|
# if type(o) == ps2mc.ps2mc_file:
|
||||||
|
# for m in dir(o):
|
||||||
|
# if getattr(o, m) == None:
|
||||||
|
# continue
|
||||||
|
# if (m == "__del__"
|
||||||
|
# or m == "__class__"
|
||||||
|
# or m == "__dict__"
|
||||||
|
# or m == "__weakref__"):
|
||||||
|
# continue
|
||||||
|
# print m
|
||||||
|
# setattr(o, m, None)
|
||||||
|
# o = None
|
||||||
|
# break
|
||||||
|
# break
|
||||||
|
# del gc.garbage[:]
|
||||||
|
# gc.collect()
|
185
guires.py
Normal file
185
guires.py
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
resources = {
|
||||||
|
"mc4.ico": (
|
||||||
|
"AAABAAIAICAQAAAAAADoAgAAJgAAADAwAAEAAAAAqA4AAA4DAAAoAAAAIAAAAEAAAAABAAQAAAAA\n"
|
||||||
|
"AIACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAgAAAAICAAIAAAACAAIAAgIAAAICAgADAwMAA\n"
|
||||||
|
"AAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
|
||||||
|
"AAAAAAAAAAcABwAAAAAAAAAAAAAAAABwAABwAAAAAAAAAAAAAAAHAAYAB3d3d3dwAAAAAAAAcABm\n"
|
||||||
|
"YAB3d3dwAAAAAAAABwAGZmYAB3d3AAAAAAAAAHAAZmZmYAB4iIiIiIiAAAcABmZmZgAAB/+P+q/4\n"
|
||||||
|
"gABwAGZmZmAAAABu7u7u6IAHAAZmZmYAAAAABu7u7u6AAABmZmZgBwcAAABu7u7ugAAGZmZmAHBw\n"
|
||||||
|
"AAAABu7u7oAAAGZmYAcHBwAAAABu7u6ABwAGZgBwcHBwAAAABu7ugAAAAGAABwcHAABAAADu7oAA\n"
|
||||||
|
"AAAAAHB3cAAEAAAG7u6AAAAAAAAAB3AAwAAAbu7ugAAABwAAAAAABAAABu7u7oAAAAhgAAAAAMAH\n"
|
||||||
|
"AG7u7u6AAAAI5gAAAAAAdwbu7u7ugAAACO5gAAxAAABu7u7u7oAAAAju5gAEzAAG7u7u7u6AAAAI\n"
|
||||||
|
"7u5gAAwAbu7u7u7ugAAACO7u5gAABu7u7u7u7oAAAAju7u5gAG7u7u7u7u6AAAAI7u7u5gbu7u7u\n"
|
||||||
|
"7u7ugAAACI7u7u7u7u7u7u7u6IAAAAiIiIiIiIiIiIiIiIiAAAAIiIiIiIiIiIiIiIiIgAAAAAAA\n"
|
||||||
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////g////wH///4AAH/8AAH/+AAD//AA\n"
|
||||||
|
"AAHgAAABwAAAAYAAAAGAAAABgAAAAYAAAAGAAAAB4AAAAfAAAAH4AAAB+AAAAfgAAAH4AAAB+AAA\n"
|
||||||
|
"AfgAAAH4AAAB+AAAAfgAAAH4AAAB+AAAAfgAAAH4AAAB//////////8oAAAAMAAAAGAAAAABAAgA\n"
|
||||||
|
"AAAAAIAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAgAAAAICAAIAAAACAAIAAgIAAAMDAwADA\n"
|
||||||
|
"3MAA8MqmAAAgQAAAIGAAACCAAAAgoAAAIMAAACDgAABAAAAAQCAAAEBAAABAYAAAQIAAAECgAABA\n"
|
||||||
|
"wAAAQOAAAGAAAABgIAAAYEAAAGBgAABggAAAYKAAAGDAAABg4AAAgAAAAIAgAACAQAAAgGAAAICA\n"
|
||||||
|
"AACAoAAAgMAAAIDgAACgAAAAoCAAAKBAAACgYAAAoIAAAKCgAACgwAAAoOAAAMAAAADAIAAAwEAA\n"
|
||||||
|
"AMBgAADAgAAAwKAAAMDAAADA4AAA4AAAAOAgAADgQAAA4GAAAOCAAADgoAAA4MAAAODgAEAAAABA\n"
|
||||||
|
"ACAAQABAAEAAYABAAIAAQACgAEAAwABAAOAAQCAAAEAgIABAIEAAQCBgAEAggABAIKAAQCDAAEAg\n"
|
||||||
|
"4ABAQAAAQEAgAEBAQABAQGAAQECAAEBAoABAQMAAQEDgAEBgAABAYCAAQGBAAEBgYABAYIAAQGCg\n"
|
||||||
|
"AEBgwABAYOAAQIAAAECAIABAgEAAQIBgAECAgABAgKAAQIDAAECA4ABAoAAAQKAgAECgQABAoGAA\n"
|
||||||
|
"QKCAAECgoABAoMAAQKDgAEDAAABAwCAAQMBAAEDAYABAwIAAQMCgAEDAwABAwOAAQOAAAEDgIABA\n"
|
||||||
|
"4EAAQOBgAEDggABA4KAAQODAAEDg4ACAAAAAgAAgAIAAQACAAGAAgACAAIAAoACAAMAAgADgAIAg\n"
|
||||||
|
"AACAICAAgCBAAIAgYACAIIAAgCCgAIAgwACAIOAAgEAAAIBAIACAQEAAgEBgAIBAgACAQKAAgEDA\n"
|
||||||
|
"AIBA4ACAYAAAgGAgAIBgQACAYGAAgGCAAIBgoACAYMAAgGDgAICAAACAgCAAgIBAAICAYACAgIAA\n"
|
||||||
|
"gICgAICAwACAgOAAgKAAAICgIACAoEAAgKBgAICggACAoKAAgKDAAICg4ACAwAAAgMAgAIDAQACA\n"
|
||||||
|
"wGAAgMCAAIDAoACAwMAAgMDgAIDgAACA4CAAgOBAAIDgYACA4IAAgOCgAIDgwACA4OAAwAAAAMAA\n"
|
||||||
|
"IADAAEAAwABgAMAAgADAAKAAwADAAMAA4ADAIAAAwCAgAMAgQADAIGAAwCCAAMAgoADAIMAAwCDg\n"
|
||||||
|
"AMBAAADAQCAAwEBAAMBAYADAQIAAwECgAMBAwADAQOAAwGAAAMBgIADAYEAAwGBgAMBggADAYKAA\n"
|
||||||
|
"wGDAAMBg4ADAgAAAwIAgAMCAQADAgGAAwICAAMCAoADAgMAAwIDgAMCgAADAoCAAwKBAAMCgYADA\n"
|
||||||
|
"oIAAwKCgAMCgwADAoOAAwMAAAMDAIADAwEAAwMBgAMDAgADAwKAA8Pv/AKSgoACAgIAAAAD/AAD/\n"
|
||||||
|
"AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
|
||||||
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
|
||||||
|
"AAAAAAAAAAAAAAAAAAAAAAAAAKQAAKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
|
||||||
|
"AAAAAAAAAAAApAAAAACkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACk\n"
|
||||||
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKQAAAAAAAAApAAA\n"
|
||||||
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApAAAAAAACQAAAACkpKSkpKSkpKSk\n"
|
||||||
|
"pKSkpAAAAAAAAAAAAAAAAAAAAAAAAAAAAACkAAAAAAAJCQkAAACkpKSkpKSkpKSkpKQAAAAAAAAA\n"
|
||||||
|
"AAAAAAAAAAAAAAAAAAAAAKQAAAAAAAkJCQkJAAAAAKSkpKSkpKQAAAAAAAAAAAAAAAAAAAAAAAAA\n"
|
||||||
|
"AAAAAAAApAAAAAAACQkJCQkJCQAAAACkpKSkpKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACkAAAA\n"
|
||||||
|
"AAAJCQkJCQkJCQkAAAAApPf39/f39/f39/f39/f39/f39/cAAAAAAAAAAKQAAAAAAAkJCQkJCQkJ\n"
|
||||||
|
"CQkJAAAAAKQHBwf3BwcH9/f39wcHB/f39/cAAAAAAAAApAAAAAAACQkJCQkJCQkJCQkAAAAAAACk\n"
|
||||||
|
"9vb39vb29/r69/b29vf39/cAAAAAAACkAAAAAAAJCQkJCQkJCQkJCQAAAAAAAAAApPf39/f39/f3\n"
|
||||||
|
"9/f39/f39/cAAAAAAKQAAAAAAAkJCQkJCQkJCQkJAAAAAAAAAAAAAKT39/f39/f39/f39/f39/cA\n"
|
||||||
|
"AAAApAAAAAAACQkJCQkJCQkJCQkAAAAAAAAAAAAAAAD3/v7+/v7+/v7+/v739/cAAACkAAAAAAAJ\n"
|
||||||
|
"CQkJCQkJCQkJCQAApAAApAAAAAAAAAAAB/7+/v7+/v7+/v7+9/cAAAAAAAAAAAkJCQkJCQkJCQkJ\n"
|
||||||
|
"AACkAACkAAAAAAAAAAAAAAf+/v7+/v7+/v7+9/cAAAAAAAAACQkJCQkJCQkJCQkAAKQAAKQAAAAA\n"
|
||||||
|
"AAAAAAAAAAAH/v7+/v7+/v7+9/cAAACkAAAAAAkJCQkJCQkJCQAApAAApAAAAKQAAAAAAAAAAAAA\n"
|
||||||
|
"B/7+/v7+/v7+9/cAAAAApAAAAAAJCQkJCQkJAACkAACkAAAApACkAAAAAAAAAAAAAAf+/v7+/v7+\n"
|
||||||
|
"9/cAAAAAAACkAAAACQkJCQkAAKQAAKQAAACkAKQAAAAAAAAAAAAAAAAH/v7+/v7+9/cAAAAAAAAA\n"
|
||||||
|
"AAAAAAkJCQAAAAAApAAAAAAApAAAAAAAAADAAAAAAAAAB/7+/v7+9/cAAAAAAAAAAKQAAAAJAAAA\n"
|
||||||
|
"AACkAAAKpKQAAAAAAAAAAMAAAAAAAAAAAP7+/v7+9/cAAAAAAAAAAAAAAAAAAAAAAKQAAACkAKSk\n"
|
||||||
|
"AAAAAAAAwAAAAAAAAAAAB/7+/v7+9/cAAAAAAAAAAACkAAAAAAAAAAAAAACkpACkAAAAAADAAAAA\n"
|
||||||
|
"AAAAAAAH/v7+/v7+9/cAAAAAAAAAAAD3pAAAAAAAAAAAAAAApKQKAAAAAMDAAAAAAAAAAAf+/v7+\n"
|
||||||
|
"/v7+9/cAAAAAAAAAAAD39wcAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAB/7+/v7+/v7+9/cAAAAA\n"
|
||||||
|
"AAAAAAD39/4HAAAAAAAAAAAAAAAAAADAAAAACqQAAAAH/v7+/v7+/v7+9/cAAAAAAAAAAAD39/7+\n"
|
||||||
|
"BwAAAAAAAAAAAAAAAMDAAAAKpAoAAAf+/v7+/v7+/v7+9/cAAAAAAAAAAAD39/7+/gcAAAAAAAAA\n"
|
||||||
|
"AAAAAAAAAAqkpAAAB/7+/v7+/v7+/v7+9/cAAAAAAAAAAAD39/7+/v4HAAAAAAAAAAAAAAAAAKQK\n"
|
||||||
|
"AAAH/v7+/v7+/v7+/v7+9/cAAAAAAAAAAAD39/7+/v7+BwAAAAAAwADAAAAAAAAAAAf+/v7+/v7+\n"
|
||||||
|
"/v7+/v7+9/cAAAAAAAAAAAD39/7+/v7+/gcAAAAAAMDAwAAAAAAAB/7+/v7+/v7+/v7+/v7+9/cA\n"
|
||||||
|
"AAAAAAAAAAD39/7+/v7+/v4HAAAAwAAAwAAAAAAH/v7+/v7+/v7+/v7+/v7+9/cAAAAAAAAAAAD3\n"
|
||||||
|
"9/7+/v7+/v7+BwAAAAAAwAAAAAf+/v7+/v7+/v7+/v7+/v7+9/cAAAAAAAAAAAD39/7+/v7+/v7+\n"
|
||||||
|
"/gcAAAAAAAAAB/7+/v7+/v7+/v7+/v7+/v7+9/cAAAAAAAAAAAD39/7+/v7+/v7+/v4HAAAAAAAH\n"
|
||||||
|
"/v7+/v7+/v7+/v7+/v7+/v7+9/cAAAAAAAAAAAD39/7+/v7+/v7+/v7+BwAAAAf+/v7+/v7+/v7+\n"
|
||||||
|
"/v7+/v7+/v7+9/cAAAAAAAAAAAD39/7+/v7+/v7+/v7+/gcAB/7+/v7+/v7+/v7+/v7+/v7+/v7+\n"
|
||||||
|
"9/cAAAAAAAAAAAD39/7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+9/cAAAAAAAAA\n"
|
||||||
|
"AAD39/f+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v739/cAAAAAAAAAAAD3+/v79/f3\n"
|
||||||
|
"9/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/cAAAAAAAAAAAD30NDQ9/f39/f39/f39/f3\n"
|
||||||
|
"9/f39/f39/f39/f39/f396SkpKSk9/cAAAAAAAAAAAD3+ff59/f39/f39/f39/f39/f39/f39/f3\n"
|
||||||
|
"9/f39/f39/f39/f39/cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
|
||||||
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
|
||||||
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////wAA////\n"
|
||||||
|
"////AAD//h////8AAP/8D////wAA//gP////AAD/8Af///8AAP/gAAAP/wAA/8AAAD//AAD/gAAB\n"
|
||||||
|
"//8AAP8AAAH//wAA/gAAAAADAAD8AAAAAAMAAPgAAAAAAwAA8AAAAAADAADgAAAAAAMAAMAAAAAA\n"
|
||||||
|
"AwAAgAAAAAADAACAAAAAAAMAAIAAAAAAAwAAgAAAAAADAADAAAAAAAMAAPAAAAAAAwAA+AAAAAAD\n"
|
||||||
|
"AAD8AAAAAAMAAP4AAAAAAwAA/gAAAAADAAD+AAAAAAMAAP4AAAAAAwAA/gAAAAADAAD+AAAAAAMA\n"
|
||||||
|
"AP4AAAAAAwAA/gAAAAADAAD+AAAAAAMAAP4AAAAAAwAA/gAAAAADAAD+AAAAAAMAAP4AAAAAAwAA\n"
|
||||||
|
"/gAAAAADAAD+AAAAAAMAAP4AAAAAAwAA/gAAAAADAAD+AAAAAAMAAP4AAAAAAwAA/gAAAAADAAD+\n"
|
||||||
|
"AAAAAAMAAP///////wAA////////AAD///////8AAA==\n"
|
||||||
|
).decode("base64_codec"),
|
||||||
|
"mc5b.ico": (
|
||||||
|
"AAABAAEAICAQAAAAAADoAgAAFgAAACgAAAAgAAAAQAAAAAEABAAAAAAAgAIAAAAAAAAAAAAAAAAA\n"
|
||||||
|
"AAAAAAAAAAAAAACAAACAAAAAgIAAgAAAAIAAgACAgAAAgICAAMDAwAAAAP8AAP8AAAD//wD/AAAA\n"
|
||||||
|
"/wD/AP//AAD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
|
||||||
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcABwAAAAAAAAAAAAAAAABwAAAAAAAAAAAAAAAAAA\n"
|
||||||
|
"AHAAYAAAAAAAAAAAAAAAAAcABmYAAAAAAAAAAAAAAKBwAGZmYABwAAAAAAAAAACqAAZmZmYABwAA\n"
|
||||||
|
"AAAAAAAAqqBmZmZgAABwAAAAAAAAB6qqZmZmAAAABwAACqqqqqqiqqZmYAAAAABwAAqqqqqqoiqq\n"
|
||||||
|
"ZgBwcAAABwAKoiIiIiIiqqAHBwAAAABwCqIiIiIiIiqqcHBwAAAABwqiIiIiIiIqqgcHBwAAAAAK\n"
|
||||||
|
"oiIiIiIiqqBwcHAABAAACqqqqqqiKqoHB3cAAEAAAAqqqqqqoqqgAAB3AAwAAAcAAAAAAKqqAAAA\n"
|
||||||
|
"AABAAABwAAAAAACqpwAAAAAMAHAHAAAAAAAAqgBwAAAAAAdwcAAAAAAAAKAABwAAxAAABwAAAAAA\n"
|
||||||
|
"AAAAAABwAEzAAHAAAAAAAAAAAAAABwAAwAcAAAAAAAAAAAAAAABwAABwAAAAAAAAAAAAAAAABwAH\n"
|
||||||
|
"AAAAAAAAAAAAAAAAAABwcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
|
||||||
|
"AAAAAAAAAAAAAAD///////////////////////8H///+B////AP///gB///QAH//wAA//8AAH/+A\n"
|
||||||
|
"AA+AAAAHgAAAA4AAAAGAAAAAgAAAAIAAAACAAAAAgAAAAP/AAAH/wAAD/8wAB//eAA///wAf//+A\n"
|
||||||
|
"P///wH///+D////x/////////////////w==\n"
|
||||||
|
).decode("base64_codec"),
|
||||||
|
"mc2.ico": (
|
||||||
|
"AAABAAEAMDAAAQAAAACoDgAAFgAAACgAAAAwAAAAYAAAAAEACAAAAAAAgAoAAAAAAAAAAAAAAAAA\n"
|
||||||
|
"AAAAAAAAAAAAAACAAACAAAAAgIAAgAAAAIAAgACAgAAAwMDAAMDcwADwyqYAACBAAAAgYAAAIIAA\n"
|
||||||
|
"ACCgAAAgwAAAIOAAAEAAAABAIAAAQEAAAEBgAABAgAAAQKAAAEDAAABA4AAAYAAAAGAgAABgQAAA\n"
|
||||||
|
"YGAAAGCAAABgoAAAYMAAAGDgAACAAAAAgCAAAIBAAACAYAAAgIAAAICgAACAwAAAgOAAAKAAAACg\n"
|
||||||
|
"IAAAoEAAAKBgAACggAAAoKAAAKDAAACg4AAAwAAAAMAgAADAQAAAwGAAAMCAAADAoAAAwMAAAMDg\n"
|
||||||
|
"AADgAAAA4CAAAOBAAADgYAAA4IAAAOCgAADgwAAA4OAAQAAAAEAAIABAAEAAQABgAEAAgABAAKAA\n"
|
||||||
|
"QADAAEAA4ABAIAAAQCAgAEAgQABAIGAAQCCAAEAgoABAIMAAQCDgAEBAAABAQCAAQEBAAEBAYABA\n"
|
||||||
|
"QIAAQECgAEBAwABAQOAAQGAAAEBgIABAYEAAQGBgAEBggABAYKAAQGDAAEBg4ABAgAAAQIAgAECA\n"
|
||||||
|
"QABAgGAAQICAAECAoABAgMAAQIDgAECgAABAoCAAQKBAAECgYABAoIAAQKCgAECgwABAoOAAQMAA\n"
|
||||||
|
"AEDAIABAwEAAQMBgAEDAgABAwKAAQMDAAEDA4ABA4AAAQOAgAEDgQABA4GAAQOCAAEDgoABA4MAA\n"
|
||||||
|
"QODgAIAAAACAACAAgABAAIAAYACAAIAAgACgAIAAwACAAOAAgCAAAIAgIACAIEAAgCBgAIAggACA\n"
|
||||||
|
"IKAAgCDAAIAg4ACAQAAAgEAgAIBAQACAQGAAgECAAIBAoACAQMAAgEDgAIBgAACAYCAAgGBAAIBg\n"
|
||||||
|
"YACAYIAAgGCgAIBgwACAYOAAgIAAAICAIACAgEAAgIBgAICAgACAgKAAgIDAAICA4ACAoAAAgKAg\n"
|
||||||
|
"AICgQACAoGAAgKCAAICgoACAoMAAgKDgAIDAAACAwCAAgMBAAIDAYACAwIAAgMCgAIDAwACAwOAA\n"
|
||||||
|
"gOAAAIDgIACA4EAAgOBgAIDggACA4KAAgODAAIDg4ADAAAAAwAAgAMAAQADAAGAAwACAAMAAoADA\n"
|
||||||
|
"AMAAwADgAMAgAADAICAAwCBAAMAgYADAIIAAwCCgAMAgwADAIOAAwEAAAMBAIADAQEAAwEBgAMBA\n"
|
||||||
|
"gADAQKAAwEDAAMBA4ADAYAAAwGAgAMBgQADAYGAAwGCAAMBgoADAYMAAwGDgAMCAAADAgCAAwIBA\n"
|
||||||
|
"AMCAYADAgIAAwICgAMCAwADAgOAAwKAAAMCgIADAoEAAwKBgAMCggADAoKAAwKDAAMCg4ADAwAAA\n"
|
||||||
|
"wMAgAMDAQADAwGAAwMCAAMDAoADw+/8ApKCgAICAgAAAAP8AAP8AAAD//wD/AAAA/wD/AP//AAD/\n"
|
||||||
|
"//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
|
||||||
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
|
||||||
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
|
||||||
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKQAAKQAAAAAAAAAAAAAAAAA\n"
|
||||||
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAACkpKSkpKSkpAAAAACkpKSkpKSkpKSkpKSkpKSkpKSkpKSk\n"
|
||||||
|
"pKQAAAAAAAAAAAAAAKSkpKSkpKQAAAAAAAAApKSkpKSkpKSkpKSkpKSkpKSkpKSkpKSkAAAAAAAA\n"
|
||||||
|
"AAAAAKSkpKSkpAcAAAAAAAAAB6SkpKSkpKSkpKSkpKSkpKSkpKSkpKSkAAAAAAAAAAAAAKSkpKSk\n"
|
||||||
|
"BwAAAAAACQAAAACkpKSkpKSkpKSkpKSkpKSkpKQAAACkAAAAAAAAAAAAAKSkpKQHAAAAAAAJCQkA\n"
|
||||||
|
"AAAHpKSkpKSkpKSkpKSkpKSkpKQAAACkAAAAAAAAAAAAAKSkpAcAAAAAAAkJCQkJAAAAAAekpKSk\n"
|
||||||
|
"pKSkpKSkpKSkpKQAAACkAAAAAAAAAAAAAKSkBwAAAAAACQkJCQkJCQAAAAAHpKSkpKSkpKSkpKSk\n"
|
||||||
|
"pKSkpKSkAAAAAAAAAAAAAKQAAAAAAAAJCQkJCQkJCQkAAAAAB6SkpKSkpKSkpKSkpKSkpKSkAAAA\n"
|
||||||
|
"AAAAAAAAAKQAAAAAAAkJCQkJCQkJCQkJAAAAAAekpKSkpKSkpKSkpKSkpKSkAAAAAAAAAAAApAAA\n"
|
||||||
|
"AAAACQkJCQkJCQkJCQkAAAAAAAAHpKSkpKSkpKSkpKSkpKSkAAAAAAAAAACkAAAAAAAJCQkJCQkJ\n"
|
||||||
|
"CQkJCQAAAAAAAAAAB6SkpKSkpKSkpKSkpKSkAAAAAAAAAKQAAAAAAAkJCQkJCQkJCQkJAAAAAAAA\n"
|
||||||
|
"AAAAAAekpKSkpKSkpKSkpKSkAAAAAAAApAAAAAAACQkJCQkJCQkJCQkAAAAAAAAAAAAAAAAHpKSk\n"
|
||||||
|
"pKSkpKSkpKSkAAAAAACkAAAAAAAJCQkJCQkJCQkJCQAApAAApAAAAAAAAAAAB6SkpKSkpKSkpKSk\n"
|
||||||
|
"AAAAAAAAAAAAAAkJCQkJCQkJCQkJAACkAACkAAAAAAAAAAAAAAekpKSkpKSkpKSkAAAAAAAAAAAA\n"
|
||||||
|
"CQkJCQkJCQkJCQkAAKQAAKQAAAAAAAAAAAAAAAAHpKSkpKSkpKSkAAAAAACkAAAAAAkJCQkJCQkJ\n"
|
||||||
|
"CQAApAAApAAAAKQAAAAAAAAAAAAAB6SkpKSkpKSkAAAAAAAApAAAAAAJCQkJCQkJAACkAACkAAAA\n"
|
||||||
|
"pACkAAAAAAAAAAAAAAekpKSkpKSkAAAAAAAAAACkAAAACQkJCQkAAKQAAKQAAACkAKQAAAAAAAAA\n"
|
||||||
|
"AAAAAAAHpKSkpKSkAAAAAAAAAAAAAAAAAAkJCQAAAAAApAAAAAAApAAAAAAAAADAAAAAAAAAB6Sk\n"
|
||||||
|
"pKSkAAAAAAAAAAAAAAAAAAAJAAAAAACkAAAKpKQAAAAAAAAAAMAAAAAAAAAAAKSkpKSkAAAAAAAA\n"
|
||||||
|
"AAAAAAAAAAAAAAAAAKQAAACkAKSkAAAAAAAAwAAAAAAAAAAAB6SkpKSkAAAAAAAAAAAAAKQAAAAA\n"
|
||||||
|
"AAAAAAAAAACkpACkAAAAAADAAAAAAAAAAAAHpKSkpKSkAAAAAAAAAAAAAKSkBwAAAAAAAAAAAAAA\n"
|
||||||
|
"pKQKAAAAAMDAAAAAAAAAAAekpKSkpKSkAAAAAAAAAAAAAKSkpAcAAAAAAAAAAAAAAAAAAAAAwAAA\n"
|
||||||
|
"AAAAAAAAB6SkpKSkpKSkAAAAAAAAAAAAAKSkpKQHAAAAAAAAAAAAAAAAAADAAAAACqQAAAAHpKSk\n"
|
||||||
|
"pKSkpKSkAAAAAAAAAAAAAKSkpKSkBwAAAAAAAAAAAAAAAMDAAAAKpAoAAAekpKSkpKSkpKSkAAAA\n"
|
||||||
|
"AAAAAAAAAKSkpKSkpAcAAAAAAAAAAAAAAAAAAAqkpAAAB6SkpKSkpKSkpKSkAAAAAAAAAAAAAKSk\n"
|
||||||
|
"pKSkpKQHAAAAAAAAAAAAAAAAAKQKAAD3pKSkpKSkpKSkpKSkAAAAAAAAAAAAAKSkpKSkpKSk9wAA\n"
|
||||||
|
"AAAAwADAAAAAAAAAAPf39/f396SkpKSkpKSkAAAAAAAAAAAAAKSkpKSkpKSk9/cAAAAAAMDAwAAA\n"
|
||||||
|
"AAAAB6SkpPf396SkpKSkpKSkAAAAAAAAAAAAAKSkpKSkpKSk9/f3AAAAwAAAwAAAAACkpKSkpPf3\n"
|
||||||
|
"96SkpKSkpKSkAAAAAAAAAAAAAKSkpKSkpKSk9/f39wAAAAAAwAAAAPekpKSkpPf396SkpKSkpKSk\n"
|
||||||
|
"AAAAAAAAAAAAAKSkpKSkpKSk9/f39/cAAAAAAAAA9/ekpKSkpPf396SkpKSkpKSkAAAAAAAAAAAA\n"
|
||||||
|
"AKSkpKSkpKSk9/f39/f3AAAAAAD39/ekpKSkpPf396SkpKSkpKSkAAAAAAAAAAAAAKSkpKSkpKSk\n"
|
||||||
|
"9/f39/f39wAAAPf39/ekpKSkpPf396SkpKSkpKSkAAAAAAAAAAAAAKSkpKSkpKSk9/f39/f39/cA\n"
|
||||||
|
"9/f39/ekpKSkpPf396SkpKSkpKSkAAAAAAAAAAAAAKSkpKSkpKSk9/f39/f39/f39/f39/ekpKSk\n"
|
||||||
|
"pPf396SkpKSkpKSkAAAAAAAAAAAAAKSkpKSkpKSk9/f39/f39/f39/f39/ekpKSkpPf396SkpKSk\n"
|
||||||
|
"pKQAAAAAAAAAAAAAAKSkpKSkpKSk9/f39/f39/f39/f39/ekpKSkpPf396SkpKSkpAAA9gAAAAAA\n"
|
||||||
|
"AAAAAACkpKSkpKSk9/f39/f39/f39/f39/f39/f39/f396SkpKSkAAAAAAAAAAAAAAAAAAAAAAAA\n"
|
||||||
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
|
||||||
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////AAD///////8AAP///////wAA////////\n"
|
||||||
|
"AAD//w////8AAP8AAAAADwAA/gAAAAAHAAD+AAAAAAcAAP4AAAAABwAA/gAAAAAHAAD+AAAAAAcA\n"
|
||||||
|
"AP4AAAAABwAA/gAAAAAHAAD+AAAAAAcAAPwAAAAABwAA+AAAAAAHAADwAAAAAAcAAOAAAAAABwAA\n"
|
||||||
|
"wAAAAAAHAADAAAAAAAcAAMAAAAAABwAAwAAAAAAHAADgAAAAAAcAAPgAAAAABwAA/AAAAAAHAAD+\n"
|
||||||
|
"AAAAAAcAAP4AAAAABwAA/gAAAAAHAAD+AAAAAAcAAP4AAAAABwAA/gAAAAAHAAD+AAAAAAcAAP4A\n"
|
||||||
|
"AAAABwAA/gAAAAAHAAD+AAAAAAcAAP4AAAAABwAA/gAAAAAHAAD+AAAAAAcAAP4AAAAABwAA/gAA\n"
|
||||||
|
"AAAHAAD+AAAAAAcAAP4AAAAABwAA/gAAAAAHAAD+AAAAAA8AAP4AAAAAGwAA/wAAAAA/AAD/////\n"
|
||||||
|
"//sAAP///////wAA\n"
|
||||||
|
).decode("base64_codec"),
|
||||||
|
"mc6a.ico": (
|
||||||
|
"AAABAAEAICAQAAAAAADoAgAAFgAAACgAAAAgAAAAQAAAAAEABAAAAAAAgAIAAAAAAAAAAAAAAAAA\n"
|
||||||
|
"AAAAAAAAAAAAAACAAACAAAAAgIAAgAAAAIAAgACAgAAAgICAAMDAwAAAAP8AAP8AAAD//wD/AAAA\n"
|
||||||
|
"/wD/AP//AAD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
|
||||||
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAHAAAAAAAAAAAAAAAAAHAAAAAAAAAAAAAAAAAAAAcABg\n"
|
||||||
|
"AAAAAAAAAAAAAAAABwAGZgAAAAAAAAAAAAAAAHAAZmZgAHAAAACgAAAAAAcABmZmZgAHAAAAqgAA\n"
|
||||||
|
"AABwAGZmZmAAAHAAAKqgAAAHAAZmZmYAAAAHAACqqgAAcABmZmZgAAqqqqqqoqqgAAAGZmZmAHB6\n"
|
||||||
|
"qqqqqqIqqgAAZmZmYAcHCqIiIiIiIqqgAAZmZgBwcHqiIiIiIiIqqnAAZmAHBwcKoiIiIiIiKqoA\n"
|
||||||
|
"AAYAAHBweqIiIiIiIqqgAAAAAAcHdwqqqqqqoiqqAAAAAAAAAHcKqqqqqqKqoAAAAHAAAAAAAEAA\n"
|
||||||
|
"AHCqqgAAAAAHAAAAAAwAcAcAqqAAAAAAAHAAAAAAB3BwAKoAAAAAAAAHAADEAAAHAACgAAAAAAAA\n"
|
||||||
|
"AHAATMAAcAAAAAAAAAAAAAAHAADABwAAAAAAAAAAAAAAAHAAAHAAAAAAAAAAAAAAAAAHAAcAAAAA\n"
|
||||||
|
"AAAAAAAAAAAAAHBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
|
||||||
|
"AAAAAAAAAAAAAAD//////////////////////wf///4H///8A///+AH///AAf3/gAD8/wAAfH4AA\n"
|
||||||
|
"Dw8AAAAHAAAAAwAAAAEAAAAAAAAAAMAAAAHgAAAD8AAAB/AAAQ/4AAMf/AAHP/4AD3//AB///4A/\n"
|
||||||
|
"///Af///4P////H//////////////////w==\n"
|
||||||
|
).decode("base64_codec"),
|
||||||
|
}
|
787
lzari.py
Normal file
787
lzari.py
Normal file
@ -0,0 +1,787 @@
|
|||||||
|
#
|
||||||
|
# lzari.py
|
||||||
|
#
|
||||||
|
# By Ross Ridge
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
Implementation of Haruhiko Okumura's LZARI data compression algorithm
|
||||||
|
in Python. Largely based on LZARI.C, one key difference is the use of
|
||||||
|
a two level dicitionary look up during compression rather than
|
||||||
|
LZARI.C's binary search tree.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_SCCS_ID = "@(#) mysc lzari.py 1.5 08/02/05 16:00:34\n"
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import array
|
||||||
|
import binascii
|
||||||
|
import string
|
||||||
|
import time
|
||||||
|
from bisect import bisect_right
|
||||||
|
from math import log
|
||||||
|
|
||||||
|
try:
|
||||||
|
import ctypes
|
||||||
|
import mymcsup
|
||||||
|
except ImportError:
|
||||||
|
mymcsup = None
|
||||||
|
|
||||||
|
hexlify = binascii.hexlify
|
||||||
|
|
||||||
|
__ALL__ = ['lzari_codec', 'string_to_bit_array', 'bit_array_to_string']
|
||||||
|
|
||||||
|
#
|
||||||
|
# Fundamental constants of the LZARI compression alogorithm.
|
||||||
|
#
|
||||||
|
# Changing any of these values will create an incompatible implementation.
|
||||||
|
#
|
||||||
|
|
||||||
|
HIST_LEN = 4096
|
||||||
|
MIN_MATCH_LEN = 3
|
||||||
|
MAX_MATCH_LEN = 60
|
||||||
|
|
||||||
|
ARITH_BITS = 15
|
||||||
|
QUADRANT1 = 1 << ARITH_BITS
|
||||||
|
QUADRANT2 = QUADRANT1 * 2
|
||||||
|
QUADRANT3 = QUADRANT1 * 3
|
||||||
|
QUADRANT4 = QUADRANT1 * 4
|
||||||
|
MAX_CUM = QUADRANT1 - 1
|
||||||
|
MAX_CHAR = (256 + MAX_MATCH_LEN - MIN_MATCH_LEN + 1)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Other constants specific to this implementation
|
||||||
|
#
|
||||||
|
|
||||||
|
MAX_SUFFIX_CHAIN = 50 # limit on how many identical suffixes to try to match
|
||||||
|
|
||||||
|
#def debug(value, msg):
|
||||||
|
# print "@@@ %s %04x" % (msg, value)
|
||||||
|
debug = lambda value, msg: None
|
||||||
|
|
||||||
|
_tr_16 = string.maketrans("0123456789abcdef",
|
||||||
|
"\x00\x01\x02\x03"
|
||||||
|
"\x10\x11\x12\x13"
|
||||||
|
"\x20\x21\x22\x23"
|
||||||
|
"\x30\x31\x32\x33")
|
||||||
|
_tr_4 = string.maketrans("0123",
|
||||||
|
"\x00\x01"
|
||||||
|
"\x10\x11")
|
||||||
|
_tr_2 = string.maketrans("01", "\x00\x01")
|
||||||
|
|
||||||
|
def string_to_bit_array(s):
|
||||||
|
"""Convert a string to an array containing a sequence of bits."""
|
||||||
|
s = binascii.hexlify(s).translate(_tr_16)
|
||||||
|
s = binascii.hexlify(s).translate(_tr_4)
|
||||||
|
s = binascii.hexlify(s).translate(_tr_2)
|
||||||
|
a = array.array('B', s)
|
||||||
|
return a
|
||||||
|
|
||||||
|
_tr_rev_2 = string.maketrans("\x00\x01", "01")
|
||||||
|
_tr_rev_4 = string.maketrans("\x00\x01"
|
||||||
|
"\x10\x11",
|
||||||
|
"0123")
|
||||||
|
_tr_rev_16 = string.maketrans("\x00\x01\x02\x03"
|
||||||
|
"\x10\x11\x12\x13"
|
||||||
|
"\x20\x21\x22\x23"
|
||||||
|
"\x30\x31\x32\x33",
|
||||||
|
"0123456789abcdef")
|
||||||
|
def bit_array_to_string(a):
|
||||||
|
"""Convert an array containing a sequence of bits to a string."""
|
||||||
|
remainder = len(a) % 8
|
||||||
|
if remainder != 0:
|
||||||
|
a.fromlist([0] * (8 - remainder))
|
||||||
|
s = a.tostring()
|
||||||
|
s = binascii.unhexlify(s.translate(_tr_rev_2))
|
||||||
|
s = binascii.unhexlify(s.translate(_tr_rev_4))
|
||||||
|
return binascii.unhexlify(s.translate(_tr_rev_16))
|
||||||
|
|
||||||
|
def _match(src, pos, hpos, mlen, end):
|
||||||
|
mlen += 1
|
||||||
|
if not src.startswith(src[hpos : hpos + mlen], pos):
|
||||||
|
return None
|
||||||
|
for i in range(mlen, end):
|
||||||
|
if src[pos + i] != src[hpos + i]:
|
||||||
|
return i
|
||||||
|
return end
|
||||||
|
|
||||||
|
def _rehash_table2(src, chars, head, next, next2, hist_invalid):
|
||||||
|
p = head
|
||||||
|
table2 = {}
|
||||||
|
l = []
|
||||||
|
while p > hist_invalid:
|
||||||
|
l.append(p)
|
||||||
|
p = next[p % HIST_LEN]
|
||||||
|
l.reverse()
|
||||||
|
for p in l:
|
||||||
|
p2 = p + MIN_MATCH_LEN
|
||||||
|
key2 = src[p2 : p2 + chars]
|
||||||
|
head2 = table2.get(key2, hist_invalid)
|
||||||
|
next2[p % HIST_LEN] = head2
|
||||||
|
table2[key2] = p
|
||||||
|
return table2
|
||||||
|
|
||||||
|
class lzari_codec(object):
|
||||||
|
# despite the name this does not implement a codec compatible
|
||||||
|
# with Python's codec system
|
||||||
|
|
||||||
|
def init(self, decode):
|
||||||
|
self.high = QUADRANT4
|
||||||
|
self.low = 0
|
||||||
|
if decode:
|
||||||
|
self.code = 0
|
||||||
|
# reverse the order of sym_cum so bisect_right() can
|
||||||
|
# be used for faster searching
|
||||||
|
self.sym_cum = range(0, MAX_CHAR + 1)
|
||||||
|
else:
|
||||||
|
self.shifts = 0
|
||||||
|
self.char_to_symbol = range(1, MAX_CHAR + 1)
|
||||||
|
self.sym_cum = range(MAX_CHAR, -1, -1)
|
||||||
|
self.next_table = [None] * HIST_LEN
|
||||||
|
self.next2_table = [None] * HIST_LEN
|
||||||
|
self.suffix_table = {}
|
||||||
|
|
||||||
|
self.symbol_to_char = [0] + range(MAX_CHAR)
|
||||||
|
self.sym_freq = [0] + [1] * MAX_CHAR
|
||||||
|
self.position_cum = [0] * (HIST_LEN + 1)
|
||||||
|
a = 0
|
||||||
|
for i in range(HIST_LEN, 0, -1):
|
||||||
|
a = a + 10000 / (200 + i)
|
||||||
|
self.position_cum[i - 1] = a
|
||||||
|
|
||||||
|
def search(self, table, x):
|
||||||
|
c = 1
|
||||||
|
s = len(table) - 1
|
||||||
|
while True:
|
||||||
|
a = (s + c) / 2
|
||||||
|
if table[a] <= x:
|
||||||
|
s = a
|
||||||
|
else:
|
||||||
|
c = a + 1
|
||||||
|
if c >= s:
|
||||||
|
break
|
||||||
|
return c
|
||||||
|
|
||||||
|
def update_model_decode(self, symbol):
|
||||||
|
# A compatible implemention to the one used while compressing.
|
||||||
|
|
||||||
|
sym_freq = self.sym_freq
|
||||||
|
sym_cum = self.sym_cum
|
||||||
|
|
||||||
|
if self.sym_cum[MAX_CHAR] >= MAX_CUM:
|
||||||
|
c = 0
|
||||||
|
for i in range(MAX_CHAR, 0, -1):
|
||||||
|
self.sym_cum[MAX_CHAR - i] = c
|
||||||
|
a = (self.sym_freq[i] + 1) / 2
|
||||||
|
self.sym_freq[i] = a
|
||||||
|
c += a
|
||||||
|
self.sym_cum[MAX_CHAR] = c
|
||||||
|
freq = sym_freq[symbol]
|
||||||
|
new_symbol = symbol
|
||||||
|
while self.sym_freq[new_symbol - 1] == freq:
|
||||||
|
new_symbol -= 1
|
||||||
|
# new_symbol = sym_freq.index(freq)
|
||||||
|
if new_symbol != symbol:
|
||||||
|
symbol_to_char = self.symbol_to_char
|
||||||
|
swap_char = symbol_to_char[new_symbol]
|
||||||
|
char = symbol_to_char[symbol]
|
||||||
|
symbol_to_char[new_symbol] = char
|
||||||
|
symbol_to_char[symbol] = swap_char
|
||||||
|
sym_freq[new_symbol] = freq + 1
|
||||||
|
for i in range(MAX_CHAR - new_symbol + 1, MAX_CHAR + 1):
|
||||||
|
sym_cum[i] += 1
|
||||||
|
|
||||||
|
def update_model_encode(self, symbol):
|
||||||
|
sym_freq = self.sym_freq
|
||||||
|
sym_cum = self.sym_cum
|
||||||
|
|
||||||
|
if sym_cum[0] >= MAX_CUM:
|
||||||
|
c = 0
|
||||||
|
for i in range(MAX_CHAR, 0, -1):
|
||||||
|
sym_cum[i] = c
|
||||||
|
a = (sym_freq[i] + 1) / 2
|
||||||
|
sym_freq[i] = a
|
||||||
|
c += a
|
||||||
|
sym_cum[0] = c
|
||||||
|
freq = sym_freq[symbol]
|
||||||
|
new_symbol = symbol
|
||||||
|
while sym_freq[new_symbol - 1] == freq:
|
||||||
|
new_symbol -= 1
|
||||||
|
if new_symbol != symbol:
|
||||||
|
debug(new_symbol, "a")
|
||||||
|
swap_char = self.symbol_to_char[new_symbol]
|
||||||
|
char = self.symbol_to_char[symbol]
|
||||||
|
self.symbol_to_char[new_symbol] = char
|
||||||
|
self.symbol_to_char[symbol] = swap_char
|
||||||
|
self.char_to_symbol[char] = new_symbol
|
||||||
|
self.char_to_symbol[swap_char] = symbol
|
||||||
|
sym_freq[new_symbol] += 1
|
||||||
|
for i in range(new_symbol):
|
||||||
|
sym_cum[i] += 1
|
||||||
|
|
||||||
|
def decode_char(self):
|
||||||
|
high = self.high
|
||||||
|
low = self.low
|
||||||
|
code = self.code
|
||||||
|
sym_cum = self.sym_cum
|
||||||
|
|
||||||
|
_range = high - low
|
||||||
|
max_cum_freq = sym_cum[MAX_CHAR]
|
||||||
|
n = ((code - low + 1) * max_cum_freq - 1) / _range
|
||||||
|
i = bisect_right(sym_cum, n, 1)
|
||||||
|
high = low + sym_cum[i] * _range / max_cum_freq
|
||||||
|
low += sym_cum[i - 1] * _range / max_cum_freq
|
||||||
|
symbol = MAX_CHAR + 1 - i
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if low < QUADRANT2:
|
||||||
|
if low < QUADRANT1 or high > QUADRANT3:
|
||||||
|
if high > QUADRANT2:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
low -= QUADRANT1
|
||||||
|
code -= QUADRANT1
|
||||||
|
high -= QUADRANT1
|
||||||
|
else:
|
||||||
|
low -= QUADRANT2
|
||||||
|
code -= QUADRANT2
|
||||||
|
high -= QUADRANT2
|
||||||
|
low *= 2
|
||||||
|
high *= 2
|
||||||
|
code = code * 2 + self.in_iter()
|
||||||
|
|
||||||
|
ret = self.symbol_to_char[symbol]
|
||||||
|
self.high = high
|
||||||
|
self.low = low
|
||||||
|
self.code = code
|
||||||
|
self.update_model_decode(symbol)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def decode_position(self):
|
||||||
|
_range = self.high - self.low
|
||||||
|
max_cum = self.position_cum[0]
|
||||||
|
pos = self.search(self.position_cum,
|
||||||
|
((self.code - self.low + 1)
|
||||||
|
* max_cum - 1) / _range) - 1
|
||||||
|
self.high = (self.low +
|
||||||
|
self.position_cum[pos] * _range / max_cum)
|
||||||
|
self.low += self.position_cum[pos + 1] * _range / max_cum
|
||||||
|
while True:
|
||||||
|
if self.low < QUADRANT2:
|
||||||
|
if (self.low < QUADRANT1
|
||||||
|
or self.high > QUADRANT3):
|
||||||
|
if self.high > QUADRANT2:
|
||||||
|
return pos
|
||||||
|
else:
|
||||||
|
self.low -= QUADRANT1
|
||||||
|
self.code -= QUADRANT1
|
||||||
|
self.high -= QUADRANT1
|
||||||
|
else:
|
||||||
|
self.low -= QUADRANT2
|
||||||
|
self.code -= QUADRANT2
|
||||||
|
self.high -= QUADRANT2
|
||||||
|
self.low *= 2
|
||||||
|
self.high *= 2
|
||||||
|
self.code = self.in_iter() + self.code * 2
|
||||||
|
|
||||||
|
def add_suffix_1(self, pos, find):
|
||||||
|
# naive implemention used for testing
|
||||||
|
|
||||||
|
if not find:
|
||||||
|
return (None, 0)
|
||||||
|
src = self.src
|
||||||
|
mlen = min(1000, self.max_match, len(src) - pos)
|
||||||
|
hist_start = max(pos - HIST_LEN, 0)
|
||||||
|
while mlen >= MIN_MATCH_LEN:
|
||||||
|
i = src.rfind(src[pos : pos + mlen], hist_start, pos)
|
||||||
|
if i != -1:
|
||||||
|
assert (src[pos : pos + mlen]
|
||||||
|
== src[i: i + mlen])
|
||||||
|
return (i, mlen)
|
||||||
|
mlen -= 1
|
||||||
|
return (None, -1)
|
||||||
|
|
||||||
|
def add_suffix_2(self, pos, find):
|
||||||
|
# a two level dictionary look up that leverages Python's
|
||||||
|
# built-in dicts to get something that's hopefully faster
|
||||||
|
# than implementing binary trees in completely in Python.
|
||||||
|
|
||||||
|
src = self.src
|
||||||
|
suffix_table = self.suffix_table
|
||||||
|
max_match = min(self.max_match, len(src) - pos)
|
||||||
|
|
||||||
|
mlen = -1
|
||||||
|
mpos = None
|
||||||
|
|
||||||
|
hist_invalid = pos - HIST_LEN - 1
|
||||||
|
modpos = pos % HIST_LEN
|
||||||
|
pos2 = pos + MIN_MATCH_LEN
|
||||||
|
|
||||||
|
key = src[pos : pos2]
|
||||||
|
a = suffix_table.get(key)
|
||||||
|
if a != None:
|
||||||
|
next = self.next_table
|
||||||
|
next2 = self.next2_table
|
||||||
|
|
||||||
|
[count, head, table2, chars] = a
|
||||||
|
|
||||||
|
pos3 = pos2 + chars
|
||||||
|
key2 = src[pos2 : pos3]
|
||||||
|
min_match2 = MIN_MATCH_LEN + chars
|
||||||
|
if find:
|
||||||
|
p = table2.get(key2, hist_invalid)
|
||||||
|
maxmlen = max_match - min_match2
|
||||||
|
while p > hist_invalid and mlen != maxmlen:
|
||||||
|
p3 = p + min_match2
|
||||||
|
if mpos == None and p3 <= pos:
|
||||||
|
mpos = p
|
||||||
|
mlen = 0
|
||||||
|
if p3 >= pos:
|
||||||
|
p = next2[p % HIST_LEN]
|
||||||
|
continue
|
||||||
|
rlen = _match(src, pos3, p3, mlen,
|
||||||
|
min(maxmlen, pos - p3))
|
||||||
|
if rlen != None:
|
||||||
|
mpos = p
|
||||||
|
mlen = rlen
|
||||||
|
p = next2[p % HIST_LEN]
|
||||||
|
if mpos != None:
|
||||||
|
mlen += min_match2
|
||||||
|
elif find:
|
||||||
|
p = head
|
||||||
|
maxmlen = min(chars, max_match - MIN_MATCH_LEN)
|
||||||
|
i = 0
|
||||||
|
while (p > hist_invalid and i < 50000
|
||||||
|
and mlen < maxmlen):
|
||||||
|
assert i < count
|
||||||
|
i += 1
|
||||||
|
p2 = p + MIN_MATCH_LEN
|
||||||
|
l2 = pos - p2
|
||||||
|
if mpos == None and l2 >= 0:
|
||||||
|
mpos = p
|
||||||
|
mlen = 0
|
||||||
|
if l2 <= 0:
|
||||||
|
p = next[p % HIST_LEN]
|
||||||
|
continue
|
||||||
|
if l2 > maxmlen:
|
||||||
|
l2 = maxmlen
|
||||||
|
m = mlen + 1
|
||||||
|
if src.startswith(src[p2 : p2 + m],
|
||||||
|
pos2):
|
||||||
|
mpos = p
|
||||||
|
for j in range(m, l2):
|
||||||
|
if (src[pos2 + j]
|
||||||
|
!= src[p2 + j]):
|
||||||
|
mlen = j
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
mlen = l2
|
||||||
|
#rlen = _match(src, pos2, p2, mlen, l2)
|
||||||
|
#if rlen != None:
|
||||||
|
# mpos = p
|
||||||
|
# mlen = rlen
|
||||||
|
p = next[p % HIST_LEN]
|
||||||
|
|
||||||
|
if mpos != None:
|
||||||
|
mlen += MIN_MATCH_LEN
|
||||||
|
|
||||||
|
count += 1
|
||||||
|
new_chars = int(log(count, 2))
|
||||||
|
# new_chars = 50
|
||||||
|
new_chars = min(new_chars, max_match - MIN_MATCH_LEN)
|
||||||
|
if new_chars > chars:
|
||||||
|
chars = new_chars
|
||||||
|
table2 = _rehash_table2(src, chars, head,
|
||||||
|
next, next2,
|
||||||
|
hist_invalid)
|
||||||
|
|
||||||
|
next[modpos] = head
|
||||||
|
head = pos
|
||||||
|
|
||||||
|
key2 = src[pos2 : pos2 + chars]
|
||||||
|
head2 = table2.get(key2, hist_invalid)
|
||||||
|
next2[modpos] = head2
|
||||||
|
table2[key2] = pos
|
||||||
|
|
||||||
|
a[0] = count
|
||||||
|
a[1] = head
|
||||||
|
a[2] = table2
|
||||||
|
a[3] = chars
|
||||||
|
else:
|
||||||
|
self.next_table[modpos] = hist_invalid
|
||||||
|
self.next2_table[modpos] = hist_invalid
|
||||||
|
key2 = ""
|
||||||
|
# key2 = src[pos2 : pos2 + 1]
|
||||||
|
suffix_table[key] = [1, pos, {key2: pos}, len(key2)]
|
||||||
|
|
||||||
|
p = pos - HIST_LEN
|
||||||
|
if p >= 0:
|
||||||
|
p2 = p + MIN_MATCH_LEN
|
||||||
|
key = src[p : p2]
|
||||||
|
a = suffix_table[key]
|
||||||
|
(count, head, table2, chars) = a
|
||||||
|
count -= 1
|
||||||
|
if count == 0:
|
||||||
|
assert head == p
|
||||||
|
del suffix_table[key]
|
||||||
|
else:
|
||||||
|
key2 = src[p2 : p2 + chars]
|
||||||
|
if table2[key2] == p:
|
||||||
|
del table2[key2]
|
||||||
|
a[0] = count
|
||||||
|
assert (mpos == None
|
||||||
|
or src[pos : pos + mlen] == src[mpos : mpos + mlen])
|
||||||
|
return (mpos, mlen)
|
||||||
|
|
||||||
|
def _add_suffix(self, pos, find):
|
||||||
|
r = self.add_suffix_2(pos, find)
|
||||||
|
start_pos = self.start_pos
|
||||||
|
if find and r[0] != None:
|
||||||
|
print ("%4d %02x %4d %2d"
|
||||||
|
% (pos - start_pos, ord(self.src[pos]),
|
||||||
|
r[0] - start_pos, r[1]))
|
||||||
|
else:
|
||||||
|
print ("%4d %02x"
|
||||||
|
% (pos - start_pos, ord(self.src[pos])))
|
||||||
|
return r
|
||||||
|
|
||||||
|
add_suffix = add_suffix_2
|
||||||
|
|
||||||
|
def output_bit(self, bit):
|
||||||
|
self.append_bit(bit)
|
||||||
|
bit ^= 1
|
||||||
|
for i in range(self.shifts):
|
||||||
|
self.append_bit(bit)
|
||||||
|
self.shifts = 0
|
||||||
|
|
||||||
|
def encode_char(self, char):
|
||||||
|
low = self.low
|
||||||
|
high = self.high
|
||||||
|
sym_cum = self.sym_cum
|
||||||
|
|
||||||
|
symbol = self.char_to_symbol[char]
|
||||||
|
range = high - low
|
||||||
|
|
||||||
|
high = low + range * sym_cum[symbol - 1] / sym_cum[0]
|
||||||
|
low += range * sym_cum[symbol] / sym_cum[0]
|
||||||
|
debug(high, "high");
|
||||||
|
debug(low, "low");
|
||||||
|
while True:
|
||||||
|
if high <= QUADRANT2:
|
||||||
|
self.output_bit(0)
|
||||||
|
elif low >= QUADRANT2:
|
||||||
|
self.output_bit(1)
|
||||||
|
low -= QUADRANT2
|
||||||
|
high -= QUADRANT2
|
||||||
|
elif low >= QUADRANT1 and high <= QUADRANT3:
|
||||||
|
self.shifts += 1
|
||||||
|
low -= QUADRANT1
|
||||||
|
high -= QUADRANT1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
low *= 2
|
||||||
|
high *= 2
|
||||||
|
self.low = low
|
||||||
|
self.high = high
|
||||||
|
self.update_model_encode(symbol)
|
||||||
|
|
||||||
|
def encode_position(self, position):
|
||||||
|
position_cum = self.position_cum
|
||||||
|
low = self.low
|
||||||
|
high = self.high
|
||||||
|
|
||||||
|
range = high - low
|
||||||
|
high = low + range * position_cum[position] / position_cum[0]
|
||||||
|
low += range * position_cum[position + 1] / position_cum[0]
|
||||||
|
|
||||||
|
debug(high, "high");
|
||||||
|
debug(low, "low");
|
||||||
|
while True:
|
||||||
|
if high <= QUADRANT2:
|
||||||
|
self.output_bit(0)
|
||||||
|
elif low >= QUADRANT2:
|
||||||
|
self.output_bit(1)
|
||||||
|
low -= QUADRANT2
|
||||||
|
high -= QUADRANT2
|
||||||
|
elif low >= QUADRANT1 and high <= QUADRANT3:
|
||||||
|
self.shifts += 1
|
||||||
|
low -= QUADRANT1
|
||||||
|
high -= QUADRANT1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
low *= 2
|
||||||
|
high *= 2
|
||||||
|
|
||||||
|
self.low = low
|
||||||
|
self.high = high
|
||||||
|
|
||||||
|
def encode(self, src, progress = None):
|
||||||
|
"""Compress a string."""
|
||||||
|
|
||||||
|
length = len(src)
|
||||||
|
if length == 0:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
out_array = array.array('B')
|
||||||
|
self.out_array = out_array
|
||||||
|
self.append_bit = out_array.append
|
||||||
|
|
||||||
|
self.init(False)
|
||||||
|
|
||||||
|
max_match = min(MAX_MATCH_LEN, length)
|
||||||
|
self.max_match = max_match
|
||||||
|
self.src = src = "\x20" * max_match + src
|
||||||
|
|
||||||
|
in_length = len(src)
|
||||||
|
|
||||||
|
self.start_pos = max_match
|
||||||
|
|
||||||
|
for in_pos in range(max_match):
|
||||||
|
self.add_suffix(in_pos, False)
|
||||||
|
in_pos += 1
|
||||||
|
last_percent = -1
|
||||||
|
while in_pos < in_length:
|
||||||
|
if progress:
|
||||||
|
percent = (in_pos - max_match) * 100 / length
|
||||||
|
if percent != last_percent:
|
||||||
|
sys.stderr.write("%s%3d%%\r"
|
||||||
|
% (progress, percent))
|
||||||
|
last_percent = percent
|
||||||
|
debug(ord(src[in_pos]), "src")
|
||||||
|
(match_pos, match_len) = self.add_suffix(in_pos, True)
|
||||||
|
if match_len < MIN_MATCH_LEN:
|
||||||
|
self.encode_char(ord(src[in_pos]))
|
||||||
|
else:
|
||||||
|
debug(in_pos - match_pos - 1, "match_pos")
|
||||||
|
debug(match_len, "match_len")
|
||||||
|
self.encode_char(256 - MIN_MATCH_LEN
|
||||||
|
+ match_len)
|
||||||
|
self.encode_position(in_pos - match_pos - 1)
|
||||||
|
for i in range(match_len - 1):
|
||||||
|
in_pos += 1
|
||||||
|
self.add_suffix(in_pos, False)
|
||||||
|
in_pos += 1
|
||||||
|
|
||||||
|
self.shifts += 1
|
||||||
|
if self.low < QUADRANT1:
|
||||||
|
self.output_bit(0)
|
||||||
|
else:
|
||||||
|
self.output_bit(1)
|
||||||
|
|
||||||
|
#for k, v in sorted(self.suffix_table.items()):
|
||||||
|
# count, head, table2, chars = v
|
||||||
|
# print hexlify(k), count, head, len(table2), chars
|
||||||
|
|
||||||
|
if progress:
|
||||||
|
sys.stderr.write("%s100%%\n" % progress)
|
||||||
|
|
||||||
|
return bit_array_to_string(out_array)
|
||||||
|
|
||||||
|
def decode(self, src, out_length, progress = None):
|
||||||
|
"""Decompress a string."""
|
||||||
|
|
||||||
|
a = string_to_bit_array(src)
|
||||||
|
a.fromlist([0] * 32) # add some extra bits
|
||||||
|
self.in_iter = iter(a).next
|
||||||
|
|
||||||
|
out = array.array('B', "\0") * out_length
|
||||||
|
outpos = 0
|
||||||
|
|
||||||
|
self.init(True)
|
||||||
|
|
||||||
|
self.code = 0
|
||||||
|
for i in range(ARITH_BITS + 2):
|
||||||
|
self.code += self.code + self.in_iter()
|
||||||
|
|
||||||
|
hist_pos = HIST_LEN - MAX_MATCH_LEN
|
||||||
|
history = [0x20] * hist_pos + [0] * MAX_MATCH_LEN
|
||||||
|
|
||||||
|
decode_char = self.decode_char
|
||||||
|
last_percent = -1
|
||||||
|
last_time = time.time()
|
||||||
|
while outpos < out_length:
|
||||||
|
if progress:
|
||||||
|
percent = outpos * 100 / out_length
|
||||||
|
if percent != last_percent:
|
||||||
|
now = time.time()
|
||||||
|
if now - last_time >= 1:
|
||||||
|
sys.stderr.write("%s%3d%%\r"
|
||||||
|
% (progress, percent))
|
||||||
|
last_percent = percent
|
||||||
|
last_time = now
|
||||||
|
char = decode_char()
|
||||||
|
if char >= 0x100:
|
||||||
|
pos = self.decode_position()
|
||||||
|
length = char - 0x100 + MIN_MATCH_LEN
|
||||||
|
base = (hist_pos - pos - 1) % HIST_LEN
|
||||||
|
for off in range(length):
|
||||||
|
a = history[(base + off) % HIST_LEN]
|
||||||
|
out[outpos] = a
|
||||||
|
outpos += 1
|
||||||
|
history[hist_pos] = a
|
||||||
|
hist_pos = (hist_pos + 1) % HIST_LEN
|
||||||
|
else:
|
||||||
|
out[outpos] = char
|
||||||
|
outpos += 1
|
||||||
|
history[hist_pos] = char
|
||||||
|
hist_pos = (hist_pos + 1) % HIST_LEN
|
||||||
|
|
||||||
|
self.in_iter = None
|
||||||
|
if progress:
|
||||||
|
sys.stderr.write("%s100%%\n" % progress)
|
||||||
|
return out.tostring()
|
||||||
|
|
||||||
|
if mymcsup == None:
|
||||||
|
def decode(src, out_length, progress = None):
|
||||||
|
return lzari_codec().decode(src, out_length, progress)
|
||||||
|
|
||||||
|
def encode(src, progress = None):
|
||||||
|
return lzari_codec().encode(src, progress)
|
||||||
|
else:
|
||||||
|
mylzari_decode = mymcsup.mylzari_decode
|
||||||
|
mylzari_encode = mymcsup.mylzari_encode
|
||||||
|
mylzari_free_encoded = mymcsup.mylzari_free_encoded
|
||||||
|
|
||||||
|
def decode(src, out_length, progress = None):
|
||||||
|
out = ctypes.create_string_buffer(out_length)
|
||||||
|
if (mylzari_decode(src, len(src), out, out_length, progress)
|
||||||
|
== -1):
|
||||||
|
raise ValueError, "compressed input is corrupt"
|
||||||
|
return ctypes.string_at(out, out_length)
|
||||||
|
|
||||||
|
def encode(src, progress = None):
|
||||||
|
(r, compressed, comp_len) = mylzari_encode(src, len(src),
|
||||||
|
progress)
|
||||||
|
# print r, compressed.value, comp_len
|
||||||
|
if r == -1:
|
||||||
|
raise MemoryError, "out of memory during compression"
|
||||||
|
ret = ctypes.string_at(compressed.value, comp_len.value)
|
||||||
|
mylzari_free_encoded(compressed)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
def main2(args):
|
||||||
|
import struct
|
||||||
|
import os
|
||||||
|
|
||||||
|
src = file(args[2], "rb").read()
|
||||||
|
lzari = lzari_codec()
|
||||||
|
out = file(args[3], "wb")
|
||||||
|
start = os.times()
|
||||||
|
if args[1] == "c":
|
||||||
|
dest = lzari.encode(src)
|
||||||
|
now = os.times()
|
||||||
|
out.write(struct.pack("L", len(src)))
|
||||||
|
else:
|
||||||
|
dest = lzari.decode(src[4:],
|
||||||
|
struct.unpack("L", src[:4])[0])
|
||||||
|
now = os.times()
|
||||||
|
out.write(dest)
|
||||||
|
out.close()
|
||||||
|
print "time:", now[0] - start[0], now[1] - start[1], now[4] - start[4]
|
||||||
|
|
||||||
|
|
||||||
|
def _get_hotshot_lineinfo(filename):
|
||||||
|
import hotshot.log
|
||||||
|
log = hotshot.log.LogReader(filename)
|
||||||
|
timings = {}
|
||||||
|
for what, loc, tdelta in log:
|
||||||
|
if what == hotshot.log.LINE:
|
||||||
|
a = timings.get(loc)
|
||||||
|
if a == None:
|
||||||
|
timings[loc] = [1, tdelta]
|
||||||
|
else:
|
||||||
|
a[0] += 1
|
||||||
|
a[1] += tdelta
|
||||||
|
return timings.items()
|
||||||
|
|
||||||
|
def _dump_hotshot_lineinfo(log):
|
||||||
|
a = sorted(_get_hotshot_lineinfo(log))
|
||||||
|
total_count = sum((time[0]
|
||||||
|
for (loc, time) in a))
|
||||||
|
total_time = sum((time[1]
|
||||||
|
for (loc, time) in a))
|
||||||
|
for (loc, [count, time]) in a:
|
||||||
|
print ("%8d %6.3f%% %8d %6.3f%%"
|
||||||
|
% (time, time * 100.0 / total_time,
|
||||||
|
count, count * 100.0 / total_count)),
|
||||||
|
print "%s:%d(%s)" % loc
|
||||||
|
|
||||||
|
def _dump_hotshot_lineinfo2(log):
|
||||||
|
cur = None
|
||||||
|
a = sorted(_get_hotshot_lineinfo(log))
|
||||||
|
total_count = sum((time[0]
|
||||||
|
for (loc, time) in a))
|
||||||
|
total_time = sum((time[1]
|
||||||
|
for (loc, time) in a))
|
||||||
|
for ((filename, lineno, fn), [count, time]) in a:
|
||||||
|
if cur != filename:
|
||||||
|
if cur != None and f != None:
|
||||||
|
for line in f:
|
||||||
|
print line[:-1]
|
||||||
|
f.close()
|
||||||
|
try:
|
||||||
|
f = file(filename, "r")
|
||||||
|
except OSError:
|
||||||
|
f = None
|
||||||
|
cur = filename
|
||||||
|
l = 0
|
||||||
|
print "#", filename
|
||||||
|
if f != None:
|
||||||
|
while l < lineno:
|
||||||
|
print f.readline()[:-1]
|
||||||
|
l += 1
|
||||||
|
print ("# %8d %6.3f%% %8d %6.3f%%"
|
||||||
|
% (time, time * 100.0 / total_time,
|
||||||
|
count, count * 100.0 / total_count))
|
||||||
|
if cur != None and f != None:
|
||||||
|
for line in f:
|
||||||
|
print line[:-1]
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
def main(args):
|
||||||
|
import os
|
||||||
|
|
||||||
|
if args[1] == "pc":
|
||||||
|
import profile
|
||||||
|
pr = profile.Profile()
|
||||||
|
for i in range(5):
|
||||||
|
print pr.calibrate(100000)
|
||||||
|
return
|
||||||
|
elif args[1] == "p":
|
||||||
|
import profile
|
||||||
|
ret = 0
|
||||||
|
# profile.Profile.bias = 5.26e-6
|
||||||
|
profile.runctx("ret = main2(args[1:])",
|
||||||
|
globals(), locals())
|
||||||
|
return ret
|
||||||
|
elif args[1].startswith("h"):
|
||||||
|
import hotshot, hotshot.stats
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
warnings.filterwarnings("ignore")
|
||||||
|
tmp = os.tempnam()
|
||||||
|
try:
|
||||||
|
l = args[1].startswith("hl")
|
||||||
|
p = hotshot.Profile(tmp, l)
|
||||||
|
ret = p.runcall(main2, args[1:])
|
||||||
|
p.close()
|
||||||
|
p = None
|
||||||
|
if l:
|
||||||
|
if args[1] == "hl2":
|
||||||
|
_dump_hotshot_lineinfo2(tmp)
|
||||||
|
else:
|
||||||
|
_dump_hotshot_lineinfo(tmp)
|
||||||
|
else:
|
||||||
|
hotshot.stats.load(tmp).print_stats()
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
os.remove(tmp)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
return ret
|
||||||
|
|
||||||
|
return main2(args)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main(sys.argv))
|
||||||
|
|
810
mymc.py
Normal file
810
mymc.py
Normal file
@ -0,0 +1,810 @@
|
|||||||
|
#
|
||||||
|
# mymc.py
|
||||||
|
#
|
||||||
|
# By Ross Ridge
|
||||||
|
# Public Domain
|
||||||
|
#
|
||||||
|
|
||||||
|
"""A utility for manipulating PS2 memory card images."""
|
||||||
|
|
||||||
|
_SCCS_ID = "@(#) mysc mymc.py 1.11 08/08/11 14:12:26\n"[:-1]
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import optparse
|
||||||
|
import textwrap
|
||||||
|
import binascii
|
||||||
|
import string
|
||||||
|
from errno import EEXIST, EIO
|
||||||
|
|
||||||
|
import ps2mc
|
||||||
|
import ps2save
|
||||||
|
from ps2mc_dir import *
|
||||||
|
from round import *
|
||||||
|
import verbuild
|
||||||
|
|
||||||
|
class subopt_error(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
io_error = ps2mc.io_error
|
||||||
|
|
||||||
|
if os.name == "nt":
|
||||||
|
import codecs
|
||||||
|
|
||||||
|
class file_wrap(object):
|
||||||
|
""" wrap a file-like object with a new encoding attribute. """
|
||||||
|
|
||||||
|
def __init__(self, f, encoding):
|
||||||
|
object.__setattr__(self, "_f", f)
|
||||||
|
object.__setattr__(self, "encoding", encoding)
|
||||||
|
|
||||||
|
def __getattribute__(self, name):
|
||||||
|
if name == "encoding":
|
||||||
|
return object.__getattribute__(self, name)
|
||||||
|
return getattr(object.__getattribute__(self, "_f"),
|
||||||
|
name)
|
||||||
|
|
||||||
|
def __setattr__(self, name, value):
|
||||||
|
if name == "encoding":
|
||||||
|
raise TypeError, "readonly attribute"
|
||||||
|
return setattr(object.__getattribute__(self, "_f"),
|
||||||
|
name, value)
|
||||||
|
|
||||||
|
for name in ["stdin", "stdout", "stderr"]:
|
||||||
|
f = getattr(sys, name)
|
||||||
|
cur = getattr(f, "encoding", None)
|
||||||
|
if cur == "ascii" or cur == None:
|
||||||
|
f = file_wrap(f, "mbcs")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
codecs.lookup(cur)
|
||||||
|
except LookupError:
|
||||||
|
f = file_wrap(f, "mbcs")
|
||||||
|
setattr(sys, name, f)
|
||||||
|
|
||||||
|
|
||||||
|
if os.name in ["nt", "os2", "ce"]:
|
||||||
|
from glob import glob
|
||||||
|
else:
|
||||||
|
# assume globing is done by the shell
|
||||||
|
glob = lambda pattern: [pattern]
|
||||||
|
|
||||||
|
|
||||||
|
def glob_args(args, globfn):
|
||||||
|
ret = []
|
||||||
|
for arg in args:
|
||||||
|
match = globfn(arg)
|
||||||
|
if len(match) == 0:
|
||||||
|
ret.append(arg)
|
||||||
|
else:
|
||||||
|
ret += match
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def _copy(fout, fin):
|
||||||
|
"""copy the contents of one file to another"""
|
||||||
|
|
||||||
|
while True:
|
||||||
|
s = fin.read(1024)
|
||||||
|
if s == "":
|
||||||
|
break
|
||||||
|
fout.write(s)
|
||||||
|
|
||||||
|
|
||||||
|
def do_ls(cmd, mc, opts, args, opterr):
|
||||||
|
mode_bits = "rwxpfdD81C+KPH4"
|
||||||
|
|
||||||
|
if len(args) == 0:
|
||||||
|
args = ["/"]
|
||||||
|
|
||||||
|
out = sys.stdout
|
||||||
|
args = glob_args(args, mc.glob)
|
||||||
|
for dirname in args:
|
||||||
|
dir = mc.dir_open(dirname)
|
||||||
|
try:
|
||||||
|
if len(args) > 1:
|
||||||
|
sys.stdout.write("\n" + dirname + ":\n")
|
||||||
|
for ent in dir:
|
||||||
|
mode = ent[0]
|
||||||
|
if (mode & DF_EXISTS) == 0:
|
||||||
|
continue
|
||||||
|
for bit in range(0, 15):
|
||||||
|
if mode & (1 << bit):
|
||||||
|
out.write(mode_bits[bit])
|
||||||
|
else:
|
||||||
|
out.write("-")
|
||||||
|
if opts.creation_time:
|
||||||
|
tod = ent[3]
|
||||||
|
else:
|
||||||
|
tod = ent[6]
|
||||||
|
tm = time.localtime(tod_to_time(tod))
|
||||||
|
out.write(" %7d %04d-%02d-%02d"
|
||||||
|
" %02d:%02d:%02d %s\n"
|
||||||
|
% (ent[2],
|
||||||
|
tm.tm_year, tm.tm_mon, tm.tm_mday,
|
||||||
|
tm.tm_hour, tm.tm_min, tm.tm_sec,
|
||||||
|
ent[8]))
|
||||||
|
finally:
|
||||||
|
dir.close()
|
||||||
|
|
||||||
|
|
||||||
|
def do_add(cmd, mc, opts, args, opterr):
|
||||||
|
if len(args) < 1:
|
||||||
|
opterr("Filename required.")
|
||||||
|
if opts.directory != None:
|
||||||
|
mc.chdir(opts.directory)
|
||||||
|
for src in glob_args(args, glob):
|
||||||
|
f = open(src, "rb")
|
||||||
|
dest = os.path.basename(src)
|
||||||
|
out = mc.open(dest, "wb")
|
||||||
|
_copy(out, f)
|
||||||
|
out.close()
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
def do_extract(cmd, mc, opts, args, opterr):
|
||||||
|
if len(args) < 1:
|
||||||
|
opterr("Filename required.")
|
||||||
|
|
||||||
|
if opts.directory != None:
|
||||||
|
mc.chdir(opts.directory)
|
||||||
|
|
||||||
|
close_out = False
|
||||||
|
out = None
|
||||||
|
if opts.output != None:
|
||||||
|
if opts.use_stdout:
|
||||||
|
opterr("The -o and -p options are mutually exclusive.")
|
||||||
|
dont_close_out = True
|
||||||
|
out = file(opts.output, "wb")
|
||||||
|
elif opts.use_stdout:
|
||||||
|
out = sys.stdout
|
||||||
|
|
||||||
|
try:
|
||||||
|
for filename in glob_args(args, mc.glob):
|
||||||
|
f = mc.open(filename, "rb")
|
||||||
|
try:
|
||||||
|
if out != None:
|
||||||
|
_copy(out, f)
|
||||||
|
continue
|
||||||
|
a = filename.split("/")
|
||||||
|
o = file(a[-1], "wb")
|
||||||
|
try:
|
||||||
|
_copy(o, f)
|
||||||
|
finally:
|
||||||
|
o.close()
|
||||||
|
finally:
|
||||||
|
f.close()
|
||||||
|
finally:
|
||||||
|
if close_out:
|
||||||
|
out.close()
|
||||||
|
|
||||||
|
def do_mkdir(cmd, mc, opts, args, opterr):
|
||||||
|
if len(args) < 1:
|
||||||
|
opterr("Directory required.")
|
||||||
|
|
||||||
|
for filename in args:
|
||||||
|
mc.mkdir(filename)
|
||||||
|
|
||||||
|
def do_remove(cmd, mc, opts, args, opterr):
|
||||||
|
if len(args) < 1:
|
||||||
|
opterr("Filename required.")
|
||||||
|
|
||||||
|
for filename in args:
|
||||||
|
mc.remove(filename)
|
||||||
|
|
||||||
|
def do_import(cmd, mc, opts, args, opterr):
|
||||||
|
if len(args) < 1:
|
||||||
|
opterr("Filename required.")
|
||||||
|
|
||||||
|
args = glob_args(args, glob)
|
||||||
|
if opts.directory != None and len(args) > 1:
|
||||||
|
opterr("The -d option can only be used with a"
|
||||||
|
"single savefile.")
|
||||||
|
|
||||||
|
for filename in args:
|
||||||
|
sf = ps2save.ps2_save_file()
|
||||||
|
f = file(filename, "rb")
|
||||||
|
try:
|
||||||
|
ftype = ps2save.detect_file_type(f)
|
||||||
|
f.seek(0)
|
||||||
|
if ftype == "max":
|
||||||
|
sf.load_max_drive(f)
|
||||||
|
elif ftype == "psu":
|
||||||
|
sf.load_ems(f)
|
||||||
|
elif ftype == "cbs":
|
||||||
|
sf.load_codebreaker(f)
|
||||||
|
elif ftype == "sps":
|
||||||
|
sf.load_sharkport(f)
|
||||||
|
elif ftype == "npo":
|
||||||
|
raise io_error, (EIO, "nPort saves"
|
||||||
|
" are not supported.",
|
||||||
|
filename)
|
||||||
|
else:
|
||||||
|
raise io_error, (EIO, "Save file format not"
|
||||||
|
" recognized", filename)
|
||||||
|
finally:
|
||||||
|
f.close()
|
||||||
|
dirname = opts.directory
|
||||||
|
if dirname == None:
|
||||||
|
dirname = sf.get_directory()[8]
|
||||||
|
print "Importing", filename, "to", dirname
|
||||||
|
if not mc.import_save_file(sf, opts.ignore_existing,
|
||||||
|
opts.directory):
|
||||||
|
print (filename + ": already in memory card image,"
|
||||||
|
" ignored.")
|
||||||
|
|
||||||
|
#re_num = re.compile("[0-9]+")
|
||||||
|
|
||||||
|
def do_export(cmd, mc, opts, args, opterr):
|
||||||
|
if len(args) < 1:
|
||||||
|
opterr("Directory name required")
|
||||||
|
|
||||||
|
if opts.overwrite_existing and opts.ignore_existing:
|
||||||
|
opterr("The -i and -f options are mutually exclusive.")
|
||||||
|
|
||||||
|
args = glob_args(args, mc.glob)
|
||||||
|
if opts.output_file != None:
|
||||||
|
if len(args) > 1:
|
||||||
|
opterr("Only one directory can be exported"
|
||||||
|
" when the -o option is used.")
|
||||||
|
if opts.longnames:
|
||||||
|
opterr("The -o and -l options are mutually exclusive.")
|
||||||
|
|
||||||
|
if opts.directory != None:
|
||||||
|
os.chdir(opts.directory)
|
||||||
|
|
||||||
|
for dirname in args:
|
||||||
|
sf = mc.export_save_file(dirname)
|
||||||
|
filename = opts.output_file
|
||||||
|
if opts.longnames:
|
||||||
|
filename = (ps2save.make_longname(dirname, sf)
|
||||||
|
+ "." + opts.type)
|
||||||
|
if filename == None:
|
||||||
|
filename = dirname + "." + opts.type
|
||||||
|
|
||||||
|
if not opts.overwrite_existing:
|
||||||
|
exists = True
|
||||||
|
try:
|
||||||
|
open(filename, "rb").close()
|
||||||
|
except EnvironmentError:
|
||||||
|
exists = False
|
||||||
|
if exists:
|
||||||
|
if opts.ignore_existing:
|
||||||
|
continue
|
||||||
|
raise io_error(EEXIST, "File exists", filename)
|
||||||
|
|
||||||
|
f = file(filename, "wb")
|
||||||
|
try:
|
||||||
|
print "Exporing", dirname, "to", filename
|
||||||
|
|
||||||
|
if opts.type == "max":
|
||||||
|
sf.save_max_drive(f)
|
||||||
|
else:
|
||||||
|
sf.save_ems(f)
|
||||||
|
finally:
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
def do_delete(cmd, mc, opts, args, opterr):
|
||||||
|
if len(args) < 1:
|
||||||
|
opterr("Directory required.")
|
||||||
|
|
||||||
|
for dirname in args:
|
||||||
|
mc.rmdir(dirname)
|
||||||
|
|
||||||
|
def do_setmode(cmd, mc, opts, args, opterr):
|
||||||
|
set_mask = 0
|
||||||
|
clear_mask = ~0
|
||||||
|
for (opt, bit) in [(opts.read, DF_READ),
|
||||||
|
(opts.write, DF_WRITE),
|
||||||
|
(opts.execute, DF_EXECUTE),
|
||||||
|
(opts.protected, DF_PROTECTED),
|
||||||
|
(opts.psx, DF_PSX),
|
||||||
|
(opts.pocketstation, DF_POCKETSTN),
|
||||||
|
(opts.hidden, DF_HIDDEN)]:
|
||||||
|
if opt != None:
|
||||||
|
if opt:
|
||||||
|
set_mask |= bit
|
||||||
|
else:
|
||||||
|
clear_mask ^= bit
|
||||||
|
|
||||||
|
value = opts.hex_value
|
||||||
|
if set_mask == 0 and clear_mask == ~0:
|
||||||
|
if value == None:
|
||||||
|
opterr("At least one option must be given.")
|
||||||
|
if value.startswith("0x") or value.startswith("0X"):
|
||||||
|
value = int(value[2:], 16)
|
||||||
|
else:
|
||||||
|
value = int(value, 16)
|
||||||
|
else:
|
||||||
|
if value != None:
|
||||||
|
opterr("The -X option can't be combined with"
|
||||||
|
" other options.")
|
||||||
|
|
||||||
|
for arg in glob_args(args, mc.glob):
|
||||||
|
ent = mc.get_dirent(arg)
|
||||||
|
if value == None:
|
||||||
|
ent[0] = (ent[0] & clear_mask) | set_mask
|
||||||
|
print "new %04x" % ent[0]
|
||||||
|
else:
|
||||||
|
ent[0] = value
|
||||||
|
mc.set_dirent(arg, ent)
|
||||||
|
|
||||||
|
def _get_ps2_title(mc, enc):
|
||||||
|
s = mc.get_icon_sys(".");
|
||||||
|
if s == None:
|
||||||
|
return None
|
||||||
|
a = ps2save.unpack_icon_sys(s)
|
||||||
|
return ps2save.icon_sys_title(a, enc)
|
||||||
|
|
||||||
|
def _get_psx_title(mc, savename, enc):
|
||||||
|
mode = mc.get_mode(savename)
|
||||||
|
if mode == None or not mode_is_file(mode):
|
||||||
|
return None
|
||||||
|
f = mc.open(savename)
|
||||||
|
s = f.read(128)
|
||||||
|
if len(s) != 128:
|
||||||
|
return None
|
||||||
|
(magic, icon, blocks, title) = struct.unpack("<2sBB64s28x32x", s)
|
||||||
|
if magic != "SC":
|
||||||
|
return None
|
||||||
|
return [ps2save.shift_jis_conv(zero_terminate(title), enc), ""]
|
||||||
|
|
||||||
|
def do_dir(cmd, mc, opts, args, opterr):
|
||||||
|
if len(args) != 0:
|
||||||
|
opterr("Incorrect number of arguments.")
|
||||||
|
f = None
|
||||||
|
dir = mc.dir_open("/")
|
||||||
|
try:
|
||||||
|
for ent in list(dir)[2:]:
|
||||||
|
dirmode = ent[0]
|
||||||
|
if not mode_is_dir(dirmode):
|
||||||
|
continue
|
||||||
|
dirname = "/" + ent[8]
|
||||||
|
mc.chdir(dirname)
|
||||||
|
length = mc.dir_size(".");
|
||||||
|
enc = getattr(sys.stdout, "encoding", None)
|
||||||
|
if dirmode & DF_PSX:
|
||||||
|
title = _get_psx_title(mc, ent[8], enc)
|
||||||
|
else:
|
||||||
|
title = _get_ps2_title(mc, enc)
|
||||||
|
if title == None:
|
||||||
|
title = ["Corrupt", ""]
|
||||||
|
protection = dirmode & (DF_PROTECTED | DF_WRITE)
|
||||||
|
if protection == 0:
|
||||||
|
protection = "Delete Protected"
|
||||||
|
elif protection == DF_WRITE:
|
||||||
|
protection = "Not Protected"
|
||||||
|
elif protection == DF_PROTECTED:
|
||||||
|
protection = "Copy & Delete Protected"
|
||||||
|
else:
|
||||||
|
protection = "Copy Protected"
|
||||||
|
|
||||||
|
type = None
|
||||||
|
if dirmode & DF_PSX:
|
||||||
|
type = "PlayStation"
|
||||||
|
if dirmode & DF_POCKETSTN:
|
||||||
|
type = "PocketStation"
|
||||||
|
if type != None:
|
||||||
|
protection = type
|
||||||
|
|
||||||
|
print "%-32s %s" % (ent[8], title[0])
|
||||||
|
print ("%4dKB %-25s %s"
|
||||||
|
% (length / 1024, protection, title[1]))
|
||||||
|
print
|
||||||
|
finally:
|
||||||
|
if f != None:
|
||||||
|
f.close()
|
||||||
|
dir.close()
|
||||||
|
|
||||||
|
free = mc.get_free_space() / 1024
|
||||||
|
if free > 999999:
|
||||||
|
free = "%d,%03d,%03d" % (free / 1000000, free / 1000 % 1000,
|
||||||
|
free % 1000)
|
||||||
|
elif free > 999:
|
||||||
|
free = "%d,%03d" % (free / 1000, free % 1000)
|
||||||
|
else:
|
||||||
|
free = "%d" % free
|
||||||
|
|
||||||
|
print free + " KB Free"
|
||||||
|
|
||||||
|
def do_df(cmd, mc, opts, args, opterr):
|
||||||
|
if len(args) != 0:
|
||||||
|
opterr("Incorrect number of arguments.")
|
||||||
|
print mc.f.name + ":", mc.get_free_space(), "bytes free."
|
||||||
|
|
||||||
|
def do_check(cmd, mc, opts, args, opterr):
|
||||||
|
if len(args) != 0:
|
||||||
|
opterr("Incorrect number of arguments.")
|
||||||
|
if mc.check():
|
||||||
|
print "No errors found."
|
||||||
|
return 0
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def do_format(cmd, mcname, opts, args, opterr):
|
||||||
|
if len(args) != 0:
|
||||||
|
opterr("Incorrect number of arguments.")
|
||||||
|
pages_per_card = ps2mc.PS2MC_STANDARD_PAGES_PER_CARD
|
||||||
|
if opts.clusters != None:
|
||||||
|
pages_per_cluster = (ps2mc.PS2MC_CLUSTER_SIZE
|
||||||
|
/ ps2mc.PS2MC_STANDARD_PAGE_SIZE)
|
||||||
|
pages_per_card = opts.clusters * pages_per_cluster
|
||||||
|
params = (True,
|
||||||
|
ps2mc.PS2MC_STANDARD_PAGE_SIZE,
|
||||||
|
ps2mc.PS2MC_STANDARD_PAGES_PER_ERASE_BLOCK,
|
||||||
|
pages_per_card)
|
||||||
|
|
||||||
|
if not opts.overwrite_existing:
|
||||||
|
exists = True
|
||||||
|
try:
|
||||||
|
file(mcname, "rb").close()
|
||||||
|
except EnvironmentError:
|
||||||
|
exists = False
|
||||||
|
if exists:
|
||||||
|
raise io_error, (EEXIST, "file exists", mcname)
|
||||||
|
|
||||||
|
f = file(mcname, "w+b")
|
||||||
|
try:
|
||||||
|
ps2mc.ps2mc(f, True, params).close()
|
||||||
|
finally:
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
def do_gui(cmd, mcname, opts, args, opterr):
|
||||||
|
if len(args) != 0:
|
||||||
|
opterr("Incorrect number of arguments.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
import gui
|
||||||
|
except ImportError:
|
||||||
|
write_error(None, "GUI not available")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
gui.run(mcname)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def do_create_pad(cmd, mc, opts, args, opterr):
|
||||||
|
length = mc.clusters_per_card
|
||||||
|
if len(args) > 1:
|
||||||
|
length = int(args[1])
|
||||||
|
pad = "\0" * mc.cluster_size
|
||||||
|
f = mc.open(args[0], "wb")
|
||||||
|
try:
|
||||||
|
for i in xrange(length):
|
||||||
|
f.write(pad)
|
||||||
|
finally:
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
|
def do_frob(cmd, mc, opts, args, opterr):
|
||||||
|
mc.write_superblock()
|
||||||
|
|
||||||
|
_trans = string.maketrans("".join(map(chr, range(32))), " " * 32)
|
||||||
|
|
||||||
|
def _print_bin(base, s):
|
||||||
|
for off in range(0, len(s), 16):
|
||||||
|
print "%04X" % (base + off),
|
||||||
|
a = s[off : off + 16]
|
||||||
|
for b in a:
|
||||||
|
print "%02X" % ord(b),
|
||||||
|
print "", a.translate(_trans)
|
||||||
|
|
||||||
|
def _print_erase_block(mc, n):
|
||||||
|
ppb = mc.pages_per_erase_block
|
||||||
|
base = n * ppb
|
||||||
|
for i in range(ppb):
|
||||||
|
s = mc.read_page(base + i)
|
||||||
|
_print_bin(i * mc.page_size, s)
|
||||||
|
print
|
||||||
|
|
||||||
|
def do_print_good_blocks(cmd, mc, opts, args, opterr):
|
||||||
|
print "good_block2:"
|
||||||
|
_print_erase_block(mc, mc.good_block2)
|
||||||
|
print "good_block1:"
|
||||||
|
_print_erase_block(mc, mc.good_block1)
|
||||||
|
|
||||||
|
def do_ecc_check(cmd, mc, opts, args, opterr):
|
||||||
|
for i in range(mc.clusters_per_card * mc.pages_per_cluster):
|
||||||
|
try:
|
||||||
|
mc.read_page(i)
|
||||||
|
except ps2mc.ecc_error:
|
||||||
|
print "bad: %05x" % i
|
||||||
|
|
||||||
|
opt = optparse.make_option
|
||||||
|
|
||||||
|
#
|
||||||
|
# Each value in the dictionary is a tuple consisting of:
|
||||||
|
# - function implementing the command
|
||||||
|
# - mode to use to open the ps2 save file
|
||||||
|
# - help description of the command
|
||||||
|
# - list of options supported by the command
|
||||||
|
#
|
||||||
|
cmd_table = {
|
||||||
|
"ls": (do_ls, "rb",
|
||||||
|
"[directory ...]",
|
||||||
|
"List the contents of a directory.",
|
||||||
|
[opt("-c", "--creation-time", action="store_true",
|
||||||
|
help = "Display creation times.")]),
|
||||||
|
"extract": (do_extract, "rb",
|
||||||
|
"filename ...",
|
||||||
|
"Extract files from the memory card.",
|
||||||
|
[opt("-o", "--output", metavar = "FILE",
|
||||||
|
help = 'Extract file to "FILE".'),
|
||||||
|
opt("-d", "--directory",
|
||||||
|
help = 'Extract files from "DIRECTORY".'),
|
||||||
|
opt("-p", "--use-stdout", action="store_true",
|
||||||
|
help = "Extract files to standard output.")]),
|
||||||
|
"add": (do_add, "r+b",
|
||||||
|
"filename ...",
|
||||||
|
"Add files to the memory card.",
|
||||||
|
[opt("-d", "--directory",
|
||||||
|
help = 'Add files to "directory".')]),
|
||||||
|
"mkdir": (do_mkdir, "r+b",
|
||||||
|
"directory ...",
|
||||||
|
"Make directories.",
|
||||||
|
[]),
|
||||||
|
"remove": (do_remove, "r+b",
|
||||||
|
"filename ...",
|
||||||
|
"Remove files and directories.",
|
||||||
|
[]),
|
||||||
|
"import": (do_import, "r+b",
|
||||||
|
"savefile ...",
|
||||||
|
"Import save files into the memory card.",
|
||||||
|
[opt("-i", "--ignore-existing", action="store_true",
|
||||||
|
help = ("Ignore files that already exist"
|
||||||
|
"on the image.")),
|
||||||
|
opt("-d", "--directory", metavar="DEST",
|
||||||
|
help = 'Import to "DEST".')]),
|
||||||
|
"export": (do_export, "rb",
|
||||||
|
"directory ...",
|
||||||
|
"Export save files from the memory card.",
|
||||||
|
[opt("-f", "--overwrite-existing", action = "store_true",
|
||||||
|
help = "Overwrite any save files already exported."),
|
||||||
|
opt("-i", "--ignore-existing", action = "store_true",
|
||||||
|
help = "Ingore any save files already exported."),
|
||||||
|
opt("-o", "--output-file", metavar = "filename",
|
||||||
|
help = 'Use "filename" as the name of the save file.'),
|
||||||
|
opt("-d", "--directory", metavar = "directory",
|
||||||
|
help = 'Export save files to "directory".'),
|
||||||
|
opt("-l", "--longnames", action = "store_true",
|
||||||
|
help = ("Generate longer, more descriptive,"
|
||||||
|
" filenames.")),
|
||||||
|
opt("-p", "--ems", action = "store_const",
|
||||||
|
dest = "type", const = "psu", default = "psu",
|
||||||
|
help = "Use the EMS .psu save file format. [default]"),
|
||||||
|
opt("-m", "--max-drive", action = "store_const",
|
||||||
|
dest = "type", const = "max",
|
||||||
|
help = "Use the MAX Drive save file format.")]),
|
||||||
|
"delete": (do_delete, "r+b",
|
||||||
|
"dirname ...",
|
||||||
|
"Recursively delete a directory (save file).",
|
||||||
|
[]),
|
||||||
|
"set": (do_setmode, "r+b",
|
||||||
|
"filename ...",
|
||||||
|
"Set mode flags on files and directories",
|
||||||
|
[opt("-p", "--protected", action="store_true",
|
||||||
|
help = "Set copy protected flag"),
|
||||||
|
opt("-P", "--psx", action="store_true",
|
||||||
|
help = "Set PSX flag"),
|
||||||
|
opt("-K", "--pocketstation", action="store_true",
|
||||||
|
help = "Set PocketStation flag"),
|
||||||
|
opt("-H", "--hidden", action="store_true",
|
||||||
|
help = "Set hidden flag"),
|
||||||
|
opt("-r", "--read", action="store_true",
|
||||||
|
help = "Set read allowed flag"),
|
||||||
|
opt("-w", "--write", action="store_true",
|
||||||
|
help = "Set write allowed flag"),
|
||||||
|
opt("-x", "--execute", action="store_true",
|
||||||
|
help = "Set executable flag"),
|
||||||
|
opt("-X", "--hex-value", metavar="mode",
|
||||||
|
help = 'Set mode to "mode".')]),
|
||||||
|
"clear": (do_setmode, "r+b",
|
||||||
|
"filename ...",
|
||||||
|
"Clear mode flags on files and directories",
|
||||||
|
[opt("-p", "--protected", action="store_false",
|
||||||
|
help = "Clear copy protected flag"),
|
||||||
|
opt("-P", "--psx", action="store_false",
|
||||||
|
help = "Clear PSX flag"),
|
||||||
|
opt("-K", "--pocketstation", action="store_false",
|
||||||
|
help = "Clear PocketStation flag"),
|
||||||
|
opt("-H", "--hidden", action="store_false",
|
||||||
|
help = "Clear hidden flag"),
|
||||||
|
opt("-r", "--read", action="store_false",
|
||||||
|
help = "Clear read allowed flag"),
|
||||||
|
opt("-w", "--write", action="store_false",
|
||||||
|
help = "Clear write allowed flag"),
|
||||||
|
opt("-x", "--execute", action="store_false",
|
||||||
|
help = "Clear executable flag"),
|
||||||
|
opt("-X", dest="hex_value", default=None,
|
||||||
|
help = optparse.SUPPRESS_HELP)]),
|
||||||
|
"dir": (do_dir, "rb",
|
||||||
|
None,
|
||||||
|
"Display save file information.",
|
||||||
|
[]),
|
||||||
|
"df": (do_df, "rb",
|
||||||
|
None,
|
||||||
|
"Display the amount free space.",
|
||||||
|
[]),
|
||||||
|
"check": (do_check, "rb",
|
||||||
|
"",
|
||||||
|
"Check for file system errors.",
|
||||||
|
[]),
|
||||||
|
"format": (do_format, None,
|
||||||
|
"",
|
||||||
|
"Creates a new memory card image.",
|
||||||
|
[opt("-c", "--clusters", type="int",
|
||||||
|
help = "Size in clusters of the memory card."),
|
||||||
|
opt("-f", "--overwrite-existing", action="store_true",
|
||||||
|
help = "Overwrite any existing file")]),
|
||||||
|
"gui": (do_gui, None,
|
||||||
|
"",
|
||||||
|
"Starts the graphical user interface.",
|
||||||
|
[]),
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# secret commands for debugging purposes.
|
||||||
|
#
|
||||||
|
debug_cmd_table = {
|
||||||
|
"frob": (do_frob, "r+b",
|
||||||
|
"",
|
||||||
|
None,
|
||||||
|
[]),
|
||||||
|
"print_good_blocks": (do_print_good_blocks, "rb",
|
||||||
|
"",
|
||||||
|
None,
|
||||||
|
[]),
|
||||||
|
"ecc_check": (do_ecc_check, "rb",
|
||||||
|
"",
|
||||||
|
None,
|
||||||
|
[]),
|
||||||
|
"create_pad": (do_create_pad, "r+b",
|
||||||
|
"",
|
||||||
|
None,
|
||||||
|
[])
|
||||||
|
}
|
||||||
|
|
||||||
|
del opt # clean up name space
|
||||||
|
|
||||||
|
|
||||||
|
def write_error(filename, msg):
|
||||||
|
if filename == None:
|
||||||
|
sys.stderr.write(msg + "\n")
|
||||||
|
else:
|
||||||
|
sys.stderr.write(filename + ": " + msg + "\n")
|
||||||
|
|
||||||
|
class suboption_parser(optparse.OptionParser):
|
||||||
|
def exit(self, status = 0, msg = None):
|
||||||
|
if msg:
|
||||||
|
sys.stderr.write(msg)
|
||||||
|
raise subopt_error, status
|
||||||
|
|
||||||
|
class my_help_formatter(optparse.IndentedHelpFormatter):
|
||||||
|
"""A better formatter for optparser's help message"""
|
||||||
|
|
||||||
|
def format_description(self, description):
|
||||||
|
if not description:
|
||||||
|
return ""
|
||||||
|
desc_width = self.width - self.current_indent
|
||||||
|
indent = " " * self.current_indent
|
||||||
|
lines = []
|
||||||
|
for line in description.split('\n'):
|
||||||
|
ii = indent
|
||||||
|
si = indent
|
||||||
|
if line.startswith("\t"):
|
||||||
|
line = line[1:]
|
||||||
|
ii = indent + " " * 4
|
||||||
|
si = ii + " " * line.find(":") + 2
|
||||||
|
line = textwrap.fill(line, desc_width,
|
||||||
|
initial_indent = ii,
|
||||||
|
subsequent_indent = si)
|
||||||
|
lines.append(line)
|
||||||
|
return "\n".join(lines) + "\n"
|
||||||
|
|
||||||
|
def main():
|
||||||
|
prog = sys.argv[0].decode(sys.getdefaultencoding(), "replace")
|
||||||
|
usage = "usage: %prog [-ih] memcard.ps2 command [...]"
|
||||||
|
description = ("Manipulate PS2 memory card images.\n\n"
|
||||||
|
"Supported commands: ")
|
||||||
|
for cmd in sorted(cmd_table.keys()):
|
||||||
|
description += "\n " + cmd + ": " + cmd_table[cmd][3]
|
||||||
|
|
||||||
|
version = ("mymc "
|
||||||
|
+ verbuild.MYMC_VERSION_MAJOR
|
||||||
|
+ "." + verbuild.MYMC_VERSION_BUILD
|
||||||
|
+ " (" + _SCCS_ID + ")")
|
||||||
|
|
||||||
|
optparser = optparse.OptionParser(prog = prog, usage = usage,
|
||||||
|
description = description,
|
||||||
|
version = version,
|
||||||
|
formatter = my_help_formatter())
|
||||||
|
optparser.add_option("-D", dest = "debug", action = "store_true",
|
||||||
|
default = False, help = optparse.SUPPRESS_HELP)
|
||||||
|
optparser.add_option("-i", "--ignore-ecc", action = "store_true",
|
||||||
|
help = "Ignore ECC errors while reading.")
|
||||||
|
|
||||||
|
optparser.disable_interspersed_args()
|
||||||
|
(opts, args) = optparser.parse_args()
|
||||||
|
|
||||||
|
if len(args) == 0:
|
||||||
|
try:
|
||||||
|
import gui
|
||||||
|
except ImportError:
|
||||||
|
gui = None
|
||||||
|
if gui != None:
|
||||||
|
gui.run()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
if len(args) < 2:
|
||||||
|
optparser.error("Incorrect number of arguments.")
|
||||||
|
|
||||||
|
if opts.debug:
|
||||||
|
cmd_table.update(debug_cmd_table)
|
||||||
|
cmd = args[1]
|
||||||
|
if cmd not in cmd_table:
|
||||||
|
optparser.error('Command "%s" not recognized.' % cmd)
|
||||||
|
(fn, mode, usage_args, description, optlist) = cmd_table[cmd]
|
||||||
|
|
||||||
|
usage = "%prog"
|
||||||
|
if len(optlist) > 0:
|
||||||
|
usage += " [options]"
|
||||||
|
if usage_args != None:
|
||||||
|
usage += " " + usage_args
|
||||||
|
subprog = prog + " memcard.ps2 " + cmd
|
||||||
|
subopt_parser = suboption_parser(prog = subprog, usage = usage,
|
||||||
|
description = description,
|
||||||
|
option_list = optlist)
|
||||||
|
subopt_parser.disable_interspersed_args()
|
||||||
|
|
||||||
|
f = None
|
||||||
|
mc = None
|
||||||
|
ret = 0
|
||||||
|
mcname = args[0]
|
||||||
|
|
||||||
|
try:
|
||||||
|
(subopts, subargs) = subopt_parser.parse_args(args[2:])
|
||||||
|
try:
|
||||||
|
if mode == None:
|
||||||
|
ret = fn(cmd, mcname, subopts, subargs,
|
||||||
|
subopt_parser.error)
|
||||||
|
else:
|
||||||
|
f = file(mcname, mode)
|
||||||
|
mc = ps2mc.ps2mc(f, opts.ignore_ecc)
|
||||||
|
ret = fn(cmd, mc, subopts, subargs,
|
||||||
|
subopt_parser.error)
|
||||||
|
finally:
|
||||||
|
if mc != None:
|
||||||
|
mc.close()
|
||||||
|
if f != None:
|
||||||
|
# print "f.close()"
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
except EnvironmentError, value:
|
||||||
|
if getattr(value, "filename", None) != None:
|
||||||
|
write_error(value.filename, value.strerror)
|
||||||
|
ret = 1
|
||||||
|
elif getattr(value, "strerror", None) != None:
|
||||||
|
write_error(mcname, value.strerror)
|
||||||
|
ret = 1
|
||||||
|
else:
|
||||||
|
# something weird
|
||||||
|
raise
|
||||||
|
if opts.debug:
|
||||||
|
raise
|
||||||
|
|
||||||
|
except subopt_error, (ret,):
|
||||||
|
pass
|
||||||
|
|
||||||
|
except (ps2mc.error, ps2save.error), value:
|
||||||
|
fn = getattr(value, "filename", None)
|
||||||
|
if fn == None:
|
||||||
|
fn = mcname
|
||||||
|
write_error(fn, str(value))
|
||||||
|
if opts.debug:
|
||||||
|
raise
|
||||||
|
ret = 1
|
||||||
|
|
||||||
|
if ret == None:
|
||||||
|
ret = 0
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
sys.exit(main())
|
||||||
|
|
131
ps2mc_dir.py
Normal file
131
ps2mc_dir.py
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
#
|
||||||
|
# ps2mc_dir.py
|
||||||
|
#
|
||||||
|
# By Ross Ridge
|
||||||
|
# Public Domain
|
||||||
|
#
|
||||||
|
|
||||||
|
"""Functions for working with PS2 memory card directory entries."""
|
||||||
|
|
||||||
|
_SCCS_ID = "@(#) mysc ps2mc_dir.py 1.3 08/02/05 15:51:58\n"
|
||||||
|
|
||||||
|
import struct
|
||||||
|
import time
|
||||||
|
import calendar
|
||||||
|
|
||||||
|
PS2MC_DIRENT_LENGTH = 512
|
||||||
|
|
||||||
|
DF_READ = 0x0001
|
||||||
|
DF_WRITE = 0x0002
|
||||||
|
DF_EXECUTE = 0x0004
|
||||||
|
DF_RWX = DF_READ | DF_WRITE | DF_EXECUTE
|
||||||
|
DF_PROTECTED = 0x0008
|
||||||
|
DF_FILE = 0x0010
|
||||||
|
DF_DIR = 0x0020
|
||||||
|
DF_O_DCREAT = 0x0040
|
||||||
|
DF_0080 = 0x0080
|
||||||
|
DF_0100 = 0x0100
|
||||||
|
DF_O_CREAT = 0x0200
|
||||||
|
DF_0400 = 0x0400
|
||||||
|
DF_POCKETSTN = 0x0800
|
||||||
|
DF_PSX = 0x1000
|
||||||
|
DF_HIDDEN = 0x2000
|
||||||
|
DF_4000 = 0x4000
|
||||||
|
DF_EXISTS = 0x8000
|
||||||
|
|
||||||
|
def zero_terminate(s):
|
||||||
|
"""Truncate a string at the first NUL ('\0') character, if any."""
|
||||||
|
|
||||||
|
i = s.find('\0')
|
||||||
|
if i == -1:
|
||||||
|
return s
|
||||||
|
return s[:i]
|
||||||
|
|
||||||
|
# mode, ???, length, created,
|
||||||
|
# fat_cluster, parent_entry, modified, attr,
|
||||||
|
# name
|
||||||
|
_dirent_fmt = "<HHL8sLL8sL28x448s"
|
||||||
|
|
||||||
|
# secs, mins, hours, mday, month, year
|
||||||
|
_tod_fmt = "<xBBBBBH"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Use the new Struct object if available
|
||||||
|
#
|
||||||
|
if hasattr(struct, "Struct"):
|
||||||
|
_dirent_struct = struct.Struct(_dirent_fmt)
|
||||||
|
_tod_struct = struct.Struct(_tod_fmt)
|
||||||
|
|
||||||
|
def unpack_tod(s):
|
||||||
|
return _tod_struct.unpack(s)
|
||||||
|
|
||||||
|
def pack_tod(tod):
|
||||||
|
return _tod_struct.pack(tod)
|
||||||
|
|
||||||
|
def unpack_dirent(s):
|
||||||
|
ent = _dirent_struct.unpack(s)
|
||||||
|
ent = list(ent)
|
||||||
|
ent[3] = _tod_struct.unpack(ent[3])
|
||||||
|
ent[6] = _tod_struct.unpack(ent[6])
|
||||||
|
ent[8] = zero_terminate(ent[8])
|
||||||
|
return ent
|
||||||
|
|
||||||
|
def pack_dirent(ent):
|
||||||
|
ent = list(ent)
|
||||||
|
ent[3] = _tod_struct.pack(*ent[3])
|
||||||
|
ent[6] = _tod_struct.pack(*ent[6])
|
||||||
|
return _dirent_struct.pack(*ent)
|
||||||
|
else:
|
||||||
|
def unpack_tod(s):
|
||||||
|
return struct.unpack(_tod_fmt, s)
|
||||||
|
|
||||||
|
def pack_tod(tod):
|
||||||
|
return struct.pack(_tod_fmt, tod)
|
||||||
|
|
||||||
|
def unpack_dirent(s):
|
||||||
|
# mode, ???, length, created,
|
||||||
|
# fat_cluster, parent_entry, modified, attr,
|
||||||
|
# name
|
||||||
|
ent = struct.unpack(_dirent_fmt, s)
|
||||||
|
ent = list(ent)
|
||||||
|
ent[3] = struct.unpack(_tod_fmt, ent[3])
|
||||||
|
ent[6] = struct.unpack(_tod_fmt, ent[6])
|
||||||
|
ent[8] = zero_terminate(ent[8])
|
||||||
|
return ent
|
||||||
|
|
||||||
|
def pack_dirent(ent):
|
||||||
|
ent = list(ent)
|
||||||
|
ent[3] = struct.pack(_tod_fmt, *ent[3])
|
||||||
|
ent[6] = struct.pack(_tod_fmt, *ent[6])
|
||||||
|
return struct.pack(_dirent_fmt, *ent)
|
||||||
|
|
||||||
|
def time_to_tod(when):
|
||||||
|
"""Convert a Python time value to a ToD tuple"""
|
||||||
|
|
||||||
|
tm = time.gmtime(when + 9 * 3600)
|
||||||
|
return (tm.tm_sec, tm.tm_min, tm.tm_hour,
|
||||||
|
tm.tm_mday, tm.tm_mon, tm.tm_year)
|
||||||
|
|
||||||
|
def tod_to_time(tod):
|
||||||
|
"""Convert a ToD tuple to a Python time value."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
month = tod[4]
|
||||||
|
if month == 0:
|
||||||
|
month = 1
|
||||||
|
return calendar.timegm((tod[5], month, tod[3],
|
||||||
|
tod[2], tod[1], tod[0],
|
||||||
|
None, None, 0)) - 9 * 3600
|
||||||
|
except ValueError:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def tod_now():
|
||||||
|
"""Get the current time as a ToD tuple."""
|
||||||
|
return time_to_tod(time.time())
|
||||||
|
|
||||||
|
def mode_is_file(mode):
|
||||||
|
return (mode & (DF_FILE | DF_DIR | DF_EXISTS)) == (DF_FILE | DF_EXISTS)
|
||||||
|
|
||||||
|
def mode_is_dir(mode):
|
||||||
|
return (mode & (DF_FILE | DF_DIR | DF_EXISTS)) == (DF_DIR | DF_EXISTS)
|
||||||
|
|
182
ps2mc_ecc.py
Normal file
182
ps2mc_ecc.py
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
#
|
||||||
|
# ps2mc_ecc.py
|
||||||
|
#
|
||||||
|
# By Ross Ridge
|
||||||
|
# Public Domain
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
Routines for calculating the Hamming codes, a simple form of error
|
||||||
|
correcting codes (ECC), as used on PS2 memory cards.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_SCCS_ID = "@(#) mysc ps2mc_ecc.py 1.4 07/12/17 02:34:04\n"
|
||||||
|
|
||||||
|
import array
|
||||||
|
|
||||||
|
from round import div_round_up
|
||||||
|
|
||||||
|
try:
|
||||||
|
import ctypes
|
||||||
|
import mymcsup
|
||||||
|
except ImportError:
|
||||||
|
mymcsup = None
|
||||||
|
|
||||||
|
__ALL__ = ["ECC_CHECK_OK", "ECC_CHECK_CORRECTED", "ECC_CHECK_FAILED",
|
||||||
|
"ecc_calculate", "ecc_check",
|
||||||
|
"ecc_calculate_page", "ecc_check_page"]
|
||||||
|
|
||||||
|
ECC_CHECK_OK = 0
|
||||||
|
ECC_CHECK_CORRECTED = 1
|
||||||
|
ECC_CHECK_FAILED = 2
|
||||||
|
|
||||||
|
def _popcount(a):
|
||||||
|
count = 0
|
||||||
|
while a != 0:
|
||||||
|
a &= a - 1
|
||||||
|
count += 1
|
||||||
|
return count
|
||||||
|
|
||||||
|
def _parityb(a):
|
||||||
|
a = (a ^ (a >> 1))
|
||||||
|
a = (a ^ (a >> 2))
|
||||||
|
a = (a ^ (a >> 4))
|
||||||
|
return a & 1
|
||||||
|
|
||||||
|
def _make_ecc_tables():
|
||||||
|
parity_table = [_parityb(b)
|
||||||
|
for b in range(256)]
|
||||||
|
cpmasks = [0x55, 0x33, 0x0F, 0x00, 0xAA, 0xCC, 0xF0]
|
||||||
|
|
||||||
|
column_parity_masks = [None] * 256
|
||||||
|
for b in range(256):
|
||||||
|
mask = 0
|
||||||
|
for i in range(len(cpmasks)):
|
||||||
|
mask |= parity_table[b & cpmasks[i]] << i
|
||||||
|
column_parity_masks[b] = mask
|
||||||
|
|
||||||
|
return parity_table, column_parity_masks
|
||||||
|
|
||||||
|
_parity_table, _column_parity_masks = _make_ecc_tables()
|
||||||
|
|
||||||
|
def _ecc_calculate(s):
|
||||||
|
"Calculate the Hamming code for a 128 byte long string or byte array."
|
||||||
|
|
||||||
|
if not isinstance(s, array.array):
|
||||||
|
a = array.array('B')
|
||||||
|
a.fromstring(s)
|
||||||
|
s = a
|
||||||
|
column_parity = 0x77
|
||||||
|
line_parity_0 = 0x7F
|
||||||
|
line_parity_1 = 0x7F
|
||||||
|
for i in range(len(s)):
|
||||||
|
b = s[i]
|
||||||
|
column_parity ^= _column_parity_masks[b]
|
||||||
|
if _parity_table[b]:
|
||||||
|
line_parity_0 ^= ~i
|
||||||
|
line_parity_1 ^= i
|
||||||
|
return [column_parity, line_parity_0 & 0x7F, line_parity_1]
|
||||||
|
|
||||||
|
def _ecc_check(s, ecc):
|
||||||
|
"""Detect and correct any single bit errors.
|
||||||
|
|
||||||
|
The parameters "s" and "ecc", the data and expected Hamming code
|
||||||
|
repectively, must be modifiable sequences of integers and are
|
||||||
|
updated with the corrected values if necessary."""
|
||||||
|
|
||||||
|
computed = ecc_calculate(s)
|
||||||
|
if computed == ecc:
|
||||||
|
return ECC_CHECK_OK
|
||||||
|
|
||||||
|
#print
|
||||||
|
#_print_bin(0, s.tostring())
|
||||||
|
#print "computed %02x %02x %02x" % tuple(computed)
|
||||||
|
#print "actual %02x %02x %02x" % tuple(ecc)
|
||||||
|
|
||||||
|
# ECC mismatch
|
||||||
|
|
||||||
|
cp_diff = (computed[0] ^ ecc[0]) & 0x77
|
||||||
|
lp0_diff = (computed[1] ^ ecc[1]) & 0x7F
|
||||||
|
lp1_diff = (computed[2] ^ ecc[2]) & 0x7F
|
||||||
|
lp_comp = lp0_diff ^ lp1_diff
|
||||||
|
cp_comp = (cp_diff >> 4) ^ (cp_diff & 0x07)
|
||||||
|
|
||||||
|
#print "%02x %02x %02x %02x %02x" % (cp_diff, lp0_diff, lp1_diff,
|
||||||
|
# lp_comp, cp_comp)
|
||||||
|
|
||||||
|
if lp_comp == 0x7F and cp_comp == 0x07:
|
||||||
|
print "corrected 1"
|
||||||
|
# correctable 1 bit error in data
|
||||||
|
s[lp1_diff] ^= 1 << (cp_diff >> 4)
|
||||||
|
return ECC_CHECK_CORRECTED
|
||||||
|
if ((cp_diff == 0 and lp0_diff == 0 and lp1_diff == 0)
|
||||||
|
or _popcount(lp_comp) + _popcount(cp_comp) == 1):
|
||||||
|
print "corrected 2"
|
||||||
|
# correctable 1 bit error in ECC
|
||||||
|
# (and/or one of the unused bits was set)
|
||||||
|
ecc[0] = computed[0]
|
||||||
|
ecc[1] = computed[1]
|
||||||
|
ecc[2] = computed[2]
|
||||||
|
return ECC_CHECK_CORRECTED
|
||||||
|
|
||||||
|
# uncorrectable error
|
||||||
|
return ECC_CHECK_FAILED
|
||||||
|
|
||||||
|
def ecc_calculate_page(page):
|
||||||
|
"""Return a list of the ECC codes for a PS2 memory card page."""
|
||||||
|
return [ecc_calculate(page[i * 128 : i * 128 + 128])
|
||||||
|
for i in range(div_round_up(len(page), 128))]
|
||||||
|
|
||||||
|
def ecc_check_page(page, spare):
|
||||||
|
"Check and correct any single bit errors in a PS2 memory card page."
|
||||||
|
|
||||||
|
failed = False
|
||||||
|
corrected = False
|
||||||
|
|
||||||
|
#chunks = [(array.array('B', page[i * 128 : i * 128 + 128]),
|
||||||
|
# map(ord, spare[i * 3 : i * 3 + 3]))
|
||||||
|
# for i in range(div_round_up(len(page), 128))]
|
||||||
|
|
||||||
|
chunks = []
|
||||||
|
for i in range(div_round_up(len(page), 128)):
|
||||||
|
a = array.array('B')
|
||||||
|
a.fromstring(page[i * 128 : i * 128 + 128])
|
||||||
|
chunks.append((a, map(ord, spare[i * 3 : i * 3 + 3])))
|
||||||
|
|
||||||
|
r = [ecc_check(s, ecc)
|
||||||
|
for (s, ecc) in chunks]
|
||||||
|
ret = ECC_CHECK_OK
|
||||||
|
if ECC_CHECK_CORRECTED in r:
|
||||||
|
# rebuild sector and spare from the corrected versions
|
||||||
|
page = "".join([a[0].tostring()
|
||||||
|
for a in chunks])
|
||||||
|
spare = "".join([chr(a[1][i])
|
||||||
|
for a in chunks
|
||||||
|
for i in range(3)])
|
||||||
|
ret = ECC_CHECK_CORRECTED
|
||||||
|
if ECC_CHECK_FAILED in r:
|
||||||
|
ret = ECC_CHECK_FAILED
|
||||||
|
return (ret, page, spare)
|
||||||
|
|
||||||
|
if mymcsup == None:
|
||||||
|
ecc_calculate = _ecc_calculate
|
||||||
|
ecc_check = _ecc_check
|
||||||
|
else:
|
||||||
|
# _c_ubyte_p = ctypes.POINTER(ctypes.c_ubyte)
|
||||||
|
def ecc_calculate(s):
|
||||||
|
aecc = array.array('B', "\0\0\0")
|
||||||
|
cecc = ctypes.c_ubyte.from_address(aecc.buffer_info()[0])
|
||||||
|
mymcsup.ecc_calculate(s, len(s), cecc)
|
||||||
|
return list(aecc)
|
||||||
|
|
||||||
|
def ecc_check(s, ecc):
|
||||||
|
cs = ctypes.c_ubyte.from_address(s.buffer_info()[0])
|
||||||
|
# print "%08X" % s.buffer_info()[0]
|
||||||
|
aecc = array.array('B', ecc)
|
||||||
|
cecc = ctypes.c_ubyte.from_address(aecc.buffer_info()[0])
|
||||||
|
ret = mymcsup.ecc_check(cs, len(s), cecc)
|
||||||
|
ecc[0] = aecc[0]
|
||||||
|
ecc[1] = aecc[1]
|
||||||
|
ecc[2] = aecc[2]
|
||||||
|
return ret
|
||||||
|
|
625
ps2save.py
Normal file
625
ps2save.py
Normal file
@ -0,0 +1,625 @@
|
|||||||
|
#
|
||||||
|
# ps2save.py
|
||||||
|
#
|
||||||
|
# By Ross Ridge
|
||||||
|
# Public Domain
|
||||||
|
#
|
||||||
|
# A simple interface for working with various PS2 save file formats.
|
||||||
|
#
|
||||||
|
|
||||||
|
_SCCS_ID = "@(#) mysc ps2save.py 1.6 08/08/11 14:11:54\n"
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import string
|
||||||
|
import struct
|
||||||
|
import binascii
|
||||||
|
import array
|
||||||
|
import zlib
|
||||||
|
|
||||||
|
from round import div_round_up, round_up
|
||||||
|
from ps2mc_dir import *
|
||||||
|
from sjistab import shift_jis_normalize_table
|
||||||
|
|
||||||
|
try:
|
||||||
|
import lzari
|
||||||
|
except ImportError:
|
||||||
|
lzari = None
|
||||||
|
|
||||||
|
PS2SAVE_MAX_MAGIC = "Ps2PowerSave"
|
||||||
|
PS2SAVE_SPS_MAGIC = "\x0d\0\0\0SharkPortSave"
|
||||||
|
PS2SAVE_CBS_MAGIC = "CFU\0"
|
||||||
|
PS2SAVE_NPO_MAGIC = "nPort"
|
||||||
|
|
||||||
|
# This is the initial permutation state ("S") for the RC4 stream cipher
|
||||||
|
# algorithm used to encrpyt and decrypt Codebreaker saves.
|
||||||
|
PS2SAVE_CBS_RC4S = [0x5f, 0x1f, 0x85, 0x6f, 0x31, 0xaa, 0x3b, 0x18,
|
||||||
|
0x21, 0xb9, 0xce, 0x1c, 0x07, 0x4c, 0x9c, 0xb4,
|
||||||
|
0x81, 0xb8, 0xef, 0x98, 0x59, 0xae, 0xf9, 0x26,
|
||||||
|
0xe3, 0x80, 0xa3, 0x29, 0x2d, 0x73, 0x51, 0x62,
|
||||||
|
0x7c, 0x64, 0x46, 0xf4, 0x34, 0x1a, 0xf6, 0xe1,
|
||||||
|
0xba, 0x3a, 0x0d, 0x82, 0x79, 0x0a, 0x5c, 0x16,
|
||||||
|
0x71, 0x49, 0x8e, 0xac, 0x8c, 0x9f, 0x35, 0x19,
|
||||||
|
0x45, 0x94, 0x3f, 0x56, 0x0c, 0x91, 0x00, 0x0b,
|
||||||
|
0xd7, 0xb0, 0xdd, 0x39, 0x66, 0xa1, 0x76, 0x52,
|
||||||
|
0x13, 0x57, 0xf3, 0xbb, 0x4e, 0xe5, 0xdc, 0xf0,
|
||||||
|
0x65, 0x84, 0xb2, 0xd6, 0xdf, 0x15, 0x3c, 0x63,
|
||||||
|
0x1d, 0x89, 0x14, 0xbd, 0xd2, 0x36, 0xfe, 0xb1,
|
||||||
|
0xca, 0x8b, 0xa4, 0xc6, 0x9e, 0x67, 0x47, 0x37,
|
||||||
|
0x42, 0x6d, 0x6a, 0x03, 0x92, 0x70, 0x05, 0x7d,
|
||||||
|
0x96, 0x2f, 0x40, 0x90, 0xc4, 0xf1, 0x3e, 0x3d,
|
||||||
|
0x01, 0xf7, 0x68, 0x1e, 0xc3, 0xfc, 0x72, 0xb5,
|
||||||
|
0x54, 0xcf, 0xe7, 0x41, 0xe4, 0x4d, 0x83, 0x55,
|
||||||
|
0x12, 0x22, 0x09, 0x78, 0xfa, 0xde, 0xa7, 0x06,
|
||||||
|
0x08, 0x23, 0xbf, 0x0f, 0xcc, 0xc1, 0x97, 0x61,
|
||||||
|
0xc5, 0x4a, 0xe6, 0xa0, 0x11, 0xc2, 0xea, 0x74,
|
||||||
|
0x02, 0x87, 0xd5, 0xd1, 0x9d, 0xb7, 0x7e, 0x38,
|
||||||
|
0x60, 0x53, 0x95, 0x8d, 0x25, 0x77, 0x10, 0x5e,
|
||||||
|
0x9b, 0x7f, 0xd8, 0x6e, 0xda, 0xa2, 0x2e, 0x20,
|
||||||
|
0x4f, 0xcd, 0x8f, 0xcb, 0xbe, 0x5a, 0xe0, 0xed,
|
||||||
|
0x2c, 0x9a, 0xd4, 0xe2, 0xaf, 0xd0, 0xa9, 0xe8,
|
||||||
|
0xad, 0x7a, 0xbc, 0xa8, 0xf2, 0xee, 0xeb, 0xf5,
|
||||||
|
0xa6, 0x99, 0x28, 0x24, 0x6c, 0x2b, 0x75, 0x5d,
|
||||||
|
0xf8, 0xd3, 0x86, 0x17, 0xfb, 0xc0, 0x7b, 0xb3,
|
||||||
|
0x58, 0xdb, 0xc7, 0x4b, 0xff, 0x04, 0x50, 0xe9,
|
||||||
|
0x88, 0x69, 0xc9, 0x2a, 0xab, 0xfd, 0x5b, 0x1b,
|
||||||
|
0x8a, 0xd9, 0xec, 0x27, 0x44, 0x0e, 0x33, 0xc8,
|
||||||
|
0x6b, 0x93, 0x32, 0x48, 0xb6, 0x30, 0x43, 0xa5]
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Table of graphically similar ASCII characters that can be used
|
||||||
|
# as substitutes for Unicode characters.
|
||||||
|
#
|
||||||
|
char_substs = {
|
||||||
|
u'\u00a2': u"c",
|
||||||
|
u'\u00b4': u"'",
|
||||||
|
u'\u00d7': u"x",
|
||||||
|
u'\u00f7': u"/",
|
||||||
|
u'\u2010': u"-",
|
||||||
|
u'\u2015': u"-",
|
||||||
|
u'\u2018': u"'",
|
||||||
|
u'\u2019': u"'",
|
||||||
|
u'\u201c': u'"',
|
||||||
|
u'\u201d': u'"',
|
||||||
|
u'\u2032': u"'",
|
||||||
|
u'\u2212': u"-",
|
||||||
|
u'\u226a': u"<<",
|
||||||
|
u'\u226b': u">>",
|
||||||
|
u'\u2500': u"-",
|
||||||
|
u'\u2501': u"-",
|
||||||
|
u'\u2502': u"|",
|
||||||
|
u'\u2503': u"|",
|
||||||
|
u'\u250c': u"+",
|
||||||
|
u'\u250f': u"+",
|
||||||
|
u'\u2510': u"+",
|
||||||
|
u'\u2513': u"+",
|
||||||
|
u'\u2514': u"+",
|
||||||
|
u'\u2517': u"+",
|
||||||
|
u'\u2518': u"+",
|
||||||
|
u'\u251b': u"+",
|
||||||
|
u'\u251c': u"+",
|
||||||
|
u'\u251d': u"+",
|
||||||
|
u'\u2520': u"+",
|
||||||
|
u'\u2523': u"+",
|
||||||
|
u'\u2524': u"+",
|
||||||
|
u'\u2525': u"+",
|
||||||
|
u'\u2528': u"+",
|
||||||
|
u'\u252b': u"+",
|
||||||
|
u'\u252c': u"+",
|
||||||
|
u'\u252f': u"+",
|
||||||
|
u'\u2530': u"+",
|
||||||
|
u'\u2533': u"+",
|
||||||
|
u'\u2537': u"+",
|
||||||
|
u'\u2538': u"+",
|
||||||
|
u'\u253b': u"+",
|
||||||
|
u'\u253c': u"+",
|
||||||
|
u'\u253f': u"+",
|
||||||
|
u'\u2542': u"+",
|
||||||
|
u'\u254b': u"+",
|
||||||
|
u'\u25a0': u"#",
|
||||||
|
u'\u25a1': u"#",
|
||||||
|
u'\u3001': u",",
|
||||||
|
u'\u3002': u".",
|
||||||
|
u'\u3003': u'"',
|
||||||
|
u'\u3007': u'0',
|
||||||
|
u'\u3008': u'<',
|
||||||
|
u'\u3009': u'>',
|
||||||
|
u'\u300a': u'<<',
|
||||||
|
u'\u300b': u'>>',
|
||||||
|
u'\u300a': u'<<',
|
||||||
|
u'\u300b': u'>>',
|
||||||
|
u'\u300c': u'[',
|
||||||
|
u'\u300d': u']',
|
||||||
|
u'\u300e': u'[',
|
||||||
|
u'\u300f': u']',
|
||||||
|
u'\u3010': u'[',
|
||||||
|
u'\u3011': u']',
|
||||||
|
u'\u3014': u'[',
|
||||||
|
u'\u3015': u']',
|
||||||
|
u'\u301c': u'~',
|
||||||
|
u'\u30fc': u'-',
|
||||||
|
}
|
||||||
|
|
||||||
|
def shift_jis_conv(src, encoding = None):
|
||||||
|
"""Convert Shift-JIS strings to a graphically similar representation.
|
||||||
|
|
||||||
|
If encoding is "unicode" then a Unicode string is returned,
|
||||||
|
otherwise a string in encoding specified is returned. If necessary,
|
||||||
|
graphically similar characters are used to replace characters not
|
||||||
|
exactly representable in the desired encoding.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if encoding == None:
|
||||||
|
encoding = sys.getdefaultencoding()
|
||||||
|
if encoding == "shift_jis":
|
||||||
|
return src
|
||||||
|
u = src.decode("shift_jis", "replace")
|
||||||
|
if encoding == "unicode":
|
||||||
|
return u
|
||||||
|
a = []
|
||||||
|
for uc in u:
|
||||||
|
try:
|
||||||
|
uc.encode(encoding)
|
||||||
|
a.append(uc)
|
||||||
|
except UnicodeError:
|
||||||
|
for uc2 in shift_jis_normalize_table.get(uc, uc):
|
||||||
|
a.append(char_substs.get(uc2, uc2))
|
||||||
|
|
||||||
|
return u"".join(a).encode(encoding, "replace")
|
||||||
|
|
||||||
|
def rc4_crypt(s, t):
|
||||||
|
"""RC4 encrypt/decrypt the string t using the permutation s.
|
||||||
|
|
||||||
|
Returns a byte array."""
|
||||||
|
|
||||||
|
s = array.array('B', s)
|
||||||
|
t = array.array('B', t)
|
||||||
|
j = 0
|
||||||
|
for ii in range(len(t)):
|
||||||
|
i = (ii + 1) % 256
|
||||||
|
j = (j + s[i]) % 256
|
||||||
|
(s[i], s[j]) = (s[j], s[i])
|
||||||
|
t[ii] ^= s[(s[i] + s[j]) % 256]
|
||||||
|
return t
|
||||||
|
|
||||||
|
# def sps_check(s):
|
||||||
|
# """Calculate the checksum for a SharkPort save."""
|
||||||
|
#
|
||||||
|
# h = 0
|
||||||
|
# for c in array.array('B', s):
|
||||||
|
# h += c << (h % 24)
|
||||||
|
# h &= 0xFFFFFFFF
|
||||||
|
# return h
|
||||||
|
|
||||||
|
def unpack_icon_sys(s):
|
||||||
|
"""Unpack an icon.sys file into a tuple."""
|
||||||
|
|
||||||
|
# magic, title offset, ...
|
||||||
|
# [14] title, normal icon, copy icon, del icon
|
||||||
|
a = struct.unpack("<4s2xH4x"
|
||||||
|
"L" "16s16s16s16s" "16s16s16s" "16s16s16s" "16s"
|
||||||
|
"68s64s64s64s512x", s)
|
||||||
|
a = list(a)
|
||||||
|
for i in range(3, 7):
|
||||||
|
a[i] = struct.unpack("<4L", a[i])
|
||||||
|
a[i] = map(hex, a[i])
|
||||||
|
for i in range(7, 14):
|
||||||
|
a[i] = struct.unpack("<4f", a[i])
|
||||||
|
a[14] = zero_terminate(a[14])
|
||||||
|
a[15] = zero_terminate(a[15])
|
||||||
|
a[16] = zero_terminate(a[16])
|
||||||
|
a[17] = zero_terminate(a[17])
|
||||||
|
return a
|
||||||
|
|
||||||
|
def icon_sys_title(icon_sys, encoding = None):
|
||||||
|
"""Extract the two lines of the title stored in an icon.sys tuple."""
|
||||||
|
|
||||||
|
offset = icon_sys[1]
|
||||||
|
title = icon_sys[14]
|
||||||
|
title2 = shift_jis_conv(title[offset:], encoding)
|
||||||
|
title1 = shift_jis_conv(title[:offset], encoding)
|
||||||
|
return (title1, title2)
|
||||||
|
|
||||||
|
def _read_fixed(f, n):
|
||||||
|
"""Read a string of a fixed length from a file."""
|
||||||
|
|
||||||
|
s = f.read(n)
|
||||||
|
if len(s) != n:
|
||||||
|
raise eof, f
|
||||||
|
return s
|
||||||
|
|
||||||
|
def _read_long_string(f):
|
||||||
|
"""Read a string prefixed with a 32-bit length from a file."""
|
||||||
|
|
||||||
|
length = struct.unpack("<L", _read_fixed(f, 4))[0]
|
||||||
|
return _read_fixed(f, length)
|
||||||
|
|
||||||
|
class ps2_save_file(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 = False
|
||||||
|
|
||||||
|
def set_directory(self, ent, defer = False):
|
||||||
|
self._defer_load_max = defer
|
||||||
|
self._compressed = None
|
||||||
|
self.file_ents = [None] * ent[2]
|
||||||
|
self.file_data = [None] * ent[2]
|
||||||
|
self.dirent = 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:
|
||||||
|
self._defer_load_max = False
|
||||||
|
self._load_max_drive_2()
|
||||||
|
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] == "icon.sys" and len(data) >= 964:
|
||||||
|
return unpack_icon_sys(data[:964])
|
||||||
|
return None
|
||||||
|
|
||||||
|
def load_ems(self, f):
|
||||||
|
"""Load EMS (.psu) save files."""
|
||||||
|
|
||||||
|
cluster_size = 1024
|
||||||
|
|
||||||
|
dirent = unpack_dirent(_read_fixed(f, PS2MC_DIRENT_LENGTH))
|
||||||
|
dotent = unpack_dirent(_read_fixed(f, PS2MC_DIRENT_LENGTH))
|
||||||
|
dotdotent = unpack_dirent(_read_fixed(f, PS2MC_DIRENT_LENGTH))
|
||||||
|
if (not mode_is_dir(dirent[0])
|
||||||
|
or not mode_is_dir(dotent[0])
|
||||||
|
or not mode_is_dir(dotdotent[0])
|
||||||
|
or dirent[2] < 2):
|
||||||
|
raise corrupt, ("Not a EMS (.psu) save file.", f)
|
||||||
|
|
||||||
|
dirent[2] -= 2
|
||||||
|
self.set_directory(dirent)
|
||||||
|
|
||||||
|
for i in range(dirent[2]):
|
||||||
|
ent = unpack_dirent(_read_fixed(f,
|
||||||
|
PS2MC_DIRENT_LENGTH))
|
||||||
|
if not mode_is_file(ent[0]):
|
||||||
|
raise subdir, f
|
||||||
|
flen = ent[2]
|
||||||
|
self.set_file(i, ent, _read_fixed(f, flen))
|
||||||
|
_read_fixed(f, round_up(flen, cluster_size) - flen)
|
||||||
|
|
||||||
|
|
||||||
|
def save_ems(self, f):
|
||||||
|
cluster_size = 1024
|
||||||
|
|
||||||
|
dirent = self.dirent[:]
|
||||||
|
dirent[2] += 2
|
||||||
|
f.write(pack_dirent(dirent))
|
||||||
|
f.write(pack_dirent((DF_RWX | DF_DIR | DF_0400 | DF_EXISTS,
|
||||||
|
0, 0, dirent[3],
|
||||||
|
0, 0, dirent[3], 0, ".")))
|
||||||
|
f.write(pack_dirent((DF_RWX | DF_DIR | DF_0400 | DF_EXISTS,
|
||||||
|
0, 0, dirent[3],
|
||||||
|
0, 0, dirent[3], 0, "..")))
|
||||||
|
|
||||||
|
for i in range(dirent[2] - 2):
|
||||||
|
(ent, data) = self.get_file(i)
|
||||||
|
f.write(pack_dirent(ent))
|
||||||
|
if not mode_is_file(ent[0]):
|
||||||
|
raise error, "Directory has a subdirectory."
|
||||||
|
f.write(data)
|
||||||
|
f.write("\0" * (round_up(len(data), cluster_size)
|
||||||
|
- len(data)))
|
||||||
|
f.flush()
|
||||||
|
|
||||||
|
def _load_max_drive_2(self):
|
||||||
|
(length, s) = self._compressed
|
||||||
|
self._compressed = None
|
||||||
|
|
||||||
|
if lzari == None:
|
||||||
|
raise error, ("The lzari module is needed to "
|
||||||
|
" decompress MAX Drive saves.")
|
||||||
|
s = lzari.decode(s, length,
|
||||||
|
"decompressing " + self.dirent[8] + ": ")
|
||||||
|
dirlen = self.dirent[2]
|
||||||
|
now = tod_now()
|
||||||
|
off = 0
|
||||||
|
for i in range(dirlen):
|
||||||
|
if len(s) - off < 36:
|
||||||
|
raise eof, f
|
||||||
|
(l, name) = struct.unpack("<L32s", s[off : off + 36])
|
||||||
|
name = zero_terminate(name)
|
||||||
|
# print "%08x %08x %s" % (off, l, name)
|
||||||
|
off += 36
|
||||||
|
data = s[off : off + l]
|
||||||
|
if len(data) != l:
|
||||||
|
raise eof, f
|
||||||
|
self.set_file(i,
|
||||||
|
(DF_RWX | DF_FILE | DF_0400, 0, l,
|
||||||
|
now, 0, 0, now, 0, name),
|
||||||
|
data)
|
||||||
|
off += l
|
||||||
|
off = round_up(off + 8, 16) - 8
|
||||||
|
|
||||||
|
def load_max_drive(self, f):
|
||||||
|
s = f.read(0x5C)
|
||||||
|
magic = None
|
||||||
|
if len(s) == 0x5C:
|
||||||
|
(magic, crc, dirname, iconsysname, clen, dirlen,
|
||||||
|
length) = struct.unpack("<12sL32s32sLLL", s)
|
||||||
|
if magic != PS2SAVE_MAX_MAGIC:
|
||||||
|
raise corrupt, ("Not a MAX Drive save file", f)
|
||||||
|
if clen == length:
|
||||||
|
# some saves have the uncompressed size here
|
||||||
|
# instead of the compressed size
|
||||||
|
s = f.read()
|
||||||
|
else:
|
||||||
|
s = _read_fixed(f, clen - 4)
|
||||||
|
dirname = zero_terminate(dirname)
|
||||||
|
now = tod_now()
|
||||||
|
self.set_directory((DF_RWX | DF_DIR | DF_0400, 0,
|
||||||
|
dirlen, now, 0, 0, now, 0, dirname),
|
||||||
|
True)
|
||||||
|
self._compressed = (length, s)
|
||||||
|
|
||||||
|
def save_max_drive(self, f):
|
||||||
|
if lzari == None:
|
||||||
|
raise error, ("The lzari module is needed to "
|
||||||
|
" decompress MAX Drive saves.")
|
||||||
|
iconsysname = ""
|
||||||
|
icon_sys = self.get_icon_sys()
|
||||||
|
if icon_sys != None:
|
||||||
|
title = icon_sys_title(icon_sys, "ascii")
|
||||||
|
if len(title[0]) > 0 and title[0][-1] != ' ':
|
||||||
|
iconsysname = title[0] + " " + title[1].strip()
|
||||||
|
else:
|
||||||
|
iconsysname = title[0] + title[1].rstrip()
|
||||||
|
s = ""
|
||||||
|
dirent = self.dirent
|
||||||
|
for i in range(dirent[2]):
|
||||||
|
(ent, data) = self.get_file(i)
|
||||||
|
if not mode_is_file(ent[0]):
|
||||||
|
raise error, "Non-file in save file."
|
||||||
|
s += struct.pack("<L32s", ent[2], ent[8])
|
||||||
|
s += data
|
||||||
|
s += "\0" * (round_up(len(s) + 8, 16) - 8 - len(s))
|
||||||
|
length = len(s)
|
||||||
|
progress = "compressing " + dirent[8] + ": "
|
||||||
|
compressed = lzari.encode(s, progress)
|
||||||
|
hdr = struct.pack("<12sL32s32sLLL", PS2SAVE_MAX_MAGIC,
|
||||||
|
0, dirent[8], iconsysname,
|
||||||
|
len(compressed) + 4, dirent[2], length)
|
||||||
|
crc = binascii.crc32(hdr)
|
||||||
|
crc = binascii.crc32(compressed, crc)
|
||||||
|
f.write(struct.pack("<12sL32s32sLLL", PS2SAVE_MAX_MAGIC,
|
||||||
|
crc & 0xFFFFFFFF, dirent[8], iconsysname,
|
||||||
|
len(compressed) + 4, dirent[2], length))
|
||||||
|
f.write(compressed)
|
||||||
|
f.flush()
|
||||||
|
|
||||||
|
def load_codebreaker(self, f):
|
||||||
|
magic = f.read(4)
|
||||||
|
if magic != PS2SAVE_CBS_MAGIC:
|
||||||
|
raise corrupt, ("Not a Codebreaker save file.", f)
|
||||||
|
(d04, hlen) = struct.unpack("<LL", _read_fixed(f, 8))
|
||||||
|
if hlen < 92 + 32:
|
||||||
|
raise corrupt, ("Header lengh too short.", f)
|
||||||
|
(dlen, flen, dirname, created, modified, d44, d48, dirmode,
|
||||||
|
d50, d54, d58, title) \
|
||||||
|
= struct.unpack("<LL32s8s8sLLLLLL%ds" % (hlen - 92),
|
||||||
|
_read_fixed(f, hlen - 12))
|
||||||
|
dirname = zero_terminate(dirname)
|
||||||
|
created = unpack_tod(created)
|
||||||
|
modified = unpack_tod(modified)
|
||||||
|
title = zero_terminate(title)
|
||||||
|
|
||||||
|
# These fields don't always seem to be set correctly.
|
||||||
|
if not mode_is_dir(dirmode):
|
||||||
|
dirmode = DF_RWX | DF_DIR | DF_0400
|
||||||
|
if tod_to_time(created) == 0:
|
||||||
|
created = tod_now()
|
||||||
|
if tod_to_time(modified) == 0:
|
||||||
|
modified = tod_now()
|
||||||
|
|
||||||
|
# flen can either be the total length of the file,
|
||||||
|
# or the length of compressed body of the file
|
||||||
|
body = f.read(flen)
|
||||||
|
clen = len(body)
|
||||||
|
if clen != flen and clen != flen - hlen:
|
||||||
|
raise eof, f
|
||||||
|
body = rc4_crypt(PS2SAVE_CBS_RC4S, body)
|
||||||
|
dcobj = zlib.decompressobj()
|
||||||
|
body = dcobj.decompress(body, dlen)
|
||||||
|
|
||||||
|
files = []
|
||||||
|
while body != "":
|
||||||
|
if len(body) < 64:
|
||||||
|
raise eof, f
|
||||||
|
header = struct.unpack("<8s8sLHHLL32s", body[:64])
|
||||||
|
size = header[2]
|
||||||
|
data = body[64 : 64 + size]
|
||||||
|
if len(data) != size:
|
||||||
|
raise eof, f
|
||||||
|
body = body[64 + size:]
|
||||||
|
files.append((header, data))
|
||||||
|
|
||||||
|
self.set_directory((dirmode, 0, len(files), created, 0, 0,
|
||||||
|
modified, 0, dirname))
|
||||||
|
for i in range(len(files)):
|
||||||
|
(header, data) = files[i]
|
||||||
|
(created, modified, size, mode, h06, h08, h0C, name) \
|
||||||
|
= header
|
||||||
|
name = zero_terminate(name)
|
||||||
|
created = unpack_tod(created)
|
||||||
|
modified = unpack_tod(modified)
|
||||||
|
if not mode_is_file(mode):
|
||||||
|
raise subdir, f
|
||||||
|
if tod_to_time(created) == 0:
|
||||||
|
created = tod_now()
|
||||||
|
if tod_to_time(modified) == 0:
|
||||||
|
modified = tod_now()
|
||||||
|
self.set_file(i, (mode, 0, size, created, 0, 0,
|
||||||
|
modified, 0, name), data)
|
||||||
|
|
||||||
|
def load_sharkport(self, f):
|
||||||
|
magic = f.read(17)
|
||||||
|
if magic != PS2SAVE_SPS_MAGIC:
|
||||||
|
raise corrupt, ("Not a SharkPort/X-Port save file.", f)
|
||||||
|
(savetype,) = struct.unpack("<L", _read_fixed(f, 4))
|
||||||
|
dirname = _read_long_string(f)
|
||||||
|
datestamp = _read_long_string(f)
|
||||||
|
comment = _read_long_string(f)
|
||||||
|
|
||||||
|
(flen,) = struct.unpack("<L", _read_fixed(f, 4))
|
||||||
|
|
||||||
|
(hlen, dirname, dirlen, dirmode, created, modified) \
|
||||||
|
= struct.unpack("<H64sL8xH2x8s8s", _read_fixed(f, 98))
|
||||||
|
_read_fixed(f, hlen - 98)
|
||||||
|
|
||||||
|
dirname = zero_terminate(dirname)
|
||||||
|
created = unpack_tod(created)
|
||||||
|
modified = unpack_tod(modified)
|
||||||
|
|
||||||
|
# mode values are byte swapped
|
||||||
|
dirmode = dirmode / 256 % 256 + dirmode % 256 * 256
|
||||||
|
dirlen -= 2
|
||||||
|
if not mode_is_dir(dirmode) or dirlen < 0:
|
||||||
|
raise corrupt, ("Bad values in directory entry.", f)
|
||||||
|
self.set_directory((dirmode, 0, dirlen, created, 0, 0,
|
||||||
|
modified, 0, dirname))
|
||||||
|
|
||||||
|
for i in range(dirlen):
|
||||||
|
(hlen, name, flen, mode, created, modified) \
|
||||||
|
= struct.unpack("<H64sL8xH2x8s8s",
|
||||||
|
_read_fixed(f, 98))
|
||||||
|
if hlen < 98:
|
||||||
|
raise corrupt, ("Header length too short.", f)
|
||||||
|
_read_fixed(f, hlen - 98)
|
||||||
|
name = zero_terminate(name)
|
||||||
|
created = unpack_tod(created)
|
||||||
|
modified = unpack_tod(modified)
|
||||||
|
mode = mode / 256 % 256 + mode % 256 * 256
|
||||||
|
if not mode_is_file(mode):
|
||||||
|
raise subdir, f
|
||||||
|
self.set_file(i, (mode, 0, flen, created, 0, 0,
|
||||||
|
modified, 0, name),
|
||||||
|
_read_fixed(f, flen))
|
||||||
|
|
||||||
|
# ignore 4 byte checksum at the end
|
||||||
|
|
||||||
|
def detect_file_type(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)
|
||||||
|
if hdr[:12] == PS2SAVE_MAX_MAGIC:
|
||||||
|
return "max"
|
||||||
|
if hdr[:17] == PS2SAVE_SPS_MAGIC:
|
||||||
|
return "sps"
|
||||||
|
if hdr[:4] == PS2SAVE_CBS_MAGIC:
|
||||||
|
return "cbs"
|
||||||
|
if hdr[:5] == PS2SAVE_NPO_MAGIC:
|
||||||
|
return "npo"
|
||||||
|
#
|
||||||
|
# EMS (.psu) save files don't have a magic number. Check to
|
||||||
|
# see if it looks enough like one.
|
||||||
|
#
|
||||||
|
if len(hdr) != PS2MC_DIRENT_LENGTH * 3:
|
||||||
|
return None
|
||||||
|
dirent = unpack_dirent(hdr[:PS2MC_DIRENT_LENGTH])
|
||||||
|
dotent = unpack_dirent(hdr[PS2MC_DIRENT_LENGTH
|
||||||
|
: PS2MC_DIRENT_LENGTH * 2])
|
||||||
|
dotdotent = unpack_dirent(hdr[PS2MC_DIRENT_LENGTH * 2:])
|
||||||
|
if (mode_is_dir(dirent[0]) and mode_is_dir(dotent[0])
|
||||||
|
and mode_is_dir(dotdotent[0]) and dirent[2] >= 2
|
||||||
|
and dotent[8] == "." and dotdotent[8] == ".."):
|
||||||
|
return "psu"
|
||||||
|
return None
|
||||||
|
|
||||||
|
#
|
||||||
|
# Set up tables of illegal and problematic characters in file names.
|
||||||
|
#
|
||||||
|
_bad_filename_chars = ("".join(map(chr, range(32)))
|
||||||
|
+ "".join(map(chr, 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 = string.maketrans(_bad_filename_chars, _bad_filename_repl);
|
||||||
|
_filename_trans2 = string.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 != None:
|
||||||
|
title = icon_sys_title(icon_sys, "ascii")
|
||||||
|
title = title[0] + " " + title[1]
|
||||||
|
title = " ".join(title.split())
|
||||||
|
crc = binascii.crc32("")
|
||||||
|
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))
|
||||||
|
|
21
round.py
Normal file
21
round.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#
|
||||||
|
# round.py
|
||||||
|
#
|
||||||
|
# By Ross Ridge
|
||||||
|
# Public Domain
|
||||||
|
#
|
||||||
|
# Simple rounding functions.
|
||||||
|
#
|
||||||
|
|
||||||
|
_SCCS_ID = "@(#) mysc round.py 1.3 07/04/17 02:10:27\n"
|
||||||
|
|
||||||
|
def div_round_up(a, b):
|
||||||
|
return (a + b - 1) / b
|
||||||
|
|
||||||
|
def round_up(a, b):
|
||||||
|
return (a + b - 1) / b * b
|
||||||
|
|
||||||
|
def round_down(a, b):
|
||||||
|
return a / b * b
|
||||||
|
|
||||||
|
|
2
sjistab.py
Normal file
2
sjistab.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# automatically generated
|
||||||
|
shift_jis_normalize_table = {u'\uff81': u'\u30c1', u'\u3000': u' ', u'\uff85': u'\u30ca', u'\uff06': u'&', u'\uff89': u'\u30ce', u'\uff0a': u'*', u'\uff8d': u'\u30d8', u'\uff0e': u'.', u'\uff91': u'\u30e0', u'\uff12': u'2', u'\uff95': u'\u30e6', u'\uff16': u'6', u'\uff99': u'\u30eb', u'\u309b': u' \u3099', u'\uff1a': u':', u'\uff9d': u'\u30f3', u'\uff03': u'#', u'\uff1e': u'>', u'\uff22': u'B', u'\uff26': u'F', u'\uff2a': u'J', u'\u222c': u'\u222b\u222b', u'\uff2e': u'N', u'\uff32': u'R', u'\uff36': u'V', u'\uff3a': u'Z', u'\uff3e': u'^', u'\uff42': u'b', u'\uff46': u'f', u'\uff4a': u'j', u'\uff4e': u'n', u'\uff52': u'r', u'\uff56': u'v', u'\uff5a': u'z', u'\uff62': u'\u300c', u'\uffe5': u'\xa5', u'\uff66': u'\u30f2', u'\uff6a': u'\u30a7', u'\uff6e': u'\u30e7', u'\uff72': u'\u30a4', u'\uff76': u'\u30ab', u'\uff7a': u'\u30b3', u'\uff7e': u'\u30bb', u'\uff01': u'!', u'\uff82': u'\u30c4', u'\uff05': u'%', u'\uff86': u'\u30cb', u'\uff09': u')', u'\uff8a': u'\u30cf', u'\uff8e': u'\u30db', u'\uff11': u'1', u'\uff92': u'\u30e1', u'\uff15': u'5', u'\uff96': u'\u30e8', u'\uff19': u'9', u'\uff9a': u'\u30ec', u'\uff1d': u'=', u'\u309c': u' \u309a', u'\uff9e': u'\u3099', u'\uff21': u'A', u'\uff25': u'E', u'\uff29': u'I', u'\xa8': u' \u0308', u'\uff2d': u'M', u'\uff31': u'Q', u'\u2033': u'\u2032\u2032', u'\uff35': u'U', u'\xb4': u' \u0301', u'\uff39': u'Y', u'\uff3d': u']', u'\uff41': u'a', u'\uff45': u'e', u'\uff49': u'i', u'\uff4d': u'm', u'\uff51': u'q', u'\uff55': u'u', u'\uff59': u'y', u'\uff5d': u'}', u'\uff61': u'\u3002', u'\uff65': u'\u30fb', u'\uff69': u'\u30a5', u'\uff6d': u'\u30e5', u'\uff71': u'\u30a2', u'\uff75': u'\u30aa', u'\uff79': u'\u30b1', u'\uff7d': u'\u30b9', u'\uff83': u'\u30c6', u'\uff04': u'$', u'\uff87': u'\u30cc', u'\uff08': u'(', u'\uff8b': u'\u30d2', u'\uff0c': u',', u'\uff8f': u'\u30de', u'\uff10': u'0', u'\uff93': u'\u30e2', u'\uff14': u'4', u'\uff97': u'\u30e9', u'\uff18': u'8', u'\uff9b': u'\u30ed', u'\uff1c': u'<', u'\uff9f': u'\u309a', u'\uff20': u'@', u'\uff24': u'D', u'\u2026': u'...', u'\uff28': u'H', u'\uff2c': u'L', u'\uff30': u'P', u'\uff34': u'T', u'\uff38': u'X', u'\uff3c': u'\\', u'\uff40': u'`', u'\uff44': u'd', u'\uff48': u'h', u'\uff4c': u'l', u'\uff50': u'p', u'\uff54': u't', u'\uff58': u'x', u'\uff5c': u'|', u'\uffe3': u' \u0304', u'\uff64': u'\u3001', u'\uff68': u'\u30a3', u'\uff6c': u'\u30e3', u'\uff70': u'\u30fc', u'\uff74': u'\u30a8', u'\uff78': u'\u30af', u'\uff7c': u'\u30b7', u'\uff80': u'\u30bf', u'\u2103': u'\xb0C', u'\uff84': u'\u30c8', u'\uff88': u'\u30cd', u'\uff0b': u'+', u'\uff8c': u'\u30d5', u'\uff0f': u'/', u'\uff90': u'\u30df', u'\uff13': u'3', u'\uff94': u'\u30e4', u'\uff17': u'7', u'\uff98': u'\u30ea', u'\uff1b': u';', u'\uff9c': u'\u30ef', u'\uff1f': u'?', u'\uff23': u'C', u'\u2025': u'..', u'\uff27': u'G', u'\u212b': u'\xc5', u'\uff2f': u'O', u'\uff33': u'S', u'\uff37': u'W', u'\uff3b': u'[', u'\uff3f': u'_', u'\uff43': u'c', u'\uff47': u'g', u'\uff4b': u'k', u'\uff4f': u'o', u'\uff53': u's', u'\uff57': u'w', u'\uff5b': u'{', u'\uff63': u'\u300d', u'\uff67': u'\u30a1', u'\uff6b': u'\u30a9', u'\uff6f': u'\u30c3', u'\uff73': u'\u30a6', u'\uff77': u'\u30ad', u'\uff7b': u'\u30b5', u'\uff2b': u'K', u'\uff7f': u'\u30bd'}
|
2
verbuild.py
Normal file
2
verbuild.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
MYMC_VERSION_BUILD = r'''5'''
|
||||||
|
MYMC_VERSION_MAJOR = r'''2'''
|
Loading…
Reference in New Issue
Block a user