diff --git a/README.md b/README.md index 94e2d7b..3873415 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,48 @@ Options: -e, --no-ecc Create virtual memory card without ecc. Useful for SD2PSX/MemCard PRO2 ``` +### Tree-style Directory Listing + +Use `ls -R` to display the memory card's directory structure as a tree: + +``` +mymcplusplus card.mc2 ls -R +``` + +``` +/ +├── BASLUS-21313FrndsUS +│ ├── BASLUS-21313FrndsUS +│ ├── icon.sys +│ └── icon1.ico +└── PSDCGT-01412 + ├── game.ico + └── icon.sys +``` + +Add `-l` to show mode bits, file size, and timestamps: + +``` +mymcplusplus card.mc2 ls -R -l +``` + +``` +/ +├── BASLUS-21313FrndsUS rwx--d----+---- 5 2025-09-26 21:50:42 +│ ├── BASLUS-21313FrndsUS rwx-f--8--+---- 47490 2025-09-26 21:50:41 +│ ├── icon.sys rwx-f--8--+---- 964 2025-09-26 21:50:41 +│ └── icon1.ico rwx-f--8--+---- 41528 2025-09-26 21:50:42 +└── PSDCGT-01412 rwx--d----+---- 13 2025-06-11 21:53:58 + ├── game.ico rwx-f--8--+---- 33688 2025-05-25 13:46:31 + └── icon.sys rwx-f--8--+---- 964 2025-05-25 13:46:32 +``` + +Add `-c` to show creation times instead of modification times: + +``` +mymcplusplus card.mc2 ls -R -l -c +``` + It is always necessary to specify the path to a memory card image with `-i ` first. For example: diff --git a/mymcplusplus/gui/dirlist_control.py b/mymcplusplus/gui/dirlist_control.py index 37d4a8a..db6a050 100644 --- a/mymcplusplus/gui/dirlist_control.py +++ b/mymcplusplus/gui/dirlist_control.py @@ -49,21 +49,37 @@ def __init__(self, type, dirent, icon_sys, size, title): self.size = size self.title = title + _PARENT_ENTRY = 0x7FFFFFFE + def __init__(self, parent, evt_focus, evt_select, config): self.config = config self.selected = set() self.dirtable = [] + self.current_path = "/" + self._mc = None + self._on_navigate = None + self._updating = False # current sort state for column header clicks self.sort_column = self.Column.DIRECTORY self.sort_reverse = False self.evt_select = evt_select + self.evt_focus = evt_focus wx.ListCtrl.__init__(self, parent, wx.ID_ANY, style=wx.LC_REPORT) self.Bind(wx.EVT_LIST_COL_CLICK, self.evt_col_click) - self.Bind(wx.EVT_LIST_ITEM_FOCUSED, evt_focus) + self.Bind(wx.EVT_LIST_ITEM_FOCUSED, self._evt_focus_wrapper) self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.evt_item_selected) self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.evt_item_deselected) + self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.evt_item_activated) + + def _evt_focus_wrapper(self, event): + """Skip focus events for the '..' entry or during updates.""" + if self._updating: + return + if event.GetData() == self._PARENT_ENTRY: + return + self.evt_focus(event) def _update_dirtable(self, mc, dir): self.dirtable = table = [] @@ -71,32 +87,41 @@ def _update_dirtable(self, mc, dir): if self.config.get_ascii(): enc = "ascii" for ent in dir: - if not ps2mc.mode_is_dir(ent[0]): + mode = ent[0] + if not (mode & ps2mc.DF_EXISTS): + continue + name = ent[8].decode("ascii") + if name in (".", ".."): continue - dirname = ent[8].decode("ascii") - dirpath = "/" + dirname - - if ps2mc.mode_is_psx_dir(ent[0]): - type = self.TableEntry.Type.PS1 - title = (dirname, "") - icon_sys = None - else: - type = self.TableEntry.Type.PS2 - icon_sys_data = mc.get_icon_sys(dirpath) - if icon_sys_data is None: - continue - icon_sys = ps2iconsys.IconSys(icon_sys_data) - title = icon_sys.get_title(enc) - - size = mc.dir_size(dirpath) - table.append(self.TableEntry(type, ent, icon_sys, size, title)) + if ps2mc.mode_is_dir(mode): + dirpath = self.current_path.rstrip("/") + "/" + name + if ps2mc.mode_is_psx_dir(mode): + type = self.TableEntry.Type.PS1 + title = (name, "") + icon_sys = None + else: + type = self.TableEntry.Type.PS2 + icon_sys_data = mc.get_icon_sys(dirpath) + if icon_sys_data is None: + type = self.TableEntry.Type.PS1 + title = (name, "") + icon_sys = None + else: + icon_sys = ps2iconsys.IconSys(icon_sys_data) + title = icon_sys.get_title(enc) + size = mc.dir_size(dirpath) + table.append(self.TableEntry(type, ent, icon_sys, size, title)) + elif self.current_path != "/": + # Show files when inside a subdirectory + table.append(self.TableEntry( + self.TableEntry.Type.PS2, ent, None, ent[2], (name, ""))) def update_dirtable(self, mc): self.dirtable = [] if mc is None: return - dir = mc.dir_open("/") + dir = mc.dir_open(self.current_path) try: self._update_dirtable(mc, dir) finally: @@ -136,6 +161,10 @@ def _apply_sort_direction(self, result): return result def cmp_dir_name(self, i1, i2): + if i1 == self._PARENT_ENTRY: + return -1 + if i2 == self._PARENT_ENTRY: + return 1 v1 = self.dirtable[i1].dirent[8] v2 = self.dirtable[i2].dirent[8] if v1 < v2: @@ -147,6 +176,10 @@ def cmp_dir_name(self, i1, i2): return self._apply_sort_direction(result) def cmp_dir_title(self, i1, i2): + if i1 == self._PARENT_ENTRY: + return -1 + if i2 == self._PARENT_ENTRY: + return 1 v1 = self.dirtable[i1].title v2 = self.dirtable[i2].title if v1 < v2: @@ -158,6 +191,10 @@ def cmp_dir_title(self, i1, i2): return self._apply_sort_direction(result) def cmp_dir_size(self, i1, i2): + if i1 == self._PARENT_ENTRY: + return -1 + if i2 == self._PARENT_ENTRY: + return 1 v1 = self.dirtable[i1].size v2 = self.dirtable[i2].size if v1 < v2: @@ -169,6 +206,10 @@ def cmp_dir_size(self, i1, i2): return self._apply_sort_direction(result) def cmp_dir_modified(self, i1, i2): + if i1 == self._PARENT_ENTRY: + return -1 + if i2 == self._PARENT_ENTRY: + return 1 m1 = list(self.dirtable[i1].dirent[6]) m2 = list(self.dirtable[i2].dirent[6]) # dirent[6] is a tuple like (sec, min, hour, day, month, year) @@ -211,17 +252,49 @@ def evt_col_click(self, event): self.SortItems(cmp) def evt_item_selected(self, event): - self.selected.add(event.GetData()) + idx = event.GetData() + if idx == self._PARENT_ENTRY: + return + self.selected.add(idx) self.evt_select(event) def evt_item_deselected(self, event): - self.selected.discard(event.GetData()) + idx = event.GetData() + if idx == self._PARENT_ENTRY: + return + self.selected.discard(idx) self.evt_select(event) + def evt_item_activated(self, event): + """Handle double-click: navigate into directories or go up with '..'.""" + idx = event.GetData() + if idx == self._PARENT_ENTRY: + # '..' entry + self.navigate_up() + return + entry = self.dirtable[idx] + if entry.dirent[0] & ps2mc.DF_DIR: + dirname = entry.dirent[8].decode("ascii") + self.current_path = self.current_path.rstrip("/") + "/" + dirname + self.update(self._mc) + if self._on_navigate: + self._on_navigate() + + def navigate_up(self): + if self.current_path == "/": + return + # Go up one level + self.current_path = "/".join(self.current_path.rstrip("/").split("/")[:-1]) or "/" + self.update(self._mc) + if self._on_navigate: + self._on_navigate() + def update(self, mc): """Update the ListCtrl according to the contents of the memory card image.""" + self._mc = mc + self._updating = True self.ClearAll() self.selected = set() @@ -247,13 +320,25 @@ def update(self, mc): self.update_dirtable(mc) - empty = len(self.dirtable) == 0 + empty = len(self.dirtable) == 0 and self.current_path == "/" self.Enable(not empty) - if empty: + + item_offset = 0 + # Add '..' entry when not at root + if self.current_path != "/": + li = self.InsertItem(0, "..") + self.SetItem(li, 1, "") + self.SetItem(li, 2, "") + self.SetItem(li, 3, "(Parent Directory)") + self.SetItemData(li, self._PARENT_ENTRY) + item_offset = 1 + + if len(self.dirtable) == 0 and self.current_path == "/": + self._updating = False return for (i, a) in enumerate(self.dirtable): - li = self.InsertItem(i, a.dirent[8]) + li = self.InsertItem(i + item_offset, a.dirent[8]) self.SetItem(li, 1, "%dK" % (a.size // 1024)) m = a.dirent[6] m = ("%04d-%02d-%02d %02d:%02d" @@ -269,3 +354,4 @@ def update(self, mc): # Apply default sort (Directory, ascending) self.SortItems(self.cmp_dir_name) + self._updating = False diff --git a/mymcplusplus/gui/gui.py b/mymcplusplus/gui/gui.py index af60f05..7ae063b 100644 --- a/mymcplusplus/gui/gui.py +++ b/mymcplusplus/gui/gui.py @@ -257,6 +257,7 @@ def __init__(self, parent, title, mcname=None): self.evt_dirlist_select, self.config, ) + self.dirlist._on_navigate = self._on_dirlist_navigate if mcname is not None: self.open_mc(mcname) @@ -362,6 +363,23 @@ def _close_mc(self): self.f = None self.mcname = None + def _format_title(self): + """Format window title based on current state.""" + if self.mcname is None: + return self.title + path = self.dirlist.current_path + if path == "/": + return self.mcname + " - " + self.title + return self.mcname + " - " + path + " - " + self.title + + def _on_dirlist_navigate(self): + """Called when the user navigates into or out of a directory.""" + at_root = self.dirlist.current_path == "/" + self.toolbar.EnableTool(self.ID_CMD_IMPORT, self.mc is not None and at_root) + self.toolbar.EnableTool(self.ID_CMD_EXPORT, False) + if self.mc is not None: + self.SetTitle(self._format_title()) + def refresh(self): try: self.dirlist.update(self.mc) @@ -371,16 +389,19 @@ def refresh(self): self.dirlist.update(None) mc = self.mc + at_root = self.dirlist.current_path == "/" - self.toolbar.EnableTool(self.ID_CMD_IMPORT, mc is not None) + self.toolbar.EnableTool(self.ID_CMD_IMPORT, mc is not None and at_root) self.toolbar.EnableTool(self.ID_CMD_EXPORT, False) if mc is None: status = "No memory card image" + self.SetTitle(self.title) else: free = mc.get_free_space() // 1024 limit = mc.get_allocatable_space() // 1024 status = "%dK of %dK free" % (free, limit) + self.SetTitle(self._format_title()) self.statusbar.SetStatusText(status, 1) def open_mc(self, filename): @@ -388,6 +409,7 @@ def open_mc(self, filename): self.statusbar.SetStatusText("", 1) if self.icon_win is not None: self.icon_win.load_icon(None, None) + self.dirlist.current_path = "/" f = None try: @@ -404,7 +426,6 @@ def open_mc(self, filename): self.f = f self.mc = mc self.mcname = filename - self.SetTitle(filename + " - " + self.title) self.refresh() def delete_selected(self): @@ -414,31 +435,38 @@ def delete_selected(self): selected = self.dirlist.selected dirtable = self.dirlist.dirtable + current_path = self.dirlist.current_path - dirnames = [ + names = [ dirtable[i].dirent[8].decode("ascii") for i in selected ] if len(selected) == 1: title = dirtable[list(selected)[0]].title - s = dirnames[0] + " (" + utils.single_title(title) + ")" + s = names[0] + " (" + utils.single_title(title) + ")" else: - s = ", ".join(dirnames) + s = ", ".join(names) if len(s) > 200: s = s[:200] + "..." r = self.message_box( "Are you sure you want to delete " + s + "?", - "Delete Save File Confirmation", + "Delete Confirmation", wx.YES_NO, ) if r != wx.YES: return - for dn in dirnames: + for i in selected: + entry = dirtable[i] + name = entry.dirent[8].decode("ascii") + fullpath = current_path.rstrip("/") + "/" + name try: - mc.rmdir("/" + dn) + if ps2mc.mode_is_dir(entry.dirent[0]): + mc.rmdir(fullpath) + else: + mc.remove(fullpath) except EnvironmentError as value: - self.mc_error(value, dn) + self.mc_error(value, name) mc.check() self.refresh() @@ -539,7 +567,8 @@ def _update_force_import_status(self): def evt_menu_open(self, event): # Enable/disable basic actions based on current state - self.import_menu_item.Enable(self.mc is not None) + at_root = self.dirlist.current_path == "/" + self.import_menu_item.Enable(self.mc is not None and at_root) selected = self.mc is not None and len(self.dirlist.selected) > 0 self.export_menu_item.Enable(selected) @@ -591,12 +620,49 @@ def evt_dirlist_item_focused(self, event): icon_sys = entry.icon_sys mc = self.mc - if mc is None or icon_sys is None: + if mc is None: + self.icon_win.load_icon(None, None) + return + + current_path = self.dirlist.current_path + + # If inside a subdirectory and icon.sys is selected, parse and render it + if icon_sys is None and current_path != "/": + name = entry.dirent[8].decode("ascii") + if name == "icon.sys": + try: + mc.chdir(current_path) + f = mc.open("icon.sys", "rb") + try: + icon_sys_data = f.read() + finally: + f.close() + icon_sys = ps2iconsys.IconSys(icon_sys_data) + self.info1.SetLabel(icon_sys.get_title("ascii")[0]) + self.info2.SetLabel(icon_sys.get_title("ascii")[1]) + f = mc.open(icon_sys.icon_file_normal, "rb") + try: + icon = f.read() + finally: + f.close() + self.icon_win.load_icon(icon_sys, icon) + return + except Exception: + pass + self.icon_win.load_icon(None, None) + return + + if icon_sys is None: + self.icon_win.load_icon(None, None) + return + + if not (entry.dirent[0] & ps2mc.DF_DIR): self.icon_win.load_icon(None, None) return try: - mc.chdir("/" + entry.dirent[8].decode("ascii")) + dirpath = current_path.rstrip("/") + "/" + entry.dirent[8].decode("ascii") + mc.chdir(dirpath) f = mc.open(icon_sys.icon_file_normal, "rb") try: icon = f.read() @@ -610,7 +676,8 @@ def evt_dirlist_item_focused(self, event): self.icon_win.load_icon(icon_sys, icon) def evt_dirlist_select(self, event): - self.toolbar.EnableTool(self.ID_CMD_IMPORT, self.mc is not None) + at_root = self.dirlist.current_path == "/" + self.toolbar.EnableTool(self.ID_CMD_IMPORT, self.mc is not None and at_root) self.toolbar.EnableTool( self.ID_CMD_EXPORT, len(self.dirlist.selected) > 0, @@ -841,6 +908,75 @@ def evt_cmd_export(self, event): selected = self.dirlist.selected dirtable = self.dirlist.dirtable + current_path = self.dirlist.current_path + + # Inside a subdirectory: extract raw files + if current_path != "/": + names = [dirtable[i].dirent[8].decode("ascii") for i in selected] + if not names: + return + + dir = self.config.get_savefile_dir("") + if len(names) == 1: + fn = wx.FileSelector( + "Extract " + names[0], + dir, + names[0], + "", + "All files (*.*)|*.*", + (wx.FD_OVERWRITE_PROMPT | wx.FD_SAVE), + self, + ) + if fn == "": + return + try: + mc.chdir(current_path) + f_in = mc.open(names[0], "rb") + try: + with open(fn, "wb") as f_out: + while True: + data = f_in.read(1024) + if not data: + break + f_out.write(data) + finally: + f_in.close() + except EnvironmentError as value: + self.mc_error(value, names[0]) + return + dir = os.path.dirname(fn) + if os.path.isabs(dir): + self.config.set_savefile_dir(dir) + self.message_box("Extracted " + fn + " successfully.") + else: + dir = wx.DirSelector("Extract Files", dir, parent=self) + if dir == "": + return + count = 0 + mc.chdir(current_path) + for name in names: + fn = os.path.join(dir, name) + try: + f_in = mc.open(name, "rb") + try: + with open(fn, "wb") as f_out: + while True: + data = f_in.read(1024) + if not data: + break + f_out.write(data) + finally: + f_in.close() + count += 1 + except EnvironmentError as value: + self.mc_error(value, name) + if count > 0: + if os.path.isabs(dir): + self.config.set_savefile_dir(dir) + self.message_box("Extracted %d file(s) successfully." % count) + return + + # At root: export save directories as .psu/.max sfiles = [] for i in selected: dirname = dirtable[i].dirent[8].decode("ascii") @@ -849,7 +985,7 @@ def evt_cmd_export(self, event): longname = ps2save.make_longname(dirname, sf) sfiles.append((dirname, sf, longname)) except EnvironmentError as value: - self.mc_error(value. dirname) + self.mc_error(value, dirname) if len(sfiles) == 0: return diff --git a/mymcplusplus/gui/icon_renderer.py b/mymcplusplus/gui/icon_renderer.py index 65a5e0b..f91fcb9 100644 --- a/mymcplusplus/gui/icon_renderer.py +++ b/mymcplusplus/gui/icon_renderer.py @@ -453,8 +453,8 @@ def paint(self, canvas, animation_time): self._upload_icon() size = canvas.Size - - glViewport(0, 0, size.Width, size.Height) + scale = canvas.GetContentScaleFactor() + glViewport(0, 0, int(size.Width * scale), int(size.Height * scale)) if self.background_color is not None: glClearColor(*self.background_color, 1.0) diff --git a/mymcplusplus/gui/icon_window.py b/mymcplusplus/gui/icon_window.py index 4626a71..d22f75c 100644 --- a/mymcplusplus/gui/icon_window.py +++ b/mymcplusplus/gui/icon_window.py @@ -150,7 +150,7 @@ def make_attrib_list(samples): self.canvas.Bind(wx.EVT_PAINT, self.paint) - self.sizer.Add(self.canvas, wx.EXPAND, wx.EXPAND) + self.sizer.Add(self.canvas, 1, wx.EXPAND) self.SetSizer(self.sizer) #self.config = config = mymcsup.icon_config() diff --git a/mymcplusplus/mymc.py b/mymcplusplus/mymc.py index 78a189e..4a5c4af 100644 --- a/mymcplusplus/mymc.py +++ b/mymcplusplus/mymc.py @@ -97,6 +97,50 @@ def _copy(fout, fin): fout.write(s) +def _tree_collect(mc, dirname, prefix=""): + """Collect all tree lines as (prefix+connector+name, ent) tuples.""" + lines = [] + dir = mc.dir_open(dirname) + try: + entries = [ent for ent in dir + if (ent[0] & DF_EXISTS) and ent[8] not in (b".", b"..")] + finally: + dir.close() + + for i, ent in enumerate(entries): + is_last = (i == len(entries) - 1) + connector = "└── " if is_last else "├── " + name = ent[8].decode("ascii") + lines.append((prefix + connector + name, ent)) + if mode_is_dir(ent[0]): + extension = " " if is_last else "│ " + lines.extend(_tree_collect(mc, dirname.rstrip("/") + "/" + name, prefix + extension)) + return lines + + +def _tree(mc, dirname, opts, prefix=""): + mode_bits = "rwxpfdD81C+KPH4" + lines = _tree_collect(mc, dirname, prefix) + + if not opts.long: + for tree_part, ent in lines: + sys.stdout.write(tree_part + "\n") + return + + max_width = max((len(tree_part) for tree_part, _ in lines), default=0) + for tree_part, ent in lines: + mode = ent[0] + mode_str = "" + for bit in range(0, 15): + mode_str += mode_bits[bit] if mode & (1 << bit) else "-" + tod = ent[3] if opts.creation_time else ent[6] + tm = time.localtime(tod_to_time(tod)) + sys.stdout.write("%-*s %s %7d %04d-%02d-%02d %02d:%02d:%02d\n" % ( + max_width, tree_part, mode_str, ent[2], + tm.tm_year, tm.tm_mon, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec)) + + def do_ls(cmd, mc, opts, args, opterr): mode_bits = "rwxpfdD81C+KPH4" @@ -105,6 +149,13 @@ def do_ls(cmd, mc, opts, args, opterr): out = sys.stdout args = glob_args(args, mc.glob) + + if opts.recursive: + for dirname in args: + sys.stdout.write(dirname + "\n") + _tree(mc, dirname, opts) + return + for dirname in args: dir = mc.dir_open(dirname) try: @@ -520,7 +571,12 @@ def do_ecc_check(cmd, mc, opts, args, opterr): "[directory ...]", "List the contents of a directory.", [opt("-c", "--creation-time", action="store_true", - help = "Display creation times.")]), + help = "Display creation times."), + opt("-R", "--recursive", action="store_true", + help = "Display directory tree recursively."), + opt("-l", "--long-recursive", action="store_true", + dest="long", + help = "Show details (mode, size, timestamp) in tree mode. Only used with -R.")]), "extract": (do_extract, "rb", "filename ...", "Extract files from the memory card.", diff --git a/mymcplusplus/ps2icon.py b/mymcplusplus/ps2icon.py index 2350c6e..cd75ee3 100644 --- a/mymcplusplus/ps2icon.py +++ b/mymcplusplus/ps2icon.py @@ -91,10 +91,13 @@ def __init__(self, data): length = len(data) offset = 0 - offset = self.__load_header(data, length, offset) - offset = self.__load_vertex_data(data, length, offset) - offset = self.__load_animation_data(data, length, offset) - offset = self.__load_texture(data, length, offset) + try: + offset = self.__load_header(data, length, offset) + offset = self.__load_vertex_data(data, length, offset) + offset = self.__load_animation_data(data, length, offset) + offset = self.__load_texture(data, length, offset) + except FileTooSmall: + print("Warning: Icon file too small, rendering with available data.") if length > offset: print("Warning: Icon file larger than expected.") @@ -209,11 +212,17 @@ def __load_texture(self, data, length, offset): if offset == length: self.texture = [0xFFFF] return offset - - if self.tex_type == 0x7: - return self.__load_texture_uncompressed(data, length, offset) - else: + + # bit 2 (0x04) indicates texture is present + if not (self.tex_type & 0x04): + self.texture = [0xFFFF] + return offset + + # bit 3 (0x08) indicates compressed texture + if self.tex_type & 0x08: return self.__load_texture_compressed(data, length, offset) + else: + return self.__load_texture_uncompressed(data, length, offset) def __load_texture_uncompressed(self, data, length, offset): @@ -247,7 +256,7 @@ def __load_texture_compressed(self, data, length, offset): rle_code = int(data[offset + rle_offset]) | (int(data[offset + rle_offset + 1]) << 8) rle_offset += 2 - if rle_code & 0xff00 == 0xff00: # use the next (0xffff - rle_code) * 2 bytes as they are + if rle_code & 0x8000: # use the next (0x10000 - rle_code) * 2 bytes as they are sublength = (0x10000 - rle_code) * 2 if compressed_size < rle_offset + sublength: raise Corrupt("Compressed data is too short.") @@ -261,18 +270,19 @@ def __load_texture_compressed(self, data, length, offset): else: # repeat next 2 bytes rle_code times rep = rle_code - if compressed_size < rle_offset + 2: - raise Corrupt("Compressed data is too short.") - if tex_offset + rep * 2 > _TEXTURE_SIZE: - raise Corrupt("Decompressed data exceeds texture size.") - - subdata = data[(offset + rle_offset):(offset + rle_offset + 2)] - rle_offset += 2 - - for i in range(rep): - texture_buf[tex_offset] = subdata[0] - texture_buf[tex_offset+1] = subdata[1] - tex_offset += 2 + if rep > 0: + if compressed_size < rle_offset + 2: + raise Corrupt("Compressed data is too short.") + if tex_offset + rep * 2 > _TEXTURE_SIZE: + raise Corrupt("Decompressed data exceeds texture size.") + + subdata = data[(offset + rle_offset):(offset + rle_offset + 2)] + rle_offset += 2 + + for i in range(rep): + texture_buf[tex_offset] = subdata[0] + texture_buf[tex_offset+1] = subdata[1] + tex_offset += 2 assert rle_offset == compressed_size diff --git a/mymcplusplus/verbuild.py b/mymcplusplus/verbuild.py index 0988c7f..4f535ee 100644 --- a/mymcplusplus/verbuild.py +++ b/mymcplusplus/verbuild.py @@ -15,5 +15,5 @@ # along with mymc+. If not, see . # -MYMC_VERSION_BUILD = r'''0''' +MYMC_VERSION_BUILD = r'''2''' MYMC_VERSION_MAJOR = r'''3''' diff --git a/setup.py b/setup.py index 00da978..d4cb7be 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ setup( name="mymcplusplus", - version="3.1.0", + version="3.2.0", description="A PlayStation 2 memory card manager", long_description=long_description, long_description_content_type="text/plain", diff --git a/test/conftest.py b/test/conftest.py index 8078acb..e94592a 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -28,7 +28,7 @@ def data(tmpdir_factory): data_dir = tmpdir_factory.mktemp("data") path = os.path.join(test_dir, "data.tar.gz") tar = tarfile.open(path) - tar.extractall(data_dir.strpath) + tar.extractall(data_dir.strpath, filter="data") return data_dir @pytest.fixture diff --git a/test/test_ecc.py b/test/test_ecc.py index e001352..939c3f8 100644 --- a/test/test_ecc.py +++ b/test/test_ecc.py @@ -16,7 +16,7 @@ # import array -from mymcplus import ps2mc_ecc +from mymcplusplus import ps2mc_ecc import base64 diff --git a/test/test_iconsys.py b/test/test_iconsys.py index 23d18a8..1e285d2 100644 --- a/test/test_iconsys.py +++ b/test/test_iconsys.py @@ -17,7 +17,7 @@ import base64 -from mymcplus import ps2iconsys +from mymcplusplus import ps2iconsys _icon_sys_data = base64.b64decode( b"UFMyRAAAIAAAAAAAcwAAABQAAAAUAAAAPAAAAAAAAAAUAAAAFAAAADwAAAAAAAAAFAAAABQAAAA8" diff --git a/test/test_lzari.py b/test/test_lzari.py index 8634764..bf27f9e 100644 --- a/test/test_lzari.py +++ b/test/test_lzari.py @@ -16,7 +16,7 @@ # import array -from mymcplus.save import lzari +from mymcplusplus.save import lzari def bits_to_str(bits): diff --git a/test/test_memorycard.py b/test/test_memorycard.py index 827f2b9..a4974b5 100644 --- a/test/test_memorycard.py +++ b/test/test_memorycard.py @@ -15,7 +15,7 @@ # along with mymc+. If not, see . # -from mymcplus import mymc +from mymcplusplus import mymc def md5(fn): @@ -65,7 +65,7 @@ def test_extract(capsys, data, tmpdir): def test_add(monkeypatch, capsys, mc01_copy, tmpdir): - from mymcplus import ps2mc + from mymcplusplus import ps2mc patch_fixed_time(monkeypatch, ps2mc) patch_localtime(monkeypatch) @@ -200,7 +200,7 @@ def test_dir(capsys, data): def test_format(monkeypatch, capsys, tmpdir): - from mymcplus import ps2mc + from mymcplusplus import ps2mc patch_fixed_time(monkeypatch, ps2mc) mc_file = tmpdir.join("mc.ps2").strpath @@ -217,7 +217,7 @@ def test_format(monkeypatch, capsys, tmpdir): def test_mkdir(monkeypatch, capsys, mc01_copy): - from mymcplus import ps2mc + from mymcplusplus import ps2mc patch_fixed_time(monkeypatch, ps2mc) mc_file = mc01_copy.join("mc01.ps2").strpath @@ -267,10 +267,12 @@ def test_export_psu(capsys, data, tmpdir): "export", "-d", tmpdir.strpath, "-p", "BESCES-50501REZ"]) output = capsys.readouterr() - assert output.out == "Exporing BESCES-50501REZ to BESCES-50501REZ.psu\n" + # "Exporing" typo fixed to "Exporting" in code; longname is default since PR #22 + assert output.out == "Exporting BESCES-50501REZ to SCES-50501 Rez (DD5DAEA3).psu\n" assert output.err == "" - assert md5(tmpdir.join("BESCES-50501REZ.psu").strpath) == "d86c82e559c8250c894fbbc4405d8789" + # Filename uses make_longname by default since PR #22 + assert md5(tmpdir.join("SCES-50501 Rez (DD5DAEA3).psu").strpath) == "d86c82e559c8250c894fbbc4405d8789" def test_export_max(capsys, data, tmpdir): @@ -281,13 +283,15 @@ def test_export_max(capsys, data, tmpdir): "export", "-d", tmpdir.strpath, "-m", "BESCES-50501REZ"]) output = capsys.readouterr() - assert output.out == "Exporing BESCES-50501REZ to BESCES-50501REZ.max\n" + # "Exporing" typo fixed to "Exporting" in code; longname is default since PR #22 + assert output.out == "Exporting BESCES-50501REZ to SCES-50501 Rez (DD5DAEA3).max\n" - assert md5(tmpdir.join("BESCES-50501REZ.max").strpath) == "3f63d38668a0a5a5fa508ab8c3bb469a" + # Filename uses make_longname by default since PR #22 + assert md5(tmpdir.join("SCES-50501 Rez (DD5DAEA3).max").strpath) == "3f63d38668a0a5a5fa508ab8c3bb469a" def test_import_psu(monkeypatch, capsys, data, mc02_copy): - from mymcplus import ps2mc + from mymcplusplus import ps2mc patch_fixed_time(monkeypatch, ps2mc) mc_file = mc02_copy.join("mc02.ps2").strpath @@ -305,9 +309,9 @@ def test_import_psu(monkeypatch, capsys, data, mc02_copy): def test_import_max(monkeypatch, capsys, data, mc02_copy): - from mymcplus import ps2mc - from mymcplus import ps2mc_dir - from mymcplus.save import ps2save + from mymcplusplus import ps2mc + from mymcplusplus import ps2mc_dir + from mymcplusplus.save import ps2save patch_fixed_time(monkeypatch, ps2mc) patch_fixed_time(monkeypatch, ps2mc_dir) patch_fixed_time(monkeypatch, ps2save) @@ -326,7 +330,7 @@ def test_import_max(monkeypatch, capsys, data, mc02_copy): def test_import_sps(monkeypatch, capsys, data, mc02_copy): - from mymcplus import ps2mc + from mymcplusplus import ps2mc patch_fixed_time(monkeypatch, ps2mc) mc_file = mc02_copy.join("mc02.ps2").strpath @@ -344,7 +348,7 @@ def test_import_sps(monkeypatch, capsys, data, mc02_copy): def test_import_xps(monkeypatch, capsys, data, mc02_copy): - from mymcplus import ps2mc + from mymcplusplus import ps2mc patch_fixed_time(monkeypatch, ps2mc) mc_file = mc02_copy.join("mc02.ps2").strpath @@ -362,9 +366,9 @@ def test_import_xps(monkeypatch, capsys, data, mc02_copy): def test_import_cbs(monkeypatch, capsys, data, mc02_copy): - from mymcplus import ps2mc - from mymcplus import ps2mc_dir - from mymcplus.save import ps2save + from mymcplusplus import ps2mc + from mymcplusplus import ps2mc_dir + from mymcplusplus.save import ps2save patch_fixed_time(monkeypatch, ps2mc) patch_fixed_time(monkeypatch, ps2mc_dir) patch_fixed_time(monkeypatch, ps2save) @@ -384,8 +388,8 @@ def test_import_cbs(monkeypatch, capsys, data, mc02_copy): def test_import_psv_ps2(monkeypatch, capsys, data, mc02_copy): - from mymcplus import ps2mc - from mymcplus import ps2mc_dir + from mymcplusplus import ps2mc + from mymcplusplus import ps2mc_dir patch_fixed_time(monkeypatch, ps2mc) patch_fixed_time(monkeypatch, ps2mc_dir) @@ -404,8 +408,8 @@ def test_import_psv_ps2(monkeypatch, capsys, data, mc02_copy): def test_import_psv_ps1(monkeypatch, capsys, data, mc02_copy): - from mymcplus import ps2mc - from mymcplus import ps2mc_dir + from mymcplusplus import ps2mc + from mymcplusplus import ps2mc_dir patch_fixed_time(monkeypatch, ps2mc) patch_fixed_time(monkeypatch, ps2mc_dir)